Changeset View
Changeset View
Standalone View
Standalone View
smb/kio_smb_browse.cpp
1 | 1 | | |||
---|---|---|---|---|---|
2 | ///////////////////////////////////////////////////////////////////////////// | 2 | ///////////////////////////////////////////////////////////////////////////// | ||
3 | // | 3 | // | ||
4 | // Project: SMB kioslave for KDE2 | 4 | // Project: SMB kioslave for KDE2 | ||
5 | // | 5 | // | ||
6 | // File: kio_smb_browse.cpp | 6 | // File: kio_smb_browse.cpp | ||
7 | // | 7 | // | ||
8 | // Abstract: member function implementations for SMBSlave that deal with | 8 | // Abstract: member function implementations for SMBSlave that deal with | ||
9 | // SMB browsing | 9 | // SMB browsing | ||
10 | // | 10 | // | ||
11 | // Author(s): Matthew Peterson <mpeterson@caldera.com> | 11 | // Author(s): Matthew Peterson <mpeterson@caldera.com> | ||
12 | // | 12 | // | ||
13 | //--------------------------------------------------------------------------- | 13 | //--------------------------------------------------------------------------- | ||
14 | // | 14 | // | ||
15 | // Copyright (c) 2000 Caldera Systems, Inc. | 15 | // Copyright (c) 2000 Caldera Systems, Inc. | ||
16 | // Copyright (c) 2018 Harald Sitter <sitter@kde.org> | 16 | // Copyright (c) 2018-2020 Harald Sitter <sitter@kde.org> | ||
17 | // | 17 | // | ||
18 | // This program is free software; you can redistribute it and/or modify it | 18 | // This program is free software; you can redistribute it and/or modify it | ||
19 | // under the terms of the GNU General Public License as published by the | 19 | // under the terms of the GNU General Public License as published by the | ||
20 | // Free Software Foundation; either version 2.1 of the License, or | 20 | // Free Software Foundation; either version 2.1 of the License, or | ||
21 | // (at your option) any later version. | 21 | // (at your option) any later version. | ||
22 | // | 22 | // | ||
23 | // This program is distributed in the hope that it will be useful, | 23 | // This program is distributed in the hope that it will be useful, | ||
24 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | 24 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
Show All 11 Lines | |||||
36 | 36 | | |||
37 | #include <DNSSD/ServiceBrowser> | 37 | #include <DNSSD/ServiceBrowser> | ||
38 | #include <DNSSD/RemoteService> | 38 | #include <DNSSD/RemoteService> | ||
39 | #include <KLocalizedString> | 39 | #include <KLocalizedString> | ||
40 | #include <KIO/Job> | 40 | #include <KIO/Job> | ||
41 | 41 | | |||
42 | #include <QEventLoop> | 42 | #include <QEventLoop> | ||
43 | 43 | | |||
44 | #include <QTimer> | ||||
45 | | ||||
44 | #include <pwd.h> | 46 | #include <pwd.h> | ||
45 | #include <grp.h> | 47 | #include <grp.h> | ||
46 | 48 | | |||
47 | #include <config-runtime.h> | 49 | #include <config-runtime.h> | ||
50 | #include "wsdiscoverer.h" | ||||
51 | #include "dnssddiscoverer.h" | ||||
48 | 52 | | |||
49 | using namespace KIO; | 53 | using namespace KIO; | ||
50 | 54 | | |||
51 | int SMBSlave::cache_stat(const SMBUrl &url, struct stat* st ) | 55 | int SMBSlave::cache_stat(const SMBUrl &url, struct stat* st ) | ||
52 | { | 56 | { | ||
53 | int cacheStatErr; | 57 | int cacheStatErr; | ||
54 | int result = smbc_stat( url.toSmbcUrl(), st); | 58 | int result = smbc_stat( url.toSmbcUrl(), st); | ||
55 | if (result == 0){ | 59 | if (result == 0){ | ||
▲ Show 20 Lines • Show All 307 Lines • ▼ Show 20 Line(s) | 365 | if ( dirp->smbc_type == SMBC_SERVER || dirp->smbc_type == SMBC_WORKGROUP ) { | |||
363 | udsName[0] = dirpName.at( 0 ).toUpper(); | 367 | udsName[0] = dirpName.at( 0 ).toUpper(); | ||
364 | if ( !comment.isEmpty() && dirp->smbc_type == SMBC_SERVER ) | 368 | if ( !comment.isEmpty() && dirp->smbc_type == SMBC_SERVER ) | ||
365 | udsName += " (" + comment + ')'; | 369 | udsName += " (" + comment + ')'; | ||
366 | } else | 370 | } else | ||
367 | udsName = dirpName; | 371 | udsName = dirpName; | ||
368 | 372 | | |||
369 | qCDebug(KIO_SMB_LOG) << "dirp->name " << dirp->name << " " << dirpName << " '" << comment << "'" << " " << dirp->smbc_type; | 373 | qCDebug(KIO_SMB_LOG) << "dirp->name " << dirp->name << " " << dirpName << " '" << comment << "'" << " " << dirp->smbc_type; | ||
370 | 374 | | |||
371 | udsentry.fastInsert( KIO::UDSEntry::UDS_NAME, udsName ); | 375 | udsentry.fastInsert( KIO::UDSEntry::UDS_NAME, udsName ); | ||
meven: remove | |||||
372 | 376 | | |||
373 | // Mark all administrative shares, e.g ADMIN$, as hidden. #197903 | 377 | // Mark all administrative shares, e.g ADMIN$, as hidden. #197903 | ||
374 | if (dirpName.endsWith(QLatin1Char('$'))) { | 378 | if (dirpName.endsWith(QLatin1Char('$'))) { | ||
375 | //qCDebug(KIO_SMB_LOG) << dirpName << "marked as hidden"; | 379 | //qCDebug(KIO_SMB_LOG) << dirpName << "marked as hidden"; | ||
376 | udsentry.fastInsert(KIO::UDSEntry::UDS_HIDDEN, 1); | 380 | udsentry.fastInsert(KIO::UDSEntry::UDS_HIDDEN, 1); | ||
377 | } | 381 | } | ||
378 | 382 | | |||
379 | if (udsName == ".") | 383 | if (udsName == ".") | ||
▲ Show 20 Lines • Show All 78 Lines • ▼ Show 20 Line(s) | 460 | { | |||
458 | // TODO: we don't handle SMBC_IPC_SHARE, SMBC_PRINTER_SHARE | 462 | // TODO: we don't handle SMBC_IPC_SHARE, SMBC_PRINTER_SHARE | ||
459 | // SMBC_LINK, SMBC_COMMS_SHARE | 463 | // SMBC_LINK, SMBC_COMMS_SHARE | ||
460 | //SlaveBase::error(ERR_INTERNAL, TEXT_UNSUPPORTED_FILE_TYPE); | 464 | //SlaveBase::error(ERR_INTERNAL, TEXT_UNSUPPORTED_FILE_TYPE); | ||
461 | // continue; | 465 | // continue; | ||
462 | } | 466 | } | ||
463 | udsentry.clear(); | 467 | udsentry.clear(); | ||
464 | } while (dirp); // checked already in the head | 468 | } while (dirp); // checked already in the head | ||
465 | 469 | | |||
466 | listDNSSD(udsentry, url, direntCount); | 470 | // Run service discovery if the path is root. This augments | ||
471 | // "native" results from libsmbclient. | ||||
472 | auto normalizedUrl = url.adjusted(QUrl::NormalizePathSegments); | ||||
473 | if (normalizedUrl.path().isEmpty()) { | ||||
474 | qCDebug(KIO_SMB_LOG) << "Trying modern discovery (dnssd/wsdiscovery)"; | ||||
475 | | ||||
476 | QEventLoop e; | ||||
477 | | ||||
478 | UDSEntryList list; | ||||
dfaure: lol | |||||
479 | | ||||
480 | const auto flushEntries = [this, &list]() { | ||||
481 | if (list.isEmpty()) { | ||||
482 | return; | ||||
483 | } | ||||
484 | listEntries(list); | ||||
485 | list.clear(); | ||||
486 | }; | ||||
487 | | ||||
488 | const auto quitLoop = [&e, &flushEntries]() { | ||||
489 | flushEntries(); | ||||
490 | e.quit(); | ||||
491 | }; | ||||
492 | | ||||
493 | // Since slavebase has no eventloop it wont publish results | ||||
dfaure: isEmpty() is more readable. | |||||
494 | // on a timer, since we do not know how long our discovery | ||||
495 | // will take this is super meh because we may appear | ||||
496 | // stuck for a while. Implement our own listing system | ||||
497 | // based on QTimer to mitigate. | ||||
498 | QTimer sendTimer; | ||||
499 | sendTimer.setInterval(300); | ||||
500 | connect(&sendTimer, &QTimer::timeout, this, flushEntries); | ||||
501 | sendTimer.start(); | ||||
502 | | ||||
With my suggestion to return a udsentry, this whole lambda becomes list.append(discovery->toEntry()) (another move, no copy) dfaure: With my suggestion to return a udsentry, this whole lambda becomes
`list.append(discovery… | |||||
503 | DNSSDDiscoverer d; | ||||
504 | WSDiscoverer w; | ||||
505 | | ||||
506 | const QList<Discoverer *> discoverers {&d, &w}; | ||||
507 | | ||||
508 | auto appendDiscovery = [&](Discovery::Ptr discovery) { | ||||
509 | list.append(discovery->toEntry()); | ||||
510 | }; | ||||
dfaure: const QList.... to avoid detaching in range-for below | |||||
511 | | ||||
512 | auto maybeFinished = [&] { // finishes if all discoveries finished | ||||
513 | bool allFinished = true; | ||||
514 | for (auto discoverer : discoverers) { | ||||
515 | allFinished = allFinished && discoverer->isFinished(); | ||||
516 | } | ||||
517 | if (allFinished) { | ||||
518 | quitLoop(); | ||||
never use &= for booleans, it's not meant for that (it won't work for '1' and '2' which are both 'true' for booleans) allFinished = allFinished && discoverer->isFinished() Unfortunately there's no &&= in C++. dfaure: never use &= for booleans, it's not meant for that
(it won't work for '1' and '2' which are… | |||||
519 | } | ||||
520 | }; | ||||
521 | | ||||
522 | connect(&d, &DNSSDDiscoverer::newDiscovery, this, appendDiscovery); | ||||
523 | connect(&w, &WSDiscoverer::newDiscovery, this, appendDiscovery); | ||||
524 | | ||||
525 | connect(&d, &DNSSDDiscoverer::finished, this, maybeFinished); | ||||
526 | connect(&w, &WSDiscoverer::finished, this, maybeFinished); | ||||
527 | | ||||
528 | d.start(); | ||||
529 | w.start(); | ||||
530 | | ||||
531 | QTimer::singleShot(16000, &e, quitLoop); // max execution time! | ||||
532 | e.exec(); | ||||
533 | | ||||
534 | qCDebug(KIO_SMB_LOG) << "Modern discovery finished."; | ||||
535 | } | ||||
467 | 536 | | |||
468 | if (dir_is_root) { | 537 | if (dir_is_root) { | ||
469 | udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); | 538 | udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); | ||
meven: qCDebug or remove | |||||
470 | udsentry.fastInsert(KIO::UDSEntry::UDS_NAME, "."); | 539 | udsentry.fastInsert(KIO::UDSEntry::UDS_NAME, "."); | ||
471 | udsentry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH)); | 540 | udsentry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH)); | ||
472 | } | 541 | } | ||
473 | else | 542 | else | ||
474 | { | 543 | { | ||
475 | udsentry.fastInsert(KIO::UDSEntry::UDS_NAME, "."); | 544 | udsentry.fastInsert(KIO::UDSEntry::UDS_NAME, "."); | ||
meven: qCDebug or remove | |||||
476 | const int statErr = browse_stat_path(m_current_url, udsentry); | 545 | const int statErr = browse_stat_path(m_current_url, udsentry); | ||
477 | if (statErr) | 546 | if (statErr) | ||
478 | { | 547 | { | ||
479 | if (statErr == ENOENT || statErr == ENOTDIR) | 548 | if (statErr == ENOENT || statErr == ENOTDIR) | ||
480 | { | 549 | { | ||
481 | reportWarning(m_current_url, statErr); | 550 | reportWarning(m_current_url, statErr); | ||
482 | } | 551 | } | ||
483 | // Create a default UDSEntry if we could not stat the actual directory | 552 | // Create a default UDSEntry if we could not stat the actual directory | ||
Show All 25 Lines | 564 | { | |||
509 | 578 | | |||
510 | reportError(m_current_url, errNum); | 579 | reportError(m_current_url, errNum); | ||
511 | return; | 580 | return; | ||
512 | } | 581 | } | ||
513 | 582 | | |||
514 | finished(); | 583 | finished(); | ||
515 | } | 584 | } | ||
516 | 585 | | |||
517 | void SMBSlave::listDNSSD(UDSEntry &udsentry, const QUrl &url, const uint direntCount) | | |||
518 | { | | |||
519 | // Certain versions of KDNSSD suffer from signal races which can easily | | |||
520 | // deadlock the slave. | | |||
521 | #ifndef HAVE_KDNSSD_WITH_SIGNAL_RACE_PROTECTION | | |||
522 | return; | | |||
523 | #endif // HAVE_KDNSSD_WITH_SIGNAL_RACE_PROTECTION | | |||
524 | | ||||
525 | // This entire method act as fallback logic iff SMB discovery is not working | | |||
526 | // (for example when using a protocol version that doesn't have discovery). | | |||
527 | // As such we can return if entries were discovered or the URL is not '/' | | |||
528 | auto normalizedUrl = url.adjusted(QUrl::NormalizePathSegments); | | |||
529 | if (direntCount > 0 || !normalizedUrl.path().isEmpty()) { | | |||
530 | return; | | |||
531 | } | | |||
532 | | ||||
533 | // Slaves have no event loop, start one for the poll. | | |||
534 | // KDNSSD has an internal timeout which may trigger if this takes too long | | |||
535 | // so in theory this should not ever be able to get stuck. | | |||
536 | // The eventloop runs until the discovery is finished. The finished slot | | |||
537 | // will quit it. | | |||
538 | QList<KDNSSD::RemoteService::Ptr> services; | | |||
539 | QEventLoop e; | | |||
540 | KDNSSD::ServiceBrowser browser(QStringLiteral("_smb._tcp")); | | |||
541 | connect(&browser, &KDNSSD::ServiceBrowser::serviceAdded, | | |||
542 | this, [&services](KDNSSD::RemoteService::Ptr service){ | | |||
543 | qCDebug(KIO_SMB_LOG) << "DNSSD added:" | | |||
544 | << service->serviceName() | | |||
545 | << service->type() | | |||
546 | << service->domain() | | |||
547 | << service->hostName() | | |||
548 | << service->port(); | | |||
549 | // Manual contains check. We need to use the == of the underlying | | |||
550 | // objects, not the pointers. The same service may have >1 | | |||
551 | // RemoteService* instances representing it, so the == impl of | | |||
552 | // RemoteService::Ptr is useless here. | | |||
553 | for (const auto &it : services) { | | |||
554 | if (*service == *it) { | | |||
555 | return; | | |||
556 | } | | |||
557 | } | | |||
558 | // Schedule resolution of hostname. We'll later call resolve | | |||
559 | // which will block until the resolution is done. This basically | | |||
560 | // gives us a head start. | | |||
561 | service->resolveAsync(); | | |||
562 | services.append(service); | | |||
563 | }); | | |||
564 | connect(&browser, &KDNSSD::ServiceBrowser::serviceRemoved, | | |||
565 | this, [&services](KDNSSD::RemoteService::Ptr service){ | | |||
566 | qCDebug(KIO_SMB_LOG) << "DNSSD removed:" | | |||
567 | << service->serviceName() | | |||
568 | << service->type() | | |||
569 | << service->domain() | | |||
570 | << service->hostName() | | |||
571 | << service->port(); | | |||
572 | services.removeAll(service); | | |||
573 | }); | | |||
574 | connect(&browser, &KDNSSD::ServiceBrowser::finished, | | |||
575 | this, [&]() { | | |||
576 | browser.disconnect(); // Stop sending anything, we'll exit here. | | |||
577 | // Resolution still requires an event loop. So, let the resolutions | | |||
578 | // finish and then quit the loop. Services that fail resolution | | |||
579 | // get dropped since we won't be able to access them properly. | | |||
580 | for (auto it = services.begin(); it != services.end(); ++it) { | | |||
581 | auto service = *it; | | |||
582 | if (!service->resolve()) { | | |||
583 | qCWarning(KIO_SMB_LOG) << "Failed to resolve DNSSD service" | | |||
584 | << service->serviceName(); | | |||
585 | it = services.erase(it); | | |||
586 | } | | |||
587 | } | | |||
588 | e.quit(); | | |||
589 | }); | | |||
590 | browser.startBrowse(); | | |||
591 | e.exec(); | | |||
592 | | ||||
593 | for (const auto &service : services) { | | |||
594 | udsentry.fastInsert(KIO::UDSEntry::UDS_NAME, service->serviceName()); | | |||
595 | | ||||
596 | udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); | | |||
597 | udsentry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)); | | |||
598 | | ||||
599 | // TODO: it may be better to resolve the host to an ip address. dnssd | | |||
600 | // being able to find a service doesn't mean name resolution is | | |||
601 | // properly set up for its domain. So, we may not be able to resolve | | |||
602 | // this without help from avahi. OTOH KDNSSD doesn't have API for this | | |||
603 | // and from a platform POV we should probably assume that if avahi | | |||
604 | // is functional it is also set up as resolution provider. | | |||
605 | // Given the plugin design on glibc's libnss however I am not sure | | |||
606 | // that assumption will be true all the time. ~sitter, 2018 | | |||
607 | QUrl u(QStringLiteral("smb://")); | | |||
608 | u.setHost(service->hostName()); | | |||
609 | if (service->port() > 0 && service->port() != 445 /* default smb */) { | | |||
610 | u.setPort(service->port()); | | |||
611 | } | | |||
612 | | ||||
613 | udsentry.fastInsert(KIO::UDSEntry::UDS_URL, u.url()); | | |||
614 | udsentry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, | | |||
615 | QStringLiteral("application/x-smb-server")); | | |||
616 | | ||||
617 | listEntry(udsentry); | | |||
618 | udsentry.clear(); | | |||
619 | } | | |||
620 | | ||||
621 | // NOTE: workgroups cannot be properly resolved because libsmbclient | | |||
622 | // seems to lack the appropriate API to pull this data out of netbios. | | |||
623 | // Netbios is also not working on IPv6 and its replacement (LLMNR) | | |||
624 | // doesn't support the concept of workgroups. | | |||
625 | } | | |||
626 | | ||||
627 | void SMBSlave::fileSystemFreeSpace(const QUrl& url) | 586 | void SMBSlave::fileSystemFreeSpace(const QUrl& url) | ||
628 | { | 587 | { | ||
629 | qCDebug(KIO_SMB_LOG) << url; | 588 | qCDebug(KIO_SMB_LOG) << url; | ||
630 | 589 | | |||
631 | // Avoid crashing in smbc_fstatvfs below when | 590 | // Avoid crashing in smbc_fstatvfs below when | ||
632 | // requesting free space for smb:// which doesn't | 591 | // requesting free space for smb:// which doesn't | ||
633 | // make sense to do to begin with | 592 | // make sense to do to begin with | ||
634 | if (url.host().isEmpty()) { | 593 | if (url.host().isEmpty()) { | ||
Show All 39 Lines |
remove