vi: fixes to string search in colon commands, closes 10321
[oweals/busybox.git] / miscutils / inotifyd.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * simple inotify daemon
4  * reports filesystem changes via userspace agent
5  *
6  * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
7  *
8  * Licensed under GPLv2, see file LICENSE in this source tree.
9  */
10
11 /*
12  * Use as follows:
13  * # inotifyd /user/space/agent dir/or/file/being/watched[:mask] ...
14  *
15  * When a filesystem event matching the specified mask is occurred on specified file (or directory)
16  * a userspace agent is spawned and given the following parameters:
17  * $1. actual event(s)
18  * $2. file (or directory) name
19  * $3. name of subfile (if any), in case of watching a directory
20  *
21  * E.g. inotifyd ./dev-watcher /dev:n
22  *
23  * ./dev-watcher can be, say:
24  * #!/bin/sh
25  * echo "We have new device in here! Hello, $3!"
26  *
27  * See below for mask names explanation.
28  */
29 //config:config INOTIFYD
30 //config:       bool "inotifyd (3.6 kb)"
31 //config:       default n  # doesn't build on Knoppix 5
32 //config:       help
33 //config:       Simple inotify daemon. Reports filesystem changes. Requires
34 //config:       kernel >= 2.6.13
35
36 //applet:IF_INOTIFYD(APPLET(inotifyd, BB_DIR_SBIN, BB_SUID_DROP))
37
38 //kbuild:lib-$(CONFIG_INOTIFYD) += inotifyd.o
39
40 //usage:#define inotifyd_trivial_usage
41 //usage:        "PROG FILE1[:MASK]..."
42 //usage:#define inotifyd_full_usage "\n\n"
43 //usage:       "Run PROG on filesystem changes."
44 //usage:     "\nWhen a filesystem event matching MASK occurs on FILEn,"
45 //usage:     "\nPROG ACTUAL_EVENTS FILEn [SUBFILE] is run."
46 //usage:     "\nIf PROG is -, events are sent to stdout."
47 //usage:     "\nEvents:"
48 //usage:     "\n        a       File is accessed"
49 //usage:     "\n        c       File is modified"
50 //usage:     "\n        e       Metadata changed"
51 //usage:     "\n        w       Writable file is closed"
52 //usage:     "\n        0       Unwritable file is closed"
53 //usage:     "\n        r       File is opened"
54 //usage:     "\n        D       File is deleted"
55 //usage:     "\n        M       File is moved"
56 //usage:     "\n        u       Backing fs is unmounted"
57 //usage:     "\n        o       Event queue overflowed"
58 //usage:     "\n        x       File can't be watched anymore"
59 //usage:     "\nIf watching a directory:"
60 //usage:     "\n        y       Subfile is moved into dir"
61 //usage:     "\n        m       Subfile is moved out of dir"
62 //usage:     "\n        n       Subfile is created"
63 //usage:     "\n        d       Subfile is deleted"
64 //usage:     "\n"
65 //usage:     "\ninotifyd waits for PROG to exit."
66 //usage:     "\nWhen x event happens for all FILEs, inotifyd exits."
67
68 #include "libbb.h"
69 #include "common_bufsiz.h"
70 #include <sys/inotify.h>
71
72 static const char mask_names[] ALIGN1 =
73         "a"     // 0x00000001   File was accessed
74         "c"     // 0x00000002   File was modified
75         "e"     // 0x00000004   Metadata changed
76         "w"     // 0x00000008   Writable file was closed
77         "0"     // 0x00000010   Unwritable file closed
78         "r"     // 0x00000020   File was opened
79         "m"     // 0x00000040   File was moved from X
80         "y"     // 0x00000080   File was moved to Y
81         "n"     // 0x00000100   Subfile was created
82         "d"     // 0x00000200   Subfile was deleted
83         "D"     // 0x00000400   Self was deleted
84         "M"     // 0x00000800   Self was moved
85         "\0"    // 0x00001000   (unused)
86         // Kernel events, always reported:
87         "u"     // 0x00002000   Backing fs was unmounted
88         "o"     // 0x00004000   Event queued overflowed
89         "x"     // 0x00008000   File is no longer watched (usually deleted)
90 ;
91 enum {
92         MASK_BITS = sizeof(mask_names) - 1
93 };
94
95 int inotifyd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
96 int inotifyd_main(int argc, char **argv)
97 {
98         int n;
99         unsigned mask;
100         struct pollfd pfd;
101         char **watches; // names of files being watched
102         const char *args[5];
103
104         // sanity check: agent and at least one watch must be given
105         if (!argv[1] || !argv[2])
106                 bb_show_usage();
107
108         argv++;
109         // inotify_add_watch will number watched files
110         // starting from 1, thus watches[0] is unimportant,
111         // and 1st file name is watches[1].
112         watches = argv;
113         args[0] = *argv;
114         args[4] = NULL;
115         argc -= 2; // number of files we watch
116
117         // open inotify
118         pfd.fd = inotify_init();
119         if (pfd.fd < 0)
120                 bb_simple_perror_msg_and_die("no kernel support");
121
122         // setup watches
123         while (*++argv) {
124                 char *path = *argv;
125                 char *masks = strchr(path, ':');
126
127                 mask = 0x0fff; // assuming we want all non-kernel events
128                 // if mask is specified ->
129                 if (masks) {
130                         *masks = '\0'; // split path and mask
131                         // convert mask names to mask bitset
132                         mask = 0;
133                         while (*++masks) {
134                                 const char *found;
135                                 found = memchr(mask_names, *masks, MASK_BITS);
136                                 if (found)
137                                         mask |= (1 << (found - mask_names));
138                         }
139                 }
140                 // add watch
141                 n = inotify_add_watch(pfd.fd, path, mask);
142                 if (n < 0)
143                         bb_perror_msg_and_die("add watch (%s) failed", path);
144                 //bb_error_msg("added %d [%s]:%4X", n, path, mask);
145         }
146
147         // setup signals
148         bb_signals(BB_FATAL_SIGS, record_signo);
149
150         // do watch
151         pfd.events = POLLIN;
152         while (1) {
153                 int len;
154                 void *buf;
155                 struct inotify_event *ie;
156  again:
157                 if (bb_got_signal)
158                         break;
159                 n = poll(&pfd, 1, -1);
160                 // Signal interrupted us?
161                 if (n < 0 && errno == EINTR)
162                         goto again;
163                 // Under Linux, above if() is not necessary.
164                 // Non-fatal signals, e.g. SIGCHLD, when set to SIG_DFL,
165                 // are not interrupting poll().
166                 // Thus we can just break if n <= 0 (see below),
167                 // because EINTR will happen only on SIGTERM et al.
168                 // But this might be not true under other Unixes,
169                 // and is generally way too subtle to depend on.
170                 if (n <= 0) // strange error?
171                         break;
172
173                 // read out all pending events
174                 // (NB: len must be int, not ssize_t or long!)
175 #define eventbuf bb_common_bufsiz1
176                 setup_common_bufsiz();
177                 xioctl(pfd.fd, FIONREAD, &len);
178                 ie = buf = (len <= COMMON_BUFSIZE) ? eventbuf : xmalloc(len);
179                 len = full_read(pfd.fd, buf, len);
180                 // process events. N.B. events may vary in length
181                 while (len > 0) {
182                         int i;
183                         // cache relevant events mask
184                         unsigned m = ie->mask & ((1 << MASK_BITS) - 1);
185                         if (m) {
186                                 char events[MASK_BITS + 1];
187                                 char *s = events;
188                                 for (i = 0; i < MASK_BITS; ++i, m >>= 1) {
189                                         if ((m & 1) && (mask_names[i] != '\0'))
190                                                 *s++ = mask_names[i];
191                                 }
192                                 *s = '\0';
193                                 if (LONE_CHAR(args[0], '-')) {
194                                         /* "inotifyd - FILE": built-in echo */
195                                         printf(ie->len ? "%s\t%s\t%s\n" : "%s\t%s\n", events,
196                                                         watches[ie->wd],
197                                                         ie->name);
198                                         fflush(stdout);
199                                 } else {
200 //                                      bb_error_msg("exec %s %08X\t%s\t%s\t%s", args[0],
201 //                                              ie->mask, events, watches[ie->wd], ie->len ? ie->name : "");
202                                         args[1] = events;
203                                         args[2] = watches[ie->wd];
204                                         args[3] = ie->len ? ie->name : NULL;
205                                         spawn_and_wait((char **)args);
206                                 }
207                                 // we are done if all files got final x event
208                                 if (ie->mask & 0x8000) {
209                                         if (--argc <= 0)
210                                                 goto done;
211                                         inotify_rm_watch(pfd.fd, ie->wd);
212                                 }
213                         }
214                         // next event
215                         i = sizeof(struct inotify_event) + ie->len;
216                         len -= i;
217                         ie = (void*)((char*)ie + i);
218                 }
219                 if (eventbuf != buf)
220                         free(buf);
221         } // while (1)
222  done:
223         return bb_got_signal;
224 }