busybox/miscutils/inotifyd.c
<<
>>
Prefs
   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
  72static 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;
  91enum {
  92        MASK_BITS = sizeof(mask_names) - 1
  93};
  94
  95int inotifyd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  96int 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_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}
 225