aboutsummaryrefslogtreecommitdiff
path: root/toys/pending/inotifyd.c
blob: 657fcb5f32053484e43f1205ae85aa4ae809b4c3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
/* inotifyd.c - inotify daemon. 
 *
 * Copyright 2013 Ashwini Kumar <ak.ashwini1981@gmail.com>
 * Copyright 2013 Kyungwan Han <asura321@gmail.com>
 *
 * No Standard.

USE_INOTIFYD(NEWTOY(inotifyd, "<2", TOYFLAG_USR|TOYFLAG_BIN))

config INOTIFYD
  bool "inotifyd"
  default n
  help
    usage: inotifyd PROG FILE[:mask] ...

    Run PROG on filesystem changes.
    When a filesystem event matching MASK occurs on FILEn,
    PROG ACTUAL_EVENTS FILEn [SUBFILE] is run.
    If PROG is -, events are sent to stdout.

    Events:
      a   File is accessed
      c   File is modified
      e   Metadata changed
      w   Writable file is closed
      0   Unwritable file is closed
      r   File is opened
      D   File is deleted
      M   File is moved
      u   Backing fs is unmounted
      o   Event queue overflowed
      x   File can't be watched anymore
    If watching a directory:
      m   Subfile is moved into dir
      y   Subfile is moved out of dir
      n   Subfile is created
      d   Subfile is deleted

    inotifyd waits for PROG to exit.
    When x event happens for all FILEs, inotifyd exits.
*/
#define FOR_inotifyd
#include "toys.h"
#include <sys/inotify.h>

GLOBALS(
  int gotsignal;
)

static void sig_handler(int sig)
{
  TT.gotsignal = sig;
}

static int exec_wait(char **args)
{
  int status = 0;
  pid_t pid = xfork();

  if (!pid) xexec(args);
  else waitpid(pid, &status, 0);
  return WEXITSTATUS(status);
}

void inotifyd_main(void)
{
  struct pollfd fds;
  char *prog_args[5], **files, **restore;
  struct mask_nameval {
    char name;
    unsigned long val;
  } mask_nv[] = {
    { 'a', IN_ACCESS },
    { 'c', IN_MODIFY },
    { 'e', IN_ATTRIB },
    { 'w', IN_CLOSE_WRITE },
    { '0', IN_CLOSE_NOWRITE },
    { 'r', IN_OPEN },
    { 'm', IN_MOVED_FROM },
    { 'y', IN_MOVED_TO },
    { 'n', IN_CREATE },
    { 'd', IN_DELETE },
    { 'D', IN_DELETE_SELF },
    { 'M', IN_MOVE_SELF },
    { 'u', IN_UNMOUNT },
    { 'o', IN_Q_OVERFLOW },
    { 'x', IN_IGNORED }
  };
  int mask, masks_len = ARRAY_LEN(mask_nv);

  prog_args[0] = toys.optargs[0];
  prog_args[4] = NULL;
  toys.optc--; // 1st one is program, rest are files to be watched
  //wd ZERO is not used, hence toys.optargs is assigned to files.
  restore = files = toys.optargs;
  if ((fds.fd = inotify_init()) == -1) perror_exit("initialization failed");

  while (*++toys.optargs) {
    char *path = *toys.optargs;
    char *masks = strchr(path, ':');

    mask = 0x0fff; //assuming all non-kernel events to be notified.
    if (masks) {
      *masks++ = '\0';
      mask = 0; 
      while (*masks) {
        int i = 0;

        for (i = 0; i < masks_len; i++) {
          if (*masks == mask_nv[i].name) {
            mask |= mask_nv[i].val;
            break;
          }
        }
        if (i == masks_len) error_exit("wrong mask '%c'", *masks);
        masks++;
      }
    }
    if (inotify_add_watch(fds.fd, path, mask) < 0) 
      perror_exit("add watch '%s' failed", path);
  }
  toys.optargs = restore;
  sigatexit(sig_handler);
  fds.events = POLLIN;

  while (1) {
    int ret = 0, queue_len;
    void *buf = NULL;
    struct inotify_event *event;

retry:
    if (TT.gotsignal) break;
    ret = poll(&fds, 1, -1);
    if (ret < 0 && errno == EINTR) goto retry;
    if (ret <= 0) break;
    xioctl(fds.fd, FIONREAD, &queue_len);
    event = buf = xmalloc(queue_len);
    queue_len = readall(fds.fd, buf, queue_len);
    while (queue_len > 0) {
      uint32_t m = event->mask;

      if (m) {
        char evts[masks_len+1], *s = evts;
        int i;

        for (i = 0; i < masks_len; i++) 
          if (m & mask_nv[i].val) *s++ = mask_nv[i].name;
        *s = '\0';

        if (prog_args[0][0] == '-' && !prog_args[0][1]) { //stdout
          printf(event->len ? "%s\t%s\t%s\n" : "%s\t%s\n", evts,
              files[event->wd], event->name);
          fflush(stdout);
        } else {
          prog_args[1] = evts;
          prog_args[2] = files[event->wd];
          prog_args[3] = event->len ? event->name : NULL;
          exec_wait(prog_args); //exec and wait...
        }
        if (event->mask & IN_IGNORED) {
          if (--toys.optc <= 0) {
            free(buf);
            goto done;
          }
          inotify_rm_watch(fds.fd, event->wd);
        }
      }
      queue_len -= sizeof(struct inotify_event) + event->len;
      event = (void*)((char*)event + sizeof(struct inotify_event) + event->len); //next event
    }
    free(buf);
  }
done:
  toys.exitval = TT.gotsignal;
}