2 * Copyright 2000-2010 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #include "fsnotifier.h"
24 #include <sys/inotify.h>
25 #include <sys/select.h>
29 #define LOG_ENV "FSNOTIFIER_LOG_LEVEL"
30 #define LOG_ENV_DEBUG "debug"
31 #define LOG_ENV_INFO "info"
32 #define LOG_ENV_WARNING "warning"
33 #define LOG_ENV_ERROR "error"
34 #define LOG_ENV_OFF "off"
37 "fsnotifier - IntelliJ IDEA companion program for watching and reporting file and directory structure modifications.\n\n" \
38 "fsnotifier utilizes \"user\" facility of syslog(3) - messages usually can be found in /var/log/user.log.\n" \
39 "Verbosity is regulated via " LOG_ENV " environment variable, possible values are: " \
40 LOG_ENV_DEBUG ", " LOG_ENV_INFO ", " LOG_ENV_WARNING ", " LOG_ENV_ERROR ", " LOG_ENV_OFF "; latter is the default.\n\n" \
41 "Use 'fsnotifier --selftest' to perform some self-diagnostics (output will be logged and printed to console).\n"
44 "Try 'fsnotifier --help' for more information.\n"
46 #define INOTIFY_LIMIT_MSG \
47 "The current <b>inotify</b>(7) watch limit of %d is too low. " \
48 "<a href=\"http://confluence.jetbrains.net/display/IDEADEV/Inotify+Watches+Limit\">More details.</a>\n"
55 static array* roots = NULL;
57 static bool show_warning = true;
59 static bool self_test = false;
61 #define CHECK_NULL(p) if (p == NULL) { userlog(LOG_ERR, "out of memory"); return false; }
63 static void init_log();
64 static void run_self_test();
65 static void main_loop();
66 static bool read_input();
67 static bool update_roots(array* new_roots);
68 static void unregister_roots();
69 static bool register_roots(array* new_roots, array* unwatchable);
70 static bool unwatchable_mounts(array* mounts);
71 static void inotify_callback(char* path, int event);
72 static void output(const char* format, ...);
75 int main(int argc, char** argv) {
77 if (strcmp(argv[1], "--help") == 0) {
81 else if (strcmp(argv[1], "--selftest") == 0) {
85 printf("unrecognized option: %s\n", argv[1]);
93 userlog(LOG_INFO, "started");
96 userlog(LOG_INFO, "started (self-test mode)");
99 setvbuf(stdin, NULL, _IONBF, 0);
100 setvbuf(stdout, NULL, _IONBF, 0);
102 roots = array_create(20);
103 if (init_inotify() && roots != NULL) {
104 set_inotify_callback(&inotify_callback);
121 userlog(LOG_INFO, "finished");
128 static void init_log() {
129 char* env_level = getenv(LOG_ENV);
130 int level = LOG_EMERG;
131 if (env_level != NULL) {
132 if (strcmp(env_level, LOG_ENV_DEBUG) == 0) level = LOG_DEBUG;
133 else if (strcmp(env_level, LOG_ENV_INFO) == 0) level = LOG_INFO;
134 else if (strcmp(env_level, LOG_ENV_WARNING) == 0) level = LOG_WARNING;
135 else if (strcmp(env_level, LOG_ENV_ERROR) == 0) level = LOG_ERR;
143 snprintf(ident, sizeof(ident), "fsnotifier[%d]", getpid());
144 openlog(ident, 0, LOG_USER);
145 setlogmask(LOG_UPTO(level));
149 void userlog(int priority, const char* format, ...) {
152 va_start(ap, format);
153 vsyslog(priority, format, ap);
157 const char* level = "debug";
159 case LOG_ERR: level = "error"; break;
160 case LOG_WARNING: level = " warn"; break;
161 case LOG_INFO: level = " info"; break;
163 printf("fsnotifier[%d] %s: ", getpid(), level);
165 va_start(ap, format);
174 static void run_self_test() {
175 array* test_roots = array_create(1);
176 char* cwd = malloc(PATH_MAX);
177 if (getcwd(cwd, PATH_MAX) == NULL) {
178 strncpy(cwd, ".", PATH_MAX);
180 array_push(test_roots, cwd);
181 update_roots(test_roots);
185 static void main_loop() {
186 int input_fd = fileno(stdin), inotify_fd = get_inotify_fd();
187 int nfds = (inotify_fd > input_fd ? inotify_fd : input_fd) + 1;
193 FD_SET(input_fd, &rfds);
194 FD_SET(inotify_fd, &rfds);
195 if (select(nfds, &rfds, NULL, NULL, NULL) < 0) {
196 userlog(LOG_ERR, "select: %s", strerror(errno));
199 else if (FD_ISSET(input_fd, &rfds)) {
200 go_on = read_input();
202 else if (FD_ISSET(inotify_fd, &rfds)) {
203 go_on = process_inotify_input();
209 static bool read_input() {
210 char* line = read_line(stdin);
211 userlog(LOG_DEBUG, "input: %s", (line ? line : "<null>"));
213 if (line == NULL || strcmp(line, "EXIT") == 0) {
217 if (strcmp(line, "ROOTS") == 0) {
218 array* new_roots = array_create(20);
219 CHECK_NULL(new_roots);
222 line = read_line(stdin);
223 userlog(LOG_DEBUG, "input: %s", (line ? line : "<null>"));
224 if (line == NULL || strlen(line) == 0) {
227 else if (strcmp(line, "#") == 0) {
231 if (line[0] == '|') line++; // flat roots will be differentiated later
233 int l = strlen(line);
234 if (l > 1 && line[l-1] == '/') line[l-1] = '\0';
236 CHECK_NULL(array_push(new_roots, strdup(line)));
240 return update_roots(new_roots);
247 static bool update_roots(array* new_roots) {
248 userlog(LOG_INFO, "updating roots (curr:%d, new:%d)", array_size(roots), array_size(new_roots));
251 if (array_size(new_roots) == 0) {
254 else if (array_size(new_roots) == 1 && strcmp(array_get(new_roots, 0), "/") == 0) { // refuse to watch entire tree
255 output("UNWATCHEABLE\n/\n#\n");
256 userlog(LOG_INFO, "unwatchable: /");
257 array_delete_vs_data(new_roots);
261 array* unwatchable = array_create(20);
262 CHECK_NULL(unwatchable);
263 if (!unwatchable_mounts(unwatchable)) {
267 if (!register_roots(new_roots, unwatchable)) {
271 // todo: sort/optimize list
272 output("UNWATCHEABLE\n");
273 for (int i=0; i<array_size(unwatchable); i++) {
274 char* s = array_get(unwatchable, i);
276 userlog(LOG_INFO, "unwatchable: %s", s);
280 array_delete_vs_data(unwatchable);
281 array_delete(new_roots);
287 static void unregister_roots() {
289 while ((root = array_pop(roots)) != NULL) {
290 userlog(LOG_INFO, "unregistering root: %s", root->name);
298 static bool register_roots(array* new_roots, array* unwatchable) {
299 for (int i=0; i<array_size(new_roots); i++) {
300 char* new_root = array_get(new_roots, i);
301 userlog(LOG_INFO, "registering root: %s", new_root);
302 int id = watch(new_root, unwatchable);
303 if (id == ERR_ABORT) {
307 watch_root* root = malloc(sizeof(watch_root));
310 root->name = new_root;
311 CHECK_NULL(array_push(roots, root));
314 if (show_warning && watch_limit_reached()) {
315 int limit = get_watch_count();
316 userlog(LOG_WARNING, "watch limit (%d) reached", limit);
317 output("MESSAGE\n" INOTIFY_LIMIT_MSG, limit);
318 show_warning = false; // warn only once
320 CHECK_NULL(array_push(unwatchable, new_root));
327 static bool is_watchable(const char* dev, const char* mnt, const char* fs) {
328 // don't watch special and network filesystems
329 return !(strncmp(mnt, "/dev", 4) == 0 || strncmp(mnt, "/proc", 5) == 0 || strncmp(mnt, "/sys", 4) == 0 ||
330 strcmp(fs, "fuse.gvfs-fuse-daemon") == 0 || strcmp(fs, "cifs") == 0 || strcmp(fs, "nfs") == 0);
333 #define MTAB_DELIMS " \t"
335 static bool unwatchable_mounts(array* mounts) {
336 FILE* mtab = fopen("/etc/mtab", "r");
338 mtab = fopen("/proc/mounts", "r");
341 userlog(LOG_ERR, "neither /etc/mtab nor /proc/mounts can be read");
346 while ((line = read_line(mtab)) != NULL) {
347 userlog(LOG_DEBUG, "mtab: %s", line);
348 char* dev = strtok(line, MTAB_DELIMS);
349 char* point = strtok(NULL, MTAB_DELIMS);
350 char* fs = strtok(NULL, MTAB_DELIMS);
352 if (dev == NULL || point == NULL || fs == NULL) {
353 userlog(LOG_ERR, "can't parse mount line");
357 if (!is_watchable(dev, point, fs)) {
358 CHECK_NULL(array_push(mounts, strdup(point)));
367 static void inotify_callback(char* path, int event) {
368 if (event & IN_CREATE || event & IN_MOVED_TO) {
369 output("CREATE\n%s\n", path);
370 userlog(LOG_DEBUG, "CREATE: %s", path);
374 if (event & IN_MODIFY) {
375 output("CHANGE\n%s\n", path);
376 userlog(LOG_DEBUG, "CHANGE: %s", path);
380 if (event & IN_ATTRIB) {
381 output("STATS\n%s\n", path);
382 userlog(LOG_DEBUG, "STATS: %s", path);
386 if (event & IN_DELETE || event & IN_MOVED_FROM) {
387 output("DELETE\n%s\n", path);
388 userlog(LOG_DEBUG, "DELETE: %s", path);
392 if (event & IN_UNMOUNT) {
394 userlog(LOG_DEBUG, "RESET");
400 static void output(const char* format, ...) {
406 va_start(ap, format);