File indexing completed on 2024-04-28 04:58:01

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2000 Caldera Systems Inc.
0004     SPDX-FileCopyrightText: 2018-2022 Harald Sitter <sitter@kde.org>
0005     SPDX-FileContributor: Matthew Peterson <mpeterson@caldera.com>
0006 */
0007 
0008 #include "kio_smb.h"
0009 #include "smburl.h"
0010 
0011 #include <utility>
0012 
0013 #include <KDNSSD/RemoteService>
0014 #include <KDNSSD/ServiceBrowser>
0015 #include <KIO/Job>
0016 #include <KLocalizedString>
0017 
0018 #include <QEventLoop>
0019 #include <QTimer>
0020 
0021 #include <grp.h>
0022 #include <pwd.h>
0023 
0024 #include "dnssddiscoverer.h"
0025 #include "smbcdiscoverer.h"
0026 #include "wsdiscoverer.h"
0027 #include <config-runtime.h>
0028 
0029 using namespace KIO;
0030 
0031 int SMBWorker::cache_stat(const SMBUrl &url, struct stat *st)
0032 {
0033     int cacheStatErr = 0;
0034     int result = smbc_stat(url.toSmbcUrl(), st);
0035     if (result == 0) {
0036         cacheStatErr = 0;
0037     } else {
0038         cacheStatErr = errno;
0039     }
0040     qCDebug(KIO_SMB_LOG) << "size " << static_cast<KIO::filesize_t>(st->st_size);
0041     return cacheStatErr;
0042 }
0043 
0044 int SMBWorker::browse_stat_path(const SMBUrl &url, UDSEntry &udsentry)
0045 {
0046     int cacheStatErr = cache_stat(url, &st);
0047     if (cacheStatErr == 0) {
0048         return statToUDSEntry(url, st, udsentry);
0049     }
0050 
0051     return cacheStatErr;
0052 }
0053 
0054 int SMBWorker::statToUDSEntry(const QUrl &url, const struct stat &st, KIO::UDSEntry &udsentry)
0055 {
0056     if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) {
0057         qCDebug(KIO_SMB_LOG) << "mode: " << st.st_mode;
0058         warning(
0059             i18n("%1:\n"
0060                  "Unknown file type, neither directory or file.",
0061                  url.toDisplayString()));
0062         return EINVAL;
0063     }
0064 
0065     if (!S_ISDIR(st.st_mode)) {
0066         // Awkwardly documented at
0067         //    https://www.samba.org/samba/docs/using_samba/ch08.html
0068         // libsmb_stat.c assigns special meaning to +x permissions
0069         // (obviously only on files, all dirs are +x so this hacky representation
0070         //  wouldn't work!):
0071         // - S_IXUSR = DOS archive: This file has been touched since the last DOS backup was performed on it.
0072         // - S_IXGRP = DOS system: This file has a specific purpose required by the operating system.
0073         // - 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.
0074         // Only hiding has backing through KIO right now.
0075         if (st.st_mode & S_IXOTH) { // DOS hidden
0076             udsentry.fastInsert(KIO::UDSEntry::UDS_HIDDEN, true);
0077         }
0078     }
0079 
0080     // UID and GID **must** not be mapped. The values returned by libsmbclient are
0081     // simply the getuid/getgid of the process. They mean absolutely nothing.
0082     // Also see libsmb_stat.c.
0083     // Related: https://bugs.kde.org/show_bug.cgi?id=212801
0084 
0085     // POSIX Access mode must not be mapped either!
0086     // It's meaningless for smb shares and downright disadvantageous.
0087     // The mode attributes outside the ones used and document above are
0088     // useless. The only one actively set is readonlyness.
0089     //
0090     // BUT the READONLY attribute does nothing on NT systems:
0091     // https://support.microsoft.com/en-us/help/326549/you-cannot-view-or-change-the-read-only-or-the-system-attributes-of-fo
0092     // The Read-only and System attributes is only used by Windows Explorer to determine
0093     // whether the folder is a special folder, such as a system folder that has its view
0094     // customized by Windows (for example, My Documents, Favorites, Fonts, Downloaded Program Files),
0095     // or a folder that you customized by using the Customize tab of the folder's Properties dialog box.
0096     //
0097     // As such respecting it on a KIO level is actually wrong as it doesn't indicate actual
0098     // readonlyness since the 90s and causes us to show readonly UI states when in fact
0099     // the directory is perfectly writable.
0100     // https://bugs.kde.org/show_bug.cgi?id=414482
0101     //
0102     // Should we ever want to parse desktop.ini like we do .directory we'd only want to when a
0103     // dir is readonly as per the above microsoft support article.
0104     // Also see:
0105     // https://docs.microsoft.com/en-us/windows/win32/shell/how-to-customize-folders-with-desktop-ini
0106 
0107     udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, st.st_mode & S_IFMT);
0108     udsentry.fastInsert(KIO::UDSEntry::UDS_SIZE, st.st_size);
0109     udsentry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, st.st_mtime);
0110     udsentry.fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, st.st_atime);
0111     // No, st_ctime is not UDS_CREATION_TIME...
0112 
0113     return 0;
0114 }
0115 
0116 WorkerResult SMBWorker::stat(const QUrl &kurl)
0117 {
0118     qCDebug(KIO_SMB_LOG) << kurl;
0119     // make a valid URL
0120     QUrl url = checkURL(kurl);
0121 
0122     // if URL is not valid we have to redirect to correct URL
0123     if (url != kurl) {
0124         qCDebug(KIO_SMB_LOG) << "redirection " << url;
0125         redirection(url);
0126         return WorkerResult::pass();
0127     }
0128 
0129     m_current_url = url;
0130 
0131     UDSEntry udsentry;
0132     // Set name
0133     udsentry.fastInsert(KIO::UDSEntry::UDS_NAME, kurl.fileName());
0134 
0135     switch (m_current_url.getType()) {
0136     case SMBURLTYPE_UNKNOWN:
0137         return WorkerResult::fail(ERR_MALFORMED_URL, url.toDisplayString());
0138     case SMBURLTYPE_PRINTER:
0139         return WorkerResult::fail(ERR_UNSUPPORTED_ACTION, url.toDisplayString());
0140     case SMBURLTYPE_ENTIRE_NETWORK:
0141     case SMBURLTYPE_WORKGROUP_OR_SERVER:
0142         udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
0143         statEntry(udsentry);
0144         return WorkerResult::pass();
0145     case SMBURLTYPE_SHARE_OR_PATH: {
0146         int ret = browse_stat_path(m_current_url, udsentry);
0147 
0148         if (ret == EPERM || ret == EACCES || workaroundEEXIST(ret)) {
0149             SMBUrl smbUrl(url);
0150 
0151             const int passwordError = checkPassword(smbUrl);
0152             if (passwordError == KJob::NoError) {
0153                 redirection(smbUrl);
0154                 return WorkerResult::pass();
0155             }
0156             if (passwordError == KIO::ERR_USER_CANCELED) {
0157                 return reportError(url, ret);
0158             }
0159             return WorkerResult::fail(passwordError, url.toString());
0160         }
0161         if (ret != 0) {
0162             qCDebug(KIO_SMB_LOG) << "stat() error" << ret << url;
0163             return reportError(url, ret);
0164         }
0165 
0166         statEntry(udsentry);
0167         return WorkerResult::pass();
0168     }
0169     }
0170 
0171     qCDebug(KIO_SMB_LOG) << "UNKNOWN " << url;
0172     return WorkerResult::pass();
0173 }
0174 
0175 // TODO: complete checking <-- what does that even mean?
0176 // TODO: why is this not part of SMBUrl or at the very least URL validation should
0177 //    be 100% shared between this and SMBUrl. Notably SMBUrl has code that looks
0178 //    to do a similar thing but is much less complete.
0179 QUrl SMBWorker::checkURL(const QUrl &kurl_) const
0180 {
0181     qCDebug(KIO_SMB_LOG) << "checkURL " << kurl_;
0182 
0183     QUrl kurl(kurl_);
0184     // We treat cifs as an alias but need to translate it to smb.
0185     // https://bugs.kde.org/show_bug.cgi?id=327295
0186     // It's not IANA registered and also libsmbc internally expects
0187     // smb URIs so we do very broadly coerce cifs to smb.
0188     // Also see SMBUrl.
0189     if (kurl.scheme() == "cifs") {
0190         kurl.setScheme("smb");
0191     }
0192 
0193     // For WS-Discovered hosts we assume they'll respond to DNSSD names on .local but
0194     // they may only respond to llmnr/netbios names. Transparently fall back.
0195     //
0196     // Desktop linuxes tend to have llmnr disabled, by contrast win10 has dnssd enabled,
0197     // so chances are we'll be able to find a host.local more reliably.
0198     // Attempt to resolve foo.local natively, if that works use it, otherwise default to
0199     // the presumed LLMNR/netbios name found during discovery.
0200     // This should then yield reasonable results with any combination of WSD/DNSSD/LLMNR support.
0201     // - WSD+Avahi (on linux)
0202     // - WSD+Win10 (i.e. dnssd + llmnr)
0203     // - WSD+CrappyNAS (e.g. llmnr or netbios only)
0204     //
0205     // NB: smbc has no way to resolve a name without also triggering auth etc.: we must
0206     //   rely on the system's ability to resolve DNSSD for this check.
0207     const QLatin1String wsdSuffix(".kio-discovery-wsd");
0208     if (kurl.host().endsWith(wsdSuffix)) {
0209         QString host = kurl.host();
0210         host.chop(wsdSuffix.size());
0211         const QString dnssd(host + ".local");
0212         auto dnssdHost = KDNSSD::ServiceBrowser::resolveHostName(dnssd);
0213         if (!dnssdHost.isNull()) {
0214             qCDebug(KIO_SMB_LOG) << "Resolved DNSSD name:" << dnssd;
0215             host = dnssd;
0216         } else {
0217             qCDebug(KIO_SMB_LOG) << "Failed to resolve DNSSD name:" << dnssd;
0218             qCDebug(KIO_SMB_LOG) << "Falling back to LLMNR name:" << host;
0219         }
0220         kurl.setHost(host);
0221     }
0222 
0223     QString surl = kurl.url();
0224     // transform any links in the form smb:/ into smb://
0225     if (surl.startsWith(QLatin1String("smb:/"))) {
0226         if (surl.length() == 5) {
0227             return QUrl("smb://");
0228         }
0229         if (surl.at(5) != '/') {
0230             surl = "smb://" + surl.mid(5);
0231             qCDebug(KIO_SMB_LOG) << "checkURL return1 " << surl << " " << QUrl(surl);
0232             return QUrl(surl);
0233         }
0234     }
0235     if (surl == QLatin1String("smb://")) {
0236         return kurl; // unchanged
0237     }
0238 
0239     // smb:// normally have no userinfo
0240     // we must redirect ourself to remove the username and password
0241     if (surl.contains('@') && !surl.contains("smb://")) {
0242         QUrl url(kurl);
0243         url.setPath('/' + kurl.url().right(kurl.url().length() - kurl.url().indexOf('@') - 1));
0244         QString userinfo = kurl.url().mid(5, kurl.url().indexOf('@') - 5);
0245         if (userinfo.contains(':')) {
0246             url.setUserName(userinfo.left(userinfo.indexOf(':')));
0247             url.setPassword(userinfo.right(userinfo.length() - userinfo.indexOf(':') - 1));
0248         } else {
0249             url.setUserName(userinfo);
0250         }
0251         qCDebug(KIO_SMB_LOG) << "checkURL return2 " << url;
0252         return url;
0253     }
0254 
0255     // if there's a valid host, don't have an empty path
0256     QUrl url(kurl);
0257 
0258     if (url.path().isEmpty()) {
0259         url.setPath("/");
0260     }
0261 
0262     qCDebug(KIO_SMB_LOG) << "checkURL return3 " << url;
0263     return url;
0264 }
0265 
0266 SMBWorker::SMBError SMBWorker::errnumToKioError(const SMBUrl &url, const int errNum)
0267 {
0268     qCDebug(KIO_SMB_LOG) << "errNum" << errNum;
0269 
0270     switch (errNum) {
0271     case ENOENT:
0272         if (url.getType() == SMBURLTYPE_ENTIRE_NETWORK) {
0273             return SMBError{ERR_WORKER_DEFINED, i18n("Unable to find any workgroups in your local network. This might be caused by an enabled firewall.")};
0274         }
0275         return SMBError{ERR_DOES_NOT_EXIST, url.toDisplayString()};
0276 #ifdef ENOMEDIUM
0277     case ENOMEDIUM:
0278         return SMBError{ERR_WORKER_DEFINED, i18n("No media in device for %1", url.toDisplayString())};
0279 #endif
0280 #ifdef EHOSTDOWN
0281     case EHOSTDOWN:
0282 #endif
0283     case ECONNREFUSED:
0284         return SMBError{ERR_WORKER_DEFINED, i18n("Could not connect to host for %1", url.toDisplayString())};
0285     case ENOTDIR:
0286         return SMBError{ERR_CANNOT_ENTER_DIRECTORY, url.toDisplayString()};
0287     case EFAULT:
0288     case EINVAL:
0289         return SMBError{ERR_DOES_NOT_EXIST, url.toDisplayString()};
0290     case EPERM:
0291     case EACCES:
0292         return SMBError{ERR_ACCESS_DENIED, url.toDisplayString()};
0293     case EIO:
0294     case ENETUNREACH:
0295         if (url.getType() == SMBURLTYPE_ENTIRE_NETWORK || url.getType() == SMBURLTYPE_WORKGROUP_OR_SERVER) {
0296             return SMBError{ERR_WORKER_DEFINED, i18n("Error while connecting to server responsible for %1", url.toDisplayString())};
0297         }
0298         return SMBError{ERR_CONNECTION_BROKEN, url.toDisplayString()};
0299     case ENOMEM:
0300         return SMBError{ERR_OUT_OF_MEMORY, url.toDisplayString()};
0301     case ENODEV:
0302         return SMBError{ERR_WORKER_DEFINED, i18n("Share could not be found on given server")};
0303     case EBADF:
0304         return SMBError{ERR_INTERNAL, i18n("Bad file descriptor")};
0305     case ETIMEDOUT:
0306         return SMBError{ERR_SERVER_TIMEOUT, url.host()};
0307     case ENOTEMPTY:
0308         return SMBError{ERR_CANNOT_RMDIR, url.toDisplayString()};
0309 #ifdef ENOTUNIQ
0310     case ENOTUNIQ:
0311         return SMBError{ERR_WORKER_DEFINED,
0312                         i18n("The given name could not be resolved to a unique server. "
0313                              "Make sure your network is setup without any name conflicts "
0314                              "between names used by Windows and by UNIX name resolution.")};
0315 #endif
0316     case ECONNABORTED:
0317         return SMBError{ERR_CONNECTION_BROKEN, url.host()};
0318     case EHOSTUNREACH:
0319         return SMBError{ERR_CANNOT_CONNECT,
0320                         i18nc("@info:status smb failed to reach the server (e.g. server offline or network failure). %1 is an ip address or hostname",
0321                               "%1: Host unreachable",
0322                               url.host())};
0323     case 0: // success
0324         return SMBError{ERR_INTERNAL,
0325                         i18n("libsmbclient reported an error, but did not specify "
0326                              "what the problem is. This might indicate a severe problem "
0327                              "with your network - but also might indicate a problem with "
0328                              "libsmbclient.\n"
0329                              "If you want to help us, please provide a tcpdump of the "
0330                              "network interface while you try to browse (be aware that "
0331                              "it might contain private data, so do not post it if you are "
0332                              "unsure about that - you can send it privately to the developers "
0333                              "if they ask for it)")};
0334     default:
0335         return SMBError{ERR_INTERNAL,
0336                         i18nc("%1 is an error number, %2 either a pretty string or the number",
0337                               "Unknown error condition: [%1] %2",
0338                               QString::number(errNum),
0339                               QString::fromLocal8Bit(strerror(errNum)))};
0340     }
0341 }
0342 
0343 WorkerResult SMBWorker::reportError(const SMBUrl &url, const int errNum)
0344 {
0345     const SMBError smbErr = errnumToKioError(url, errNum);
0346     return WorkerResult::fail(smbErr.kioErrorId, smbErr.errorString);
0347 }
0348 
0349 void SMBWorker::reportWarning(const SMBUrl &url, const int errNum)
0350 {
0351     const SMBError smbErr = errnumToKioError(url, errNum);
0352     const QString errorString = buildErrorString(smbErr.kioErrorId, smbErr.errorString);
0353 
0354     warning(xi18n("Error occurred while trying to access %1<nl/>%2", url.url(), errorString));
0355 }
0356 
0357 WorkerResult SMBWorker::listDir(const QUrl &kurl)
0358 {
0359     qCDebug(KIO_SMB_LOG) << kurl;
0360 
0361     // check (correct) URL
0362     QUrl url = checkURL(kurl);
0363     // if URL is not valid we have to redirect to correct URL
0364     if (url != kurl) {
0365         redirection(url);
0366         return WorkerResult::pass();
0367     }
0368 
0369     m_current_url = kurl;
0370 
0371     QEventLoop e;
0372 
0373     UDSEntryList list;
0374     QStringList discoveredNames;
0375 
0376     const auto flushEntries = [this, &list]() {
0377         if (list.isEmpty()) {
0378             return;
0379         }
0380         listEntries(list);
0381         list.clear();
0382     };
0383 
0384     // Since WorkerBase has no eventloop it wont publish results
0385     // on a timer, since we do not know how long our discovery
0386     // will take this is super meh because we may appear
0387     // stuck for a while. Implement our own listing system
0388     // based on QTimer to mitigate.
0389     QTimer sendTimer;
0390     sendTimer.setInterval(300);
0391     connect(&sendTimer, &QTimer::timeout, this, flushEntries);
0392     sendTimer.start();
0393 
0394     QSharedPointer<SMBCDiscoverer> smbc(new SMBCDiscoverer(m_current_url, &e, this));
0395 
0396     QVector<QSharedPointer<Discoverer>> discoverers;
0397     discoverers << smbc;
0398 
0399     auto appendDiscovery = [&](const Discovery::Ptr &discovery) {
0400         if (discoveredNames.contains(discovery->udsName(), Qt::CaseInsensitive)) {
0401             return;
0402         }
0403         // Not tracking hosts. Tracking hosts means **guessing** if foo.local
0404         // and foo and foo.kio-discovery-wsd will actually resolve to the same
0405         // IP address, which is tricky to do at best. In the interest of efficiency
0406         // I'd rather have the de-duplication requirement be that the name of
0407         // two competing service discovery systems needs to be the same.
0408         discoveredNames << discovery->udsName();
0409         list.append(discovery->toEntry());
0410     };
0411 
0412     auto maybeFinished = [&] { // finishes if all discoveries finished
0413         bool allFinished = true;
0414         for (const auto &discoverer : std::as_const(discoverers)) {
0415             allFinished = allFinished && discoverer->isFinished();
0416         }
0417         if (allFinished) {
0418             flushEntries();
0419             e.quit();
0420         }
0421     };
0422 
0423     connect(smbc.data(), &SMBCDiscoverer::newDiscovery, this, appendDiscovery);
0424     connect(smbc.data(), &SMBCDiscoverer::finished, this, maybeFinished);
0425 
0426     // Run service discovery if the path is root. This augments
0427     // "native" results from libsmbclient.
0428     // Also, should native resolution have encountered an error it will not matter.
0429     if (m_current_url.getType() == SMBURLTYPE_ENTIRE_NETWORK) {
0430         QSharedPointer<DNSSDDiscoverer> dnssd(new DNSSDDiscoverer);
0431         QSharedPointer<WSDiscoverer> wsd(new WSDiscoverer);
0432         discoverers << dnssd << wsd;
0433 
0434         qCDebug(KIO_SMB_LOG) << "Adding modern discovery (dnssd/wsdiscovery)";
0435 
0436         connect(dnssd.data(), &DNSSDDiscoverer::newDiscovery, this, appendDiscovery);
0437         connect(wsd.data(), &WSDiscoverer::newDiscovery, this, appendDiscovery);
0438 
0439         connect(dnssd.data(), &DNSSDDiscoverer::finished, this, maybeFinished);
0440         connect(wsd.data(), &WSDiscoverer::finished, this, maybeFinished);
0441 
0442         dnssd->start();
0443         wsd->start();
0444 
0445         qCDebug(KIO_SMB_LOG) << "Modern discovery set up.";
0446     }
0447 
0448     qCDebug(KIO_SMB_LOG) << "Starting discovery.";
0449     smbc->start();
0450     e.exec();
0451 
0452     qCDebug(KIO_SMB_LOG) << "Discovery finished.";
0453 
0454     if (m_current_url.getType() != SMBURLTYPE_ENTIRE_NETWORK && smbc->error() != 0) {
0455         // not smb:// and had an error -> handle it
0456         const int err = smbc->error();
0457         if (err == EPERM || err == EACCES || err == EINVAL || workaroundEEXIST(err)) {
0458             qCDebug(KIO_SMB_LOG) << "trying checkPassword";
0459             const int passwordError = checkPassword(m_current_url);
0460             if (passwordError == KJob::NoError) {
0461                 redirection(m_current_url);
0462                 return WorkerResult::pass();
0463             }
0464             if (passwordError == KIO::ERR_USER_CANCELED) {
0465                 qCDebug(KIO_SMB_LOG) << "user cancelled password request";
0466                 return reportError(m_current_url, err);
0467             }
0468             qCDebug(KIO_SMB_LOG) << "generic password error:" << passwordError;
0469             return WorkerResult::fail(passwordError, m_current_url.toString());
0470         }
0471 
0472         qCDebug(KIO_SMB_LOG) << "reporting generic error:" << err;
0473         return reportError(m_current_url, err);
0474     }
0475 
0476     UDSEntry udsentry;
0477     if (smbc->dirWasRoot()) {
0478         udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
0479         udsentry.fastInsert(KIO::UDSEntry::UDS_NAME, ".");
0480         udsentry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH));
0481     } else {
0482         udsentry.fastInsert(KIO::UDSEntry::UDS_NAME, ".");
0483         const int statErr = browse_stat_path(m_current_url, udsentry);
0484         if (statErr != 0) {
0485             if (statErr == ENOENT || statErr == ENOTDIR) {
0486                 reportWarning(m_current_url, statErr);
0487             }
0488             // Create a default UDSEntry if we could not stat the actual directory
0489             udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
0490             udsentry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH));
0491         }
0492     }
0493     listEntry(udsentry);
0494 
0495     return WorkerResult::pass();
0496 }
0497 
0498 WorkerResult SMBWorker::fileSystemFreeSpace(const QUrl &url)
0499 {
0500     if (url.host().endsWith("kio-discovery-wsd")) {
0501         return WorkerResult::fail(KIO::ERR_UNKNOWN_HOST, url.url());
0502     }
0503     qCDebug(KIO_SMB_LOG) << url;
0504 
0505     // Avoid crashing in smbc_fstatvfs below when
0506     // requesting free space for smb:// which doesn't
0507     // make sense to do to begin with
0508     if (url.host().isEmpty()) {
0509         return WorkerResult::fail(KIO::ERR_CANNOT_STAT, url.url());
0510     }
0511 
0512     SMBUrl smbUrl = url;
0513 
0514     struct statvfs dirStat {
0515     };
0516     memset(&dirStat, 0, sizeof(struct statvfs));
0517     auto smbcUrl = smbUrl.toSmbcUrl(); // do not use temporary in function call, makes clazy happy
0518     const int err = smbc_statvfs(smbcUrl.data(), &dirStat);
0519     if (err < 0) {
0520         return WorkerResult::fail(KIO::ERR_CANNOT_STAT, url.url());
0521     }
0522 
0523     // libsmb_stat.c has very awkward conditional branching that results
0524     // in data meaning different things based on context:
0525     // A samba host with unix extensions has f_frsize==0 and the f_bsize is
0526     // the actual block size. Any other server (such as windows) has a non-zero
0527     // f_frsize denoting the amount of sectors in a block and the f_bsize is
0528     // the amount of bytes in a sector. As such frsize*bsize is the actual
0529     // block size.
0530     // This was also broken in different ways throughout history, so depending
0531     // on the specific libsmbc versions the milage will vary. 4.7 to 4.11 are
0532     // at least behaving as described though.
0533     // https://bugs.kde.org/show_bug.cgi?id=298801
0534     const auto frames = (dirStat.f_frsize == 0) ? 1 : dirStat.f_frsize;
0535     const auto blockSize = dirStat.f_bsize * frames;
0536     // Further more on older versions of samba f_bavail may not be set...
0537     const auto total = blockSize * dirStat.f_blocks;
0538     const auto available = blockSize * ((dirStat.f_bavail != 0) ? dirStat.f_bavail : dirStat.f_bfree);
0539 
0540     // If total is 0 don't bother reporting it, it just makes dolphin misbehave. And is indicative of
0541     // us not having any viable data.
0542     // https://bugs.kde.org/show_bug.cgi?id=431050
0543     if (total <= 0) {
0544         return WorkerResult::fail(KIO::ERR_UNSUPPORTED_ACTION, url.url());
0545     }
0546 
0547     setMetaData("total", QString::number(total));
0548     setMetaData("available", QString::number(available));
0549 
0550     return WorkerResult::pass();
0551 }
0552 
0553 bool SMBWorker::workaroundEEXIST(const int errNum) const
0554 {
0555     return (errNum == EEXIST) && m_enableEEXISTWorkaround;
0556 }