2009-09-25 18:32:00 +02:00
|
|
|
/*
|
2017-01-03 20:48:59 +01:00
|
|
|
* Copyright 2003-2017 The Music Player Daemon Project
|
2009-09-25 18:32:00 +02:00
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2009-11-11 14:15:34 +01:00
|
|
|
#include "config.h" /* must be first for large file support */
|
2013-01-02 19:22:15 +01:00
|
|
|
#include "InotifyUpdate.hxx"
|
|
|
|
#include "InotifySource.hxx"
|
|
|
|
#include "InotifyQueue.hxx"
|
2013-09-27 22:31:24 +02:00
|
|
|
#include "InotifyDomain.hxx"
|
2014-02-07 22:16:37 +01:00
|
|
|
#include "storage/StorageInterface.hxx"
|
2013-10-17 21:59:35 +02:00
|
|
|
#include "fs/AllocatedPath.hxx"
|
2015-02-28 20:42:50 +01:00
|
|
|
#include "fs/FileInfo.hxx"
|
2013-09-27 22:31:24 +02:00
|
|
|
#include "Log.hxx"
|
2009-09-25 18:32:00 +02:00
|
|
|
|
2013-10-17 01:16:46 +02:00
|
|
|
#include <string>
|
2013-01-30 15:41:38 +01:00
|
|
|
#include <map>
|
2013-01-30 15:39:29 +01:00
|
|
|
#include <forward_list>
|
2013-01-30 15:41:38 +01:00
|
|
|
|
2009-09-25 18:32:00 +02:00
|
|
|
#include <assert.h>
|
|
|
|
#include <sys/inotify.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <dirent.h>
|
|
|
|
|
2013-10-20 13:51:55 +02:00
|
|
|
static constexpr unsigned IN_MASK =
|
2009-09-25 18:32:00 +02:00
|
|
|
#ifdef IN_ONLYDIR
|
2013-10-20 13:51:55 +02:00
|
|
|
IN_ONLYDIR|
|
2009-09-25 18:32:00 +02:00
|
|
|
#endif
|
2013-10-20 13:51:55 +02:00
|
|
|
IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF
|
|
|
|
|IN_MOVE|IN_MOVE_SELF;
|
2009-09-25 18:32:00 +02:00
|
|
|
|
2013-01-30 16:36:58 +01:00
|
|
|
struct WatchDirectory {
|
|
|
|
WatchDirectory *parent;
|
2009-09-25 18:32:00 +02:00
|
|
|
|
2013-10-17 21:59:35 +02:00
|
|
|
AllocatedPath name;
|
2009-09-25 18:32:00 +02:00
|
|
|
|
|
|
|
int descriptor;
|
|
|
|
|
2013-01-30 15:39:29 +01:00
|
|
|
std::forward_list<WatchDirectory> children;
|
2013-01-30 16:35:56 +01:00
|
|
|
|
2013-10-17 01:16:46 +02:00
|
|
|
template<typename N>
|
|
|
|
WatchDirectory(WatchDirectory *_parent, N &&_name,
|
2013-01-30 16:35:56 +01:00
|
|
|
int _descriptor)
|
2013-10-17 01:16:46 +02:00
|
|
|
:parent(_parent), name(std::forward<N>(_name)),
|
2013-01-30 15:39:29 +01:00
|
|
|
descriptor(_descriptor) {}
|
|
|
|
|
|
|
|
WatchDirectory(const WatchDirectory &) = delete;
|
|
|
|
WatchDirectory &operator=(const WatchDirectory &) = delete;
|
2014-02-07 22:24:26 +01:00
|
|
|
|
|
|
|
gcc_pure
|
2017-05-08 14:44:49 +02:00
|
|
|
unsigned GetDepth() const noexcept;
|
2014-02-07 22:24:26 +01:00
|
|
|
|
|
|
|
gcc_pure
|
2017-05-08 14:44:49 +02:00
|
|
|
AllocatedPath GetUriFS() const noexcept;
|
2009-09-25 18:32:00 +02:00
|
|
|
};
|
|
|
|
|
2013-01-14 09:52:35 +01:00
|
|
|
static InotifySource *inotify_source;
|
2013-01-14 10:10:21 +01:00
|
|
|
static InotifyQueue *inotify_queue;
|
2009-09-25 18:32:00 +02:00
|
|
|
|
2010-04-13 21:01:19 +02:00
|
|
|
static unsigned inotify_max_depth;
|
2013-01-30 16:35:24 +01:00
|
|
|
static WatchDirectory *inotify_root;
|
2013-01-30 16:36:58 +01:00
|
|
|
static std::map<int, WatchDirectory *> inotify_directories;
|
2009-09-25 18:32:00 +02:00
|
|
|
|
|
|
|
static void
|
2013-01-30 16:36:58 +01:00
|
|
|
tree_add_watch_directory(WatchDirectory *directory)
|
2009-09-25 18:32:00 +02:00
|
|
|
{
|
2013-01-30 15:41:38 +01:00
|
|
|
inotify_directories.insert(std::make_pair(directory->descriptor,
|
|
|
|
directory));
|
2009-09-25 18:32:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2013-01-30 16:36:58 +01:00
|
|
|
tree_remove_watch_directory(WatchDirectory *directory)
|
2009-09-25 18:32:00 +02:00
|
|
|
{
|
2013-01-30 15:41:38 +01:00
|
|
|
auto i = inotify_directories.find(directory->descriptor);
|
|
|
|
assert(i != inotify_directories.end());
|
|
|
|
inotify_directories.erase(i);
|
2009-09-25 18:32:00 +02:00
|
|
|
}
|
|
|
|
|
2013-01-30 16:36:58 +01:00
|
|
|
static WatchDirectory *
|
2009-09-25 18:32:00 +02:00
|
|
|
tree_find_watch_directory(int wd)
|
|
|
|
{
|
2013-01-30 15:41:38 +01:00
|
|
|
auto i = inotify_directories.find(wd);
|
|
|
|
if (i == inotify_directories.end())
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
return i->second;
|
2009-09-25 18:32:00 +02:00
|
|
|
}
|
|
|
|
|
2013-01-30 15:39:29 +01:00
|
|
|
static void
|
|
|
|
disable_watch_directory(WatchDirectory &directory)
|
|
|
|
{
|
|
|
|
tree_remove_watch_directory(&directory);
|
|
|
|
|
|
|
|
for (WatchDirectory &child : directory.children)
|
|
|
|
disable_watch_directory(child);
|
|
|
|
|
|
|
|
inotify_source->Remove(directory.descriptor);
|
|
|
|
}
|
|
|
|
|
2009-09-25 18:32:00 +02:00
|
|
|
static void
|
2013-01-30 16:36:58 +01:00
|
|
|
remove_watch_directory(WatchDirectory *directory)
|
2009-09-25 18:32:00 +02:00
|
|
|
{
|
2013-10-19 18:19:03 +02:00
|
|
|
assert(directory != nullptr);
|
2010-05-30 23:38:56 +02:00
|
|
|
|
2013-10-19 18:19:03 +02:00
|
|
|
if (directory->parent == nullptr) {
|
2013-09-27 22:31:24 +02:00
|
|
|
LogWarning(inotify_domain,
|
|
|
|
"music directory was removed - "
|
|
|
|
"cannot continue to watch it");
|
2010-05-30 23:38:56 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-01-30 15:39:29 +01:00
|
|
|
disable_watch_directory(*directory);
|
2009-09-25 18:32:00 +02:00
|
|
|
|
2013-01-30 15:39:29 +01:00
|
|
|
/* remove it from the parent, which effectively deletes it */
|
|
|
|
directory->parent->children.remove_if([directory](const WatchDirectory &child){
|
|
|
|
return &child == directory;
|
|
|
|
});
|
2009-09-25 18:32:00 +02:00
|
|
|
}
|
|
|
|
|
2014-02-07 22:24:26 +01:00
|
|
|
AllocatedPath
|
2017-05-08 14:44:49 +02:00
|
|
|
WatchDirectory::GetUriFS() const noexcept
|
2009-09-25 18:32:00 +02:00
|
|
|
{
|
2014-02-07 22:24:26 +01:00
|
|
|
if (parent == nullptr)
|
2018-01-17 12:17:41 +01:00
|
|
|
return nullptr;
|
2009-09-25 18:32:00 +02:00
|
|
|
|
2014-02-07 22:24:26 +01:00
|
|
|
const auto uri = parent->GetUriFS();
|
2013-10-17 01:16:46 +02:00
|
|
|
if (uri.IsNull())
|
2014-02-07 22:24:26 +01:00
|
|
|
return name;
|
2009-09-25 18:32:00 +02:00
|
|
|
|
2014-02-07 22:24:26 +01:00
|
|
|
return AllocatedPath::Build(uri, name);
|
2009-09-25 18:32:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* we don't look at "." / ".." nor files with newlines in their name */
|
|
|
|
static bool skip_path(const char *path)
|
|
|
|
{
|
|
|
|
return (path[0] == '.' && path[1] == 0) ||
|
|
|
|
(path[0] == '.' && path[1] == '.' && path[2] == 0) ||
|
2013-10-19 18:19:03 +02:00
|
|
|
strchr(path, '\n') != nullptr;
|
2009-09-25 18:32:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2013-01-30 16:36:58 +01:00
|
|
|
recursive_watch_subdirectories(WatchDirectory *directory,
|
2013-10-17 21:59:35 +02:00
|
|
|
const AllocatedPath &path_fs, unsigned depth)
|
2009-09-25 18:32:00 +02:00
|
|
|
{
|
|
|
|
DIR *dir;
|
|
|
|
struct dirent *ent;
|
|
|
|
|
2013-10-19 18:19:03 +02:00
|
|
|
assert(directory != nullptr);
|
2010-04-13 21:01:19 +02:00
|
|
|
assert(depth <= inotify_max_depth);
|
2013-10-17 01:16:46 +02:00
|
|
|
assert(!path_fs.IsNull());
|
2009-09-25 18:32:00 +02:00
|
|
|
|
2010-04-13 21:01:19 +02:00
|
|
|
++depth;
|
|
|
|
|
|
|
|
if (depth > inotify_max_depth)
|
|
|
|
return;
|
|
|
|
|
2013-10-17 01:16:46 +02:00
|
|
|
dir = opendir(path_fs.c_str());
|
2013-10-19 18:19:03 +02:00
|
|
|
if (dir == nullptr) {
|
2013-09-27 22:31:24 +02:00
|
|
|
FormatErrno(inotify_domain,
|
2013-10-17 01:16:46 +02:00
|
|
|
"Failed to open directory %s", path_fs.c_str());
|
2009-09-25 18:32:00 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
while ((ent = readdir(dir))) {
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (skip_path(ent->d_name))
|
|
|
|
continue;
|
|
|
|
|
2013-10-17 21:59:35 +02:00
|
|
|
const auto child_path_fs =
|
|
|
|
AllocatedPath::Build(path_fs, ent->d_name);
|
2015-02-28 20:42:50 +01:00
|
|
|
|
|
|
|
FileInfo fi;
|
2016-10-28 10:23:05 +02:00
|
|
|
try {
|
|
|
|
fi = FileInfo(child_path_fs);
|
2017-12-19 10:56:23 +01:00
|
|
|
} catch (...) {
|
|
|
|
LogError(std::current_exception());
|
2009-09-25 18:32:00 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-02-28 20:42:50 +01:00
|
|
|
if (!fi.IsDirectory())
|
2009-09-25 18:32:00 +02:00
|
|
|
continue;
|
|
|
|
|
2016-10-28 10:23:05 +02:00
|
|
|
try {
|
|
|
|
ret = inotify_source->Add(child_path_fs.c_str(),
|
|
|
|
IN_MASK);
|
2017-12-19 10:56:23 +01:00
|
|
|
} catch (...) {
|
|
|
|
FormatError(std::current_exception(),
|
2013-10-17 01:16:46 +02:00
|
|
|
"Failed to register %s",
|
|
|
|
child_path_fs.c_str());
|
2009-09-25 18:32:00 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2013-01-30 16:35:56 +01:00
|
|
|
WatchDirectory *child = tree_find_watch_directory(ret);
|
2013-10-17 01:16:46 +02:00
|
|
|
if (child != nullptr)
|
2009-09-25 18:32:00 +02:00
|
|
|
/* already being watched */
|
|
|
|
continue;
|
|
|
|
|
2013-10-17 01:16:46 +02:00
|
|
|
directory->children.emplace_front(directory,
|
2013-10-17 21:59:35 +02:00
|
|
|
AllocatedPath::FromFS(ent->d_name),
|
2013-10-17 01:16:46 +02:00
|
|
|
ret);
|
2013-01-30 15:39:29 +01:00
|
|
|
child = &directory->children.front();
|
2009-09-25 18:32:00 +02:00
|
|
|
|
|
|
|
tree_add_watch_directory(child);
|
|
|
|
|
2010-04-13 21:01:19 +02:00
|
|
|
recursive_watch_subdirectories(child, child_path_fs, depth);
|
2009-09-25 18:32:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
closedir(dir);
|
|
|
|
}
|
|
|
|
|
2013-08-04 23:48:01 +02:00
|
|
|
gcc_pure
|
2014-02-07 22:24:26 +01:00
|
|
|
unsigned
|
2017-05-08 14:44:49 +02:00
|
|
|
WatchDirectory::GetDepth() const noexcept
|
2010-04-13 21:01:19 +02:00
|
|
|
{
|
2014-02-07 22:24:26 +01:00
|
|
|
const WatchDirectory *d = this;
|
2010-04-13 21:01:19 +02:00
|
|
|
unsigned depth = 0;
|
2013-10-19 18:19:03 +02:00
|
|
|
while ((d = d->parent) != nullptr)
|
2010-04-13 21:01:19 +02:00
|
|
|
++depth;
|
|
|
|
|
|
|
|
return depth;
|
|
|
|
}
|
|
|
|
|
2009-09-25 18:32:00 +02:00
|
|
|
static void
|
|
|
|
mpd_inotify_callback(int wd, unsigned mask,
|
2013-08-04 23:48:01 +02:00
|
|
|
gcc_unused const char *name, gcc_unused void *ctx)
|
2009-09-25 18:32:00 +02:00
|
|
|
{
|
2013-01-30 16:36:58 +01:00
|
|
|
WatchDirectory *directory;
|
2009-09-25 18:32:00 +02:00
|
|
|
|
2013-09-27 22:31:24 +02:00
|
|
|
/*FormatDebug(inotify_domain, "wd=%d mask=0x%x name='%s'", wd, mask, name);*/
|
2009-09-25 18:32:00 +02:00
|
|
|
|
|
|
|
directory = tree_find_watch_directory(wd);
|
2013-10-19 18:19:03 +02:00
|
|
|
if (directory == nullptr)
|
2009-09-25 18:32:00 +02:00
|
|
|
return;
|
|
|
|
|
2014-02-07 22:24:26 +01:00
|
|
|
const auto uri_fs = directory->GetUriFS();
|
2009-09-25 18:32:00 +02:00
|
|
|
|
|
|
|
if ((mask & (IN_DELETE_SELF|IN_MOVE_SELF)) != 0) {
|
|
|
|
remove_watch_directory(directory);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((mask & (IN_ATTRIB|IN_CREATE|IN_MOVE)) != 0 &&
|
|
|
|
(mask & IN_ISDIR) != 0) {
|
|
|
|
/* a sub directory was changed: register those in
|
|
|
|
inotify */
|
2014-02-07 22:30:01 +01:00
|
|
|
const auto &root = inotify_root->name;
|
2012-02-13 20:10:19 +01:00
|
|
|
|
2013-10-17 21:59:35 +02:00
|
|
|
const auto path_fs = uri_fs.IsNull()
|
2013-10-17 01:16:46 +02:00
|
|
|
? root
|
2013-10-17 21:59:35 +02:00
|
|
|
: AllocatedPath::Build(root, uri_fs.c_str());
|
2009-09-25 18:32:00 +02:00
|
|
|
|
2010-04-13 21:01:19 +02:00
|
|
|
recursive_watch_subdirectories(directory, path_fs,
|
2014-02-07 22:24:26 +01:00
|
|
|
directory->GetDepth());
|
2009-09-25 18:32:00 +02:00
|
|
|
}
|
|
|
|
|
2010-04-13 23:37:44 +02:00
|
|
|
if ((mask & (IN_CLOSE_WRITE|IN_MOVE|IN_DELETE)) != 0 ||
|
|
|
|
/* at the maximum depth, we watch out for newly created
|
|
|
|
directories */
|
2014-02-07 22:24:26 +01:00
|
|
|
(directory->GetDepth() == inotify_max_depth &&
|
2010-04-13 23:37:44 +02:00
|
|
|
(mask & (IN_CREATE|IN_ISDIR)) == (IN_CREATE|IN_ISDIR))) {
|
|
|
|
/* a file was changed, or a directory was
|
2009-09-25 18:32:00 +02:00
|
|
|
moved/deleted: queue a database update */
|
|
|
|
|
2013-10-17 01:16:46 +02:00
|
|
|
if (!uri_fs.IsNull()) {
|
|
|
|
const std::string uri_utf8 = uri_fs.ToUTF8();
|
2013-01-23 21:26:38 +01:00
|
|
|
if (!uri_utf8.empty())
|
|
|
|
inotify_queue->Enqueue(uri_utf8.c_str());
|
2013-01-03 09:40:37 +01:00
|
|
|
}
|
2013-01-23 21:26:38 +01:00
|
|
|
else
|
|
|
|
inotify_queue->Enqueue("");
|
2009-09-25 18:32:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2014-02-07 22:16:37 +01:00
|
|
|
mpd_inotify_init(EventLoop &loop, Storage &storage, UpdateService &update,
|
|
|
|
unsigned max_depth)
|
2009-09-25 18:32:00 +02:00
|
|
|
{
|
2013-09-27 22:31:24 +02:00
|
|
|
LogDebug(inotify_domain, "initializing inotify");
|
2009-09-25 18:32:00 +02:00
|
|
|
|
2014-02-07 22:16:37 +01:00
|
|
|
const auto path = storage.MapFS("");
|
2013-01-23 19:38:09 +01:00
|
|
|
if (path.IsNull()) {
|
2013-09-27 22:31:24 +02:00
|
|
|
LogDebug(inotify_domain, "no music directory configured");
|
2009-09-25 18:32:00 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-10-28 10:23:05 +02:00
|
|
|
try {
|
|
|
|
inotify_source = new InotifySource(loop,
|
|
|
|
mpd_inotify_callback,
|
|
|
|
nullptr);
|
2017-12-19 10:56:23 +01:00
|
|
|
} catch (...) {
|
|
|
|
LogError(std::current_exception());
|
2009-09-25 18:32:00 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-04-13 21:01:19 +02:00
|
|
|
inotify_max_depth = max_depth;
|
|
|
|
|
2016-10-28 10:23:05 +02:00
|
|
|
int descriptor;
|
|
|
|
try {
|
|
|
|
descriptor = inotify_source->Add(path.c_str(), IN_MASK);
|
2017-12-19 10:56:23 +01:00
|
|
|
} catch (...) {
|
|
|
|
LogError(std::current_exception());
|
2013-01-14 09:52:35 +01:00
|
|
|
delete inotify_source;
|
2013-10-19 18:19:03 +02:00
|
|
|
inotify_source = nullptr;
|
2009-09-25 18:32:00 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-10-17 01:16:46 +02:00
|
|
|
inotify_root = new WatchDirectory(nullptr, path, descriptor);
|
2009-09-25 18:32:00 +02:00
|
|
|
|
2013-01-30 16:35:24 +01:00
|
|
|
tree_add_watch_directory(inotify_root);
|
|
|
|
|
2013-10-17 01:16:46 +02:00
|
|
|
recursive_watch_subdirectories(inotify_root, path, 0);
|
2009-09-25 18:32:00 +02:00
|
|
|
|
2014-01-29 20:16:43 +01:00
|
|
|
inotify_queue = new InotifyQueue(loop, update);
|
2009-09-25 18:32:00 +02:00
|
|
|
|
2013-09-27 22:31:24 +02:00
|
|
|
LogDebug(inotify_domain, "watching music directory");
|
2009-09-25 18:32:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2017-05-08 14:44:49 +02:00
|
|
|
mpd_inotify_finish(void) noexcept
|
2009-09-25 18:32:00 +02:00
|
|
|
{
|
2013-10-19 18:19:03 +02:00
|
|
|
if (inotify_source == nullptr)
|
2009-09-25 18:32:00 +02:00
|
|
|
return;
|
|
|
|
|
2013-01-14 10:10:21 +01:00
|
|
|
delete inotify_queue;
|
2013-01-14 09:52:35 +01:00
|
|
|
delete inotify_source;
|
2013-01-30 15:39:29 +01:00
|
|
|
delete inotify_root;
|
2013-01-30 15:41:38 +01:00
|
|
|
inotify_directories.clear();
|
2009-09-25 18:32:00 +02:00
|
|
|
}
|