
libsmbclient is not thread-safe nor reentrant. We must protect all function calls with a global mutex, unfortunately.
289 lines
6.2 KiB
C++
289 lines
6.2 KiB
C++
/*
|
|
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
|
* http://www.musicpd.org
|
|
*
|
|
* 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 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "SmbclientNeighborPlugin.hxx"
|
|
#include "lib/smbclient/Init.hxx"
|
|
#include "lib/smbclient/Domain.hxx"
|
|
#include "lib/smbclient/Mutex.hxx"
|
|
#include "neighbor/NeighborPlugin.hxx"
|
|
#include "neighbor/Explorer.hxx"
|
|
#include "neighbor/Listener.hxx"
|
|
#include "neighbor/Info.hxx"
|
|
#include "thread/Mutex.hxx"
|
|
#include "thread/Cond.hxx"
|
|
#include "thread/Thread.hxx"
|
|
#include "thread/Name.hxx"
|
|
#include "util/Macros.hxx"
|
|
#include "util/Domain.hxx"
|
|
#include "util/Error.hxx"
|
|
#include "Log.hxx"
|
|
|
|
#include <libsmbclient.h>
|
|
|
|
#include <list>
|
|
#include <algorithm>
|
|
|
|
class SmbclientNeighborExplorer final : public NeighborExplorer {
|
|
struct Server {
|
|
std::string name, comment;
|
|
|
|
bool alive;
|
|
|
|
Server(std::string &&_name, std::string &&_comment)
|
|
:name(std::move(_name)), comment(std::move(_comment)),
|
|
alive(true) {}
|
|
Server(const Server &) = delete;
|
|
|
|
gcc_pure
|
|
bool operator==(const Server &other) const {
|
|
return name == other.name;
|
|
}
|
|
|
|
gcc_pure
|
|
NeighborInfo Export() const {
|
|
return { "smb://" + name + "/", comment };
|
|
}
|
|
};
|
|
|
|
Thread thread;
|
|
|
|
mutable Mutex mutex;
|
|
Cond cond;
|
|
|
|
List list;
|
|
|
|
bool quit;
|
|
|
|
public:
|
|
SmbclientNeighborExplorer(NeighborListener &_listener)
|
|
:NeighborExplorer(_listener) {}
|
|
|
|
/* virtual methods from class NeighborExplorer */
|
|
virtual bool Open(Error &error) override;
|
|
virtual void Close() override;
|
|
virtual List GetList() const override;
|
|
|
|
private:
|
|
void Run();
|
|
void ThreadFunc();
|
|
static void ThreadFunc(void *ctx);
|
|
};
|
|
|
|
bool
|
|
SmbclientNeighborExplorer::Open(Error &error)
|
|
{
|
|
quit = false;
|
|
return thread.Start(ThreadFunc, this, error);
|
|
}
|
|
|
|
void
|
|
SmbclientNeighborExplorer::Close()
|
|
{
|
|
mutex.lock();
|
|
quit = true;
|
|
cond.signal();
|
|
mutex.unlock();
|
|
|
|
thread.Join();
|
|
}
|
|
|
|
NeighborExplorer::List
|
|
SmbclientNeighborExplorer::GetList() const
|
|
{
|
|
const ScopeLock protect(mutex);
|
|
/*
|
|
List list;
|
|
for (const auto &i : servers)
|
|
list.emplace_front(i.Export());
|
|
*/
|
|
return list;
|
|
}
|
|
|
|
static void
|
|
ReadServer(NeighborExplorer::List &list, const smbc_dirent &e)
|
|
{
|
|
const std::string name(e.name, e.namelen);
|
|
const std::string comment(e.comment, e.commentlen);
|
|
|
|
list.emplace_front("smb://" + name, name + " (" + comment + ")");
|
|
}
|
|
|
|
static void
|
|
ReadServers(NeighborExplorer::List &list, const char *uri);
|
|
|
|
static void
|
|
ReadWorkgroup(NeighborExplorer::List &list, const std::string &name)
|
|
{
|
|
std::string uri = "smb://" + name;
|
|
ReadServers(list, uri.c_str());
|
|
}
|
|
|
|
static void
|
|
ReadEntry(NeighborExplorer::List &list, const smbc_dirent &e)
|
|
{
|
|
switch (e.smbc_type) {
|
|
case SMBC_WORKGROUP:
|
|
ReadWorkgroup(list, std::string(e.name, e.namelen));
|
|
break;
|
|
|
|
case SMBC_SERVER:
|
|
ReadServer(list, e);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
ReadServers(NeighborExplorer::List &list, int fd)
|
|
{
|
|
smbc_dirent *e;
|
|
while ((e = smbc_readdir(fd)) != nullptr)
|
|
ReadEntry(list, *e);
|
|
|
|
smbc_closedir(fd);
|
|
}
|
|
|
|
static void
|
|
ReadServers(NeighborExplorer::List &list, const char *uri)
|
|
{
|
|
int fd = smbc_opendir(uri);
|
|
if (fd >= 0) {
|
|
ReadServers(list, fd);
|
|
smbc_closedir(fd);
|
|
} else
|
|
FormatErrno(smbclient_domain, "smbc_opendir('%s') failed",
|
|
uri);
|
|
}
|
|
|
|
gcc_pure
|
|
static NeighborExplorer::List
|
|
DetectServers()
|
|
{
|
|
NeighborExplorer::List list;
|
|
const ScopeLock protect(smbclient_mutex);
|
|
ReadServers(list, "smb://");
|
|
return list;
|
|
}
|
|
|
|
gcc_pure
|
|
static NeighborExplorer::List::const_iterator
|
|
FindBeforeServerByURI(NeighborExplorer::List::const_iterator prev,
|
|
NeighborExplorer::List::const_iterator end,
|
|
const std::string &uri)
|
|
{
|
|
for (auto i = std::next(prev); i != end; prev = i, i = std::next(prev))
|
|
if (i->uri == uri)
|
|
return prev;
|
|
|
|
return end;
|
|
}
|
|
|
|
inline void
|
|
SmbclientNeighborExplorer::Run()
|
|
{
|
|
List found = DetectServers(), lost;
|
|
|
|
mutex.lock();
|
|
|
|
const auto found_before_begin = found.before_begin();
|
|
const auto found_end = found.end();
|
|
|
|
for (auto prev = list.before_begin(), i = std::next(prev), end = list.end();
|
|
i != end; i = std::next(prev)) {
|
|
auto f = FindBeforeServerByURI(found_before_begin, found_end,
|
|
i->uri);
|
|
if (f != found_end) {
|
|
/* still visible: remove from "found" so we
|
|
don't believe it's a new one */
|
|
*i = std::move(*std::next(f));
|
|
found.erase_after(f);
|
|
prev = i;
|
|
} else {
|
|
/* can't see it anymore: move to "lost" */
|
|
#if defined(__clang__) || GCC_CHECK_VERSION(4,7)
|
|
lost.splice_after(lost.before_begin(), list, prev);
|
|
#else
|
|
/* the forward_list::splice_after() lvalue
|
|
reference overload is missing in gcc 4.6 */
|
|
lost.emplace_front(std::move(*i));
|
|
list.erase_after(prev);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
for (auto prev = found_before_begin, i = std::next(prev);
|
|
i != found_end; prev = i, i = std::next(prev))
|
|
list.push_front(*i);
|
|
|
|
mutex.unlock();
|
|
|
|
for (auto &i : lost)
|
|
listener.LostNeighbor(i);
|
|
|
|
for (auto &i : found)
|
|
listener.FoundNeighbor(i);
|
|
}
|
|
|
|
inline void
|
|
SmbclientNeighborExplorer::ThreadFunc()
|
|
{
|
|
mutex.lock();
|
|
|
|
while (!quit) {
|
|
mutex.unlock();
|
|
|
|
Run();
|
|
|
|
mutex.lock();
|
|
if (quit)
|
|
break;
|
|
|
|
// TODO: sleep for how long?
|
|
cond.timed_wait(mutex, 10000);
|
|
}
|
|
|
|
mutex.unlock();
|
|
}
|
|
|
|
void
|
|
SmbclientNeighborExplorer::ThreadFunc(void *ctx)
|
|
{
|
|
SetThreadName("smbclient");
|
|
|
|
SmbclientNeighborExplorer &e = *(SmbclientNeighborExplorer *)ctx;
|
|
e.ThreadFunc();
|
|
}
|
|
|
|
static NeighborExplorer *
|
|
smbclient_neighbor_create(gcc_unused EventLoop &loop,
|
|
NeighborListener &listener,
|
|
gcc_unused const config_param ¶m,
|
|
gcc_unused Error &error)
|
|
{
|
|
if (!SmbclientInit(error))
|
|
return nullptr;
|
|
|
|
return new SmbclientNeighborExplorer(listener);
|
|
}
|
|
|
|
const NeighborPlugin smbclient_neighbor_plugin = {
|
|
"smbclient",
|
|
smbclient_neighbor_create,
|
|
};
|