diff --git a/CMakeLists.txt b/CMakeLists.txt index a50b1f03..9c91d981 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,207 +1,207 @@ cmake_minimum_required(VERSION 3.0) # KDE Applications Version, managed by release script set (KDE_APPLICATIONS_VERSION_MAJOR "20") set (KDE_APPLICATIONS_VERSION_MINOR "03") set (KDE_APPLICATIONS_VERSION_MICRO "70") set (KDE_APPLICATIONS_VERSION "${KDE_APPLICATIONS_VERSION_MAJOR}.${KDE_APPLICATIONS_VERSION_MINOR}.${KDE_APPLICATIONS_VERSION_MICRO}") project(kio-extras VERSION ${KDE_APPLICATIONS_VERSION}) include(FeatureSummary) set(QT_MIN_VERSION "5.8.0") -set(KF5_MIN_VERSION "5.48.0") +set(KF5_MIN_VERSION "5.64.0") find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS DBus Network Widgets Svg) find_package(Qt5Test ${QT_MIN_VERSION} CONFIG QUIET) set_package_properties(Qt5Test PROPERTIES PURPOSE "Required for tests" TYPE OPTIONAL ) add_feature_info("Qt5Test" Qt5Test_FOUND "Required for building tests") if (NOT Qt5Test_FOUND) set(BUILD_TESTING OFF CACHE BOOL "Build the testing tree.") endif() find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Archive Config ConfigWidgets CoreAddons DBusAddons DocTools DNSSD IconThemes I18n KIO Solid Bookmarks GuiAddons SyntaxHighlighting ) # As this is the check used for linkage, only require it in the same location... if (UNIX) find_package(KF5Pty ${KF5_MIN_VERSION} REQUIRED) endif() include(CheckIncludeFile) include(CMakePackageConfigHelpers) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMMarkNonGuiExecutable) include(ECMMarkAsTest) include(ECMOptionalAddSubdirectory) include(ECMQtDeclareLoggingCategory) add_definitions(-DQT_NO_URL_CAST_FROM_STRING) find_package(KF5Activities QUIET) set_package_properties(KF5Activities PROPERTIES PURPOSE "Provides the activities:/ kioslave and fileitem plugin." TYPE OPTIONAL ) find_package(KF5ActivitiesStats 5.62 QUIET) set_package_properties(KF5ActivitiesStats PROPERTIES PURPOSE "Provides the recentlyused:/ kioslave." TYPE OPTIONAL ) find_package(Phonon4Qt5 4.6.60 NO_MODULE) set_package_properties(Phonon4Qt5 PROPERTIES DESCRIPTION "Qt-based audio library" PURPOSE "Required for the audio preview plugin" TYPE OPTIONAL) include_directories(${CMAKE_CURRENT_BINARY_DIR}) if(NOT WIN32) # we need a version of samba which has already smbc_set_context(), Alex set(SAMBA_REQUIRE_SMBC_SET_CONTEXT TRUE) set(SAMBA_REQUIRE_SMBC_OPTION_SET TRUE) find_package(Samba) set_package_properties(Samba PROPERTIES DESCRIPTION "the SMB client library, a version with smbc_set_context() and smbc_option_set()" URL "https://www.samba.org/" TYPE OPTIONAL PURPOSE "Needed to build the SMB kioslave" ) endif() find_package(libssh 0.7.0 MODULE) set_package_properties(libssh PROPERTIES DESCRIPTION "the SSH library with SFTP support" URL "https://www.libssh.org/" TYPE OPTIONAL PURPOSE "Needed to build the SFTP kioslave" ) find_package(Mtp) set_package_properties(Mtp PROPERTIES DESCRIPTION "the MTP library" URL "http://libmtp.sourceforge.net/" TYPE OPTIONAL PURPOSE "Needed to build the MTP kioslave" ) check_include_file(utime.h HAVE_UTIME_H) # ECM's KDECompilerSettings.cmake should take care of enabling supporting on # 32bit architectures. # Thorw a fatal error if off_t isn't >=64bit to ensure that large files are working # as expected. # BUG: 165449 if(UNIX) check_cxx_source_compiles(" #include /* Check that off_t can represent 2**63 - 1 correctly. We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ #define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; int main() { return 0; } " OFFT_IS_64BIT) if(NOT OFFT_IS_64BIT) message(FATAL_ERROR "Large file support is not enabled.") endif() find_package(Gperf) set_package_properties(Gperf PROPERTIES TYPE OPTIONAL PURPOSE "Needed to build the man kioslave" ) else() # FIXME: on windows we ignore support until trash gets integrated endif() add_subdirectory( doc ) add_subdirectory( about ) if(KF5Activities_FOUND) add_subdirectory( activities ) endif() if(KF5ActivitiesStats_FOUND) add_subdirectory( recentlyused ) endif() add_subdirectory( bookmarks ) add_subdirectory( filter ) if(Phonon4Qt5_FOUND) add_subdirectory( kfileaudiopreview ) endif() add_subdirectory( info ) add_subdirectory( archive ) if(NOT WIN32) add_subdirectory( network ) endif() add_subdirectory( recentdocuments ) if (NOT WIN32) # does not compile: fish.cpp(41): fatal error C1083: Cannot open include file: 'sys/resource.h': No such file or directory # Used for getting the resource limit for closing all child process FDs. Could be completely replaced by fcloseall() if available for Unix or _fcloseall() for Windows, either conditionally on Q_OS_type or using a configure test. add_subdirectory( fish ) endif() add_subdirectory( thumbnail ) add_subdirectory( docfilter ) if (libssh_FOUND) add_subdirectory(sftp) endif () add_subdirectory(settings) add_subdirectory( filenamesearch ) if (MTP_FOUND) add_subdirectory(mtp) endif() if(NOT WIN32) if(Gperf_FOUND) add_subdirectory( man ) endif() check_include_files(rpc/rpc.h HAVE_RPC_RPC_H) add_feature_info("NFS kioslave" HAVE_RPC_RPC_H "The RPC library is needed to build the NFS kioslave") if(HAVE_RPC_RPC_H) add_subdirectory( nfs ) endif() endif() # KDNSSD before 5.54 suffers from a race condition in avahi's dbus API and # ideally should not be used in ways that can deadlock a slave. if(${KF5DNSSD_FOUND} AND ${KF5DNSSD_VERSION} VERSION_GREATER "5.53") set(HAVE_KDNSSD_WITH_SIGNAL_RACE_PROTECTION TRUE) endif() if(SAMBA_FOUND) add_subdirectory(smb) endif() configure_file (config-runtime.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-runtime.h ) if (${ECM_VERSION} STRGREATER "5.58.0") install(FILES kio-extras.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) else() install(FILES kio-extras.categories DESTINATION ${KDE_INSTALL_CONFDIR}) endif() feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/nfs/nfsv2.cpp b/nfs/nfsv2.cpp index 68ada394..a0ed9f53 100644 --- a/nfs/nfsv2.cpp +++ b/nfs/nfsv2.cpp @@ -1,1858 +1,1858 @@ /* This file is part of the KDE project Copyright(C) 2000 Alexander Neundorf , 2014 Mathias Tillman This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "nfsv2.h" #include #include // This is needed on Solaris so that rpc.h defines clnttcp_create etc. #ifndef PORTMAP #define PORTMAP #endif #include // for rpc calls #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // This is for NFS version 2. #define NFSPROG 100003UL #define NFSVERS 2UL NFSProtocolV2::NFSProtocolV2(NFSSlave* slave) : NFSProtocol(slave), m_slave(slave), m_mountClient(nullptr), m_mountSock(-1), m_nfsClient(nullptr), m_nfsSock(-1) { qCDebug(LOG_KIO_NFS) << "NFS2::NFS2"; clnt_timeout.tv_sec = 20; clnt_timeout.tv_usec = 0; } NFSProtocolV2::~NFSProtocolV2() { closeConnection(); } bool NFSProtocolV2::isCompatible(bool& connectionError) { int ret = -1; CLIENT* client = nullptr; int sock = 0; if (NFSProtocol::openConnection(m_currentHost, NFSPROG, NFSVERS, client, sock) == 0) { // Check if the NFS version is compatible ret = clnt_call(client, NFSPROC_NULL, (xdrproc_t) xdr_void, nullptr, (xdrproc_t) xdr_void, nullptr, clnt_timeout); connectionError = false; } else { qCDebug(LOG_KIO_NFS) << "openConnection failed"; connectionError = true; } if (sock != -1) { ::close(sock); } if (client != nullptr) { CLNT_DESTROY(client); } qCDebug(LOG_KIO_NFS) << ret; return (ret == RPC_SUCCESS); } bool NFSProtocolV2::isConnected() const { return (m_nfsClient != nullptr); } void NFSProtocolV2::closeConnection() { qCDebug(LOG_KIO_NFS); // Unmount all exported dirs(if any) if (m_mountClient != nullptr) { clnt_call(m_mountClient, MOUNTPROC_UMNTALL, (xdrproc_t) xdr_void, nullptr, (xdrproc_t) xdr_void, nullptr, clnt_timeout); } if (m_mountSock >= 0) { ::close(m_mountSock); m_mountSock = -1; } if (m_nfsSock >= 0) { ::close(m_nfsSock); m_nfsSock = -1; } if (m_mountClient != nullptr) { CLNT_DESTROY(m_mountClient); m_mountClient = nullptr; } if (m_nfsClient != nullptr) { CLNT_DESTROY(m_nfsClient); m_nfsClient = nullptr; } } NFSFileHandle NFSProtocolV2::lookupFileHandle(const QString& path) { int rpcStatus; diropres res; if (lookupHandle(path, rpcStatus, res)) { NFSFileHandle fh = res.diropres_u.diropres.file; // It it a link? Get the link target. if (res.diropres_u.diropres.attributes.type == NFLNK) { nfs_fh readLinkArgs; fh.toFH(readLinkArgs); char dataBuffer[NFS_MAXPATHLEN]; readlinkres readLinkRes; memset(&readLinkRes, 0, sizeof(readLinkRes)); readLinkRes.readlinkres_u.data = dataBuffer; int rpcStatus = clnt_call(m_nfsClient, NFSPROC_READLINK, (xdrproc_t) xdr_nfs_fh, reinterpret_cast(&readLinkArgs), (xdrproc_t) xdr_readlinkres, reinterpret_cast(&readLinkRes), clnt_timeout); if (rpcStatus == RPC_SUCCESS && readLinkRes.status == NFS_OK) { const QString linkDest = QString::fromLocal8Bit(readLinkRes.readlinkres_u.data); QString linkPath; if (QFileInfo(linkDest).isAbsolute()) { linkPath = linkDest; } else { linkPath = QFileInfo(QFileInfo(path).path(), linkDest).absoluteFilePath(); } diropres linkRes; if (lookupHandle(linkPath, rpcStatus, linkRes)) { NFSFileHandle linkFH = linkRes.diropres_u.diropres.file; linkFH.setLinkSource(res.diropres_u.diropres.file); qCDebug(LOG_KIO_NFS) << "Found target -" << linkPath; return linkFH; } } // If we have reached this point the file is a link, but we failed to get the target. fh.setBadLink(); } return fh; } return NFSFileHandle(); } /* Open connection connects to the mount daemon on the server side. In order to do this it needs authentication and calls auth_unix_create(). Then it asks the mount daemon for the exported shares. Then it tries to mount all these shares. If this succeeded for at least one of them, a client for the nfs daemon is created. */ void NFSProtocolV2::openConnection() { qCDebug(LOG_KIO_NFS) << m_currentHost; int connErr; if ((connErr = NFSProtocol::openConnection(m_currentHost, MOUNTPROG, MOUNTVERS, m_mountClient, m_mountSock)) != 0) { // Close the connection and send the error id to the slave closeConnection(); m_slave->error(connErr, m_currentHost); return; } exports exportlist; memset(&exportlist, 0, sizeof(exportlist)); int clnt_stat = clnt_call(m_mountClient, MOUNTPROC_EXPORT, (xdrproc_t) xdr_void, nullptr, (xdrproc_t) xdr_exports, reinterpret_cast(&exportlist), clnt_timeout); if (!checkForError(clnt_stat, 0, m_currentHost.toLatin1())) { return; } int exportsCount = 0; QStringList failList; fhstatus fhStatus; for (; exportlist != nullptr; exportlist = exportlist->ex_next, exportsCount++) { memset(&fhStatus, 0, sizeof(fhStatus)); clnt_stat = clnt_call(m_mountClient, MOUNTPROC_MNT, (xdrproc_t) xdr_dirpath, reinterpret_cast(&exportlist->ex_dir), (xdrproc_t) xdr_fhstatus, reinterpret_cast(&fhStatus), clnt_timeout); if (fhStatus.fhs_status == 0) { QString fname = QFileInfo(QDir("/"), exportlist->ex_dir).filePath(); // Check if the dir is already exported if (NFSProtocol::isExportedDir(fname)) { continue; } addFileHandle(fname, static_cast(fhStatus.fhstatus_u.fhs_fhandle)); addExportedDir(fname); } else { failList.append(exportlist->ex_dir); } } // Check if some exported dirs failed to mount if (failList.size() > 0) { m_slave->error(KIO::ERR_COULD_NOT_MOUNT, i18n("Failed to mount %1", failList.join(", "))); // All exports failed to mount, fail if (failList.size() == exportsCount) { closeConnection(); return; } } if ((connErr = NFSProtocol::openConnection(m_currentHost, NFSPROG, NFSVERS, m_nfsClient, m_nfsSock)) != 0) { closeConnection(); m_slave->error(connErr, m_currentHost); } m_slave->connected(); qCDebug(LOG_KIO_NFS) << "openConnection succeeded"; } void NFSProtocolV2::listDir(const QUrl& url) { // We should always be connected if it reaches this point, // but better safe than sorry! if (!isConnected()) { return; } if (url.isEmpty()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } const QString path(url.path()); // The root "directory" is just a list of the exported directories, // so list them here. if (isExportedDir(path)) { qCDebug(LOG_KIO_NFS) << "Listing exported dir"; QStringList virtualList; for (QStringList::const_iterator it = getExportedDirs().constBegin(); it != getExportedDirs().constEnd(); ++it) { // When an export is multiple levels deep(mnt/nfs for example) we only // want to display one level at a time. QString name = (*it); name = name.remove(0, path.length()); if (name.startsWith('/')) { name = name.mid(1); } if (name.indexOf('/') != -1) { name.truncate(name.indexOf('/')); } if (!virtualList.contains(name)) { virtualList.append(name); } } for (QStringList::const_iterator it = virtualList.constBegin(); it != virtualList.constEnd(); ++it) { qCDebug(LOG_KIO_NFS) << (*it) << "found in exported dir"; KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, (*it)); createVirtualDirEntry(entry); m_slave->listEntry(entry); } m_slave->finished(); return; } const NFSFileHandle fh = getFileHandle(path); if (fh.isInvalid() || fh.isBadLink()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, path); return; } readdirargs listargs; memset(&listargs, 0, sizeof(listargs)); listargs.count = 1024 * sizeof(entry); fh.toFH(listargs.dir); readdirres listres; QStringList filesToList; entry* lastEntry = nullptr; do { memset(&listres, 0, sizeof(listres)); // In case that we didn't get all entries we need to set the cookie to the last one we actually received. if (lastEntry != nullptr) { memcpy(listargs.cookie, lastEntry->cookie, NFS_COOKIESIZE); } int clnt_stat = clnt_call(m_nfsClient, NFSPROC_READDIR, (xdrproc_t) xdr_readdirargs, reinterpret_cast(&listargs), (xdrproc_t) xdr_readdirres, reinterpret_cast(&listres), clnt_timeout); if (!checkForError(clnt_stat, listres.status, path)) { return; } for (entry* dirEntry = listres.readdirres_u.reply.entries; dirEntry != nullptr; dirEntry = dirEntry->nextentry) { if (dirEntry->name != QString(".") && dirEntry->name != QString("..")) { filesToList.append(QFile::decodeName(dirEntry->name)); } lastEntry = dirEntry; } } while (!listres.readdirres_u.reply.eof); KIO::UDSEntry entry; for (QStringList::const_iterator it = filesToList.constBegin(); it != filesToList.constEnd(); ++it) { QString filePath = QFileInfo(QDir(path), (*it)).filePath(); int rpcStatus; diropres dirres; if (!lookupHandle(filePath, rpcStatus, dirres)) { qCDebug(LOG_KIO_NFS) << "Failed to lookup" << filePath << ", rpc:" << rpcStatus << ", nfs:" << dirres.status; // Try the next file instead of failing continue; } entry.clear(); entry.insert(KIO::UDSEntry::UDS_NAME, (*it)); //is it a symlink ? if (dirres.diropres_u.diropres.attributes.type == NFLNK) { int rpcStatus; readlinkres readLinkRes; char nameBuf[NFS_MAXPATHLEN]; if (readLink(filePath, rpcStatus, readLinkRes, nameBuf)) { const QString linkDest = QString::fromLocal8Bit(readLinkRes.readlinkres_u.data); entry.insert(KIO::UDSEntry::UDS_LINK_DEST, linkDest); bool badLink = true; NFSFileHandle linkFH; if (isValidLink(path, linkDest)) { QString linkPath; if (QFileInfo(linkDest).isAbsolute()) { linkPath = linkDest; } else { linkPath = QFileInfo(path, linkDest).absoluteFilePath(); } int rpcStatus; diropres lookupRes; if (lookupHandle(linkPath, rpcStatus, lookupRes)) { attrstat attrAndStat; if (getAttr(linkPath, rpcStatus, attrAndStat)) { badLink = false; linkFH = lookupRes.diropres_u.diropres.file; linkFH.setLinkSource(dirres.diropres_u.diropres.file); completeUDSEntry(entry, attrAndStat.attrstat_u.attributes); } } } if (badLink) { linkFH = dirres.diropres_u.diropres.file; linkFH.setBadLink(); completeBadLinkUDSEntry(entry, dirres.diropres_u.diropres.attributes); } addFileHandle(filePath, linkFH); } else { entry.insert(KIO::UDSEntry::UDS_LINK_DEST, i18n("Unknown target")); completeBadLinkUDSEntry(entry, dirres.diropres_u.diropres.attributes); } } else { addFileHandle(filePath, dirres.diropres_u.diropres.file); completeUDSEntry(entry, dirres.diropres_u.diropres.attributes); } m_slave->listEntry(entry); } m_slave->finished(); } void NFSProtocolV2::stat(const QUrl& url) { qCDebug(LOG_KIO_NFS) << url; const QString path(url.path()); if (isExportedDir(path)) { KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, path); createVirtualDirEntry(entry); m_slave->statEntry(entry); m_slave->finished(); return; } const NFSFileHandle fh = getFileHandle(path); if (fh.isInvalid()) { qCDebug(LOG_KIO_NFS) << "File handle is invalid"; m_slave->error(KIO::ERR_DOES_NOT_EXIST, path); return; } int rpcStatus; attrstat attrAndStat; if (!getAttr(path, rpcStatus, attrAndStat)) { checkForError(rpcStatus, attrAndStat.status, path); return; } const QFileInfo fileInfo(path); KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, fileInfo.fileName()); // Is it a symlink? if (attrAndStat.attrstat_u.attributes.type == NFLNK) { qCDebug(LOG_KIO_NFS) << "It's a symlink"; QString linkDest; int rpcStatus; readlinkres readLinkRes; char nameBuf[NFS_MAXPATHLEN]; if (readLink(path, rpcStatus, readLinkRes, nameBuf)) { linkDest = QString::fromLocal8Bit(readLinkRes.readlinkres_u.data); } else { entry.insert(KIO::UDSEntry::UDS_LINK_DEST, i18n("Unknown target")); completeBadLinkUDSEntry(entry, attrAndStat.attrstat_u.attributes); m_slave->statEntry(entry); m_slave->finished(); return; } qCDebug(LOG_KIO_NFS) << "link dest is" << linkDest; entry.insert(KIO::UDSEntry::UDS_LINK_DEST, linkDest); if (!isValidLink(fileInfo.path(), linkDest)) { completeBadLinkUDSEntry(entry, attrAndStat.attrstat_u.attributes); } else { QString linkPath; if (QFileInfo(linkDest).isAbsolute()) { linkPath = linkDest; } else { linkPath = QFileInfo(fileInfo.path(), linkDest).absoluteFilePath(); } int rpcStatus; attrstat attrAndStat; if (!getAttr(linkPath, rpcStatus, attrAndStat)) { checkForError(rpcStatus, attrAndStat.status, linkPath); return; } completeUDSEntry(entry, attrAndStat.attrstat_u.attributes); } } else { completeUDSEntry(entry, attrAndStat.attrstat_u.attributes); } m_slave->statEntry(entry); m_slave->finished(); } void NFSProtocolV2::setHost(const QString& host) { qCDebug(LOG_KIO_NFS) << host; if (host.isEmpty()) { m_slave->error(KIO::ERR_UNKNOWN_HOST, QString()); return; } if (host == m_currentHost) { return; } // Set the new host and close the current connection m_currentHost = host; closeConnection(); } void NFSProtocolV2::mkdir(const QUrl& url, int permissions) { qCDebug(LOG_KIO_NFS) << url; const QString path(url.path()); const QFileInfo fileInfo(path); if (isExportedDir(fileInfo.path())) { m_slave->error(KIO::ERR_WRITE_ACCESS_DENIED, path); return; } const NFSFileHandle fh = getFileHandle(fileInfo.path()); if (fh.isInvalid() || fh.isBadLink()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, path); return; } createargs createArgs; fh.toFH(createArgs.where.dir); QByteArray tmpName = QFile::encodeName(fileInfo.fileName()); createArgs.where.name = tmpName.data(); if (permissions == -1) { createArgs.attributes.mode = 0755; } else { createArgs.attributes.mode = permissions; } diropres dirres; memset(&dirres, 0, sizeof(diropres)); int clnt_stat = clnt_call(m_nfsClient, NFSPROC_MKDIR, (xdrproc_t) xdr_createargs, reinterpret_cast(&createArgs), (xdrproc_t) xdr_diropres, reinterpret_cast(&dirres), clnt_timeout); if (!checkForError(clnt_stat, dirres.status, path)) { return; } m_slave->finished(); } void NFSProtocolV2::del(const QUrl& url, bool) { int rpcStatus; nfsstat nfsStatus; if (!remove(url.path(), rpcStatus, nfsStatus)) { checkForError(rpcStatus, nfsStatus, url.path()); qCDebug(LOG_KIO_NFS) << "Could not delete" << url; return; } m_slave->finished(); } void NFSProtocolV2::chmod(const QUrl& url, int permissions) { qCDebug(LOG_KIO_NFS) << url; const QString path(url.path()); if (isExportedDir(path)) { m_slave->error(KIO::ERR_ACCESS_DENIED, path); return; } sattr attributes; memset(&attributes, 0xFF, sizeof(attributes)); attributes.mode = permissions; int rpcStatus; nfsstat result; if (!setAttr(path, attributes, rpcStatus, result)) { checkForError(rpcStatus, result, path); return; } m_slave->finished(); } void NFSProtocolV2::get(const QUrl& url) { qCDebug(LOG_KIO_NFS) << url; const QString path(url.path()); const NFSFileHandle fh = getFileHandle(path); if (fh.isInvalid() || fh.isBadLink()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, path); return; } readargs readArgs; fh.toFH(readArgs.file); readArgs.offset = 0; readArgs.count = NFS_MAXDATA; readArgs.totalcount = NFS_MAXDATA; readres readRes; memset(&readRes, 0, sizeof(readres)); char buf[NFS_MAXDATA]; readRes.readres_u.reply.data.data_val = buf; bool validRead = false; int offset = 0; QByteArray readBuffer; do { int clnt_stat = clnt_call(m_nfsClient, NFSPROC_READ, (xdrproc_t) xdr_readargs, reinterpret_cast(&readArgs), (xdrproc_t) xdr_readres, reinterpret_cast(&readRes), clnt_timeout); if (!checkForError(clnt_stat, readRes.status, path)) { return; } if (readArgs.offset == 0) { m_slave->totalSize(readRes.readres_u.reply.attributes.size); const QMimeDatabase db; const QMimeType type = db.mimeTypeForFileNameAndData(url.fileName(), readBuffer); m_slave->mimeType(type.name()); } offset = readRes.readres_u.reply.data.data_len; readArgs.offset += offset; if (offset > 0) { validRead = true; readBuffer = QByteArray::fromRawData(readRes.readres_u.reply.data.data_val, offset); m_slave->data(readBuffer); readBuffer.clear(); m_slave->processedSize(readArgs.offset); } } while (offset > 0); if (validRead) { m_slave->data(QByteArray()); m_slave->processedSize(readArgs.offset); } m_slave->finished(); } void NFSProtocolV2::put(const QUrl& url, int _mode, KIO::JobFlags flags) { qCDebug(LOG_KIO_NFS) << url << _mode; const QString destPath(url.path()); const QFileInfo fileInfo(destPath); if (isExportedDir(fileInfo.path())) { m_slave->error(KIO::ERR_WRITE_ACCESS_DENIED, destPath); return; } NFSFileHandle destFH = getFileHandle(destPath); if (destFH.isBadLink()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, destPath); return; } //the file exists and we don't want to overwrite if (!destFH.isInvalid() && (!(flags & KIO::Overwrite))) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } int rpcStatus; diropres dirOpRes; if (!create(destPath, _mode, rpcStatus, dirOpRes)) { checkForError(rpcStatus, dirOpRes.status, fileInfo.fileName()); return; } destFH = dirOpRes.diropres_u.diropres.file.data; writeargs writeArgs; memset(&writeArgs, 0, sizeof(writeargs)); destFH.toFH(writeArgs.file); writeArgs.beginoffset = 0; writeArgs.totalcount = 0; writeArgs.offset = 0; attrstat attrStat; int result = 0, bytesWritten = 0; do { // Request new data m_slave->dataReq(); QByteArray buffer; result = m_slave->readData(buffer); char* data = buffer.data(); int bytesToWrite = buffer.size(), writeNow = 0; if (result > 0) { do { if (bytesToWrite > NFS_MAXDATA) { writeNow = NFS_MAXDATA; } else { writeNow = bytesToWrite; } writeArgs.data.data_val = data; writeArgs.data.data_len = writeNow; int clnt_stat = clnt_call(m_nfsClient, NFSPROC_WRITE, (xdrproc_t) xdr_writeargs, reinterpret_cast(&writeArgs), (xdrproc_t) xdr_attrstat, reinterpret_cast(&attrStat), clnt_timeout); if (!checkForError(clnt_stat, attrStat.status, fileInfo.fileName())) { return; } bytesWritten += writeNow; writeArgs.offset = bytesWritten; data = data + writeNow; bytesToWrite -= writeNow; } while (bytesToWrite > 0); } } while (result > 0); m_slave->finished(); } void NFSProtocolV2::rename(const QUrl& src, const QUrl& dest, KIO::JobFlags _flags) { qCDebug(LOG_KIO_NFS) << src << "to" << dest; const QString srcPath(src.path()); if (isExportedDir(srcPath)) { m_slave->error(KIO::ERR_CANNOT_RENAME, srcPath); return; } const QString destPath(dest.path()); if (isExportedDir(destPath)) { m_slave->error(KIO::ERR_ACCESS_DENIED, destPath); return; } if (!getFileHandle(destPath).isInvalid() && (_flags & KIO::Overwrite) == 0) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } int rpcStatus; nfsstat nfsStatus; if (!rename(src.path(), destPath, rpcStatus, nfsStatus)) { if (!checkForError(rpcStatus, nfsStatus, destPath)) { return; } } m_slave->finished(); } void NFSProtocolV2::copySame(const QUrl& src, const QUrl& dest, int _mode, KIO::JobFlags _flags) { qCDebug(LOG_KIO_NFS) << src << "to" << dest; const QString srcPath(src.path()); const NFSFileHandle srcFH = getFileHandle(srcPath); if (srcFH.isInvalid()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, srcPath); return; } const QString destPath = dest.path(); if (isExportedDir(QFileInfo(destPath).path())) { m_slave->error(KIO::ERR_ACCESS_DENIED, destPath); return; } // The file exists and we don't want to overwrite if (!getFileHandle(destPath).isInvalid() && (_flags & KIO::Overwrite) == 0) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } // Is it a link? No need to copy the data then, just copy the link destination. if (srcFH.isLink()) { //get the link dest int rpcStatus; readlinkres readLinkRes; char nameBuf[NFS_MAXPATHLEN]; if (!readLink(srcPath, rpcStatus, readLinkRes, nameBuf)) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, srcPath); return; } const QString linkPath = QString::fromLocal8Bit(readLinkRes.readlinkres_u.data); nfsstat linkRes; if (!symLink(linkPath, destPath, rpcStatus, linkRes)) { checkForError(rpcStatus, linkRes, linkPath); return; } m_slave->finished(); return; } unsigned long resumeOffset = 0; bool bResume = false; const QString partFilePath = destPath + QLatin1String(".part"); const NFSFileHandle partFH = getFileHandle(partFilePath); const bool bPartExists = !partFH.isInvalid(); - const bool bMarkPartial = m_slave->config()->readEntry("MarkPartial", true); + const bool bMarkPartial = m_slave->configValue(QStringLiteral("MarkPartial"), true); if (bPartExists) { int rpcStatus; diropres partRes; if (lookupHandle(partFilePath, rpcStatus, partRes)) { if (bMarkPartial && partRes.diropres_u.diropres.attributes.size > 0) { if (partRes.diropres_u.diropres.attributes.type == NFDIR) { m_slave->error(KIO::ERR_IS_DIRECTORY, partFilePath); return; } bResume = m_slave->canResume(partRes.diropres_u.diropres.attributes.size); if (bResume) { resumeOffset = partRes.diropres_u.diropres.attributes.size; } } } // Remove the part file if we are not resuming if (!bResume) { if (!remove(partFilePath)) { qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring..."; } } } // Create the file if we are not resuming a parted transfer, // or if we are not using part files(bResume is false in that case) NFSFileHandle destFH; if (!bResume) { QString createPath; if (bMarkPartial) { createPath = partFilePath; } else { createPath = destPath; } int rpcStatus; diropres dirOpRes; if (!create(createPath, _mode, rpcStatus, dirOpRes)) { checkForError(rpcStatus, dirOpRes.status, createPath); return; } destFH = dirOpRes.diropres_u.diropres.file.data; } else { // Since we are resuming it's implied that we are using a part file, // which should exist at this point. destFH = getFileHandle(partFilePath); qCDebug(LOG_KIO_NFS) << "Resuming old transfer"; } char buf[NFS_MAXDATA]; writeargs writeArgs; destFH.toFH(writeArgs.file); writeArgs.beginoffset = 0; writeArgs.totalcount = 0; writeArgs.offset = 0; writeArgs.data.data_val = buf; readargs readArgs; srcFH.toFH(readArgs.file); readArgs.offset = 0; readArgs.count = NFS_MAXDATA; readArgs.totalcount = NFS_MAXDATA; if (bResume) { writeArgs.offset = resumeOffset; readArgs.offset = resumeOffset; } readres readRes; memset(&readRes, 0, sizeof(readres)); readRes.readres_u.reply.data.data_val = buf; attrstat attrStat; memset(&attrStat, 0, sizeof(attrstat)); bool error = false; int bytesRead = 0; do { int clnt_stat = clnt_call(m_nfsClient, NFSPROC_READ, (xdrproc_t) xdr_readargs, reinterpret_cast(&readArgs), (xdrproc_t) xdr_readres, reinterpret_cast(&readRes), clnt_timeout); if (!checkForError(clnt_stat, readRes.status, destPath)) { error = true; break; } bytesRead = readRes.readres_u.reply.data.data_len; // We should only send out the total size and mimetype at the start of the transfer if (readArgs.offset == 0 || (bResume && writeArgs.offset == resumeOffset)) { m_slave->totalSize(readRes.readres_u.reply.attributes.size); QMimeDatabase db; QMimeType type = db.mimeTypeForFileNameAndData(src.fileName(), QByteArray::fromRawData(writeArgs.data.data_val, bytesRead)); m_slave->mimeType(type.name()); } if (bytesRead > 0) { readArgs.offset += bytesRead; writeArgs.data.data_len = bytesRead; clnt_stat = clnt_call(m_nfsClient, NFSPROC_WRITE, (xdrproc_t) xdr_writeargs, reinterpret_cast(&writeArgs), (xdrproc_t) xdr_attrstat, reinterpret_cast(&attrStat), clnt_timeout); if (!checkForError(clnt_stat, attrStat.status, destPath)) { error = true; break; } writeArgs.offset += bytesRead; m_slave->processedSize(readArgs.offset); } } while (bytesRead > 0); if (error) { if (bMarkPartial) { // Remove the part file if it's smaller than the minimum keep size. - const unsigned int size = m_slave->config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); + const unsigned int size = m_slave->configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE); if (writeArgs.offset < size) { if (!remove(partFilePath)) { qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring..."; } } } } else { // Rename partial file to its original name. if (bMarkPartial) { // Remove the destination file(if it exists) if (!getFileHandle(destPath).isInvalid() && !remove(destPath)) { qCDebug(LOG_KIO_NFS) << "Could not remove destination file" << destPath << ", ignoring..."; } if (!rename(partFilePath, destPath)) { qCDebug(LOG_KIO_NFS) << "Failed to rename" << partFilePath << "to" << destPath; m_slave->error(KIO::ERR_CANNOT_RENAME_PARTIAL, partFilePath); return; } } // Restore modification time int rpcStatus; attrstat attrRes; if (getAttr(srcPath, rpcStatus, attrRes)) { sattr attributes; memset(&attributes, 0xFF, sizeof(attributes)); attributes.mtime.seconds = attrRes.attrstat_u.attributes.mtime.seconds; attributes.mtime.useconds = attrRes.attrstat_u.attributes.mtime.useconds; nfsstat attrSetRes; if (!setAttr(destPath, attributes, rpcStatus, attrSetRes)) { qCDebug(LOG_KIO_NFS) << "Failed to restore mtime, ignoring..." << rpcStatus << attrSetRes; } } qCDebug(LOG_KIO_NFS) << "Copied" << writeArgs.offset << "bytes of data"; m_slave->processedSize(readArgs.offset); m_slave->finished(); } } void NFSProtocolV2::copyFrom(const QUrl& src, const QUrl& dest, int _mode, KIO::JobFlags _flags) { qCDebug(LOG_KIO_NFS) << src << "to" << dest; const QString srcPath(src.path()); const NFSFileHandle srcFH = getFileHandle(srcPath); if (srcFH.isInvalid()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, srcPath); return; } const QString destPath(dest.path()); // The file exists and we don't want to overwrite if (QFile::exists(destPath) && (_flags & KIO::Overwrite) == 0) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } // Is it a link? No need to copy the data then, just copy the link destination. if (srcFH.isLink()) { qCDebug(LOG_KIO_NFS) << "Is a link"; int rpcStatus; readlinkres readLinkRes; char nameBuf[NFS_MAXPATHLEN]; if (!readLink(srcPath, rpcStatus, readLinkRes, nameBuf)) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, srcPath); return; } QFile::link(QString::fromLocal8Bit(readLinkRes.readlinkres_u.data), destPath); m_slave->finished(); return; } bool bResume = false; const QFileInfo partInfo(destPath + QLatin1String(".part")); const bool bPartExists = partInfo.exists(); - const bool bMarkPartial = m_slave->config()->readEntry("MarkPartial", true); + const bool bMarkPartial = m_slave->configValue(QStringLiteral("MarkPartial"), true); if (bMarkPartial && bPartExists && partInfo.size() > 0) { if (partInfo.isDir()) { m_slave->error(KIO::ERR_IS_DIRECTORY, partInfo.absoluteFilePath()); return; } bResume = m_slave->canResume(partInfo.size()); } if (bPartExists && !bResume) { QFile::remove(partInfo.absoluteFilePath()); } QFile::OpenMode openMode; QString outFileName; if (bResume) { outFileName = partInfo.absoluteFilePath(); openMode = QFile::WriteOnly | QFile::Append; } else { outFileName = (bMarkPartial ? partInfo.absoluteFilePath() : destPath); openMode = QFile::WriteOnly | QFile::Truncate; } QFile destFile(outFileName); if (!bResume) { QFile::Permissions perms; if (_mode == -1) { perms = QFile::ReadOwner | QFile::WriteOwner; } else { perms = KIO::convertPermissions(_mode | QFile::WriteOwner); } destFile.setPermissions(perms); } if (!destFile.open(openMode)) { switch (destFile.error()) { case QFile::OpenError: if (bResume) { m_slave->error(KIO::ERR_CANNOT_RESUME, destPath); } else { m_slave->error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, destPath); } break; case QFile::PermissionsError: m_slave->error(KIO::ERR_WRITE_ACCESS_DENIED, destPath); break; default: m_slave->error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, destPath); break; } return; } char buf[NFS_MAXDATA]; readargs readArgs; srcFH.toFH(readArgs.file); if (bResume) { readArgs.offset = partInfo.size(); } else { readArgs.offset = 0; } readArgs.count = NFS_MAXDATA; readArgs.totalcount = NFS_MAXDATA; readres readRes; memset(&readRes, 0, sizeof(readres)); readRes.readres_u.reply.data.data_val = buf; attrstat attrStat; memset(&attrStat, 0, sizeof(attrstat)); bool error = false; int bytesRead = 0; do { int clnt_stat = clnt_call(m_nfsClient, NFSPROC_READ, (xdrproc_t) xdr_readargs, reinterpret_cast(&readArgs), (xdrproc_t) xdr_readres, reinterpret_cast(&readRes), clnt_timeout); if (!checkForError(clnt_stat, readRes.status, destPath)) { error = true; break; } bytesRead = readRes.readres_u.reply.data.data_len; if (readArgs.offset == 0) { m_slave->totalSize(readRes.readres_u.reply.attributes.size); QMimeDatabase db; QMimeType type = db.mimeTypeForFileNameAndData(src.fileName(), QByteArray::fromRawData(readRes.readres_u.reply.data.data_val, bytesRead)); m_slave->mimeType(type.name()); } if (bytesRead > 0) { readArgs.offset += bytesRead; if (destFile.write(readRes.readres_u.reply.data.data_val, bytesRead) != bytesRead) { m_slave->error(KIO::ERR_COULD_NOT_WRITE, destPath); error = true; break; } m_slave->processedSize(readArgs.offset); } } while (bytesRead > 0); // Close the file so we can modify the modification time later. destFile.close(); if (error) { if (bMarkPartial) { // Remove the part file if it's smaller than the minimum keep - const int size = m_slave->config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); + const int size = m_slave->configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE); if (partInfo.size() < size) { QFile::remove(partInfo.absoluteFilePath()); } } } else { // Rename partial file to its original name. if (bMarkPartial) { const QString sPart = partInfo.absoluteFilePath(); if (QFile::exists(destPath)) { QFile::remove(destPath); } if (!QFile::rename(sPart, destPath)) { qCDebug(LOG_KIO_NFS) << "Failed to rename" << sPart << "to" << destPath; m_slave->error(KIO::ERR_CANNOT_RENAME_PARTIAL, sPart); return; } } // Restore the mtime on the file. const QString mtimeStr = m_slave->metaData("modified"); if (!mtimeStr.isEmpty()) { QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate); if (dt.isValid()) { qCDebug(LOG_KIO_NFS) << "Setting modification time to" << dt.toTime_t(); struct utimbuf utbuf; utbuf.actime = QFileInfo(destPath).lastRead().toTime_t(); // access time, unchanged utbuf.modtime = dt.toTime_t(); // modification time utime(QFile::encodeName(destPath).constData(), &utbuf); } } qCDebug(LOG_KIO_NFS) << "Copied" << readArgs.offset << "bytes of data"; m_slave->processedSize(readArgs.offset); m_slave->finished(); } } void NFSProtocolV2::copyTo(const QUrl& src, const QUrl& dest, int _mode, KIO::JobFlags _flags) { qCDebug(LOG_KIO_NFS) << src << "to" << dest; // The source does not exist, how strange. const QString srcPath(src.path()); if (!QFile::exists(srcPath)) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, srcPath); return; } const QString destPath(dest.path()); if (isExportedDir(destPath)) { m_slave->error(KIO::ERR_ACCESS_DENIED, destPath); return; } // The file exists and we don't want to overwrite. if (!getFileHandle(destPath).isInvalid() && (_flags & KIO::Overwrite) == 0) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } // Is it a link? No need to copy the data then, just copy the link destination. const QString symlinkTarget = QFile::symLinkTarget(srcPath); if (!symlinkTarget.isEmpty()) { int rpcStatus; nfsstat linkRes; if (!symLink(symlinkTarget, destPath, rpcStatus, linkRes)) { checkForError(rpcStatus, linkRes, symlinkTarget); return; } m_slave->finished(); return; } unsigned long resumeOffset = 0; bool bResume = false; const QString partFilePath = destPath + QLatin1String(".part"); const NFSFileHandle partFH = getFileHandle(partFilePath); const bool bPartExists = !partFH.isInvalid(); - const bool bMarkPartial = m_slave->config()->readEntry("MarkPartial", true); + const bool bMarkPartial = m_slave->configValue(QStringLiteral("MarkPartial"), true); if (bPartExists) { int rpcStatus; diropres partRes; if (lookupHandle(partFilePath, rpcStatus, partRes)) { if (bMarkPartial && partRes.diropres_u.diropres.attributes.size > 0) { if (partRes.diropres_u.diropres.attributes.type == NFDIR) { m_slave->error(KIO::ERR_IS_DIRECTORY, partFilePath); return; } bResume = m_slave->canResume(partRes.diropres_u.diropres.attributes.size); if (bResume) { resumeOffset = partRes.diropres_u.diropres.attributes.size; } } } // Remove the part file if we are not resuming if (!bResume) { if (!remove(partFilePath)) { qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring..."; } } } // Open the source file QFile srcFile(srcPath); if (!srcFile.open(QIODevice::ReadOnly)) { m_slave->error(KIO::ERR_CANNOT_OPEN_FOR_READING, srcPath); return; } // Create the file if we are not resuming a parted transfer, // or if we are not using part files(bResume is false in that case) NFSFileHandle destFH; if (!bResume) { QString createPath; if (bMarkPartial) { createPath = partFilePath; } else { createPath = destPath; } int rpcStatus; diropres dirOpRes; if (!create(createPath, _mode, rpcStatus, dirOpRes)) { checkForError(rpcStatus, dirOpRes.status, createPath); return; } destFH = dirOpRes.diropres_u.diropres.file.data; } else { // Since we are resuming it's implied that we are using a part file, // which should exist at this point. destFH = getFileHandle(partFilePath); qCDebug(LOG_KIO_NFS) << "Resuming old transfer"; } // Send the total size to the slave. m_slave->totalSize(srcFile.size()); // Set up write arguments. char buf[NFS_MAXDATA]; writeargs writeArgs; memset(&writeArgs, 0, sizeof(writeargs)); destFH.toFH(writeArgs.file); writeArgs.data.data_val = buf; writeArgs.beginoffset = 0; writeArgs.totalcount = 0; if (bResume) { writeArgs.offset = resumeOffset; } else { writeArgs.offset = 0; } attrstat attrStat; memset(&attrStat, 0, sizeof(attrstat)); bool error = false; int bytesRead = 0; do { bytesRead = srcFile.read(writeArgs.data.data_val, NFS_MAXDATA); if (bytesRead < 0) { m_slave->error(KIO::ERR_COULD_NOT_READ, srcPath); error = true; break; } if (bytesRead > 0) { writeArgs.data.data_len = bytesRead; int clnt_stat = clnt_call(m_nfsClient, NFSPROC_WRITE, (xdrproc_t) xdr_writeargs, reinterpret_cast(&writeArgs), (xdrproc_t) xdr_attrstat, reinterpret_cast(&attrStat), clnt_timeout); if (!checkForError(clnt_stat, attrStat.status, destPath)) { error = true; break; } writeArgs.offset += bytesRead; m_slave->processedSize(writeArgs.offset); } } while (bytesRead > 0); if (error) { if (bMarkPartial) { // Remove the part file if it's smaller than the minimum keep size. - const unsigned int size = m_slave->config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); + const unsigned int size = m_slave->configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE); if (writeArgs.offset < size) { if (!remove(partFilePath)) { qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring..."; } } } } else { // Rename partial file to its original name. if (bMarkPartial) { // Remove the destination file(if it exists) if (!getFileHandle(destPath).isInvalid() && !remove(destPath)) { qCDebug(LOG_KIO_NFS) << "Could not remove destination file" << destPath << ", ignoring..."; } if (!rename(partFilePath, destPath)) { qCDebug(LOG_KIO_NFS) << "Failed to rename" << partFilePath << "to" << destPath; m_slave->error(KIO::ERR_CANNOT_RENAME_PARTIAL, partFilePath); return; } } // Restore the mtime on the file. const QString mtimeStr = m_slave->metaData("modified"); if (!mtimeStr.isEmpty()) { QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate); if (dt.isValid()) { sattr attributes; memset(&attributes, 0xFF, sizeof(attributes)); attributes.mtime.seconds = dt.toTime_t(); attributes.mtime.useconds = attributes.mtime.seconds * 1000000ULL; int rpcStatus; nfsstat attrSetRes; if (!setAttr(destPath, attributes, rpcStatus, attrSetRes)) { qCDebug(LOG_KIO_NFS) << "Failed to restore mtime, ignoring..." << rpcStatus << attrSetRes; } } } qCDebug(LOG_KIO_NFS) << "Copied" << writeArgs.offset << "bytes of data"; m_slave->processedSize(writeArgs.offset); m_slave->finished(); } } void NFSProtocolV2::symlink(const QString& target, const QUrl& dest, KIO::JobFlags flags) { const QString destPath(dest.path()); if (isExportedDir(QFileInfo(destPath).path())) { m_slave->error(KIO::ERR_ACCESS_DENIED, destPath); return; } if (!getFileHandle(destPath).isInvalid() && (flags & KIO::Overwrite) == 0) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } int rpcStatus; nfsstat res; if (!symLink(target, destPath, rpcStatus, res)) { checkForError(rpcStatus, res, destPath); return; } m_slave->finished(); } bool NFSProtocolV2::create(const QString& path, int mode, int& rpcStatus, diropres& result) { memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); if (!isConnected()) { result.status = NFSERR_ACCES; return false; } const QFileInfo fileInfo(path); if (isExportedDir(fileInfo.path())) { result.status = NFSERR_ACCES; return false; } const NFSFileHandle directoryFH = getFileHandle(fileInfo.path()); if (directoryFH.isInvalid()) { result.status = NFSERR_NOENT; return false; } QByteArray tmpName = QFile::encodeName(fileInfo.fileName()); createargs args; directoryFH.toFH(args.where.dir); args.where.name = tmpName.data(); memset(&args.attributes, 0xFF, sizeof(sattr)); if (mode == -1) { args.attributes.mode = 0644; } else { args.attributes.mode = mode; } args.attributes.uid = geteuid(); args.attributes.gid = getegid(); args.attributes.size = 0; rpcStatus = clnt_call(m_nfsClient, NFSPROC_CREATE, (xdrproc_t) xdr_createargs, reinterpret_cast(&args), (xdrproc_t) xdr_diropres, reinterpret_cast(&result), clnt_timeout); return (rpcStatus == RPC_SUCCESS && result.status == NFS_OK); } bool NFSProtocolV2::getAttr(const QString& path, int& rpcStatus, attrstat& result) { memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); if (!isConnected()) { result.status = NFSERR_ACCES; return false; } const NFSFileHandle fileFH = getFileHandle(path); if (fileFH.isInvalid()) { result.status = NFSERR_NOENT; return false; } nfs_fh fh; fileFH.toFH(fh); rpcStatus = clnt_call(m_nfsClient, NFSPROC_GETATTR, (xdrproc_t) xdr_nfs_fh, reinterpret_cast(&fh), (xdrproc_t) xdr_attrstat, reinterpret_cast(&result), clnt_timeout); return (rpcStatus == RPC_SUCCESS && result.status == NFS_OK); } bool NFSProtocolV2::lookupHandle(const QString& path, int& rpcStatus, diropres& result) { memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); if (!isConnected()) { result.status = NFSERR_ACCES; return false; } const QFileInfo fileInfo(path); const NFSFileHandle parentFH = getFileHandle(fileInfo.path()); if (parentFH.isInvalid()) { result.status = NFSERR_NOENT; return false; } QByteArray tmpName = QFile::encodeName(fileInfo.fileName()); diropargs dirargs; memset(&dirargs, 0, sizeof(diropargs)); parentFH.toFH(dirargs.dir); dirargs.name = tmpName.data(); memset(&result, 0, sizeof(diropres)); rpcStatus = clnt_call(m_nfsClient, NFSPROC_LOOKUP, (xdrproc_t) xdr_diropargs, reinterpret_cast(&dirargs), (xdrproc_t) xdr_diropres, reinterpret_cast(&result), clnt_timeout); return (rpcStatus == RPC_SUCCESS && result.status == NFS_OK); } bool NFSProtocolV2::readLink(const QString& path, int& rpcStatus, readlinkres& result, char* dataBuffer) { const NFSFileHandle fh = getFileHandle(path); nfs_fh nfsFH; if (fh.isLink() && !fh.isBadLink()) { fh.toFHLink(nfsFH); } else { fh.toFH(nfsFH); } result.readlinkres_u.data = dataBuffer; rpcStatus = clnt_call(m_nfsClient, NFSPROC_READLINK, (xdrproc_t) xdr_nfs_fh, reinterpret_cast(&nfsFH), (xdrproc_t) xdr_readlinkres, reinterpret_cast(&result), clnt_timeout); return (rpcStatus == RPC_SUCCESS && result.status == NFS_OK); } bool NFSProtocolV2::remove(const QString& path) { int rpcStatus; nfsstat nfsStatus; return remove(path, rpcStatus, nfsStatus); } bool NFSProtocolV2::remove(const QString& path, int& rpcStatus, nfsstat& result) { qCDebug(LOG_KIO_NFS) << path; memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); if (!isConnected()) { result = NFSERR_PERM; return false; } const QFileInfo fileInfo(path); if (isExportedDir(fileInfo.path())) { result = NFSERR_ACCES; return false; } const NFSFileHandle directoryFH = getFileHandle(fileInfo.path()); if (directoryFH.isInvalid()) { result = NFSERR_NOENT; return false; } int rpcLookupStatus; diropres lookupRes; if (!lookupHandle(path, rpcLookupStatus, lookupRes)) { result = NFSERR_NOENT; return false; } QByteArray tmpName = QFile::encodeName(fileInfo.fileName()); diropargs dirargs; memset(&dirargs, 0, sizeof(diropargs)); directoryFH.toFH(dirargs.dir); dirargs.name = tmpName.data(); if (lookupRes.diropres_u.diropres.attributes.type != NFDIR) { rpcStatus = clnt_call(m_nfsClient, NFSPROC_REMOVE, (xdrproc_t) xdr_diropargs, reinterpret_cast(&dirargs), (xdrproc_t) xdr_nfsstat, reinterpret_cast(&result), clnt_timeout); } else { rpcStatus = clnt_call(m_nfsClient, NFSPROC_RMDIR, (xdrproc_t) xdr_diropargs, reinterpret_cast(&dirargs), (xdrproc_t) xdr_nfsstat, reinterpret_cast(&result), clnt_timeout); } bool ret = (rpcStatus == RPC_SUCCESS && result == NFS_OK); if (ret) { removeFileHandle(path); } return ret; } bool NFSProtocolV2::rename(const QString& src, const QString& dest) { int rpcStatus; nfsstat result; return rename(src, dest, rpcStatus, result); } bool NFSProtocolV2::rename(const QString& src, const QString& dest, int& rpcStatus, nfsstat& result) { qCDebug(LOG_KIO_NFS) << src << dest; memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); const QFileInfo srcFileInfo(src); if (isExportedDir(srcFileInfo.path())) { result = NFSERR_ACCES; return false; } const NFSFileHandle srcDirectoryFH = getFileHandle(srcFileInfo.path()); if (srcDirectoryFH.isInvalid()) { result = NFSERR_NOENT; return false; } const QFileInfo destFileInfo(dest); if (isExportedDir(destFileInfo.path())) { result = NFSERR_ACCES; return false; } const NFSFileHandle destDirectoryFH = getFileHandle(destFileInfo.path()); if (destDirectoryFH.isInvalid()) { result = NFSERR_NOENT; return false; } renameargs renameArgs; memset(&renameArgs, 0, sizeof(renameargs)); QByteArray srcByteName = QFile::encodeName(srcFileInfo.fileName()); srcDirectoryFH.toFH(renameArgs.from.dir); renameArgs.from.name = srcByteName.data(); QByteArray destByteName = QFile::encodeName(destFileInfo.fileName()); destDirectoryFH.toFH(renameArgs.to.dir); renameArgs.to.name = destByteName.data(); rpcStatus = clnt_call(m_nfsClient, NFSPROC_RENAME, (xdrproc_t) xdr_renameargs, reinterpret_cast(&renameArgs), (xdrproc_t) xdr_nfsstat, reinterpret_cast(&result), clnt_timeout); bool ret = (rpcStatus == RPC_SUCCESS && result == NFS_OK); if (ret) { // Can we actually find the new handle? int lookupStatus; diropres lookupRes; if (lookupHandle(dest, lookupStatus, lookupRes)) { // Remove the old file, and add the new one removeFileHandle(src); addFileHandle(dest, lookupRes.diropres_u.diropres.file); } } return ret; } bool NFSProtocolV2::setAttr(const QString& path, const sattr& attributes, int& rpcStatus, nfsstat& result) { qCDebug(LOG_KIO_NFS) << path; memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); const NFSFileHandle fh = getFileHandle(path); if (fh.isInvalid()) { result = NFSERR_NOENT; return false; } sattrargs sAttrArgs; fh.toFH(sAttrArgs.file); memcpy(&sAttrArgs.attributes, &attributes, sizeof(attributes)); rpcStatus = clnt_call(m_nfsClient, NFSPROC_SETATTR, (xdrproc_t) xdr_sattrargs, reinterpret_cast(&sAttrArgs), (xdrproc_t) xdr_nfsstat, reinterpret_cast(&result), clnt_timeout); return (rpcStatus == RPC_SUCCESS && result == NFS_OK); } bool NFSProtocolV2::symLink(const QString& target, const QString& dest, int& rpcStatus, nfsstat& result) { qCDebug(LOG_KIO_NFS) << target << dest; memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); // Remove dest first, we don't really care about the return value at this point, // the symlink call will fail if dest was not removed correctly. remove(dest); const QFileInfo fileInfo(dest); if (isExportedDir(fileInfo.path())) { result = NFSERR_ACCES; return false; } const NFSFileHandle fh = getFileHandle(fileInfo.path()); if (fh.isInvalid()) { result = NFSERR_NOENT; return false; } QByteArray fromBytes = QFile::encodeName(fileInfo.fileName()); QByteArray toBytes = QFile::encodeName(target); symlinkargs symLinkArgs; memset(&symLinkArgs, 0, sizeof(symLinkArgs)); fh.toFH(symLinkArgs.from.dir); symLinkArgs.from.name = fromBytes.data(); symLinkArgs.to = toBytes.data(); rpcStatus = clnt_call(m_nfsClient, NFSPROC_SYMLINK, (xdrproc_t) xdr_symlinkargs, reinterpret_cast(&symLinkArgs), (xdrproc_t) xdr_nfsstat, reinterpret_cast(&result), clnt_timeout); // Add the new handle to the cache NFSFileHandle destFH = getFileHandle(dest); if (!destFH.isInvalid()) { addFileHandle(dest, destFH); } return (rpcStatus == RPC_SUCCESS && result == NFS_OK); } void NFSProtocolV2::completeUDSEntry(KIO::UDSEntry& entry, const fattr& attributes) { entry.insert(KIO::UDSEntry::UDS_SIZE, attributes.size); entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, attributes.mtime.seconds); entry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, attributes.atime.seconds); entry.insert(KIO::UDSEntry::UDS_CREATION_TIME, attributes.ctime.seconds); entry.insert(KIO::UDSEntry::UDS_ACCESS, (attributes.mode & 07777)); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, attributes.mode & S_IFMT); // extract file type QString str; const uid_t uid = attributes.uid; if (!m_usercache.contains(uid)) { struct passwd* user = getpwuid(uid); if (user) { m_usercache.insert(uid, QString::fromLatin1(user->pw_name)); str = user->pw_name; } else { str = QString::number(uid); } } else { str = m_usercache.value(uid); } entry.insert(KIO::UDSEntry::UDS_USER, str); const gid_t gid = attributes.gid; if (!m_groupcache.contains(gid)) { struct group* grp = getgrgid(gid); if (grp) { m_groupcache.insert(gid, QString::fromLatin1(grp->gr_name)); str = grp->gr_name; } else { str = QString::number(gid); } } else { str = m_groupcache.value(gid); } entry.insert(KIO::UDSEntry::UDS_GROUP, str); } void NFSProtocolV2::completeBadLinkUDSEntry(KIO::UDSEntry& entry, const fattr& attributes) { entry.insert(KIO::UDSEntry::UDS_SIZE, 0LL); entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, attributes.mtime.seconds); entry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, attributes.atime.seconds); entry.insert(KIO::UDSEntry::UDS_CREATION_TIME, attributes.ctime.seconds); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFMT - 1); entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRWXU | S_IRWXG | S_IRWXO); entry.insert(KIO::UDSEntry::UDS_USER, attributes.uid); entry.insert(KIO::UDSEntry::UDS_GROUP, attributes.gid); } diff --git a/nfs/nfsv3.cpp b/nfs/nfsv3.cpp index a76bc404..9ce93f05 100644 --- a/nfs/nfsv3.cpp +++ b/nfs/nfsv3.cpp @@ -1,2195 +1,2195 @@ /* This file is part of the KDE project Copyright(C) 2000 Alexander Neundorf , 2014 Mathias Tillman This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "nfsv3.h" #include #include // This is needed on Solaris so that rpc.h defines clnttcp_create etc. #ifndef PORTMAP #define PORTMAP #endif #include // for rpc calls #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // This ioslave is for NFS version 3. #define NFSPROG 100003UL #define NFSVERS 3UL #define NFS3_MAXDATA 32768 #define NFS3_MAXPATHLEN PATH_MAX NFSProtocolV3::NFSProtocolV3(NFSSlave* slave) : NFSProtocol(slave), m_slave(slave), m_mountClient(nullptr), m_mountSock(-1), m_nfsClient(nullptr), m_nfsSock(-1), m_readBufferSize(0), m_writeBufferSize(0), m_readDirSize(0) { qCDebug(LOG_KIO_NFS) << "NFS3::NFS3"; clnt_timeout.tv_sec = 20; clnt_timeout.tv_usec = 0; } NFSProtocolV3::~NFSProtocolV3() { closeConnection(); } bool NFSProtocolV3::isCompatible(bool& connectionError) { qCDebug(LOG_KIO_NFS); int ret = -1; CLIENT* client = nullptr; int sock = 0; if (NFSProtocol::openConnection(m_currentHost, NFSPROG, NFSVERS, client, sock) == 0) { timeval check_timeout; check_timeout.tv_sec = 20; check_timeout.tv_usec = 0; // Check if the NFS version is compatible ret = clnt_call(client, NFSPROC3_NULL, (xdrproc_t) xdr_void, nullptr, (xdrproc_t) xdr_void, nullptr, check_timeout); connectionError = false; } else { qCDebug(LOG_KIO_NFS) << "openConnection failed"; connectionError = true; } if (sock != -1) { ::close(sock); } if (client != nullptr) { CLNT_DESTROY(client); } qCDebug(LOG_KIO_NFS) << ret; return (ret == RPC_SUCCESS); } bool NFSProtocolV3::isConnected() const { return (m_nfsClient != nullptr); } void NFSProtocolV3::closeConnection() { qCDebug(LOG_KIO_NFS); // Unmount all exported dirs(if any) if (m_mountClient != nullptr) { clnt_call(m_mountClient, MOUNTPROC3_UMNTALL, (xdrproc_t) xdr_void, nullptr, (xdrproc_t) xdr_void, nullptr, clnt_timeout); } if (m_mountSock >= 0) { ::close(m_mountSock); m_mountSock = -1; } if (m_nfsSock >= 0) { ::close(m_nfsSock); m_nfsSock = -1; } if (m_mountClient != nullptr) { CLNT_DESTROY(m_mountClient); m_mountClient = nullptr; } if (m_nfsClient != nullptr) { CLNT_DESTROY(m_nfsClient); m_nfsClient = nullptr; } } NFSFileHandle NFSProtocolV3::lookupFileHandle(const QString& path) { int rpcStatus; LOOKUP3res res; if (lookupHandle(path, rpcStatus, res)) { NFSFileHandle fh = res.LOOKUP3res_u.resok.object; // Is it a link? Get the link target. if (res.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.type == NF3LNK) { READLINK3args readLinkArgs; memset(&readLinkArgs, 0, sizeof(readLinkArgs)); fh.toFH(readLinkArgs.symlink); char dataBuffer[NFS3_MAXPATHLEN]; READLINK3res readLinkRes; memset(&readLinkRes, 0, sizeof(readLinkRes)); readLinkRes.READLINK3res_u.resok.data = dataBuffer; int rpcStatus = clnt_call(m_nfsClient, NFSPROC3_READLINK, (xdrproc_t) xdr_READLINK3args, reinterpret_cast(&readLinkArgs), (xdrproc_t) xdr_READLINK3res, reinterpret_cast(&readLinkRes), clnt_timeout); if (rpcStatus == RPC_SUCCESS && readLinkRes.status == NFS3_OK) { const QString linkDest = QString::fromLocal8Bit(readLinkRes.READLINK3res_u.resok.data); QString linkPath; if (QFileInfo(linkDest).isAbsolute()) { linkPath = linkDest; } else { linkPath = QFileInfo(QFileInfo(path).path(), linkDest).absoluteFilePath(); } LOOKUP3res linkRes; if (lookupHandle(linkPath, rpcStatus, linkRes)) { // It's a link, so return the target file handle, and add the link source to it. NFSFileHandle linkFh = linkRes.LOOKUP3res_u.resok.object; linkFh.setLinkSource(res.LOOKUP3res_u.resok.object); qCDebug(LOG_KIO_NFS) << "Found target -" << linkPath; return linkFh; } } // If we have reached this point the file is a link, but we failed to get the target. fh.setBadLink(); qCDebug(LOG_KIO_NFS) << path << "is an invalid link!!"; } return fh; } return NFSFileHandle(); } /* Open connection connects to the mount daemon on the server side. In order to do this it needs authentication and calls auth_unix_create(). Then it asks the mount daemon for the exported shares. Then it tries to mount all these shares. If this succeeded for at least one of them, a client for the nfs daemon is created. */ void NFSProtocolV3::openConnection() { qCDebug(LOG_KIO_NFS) << m_currentHost; // Destroy the old connection first closeConnection(); int connErr; if ((connErr = NFSProtocol::openConnection(m_currentHost, MOUNT_PROGRAM, MOUNT_V3, m_mountClient, m_mountSock)) != 0) { closeConnection(); m_slave->error(connErr, m_currentHost); return; } exports3 exportlist; memset(&exportlist, 0, sizeof(exportlist)); int clnt_stat = clnt_call(m_mountClient, MOUNTPROC3_EXPORT, (xdrproc_t) xdr_void, nullptr, (xdrproc_t) xdr_exports3, reinterpret_cast(&exportlist), clnt_timeout); if (!checkForError(clnt_stat, 0, m_currentHost.toLatin1())) { closeConnection(); return; } int exportsCount = 0; QStringList failList; mountres3 fhStatus; for (; exportlist != nullptr; exportlist = exportlist->ex_next, exportsCount++) { memset(&fhStatus, 0, sizeof(fhStatus)); clnt_stat = clnt_call(m_mountClient, MOUNTPROC3_MNT, (xdrproc_t) xdr_dirpath3, reinterpret_cast(&exportlist->ex_dir), (xdrproc_t) xdr_mountres3, reinterpret_cast(&fhStatus), clnt_timeout); if (fhStatus.fhs_status == 0) { QString fname = QFileInfo(QDir("/"), exportlist->ex_dir).filePath(); // Check if the dir is already exported if (NFSProtocol::isExportedDir(fname)) { continue; } addFileHandle(fname, static_cast(fhStatus.mountres3_u.mountinfo.fhandle)); addExportedDir(fname); } else { failList.append(exportlist->ex_dir); } } if (failList.size() > 0) { m_slave->error(KIO::ERR_COULD_NOT_MOUNT, i18n("Failed to mount %1", failList.join(", "))); // All exports failed to mount, fail if (failList.size() == exportsCount) { closeConnection(); return; } } if ((connErr = NFSProtocol::openConnection(m_currentHost, NFSPROG, NFSVERS, m_nfsClient, m_nfsSock)) != 0) { closeConnection(); m_slave->error(connErr, m_currentHost); } m_slave->connected(); qCDebug(LOG_KIO_NFS) << "openConnection succeeded"; } void NFSProtocolV3::listDir(const QUrl& url) { qCDebug(LOG_KIO_NFS) << url; // We should always be connected if it reaches this point, // but better safe than sorry! if (!isConnected()) { return; } if (url.isEmpty()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } const QString path(url.path()); // Is it part of an exported(virtual) dir? if (isExportedDir(path)) { qCDebug(LOG_KIO_NFS) << "Listing virtual dir" << path; QStringList virtualList; for (QStringList::const_iterator it = getExportedDirs().constBegin(); it != getExportedDirs().constEnd(); ++it) { // When an export is multiple levels deep(/mnt/nfs for example) we only // want to display one level at a time. QString name = (*it); name = name.remove(0, path.length()); if (name.startsWith(QDir::separator())) { name = name.mid(1); } if (name.indexOf(QDir::separator()) != -1) { name.truncate(name.indexOf(QDir::separator())); } if (!virtualList.contains(name)) { virtualList.append(name); } } for (QStringList::const_iterator it = virtualList.constBegin(); it != virtualList.constEnd(); ++it) { qCDebug(LOG_KIO_NFS) << "Found " << (*it) << "in exported dir"; KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, (*it)); createVirtualDirEntry(entry); m_slave->listEntry(entry); } m_slave->finished(); return; } const NFSFileHandle fh = getFileHandle(path); // There doesn't seem to be an invalid link error code in KIO, so this will have to do. if (fh.isInvalid() || fh.isBadLink()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, path); return; } // Get the preferred read dir size from the server if (m_readDirSize == 0) { initPreferredSizes(fh); } READDIRPLUS3args listargs; memset(&listargs, 0, sizeof(listargs)); listargs.dircount = m_readDirSize; listargs.maxcount = sizeof(entryplus3) * m_readDirSize; // Not really sure what this should be set to. fh.toFH(listargs.dir); READDIRPLUS3res listres; memset(&listres, 0, sizeof(listres)); entryplus3* lastEntry = nullptr; do { memset(&listres, 0, sizeof(listres)); // In case that we didn't get all entries we need to set the cookie to the last one we actually received. if (lastEntry != nullptr) { listargs.cookie = lastEntry->cookie; } int clnt_stat = clnt_call(m_nfsClient, NFSPROC3_READDIRPLUS, (xdrproc_t) xdr_READDIRPLUS3args, reinterpret_cast(&listargs), (xdrproc_t) xdr_READDIRPLUS3res, reinterpret_cast(&listres), clnt_timeout); // Not a supported call? Try the old READDIR method. if (listres.status == NFS3ERR_NOTSUPP) { listDirCompat(url); return; } // Do we have an error? There's not much more we can do but to abort at this point. if (!checkForError(clnt_stat, listres.status, path)) { return; } for (entryplus3* dirEntry = listres.READDIRPLUS3res_u.resok.reply.entries; dirEntry != nullptr; dirEntry = dirEntry->nextentry) { if (dirEntry->name == QString(".") || dirEntry->name == QString("..")) { continue; } const QString& filePath = QFileInfo(QDir(path), dirEntry->name).filePath(); KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, dirEntry->name); // Is it a symlink ? if (dirEntry->name_attributes.post_op_attr_u.attributes.type == NF3LNK) { int rpcStatus; READLINK3res readLinkRes; char nameBuf[NFS3_MAXPATHLEN]; if (readLink(filePath, rpcStatus, readLinkRes, nameBuf)) { QString linkDest = QString::fromLocal8Bit(readLinkRes.READLINK3res_u.resok.data); entry.insert(KIO::UDSEntry::UDS_LINK_DEST, linkDest); bool badLink = true; NFSFileHandle linkFH; if (isValidLink(path, linkDest)) { QString linkPath; if (QFileInfo(linkDest).isAbsolute()) { linkPath = linkDest; } else { linkPath = QFileInfo(path, linkDest).absoluteFilePath(); } int rpcStatus; LOOKUP3res lookupRes; if (lookupHandle(linkPath, rpcStatus, lookupRes)) { GETATTR3res attrAndStat; if (getAttr(linkPath, rpcStatus, attrAndStat)) { badLink = false; linkFH = lookupRes.LOOKUP3res_u.resok.object; linkFH.setLinkSource(dirEntry->name_handle.post_op_fh3_u.handle); completeUDSEntry(entry, attrAndStat.GETATTR3res_u.resok.obj_attributes); } } } if (badLink) { linkFH = dirEntry->name_handle.post_op_fh3_u.handle; linkFH.setBadLink(); completeBadLinkUDSEntry(entry, dirEntry->name_attributes.post_op_attr_u.attributes); } addFileHandle(filePath, linkFH); } else { entry.insert(KIO::UDSEntry::UDS_LINK_DEST, i18n("Unknown target")); completeBadLinkUDSEntry(entry, dirEntry->name_attributes.post_op_attr_u.attributes); } } else { addFileHandle(filePath, static_cast(dirEntry->name_handle.post_op_fh3_u.handle)); completeUDSEntry(entry, dirEntry->name_attributes.post_op_attr_u.attributes); } m_slave->listEntry(entry); lastEntry = dirEntry; } } while (listres.READDIRPLUS3res_u.resok.reply.entries != nullptr && !listres.READDIRPLUS3res_u.resok.reply.eof); m_slave->finished(); } void NFSProtocolV3::listDirCompat(const QUrl& url) { // We should always be connected if it reaches this point, // but better safe than sorry! if (!isConnected()) { return; } if (url.isEmpty()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, url.path()); } const QString path(url.path()); // Is it part of an exported (virtual) dir? if (NFSProtocol::isExportedDir(path)) { QStringList virtualList; for (QStringList::const_iterator it = getExportedDirs().constBegin(); it != getExportedDirs().constEnd(); ++it) { // When an export is multiple levels deep(mnt/nfs for example) we only // want to display one level at a time. QString name = (*it); name = name.remove(0, path.length()); if (name.startsWith('/')) { name = name.mid(1); } if (name.indexOf('/') != -1) { name.truncate(name.indexOf('/')); } if (!virtualList.contains(name)) { virtualList.append(name); } } for (QStringList::const_iterator it = virtualList.constBegin(); it != virtualList.constEnd(); ++it) { qCDebug(LOG_KIO_NFS) << "Found " << (*it) << "in exported dir"; KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, (*it)); createVirtualDirEntry(entry); m_slave->listEntry(entry); } m_slave->finished(); return; } const NFSFileHandle fh = getFileHandle(path); if (fh.isInvalid() || fh.isBadLink()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, path); return; } QStringList filesToList; READDIR3args listargs; memset(&listargs, 0, sizeof(listargs)); listargs.count = m_readDirSize; fh.toFH(listargs.dir); READDIR3res listres; entry3* lastEntry = nullptr; do { memset(&listres, 0, sizeof(listres)); // In case that we didn't get all entries we need to set the cookie to the last one we actually received if (lastEntry != nullptr) { listargs.cookie = lastEntry->cookie; } int clnt_stat = clnt_call(m_nfsClient, NFSPROC3_READDIR, (xdrproc_t) xdr_READDIR3args, reinterpret_cast(&listargs), (xdrproc_t) xdr_READDIR3res, reinterpret_cast(&listres), clnt_timeout); if (!checkForError(clnt_stat, listres.status, path)) { return; } for (entry3* dirEntry = listres.READDIR3res_u.resok.reply.entries; dirEntry != nullptr; dirEntry = dirEntry->nextentry) { if (dirEntry->name != QString(".") && dirEntry->name != QString("..")) { filesToList.append(QFile::decodeName(dirEntry->name)); } lastEntry = dirEntry; } } while (!listres.READDIR3res_u.resok.reply.eof); // Loop through all files, getting attributes and link path. KIO::UDSEntry entry; for (QStringList::const_iterator it = filesToList.constBegin(); it != filesToList.constEnd(); ++it) { QString filePath = QFileInfo(QDir(path), (*it)).filePath(); int rpcStatus; LOOKUP3res dirres; if (!lookupHandle(filePath, rpcStatus, dirres)) { qCDebug(LOG_KIO_NFS) << "Failed to lookup" << filePath << ", rpc:" << rpcStatus << ", nfs:" << dirres.status; // Try the next file instead of aborting continue; } entry.clear(); entry.insert(KIO::UDSEntry::UDS_NAME, (*it)); // Is it a symlink? if (dirres.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.type == NF3LNK) { int rpcStatus; READLINK3res readLinkRes; char nameBuf[NFS3_MAXPATHLEN]; if (readLink(filePath, rpcStatus, readLinkRes, nameBuf)) { const QString linkDest = QString::fromLocal8Bit(readLinkRes.READLINK3res_u.resok.data); entry.insert(KIO::UDSEntry::UDS_LINK_DEST, linkDest); bool badLink = true; NFSFileHandle linkFH; if (isValidLink(path, linkDest)) { QString linkPath; if (QFileInfo(linkDest).isAbsolute()) { linkPath = linkDest; } else { linkPath = QFileInfo(path, linkDest).absoluteFilePath(); } int rpcStatus; LOOKUP3res lookupRes; if (lookupHandle(linkPath, rpcStatus, lookupRes)) { GETATTR3res attrAndStat; if (getAttr(linkPath, rpcStatus, attrAndStat)) { badLink = false; linkFH = lookupRes.LOOKUP3res_u.resok.object; linkFH.setLinkSource(dirres.LOOKUP3res_u.resok.object); completeUDSEntry(entry, attrAndStat.GETATTR3res_u.resok.obj_attributes); } } } if (badLink) { linkFH = dirres.LOOKUP3res_u.resok.object; linkFH.setBadLink(); completeBadLinkUDSEntry(entry, dirres.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes); } addFileHandle(filePath, linkFH); } else { entry.insert(KIO::UDSEntry::UDS_LINK_DEST, i18n("Unknown target")); completeBadLinkUDSEntry(entry, dirres.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes); } } else { addFileHandle(filePath, dirres.LOOKUP3res_u.resok.object); completeUDSEntry(entry, dirres.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes); } m_slave->listEntry(entry); } m_slave->finished(); } void NFSProtocolV3::stat(const QUrl& url) { qCDebug(LOG_KIO_NFS) << url; const QString path(url.path()); // We can't stat an exported dir, but we know it's a dir. if (isExportedDir(path)) { KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, path); createVirtualDirEntry(entry); m_slave->statEntry(entry); m_slave->finished(); return; } const NFSFileHandle fh = getFileHandle(path); if (fh.isInvalid()) { qCDebug(LOG_KIO_NFS) << "File handle is invalid"; m_slave->error(KIO::ERR_DOES_NOT_EXIST, path); return; } int rpcStatus; GETATTR3res attrAndStat; if (!getAttr(path, rpcStatus, attrAndStat)) { checkForError(rpcStatus, attrAndStat.status, path); return; } const QFileInfo fileInfo(path); KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, fileInfo.fileName()); // Is it a symlink? if (attrAndStat.GETATTR3res_u.resok.obj_attributes.type == NF3LNK) { qCDebug(LOG_KIO_NFS) << "It's a symlink"; //get the link dest QString linkDest; int rpcStatus; READLINK3res readLinkRes; char nameBuf[NFS3_MAXPATHLEN]; if (readLink(path, rpcStatus, readLinkRes, nameBuf)) { linkDest = QString::fromLocal8Bit(readLinkRes.READLINK3res_u.resok.data); } else { entry.insert(KIO::UDSEntry::UDS_LINK_DEST, linkDest); completeBadLinkUDSEntry(entry, attrAndStat.GETATTR3res_u.resok.obj_attributes); m_slave->statEntry(entry); m_slave->finished(); return; } qCDebug(LOG_KIO_NFS) << "link dest is" << linkDest; entry.insert(KIO::UDSEntry::UDS_LINK_DEST, linkDest); if (!isValidLink(fileInfo.path(), linkDest)) { completeBadLinkUDSEntry(entry, attrAndStat.GETATTR3res_u.resok.obj_attributes); } else { QString linkPath; if (QFileInfo(linkDest).isAbsolute()) { linkPath = linkDest; } else { linkPath = QFileInfo(fileInfo.path(), linkDest).absoluteFilePath(); } int rpcStatus; GETATTR3res attrAndStat; if (!getAttr(linkPath, rpcStatus, attrAndStat)) { checkForError(rpcStatus, attrAndStat.status, linkPath); return; } completeUDSEntry(entry, attrAndStat.GETATTR3res_u.resok.obj_attributes); } } else { completeUDSEntry(entry, attrAndStat.GETATTR3res_u.resok.obj_attributes); } m_slave->statEntry(entry); m_slave->finished(); } void NFSProtocolV3::setHost(const QString& host) { qCDebug(LOG_KIO_NFS) << host; if (host.isEmpty()) { m_slave->error(KIO::ERR_UNKNOWN_HOST, QString()); return; } // No need to update if the host hasn't changed if (host == m_currentHost) { return; } m_currentHost = host; closeConnection(); } void NFSProtocolV3::mkdir(const QUrl& url, int permissions) { qCDebug(LOG_KIO_NFS) << url; const QString path(url.path()); const QFileInfo fileInfo(path); if (isExportedDir(fileInfo.path())) { m_slave->error(KIO::ERR_ACCESS_DENIED, path); return; } const NFSFileHandle fh = getFileHandle(fileInfo.path()); if (fh.isInvalid() || fh.isBadLink()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, path); return; } MKDIR3args createArgs; memset(&createArgs, 0, sizeof(createArgs)); fh.toFH(createArgs.where.dir); QByteArray tmpName = QFile::encodeName(fileInfo.fileName()); createArgs.where.name = tmpName.data(); createArgs.attributes.mode.set_it = true; if (permissions == -1) { createArgs.attributes.mode.set_mode3_u.mode = 0755; } else { createArgs.attributes.mode.set_mode3_u.mode = permissions; } MKDIR3res dirres; memset(&dirres, 0, sizeof(dirres)); int clnt_stat = clnt_call(m_nfsClient, NFSPROC3_MKDIR, (xdrproc_t) xdr_MKDIR3args, reinterpret_cast(&createArgs), (xdrproc_t) xdr_MKDIR3res, reinterpret_cast(&dirres), clnt_timeout); if (!checkForError(clnt_stat, dirres.status, path)) { return; } m_slave->finished(); } void NFSProtocolV3::del(const QUrl& url, bool/* isfile*/) { qCDebug(LOG_KIO_NFS) << url; const QString path(url.path()); if (isExportedDir(QFileInfo(path).path())) { m_slave->error(KIO::ERR_ACCESS_DENIED, path); return; } int rpcStatus; REMOVE3res res; if (!remove(path, rpcStatus, res)) { checkForError(rpcStatus, res.status, path); return; } m_slave->finished(); } void NFSProtocolV3::chmod(const QUrl& url, int permissions) { qCDebug(LOG_KIO_NFS) << url; const QString path(url.path()); if (isExportedDir(path)) { m_slave->error(KIO::ERR_ACCESS_DENIED, path); return; } sattr3 attributes; memset(&attributes, 0, sizeof(attributes)); attributes.mode.set_it = true; attributes.mode.set_mode3_u.mode = permissions; int rpcStatus; SETATTR3res setAttrRes; if (!setAttr(path, attributes, rpcStatus, setAttrRes)) { checkForError(rpcStatus, setAttrRes.status, path); return; } m_slave->finished(); } void NFSProtocolV3::get(const QUrl& url) { qCDebug(LOG_KIO_NFS) << url; const QString path(url.path()); const NFSFileHandle fh = getFileHandle(path); if (fh.isInvalid() || fh.isBadLink()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, path); return; } // Get the optimal read buffer size. if (m_readBufferSize == 0) { initPreferredSizes(fh); } READ3args readArgs; memset(&readArgs, 0, sizeof(readArgs)); fh.toFH(readArgs.file); readArgs.offset = 0; readArgs.count = m_readBufferSize; READ3res readRes; memset(&readRes, 0, sizeof(readRes)); readRes.READ3res_u.resok.data.data_len = m_readBufferSize; readRes.READ3res_u.resok.data.data_val = new char[m_readBufferSize]; // Most likely indicates out of memory if (!readRes.READ3res_u.resok.data.data_val) { m_slave->error(KIO::ERR_OUT_OF_MEMORY, path); return; } bool validRead = false; bool hasError = false; int read = 0; QByteArray readBuffer; do { int clnt_stat = clnt_call(m_nfsClient, NFSPROC3_READ, (xdrproc_t) xdr_READ3args, reinterpret_cast(&readArgs), (xdrproc_t) xdr_READ3res, reinterpret_cast(&readRes), clnt_timeout); // We are trying to read a directory, fail quietly if (readRes.status == NFS3ERR_ISDIR) { break; } if (!checkForError(clnt_stat, readRes.status, path)) { hasError = true; break; } read = readRes.READ3res_u.resok.count; readBuffer.setRawData(readRes.READ3res_u.resok.data.data_val, read); if (readArgs.offset == 0) { const QMimeDatabase db; const QMimeType type = db.mimeTypeForFileNameAndData(url.fileName(), readBuffer); m_slave->mimeType(type.name()); m_slave->totalSize(readRes.READ3res_u.resok.file_attributes.post_op_attr_u.attributes.size); } readArgs.offset += read; if (read > 0) { validRead = true; m_slave->data(readBuffer); m_slave->processedSize(readArgs.offset); } } while (read > 0); if (readRes.READ3res_u.resok.data.data_val != nullptr) { delete [] readRes.READ3res_u.resok.data.data_val; } // Only send the read data to the slave if we have actually sent some. if (validRead) { m_slave->data(QByteArray()); m_slave->processedSize(readArgs.offset); } if (!hasError) { m_slave->finished(); } } void NFSProtocolV3::put(const QUrl& url, int _mode, KIO::JobFlags flags) { qCDebug(LOG_KIO_NFS) << url; const QString destPath(url.path()); if (isExportedDir(QFileInfo(destPath).path())) { m_slave->error(KIO::ERR_WRITE_ACCESS_DENIED, destPath); return; } NFSFileHandle destFH = getFileHandle(destPath); if (destFH.isBadLink()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, destPath); return; } // the file exists and we don't want to overwrite if (!destFH.isInvalid() && ((flags & KIO::Overwrite) == 0)) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } // Get the optimal write buffer size if (m_writeBufferSize == 0) { initPreferredSizes(destFH); } int rpcStatus; CREATE3res createRes; if (!create(destPath, _mode, rpcStatus, createRes)) { checkForError(rpcStatus, createRes.status, destPath); return; } // We created the file successfully. destFH = createRes.CREATE3res_u.resok.obj.post_op_fh3_u.handle; int result; WRITE3args writeArgs; memset(&writeArgs, 0, sizeof(writeArgs)); destFH.toFH(writeArgs.file); writeArgs.offset = 0; writeArgs.stable = FILE_SYNC; WRITE3res writeRes; memset(&writeRes, 0, sizeof(writeRes)); // Loop until we get 0 (end of data). int bytesWritten = 0; bool error = false; do { QByteArray buffer; m_slave->dataReq(); result = m_slave->readData(buffer); if (result > 0) { char* data = buffer.data(); uint32 bytesToWrite = buffer.size(); int writeNow(0); do { if (bytesToWrite > m_writeBufferSize) { writeNow = m_writeBufferSize; } else { writeNow = bytesToWrite; } writeArgs.data.data_val = data; writeArgs.data.data_len = writeNow; writeArgs.count = writeNow; int clnt_stat = clnt_call(m_nfsClient, NFSPROC3_WRITE, (xdrproc_t) xdr_WRITE3args, reinterpret_cast(&writeArgs), (xdrproc_t) xdr_WRITE3res, reinterpret_cast(&writeRes), clnt_timeout); if (!checkForError(clnt_stat, writeRes.status, destPath)) { error = true; break; } writeNow = writeRes.WRITE3res_u.resok.count; bytesWritten += writeNow; writeArgs.offset = bytesWritten; data = data + writeNow; bytesToWrite -= writeNow; } while (bytesToWrite > 0); } if (error) { break; } } while (result > 0); if (!error) { m_slave->finished(); } } void NFSProtocolV3::rename(const QUrl& src, const QUrl& dest, KIO::JobFlags _flags) { qCDebug(LOG_KIO_NFS) << src << dest; const QString srcPath(src.path()); if (isExportedDir(srcPath)) { m_slave->error(KIO::ERR_CANNOT_RENAME, srcPath); return; } const QString destPath(dest.path()); if (isExportedDir(destPath)) { m_slave->error(KIO::ERR_ACCESS_DENIED, destPath); return; } if (!getFileHandle(destPath).isInvalid() && (_flags & KIO::Overwrite) == 0) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } int rpcStatus; RENAME3res res; if (!rename(srcPath, destPath, rpcStatus, res)) { checkForError(rpcStatus, res.status, destPath); return; } m_slave->finished(); } void NFSProtocolV3::copySame(const QUrl& src, const QUrl& dest, int _mode, KIO::JobFlags _flags) { qCDebug(LOG_KIO_NFS) << src << "to" << dest; const QString srcPath(src.path()); if (isExportedDir(QFileInfo(srcPath).path())) { m_slave->error(KIO::ERR_ACCESS_DENIED, srcPath); return; } const NFSFileHandle srcFH = getFileHandle(srcPath); if (srcFH.isInvalid()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, srcPath); return; } const QString destPath(dest.path()); if (isExportedDir(QFileInfo(destPath).path())) { m_slave->error(KIO::ERR_ACCESS_DENIED, destPath); return; } // The file exists and we don't want to overwrite if (!getFileHandle(destPath).isInvalid() && (_flags & KIO::Overwrite) == 0) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } // Is it a link? No need to copy the data then, just copy the link destination. if (srcFH.isLink()) { //get the link dest int rpcStatus; READLINK3res readLinkRes; char nameBuf[NFS3_MAXPATHLEN]; if (!readLink(srcPath, rpcStatus, readLinkRes, nameBuf)) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, srcPath); return; } const QString linkPath = QString::fromLocal8Bit(readLinkRes.READLINK3res_u.resok.data); SYMLINK3res linkRes; if (!symLink(linkPath, destPath, rpcStatus, linkRes)) { checkForError(rpcStatus, linkRes.status, linkPath); return; } m_slave->finished(); return; } unsigned long resumeOffset = 0; bool bResume = false; const QString partFilePath = destPath + QLatin1String(".part"); const NFSFileHandle partFH = getFileHandle(partFilePath); const bool bPartExists = !partFH.isInvalid(); - const bool bMarkPartial = m_slave->config()->readEntry("MarkPartial", true); + const bool bMarkPartial = m_slave->configValue(QStringLiteral("MarkPartial"), true); if (bPartExists) { int rpcStatus; LOOKUP3res partRes; if (lookupHandle(partFilePath, rpcStatus, partRes)) { if (bMarkPartial && partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.size > 0) { if (partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.type == NF3DIR) { m_slave->error(KIO::ERR_IS_DIRECTORY, partFilePath); return; } bResume = m_slave->canResume(partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.size); if (bResume) { resumeOffset = partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.size; } } } // Remove the part file if we are not resuming if (!bResume) { if (!remove(partFilePath)) { qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring..."; } } } // Create the file if we are not resuming a parted transfer, // or if we are not using part files(bResume is false in that case) NFSFileHandle destFH; if (!bResume) { QString createPath; if (bMarkPartial) { createPath = partFilePath; } else { createPath = destPath; } int rpcStatus; CREATE3res createRes; if (!create(createPath, _mode, rpcStatus, createRes)) { checkForError(rpcStatus, createRes.status, createPath); return; } destFH = createRes.CREATE3res_u.resok.obj.post_op_fh3_u.handle; } else { // Since we are resuming it's implied that we are using a part file, // which should exist at this point. destFH = getFileHandle(partFilePath); qCDebug(LOG_KIO_NFS) << "Resuming old transfer"; } // Check what buffer size we should use, always use the smallest one. const int bufferSize = (m_readBufferSize < m_writeBufferSize) ? m_readBufferSize : m_writeBufferSize; WRITE3args writeArgs; memset(&writeArgs, 0, sizeof(writeArgs)); destFH.toFH(writeArgs.file); writeArgs.offset = 0; writeArgs.data.data_val = new char[bufferSize]; writeArgs.stable = FILE_SYNC; READ3args readArgs; memset(&readArgs, 0, sizeof(readArgs)); srcFH.toFH(readArgs.file); readArgs.offset = 0; readArgs.count = bufferSize; if (bResume) { writeArgs.offset = resumeOffset; readArgs.offset = resumeOffset; } READ3res readRes; readRes.READ3res_u.resok.data.data_val = writeArgs.data.data_val; WRITE3res writeRes; memset(&writeRes, 0, sizeof(WRITE3res)); bool error = false; int bytesRead = 0; do { int clnt_stat = clnt_call(m_nfsClient, NFSPROC3_READ, (xdrproc_t) xdr_READ3args, reinterpret_cast(&readArgs), (xdrproc_t) xdr_READ3res, reinterpret_cast(&readRes), clnt_timeout); if (!checkForError(clnt_stat, readRes.status, srcPath)) { error = true; break; } bytesRead = readRes.READ3res_u.resok.data.data_len; // We should only send out the total size and mimetype at the start of the transfer if (readArgs.offset == 0 || (bResume && writeArgs.offset == resumeOffset)) { QMimeDatabase db; QMimeType type = db.mimeTypeForFileNameAndData(src.fileName(), QByteArray::fromRawData(writeArgs.data.data_val, bytesRead)); m_slave->mimeType(type.name()); m_slave->totalSize(readRes.READ3res_u.resok.file_attributes.post_op_attr_u.attributes.size); } if (bytesRead > 0) { readArgs.offset += bytesRead; writeArgs.count = bytesRead; writeArgs.data.data_len = bytesRead; clnt_stat = clnt_call(m_nfsClient, NFSPROC3_WRITE, (xdrproc_t) xdr_WRITE3args, reinterpret_cast(&writeArgs), (xdrproc_t) xdr_WRITE3res, reinterpret_cast(&writeRes), clnt_timeout); if (!checkForError(clnt_stat, writeRes.status, destPath)) { error = true; break; } writeArgs.offset += bytesRead; m_slave->processedSize(readArgs.offset); } } while (bytesRead > 0); delete [] writeArgs.data.data_val; if (error) { if (bMarkPartial) { // Remove the part file if it's smaller than the minimum keep size. - const unsigned int size = m_slave->config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); + const unsigned int size = m_slave->configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE); if (writeArgs.offset < size) { if (!remove(partFilePath)) { qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring..."; } } } } else { // Rename partial file to its original name. if (bMarkPartial) { // Remove the destination file(if it exists) if (!getFileHandle(destPath).isInvalid() && !remove(destPath)) { qCDebug(LOG_KIO_NFS) << "Could not remove destination file" << destPath << ", ignoring..."; } if (!rename(partFilePath, destPath)) { qCDebug(LOG_KIO_NFS) << "failed to rename" << partFilePath << "to" << destPath; m_slave->error(KIO::ERR_CANNOT_RENAME_PARTIAL, partFilePath); return; } } // Restore modification time int rpcStatus; GETATTR3res attrRes; if (getAttr(srcPath, rpcStatus, attrRes)) { sattr3 attributes; memset(&attributes, 0, sizeof(attributes)); attributes.mtime.set_it = SET_TO_CLIENT_TIME; attributes.mtime.set_mtime_u.mtime.seconds = attrRes.GETATTR3res_u.resok.obj_attributes.mtime.seconds; attributes.mtime.set_mtime_u.mtime.nseconds = attrRes.GETATTR3res_u.resok.obj_attributes.mtime.nseconds; SETATTR3res attrSetRes; if (!setAttr(destPath, attributes, rpcStatus, attrSetRes)) { qCDebug(LOG_KIO_NFS) << "Failed to restore mtime, ignoring..." << rpcStatus << attrSetRes.status; } } qCDebug(LOG_KIO_NFS) << "Copied" << writeArgs.offset << "bytes of data"; m_slave->processedSize(readArgs.offset); m_slave->finished(); } } void NFSProtocolV3::copyFrom(const QUrl& src, const QUrl& dest, int _mode, KIO::JobFlags _flags) { qCDebug(LOG_KIO_NFS) << src << "to" << dest; const QString srcPath(src.path()); const NFSFileHandle srcFH = getFileHandle(srcPath); if (srcFH.isInvalid()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, srcPath); return; } const QString destPath(dest.path()); // The file exists and we don't want to overwrite. if (QFile::exists(destPath) && (_flags & KIO::Overwrite) == 0) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } // Is it a link? No need to copy the data then, just copy the link destination. if (srcFH.isLink()) { qCDebug(LOG_KIO_NFS) << "Is a link"; //get the link dest int rpcStatus; READLINK3res readLinkRes; char nameBuf[NFS3_MAXPATHLEN]; if (!readLink(srcPath, rpcStatus, readLinkRes, nameBuf)) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, srcPath); return; } QFile::link(QString::fromLocal8Bit(readLinkRes.READLINK3res_u.resok.data), destPath); m_slave->finished(); return; } if (m_readBufferSize == 0) { initPreferredSizes(srcFH); } unsigned int resumeOffset = 0; bool bResume = false; const QFileInfo partInfo(destPath + QLatin1String(".part")); const bool bPartExists = partInfo.exists(); - const bool bMarkPartial = m_slave->config()->readEntry("MarkPartial", true); + const bool bMarkPartial = m_slave->configValue(QStringLiteral("MarkPartial"), true); if (bMarkPartial && bPartExists && partInfo.size() > 0) { if (partInfo.isDir()) { m_slave->error(KIO::ERR_IS_DIRECTORY, partInfo.absoluteFilePath()); return; } bResume = m_slave->canResume(partInfo.size()); resumeOffset = partInfo.size(); } if (bPartExists && !bResume) { QFile::remove(partInfo.absoluteFilePath()); } QFile::OpenMode openMode; QString outFileName; if (bResume) { outFileName = partInfo.absoluteFilePath(); openMode = QFile::WriteOnly | QFile::Append; } else { outFileName = (bMarkPartial ? partInfo.absoluteFilePath() : destPath); openMode = QFile::WriteOnly | QFile::Truncate; } QFile destFile(outFileName); if (!bResume) { QFile::Permissions perms; if (_mode == -1) { perms = QFile::ReadOwner | QFile::WriteOwner; } else { perms = KIO::convertPermissions(_mode | QFile::WriteOwner); } destFile.setPermissions(perms); } if (!destFile.open(openMode)) { switch (destFile.error()) { case QFile::OpenError: if (bResume) { m_slave->error(KIO::ERR_CANNOT_RESUME, destPath); } else { m_slave->error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, destPath); } break; case QFile::PermissionsError: m_slave->error(KIO::ERR_WRITE_ACCESS_DENIED, destPath); break; default: m_slave->error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, destPath); break; } return; } READ3args readArgs; srcFH.toFH(readArgs.file); if (bResume) { readArgs.offset = resumeOffset; } else { readArgs.offset = 0; } readArgs.count = m_readBufferSize; READ3res readRes; memset(&readRes, 0, sizeof(readres)); readRes.READ3res_u.resok.data.data_val = new char[m_readBufferSize]; readRes.READ3res_u.resok.data.data_len = m_readBufferSize; bool error = false; unsigned long bytesToRead = 0, bytesRead = 0; do { int clnt_stat = clnt_call(m_nfsClient, NFSPROC3_READ, (xdrproc_t) xdr_READ3args, reinterpret_cast(&readArgs), (xdrproc_t) xdr_READ3res, reinterpret_cast(&readRes), clnt_timeout); if (!checkForError(clnt_stat, readRes.status, destPath)) { error = true; break; } bytesRead = readRes.READ3res_u.resok.count; if (readArgs.offset == 0 || (bResume && readArgs.offset == resumeOffset)) { bytesToRead = readRes.READ3res_u.resok.file_attributes.post_op_attr_u.attributes.size; m_slave->totalSize(bytesToRead); QMimeDatabase db; QMimeType type = db.mimeTypeForFileNameAndData(src.fileName(), QByteArray::fromRawData(readRes.READ3res_u.resok.data.data_val, bytesRead)); m_slave->mimeType(type.name()); } if (bytesRead > 0) { readArgs.offset += bytesRead; if (destFile.write(readRes.READ3res_u.resok.data.data_val, bytesRead) < 0) { m_slave->error(KIO::ERR_COULD_NOT_WRITE, destPath); error = true; break; } m_slave->processedSize(readArgs.offset); } } while (readArgs.offset < bytesToRead); delete [] readRes.READ3res_u.resok.data.data_val; // Close the file so we can modify the modification time later. destFile.close(); if (error) { if (bMarkPartial) { // Remove the part file if it's smaller than the minimum keep - const int size = m_slave->config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); + const int size = m_slave->configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE); if (partInfo.size() < size) { QFile::remove(partInfo.absoluteFilePath()); } } } else { // Rename partial file to its original name. if (bMarkPartial) { const QString sPart = partInfo.absoluteFilePath(); if (QFile::exists(destPath)) { QFile::remove(destPath); } if (!QFile::rename(sPart, destPath)) { qCDebug(LOG_KIO_NFS) << "failed to rename" << sPart << "to" << destPath; m_slave->error(KIO::ERR_CANNOT_RENAME_PARTIAL, sPart); return; } } // Restore the mtime on the file. const QString mtimeStr = m_slave->metaData("modified"); if (!mtimeStr.isEmpty()) { QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate); if (dt.isValid()) { struct utimbuf utbuf; utbuf.actime = QFileInfo(destPath).lastRead().toTime_t(); // access time, unchanged utbuf.modtime = dt.toTime_t(); // modification time utime(QFile::encodeName(destPath).constData(), &utbuf); } } qCDebug(LOG_KIO_NFS) << "Copied" << readArgs.offset << "bytes of data"; m_slave->processedSize(readArgs.offset); m_slave->finished(); } } void NFSProtocolV3::copyTo(const QUrl& src, const QUrl& dest, int _mode, KIO::JobFlags _flags) { qCDebug(LOG_KIO_NFS) << src << "to" << dest; // The source does not exist, how strange const QString srcPath(src.path()); if (!QFile::exists(srcPath)) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, srcPath); return; } const QString destPath(dest.path()); if (isExportedDir(QFileInfo(destPath).path())) { m_slave->error(KIO::ERR_ACCESS_DENIED, destPath); return; } // The file exists and we don't want to overwrite. if (!getFileHandle(destPath).isInvalid() && (_flags & KIO::Overwrite) == 0) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } // Is it a link? No need to copy the data then, just copy the link destination. const QString symlinkTarget = QFile::symLinkTarget(srcPath); if (!symlinkTarget.isEmpty()) { int rpcStatus; SYMLINK3res linkRes; if (!symLink(symlinkTarget, destPath, rpcStatus, linkRes)) { checkForError(rpcStatus, linkRes.status, symlinkTarget); return; } m_slave->finished(); return; } unsigned long resumeOffset = 0; bool bResume = false; const QString partFilePath = destPath + QLatin1String(".part"); const NFSFileHandle partFH = getFileHandle(partFilePath); const bool bPartExists = !partFH.isInvalid(); - const bool bMarkPartial = m_slave->config()->readEntry("MarkPartial", true); + const bool bMarkPartial = m_slave->configValue(QStringLiteral("MarkPartial"), true); if (bPartExists) { int rpcStatus; LOOKUP3res partRes; if (lookupHandle(partFilePath, rpcStatus, partRes)) { if (bMarkPartial && partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.size > 0) { if (partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.type == NF3DIR) { m_slave->error(KIO::ERR_IS_DIRECTORY, partFilePath); return; } bResume = m_slave->canResume(partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.size); if (bResume) { resumeOffset = partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.size; } } } // Remove the part file if we are not resuming if (!bResume) { if (!remove(partFilePath)) { qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring..."; } } } // Open the source file QFile srcFile(srcPath); if (!srcFile.open(QIODevice::ReadOnly)) { m_slave->error(KIO::ERR_CANNOT_OPEN_FOR_READING, srcPath); return; } // Create the file if we are not resuming a parted transfer, // or if we are not using part files (bResume is false in that case) NFSFileHandle destFH; if (!bResume) { QString createPath; if (bMarkPartial) { createPath = partFilePath; } else { createPath = destPath; } int rpcStatus; CREATE3res createRes; if (!create(createPath, _mode, rpcStatus, createRes)) { checkForError(rpcStatus, createRes.status, createPath); return; } destFH = createRes.CREATE3res_u.resok.obj.post_op_fh3_u.handle; } else { // Since we are resuming it's implied that we are using a part file, // which should exist at this point. destFH = getFileHandle(partFilePath); qCDebug(LOG_KIO_NFS) << "Resuming old transfer"; } // Send the total size to the slave. m_slave->totalSize(srcFile.size()); // Get the optimal write buffer size if (m_writeBufferSize == 0) { initPreferredSizes(destFH); } // Set up write arguments. WRITE3args writeArgs; memset(&writeArgs, 0, sizeof(writeArgs)); destFH.toFH(writeArgs.file); writeArgs.data.data_val = new char[m_writeBufferSize]; writeArgs.stable = FILE_SYNC; if (bResume) { writeArgs.offset = resumeOffset; } else { writeArgs.offset = 0; } WRITE3res writeRes; memset(&writeRes, 0 , sizeof(writeRes)); bool error = false; int bytesRead = 0; do { memset(writeArgs.data.data_val, 0, m_writeBufferSize); bytesRead = srcFile.read(writeArgs.data.data_val, m_writeBufferSize); if (bytesRead < 0) { m_slave->error(KIO::ERR_COULD_NOT_READ, srcPath); error = true; break; } if (bytesRead > 0) { writeArgs.count = bytesRead; writeArgs.data.data_len = bytesRead; int clnt_stat = clnt_call(m_nfsClient, NFSPROC3_WRITE, (xdrproc_t) xdr_WRITE3args, reinterpret_cast(&writeArgs), (xdrproc_t) xdr_WRITE3res, reinterpret_cast(&writeRes), clnt_timeout); if (!checkForError(clnt_stat, writeRes.status, destPath)) { error = true; break; } writeArgs.offset += bytesRead; m_slave->processedSize(writeArgs.offset); } } while (bytesRead > 0); delete [] writeArgs.data.data_val; if (error) { if (bMarkPartial) { // Remove the part file if it's smaller than the minimum keep size. - const unsigned int size = m_slave->config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); + const unsigned int size = m_slave->configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE); if (writeArgs.offset < size) { if (!remove(partFilePath)) { qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring..."; } } } } else { // Rename partial file to its original name. if (bMarkPartial) { // Remove the destination file(if it exists) if (!getFileHandle(destPath).isInvalid() && !remove(destPath)) { qCDebug(LOG_KIO_NFS) << "Could not remove destination file" << destPath << ", ignoring..."; } if (!rename(partFilePath, destPath)) { qCDebug(LOG_KIO_NFS) << "failed to rename" << partFilePath << "to" << destPath; m_slave->error(KIO::ERR_CANNOT_RENAME_PARTIAL, partFilePath); return; } } // Restore the mtime on the file. const QString mtimeStr = m_slave->metaData("modified"); if (!mtimeStr.isEmpty()) { QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate); if (dt.isValid()) { sattr3 attributes; memset(&attributes, 0, sizeof(attributes)); attributes.mtime.set_it = SET_TO_CLIENT_TIME; attributes.mtime.set_mtime_u.mtime.seconds = dt.toTime_t(); attributes.mtime.set_mtime_u.mtime.nseconds = attributes.mtime.set_mtime_u.mtime.seconds * 1000000000ULL; int rpcStatus; SETATTR3res attrSetRes; if (!setAttr(destPath, attributes, rpcStatus, attrSetRes)) { qCDebug(LOG_KIO_NFS) << "Failed to restore mtime, ignoring..." << rpcStatus << attrSetRes.status; } } } qCDebug(LOG_KIO_NFS) << "Copied" << writeArgs.offset << "bytes of data"; m_slave->processedSize(writeArgs.offset); m_slave->finished(); } } void NFSProtocolV3::symlink(const QString& target, const QUrl& dest, KIO::JobFlags flags) { const QString destPath(dest.path()); if (isExportedDir(QFileInfo(destPath).path())) { m_slave->error(KIO::ERR_ACCESS_DENIED, destPath); return; } if (!getFileHandle(destPath).isInvalid() && (flags & KIO::Overwrite) == 0) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } int rpcStatus; SYMLINK3res res; if (!symLink(target, destPath, rpcStatus, res)) { checkForError(rpcStatus, res.status, destPath); return; } m_slave->finished(); } void NFSProtocolV3::initPreferredSizes(const NFSFileHandle& fh) { FSINFO3args fsArgs; memset(&fsArgs, 0, sizeof(fsArgs)); fh.toFH(fsArgs.fsroot); FSINFO3res fsRes; memset(&fsRes, 0, sizeof(fsRes)); int clnt_stat = clnt_call(m_nfsClient, NFSPROC3_FSINFO, (xdrproc_t) xdr_FSINFO3args, reinterpret_cast(&fsArgs), (xdrproc_t) xdr_FSINFO3res, reinterpret_cast(&fsRes), clnt_timeout); if (clnt_stat == RPC_SUCCESS && fsRes.status == NFS3_OK) { m_writeBufferSize = fsRes.FSINFO3res_u.resok.wtpref; m_readBufferSize = fsRes.FSINFO3res_u.resok.rtpref; m_readDirSize = fsRes.FSINFO3res_u.resok.dtpref; } else { m_writeBufferSize = NFS3_MAXDATA; m_readBufferSize = NFS3_MAXDATA; m_readDirSize = NFS3_MAXDATA; } qCDebug(LOG_KIO_NFS) << "Preferred sizes - write" << m_writeBufferSize << ", read" << m_readBufferSize << ", read dir" << m_readDirSize; } bool NFSProtocolV3::create(const QString& path, int mode, int& rpcStatus, CREATE3res& result) { qCDebug(LOG_KIO_NFS) << path; memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); if (!isConnected()) { result.status = NFS3ERR_ACCES; return false; } const QFileInfo fileInfo(path); const NFSFileHandle directoryFH = getFileHandle(fileInfo.path()); if (directoryFH.isInvalid()) { result.status = NFS3ERR_NOENT; return false; } QByteArray tmpName = QFile::encodeName(fileInfo.fileName()); CREATE3args args; memset(&args, 0, sizeof(args)); directoryFH.toFH(args.where.dir); args.where.name = tmpName.data(); args.how.createhow3_u.obj_attributes.mode.set_it = true; args.how.createhow3_u.obj_attributes.uid.set_it = true; args.how.createhow3_u.obj_attributes.gid.set_it = true; args.how.createhow3_u.obj_attributes.size.set_it = true; if (mode == -1) { args.how.createhow3_u.obj_attributes.mode.set_mode3_u.mode = 0644; } else { args.how.createhow3_u.obj_attributes.mode.set_mode3_u.mode = mode; } args.how.createhow3_u.obj_attributes.uid.set_uid3_u.uid = geteuid(); args.how.createhow3_u.obj_attributes.gid.set_gid3_u.gid = getegid(); args.how.createhow3_u.obj_attributes.size.set_size3_u.size = 0; rpcStatus = clnt_call(m_nfsClient, NFSPROC3_CREATE, (xdrproc_t) xdr_CREATE3args, reinterpret_cast(&args), (xdrproc_t) xdr_CREATE3res, reinterpret_cast(&result), clnt_timeout); return (rpcStatus == RPC_SUCCESS && result.status == NFS3_OK); } bool NFSProtocolV3::getAttr(const QString& path, int& rpcStatus, GETATTR3res& result) { qCDebug(LOG_KIO_NFS) << path; memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); if (!isConnected()) { result.status = NFS3ERR_ACCES; return false; } const NFSFileHandle fileFH = getFileHandle(path); if (fileFH.isInvalid()) { result.status = NFS3ERR_NOENT; return false; } GETATTR3args args; memset(&args, 0, sizeof(GETATTR3args)); fileFH.toFH(args.object); rpcStatus = clnt_call(m_nfsClient, NFSPROC3_GETATTR, (xdrproc_t) xdr_GETATTR3args, reinterpret_cast(&args), (xdrproc_t) xdr_GETATTR3res, reinterpret_cast(&result), clnt_timeout); return (rpcStatus == RPC_SUCCESS && result.status == NFS3_OK); } bool NFSProtocolV3::lookupHandle(const QString& path, int& rpcStatus, LOOKUP3res& result) { memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); if (!isConnected()) { result.status = NFS3ERR_ACCES; return false; } const QFileInfo fileInfo(path); const NFSFileHandle parentFH = getFileHandle(fileInfo.path()); if (parentFH.isInvalid()) { result.status = NFS3ERR_NOENT; return false; } QByteArray tmpName = QFile::encodeName(fileInfo.fileName()); // do the rpc call LOOKUP3args args; memset(&args, 0, sizeof(args)); parentFH.toFH(args.what.dir); args.what.name = tmpName.data(); rpcStatus = clnt_call(m_nfsClient, NFSPROC3_LOOKUP, (xdrproc_t) xdr_LOOKUP3args, reinterpret_cast(&args), (xdrproc_t) xdr_LOOKUP3res, reinterpret_cast(&result), clnt_timeout); return (rpcStatus == RPC_SUCCESS && result.status == NFS3_OK); } bool NFSProtocolV3::readLink(const QString& path, int& rpcStatus, READLINK3res& result, char* dataBuffer) { qCDebug(LOG_KIO_NFS) << path; memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); const NFSFileHandle fh = getFileHandle(path); if (fh.isInvalid()) { result.status = NFS3ERR_NOENT; return false; } READLINK3args readLinkArgs; memset(&readLinkArgs, 0, sizeof(readLinkArgs)); if (fh.isLink() && !fh.isBadLink()) { fh.toFHLink(readLinkArgs.symlink); } else { fh.toFH(readLinkArgs.symlink); } result.READLINK3res_u.resok.data = dataBuffer; rpcStatus = clnt_call(m_nfsClient, NFSPROC3_READLINK, (xdrproc_t) xdr_READLINK3args, reinterpret_cast(&readLinkArgs), (xdrproc_t) xdr_READLINK3res, reinterpret_cast(&result), clnt_timeout); return (rpcStatus == RPC_SUCCESS && result.status == NFS3_OK); } bool NFSProtocolV3::remove(const QString& path) { int rpcStatus; REMOVE3res result; return remove(path, rpcStatus, result); } bool NFSProtocolV3::remove(const QString& path, int& rpcStatus, REMOVE3res& result) { qCDebug(LOG_KIO_NFS) << path; memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); if (!isConnected()) { result.status = NFS3ERR_PERM; return false; } const QFileInfo fileInfo(path); if (isExportedDir(fileInfo.path())) { result.status = NFS3ERR_ACCES; return false; } const NFSFileHandle directoryFH = getFileHandle(fileInfo.path()); if (directoryFH.isInvalid()) { result.status = NFS3ERR_NOENT; return false; } int rpcLookupStatus; LOOKUP3res lookupRes; if (!lookupHandle(path, rpcLookupStatus, lookupRes)) { result.status = NFS3ERR_NOENT; return false; } QByteArray tmpName = QFile::encodeName(fileInfo.fileName()); REMOVE3args args; memset(&args, 0, sizeof(args)); directoryFH.toFH(args.object.dir); args.object.name = tmpName.data(); if (lookupRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.type != NF3DIR) { rpcStatus = clnt_call(m_nfsClient, NFSPROC3_REMOVE, (xdrproc_t) xdr_REMOVE3args, reinterpret_cast(&args), (xdrproc_t) xdr_REMOVE3res, reinterpret_cast(&result), clnt_timeout); } else { rpcStatus = clnt_call(m_nfsClient, NFSPROC3_RMDIR, (xdrproc_t) xdr_RMDIR3args, reinterpret_cast(&args), (xdrproc_t) xdr_RMDIR3res, reinterpret_cast(&result), clnt_timeout); } bool ret = (rpcStatus == RPC_SUCCESS && result.status == NFS3_OK); if (ret) { // Remove it from the cache as well removeFileHandle(path); } return ret; } bool NFSProtocolV3::rename(const QString& src, const QString& dest) { int rpcStatus; RENAME3res result; return rename(src, dest, rpcStatus, result); } bool NFSProtocolV3::rename(const QString& src, const QString& dest, int& rpcStatus, RENAME3res& result) { qCDebug(LOG_KIO_NFS) << src << dest; memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); const QFileInfo srcFileInfo(src); if (isExportedDir(srcFileInfo.path())) { result.status = NFS3ERR_ACCES; return false; } const NFSFileHandle srcDirectoryFH = getFileHandle(srcFileInfo.path()); if (srcDirectoryFH.isInvalid()) { result.status = NFS3ERR_NOENT; return false; } const QFileInfo destFileInfo(dest); if (isExportedDir(destFileInfo.path())) { result.status = NFS3ERR_ACCES; return false; } const NFSFileHandle destDirectoryFH = getFileHandle(destFileInfo.path()); if (destDirectoryFH.isInvalid()) { result.status = NFS3ERR_NOENT; return false; } RENAME3args args; memset(&args, 0, sizeof(args)); QByteArray srcByteName = QFile::encodeName(srcFileInfo.fileName()); srcDirectoryFH.toFH(args.from.dir); args.from.name = srcByteName.data(); QByteArray destByteName = QFile::encodeName(destFileInfo.fileName()); destDirectoryFH.toFH(args.to.dir); args.to.name = destByteName.data(); rpcStatus = clnt_call(m_nfsClient, NFSPROC3_RENAME, (xdrproc_t) xdr_RENAME3args, reinterpret_cast(&args), (xdrproc_t) xdr_RENAME3res, reinterpret_cast(&result), clnt_timeout); bool ret = (rpcStatus == RPC_SUCCESS && result.status == NFS3_OK); if (ret) { // Can we actually find the new handle? int lookupStatus; LOOKUP3res lookupRes; if (lookupHandle(dest, lookupStatus, lookupRes)) { // Remove the old file, and add the new one removeFileHandle(src); addFileHandle(dest, lookupRes.LOOKUP3res_u.resok.object); } } return ret; } bool NFSProtocolV3::setAttr(const QString& path, const sattr3& attributes, int& rpcStatus, SETATTR3res& result) { qCDebug(LOG_KIO_NFS) << path; memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); const NFSFileHandle fh = getFileHandle(path); if (fh.isInvalid()) { result.status = NFS3ERR_NOENT; return false; } SETATTR3args setAttrArgs; memset(&setAttrArgs, 0, sizeof(setAttrArgs)); fh.toFH(setAttrArgs.object); memcpy(&setAttrArgs.new_attributes, &attributes, sizeof(attributes)); rpcStatus = clnt_call(m_nfsClient, NFSPROC3_SETATTR, (xdrproc_t) xdr_SETATTR3args, reinterpret_cast(&setAttrArgs), (xdrproc_t) xdr_SETATTR3res, reinterpret_cast(&result), clnt_timeout); return (rpcStatus == RPC_SUCCESS && result.status == NFS3_OK); } bool NFSProtocolV3::symLink(const QString& target, const QString& dest, int& rpcStatus, SYMLINK3res& result) { qCDebug(LOG_KIO_NFS) << target << dest; memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); // Remove dest first, we don't really care about the return value at this point, // the symlink call will fail if dest was not removed correctly. remove(dest); const QFileInfo fileInfo(dest); const NFSFileHandle fh = getFileHandle(fileInfo.path()); if (fh.isInvalid()) { result.status = NFS3ERR_NOENT; return false; } QByteArray tmpStr = QFile::encodeName(fileInfo.fileName()); QByteArray tmpStr2 = QFile::encodeName(target); SYMLINK3args symLinkArgs; memset(&symLinkArgs, 0, sizeof(symLinkArgs)); fh.toFH(symLinkArgs.where.dir); symLinkArgs.where.name = tmpStr.data(); symLinkArgs.symlink.symlink_data = tmpStr2.data(); rpcStatus = clnt_call(m_nfsClient, NFSPROC3_SYMLINK, (xdrproc_t) xdr_SYMLINK3args, reinterpret_cast(&symLinkArgs), (xdrproc_t) xdr_SYMLINK3res, reinterpret_cast(&result), clnt_timeout); // Add the new handle to the cache NFSFileHandle destFH = getFileHandle(dest); if (!destFH.isInvalid()) { addFileHandle(dest, destFH); } return (rpcStatus == RPC_SUCCESS && result.status == NFS3_OK); } void NFSProtocolV3::completeUDSEntry(KIO::UDSEntry& entry, const fattr3& attributes) { entry.insert(KIO::UDSEntry::UDS_SIZE, attributes.size); entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, attributes.mtime.seconds); entry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, attributes.atime.seconds); entry.insert(KIO::UDSEntry::UDS_CREATION_TIME, attributes.ctime.seconds); // Some servers still send the file type information in the mode, even though // the reference specifies NFSv3 shouldn't, so we need to work around that here. // Not sure this is the best way to do it, but it works. if (attributes.mode > 0777) { entry.insert(KIO::UDSEntry::UDS_ACCESS, (attributes.mode & 07777)); } else { entry.insert(KIO::UDSEntry::UDS_ACCESS, attributes.mode); } unsigned int type; switch (attributes.type) { case NF3DIR: type = S_IFDIR; break; case NF3BLK: type = S_IFBLK; break; case NF3CHR: type = S_IFCHR; break; case NF3LNK: type = S_IFLNK; break; case NF3SOCK: type = S_IFSOCK; break; case NF3FIFO: type = S_IFIFO; break; default: type = S_IFREG; break; } entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, type); QString str; const uid_t uid = attributes.uid; if (!m_usercache.contains(uid)) { struct passwd* user = getpwuid(uid); if (user) { m_usercache.insert(uid, QString::fromLatin1(user->pw_name)); str = user->pw_name; } else { str = QString::number(uid); } } else { str = m_usercache.value(uid); } entry.insert(KIO::UDSEntry::UDS_USER, str); const gid_t gid = attributes.gid; if (!m_groupcache.contains(gid)) { struct group* grp = getgrgid(gid); if (grp) { m_groupcache.insert(gid, QString::fromLatin1(grp->gr_name)); str = grp->gr_name; } else { str = QString::number(gid); } } else { str = m_groupcache.value(gid); } entry.insert(KIO::UDSEntry::UDS_GROUP, str); } void NFSProtocolV3::completeBadLinkUDSEntry(KIO::UDSEntry& entry, const fattr3& attributes) { entry.insert(KIO::UDSEntry::UDS_SIZE, 0LL); entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, attributes.mtime.seconds); entry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, attributes.atime.seconds); entry.insert(KIO::UDSEntry::UDS_CREATION_TIME, attributes.ctime.seconds); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFMT - 1); entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRWXU | S_IRWXG | S_IRWXO); entry.insert(KIO::UDSEntry::UDS_USER, attributes.uid); entry.insert(KIO::UDSEntry::UDS_GROUP, attributes.gid); } diff --git a/sftp/kio_sftp.cpp b/sftp/kio_sftp.cpp index 2beff9d7..1b308018 100644 --- a/sftp/kio_sftp.cpp +++ b/sftp/kio_sftp.cpp @@ -1,2667 +1,2667 @@ /* * Copyright (c) 2001 Lucas Fisher * Copyright (c) 2009 Andreas Schneider * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License (LGPL) as published by the Free Software Foundation; * either version 2 of the License, or (at your option) any later * version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kio_sftp.h" #include #include "kio_sftp_debug.h" #include "kio_sftp_trace_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include // for permissions using namespace std::experimental::filesystem; #include #else #include #endif #define KIO_SFTP_SPECIAL_TIMEOUT 30 // How big should each data packet be? Definitely not bigger than 64kb or // you will overflow the 2 byte size variable in a sftp packet. #define MAX_XFER_BUF_SIZE (60 * 1024) #define KSFTP_ISDIR(sb) (sb->type == SSH_FILEXFER_TYPE_DIRECTORY) using namespace KIO; extern "C" { int Q_DECL_EXPORT kdemain( int argc, char **argv ) { QCoreApplication app(argc, argv); app.setApplicationName("kio_sftp"); qCDebug(KIO_SFTP_LOG) << "*** Starting kio_sftp "; if (argc != 4) { qCDebug(KIO_SFTP_LOG) << "Usage: kio_sftp protocol domain-socket1 domain-socket2"; exit(-1); } sftpProtocol slave(argv[2], argv[3]); slave.dispatchLoop(); qCDebug(KIO_SFTP_LOG) << "*** kio_sftp Done"; return 0; } } // Converts SSH error into KIO error. Only must be called for error handling // as this will always return an error state and never NoError. static int toKIOError (const int err) { switch (err) { case SSH_FX_NO_SUCH_FILE: case SSH_FX_NO_SUCH_PATH: return KIO::ERR_DOES_NOT_EXIST; case SSH_FX_PERMISSION_DENIED: return KIO::ERR_ACCESS_DENIED; case SSH_FX_FILE_ALREADY_EXISTS: return KIO::ERR_FILE_ALREADY_EXIST; case SSH_FX_INVALID_HANDLE: return KIO::ERR_MALFORMED_URL; case SSH_FX_OP_UNSUPPORTED: return KIO::ERR_UNSUPPORTED_ACTION; case SSH_FX_BAD_MESSAGE: return KIO::ERR_UNKNOWN; default: return KIO::ERR_INTERNAL; } // We should not get here. When this function gets called we've // encountered an error on the libssh side, this needs to be mapped to *any* // KIO error. Not mapping is not an option at this point, even if the ssh err // is wrong or 'ok'. Q_UNREACHABLE(); return KIO::ERR_UNKNOWN; } // Writes 'len' bytes from 'buf' to the file handle 'fd'. static int writeToFile(int fd, const char *buf, size_t len) { while (len > 0) { ssize_t written = write(fd, buf, len); if (written >= 0) { buf += written; len -= written; continue; } switch(errno) { case EINTR: case EAGAIN: continue; case EPIPE: return ERR_CONNECTION_BROKEN; case ENOSPC: return ERR_DISK_FULL; default: return ERR_COULD_NOT_WRITE; } } return 0; } static int seekPos(int fd, KIO::fileoffset_t pos, int mode) { KIO::fileoffset_t offset = -1; while ((offset = QT_LSEEK(fd, pos, mode)) == EAGAIN); return offset; } static bool wasUsernameChanged(const QString& username, const KIO::AuthInfo& info) { QString loginName (username); // If username is empty, assume the current logged in username. Why ? // Because libssh's SSH_OPTIONS_USER will default to that when it is not // set and it won't be set unless the user explicitly typed a user user // name as part of the request URL. if (loginName.isEmpty()) { KUser u; loginName = u.loginName(); } return (loginName != info.username); } // The callback function for libssh static int auth_callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata) { if (userdata == nullptr) { return -1; } sftpProtocol *slave = (sftpProtocol *) userdata; if (slave->auth_callback(prompt, buf, len, echo, verify, userdata) < 0) { return -1; } return 0; } static void log_callback(int priority, const char *function, const char *buffer, void *userdata) { if (userdata == nullptr) { return; } sftpProtocol *slave = (sftpProtocol *) userdata; slave->log_callback(priority, function, buffer, userdata); } int sftpProtocol::auth_callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata) { // unused variables (void) echo; (void) verify; (void) userdata; QString errMsg; if (!mPublicKeyAuthInfo) { mPublicKeyAuthInfo = new KIO::AuthInfo; } else { errMsg = i18n("Incorrect or invalid passphrase"); } mPublicKeyAuthInfo->url.setScheme(QLatin1String("sftp")); mPublicKeyAuthInfo->url.setHost(mHost); if (mPort > 0 && mPort != DEFAULT_SFTP_PORT) { mPublicKeyAuthInfo->url.setPort(mPort); } mPublicKeyAuthInfo->url.setUserName(mUsername); QUrl u (mPublicKeyAuthInfo->url); u.setPath(QString()); mPublicKeyAuthInfo->comment = u.url(); mPublicKeyAuthInfo->readOnly = true; mPublicKeyAuthInfo->prompt = QString::fromUtf8(prompt); mPublicKeyAuthInfo->keepPassword = false; // don't save passwords for public key, // that's the task of ssh-agent. mPublicKeyAuthInfo->setExtraField(QLatin1String("hide-username-line"), true); mPublicKeyAuthInfo->setModified(false); qCDebug(KIO_SFTP_LOG) << "Entering authentication callback, prompt=" << mPublicKeyAuthInfo->prompt; if (openPasswordDialogV2(*mPublicKeyAuthInfo, errMsg) != 0) { qCDebug(KIO_SFTP_LOG) << "User canceled public key passpharse dialog"; return -1; } strncpy(buf, mPublicKeyAuthInfo->password.toUtf8().constData(), len - 1); mPublicKeyAuthInfo->password.fill('x'); mPublicKeyAuthInfo->password.clear(); return 0; } void sftpProtocol::log_callback(int priority, const char *function, const char *buffer, void *userdata) { (void) userdata; qCDebug(KIO_SFTP_LOG) << "[" << function << "] (" << priority << ") " << buffer; } void sftpProtocol::virtual_hook(int id, void *data) { switch(id) { case SlaveBase::GetFileSystemFreeSpace: { QUrl *url = static_cast(data); fileSystemFreeSpace(*url); } break; default: SlaveBase::virtual_hook(id, data); } } int sftpProtocol::authenticateKeyboardInteractive(AuthInfo &info) { int err = ssh_userauth_kbdint(mSession, nullptr, nullptr); while (err == SSH_AUTH_INFO) { const QString name = QString::fromUtf8(ssh_userauth_kbdint_getname(mSession)); const QString instruction = QString::fromUtf8(ssh_userauth_kbdint_getinstruction(mSession)); const int n = ssh_userauth_kbdint_getnprompts(mSession); qCDebug(KIO_SFTP_LOG) << "name=" << name << " instruction=" << instruction << " prompts=" << n; for (int i = 0; i < n; ++i) { char echo; const char *answer = ""; const QString prompt = QString::fromUtf8(ssh_userauth_kbdint_getprompt(mSession, i, &echo)); qCDebug(KIO_SFTP_LOG) << "prompt=" << prompt << " echo=" << QString::number(echo); if (echo) { // See RFC4256 Section 3.3 User Interface KIO::AuthInfo infoKbdInt; infoKbdInt.url.setScheme("sftp"); infoKbdInt.url.setHost(mHost); if (mPort > 0 && mPort != DEFAULT_SFTP_PORT) { infoKbdInt.url.setPort(mPort); } if (!name.isEmpty()) { infoKbdInt.caption = QString(i18n("SFTP Login") + " - " + name); } else { infoKbdInt.caption = i18n("SFTP Login"); } infoKbdInt.comment = "sftp://" + mUsername + "@" + mHost; QString newPrompt; if (!instruction.isEmpty()) { newPrompt = instruction + "

"; } newPrompt.append(prompt); infoKbdInt.prompt = newPrompt; infoKbdInt.readOnly = false; infoKbdInt.keepPassword = false; if (openPasswordDialogV2(infoKbdInt, i18n("Use the username input field to answer this question.")) == 0) { qCDebug(KIO_SFTP_LOG) << "Got the answer from the password dialog"; answer = info.username.toUtf8().constData(); } if (ssh_userauth_kbdint_setanswer(mSession, i, answer) < 0) { qCDebug(KIO_SFTP_LOG) << "An error occurred setting the answer: " << ssh_get_error(mSession); return SSH_AUTH_ERROR; } break; } else { if (prompt.startsWith(QLatin1String("password:"), Qt::CaseInsensitive)) { info.prompt = i18n("Please enter your password."); } else { info.prompt = prompt; } info.comment = info.url.url(); info.commentLabel = i18n("Site:"); info.setExtraField(QLatin1String("hide-username-line"), true); if (openPasswordDialogV2(info) == 0) { qCDebug(KIO_SFTP_LOG) << "Got the answer from the password dialog"; answer = info.password.toUtf8().constData(); } if (ssh_userauth_kbdint_setanswer(mSession, i, answer) < 0) { qCDebug(KIO_SFTP_LOG) << "An error occurred setting the answer: " << ssh_get_error(mSession); return SSH_AUTH_ERROR; } } } err = ssh_userauth_kbdint(mSession, nullptr, nullptr); } return err; } void sftpProtocol::reportError(const QUrl &url, const int err) { qCDebug(KIO_SFTP_LOG) << "url = " << url << " - err=" << err; const int kioError = toKIOError(err); Q_ASSERT(kioError != 0); error(kioError, url.toDisplayString()); } bool sftpProtocol::createUDSEntry(const QString &filename, const QByteArray &path, UDSEntry &entry, short int details) { mode_t access; char *link; bool isBrokenLink = false; long long fileType = QT_STAT_REG; long long size = 0LL; Q_ASSERT(entry.count() == 0); sftp_attributes sb = sftp_lstat(mSftp, path.constData()); if (sb == nullptr) { return false; } entry.fastInsert(KIO::UDSEntry::UDS_NAME, filename); if (sb->type == SSH_FILEXFER_TYPE_SYMLINK) { link = sftp_readlink(mSftp, path.constData()); if (link == nullptr) { sftp_attributes_free(sb); return false; } entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, QFile::decodeName(link)); free(link); // A symlink -> follow it only if details > 1 if (details > 1) { sftp_attributes sb2 = sftp_stat(mSftp, path.constData()); if (sb2 == nullptr) { isBrokenLink = true; } else { sftp_attributes_free(sb); sb = sb2; } } } if (isBrokenLink) { // It is a link pointing to nowhere fileType = QT_STAT_MASK - 1; #ifdef Q_OS_WIN access = static_cast(perms::owner_all | perms::group_all | perms::others_all); #else access = S_IRWXU | S_IRWXG | S_IRWXO; #endif size = 0LL; } else { switch (sb->type) { case SSH_FILEXFER_TYPE_REGULAR: fileType = QT_STAT_REG; break; case SSH_FILEXFER_TYPE_DIRECTORY: fileType = QT_STAT_DIR; break; case SSH_FILEXFER_TYPE_SYMLINK: fileType = QT_STAT_LNK; break; case SSH_FILEXFER_TYPE_SPECIAL: case SSH_FILEXFER_TYPE_UNKNOWN: fileType = QT_STAT_MASK - 1; break; } access = sb->permissions & 07777; size = sb->size; } entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, fileType); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, access); entry.fastInsert(KIO::UDSEntry::UDS_SIZE, size); if (details > 0) { if (sb->owner) { entry.fastInsert(KIO::UDSEntry::UDS_USER, QString::fromUtf8(sb->owner)); } else { entry.fastInsert(KIO::UDSEntry::UDS_USER, QString::number(sb->uid)); } if (sb->group) { entry.fastInsert(KIO::UDSEntry::UDS_GROUP, QString::fromUtf8(sb->group)); } else { entry.fastInsert(KIO::UDSEntry::UDS_GROUP, QString::number(sb->gid)); } entry.fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, sb->atime); entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, sb->mtime); entry.fastInsert(KIO::UDSEntry::UDS_CREATION_TIME, sb->createtime); } sftp_attributes_free(sb); return true; } QString sftpProtocol::canonicalizePath(const QString &path) { qCDebug(KIO_SFTP_LOG) << "Path to canonicalize: " << path; QString cPath; char *sPath = nullptr; if (path.isEmpty()) { return cPath; } sPath = sftp_canonicalize_path(mSftp, path.toUtf8().constData()); if (sPath == nullptr) { qCDebug(KIO_SFTP_LOG) << "Could not canonicalize path: " << path; return cPath; } cPath = QFile::decodeName(sPath); ssh_string_free_char(sPath); qCDebug(KIO_SFTP_LOG) << "Canonicalized path: " << cPath; return cPath; } sftpProtocol::sftpProtocol(const QByteArray &pool_socket, const QByteArray &app_socket) : SlaveBase("kio_sftp", pool_socket, app_socket), mConnected(false), mPort(-1), mSession(nullptr), mSftp(nullptr), mPublicKeyAuthInfo(nullptr) { #ifndef Q_OS_WIN qCDebug(KIO_SFTP_LOG) << "pid = " << getpid(); qCDebug(KIO_SFTP_LOG) << "debug = " << getenv("KIO_SFTP_LOG_VERBOSITY"); #endif // Members are 'value initialized' to zero because of non-user defined ()! mCallbacks = new struct ssh_callbacks_struct(); if (mCallbacks == nullptr) { error(KIO::ERR_OUT_OF_MEMORY, i18n("Could not allocate callbacks")); return; } mCallbacks->userdata = this; mCallbacks->auth_function = ::auth_callback; ssh_callbacks_init(mCallbacks); bool ok; int level = qEnvironmentVariableIntValue("KIO_SFTP_LOG_VERBOSITY", &ok); if (ok) { int rc = ssh_set_log_level(level); if (rc != SSH_OK) { error(KIO::ERR_INTERNAL, i18n("Could not set log verbosity.")); return; } rc = ssh_set_log_userdata(this); if (rc != SSH_OK) { error(KIO::ERR_INTERNAL, i18n("Could not set log userdata.")); return; } rc = ssh_set_log_callback(::log_callback); if (rc != SSH_OK) { error(KIO::ERR_INTERNAL, i18n("Could not set log callback.")); return; } } } sftpProtocol::~sftpProtocol() { #ifndef Q_OS_WIN qCDebug(KIO_SFTP_LOG) << "pid = " << getpid(); #endif closeConnection(); delete mCallbacks; delete mPublicKeyAuthInfo; // for precaution /* cleanup and shut down cryto stuff */ ssh_finalize(); } void sftpProtocol::setHost(const QString& host, quint16 port, const QString& user, const QString& pass) { qCDebug(KIO_SFTP_LOG) << user << "@" << host << ":" << port; // Close connection if the request is to another server... if (host != mHost || port != mPort || user != mUsername || pass != mPassword) { closeConnection(); } mHost = host; mPort = port; mUsername = user; mPassword = pass; } bool sftpProtocol::sftpOpenConnection (const AuthInfo& info) { mSession = ssh_new(); if (mSession == nullptr) { error(KIO::ERR_OUT_OF_MEMORY, i18n("Could not create a new SSH session.")); return false; } long timeout_sec = 30, timeout_usec = 0; qCDebug(KIO_SFTP_LOG) << "Creating the SSH session and setting options"; // Set timeout int rc = ssh_options_set(mSession, SSH_OPTIONS_TIMEOUT, &timeout_sec); if (rc < 0) { error(KIO::ERR_INTERNAL, i18n("Could not set a timeout.")); return false; } rc = ssh_options_set(mSession, SSH_OPTIONS_TIMEOUT_USEC, &timeout_usec); if (rc < 0) { error(KIO::ERR_INTERNAL, i18n("Could not set a timeout.")); return false; } #if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0, 8, 0) // Disable Nagle's Algorithm (TCP_NODELAY). Usually faster for sftp. bool nodelay = true; rc = ssh_options_set(mSession, SSH_OPTIONS_NODELAY, &nodelay); if (rc < 0) { error(KIO::ERR_INTERNAL, i18n("Could not disable Nagle's Algorithm.")); return false; } #endif // 0.8.0 // Don't use any compression rc = ssh_options_set(mSession, SSH_OPTIONS_COMPRESSION_C_S, "none"); if (rc < 0) { error(KIO::ERR_INTERNAL, i18n("Could not set compression.")); return false; } rc = ssh_options_set(mSession, SSH_OPTIONS_COMPRESSION_S_C, "none"); if (rc < 0) { error(KIO::ERR_INTERNAL, i18n("Could not set compression.")); return false; } // Set host and port rc = ssh_options_set(mSession, SSH_OPTIONS_HOST, mHost.toUtf8().constData()); if (rc < 0) { error(KIO::ERR_INTERNAL, i18n("Could not set host.")); return false; } if (mPort > 0) { rc = ssh_options_set(mSession, SSH_OPTIONS_PORT, &mPort); if (rc < 0) { error(KIO::ERR_INTERNAL, i18n("Could not set port.")); return false; } } // Set the username if (!info.username.isEmpty()) { rc = ssh_options_set(mSession, SSH_OPTIONS_USER, info.username.toUtf8().constData()); if (rc < 0) { error(KIO::ERR_INTERNAL, i18n("Could not set username.")); return false; } } // Read ~/.ssh/config rc = ssh_options_parse_config(mSession, nullptr); if (rc < 0) { error(KIO::ERR_INTERNAL, i18n("Could not parse the config file.")); return false; } ssh_set_callbacks(mSession, mCallbacks); qCDebug(KIO_SFTP_LOG) << "Trying to connect to the SSH server"; unsigned int effectivePort; if (mPort > 0) { effectivePort = mPort; } else { effectivePort = DEFAULT_SFTP_PORT; ssh_options_get_port(mSession, &effectivePort); } qCDebug(KIO_SFTP_LOG) << "username=" << mUsername << ", host=" << mHost << ", port=" << effectivePort; infoMessage(xi18n("Opening SFTP connection to host %1:%2", mHost, QString::number(effectivePort))); /* try to connect */ rc = ssh_connect(mSession); if (rc < 0) { error(KIO::ERR_SLAVE_DEFINED, QString::fromUtf8(ssh_get_error(mSession))); closeConnection(); return false; } return true; } #if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0, 8, 3) void sftpProtocol::openConnection() { if (mConnected) { return; } if (mHost.isEmpty()) { qCDebug(KIO_SFTP_LOG) << "openConnection(): Need hostname..."; error(KIO::ERR_UNKNOWN_HOST, QString()); return; } AuthInfo info; info.url.setScheme("sftp"); info.url.setHost(mHost); if ( mPort > 0 && mPort != DEFAULT_SFTP_PORT ) { info.url.setPort(mPort); } info.url.setUserName(mUsername); info.username = mUsername; // Check for cached authentication info if no password is specified... if (mPassword.isEmpty()) { qCDebug(KIO_SFTP_LOG) << "checking cache: info.username =" << info.username << ", info.url =" << info.url.toDisplayString(); checkCachedAuthentication(info); } else { info.password = mPassword; } // Start the ssh connection. QString msg; // msg for dialog box QString caption; // dialog box caption unsigned char *hash = nullptr; // the server hash size_t hlen; ssh_key srv_pubkey = nullptr; const char *srv_pubkey_type = nullptr; char *fingerprint = nullptr; enum ssh_known_hosts_e state; int rc; // Attempt to start a ssh session and establish a connection with the server. if (!sftpOpenConnection(info)) { return; } qCDebug(KIO_SFTP_LOG) << "Getting the SSH server hash"; /* get the hash */ rc = ssh_get_server_publickey(mSession, &srv_pubkey); if (rc < 0) { error(KIO::ERR_SLAVE_DEFINED, QString::fromUtf8(ssh_get_error(mSession))); closeConnection(); return; } srv_pubkey_type = ssh_key_type_to_char(ssh_key_type(srv_pubkey)); if (srv_pubkey_type == nullptr) { ssh_key_free(srv_pubkey); error(KIO::ERR_SLAVE_DEFINED, i18n("Could not get server public key type name")); closeConnection(); return; } rc = ssh_get_publickey_hash(srv_pubkey, SSH_PUBLICKEY_HASH_SHA256, &hash, &hlen); ssh_key_free(srv_pubkey); if (rc != SSH_OK) { error(KIO::ERR_SLAVE_DEFINED, i18n("Could not create hash from server public key")); closeConnection(); return; } fingerprint = ssh_get_fingerprint_hash(SSH_PUBLICKEY_HASH_SHA256, hash, hlen); ssh_string_free_char((char *)hash); if (fingerprint == nullptr) { error(KIO::ERR_SLAVE_DEFINED, i18n("Could not create fingerprint for server public key")); closeConnection(); return; } qCDebug(KIO_SFTP_LOG) << "Checking if the SSH server is known"; /* check the server public key hash */ state = ssh_session_is_known_server(mSession); switch (state) { case SSH_KNOWN_HOSTS_OTHER: ssh_string_free_char(fingerprint); error(KIO::ERR_SLAVE_DEFINED, i18n("An %1 host key for this server was " "not found, but another type of key exists.\n" "An attacker might change the default server key to confuse your " "client into thinking the key does not exist.\n" "Please contact your system administrator.\n" "%2", QString::fromUtf8(srv_pubkey_type), QString::fromUtf8(ssh_get_error(mSession)))); closeConnection(); return; case SSH_KNOWN_HOSTS_CHANGED: error(KIO::ERR_SLAVE_DEFINED, i18n("The host key for the server %1 has changed.\n" "This could either mean that DNS SPOOFING is happening or the IP " "address for the host and its host key have changed at the same time.\n" "The fingerprint for the %2 key sent by the remote host is:\n" " SHA256:%3\n" "Please contact your system administrator.\n%4", mHost, QString::fromUtf8(srv_pubkey_type), QString::fromUtf8(fingerprint), QString::fromUtf8(ssh_get_error(mSession)))); ssh_string_free_char(fingerprint); closeConnection(); return; case SSH_KNOWN_HOSTS_NOT_FOUND: case SSH_KNOWN_HOSTS_UNKNOWN: caption = i18n("Warning: Cannot verify host's identity."); msg = i18n("The authenticity of host %1 cannot be established.\n" "The %2 key fingerprint is: %3\n" "Are you sure you want to continue connecting?", mHost, QString::fromUtf8(srv_pubkey_type), QString::fromUtf8(fingerprint)); ssh_string_free_char(fingerprint); if (KMessageBox::Yes != messageBox(WarningYesNo, msg, caption)) { closeConnection(); error(KIO::ERR_USER_CANCELED, QString()); return; } /* write the known_hosts file */ qCDebug(KIO_SFTP_LOG) << "Adding server to known_hosts file."; rc = ssh_session_update_known_hosts(mSession); if (rc != SSH_OK) { error(KIO::ERR_USER_CANCELED, QString::fromUtf8(ssh_get_error(mSession))); closeConnection(); return; } break; case SSH_KNOWN_HOSTS_ERROR: ssh_string_free_char(fingerprint); error(KIO::ERR_SLAVE_DEFINED, QString::fromUtf8(ssh_get_error(mSession))); return; case SSH_KNOWN_HOSTS_OK: break; } qCDebug(KIO_SFTP_LOG) << "Trying to authenticate with the server"; // Try to login without authentication rc = ssh_userauth_none(mSession, nullptr); if (rc == SSH_AUTH_ERROR) { closeConnection(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); return; } // This NEEDS to be called after ssh_userauth_none() !!! int method = ssh_auth_list(mSession); if (rc != SSH_AUTH_SUCCESS && method == 0) { closeConnection(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed. The server " "didn't send any authentication methods")); return; } // Try to authenticate with public key first if (rc != SSH_AUTH_SUCCESS && (method & SSH_AUTH_METHOD_PUBLICKEY)) { qCDebug(KIO_SFTP_LOG) << "Trying to authenticate with public key"; for(;;) { rc = ssh_userauth_publickey_auto(mSession, nullptr, nullptr); if (rc == SSH_AUTH_ERROR) { qCDebug(KIO_SFTP_LOG) << "Public key authentication failed:" << QString::fromUtf8(ssh_get_error(mSession)); closeConnection(); clearPubKeyAuthInfo(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); return; } else if (rc != SSH_AUTH_DENIED || !mPublicKeyAuthInfo || !mPublicKeyAuthInfo->isModified()) { clearPubKeyAuthInfo(); break; } } } // Try to authenticate with GSSAPI if (rc != SSH_AUTH_SUCCESS && (method & SSH_AUTH_METHOD_GSSAPI_MIC)) { qCDebug(KIO_SFTP_LOG) << "Trying to authenticate with GSSAPI"; rc = ssh_userauth_gssapi(mSession); if (rc == SSH_AUTH_ERROR) { qCDebug(KIO_SFTP_LOG) << "Public key authentication failed:" << QString::fromUtf8(ssh_get_error(mSession)); closeConnection(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); return; } } // Try to authenticate with keyboard interactive if (rc != SSH_AUTH_SUCCESS && (method & SSH_AUTH_METHOD_INTERACTIVE)) { qCDebug(KIO_SFTP_LOG) << "Trying to authenticate with keyboard interactive"; AuthInfo info2 (info); rc = authenticateKeyboardInteractive(info2); if (rc == SSH_AUTH_SUCCESS) { info = info2; } else if (rc == SSH_AUTH_ERROR) { qCDebug(KIO_SFTP_LOG) << "Keyboard interactive authentication failed:" << QString::fromUtf8(ssh_get_error(mSession)); closeConnection(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); return; } } // Try to authenticate with password if (rc != SSH_AUTH_SUCCESS && (method & SSH_AUTH_METHOD_PASSWORD)) { qCDebug(KIO_SFTP_LOG) << "Trying to authenticate with password"; info.caption = i18n("SFTP Login"); info.prompt = i18n("Please enter your username and password."); info.comment = info.url.url(); info.commentLabel = i18n("Site:"); bool isFirstLoginAttempt = true; for(;;) { if (!isFirstLoginAttempt || info.password.isEmpty()) { info.keepPassword = true; // make the "keep Password" check box visible to the user. info.setModified(false); QString username (info.username); const QString errMsg(isFirstLoginAttempt ? QString() : i18n("Incorrect username or password")); qCDebug(KIO_SFTP_LOG) << "Username:" << username << "first attempt?" << isFirstLoginAttempt << "error:" << errMsg; // Handle user canceled or dialog failed to open... int errCode = openPasswordDialogV2(info, errMsg); if (errCode != 0) { qCDebug(KIO_SFTP_LOG) << "User canceled password/retry dialog"; closeConnection(); error(errCode, QString()); return; } // If the user name changes, we have to restablish connection again // since the user name must always be set before calling ssh_connect. if (wasUsernameChanged(username, info)) { qCDebug(KIO_SFTP_LOG) << "Username changed to" << info.username; if (!info.url.userName().isEmpty()) { info.url.setUserName(info.username); } closeConnection(); if (!sftpOpenConnection(info)) { return; } } } rc = ssh_userauth_password(mSession, info.username.toUtf8().constData(), info.password.toUtf8().constData()); if (rc == SSH_AUTH_SUCCESS) { break; } else if (rc == SSH_AUTH_ERROR) { qCDebug(KIO_SFTP_LOG) << "Password authentication failed:" << QString::fromUtf8(ssh_get_error(mSession)); closeConnection(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); return; } isFirstLoginAttempt = false; // failed attempt to login. info.password.clear(); // clear the password after failed attempts. } } // If we're still not authenticated then we need to leave. if (rc != SSH_AUTH_SUCCESS) { error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); return; } // start sftp session qCDebug(KIO_SFTP_LOG) << "Trying to request the sftp session"; mSftp = sftp_new(mSession); if (mSftp == nullptr) { closeConnection(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Unable to request the SFTP subsystem. " "Make sure SFTP is enabled on the server.")); return; } qCDebug(KIO_SFTP_LOG) << "Trying to initialize the sftp session"; if (sftp_init(mSftp) < 0) { closeConnection(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Could not initialize the SFTP session.")); return; } // Login succeeded! infoMessage(i18n("Successfully connected to %1", mHost)); if (info.keepPassword) { qCDebug(KIO_SFTP_LOG) << "Caching info.username = " << info.username << ", info.url = " << info.url.toDisplayString(); cacheAuthentication(info); } // Update the original username in case it was changed! if (!mUsername.isEmpty()) { mUsername = info.username; } setTimeoutSpecialCommand(KIO_SFTP_SPECIAL_TIMEOUT); mConnected = true; connected(); info.password.fill('x'); info.password.clear(); } #else // < 0.8.0 void sftpProtocol::openConnection() { if (mConnected) { return; } if (mHost.isEmpty()) { qCDebug(KIO_SFTP_LOG) << "openConnection(): Need hostname..."; error(KIO::ERR_UNKNOWN_HOST, QString()); return; } AuthInfo info; info.url.setScheme("sftp"); info.url.setHost(mHost); if ( mPort > 0 && mPort != DEFAULT_SFTP_PORT ) { info.url.setPort(mPort); } info.url.setUserName(mUsername); info.username = mUsername; // Check for cached authentication info if no password is specified... if (mPassword.isEmpty()) { qCDebug(KIO_SFTP_LOG) << "checking cache: info.username =" << info.username << ", info.url =" << info.url.toDisplayString(); checkCachedAuthentication(info); } else { info.password = mPassword; } // Start the ssh connection. QString msg; // msg for dialog box QString caption; // dialog box caption unsigned char *hash = nullptr; // the server hash ssh_key srv_pubkey; char *hexa; size_t hlen; int rc, state; // Attempt to start a ssh session and establish a connection with the server. if (!sftpOpenConnection(info)) { return; } qCDebug(KIO_SFTP_LOG) << "Getting the SSH server hash"; /* get the hash */ rc = ssh_get_publickey(mSession, &srv_pubkey); if (rc < 0) { error(KIO::ERR_SLAVE_DEFINED, QString::fromUtf8(ssh_get_error(mSession))); closeConnection(); return; } rc = ssh_get_publickey_hash(srv_pubkey, SSH_PUBLICKEY_HASH_SHA1, &hash, &hlen); ssh_key_free(srv_pubkey); if (rc < 0) { error(KIO::ERR_SLAVE_DEFINED, i18n("Could not create hash from server public key")); closeConnection(); return; } qCDebug(KIO_SFTP_LOG) << "Checking if the SSH server is known"; /* check the server public key hash */ state = ssh_is_server_known(mSession); switch (state) { case SSH_SERVER_KNOWN_OK: break; case SSH_SERVER_FOUND_OTHER: ssh_string_free_char((char *)hash); error(KIO::ERR_SLAVE_DEFINED, i18n("The host key for this server was " "not found, but another type of key exists.\n" "An attacker might change the default server key to confuse your " "client into thinking the key does not exist.\n" "Please contact your system administrator.\n%1", QString::fromUtf8(ssh_get_error(mSession)))); closeConnection(); return; case SSH_SERVER_KNOWN_CHANGED: hexa = ssh_get_hexa(hash, hlen); ssh_string_free_char((char *)hash); /* TODO print known_hosts file, port? */ error(KIO::ERR_SLAVE_DEFINED, i18n("The host key for the server %1 has changed.\n" "This could either mean that DNS SPOOFING is happening or the IP " "address for the host and its host key have changed at the same time.\n" "The fingerprint for the key sent by the remote host is:\n %2\n" "Please contact your system administrator.\n%3", mHost, QString::fromUtf8(hexa), QString::fromUtf8(ssh_get_error(mSession)))); ssh_string_free_char(hexa); closeConnection(); return; case SSH_SERVER_FILE_NOT_FOUND: case SSH_SERVER_NOT_KNOWN: hexa = ssh_get_hexa(hash, hlen); ssh_string_free_char((char *)hash); caption = i18n("Warning: Cannot verify host's identity."); msg = i18n("The authenticity of host %1 cannot be established.\n" "The key fingerprint is: %2\n" "Are you sure you want to continue connecting?", mHost, hexa); ssh_string_free_char(hexa); if (KMessageBox::Yes != messageBox(WarningYesNo, msg, caption)) { closeConnection(); error(KIO::ERR_USER_CANCELED, QString()); return; } /* write the known_hosts file */ qCDebug(KIO_SFTP_LOG) << "Adding server to known_hosts file."; if (ssh_write_knownhost(mSession) < 0) { error(KIO::ERR_USER_CANCELED, QString::fromUtf8(ssh_get_error(mSession))); closeConnection(); return; } break; case SSH_SERVER_ERROR: ssh_string_free_char((char *)hash); error(KIO::ERR_SLAVE_DEFINED, QString::fromUtf8(ssh_get_error(mSession))); return; } qCDebug(KIO_SFTP_LOG) << "Trying to authenticate with the server"; // Try to login without authentication rc = ssh_userauth_none(mSession, nullptr); if (rc == SSH_AUTH_ERROR) { closeConnection(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); return; } // This NEEDS to be called after ssh_userauth_none() !!! int method = ssh_auth_list(mSession); if (rc != SSH_AUTH_SUCCESS && method == 0) { closeConnection(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed. The server " "didn't send any authentication methods")); return; } // Try to authenticate with public key first if (rc != SSH_AUTH_SUCCESS && (method & SSH_AUTH_METHOD_PUBLICKEY)) { qCDebug(KIO_SFTP_LOG) << "Trying to authenticate with public key"; for(;;) { rc = ssh_userauth_publickey_auto(mSession, nullptr, nullptr); if (rc == SSH_AUTH_ERROR) { qCDebug(KIO_SFTP_LOG) << "Public key authentication failed:" << QString::fromUtf8(ssh_get_error(mSession)); closeConnection(); clearPubKeyAuthInfo(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); return; } else if (rc != SSH_AUTH_DENIED || !mPublicKeyAuthInfo || !mPublicKeyAuthInfo->isModified()) { clearPubKeyAuthInfo(); break; } } } // Try to authenticate with GSSAPI if (rc != SSH_AUTH_SUCCESS && (method & SSH_AUTH_METHOD_GSSAPI_MIC)) { qCDebug(KIO_SFTP_LOG) << "Trying to authenticate with GSSAPI"; rc = ssh_userauth_gssapi(mSession); if (rc == SSH_AUTH_ERROR) { qCDebug(KIO_SFTP_LOG) << "Public key authentication failed:" << QString::fromUtf8(ssh_get_error(mSession)); closeConnection(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); return; } } // Try to authenticate with keyboard interactive if (rc != SSH_AUTH_SUCCESS && (method & SSH_AUTH_METHOD_INTERACTIVE)) { qCDebug(KIO_SFTP_LOG) << "Trying to authenticate with keyboard interactive"; AuthInfo info2 (info); rc = authenticateKeyboardInteractive(info2); if (rc == SSH_AUTH_SUCCESS) { info = info2; } else if (rc == SSH_AUTH_ERROR) { qCDebug(KIO_SFTP_LOG) << "Keyboard interactive authentication failed:" << QString::fromUtf8(ssh_get_error(mSession)); closeConnection(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); return; } } // Try to authenticate with password if (rc != SSH_AUTH_SUCCESS && (method & SSH_AUTH_METHOD_PASSWORD)) { qCDebug(KIO_SFTP_LOG) << "Trying to authenticate with password"; info.caption = i18n("SFTP Login"); info.prompt = i18n("Please enter your username and password."); info.comment = info.url.url(); info.commentLabel = i18n("Site:"); bool isFirstLoginAttempt = true; for(;;) { if (!isFirstLoginAttempt || info.password.isEmpty()) { info.keepPassword = true; // make the "keep Password" check box visible to the user. info.setModified(false); QString username (info.username); const QString errMsg(isFirstLoginAttempt ? QString() : i18n("Incorrect username or password")); qCDebug(KIO_SFTP_LOG) << "Username:" << username << "first attempt?" << isFirstLoginAttempt << "error:" << errMsg; // Handle user canceled or dialog failed to open... int errCode = openPasswordDialogV2(info, errMsg); if (errCode != 0) { qCDebug(KIO_SFTP_LOG) << "User canceled password/retry dialog"; closeConnection(); error(errCode, QString()); return; } // If the user name changes, we have to restablish connection again // since the user name must always be set before calling ssh_connect. if (wasUsernameChanged(username, info)) { qCDebug(KIO_SFTP_LOG) << "Username changed to" << info.username; if (!info.url.userName().isEmpty()) { info.url.setUserName(info.username); } closeConnection(); if (!sftpOpenConnection(info)) { return; } } } rc = ssh_userauth_password(mSession, info.username.toUtf8().constData(), info.password.toUtf8().constData()); if (rc == SSH_AUTH_SUCCESS) { break; } else if (rc == SSH_AUTH_ERROR) { qCDebug(KIO_SFTP_LOG) << "Password authentication failed:" << QString::fromUtf8(ssh_get_error(mSession)); closeConnection(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); return; } isFirstLoginAttempt = false; // failed attempt to login. info.password.clear(); // clear the password after failed attempts. } } // If we're still not authenticated then we need to leave. if (rc != SSH_AUTH_SUCCESS) { error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); return; } // start sftp session qCDebug(KIO_SFTP_LOG) << "Trying to request the sftp session"; mSftp = sftp_new(mSession); if (mSftp == nullptr) { closeConnection(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Unable to request the SFTP subsystem. " "Make sure SFTP is enabled on the server.")); return; } qCDebug(KIO_SFTP_LOG) << "Trying to initialize the sftp session"; if (sftp_init(mSftp) < 0) { closeConnection(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Could not initialize the SFTP session.")); return; } // Login succeeded! infoMessage(i18n("Successfully connected to %1", mHost)); if (info.keepPassword) { qCDebug(KIO_SFTP_LOG) << "Caching info.username = " << info.username << ", info.url = " << info.url.toDisplayString(); cacheAuthentication(info); } // Update the original username in case it was changed! if (!mUsername.isEmpty()) { mUsername = info.username; } setTimeoutSpecialCommand(KIO_SFTP_SPECIAL_TIMEOUT); mConnected = true; connected(); info.password.fill('x'); info.password.clear(); } #endif // 0.8.0 void sftpProtocol::closeConnection() { qCDebug(KIO_SFTP_LOG); if (mSftp) { sftp_free(mSftp); mSftp = nullptr; } if (mSession) { ssh_disconnect(mSession); ssh_free(mSession); mSession = nullptr; } mConnected = false; } void sftpProtocol::special(const QByteArray &) { int rc; qCDebug(KIO_SFTP_LOG) << "special(): polling"; if (!mSftp) { error(KIO::ERR_INTERNAL, i18n("Invalid sftp context")); return; } /* * ssh_channel_poll() returns the number of bytes that may be read on the * channel. It does so by checking the input buffer and eventually the * network socket for data to read. If the input buffer is not empty, it * will not probe the network (and such not read packets nor reply to * keepalives). * * As ssh_channel_poll can act on two specific buffers (a channel has two * different stream: stdio and stderr), polling for data on the stderr * stream has more chance of not being in the problematic case (data left * in the buffer). Checking the return value (for >0) would be a good idea * to debug the problem. */ rc = ssh_channel_poll(mSftp->channel, 0); if (rc > 0) { rc = ssh_channel_poll(mSftp->channel, 1); } if (rc < 0) { qCDebug(KIO_SFTP_LOG) << "ssh_channel_poll failed: " << ssh_get_error(mSession); } setTimeoutSpecialCommand(KIO_SFTP_SPECIAL_TIMEOUT); finished(); } void sftpProtocol::open(const QUrl &url, QIODevice::OpenMode mode) { qCDebug(KIO_SFTP_LOG) << "open: " << url; if (!sftpLogin()) { // sftpLogin finished() return; } const QString path = url.path(); const QByteArray path_c = path.toUtf8(); sftp_attributes sb = sftp_lstat(mSftp, path_c.constData()); if (sb == nullptr) { reportError(url, sftp_get_error(mSftp)); return; } switch (sb->type) { case SSH_FILEXFER_TYPE_DIRECTORY: error(KIO::ERR_IS_DIRECTORY, url.toDisplayString()); sftp_attributes_free(sb); return; case SSH_FILEXFER_TYPE_SPECIAL: case SSH_FILEXFER_TYPE_UNKNOWN: error(KIO::ERR_CANNOT_OPEN_FOR_READING, url.toDisplayString()); sftp_attributes_free(sb); return; case SSH_FILEXFER_TYPE_SYMLINK: case SSH_FILEXFER_TYPE_REGULAR: break; } KIO::filesize_t fileSize = sb->size; sftp_attributes_free(sb); int flags = 0; if (mode & QIODevice::ReadOnly) { if (mode & QIODevice::WriteOnly) { flags = O_RDWR | O_CREAT; } else { flags = O_RDONLY; } } else if (mode & QIODevice::WriteOnly) { flags = O_WRONLY | O_CREAT; } if (mode & QIODevice::Append) { flags |= O_APPEND; } else if (mode & QIODevice::Truncate) { flags |= O_TRUNC; } if (flags & O_CREAT) { mOpenFile = sftp_open(mSftp, path_c.constData(), flags, 0644); } else { mOpenFile = sftp_open(mSftp, path_c.constData(), flags, 0); } if (mOpenFile == nullptr) { error(KIO::ERR_CANNOT_OPEN_FOR_READING, path); return; } // Determine the mimetype of the file to be retrieved, and emit it. // This is mandatory in all slaves (for KRun/BrowserRun to work). // If we're not opening the file ReadOnly or ReadWrite, don't attempt to // read the file and send the mimetype. if (mode & QIODevice::ReadOnly) { size_t bytesRequested = 1024; ssize_t bytesRead = 0; QVarLengthArray buffer(bytesRequested); bytesRead = sftp_read(mOpenFile, buffer.data(), bytesRequested); if (bytesRead < 0) { error(KIO::ERR_COULD_NOT_READ, mOpenUrl.toDisplayString()); closeWithoutFinish(); return; } else { QByteArray fileData = QByteArray::fromRawData(buffer.data(), bytesRead); QMimeDatabase db; QMimeType mime = db.mimeTypeForFileNameAndData(mOpenUrl.fileName(), fileData); mimeType(mime.name()); // Go back to the beginning of the file. sftp_rewind(mOpenFile); } } mOpenUrl = url; openOffset = 0; totalSize(fileSize); position(0); opened(); } void sftpProtocol::read(KIO::filesize_t bytes) { qCDebug(KIO_SFTP_LOG) << "read, offset = " << openOffset << ", bytes = " << bytes; Q_ASSERT(mOpenFile != nullptr); QVarLengthArray buffer(bytes); ssize_t bytesRead = sftp_read(mOpenFile, buffer.data(), bytes); Q_ASSERT(bytesRead <= static_cast(bytes)); if (bytesRead < 0) { qCDebug(KIO_SFTP_LOG) << "Could not read " << mOpenUrl; error(KIO::ERR_COULD_NOT_READ, mOpenUrl.toDisplayString()); closeWithoutFinish(); return; } const QByteArray fileData = QByteArray::fromRawData(buffer.data(), bytesRead); data(fileData); } void sftpProtocol::write(const QByteArray &data) { qCDebug(KIO_SFTP_LOG) << "write, offset = " << openOffset << ", bytes = " << data.size(); Q_ASSERT(mOpenFile != nullptr); ssize_t bytesWritten = sftp_write(mOpenFile, data.data(), data.size()); if (bytesWritten < 0) { qCDebug(KIO_SFTP_LOG) << "Could not write to " << mOpenUrl; error(KIO::ERR_COULD_NOT_WRITE, mOpenUrl.toDisplayString()); closeWithoutFinish(); return; } written(bytesWritten); } void sftpProtocol::seek(KIO::filesize_t offset) { qCDebug(KIO_SFTP_LOG) << "seek, offset = " << offset; Q_ASSERT(mOpenFile != nullptr); if (sftp_seek64(mOpenFile, static_cast(offset)) < 0) { error(KIO::ERR_COULD_NOT_SEEK, mOpenUrl.path()); closeWithoutFinish(); return; } position(sftp_tell64(mOpenFile)); } void sftpProtocol::close() { closeWithoutFinish(); finished(); } void sftpProtocol::get(const QUrl& url) { qCDebug(KIO_SFTP_LOG) << url; int errorCode = 0; const sftpProtocol::StatusCode cs = sftpGet(url, errorCode); // The call to sftpGet should only return server side errors since the file // descriptor parameter is set to -1. if (cs == sftpProtocol::ServerError && errorCode) { error(errorCode, url.toDisplayString()); return; } finished(); } sftpProtocol::StatusCode sftpProtocol::sftpGet(const QUrl& url, int& errorCode, KIO::fileoffset_t offset, int fd) { qCDebug(KIO_SFTP_LOG) << url; if (!sftpLogin()) { return sftpProtocol::ServerError; } QByteArray path = url.path().toUtf8(); sftp_file file = nullptr; KIO::filesize_t totalbytesread = 0; QByteArray filedata; sftp_attributes sb = sftp_lstat(mSftp, path.constData()); if (sb == nullptr) { errorCode = toKIOError(sftp_get_error(mSftp)); return sftpProtocol::ServerError; } switch (sb->type) { case SSH_FILEXFER_TYPE_DIRECTORY: errorCode = KIO::ERR_IS_DIRECTORY; sftp_attributes_free(sb); return sftpProtocol::ServerError; case SSH_FILEXFER_TYPE_SPECIAL: case SSH_FILEXFER_TYPE_UNKNOWN: errorCode = KIO::ERR_CANNOT_OPEN_FOR_READING; sftp_attributes_free(sb); return sftpProtocol::ServerError; case SSH_FILEXFER_TYPE_SYMLINK: case SSH_FILEXFER_TYPE_REGULAR: break; } // Open file file = sftp_open(mSftp, path.constData(), O_RDONLY, 0); if (file == nullptr) { errorCode = KIO::ERR_CANNOT_OPEN_FOR_READING; sftp_attributes_free(sb); return sftpProtocol::ServerError; } char mimeTypeBuf[1024]; ssize_t bytesread = sftp_read(file, mimeTypeBuf, sizeof(mimeTypeBuf)); if (bytesread < 0) { errorCode = KIO::ERR_COULD_NOT_READ; return sftpProtocol::ServerError; } else { QMimeDatabase db; QMimeType mime = db.mimeTypeForFileNameAndData(url.fileName(), QByteArray(mimeTypeBuf, bytesread)); if (!mime.isDefault()) { mimeType(mime.name()); } else { mime = db.mimeTypeForUrl(url); mimeType(mime.name()); } sftp_rewind(file); } // Set the total size totalSize(sb->size); // If offset is not specified, check the "resume" meta-data. if (offset < 0) { const QString resumeOffsetStr = metaData(QLatin1String("resume")); if (!resumeOffsetStr.isEmpty()) { bool ok; qlonglong resumeOffset = resumeOffsetStr.toLongLong(&ok); if (ok) { offset = resumeOffset; } } } // If we can resume, offset the buffer properly. if (offset > 0 && ((unsigned long long) offset < sb->size)) { if (sftp_seek64(file, offset) == 0) { canResume(); totalbytesread = offset; qCDebug(KIO_SFTP_LOG) << "Resume offset: " << QString::number(offset); } } bytesread = 0; sftpProtocol::GetRequest request(file, sb); for (;;) { // Enqueue get requests if (!request.enqueueChunks()) { errorCode = KIO::ERR_COULD_NOT_READ; return sftpProtocol::ServerError; } filedata.clear(); bytesread = request.readChunks(filedata); // Read pending get requests if (bytesread == -1) { errorCode = KIO::ERR_COULD_NOT_READ; return sftpProtocol::ServerError; } else if (bytesread == 0) { if (file->eof) break; else continue; } if (fd == -1) { data(filedata); } else if ((errorCode = writeToFile(fd, filedata.constData(), filedata.size())) != 0) { return sftpProtocol::ClientError; } // increment total bytes read totalbytesread += filedata.length(); processedSize(totalbytesread); } if (fd == -1) data(QByteArray()); processedSize(static_cast(sb->size)); return sftpProtocol::Success; } void sftpProtocol::put(const QUrl& url, int permissions, KIO::JobFlags flags) { qCDebug(KIO_SFTP_LOG) << url << ", permissions =" << permissions << ", overwrite =" << (flags & KIO::Overwrite) << ", resume =" << (flags & KIO::Resume); qCDebug(KIO_SFTP_LOG) << url; int errorCode = 0; const sftpProtocol::StatusCode cs = sftpPut(url, permissions, flags, errorCode); // The call to sftpPut should only return server side errors since the file // descriptor parameter is set to -1. if (cs == sftpProtocol::ServerError && errorCode) { error(errorCode, url.toDisplayString()); return; } finished(); } sftpProtocol::StatusCode sftpProtocol::sftpPut(const QUrl& url, int permissions, JobFlags flags, int& errorCode, int fd) { qCDebug(KIO_SFTP_LOG) << url << ", permissions =" << permissions << ", overwrite =" << (flags & KIO::Overwrite) << ", resume =" << (flags & KIO::Resume); if (!sftpLogin()) { return sftpProtocol::ServerError; } const QString dest_orig = url.path(); const QByteArray dest_orig_c = dest_orig.toUtf8(); const QString dest_part = dest_orig + ".part"; const QByteArray dest_part_c = dest_part.toUtf8(); uid_t owner = 0; gid_t group = 0; sftp_attributes sb = sftp_lstat(mSftp, dest_orig_c.constData()); const bool bOrigExists = (sb != nullptr); bool bPartExists = false; - const bool bMarkPartial = config()->readEntry("MarkPartial", true); + const bool bMarkPartial = configValue(QStringLiteral("MarkPartial"), true); // Don't change permissions of the original file if (bOrigExists) { permissions = sb->permissions; owner = sb->uid; group = sb->gid; } if (bMarkPartial) { sftp_attributes sbPart = sftp_lstat(mSftp, dest_part_c.constData()); bPartExists = (sbPart != nullptr); if (bPartExists && !(flags & KIO::Resume) && !(flags & KIO::Overwrite) && sbPart->size > 0 && sbPart->type == SSH_FILEXFER_TYPE_REGULAR) { if (fd == -1) { // Maybe we can use this partial file for resuming // Tell about the size we have, and the app will tell us // if it's ok to resume or not. qCDebug(KIO_SFTP_LOG) << "calling canResume with " << sbPart->size; flags |= canResume(sbPart->size) ? KIO::Resume : KIO::DefaultFlags; qCDebug(KIO_SFTP_LOG) << "put got answer " << (flags & KIO::Resume); } else { KIO::filesize_t pos = seekPos(fd, sbPart->size, SEEK_SET); if (pos != sbPart->size) { qCDebug(KIO_SFTP_LOG) << "Failed to seek to" << sbPart->size << "bytes in source file. Reason given" << strerror(errno); sftp_attributes_free(sb); sftp_attributes_free(sbPart); errorCode = ERR_COULD_NOT_SEEK; return sftpProtocol::ClientError; } flags |= KIO::Resume; } qCDebug(KIO_SFTP_LOG) << "Resuming at" << sbPart->size; sftp_attributes_free(sbPart); } } if (bOrigExists && !(flags & KIO::Overwrite) && !(flags & KIO::Resume)) { errorCode = KSFTP_ISDIR(sb) ? KIO::ERR_DIR_ALREADY_EXIST : KIO::ERR_FILE_ALREADY_EXIST; sftp_attributes_free(sb); return sftpProtocol::ServerError; } QByteArray dest; int result = -1; sftp_file file = nullptr; StatusCode cs = sftpProtocol::Success; KIO::fileoffset_t totalBytesSent = 0; // Loop until we got 0 (end of data) do { QByteArray buffer; if (fd == -1) { dataReq(); // Request for data result = readData(buffer); } else { char buf[MAX_XFER_BUF_SIZE]; // result = ::read(fd, buf, sizeof(buf)); if(result < 0) { errorCode = ERR_COULD_NOT_READ; cs = sftpProtocol::ClientError; break; } buffer = QByteArray(buf, result); } if (result >= 0) { if (dest.isEmpty()) { if (bMarkPartial) { qCDebug(KIO_SFTP_LOG) << "Appending .part extension to" << dest_orig; dest = dest_part_c; if (bPartExists && !(flags & KIO::Resume)) { qCDebug(KIO_SFTP_LOG) << "Deleting partial file" << dest_part; sftp_unlink(mSftp, dest_part_c.constData()); // Catch errors when we try to open the file. } } else { dest = dest_orig_c; // Will be automatically truncated below... } // bMarkPartial if ((flags & KIO::Resume)) { sftp_attributes fstat; qCDebug(KIO_SFTP_LOG) << "Trying to append: " << dest; file = sftp_open(mSftp, dest.constData(), O_RDWR, 0); // append if resuming if (file) { fstat = sftp_fstat(file); if (fstat) { sftp_seek64(file, fstat->size); // Seek to end TODO totalBytesSent += fstat->size; sftp_attributes_free(fstat); } } } else { mode_t initialMode; if (permissions != -1) { #ifdef Q_OS_WIN initialMode = permissions | static_cast(perms::owner_write | perms::owner_read); #else initialMode = permissions | S_IWUSR | S_IRUSR; #endif } else { initialMode = 0644; } qCDebug(KIO_SFTP_LOG) << "Trying to open:" << QString(dest) << ", mode=" << QString::number(initialMode); file = sftp_open(mSftp, dest.constData(), O_CREAT | O_TRUNC | O_WRONLY, initialMode); } // flags & KIO::Resume if (file == nullptr) { qCDebug(KIO_SFTP_LOG) << "COULD NOT WRITE " << QString(dest) << ", permissions=" << permissions << ", error=" << ssh_get_error(mSession); if (sftp_get_error(mSftp) == SSH_FX_PERMISSION_DENIED) { errorCode = KIO::ERR_WRITE_ACCESS_DENIED; } else { errorCode = KIO::ERR_CANNOT_OPEN_FOR_WRITING; } cs = sftpProtocol::ServerError; result = -1; continue; } // file } // dest.isEmpty ssize_t bytesWritten = sftp_write(file, buffer.data(), buffer.size()); if (bytesWritten < 0) { errorCode = KIO::ERR_COULD_NOT_WRITE; result = -1; } else { totalBytesSent += bytesWritten; processedSize(totalBytesSent); } } // result } while (result > 0); sftp_attributes_free(sb); // An error occurred deal with it. if (result < 0) { qCDebug(KIO_SFTP_LOG) << "Error during 'put'. Aborting."; if (file != nullptr) { sftp_close(file); sftp_attributes attr = sftp_stat(mSftp, dest.constData()); if (bMarkPartial && attr != nullptr) { - size_t size = config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); + size_t size = configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE); if (attr->size < size) { sftp_unlink(mSftp, dest.constData()); } } sftp_attributes_free(attr); } //::exit(255); return cs; } if (file == nullptr) { // we got nothing to write out, so we never opened the file return sftpProtocol::Success; } if (sftp_close(file) < 0) { qCWarning(KIO_SFTP_LOG) << "Error when closing file descriptor"; error(KIO::ERR_COULD_NOT_WRITE, dest_orig); return sftpProtocol::ServerError; } // after full download rename the file back to original name if (bMarkPartial) { // If the original URL is a symlink and we were asked to overwrite it, // remove the symlink first. This ensures that we do not overwrite the // current source if the symlink points to it. if ((flags & KIO::Overwrite)) { sftp_unlink(mSftp, dest_orig_c.constData()); } if (sftp_rename(mSftp, dest.constData(), dest_orig_c.constData()) < 0) { qCWarning(KIO_SFTP_LOG) << " Couldn't rename " << dest << " to " << dest_orig; errorCode = KIO::ERR_CANNOT_RENAME_PARTIAL; return sftpProtocol::ServerError; } } // set final permissions if (permissions != -1 && !(flags & KIO::Resume)) { qCDebug(KIO_SFTP_LOG) << "Trying to set final permissions of " << dest_orig << " to " << QString::number(permissions); if (sftp_chmod(mSftp, dest_orig_c.constData(), permissions) < 0) { errorCode = -1; // force copy to call sftpSendWarning... return sftpProtocol::ServerError; } } // set original owner and group if (bOrigExists) { qCDebug(KIO_SFTP_LOG) << "Trying to restore original owner and group of " << dest_orig; if (sftp_chown(mSftp, dest_orig_c.constData(), owner, group) < 0) { qCWarning(KIO_SFTP_LOG) << "Could not change owner and group for" << dest_orig; // warning(i18n( "Could not change owner and group for\n%1", dest_orig)); } } // set modification time const QString mtimeStr = metaData("modified"); if (!mtimeStr.isEmpty()) { QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate); if (dt.isValid()) { struct timeval times[2]; sftp_attributes attr = sftp_lstat(mSftp, dest_orig_c.constData()); if (attr != nullptr) { times[0].tv_sec = attr->atime; //// access time, unchanged times[1].tv_sec = dt.toTime_t(); // modification time times[0].tv_usec = times[1].tv_usec = 0; qCDebug(KIO_SFTP_LOG) << "Trying to restore mtime for " << dest_orig << " to: " << mtimeStr; result = sftp_utimes(mSftp, dest_orig_c.constData(), times); if (result < 0) { qCWarning(KIO_SFTP_LOG) << "Failed to set mtime for" << dest_orig; } sftp_attributes_free(attr); } } } return sftpProtocol::Success; } void sftpProtocol::copy(const QUrl &src, const QUrl &dest, int permissions, KIO::JobFlags flags) { qCDebug(KIO_SFTP_LOG) << src << " -> " << dest << " , permissions = " << QString::number(permissions) << ", overwrite = " << (flags & KIO::Overwrite) << ", resume = " << (flags & KIO::Resume); QString sCopyFile; int errorCode = 0; StatusCode cs = sftpProtocol::ClientError; const bool isSourceLocal = src.isLocalFile(); const bool isDestinationLocal = dest.isLocalFile(); if (!isSourceLocal && isDestinationLocal) { // sftp -> file sCopyFile = dest.toLocalFile(); cs = sftpCopyGet(src, sCopyFile, permissions, flags, errorCode); if (cs == sftpProtocol::ServerError) sCopyFile = src.url(); } else if (isSourceLocal && !isDestinationLocal) { // file -> sftp sCopyFile = src.toLocalFile(); cs = sftpCopyPut(dest, sCopyFile, permissions, flags, errorCode); if (cs == sftpProtocol::ServerError) sCopyFile = dest.url(); } else { errorCode = KIO::ERR_UNSUPPORTED_ACTION; sCopyFile.clear(); } if (cs != sftpProtocol::Success && errorCode > 0) { error(errorCode, sCopyFile); return; } if (errorCode < 0) { sftpSendWarning(errorCode, sCopyFile); } finished(); } sftpProtocol::StatusCode sftpProtocol::sftpCopyGet(const QUrl& url, const QString& sCopyFile, int permissions, KIO::JobFlags flags, int& errorCode) { qCDebug(KIO_SFTP_LOG) << url << "->" << sCopyFile << ", permissions=" << permissions; // check if destination is ok ... QFileInfo copyFile(sCopyFile); const bool bDestExists = copyFile.exists(); if(bDestExists) { if(copyFile.isDir()) { errorCode = ERR_IS_DIRECTORY; return sftpProtocol::ClientError; } if(!(flags & KIO::Overwrite)) { errorCode = ERR_FILE_ALREADY_EXIST; return sftpProtocol::ClientError; } } bool bResume = false; const QString sPart = sCopyFile + QLatin1String(".part"); // do we have a ".part" file? QFileInfo partFile(sPart); const bool bPartExists = partFile.exists(); - const bool bMarkPartial = config()->readEntry("MarkPartial", true); + const bool bMarkPartial = configValue(QStringLiteral("MarkPartial"), true); const QString dest = (bMarkPartial ? sPart : sCopyFile); if (bMarkPartial && bPartExists && copyFile.size() > 0) { if(partFile.isDir()) { errorCode = ERR_DIR_ALREADY_EXIST; return sftpProtocol::ClientError; // client side error } bResume = canResume( copyFile.size() ); } if (bPartExists && !bResume) // get rid of an unwanted ".part" file QFile::remove(sPart); // WABA: Make sure that we keep writing permissions ourselves, // otherwise we can be in for a surprise on NFS. mode_t initialMode; if (permissions != -1) #ifdef Q_OS_WIN initialMode = permissions | static_cast(perms::owner_write); #else initialMode = permissions | S_IWUSR; #endif else initialMode = 0666; // open the output file ... int fd = -1; KIO::fileoffset_t offset = 0; if (bResume) { fd = QT_OPEN( QFile::encodeName(sPart), O_RDWR ); // append if resuming offset = seekPos(fd, 0, SEEK_END); if(offset < 0) { errorCode = ERR_CANNOT_RESUME; ::close(fd); return sftpProtocol::ClientError; // client side error } qCDebug(KIO_SFTP_LOG) << "resuming at" << offset; } else { fd = QT_OPEN(QFile::encodeName(dest), O_CREAT | O_TRUNC | O_WRONLY, initialMode); } if (fd == -1) { qCDebug(KIO_SFTP_LOG) << "could not write to" << sCopyFile; errorCode = (errno == EACCES) ? ERR_WRITE_ACCESS_DENIED : ERR_CANNOT_OPEN_FOR_WRITING; return sftpProtocol::ClientError; } StatusCode result = sftpGet(url, errorCode, offset, fd); if( ::close(fd) && result == sftpProtocol::Success ) { errorCode = ERR_COULD_NOT_WRITE; result = sftpProtocol::ClientError; } // handle renaming or deletion of a partial file ... if (bMarkPartial) { if (result == sftpProtocol::Success) { // rename ".part" on success if (!QFile::rename(sPart, sCopyFile)) { // If rename fails, try removing the destination first if it exists. if (!bDestExists || !QFile::remove(sCopyFile) || !QFile::rename(sPart, sCopyFile)) { qCDebug(KIO_SFTP_LOG) << "cannot rename " << sPart << " to " << sCopyFile; errorCode = ERR_CANNOT_RENAME_PARTIAL; result = sftpProtocol::ClientError; } } } else{ partFile.refresh(); - const int size = config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); + const int size = configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE); if (partFile.exists() && partFile.size() < size) { // should a very small ".part" be deleted? QFile::remove(sPart); } } } const QString mtimeStr = metaData("modified"); if (!mtimeStr.isEmpty()) { QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate); if (dt.isValid()) { QFile receivedFile(sCopyFile); if (receivedFile.exists()) { if (!receivedFile.open(QIODevice::ReadWrite | QIODevice::Text)) { QString error_msg = receivedFile.errorString(); qCDebug(KIO_SFTP_LOG) << "Couldn't update modified time : " << error_msg; } else { receivedFile.setFileTime(dt, QFileDevice::FileModificationTime); } } } } return result; } sftpProtocol::StatusCode sftpProtocol::sftpCopyPut(const QUrl& url, const QString& sCopyFile, int permissions, JobFlags flags, int& errorCode) { qCDebug(KIO_SFTP_LOG) << sCopyFile << "->" << url << ", permissions=" << permissions << ", flags" << flags; // check if source is ok ... QFileInfo copyFile(sCopyFile); bool bSrcExists = copyFile.exists(); if (bSrcExists) { if(copyFile.isDir()) { errorCode = ERR_IS_DIRECTORY; return sftpProtocol::ClientError; } } else { errorCode = ERR_DOES_NOT_EXIST; return sftpProtocol::ClientError; } const int fd = QT_OPEN(QFile::encodeName(sCopyFile), O_RDONLY); if(fd == -1) { errorCode = ERR_CANNOT_OPEN_FOR_READING; return sftpProtocol::ClientError; } totalSize(copyFile.size()); // delegate the real work (errorCode gets status) ... StatusCode ret = sftpPut(url, permissions, flags, errorCode, fd); ::close(fd); return ret; } void sftpProtocol::stat(const QUrl& url) { qCDebug(KIO_SFTP_LOG) << url; if (!sftpLogin()) { // sftpLogin finished() return; } if (url.path().isEmpty() || QDir::isRelativePath(url.path()) || url.path().contains("/./") || url.path().contains("/../")) { QString cPath; if (!url.path().isEmpty()) { cPath = canonicalizePath(url.path()); } else { cPath = canonicalizePath(QLatin1String(".")); } if (cPath.isEmpty()) { error(KIO::ERR_MALFORMED_URL, url.toDisplayString()); return; } QUrl redir(url); redir.setPath(cPath); redirection(redir); qCDebug(KIO_SFTP_LOG) << "redirecting to " << redir.url(); finished(); return; } QByteArray path = url.path().toUtf8(); const QString sDetails = metaData(QLatin1String("details")); const int details = sDetails.isEmpty() ? 2 : sDetails.toInt(); UDSEntry entry; entry.clear(); if (!createUDSEntry(url.fileName(), path, entry, details)) { error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); return; } statEntry(entry); finished(); } void sftpProtocol::mimetype(const QUrl& url){ qCDebug(KIO_SFTP_LOG) << url; if (!sftpLogin()) { // sftpLogin finished() return; } // open() feeds the mimetype open(url, QIODevice::ReadOnly); // open() finished(), don't finish in close again. closeWithoutFinish(); } void sftpProtocol::listDir(const QUrl& url) { qCDebug(KIO_SFTP_LOG) << "list directory: " << url; if (!sftpLogin()) { // sftpLogin finished() return; } if (url.path().isEmpty() || QDir::isRelativePath(url.path()) || url.path().contains("/./") || url.path().contains("/../")) { QString cPath; if (!url.path().isEmpty() ) { cPath = canonicalizePath(url.path()); } else { cPath = canonicalizePath(QStringLiteral(".")); } if (cPath.isEmpty()) { error(KIO::ERR_MALFORMED_URL, url.toDisplayString()); return; } QUrl redir(url); redir.setPath(cPath); redirection(redir); qCDebug(KIO_SFTP_LOG) << "redirecting to " << redir.url(); finished(); return; } QByteArray path = url.path().toUtf8(); sftp_dir dp = sftp_opendir(mSftp, path.constData()); if (dp == nullptr) { reportError(url, sftp_get_error(mSftp)); return; } sftp_attributes dirent = nullptr; const QString sDetails = metaData(QLatin1String("details")); const int details = sDetails.isEmpty() ? 2 : sDetails.toInt(); UDSEntry entry; qCDebug(KIO_SFTP_LOG) << "readdir: " << path << ", details: " << QString::number(details); for (;;) { mode_t access; char *link; bool isBrokenLink = false; long long fileType = QT_STAT_REG; long long size = 0LL; dirent = sftp_readdir(mSftp, dp); if (dirent == nullptr) { break; } entry.clear(); entry.fastInsert(KIO::UDSEntry::UDS_NAME, QFile::decodeName(dirent->name)); if (dirent->type == SSH_FILEXFER_TYPE_SYMLINK) { QByteArray file = path + '/' + QFile::decodeName(dirent->name).toUtf8(); link = sftp_readlink(mSftp, file.constData()); if (link == nullptr) { sftp_attributes_free(dirent); error(KIO::ERR_INTERNAL, i18n("Could not read link: %1", QString::fromUtf8(file))); return; } entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, QFile::decodeName(link)); free(link); // A symlink -> follow it only if details > 1 if (details > 1) { sftp_attributes sb = sftp_stat(mSftp, file.constData()); if (sb == nullptr) { isBrokenLink = true; } else { sftp_attributes_free(dirent); dirent = sb; } } } if (isBrokenLink) { // It is a link pointing to nowhere fileType = QT_STAT_MASK - 1; #ifdef Q_OS_WIN access = static_cast(perms::owner_all | perms::group_all | perms::others_all); #else access = S_IRWXU | S_IRWXG | S_IRWXO; #endif size = 0LL; } else { switch (dirent->type) { case SSH_FILEXFER_TYPE_REGULAR: fileType = QT_STAT_REG; break; case SSH_FILEXFER_TYPE_DIRECTORY: fileType = QT_STAT_DIR; break; case SSH_FILEXFER_TYPE_SYMLINK: fileType = QT_STAT_LNK; break; case SSH_FILEXFER_TYPE_SPECIAL: case SSH_FILEXFER_TYPE_UNKNOWN: break; } access = dirent->permissions & 07777; size = dirent->size; } entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, fileType); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, access); entry.fastInsert(KIO::UDSEntry::UDS_SIZE, size); if (details > 0) { if (dirent->owner) { entry.fastInsert(KIO::UDSEntry::UDS_USER, QString::fromUtf8(dirent->owner)); } else { entry.fastInsert(KIO::UDSEntry::UDS_USER, QString::number(dirent->uid)); } if (dirent->group) { entry.fastInsert(KIO::UDSEntry::UDS_GROUP, QString::fromUtf8(dirent->group)); } else { entry.fastInsert(KIO::UDSEntry::UDS_GROUP, QString::number(dirent->gid)); } entry.fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, dirent->atime); entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, dirent->mtime); entry.fastInsert(KIO::UDSEntry::UDS_CREATION_TIME, dirent->createtime); } sftp_attributes_free(dirent); listEntry(entry); } // for ever sftp_closedir(dp); finished(); } void sftpProtocol::mkdir(const QUrl &url, int permissions) { qCDebug(KIO_SFTP_LOG) << "create directory: " << url; if (!sftpLogin()) { // sftpLogin finished() return; } if (url.path().isEmpty()) { error(KIO::ERR_MALFORMED_URL, url.toDisplayString()); return; } const QString path = url.path(); const QByteArray path_c = path.toUtf8(); // Remove existing file or symlink, if requested. if (metaData(QLatin1String("overwrite")) == QLatin1String("true")) { qCDebug(KIO_SFTP_LOG) << "overwrite set, remove existing file or symlink: " << url; sftp_unlink(mSftp, path_c.constData()); } qCDebug(KIO_SFTP_LOG) << "Trying to create directory: " << path; sftp_attributes sb = sftp_lstat(mSftp, path_c.constData()); if (sb == nullptr) { if (sftp_mkdir(mSftp, path_c.constData(), 0777) < 0) { reportError(url, sftp_get_error(mSftp)); sftp_attributes_free(sb); return; } qCDebug(KIO_SFTP_LOG) << "Successfully created directory: " << url; if (permissions != -1) { // This will report an error or finished. chmod(url, permissions); } else { finished(); } sftp_attributes_free(sb); return; } auto err = KSFTP_ISDIR(sb) ? KIO::ERR_DIR_ALREADY_EXIST : KIO::ERR_FILE_ALREADY_EXIST; sftp_attributes_free(sb); error(err, path); } void sftpProtocol::rename(const QUrl& src, const QUrl& dest, KIO::JobFlags flags) { qCDebug(KIO_SFTP_LOG) << "rename " << src << " to " << dest << flags; if (!sftpLogin()) { // sftpLogin finished() return; } QByteArray qsrc = src.path().toUtf8(); QByteArray qdest = dest.path().toUtf8(); sftp_attributes sb = sftp_lstat(mSftp, qdest.constData()); if (sb != nullptr) { const bool isDir = KSFTP_ISDIR(sb); if (!(flags & KIO::Overwrite)) { error(isDir ? KIO::ERR_DIR_ALREADY_EXIST : KIO::ERR_FILE_ALREADY_EXIST, dest.url()); sftp_attributes_free(sb); return; } // Delete the existing destination file/dir... if (isDir) { if (sftp_rmdir(mSftp, qdest.constData()) < 0) { reportError(dest, sftp_get_error(mSftp)); return; } } else { if (sftp_unlink(mSftp, qdest.constData()) < 0) { reportError(dest, sftp_get_error(mSftp)); return; } } } sftp_attributes_free(sb); if (sftp_rename(mSftp, qsrc.constData(), qdest.constData()) < 0) { reportError(dest, sftp_get_error(mSftp)); return; } finished(); } void sftpProtocol::symlink(const QString &target, const QUrl &dest, KIO::JobFlags flags) { qCDebug(KIO_SFTP_LOG) << "link " << target << "->" << dest << ", overwrite = " << (flags & KIO::Overwrite) << ", resume = " << (flags & KIO::Resume); if (!sftpLogin()) { // sftpLogin finished() return; } QByteArray t = target.toUtf8(); QByteArray d = dest.path().toUtf8(); bool failed = false; if (sftp_symlink(mSftp, t.constData(), d.constData()) < 0) { if (flags == KIO::Overwrite) { sftp_attributes sb = sftp_lstat(mSftp, d.constData()); if (sb == nullptr) { failed = true; } else { if (sftp_unlink(mSftp, d.constData()) < 0) { failed = true; } else { if (sftp_symlink(mSftp, t.constData(), d.constData()) < 0) { failed = true; } } } sftp_attributes_free(sb); } } if (failed) { reportError(dest, sftp_get_error(mSftp)); return; } finished(); } void sftpProtocol::chmod(const QUrl& url, int permissions) { qCDebug(KIO_SFTP_LOG) << "change permission of " << url << " to " << QString::number(permissions); if (!sftpLogin()) { // sftpLogin finished() return; } QByteArray path = url.path().toUtf8(); if (sftp_chmod(mSftp, path.constData(), permissions) < 0) { reportError(url, sftp_get_error(mSftp)); return; } finished(); } void sftpProtocol::del(const QUrl &url, bool isfile){ qCDebug(KIO_SFTP_LOG) << "deleting " << (isfile ? "file: " : "directory: ") << url; if (!sftpLogin()) { // sftpLogin finished() return; } QByteArray path = url.path().toUtf8(); if (isfile) { if (sftp_unlink(mSftp, path.constData()) < 0) { reportError(url, sftp_get_error(mSftp)); return; } } else { if (sftp_rmdir(mSftp, path.constData()) < 0) { reportError(url, sftp_get_error(mSftp)); return; } } finished(); } void sftpProtocol::slave_status() { qCDebug(KIO_SFTP_LOG) << "connected to " << mHost << "?: " << mConnected; slaveStatus((mConnected ? mHost : QString()), mConnected); } sftpProtocol::GetRequest::GetRequest(sftp_file file, sftp_attributes sb, ushort maxPendingRequests) :mFile(file), mSb(sb), mMaxPendingRequests(maxPendingRequests) { } bool sftpProtocol::GetRequest::enqueueChunks() { sftpProtocol::GetRequest::Request request; qCDebug(KIO_SFTP_TRACE_LOG) << "enqueueChunks"; while (pendingRequests.count() < mMaxPendingRequests) { request.expectedLength = MAX_XFER_BUF_SIZE; request.startOffset = mFile->offset; request.id = sftp_async_read_begin(mFile, request.expectedLength); if (request.id < 0) { if (pendingRequests.isEmpty()) { return false; } else { break; } } pendingRequests.enqueue(request); if (mFile->offset >= mSb->size) { // Do not add any more chunks if the offset is larger than the given file size. // However this is done after adding a request as the remote file size may // have changed in the meantime. break; } } qCDebug(KIO_SFTP_TRACE_LOG) << "enqueueChunks done" << QString::number(pendingRequests.size()); return true; } int sftpProtocol::GetRequest::readChunks(QByteArray &data) { int totalRead = 0; ssize_t bytesread = 0; while (!pendingRequests.isEmpty()) { sftpProtocol::GetRequest::Request &request = pendingRequests.head(); int dataSize = data.size() + request.expectedLength; data.resize(dataSize); if (data.size() < dataSize) { // Could not allocate enough memory - skip current chunk data.resize(dataSize - request.expectedLength); break; } bytesread = sftp_async_read(mFile, data.data() + totalRead, request.expectedLength, request.id); // qCDebug(KIO_SFTP_LOG) << "bytesread=" << QString::number(bytesread); if (bytesread == 0 || bytesread == SSH_AGAIN) { // Done reading or timeout data.resize(data.size() - request.expectedLength); if (bytesread == 0) { pendingRequests.dequeue(); // This frees QByteArray &data! } break; } else if (bytesread == SSH_ERROR) { return -1; } totalRead += bytesread; if (bytesread < request.expectedLength) { int rc; // If less data is read than expected - requeue the request data.resize(data.size() - (request.expectedLength - bytesread)); // Modify current request request.expectedLength -= bytesread; request.startOffset += bytesread; rc = sftp_seek64(mFile, request.startOffset); if (rc < 0) { // Failed to continue reading return -1; } request.id = sftp_async_read_begin(mFile, request.expectedLength); if (request.id < 0) { // Failed to dispatch rerequest return -1; } return totalRead; } pendingRequests.dequeue(); } return totalRead; } sftpProtocol::GetRequest::~GetRequest() { sftpProtocol::GetRequest::Request request; char buf[MAX_XFER_BUF_SIZE]; // Remove pending reads to avoid memory leaks while (!pendingRequests.isEmpty()) { request = pendingRequests.dequeue(); sftp_async_read(mFile, buf, request.expectedLength, request.id); } // Close channel & free attributes sftp_close(mFile); sftp_attributes_free(mSb); } void sftpProtocol::requiresUserNameRedirection() { QUrl redirectUrl; redirectUrl.setScheme( QLatin1String("sftp") ); redirectUrl.setUserName( mUsername ); redirectUrl.setPassword( mPassword ); redirectUrl.setHost( mHost ); if (mPort > 0 && mPort != DEFAULT_SFTP_PORT) { redirectUrl.setPort( mPort ); } qCDebug(KIO_SFTP_LOG) << "redirecting to" << redirectUrl; redirection( redirectUrl ); } bool sftpProtocol::sftpLogin() { const QString origUsername = mUsername; openConnection(); qCDebug(KIO_SFTP_LOG) << "connected ?" << mConnected << "username: old=" << origUsername << "new=" << mUsername; if (!origUsername.isEmpty() && origUsername != mUsername) { requiresUserNameRedirection(); finished(); return false; } return mConnected; } void sftpProtocol::sftpSendWarning(int errorCode, const QString& url) { switch (errorCode) { case -1: warning(i18n( "Could not change permissions for\n%1", url)); break; default: break; } } void sftpProtocol::closeWithoutFinish() { sftp_close(mOpenFile); mOpenFile = nullptr; } void sftpProtocol::clearPubKeyAuthInfo() { if (mPublicKeyAuthInfo) { delete mPublicKeyAuthInfo; mPublicKeyAuthInfo = nullptr; } } void sftpProtocol::fileSystemFreeSpace(const QUrl& url) { qCDebug(KIO_SFTP_LOG) << "file system free space of" << url; if (!sftpLogin()) { // sftpLogin finished() return; } if (sftp_extension_supported(mSftp, "statvfs@openssh.com", "2") == 0) { error(ERR_UNSUPPORTED_ACTION, QString()); return; } const QByteArray path = url.path().toUtf8(); sftp_statvfs_t statvfs = sftp_statvfs(mSftp, path.constData()); if (statvfs == nullptr) { reportError(url, sftp_get_error(mSftp)); return; } setMetaData(QString::fromLatin1("total"), QString::number(statvfs->f_frsize * statvfs->f_blocks)); setMetaData(QString::fromLatin1("available"), QString::number(statvfs->f_frsize * statvfs->f_bavail)); sftp_statvfs_free(statvfs); finished(); } diff --git a/smb/kio_smb_dir.cpp b/smb/kio_smb_dir.cpp index f5e26dfa..6a4db59f 100644 --- a/smb/kio_smb_dir.cpp +++ b/smb/kio_smb_dir.cpp @@ -1,784 +1,784 @@ ///////////////////////////////////////////////////////////////////////////// // // Project: SMB kioslave for KDE2 // // File: kio_smb_dir.cpp // // Abstract: member function implementations for SMBSlave that deal with // SMB directory access // // Author(s): Matthew Peterson // ////--------------------------------------------------------------------------- // // Copyright (c) 2000 Caldera Systems, Inc. // // This program is free software; you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the // Free Software Foundation; either version 2.1 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; see the file COPYING. If not, please obtain // a copy from http://www.gnu.org/copyleft/gpl.html // ///////////////////////////////////////////////////////////////////////////// #include "kio_smb.h" #include "kio_smb_internal.h" #include #include #include #include #include //=========================================================================== void SMBSlave::copy(const QUrl& src, const QUrl& dst, int permissions, KIO::JobFlags flags) { const bool isSourceLocal = src.isLocalFile(); const bool isDestinationLocal = dst.isLocalFile(); if (!isSourceLocal && isDestinationLocal) { smbCopyGet(src, dst, permissions, flags); } else if (isSourceLocal && !isDestinationLocal) { smbCopyPut(src, dst, permissions, flags); } else { smbCopy(src, dst, permissions, flags); } } void SMBSlave::smbCopy(const QUrl& ksrc, const QUrl& kdst, int permissions, KIO::JobFlags flags) { SMBUrl src; SMBUrl dst; mode_t initialmode; ssize_t n; int dstflags; int srcfd = -1; int dstfd = -1; int errNum = 0; KIO::filesize_t processed_size = 0; unsigned char buf[MAX_XFER_BUF_SIZE]; qCDebug(KIO_SMB) << "SMBSlave::copy with src = " << ksrc << "and dest = " << kdst; // setup urls src = ksrc; dst = kdst; // Obtain information about source errNum = cache_stat(src, &st ); if( errNum != 0 ) { if ( errNum == EACCES ) { error( KIO::ERR_ACCESS_DENIED, src.toDisplayString()); } else { error( KIO::ERR_DOES_NOT_EXIST, src.toDisplayString()); } return; } if ( S_ISDIR( st.st_mode ) ) { error( KIO::ERR_IS_DIRECTORY, src.toDisplayString() ); return; } totalSize(st.st_size); // Check to se if the destination exists errNum = cache_stat(dst, &st); if( errNum == 0 ) { if(S_ISDIR(st.st_mode)) { error( KIO::ERR_DIR_ALREADY_EXIST, dst.toDisplayString()); return; } if(!(flags & KIO::Overwrite)) { error( KIO::ERR_FILE_ALREADY_EXIST, dst.toDisplayString()); return; } } // Open the source file srcfd = smbc_open(src.toSmbcUrl(), O_RDONLY, 0); if (srcfd < 0){ errNum = errno; } else { errNum = 0; } if(srcfd < 0) { if(errNum == EACCES) { error( KIO::ERR_ACCESS_DENIED, src.toDisplayString() ); } else { error( KIO::ERR_DOES_NOT_EXIST, src.toDisplayString() ); } return; } // Determine initial creation mode if(permissions != -1) { initialmode = permissions | S_IWUSR; } else { initialmode = 0 | S_IWUSR;//0666; } // Open the destination file dstflags = O_CREAT | O_TRUNC | O_WRONLY; if(!(flags & KIO::Overwrite)) { dstflags |= O_EXCL; } dstfd = smbc_open(dst.toSmbcUrl(), dstflags, initialmode); if (dstfd < 0){ errNum = errno; } else { errNum = 0; } if(dstfd < 0) { if(errNum == EACCES) { error(KIO::ERR_WRITE_ACCESS_DENIED, dst.toDisplayString()); } else { error(KIO::ERR_CANNOT_OPEN_FOR_READING, dst.toDisplayString()); } if(srcfd >= 0 ) { smbc_close(srcfd); } return; } // Perform copy while(1) { n = smbc_read(srcfd, buf, MAX_XFER_BUF_SIZE ); if(n > 0) { n = smbc_write(dstfd, buf, n); if(n == -1) { qCDebug(KIO_SMB) << "SMBSlave::copy copy now KIO::ERR_COULD_NOT_WRITE"; error( KIO::ERR_COULD_NOT_WRITE, dst.toDisplayString()); break; } processed_size += n; processedSize(processed_size); } else if(n == 0) { break; // finished } else { error( KIO::ERR_COULD_NOT_READ, src.toDisplayString()); break; } } // FINISHED: if(srcfd >= 0 ) { smbc_close(srcfd); } if(dstfd >= 0) { if(smbc_close(dstfd) == 0) { // TODO: set final permissions } else { error( KIO::ERR_COULD_NOT_WRITE, dst.toDisplayString()); return; } } finished(); } void SMBSlave::smbCopyGet(const QUrl& ksrc, const QUrl& kdst, int permissions, KIO::JobFlags flags) { qCDebug(KIO_SMB) << "src = " << ksrc << ", dest = " << kdst; // check if destination is ok ... const QString dstFile = kdst.toLocalFile(); const QFileInfo dstInfo (dstFile); if(dstInfo.exists()) { if(dstInfo.isDir()) { error (ERR_IS_DIRECTORY, kdst.toDisplayString()); return; } if(!(flags & KIO::Overwrite)) { error(ERR_FILE_ALREADY_EXIST, kdst.toDisplayString()); return; } } bool bResume = false; const QFileInfo partInfo (dstFile + QLatin1String(".part")); const bool bPartExists = partInfo.exists(); - const bool bMarkPartial = config()->readEntry("MarkPartial", true); + const bool bMarkPartial = configValue(QStringLiteral("MarkPartial"), true); if (bMarkPartial && bPartExists && partInfo.size() > 0) { if (partInfo.isDir()) { error(ERR_IS_DIRECTORY, partInfo.absoluteFilePath()); return; } bResume = canResume(partInfo.size()); } if (bPartExists && !bResume) // get rid of an unwanted ".part" file QFile::remove(partInfo.absoluteFilePath()); // open the output file... QFile::OpenMode mode; QString filename; if (bResume) { filename = partInfo.absoluteFilePath(); mode = QFile::WriteOnly | QFile::Append; } else { filename = (bMarkPartial ? partInfo.absoluteFilePath() : dstFile); mode = QFile::WriteOnly | QFile::Truncate; } QFile file (filename); if (!bResume) { QFile::Permissions perms; if (permissions == -1) { perms = QFile::ReadOwner | QFile::WriteOwner; } else { perms = KIO::convertPermissions(permissions | QFile::WriteOwner); } file.setPermissions(perms); } if (!file.open(mode)) { qCDebug(KIO_SMB) << "could not write to" << dstFile; switch (file.error()) { case QFile::OpenError: if (bResume) { error (ERR_CANNOT_RESUME, kdst.toDisplayString()); } else { error(ERR_CANNOT_OPEN_FOR_WRITING, kdst.toDisplayString()); } break; case QFile::PermissionsError: error(ERR_WRITE_ACCESS_DENIED, kdst.toDisplayString()); break; default: error(ERR_CANNOT_OPEN_FOR_WRITING, kdst.toDisplayString()); break; } return; } // setup the source urls const SMBUrl src(ksrc); // Obtain information about source int errNum = cache_stat (src, &st); if (errNum != 0) { if (errNum == EACCES) { error (KIO::ERR_ACCESS_DENIED, src.toDisplayString()); } else { error (KIO::ERR_DOES_NOT_EXIST, src.toDisplayString()); } return; } if (S_ISDIR( st.st_mode )) { error (KIO::ERR_IS_DIRECTORY, src.toDisplayString()); return; } totalSize(st.st_size); // Open the source file KIO::filesize_t processed_size = 0; int srcfd = smbc_open(src.toSmbcUrl(), O_RDONLY, 0); if (srcfd < 0){ errNum = errno; } else { errNum = 0; if (bResume) { qCDebug(KIO_SMB) << "seeking to size" << partInfo.size(); off_t offset = smbc_lseek(srcfd, partInfo.size(), SEEK_SET); if (offset == -1) { error(KIO::ERR_COULD_NOT_SEEK, src.toDisplayString()); smbc_close(srcfd); return; } else { processed_size += offset; } } } if (srcfd < 0) { if(errNum == EACCES) { error( KIO::ERR_ACCESS_DENIED, src.toDisplayString() ); } else { error( KIO::ERR_DOES_NOT_EXIST, src.toDisplayString() ); } return; } // Perform the copy char buf[MAX_XFER_BUF_SIZE]; bool isErr = false; while (1) { const ssize_t bytesRead = smbc_read(srcfd, buf, MAX_XFER_BUF_SIZE); if (bytesRead <= 0) { if (bytesRead < 0) { error( KIO::ERR_COULD_NOT_READ, src.toDisplayString()); isErr = true; } break; } const qint64 bytesWritten = file.write(buf, bytesRead); if (bytesWritten == -1) { qCDebug(KIO_SMB) << "copy now KIO::ERR_COULD_NOT_WRITE"; error( KIO::ERR_COULD_NOT_WRITE, kdst.toDisplayString()); isErr = true; break; } processed_size += bytesWritten; processedSize(processed_size); } // FINISHED smbc_close(srcfd); // Handle error condition. if (isErr) { const QString sPart = partInfo.absoluteFilePath(); if (bMarkPartial) { - const int size = config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); + const int size = configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE); if (partInfo.size() < size) { QFile::remove(sPart); } } return; } // Rename partial file to its original name. if (bMarkPartial) { const QString sPart = partInfo.absoluteFilePath(); // Remove old dest file if it exists.. if (dstInfo.exists()) { QFile::remove(dstFile); } if (!QFile::rename(sPart, dstFile)) { qCDebug(KIO_SMB) << "failed to rename" << sPart << "to" << dstFile; error(ERR_CANNOT_RENAME_PARTIAL, sPart); return; } } // Restore the mtime on the file. const QString mtimeStr = metaData("modified"); qCDebug(KIO_SMB) << "modified:" << mtimeStr; if (!mtimeStr.isEmpty()) { QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate); if (dt.isValid()) { struct utimbuf utbuf; utbuf.actime = QFileInfo(file).lastRead().toTime_t(); // access time, unchanged utbuf.modtime = dt.toTime_t(); // modification time utime(QFile::encodeName(dstFile).constData(), &utbuf); } } finished(); } void SMBSlave::smbCopyPut(const QUrl& ksrc, const QUrl& kdst, int permissions, KIO::JobFlags flags) { qCDebug(KIO_SMB) << "src = " << ksrc << ", dest = " << kdst; QFile srcFile (ksrc.toLocalFile()); const QFileInfo srcInfo (srcFile); if (srcInfo.exists()) { if (srcInfo.isDir()) { error(KIO::ERR_IS_DIRECTORY, ksrc.toDisplayString()); return; } } else { error(KIO::ERR_DOES_NOT_EXIST, ksrc.toDisplayString()); return; } if (!srcFile.open(QFile::ReadOnly)) { qCDebug(KIO_SMB) << "could not read from" << ksrc; switch (srcFile.error()) { case QFile::PermissionsError: error(KIO::ERR_WRITE_ACCESS_DENIED, ksrc.toDisplayString()); break; case QFile::OpenError: default: error(KIO::ERR_CANNOT_OPEN_FOR_READING, ksrc.toDisplayString()); break; } return; } totalSize(static_cast(srcInfo.size())); bool bResume = false; bool bPartExists = false; - const bool bMarkPartial = config()->readEntry("MarkPartial", true); + const bool bMarkPartial = configValue(QStringLiteral("MarkPartial"), true); const SMBUrl dstOrigUrl (kdst); if (bMarkPartial) { const int errNum = cache_stat(dstOrigUrl.partUrl(), &st); bPartExists = (errNum == 0); if (bPartExists) { if (!(flags & KIO::Overwrite) && !(flags & KIO::Resume)) { bResume = canResume(st.st_size); } else { bResume = (flags & KIO::Resume); } } } int dstfd = -1; int errNum = cache_stat(dstOrigUrl, &st); if (errNum == 0 && !(flags & KIO::Overwrite) && !(flags & KIO::Resume)) { if (S_ISDIR(st.st_mode)) { error( KIO::ERR_IS_DIRECTORY, dstOrigUrl.toDisplayString()); } else { error( KIO::ERR_FILE_ALREADY_EXIST, dstOrigUrl.toDisplayString()); } return; } KIO::filesize_t processed_size = 0; const SMBUrl dstUrl(bMarkPartial ? dstOrigUrl.partUrl() : dstOrigUrl); if (bResume) { // append if resuming qCDebug(KIO_SMB) << "resume" << dstUrl; dstfd = smbc_open(dstUrl.toSmbcUrl(), O_RDWR, 0 ); if (dstfd < 0) { errNum = errno; } else { const off_t offset = smbc_lseek(dstfd, 0, SEEK_END); if (offset == (off_t)-1) { error(KIO::ERR_COULD_NOT_SEEK, dstUrl.toDisplayString()); smbc_close(dstfd); return; } else { processed_size = offset; } } } else { mode_t mode; if (permissions == -1) { mode = 600; } else { mode = permissions | S_IRUSR | S_IWUSR; } qCDebug(KIO_SMB) << "NO resume" << dstUrl; dstfd = smbc_open(dstUrl.toSmbcUrl(), O_CREAT | O_TRUNC | O_WRONLY, mode); if (dstfd < 0) { errNum = errno; } } if (dstfd < 0) { if (errNum == EACCES) { qCDebug(KIO_SMB) << "access denied"; error( KIO::ERR_WRITE_ACCESS_DENIED, dstUrl.toDisplayString()); } else { qCDebug(KIO_SMB) << "can not open for writing"; error( KIO::ERR_CANNOT_OPEN_FOR_WRITING, dstUrl.toDisplayString()); } return; } bool isErr = false; if (processed_size == 0 || srcFile.seek(processed_size)) { // Perform the copy char buf[MAX_XFER_BUF_SIZE]; while (1) { const ssize_t bytesRead = srcFile.read(buf, MAX_XFER_BUF_SIZE); if (bytesRead <= 0) { if (bytesRead < 0) { error(KIO::ERR_COULD_NOT_READ, ksrc.toDisplayString()); isErr = true; } break; } const qint64 bytesWritten = smbc_write(dstfd, buf, bytesRead); if (bytesWritten == -1) { error(KIO::ERR_COULD_NOT_WRITE, kdst.toDisplayString()); isErr = true; break; } processed_size += bytesWritten; processedSize(processed_size); } } else { isErr = true; error(KIO::ERR_COULD_NOT_SEEK, ksrc.toDisplayString()); } // FINISHED if (smbc_close(dstfd) < 0) { qCDebug(KIO_SMB) << dstUrl << "could not write"; error( KIO::ERR_COULD_NOT_WRITE, dstUrl.toDisplayString()); return; } // Handle error condition. if (isErr) { if (bMarkPartial) { - const int size = config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); + const int size = configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE); const int errNum = cache_stat(dstUrl, &st); if (errNum == 0 && st.st_size < size) { smbc_unlink(dstUrl.toSmbcUrl()); } } return; } // Rename partial file to its original name. if (bMarkPartial) { smbc_unlink(dstOrigUrl.toSmbcUrl()); if (smbc_rename(dstUrl.toSmbcUrl(), dstOrigUrl.toSmbcUrl()) < 0) { qCDebug(KIO_SMB) << "failed to rename" << dstUrl << "to" << dstOrigUrl << "->" << strerror(errno); error(ERR_CANNOT_RENAME_PARTIAL, dstUrl.toDisplayString()); return; } } #ifdef HAVE_UTIME_H // set modification time const QString mtimeStr = metaData( "modified" ); if (!mtimeStr.isEmpty() ) { QDateTime dt = QDateTime::fromString( mtimeStr, Qt::ISODate ); if ( dt.isValid() ) { struct utimbuf utbuf; utbuf.actime = st.st_atime; // access time, unchanged utbuf.modtime = dt.toTime_t(); // modification time smbc_utime( dstUrl.toSmbcUrl(), &utbuf ); } } #endif // We have done our job => finish finished(); } //=========================================================================== void SMBSlave::del( const QUrl &kurl, bool isfile) { qCDebug(KIO_SMB) << kurl; m_current_url = kurl; int errNum = 0; int retVal = 0; if(isfile) { // Delete file qCDebug(KIO_SMB) << "Deleting file" << kurl; retVal = smbc_unlink(m_current_url.toSmbcUrl()); if ( retVal < 0 ){ errNum = errno; } else { errNum = 0; } } else { qCDebug(KIO_SMB) << "Deleting directory" << kurl; // Delete directory retVal = smbc_rmdir(m_current_url.toSmbcUrl()); if( retVal < 0 ) { errNum = errno; } else { errNum = 0; } } if( errNum != 0 ) { reportError(kurl, errNum); } else { finished(); } } //=========================================================================== void SMBSlave::mkdir( const QUrl &kurl, int permissions ) { qCDebug(KIO_SMB) << kurl; int errNum = 0; int retVal = 0; m_current_url = kurl; retVal = smbc_mkdir(m_current_url.toSmbcUrl(), 0777); if( retVal < 0 ){ errNum = errno; } else { errNum = 0; } if( retVal < 0 ) { if (errNum == EEXIST) { errNum = cache_stat(m_current_url, &st ); if (errNum == 0 && S_ISDIR(st.st_mode)) { error( KIO::ERR_DIR_ALREADY_EXIST, m_current_url.toDisplayString()); } else { error( KIO::ERR_FILE_ALREADY_EXIST, m_current_url.toDisplayString()); } } else { reportError(kurl, errNum); } qCDebug(KIO_SMB) << "exit with error " << kurl; } else // success { if(permissions != -1) { // TODO enable the following when complete //smbc_chmod( url.toSmbcUrl(), permissions ); } finished(); } } //=========================================================================== void SMBSlave::rename( const QUrl& ksrc, const QUrl& kdest, KIO::JobFlags flags ) { SMBUrl src; SMBUrl dst; int errNum = 0; int retVal = 0; qCDebug(KIO_SMB) << "old name = " << ksrc << ", new name = " << kdest; src = ksrc; dst = kdest; // Check to se if the destination exists qCDebug(KIO_SMB) << "stat dst"; errNum = cache_stat(dst, &st); if( errNum == 0 ) { if(S_ISDIR(st.st_mode)) { qCDebug(KIO_SMB) << "KIO::ERR_DIR_ALREADY_EXIST"; error( KIO::ERR_DIR_ALREADY_EXIST, dst.toDisplayString()); return; } if(!(flags & KIO::Overwrite)) { qCDebug(KIO_SMB) << "KIO::ERR_FILE_ALREADY_EXIST"; error( KIO::ERR_FILE_ALREADY_EXIST, dst.toDisplayString()); return; } } qCDebug(KIO_SMB ) << "smbc_rename " << src.toSmbcUrl() << " " << dst.toSmbcUrl(); retVal = smbc_rename(src.toSmbcUrl(), dst.toSmbcUrl()); if( retVal < 0 ){ errNum = errno; } else { errNum = 0; } if( retVal < 0 ) { qCDebug(KIO_SMB ) << "failed "; switch(errNum) { case ENOENT: errNum = cache_stat(src, &st); if( errNum != 0 ) { if(errNum == EACCES) { qCDebug(KIO_SMB) << "KIO::ERR_ACCESS_DENIED"; error(KIO::ERR_ACCESS_DENIED, src.toDisplayString()); } else { qCDebug(KIO_SMB) << "KIO::ERR_DOES_NOT_EXIST"; error(KIO::ERR_DOES_NOT_EXIST, src.toDisplayString()); } } break; case EACCES: case EPERM: qCDebug(KIO_SMB) << "KIO::ERR_ACCESS_DENIED"; error( KIO::ERR_ACCESS_DENIED, dst.toDisplayString() ); break; default: qCDebug(KIO_SMB) << "KIO::ERR_CANNOT_RENAME"; error( KIO::ERR_CANNOT_RENAME, src.toDisplayString() ); } qCDebug(KIO_SMB) << "exit with error"; return; } qCDebug(KIO_SMB ) << "everything fine\n"; finished(); }