File indexing completed on 2024-04-14 04:52:32

0001 /*  This file is part of the KDE project
0002 
0003     SPDX-FileCopyrightText: 2000 Alexander Neundorf <neundorf@kde.org>
0004     SPDX-FileCopyrightText: 2014 Mathias Tillman <master.homer@gmail.com>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "nfsv2.h"
0010 #include "kio_nfs_debug.h"
0011 
0012 #include <config-runtime.h>
0013 
0014 #include <arpa/inet.h>
0015 
0016 // This is needed on Solaris so that rpc.h defines clnttcp_create etc.
0017 #ifndef PORTMAP
0018 #define PORTMAP
0019 #endif
0020 #include <rpc/rpc.h> // for rpc calls
0021 
0022 #include <errno.h>
0023 #include <grp.h>
0024 #include <memory.h>
0025 #include <netdb.h>
0026 #include <pwd.h>
0027 #include <stdio.h>
0028 #include <stdlib.h>
0029 #include <strings.h>
0030 #include <time.h>
0031 #include <unistd.h>
0032 #include <utime.h>
0033 
0034 #include <QDebug>
0035 #include <QDir>
0036 #include <QFile>
0037 #include <QMimeDatabase>
0038 #include <QMimeType>
0039 #include <QUrl>
0040 
0041 #include <KIO/Global>
0042 #include <KLocalizedString>
0043 #include <kio/ioworker_defaults.h>
0044 
0045 // For the complete NFSv2 reference see http://tools.ietf.org/html/rfc1094
0046 
0047 // This is for NFS version 2.
0048 #define NFSPROG 100003UL
0049 #define NFSVERS 2UL
0050 
0051 NFSProtocolV2::NFSProtocolV2(NFSSlave *slave)
0052     : NFSProtocol(slave)
0053     , m_mountClient(nullptr)
0054     , m_mountSock(-1)
0055     , m_nfsClient(nullptr)
0056     , m_nfsSock(-1)
0057 {
0058     qCDebug(LOG_KIO_NFS);
0059 
0060     clnt_timeout.tv_sec = 20;
0061     clnt_timeout.tv_usec = 0;
0062 }
0063 
0064 NFSProtocolV2::~NFSProtocolV2()
0065 {
0066     closeConnection();
0067 }
0068 
0069 bool NFSProtocolV2::isCompatible(bool &connectionError)
0070 {
0071     int ret = -1;
0072 
0073     CLIENT *client = nullptr;
0074     int sock = 0;
0075     if (NFSProtocol::openConnection(currentHost(), NFSPROG, NFSVERS, client, sock) == 0) {
0076         // Check if the NFS version is compatible
0077         ret = clnt_call(client, NFSPROC_NULL, (xdrproc_t)xdr_void, nullptr, (xdrproc_t)xdr_void, nullptr, clnt_timeout);
0078 
0079         connectionError = false;
0080     } else {
0081         qCDebug(LOG_KIO_NFS) << "openConnection failed";
0082         connectionError = true;
0083     }
0084 
0085     if (sock != -1) {
0086         ::close(sock);
0087     }
0088 
0089     if (client != nullptr) {
0090         CLNT_DESTROY(client);
0091     }
0092 
0093     qCDebug(LOG_KIO_NFS) << "RPC status" << ret << "connectionError" << connectionError;
0094     return (ret == RPC_SUCCESS);
0095 }
0096 
0097 bool NFSProtocolV2::isConnected() const
0098 {
0099     return (m_nfsClient != nullptr);
0100 }
0101 
0102 void NFSProtocolV2::closeConnection()
0103 {
0104     qCDebug(LOG_KIO_NFS);
0105 
0106     // Unmount all exported dirs(if any)
0107     if (m_mountClient != nullptr) {
0108         clnt_call(m_mountClient, MOUNTPROC_UMNTALL, (xdrproc_t)xdr_void, nullptr, (xdrproc_t)xdr_void, nullptr, clnt_timeout);
0109     }
0110 
0111     if (m_mountSock >= 0) {
0112         ::close(m_mountSock);
0113         m_mountSock = -1;
0114     }
0115     if (m_nfsSock >= 0) {
0116         ::close(m_nfsSock);
0117         m_nfsSock = -1;
0118     }
0119 
0120     if (m_mountClient != nullptr) {
0121         CLNT_DESTROY(m_mountClient);
0122         m_mountClient = nullptr;
0123     }
0124     if (m_nfsClient != nullptr) {
0125         CLNT_DESTROY(m_nfsClient);
0126         m_nfsClient = nullptr;
0127     }
0128 }
0129 
0130 NFSFileHandle NFSProtocolV2::lookupFileHandle(const QString &path)
0131 {
0132     NFSFileHandle fh;
0133     int rpcStatus;
0134     diropres res;
0135 
0136     if (lookupHandle(path, rpcStatus, res)) {
0137         fh = res.diropres_u.diropres.file;
0138 
0139         // It it a link? Get the link target.
0140         if (res.diropres_u.diropres.attributes.type == NFLNK) {
0141             nfs_fh readLinkArgs;
0142             fh.toFH(readLinkArgs);
0143 
0144             char dataBuffer[NFS_MAXPATHLEN];
0145 
0146             readlinkres readLinkRes;
0147             memset(&readLinkRes, 0, sizeof(readLinkRes));
0148             readLinkRes.readlinkres_u.data = dataBuffer;
0149 
0150             int rpcStatus = clnt_call(m_nfsClient,
0151                                       NFSPROC_READLINK,
0152                                       (xdrproc_t)xdr_nfs_fh,
0153                                       reinterpret_cast<caddr_t>(&readLinkArgs),
0154                                       (xdrproc_t)xdr_readlinkres,
0155                                       reinterpret_cast<caddr_t>(&readLinkRes),
0156                                       clnt_timeout);
0157 
0158             if (rpcStatus == RPC_SUCCESS && readLinkRes.status == NFS_OK) { // get the absolute link target
0159                 QString linkPath = QString::fromLocal8Bit(readLinkRes.readlinkres_u.data);
0160                 linkPath = QFileInfo(QFileInfo(path).path(), linkPath).absoluteFilePath();
0161 
0162                 // As with the tests done in NFSProtocol::isValidLink(), the link
0163                 // target may not be valid on the NFS server (i.e. it may point
0164                 // outside of the exported directories).  Check for this before
0165                 // calling lookupHandle() on the target of the link, as otherwise
0166                 // an error will be set which is not relevant.
0167                 if (isValidPath(linkPath)) {
0168                     diropres linkRes;
0169                     if (lookupHandle(linkPath, rpcStatus, linkRes)) {
0170                         NFSFileHandle linkFh = linkRes.diropres_u.diropres.file;
0171                         linkFh.setLinkSource(res.diropres_u.diropres.file);
0172                         qCDebug(LOG_KIO_NFS) << "Found link target" << linkPath;
0173                         return linkFh;
0174                     }
0175                 }
0176             }
0177 
0178             // If we have reached this point the file is a link,
0179             // but we failed to read the target.
0180             fh.setBadLink();
0181             qCDebug(LOG_KIO_NFS) << "Invalid link" << path;
0182         }
0183     }
0184 
0185     return fh;
0186 }
0187 
0188 /* Open connection connects to the mount daemon on the server side.
0189  In order to do this it needs authentication and calls auth_unix_create().
0190  Then it asks the mount daemon for the exported shares. Then it tries
0191  to mount all these shares. If this succeeded for at least one of them,
0192  a client for the nfs daemon is created.
0193  */
0194 void NFSProtocolV2::openConnection()
0195 {
0196     const QString host = currentHost();
0197     qCDebug(LOG_KIO_NFS) << "to" << host;
0198 
0199     KIO::Error connErr = NFSProtocol::openConnection(host, MOUNTPROG, MOUNTVERS, m_mountClient, m_mountSock);
0200     if (connErr != 0) {
0201         // Close the connection and send the error id to the slave
0202         closeConnection();
0203         setError(connErr, host);
0204         return;
0205     }
0206 
0207     exports exportlist;
0208     memset(&exportlist, 0, sizeof(exportlist));
0209 
0210     int clnt_stat =
0211         clnt_call(m_mountClient, MOUNTPROC_EXPORT, (xdrproc_t)xdr_void, nullptr, (xdrproc_t)xdr_exports, reinterpret_cast<caddr_t>(&exportlist), clnt_timeout);
0212 
0213     if (!checkForError(clnt_stat, 0, host.toLatin1())) {
0214         return;
0215     }
0216 
0217     int exportsCount = 0;
0218     bool mountHint = false;
0219 
0220     fhstatus fhStatus;
0221     for (; exportlist != nullptr; exportlist = exportlist->ex_next, exportsCount++) {
0222         memset(&fhStatus, 0, sizeof(fhStatus));
0223 
0224         clnt_stat = clnt_call(m_mountClient,
0225                               MOUNTPROC_MNT,
0226                               (xdrproc_t)xdr_dirpath,
0227                               reinterpret_cast<caddr_t>(&exportlist->ex_dir),
0228                               (xdrproc_t)xdr_fhstatus,
0229                               reinterpret_cast<caddr_t>(&fhStatus),
0230                               clnt_timeout);
0231 
0232         QString fname = QFileInfo(QDir::root(), exportlist->ex_dir).filePath();
0233         if (fhStatus.fhs_status == 0) {
0234             // Check if the directory is already noted as exported,
0235             // if so there is no need to add it again.
0236             if (NFSProtocol::isExportedDir(fname)) {
0237                 continue;
0238             }
0239 
0240             // Save the exported directory and its NFS file handle.
0241             addFileHandle(fname, static_cast<NFSFileHandle>(fhStatus.fhstatus_u.fhs_fhandle));
0242             addExportedDir(fname);
0243         } else { // mount failed with error
0244             qCDebug(LOG_KIO_NFS) << "Cannot mount" << fname << "- status" << fhStatus.fhs_status;
0245 
0246             // Even if the mount failed, record the directory path as exported
0247             // so that it can be listed as a virtual directory.  However, do
0248             // not record its (invalid) file handle in the cache.  Trying to
0249             // access the directory in any way other than just listing it, or
0250             // accessing anything below it, will be detected in
0251             // NFSProtocol::getFileHandle() and fail with an appropriate
0252             // error.
0253             if (!isExportedDir(fname))
0254                 addExportedDir(fname);
0255 
0256             // Many modern NFS servers by default reject any access attempted to
0257             // them from a non-reserved source port (i.e. above 1024).  Since
0258             // this KIO slave runs as a normal user, it is not able to use the
0259             // reserved port numbers and hence the access will be rejected.  Show
0260             // a hint if this could possibly be the problem - only once, as the
0261             // server may have many exported directories.
0262             if (fhStatus.fhs_status == NFSERR_ACCES) {
0263                 if (!mountHint) {
0264                     qCDebug(LOG_KIO_NFS) << "Check that the NFS server is exporting the filesystem";
0265                     qCDebug(LOG_KIO_NFS) << "with appropriate access permissions.  Note that it must";
0266                     qCDebug(LOG_KIO_NFS) << "allow mount requests originating from an unprivileged";
0267                     qCDebug(LOG_KIO_NFS) << "source port (see exports(5), the 'insecure' option may";
0268                     qCDebug(LOG_KIO_NFS) << "be required).";
0269                     mountHint = true;
0270                 }
0271             }
0272         }
0273     }
0274 
0275     // If nothing can be mounted then there is no point trying to open the
0276     // NFS server connection here.  However, call openConnection() anyway
0277     // and pretend that we are connected so that listing virtual directories
0278     // will work.
0279     if ((connErr = NFSProtocol::openConnection(host, NFSPROG, NFSVERS, m_nfsClient, m_nfsSock)) != 0) {
0280         closeConnection();
0281         setError(connErr, host);
0282     }
0283 
0284     slave()->connected();
0285 
0286     qCDebug(LOG_KIO_NFS) << "openConnection succeeded";
0287 }
0288 
0289 void NFSProtocolV2::listDir(const QUrl &url)
0290 {
0291     qCDebug(LOG_KIO_NFS) << url;
0292 
0293     const QString path = listDirInternal(url); // check path, list virtual dir
0294     if (path.isEmpty())
0295         return; // no more to do
0296 
0297     const NFSFileHandle fh = getFileHandle(path);
0298     if (fh.isInvalid() || fh.isBadLink()) {
0299         setError(KIO::ERR_DOES_NOT_EXIST, path);
0300         return;
0301     }
0302 
0303     readdirargs listargs;
0304     memset(&listargs, 0, sizeof(listargs));
0305     listargs.count = 1024 * sizeof(entry);
0306     fh.toFH(listargs.dir);
0307 
0308     readdirres listres;
0309 
0310     QStringList filesToList;
0311     entry *lastEntry = nullptr;
0312     do {
0313         memset(&listres, 0, sizeof(listres));
0314         // In case that we didn't get all entries we need to set the cookie to the last one we actually received.
0315         if (lastEntry != nullptr) {
0316             memcpy(listargs.cookie, lastEntry->cookie, NFS_COOKIESIZE);
0317         }
0318 
0319         int clnt_stat = clnt_call(m_nfsClient,
0320                                   NFSPROC_READDIR,
0321                                   (xdrproc_t)xdr_readdirargs,
0322                                   reinterpret_cast<caddr_t>(&listargs),
0323                                   (xdrproc_t)xdr_readdirres,
0324                                   reinterpret_cast<caddr_t>(&listres),
0325                                   clnt_timeout);
0326 
0327         if (!checkForError(clnt_stat, listres.status, path)) {
0328             return;
0329         }
0330 
0331         for (entry *dirEntry = listres.readdirres_u.reply.entries; dirEntry != nullptr; dirEntry = dirEntry->nextentry) {
0332             if (dirEntry->name != QString("..")) {
0333                 filesToList.append(QFile::decodeName(dirEntry->name));
0334             }
0335 
0336             lastEntry = dirEntry;
0337         }
0338     } while (!listres.readdirres_u.reply.eof);
0339 
0340     for (QStringList::const_iterator it = filesToList.constBegin(); it != filesToList.constEnd(); ++it) {
0341         QString filePath = QFileInfo(QDir(path), (*it)).filePath();
0342 
0343         int rpcStatus;
0344         diropres dirres;
0345         if (!lookupHandle(filePath, rpcStatus, dirres)) {
0346             qCDebug(LOG_KIO_NFS) << "Failed to lookup" << filePath << ", rpc:" << rpcStatus << ", nfs:" << dirres.status;
0347             // Try the next file instead of failing
0348             continue;
0349         }
0350 
0351         KIO::UDSEntry entry;
0352         entry.fastInsert(KIO::UDSEntry::UDS_NAME, (*it));
0353 
0354         // is it a symlink ?
0355         if (dirres.diropres_u.diropres.attributes.type == NFLNK) {
0356             int rpcStatus;
0357             readlinkres readLinkRes;
0358             char nameBuf[NFS_MAXPATHLEN];
0359             if (symLinkTarget(filePath, rpcStatus, readLinkRes, nameBuf)) {
0360                 const QString linkDest = QString::fromLocal8Bit(readLinkRes.readlinkres_u.data);
0361                 entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, linkDest);
0362 
0363                 bool badLink = true;
0364                 NFSFileHandle linkFH;
0365                 if (isValidLink(path, linkDest)) {
0366                     QString linkPath = QFileInfo(path, linkDest).absoluteFilePath();
0367 
0368                     int rpcStatus;
0369                     diropres lookupRes;
0370                     if (lookupHandle(linkPath, rpcStatus, lookupRes)) {
0371                         attrstat attrAndStat;
0372                         if (getAttr(linkPath, rpcStatus, attrAndStat)) {
0373                             badLink = false;
0374 
0375                             linkFH = lookupRes.diropres_u.diropres.file;
0376                             linkFH.setLinkSource(dirres.diropres_u.diropres.file);
0377 
0378                             completeUDSEntry(entry, attrAndStat.attrstat_u.attributes);
0379                         }
0380                     }
0381                 }
0382 
0383                 if (badLink) {
0384                     linkFH = dirres.diropres_u.diropres.file;
0385                     linkFH.setBadLink();
0386 
0387                     completeBadLinkUDSEntry(entry, dirres.diropres_u.diropres.attributes);
0388                 }
0389 
0390                 addFileHandle(filePath, linkFH);
0391             } else {
0392                 entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, i18n("Unknown target"));
0393                 completeBadLinkUDSEntry(entry, dirres.diropres_u.diropres.attributes);
0394             }
0395         } else {
0396             addFileHandle(filePath, dirres.diropres_u.diropres.file);
0397             completeUDSEntry(entry, dirres.diropres_u.diropres.attributes);
0398         }
0399 
0400         slave()->listEntry(entry);
0401     }
0402 }
0403 
0404 void NFSProtocolV2::stat(const QUrl &url)
0405 {
0406     qCDebug(LOG_KIO_NFS) << url;
0407 
0408     const QString path = statInternal(url); // check path, process virtual dir
0409     if (path.isEmpty())
0410         return; // no more to do
0411 
0412     const NFSFileHandle fh = getFileHandle(path);
0413     if (fh.isInvalid()) {
0414         qCDebug(LOG_KIO_NFS) << "File handle is invalid";
0415         setError(KIO::ERR_DOES_NOT_EXIST, path);
0416         return;
0417     }
0418 
0419     int rpcStatus;
0420     attrstat attrAndStat;
0421     if (!getAttr(path, rpcStatus, attrAndStat)) {
0422         checkForError(rpcStatus, attrAndStat.status, path);
0423         return;
0424     }
0425 
0426     const QFileInfo fileInfo(path);
0427 
0428     KIO::UDSEntry entry;
0429     entry.fastInsert(KIO::UDSEntry::UDS_NAME, fileInfo.fileName());
0430 
0431     // Is it a symlink?
0432     if (attrAndStat.attrstat_u.attributes.type == NFLNK) {
0433         qCDebug(LOG_KIO_NFS) << "It's a symlink";
0434 
0435         QString linkDest;
0436 
0437         int rpcStatus;
0438         readlinkres readLinkRes;
0439         char nameBuf[NFS_MAXPATHLEN];
0440         if (symLinkTarget(path, rpcStatus, readLinkRes, nameBuf)) {
0441             linkDest = QString::fromLocal8Bit(readLinkRes.readlinkres_u.data);
0442         } else {
0443             entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, i18n("Unknown target"));
0444             completeBadLinkUDSEntry(entry, attrAndStat.attrstat_u.attributes);
0445 
0446             slave()->statEntry(entry);
0447             return;
0448         }
0449 
0450         qCDebug(LOG_KIO_NFS) << "link dest is" << linkDest;
0451 
0452         entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, linkDest);
0453         if (!isValidLink(fileInfo.path(), linkDest)) {
0454             completeBadLinkUDSEntry(entry, attrAndStat.attrstat_u.attributes);
0455         } else {
0456             QString linkPath = QFileInfo(fileInfo.path(), linkDest).absoluteFilePath();
0457 
0458             int rpcStatus;
0459             attrstat attrAndStat;
0460             if (!getAttr(linkPath, rpcStatus, attrAndStat)) {
0461                 checkForError(rpcStatus, attrAndStat.status, linkPath);
0462                 return;
0463             }
0464 
0465             completeUDSEntry(entry, attrAndStat.attrstat_u.attributes);
0466         }
0467     } else {
0468         completeUDSEntry(entry, attrAndStat.attrstat_u.attributes);
0469     }
0470 
0471     slave()->statEntry(entry);
0472 }
0473 
0474 void NFSProtocolV2::mkdir(const QUrl &url, int permissions)
0475 {
0476     qCDebug(LOG_KIO_NFS) << url;
0477 
0478     const QString path(url.path());
0479     const QFileInfo fileInfo(path);
0480     if (isExportedDir(fileInfo.path())) {
0481         setError(KIO::ERR_WRITE_ACCESS_DENIED, path);
0482         return;
0483     }
0484 
0485     const NFSFileHandle fh = getFileHandle(fileInfo.path());
0486     if (fh.isInvalid() || fh.isBadLink()) {
0487         setError(KIO::ERR_DOES_NOT_EXIST, path);
0488         return;
0489     }
0490 
0491     createargs createArgs;
0492     fh.toFH(createArgs.where.dir);
0493 
0494     QByteArray tmpName = QFile::encodeName(fileInfo.fileName());
0495     createArgs.where.name = tmpName.data();
0496 
0497     if (permissions == -1) {
0498         createArgs.attributes.mode = 0755;
0499     } else {
0500         createArgs.attributes.mode = permissions;
0501     }
0502 
0503     diropres dirres;
0504     memset(&dirres, 0, sizeof(diropres));
0505 
0506     int clnt_stat = clnt_call(m_nfsClient,
0507                               NFSPROC_MKDIR,
0508                               (xdrproc_t)xdr_createargs,
0509                               reinterpret_cast<caddr_t>(&createArgs),
0510                               (xdrproc_t)xdr_diropres,
0511                               reinterpret_cast<caddr_t>(&dirres),
0512                               clnt_timeout);
0513 
0514     checkForError(clnt_stat, dirres.status, path);
0515 }
0516 
0517 void NFSProtocolV2::del(const QUrl &url, bool)
0518 {
0519     int rpcStatus;
0520     nfsstat nfsStatus;
0521     remove(url.path(), rpcStatus, nfsStatus);
0522     checkForError(rpcStatus, nfsStatus, url.path());
0523 }
0524 
0525 void NFSProtocolV2::chmod(const QUrl &url, int permissions)
0526 {
0527     qCDebug(LOG_KIO_NFS) << url;
0528 
0529     const QString path(url.path());
0530     if (isExportedDir(path)) {
0531         setError(KIO::ERR_ACCESS_DENIED, path);
0532         return;
0533     }
0534 
0535     sattr attributes;
0536     memset(&attributes, 0xFF, sizeof(attributes));
0537     attributes.mode = permissions;
0538 
0539     int rpcStatus;
0540     nfsstat result;
0541     setAttr(path, attributes, rpcStatus, result);
0542     checkForError(rpcStatus, result, path);
0543 }
0544 
0545 void NFSProtocolV2::get(const QUrl &url)
0546 {
0547     qCDebug(LOG_KIO_NFS) << url;
0548 
0549     const QString path(url.path());
0550 
0551     const NFSFileHandle fh = getFileHandle(path);
0552     if (fh.isInvalid() || fh.isBadLink()) {
0553         setError(KIO::ERR_DOES_NOT_EXIST, path);
0554         return;
0555     }
0556 
0557     readargs readArgs;
0558     fh.toFH(readArgs.file);
0559     readArgs.offset = 0;
0560     readArgs.count = NFS_MAXDATA;
0561     readArgs.totalcount = NFS_MAXDATA;
0562 
0563     readres readRes;
0564     memset(&readRes, 0, sizeof(readres));
0565 
0566     char buf[NFS_MAXDATA];
0567     readRes.readres_u.reply.data.data_val = buf;
0568 
0569     bool validRead = false;
0570     int offset = 0;
0571     QByteArray readBuffer;
0572     do {
0573         int clnt_stat = clnt_call(m_nfsClient,
0574                                   NFSPROC_READ,
0575                                   (xdrproc_t)xdr_readargs,
0576                                   reinterpret_cast<caddr_t>(&readArgs),
0577                                   (xdrproc_t)xdr_readres,
0578                                   reinterpret_cast<caddr_t>(&readRes),
0579                                   clnt_timeout);
0580 
0581         if (!checkForError(clnt_stat, readRes.status, path)) {
0582             return;
0583         }
0584 
0585         if (readArgs.offset == 0) {
0586             slave()->totalSize(readRes.readres_u.reply.attributes.size);
0587 
0588             const QMimeDatabase db;
0589             const QMimeType type = db.mimeTypeForFileNameAndData(url.fileName(), readBuffer);
0590             slave()->mimeType(type.name());
0591         }
0592 
0593         offset = readRes.readres_u.reply.data.data_len;
0594         readArgs.offset += offset;
0595         if (offset > 0) {
0596             validRead = true;
0597 
0598             readBuffer = QByteArray::fromRawData(readRes.readres_u.reply.data.data_val, offset);
0599             slave()->data(readBuffer);
0600             readBuffer.clear();
0601 
0602             slave()->processedSize(readArgs.offset);
0603         }
0604 
0605     } while (offset > 0);
0606 
0607     if (validRead) {
0608         slave()->data(QByteArray());
0609         slave()->processedSize(readArgs.offset);
0610     }
0611 }
0612 
0613 void NFSProtocolV2::put(const QUrl &url, int _mode, KIO::JobFlags flags)
0614 {
0615     qCDebug(LOG_KIO_NFS) << url << _mode;
0616 
0617     const QString destPath(url.path());
0618 
0619     const QFileInfo fileInfo(destPath);
0620     if (isExportedDir(fileInfo.path())) {
0621         setError(KIO::ERR_WRITE_ACCESS_DENIED, destPath);
0622         return;
0623     }
0624 
0625     NFSFileHandle destFH = getFileHandle(destPath);
0626     if (destFH.isBadLink()) {
0627         setError(KIO::ERR_DOES_NOT_EXIST, destPath);
0628         return;
0629     }
0630 
0631     // the file exists and we don't want to overwrite
0632     if (!destFH.isInvalid() && (!(flags & KIO::Overwrite))) {
0633         setError(KIO::ERR_FILE_ALREADY_EXIST, destPath);
0634         return;
0635     }
0636 
0637     int rpcStatus;
0638     diropres dirOpRes;
0639     if (!create(destPath, _mode, rpcStatus, dirOpRes)) {
0640         checkForError(rpcStatus, dirOpRes.status, fileInfo.fileName());
0641         return;
0642     }
0643 
0644     destFH = dirOpRes.diropres_u.diropres.file.data;
0645 
0646     writeargs writeArgs;
0647     memset(&writeArgs, 0, sizeof(writeargs));
0648     destFH.toFH(writeArgs.file);
0649     writeArgs.beginoffset = 0;
0650     writeArgs.totalcount = 0;
0651     writeArgs.offset = 0;
0652 
0653     attrstat attrStat;
0654 
0655     int result = 0, bytesWritten = 0;
0656     do {
0657         // Request new data
0658         slave()->dataReq();
0659 
0660         QByteArray buffer;
0661         result = slave()->readData(buffer);
0662 
0663         char *data = buffer.data();
0664         int bytesToWrite = buffer.size(), writeNow = 0;
0665         if (result > 0) {
0666             do {
0667                 if (bytesToWrite > NFS_MAXDATA) {
0668                     writeNow = NFS_MAXDATA;
0669                 } else {
0670                     writeNow = bytesToWrite;
0671                 }
0672 
0673                 writeArgs.data.data_val = data;
0674                 writeArgs.data.data_len = writeNow;
0675 
0676                 int clnt_stat = clnt_call(m_nfsClient,
0677                                           NFSPROC_WRITE,
0678                                           (xdrproc_t)xdr_writeargs,
0679                                           reinterpret_cast<caddr_t>(&writeArgs),
0680                                           (xdrproc_t)xdr_attrstat,
0681                                           reinterpret_cast<caddr_t>(&attrStat),
0682                                           clnt_timeout);
0683 
0684                 if (!checkForError(clnt_stat, attrStat.status, fileInfo.fileName())) {
0685                     return;
0686                 }
0687 
0688                 bytesWritten += writeNow;
0689                 writeArgs.offset = bytesWritten;
0690 
0691                 data = data + writeNow;
0692                 bytesToWrite -= writeNow;
0693             } while (bytesToWrite > 0);
0694         }
0695     } while (result > 0);
0696 }
0697 
0698 void NFSProtocolV2::rename(const QUrl &src, const QUrl &dest, KIO::JobFlags _flags)
0699 {
0700     qCDebug(LOG_KIO_NFS) << src << "to" << dest;
0701 
0702     const QString srcPath(src.path());
0703     if (isExportedDir(srcPath)) {
0704         setError(KIO::ERR_CANNOT_RENAME, srcPath);
0705         return;
0706     }
0707 
0708     const QString destPath(dest.path());
0709     if (isExportedDir(destPath)) {
0710         setError(KIO::ERR_ACCESS_DENIED, destPath);
0711         return;
0712     }
0713 
0714     if (!getFileHandle(destPath).isInvalid() && (_flags & KIO::Overwrite) == 0) {
0715         setError(KIO::ERR_FILE_ALREADY_EXIST, destPath);
0716         return;
0717     }
0718 
0719     int rpcStatus;
0720     nfsstat nfsStatus;
0721     rename(src.path(), destPath, rpcStatus, nfsStatus);
0722     checkForError(rpcStatus, nfsStatus, destPath);
0723 }
0724 
0725 void NFSProtocolV2::copySame(const QUrl &src, const QUrl &dest, int _mode, KIO::JobFlags _flags)
0726 {
0727     qCDebug(LOG_KIO_NFS) << src << "to" << dest;
0728 
0729     const QString srcPath(src.path());
0730 
0731     const NFSFileHandle srcFH = getFileHandle(srcPath);
0732     if (srcFH.isInvalid()) {
0733         setError(KIO::ERR_DOES_NOT_EXIST, srcPath);
0734         return;
0735     }
0736 
0737     const QString destPath = dest.path();
0738     if (isExportedDir(QFileInfo(destPath).path())) {
0739         setError(KIO::ERR_ACCESS_DENIED, destPath);
0740         return;
0741     }
0742 
0743     // The file exists and we don't want to overwrite
0744     if (!getFileHandle(destPath).isInvalid() && (_flags & KIO::Overwrite) == 0) {
0745         setError(KIO::ERR_FILE_ALREADY_EXIST, destPath);
0746         return;
0747     }
0748 
0749     // Is it a link? No need to copy the data then, just copy the link destination.
0750     if (srcFH.isLink()) {
0751         // get the link dest
0752         int rpcStatus;
0753         readlinkres readLinkRes;
0754         char nameBuf[NFS_MAXPATHLEN];
0755         if (!symLinkTarget(srcPath, rpcStatus, readLinkRes, nameBuf)) {
0756             setError(KIO::ERR_DOES_NOT_EXIST, srcPath);
0757             return;
0758         }
0759 
0760         const QString linkPath = QString::fromLocal8Bit(readLinkRes.readlinkres_u.data);
0761 
0762         nfsstat linkRes;
0763         symLink(linkPath, destPath, rpcStatus, linkRes);
0764         checkForError(rpcStatus, linkRes, linkPath);
0765         return;
0766     }
0767 
0768     unsigned long resumeOffset = 0;
0769     bool bResume = false;
0770     const QString partFilePath = destPath + QLatin1String(".part");
0771     const NFSFileHandle partFH = getFileHandle(partFilePath);
0772     const bool bPartExists = !partFH.isInvalid();
0773     const bool bMarkPartial = slave()->configValue(QStringLiteral("MarkPartial"), true);
0774 
0775     if (bPartExists) {
0776         int rpcStatus;
0777         diropres partRes;
0778         if (lookupHandle(partFilePath, rpcStatus, partRes)) {
0779             if (bMarkPartial && partRes.diropres_u.diropres.attributes.size > 0) {
0780                 if (partRes.diropres_u.diropres.attributes.type == NFDIR) {
0781                     setError(KIO::ERR_IS_DIRECTORY, partFilePath);
0782                     return;
0783                 }
0784 
0785                 bResume = slave()->canResume(partRes.diropres_u.diropres.attributes.size);
0786                 if (bResume) {
0787                     resumeOffset = partRes.diropres_u.diropres.attributes.size;
0788                 }
0789             }
0790         }
0791 
0792         // Remove the part file if we are not resuming
0793         if (!bResume) {
0794             if (!remove(partFilePath)) {
0795                 qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring...";
0796             }
0797         }
0798     }
0799 
0800     // Create the file if we are not resuming a parted transfer,
0801     // or if we are not using part files(bResume is false in that case)
0802     NFSFileHandle destFH;
0803     if (!bResume) {
0804         QString createPath;
0805         if (bMarkPartial) {
0806             createPath = partFilePath;
0807         } else {
0808             createPath = destPath;
0809         }
0810 
0811         int rpcStatus;
0812         diropres dirOpRes;
0813         if (!create(createPath, _mode, rpcStatus, dirOpRes)) {
0814             checkForError(rpcStatus, dirOpRes.status, createPath);
0815             return;
0816         }
0817 
0818         destFH = dirOpRes.diropres_u.diropres.file.data;
0819     } else {
0820         // Since we are resuming it's implied that we are using a part file,
0821         // which should exist at this point.
0822         destFH = getFileHandle(partFilePath);
0823 
0824         qCDebug(LOG_KIO_NFS) << "Resuming old transfer";
0825     }
0826 
0827     char buf[NFS_MAXDATA];
0828 
0829     writeargs writeArgs;
0830     destFH.toFH(writeArgs.file);
0831     writeArgs.beginoffset = 0;
0832     writeArgs.totalcount = 0;
0833     writeArgs.offset = 0;
0834     writeArgs.data.data_val = buf;
0835 
0836     readargs readArgs;
0837     srcFH.toFH(readArgs.file);
0838     readArgs.offset = 0;
0839     readArgs.count = NFS_MAXDATA;
0840     readArgs.totalcount = NFS_MAXDATA;
0841 
0842     if (bResume) {
0843         writeArgs.offset = resumeOffset;
0844         readArgs.offset = resumeOffset;
0845     }
0846 
0847     readres readRes;
0848     memset(&readRes, 0, sizeof(readres));
0849     readRes.readres_u.reply.data.data_val = buf;
0850 
0851     attrstat attrStat;
0852     memset(&attrStat, 0, sizeof(attrstat));
0853 
0854     bool error = false;
0855     int bytesRead = 0;
0856     do {
0857         int clnt_stat = clnt_call(m_nfsClient,
0858                                   NFSPROC_READ,
0859                                   (xdrproc_t)xdr_readargs,
0860                                   reinterpret_cast<caddr_t>(&readArgs),
0861                                   (xdrproc_t)xdr_readres,
0862                                   reinterpret_cast<caddr_t>(&readRes),
0863                                   clnt_timeout);
0864 
0865         if (!checkForError(clnt_stat, readRes.status, destPath)) {
0866             error = true;
0867             break;
0868         }
0869 
0870         bytesRead = readRes.readres_u.reply.data.data_len;
0871 
0872         // We should only send out the total size and mimetype at the start of the transfer
0873         if (readArgs.offset == 0 || (bResume && writeArgs.offset == resumeOffset)) {
0874             slave()->totalSize(readRes.readres_u.reply.attributes.size);
0875 
0876             QMimeDatabase db;
0877             QMimeType type = db.mimeTypeForFileNameAndData(src.fileName(), QByteArray::fromRawData(writeArgs.data.data_val, bytesRead));
0878             slave()->mimeType(type.name());
0879         }
0880 
0881         if (bytesRead > 0) {
0882             readArgs.offset += bytesRead;
0883 
0884             writeArgs.data.data_len = bytesRead;
0885 
0886             clnt_stat = clnt_call(m_nfsClient,
0887                                   NFSPROC_WRITE,
0888                                   (xdrproc_t)xdr_writeargs,
0889                                   reinterpret_cast<caddr_t>(&writeArgs),
0890                                   (xdrproc_t)xdr_attrstat,
0891                                   reinterpret_cast<caddr_t>(&attrStat),
0892                                   clnt_timeout);
0893 
0894             if (!checkForError(clnt_stat, attrStat.status, destPath)) {
0895                 error = true;
0896                 break;
0897             }
0898 
0899             writeArgs.offset += bytesRead;
0900 
0901             slave()->processedSize(readArgs.offset);
0902         }
0903     } while (bytesRead > 0);
0904 
0905     if (error) {
0906         if (bMarkPartial) {
0907             // Remove the part file if it's smaller than the minimum keep size.
0908             const unsigned int size = slave()->configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE);
0909             if (writeArgs.offset < size) {
0910                 if (!remove(partFilePath)) {
0911                     qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring...";
0912                 }
0913             }
0914         }
0915     } else {
0916         // Rename partial file to its original name.
0917         if (bMarkPartial) {
0918             // Remove the destination file(if it exists)
0919             if (!getFileHandle(destPath).isInvalid() && !remove(destPath)) {
0920                 qCDebug(LOG_KIO_NFS) << "Could not remove destination file" << destPath << ", ignoring...";
0921             }
0922 
0923             if (!rename(partFilePath, destPath)) {
0924                 qCDebug(LOG_KIO_NFS) << "Failed to rename" << partFilePath << "to" << destPath;
0925                 setError(KIO::ERR_CANNOT_RENAME_PARTIAL, partFilePath);
0926                 return;
0927             }
0928         }
0929 
0930         // Restore modification time
0931         int rpcStatus;
0932         attrstat attrRes;
0933         if (getAttr(srcPath, rpcStatus, attrRes)) {
0934             sattr attributes;
0935             memset(&attributes, 0xFF, sizeof(attributes));
0936             attributes.mtime.seconds = attrRes.attrstat_u.attributes.mtime.seconds;
0937             attributes.mtime.useconds = attrRes.attrstat_u.attributes.mtime.useconds;
0938 
0939             nfsstat attrSetRes;
0940             if (!setAttr(destPath, attributes, rpcStatus, attrSetRes)) {
0941                 qCDebug(LOG_KIO_NFS) << "Failed to restore mtime, ignoring..." << rpcStatus << attrSetRes;
0942             }
0943         }
0944 
0945         qCDebug(LOG_KIO_NFS) << "Copied" << writeArgs.offset << "bytes of data";
0946 
0947         slave()->processedSize(readArgs.offset);
0948     }
0949 }
0950 
0951 void NFSProtocolV2::copyFrom(const QUrl &src, const QUrl &dest, int _mode, KIO::JobFlags _flags)
0952 {
0953     qCDebug(LOG_KIO_NFS) << src << "to" << dest;
0954 
0955     const QString srcPath(src.path());
0956 
0957     const NFSFileHandle srcFH = getFileHandle(srcPath);
0958     if (srcFH.isInvalid()) {
0959         setError(KIO::ERR_DOES_NOT_EXIST, srcPath);
0960         return;
0961     }
0962 
0963     const QString destPath(dest.path());
0964 
0965     // The file exists and we don't want to overwrite
0966     if (QFile::exists(destPath) && (_flags & KIO::Overwrite) == 0) {
0967         setError(KIO::ERR_FILE_ALREADY_EXIST, destPath);
0968         return;
0969     }
0970 
0971     // Is it a link? No need to copy the data then, just copy the link destination.
0972     if (srcFH.isLink()) {
0973         qCDebug(LOG_KIO_NFS) << "Is a link";
0974 
0975         int rpcStatus;
0976         readlinkres readLinkRes;
0977         char nameBuf[NFS_MAXPATHLEN];
0978         if (!symLinkTarget(srcPath, rpcStatus, readLinkRes, nameBuf)) {
0979             setError(KIO::ERR_DOES_NOT_EXIST, srcPath);
0980             return;
0981         }
0982 
0983         QFile::link(QString::fromLocal8Bit(readLinkRes.readlinkres_u.data), destPath);
0984         return;
0985     }
0986 
0987     bool bResume = false;
0988     const QFileInfo partInfo(destPath + QLatin1String(".part"));
0989     const bool bPartExists = partInfo.exists();
0990     const bool bMarkPartial = slave()->configValue(QStringLiteral("MarkPartial"), true);
0991 
0992     if (bMarkPartial && bPartExists && partInfo.size() > 0) {
0993         if (partInfo.isDir()) {
0994             setError(KIO::ERR_IS_DIRECTORY, partInfo.absoluteFilePath());
0995             return;
0996         }
0997 
0998         bResume = slave()->canResume(partInfo.size());
0999     }
1000 
1001     if (bPartExists && !bResume) {
1002         QFile::remove(partInfo.absoluteFilePath());
1003     }
1004 
1005     QFile::OpenMode openMode;
1006     QString outFileName;
1007     if (bResume) {
1008         outFileName = partInfo.absoluteFilePath();
1009         openMode = QFile::WriteOnly | QFile::Append;
1010     } else {
1011         outFileName = (bMarkPartial ? partInfo.absoluteFilePath() : destPath);
1012         openMode = QFile::WriteOnly | QFile::Truncate;
1013     }
1014 
1015     QFile destFile(outFileName);
1016     if (!bResume) {
1017         QFile::Permissions perms;
1018         if (_mode == -1) {
1019             perms = QFile::ReadOwner | QFile::WriteOwner;
1020         } else {
1021             perms = KIO::convertPermissions(_mode | QFile::WriteOwner);
1022         }
1023         destFile.setPermissions(perms);
1024     }
1025 
1026     if (!destFile.open(openMode)) {
1027         switch (destFile.error()) {
1028         case QFile::OpenError:
1029             if (bResume) {
1030                 setError(KIO::ERR_CANNOT_RESUME, destPath);
1031             } else {
1032                 setError(KIO::ERR_CANNOT_OPEN_FOR_WRITING, destPath);
1033             }
1034             break;
1035         case QFile::PermissionsError:
1036             setError(KIO::ERR_WRITE_ACCESS_DENIED, destPath);
1037             break;
1038         default:
1039             setError(KIO::ERR_CANNOT_OPEN_FOR_WRITING, destPath);
1040             break;
1041         }
1042         return;
1043     }
1044 
1045     char buf[NFS_MAXDATA];
1046 
1047     readargs readArgs;
1048     srcFH.toFH(readArgs.file);
1049     if (bResume) {
1050         readArgs.offset = partInfo.size();
1051     } else {
1052         readArgs.offset = 0;
1053     }
1054     readArgs.count = NFS_MAXDATA;
1055     readArgs.totalcount = NFS_MAXDATA;
1056 
1057     readres readRes;
1058     memset(&readRes, 0, sizeof(readres));
1059     readRes.readres_u.reply.data.data_val = buf;
1060 
1061     attrstat attrStat;
1062     memset(&attrStat, 0, sizeof(attrstat));
1063 
1064     bool error = false;
1065     int bytesRead = 0;
1066     do {
1067         int clnt_stat = clnt_call(m_nfsClient,
1068                                   NFSPROC_READ,
1069                                   (xdrproc_t)xdr_readargs,
1070                                   reinterpret_cast<caddr_t>(&readArgs),
1071                                   (xdrproc_t)xdr_readres,
1072                                   reinterpret_cast<caddr_t>(&readRes),
1073                                   clnt_timeout);
1074 
1075         if (!checkForError(clnt_stat, readRes.status, destPath)) {
1076             error = true;
1077             break;
1078         }
1079 
1080         bytesRead = readRes.readres_u.reply.data.data_len;
1081 
1082         if (readArgs.offset == 0) {
1083             slave()->totalSize(readRes.readres_u.reply.attributes.size);
1084 
1085             QMimeDatabase db;
1086             QMimeType type = db.mimeTypeForFileNameAndData(src.fileName(), QByteArray::fromRawData(readRes.readres_u.reply.data.data_val, bytesRead));
1087             slave()->mimeType(type.name());
1088         }
1089 
1090         if (bytesRead > 0) {
1091             readArgs.offset += bytesRead;
1092 
1093             if (destFile.write(readRes.readres_u.reply.data.data_val, bytesRead) != bytesRead) {
1094                 setError(KIO::ERR_CANNOT_WRITE, destPath);
1095 
1096                 error = true;
1097                 break;
1098             }
1099 
1100             slave()->processedSize(readArgs.offset);
1101         }
1102     } while (bytesRead > 0);
1103 
1104     // Close the file so we can modify the modification time later.
1105     destFile.close();
1106 
1107     if (error) {
1108         if (bMarkPartial) {
1109             // Remove the part file if it's smaller than the minimum keep
1110             const int size = slave()->configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE);
1111             if (partInfo.size() < size) {
1112                 QFile::remove(partInfo.absoluteFilePath());
1113             }
1114         }
1115     } else {
1116         // Rename partial file to its original name.
1117         if (bMarkPartial) {
1118             const QString sPart = partInfo.absoluteFilePath();
1119             if (QFile::exists(destPath)) {
1120                 QFile::remove(destPath);
1121             }
1122             if (!QFile::rename(sPart, destPath)) {
1123                 qCDebug(LOG_KIO_NFS) << "Failed to rename" << sPart << "to" << destPath;
1124                 setError(KIO::ERR_CANNOT_RENAME_PARTIAL, sPart);
1125                 return;
1126             }
1127         }
1128 
1129         // Restore the mtime on the file.
1130         const QString mtimeStr = slave()->metaData("modified");
1131         if (!mtimeStr.isEmpty()) {
1132             QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate);
1133             if (dt.isValid()) {
1134                 qCDebug(LOG_KIO_NFS) << "Setting modification time to" << dt.toSecsSinceEpoch();
1135 
1136                 struct utimbuf utbuf;
1137                 utbuf.actime = QFileInfo(destPath).lastRead().toSecsSinceEpoch(); // access time, unchanged
1138                 utbuf.modtime = dt.toSecsSinceEpoch(); // modification time
1139                 utime(QFile::encodeName(destPath).constData(), &utbuf);
1140             }
1141         }
1142 
1143         qCDebug(LOG_KIO_NFS) << "Copied" << readArgs.offset << "bytes of data";
1144 
1145         slave()->processedSize(readArgs.offset);
1146     }
1147 }
1148 
1149 void NFSProtocolV2::copyTo(const QUrl &src, const QUrl &dest, int _mode, KIO::JobFlags _flags)
1150 {
1151     qCDebug(LOG_KIO_NFS) << src << "to" << dest;
1152 
1153     // The source does not exist, how strange.
1154     const QString srcPath(src.path());
1155     if (!QFile::exists(srcPath)) {
1156         setError(KIO::ERR_DOES_NOT_EXIST, srcPath);
1157         return;
1158     }
1159 
1160     const QString destPath(dest.path());
1161     if (isExportedDir(destPath)) {
1162         setError(KIO::ERR_ACCESS_DENIED, destPath);
1163         return;
1164     }
1165 
1166     // The file exists and we don't want to overwrite.
1167     if (!getFileHandle(destPath).isInvalid() && (_flags & KIO::Overwrite) == 0) {
1168         setError(KIO::ERR_FILE_ALREADY_EXIST, destPath);
1169         return;
1170     }
1171 
1172     // Is it a link? No need to copy the data then, just copy the link destination.
1173     const QString symlinkTarget = QFile::symLinkTarget(srcPath);
1174     if (!symlinkTarget.isEmpty()) {
1175         int rpcStatus;
1176         nfsstat linkRes;
1177         symLink(symlinkTarget, destPath, rpcStatus, linkRes);
1178         checkForError(rpcStatus, linkRes, symlinkTarget);
1179         return;
1180     }
1181 
1182     unsigned long resumeOffset = 0;
1183     bool bResume = false;
1184     const QString partFilePath = destPath + QLatin1String(".part");
1185     const NFSFileHandle partFH = getFileHandle(partFilePath);
1186     const bool bPartExists = !partFH.isInvalid();
1187     const bool bMarkPartial = slave()->configValue(QStringLiteral("MarkPartial"), true);
1188 
1189     if (bPartExists) {
1190         int rpcStatus;
1191         diropres partRes;
1192         if (lookupHandle(partFilePath, rpcStatus, partRes)) {
1193             if (bMarkPartial && partRes.diropres_u.diropres.attributes.size > 0) {
1194                 if (partRes.diropres_u.diropres.attributes.type == NFDIR) {
1195                     setError(KIO::ERR_IS_DIRECTORY, partFilePath);
1196                     return;
1197                 }
1198 
1199                 bResume = slave()->canResume(partRes.diropres_u.diropres.attributes.size);
1200                 if (bResume) {
1201                     resumeOffset = partRes.diropres_u.diropres.attributes.size;
1202                 }
1203             }
1204         }
1205 
1206         // Remove the part file if we are not resuming
1207         if (!bResume) {
1208             if (!remove(partFilePath)) {
1209                 qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring...";
1210             }
1211         }
1212     }
1213 
1214     // Open the source file
1215     QFile srcFile(srcPath);
1216     if (!srcFile.open(QIODevice::ReadOnly)) {
1217         setError(KIO::ERR_CANNOT_OPEN_FOR_READING, srcPath);
1218         return;
1219     }
1220 
1221     // Create the file if we are not resuming a parted transfer,
1222     // or if we are not using part files(bResume is false in that case)
1223     NFSFileHandle destFH;
1224     if (!bResume) {
1225         QString createPath;
1226         if (bMarkPartial) {
1227             createPath = partFilePath;
1228         } else {
1229             createPath = destPath;
1230         }
1231 
1232         int rpcStatus;
1233         diropres dirOpRes;
1234         if (!create(createPath, _mode, rpcStatus, dirOpRes)) {
1235             checkForError(rpcStatus, dirOpRes.status, createPath);
1236             return;
1237         }
1238 
1239         destFH = dirOpRes.diropres_u.diropres.file.data;
1240     } else {
1241         // Since we are resuming it's implied that we are using a part file,
1242         // which should exist at this point.
1243         destFH = getFileHandle(partFilePath);
1244 
1245         qCDebug(LOG_KIO_NFS) << "Resuming old transfer";
1246     }
1247 
1248     // Send the total size to the slave.
1249     slave()->totalSize(srcFile.size());
1250 
1251     // Set up write arguments.
1252     char buf[NFS_MAXDATA];
1253 
1254     writeargs writeArgs;
1255     memset(&writeArgs, 0, sizeof(writeargs));
1256     destFH.toFH(writeArgs.file);
1257     writeArgs.data.data_val = buf;
1258     writeArgs.beginoffset = 0;
1259     writeArgs.totalcount = 0;
1260     if (bResume) {
1261         writeArgs.offset = resumeOffset;
1262     } else {
1263         writeArgs.offset = 0;
1264     }
1265 
1266     attrstat attrStat;
1267     memset(&attrStat, 0, sizeof(attrstat));
1268 
1269     bool error = false;
1270     int bytesRead = 0;
1271     do {
1272         bytesRead = srcFile.read(writeArgs.data.data_val, NFS_MAXDATA);
1273         if (bytesRead < 0) {
1274             setError(KIO::ERR_CANNOT_READ, srcPath);
1275 
1276             error = true;
1277             break;
1278         }
1279 
1280         if (bytesRead > 0) {
1281             writeArgs.data.data_len = bytesRead;
1282 
1283             int clnt_stat = clnt_call(m_nfsClient,
1284                                       NFSPROC_WRITE,
1285                                       (xdrproc_t)xdr_writeargs,
1286                                       reinterpret_cast<caddr_t>(&writeArgs),
1287                                       (xdrproc_t)xdr_attrstat,
1288                                       reinterpret_cast<caddr_t>(&attrStat),
1289                                       clnt_timeout);
1290 
1291             if (!checkForError(clnt_stat, attrStat.status, destPath)) {
1292                 error = true;
1293                 break;
1294             }
1295 
1296             writeArgs.offset += bytesRead;
1297 
1298             slave()->processedSize(writeArgs.offset);
1299         }
1300     } while (bytesRead > 0);
1301 
1302     if (error) {
1303         if (bMarkPartial) {
1304             // Remove the part file if it's smaller than the minimum keep size.
1305             const unsigned int size = slave()->configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE);
1306             if (writeArgs.offset < size) {
1307                 if (!remove(partFilePath)) {
1308                     qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring...";
1309                 }
1310             }
1311         }
1312     } else {
1313         // Rename partial file to its original name.
1314         if (bMarkPartial) {
1315             // Remove the destination file(if it exists)
1316             if (!getFileHandle(destPath).isInvalid() && !remove(destPath)) {
1317                 qCDebug(LOG_KIO_NFS) << "Could not remove destination file" << destPath << ", ignoring...";
1318             }
1319 
1320             if (!rename(partFilePath, destPath)) {
1321                 qCDebug(LOG_KIO_NFS) << "Failed to rename" << partFilePath << "to" << destPath;
1322                 setError(KIO::ERR_CANNOT_RENAME_PARTIAL, partFilePath);
1323                 return;
1324             }
1325         }
1326 
1327         // Restore the mtime on the file.
1328         const QString mtimeStr = slave()->metaData("modified");
1329         if (!mtimeStr.isEmpty()) {
1330             QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate);
1331             if (dt.isValid()) {
1332                 sattr attributes;
1333                 memset(&attributes, 0xFF, sizeof(attributes));
1334                 attributes.mtime.seconds = dt.toSecsSinceEpoch();
1335                 attributes.mtime.useconds = attributes.mtime.seconds * 1000000ULL;
1336 
1337                 int rpcStatus;
1338                 nfsstat attrSetRes;
1339                 if (!setAttr(destPath, attributes, rpcStatus, attrSetRes)) {
1340                     qCDebug(LOG_KIO_NFS) << "Failed to restore mtime, ignoring..." << rpcStatus << attrSetRes;
1341                 }
1342             }
1343         }
1344 
1345         qCDebug(LOG_KIO_NFS) << "Copied" << writeArgs.offset << "bytes of data";
1346 
1347         slave()->processedSize(writeArgs.offset);
1348     }
1349 }
1350 
1351 void NFSProtocolV2::symlink(const QString &target, const QUrl &dest, KIO::JobFlags flags)
1352 {
1353     const QString destPath(dest.path());
1354     if (isExportedDir(QFileInfo(destPath).path())) {
1355         setError(KIO::ERR_ACCESS_DENIED, destPath);
1356         return;
1357     }
1358 
1359     if (!getFileHandle(destPath).isInvalid() && (flags & KIO::Overwrite) == 0) {
1360         setError(KIO::ERR_FILE_ALREADY_EXIST, destPath);
1361         return;
1362     }
1363 
1364     int rpcStatus;
1365     nfsstat res;
1366     symLink(target, destPath, rpcStatus, res);
1367     checkForError(rpcStatus, res, destPath);
1368 }
1369 
1370 bool NFSProtocolV2::create(const QString &path, int mode, int &rpcStatus, diropres &result)
1371 {
1372     memset(&rpcStatus, 0, sizeof(int));
1373     memset(&result, 0, sizeof(result));
1374 
1375     if (!isConnected()) {
1376         result.status = NFSERR_ACCES;
1377         return false;
1378     }
1379 
1380     const QFileInfo fileInfo(path);
1381     if (isExportedDir(fileInfo.path())) {
1382         result.status = NFSERR_ACCES;
1383         return false;
1384     }
1385 
1386     const NFSFileHandle directoryFH = getFileHandle(fileInfo.path());
1387     if (directoryFH.isInvalid()) {
1388         result.status = NFSERR_NOENT;
1389         return false;
1390     }
1391 
1392     QByteArray tmpName = QFile::encodeName(fileInfo.fileName());
1393 
1394     createargs args;
1395     directoryFH.toFH(args.where.dir);
1396     args.where.name = tmpName.data();
1397 
1398     memset(&args.attributes, 0xFF, sizeof(sattr));
1399     if (mode == -1) {
1400         args.attributes.mode = 0644;
1401     } else {
1402         args.attributes.mode = mode;
1403     }
1404     args.attributes.uid = geteuid();
1405     args.attributes.gid = getegid();
1406     args.attributes.size = 0;
1407 
1408     rpcStatus = clnt_call(m_nfsClient,
1409                           NFSPROC_CREATE,
1410                           (xdrproc_t)xdr_createargs,
1411                           reinterpret_cast<caddr_t>(&args),
1412                           (xdrproc_t)xdr_diropres,
1413                           reinterpret_cast<caddr_t>(&result),
1414                           clnt_timeout);
1415 
1416     return (rpcStatus == RPC_SUCCESS && result.status == NFS_OK);
1417 }
1418 
1419 bool NFSProtocolV2::getAttr(const QString &path, int &rpcStatus, attrstat &result)
1420 {
1421     memset(&rpcStatus, 0, sizeof(int));
1422     memset(&result, 0, sizeof(result));
1423 
1424     if (!isConnected()) {
1425         result.status = NFSERR_ACCES;
1426         return false;
1427     }
1428 
1429     const NFSFileHandle fileFH = getFileHandle(path);
1430     if (fileFH.isInvalid()) {
1431         result.status = NFSERR_NOENT;
1432         return false;
1433     }
1434 
1435     nfs_fh fh;
1436     fileFH.toFH(fh);
1437 
1438     rpcStatus = clnt_call(m_nfsClient,
1439                           NFSPROC_GETATTR,
1440                           (xdrproc_t)xdr_nfs_fh,
1441                           reinterpret_cast<caddr_t>(&fh),
1442                           (xdrproc_t)xdr_attrstat,
1443                           reinterpret_cast<caddr_t>(&result),
1444                           clnt_timeout);
1445 
1446     return (rpcStatus == RPC_SUCCESS && result.status == NFS_OK);
1447 }
1448 
1449 bool NFSProtocolV2::lookupHandle(const QString &path, int &rpcStatus, diropres &result)
1450 {
1451     memset(&rpcStatus, 0, sizeof(int));
1452     memset(&result, 0, sizeof(result));
1453 
1454     if (!isConnected()) {
1455         result.status = NFSERR_ACCES;
1456         return false;
1457     }
1458 
1459     const QFileInfo fileInfo(path);
1460 
1461     const NFSFileHandle parentFH = getFileHandle(fileInfo.path());
1462     if (parentFH.isInvalid()) {
1463         result.status = NFSERR_NOENT;
1464         return false;
1465     }
1466 
1467     QByteArray tmpName = QFile::encodeName(fileInfo.fileName());
1468 
1469     diropargs dirargs;
1470     memset(&dirargs, 0, sizeof(diropargs));
1471     parentFH.toFH(dirargs.dir);
1472     dirargs.name = tmpName.data();
1473 
1474     memset(&result, 0, sizeof(diropres));
1475 
1476     rpcStatus = clnt_call(m_nfsClient,
1477                           NFSPROC_LOOKUP,
1478                           (xdrproc_t)xdr_diropargs,
1479                           reinterpret_cast<caddr_t>(&dirargs),
1480                           (xdrproc_t)xdr_diropres,
1481                           reinterpret_cast<caddr_t>(&result),
1482                           clnt_timeout);
1483 
1484     return (rpcStatus == RPC_SUCCESS && result.status == NFS_OK);
1485 }
1486 
1487 bool NFSProtocolV2::symLinkTarget(const QString &path, int &rpcStatus, readlinkres &result, char *dataBuffer)
1488 {
1489     const NFSFileHandle fh = getFileHandle(path);
1490 
1491     nfs_fh nfsFH;
1492     if (fh.isLink() && !fh.isBadLink()) {
1493         fh.toFHLink(nfsFH);
1494     } else {
1495         fh.toFH(nfsFH);
1496     }
1497 
1498     result.readlinkres_u.data = dataBuffer;
1499 
1500     rpcStatus = clnt_call(m_nfsClient,
1501                           NFSPROC_READLINK,
1502                           (xdrproc_t)xdr_nfs_fh,
1503                           reinterpret_cast<caddr_t>(&nfsFH),
1504                           (xdrproc_t)xdr_readlinkres,
1505                           reinterpret_cast<caddr_t>(&result),
1506                           clnt_timeout);
1507 
1508     return (rpcStatus == RPC_SUCCESS && result.status == NFS_OK);
1509 }
1510 
1511 bool NFSProtocolV2::remove(const QString &path)
1512 {
1513     int rpcStatus;
1514     nfsstat nfsStatus;
1515 
1516     return remove(path, rpcStatus, nfsStatus);
1517 }
1518 
1519 bool NFSProtocolV2::remove(const QString &path, int &rpcStatus, nfsstat &result)
1520 {
1521     qCDebug(LOG_KIO_NFS) << path;
1522 
1523     memset(&rpcStatus, 0, sizeof(int));
1524     memset(&result, 0, sizeof(result));
1525 
1526     if (!isConnected()) {
1527         result = NFSERR_PERM;
1528         return false;
1529     }
1530 
1531     const QFileInfo fileInfo(path);
1532     if (isExportedDir(fileInfo.path())) {
1533         result = NFSERR_ACCES;
1534         return false;
1535     }
1536 
1537     const NFSFileHandle directoryFH = getFileHandle(fileInfo.path());
1538     if (directoryFH.isInvalid()) {
1539         result = NFSERR_NOENT;
1540         return false;
1541     }
1542 
1543     int rpcLookupStatus;
1544     diropres lookupRes;
1545     if (!lookupHandle(path, rpcLookupStatus, lookupRes)) {
1546         result = NFSERR_NOENT;
1547         return false;
1548     }
1549 
1550     QByteArray tmpName = QFile::encodeName(fileInfo.fileName());
1551 
1552     diropargs dirargs;
1553     memset(&dirargs, 0, sizeof(diropargs));
1554     directoryFH.toFH(dirargs.dir);
1555     dirargs.name = tmpName.data();
1556 
1557     if (lookupRes.diropres_u.diropres.attributes.type != NFDIR) {
1558         rpcStatus = clnt_call(m_nfsClient,
1559                               NFSPROC_REMOVE,
1560                               (xdrproc_t)xdr_diropargs,
1561                               reinterpret_cast<caddr_t>(&dirargs),
1562                               (xdrproc_t)xdr_nfsstat,
1563                               reinterpret_cast<caddr_t>(&result),
1564                               clnt_timeout);
1565     } else {
1566         rpcStatus = clnt_call(m_nfsClient,
1567                               NFSPROC_RMDIR,
1568                               (xdrproc_t)xdr_diropargs,
1569                               reinterpret_cast<caddr_t>(&dirargs),
1570                               (xdrproc_t)xdr_nfsstat,
1571                               reinterpret_cast<caddr_t>(&result),
1572                               clnt_timeout);
1573     }
1574 
1575     bool ret = (rpcStatus == RPC_SUCCESS && result == NFS_OK);
1576     if (ret) {
1577         removeFileHandle(path);
1578     }
1579 
1580     return ret;
1581 }
1582 
1583 bool NFSProtocolV2::rename(const QString &src, const QString &dest)
1584 {
1585     int rpcStatus;
1586     nfsstat result;
1587 
1588     return rename(src, dest, rpcStatus, result);
1589 }
1590 
1591 bool NFSProtocolV2::rename(const QString &src, const QString &dest, int &rpcStatus, nfsstat &result)
1592 {
1593     qCDebug(LOG_KIO_NFS) << src << dest;
1594 
1595     memset(&rpcStatus, 0, sizeof(int));
1596     memset(&result, 0, sizeof(result));
1597 
1598     const QFileInfo srcFileInfo(src);
1599     if (isExportedDir(srcFileInfo.path())) {
1600         result = NFSERR_ACCES;
1601         return false;
1602     }
1603 
1604     const NFSFileHandle srcDirectoryFH = getFileHandle(srcFileInfo.path());
1605     if (srcDirectoryFH.isInvalid()) {
1606         result = NFSERR_NOENT;
1607         return false;
1608     }
1609 
1610     const QFileInfo destFileInfo(dest);
1611     if (isExportedDir(destFileInfo.path())) {
1612         result = NFSERR_ACCES;
1613         return false;
1614     }
1615 
1616     const NFSFileHandle destDirectoryFH = getFileHandle(destFileInfo.path());
1617     if (destDirectoryFH.isInvalid()) {
1618         result = NFSERR_NOENT;
1619         return false;
1620     }
1621 
1622     renameargs renameArgs;
1623     memset(&renameArgs, 0, sizeof(renameargs));
1624 
1625     QByteArray srcByteName = QFile::encodeName(srcFileInfo.fileName());
1626     srcDirectoryFH.toFH(renameArgs.from.dir);
1627     renameArgs.from.name = srcByteName.data();
1628 
1629     QByteArray destByteName = QFile::encodeName(destFileInfo.fileName());
1630     destDirectoryFH.toFH(renameArgs.to.dir);
1631     renameArgs.to.name = destByteName.data();
1632 
1633     rpcStatus = clnt_call(m_nfsClient,
1634                           NFSPROC_RENAME,
1635                           (xdrproc_t)xdr_renameargs,
1636                           reinterpret_cast<caddr_t>(&renameArgs),
1637                           (xdrproc_t)xdr_nfsstat,
1638                           reinterpret_cast<caddr_t>(&result),
1639                           clnt_timeout);
1640 
1641     bool ret = (rpcStatus == RPC_SUCCESS && result == NFS_OK);
1642     if (ret) {
1643         // Can we actually find the new handle?
1644         int lookupStatus;
1645         diropres lookupRes;
1646         if (lookupHandle(dest, lookupStatus, lookupRes)) {
1647             // Remove the old file, and add the new one
1648             removeFileHandle(src);
1649             addFileHandle(dest, lookupRes.diropres_u.diropres.file);
1650         }
1651     }
1652 
1653     return ret;
1654 }
1655 
1656 bool NFSProtocolV2::setAttr(const QString &path, const sattr &attributes, int &rpcStatus, nfsstat &result)
1657 {
1658     qCDebug(LOG_KIO_NFS) << path;
1659 
1660     memset(&rpcStatus, 0, sizeof(int));
1661     memset(&result, 0, sizeof(result));
1662 
1663     const NFSFileHandle fh = getFileHandle(path);
1664     if (fh.isInvalid()) {
1665         result = NFSERR_NOENT;
1666         return false;
1667     }
1668 
1669     sattrargs sAttrArgs;
1670     fh.toFH(sAttrArgs.file);
1671     memcpy(&sAttrArgs.attributes, &attributes, sizeof(attributes));
1672 
1673     rpcStatus = clnt_call(m_nfsClient,
1674                           NFSPROC_SETATTR,
1675                           (xdrproc_t)xdr_sattrargs,
1676                           reinterpret_cast<caddr_t>(&sAttrArgs),
1677                           (xdrproc_t)xdr_nfsstat,
1678                           reinterpret_cast<caddr_t>(&result),
1679                           clnt_timeout);
1680 
1681     return (rpcStatus == RPC_SUCCESS && result == NFS_OK);
1682 }
1683 
1684 bool NFSProtocolV2::symLink(const QString &target, const QString &dest, int &rpcStatus, nfsstat &result)
1685 {
1686     qCDebug(LOG_KIO_NFS) << target << dest;
1687 
1688     memset(&rpcStatus, 0, sizeof(int));
1689     memset(&result, 0, sizeof(result));
1690 
1691     // Remove dest first, we don't really care about the return value at this point,
1692     // the symlink call will fail if dest was not removed correctly.
1693     remove(dest);
1694 
1695     const QFileInfo fileInfo(dest);
1696     if (isExportedDir(fileInfo.path())) {
1697         result = NFSERR_ACCES;
1698         return false;
1699     }
1700 
1701     const NFSFileHandle fh = getFileHandle(fileInfo.path());
1702     if (fh.isInvalid()) {
1703         result = NFSERR_NOENT;
1704         return false;
1705     }
1706 
1707     QByteArray fromBytes = QFile::encodeName(fileInfo.fileName());
1708     QByteArray toBytes = QFile::encodeName(target);
1709 
1710     symlinkargs symLinkArgs;
1711     memset(&symLinkArgs, 0, sizeof(symLinkArgs));
1712 
1713     fh.toFH(symLinkArgs.from.dir);
1714     symLinkArgs.from.name = fromBytes.data();
1715     symLinkArgs.to = toBytes.data();
1716 
1717     rpcStatus = clnt_call(m_nfsClient,
1718                           NFSPROC_SYMLINK,
1719                           (xdrproc_t)xdr_symlinkargs,
1720                           reinterpret_cast<caddr_t>(&symLinkArgs),
1721                           (xdrproc_t)xdr_nfsstat,
1722                           reinterpret_cast<caddr_t>(&result),
1723                           clnt_timeout);
1724 
1725     // Add the new handle to the cache
1726     NFSFileHandle destFH = getFileHandle(dest);
1727     if (!destFH.isInvalid()) {
1728         addFileHandle(dest, destFH);
1729     }
1730 
1731     return (rpcStatus == RPC_SUCCESS && result == NFS_OK);
1732 }
1733 
1734 void NFSProtocolV2::completeUDSEntry(KIO::UDSEntry &entry, const fattr &attributes)
1735 {
1736     entry.replace(KIO::UDSEntry::UDS_SIZE, attributes.size);
1737     entry.replace(KIO::UDSEntry::UDS_MODIFICATION_TIME, attributes.mtime.seconds);
1738     entry.replace(KIO::UDSEntry::UDS_ACCESS_TIME, attributes.atime.seconds);
1739     entry.replace(KIO::UDSEntry::UDS_ACCESS, (attributes.mode & 07777));
1740     entry.replace(KIO::UDSEntry::UDS_FILE_TYPE, attributes.mode & S_IFMT); // extract file type
1741 
1742     NFSProtocol::completeUDSEntry(entry, attributes.uid, attributes.gid);
1743 }
1744 
1745 void NFSProtocolV2::completeBadLinkUDSEntry(KIO::UDSEntry &entry, const fattr &attributes)
1746 {
1747     entry.replace(KIO::UDSEntry::UDS_MODIFICATION_TIME, attributes.mtime.seconds);
1748     entry.replace(KIO::UDSEntry::UDS_ACCESS_TIME, attributes.atime.seconds);
1749 
1750     NFSProtocol::completeInvalidUDSEntry(entry);
1751 }