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

0001 /*
0002     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0003     SPDX-FileCopyrightText: 2020-2021 Harald Sitter <sitter@kde.org>
0004 */
0005 
0006 #include <QCoreApplication>
0007 #include <QScopeGuard>
0008 #include <QUrlQuery>
0009 
0010 #include "smbcdiscoverer.h"
0011 
0012 static QEvent::Type LoopEvent = QEvent::User;
0013 
0014 class SMBCServerDiscovery : public SMBCDiscovery
0015 {
0016 public:
0017     SMBCServerDiscovery(const UDSEntry &entry)
0018         : SMBCDiscovery(entry)
0019     {
0020         m_entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
0021         m_entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH));
0022         m_entry.fastInsert(KIO::UDSEntry::UDS_URL, url());
0023         m_entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("application/x-smb-server"));
0024         m_entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("network-server"));
0025     }
0026 
0027     QString url()
0028     {
0029         QUrl u("smb://");
0030         u.setHost(udsName());
0031         return u.url();
0032     }
0033 };
0034 
0035 class SMBCShareDiscovery : public SMBCDiscovery
0036 {
0037 public:
0038     SMBCShareDiscovery(const UDSEntry &entry)
0039         : SMBCDiscovery(entry)
0040     {
0041         m_entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
0042         m_entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH));
0043     }
0044 };
0045 
0046 class SMBCWorkgroupDiscovery : public SMBCDiscovery
0047 {
0048 public:
0049     SMBCWorkgroupDiscovery(const UDSEntry &entry)
0050         : SMBCDiscovery(entry)
0051     {
0052         m_entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
0053         m_entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH));
0054         m_entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("application/x-smb-workgroup"));
0055         m_entry.fastInsert(KIO::UDSEntry::UDS_URL, url());
0056     }
0057 
0058     QString url()
0059     {
0060         QUrl u("smb://");
0061         u.setHost(udsName());
0062         if (!u.isValid()) {
0063             // In the event that the workgroup contains bad characters, put it in a query instead.
0064             // This is transparently handled by SMBUrl when we get this as input again.
0065             // Also see documentation there.
0066             // https://bugs.kde.org/show_bug.cgi?id=204423
0067             u.setHost(QString());
0068             QUrlQuery q;
0069             q.addQueryItem("kio-workgroup", udsName());
0070             u.setQuery(q);
0071         }
0072         return u.url();
0073     }
0074 };
0075 
0076 class SMBCPrinterDiscovery : public SMBCDiscovery
0077 {
0078 public:
0079     SMBCPrinterDiscovery(const UDSEntry &entry)
0080         : SMBCDiscovery(entry)
0081     {
0082         m_entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, 0x0);
0083         m_entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/vnd.kde.kio.smb.printer"));
0084         // Relative to parent, but need to set it so we can append a marker. Subsequent stat calls wouldn't know that this is a printer
0085         // because libsmbc doesn't reflect that in the stat output.
0086         m_entry.fastInsert(KIO::UDSEntry::UDS_URL, udsName() + QStringLiteral("?kio-printer=true"));
0087     }
0088 };
0089 
0090 SMBCDiscovery::SMBCDiscovery(const UDSEntry &entry)
0091     : m_entry(entry)
0092     // cache the name, it may get accessed more than once
0093     , m_name(entry.stringValue(KIO::UDSEntry::UDS_NAME))
0094 {
0095 }
0096 
0097 QString SMBCDiscovery::udsName() const
0098 {
0099     return m_name;
0100 }
0101 
0102 KIO::UDSEntry SMBCDiscovery::toEntry() const
0103 {
0104     return m_entry;
0105 }
0106 
0107 SMBCDiscoverer::SMBCDiscoverer(const SMBUrl &url, QEventLoop *loop, SMBWorker *worker)
0108     : m_url(url)
0109     , m_loop(loop)
0110     , m_worker(worker)
0111 {
0112 }
0113 
0114 SMBCDiscoverer::~SMBCDiscoverer()
0115 {
0116     if (m_dirFd > 0) {
0117         smbc_closedir(m_dirFd);
0118     }
0119 }
0120 
0121 void SMBCDiscoverer::start()
0122 {
0123     queue();
0124 }
0125 
0126 bool SMBCDiscoverer::discoverNextFileInfo()
0127 {
0128 #ifdef HAVE_READDIRPLUS2
0129     // Readdirplus2 dir/file listing. Becomes noop when at end of data associated with dirfd.
0130     // If readdirplus2 isn't available the regular dirent listing is done.
0131     // readdirplus2 improves performance by giving us a stat without separate call (Samba>=4.12)
0132     struct stat st;
0133     const struct libsmb_file_info *fileInfo = smbc_readdirplus2(m_dirFd, &st);
0134     if (fileInfo) {
0135         const QString name = QString::fromUtf8(fileInfo->name);
0136         qCDebug(KIO_SMB_LOG) << "fileInfo"
0137                              << "name:" << name;
0138         if (name == ".") {
0139             return true;
0140         } else if (name == "..") {
0141             m_dirWasRoot = false;
0142             return true;
0143         }
0144         UDSEntry entry;
0145         entry.reserve(5); // Minimal size. stat will set at least 4 fields.
0146         entry.fastInsert(KIO::UDSEntry::UDS_NAME, name);
0147 
0148         m_url.addPath(name);
0149         m_worker->statToUDSEntry(m_url, st, entry); // won't produce useful error
0150         Q_EMIT newDiscovery(Discovery::Ptr(new SMBCDiscovery(entry)));
0151         m_url.cdUp();
0152         return true;
0153     }
0154 #endif // HAVE_READDIRPLUS2
0155     return false;
0156 }
0157 
0158 void SMBCDiscoverer::discoverNext()
0159 {
0160     // Poor man's concurrency. smbc isn't thread safe so we'd hold up other
0161     // discoverers until we are done. While that will likely happen anyway
0162     // because smbc_opendir (usually?) blocks until it actually has all
0163     // the data to loop on, meaning the actual looping after open is fairly
0164     // fast. Even so, there's benefit in letting other discoverers do
0165     // their work in the meantime because they may do more atomic
0166     // requests that are async and can take a while due to network latency.
0167     // To get somewhat reasonable behavior we simulate an async smbc discovery
0168     // by posting loop events to the eventloop and each loop run we process
0169     // a single dirent.
0170     // This effectively unblocks the eventloop between iterations.
0171     // Once we are out of entries this discoverer is considered finished.
0172 
0173     // Always queue a new iteration when returning so we don't forget to.
0174     auto autoQueue = qScopeGuard([this] {
0175         queue();
0176     });
0177 
0178     if (m_dirFd == -1) {
0179         init();
0180         Q_ASSERT(m_dirFd || m_finished);
0181         return;
0182     }
0183 
0184     static bool printersOnly = qEnvironmentVariableIntValue("KIO_SMB_PRINTERS") > 0;
0185 
0186     if (!printersOnly && discoverNextFileInfo()) {
0187         return;
0188     }
0189 
0190     qCDebug(KIO_SMB_LOG) << "smbc_readdir ";
0191     struct smbc_dirent *dirp = smbc_readdir(m_dirFd);
0192     if (dirp == nullptr) {
0193         qCDebug(KIO_SMB_LOG) << "done with smbc";
0194         stop();
0195         return;
0196     }
0197 
0198     // We cannot trust dirp->commentlen as it might be with or without the NUL character
0199     // See KDE bug #111430 and Samba bug #3030.
0200     // This further complicates things because name is really declared as name[1]
0201     // which is insofar a problem as in Qt6 QByteArrayView knows to handle this properly
0202     // for the fromUtf8 conversion resulting in only a single character getting extracted.
0203     // To mitigate both problems we a) always rely on the terminal NUL detection and
0204     // b) explicitly decay name to a pointer such that QString has to obey a).
0205     const QString name = QString::fromUtf8(std::addressof(dirp->name[0]));
0206     const QString comment = QString::fromUtf8(dirp->comment);
0207 
0208     qCDebug(KIO_SMB_LOG) << "dirent "
0209                          << "name:" << name << "comment:" << comment << "type:" << dirp->smbc_type;
0210 
0211     UDSEntry entry;
0212     // Minimal potential size. The actual size depends on this function,
0213     // possibly the stat function, and lastly the Discovery objects themselves.
0214     // The smallest will be a ShareDiscovery with 5 fields.
0215     entry.reserve(5);
0216     entry.fastInsert(KIO::UDSEntry::UDS_NAME, name);
0217     entry.fastInsert(KIO::UDSEntry::UDS_COMMENT, comment);
0218     // Ensure system shares are marked hidden.
0219     if (name.endsWith(QLatin1Char('$'))) {
0220         entry.fastInsert(KIO::UDSEntry::UDS_HIDDEN, 1);
0221     }
0222 
0223     if (printersOnly) {
0224         if (dirp->smbc_type == SMBC_PRINTER_SHARE) {
0225             Q_EMIT newDiscovery(Discovery::Ptr(new SMBCPrinterDiscovery(entry)));
0226         }
0227         return;
0228     }
0229 
0230 #if !defined(HAVE_READDIRPLUS2)
0231     // . and .. are always of the dir type so they are of no consequence outside
0232     // actual dir listing and that'd be done by readdirplus2 already
0233     if (name == ".") {
0234         // Skip the "." entry
0235         // Mind the way m_currentUrl is handled in the loop
0236     } else if (name == "..") {
0237         m_dirWasRoot = false;
0238     } else if (dirp->smbc_type == SMBC_FILE || dirp->smbc_type == SMBC_DIR) {
0239         // Set stat information
0240         m_url.addPath(name);
0241         const int statErr = m_worker->browse_stat_path(m_url, entry);
0242         if (statErr != 0) {
0243             // The entry can disappear in the time span between
0244             // listing and the stat call. There's nothing we or the user
0245             // can do about it. Log the incident and move on with listing.
0246             qCWarning(KIO_SMB_LOG) << "Failed to stat" << m_url << statErr;
0247         } else {
0248             Q_EMIT newDiscovery(Discovery::Ptr(new SMBCDiscovery(entry)));
0249         }
0250         m_url.cdUp();
0251     }
0252 #endif // HAVE_READDIRPLUS2
0253 
0254     if (dirp->smbc_type == SMBC_SERVER) {
0255         Q_EMIT newDiscovery(Discovery::Ptr(new SMBCServerDiscovery(entry)));
0256     } else if (dirp->smbc_type == SMBC_FILE_SHARE) {
0257         Q_EMIT newDiscovery(Discovery::Ptr(new SMBCShareDiscovery(entry)));
0258     } else if (dirp->smbc_type == SMBC_WORKGROUP) {
0259         Q_EMIT newDiscovery(Discovery::Ptr(new SMBCWorkgroupDiscovery(entry)));
0260     } else {
0261         qCDebug(KIO_SMB_LOG) << "SMBC_UNKNOWN :" << name;
0262     }
0263 }
0264 
0265 void SMBCDiscoverer::customEvent(QEvent *event)
0266 {
0267     if (event->type() == LoopEvent) {
0268         if (!m_finished) {
0269             discoverNext();
0270         }
0271         return;
0272     }
0273     QObject::customEvent(event);
0274 }
0275 
0276 void SMBCDiscoverer::stop()
0277 {
0278     m_finished = true;
0279     Q_EMIT finished();
0280 }
0281 
0282 bool SMBCDiscoverer::isFinished() const
0283 {
0284     return m_finished;
0285 }
0286 
0287 bool SMBCDiscoverer::dirWasRoot() const
0288 {
0289     return m_dirWasRoot;
0290 }
0291 
0292 int SMBCDiscoverer::error() const
0293 {
0294     return m_error;
0295 }
0296 
0297 void SMBCDiscoverer::init()
0298 {
0299     Q_ASSERT(m_dirFd < 0);
0300 
0301     m_dirFd = smbc_opendir(m_url.toSmbcUrl());
0302     if (m_dirFd >= 0) {
0303         m_error = 0;
0304     } else {
0305         m_error = errno;
0306         stop();
0307     }
0308 
0309     qCDebug(KIO_SMB_LOG) << "open" << m_url.toSmbcUrl() << "url-type:" << m_url.getType() << "dirfd:" << m_dirFd << "errNum:" << m_error;
0310 
0311     return;
0312 }
0313 
0314 void SMBCDiscoverer::queue()
0315 {
0316     if (m_finished) {
0317         return;
0318     }
0319 
0320     // Queue low priority events. For server discovery (that is: other discoverers run as well)
0321     // we want the modern discoverers to be peferred. For other discoveries only
0322     // SMBC is running on the loop and so the priority has no negative impact.
0323     QCoreApplication::postEvent(this, new QEvent(LoopEvent), Qt::LowEventPriority);
0324 }
0325 
0326 #include "moc_smbcdiscoverer.cpp"