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"