File indexing completed on 2024-11-10 04:30:38
0001 /* 0002 SPDX-License-Identifier: GPL-2.0-or-later 0003 SPDX-FileCopyrightText: 2000 Caldera Systems Inc. 0004 SPDX-FileCopyrightText: 2018-2021 Harald Sitter <sitter@kde.org> 0005 SPDX-FileContributor: Matthew Peterson <mpeterson@caldera.com> 0006 */ 0007 0008 #include "kio_smb.h" 0009 #include "smburl.h" 0010 0011 #include <QMimeDatabase> 0012 #include <QMimeType> 0013 #include <QScopeGuard> 0014 #include <QVarLengthArray> 0015 0016 #include <KLocalizedString> 0017 0018 #include <future> 0019 0020 #include "transfer.h" 0021 0022 WorkerResult SMBWorker::get(const QUrl &kurl) 0023 { 0024 qCDebug(KIO_SMB_LOG) << kurl; 0025 0026 // check (correct) URL 0027 QUrl kvurl = checkURL(kurl); 0028 // if URL is not valid we have to redirect to correct URL 0029 if (kvurl != kurl) { 0030 redirection(kvurl); 0031 return WorkerResult::pass(); 0032 } 0033 0034 if (!m_context.isValid()) { 0035 return WorkerResult::fail(ERR_INTERNAL, i18n("libsmbclient failed to create context")); 0036 } 0037 0038 // Stat 0039 SMBUrl url = kurl; 0040 int errNum = cache_stat(url, &st); 0041 if (errNum != 0) { 0042 if (errNum == EACCES) { 0043 return WorkerResult::fail(KIO::ERR_ACCESS_DENIED, url.toDisplayString()); 0044 } 0045 return WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); 0046 } 0047 if (S_ISDIR(st.st_mode)) { 0048 return WorkerResult::fail(KIO::ERR_IS_DIRECTORY, url.toDisplayString()); 0049 } 0050 0051 // Set the total size 0052 totalSize(st.st_size); 0053 0054 // Open and read the file 0055 int filefd = smbc_open(url.toSmbcUrl(), O_RDONLY, 0); 0056 if (filefd < 0) { 0057 return WorkerResult::fail(KIO::ERR_CANNOT_OPEN_FOR_READING, url.toDisplayString()); 0058 } 0059 auto filefdClose = qScopeGuard([filefd] { 0060 smbc_close(filefd); 0061 }); 0062 0063 KIO::filesize_t totalbytesread = 0; 0064 QByteArray filedata; 0065 bool isFirstPacket = true; 0066 0067 TransferRingBuffer buffer(st.st_size); 0068 auto future = std::async(std::launch::async, [&buffer, &filefd]() -> int { 0069 while (true) { 0070 TransferSegment *s = buffer.nextFree(); 0071 s->size = smbc_read(filefd, s->buf.data(), s->buf.capacity()); 0072 if (s->size <= 0) { 0073 buffer.push(); 0074 buffer.done(); 0075 if (s->size < 0) { 0076 return KIO::ERR_CANNOT_READ; 0077 } 0078 break; 0079 } 0080 buffer.push(); 0081 } 0082 return KJob::NoError; 0083 }); 0084 0085 while (true) { 0086 TransferSegment *s = buffer.pop(); 0087 if (!s) { // done, no more segments pending 0088 break; 0089 } 0090 0091 filedata = QByteArray::fromRawData(s->buf.data(), s->size); 0092 if (isFirstPacket) { 0093 QMimeDatabase db; 0094 QMimeType type = db.mimeTypeForFileNameAndData(url.fileName(), filedata); 0095 mimeType(type.name()); 0096 isFirstPacket = false; 0097 } 0098 data(filedata); 0099 filedata.clear(); 0100 0101 // increment total bytes read 0102 totalbytesread += s->size; 0103 0104 processedSize(totalbytesread); 0105 buffer.unpop(); 0106 } 0107 if (future.get() != KJob::NoError) { // check if read had an error 0108 return WorkerResult::fail(future.get(), url.toDisplayString()); 0109 } 0110 0111 data(QByteArray()); 0112 if (totalbytesread != static_cast<KIO::filesize_t>(st.st_size)) { 0113 qCWarning(KIO_SMB_LOG) << "Got" << totalbytesread << "bytes but expected" << st.st_size; 0114 } 0115 processedSize(static_cast<KIO::filesize_t>(st.st_size)); 0116 0117 return WorkerResult::pass(); 0118 } 0119 0120 WorkerResult SMBWorker::open(const QUrl &kurl, QIODevice::OpenMode mode) 0121 { 0122 int errNum = 0; 0123 qCDebug(KIO_SMB_LOG) << kurl; 0124 0125 // check (correct) URL 0126 QUrl kvurl = checkURL(kurl); 0127 0128 // if URL is not valid we have to redirect to correct URL 0129 if (kvurl != kurl) { 0130 redirection(kvurl); 0131 return WorkerResult::pass(); 0132 } 0133 0134 if (!m_context.isValid()) { 0135 return WorkerResult::fail(KIO::ERR_ACCESS_DENIED, kurl.toDisplayString()); 0136 } 0137 0138 // Save the URL as a private member 0139 // FIXME For some reason m_openUrl has be be declared in bottom private 0140 // section of the class SMBWorker declaration instead of the top section 0141 // or else this assignment fails 0142 m_openUrl = kurl; 0143 0144 // FIXME authentication is missing here. when starting a FileJob without first otherwise accessing the path 0145 // to cache credentials this results in failure to open the file... should do the auth dance from browse here 0146 0147 // Stat 0148 errNum = cache_stat(m_openUrl, &st); 0149 if (errNum != 0) { 0150 if (errNum == EACCES) { 0151 return WorkerResult::fail(KIO::ERR_ACCESS_DENIED, m_openUrl.toDisplayString()); 0152 } 0153 return WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, m_openUrl.toDisplayString()); 0154 } 0155 if (S_ISDIR(st.st_mode)) { 0156 return WorkerResult::fail(KIO::ERR_IS_DIRECTORY, m_openUrl.toDisplayString()); 0157 } 0158 0159 // Set the total size 0160 totalSize(st.st_size); 0161 0162 // Convert permissions 0163 int flags = 0; 0164 if (mode & QIODevice::ReadOnly) { 0165 if (mode & QIODevice::WriteOnly) { 0166 flags = O_RDWR | O_CREAT; 0167 } else { 0168 flags = O_RDONLY; 0169 } 0170 } else if (mode & QIODevice::WriteOnly) { 0171 flags = O_WRONLY | O_CREAT; 0172 } 0173 0174 if (mode & QIODevice::Append) { 0175 flags |= O_APPEND; 0176 } else if (mode & QIODevice::Truncate) { 0177 flags |= O_TRUNC; 0178 } 0179 0180 // Open the file 0181 m_openFd = smbc_open(m_openUrl.toSmbcUrl(), flags, 0); 0182 if (m_openFd < 0) { 0183 return WorkerResult::fail(KIO::ERR_CANNOT_OPEN_FOR_READING, m_openUrl.toDisplayString()); 0184 } 0185 0186 // Determine the mimetype of the file to be retrieved, and emit it. 0187 // This is mandatory in all workers (for KRun/BrowserRun to work). 0188 // If we're not opening the file ReadOnly or ReadWrite, don't attempt to 0189 // read the file and send the mimetype. 0190 if (mode & QIODevice::ReadOnly) { 0191 ssize_t bytesRequested = 1024; 0192 ssize_t bytesRead = 0; 0193 QVarLengthArray<char> buffer(bytesRequested); 0194 bytesRead = smbc_read(m_openFd, buffer.data(), bytesRequested); 0195 if (bytesRead < 0) { 0196 closeWithoutFinish(); 0197 return WorkerResult::fail(KIO::ERR_CANNOT_READ, m_openUrl.toDisplayString()); 0198 } 0199 QByteArray fileData = QByteArray::fromRawData(buffer.data(), bytesRead); 0200 QMimeDatabase db; 0201 QMimeType type = db.mimeTypeForFileNameAndData(m_openUrl.fileName(), fileData); 0202 mimeType(type.name()); 0203 0204 off_t res = smbc_lseek(m_openFd, 0, SEEK_SET); 0205 if (res == (off_t)-1) { 0206 closeWithoutFinish(); 0207 return WorkerResult::fail(KIO::ERR_CANNOT_SEEK, m_openUrl.path()); 0208 } 0209 } 0210 0211 position(0); 0212 return WorkerResult::pass(); 0213 } 0214 0215 WorkerResult SMBWorker::read(KIO::filesize_t bytesRequested) 0216 { 0217 Q_ASSERT(m_openFd != -1); 0218 0219 QVarLengthArray<char> buffer(bytesRequested); 0220 ssize_t bytesRead = 0; 0221 0222 bytesRead = smbc_read(m_openFd, buffer.data(), bytesRequested); 0223 Q_ASSERT(bytesRead <= static_cast<ssize_t>(bytesRequested)); 0224 0225 if (bytesRead < 0) { 0226 qCDebug(KIO_SMB_LOG) << "Could not read " << m_openUrl; 0227 closeWithoutFinish(); 0228 return WorkerResult::fail(KIO::ERR_CANNOT_READ, m_openUrl.toDisplayString()); 0229 } 0230 0231 QByteArray fileData = QByteArray::fromRawData(buffer.data(), bytesRead); 0232 data(fileData); 0233 return WorkerResult::pass(); 0234 } 0235 0236 WorkerResult SMBWorker::write(const QByteArray &fileData) 0237 { 0238 Q_ASSERT(m_openFd != -1); 0239 0240 QByteArray buf(fileData); 0241 0242 ssize_t size = smbc_write(m_openFd, buf.data(), buf.size()); 0243 if (size < 0) { 0244 qCDebug(KIO_SMB_LOG) << "Could not write to " << m_openUrl; 0245 closeWithoutFinish(); 0246 return WorkerResult::fail(KIO::ERR_CANNOT_WRITE, m_openUrl.toDisplayString()); 0247 } 0248 0249 written(size); 0250 return WorkerResult::pass(); 0251 } 0252 0253 WorkerResult SMBWorker::seek(KIO::filesize_t offset) 0254 { 0255 off_t res = smbc_lseek(m_openFd, static_cast<off_t>(offset), SEEK_SET); 0256 if (res == (off_t)-1) { 0257 closeWithoutFinish(); 0258 return WorkerResult::fail(KIO::ERR_CANNOT_SEEK, m_openUrl.path()); 0259 } 0260 qCDebug(KIO_SMB_LOG) << "res" << res; 0261 position(res); 0262 return WorkerResult::pass(); 0263 } 0264 0265 WorkerResult SMBWorker::truncate(KIO::filesize_t length) 0266 { 0267 off_t res = smbc_ftruncate(m_openFd, static_cast<off_t>(length)); 0268 if (res < 0) { 0269 closeWithoutFinish(); 0270 return WorkerResult::fail(KIO::ERR_CANNOT_TRUNCATE, m_openUrl.path()); 0271 } 0272 qCDebug(KIO_SMB_LOG) << "res" << res; 0273 truncated(length); 0274 return WorkerResult::pass(); 0275 } 0276 0277 void SMBWorker::closeWithoutFinish() 0278 { 0279 smbc_close(m_openFd); 0280 } 0281 0282 WorkerResult SMBWorker::close() 0283 { 0284 closeWithoutFinish(); 0285 return WorkerResult::pass(); 0286 } 0287 0288 WorkerResult SMBWorker::put(const QUrl &kurl, int permissions, KIO::JobFlags flags) 0289 { 0290 void *buf; 0291 size_t bufsize; 0292 0293 m_current_url = kurl; 0294 0295 int filefd; 0296 bool exists; 0297 int errNum = 0; 0298 off_t retValLSeek = 0; 0299 mode_t mode; 0300 QByteArray filedata; 0301 0302 qCDebug(KIO_SMB_LOG) << kurl << flags; 0303 0304 errNum = cache_stat(m_current_url, &st); 0305 exists = (errNum == 0); 0306 if (exists && !(flags & KIO::Overwrite) && !(flags & KIO::Resume)) { 0307 if (S_ISDIR(st.st_mode)) { 0308 qCDebug(KIO_SMB_LOG) << kurl << " already isdir !!"; 0309 return WorkerResult::fail(KIO::ERR_DIR_ALREADY_EXIST, m_current_url.toDisplayString()); 0310 } 0311 qCDebug(KIO_SMB_LOG) << kurl << " already exist !!"; 0312 return WorkerResult::fail(KIO::ERR_FILE_ALREADY_EXIST, m_current_url.toDisplayString()); 0313 } 0314 0315 if (exists && !(flags & KIO::Resume) && (flags & KIO::Overwrite)) { 0316 qCDebug(KIO_SMB_LOG) << "exists try to remove " << m_current_url.toSmbcUrl(); 0317 // remove(m_current_url.url().toLocal8Bit()); 0318 } 0319 0320 if (flags & KIO::Resume) { 0321 // append if resuming 0322 qCDebug(KIO_SMB_LOG) << "resume " << m_current_url.toSmbcUrl(); 0323 filefd = smbc_open(m_current_url.toSmbcUrl(), O_RDWR, 0); 0324 if (filefd < 0) { 0325 errNum = errno; 0326 } else { 0327 errNum = 0; 0328 } 0329 0330 retValLSeek = smbc_lseek(filefd, 0, SEEK_END); 0331 if (retValLSeek == (off_t)-1) { 0332 errNum = errno; 0333 } else { 0334 errNum = 0; 0335 } 0336 } else { 0337 if (permissions != -1) { 0338 mode = permissions | S_IWUSR | S_IRUSR; 0339 } else { 0340 mode = S_IWUSR | S_IRUSR; 0341 } 0342 0343 qCDebug(KIO_SMB_LOG) << "NO resume " << m_current_url.toSmbcUrl(); 0344 filefd = smbc_open(m_current_url.toSmbcUrl(), O_CREAT | O_TRUNC | O_WRONLY, mode); 0345 if (filefd < 0) { 0346 errNum = errno; 0347 } else { 0348 errNum = 0; 0349 } 0350 } 0351 0352 if (filefd < 0) { 0353 if (errNum == EACCES) { 0354 qCDebug(KIO_SMB_LOG) << "error " << kurl << " access denied !!"; 0355 return WorkerResult::fail(KIO::ERR_WRITE_ACCESS_DENIED, m_current_url.toDisplayString()); 0356 } 0357 qCDebug(KIO_SMB_LOG) << "error " << kurl << " can not open for writing !!"; 0358 return WorkerResult::fail(KIO::ERR_CANNOT_OPEN_FOR_WRITING, m_current_url.toDisplayString()); 0359 } 0360 auto closeFileFd = qScopeGuard([filefd] { 0361 smbc_close(filefd); 0362 }); 0363 0364 // Loop until we got 0 (end of data) 0365 while (true) { 0366 qCDebug(KIO_SMB_LOG) << "request data "; 0367 dataReq(); // Request for data 0368 qCDebug(KIO_SMB_LOG) << "write " << m_current_url.toSmbcUrl(); 0369 0370 if (readData(filedata) <= 0) { 0371 qCDebug(KIO_SMB_LOG) << "readData <= 0"; 0372 break; 0373 } 0374 qCDebug(KIO_SMB_LOG) << "write " << m_current_url.toSmbcUrl(); 0375 buf = filedata.data(); 0376 bufsize = filedata.size(); 0377 ssize_t size = smbc_write(filefd, buf, bufsize); 0378 if (size < 0) { 0379 qCDebug(KIO_SMB_LOG) << "error " << kurl << "could not write !!"; 0380 return WorkerResult::fail(KIO::ERR_CANNOT_WRITE, m_current_url.toDisplayString()); 0381 } 0382 qCDebug(KIO_SMB_LOG) << "wrote " << size; 0383 } 0384 qCDebug(KIO_SMB_LOG) << "close " << m_current_url.toSmbcUrl(); 0385 0386 if (smbc_close(filefd) < 0) { 0387 qCDebug(KIO_SMB_LOG) << kurl << "could not write !!"; 0388 return WorkerResult::fail(KIO::ERR_CANNOT_WRITE, m_current_url.toDisplayString()); 0389 } 0390 0391 // set final permissions, if the file was just created 0392 if (permissions != -1 && !exists) { 0393 // TODO: did the smbc_chmod fail? 0394 // TODO: put in call to chmod when it is working! 0395 // smbc_chmod(url.toSmbcUrl(),permissions); 0396 } 0397 0398 applyMTimeSMBC(m_current_url); 0399 0400 return WorkerResult::pass(); 0401 }