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 occured 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
  30//usage:#define inotifyd_trivial_usage
  31//usage:        "PROG FILE1[:MASK]..."
  32//usage:#define inotifyd_full_usage "\n\n"
  33//usage:       "Run PROG on filesystem changes."
  34//usage:     "\nWhen a filesystem event matching MASK occurs on FILEn,"
  35//usage:     "\nPROG ACTUAL_EVENTS FILEn [SUBFILE] is run."
  36//usage:     "\nIf PROG is -, events are sent to stdout."
  37//usage:     "\nEvents:"
  38//usage:     "\n        a       File is accessed"
  39//usage:     "\n        c       File is modified"
  40//usage:     "\n        e       Metadata changed"
  41//usage:     "\n        w       Writable file is closed"
  42//usage:     "\n        0       Unwritable file is closed"
  43//usage:     "\n        r       File is opened"
  44//usage:     "\n        D       File is deleted"
  45//usage:     "\n        M       File is moved"
  46//usage:     "\n        u       Backing fs is unmounted"
  47//usage:     "\n        o       Event queue overflowed"
  48//usage:     "\n        x       File can't be watched anymore"
  49//usage:     "\nIf watching a directory:"
  50//usage:     "\n        m       Subfile is moved into dir"
  51//usage:     "\n        y       Subfile is moved out of dir"
  52//usage:     "\n        n       Subfile is created"
  53//usage:     "\n        d       Subfile is deleted"
  54//usage:     "\n"
  55//usage:     "\ninotifyd waits for PROG to exit."
  56//usage:     "\nWhen x event happens for all FILEs, inotifyd exits."
  57
  58#include "libbb.h"
  59#include <sys/inotify.h>
  60
  61static const char mask_names[] ALIGN1 =
  62        "a"     // 0x00000001   File was accessed
  63        "c"     // 0x00000002   File was modified
  64        "e"     // 0x00000004   Metadata changed
  65        "w"     // 0x00000008   Writable file was closed
  66        "0"     // 0x00000010   Unwritable file closed
  67        "r"     // 0x00000020   File was opened
  68        "m"     // 0x00000040   File was moved from X
  69        "y"     // 0x00000080   File was moved to Y
  70        "n"     // 0x00000100   Subfile was created
  71        "d"     // 0x00000200   Subfile was deleted
  72        "D"     // 0x00000400   Self was deleted
  73        "M"     // 0x00000800   Self was moved
  74        "\0"    // 0x00001000   (unused)
  75        // Kernel events, always reported:
  76        "u"     // 0x00002000   Backing fs was unmounted
  77        "o"     // 0x00004000   Event queued overflowed
  78        "x"     // 0x00008000   File is no longer watched (usually deleted)
  79;
  80enum {
  81        MASK_BITS = sizeof(mask_names) - 1
  82};
  83
  84int inotifyd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  85int inotifyd_main(int argc, char **argv)
  86{
  87        int n;
  88        unsigned mask;
  89        struct pollfd pfd;
  90        char **watches; // names of files being watched
  91        const char *args[5];
  92
  93        // sanity check: agent and at least one watch must be given
  94        if (!argv[1] || !argv[2])
  95                bb_show_usage();
  96
  97        argv++;
  98        // inotify_add_watch will number watched files
  99        // starting from 1, thus watches[0] is unimportant,
 100        // and 1st file name is watches[1].
 101        watches = argv;
 102        args[0] = *argv;
 103        args[4] = NULL;
 104        argc -= 2; // number of files we watch
 105
 106        // open inotify
 107        pfd.fd = inotify_init();
 108        if (pfd.fd < 0)
 109                bb_perror_msg_and_die("no kernel support");
 110
 111        // setup watches
 112        while (*++argv) {
 113                char *path = *argv;
 114                char *masks = strchr(path, ':');
 115
 116                mask = 0x0fff; // assuming we want all non-kernel events
 117                // if mask is specified ->
 118                if (masks) {
 119                        *masks = '\0'; // split path and mask
 120                        // convert mask names to mask bitset
 121                        mask = 0;
 122                        while (*++masks) {
 123                                const char *found;
 124                                found = memchr(mask_names, *masks, MASK_BITS);
 125                                if (found)
 126                                        mask |= (1 << (found - mask_names));
 127                        }
 128                }
 129                // add watch
 130                n = inotify_add_watch(pfd.fd, path, mask);
 131                if (n < 0)
 132                        bb_perror_msg_and_die("add watch (%s) failed", path);
 133                //bb_error_msg("added %d [%s]:%4X", n, path, mask);
 134        }
 135
 136        // setup signals
 137        bb_signals(BB_FATAL_SIGS, record_signo);
 138
 139        // do watch
 140        pfd.events = POLLIN;
 141        while (1) {
 142                int len;
 143                void *buf;
 144                struct inotify_event *ie;
 145 again:
 146                if (bb_got_signal)
 147                        break;
 148                n = poll(&pfd, 1, -1);
 149                // Signal interrupted us?
 150                if (n < 0 && errno == EINTR)
 151                        goto again;
 152                // Under Linux, above if() is not necessary.
 153                // Non-fatal signals, e.g. SIGCHLD, when set to SIG_DFL,
 154                // are not interrupting poll().
 155                // Thus we can just break if n <= 0 (see below),
 156                // because EINTR will happen only on SIGTERM et al.
 157                // But this might be not true under other Unixes,
 158                // and is generally way too subtle to depend on.
 159                if (n <= 0) // strange error?
 160                        break;
 161
 162                // read out all pending events
 163                // (NB: len must be int, not ssize_t or long!)
 164                xioctl(pfd.fd, FIONREAD, &len);
 165#define eventbuf bb_common_bufsiz1
 166                ie = buf = (len <= sizeof(eventbuf)) ? eventbuf : xmalloc(len);
 167                len = full_read(pfd.fd, buf, len);
 168                // process events. N.B. events may vary in length
 169                while (len > 0) {
 170                        int i;
 171                        // cache relevant events mask
 172                        unsigned m = ie->mask & ((1 << MASK_BITS) - 1);
 173                        if (m) {
 174                                char events[MASK_BITS + 1];
 175                                char *s = events;
 176                                for (i = 0; i < MASK_BITS; ++i, m >>= 1) {
 177                                        if ((m & 1) && (mask_names[i] != '\0'))
 178                                                *s++ = mask_names[i];
 179                                }
 180                                *s = '\0';
 181                                if (LONE_CHAR(args[0], '-')) {
 182                                        /* "inotifyd - FILE": built-in echo */
 183                                        printf(ie->len ? "%s\t%s\t%s\n" : "%s\t%s\n", events,
 184                                                        watches[ie->wd],
 185                                                        ie->name);
 186                                        fflush(stdout);
 187                                } else {
 188//                                      bb_error_msg("exec %s %08X\t%s\t%s\t%s", args[0],
 189//                                              ie->mask, events, watches[ie->wd], ie->len ? ie->name : "");
 190                                        args[1] = events;
 191                                        args[2] = watches[ie->wd];
 192                                        args[3] = ie->len ? ie->name : NULL;
 193                                        spawn_and_wait((char **)args);
 194                                }
 195                                // we are done if all files got final x event
 196                                if (ie->mask & 0x8000) {
 197                                        if (--argc <= 0)
 198                                                goto done;
 199                                        inotify_rm_watch(pfd.fd, ie->wd);
 200                                }
 201                        }
 202                        // next event
 203                        i = sizeof(struct inotify_event) + ie->len;
 204                        len -= i;
 205                        ie = (void*)((char*)ie + i);
 206                }
 207                if (eventbuf != buf)
 208                        free(buf);
 209        } // while (1)
 210 done:
 211        return bb_got_signal;
 212}
 213