todo: Remove obsolete items from TODO file.
[libguestfs.git] / daemon / inotify.c
1 /* libguestfs - the guestfsd daemon
2  * Copyright (C) 2009 Red Hat Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <config.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <fcntl.h>
26
27 #ifdef HAVE_SYS_INOTIFY_H
28 #include <sys/inotify.h>
29 #endif
30
31 #include "../src/guestfs_protocol.h"
32 #include "daemon.h"
33 #include "actions.h"
34 #include "optgroups.h"
35
36 #ifdef HAVE_SYS_INOTIFY_H
37 /* Currently open inotify handle, or -1 if not opened. */
38 static int inotify_fd = -1;
39
40 static char inotify_buf[64*1024*1024];  /* Event buffer, [0..posn-1] is valid */
41 static size_t inotify_posn = 0;
42
43 int
44 optgroup_inotify_available (void)
45 {
46   return 1;
47 }
48 #else /* !HAVE_SYS_INOTIFY_H */
49 int
50 optgroup_inotify_available (void)
51 {
52   return 0;
53 }
54 #endif
55
56 /* Because inotify_init does NEED_ROOT, NEED_INOTIFY implies NEED_ROOT. */
57 #define NEED_INOTIFY(errcode)                                           \
58   do {                                                                  \
59     if (inotify_fd == -1) {                                             \
60       reply_with_error ("%s: you must call 'inotify_init' first to initialize inotify", __func__); \
61       return (errcode);                                                 \
62     }                                                                   \
63   } while (0)
64
65 #define MQE_PATH "/proc/sys/fs/inotify/max_queued_events"
66
67 int
68 do_inotify_init (int max_events)
69 {
70 #ifdef HAVE_SYS_INOTIFY_H
71   FILE *fp;
72
73   NEED_ROOT (0, return -1);
74
75   if (max_events < 0) {
76     reply_with_error ("max_events < 0");
77     return -1;
78   }
79
80   if (max_events > 0) {
81     fp = fopen (MQE_PATH, "w");
82     if (fp == NULL) {
83       reply_with_perror (MQE_PATH);
84       return -1;
85     }
86     fprintf (fp, "%d\n", max_events);
87     fclose (fp);
88   }
89
90   if (inotify_fd >= 0)
91     if (do_inotify_close () == -1)
92       return -1;
93
94 #ifdef HAVE_INOTIFY_INIT1
95   inotify_fd = inotify_init1 (IN_NONBLOCK | IN_CLOEXEC);
96   if (inotify_fd == -1) {
97     reply_with_perror ("inotify_init1");
98     return -1;
99   }
100 #else
101   inotify_fd = inotify_init ();
102   if (inotify_fd == -1) {
103     reply_with_perror ("inotify_init");
104     return -1;
105   }
106   if (fcntl (inotify_fd, F_SETFL, O_NONBLOCK) == -1) {
107     reply_with_perror ("fcntl: O_NONBLOCK");
108     close (inotify_fd);
109     inotify_fd = -1;
110     return -1;
111   }
112   if (fcntl (inotify_fd, F_SETFD, FD_CLOEXEC) == -1) {
113     reply_with_perror ("fcntl: FD_CLOEXEC");
114     close (inotify_fd);
115     inotify_fd = -1;
116     return -1;
117   }
118 #endif
119
120   return 0;
121 #else
122   NOT_AVAILABLE (-1);
123 #endif
124 }
125
126 int
127 do_inotify_close (void)
128 {
129 #ifdef HAVE_SYS_INOTIFY_H
130   NEED_INOTIFY (-1);
131
132   if (inotify_fd == -1) {
133     reply_with_error ("handle is not open");
134     return -1;
135   }
136
137   if (close (inotify_fd) == -1) {
138     reply_with_perror ("close");
139     return -1;
140   }
141
142   inotify_fd = -1;
143   inotify_posn = 0;
144
145   return 0;
146 #else
147   NOT_AVAILABLE (-1);
148 #endif
149 }
150
151 int64_t
152 do_inotify_add_watch (const char *path, int mask)
153 {
154 #ifdef HAVE_SYS_INOTIFY_H
155   int64_t r;
156   char *buf;
157
158   NEED_INOTIFY (-1);
159
160   buf = sysroot_path (path);
161   if (!buf) {
162     reply_with_perror ("malloc");
163     return -1;
164   }
165
166   r = inotify_add_watch (inotify_fd, buf, mask);
167   free (buf);
168   if (r == -1) {
169     reply_with_perror ("%s", path);
170     return -1;
171   }
172
173   return r;
174 #else
175   NOT_AVAILABLE (-1);
176 #endif
177 }
178
179 int
180 do_inotify_rm_watch (int wd)
181 {
182 #ifdef HAVE_SYS_INOTIFY_H
183   NEED_INOTIFY (-1);
184
185   if (inotify_rm_watch (inotify_fd, wd) == -1) {
186     reply_with_perror ("%d", wd);
187     return -1;
188   }
189
190   return 0;
191 #else
192   NOT_AVAILABLE (-1);
193 #endif
194 }
195
196 guestfs_int_inotify_event_list *
197 do_inotify_read (void)
198 {
199 #ifdef HAVE_SYS_INOTIFY_H
200   int space;
201   guestfs_int_inotify_event_list *ret;
202
203   NEED_INOTIFY (NULL);
204
205   ret = malloc (sizeof *ret);
206   if (ret == NULL) {
207     reply_with_perror ("malloc");
208     return NULL;
209   }
210   ret->guestfs_int_inotify_event_list_len = 0;
211   ret->guestfs_int_inotify_event_list_val = NULL;
212
213   /* Read events that are available, but make sure we won't exceed
214    * maximum message size.  In order to achieve this we have to
215    * guesstimate the remaining space available.
216    */
217   space = GUESTFS_MESSAGE_MAX / 2;
218
219   while (space > 0) {
220     struct inotify_event *event;
221     int r;
222     size_t n;
223
224     r = read (inotify_fd, inotify_buf + inotify_posn,
225               sizeof (inotify_buf) - inotify_posn);
226     if (r == -1) {
227       if (errno == EWOULDBLOCK || errno == EAGAIN) /* End of list. */
228         break;
229       reply_with_perror ("read");
230       goto error;
231     }
232     if (r == 0) {               /* End of file - we're not expecting it. */
233       reply_with_error ("unexpected end of file");
234       goto error;
235     }
236
237     inotify_posn += r;
238
239     /* Read complete events from the buffer and add them to the result. */
240     n = 0;
241     while (n < inotify_posn) {
242       guestfs_int_inotify_event *np;
243       guestfs_int_inotify_event *in;
244
245       event = (struct inotify_event *) &inotify_buf[n];
246
247       /* Have we got a complete event in the buffer? */
248 #ifdef __GNUC__
249       if (n + sizeof (struct inotify_event) > inotify_posn ||
250           n + sizeof (struct inotify_event) + event->len > inotify_posn)
251         break;
252 #else
253 #error "this code needs fixing so it works on non-GCC compilers"
254 #endif
255
256       np = realloc (ret->guestfs_int_inotify_event_list_val,
257                     (ret->guestfs_int_inotify_event_list_len + 1) *
258                     sizeof (guestfs_int_inotify_event));
259       if (np == NULL) {
260         reply_with_perror ("realloc");
261         goto error;
262       }
263       ret->guestfs_int_inotify_event_list_val = np;
264       in = &ret->guestfs_int_inotify_event_list_val[ret->guestfs_int_inotify_event_list_len];
265       ret->guestfs_int_inotify_event_list_len++;
266
267       in->in_wd = event->wd;
268       in->in_mask = event->mask;
269       in->in_cookie = event->cookie;
270
271       if (event->len > 0)
272         in->in_name = strdup (event->name);
273       else
274         in->in_name = strdup (""); /* Should have optional string fields XXX. */
275       if (in->in_name == NULL) {
276         reply_with_perror ("strdup");
277         goto error;
278       }
279
280       /* Estimate space used by this event in the message. */
281       space -= 16 + 4 + strlen (in->in_name) + 4;
282
283       /* Move pointer to next event. */
284 #ifdef __GNUC__
285       n += sizeof (struct inotify_event) + event->len;
286 #else
287 #error "this code needs fixing so it works on non-GCC compilers"
288 #endif
289     }
290
291     /* 'n' now points to the first unprocessed/incomplete
292      * message in the buffer. Copy that to offset 0 in the buffer.
293      */
294     memmove (inotify_buf, &inotify_buf[n], inotify_posn - n);
295     inotify_posn -= n;
296   }
297
298   /* Return the messages. */
299   return ret;
300
301  error:
302   xdr_free ((xdrproc_t) xdr_guestfs_int_inotify_event_list, (char *) ret);
303   free (ret);
304   return NULL;
305 #else
306   NOT_AVAILABLE (NULL);
307 #endif
308 }
309
310 char **
311 do_inotify_files (void)
312 {
313 #ifdef HAVE_SYS_INOTIFY_H
314   char **ret = NULL;
315   int size = 0, alloc = 0;
316   unsigned int i;
317   FILE *fp;
318   guestfs_int_inotify_event_list *events;
319   char buf[PATH_MAX];
320
321   NEED_INOTIFY (NULL);
322
323   fp = popen ("sort -u > /tmp/inotify", "w");
324   if (fp == NULL) {
325     reply_with_perror ("sort");
326     return NULL;
327   }
328
329   while (1) {
330     events = do_inotify_read ();
331     if (events == NULL)
332       goto error;
333
334     if (events->guestfs_int_inotify_event_list_len == 0) {
335       free (events);
336       break;                    /* End of list of events. */
337     }
338
339     for (i = 0; i < events->guestfs_int_inotify_event_list_len; ++i) {
340       const char *name = events->guestfs_int_inotify_event_list_val[i].in_name;
341
342       if (name[0] != '\0')
343         fprintf (fp, "%s\n", name);
344     }
345
346     xdr_free ((xdrproc_t) xdr_guestfs_int_inotify_event_list, (char *) events);
347     free (events);
348   }
349
350   pclose (fp);
351
352   fp = fopen ("/tmp/inotify", "r");
353   if (fp == NULL) {
354     reply_with_perror ("/tmp/inotify");
355     return NULL;
356   }
357
358   while (fgets (buf, sizeof buf, fp) != NULL) {
359     int len = strlen (buf);
360
361     if (len > 0 && buf[len-1] == '\n')
362       buf[len-1] = '\0';
363
364     if (add_string (&ret, &size, &alloc, buf) == -1) {
365       fclose (fp);
366       goto error;
367     }
368   }
369
370   fclose (fp);
371
372   if (add_string (&ret, &size, &alloc, NULL) == -1)
373     goto error;
374
375   unlink ("/tmp/inotify");
376   return ret;
377
378  error:
379   unlink ("/tmp/inotify");
380   return NULL;
381 #else
382   NOT_AVAILABLE (NULL);
383 #endif
384 }