InotifyUpdate.cxx 7.9 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2014 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 * 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.
 */

20
#include "config.h" /* must be first for large file support */
Max Kellermann's avatar
Max Kellermann committed
21 22 23
#include "InotifyUpdate.hxx"
#include "InotifySource.hxx"
#include "InotifyQueue.hxx"
24
#include "InotifyDomain.hxx"
25
#include "storage/StorageInterface.hxx"
26
#include "fs/AllocatedPath.hxx"
27
#include "fs/FileSystem.hxx"
28
#include "util/Error.hxx"
29
#include "Log.hxx"
30

31
#include <string>
32
#include <map>
33
#include <forward_list>
34

35 36 37 38 39 40
#include <assert.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <string.h>
#include <dirent.h>

41
static constexpr unsigned IN_MASK =
42
#ifdef IN_ONLYDIR
43
	IN_ONLYDIR|
44
#endif
45 46
	IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF
	|IN_MOVE|IN_MOVE_SELF;
47

48 49
struct WatchDirectory {
	WatchDirectory *parent;
50

51
	AllocatedPath name;
52 53 54

	int descriptor;

55
	std::forward_list<WatchDirectory> children;
56

57 58
	template<typename N>
	WatchDirectory(WatchDirectory *_parent, N &&_name,
59
		       int _descriptor)
60
		:parent(_parent), name(std::forward<N>(_name)),
61 62 63 64
		 descriptor(_descriptor) {}

	WatchDirectory(const WatchDirectory &) = delete;
	WatchDirectory &operator=(const WatchDirectory &) = delete;
65 66 67 68 69 70

	gcc_pure
	unsigned GetDepth() const;

	gcc_pure
	AllocatedPath GetUriFS() const;
71 72
};

73
static InotifySource *inotify_source;
74
static InotifyQueue *inotify_queue;
75

76
static unsigned inotify_max_depth;
77
static WatchDirectory *inotify_root;
78
static std::map<int, WatchDirectory *> inotify_directories;
79 80

static void
81
tree_add_watch_directory(WatchDirectory *directory)
82
{
83 84
	inotify_directories.insert(std::make_pair(directory->descriptor,
						  directory));
85 86 87
}

static void
88
tree_remove_watch_directory(WatchDirectory *directory)
89
{
90 91 92
	auto i = inotify_directories.find(directory->descriptor);
	assert(i != inotify_directories.end());
	inotify_directories.erase(i);
93 94
}

95
static WatchDirectory *
96 97
tree_find_watch_directory(int wd)
{
98 99 100 101 102
	auto i = inotify_directories.find(wd);
	if (i == inotify_directories.end())
		return nullptr;

	return i->second;
103 104
}

105 106 107 108 109 110 111 112 113 114 115
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);
}

116
static void
117
remove_watch_directory(WatchDirectory *directory)
118
{
119
	assert(directory != nullptr);
120

121
	if (directory->parent == nullptr) {
122 123 124
		LogWarning(inotify_domain,
			   "music directory was removed - "
			   "cannot continue to watch it");
125 126 127
		return;
	}

128
	disable_watch_directory(*directory);
129

130 131 132 133
	/* remove it from the parent, which effectively deletes it */
	directory->parent->children.remove_if([directory](const WatchDirectory &child){
			return &child == directory;
		});
134 135
}

136 137
AllocatedPath
WatchDirectory::GetUriFS() const
138
{
139
	if (parent == nullptr)
140
		return AllocatedPath::Null();
141

142
	const auto uri = parent->GetUriFS();
143
	if (uri.IsNull())
144
		return name;
145

146
	return AllocatedPath::Build(uri, name);
147 148 149 150 151 152 153
}

/* 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) ||
154
		strchr(path, '\n') != nullptr;
155 156 157
}

static void
158
recursive_watch_subdirectories(WatchDirectory *directory,
159
			       const AllocatedPath &path_fs, unsigned depth)
160
{
161
	Error error;
162 163 164
	DIR *dir;
	struct dirent *ent;

165
	assert(directory != nullptr);
166
	assert(depth <= inotify_max_depth);
167
	assert(!path_fs.IsNull());
168

169 170 171 172 173
	++depth;

	if (depth > inotify_max_depth)
		return;

174
	dir = opendir(path_fs.c_str());
175
	if (dir == nullptr) {
176
		FormatErrno(inotify_domain,
177
			    "Failed to open directory %s", path_fs.c_str());
178 179 180 181 182 183 184 185 186 187
		return;
	}

	while ((ent = readdir(dir))) {
		struct stat st;
		int ret;

		if (skip_path(ent->d_name))
			continue;

188 189
		const auto child_path_fs =
			AllocatedPath::Build(path_fs, ent->d_name);
190
		ret = StatFile(child_path_fs, st);
191
		if (ret < 0) {
192 193
			FormatErrno(inotify_domain,
				    "Failed to stat %s",
194
				    child_path_fs.c_str());
195 196 197
			continue;
		}

198
		if (!S_ISDIR(st.st_mode))
199 200
			continue;

201 202
		ret = inotify_source->Add(child_path_fs.c_str(), IN_MASK,
					  error);
203
		if (ret < 0) {
204
			FormatError(error,
205 206
				    "Failed to register %s",
				    child_path_fs.c_str());
207
			error.Clear();
208 209 210
			continue;
		}

211
		WatchDirectory *child = tree_find_watch_directory(ret);
212
		if (child != nullptr)
213 214 215
			/* already being watched */
			continue;

216
		directory->children.emplace_front(directory,
217
						  AllocatedPath::FromFS(ent->d_name),
218
						  ret);
219
		child = &directory->children.front();
220 221 222

		tree_add_watch_directory(child);

223
		recursive_watch_subdirectories(child, child_path_fs, depth);
224 225 226 227 228
	}

	closedir(dir);
}

229
gcc_pure
230 231
unsigned
WatchDirectory::GetDepth() const
232
{
233
	const WatchDirectory *d = this;
234
	unsigned depth = 0;
235
	while ((d = d->parent) != nullptr)
236 237 238 239 240
		++depth;

	return depth;
}

241 242
static void
mpd_inotify_callback(int wd, unsigned mask,
243
		     gcc_unused const char *name, gcc_unused void *ctx)
244
{
245
	WatchDirectory *directory;
246

247
	/*FormatDebug(inotify_domain, "wd=%d mask=0x%x name='%s'", wd, mask, name);*/
248 249

	directory = tree_find_watch_directory(wd);
250
	if (directory == nullptr)
251 252
		return;

253
	const auto uri_fs = directory->GetUriFS();
254 255 256 257 258 259 260 261 262 263

	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 */
264
		const auto &root = inotify_root->name;
265

266
		const auto path_fs = uri_fs.IsNull()
267
			? root
268
			: AllocatedPath::Build(root, uri_fs.c_str());
269

270
		recursive_watch_subdirectories(directory, path_fs,
271
					       directory->GetDepth());
272 273
	}

274 275 276
	if ((mask & (IN_CLOSE_WRITE|IN_MOVE|IN_DELETE)) != 0 ||
	    /* at the maximum depth, we watch out for newly created
	       directories */
277
	    (directory->GetDepth() == inotify_max_depth &&
278 279
	     (mask & (IN_CREATE|IN_ISDIR)) == (IN_CREATE|IN_ISDIR))) {
		/* a file was changed, or a directory was
280 281
		   moved/deleted: queue a database update */

282 283
		if (!uri_fs.IsNull()) {
			const std::string uri_utf8 = uri_fs.ToUTF8();
284 285
			if (!uri_utf8.empty())
				inotify_queue->Enqueue(uri_utf8.c_str());
286
		}
287 288
		else
			inotify_queue->Enqueue("");
289 290 291 292
	}
}

void
293 294
mpd_inotify_init(EventLoop &loop, Storage &storage, UpdateService &update,
		 unsigned max_depth)
295
{
296
	LogDebug(inotify_domain, "initializing inotify");
297

298
	const auto path = storage.MapFS("");
299
	if (path.IsNull()) {
300
		LogDebug(inotify_domain, "no music directory configured");
301 302 303
		return;
	}

304
	Error error;
305
	inotify_source = InotifySource::Create(loop,
306
					       mpd_inotify_callback, nullptr,
307
					       error);
308
	if (inotify_source == nullptr) {
309
		LogError(error);
310 311 312
		return;
	}

313 314
	inotify_max_depth = max_depth;

315
	int descriptor = inotify_source->Add(path.c_str(), IN_MASK, error);
316
	if (descriptor < 0) {
317
		LogError(error);
318
		delete inotify_source;
319
		inotify_source = nullptr;
320 321 322
		return;
	}

323
	inotify_root = new WatchDirectory(nullptr, path, descriptor);
324

325 326
	tree_add_watch_directory(inotify_root);

327
	recursive_watch_subdirectories(inotify_root, path, 0);
328

329
	inotify_queue = new InotifyQueue(loop, update);
330

331
	LogDebug(inotify_domain, "watching music directory");
332 333 334 335 336
}

void
mpd_inotify_finish(void)
{
337
	if (inotify_source == nullptr)
338 339
		return;

340
	delete inotify_queue;
341
	delete inotify_source;
342
	delete inotify_root;
343
	inotify_directories.clear();
344
}