1 /*  This file is part of the KDE project
2 
3     SPDX-FileCopyrightText: 2000 Alexander Neundorf <neundorf@kde.org>
4     SPDX-FileCopyrightText: 2014 Mathias Tillman <master.homer@gmail.com>
5 
6     SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8 
9 #include "nfsv2.h"
10 #include "kio_nfs_debug.h"
11 
12 #include <config-runtime.h>
13 
14 #include <arpa/inet.h>
15 
16 // This is needed on Solaris so that rpc.h defines clnttcp_create etc.
17 #ifndef PORTMAP
18 #define PORTMAP
19 #endif
20 #include <rpc/rpc.h> // for rpc calls
21 
22 #include <errno.h>
23 #include <grp.h>
24 #include <memory.h>
25 #include <netdb.h>
26 #include <pwd.h>
27 #include <stdlib.h>
28 #include <strings.h>
29 #include <stdio.h>
30 #include <time.h>
31 #include <unistd.h>
32 #include <utime.h>
33 
34 #include <QFile>
35 #include <QDir>
36 #include <QDebug>
37 #include <QMimeDatabase>
38 #include <QMimeType>
39 
40 #include <KLocalizedString>
41 #include <kio/global.h>
42 #include <kio/ioslave_defaults.h>
43 
44 // For the complete NFSv2 reference see http://tools.ietf.org/html/rfc1094
45 
46 // This is for NFS version 2.
47 #define NFSPROG 100003UL
48 #define NFSVERS 2UL
49 
50 
NFSProtocolV2(NFSSlave * slave)51 NFSProtocolV2::NFSProtocolV2(NFSSlave* slave)
52     :  NFSProtocol(slave),
53        m_mountClient(nullptr),
54        m_mountSock(-1),
55        m_nfsClient(nullptr),
56        m_nfsSock(-1)
57 {
58     qCDebug(LOG_KIO_NFS);
59 
60     clnt_timeout.tv_sec = 20;
61     clnt_timeout.tv_usec = 0;
62 }
63 
~NFSProtocolV2()64 NFSProtocolV2::~NFSProtocolV2()
65 {
66     closeConnection();
67 }
68 
isCompatible(bool & connectionError)69 bool NFSProtocolV2::isCompatible(bool& connectionError)
70 {
71     int ret = -1;
72 
73     CLIENT* client = nullptr;
74     int sock = 0;
75     if (NFSProtocol::openConnection(currentHost(), NFSPROG, NFSVERS, client, sock) == 0) {
76         // Check if the NFS version is compatible
77         ret = clnt_call(client, NFSPROC_NULL,
78                         (xdrproc_t) xdr_void, nullptr,
79                         (xdrproc_t) xdr_void, nullptr, clnt_timeout);
80 
81         connectionError = false;
82     } else {
83         qCDebug(LOG_KIO_NFS) << "openConnection failed";
84         connectionError = true;
85     }
86 
87     if (sock != -1) {
88         ::close(sock);
89     }
90 
91     if (client != nullptr) {
92         CLNT_DESTROY(client);
93     }
94 
95     qCDebug(LOG_KIO_NFS) << "RPC status" << ret << "connectionError" << connectionError;
96     return (ret == RPC_SUCCESS);
97 }
98 
isConnected() const99 bool NFSProtocolV2::isConnected() const
100 {
101     return (m_nfsClient != nullptr);
102 }
103 
closeConnection()104 void NFSProtocolV2::closeConnection()
105 {
106     qCDebug(LOG_KIO_NFS);
107 
108     // Unmount all exported dirs(if any)
109     if (m_mountClient != nullptr) {
110         clnt_call(m_mountClient, MOUNTPROC_UMNTALL,
111                   (xdrproc_t) xdr_void, nullptr,
112                   (xdrproc_t) xdr_void, nullptr,
113                   clnt_timeout);
114     }
115 
116     if (m_mountSock >= 0) {
117         ::close(m_mountSock);
118         m_mountSock = -1;
119     }
120     if (m_nfsSock >= 0) {
121         ::close(m_nfsSock);
122         m_nfsSock = -1;
123     }
124 
125     if (m_mountClient != nullptr) {
126         CLNT_DESTROY(m_mountClient);
127         m_mountClient = nullptr;
128     }
129     if (m_nfsClient != nullptr) {
130         CLNT_DESTROY(m_nfsClient);
131         m_nfsClient = nullptr;
132     }
133 }
134 
lookupFileHandle(const QString & path)135 NFSFileHandle NFSProtocolV2::lookupFileHandle(const QString& path)
136 {
137     NFSFileHandle fh;
138     int rpcStatus;
139     diropres res;
140 
141     if (lookupHandle(path, rpcStatus, res)) {
142         fh = res.diropres_u.diropres.file;
143 
144         // It it a link? Get the link target.
145         if (res.diropres_u.diropres.attributes.type == NFLNK) {
146             nfs_fh readLinkArgs;
147             fh.toFH(readLinkArgs);
148 
149             char dataBuffer[NFS_MAXPATHLEN];
150 
151             readlinkres readLinkRes;
152             memset(&readLinkRes, 0, sizeof(readLinkRes));
153             readLinkRes.readlinkres_u.data = dataBuffer;
154 
155             int rpcStatus = clnt_call(m_nfsClient, NFSPROC_READLINK,
156                                       (xdrproc_t) xdr_nfs_fh, reinterpret_cast<caddr_t>(&readLinkArgs),
157                                       (xdrproc_t) xdr_readlinkres, reinterpret_cast<caddr_t>(&readLinkRes),
158                                       clnt_timeout);
159 
160             if (rpcStatus == RPC_SUCCESS && readLinkRes.status == NFS_OK)
161             {   // get the absolute link target
162                 QString linkPath = QString::fromLocal8Bit(readLinkRes.readlinkres_u.data);
163                 linkPath = QFileInfo(QFileInfo(path).path(), linkPath).absoluteFilePath();
164 
165                 // As with the tests done in NFSProtocol::isValidLink(), the link
166                 // target may not be valid on the NFS server (i.e. it may point
167                 // outside of the exported directories).  Check for this before
168                 // calling lookupHandle() on the target of the link, as otherwise
169                 // an error will be set which is not relevant.
170                 if (isValidPath(linkPath))
171                 {
172                     diropres linkRes;
173                     if (lookupHandle(linkPath, rpcStatus, linkRes))
174                     {
175                         NFSFileHandle linkFh = linkRes.diropres_u.diropres.file;
176                         linkFh.setLinkSource(res.diropres_u.diropres.file);
177                         qCDebug(LOG_KIO_NFS) << "Found link target" << linkPath;
178                         return linkFh;
179                     }
180                 }
181             }
182 
183             // If we have reached this point the file is a link,
184             // but we failed to read the target.
185             fh.setBadLink();
186             qCDebug(LOG_KIO_NFS) << "Invalid link" << path;
187         }
188     }
189 
190     return fh;
191 }
192 
193 /* Open connection connects to the mount daemon on the server side.
194  In order to do this it needs authentication and calls auth_unix_create().
195  Then it asks the mount daemon for the exported shares. Then it tries
196  to mount all these shares. If this succeeded for at least one of them,
197  a client for the nfs daemon is created.
198  */
openConnection()199 void NFSProtocolV2::openConnection()
200 {
201     const QString host = currentHost();
202     qCDebug(LOG_KIO_NFS) << "to" << host;
203 
204     KIO::Error connErr = NFSProtocol::openConnection(host, MOUNTPROG, MOUNTVERS, m_mountClient, m_mountSock);
205     if (connErr != 0) {
206         // Close the connection and send the error id to the slave
207         closeConnection();
208         setError(connErr, host);
209         return;
210     }
211 
212     exports exportlist;
213     memset(&exportlist, 0, sizeof(exportlist));
214 
215     int clnt_stat = clnt_call(m_mountClient, MOUNTPROC_EXPORT,
216                               (xdrproc_t) xdr_void, nullptr,
217                               (xdrproc_t) xdr_exports, reinterpret_cast<caddr_t>(&exportlist),
218                               clnt_timeout);
219 
220     if (!checkForError(clnt_stat, 0, host.toLatin1())) {
221         return;
222     }
223 
224     int exportsCount = 0;
225     bool mountHint = false;
226 
227     fhstatus fhStatus;
228     for (; exportlist != nullptr; exportlist = exportlist->ex_next, exportsCount++) {
229         memset(&fhStatus, 0, sizeof(fhStatus));
230 
231         clnt_stat = clnt_call(m_mountClient, MOUNTPROC_MNT,
232                               (xdrproc_t) xdr_dirpath, reinterpret_cast<caddr_t>(&exportlist->ex_dir),
233                               (xdrproc_t) xdr_fhstatus, reinterpret_cast<caddr_t>(&fhStatus),
234                               clnt_timeout);
235 
236 
237         QString fname = QFileInfo(QDir::root(), exportlist->ex_dir).filePath();
238         if (fhStatus.fhs_status == 0) {
239             // Check if the directory is already noted as exported,
240             // if so there is no need to add it again.
241             if (NFSProtocol::isExportedDir(fname)) {
242                 continue;
243             }
244 
245             // Save the exported directory and its NFS file handle.
246             addFileHandle(fname, static_cast<NFSFileHandle>(fhStatus.fhstatus_u.fhs_fhandle));
247             addExportedDir(fname);
248         } else {                    // mount failed with error
249             qCDebug(LOG_KIO_NFS) << "Cannot mount" << fname << "- status" << fhStatus.fhs_status;
250 
251             // Even if the mount failed, record the directory path as exported
252             // so that it can be listed as a virtual directory.  However, do
253             // not record its (invalid) file handle in the cache.  Trying to
254             // access the directory in any way other than just listing it, or
255             // accessing anything below it, will be detected in
256             // NFSProtocol::getFileHandle() and fail with an appropriate
257             // error.
258             if (!isExportedDir(fname)) addExportedDir(fname);
259 
260             // Many modern NFS servers by default reject any access attempted to
261             // them from a non-reserved source port (i.e. above 1024).  Since
262             // this KIO slave runs as a normal user, it is not able to use the
263             // reserved port numbers and hence the access will be rejected.  Show
264             // a hint if this could possibly be the problem - only once, as the
265             // server may have many exported directories.
266             if (fhStatus.fhs_status == NFSERR_ACCES) {
267                 if (!mountHint) {
268                     qCDebug(LOG_KIO_NFS) << "Check that the NFS server is exporting the filesystem";
269                     qCDebug(LOG_KIO_NFS) << "with appropriate access permissions.  Note that it must";
270                     qCDebug(LOG_KIO_NFS) << "allow mount requests originating from an unprivileged";
271                     qCDebug(LOG_KIO_NFS) << "source port (see exports(5), the 'insecure' option may";
272                     qCDebug(LOG_KIO_NFS) << "be required).";
273                     mountHint = true;
274                 }
275             }
276         }
277     }
278 
279     // If nothing can be mounted then there is no point trying to open the
280     // NFS server connection here.  However, call openConnection() anyway
281     // and pretend that we are connected so that listing virtual directories
282     // will work.
283     if ((connErr = NFSProtocol::openConnection(host, NFSPROG, NFSVERS, m_nfsClient, m_nfsSock)) != 0)
284     {
285         closeConnection();
286         setError(connErr, host);
287     }
288 
289     slave()->connected();
290 
291     qCDebug(LOG_KIO_NFS) << "openConnection succeeded";
292 }
293 
listDir(const QUrl & url)294 void NFSProtocolV2::listDir(const QUrl& url)
295 {
296     qCDebug(LOG_KIO_NFS) << url;
297 
298     const QString path = listDirInternal(url);      // check path, list virtual dir
299     if (path.isEmpty()) return;             // no more to do
300 
301     const NFSFileHandle fh = getFileHandle(path);
302     if (fh.isInvalid() || fh.isBadLink()) {
303         setError(KIO::ERR_DOES_NOT_EXIST, path);
304         return;
305     }
306 
307     readdirargs listargs;
308     memset(&listargs, 0, sizeof(listargs));
309     listargs.count = 1024 * sizeof(entry);
310     fh.toFH(listargs.dir);
311 
312     readdirres listres;
313 
314     QStringList filesToList;
315     entry* lastEntry = nullptr;
316     do {
317         memset(&listres, 0, sizeof(listres));
318         // In case that we didn't get all entries we need to set the cookie to the last one we actually received.
319         if (lastEntry != nullptr) {
320             memcpy(listargs.cookie, lastEntry->cookie, NFS_COOKIESIZE);
321         }
322 
323         int clnt_stat = clnt_call(m_nfsClient, NFSPROC_READDIR,
324                                   (xdrproc_t) xdr_readdirargs, reinterpret_cast<caddr_t>(&listargs),
325                                   (xdrproc_t) xdr_readdirres, reinterpret_cast<caddr_t>(&listres),
326                                   clnt_timeout);
327 
328         if (!checkForError(clnt_stat, listres.status, path)) {
329             return;
330         }
331 
332         for (entry* dirEntry = listres.readdirres_u.reply.entries; dirEntry != nullptr; dirEntry = dirEntry->nextentry) {
333             if (dirEntry->name != QString("..")) {
334                 filesToList.append(QFile::decodeName(dirEntry->name));
335             }
336 
337             lastEntry = dirEntry;
338         }
339     } while (!listres.readdirres_u.reply.eof);
340 
341     for (QStringList::const_iterator it = filesToList.constBegin(); it != filesToList.constEnd(); ++it) {
342         QString filePath = QFileInfo(QDir(path), (*it)).filePath();
343 
344         int rpcStatus;
345         diropres dirres;
346         if (!lookupHandle(filePath, rpcStatus, dirres)) {
347             qCDebug(LOG_KIO_NFS) << "Failed to lookup" << filePath << ", rpc:" << rpcStatus << ", nfs:" << dirres.status;
348             // Try the next file instead of failing
349             continue;
350         }
351 
352         KIO::UDSEntry entry;
353         entry.fastInsert(KIO::UDSEntry::UDS_NAME, (*it));
354 
355         //is it a symlink ?
356         if (dirres.diropres_u.diropres.attributes.type == NFLNK) {
357             int rpcStatus;
358             readlinkres readLinkRes;
359             char nameBuf[NFS_MAXPATHLEN];
360             if (symLinkTarget(filePath, rpcStatus, readLinkRes, nameBuf)) {
361                 const QString linkDest = QString::fromLocal8Bit(readLinkRes.readlinkres_u.data);
362                 entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, linkDest);
363 
364                 bool badLink = true;
365                 NFSFileHandle linkFH;
366                 if (isValidLink(path, linkDest)) {
367                     QString linkPath = QFileInfo(path, linkDest).absoluteFilePath();
368 
369                     int rpcStatus;
370                     diropres lookupRes;
371                     if (lookupHandle(linkPath, rpcStatus, lookupRes)) {
372                         attrstat attrAndStat;
373                         if (getAttr(linkPath, rpcStatus, attrAndStat)) {
374                             badLink = false;
375 
376                             linkFH = lookupRes.diropres_u.diropres.file;
377                             linkFH.setLinkSource(dirres.diropres_u.diropres.file);
378 
379                             completeUDSEntry(entry, attrAndStat.attrstat_u.attributes);
380                         }
381                     }
382 
383                 }
384 
385                 if (badLink) {
386                     linkFH = dirres.diropres_u.diropres.file;
387                     linkFH.setBadLink();
388 
389                     completeBadLinkUDSEntry(entry, dirres.diropres_u.diropres.attributes);
390                 }
391 
392                 addFileHandle(filePath, linkFH);
393             } else {
394                 entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, i18n("Unknown target"));
395                 completeBadLinkUDSEntry(entry, dirres.diropres_u.diropres.attributes);
396             }
397         } else {
398             addFileHandle(filePath, dirres.diropres_u.diropres.file);
399             completeUDSEntry(entry, dirres.diropres_u.diropres.attributes);
400         }
401 
402         slave()->listEntry(entry);
403     }
404 }
405 
406 
stat(const QUrl & url)407 void NFSProtocolV2::stat(const QUrl &url)
408 {
409     qCDebug(LOG_KIO_NFS) << url;
410 
411     const QString path = statInternal(url);     // check path, process virtual dir
412     if (path.isEmpty()) return;             // no more to do
413 
414     const NFSFileHandle fh = getFileHandle(path);
415     if (fh.isInvalid())
416     {
417         qCDebug(LOG_KIO_NFS) << "File handle is invalid";
418         setError(KIO::ERR_DOES_NOT_EXIST, path);
419         return;
420     }
421 
422     int rpcStatus;
423     attrstat attrAndStat;
424     if (!getAttr(path, rpcStatus, attrAndStat)) {
425         checkForError(rpcStatus, attrAndStat.status, path);
426         return;
427     }
428 
429     const QFileInfo fileInfo(path);
430 
431     KIO::UDSEntry entry;
432     entry.fastInsert(KIO::UDSEntry::UDS_NAME, fileInfo.fileName());
433 
434     // Is it a symlink?
435     if (attrAndStat.attrstat_u.attributes.type == NFLNK) {
436         qCDebug(LOG_KIO_NFS) << "It's a symlink";
437 
438         QString linkDest;
439 
440         int rpcStatus;
441         readlinkres readLinkRes;
442         char nameBuf[NFS_MAXPATHLEN];
443         if (symLinkTarget(path, rpcStatus, readLinkRes, nameBuf)) {
444             linkDest = QString::fromLocal8Bit(readLinkRes.readlinkres_u.data);
445         } else {
446             entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, i18n("Unknown target"));
447             completeBadLinkUDSEntry(entry, attrAndStat.attrstat_u.attributes);
448 
449             slave()->statEntry(entry);
450             return;
451         }
452 
453         qCDebug(LOG_KIO_NFS) << "link dest is" << linkDest;
454 
455         entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, linkDest);
456         if (!isValidLink(fileInfo.path(), linkDest)) {
457             completeBadLinkUDSEntry(entry, attrAndStat.attrstat_u.attributes);
458         } else {
459             QString linkPath = QFileInfo(fileInfo.path(), linkDest).absoluteFilePath();
460 
461             int rpcStatus;
462             attrstat attrAndStat;
463             if (!getAttr(linkPath, rpcStatus, attrAndStat)) {
464                 checkForError(rpcStatus, attrAndStat.status, linkPath);
465                 return;
466             }
467 
468             completeUDSEntry(entry, attrAndStat.attrstat_u.attributes);
469         }
470     } else {
471         completeUDSEntry(entry, attrAndStat.attrstat_u.attributes);
472     }
473 
474     slave()->statEntry(entry);
475 }
476 
477 
mkdir(const QUrl & url,int permissions)478 void NFSProtocolV2::mkdir(const QUrl& url, int permissions)
479 {
480     qCDebug(LOG_KIO_NFS) << url;
481 
482     const QString path(url.path());
483     const QFileInfo fileInfo(path);
484     if (isExportedDir(fileInfo.path())) {
485         setError(KIO::ERR_WRITE_ACCESS_DENIED, path);
486         return;
487     }
488 
489     const NFSFileHandle fh = getFileHandle(fileInfo.path());
490     if (fh.isInvalid() || fh.isBadLink()) {
491         setError(KIO::ERR_DOES_NOT_EXIST, path);
492         return;
493     }
494 
495     createargs createArgs;
496     fh.toFH(createArgs.where.dir);
497 
498     QByteArray tmpName = QFile::encodeName(fileInfo.fileName());
499     createArgs.where.name = tmpName.data();
500 
501     if (permissions == -1) {
502         createArgs.attributes.mode = 0755;
503     } else {
504         createArgs.attributes.mode = permissions;
505     }
506 
507     diropres dirres;
508     memset(&dirres, 0, sizeof(diropres));
509 
510     int clnt_stat = clnt_call(m_nfsClient, NFSPROC_MKDIR,
511                               (xdrproc_t) xdr_createargs, reinterpret_cast<caddr_t>(&createArgs),
512                               (xdrproc_t) xdr_diropres, reinterpret_cast<caddr_t>(&dirres),
513                               clnt_timeout);
514 
515     checkForError(clnt_stat, dirres.status, path);
516 }
517 
518 
del(const QUrl & url,bool)519 void NFSProtocolV2::del(const QUrl& url, bool)
520 {
521     int rpcStatus;
522     nfsstat nfsStatus;
523     remove(url.path(), rpcStatus, nfsStatus);
524     checkForError(rpcStatus, nfsStatus, url.path());
525 }
526 
527 
chmod(const QUrl & url,int permissions)528 void NFSProtocolV2::chmod(const QUrl& url, int permissions)
529 {
530     qCDebug(LOG_KIO_NFS) << url;
531 
532     const QString path(url.path());
533     if (isExportedDir(path)) {
534         setError(KIO::ERR_ACCESS_DENIED, path);
535         return;
536     }
537 
538     sattr attributes;
539     memset(&attributes, 0xFF, sizeof(attributes));
540     attributes.mode = permissions;
541 
542     int rpcStatus;
543     nfsstat result;
544     setAttr(path, attributes, rpcStatus, result);
545     checkForError(rpcStatus, result, path);
546 }
547 
548 
get(const QUrl & url)549 void NFSProtocolV2::get(const QUrl& url)
550 {
551     qCDebug(LOG_KIO_NFS) << url;
552 
553     const QString path(url.path());
554 
555     const NFSFileHandle fh = getFileHandle(path);
556     if (fh.isInvalid() || fh.isBadLink()) {
557         setError(KIO::ERR_DOES_NOT_EXIST, path);
558         return;
559     }
560 
561     readargs readArgs;
562     fh.toFH(readArgs.file);
563     readArgs.offset = 0;
564     readArgs.count = NFS_MAXDATA;
565     readArgs.totalcount = NFS_MAXDATA;
566 
567     readres readRes;
568     memset(&readRes, 0, sizeof(readres));
569 
570     char buf[NFS_MAXDATA];
571     readRes.readres_u.reply.data.data_val = buf;
572 
573     bool validRead = false;
574     int offset = 0;
575     QByteArray readBuffer;
576     do {
577         int clnt_stat = clnt_call(m_nfsClient, NFSPROC_READ,
578                                   (xdrproc_t) xdr_readargs, reinterpret_cast<caddr_t>(&readArgs),
579                                   (xdrproc_t) xdr_readres, reinterpret_cast<caddr_t>(&readRes),
580                                   clnt_timeout);
581 
582         if (!checkForError(clnt_stat, readRes.status, path)) {
583             return;
584         }
585 
586         if (readArgs.offset == 0) {
587             slave()->totalSize(readRes.readres_u.reply.attributes.size);
588 
589             const QMimeDatabase db;
590             const QMimeType type = db.mimeTypeForFileNameAndData(url.fileName(), readBuffer);
591             slave()->mimeType(type.name());
592         }
593 
594         offset = readRes.readres_u.reply.data.data_len;
595         readArgs.offset += offset;
596         if (offset > 0) {
597             validRead = true;
598 
599             readBuffer = QByteArray::fromRawData(readRes.readres_u.reply.data.data_val, offset);
600             slave()->data(readBuffer);
601             readBuffer.clear();
602 
603             slave()->processedSize(readArgs.offset);
604         }
605 
606     } while (offset > 0);
607 
608     if (validRead) {
609         slave()->data(QByteArray());
610         slave()->processedSize(readArgs.offset);
611     }
612 }
613 
614 
put(const QUrl & url,int _mode,KIO::JobFlags flags)615 void NFSProtocolV2::put(const QUrl& url, int _mode, KIO::JobFlags flags)
616 {
617     qCDebug(LOG_KIO_NFS) << url << _mode;
618 
619     const QString destPath(url.path());
620 
621     const QFileInfo fileInfo(destPath);
622     if (isExportedDir(fileInfo.path())) {
623         setError(KIO::ERR_WRITE_ACCESS_DENIED, destPath);
624         return;
625     }
626 
627     NFSFileHandle destFH = getFileHandle(destPath);
628     if (destFH.isBadLink()) {
629         setError(KIO::ERR_DOES_NOT_EXIST, destPath);
630         return;
631     }
632 
633     //the file exists and we don't want to overwrite
634     if (!destFH.isInvalid() && (!(flags & KIO::Overwrite))) {
635         setError(KIO::ERR_FILE_ALREADY_EXIST, destPath);
636         return;
637     }
638 
639     int rpcStatus;
640     diropres dirOpRes;
641     if (!create(destPath, _mode, rpcStatus, dirOpRes)) {
642         checkForError(rpcStatus, dirOpRes.status, fileInfo.fileName());
643         return;
644     }
645 
646     destFH = dirOpRes.diropres_u.diropres.file.data;
647 
648     writeargs writeArgs;
649     memset(&writeArgs, 0, sizeof(writeargs));
650     destFH.toFH(writeArgs.file);
651     writeArgs.beginoffset = 0;
652     writeArgs.totalcount = 0;
653     writeArgs.offset = 0;
654 
655     attrstat attrStat;
656 
657     int result = 0, bytesWritten = 0;
658     do {
659         // Request new data
660         slave()->dataReq();
661 
662         QByteArray buffer;
663         result = slave()->readData(buffer);
664 
665         char* data = buffer.data();
666         int bytesToWrite = buffer.size(), writeNow = 0;
667         if (result > 0) {
668             do {
669                 if (bytesToWrite > NFS_MAXDATA) {
670                     writeNow = NFS_MAXDATA;
671                 } else {
672                     writeNow = bytesToWrite;
673                 }
674 
675                 writeArgs.data.data_val = data;
676                 writeArgs.data.data_len = writeNow;
677 
678                 int clnt_stat = clnt_call(m_nfsClient, NFSPROC_WRITE,
679                                           (xdrproc_t) xdr_writeargs, reinterpret_cast<caddr_t>(&writeArgs),
680                                           (xdrproc_t) xdr_attrstat, reinterpret_cast<caddr_t>(&attrStat),
681                                           clnt_timeout);
682 
683                 if (!checkForError(clnt_stat, attrStat.status, fileInfo.fileName())) {
684                     return;
685                 }
686 
687                 bytesWritten += writeNow;
688                 writeArgs.offset = bytesWritten;
689 
690                 data = data + writeNow;
691                 bytesToWrite -= writeNow;
692             } while (bytesToWrite > 0);
693         }
694     } while (result > 0);
695 }
696 
697 
rename(const QUrl & src,const QUrl & dest,KIO::JobFlags _flags)698 void NFSProtocolV2::rename(const QUrl& src, const QUrl& dest, KIO::JobFlags _flags)
699 {
700     qCDebug(LOG_KIO_NFS) << src << "to" << dest;
701 
702     const QString srcPath(src.path());
703     if (isExportedDir(srcPath)) {
704         setError(KIO::ERR_CANNOT_RENAME, srcPath);
705         return;
706     }
707 
708     const QString destPath(dest.path());
709     if (isExportedDir(destPath)) {
710         setError(KIO::ERR_ACCESS_DENIED, destPath);
711         return;
712     }
713 
714     if (!getFileHandle(destPath).isInvalid() && (_flags & KIO::Overwrite) == 0) {
715         setError(KIO::ERR_FILE_ALREADY_EXIST, destPath);
716         return;
717     }
718 
719     int rpcStatus;
720     nfsstat nfsStatus;
721     rename(src.path(), destPath, rpcStatus, nfsStatus);
722     checkForError(rpcStatus, nfsStatus, destPath);
723 }
724 
725 
copySame(const QUrl & src,const QUrl & dest,int _mode,KIO::JobFlags _flags)726 void NFSProtocolV2::copySame(const QUrl& src, const QUrl& dest, int _mode, KIO::JobFlags _flags)
727 {
728     qCDebug(LOG_KIO_NFS) << src << "to" << dest;
729 
730     const QString srcPath(src.path());
731 
732     const NFSFileHandle srcFH = getFileHandle(srcPath);
733     if (srcFH.isInvalid()) {
734         setError(KIO::ERR_DOES_NOT_EXIST, srcPath);
735         return;
736     }
737 
738     const QString destPath = dest.path();
739     if (isExportedDir(QFileInfo(destPath).path())) {
740         setError(KIO::ERR_ACCESS_DENIED, destPath);
741         return;
742     }
743 
744     // The file exists and we don't want to overwrite
745     if (!getFileHandle(destPath).isInvalid() && (_flags & KIO::Overwrite) == 0) {
746         setError(KIO::ERR_FILE_ALREADY_EXIST, destPath);
747         return;
748     }
749 
750     // Is it a link? No need to copy the data then, just copy the link destination.
751     if (srcFH.isLink()) {
752         //get the link dest
753         int rpcStatus;
754         readlinkres readLinkRes;
755         char nameBuf[NFS_MAXPATHLEN];
756         if (!symLinkTarget(srcPath, rpcStatus, readLinkRes, nameBuf)) {
757             setError(KIO::ERR_DOES_NOT_EXIST, srcPath);
758             return;
759         }
760 
761         const QString linkPath = QString::fromLocal8Bit(readLinkRes.readlinkres_u.data);
762 
763         nfsstat linkRes;
764         symLink(linkPath, destPath, rpcStatus, linkRes);
765         checkForError(rpcStatus, linkRes, linkPath);
766         return;
767     }
768 
769     unsigned long resumeOffset = 0;
770     bool bResume = false;
771     const QString partFilePath = destPath + QLatin1String(".part");
772     const NFSFileHandle partFH = getFileHandle(partFilePath);
773     const bool bPartExists = !partFH.isInvalid();
774     const bool bMarkPartial = slave()->configValue(QStringLiteral("MarkPartial"), true);
775 
776     if (bPartExists) {
777         int rpcStatus;
778         diropres partRes;
779         if (lookupHandle(partFilePath, rpcStatus, partRes)) {
780             if (bMarkPartial && partRes.diropres_u.diropres.attributes.size > 0) {
781                 if (partRes.diropres_u.diropres.attributes.type == NFDIR) {
782                     setError(KIO::ERR_IS_DIRECTORY, partFilePath);
783                     return;
784                 }
785 
786                 bResume = slave()->canResume(partRes.diropres_u.diropres.attributes.size);
787                 if (bResume) {
788                     resumeOffset = partRes.diropres_u.diropres.attributes.size;
789                 }
790             }
791         }
792 
793         // Remove the part file if we are not resuming
794         if (!bResume) {
795             if (!remove(partFilePath)) {
796                 qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring...";
797             }
798         }
799     }
800 
801     // Create the file if we are not resuming a parted transfer,
802     // or if we are not using part files(bResume is false in that case)
803     NFSFileHandle destFH;
804     if (!bResume) {
805         QString createPath;
806         if (bMarkPartial) {
807             createPath = partFilePath;
808         } else {
809             createPath = destPath;
810         }
811 
812         int rpcStatus;
813         diropres dirOpRes;
814         if (!create(createPath, _mode, rpcStatus, dirOpRes)) {
815             checkForError(rpcStatus, dirOpRes.status, createPath);
816             return;
817         }
818 
819         destFH = dirOpRes.diropres_u.diropres.file.data;
820     } else {
821         // Since we are resuming it's implied that we are using a part file,
822         // which should exist at this point.
823         destFH = getFileHandle(partFilePath);
824 
825         qCDebug(LOG_KIO_NFS) << "Resuming old transfer";
826     }
827 
828     char buf[NFS_MAXDATA];
829 
830     writeargs writeArgs;
831     destFH.toFH(writeArgs.file);
832     writeArgs.beginoffset = 0;
833     writeArgs.totalcount = 0;
834     writeArgs.offset = 0;
835     writeArgs.data.data_val = buf;
836 
837     readargs readArgs;
838     srcFH.toFH(readArgs.file);
839     readArgs.offset = 0;
840     readArgs.count = NFS_MAXDATA;
841     readArgs.totalcount = NFS_MAXDATA;
842 
843     if (bResume) {
844         writeArgs.offset = resumeOffset;
845         readArgs.offset = resumeOffset;
846     }
847 
848     readres readRes;
849     memset(&readRes, 0, sizeof(readres));
850     readRes.readres_u.reply.data.data_val = buf;
851 
852     attrstat attrStat;
853     memset(&attrStat, 0, sizeof(attrstat));
854 
855     bool error = false;
856     int bytesRead = 0;
857     do {
858         int clnt_stat = clnt_call(m_nfsClient, NFSPROC_READ,
859                                   (xdrproc_t) xdr_readargs, reinterpret_cast<caddr_t>(&readArgs),
860                                   (xdrproc_t) xdr_readres, reinterpret_cast<caddr_t>(&readRes),
861                                   clnt_timeout);
862 
863         if (!checkForError(clnt_stat, readRes.status, destPath)) {
864             error = true;
865             break;
866         }
867 
868         bytesRead = readRes.readres_u.reply.data.data_len;
869 
870         // We should only send out the total size and mimetype at the start of the transfer
871         if (readArgs.offset == 0 || (bResume && writeArgs.offset == resumeOffset)) {
872             slave()->totalSize(readRes.readres_u.reply.attributes.size);
873 
874             QMimeDatabase db;
875             QMimeType type = db.mimeTypeForFileNameAndData(src.fileName(), QByteArray::fromRawData(writeArgs.data.data_val, bytesRead));
876             slave()->mimeType(type.name());
877         }
878 
879 
880         if (bytesRead > 0) {
881             readArgs.offset += bytesRead;
882 
883             writeArgs.data.data_len = bytesRead;
884 
885             clnt_stat = clnt_call(m_nfsClient, NFSPROC_WRITE,
886                                   (xdrproc_t) xdr_writeargs, reinterpret_cast<caddr_t>(&writeArgs),
887                                   (xdrproc_t) xdr_attrstat, reinterpret_cast<caddr_t>(&attrStat),
888                                   clnt_timeout);
889 
890             if (!checkForError(clnt_stat, attrStat.status, destPath)) {
891                 error = true;
892                 break;
893             }
894 
895             writeArgs.offset += bytesRead;
896 
897             slave()->processedSize(readArgs.offset);
898         }
899     } while (bytesRead > 0);
900 
901     if (error) {
902         if (bMarkPartial) {
903             // Remove the part file if it's smaller than the minimum keep size.
904             const unsigned int size = slave()->configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE);
905             if (writeArgs.offset <  size) {
906                 if (!remove(partFilePath)) {
907                     qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring...";
908                 }
909             }
910         }
911     } else {
912         // Rename partial file to its original name.
913         if (bMarkPartial) {
914             // Remove the destination file(if it exists)
915             if (!getFileHandle(destPath).isInvalid() && !remove(destPath)) {
916                 qCDebug(LOG_KIO_NFS) << "Could not remove destination file" << destPath << ", ignoring...";
917             }
918 
919             if (!rename(partFilePath, destPath)) {
920                 qCDebug(LOG_KIO_NFS) << "Failed to rename" << partFilePath << "to" << destPath;
921                 setError(KIO::ERR_CANNOT_RENAME_PARTIAL, partFilePath);
922                 return;
923             }
924         }
925 
926         // Restore modification time
927         int rpcStatus;
928         attrstat attrRes;
929         if (getAttr(srcPath, rpcStatus, attrRes)) {
930             sattr attributes;
931             memset(&attributes, 0xFF, sizeof(attributes));
932             attributes.mtime.seconds = attrRes.attrstat_u.attributes.mtime.seconds;
933             attributes.mtime.useconds = attrRes.attrstat_u.attributes.mtime.useconds;
934 
935             nfsstat attrSetRes;
936             if (!setAttr(destPath, attributes, rpcStatus, attrSetRes)) {
937                 qCDebug(LOG_KIO_NFS) << "Failed to restore mtime, ignoring..." << rpcStatus << attrSetRes;
938             }
939         }
940 
941         qCDebug(LOG_KIO_NFS) << "Copied" << writeArgs.offset << "bytes of data";
942 
943         slave()->processedSize(readArgs.offset);
944     }
945 }
946 
947 
copyFrom(const QUrl & src,const QUrl & dest,int _mode,KIO::JobFlags _flags)948 void NFSProtocolV2::copyFrom(const QUrl& src, const QUrl& dest, int _mode, KIO::JobFlags _flags)
949 {
950     qCDebug(LOG_KIO_NFS) << src << "to" << dest;
951 
952     const QString srcPath(src.path());
953 
954     const NFSFileHandle srcFH = getFileHandle(srcPath);
955     if (srcFH.isInvalid()) {
956         setError(KIO::ERR_DOES_NOT_EXIST, srcPath);
957         return;
958     }
959 
960     const QString destPath(dest.path());
961 
962     // The file exists and we don't want to overwrite
963     if (QFile::exists(destPath) && (_flags & KIO::Overwrite) == 0) {
964         setError(KIO::ERR_FILE_ALREADY_EXIST, destPath);
965         return;
966     }
967 
968     // Is it a link? No need to copy the data then, just copy the link destination.
969     if (srcFH.isLink()) {
970         qCDebug(LOG_KIO_NFS) << "Is a link";
971 
972         int rpcStatus;
973         readlinkres readLinkRes;
974         char nameBuf[NFS_MAXPATHLEN];
975         if (!symLinkTarget(srcPath, rpcStatus, readLinkRes, nameBuf)) {
976             setError(KIO::ERR_DOES_NOT_EXIST, srcPath);
977             return;
978         }
979 
980         QFile::link(QString::fromLocal8Bit(readLinkRes.readlinkres_u.data), destPath);
981         return;
982     }
983 
984     bool bResume = false;
985     const QFileInfo partInfo(destPath + QLatin1String(".part"));
986     const bool bPartExists = partInfo.exists();
987     const bool bMarkPartial = slave()->configValue(QStringLiteral("MarkPartial"), true);
988 
989     if (bMarkPartial && bPartExists && partInfo.size() > 0) {
990         if (partInfo.isDir()) {
991             setError(KIO::ERR_IS_DIRECTORY, partInfo.absoluteFilePath());
992             return;
993         }
994 
995         bResume = slave()->canResume(partInfo.size());
996     }
997 
998     if (bPartExists && !bResume) {
999         QFile::remove(partInfo.absoluteFilePath());
1000     }
1001 
1002     QFile::OpenMode openMode;
1003     QString outFileName;
1004     if (bResume) {
1005         outFileName = partInfo.absoluteFilePath();
1006         openMode = QFile::WriteOnly | QFile::Append;
1007     } else {
1008         outFileName = (bMarkPartial ? partInfo.absoluteFilePath() : destPath);
1009         openMode = QFile::WriteOnly | QFile::Truncate;
1010     }
1011 
1012     QFile destFile(outFileName);
1013     if (!bResume) {
1014         QFile::Permissions perms;
1015         if (_mode == -1) {
1016             perms = QFile::ReadOwner | QFile::WriteOwner;
1017         } else {
1018             perms = KIO::convertPermissions(_mode | QFile::WriteOwner);
1019         }
1020         destFile.setPermissions(perms);
1021     }
1022 
1023     if (!destFile.open(openMode)) {
1024         switch (destFile.error()) {
1025         case QFile::OpenError:
1026             if (bResume) {
1027                 setError(KIO::ERR_CANNOT_RESUME, destPath);
1028             } else {
1029                 setError(KIO::ERR_CANNOT_OPEN_FOR_WRITING, destPath);
1030             }
1031             break;
1032         case QFile::PermissionsError:
1033             setError(KIO::ERR_WRITE_ACCESS_DENIED, destPath);
1034             break;
1035         default:
1036             setError(KIO::ERR_CANNOT_OPEN_FOR_WRITING, destPath);
1037             break;
1038         }
1039         return;
1040     }
1041 
1042     char buf[NFS_MAXDATA];
1043 
1044     readargs readArgs;
1045     srcFH.toFH(readArgs.file);
1046     if (bResume) {
1047         readArgs.offset = partInfo.size();
1048     } else {
1049         readArgs.offset = 0;
1050     }
1051     readArgs.count = NFS_MAXDATA;
1052     readArgs.totalcount = NFS_MAXDATA;
1053 
1054     readres readRes;
1055     memset(&readRes, 0, sizeof(readres));
1056     readRes.readres_u.reply.data.data_val = buf;
1057 
1058     attrstat attrStat;
1059     memset(&attrStat, 0, sizeof(attrstat));
1060 
1061     bool error = false;
1062     int bytesRead = 0;
1063     do {
1064         int clnt_stat = clnt_call(m_nfsClient, NFSPROC_READ,
1065                                   (xdrproc_t) xdr_readargs, reinterpret_cast<caddr_t>(&readArgs),
1066                                   (xdrproc_t) xdr_readres, reinterpret_cast<caddr_t>(&readRes),
1067                                   clnt_timeout);
1068 
1069         if (!checkForError(clnt_stat, readRes.status, destPath)) {
1070             error = true;
1071             break;
1072         }
1073 
1074         bytesRead = readRes.readres_u.reply.data.data_len;
1075 
1076         if (readArgs.offset == 0) {
1077             slave()->totalSize(readRes.readres_u.reply.attributes.size);
1078 
1079             QMimeDatabase db;
1080             QMimeType type = db.mimeTypeForFileNameAndData(src.fileName(), QByteArray::fromRawData(readRes.readres_u.reply.data.data_val, bytesRead));
1081             slave()->mimeType(type.name());
1082         }
1083 
1084 
1085         if (bytesRead > 0) {
1086             readArgs.offset += bytesRead;
1087 
1088             if (destFile.write(readRes.readres_u.reply.data.data_val, bytesRead) != bytesRead) {
1089                 setError(KIO::ERR_CANNOT_WRITE, destPath);
1090 
1091                 error = true;
1092                 break;
1093             }
1094 
1095             slave()->processedSize(readArgs.offset);
1096         }
1097     } while (bytesRead > 0);
1098 
1099     // Close the file so we can modify the modification time later.
1100     destFile.close();
1101 
1102     if (error) {
1103         if (bMarkPartial) {
1104             // Remove the part file if it's smaller than the minimum keep
1105             const int size = slave()->configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE);
1106             if (partInfo.size() <  size) {
1107                 QFile::remove(partInfo.absoluteFilePath());
1108             }
1109         }
1110     } else {
1111         // Rename partial file to its original name.
1112         if (bMarkPartial) {
1113             const QString sPart = partInfo.absoluteFilePath();
1114             if (QFile::exists(destPath)) {
1115                 QFile::remove(destPath);
1116             }
1117             if (!QFile::rename(sPart, destPath)) {
1118                 qCDebug(LOG_KIO_NFS) << "Failed to rename" << sPart << "to" << destPath;
1119                 setError(KIO::ERR_CANNOT_RENAME_PARTIAL, sPart);
1120                 return;
1121             }
1122         }
1123 
1124         // Restore the mtime on the file.
1125         const QString mtimeStr = slave()->metaData("modified");
1126         if (!mtimeStr.isEmpty()) {
1127             QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate);
1128             if (dt.isValid()) {
1129                 qCDebug(LOG_KIO_NFS) << "Setting modification time to" << dt.toSecsSinceEpoch();
1130 
1131                 struct utimbuf utbuf;
1132                 utbuf.actime = QFileInfo(destPath).lastRead().toSecsSinceEpoch(); // access time, unchanged
1133                 utbuf.modtime = dt.toSecsSinceEpoch(); // modification time
1134                 utime(QFile::encodeName(destPath).constData(), &utbuf);
1135             }
1136         }
1137 
1138         qCDebug(LOG_KIO_NFS) << "Copied" << readArgs.offset << "bytes of data";
1139 
1140         slave()->processedSize(readArgs.offset);
1141     }
1142 }
1143 
1144 
copyTo(const QUrl & src,const QUrl & dest,int _mode,KIO::JobFlags _flags)1145 void NFSProtocolV2::copyTo(const QUrl& src, const QUrl& dest, int _mode, KIO::JobFlags _flags)
1146 {
1147     qCDebug(LOG_KIO_NFS) << src << "to" << dest;
1148 
1149     // The source does not exist, how strange.
1150     const QString srcPath(src.path());
1151     if (!QFile::exists(srcPath)) {
1152         setError(KIO::ERR_DOES_NOT_EXIST, srcPath);
1153         return;
1154     }
1155 
1156     const QString destPath(dest.path());
1157     if (isExportedDir(destPath)) {
1158         setError(KIO::ERR_ACCESS_DENIED, destPath);
1159         return;
1160     }
1161 
1162     // The file exists and we don't want to overwrite.
1163     if (!getFileHandle(destPath).isInvalid() && (_flags & KIO::Overwrite) == 0) {
1164         setError(KIO::ERR_FILE_ALREADY_EXIST, destPath);
1165         return;
1166     }
1167 
1168     // Is it a link? No need to copy the data then, just copy the link destination.
1169     const QString symlinkTarget = QFile::symLinkTarget(srcPath);
1170     if (!symlinkTarget.isEmpty()) {
1171         int rpcStatus;
1172         nfsstat linkRes;
1173         symLink(symlinkTarget, destPath, rpcStatus, linkRes);
1174         checkForError(rpcStatus, linkRes, symlinkTarget);
1175         return;
1176     }
1177 
1178     unsigned long resumeOffset = 0;
1179     bool bResume = false;
1180     const QString partFilePath = destPath + QLatin1String(".part");
1181     const NFSFileHandle partFH = getFileHandle(partFilePath);
1182     const bool bPartExists = !partFH.isInvalid();
1183     const bool bMarkPartial = slave()->configValue(QStringLiteral("MarkPartial"), true);
1184 
1185     if (bPartExists) {
1186         int rpcStatus;
1187         diropres partRes;
1188         if (lookupHandle(partFilePath, rpcStatus, partRes)) {
1189             if (bMarkPartial && partRes.diropres_u.diropres.attributes.size > 0) {
1190                 if (partRes.diropres_u.diropres.attributes.type == NFDIR) {
1191                     setError(KIO::ERR_IS_DIRECTORY, partFilePath);
1192                     return;
1193                 }
1194 
1195                 bResume = slave()->canResume(partRes.diropres_u.diropres.attributes.size);
1196                 if (bResume) {
1197                     resumeOffset = partRes.diropres_u.diropres.attributes.size;
1198                 }
1199             }
1200         }
1201 
1202         // Remove the part file if we are not resuming
1203         if (!bResume) {
1204             if (!remove(partFilePath)) {
1205                 qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring...";
1206             }
1207         }
1208     }
1209 
1210     // Open the source file
1211     QFile srcFile(srcPath);
1212     if (!srcFile.open(QIODevice::ReadOnly)) {
1213         setError(KIO::ERR_CANNOT_OPEN_FOR_READING, srcPath);
1214         return;
1215     }
1216 
1217     // Create the file if we are not resuming a parted transfer,
1218     // or if we are not using part files(bResume is false in that case)
1219     NFSFileHandle destFH;
1220     if (!bResume) {
1221         QString createPath;
1222         if (bMarkPartial) {
1223             createPath = partFilePath;
1224         } else {
1225             createPath = destPath;
1226         }
1227 
1228         int rpcStatus;
1229         diropres dirOpRes;
1230         if (!create(createPath, _mode, rpcStatus, dirOpRes)) {
1231             checkForError(rpcStatus, dirOpRes.status, createPath);
1232             return;
1233         }
1234 
1235         destFH = dirOpRes.diropres_u.diropres.file.data;
1236     } else {
1237         // Since we are resuming it's implied that we are using a part file,
1238         // which should exist at this point.
1239         destFH = getFileHandle(partFilePath);
1240 
1241         qCDebug(LOG_KIO_NFS) << "Resuming old transfer";
1242     }
1243 
1244     // Send the total size to the slave.
1245     slave()->totalSize(srcFile.size());
1246 
1247     // Set up write arguments.
1248     char buf[NFS_MAXDATA];
1249 
1250     writeargs writeArgs;
1251     memset(&writeArgs, 0, sizeof(writeargs));
1252     destFH.toFH(writeArgs.file);
1253     writeArgs.data.data_val = buf;
1254     writeArgs.beginoffset = 0;
1255     writeArgs.totalcount = 0;
1256     if (bResume) {
1257         writeArgs.offset = resumeOffset;
1258     } else {
1259         writeArgs.offset = 0;
1260     }
1261 
1262     attrstat attrStat;
1263     memset(&attrStat, 0, sizeof(attrstat));
1264 
1265     bool error = false;
1266     int bytesRead = 0;
1267     do {
1268         bytesRead = srcFile.read(writeArgs.data.data_val, NFS_MAXDATA);
1269         if (bytesRead < 0) {
1270             setError(KIO::ERR_CANNOT_READ, srcPath);
1271 
1272             error = true;
1273             break;
1274         }
1275 
1276         if (bytesRead > 0) {
1277             writeArgs.data.data_len = bytesRead;
1278 
1279             int clnt_stat = clnt_call(m_nfsClient, NFSPROC_WRITE,
1280                                       (xdrproc_t) xdr_writeargs, reinterpret_cast<caddr_t>(&writeArgs),
1281                                       (xdrproc_t) xdr_attrstat, reinterpret_cast<caddr_t>(&attrStat),
1282                                       clnt_timeout);
1283 
1284             if (!checkForError(clnt_stat, attrStat.status, destPath)) {
1285                 error = true;
1286                 break;
1287             }
1288 
1289             writeArgs.offset += bytesRead;
1290 
1291             slave()->processedSize(writeArgs.offset);
1292         }
1293     } while (bytesRead > 0);
1294 
1295     if (error) {
1296         if (bMarkPartial) {
1297             // Remove the part file if it's smaller than the minimum keep size.
1298             const unsigned int size = slave()->configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE);
1299             if (writeArgs.offset <  size) {
1300                 if (!remove(partFilePath)) {
1301                     qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring...";
1302                 }
1303             }
1304         }
1305     } else {
1306         // Rename partial file to its original name.
1307         if (bMarkPartial) {
1308             // Remove the destination file(if it exists)
1309             if (!getFileHandle(destPath).isInvalid() && !remove(destPath)) {
1310                 qCDebug(LOG_KIO_NFS) << "Could not remove destination file" << destPath << ", ignoring...";
1311             }
1312 
1313             if (!rename(partFilePath, destPath)) {
1314                 qCDebug(LOG_KIO_NFS) << "Failed to rename" << partFilePath << "to" << destPath;
1315                 setError(KIO::ERR_CANNOT_RENAME_PARTIAL, partFilePath);
1316                 return;
1317             }
1318         }
1319 
1320         // Restore the mtime on the file.
1321         const QString mtimeStr = slave()->metaData("modified");
1322         if (!mtimeStr.isEmpty()) {
1323             QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate);
1324             if (dt.isValid()) {
1325                 sattr attributes;
1326                 memset(&attributes, 0xFF, sizeof(attributes));
1327                 attributes.mtime.seconds = dt.toSecsSinceEpoch();
1328                 attributes.mtime.useconds = attributes.mtime.seconds * 1000000ULL;
1329 
1330                 int rpcStatus;
1331                 nfsstat attrSetRes;
1332                 if (!setAttr(destPath, attributes, rpcStatus, attrSetRes)) {
1333                     qCDebug(LOG_KIO_NFS) << "Failed to restore mtime, ignoring..." << rpcStatus << attrSetRes;
1334                 }
1335             }
1336         }
1337 
1338         qCDebug(LOG_KIO_NFS) << "Copied" << writeArgs.offset << "bytes of data";
1339 
1340         slave()->processedSize(writeArgs.offset);
1341     }
1342 }
1343 
symlink(const QString & target,const QUrl & dest,KIO::JobFlags flags)1344 void NFSProtocolV2::symlink(const QString& target, const QUrl& dest, KIO::JobFlags flags)
1345 {
1346     const QString destPath(dest.path());
1347     if (isExportedDir(QFileInfo(destPath).path())) {
1348         setError(KIO::ERR_ACCESS_DENIED, destPath);
1349         return;
1350     }
1351 
1352     if (!getFileHandle(destPath).isInvalid() && (flags & KIO::Overwrite) == 0) {
1353         setError(KIO::ERR_FILE_ALREADY_EXIST, destPath);
1354         return;
1355     }
1356 
1357     int rpcStatus;
1358     nfsstat res;
1359     symLink(target, destPath, rpcStatus, res);
1360     checkForError(rpcStatus, res, destPath);
1361 }
1362 
1363 
create(const QString & path,int mode,int & rpcStatus,diropres & result)1364 bool NFSProtocolV2::create(const QString& path, int mode, int& rpcStatus, diropres& result)
1365 {
1366     memset(&rpcStatus, 0, sizeof(int));
1367     memset(&result, 0, sizeof(result));
1368 
1369     if (!isConnected()) {
1370         result.status = NFSERR_ACCES;
1371         return false;
1372     }
1373 
1374     const QFileInfo fileInfo(path);
1375     if (isExportedDir(fileInfo.path())) {
1376         result.status = NFSERR_ACCES;
1377         return false;
1378     }
1379 
1380     const NFSFileHandle directoryFH = getFileHandle(fileInfo.path());
1381     if (directoryFH.isInvalid()) {
1382         result.status = NFSERR_NOENT;
1383         return false;
1384     }
1385 
1386     QByteArray tmpName = QFile::encodeName(fileInfo.fileName());
1387 
1388     createargs args;
1389     directoryFH.toFH(args.where.dir);
1390     args.where.name = tmpName.data();
1391 
1392     memset(&args.attributes, 0xFF, sizeof(sattr));
1393     if (mode == -1) {
1394         args.attributes.mode = 0644;
1395     } else {
1396         args.attributes.mode = mode;
1397     }
1398     args.attributes.uid = geteuid();
1399     args.attributes.gid = getegid();
1400     args.attributes.size = 0;
1401 
1402     rpcStatus = clnt_call(m_nfsClient, NFSPROC_CREATE,
1403                           (xdrproc_t) xdr_createargs, reinterpret_cast<caddr_t>(&args),
1404                           (xdrproc_t) xdr_diropres, reinterpret_cast<caddr_t>(&result),
1405                           clnt_timeout);
1406 
1407     return (rpcStatus == RPC_SUCCESS && result.status == NFS_OK);
1408 }
1409 
getAttr(const QString & path,int & rpcStatus,attrstat & result)1410 bool NFSProtocolV2::getAttr(const QString& path, int& rpcStatus, attrstat& result)
1411 {
1412     memset(&rpcStatus, 0, sizeof(int));
1413     memset(&result, 0, sizeof(result));
1414 
1415     if (!isConnected()) {
1416         result.status = NFSERR_ACCES;
1417         return false;
1418     }
1419 
1420     const NFSFileHandle fileFH = getFileHandle(path);
1421     if (fileFH.isInvalid()) {
1422         result.status = NFSERR_NOENT;
1423         return false;
1424     }
1425 
1426     nfs_fh fh;
1427     fileFH.toFH(fh);
1428 
1429     rpcStatus = clnt_call(m_nfsClient, NFSPROC_GETATTR,
1430                           (xdrproc_t) xdr_nfs_fh, reinterpret_cast<caddr_t>(&fh),
1431                           (xdrproc_t) xdr_attrstat, reinterpret_cast<caddr_t>(&result),
1432                           clnt_timeout);
1433 
1434     return (rpcStatus == RPC_SUCCESS && result.status == NFS_OK);
1435 }
1436 
lookupHandle(const QString & path,int & rpcStatus,diropres & result)1437 bool NFSProtocolV2::lookupHandle(const QString& path, int& rpcStatus, diropres& result)
1438 {
1439     memset(&rpcStatus, 0, sizeof(int));
1440     memset(&result, 0, sizeof(result));
1441 
1442     if (!isConnected()) {
1443         result.status = NFSERR_ACCES;
1444         return false;
1445     }
1446 
1447     const QFileInfo fileInfo(path);
1448 
1449     const NFSFileHandle parentFH = getFileHandle(fileInfo.path());
1450     if (parentFH.isInvalid()) {
1451         result.status = NFSERR_NOENT;
1452         return false;
1453     }
1454 
1455     QByteArray tmpName = QFile::encodeName(fileInfo.fileName());
1456 
1457     diropargs dirargs;
1458     memset(&dirargs, 0, sizeof(diropargs));
1459     parentFH.toFH(dirargs.dir);
1460     dirargs.name = tmpName.data();
1461 
1462     memset(&result, 0, sizeof(diropres));
1463 
1464     rpcStatus = clnt_call(m_nfsClient, NFSPROC_LOOKUP,
1465                           (xdrproc_t) xdr_diropargs, reinterpret_cast<caddr_t>(&dirargs),
1466                           (xdrproc_t) xdr_diropres, reinterpret_cast<caddr_t>(&result),
1467                           clnt_timeout);
1468 
1469     return (rpcStatus == RPC_SUCCESS && result.status == NFS_OK);
1470 }
1471 
symLinkTarget(const QString & path,int & rpcStatus,readlinkres & result,char * dataBuffer)1472 bool NFSProtocolV2::symLinkTarget(const QString& path, int& rpcStatus, readlinkres& result, char* dataBuffer)
1473 {
1474     const NFSFileHandle fh = getFileHandle(path);
1475 
1476     nfs_fh nfsFH;
1477     if (fh.isLink() && !fh.isBadLink()) {
1478         fh.toFHLink(nfsFH);
1479     } else {
1480         fh.toFH(nfsFH);
1481     }
1482 
1483     result.readlinkres_u.data = dataBuffer;
1484 
1485     rpcStatus = clnt_call(m_nfsClient, NFSPROC_READLINK,
1486                           (xdrproc_t) xdr_nfs_fh, reinterpret_cast<caddr_t>(&nfsFH),
1487                           (xdrproc_t) xdr_readlinkres, reinterpret_cast<caddr_t>(&result),
1488                           clnt_timeout);
1489 
1490     return (rpcStatus == RPC_SUCCESS && result.status == NFS_OK);
1491 }
1492 
remove(const QString & path)1493 bool NFSProtocolV2::remove(const QString& path)
1494 {
1495     int rpcStatus;
1496     nfsstat nfsStatus;
1497 
1498     return remove(path, rpcStatus, nfsStatus);
1499 }
1500 
remove(const QString & path,int & rpcStatus,nfsstat & result)1501 bool NFSProtocolV2::remove(const QString& path, int& rpcStatus, nfsstat& result)
1502 {
1503     qCDebug(LOG_KIO_NFS) << path;
1504 
1505     memset(&rpcStatus, 0, sizeof(int));
1506     memset(&result, 0, sizeof(result));
1507 
1508     if (!isConnected()) {
1509         result = NFSERR_PERM;
1510         return false;
1511     }
1512 
1513     const QFileInfo fileInfo(path);
1514     if (isExportedDir(fileInfo.path())) {
1515         result = NFSERR_ACCES;
1516         return false;
1517     }
1518 
1519     const NFSFileHandle directoryFH = getFileHandle(fileInfo.path());
1520     if (directoryFH.isInvalid()) {
1521         result = NFSERR_NOENT;
1522         return false;
1523     }
1524 
1525     int rpcLookupStatus;
1526     diropres lookupRes;
1527     if (!lookupHandle(path, rpcLookupStatus, lookupRes)) {
1528         result = NFSERR_NOENT;
1529         return false;
1530     }
1531 
1532     QByteArray tmpName = QFile::encodeName(fileInfo.fileName());
1533 
1534     diropargs dirargs;
1535     memset(&dirargs, 0, sizeof(diropargs));
1536     directoryFH.toFH(dirargs.dir);
1537     dirargs.name = tmpName.data();
1538 
1539     if (lookupRes.diropres_u.diropres.attributes.type != NFDIR) {
1540         rpcStatus = clnt_call(m_nfsClient, NFSPROC_REMOVE,
1541                               (xdrproc_t) xdr_diropargs, reinterpret_cast<caddr_t>(&dirargs),
1542                               (xdrproc_t) xdr_nfsstat, reinterpret_cast<caddr_t>(&result),
1543                               clnt_timeout);
1544     } else {
1545         rpcStatus = clnt_call(m_nfsClient, NFSPROC_RMDIR,
1546                               (xdrproc_t) xdr_diropargs, reinterpret_cast<caddr_t>(&dirargs),
1547                               (xdrproc_t) xdr_nfsstat, reinterpret_cast<caddr_t>(&result),
1548                               clnt_timeout);
1549     }
1550 
1551     bool ret = (rpcStatus == RPC_SUCCESS && result == NFS_OK);
1552     if (ret) {
1553         removeFileHandle(path);
1554     }
1555 
1556     return ret;
1557 }
1558 
rename(const QString & src,const QString & dest)1559 bool NFSProtocolV2::rename(const QString& src, const QString& dest)
1560 {
1561     int rpcStatus;
1562     nfsstat result;
1563 
1564     return rename(src, dest, rpcStatus, result);
1565 }
1566 
rename(const QString & src,const QString & dest,int & rpcStatus,nfsstat & result)1567 bool NFSProtocolV2::rename(const QString& src, const QString& dest, int& rpcStatus, nfsstat& result)
1568 {
1569     qCDebug(LOG_KIO_NFS) << src << dest;
1570 
1571     memset(&rpcStatus, 0, sizeof(int));
1572     memset(&result, 0, sizeof(result));
1573 
1574     const QFileInfo srcFileInfo(src);
1575     if (isExportedDir(srcFileInfo.path())) {
1576         result = NFSERR_ACCES;
1577         return false;
1578     }
1579 
1580     const NFSFileHandle srcDirectoryFH = getFileHandle(srcFileInfo.path());
1581     if (srcDirectoryFH.isInvalid()) {
1582         result = NFSERR_NOENT;
1583         return false;
1584     }
1585 
1586     const QFileInfo destFileInfo(dest);
1587     if (isExportedDir(destFileInfo.path())) {
1588         result = NFSERR_ACCES;
1589         return false;
1590     }
1591 
1592     const NFSFileHandle destDirectoryFH = getFileHandle(destFileInfo.path());
1593     if (destDirectoryFH.isInvalid()) {
1594         result = NFSERR_NOENT;
1595         return false;
1596     }
1597 
1598     renameargs renameArgs;
1599     memset(&renameArgs, 0, sizeof(renameargs));
1600 
1601     QByteArray srcByteName = QFile::encodeName(srcFileInfo.fileName());
1602     srcDirectoryFH.toFH(renameArgs.from.dir);
1603     renameArgs.from.name = srcByteName.data();
1604 
1605     QByteArray destByteName = QFile::encodeName(destFileInfo.fileName());
1606     destDirectoryFH.toFH(renameArgs.to.dir);
1607     renameArgs.to.name = destByteName.data();
1608 
1609     rpcStatus = clnt_call(m_nfsClient, NFSPROC_RENAME,
1610                           (xdrproc_t) xdr_renameargs, reinterpret_cast<caddr_t>(&renameArgs),
1611                           (xdrproc_t) xdr_nfsstat, reinterpret_cast<caddr_t>(&result),
1612                           clnt_timeout);
1613 
1614     bool ret = (rpcStatus == RPC_SUCCESS && result == NFS_OK);
1615     if (ret) {
1616         // Can we actually find the new handle?
1617         int lookupStatus;
1618         diropres lookupRes;
1619         if (lookupHandle(dest, lookupStatus, lookupRes)) {
1620             // Remove the old file, and add the new one
1621             removeFileHandle(src);
1622             addFileHandle(dest, lookupRes.diropres_u.diropres.file);
1623         }
1624     }
1625 
1626     return ret;
1627 }
1628 
setAttr(const QString & path,const sattr & attributes,int & rpcStatus,nfsstat & result)1629 bool NFSProtocolV2::setAttr(const QString& path, const sattr& attributes, int& rpcStatus, nfsstat& result)
1630 {
1631     qCDebug(LOG_KIO_NFS) << path;
1632 
1633     memset(&rpcStatus, 0, sizeof(int));
1634     memset(&result, 0, sizeof(result));
1635 
1636     const NFSFileHandle fh = getFileHandle(path);
1637     if (fh.isInvalid()) {
1638         result = NFSERR_NOENT;
1639         return false;
1640     }
1641 
1642     sattrargs sAttrArgs;
1643     fh.toFH(sAttrArgs.file);
1644     memcpy(&sAttrArgs.attributes, &attributes, sizeof(attributes));
1645 
1646     rpcStatus = clnt_call(m_nfsClient, NFSPROC_SETATTR,
1647                           (xdrproc_t) xdr_sattrargs, reinterpret_cast<caddr_t>(&sAttrArgs),
1648                           (xdrproc_t) xdr_nfsstat, reinterpret_cast<caddr_t>(&result),
1649                           clnt_timeout);
1650 
1651     return (rpcStatus == RPC_SUCCESS && result == NFS_OK);
1652 }
1653 
symLink(const QString & target,const QString & dest,int & rpcStatus,nfsstat & result)1654 bool NFSProtocolV2::symLink(const QString& target, const QString& dest, int& rpcStatus, nfsstat& result)
1655 {
1656     qCDebug(LOG_KIO_NFS) << target << dest;
1657 
1658     memset(&rpcStatus, 0, sizeof(int));
1659     memset(&result, 0, sizeof(result));
1660 
1661     // Remove dest first, we don't really care about the return value at this point,
1662     // the symlink call will fail if dest was not removed correctly.
1663     remove(dest);
1664 
1665 
1666     const QFileInfo fileInfo(dest);
1667     if (isExportedDir(fileInfo.path())) {
1668         result = NFSERR_ACCES;
1669         return false;
1670     }
1671 
1672     const NFSFileHandle fh = getFileHandle(fileInfo.path());
1673     if (fh.isInvalid()) {
1674         result = NFSERR_NOENT;
1675         return false;
1676     }
1677 
1678     QByteArray fromBytes = QFile::encodeName(fileInfo.fileName());
1679     QByteArray toBytes = QFile::encodeName(target);
1680 
1681     symlinkargs symLinkArgs;
1682     memset(&symLinkArgs, 0, sizeof(symLinkArgs));
1683 
1684     fh.toFH(symLinkArgs.from.dir);
1685     symLinkArgs.from.name = fromBytes.data();
1686     symLinkArgs.to = toBytes.data();
1687 
1688     rpcStatus = clnt_call(m_nfsClient, NFSPROC_SYMLINK,
1689                           (xdrproc_t) xdr_symlinkargs, reinterpret_cast<caddr_t>(&symLinkArgs),
1690                           (xdrproc_t) xdr_nfsstat, reinterpret_cast<caddr_t>(&result),
1691                           clnt_timeout);
1692 
1693     // Add the new handle to the cache
1694     NFSFileHandle destFH = getFileHandle(dest);
1695     if (!destFH.isInvalid()) {
1696         addFileHandle(dest, destFH);
1697     }
1698 
1699     return (rpcStatus == RPC_SUCCESS && result == NFS_OK);
1700 }
1701 
1702 
completeUDSEntry(KIO::UDSEntry & entry,const fattr & attributes)1703 void NFSProtocolV2::completeUDSEntry(KIO::UDSEntry& entry, const fattr& attributes)
1704 {
1705     entry.replace(KIO::UDSEntry::UDS_SIZE, attributes.size);
1706     entry.replace(KIO::UDSEntry::UDS_MODIFICATION_TIME, attributes.mtime.seconds);
1707     entry.replace(KIO::UDSEntry::UDS_ACCESS_TIME, attributes.atime.seconds);
1708     entry.replace(KIO::UDSEntry::UDS_ACCESS, (attributes.mode & 07777));
1709     entry.replace(KIO::UDSEntry::UDS_FILE_TYPE, attributes.mode & S_IFMT); // extract file type
1710 
1711     NFSProtocol::completeUDSEntry(entry, attributes.uid, attributes.gid);
1712 }
1713 
1714 
completeBadLinkUDSEntry(KIO::UDSEntry & entry,const fattr & attributes)1715 void NFSProtocolV2::completeBadLinkUDSEntry(KIO::UDSEntry& entry, const fattr& attributes)
1716 {
1717     entry.replace(KIO::UDSEntry::UDS_MODIFICATION_TIME, attributes.mtime.seconds);
1718     entry.replace(KIO::UDSEntry::UDS_ACCESS_TIME, attributes.atime.seconds);
1719 
1720     NFSProtocol::completeInvalidUDSEntry(entry);
1721 }
1722