InotifyUpdate.cxx 7.71 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2020 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.
 */

Max Kellermann's avatar
Max Kellermann committed
20 21 22
#include "InotifyUpdate.hxx"
#include "InotifySource.hxx"
#include "InotifyQueue.hxx"
23
#include "InotifyDomain.hxx"
24
#include "storage/StorageInterface.hxx"
25
#include "fs/AllocatedPath.hxx"
26
#include "fs/FileInfo.hxx"
27
#include "Log.hxx"
28

29
#include <string>
30
#include <map>
31
#include <forward_list>
32

33 34 35 36 37
#include <assert.h>
#include <sys/inotify.h>
#include <string.h>
#include <dirent.h>

38
static constexpr unsigned IN_MASK =
39
#ifdef IN_ONLYDIR
40
	IN_ONLYDIR|
41
#endif
42 43
	IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF
	|IN_MOVE|IN_MOVE_SELF;
44

45 46
struct WatchDirectory {
	WatchDirectory *parent;
47

48
	AllocatedPath name;
49 50 51

	int descriptor;

52
	std::forward_list<WatchDirectory> children;
53

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

	WatchDirectory(const WatchDirectory &) = delete;
	WatchDirectory &operator=(const WatchDirectory &) = delete;
62 63

	gcc_pure
64
	unsigned GetDepth() const noexcept;
65 66

	gcc_pure
67
	AllocatedPath GetUriFS() const noexcept;
68 69
};

70
static InotifySource *inotify_source;
71
static InotifyQueue *inotify_queue;
72

73
static unsigned inotify_max_depth;
74
static WatchDirectory *inotify_root;
75
static std::map<int, WatchDirectory *> inotify_directories;
76 77

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

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

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

	return i->second;
100 101
}

102 103 104 105 106 107 108 109 110 111 112
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);
}

113
static void
114
remove_watch_directory(WatchDirectory *directory)
115
{
116
	assert(directory != nullptr);
117

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

125
	disable_watch_directory(*directory);
126

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

133
AllocatedPath
134
WatchDirectory::GetUriFS() const noexcept
135
{
136
	if (parent == nullptr)
137
		return nullptr;
138

139
	const auto uri = parent->GetUriFS();
140
	if (uri.IsNull())
141
		return name;
142

143
	return uri / name;
144 145 146 147 148 149 150
}

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

static void
155
recursive_watch_subdirectories(WatchDirectory *directory,
156
			       const AllocatedPath &path_fs, unsigned depth)
157 158 159 160
{
	DIR *dir;
	struct dirent *ent;

161
	assert(directory != nullptr);
162
	assert(depth <= inotify_max_depth);
163
	assert(!path_fs.IsNull());
164

165 166 167 168 169
	++depth;

	if (depth > inotify_max_depth)
		return;

170
	dir = opendir(path_fs.c_str());
171
	if (dir == nullptr) {
172
		FormatErrno(inotify_domain,
173
			    "Failed to open directory %s", path_fs.c_str());
174 175 176 177 178 179 180 181 182
		return;
	}

	while ((ent = readdir(dir))) {
		int ret;

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

183 184
		const auto name_fs = Path::FromFS(ent->d_name);
		const auto child_path_fs = path_fs / name_fs;
185 186

		FileInfo fi;
187 188
		try {
			fi = FileInfo(child_path_fs);
189 190
		} catch (...) {
			LogError(std::current_exception());
191 192 193
			continue;
		}

194
		if (!fi.IsDirectory())
195 196
			continue;

197 198 199
		try {
			ret = inotify_source->Add(child_path_fs.c_str(),
						  IN_MASK);
200 201
		} catch (...) {
			FormatError(std::current_exception(),
202 203
				    "Failed to register %s",
				    child_path_fs.c_str());
204 205 206
			continue;
		}

207
		WatchDirectory *child = tree_find_watch_directory(ret);
208
		if (child != nullptr)
209 210 211
			/* already being watched */
			continue;

212
		directory->children.emplace_front(directory,
213
						  name_fs,
214
						  ret);
215
		child = &directory->children.front();
216 217 218

		tree_add_watch_directory(child);

219
		recursive_watch_subdirectories(child, child_path_fs, depth);
220 221 222 223 224
	}

	closedir(dir);
}

225
gcc_pure
226
unsigned
227
WatchDirectory::GetDepth() const noexcept
228
{
229
	const WatchDirectory *d = this;
230
	unsigned depth = 0;
231
	while ((d = d->parent) != nullptr)
232 233 234 235 236
		++depth;

	return depth;
}

237 238
static void
mpd_inotify_callback(int wd, unsigned mask,
239
		     gcc_unused const char *name, gcc_unused void *ctx)
240
{
241
	WatchDirectory *directory;
242

243
	/*FormatDebug(inotify_domain, "wd=%d mask=0x%x name='%s'", wd, mask, name);*/
244 245

	directory = tree_find_watch_directory(wd);
246
	if (directory == nullptr)
247 248
		return;

249
	const auto uri_fs = directory->GetUriFS();
250 251 252 253 254 255 256 257 258 259

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

262
		const auto path_fs = uri_fs.IsNull()
263
			? root
264
			: (root / uri_fs);
265

266
		recursive_watch_subdirectories(directory, path_fs,
267
					       directory->GetDepth());
268 269
	}

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

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

void
289 290
mpd_inotify_init(EventLoop &loop, Storage &storage, UpdateService &update,
		 unsigned max_depth)
291
{
292
	LogDebug(inotify_domain, "initializing inotify");
293

294
	const auto path = storage.MapFS("");
295
	if (path.IsNull()) {
296
		LogDebug(inotify_domain, "no music directory configured");
297 298 299
		return;
	}

300 301 302 303
	try {
		inotify_source = new InotifySource(loop,
						   mpd_inotify_callback,
						   nullptr);
304 305
	} catch (...) {
		LogError(std::current_exception());
306 307 308
		return;
	}

309 310
	inotify_max_depth = max_depth;

311 312 313
	int descriptor;
	try {
		descriptor = inotify_source->Add(path.c_str(), IN_MASK);
314 315
	} catch (...) {
		LogError(std::current_exception());
316
		delete inotify_source;
317
		inotify_source = nullptr;
318 319 320
		return;
	}

321
	inotify_root = new WatchDirectory(nullptr, path, descriptor);
322

323 324
	tree_add_watch_directory(inotify_root);

325
	recursive_watch_subdirectories(inotify_root, path, 0);
326

327
	inotify_queue = new InotifyQueue(loop, update);
328

329
	LogDebug(inotify_domain, "watching music directory");
330 331 332
}

void
333
mpd_inotify_finish(void) noexcept
334
{
335
	if (inotify_source == nullptr)
336 337
		return;

338
	delete inotify_queue;
339
	delete inotify_source;
340
	delete inotify_root;
341
	inotify_directories.clear();
342
}