diff --git a/smb/CMakeLists.txt b/smb/CMakeLists.txt index 132dc4af..3504f299 100644 --- a/smb/CMakeLists.txt +++ b/smb/CMakeLists.txt @@ -1,91 +1,92 @@ option(BUILD_KDSoapWSDiscoveryClient "Automatically build WSD client if a system one isn't found." ON) find_package(KDSoapWSDiscoveryClient QUIET) set(INTERNAL_WSDCLIENT ${BUILD_KDSoapWSDiscoveryClient}) if(KDSoapWSDiscoveryClient_FOUND) set(INTERNAL_WSDCLIENT OFF) endif() if(INTERNAL_WSDCLIENT) # Special internal version, mangled to be a STATIC lib. # This is only useful and necessary until the library has # its API finalized and gotten a stable release. # Until then when using it as external library you should try # to use a snapshot that matches the internal copy's git ref. # Currently at: 14287e92e80a77aa4c0adee2871e6b87c9c3055e add_subdirectory(kdsoap-ws-discovery-client) endif() add_feature_info("Internal KDSoapWSDiscoveryClient" INTERNAL_WSDCLIENT "Building using internal client because a system-provided version could not be found.") add_feature_info("SMB DNS-SD Discovery" HAVE_KDNSSD_WITH_SIGNAL_RACE_PROTECTION "Discover SMB hosts via DNS-SD/Avahi/Bonjour. KF5DNSSD >= 5.54 is required to support this.") find_package(Threads REQUIRED) add_definitions(-DTRANSLATION_DOMAIN=\"kio5_smb\") include(CheckIncludeFile) include(CheckSymbolExists) include(CMakePushCheckState) set(CMAKE_AUTOMAKE ON) cmake_push_check_state() list(APPEND CMAKE_REQUIRED_INCLUDES ${SAMBA_INCLUDE_DIR}) list(APPEND CMAKE_REQUIRED_LIBRARIES ${SAMBA_LIBRARIES}) check_symbol_exists(smbc_readdirplus2 "libsmbclient.h" HAVE_READDIRPLUS2) cmake_pop_check_state() check_include_file(utime.h HAVE_UTIME_H) configure_file(config-smb.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-smb.h) set(kio_smb_PART_SRCS kio_smb.cpp kio_smb_auth.cpp kio_smb_browse.cpp kio_smb_config.cpp kio_smb_dir.cpp kio_smb_file.cpp smburl.cpp kio_smb_mount.cpp wsdiscoverer.cpp dnssddiscoverer.cpp discovery.cpp transfer.cpp + smbcdiscoverer.cpp ) ecm_qt_declare_logging_category(kio_smb_PART_SRCS HEADER smb-logsettings.h IDENTIFIER KIO_SMB_LOG CATEGORY_NAME log_kio_smb) include_directories(${SAMBA_INCLUDE_DIR}) # Intermediate static lib target for reuse in testing. add_library(kio_smb_static STATIC ${kio_smb_PART_SRCS}) target_include_directories(kio_smb_static PUBLIC "$" ) target_link_libraries(kio_smb_static KF5::KIOCore KF5::I18n ${SAMBA_LIBRARIES} Qt5::Network KF5::DNSSD KDSoap::WSDiscoveryClient Threads::Threads # std::async ) # Final plugin target. add_library(kio_smb MODULE main.cpp) target_link_libraries(kio_smb kio_smb_static ) set_target_properties(kio_smb PROPERTIES OUTPUT_NAME "smb") set_target_properties(kio_smb PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kio") install(TARGETS kio_smb DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kio) install(FILES smb-network.desktop DESTINATION ${KDE_INSTALL_DATADIR}/konqueror/dirtree/remote) install(FILES smb-network.desktop DESTINATION ${KDE_INSTALL_DATADIR}/remoteview) add_subdirectory(autotests) diff --git a/smb/kio_smb.h b/smb/kio_smb.h index 5158b2b7..a610c3ee 100644 --- a/smb/kio_smb.h +++ b/smb/kio_smb.h @@ -1,296 +1,297 @@ ///////////////////////////////////////////////////////////////////////////// // // Project: SMB kioslave for KDE // // File: kio_smb.h // // Abstract: The main kio slave class declaration. For convenience, // in concurrent development, the implementation for this class // is separated into several .cpp files -- the file containing // the implementation should be noted in the comments for each // member function. // // 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 https://www.gnu.org/copyleft/gpl.html // ///////////////////////////////////////////////////////////////////////////// #ifndef KIO_SMB_H_INCLUDED #define KIO_SMB_H_INCLUDED #include #include "smb-logsettings.h" //-------------- // KDE includes //-------------- #include #include //----------------------------- // Standard C library includes //----------------------------- #include #include #include #include #include #include #include #include #include #include //----------------------------- // Qt includes //----------------------------- #include #include #include //------------------------------- // Samba client library includes //------------------------------- extern "C" { #include } //--------------------------- // kio_smb internal includes //--------------------------- #include "smburl.h" using namespace KIO; class SMBSlave : public QObject, public KIO::SlaveBase { Q_OBJECT + friend class SMBCDiscoverer; private: class SMBError { public: int kioErrorId; QString errorString; }; //--------------------------------------------------------------------- // please make sure your private data does not duplicate existing data //--------------------------------------------------------------------- bool m_initialized_smbc; /** * From Controlcenter */ QString m_default_user; QString m_default_workgroup = QStringLiteral("WORKGROUP"); // overwritten with value from smbc QString m_default_password; QString m_default_encoding; /** * we store the current url, it's needed for * callback authorization method */ SMBUrl m_current_url; /** * From Controlcenter, show SHARE$ or not */ // bool m_showHiddenShares; //currently unused, Alex /** * libsmbclient need global variables to store in, * else it crashes on exit next method after use cache_stat, * looks like gcc (C/C++) failure */ struct stat st { }; protected: //--------------------------------------------- // Authentication functions (kio_smb_auth.cpp) //--------------------------------------------- // (please prefix functions with auth) /** * Description : Initializes the libsmbclient * Return : true on success false with errno set on error */ bool auth_initialize_smbc(); int checkPassword(SMBUrl &url); //--------------------------------------------- // Cache functions (kio_smb_auth.cpp) //--------------------------------------------- // Stat methods //----------------------------------------- // Browsing functions (kio_smb_browse.cpp) //----------------------------------------- // (please prefix functions with browse) /** * Description : Return a stat of given SMBUrl. Calls cache_stat and * pack it in UDSEntry. UDSEntry will not be cleared * Parameter : SMBUrl the url to stat * Return : cache_stat() return code */ int browse_stat_path(const SMBUrl &url, UDSEntry &udsentry); /** * Description : call smbc_stat and return stats of the url * Parameter : SMBUrl the url to stat * Return : stat* of the url * Note : it has some problems with stat in method, looks like * something leave(or removed) on the stack. If your * method segfault on returning try to change the stat* * variable */ int cache_stat(const SMBUrl &url, struct stat *st); //--------------------------------------------- // Configuration functions (kio_smb_config.cpp) //--------------------------------------------- // (please prefix functions with config) //--------------------------------------- // Directory functions (kio_smb_dir.cpp) //--------------------------------------- // (please prefix functions with dir) //-------------------------------------- // File IO functions (kio_smb_file.cpp) //-------------------------------------- // (please prefix functions with file) //---------------------------- // Misc functions (this file) //---------------------------- /** * Description : correct a given URL * valid URL's are * * smb://[[domain;]user[:password]@]server[:port][/share[/path[/file]]] * smb:/[[domain;]user[:password]@][group/[server[/share[/path[/file]]]]] * domain = workgroup(domain) of the user * user = username * password = password of useraccount * group = workgroup(domain) of server * server = host to connect * share = a share of the server (host) * path = a path of the share * Parameter : QUrl the url to check * Return : new QUrl if it is corrected. else the same QUrl */ QUrl checkURL(const QUrl &kurl) const; void reportError(const SMBUrl &url, const int errNum); void reportWarning(const SMBUrl &url, const int errNum); public: //----------------------------------------------------------------------- // smbclient authentication callback (note that this is called by the // global ::auth_smbc_get_data() call. void auth_smbc_get_data(const char *server, const char *share, char *workgroup, int wgmaxlen, char *username, int unmaxlen, char *password, int pwmaxlen); //----------------------------------------------------------------------- // Overwritten functions from the base class that define the operation of // this slave. (See the base class headerfile slavebase.h for more // details) //----------------------------------------------------------------------- // Functions overwritten in kio_smb.cpp SMBSlave(const QByteArray &pool, const QByteArray &app); ~SMBSlave() override; // Functions overwritten in kio_smb_browse.cpp void listDir(const QUrl &url) override; void stat(const QUrl &url) override; // Functions overwritten in kio_smb_config.cpp void reparseConfiguration() override; // Functions overwritten in kio_smb_dir.cpp void copy(const QUrl &src, const QUrl &dst, int permissions, KIO::JobFlags flags) override; void del(const QUrl &kurl, bool isfile) override; void mkdir(const QUrl &kurl, int permissions) override; void rename(const QUrl &src, const QUrl &dest, KIO::JobFlags flags) override; // Functions overwritten in kio_smb_file.cpp void get(const QUrl &kurl) override; void put(const QUrl &kurl, int permissions, KIO::JobFlags flags) override; void open(const QUrl &kurl, QIODevice::OpenMode mode) override; void read(KIO::filesize_t bytesRequested) override; void write(const QByteArray &fileData) override; void seek(KIO::filesize_t offset) override; void truncate(KIO::filesize_t length); void close() override; // Functions not implemented (yet) // virtual void setHost(const QString& host, int port, const QString& user, const QString& pass); // virtual void openConnection(); // virtual void closeConnection(); // virtual void slave_status(); void special(const QByteArray &) override; protected: void virtual_hook(int id, void *data) override; private: SMBError errnumToKioError(const SMBUrl &url, const int errNum); void smbCopy(const QUrl &src, const QUrl &dst, int permissions, KIO::JobFlags flags); void smbCopyGet(const QUrl &ksrc, const QUrl &kdst, int permissions, KIO::JobFlags flags); void smbCopyPut(const QUrl &ksrc, const QUrl &kdst, int permissions, KIO::JobFlags flags); bool workaroundEEXIST(const int errNum) const; int statToUDSEntry(const QUrl &url, const struct stat &st, KIO::UDSEntry &udsentry); void fileSystemFreeSpace(const QUrl &url); /** * Used in open(), read(), write(), and close() * FIXME Placing these in the private section above causes m_openUrl = kurl * to fail in SMBSlave::open. Need to find out why this is. */ int m_openFd; SMBUrl m_openUrl; const bool m_enableEEXISTWorkaround; /* Enables a workaround for some broken libsmbclient versions */ // Close without calling finish(). Use this to close after error. void closeWithoutFinish(); }; //========================================================================== // the global libsmbclient authentication callback function extern "C" { void auth_smbc_get_data(SMBCCTX *context, const char *server, const char *share, char *workgroup, int wgmaxlen, char *username, int unmaxlen, char *password, int pwmaxlen); } //=========================================================================== // Main slave entrypoint (see kio_smb.cpp) extern "C" { int kdemain(int argc, char **argv); } #endif //#endif KIO_SMB_H_INCLUDED diff --git a/smb/kio_smb_browse.cpp b/smb/kio_smb_browse.cpp index e8594007..62fb26d1 100644 --- a/smb/kio_smb_browse.cpp +++ b/smb/kio_smb_browse.cpp @@ -1,724 +1,580 @@ ///////////////////////////////////////////////////////////////////////////// // // Project: SMB kioslave for KDE2 // // File: kio_smb_browse.cpp // // Abstract: member function implementations for SMBSlave that deal with // SMB browsing // // Author(s): Matthew Peterson // //--------------------------------------------------------------------------- // // Copyright (c) 2000 Caldera Systems, Inc. // Copyright (c) 2018-2020 Harald Sitter // // 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 https://www.gnu.org/copyleft/gpl.html // ///////////////////////////////////////////////////////////////////////////// #include "kio_smb.h" #include "smburl.h" #include #include #include #include #include #include -#include #include #include #include "dnssddiscoverer.h" +#include "smbcdiscoverer.h" #include "wsdiscoverer.h" #include using namespace KIO; int SMBSlave::cache_stat(const SMBUrl &url, struct stat *st) { int cacheStatErr; int result = smbc_stat(url.toSmbcUrl(), st); if (result == 0) { cacheStatErr = 0; } else { cacheStatErr = errno; } qCDebug(KIO_SMB_LOG) << "size " << static_cast(st->st_size); return cacheStatErr; } int SMBSlave::browse_stat_path(const SMBUrl &url, UDSEntry &udsentry) { int cacheStatErr = cache_stat(url, &st); if (cacheStatErr == 0) { return statToUDSEntry(url, st, udsentry); } return cacheStatErr; } int SMBSlave::statToUDSEntry(const QUrl &url, const struct stat &st, KIO::UDSEntry &udsentry) { if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) { qCDebug(KIO_SMB_LOG) << "mode: "<< st.st_mode; warning(i18n("%1:\n" "Unknown file type, neither directory or file.", url.toDisplayString())); return EINVAL; } if (!S_ISDIR(st.st_mode)) { // Awkwardly documented at // https://www.samba.org/samba/docs/using_samba/ch08.html // libsmb_stat.c assigns special meaning to +x permissions // (obviously only on files, all dirs are +x so this hacky representation // wouldn't work!): // - S_IXUSR = DOS archive: This file has been touched since the last DOS backup was performed on it. // - S_IXGRP = DOS system: This file has a specific purpose required by the operating system. // - S_IXOTH = DOS hidden: This file has been marked to be invisible to the user, unless the operating system is explicitly set to show it. // Only hiding has backing through KIO right now. if (st.st_mode & S_IXOTH) { // DOS hidden udsentry.fastInsert(KIO::UDSEntry::UDS_HIDDEN, true); } } // UID and GID **must** not be mapped. The values returned by libsmbclient are // simply the getuid/getgid of the process. They mean absolutely nothing. // Also see libsmb_stat.c. // Related: https://bugs.kde.org/show_bug.cgi?id=212801 // POSIX Access mode must not be mapped either! // It's meaningless for smb shares and downright disadvantagous. // The mode attributes outside the ones used and document above are // useless. The only one actively set is readonlyness. // // BUT the READONLY attribute does nothing on NT systems: // https://support.microsoft.com/en-us/help/326549/you-cannot-view-or-change-the-read-only-or-the-system-attributes-of-fo // The Read-only and System attributes is only used by Windows Explorer to determine // whether the folder is a special folder, such as a system folder that has its view // customized by Windows (for example, My Documents, Favorites, Fonts, Downloaded Program Files), // or a folder that you customized by using the Customize tab of the folder's Properties dialog box. // // As such respecting it on a KIO level is actually wrong as it doesn't indicate actual // readonlyness since the 90s and causes us to show readonly UI states when in fact // the directory is perfectly writable. // https://bugs.kde.org/show_bug.cgi?id=414482 // // Should we ever want to parse desktop.ini like we do .directory we'd only want to when a // dir is readonly as per the above microsoft support article. // Also see: // https://docs.microsoft.com/en-us/windows/win32/shell/how-to-customize-folders-with-desktop-ini udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, st.st_mode & S_IFMT); udsentry.fastInsert(KIO::UDSEntry::UDS_SIZE, st.st_size); udsentry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, st.st_mtime); udsentry.fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, st.st_atime); // No, st_ctime is not UDS_CREATION_TIME... return 0; } void SMBSlave::stat(const QUrl &kurl) { qCDebug(KIO_SMB_LOG) << kurl; // make a valid URL QUrl url = checkURL(kurl); // if URL is not valid we have to redirect to correct URL if (url != kurl) { qCDebug(KIO_SMB_LOG) << "redirection " << url; redirection(url); finished(); return; } m_current_url = url; UDSEntry udsentry; // Set name udsentry.fastInsert(KIO::UDSEntry::UDS_NAME, kurl.fileName()); switch (m_current_url.getType()) { case SMBURLTYPE_UNKNOWN: error(ERR_MALFORMED_URL, url.toDisplayString()); return; case SMBURLTYPE_ENTIRE_NETWORK: case SMBURLTYPE_WORKGROUP_OR_SERVER: udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); statEntry(udsentry); finished(); return; case SMBURLTYPE_SHARE_OR_PATH: { int ret = browse_stat_path(m_current_url, udsentry); if (ret == EPERM || ret == EACCES || workaroundEEXIST(ret)) { SMBUrl smbUrl(url); const int passwordError = checkPassword(smbUrl); if (passwordError == KJob::NoError) { redirection(smbUrl); finished(); } else if (passwordError == KIO::ERR_USER_CANCELED) { reportError(url, ret); } else { error(passwordError, url.toString()); } return; } else if (ret != 0) { qCDebug(KIO_SMB_LOG) << "stat() error" << ret << url; reportError(url, ret); return; } statEntry(udsentry); finished(); return; } } qCDebug(KIO_SMB_LOG) << "UNKNOWN " << url; finished(); } // TODO: complete checking <-- what does that even mean? // TODO: why is this not part of SMBUrl or at the very least URL validation should // be 100% shared between this and SMBUrl. Notably SMBUrl has code that looks // to do a similar thing but is much less complete. QUrl SMBSlave::checkURL(const QUrl &kurl_) const { qCDebug(KIO_SMB_LOG) << "checkURL " << kurl_; QUrl kurl(kurl_); // We treat cifs as an alias but need to translate it to smb. // https://bugs.kde.org/show_bug.cgi?id=327295 // It's not IANA registered and also libsmbc internally expects // smb URIs so we do very broadly coerce cifs to smb. // Also see SMBUrl. if (kurl.scheme() == "cifs") { kurl.setScheme("smb"); } // For WS-Discovered hosts we assume they'll respond to DNSSD names on .local but // they may only respond to llmnr/netbios names. Transparently fall back. // // Desktop linuxes tend to have llmnr disabled, by contrast win10 has dnssd enabled, // so chances are we'll be able to find a host.local more reliably. // Attempt to resolve foo.local natively, if that works use it, otherwise default to // the presumed LLMNR/netbios name found during discovery. // This should then yield reasonable results with any combination of WSD/DNSSD/LLMNR support. // - WSD+Avahi (on linux) // - WSD+Win10 (i.e. dnssd + llmnr) // - WSD+CrappyNAS (e.g. llmnr or netbios only) // // NB: smbc has no way to resolve a name without also triggering auth etc.: we must // rely on the system's ability to resolve DNSSD for this check. const QLatin1String wsdSuffix(".kio-discovery-wsd"); if (kurl.host().endsWith(wsdSuffix)) { QString host = kurl.host(); host.chop(wsdSuffix.size()); const QString dnssd(host + ".local"); auto dnssdHost = KDNSSD::ServiceBrowser::resolveHostName(dnssd); if (!dnssdHost.isNull()) { qCDebug(KIO_SMB_LOG) << "Resolved DNSSD name:" << dnssd; host = dnssd; } else { qCDebug(KIO_SMB_LOG) << "Failed to resolve DNSSD name:" << dnssd; qCDebug(KIO_SMB_LOG) << "Falling back to LLMNR name:" << host; } kurl.setHost(host); } QString surl = kurl.url(); // transform any links in the form smb:/ into smb:// if (surl.startsWith(QLatin1String("smb:/"))) { if (surl.length() == 5) { return QUrl("smb://"); } if (surl.at(5) != '/') { surl = "smb://" + surl.mid(5); qCDebug(KIO_SMB_LOG) << "checkURL return1 " << surl << " " << QUrl(surl); return QUrl(surl); } } if (surl == QLatin1String("smb://")) { return kurl; // unchanged } // smb:// normally have no userinfo // we must redirect ourself to remove the username and password if (surl.contains('@') && !surl.contains("smb://")) { QUrl url(kurl); url.setPath('/' + kurl.url().right(kurl.url().length() - kurl.url().indexOf('@') - 1)); QString userinfo = kurl.url().mid(5, kurl.url().indexOf('@') - 5); if (userinfo.contains(':')) { url.setUserName(userinfo.left(userinfo.indexOf(':'))); url.setPassword(userinfo.right(userinfo.length() - userinfo.indexOf(':') - 1)); } else { url.setUserName(userinfo); } qCDebug(KIO_SMB_LOG) << "checkURL return2 " << url; return url; } // if there's a valid host, don't have an empty path QUrl url(kurl); if (url.path().isEmpty()) url.setPath("/"); qCDebug(KIO_SMB_LOG) << "checkURL return3 " << url; return url; } SMBSlave::SMBError SMBSlave::errnumToKioError(const SMBUrl &url, const int errNum) { qCDebug(KIO_SMB_LOG) << "errNum" << errNum; switch (errNum) { case ENOENT: if (url.getType() == SMBURLTYPE_ENTIRE_NETWORK) return SMBError {ERR_SLAVE_DEFINED, i18n("Unable to find any workgroups in your local network. This might be caused by an enabled firewall.")}; else return SMBError {ERR_DOES_NOT_EXIST, url.toDisplayString()}; #ifdef ENOMEDIUM case ENOMEDIUM: return SMBError {ERR_SLAVE_DEFINED, i18n("No media in device for %1", url.toDisplayString())}; #endif #ifdef EHOSTDOWN case EHOSTDOWN: #endif case ECONNREFUSED: return SMBError {ERR_SLAVE_DEFINED, i18n("Could not connect to host for %1", url.toDisplayString())}; case ENOTDIR: return SMBError {ERR_CANNOT_ENTER_DIRECTORY, url.toDisplayString()}; case EFAULT: case EINVAL: return SMBError {ERR_DOES_NOT_EXIST, url.toDisplayString()}; case EPERM: case EACCES: return SMBError {ERR_ACCESS_DENIED, url.toDisplayString()}; case EIO: case ENETUNREACH: if (url.getType() == SMBURLTYPE_ENTIRE_NETWORK || url.getType() == SMBURLTYPE_WORKGROUP_OR_SERVER) return SMBError {ERR_SLAVE_DEFINED, i18n("Error while connecting to server responsible for %1", url.toDisplayString())}; else return SMBError {ERR_CONNECTION_BROKEN, url.toDisplayString()}; case ENOMEM: return SMBError {ERR_OUT_OF_MEMORY, url.toDisplayString()}; case ENODEV: return SMBError {ERR_SLAVE_DEFINED, i18n("Share could not be found on given server")}; case EBADF: return SMBError {ERR_INTERNAL, i18n("Bad file descriptor")}; case ETIMEDOUT: return SMBError {ERR_SERVER_TIMEOUT, url.host()}; case ENOTEMPTY: return SMBError {ERR_CANNOT_RMDIR, url.toDisplayString()}; #ifdef ENOTUNIQ case ENOTUNIQ: return SMBError {ERR_SLAVE_DEFINED, i18n("The given name could not be resolved to a unique server. " "Make sure your network is setup without any name conflicts " "between names used by Windows and by UNIX name resolution.")}; #endif case ECONNABORTED: return SMBError {ERR_CONNECTION_BROKEN, url.host()}; case EHOSTUNREACH: return SMBError {ERR_CANNOT_CONNECT, i18nc("@info:status smb failed to reach the server (e.g. server offline or network failure). %1 is an ip address or hostname", "%1: Host unreachable", url.host())}; case 0: // success return SMBError {ERR_INTERNAL, i18n("libsmbclient reported an error, but did not specify " "what the problem is. This might indicate a severe problem " "with your network - but also might indicate a problem with " "libsmbclient.\n" "If you want to help us, please provide a tcpdump of the " "network interface while you try to browse (be aware that " "it might contain private data, so do not post it if you are " "unsure about that - you can send it privately to the developers " "if they ask for it)")}; default: return SMBError { ERR_INTERNAL, i18nc("%1 is an error number, %2 either a pretty string or the number", "Unknown error condition: [%1] %2", QString::number(errNum), QString::fromLocal8Bit(strerror(errNum))) }; } } void SMBSlave::reportError(const SMBUrl &url, const int errNum) { const SMBError smbErr = errnumToKioError(url, errNum); error(smbErr.kioErrorId, smbErr.errorString); } void SMBSlave::reportWarning(const SMBUrl &url, const int errNum) { const SMBError smbErr = errnumToKioError(url, errNum); const QString errorString = buildErrorString(smbErr.kioErrorId, smbErr.errorString); warning(xi18n("Error occurred while trying to access %1%2", url.url(), errorString)); } void SMBSlave::listDir(const QUrl &kurl) { qCDebug(KIO_SMB_LOG) << kurl; - int errNum = 0; // check (correct) URL QUrl url = checkURL(kurl); // if URL is not valid we have to redirect to correct URL if (url != kurl) { redirection(url); finished(); return; } m_current_url = kurl; - struct smbc_dirent *dirp = nullptr; - UDSEntry udsentry; - bool dir_is_root = true; + QEventLoop e; - int dirfd = smbc_opendir(m_current_url.toSmbcUrl()); - if (dirfd > 0) { - errNum = 0; - } else { - errNum = errno; - } - - qCDebug(KIO_SMB_LOG) << "open " << m_current_url.toSmbcUrl() - << "url-type:" << m_current_url.getType() - << "dirfd:" << dirfd - << "errNum:" << errNum; - if (dirfd >= 0) { -#ifdef HAVE_READDIRPLUS2 - // readdirplus2 improves performance by giving us a stat without separate call (Samba>=4.12) - while (const struct libsmb_file_info *fileInfo = smbc_readdirplus2(dirfd, &st)) { - const QString name = QString::fromUtf8(fileInfo->name); - if (name == ".") { - continue; - } else if (name == "..") { - dir_is_root = false; - continue; - } - udsentry.fastInsert(KIO::UDSEntry::UDS_NAME, name); - - m_current_url.addPath(name); - statToUDSEntry(m_current_url, st, udsentry); // won't produce useful error - listEntry(udsentry); - m_current_url.cdUp(); - - udsentry.clear(); - } -#endif // HAVE_READDIRPLUS2 - - uint direntCount = 0; - do { - qCDebug(KIO_SMB_LOG) << "smbc_readdir "; - dirp = smbc_readdir(dirfd); - if (dirp == nullptr) - break; - - ++direntCount; - - // Set name - QString udsName; - const QString dirpName = QString::fromUtf8(dirp->name); - // We cannot trust dirp->commentlen has it might be with or without the NUL character - // See KDE bug #111430 and Samba bug #3030 - const QString comment = QString::fromUtf8(dirp->comment); - if (dirp->smbc_type == SMBC_SERVER || dirp->smbc_type == SMBC_WORKGROUP) { - udsName = dirpName.toLower(); - udsName[0] = dirpName.at(0).toUpper(); - if (!comment.isEmpty() && dirp->smbc_type == SMBC_SERVER) - udsName += " (" + comment + ')'; - } else - udsName = dirpName; - - qCDebug(KIO_SMB_LOG) << "dirp->name " << dirp->name << " " << dirpName << " '" << comment << "'" << " " << dirp->smbc_type; - - udsentry.fastInsert(KIO::UDSEntry::UDS_NAME, udsName); - udsentry.fastInsert(KIO::UDSEntry::UDS_COMMENT, QString::fromUtf8(dirp->comment)); - - // Mark all administrative shares, e.g ADMIN$, as hidden. #197903 - if (dirpName.endsWith(QLatin1Char('$'))) { - // qCDebug(KIO_SMB_LOG) << dirpName << "marked as hidden"; - udsentry.fastInsert(KIO::UDSEntry::UDS_HIDDEN, 1); - } + UDSEntryList list; + QStringList discoveredNames; - if (udsName == ".") { - // Skip the "." entry - // Mind the way m_current_url is handled in the loop - } else if (udsName == "..") { - dir_is_root = false; - // fprintf(stderr,"----------- hide: -%s-\n",dirp->name); - // do nothing and hide the hidden shares -#if !defined(HAVE_READDIRPLUS2) - } else if (dirp->smbc_type == SMBC_FILE || dirp->smbc_type == SMBC_DIR) { - // Set stat information - m_current_url.addPath(dirpName); - const int statErr = browse_stat_path(m_current_url, udsentry); - if (statErr) { - if (statErr == ENOENT || statErr == ENOTDIR) { - reportWarning(m_current_url, statErr); - } - } else { - // Call base class to list entry - listEntry(udsentry); - } - m_current_url.cdUp(); -#endif // HAVE_READDIRPLUS2 - } else if (dirp->smbc_type == SMBC_SERVER || dirp->smbc_type == SMBC_FILE_SHARE) { - // Set type - udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); - - if (dirp->smbc_type == SMBC_SERVER) { - udsentry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)); - - // QString workgroup = m_current_url.host().toUpper(); - QUrl u("smb://"); - u.setHost(dirpName); - - // when libsmbclient knows - // u = QString("smb://%1?WORKGROUP=%2").arg(dirpName).arg(workgroup.toUpper()); - qCDebug(KIO_SMB_LOG) << "list item " << u; - udsentry.fastInsert(KIO::UDSEntry::UDS_URL, u.url()); - - udsentry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("application/x-smb-server")); - } else - udsentry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)); - - // Call base class to list entry - listEntry(udsentry); - } else if (dirp->smbc_type == SMBC_WORKGROUP) { - // Set type - udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); - - // Set permissions - udsentry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH)); - - udsentry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("application/x-smb-workgroup")); - - // QString workgroup = m_current_url.host().toUpper(); - QUrl u("smb://"); - u.setHost(dirpName); - if (!u.isValid()) { - // In the event that the workgroup contains bad characters, put it in a query instead. - // This is transparently handled by SMBUrl when we get this as input again. - // Also see documentation there. - // https://bugs.kde.org/show_bug.cgi?id=204423 - u.setHost(QString()); - QUrlQuery q; - q.addQueryItem("kio-workgroup", dirpName); - u.setQuery(q); - } - udsentry.fastInsert(KIO::UDSEntry::UDS_URL, u.url()); - - // Call base class to list entry - listEntry(udsentry); - } else { - qCDebug(KIO_SMB_LOG) << "SMBC_UNKNOWN :" << dirpName; - // TODO: we don't handle SMBC_IPC_SHARE, SMBC_PRINTER_SHARE - // SMBC_LINK, SMBC_COMMS_SHARE - // SlaveBase::error(ERR_INTERNAL, TEXT_UNSUPPORTED_FILE_TYPE); - // continue; - } - udsentry.clear(); - } while (dirp); // checked already in the head - - // clean up - smbc_closedir(dirfd); - } + const auto flushEntries = [this, &list]() { + if (list.isEmpty()) { + return; + } + listEntries(list); + list.clear(); + }; - // Run service discovery if the path is root. This augments - // "native" results from libsmbclient. - // Also, should native resolution have encountered an error it will not matter. - auto normalizedUrl = url.adjusted(QUrl::NormalizePathSegments); - if (normalizedUrl.path().isEmpty()) { - qCDebug(KIO_SMB_LOG) << "Trying modern discovery (dnssd/wsdiscovery)"; + const auto quitLoop = [&e, &flushEntries]() { + flushEntries(); + e.quit(); + }; - QEventLoop e; + // Since slavebase has no eventloop it wont publish results + // on a timer, since we do not know how long our discovery + // will take this is super meh because we may appear + // stuck for a while. Implement our own listing system + // based on QTimer to mitigate. + QTimer sendTimer; + sendTimer.setInterval(300); + connect(&sendTimer, &QTimer::timeout, this, flushEntries); + sendTimer.start(); - UDSEntryList list; - QStringList discoveredNames; + DNSSDDiscoverer dnssd; + WSDiscoverer wsd; + SMBCDiscoverer smbc(m_current_url, &e, this); - const auto flushEntries = [this, &list]() { - if (list.isEmpty()) { - return; - } - listEntries(list); - list.clear(); - }; + const QList discoverers {&dnssd, &wsd, &smbc}; - const auto quitLoop = [&e, &flushEntries]() { - flushEntries(); - e.quit(); - }; + auto appendDiscovery = [&](const Discovery::Ptr &discovery) { + if (discoveredNames.contains(discovery->udsName())) { + return; + } + discoveredNames << discovery->udsName(); + list.append(discovery->toEntry()); + }; - // Since slavebase has no eventloop it wont publish results - // on a timer, since we do not know how long our discovery - // will take this is super meh because we may appear - // stuck for a while. Implement our own listing system - // based on QTimer to mitigate. - QTimer sendTimer; - sendTimer.setInterval(300); - connect(&sendTimer, &QTimer::timeout, this, flushEntries); - sendTimer.start(); + auto maybeFinished = [&] { // finishes if all discoveries finished + bool allFinished = true; + for (auto discoverer : discoverers) { + allFinished = allFinished && discoverer->isFinished(); + } + if (allFinished) { + quitLoop(); + } + }; - DNSSDDiscoverer d; - WSDiscoverer w; + connect(&smbc, &SMBCDiscoverer::newDiscovery, this, appendDiscovery); + connect(&smbc, &SMBCDiscoverer::finished, this, maybeFinished); - const QList discoverers {&d, &w}; + // Run service discovery if the path is root. This augments + // "native" results from libsmbclient. + // Also, should native resolution have encountered an error it will not matter. + if (m_current_url.getType() == SMBURLTYPE_ENTIRE_NETWORK) { + qCDebug(KIO_SMB_LOG) << "Adding modern discovery (dnssd/wsdiscovery)"; - auto appendDiscovery = [&](const Discovery::Ptr &discovery) { - if (discoveredNames.contains(discovery->udsName())) { - return; - } - discoveredNames << discovery->udsName(); - list.append(discovery->toEntry()); - }; + connect(&dnssd, &DNSSDDiscoverer::newDiscovery, this, appendDiscovery); + connect(&wsd, &WSDiscoverer::newDiscovery, this, appendDiscovery); - auto maybeFinished = [&] { // finishes if all discoveries finished - bool allFinished = true; - for (auto discoverer : discoverers) { - allFinished = allFinished && discoverer->isFinished(); - } - if (allFinished) { - quitLoop(); - } - }; + connect(&dnssd, &DNSSDDiscoverer::finished, this, maybeFinished); + connect(&wsd, &WSDiscoverer::finished, this, maybeFinished); - connect(&d, &DNSSDDiscoverer::newDiscovery, this, appendDiscovery); - connect(&w, &WSDiscoverer::newDiscovery, this, appendDiscovery); + dnssd.start(); + wsd.start(); - connect(&d, &DNSSDDiscoverer::finished, this, maybeFinished); - connect(&w, &WSDiscoverer::finished, this, maybeFinished); + qCDebug(KIO_SMB_LOG) << "Modern discovery set up."; + } - d.start(); - w.start(); + qCDebug(KIO_SMB_LOG) << "Starting discovery."; + smbc.start(); + QTimer::singleShot(16000, &e, quitLoop); // max execution time! + e.exec(); - QTimer::singleShot(16000, &e, quitLoop); // max execution time! - e.exec(); + qCDebug(KIO_SMB_LOG) << "Discovery finished."; - qCDebug(KIO_SMB_LOG) << "Modern discovery finished."; - } else if (dirfd < 0) { // not smb:// and had an error -> handle it - if (errNum == EPERM || errNum == EACCES || workaroundEEXIST(errNum)) { + if (m_current_url.getType() != SMBURLTYPE_ENTIRE_NETWORK && smbc.error() != 0) { + // not smb:// and had an error -> handle it + const int err = smbc.error(); + if (err == EPERM || err == EACCES || workaroundEEXIST(err)) { qCDebug(KIO_SMB_LOG) << "trying checkPassword"; const int passwordError = checkPassword(m_current_url); if (passwordError == KJob::NoError) { redirection(m_current_url); finished(); } else if (passwordError == KIO::ERR_USER_CANCELED) { qCDebug(KIO_SMB_LOG) << "user cancelled password request"; - reportError(m_current_url, errNum); + reportError(m_current_url, err); } else { qCDebug(KIO_SMB_LOG) << "generic password error:" << passwordError; error(passwordError, m_current_url.toString()); } return; } - qCDebug(KIO_SMB_LOG) << "reporting generic error:" << errNum; - reportError(m_current_url, errNum); + qCDebug(KIO_SMB_LOG) << "reporting generic error:" << err; + reportError(m_current_url, err); return; } - if (dir_is_root) { + UDSEntry udsentry; + if (smbc.dirWasRoot()) { udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); udsentry.fastInsert(KIO::UDSEntry::UDS_NAME, "."); udsentry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH)); } else { udsentry.fastInsert(KIO::UDSEntry::UDS_NAME, "."); const int statErr = browse_stat_path(m_current_url, udsentry); if (statErr) { if (statErr == ENOENT || statErr == ENOTDIR) { reportWarning(m_current_url, statErr); } // Create a default UDSEntry if we could not stat the actual directory udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); udsentry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)); } } listEntry(udsentry); finished(); } void SMBSlave::fileSystemFreeSpace(const QUrl &url) { if (url.host().endsWith("kio-discovery-wsd")) { error(KIO::ERR_UNKNOWN_HOST, url.url()); return; } qCDebug(KIO_SMB_LOG) << url; // Avoid crashing in smbc_fstatvfs below when // requesting free space for smb:// which doesn't // make sense to do to begin with if (url.host().isEmpty()) { error(KIO::ERR_CANNOT_STAT, url.url()); return; } SMBUrl smbcUrl = url; struct statvfs dirStat { }; memset(&dirStat, 0, sizeof(struct statvfs)); const int err = smbc_statvfs(smbcUrl.toSmbcUrl().data(), &dirStat); if (err < 0) { error(KIO::ERR_CANNOT_STAT, url.url()); return; } // libsmb_stat.c has very awkward conditional branching that results // in data meaning different things based on context: // A samba host with unix extensions has f_frsize==0 and the f_bsize is // the actual block size. Any other server (such as windows) has a non-zero // f_frsize denoting the amount of sectors in a block and the f_bsize is // the amount of bytes in a sector. As such frsize*bsize is the actual // block size. // This was also broken in different ways throughout history, so depending // on the specific libsmbc versions the milage will vary. 4.7 to 4.11 are // at least behaving as described though. // https://bugs.kde.org/show_bug.cgi?id=298801 const auto frames = (dirStat.f_frsize == 0) ? 1 : dirStat.f_frsize; const auto blockSize = dirStat.f_bsize * frames; // Further more on older versions of samba f_bavail may not be set... const auto total = blockSize * dirStat.f_blocks; const auto available = blockSize * ((dirStat.f_bavail != 0) ? dirStat.f_bavail : dirStat.f_bfree); setMetaData("total", QString::number(total)); setMetaData("available", QString::number(available)); finished(); } bool SMBSlave::workaroundEEXIST(const int errNum) const { return (errNum == EEXIST) && m_enableEEXISTWorkaround; } diff --git a/smb/smbcdiscoverer.cpp b/smb/smbcdiscoverer.cpp new file mode 100644 index 00000000..b70cdd77 --- /dev/null +++ b/smb/smbcdiscoverer.cpp @@ -0,0 +1,292 @@ +/* + SPDX-FileCopyrightText: 2020 Harald Sitter + SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ + +#include +#include +#include + +#include "smbcdiscoverer.h" + +static QEvent::Type LoopEvent = QEvent::User; + +class SMBCSeverDiscovery : public SMBCDiscovery +{ +public: + SMBCSeverDiscovery(const UDSEntry &entry) + : SMBCDiscovery(entry) + { + m_entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + m_entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)); + m_entry.fastInsert(KIO::UDSEntry::UDS_URL, url()); + m_entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("application/x-smb-server")); + m_entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, "network-server"); + } + + QString url() + { + QUrl u("smb://"); + u.setHost(udsName()); + return u.url(); + } +}; + +class SMBCShareDiscovery : public SMBCDiscovery +{ +public: + SMBCShareDiscovery(const UDSEntry &entry) + : SMBCDiscovery(entry) + { + m_entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + m_entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)); + } +}; + +class SMBCWorkgroupDiscovery : public SMBCDiscovery +{ +public: + SMBCWorkgroupDiscovery(const UDSEntry &entry) + : SMBCDiscovery(entry) + { + m_entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + m_entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH)); + m_entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("application/x-smb-workgroup")); + m_entry.fastInsert(KIO::UDSEntry::UDS_URL, url()); + } + + QString url() + { + QUrl u("smb://"); + u.setHost(udsName()); + if (!u.isValid()) { + // In the event that the workgroup contains bad characters, put it in a query instead. + // This is transparently handled by SMBUrl when we get this as input again. + // Also see documentation there. + // https://bugs.kde.org/show_bug.cgi?id=204423 + u.setHost(QString()); + QUrlQuery q; + q.addQueryItem("kio-workgroup", udsName()); + u.setQuery(q); + } + return u.url(); + } +}; + +SMBCDiscovery::SMBCDiscovery(const UDSEntry &entry) + : m_entry(entry) + // cache the name, it may get accessed more than once + , m_name(entry.stringValue(KIO::UDSEntry::UDS_NAME)) +{ +} + +QString SMBCDiscovery::udsName() const +{ + return m_name; +} + +KIO::UDSEntry SMBCDiscovery::toEntry() const +{ + return m_entry; +} + +SMBCDiscoverer::SMBCDiscoverer(const SMBUrl &url, QEventLoop *loop, SMBSlave *slave) + : m_url(url) + , m_loop(loop) + , m_slave(slave) +{ +} + +SMBCDiscoverer::~SMBCDiscoverer() +{ + if (m_dirFd > 0) { + smbc_closedir(m_dirFd); + } +} + +void SMBCDiscoverer::start() +{ + queue(); +} + + +bool SMBCDiscoverer::loopFileInfo() +{ +#ifdef HAVE_READDIRPLUS2 + // Readdirplus2 dir/file listing. Becomes noop when at end of data associated with dirfd. + // If readdirplus2 isn't available the regular dirent listing is done. + // readdirplus2 improves performance by giving us a stat without separate call (Samba>=4.12) + struc stat st; + const struct libsmb_file_info *fileInfo = smbc_readdirplus2(dirfd, &st); + if (fileInfo) + const QString name = QString::fromUtf8(fileInfo->name); + if (name == ".") { + continue; + } else if (name == "..") { + dir_is_root = false; + continue; + } + UDSEntry entry; + entry.fastInsert(KIO::UDSEntry::UDS_NAME, name); + + m_currentUrl.addPath(name); + m_slave->statToUDSEntry(m_currentUrl, st, entry); // won't produce useful error + emit newDiscovery(Discovery::Ptr(new SMBCDiscovery(entry))); + m_currentUrl.cdUp(); + return true; + } +#endif // HAVE_READDIRPLUS2 + return false; +} + +void SMBCDiscoverer::loop() +{ + // Poor man's concurrency. smbc isn't thread safe so we'd hold up other + // discoverers until we are done. While that will likely happen anyway + // because smbc_opendir (usually?) blocks until it actually has all + // the data to loop on, meaning the actual looping after open is fairly + // fast. Even so, there's benefit in letting other discoverers do + // their work in the meantime because they may do more atomic + // requests that are async and can take a while due to network latency. + // To get somewhat reasonable behavior we simulate an async smbc discovery + // by posting loop events to the eventloop and each loop run we process + // a single dirent. + // This effectively unblocks the eventloop between iterations. + // Once we are out of entries this discoverer is considered finished. + + // Always queue a new iteration when returning so we don't forget to. + auto autoQueue = qScopeGuard([this] { + queue(); + }); + + if (m_dirFd == -1) { + init(); + Q_ASSERT(m_dirFd || m_finished); + return; + } + + if (loopFileInfo()) { + return; + } + + qCDebug(KIO_SMB_LOG) << "smbc_readdir "; + struct smbc_dirent *dirp = smbc_readdir(m_dirFd); + if (dirp == nullptr) { + qCDebug(KIO_SMB_LOG) << "done with smbc"; + stop(); + return; + } + + const QString name = QString::fromUtf8(dirp->name); + // We cannot trust dirp->commentlen has it might be with or without the NUL character + // See KDE bug #111430 and Samba bug #3030 + const QString comment = QString::fromUtf8(dirp->comment); + + qCDebug(KIO_SMB_LOG) << "dirent " + << "name:" << name + << "comment:" << comment + << "type:" << dirp->smbc_type; + + UDSEntry entry; + entry.fastInsert(KIO::UDSEntry::UDS_NAME, name); + entry.fastInsert(KIO::UDSEntry::UDS_COMMENT, comment); + // Ensure system shares are marked hidden. + if (name.endsWith(QLatin1Char('$'))) { + entry.fastInsert(KIO::UDSEntry::UDS_HIDDEN, 1); + } + +#if !defined(HAVE_READDIRPLUS2) + if (dirp->smbc_type == SMBC_FILE || dirp->smbc_type == SMBC_DIR) { + // Set stat information + m_url.addPath(name); + const int statErr = m_slave->browse_stat_path(m_url, entry); + if (statErr) { + if (statErr == ENOENT || statErr == ENOTDIR) { + m_slave->reportWarning(m_url, statErr); + } + } else { + emit newDiscovery(Discovery::Ptr(new SMBCDiscovery(entry))); + } + m_url.cdUp(); + } +#endif // HAVE_READDIRPLUS2 + + if (name == ".") { + // Skip the "." entry + // Mind the way m_currentUrl is handled in the loop + } else if (name == "..") { + m_dirWasRoot = false; + } else if (dirp->smbc_type == SMBC_SERVER) { + emit newDiscovery(Discovery::Ptr(new SMBCSeverDiscovery(entry))); + } else if (dirp->smbc_type == SMBC_FILE_SHARE) { + emit newDiscovery(Discovery::Ptr(new SMBCShareDiscovery(entry))); + } else if (dirp->smbc_type == SMBC_WORKGROUP) { + emit newDiscovery(Discovery::Ptr(new SMBCWorkgroupDiscovery(entry))); + } else { + qCDebug(KIO_SMB_LOG) << "SMBC_UNKNOWN :" << name; + } +} + +void SMBCDiscoverer::customEvent(QEvent *event) +{ + if (event->type() == LoopEvent) { + if (!m_finished) { + loop(); + } + return; + } + QObject::customEvent(event); +} + +void SMBCDiscoverer::stop() +{ + m_finished = true; + emit finished(); +} + +bool SMBCDiscoverer::isFinished() const +{ + return m_finished; +} + +bool SMBCDiscoverer::dirWasRoot() const +{ + return m_dirWasRoot; +} + +int SMBCDiscoverer::error() const +{ + return m_error; +} + +void SMBCDiscoverer::init() +{ + Q_ASSERT(m_dirFd < 0); + + m_dirFd = smbc_opendir(m_url.toSmbcUrl()); + if (m_dirFd >= 0) { + m_error = 0; + } else { + m_error = errno; + stop(); + } + + qCDebug(KIO_SMB_LOG) << "open " << m_url.toSmbcUrl() + << "url-type:" << m_url.getType() + << "dirfd:" << m_dirFd + << "errNum:" << m_error; + + return; +} + +void SMBCDiscoverer::queue() +{ + if (m_finished) { + return; + } + + // Queue low priority events. For server discovery (that is: other discoverers run as well) + // we want the modern discoverers to be peferred. For other discoveries only + // SMBC is running on the loop and so the priority has no negative impact. + QCoreApplication::postEvent(this, new QEvent(LoopEvent), Qt::LowEventPriority); +} diff --git a/smb/smbcdiscoverer.h b/smb/smbcdiscoverer.h new file mode 100644 index 00000000..66c45270 --- /dev/null +++ b/smb/smbcdiscoverer.h @@ -0,0 +1,82 @@ +/* + SPDX-FileCopyrightText: 2020 Harald Sitter + SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ + +#ifndef SMBCDISCOVERER_H +#define SMBCDISCOVERER_H + +#include + +#include "discovery.h" +#include "kio_smb.h" + +class QEventLoop; + +class SMBCDiscovery : public Discovery +{ +public: + SMBCDiscovery(const UDSEntry &entry); + QString udsName() const override; + KIO::UDSEntry toEntry() const override; + +protected: + UDSEntry m_entry; + +private: + const QString m_name; +}; + +class SMBCDiscoverer : public QObject, public Discoverer +{ + Q_OBJECT +public: + SMBCDiscoverer(const SMBUrl &url, QEventLoop *loop, SMBSlave *slave); + virtual ~SMBCDiscoverer(); + + void start() override; + bool isFinished() const override; + + bool dirWasRoot() const; + int error() const; + +signals: + void newDiscovery(Discovery::Ptr discovery) override; + void finished() override; + +private slots: + /** + * Process one dirent, queue a new loop event and return. + * @see customEvent + * @see queue + */ + void loop(); + +protected: + void customEvent(QEvent *event) override; + +private: + void stop() override; + + /** + * readdirplus2 based file_info looping + * @returns whether a file info was looped on + */ + bool loopFileInfo(); + + /// init discoverer (calls opendir) + void init(); + + /// queue new loop run + inline void queue(); + + SMBUrl m_url; + QEventLoop *m_loop = nullptr; + SMBSlave *m_slave = nullptr; + bool m_finished = false; + int m_error = 0; + bool m_dirWasRoot = true; + int m_dirFd = -1; +}; + +#endif // SMBCDISCOVERER_H