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