File indexing completed on 2024-04-21 04:57:39

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 "nfsv3.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 NFSv3 reference see http://tools.ietf.org/html/rfc1813
0046 
0047 // This ioslave is for NFS version 3.
0048 #define NFSPROG 100003UL
0049 #define NFSVERS 3UL
0050 
0051 #define NFS3_MAXDATA 32768
0052 #define NFS3_MAXPATHLEN PATH_MAX
0053 
0054 NFSProtocolV3::NFSProtocolV3(NFSSlave *slave)
0055     : NFSProtocol(slave)
0056     , m_mountClient(nullptr)
0057     , m_mountSock(-1)
0058     , m_nfsClient(nullptr)
0059     , m_nfsSock(-1)
0060     , m_readBufferSize(0)
0061     , m_writeBufferSize(0)
0062     , m_readDirSize(0)
0063 {
0064     qCDebug(LOG_KIO_NFS);
0065 
0066     clnt_timeout.tv_sec = 20;
0067     clnt_timeout.tv_usec = 0;
0068 }
0069 
0070 NFSProtocolV3::~NFSProtocolV3()
0071 {
0072     closeConnection();
0073 }
0074 
0075 bool NFSProtocolV3::isCompatible(bool &connectionError)
0076 {
0077     int ret = -1;
0078 
0079     CLIENT *client = nullptr;
0080     int sock = 0;
0081     if (NFSProtocol::openConnection(currentHost(), NFSPROG, NFSVERS, client, sock) == 0) {
0082         timeval check_timeout;
0083         check_timeout.tv_sec = 20;
0084         check_timeout.tv_usec = 0;
0085 
0086         // Check if the NFS version is compatible
0087         ret = clnt_call(client, NFSPROC3_NULL, (xdrproc_t)xdr_void, nullptr, (xdrproc_t)xdr_void, nullptr, check_timeout);
0088 
0089         connectionError = false;
0090     } else {
0091         qCDebug(LOG_KIO_NFS) << "openConnection failed";
0092         connectionError = true;
0093     }
0094 
0095     if (sock != -1) {
0096         ::close(sock);
0097     }
0098 
0099     if (client != nullptr) {
0100         CLNT_DESTROY(client);
0101     }
0102 
0103     qCDebug(LOG_KIO_NFS) << "RPC status" << ret << "connectionError" << connectionError;
0104     return (ret == RPC_SUCCESS);
0105 }
0106 
0107 bool NFSProtocolV3::isConnected() const
0108 {
0109     return (m_nfsClient != nullptr);
0110 }
0111 
0112 void NFSProtocolV3::closeConnection()
0113 {
0114     qCDebug(LOG_KIO_NFS);
0115 
0116     // Unmount all exported dirs(if any)
0117     if (m_mountClient != nullptr) {
0118         clnt_call(m_mountClient, MOUNTPROC3_UMNTALL, (xdrproc_t)xdr_void, nullptr, (xdrproc_t)xdr_void, nullptr, clnt_timeout);
0119     }
0120 
0121     if (m_mountSock >= 0) {
0122         ::close(m_mountSock);
0123         m_mountSock = -1;
0124     }
0125     if (m_nfsSock >= 0) {
0126         ::close(m_nfsSock);
0127         m_nfsSock = -1;
0128     }
0129 
0130     if (m_mountClient != nullptr) {
0131         CLNT_DESTROY(m_mountClient);
0132         m_mountClient = nullptr;
0133     }
0134     if (m_nfsClient != nullptr) {
0135         CLNT_DESTROY(m_nfsClient);
0136         m_nfsClient = nullptr;
0137     }
0138 }
0139 
0140 NFSFileHandle NFSProtocolV3::lookupFileHandle(const QString &path)
0141 {
0142     NFSFileHandle fh;
0143     int rpcStatus;
0144     LOOKUP3res res;
0145     if (lookupHandle(path, rpcStatus, res)) {
0146         fh = res.LOOKUP3res_u.resok.object;
0147 
0148         // Is it a link? Get the link target.
0149         if (res.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.type == NF3LNK) {
0150             READLINK3args readLinkArgs;
0151             memset(&readLinkArgs, 0, sizeof(readLinkArgs));
0152             fh.toFH(readLinkArgs.symlink);
0153 
0154             char dataBuffer[NFS3_MAXPATHLEN];
0155 
0156             READLINK3res readLinkRes;
0157             memset(&readLinkRes, 0, sizeof(readLinkRes));
0158             readLinkRes.READLINK3res_u.resok.data = dataBuffer;
0159 
0160             int rpcStatus = clnt_call(m_nfsClient,
0161                                       NFSPROC3_READLINK,
0162                                       (xdrproc_t)xdr_READLINK3args,
0163                                       reinterpret_cast<caddr_t>(&readLinkArgs),
0164                                       (xdrproc_t)xdr_READLINK3res,
0165                                       reinterpret_cast<caddr_t>(&readLinkRes),
0166                                       clnt_timeout);
0167 
0168             if (rpcStatus == RPC_SUCCESS && readLinkRes.status == NFS3_OK) { // get the absolute link target
0169                 QString linkPath = QString::fromLocal8Bit(readLinkRes.READLINK3res_u.resok.data);
0170                 linkPath = QFileInfo(QFileInfo(path).path(), linkPath).absoluteFilePath();
0171 
0172                 // As with the tests done in NFSProtocol::isValidLink(), the link
0173                 // target may not be valid on the NFS server (i.e. it may point
0174                 // outside of the exported directories).  Check for this before
0175                 // calling lookupHandle() on the target of the link, as otherwise
0176                 // an error will be set which is not relevant.
0177                 if (isValidPath(linkPath)) {
0178                     LOOKUP3res linkRes;
0179                     if (lookupHandle(linkPath, rpcStatus, linkRes)) {
0180                         // It's a link, so return the target file handle
0181                         // with the link source recorded in it.
0182                         NFSFileHandle linkFh = linkRes.LOOKUP3res_u.resok.object;
0183                         linkFh.setLinkSource(res.LOOKUP3res_u.resok.object);
0184                         qCDebug(LOG_KIO_NFS) << "Found link target" << linkPath;
0185                         return linkFh;
0186                     }
0187                 }
0188             }
0189 
0190             // If we have reached this point the file is a link,
0191             // but we failed to read the target.
0192             fh.setBadLink();
0193             qCDebug(LOG_KIO_NFS) << "Invalid link" << path;
0194         }
0195     }
0196 
0197     return fh;
0198 }
0199 
0200 /* Open connection connects to the mount daemon on the server side.
0201  In order to do this it needs authentication and calls auth_unix_create().
0202  Then it asks the mount daemon for the exported shares. Then it tries
0203  to mount all these shares. If this succeeded for at least one of them,
0204  a client for the nfs daemon is created.
0205  */
0206 void NFSProtocolV3::openConnection()
0207 {
0208     const QString host = currentHost();
0209     qCDebug(LOG_KIO_NFS) << "to" << host;
0210 
0211     // Destroy the old connection first
0212     closeConnection();
0213 
0214     KIO::Error connErr = NFSProtocol::openConnection(host, MOUNT_PROGRAM, MOUNT_V3, m_mountClient, m_mountSock);
0215     if (connErr != 0) {
0216         closeConnection();
0217         setError(connErr, host);
0218         return;
0219     }
0220 
0221     exports3 exportlist;
0222     memset(&exportlist, 0, sizeof(exportlist));
0223 
0224     int clnt_stat = clnt_call(m_mountClient,
0225                               MOUNTPROC3_EXPORT,
0226                               (xdrproc_t)xdr_void,
0227                               nullptr,
0228                               (xdrproc_t)xdr_exports3,
0229                               reinterpret_cast<caddr_t>(&exportlist),
0230                               clnt_timeout);
0231 
0232     if (!checkForError(clnt_stat, 0, host.toLatin1())) {
0233         closeConnection();
0234         return;
0235     }
0236 
0237     int exportsCount = 0;
0238     bool mountHint = false;
0239 
0240     mountres3 fhStatus;
0241     for (; exportlist != nullptr; exportlist = exportlist->ex_next, exportsCount++) {
0242         memset(&fhStatus, 0, sizeof(fhStatus));
0243         clnt_stat = clnt_call(m_mountClient,
0244                               MOUNTPROC3_MNT,
0245                               (xdrproc_t)xdr_dirpath3,
0246                               reinterpret_cast<caddr_t>(&exportlist->ex_dir),
0247                               (xdrproc_t)xdr_mountres3,
0248                               reinterpret_cast<caddr_t>(&fhStatus),
0249                               clnt_timeout);
0250 
0251         QString fname = QFileInfo(QDir::root(), exportlist->ex_dir).filePath();
0252         if (fhStatus.fhs_status == 0) { // mount succeeded
0253 
0254             // Check if the directory is already noted as exported,
0255             // if so there is no need to add it again.
0256             if (isExportedDir(fname))
0257                 continue;
0258 
0259             // Save the exported directory and its NFS file handle.
0260             addFileHandle(fname, static_cast<NFSFileHandle>(fhStatus.mountres3_u.mountinfo.fhandle));
0261             addExportedDir(fname);
0262         } else { // mount failed with error
0263             qCDebug(LOG_KIO_NFS) << "Cannot mount" << fname << "- status" << fhStatus.fhs_status;
0264 
0265             // Even if the mount failed, record the directory path as exported
0266             // so that it can be listed as a virtual directory.  However, do
0267             // not record its (invalid) file handle in the cache.  Trying to
0268             // access the directory in any way other than just listing it, or
0269             // accessing anything below it, will be detected in
0270             // NFSProtocol::getFileHandle() and fail with an appropriate
0271             // error.
0272             if (!isExportedDir(fname))
0273                 addExportedDir(fname);
0274 
0275             // Many modern NFS servers by default reject any access attempted to
0276             // them from a non-reserved source port (i.e. above 1024).  Since
0277             // this KIO slave runs as a normal user, it is not able to use the
0278             // reserved port numbers and hence the access will be rejected.  Show
0279             // a hint if this could possibly be the problem - only once, as the
0280             // server may have many exported directories.
0281             if (fhStatus.fhs_status == MNT3ERR_ACCES) {
0282                 if (!mountHint) {
0283                     qCDebug(LOG_KIO_NFS) << "Check that the NFS server is exporting the filesystem";
0284                     qCDebug(LOG_KIO_NFS) << "with appropriate access permissions.  Note that it must";
0285                     qCDebug(LOG_KIO_NFS) << "allow mount requests originating from an unprivileged";
0286                     qCDebug(LOG_KIO_NFS) << "source port (see exports(5), the 'insecure' option may";
0287                     qCDebug(LOG_KIO_NFS) << "be required).";
0288                     mountHint = true;
0289                 }
0290             }
0291         }
0292     }
0293 
0294     // If nothing can be mounted then there is no point trying to open the
0295     // NFS server connection here.  However, call openConnection() anyway
0296     // and pretend that we are connected so that listing virtual directories
0297     // will work.
0298     if ((connErr = NFSProtocol::openConnection(host, NFSPROG, NFSVERS, m_nfsClient, m_nfsSock)) != 0) {
0299         closeConnection();
0300         setError(connErr, host);
0301     }
0302 
0303     slave()->connected();
0304 
0305     qCDebug(LOG_KIO_NFS) << "openConnection succeeded";
0306 }
0307 
0308 void NFSProtocolV3::listDir(const QUrl &url)
0309 {
0310     qCDebug(LOG_KIO_NFS) << url;
0311 
0312     const QString path = listDirInternal(url); // check path, list virtual dir
0313     if (path.isEmpty())
0314         return; // no more to do
0315 
0316     const NFSFileHandle fh = getFileHandle(path);
0317     // There doesn't seem to be an invalid link error code in KIO,
0318     // so this will have to do.
0319     if (fh.isInvalid() || fh.isBadLink()) {
0320         setError(KIO::ERR_DOES_NOT_EXIST, path);
0321         return;
0322     }
0323 
0324     // Get the preferred read dir size from the server
0325     if (m_readDirSize == 0) {
0326         initPreferredSizes(fh);
0327     }
0328 
0329     if (!slave()->usedirplus3()) // want compatibility mode listing
0330     {
0331         listDirCompat(url);
0332         return;
0333     }
0334 
0335     READDIRPLUS3args listargs;
0336     memset(&listargs, 0, sizeof(listargs));
0337     listargs.dircount = m_readDirSize;
0338     listargs.maxcount = sizeof(entryplus3) * m_readDirSize; // Not really sure what this should be set to.
0339 
0340     fh.toFH(listargs.dir);
0341 
0342     READDIRPLUS3res listres;
0343     memset(&listres, 0, sizeof(listres));
0344 
0345     entryplus3 *lastEntry = nullptr;
0346     do {
0347         memset(&listres, 0, sizeof(listres));
0348 
0349         // In case that we didn't get all entries we need to set the cookie to the last one we actually received.
0350         if (lastEntry != nullptr) {
0351             listargs.cookie = lastEntry->cookie;
0352         }
0353 
0354         int clnt_stat = clnt_call(m_nfsClient,
0355                                   NFSPROC3_READDIRPLUS,
0356                                   (xdrproc_t)xdr_READDIRPLUS3args,
0357                                   reinterpret_cast<caddr_t>(&listargs),
0358                                   (xdrproc_t)xdr_READDIRPLUS3res,
0359                                   reinterpret_cast<caddr_t>(&listres),
0360                                   clnt_timeout);
0361 
0362         // Not a supported call? Try the old READDIR method.
0363         if (listres.status == NFS3ERR_NOTSUPP) {
0364             qCDebug(LOG_KIO_NFS) << "NFS server does not support READDIRPLUS3, listing in compatibility mode";
0365             listDirCompat(url);
0366             return;
0367         }
0368 
0369         // Do we have an error? There's not much more we can do but to abort at this point.
0370         if (!checkForError(clnt_stat, listres.status, path)) {
0371             return;
0372         }
0373 
0374         for (entryplus3 *dirEntry = listres.READDIRPLUS3res_u.resok.reply.entries; dirEntry != nullptr; dirEntry = dirEntry->nextentry) {
0375             if (dirEntry->name == QString("..")) {
0376                 continue;
0377             }
0378 
0379             KIO::UDSEntry entry;
0380             entry.fastInsert(KIO::UDSEntry::UDS_NAME, dirEntry->name);
0381 
0382             if (dirEntry->name == QString(".")) {
0383                 createVirtualDirEntry(entry);
0384                 completeUDSEntry(entry, dirEntry->name_attributes.post_op_attr_u.attributes);
0385                 slave()->listEntry(entry);
0386                 continue;
0387             }
0388 
0389             const QString &filePath = QFileInfo(QDir(path), dirEntry->name).filePath();
0390 
0391             // Is it a symlink ?
0392             if (dirEntry->name_attributes.post_op_attr_u.attributes.type == NF3LNK) {
0393                 int rpcStatus;
0394                 READLINK3res readLinkRes;
0395                 char nameBuf[NFS3_MAXPATHLEN];
0396 
0397                 if (symLinkTarget(filePath, rpcStatus, readLinkRes, nameBuf)) {
0398                     QString linkDest = QString::fromLocal8Bit(readLinkRes.READLINK3res_u.resok.data);
0399                     entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, linkDest);
0400 
0401                     bool badLink = true;
0402                     NFSFileHandle linkFH;
0403                     if (isValidLink(path, linkDest)) {
0404                         const QString linkPath = QFileInfo(path, linkDest).absoluteFilePath();
0405                         // get the absolute link target
0406                         int rpcStatus;
0407                         LOOKUP3res lookupRes;
0408                         if (lookupHandle(linkPath, rpcStatus, lookupRes)) {
0409                             GETATTR3res attrAndStat;
0410                             if (getAttr(linkPath, rpcStatus, attrAndStat)) {
0411                                 badLink = false;
0412 
0413                                 linkFH = lookupRes.LOOKUP3res_u.resok.object;
0414                                 linkFH.setLinkSource(dirEntry->name_handle.post_op_fh3_u.handle);
0415 
0416                                 completeUDSEntry(entry, attrAndStat.GETATTR3res_u.resok.obj_attributes);
0417                             }
0418                         }
0419                     }
0420 
0421                     if (badLink) {
0422                         linkFH = dirEntry->name_handle.post_op_fh3_u.handle;
0423                         linkFH.setBadLink();
0424 
0425                         completeBadLinkUDSEntry(entry, dirEntry->name_attributes.post_op_attr_u.attributes);
0426                     }
0427 
0428                     addFileHandle(filePath, linkFH);
0429                 } else {
0430                     entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, i18n("Unknown target"));
0431                     completeBadLinkUDSEntry(entry, dirEntry->name_attributes.post_op_attr_u.attributes);
0432                 }
0433             } else {
0434                 NFSFileHandle entryFH = dirEntry->name_handle.post_op_fh3_u.handle;
0435 
0436                 // Some NFS servers seem to return names from the READDIRPLUS3
0437                 // call without a valid file handle or attributes.  This has
0438                 // been observed with a WD MyCloud NAS running Debian wheezy.
0439                 // The READDIR3 call does not have this problem, but by the time
0440                 // it can be detected it is too late to repeat the listing in
0441                 // compatibility mode since some listEntry()'s may already have
0442                 // been done.
0443                 //
0444                 // Accept the name, and assume null values for the attributes.
0445                 // Do not save the invalid file handle in the cache;  if that
0446                 // subdirectory is accessed later on it will be listed again
0447                 // which seems to work properly.
0448                 if (entryFH.isInvalid()) {
0449                     qCDebug(LOG_KIO_NFS) << "NFS server returned invalid handle for" << (path + "/" + dirEntry->name);
0450                     completeInvalidUDSEntry(entry);
0451                 } else {
0452                     addFileHandle(filePath, entryFH);
0453                     completeUDSEntry(entry, dirEntry->name_attributes.post_op_attr_u.attributes);
0454                 }
0455             }
0456 
0457             slave()->listEntry(entry);
0458 
0459             lastEntry = dirEntry;
0460         }
0461     } while (listres.READDIRPLUS3res_u.resok.reply.entries != nullptr && !listres.READDIRPLUS3res_u.resok.reply.eof);
0462 }
0463 
0464 void NFSProtocolV3::listDirCompat(const QUrl &url)
0465 {
0466     const QString path(url.path());
0467 
0468     if (isExportedDir(path)) {
0469         // We should never get here, if the path is an exported dir
0470         // it will have been checked in listDir() and there will have been
0471         // no attempt to access the NFS server.
0472         qCWarning(LOG_KIO_NFS) << "Called for an exported dir";
0473         setError(KIO::ERR_INTERNAL, path);
0474         return;
0475     }
0476 
0477     const NFSFileHandle fh = getFileHandle(path);
0478     if (fh.isInvalid() || fh.isBadLink()) {
0479         setError(KIO::ERR_DOES_NOT_EXIST, path);
0480         return;
0481     }
0482 
0483     QStringList filesToList;
0484 
0485     READDIR3args listargs;
0486     memset(&listargs, 0, sizeof(listargs));
0487     listargs.count = m_readDirSize;
0488     fh.toFH(listargs.dir);
0489 
0490     READDIR3res listres;
0491     entry3 *lastEntry = nullptr;
0492     do {
0493         memset(&listres, 0, sizeof(listres));
0494 
0495         // In case that we didn't get all entries we need to set the cookie to the last one we actually received
0496         if (lastEntry != nullptr) {
0497             listargs.cookie = lastEntry->cookie;
0498         }
0499 
0500         int clnt_stat = clnt_call(m_nfsClient,
0501                                   NFSPROC3_READDIR,
0502                                   (xdrproc_t)xdr_READDIR3args,
0503                                   reinterpret_cast<caddr_t>(&listargs),
0504                                   (xdrproc_t)xdr_READDIR3res,
0505                                   reinterpret_cast<caddr_t>(&listres),
0506                                   clnt_timeout);
0507 
0508         if (!checkForError(clnt_stat, listres.status, path)) {
0509             return;
0510         }
0511 
0512         for (entry3 *dirEntry = listres.READDIR3res_u.resok.reply.entries; dirEntry != nullptr; dirEntry = dirEntry->nextentry) {
0513             if (dirEntry->name != QString("..")) {
0514                 filesToList.append(QFile::decodeName(dirEntry->name));
0515             }
0516 
0517             lastEntry = dirEntry;
0518         }
0519     } while (!listres.READDIR3res_u.resok.reply.eof);
0520 
0521     // Loop through all files, getting attributes and link path.
0522     for (QStringList::const_iterator it = filesToList.constBegin(); it != filesToList.constEnd(); ++it) {
0523         QString filePath = QFileInfo(QDir(path), (*it)).filePath();
0524 
0525         int rpcStatus;
0526         LOOKUP3res dirres;
0527         if (!lookupHandle(filePath, rpcStatus, dirres)) {
0528             qCDebug(LOG_KIO_NFS) << "Failed to lookup" << filePath << ", rpc:" << rpcStatus << ", nfs:" << dirres.status;
0529             // Try the next file instead of aborting
0530             continue;
0531         }
0532 
0533         KIO::UDSEntry entry;
0534         entry.fastInsert(KIO::UDSEntry::UDS_NAME, (*it));
0535 
0536         // Is it a symlink?
0537         if (dirres.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.type == NF3LNK) {
0538             int rpcStatus;
0539             READLINK3res readLinkRes;
0540             char nameBuf[NFS3_MAXPATHLEN];
0541             if (symLinkTarget(filePath, rpcStatus, readLinkRes, nameBuf)) {
0542                 const QString linkDest = QString::fromLocal8Bit(readLinkRes.READLINK3res_u.resok.data);
0543                 entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, linkDest);
0544 
0545                 bool badLink = true;
0546                 NFSFileHandle linkFH;
0547                 if (isValidLink(path, linkDest)) {
0548                     const QString linkPath = QFileInfo(path, linkDest).absoluteFilePath();
0549                     // get the absolute link target
0550                     int rpcStatus;
0551                     LOOKUP3res lookupRes;
0552                     if (lookupHandle(linkPath, rpcStatus, lookupRes)) {
0553                         GETATTR3res attrAndStat;
0554                         if (getAttr(linkPath, rpcStatus, attrAndStat)) {
0555                             badLink = false;
0556 
0557                             linkFH = lookupRes.LOOKUP3res_u.resok.object;
0558                             linkFH.setLinkSource(dirres.LOOKUP3res_u.resok.object);
0559 
0560                             completeUDSEntry(entry, attrAndStat.GETATTR3res_u.resok.obj_attributes);
0561                         }
0562                     }
0563                 }
0564 
0565                 if (badLink) {
0566                     linkFH = dirres.LOOKUP3res_u.resok.object;
0567                     linkFH.setBadLink();
0568 
0569                     completeBadLinkUDSEntry(entry, dirres.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes);
0570                 }
0571 
0572                 addFileHandle(filePath, linkFH);
0573             } else {
0574                 entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, i18n("Unknown target"));
0575                 completeBadLinkUDSEntry(entry, dirres.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes);
0576             }
0577         } else {
0578             addFileHandle(filePath, dirres.LOOKUP3res_u.resok.object);
0579             completeUDSEntry(entry, dirres.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes);
0580         }
0581 
0582         slave()->listEntry(entry);
0583     }
0584 }
0585 
0586 void NFSProtocolV3::stat(const QUrl &url)
0587 {
0588     qCDebug(LOG_KIO_NFS) << url;
0589 
0590     const QString path = statInternal(url); // check path, process virtual dir
0591     if (path.isEmpty())
0592         return; // no more to do
0593 
0594     const NFSFileHandle fh = getFileHandle(path);
0595     if (fh.isInvalid()) {
0596         qCDebug(LOG_KIO_NFS) << "File handle is invalid";
0597         setError(KIO::ERR_DOES_NOT_EXIST, path);
0598         return;
0599     }
0600 
0601     int rpcStatus;
0602     GETATTR3res attrAndStat;
0603     if (!getAttr(path, rpcStatus, attrAndStat)) {
0604         checkForError(rpcStatus, attrAndStat.status, path);
0605         return;
0606     }
0607 
0608     const QFileInfo fileInfo(path);
0609 
0610     KIO::UDSEntry entry;
0611     entry.fastInsert(KIO::UDSEntry::UDS_NAME, fileInfo.fileName());
0612 
0613     // Is it a symlink?
0614     if (attrAndStat.GETATTR3res_u.resok.obj_attributes.type == NF3LNK) {
0615         qCDebug(LOG_KIO_NFS) << "It's a symlink";
0616 
0617         // get the link dest
0618         QString linkDest;
0619 
0620         int rpcStatus;
0621         READLINK3res readLinkRes;
0622         char nameBuf[NFS3_MAXPATHLEN];
0623         if (symLinkTarget(path, rpcStatus, readLinkRes, nameBuf)) {
0624             linkDest = QString::fromLocal8Bit(readLinkRes.READLINK3res_u.resok.data);
0625         } else {
0626             entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, linkDest);
0627             completeBadLinkUDSEntry(entry, attrAndStat.GETATTR3res_u.resok.obj_attributes);
0628 
0629             slave()->statEntry(entry);
0630             return; // have result, no more to do
0631         }
0632 
0633         qCDebug(LOG_KIO_NFS) << "link dest is" << linkDest;
0634 
0635         entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, linkDest);
0636 
0637         if (!isValidLink(fileInfo.path(), linkDest)) {
0638             completeBadLinkUDSEntry(entry, attrAndStat.GETATTR3res_u.resok.obj_attributes);
0639         } else {
0640             const QString linkPath = QFileInfo(fileInfo.path(), linkDest).absoluteFilePath();
0641             // get the absolute link target
0642             int rpcStatus;
0643             GETATTR3res attrAndStat;
0644             if (!getAttr(linkPath, rpcStatus, attrAndStat)) {
0645                 checkForError(rpcStatus, attrAndStat.status, linkPath);
0646                 return;
0647             }
0648 
0649             completeUDSEntry(entry, attrAndStat.GETATTR3res_u.resok.obj_attributes);
0650         }
0651     } else {
0652         completeUDSEntry(entry, attrAndStat.GETATTR3res_u.resok.obj_attributes);
0653     }
0654 
0655     slave()->statEntry(entry);
0656 }
0657 
0658 void NFSProtocolV3::mkdir(const QUrl &url, int permissions)
0659 {
0660     qCDebug(LOG_KIO_NFS) << url;
0661 
0662     const QString path(url.path());
0663     const QFileInfo fileInfo(path);
0664     if (isExportedDir(fileInfo.path())) {
0665         setError(KIO::ERR_ACCESS_DENIED, path);
0666         return;
0667     }
0668 
0669     const NFSFileHandle fh = getFileHandle(fileInfo.path());
0670     if (fh.isInvalid() || fh.isBadLink()) {
0671         setError(KIO::ERR_DOES_NOT_EXIST, path);
0672         return;
0673     }
0674 
0675     MKDIR3args createArgs;
0676     memset(&createArgs, 0, sizeof(createArgs));
0677     fh.toFH(createArgs.where.dir);
0678 
0679     QByteArray tmpName = QFile::encodeName(fileInfo.fileName());
0680     createArgs.where.name = tmpName.data();
0681 
0682     createArgs.attributes.mode.set_it = true;
0683     if (permissions == -1) {
0684         createArgs.attributes.mode.set_mode3_u.mode = 0755;
0685     } else {
0686         createArgs.attributes.mode.set_mode3_u.mode = permissions;
0687     }
0688 
0689     MKDIR3res dirres;
0690     memset(&dirres, 0, sizeof(dirres));
0691 
0692     int clnt_stat = clnt_call(m_nfsClient,
0693                               NFSPROC3_MKDIR,
0694                               (xdrproc_t)xdr_MKDIR3args,
0695                               reinterpret_cast<caddr_t>(&createArgs),
0696                               (xdrproc_t)xdr_MKDIR3res,
0697                               reinterpret_cast<caddr_t>(&dirres),
0698                               clnt_timeout);
0699     checkForError(clnt_stat, dirres.status, path);
0700 }
0701 
0702 void NFSProtocolV3::del(const QUrl &url, bool /* isfile*/)
0703 {
0704     qCDebug(LOG_KIO_NFS) << url;
0705 
0706     const QString path(url.path());
0707     if (isExportedDir(QFileInfo(path).path())) {
0708         setError(KIO::ERR_ACCESS_DENIED, path);
0709         return;
0710     }
0711 
0712     int rpcStatus;
0713     REMOVE3res res;
0714     remove(path, rpcStatus, res);
0715     checkForError(rpcStatus, res.status, path);
0716 }
0717 
0718 void NFSProtocolV3::chmod(const QUrl &url, int permissions)
0719 {
0720     qCDebug(LOG_KIO_NFS) << url;
0721 
0722     const QString path(url.path());
0723     if (isExportedDir(path)) {
0724         setError(KIO::ERR_ACCESS_DENIED, path);
0725         return;
0726     }
0727 
0728     sattr3 attributes;
0729     memset(&attributes, 0, sizeof(attributes));
0730     attributes.mode.set_it = true;
0731     attributes.mode.set_mode3_u.mode = permissions;
0732 
0733     int rpcStatus;
0734     SETATTR3res setAttrRes;
0735     setAttr(path, attributes, rpcStatus, setAttrRes);
0736     checkForError(rpcStatus, setAttrRes.status, path);
0737 }
0738 
0739 void NFSProtocolV3::get(const QUrl &url)
0740 {
0741     qCDebug(LOG_KIO_NFS) << url;
0742 
0743     const QString path(url.path());
0744     const NFSFileHandle fh = getFileHandle(path);
0745     if (fh.isInvalid() || fh.isBadLink()) {
0746         setError(KIO::ERR_DOES_NOT_EXIST, path);
0747         return;
0748     }
0749 
0750     // Get the optimal read buffer size.
0751     if (m_readBufferSize == 0) {
0752         initPreferredSizes(fh);
0753     }
0754 
0755     READ3args readArgs;
0756     memset(&readArgs, 0, sizeof(readArgs));
0757     fh.toFH(readArgs.file);
0758     readArgs.offset = 0;
0759     readArgs.count = m_readBufferSize;
0760 
0761     READ3res readRes;
0762     memset(&readRes, 0, sizeof(readRes));
0763     readRes.READ3res_u.resok.data.data_len = m_readBufferSize;
0764     readRes.READ3res_u.resok.data.data_val = new char[m_readBufferSize];
0765 
0766     // Most likely indicates out of memory
0767     if (!readRes.READ3res_u.resok.data.data_val) {
0768         setError(KIO::ERR_OUT_OF_MEMORY, path);
0769         return;
0770     }
0771 
0772     bool validRead = false;
0773     int read = 0;
0774     QByteArray readBuffer;
0775     do {
0776         int clnt_stat = clnt_call(m_nfsClient,
0777                                   NFSPROC3_READ,
0778                                   (xdrproc_t)xdr_READ3args,
0779                                   reinterpret_cast<caddr_t>(&readArgs),
0780                                   (xdrproc_t)xdr_READ3res,
0781                                   reinterpret_cast<caddr_t>(&readRes),
0782                                   clnt_timeout);
0783 
0784         // We are trying to read a directory, fail quietly
0785         if (readRes.status == NFS3ERR_ISDIR) {
0786             break;
0787         }
0788 
0789         if (!checkForError(clnt_stat, readRes.status, path)) {
0790             break;
0791         }
0792 
0793         read = readRes.READ3res_u.resok.count;
0794         readBuffer.setRawData(readRes.READ3res_u.resok.data.data_val, read);
0795 
0796         if (readArgs.offset == 0) {
0797             const QMimeDatabase db;
0798             const QMimeType type = db.mimeTypeForFileNameAndData(url.fileName(), readBuffer);
0799             slave()->mimeType(type.name());
0800 
0801             slave()->totalSize(readRes.READ3res_u.resok.file_attributes.post_op_attr_u.attributes.size);
0802         }
0803 
0804         readArgs.offset += read;
0805         if (read > 0) {
0806             validRead = true;
0807 
0808             slave()->data(readBuffer);
0809             slave()->processedSize(readArgs.offset);
0810         }
0811 
0812     } while (read > 0);
0813 
0814     if (readRes.READ3res_u.resok.data.data_val != nullptr) {
0815         delete[] readRes.READ3res_u.resok.data.data_val;
0816     }
0817 
0818     // Only send the read data to the slave if we have actually sent some.
0819     if (validRead) {
0820         slave()->data(QByteArray());
0821         slave()->processedSize(readArgs.offset);
0822     }
0823 }
0824 
0825 void NFSProtocolV3::put(const QUrl &url, int _mode, KIO::JobFlags flags)
0826 {
0827     qCDebug(LOG_KIO_NFS) << url;
0828 
0829     const QString destPath(url.path());
0830     if (isExportedDir(QFileInfo(destPath).path())) {
0831         setError(KIO::ERR_WRITE_ACCESS_DENIED, destPath);
0832         return;
0833     }
0834 
0835     NFSFileHandle destFH = getFileHandle(destPath);
0836     if (destFH.isBadLink()) {
0837         setError(KIO::ERR_DOES_NOT_EXIST, destPath);
0838         return;
0839     }
0840 
0841     // the file exists and we don't want to overwrite
0842     if (!destFH.isInvalid() && ((flags & KIO::Overwrite) == 0)) {
0843         setError(KIO::ERR_FILE_ALREADY_EXIST, destPath);
0844         return;
0845     }
0846 
0847     // Get the optimal write buffer size
0848     if (m_writeBufferSize == 0) {
0849         initPreferredSizes(destFH);
0850     }
0851 
0852     destFH = create(destPath, _mode);
0853     if (destFH.isInvalid()) {
0854         return;
0855     }
0856     // We created the file successfully.
0857 
0858     int result;
0859 
0860     WRITE3args writeArgs;
0861     memset(&writeArgs, 0, sizeof(writeArgs));
0862 
0863     destFH.toFH(writeArgs.file);
0864     writeArgs.offset = 0;
0865     writeArgs.stable = FILE_SYNC;
0866 
0867     WRITE3res writeRes;
0868     memset(&writeRes, 0, sizeof(writeRes));
0869 
0870     // Loop until we get 0 (end of data).
0871     int bytesWritten = 0;
0872     bool error = false;
0873     do {
0874         QByteArray buffer;
0875         slave()->dataReq();
0876         result = slave()->readData(buffer);
0877 
0878         if (result > 0) {
0879             char *data = buffer.data();
0880             uint32 bytesToWrite = buffer.size();
0881             int writeNow(0);
0882 
0883             do {
0884                 if (bytesToWrite > m_writeBufferSize) {
0885                     writeNow = m_writeBufferSize;
0886                 } else {
0887                     writeNow = bytesToWrite;
0888                 }
0889 
0890                 writeArgs.data.data_val = data;
0891                 writeArgs.data.data_len = writeNow;
0892                 writeArgs.count = writeNow;
0893 
0894                 int clnt_stat = clnt_call(m_nfsClient,
0895                                           NFSPROC3_WRITE,
0896                                           (xdrproc_t)xdr_WRITE3args,
0897                                           reinterpret_cast<caddr_t>(&writeArgs),
0898                                           (xdrproc_t)xdr_WRITE3res,
0899                                           reinterpret_cast<caddr_t>(&writeRes),
0900                                           clnt_timeout);
0901 
0902                 if (!checkForError(clnt_stat, writeRes.status, destPath)) {
0903                     error = true;
0904                     break;
0905                 }
0906 
0907                 writeNow = writeRes.WRITE3res_u.resok.count;
0908 
0909                 bytesWritten += writeNow;
0910                 writeArgs.offset = bytesWritten;
0911 
0912                 data = data + writeNow;
0913                 bytesToWrite -= writeNow;
0914             } while (bytesToWrite > 0);
0915         }
0916 
0917         if (error) {
0918             break;
0919         }
0920     } while (result > 0);
0921 }
0922 
0923 void NFSProtocolV3::rename(const QUrl &src, const QUrl &dest, KIO::JobFlags _flags)
0924 {
0925     qCDebug(LOG_KIO_NFS) << src << dest;
0926 
0927     const QString srcPath(src.path());
0928     if (isExportedDir(srcPath)) {
0929         setError(KIO::ERR_CANNOT_RENAME, srcPath);
0930         return;
0931     }
0932 
0933     const QString destPath(dest.path());
0934     if (isExportedDir(destPath)) {
0935         setError(KIO::ERR_ACCESS_DENIED, destPath);
0936         return;
0937     }
0938 
0939     if (!getFileHandle(destPath).isInvalid() && (_flags & KIO::Overwrite) == 0) {
0940         setError(KIO::ERR_FILE_ALREADY_EXIST, destPath);
0941         return;
0942     }
0943 
0944     int rpcStatus;
0945     RENAME3res res;
0946     rename(srcPath, destPath, rpcStatus, res);
0947     checkForError(rpcStatus, res.status, destPath);
0948 }
0949 
0950 void NFSProtocolV3::copySame(const QUrl &src, const QUrl &dest, int _mode, KIO::JobFlags _flags)
0951 {
0952     qCDebug(LOG_KIO_NFS) << src << "to" << dest;
0953 
0954     const QString srcPath(src.path());
0955     if (isExportedDir(QFileInfo(srcPath).path())) {
0956         setError(KIO::ERR_ACCESS_DENIED, srcPath);
0957         return;
0958     }
0959 
0960     const NFSFileHandle srcFH = getFileHandle(srcPath);
0961     if (srcFH.isInvalid()) {
0962         setError(KIO::ERR_DOES_NOT_EXIST, srcPath);
0963         return;
0964     }
0965 
0966     const QString destPath(dest.path());
0967     if (isExportedDir(QFileInfo(destPath).path())) {
0968         setError(KIO::ERR_ACCESS_DENIED, destPath);
0969         return;
0970     }
0971 
0972     // The file exists and we don't want to overwrite
0973     if (!getFileHandle(destPath).isInvalid() && (_flags & KIO::Overwrite) == 0) {
0974         setError(KIO::ERR_FILE_ALREADY_EXIST, destPath);
0975         return;
0976     }
0977 
0978     // Is it a link? No need to copy the data then, just copy the link destination.
0979     if (srcFH.isLink()) {
0980         // get the link dest
0981         int rpcStatus;
0982         READLINK3res readLinkRes;
0983         char nameBuf[NFS3_MAXPATHLEN];
0984         if (!symLinkTarget(srcPath, rpcStatus, readLinkRes, nameBuf)) {
0985             setError(KIO::ERR_DOES_NOT_EXIST, srcPath);
0986             return;
0987         }
0988 
0989         const QString linkPath = QString::fromLocal8Bit(readLinkRes.READLINK3res_u.resok.data);
0990 
0991         SYMLINK3res linkRes;
0992         symLink(linkPath, destPath, rpcStatus, linkRes);
0993         checkForError(rpcStatus, linkRes.status, linkPath);
0994         return; // done, no more to do
0995     }
0996 
0997     unsigned long resumeOffset = 0;
0998     bool bResume = false;
0999     const QString partFilePath = destPath + QLatin1String(".part");
1000     const NFSFileHandle partFH = getFileHandle(partFilePath);
1001     const bool bPartExists = !partFH.isInvalid();
1002     const bool bMarkPartial = slave()->configValue(QStringLiteral("MarkPartial"), true);
1003 
1004     if (bPartExists) {
1005         int rpcStatus;
1006         LOOKUP3res partRes;
1007         if (lookupHandle(partFilePath, rpcStatus, partRes)) {
1008             if (bMarkPartial && partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.size > 0) {
1009                 if (partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.type == NF3DIR) {
1010                     setError(KIO::ERR_IS_DIRECTORY, partFilePath);
1011                     return;
1012                 }
1013 
1014                 bResume = slave()->canResume(partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.size);
1015                 if (bResume) {
1016                     resumeOffset = partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.size;
1017                 }
1018             }
1019         }
1020 
1021         // Remove the part file if we are not resuming
1022         if (!bResume) {
1023             if (!remove(partFilePath)) {
1024                 qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring...";
1025             }
1026         }
1027     }
1028 
1029     // Create the file if we are not resuming a parted transfer,
1030     // or if we are not using part files(bResume is false in that case)
1031     NFSFileHandle destFH;
1032     if (!bResume) {
1033         QString createPath;
1034         if (bMarkPartial) {
1035             createPath = partFilePath;
1036         } else {
1037             createPath = destPath;
1038         }
1039 
1040         destFH = create(createPath, _mode);
1041         if (destFH.isInvalid()) {
1042             return;
1043         }
1044     } else {
1045         // Since we are resuming it's implied that we are using a part file,
1046         // which should exist at this point.
1047         destFH = getFileHandle(partFilePath);
1048 
1049         qCDebug(LOG_KIO_NFS) << "Resuming old transfer";
1050     }
1051 
1052     // Check what buffer size we should use, always use the smallest one.
1053     const int bufferSize = (m_readBufferSize < m_writeBufferSize) ? m_readBufferSize : m_writeBufferSize;
1054 
1055     WRITE3args writeArgs;
1056     memset(&writeArgs, 0, sizeof(writeArgs));
1057 
1058     destFH.toFH(writeArgs.file);
1059     writeArgs.offset = 0;
1060     writeArgs.data.data_val = new char[bufferSize];
1061     writeArgs.stable = FILE_SYNC;
1062 
1063     READ3args readArgs;
1064     memset(&readArgs, 0, sizeof(readArgs));
1065 
1066     srcFH.toFH(readArgs.file);
1067     readArgs.offset = 0;
1068     readArgs.count = bufferSize;
1069 
1070     if (bResume) {
1071         writeArgs.offset = resumeOffset;
1072         readArgs.offset = resumeOffset;
1073     }
1074 
1075     READ3res readRes;
1076     readRes.READ3res_u.resok.data.data_val = writeArgs.data.data_val;
1077 
1078     WRITE3res writeRes;
1079     memset(&writeRes, 0, sizeof(WRITE3res));
1080 
1081     bool error = false;
1082     int bytesRead = 0;
1083     do {
1084         int clnt_stat = clnt_call(m_nfsClient,
1085                                   NFSPROC3_READ,
1086                                   (xdrproc_t)xdr_READ3args,
1087                                   reinterpret_cast<caddr_t>(&readArgs),
1088                                   (xdrproc_t)xdr_READ3res,
1089                                   reinterpret_cast<caddr_t>(&readRes),
1090                                   clnt_timeout);
1091 
1092         if (!checkForError(clnt_stat, readRes.status, srcPath)) {
1093             error = true;
1094             break;
1095         }
1096 
1097         bytesRead = readRes.READ3res_u.resok.data.data_len;
1098 
1099         // We should only send out the total size and mimetype at the start of the transfer
1100         if (readArgs.offset == 0 || (bResume && writeArgs.offset == resumeOffset)) {
1101             QMimeDatabase db;
1102             QMimeType type = db.mimeTypeForFileNameAndData(src.fileName(), QByteArray::fromRawData(writeArgs.data.data_val, bytesRead));
1103             slave()->mimeType(type.name());
1104 
1105             slave()->totalSize(readRes.READ3res_u.resok.file_attributes.post_op_attr_u.attributes.size);
1106         }
1107 
1108         if (bytesRead > 0) {
1109             readArgs.offset += bytesRead;
1110 
1111             writeArgs.count = bytesRead;
1112             writeArgs.data.data_len = bytesRead;
1113 
1114             clnt_stat = clnt_call(m_nfsClient,
1115                                   NFSPROC3_WRITE,
1116                                   (xdrproc_t)xdr_WRITE3args,
1117                                   reinterpret_cast<caddr_t>(&writeArgs),
1118                                   (xdrproc_t)xdr_WRITE3res,
1119                                   reinterpret_cast<caddr_t>(&writeRes),
1120                                   clnt_timeout);
1121 
1122             if (!checkForError(clnt_stat, writeRes.status, destPath)) {
1123                 error = true;
1124                 break;
1125             }
1126 
1127             writeArgs.offset += bytesRead;
1128 
1129             slave()->processedSize(readArgs.offset);
1130         }
1131     } while (bytesRead > 0);
1132 
1133     delete[] writeArgs.data.data_val;
1134 
1135     if (error) {
1136         if (bMarkPartial) {
1137             // Remove the part file if it's smaller than the minimum keep size.
1138             const unsigned int size = slave()->configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE);
1139             if (writeArgs.offset < size) {
1140                 if (!remove(partFilePath)) {
1141                     qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring...";
1142                 }
1143             }
1144         }
1145     } else {
1146         // Rename partial file to its original name.
1147         if (bMarkPartial) {
1148             // Remove the destination file(if it exists)
1149             if (!getFileHandle(destPath).isInvalid() && !remove(destPath)) {
1150                 qCDebug(LOG_KIO_NFS) << "Could not remove destination file" << destPath << ", ignoring...";
1151             }
1152 
1153             if (!rename(partFilePath, destPath)) {
1154                 qCDebug(LOG_KIO_NFS) << "failed to rename" << partFilePath << "to" << destPath;
1155                 setError(KIO::ERR_CANNOT_RENAME_PARTIAL, partFilePath);
1156                 return;
1157             }
1158         }
1159 
1160         // Restore modification time
1161         int rpcStatus;
1162         GETATTR3res attrRes;
1163         if (getAttr(srcPath, rpcStatus, attrRes)) {
1164             sattr3 attributes;
1165             memset(&attributes, 0, sizeof(attributes));
1166             attributes.mtime.set_it = SET_TO_CLIENT_TIME;
1167             attributes.mtime.set_mtime_u.mtime.seconds = attrRes.GETATTR3res_u.resok.obj_attributes.mtime.seconds;
1168             attributes.mtime.set_mtime_u.mtime.nseconds = attrRes.GETATTR3res_u.resok.obj_attributes.mtime.nseconds;
1169 
1170             SETATTR3res attrSetRes;
1171             if (!setAttr(destPath, attributes, rpcStatus, attrSetRes)) {
1172                 qCDebug(LOG_KIO_NFS) << "Failed to restore mtime, ignoring..." << rpcStatus << attrSetRes.status;
1173             }
1174         }
1175 
1176         qCDebug(LOG_KIO_NFS) << "Copied" << writeArgs.offset << "bytes of data";
1177 
1178         slave()->processedSize(readArgs.offset);
1179     }
1180 }
1181 
1182 void NFSProtocolV3::copyFrom(const QUrl &src, const QUrl &dest, int _mode, KIO::JobFlags _flags)
1183 {
1184     qCDebug(LOG_KIO_NFS) << src << "to" << dest;
1185 
1186     const QString srcPath(src.path());
1187     const NFSFileHandle srcFH = getFileHandle(srcPath);
1188     if (srcFH.isInvalid()) {
1189         setError(KIO::ERR_DOES_NOT_EXIST, srcPath);
1190         return;
1191     }
1192 
1193     const QString destPath(dest.path());
1194     // The file exists and we don't want to overwrite.
1195     if (QFile::exists(destPath) && (_flags & KIO::Overwrite) == 0) {
1196         setError(KIO::ERR_FILE_ALREADY_EXIST, destPath);
1197         return;
1198     }
1199 
1200     // Is it a link? No need to copy the data then, just copy the link destination.
1201     if (srcFH.isLink()) {
1202         qCDebug(LOG_KIO_NFS) << "Is a link";
1203 
1204         // get the link dest
1205         int rpcStatus;
1206         READLINK3res readLinkRes;
1207         char nameBuf[NFS3_MAXPATHLEN];
1208         if (!symLinkTarget(srcPath, rpcStatus, readLinkRes, nameBuf)) {
1209             setError(KIO::ERR_DOES_NOT_EXIST, srcPath);
1210             return;
1211         }
1212 
1213         QFile::link(QString::fromLocal8Bit(readLinkRes.READLINK3res_u.resok.data), destPath);
1214         return; // done, no more to do
1215     }
1216 
1217     if (m_readBufferSize == 0) {
1218         initPreferredSizes(srcFH);
1219     }
1220 
1221     unsigned int resumeOffset = 0;
1222     bool bResume = false;
1223     const QFileInfo partInfo(destPath + QLatin1String(".part"));
1224     const bool bPartExists = partInfo.exists();
1225     const bool bMarkPartial = slave()->configValue(QStringLiteral("MarkPartial"), true);
1226 
1227     if (bMarkPartial && bPartExists && partInfo.size() > 0) {
1228         if (partInfo.isDir()) {
1229             setError(KIO::ERR_IS_DIRECTORY, partInfo.absoluteFilePath());
1230             return;
1231         }
1232 
1233         bResume = slave()->canResume(partInfo.size());
1234         resumeOffset = partInfo.size();
1235     }
1236 
1237     if (bPartExists && !bResume) {
1238         QFile::remove(partInfo.absoluteFilePath());
1239     }
1240 
1241     QFile::OpenMode openMode;
1242     QString outFileName;
1243     if (bResume) {
1244         outFileName = partInfo.absoluteFilePath();
1245         openMode = QFile::WriteOnly | QFile::Append;
1246     } else {
1247         outFileName = (bMarkPartial ? partInfo.absoluteFilePath() : destPath);
1248         openMode = QFile::WriteOnly | QFile::Truncate;
1249     }
1250 
1251     QFile destFile(outFileName);
1252     if (!bResume) {
1253         QFile::Permissions perms;
1254         if (_mode == -1) {
1255             perms = QFile::ReadOwner | QFile::WriteOwner;
1256         } else {
1257             perms = KIO::convertPermissions(_mode | QFile::WriteOwner);
1258         }
1259 
1260         destFile.setPermissions(perms);
1261     }
1262 
1263     if (!destFile.open(openMode)) {
1264         switch (destFile.error()) {
1265         case QFile::OpenError:
1266             if (bResume) {
1267                 setError(KIO::ERR_CANNOT_RESUME, destPath);
1268             } else {
1269                 setError(KIO::ERR_CANNOT_OPEN_FOR_WRITING, destPath);
1270             }
1271             break;
1272         case QFile::PermissionsError:
1273             setError(KIO::ERR_WRITE_ACCESS_DENIED, destPath);
1274             break;
1275         default:
1276             setError(KIO::ERR_CANNOT_OPEN_FOR_WRITING, destPath);
1277             break;
1278         }
1279         return;
1280     }
1281 
1282     READ3args readArgs;
1283     srcFH.toFH(readArgs.file);
1284     if (bResume) {
1285         readArgs.offset = resumeOffset;
1286     } else {
1287         readArgs.offset = 0;
1288     }
1289     readArgs.count = m_readBufferSize;
1290 
1291     READ3res readRes;
1292     memset(&readRes, 0, sizeof(readres));
1293     readRes.READ3res_u.resok.data.data_val = new char[m_readBufferSize];
1294     readRes.READ3res_u.resok.data.data_len = m_readBufferSize;
1295 
1296     bool error = false;
1297     unsigned long bytesToRead = 0, bytesRead = 0;
1298     do {
1299         int clnt_stat = clnt_call(m_nfsClient,
1300                                   NFSPROC3_READ,
1301                                   (xdrproc_t)xdr_READ3args,
1302                                   reinterpret_cast<caddr_t>(&readArgs),
1303                                   (xdrproc_t)xdr_READ3res,
1304                                   reinterpret_cast<caddr_t>(&readRes),
1305                                   clnt_timeout);
1306 
1307         if (!checkForError(clnt_stat, readRes.status, destPath)) {
1308             error = true;
1309             break;
1310         }
1311 
1312         bytesRead = readRes.READ3res_u.resok.count;
1313 
1314         if (readArgs.offset == 0 || (bResume && readArgs.offset == resumeOffset)) {
1315             bytesToRead = readRes.READ3res_u.resok.file_attributes.post_op_attr_u.attributes.size;
1316 
1317             slave()->totalSize(bytesToRead);
1318 
1319             QMimeDatabase db;
1320             QMimeType type = db.mimeTypeForFileNameAndData(src.fileName(), QByteArray::fromRawData(readRes.READ3res_u.resok.data.data_val, bytesRead));
1321             slave()->mimeType(type.name());
1322         }
1323 
1324         if (bytesRead > 0) {
1325             readArgs.offset += bytesRead;
1326 
1327             if (destFile.write(readRes.READ3res_u.resok.data.data_val, bytesRead) < 0) {
1328                 setError(KIO::ERR_CANNOT_WRITE, destPath);
1329 
1330                 error = true;
1331                 break;
1332             }
1333 
1334             slave()->processedSize(readArgs.offset);
1335         }
1336     } while (readArgs.offset < bytesToRead);
1337 
1338     delete[] readRes.READ3res_u.resok.data.data_val;
1339 
1340     // Close the file so we can modify the modification time later.
1341     destFile.close();
1342 
1343     if (error) {
1344         if (bMarkPartial) {
1345             // Remove the part file if it's smaller than the minimum keep
1346             const int size = slave()->configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE);
1347             if (partInfo.size() < size) {
1348                 QFile::remove(partInfo.absoluteFilePath());
1349             }
1350         }
1351     } else {
1352         // Rename partial file to its original name.
1353         if (bMarkPartial) {
1354             const QString sPart = partInfo.absoluteFilePath();
1355             if (QFile::exists(destPath)) {
1356                 QFile::remove(destPath);
1357             }
1358             if (!QFile::rename(sPart, destPath)) {
1359                 qCDebug(LOG_KIO_NFS) << "failed to rename" << sPart << "to" << destPath;
1360                 setError(KIO::ERR_CANNOT_RENAME_PARTIAL, sPart);
1361                 return;
1362             }
1363         }
1364 
1365         // Restore the mtime on the file.
1366         const QString mtimeStr = slave()->metaData("modified");
1367         if (!mtimeStr.isEmpty()) {
1368             QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate);
1369             if (dt.isValid()) {
1370                 struct utimbuf utbuf;
1371                 utbuf.actime = QFileInfo(destPath).lastRead().toSecsSinceEpoch(); // access time, unchanged
1372                 utbuf.modtime = dt.toSecsSinceEpoch(); // modification time
1373                 utime(QFile::encodeName(destPath).constData(), &utbuf);
1374             }
1375         }
1376 
1377         qCDebug(LOG_KIO_NFS) << "Copied" << readArgs.offset << "bytes of data";
1378 
1379         slave()->processedSize(readArgs.offset);
1380     }
1381 }
1382 
1383 void NFSProtocolV3::copyTo(const QUrl &src, const QUrl &dest, int _mode, KIO::JobFlags _flags)
1384 {
1385     qCDebug(LOG_KIO_NFS) << src << "to" << dest;
1386 
1387     // The source does not exist, how strange
1388     const QString srcPath(src.path());
1389     if (!QFile::exists(srcPath)) {
1390         setError(KIO::ERR_DOES_NOT_EXIST, srcPath);
1391         return;
1392     }
1393 
1394     const QString destPath(dest.path());
1395     if (isExportedDir(QFileInfo(destPath).path())) {
1396         setError(KIO::ERR_ACCESS_DENIED, destPath);
1397         return;
1398     }
1399 
1400     // The file exists and we don't want to overwrite.
1401     if (!getFileHandle(destPath).isInvalid() && (_flags & KIO::Overwrite) == 0) {
1402         setError(KIO::ERR_FILE_ALREADY_EXIST, destPath);
1403         return;
1404     }
1405 
1406     // Is it a link? No need to copy the data then, just copy the link destination.
1407     const QString symlinkTarget = QFile::symLinkTarget(srcPath);
1408     if (!symlinkTarget.isEmpty()) {
1409         int rpcStatus;
1410         SYMLINK3res linkRes;
1411 
1412         symLink(symlinkTarget, destPath, rpcStatus, linkRes);
1413         checkForError(rpcStatus, linkRes.status, symlinkTarget);
1414         return; // done, no more to do
1415     }
1416 
1417     unsigned long resumeOffset = 0;
1418     bool bResume = false;
1419     const QString partFilePath = destPath + QLatin1String(".part");
1420     const NFSFileHandle partFH = getFileHandle(partFilePath);
1421     const bool bPartExists = !partFH.isInvalid();
1422     const bool bMarkPartial = slave()->configValue(QStringLiteral("MarkPartial"), true);
1423 
1424     if (bPartExists) {
1425         int rpcStatus;
1426         LOOKUP3res partRes;
1427         if (lookupHandle(partFilePath, rpcStatus, partRes)) {
1428             if (bMarkPartial && partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.size > 0) {
1429                 if (partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.type == NF3DIR) {
1430                     setError(KIO::ERR_IS_DIRECTORY, partFilePath);
1431                     return;
1432                 }
1433 
1434                 bResume = slave()->canResume(partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.size);
1435                 if (bResume) {
1436                     resumeOffset = partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.size;
1437                 }
1438             }
1439         }
1440 
1441         // Remove the part file if we are not resuming
1442         if (!bResume) {
1443             if (!remove(partFilePath)) {
1444                 qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring...";
1445             }
1446         }
1447     }
1448 
1449     // Open the source file
1450     QFile srcFile(srcPath);
1451     if (!srcFile.open(QIODevice::ReadOnly)) {
1452         setError(KIO::ERR_CANNOT_OPEN_FOR_READING, srcPath);
1453         return;
1454     }
1455 
1456     // Create the file if we are not resuming a parted transfer,
1457     // or if we are not using part files (bResume is false in that case)
1458     NFSFileHandle destFH;
1459     if (!bResume) {
1460         QString createPath;
1461         if (bMarkPartial) {
1462             createPath = partFilePath;
1463         } else {
1464             createPath = destPath;
1465         }
1466 
1467         destFH = create(createPath, _mode);
1468         if (destFH.isInvalid()) {
1469             return;
1470         }
1471     } else {
1472         // Since we are resuming it's implied that we are using a part file,
1473         // which should exist at this point.
1474         destFH = getFileHandle(partFilePath);
1475 
1476         qCDebug(LOG_KIO_NFS) << "Resuming old transfer";
1477     }
1478 
1479     // Send the total size to the slave.
1480     slave()->totalSize(srcFile.size());
1481 
1482     // Get the optimal write buffer size
1483     if (m_writeBufferSize == 0) {
1484         initPreferredSizes(destFH);
1485     }
1486 
1487     // Set up write arguments.
1488     WRITE3args writeArgs;
1489     memset(&writeArgs, 0, sizeof(writeArgs));
1490     destFH.toFH(writeArgs.file);
1491     writeArgs.data.data_val = new char[m_writeBufferSize];
1492     writeArgs.stable = FILE_SYNC;
1493     if (bResume) {
1494         writeArgs.offset = resumeOffset;
1495     } else {
1496         writeArgs.offset = 0;
1497     }
1498 
1499     WRITE3res writeRes;
1500     memset(&writeRes, 0, sizeof(writeRes));
1501 
1502     bool error = false;
1503     int bytesRead = 0;
1504     do {
1505         memset(writeArgs.data.data_val, 0, m_writeBufferSize);
1506 
1507         bytesRead = srcFile.read(writeArgs.data.data_val, m_writeBufferSize);
1508         if (bytesRead < 0) {
1509             setError(KIO::ERR_CANNOT_READ, srcPath);
1510 
1511             error = true;
1512             break;
1513         }
1514 
1515         if (bytesRead > 0) {
1516             writeArgs.count = bytesRead;
1517             writeArgs.data.data_len = bytesRead;
1518 
1519             int clnt_stat = clnt_call(m_nfsClient,
1520                                       NFSPROC3_WRITE,
1521                                       (xdrproc_t)xdr_WRITE3args,
1522                                       reinterpret_cast<caddr_t>(&writeArgs),
1523                                       (xdrproc_t)xdr_WRITE3res,
1524                                       reinterpret_cast<caddr_t>(&writeRes),
1525                                       clnt_timeout);
1526 
1527             if (!checkForError(clnt_stat, writeRes.status, destPath)) {
1528                 error = true;
1529                 break;
1530             }
1531 
1532             writeArgs.offset += bytesRead;
1533 
1534             slave()->processedSize(writeArgs.offset);
1535         }
1536     } while (bytesRead > 0);
1537 
1538     delete[] writeArgs.data.data_val;
1539 
1540     if (error) {
1541         if (bMarkPartial) {
1542             // Remove the part file if it's smaller than the minimum keep size.
1543             const unsigned int size = slave()->configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE);
1544             if (writeArgs.offset < size) {
1545                 if (!remove(partFilePath)) {
1546                     qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring...";
1547                 }
1548             }
1549         }
1550     } else {
1551         // Rename partial file to its original name.
1552         if (bMarkPartial) {
1553             // Remove the destination file(if it exists)
1554             if (!getFileHandle(destPath).isInvalid() && !remove(destPath)) {
1555                 qCDebug(LOG_KIO_NFS) << "Could not remove destination file" << destPath << ", ignoring...";
1556             }
1557 
1558             if (!rename(partFilePath, destPath)) {
1559                 qCDebug(LOG_KIO_NFS) << "failed to rename" << partFilePath << "to" << destPath;
1560                 setError(KIO::ERR_CANNOT_RENAME_PARTIAL, partFilePath);
1561                 return;
1562             }
1563         }
1564 
1565         // Restore the mtime on the file.
1566         const QString mtimeStr = slave()->metaData("modified");
1567         if (!mtimeStr.isEmpty()) {
1568             QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate);
1569             if (dt.isValid()) {
1570                 sattr3 attributes;
1571                 memset(&attributes, 0, sizeof(attributes));
1572                 attributes.mtime.set_it = SET_TO_CLIENT_TIME;
1573                 attributes.mtime.set_mtime_u.mtime.seconds = dt.toSecsSinceEpoch();
1574                 attributes.mtime.set_mtime_u.mtime.nseconds = attributes.mtime.set_mtime_u.mtime.seconds * 1000000000ULL;
1575 
1576                 int rpcStatus;
1577                 SETATTR3res attrSetRes;
1578                 if (!setAttr(destPath, attributes, rpcStatus, attrSetRes)) {
1579                     qCDebug(LOG_KIO_NFS) << "Failed to restore mtime, ignoring..." << rpcStatus << attrSetRes.status;
1580                 }
1581             }
1582         }
1583 
1584         qCDebug(LOG_KIO_NFS) << "Copied" << writeArgs.offset << "bytes of data";
1585 
1586         slave()->processedSize(writeArgs.offset);
1587     }
1588 }
1589 
1590 void NFSProtocolV3::symlink(const QString &target, const QUrl &dest, KIO::JobFlags flags)
1591 {
1592     const QString destPath(dest.path());
1593     if (isExportedDir(QFileInfo(destPath).path())) {
1594         setError(KIO::ERR_ACCESS_DENIED, destPath);
1595         return;
1596     }
1597 
1598     if (!getFileHandle(destPath).isInvalid() && (flags & KIO::Overwrite) == 0) {
1599         setError(KIO::ERR_FILE_ALREADY_EXIST, destPath);
1600         return;
1601     }
1602 
1603     int rpcStatus;
1604     SYMLINK3res res;
1605     symLink(target, destPath, rpcStatus, res);
1606     checkForError(rpcStatus, res.status, destPath);
1607 }
1608 
1609 void NFSProtocolV3::initPreferredSizes(const NFSFileHandle &fh)
1610 {
1611     FSINFO3args fsArgs;
1612     memset(&fsArgs, 0, sizeof(fsArgs));
1613     fh.toFH(fsArgs.fsroot);
1614 
1615     FSINFO3res fsRes;
1616     memset(&fsRes, 0, sizeof(fsRes));
1617 
1618     int clnt_stat = clnt_call(m_nfsClient,
1619                               NFSPROC3_FSINFO,
1620                               (xdrproc_t)xdr_FSINFO3args,
1621                               reinterpret_cast<caddr_t>(&fsArgs),
1622                               (xdrproc_t)xdr_FSINFO3res,
1623                               reinterpret_cast<caddr_t>(&fsRes),
1624                               clnt_timeout);
1625 
1626     if (clnt_stat == RPC_SUCCESS && fsRes.status == NFS3_OK) {
1627         m_writeBufferSize = fsRes.FSINFO3res_u.resok.wtpref;
1628         m_readBufferSize = fsRes.FSINFO3res_u.resok.rtpref;
1629         m_readDirSize = fsRes.FSINFO3res_u.resok.dtpref;
1630     } else {
1631         m_writeBufferSize = NFS3_MAXDATA;
1632         m_readBufferSize = NFS3_MAXDATA;
1633         m_readDirSize = NFS3_MAXDATA;
1634     }
1635 
1636     qCDebug(LOG_KIO_NFS) << "Preferred sizes - write" << m_writeBufferSize << ", read" << m_readBufferSize << ", read dir" << m_readDirSize;
1637 }
1638 
1639 NFSFileHandle NFSProtocolV3::create(const QString &path, int mode)
1640 {
1641     qCDebug(LOG_KIO_NFS) << path;
1642 
1643     if (!isConnected()) {
1644         setError(KIO::ERR_CANNOT_CONNECT, path);
1645         return NFSFileHandle();
1646     }
1647 
1648     const QFileInfo fileInfo(path);
1649 
1650     const NFSFileHandle directoryFH = getFileHandle(fileInfo.path());
1651     if (directoryFH.isInvalid()) {
1652         setError(KIO::ERR_DOES_NOT_EXIST, fileInfo.path());
1653         return NFSFileHandle();
1654     }
1655 
1656     QByteArray tmpName = QFile::encodeName(fileInfo.fileName());
1657 
1658     int rpcStatus = 0;
1659     CREATE3res result;
1660     memset(&result, 0, sizeof(result));
1661 
1662     CREATE3args args;
1663     memset(&args, 0, sizeof(args));
1664 
1665     directoryFH.toFH(args.where.dir);
1666     args.where.name = tmpName.data();
1667 
1668     args.how.createhow3_u.obj_attributes.mode.set_it = true;
1669     args.how.createhow3_u.obj_attributes.uid.set_it = true;
1670     args.how.createhow3_u.obj_attributes.gid.set_it = true;
1671     args.how.createhow3_u.obj_attributes.size.set_it = true;
1672 
1673     if (mode == -1) {
1674         args.how.createhow3_u.obj_attributes.mode.set_mode3_u.mode = 0644;
1675     } else {
1676         args.how.createhow3_u.obj_attributes.mode.set_mode3_u.mode = mode;
1677     }
1678     args.how.createhow3_u.obj_attributes.uid.set_uid3_u.uid = geteuid();
1679     args.how.createhow3_u.obj_attributes.gid.set_gid3_u.gid = getegid();
1680     args.how.createhow3_u.obj_attributes.size.set_size3_u.size = 0;
1681 
1682     rpcStatus = clnt_call(m_nfsClient,
1683                           NFSPROC3_CREATE,
1684                           (xdrproc_t)xdr_CREATE3args,
1685                           reinterpret_cast<caddr_t>(&args),
1686                           (xdrproc_t)xdr_CREATE3res,
1687                           reinterpret_cast<caddr_t>(&result),
1688                           clnt_timeout);
1689 
1690     if (rpcStatus != RPC_SUCCESS || result.status != NFS3_OK) {
1691         checkForError(rpcStatus, result.status, path);
1692         return NFSFileHandle();
1693     }
1694 
1695     return result.CREATE3res_u.resok.obj.post_op_fh3_u.handle;
1696 }
1697 
1698 bool NFSProtocolV3::getAttr(const QString &path, int &rpcStatus, GETATTR3res &result)
1699 {
1700     qCDebug(LOG_KIO_NFS) << path;
1701 
1702     memset(&rpcStatus, 0, sizeof(int));
1703     memset(&result, 0, sizeof(result));
1704 
1705     if (!isConnected()) {
1706         result.status = NFS3ERR_ACCES;
1707         return false;
1708     }
1709 
1710     const NFSFileHandle fileFH = getFileHandle(path);
1711     if (fileFH.isInvalid()) {
1712         result.status = NFS3ERR_NOENT;
1713         return false;
1714     }
1715 
1716     GETATTR3args args;
1717     memset(&args, 0, sizeof(GETATTR3args));
1718     fileFH.toFH(args.object);
1719 
1720     rpcStatus = clnt_call(m_nfsClient,
1721                           NFSPROC3_GETATTR,
1722                           (xdrproc_t)xdr_GETATTR3args,
1723                           reinterpret_cast<caddr_t>(&args),
1724                           (xdrproc_t)xdr_GETATTR3res,
1725                           reinterpret_cast<caddr_t>(&result),
1726                           clnt_timeout);
1727 
1728     return (rpcStatus == RPC_SUCCESS && result.status == NFS3_OK);
1729 }
1730 
1731 bool NFSProtocolV3::lookupHandle(const QString &path, int &rpcStatus, LOOKUP3res &result)
1732 {
1733     memset(&rpcStatus, 0, sizeof(int));
1734     memset(&result, 0, sizeof(result));
1735 
1736     if (!isConnected()) {
1737         result.status = NFS3ERR_ACCES;
1738         return false;
1739     }
1740 
1741     const QFileInfo fileInfo(path);
1742 
1743     const NFSFileHandle parentFH = getFileHandle(fileInfo.path());
1744     if (parentFH.isInvalid()) {
1745         result.status = NFS3ERR_NOENT;
1746         return false;
1747     }
1748 
1749     QByteArray tmpName = QFile::encodeName(fileInfo.fileName());
1750 
1751     // do the rpc call
1752     LOOKUP3args args;
1753     memset(&args, 0, sizeof(args));
1754     parentFH.toFH(args.what.dir);
1755     args.what.name = tmpName.data();
1756 
1757     rpcStatus = clnt_call(m_nfsClient,
1758                           NFSPROC3_LOOKUP,
1759                           (xdrproc_t)xdr_LOOKUP3args,
1760                           reinterpret_cast<caddr_t>(&args),
1761                           (xdrproc_t)xdr_LOOKUP3res,
1762                           reinterpret_cast<caddr_t>(&result),
1763                           clnt_timeout);
1764 
1765     return (rpcStatus == RPC_SUCCESS && result.status == NFS3_OK);
1766 }
1767 
1768 bool NFSProtocolV3::symLinkTarget(const QString &path, int &rpcStatus, READLINK3res &result, char *dataBuffer)
1769 {
1770     qCDebug(LOG_KIO_NFS) << path;
1771 
1772     memset(&rpcStatus, 0, sizeof(int));
1773     memset(&result, 0, sizeof(result));
1774 
1775     const NFSFileHandle fh = getFileHandle(path);
1776     if (fh.isInvalid()) {
1777         result.status = NFS3ERR_NOENT;
1778         return false;
1779     }
1780 
1781     READLINK3args readLinkArgs;
1782     memset(&readLinkArgs, 0, sizeof(readLinkArgs));
1783     if (fh.isLink() && !fh.isBadLink()) {
1784         fh.toFHLink(readLinkArgs.symlink);
1785     } else {
1786         fh.toFH(readLinkArgs.symlink);
1787     }
1788 
1789     result.READLINK3res_u.resok.data = dataBuffer;
1790 
1791     rpcStatus = clnt_call(m_nfsClient,
1792                           NFSPROC3_READLINK,
1793                           (xdrproc_t)xdr_READLINK3args,
1794                           reinterpret_cast<caddr_t>(&readLinkArgs),
1795                           (xdrproc_t)xdr_READLINK3res,
1796                           reinterpret_cast<caddr_t>(&result),
1797                           clnt_timeout);
1798 
1799     return (rpcStatus == RPC_SUCCESS && result.status == NFS3_OK);
1800 }
1801 
1802 bool NFSProtocolV3::remove(const QString &path)
1803 {
1804     int rpcStatus;
1805     REMOVE3res result;
1806 
1807     return remove(path, rpcStatus, result);
1808 }
1809 
1810 bool NFSProtocolV3::remove(const QString &path, int &rpcStatus, REMOVE3res &result)
1811 {
1812     qCDebug(LOG_KIO_NFS) << path;
1813 
1814     memset(&rpcStatus, 0, sizeof(int));
1815     memset(&result, 0, sizeof(result));
1816 
1817     if (!isConnected()) {
1818         result.status = NFS3ERR_PERM;
1819         return false;
1820     }
1821 
1822     const QFileInfo fileInfo(path);
1823     if (isExportedDir(fileInfo.path())) {
1824         result.status = NFS3ERR_ACCES;
1825         return false;
1826     }
1827 
1828     const NFSFileHandle directoryFH = getFileHandle(fileInfo.path());
1829     if (directoryFH.isInvalid()) {
1830         result.status = NFS3ERR_NOENT;
1831         return false;
1832     }
1833 
1834     int rpcLookupStatus;
1835     LOOKUP3res lookupRes;
1836     if (!lookupHandle(path, rpcLookupStatus, lookupRes)) {
1837         result.status = NFS3ERR_NOENT;
1838         return false;
1839     }
1840 
1841     QByteArray tmpName = QFile::encodeName(fileInfo.fileName());
1842 
1843     REMOVE3args args;
1844     memset(&args, 0, sizeof(args));
1845     directoryFH.toFH(args.object.dir);
1846     args.object.name = tmpName.data();
1847 
1848     if (lookupRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.type != NF3DIR) {
1849         rpcStatus = clnt_call(m_nfsClient,
1850                               NFSPROC3_REMOVE,
1851                               (xdrproc_t)xdr_REMOVE3args,
1852                               reinterpret_cast<caddr_t>(&args),
1853                               (xdrproc_t)xdr_REMOVE3res,
1854                               reinterpret_cast<caddr_t>(&result),
1855                               clnt_timeout);
1856     } else {
1857         rpcStatus = clnt_call(m_nfsClient,
1858                               NFSPROC3_RMDIR,
1859                               (xdrproc_t)xdr_RMDIR3args,
1860                               reinterpret_cast<caddr_t>(&args),
1861                               (xdrproc_t)xdr_RMDIR3res,
1862                               reinterpret_cast<caddr_t>(&result),
1863                               clnt_timeout);
1864     }
1865 
1866     bool ret = (rpcStatus == RPC_SUCCESS && result.status == NFS3_OK);
1867     if (ret) {
1868         // Remove it from the cache as well
1869         removeFileHandle(path);
1870     }
1871 
1872     return ret;
1873 }
1874 
1875 bool NFSProtocolV3::rename(const QString &src, const QString &dest)
1876 {
1877     int rpcStatus;
1878     RENAME3res result;
1879 
1880     return rename(src, dest, rpcStatus, result);
1881 }
1882 
1883 bool NFSProtocolV3::rename(const QString &src, const QString &dest, int &rpcStatus, RENAME3res &result)
1884 {
1885     qCDebug(LOG_KIO_NFS) << src << dest;
1886 
1887     memset(&rpcStatus, 0, sizeof(int));
1888     memset(&result, 0, sizeof(result));
1889 
1890     const QFileInfo srcFileInfo(src);
1891     if (isExportedDir(srcFileInfo.path())) {
1892         result.status = NFS3ERR_ACCES;
1893         return false;
1894     }
1895 
1896     const NFSFileHandle srcDirectoryFH = getFileHandle(srcFileInfo.path());
1897     if (srcDirectoryFH.isInvalid()) {
1898         result.status = NFS3ERR_NOENT;
1899         return false;
1900     }
1901 
1902     const QFileInfo destFileInfo(dest);
1903     if (isExportedDir(destFileInfo.path())) {
1904         result.status = NFS3ERR_ACCES;
1905         return false;
1906     }
1907 
1908     const NFSFileHandle destDirectoryFH = getFileHandle(destFileInfo.path());
1909     if (destDirectoryFH.isInvalid()) {
1910         result.status = NFS3ERR_NOENT;
1911         return false;
1912     }
1913 
1914     RENAME3args args;
1915     memset(&args, 0, sizeof(args));
1916 
1917     QByteArray srcByteName = QFile::encodeName(srcFileInfo.fileName());
1918     srcDirectoryFH.toFH(args.from.dir);
1919     args.from.name = srcByteName.data();
1920 
1921     QByteArray destByteName = QFile::encodeName(destFileInfo.fileName());
1922     destDirectoryFH.toFH(args.to.dir);
1923     args.to.name = destByteName.data();
1924 
1925     rpcStatus = clnt_call(m_nfsClient,
1926                           NFSPROC3_RENAME,
1927                           (xdrproc_t)xdr_RENAME3args,
1928                           reinterpret_cast<caddr_t>(&args),
1929                           (xdrproc_t)xdr_RENAME3res,
1930                           reinterpret_cast<caddr_t>(&result),
1931                           clnt_timeout);
1932 
1933     bool ret = (rpcStatus == RPC_SUCCESS && result.status == NFS3_OK);
1934     if (ret) {
1935         // Can we actually find the new handle?
1936         int lookupStatus;
1937         LOOKUP3res lookupRes;
1938         if (lookupHandle(dest, lookupStatus, lookupRes)) {
1939             // Remove the old file, and add the new one
1940             removeFileHandle(src);
1941             addFileHandle(dest, lookupRes.LOOKUP3res_u.resok.object);
1942         }
1943     }
1944 
1945     return ret;
1946 }
1947 
1948 bool NFSProtocolV3::setAttr(const QString &path, const sattr3 &attributes, int &rpcStatus, SETATTR3res &result)
1949 {
1950     qCDebug(LOG_KIO_NFS) << path;
1951 
1952     memset(&rpcStatus, 0, sizeof(int));
1953     memset(&result, 0, sizeof(result));
1954 
1955     const NFSFileHandle fh = getFileHandle(path);
1956     if (fh.isInvalid()) {
1957         result.status = NFS3ERR_NOENT;
1958         return false;
1959     }
1960 
1961     SETATTR3args setAttrArgs;
1962     memset(&setAttrArgs, 0, sizeof(setAttrArgs));
1963     fh.toFH(setAttrArgs.object);
1964     memcpy(&setAttrArgs.new_attributes, &attributes, sizeof(attributes));
1965 
1966     rpcStatus = clnt_call(m_nfsClient,
1967                           NFSPROC3_SETATTR,
1968                           (xdrproc_t)xdr_SETATTR3args,
1969                           reinterpret_cast<caddr_t>(&setAttrArgs),
1970                           (xdrproc_t)xdr_SETATTR3res,
1971                           reinterpret_cast<caddr_t>(&result),
1972                           clnt_timeout);
1973 
1974     return (rpcStatus == RPC_SUCCESS && result.status == NFS3_OK);
1975 }
1976 
1977 bool NFSProtocolV3::symLink(const QString &target, const QString &dest, int &rpcStatus, SYMLINK3res &result)
1978 {
1979     qCDebug(LOG_KIO_NFS) << target << dest;
1980 
1981     memset(&rpcStatus, 0, sizeof(int));
1982     memset(&result, 0, sizeof(result));
1983 
1984     // Remove dest first, we don't really care about the return value at this point,
1985     // the symlink call will fail if dest was not removed correctly.
1986     remove(dest);
1987 
1988     const QFileInfo fileInfo(dest);
1989 
1990     const NFSFileHandle fh = getFileHandle(fileInfo.path());
1991     if (fh.isInvalid()) {
1992         result.status = NFS3ERR_NOENT;
1993         return false;
1994     }
1995 
1996     QByteArray tmpStr = QFile::encodeName(fileInfo.fileName());
1997     QByteArray tmpStr2 = QFile::encodeName(target);
1998 
1999     SYMLINK3args symLinkArgs;
2000     memset(&symLinkArgs, 0, sizeof(symLinkArgs));
2001 
2002     fh.toFH(symLinkArgs.where.dir);
2003     symLinkArgs.where.name = tmpStr.data();
2004     symLinkArgs.symlink.symlink_data = tmpStr2.data();
2005 
2006     rpcStatus = clnt_call(m_nfsClient,
2007                           NFSPROC3_SYMLINK,
2008                           (xdrproc_t)xdr_SYMLINK3args,
2009                           reinterpret_cast<caddr_t>(&symLinkArgs),
2010                           (xdrproc_t)xdr_SYMLINK3res,
2011                           reinterpret_cast<caddr_t>(&result),
2012                           clnt_timeout);
2013 
2014     // Add the new handle to the cache
2015     NFSFileHandle destFH = getFileHandle(dest);
2016     if (!destFH.isInvalid()) {
2017         addFileHandle(dest, destFH);
2018     }
2019 
2020     return (rpcStatus == RPC_SUCCESS && result.status == NFS3_OK);
2021 }
2022 
2023 // This function and completeBadLinkUDSEntry() must use KIO::UDSEntry::replace()
2024 // because they may be called with a UDSEntry that has already been partially
2025 // filled in by NFSProtocol::createVirtualDirEntry().
2026 
2027 void NFSProtocolV3::completeUDSEntry(KIO::UDSEntry &entry, const fattr3 &attributes)
2028 {
2029     entry.replace(KIO::UDSEntry::UDS_SIZE, attributes.size);
2030     entry.replace(KIO::UDSEntry::UDS_MODIFICATION_TIME, attributes.mtime.seconds);
2031     entry.replace(KIO::UDSEntry::UDS_ACCESS_TIME, attributes.atime.seconds);
2032 
2033     // Some servers still send the file type information in the mode, even though
2034     // the reference specifies NFSv3 shouldn't, so we need to work around that here.
2035     // Not sure this is the best way to do it, but it works.
2036     if (attributes.mode > 0777) {
2037         entry.replace(KIO::UDSEntry::UDS_ACCESS, (attributes.mode & 07777));
2038     } else {
2039         entry.replace(KIO::UDSEntry::UDS_ACCESS, attributes.mode);
2040     }
2041 
2042     unsigned int type;
2043     switch (attributes.type) {
2044     case NF3DIR:
2045         type = S_IFDIR;
2046         break;
2047     case NF3BLK:
2048         type = S_IFBLK;
2049         break;
2050     case NF3CHR:
2051         type = S_IFCHR;
2052         break;
2053     case NF3LNK:
2054         type = S_IFLNK;
2055         break;
2056     case NF3SOCK:
2057         type = S_IFSOCK;
2058         break;
2059     case NF3FIFO:
2060         type = S_IFIFO;
2061         break;
2062     default:
2063         type = S_IFREG;
2064         break;
2065     }
2066     entry.replace(KIO::UDSEntry::UDS_FILE_TYPE, type);
2067 
2068     NFSProtocol::completeUDSEntry(entry, attributes.uid, attributes.gid);
2069 }
2070 
2071 void NFSProtocolV3::completeBadLinkUDSEntry(KIO::UDSEntry &entry, const fattr3 &attributes)
2072 {
2073     entry.replace(KIO::UDSEntry::UDS_MODIFICATION_TIME, attributes.mtime.seconds);
2074     entry.replace(KIO::UDSEntry::UDS_ACCESS_TIME, attributes.atime.seconds);
2075 
2076     NFSProtocol::completeInvalidUDSEntry(entry);
2077 }