qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

filemonitor-inotify.c (9444B)


      1 /*
      2  * QEMU file monitor Linux inotify impl
      3  *
      4  * Copyright (c) 2018 Red Hat, Inc.
      5  *
      6  * This library is free software; you can redistribute it and/or
      7  * modify it under the terms of the GNU Lesser General Public
      8  * License as published by the Free Software Foundation; either
      9  * version 2.1 of the License, or (at your option) any later version.
     10  *
     11  * This library is distributed in the hope that it will be useful,
     12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     14  * Lesser General Public License for more details.
     15  *
     16  * You should have received a copy of the GNU Lesser General Public
     17  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
     18  *
     19  */
     20 
     21 #include "qemu/osdep.h"
     22 #include "qemu/filemonitor.h"
     23 #include "qemu/main-loop.h"
     24 #include "qemu/error-report.h"
     25 #include "qapi/error.h"
     26 #include "trace.h"
     27 
     28 #include <sys/inotify.h>
     29 
     30 struct QFileMonitor {
     31     int fd;
     32     QemuMutex lock; /* protects dirs & idmap */
     33     GHashTable *dirs; /* dirname => QFileMonitorDir */
     34     GHashTable *idmap; /* inotify ID => dirname */
     35 };
     36 
     37 
     38 typedef struct {
     39     int64_t id; /* watch ID */
     40     char *filename; /* optional filter */
     41     QFileMonitorHandler cb;
     42     void *opaque;
     43 } QFileMonitorWatch;
     44 
     45 
     46 typedef struct {
     47     char *path;
     48     int inotify_id; /* inotify ID */
     49     int next_file_id; /* file ID counter */
     50     GArray *watches; /* QFileMonitorWatch elements */
     51 } QFileMonitorDir;
     52 
     53 
     54 static void qemu_file_monitor_watch(void *arg)
     55 {
     56     QFileMonitor *mon = arg;
     57     char buf[4096]
     58         __attribute__ ((aligned(__alignof__(struct inotify_event))));
     59     int used = 0;
     60     int len;
     61 
     62     qemu_mutex_lock(&mon->lock);
     63 
     64     if (mon->fd == -1) {
     65         qemu_mutex_unlock(&mon->lock);
     66         return;
     67     }
     68 
     69     len = read(mon->fd, buf, sizeof(buf));
     70 
     71     if (len < 0) {
     72         if (errno != EAGAIN) {
     73             error_report("Failure monitoring inotify FD '%s',"
     74                          "disabling events", strerror(errno));
     75             goto cleanup;
     76         }
     77 
     78         /* no more events right now */
     79         goto cleanup;
     80     }
     81 
     82     /* Loop over all events in the buffer */
     83     while (used < len) {
     84         struct inotify_event *ev =
     85             (struct inotify_event *)(buf + used);
     86         const char *name = ev->len ? ev->name : "";
     87         QFileMonitorDir *dir = g_hash_table_lookup(mon->idmap,
     88                                                    GINT_TO_POINTER(ev->wd));
     89         uint32_t iev = ev->mask &
     90             (IN_CREATE | IN_MODIFY | IN_DELETE | IN_IGNORED |
     91              IN_MOVED_TO | IN_MOVED_FROM | IN_ATTRIB);
     92         int qev;
     93         gsize i;
     94 
     95         used += sizeof(struct inotify_event) + ev->len;
     96 
     97         if (!dir) {
     98             continue;
     99         }
    100 
    101         /*
    102          * During a rename operation, the old name gets
    103          * IN_MOVED_FROM and the new name gets IN_MOVED_TO.
    104          * To simplify life for callers, we turn these into
    105          * DELETED and CREATED events
    106          */
    107         switch (iev) {
    108         case IN_CREATE:
    109         case IN_MOVED_TO:
    110             qev = QFILE_MONITOR_EVENT_CREATED;
    111             break;
    112         case IN_MODIFY:
    113             qev = QFILE_MONITOR_EVENT_MODIFIED;
    114             break;
    115         case IN_DELETE:
    116         case IN_MOVED_FROM:
    117             qev = QFILE_MONITOR_EVENT_DELETED;
    118             break;
    119         case IN_ATTRIB:
    120             qev = QFILE_MONITOR_EVENT_ATTRIBUTES;
    121             break;
    122         case IN_IGNORED:
    123             qev = QFILE_MONITOR_EVENT_IGNORED;
    124             break;
    125         default:
    126             g_assert_not_reached();
    127         }
    128 
    129         trace_qemu_file_monitor_event(mon, dir->path, name, ev->mask,
    130                                       dir->inotify_id);
    131         for (i = 0; i < dir->watches->len; i++) {
    132             QFileMonitorWatch *watch = &g_array_index(dir->watches,
    133                                                       QFileMonitorWatch,
    134                                                       i);
    135 
    136             if (watch->filename == NULL ||
    137                 (name && g_str_equal(watch->filename, name))) {
    138                 trace_qemu_file_monitor_dispatch(mon, dir->path, name,
    139                                                  qev, watch->cb,
    140                                                  watch->opaque, watch->id);
    141                 watch->cb(watch->id, qev, name, watch->opaque);
    142             }
    143         }
    144     }
    145 
    146  cleanup:
    147     qemu_mutex_unlock(&mon->lock);
    148 }
    149 
    150 
    151 static void
    152 qemu_file_monitor_dir_free(void *data)
    153 {
    154     QFileMonitorDir *dir = data;
    155     gsize i;
    156 
    157     for (i = 0; i < dir->watches->len; i++) {
    158         QFileMonitorWatch *watch = &g_array_index(dir->watches,
    159                                                   QFileMonitorWatch, i);
    160         g_free(watch->filename);
    161     }
    162     g_array_unref(dir->watches);
    163     g_free(dir->path);
    164     g_free(dir);
    165 }
    166 
    167 
    168 QFileMonitor *
    169 qemu_file_monitor_new(Error **errp)
    170 {
    171     int fd;
    172     QFileMonitor *mon;
    173 
    174     fd = inotify_init1(IN_NONBLOCK);
    175     if (fd < 0) {
    176         error_setg_errno(errp, errno,
    177                          "Unable to initialize inotify");
    178         return NULL;
    179     }
    180 
    181     mon = g_new0(QFileMonitor, 1);
    182     qemu_mutex_init(&mon->lock);
    183     mon->fd = fd;
    184 
    185     mon->dirs = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
    186                                       qemu_file_monitor_dir_free);
    187     mon->idmap = g_hash_table_new(g_direct_hash, g_direct_equal);
    188 
    189     trace_qemu_file_monitor_new(mon, mon->fd);
    190 
    191     return mon;
    192 }
    193 
    194 static gboolean
    195 qemu_file_monitor_free_idle(void *opaque)
    196 {
    197     QFileMonitor *mon = opaque;
    198 
    199     if (!mon) {
    200         return G_SOURCE_REMOVE;
    201     }
    202 
    203     qemu_mutex_lock(&mon->lock);
    204 
    205     g_hash_table_unref(mon->idmap);
    206     g_hash_table_unref(mon->dirs);
    207 
    208     qemu_mutex_unlock(&mon->lock);
    209 
    210     qemu_mutex_destroy(&mon->lock);
    211     g_free(mon);
    212 
    213     return G_SOURCE_REMOVE;
    214 }
    215 
    216 void
    217 qemu_file_monitor_free(QFileMonitor *mon)
    218 {
    219     if (!mon) {
    220         return;
    221     }
    222 
    223     qemu_mutex_lock(&mon->lock);
    224     if (mon->fd != -1) {
    225         qemu_set_fd_handler(mon->fd, NULL, NULL, NULL);
    226         close(mon->fd);
    227         mon->fd = -1;
    228     }
    229     qemu_mutex_unlock(&mon->lock);
    230 
    231     /*
    232      * Can't free it yet, because another thread
    233      * may be running event loop, so the inotify
    234      * callback might be pending. Using an idle
    235      * source ensures we'll only free after the
    236      * pending callback is done
    237      */
    238     g_idle_add((GSourceFunc)qemu_file_monitor_free_idle, mon);
    239 }
    240 
    241 int64_t
    242 qemu_file_monitor_add_watch(QFileMonitor *mon,
    243                             const char *dirpath,
    244                             const char *filename,
    245                             QFileMonitorHandler cb,
    246                             void *opaque,
    247                             Error **errp)
    248 {
    249     QFileMonitorDir *dir;
    250     QFileMonitorWatch watch;
    251     int64_t ret = -1;
    252 
    253     qemu_mutex_lock(&mon->lock);
    254     dir = g_hash_table_lookup(mon->dirs, dirpath);
    255     if (!dir) {
    256         int rv = inotify_add_watch(mon->fd, dirpath,
    257                                    IN_CREATE | IN_DELETE | IN_MODIFY |
    258                                    IN_MOVED_TO | IN_MOVED_FROM | IN_ATTRIB);
    259 
    260         if (rv < 0) {
    261             error_setg_errno(errp, errno, "Unable to watch '%s'", dirpath);
    262             goto cleanup;
    263         }
    264 
    265         trace_qemu_file_monitor_enable_watch(mon, dirpath, rv);
    266 
    267         dir = g_new0(QFileMonitorDir, 1);
    268         dir->path = g_strdup(dirpath);
    269         dir->inotify_id = rv;
    270         dir->watches = g_array_new(FALSE, TRUE, sizeof(QFileMonitorWatch));
    271 
    272         g_hash_table_insert(mon->dirs, dir->path, dir);
    273         g_hash_table_insert(mon->idmap, GINT_TO_POINTER(rv), dir);
    274 
    275         if (g_hash_table_size(mon->dirs) == 1) {
    276             qemu_set_fd_handler(mon->fd, qemu_file_monitor_watch, NULL, mon);
    277         }
    278     }
    279 
    280     watch.id = (((int64_t)dir->inotify_id) << 32) | dir->next_file_id++;
    281     watch.filename = g_strdup(filename);
    282     watch.cb = cb;
    283     watch.opaque = opaque;
    284 
    285     g_array_append_val(dir->watches, watch);
    286 
    287     trace_qemu_file_monitor_add_watch(mon, dirpath,
    288                                       filename ? filename : "<none>",
    289                                       cb, opaque, watch.id);
    290 
    291     ret = watch.id;
    292 
    293  cleanup:
    294     qemu_mutex_unlock(&mon->lock);
    295     return ret;
    296 }
    297 
    298 
    299 void qemu_file_monitor_remove_watch(QFileMonitor *mon,
    300                                     const char *dirpath,
    301                                     int64_t id)
    302 {
    303     QFileMonitorDir *dir;
    304     gsize i;
    305 
    306     qemu_mutex_lock(&mon->lock);
    307 
    308     trace_qemu_file_monitor_remove_watch(mon, dirpath, id);
    309 
    310     dir = g_hash_table_lookup(mon->dirs, dirpath);
    311     if (!dir) {
    312         goto cleanup;
    313     }
    314 
    315     for (i = 0; i < dir->watches->len; i++) {
    316         QFileMonitorWatch *watch = &g_array_index(dir->watches,
    317                                                   QFileMonitorWatch, i);
    318         if (watch->id == id) {
    319             g_free(watch->filename);
    320             g_array_remove_index(dir->watches, i);
    321             break;
    322         }
    323     }
    324 
    325     if (dir->watches->len == 0) {
    326         inotify_rm_watch(mon->fd, dir->inotify_id);
    327         trace_qemu_file_monitor_disable_watch(mon, dir->path, dir->inotify_id);
    328 
    329         g_hash_table_remove(mon->idmap, GINT_TO_POINTER(dir->inotify_id));
    330         g_hash_table_remove(mon->dirs, dir->path);
    331 
    332         if (g_hash_table_size(mon->dirs) == 0) {
    333             qemu_set_fd_handler(mon->fd, NULL, NULL, NULL);
    334         }
    335     }
    336 
    337  cleanup:
    338     qemu_mutex_unlock(&mon->lock);
    339 }