File indexing completed on 2024-04-28 04:58:01

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 }