--- /dev/null
+#include "git-compat-util.h"
+#include "dir.h"
+#include "fsmonitor-ll.h"
+#include "fsm-listen.h"
+#include "fsmonitor--daemon.h"
+#include "fsmonitor-path-utils.h"
+#include "gettext.h"
+#include "simple-ipc.h"
+#include "string-list.h"
+#include "trace.h"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <sys/inotify.h>
+#include <sys/stat.h>
+
+/*
+ * Safe value to bitwise OR with rest of mask for
+ * kernels that do not support IN_MASK_CREATE
+ */
+#ifndef IN_MASK_CREATE
+#define IN_MASK_CREATE 0x00000000
+#endif
+
+enum shutdown_reason {
+ SHUTDOWN_CONTINUE = 0,
+ SHUTDOWN_STOP,
+ SHUTDOWN_ERROR,
+ SHUTDOWN_FORCE
+};
+
+struct watch_entry {
+ struct hashmap_entry ent;
+ int wd;
+ uint32_t cookie;
+ const char *dir;
+};
+
+struct rename_entry {
+ struct hashmap_entry ent;
+ time_t whence;
+ uint32_t cookie;
+ const char *dir;
+};
+
+struct fsm_listen_data {
+ int fd_inotify;
+ enum shutdown_reason shutdown;
+ struct hashmap watches;
+ struct hashmap renames;
+ struct hashmap revwatches;
+};
+
+static int watch_entry_cmp(const void *cmp_data UNUSED,
+ const struct hashmap_entry *eptr,
+ const struct hashmap_entry *entry_or_key,
+ const void *keydata UNUSED)
+{
+ const struct watch_entry *e1, *e2;
+
+ e1 = container_of(eptr, const struct watch_entry, ent);
+ e2 = container_of(entry_or_key, const struct watch_entry, ent);
+ return e1->wd != e2->wd;
+}
+
+static int revwatches_entry_cmp(const void *cmp_data UNUSED,
+ const struct hashmap_entry *eptr,
+ const struct hashmap_entry *entry_or_key,
+ const void *keydata UNUSED)
+{
+ const struct watch_entry *e1, *e2;
+
+ e1 = container_of(eptr, const struct watch_entry, ent);
+ e2 = container_of(entry_or_key, const struct watch_entry, ent);
+ return strcmp(e1->dir, e2->dir);
+}
+
+static int rename_entry_cmp(const void *cmp_data UNUSED,
+ const struct hashmap_entry *eptr,
+ const struct hashmap_entry *entry_or_key,
+ const void *keydata UNUSED)
+{
+ const struct rename_entry *e1, *e2;
+
+ e1 = container_of(eptr, const struct rename_entry, ent);
+ e2 = container_of(entry_or_key, const struct rename_entry, ent);
+ return e1->cookie != e2->cookie;
+}
+
+/*
+ * Register an inotify watch, add watch descriptor to path mapping
+ * and the reverse mapping.
+ */
+static int add_watch(const char *path, struct fsm_listen_data *data)
+{
+ const char *interned = strintern(path);
+ struct watch_entry *w1, *w2;
+
+ /* add the inotify watch, don't allow watches to be modified */
+ int wd = inotify_add_watch(data->fd_inotify, interned,
+ (IN_ALL_EVENTS | IN_ONLYDIR | IN_MASK_CREATE)
+ ^ IN_ACCESS ^ IN_CLOSE ^ IN_OPEN);
+ if (wd < 0) {
+ if (errno == ENOENT || errno == ENOTDIR)
+ return 0; /* directory was deleted or is not a directory */
+ if (errno == EEXIST)
+ return 0; /* watch already exists, no action needed */
+ return error_errno(_("inotify_add_watch('%s') failed"), interned);
+ }
+
+ /* add watch descriptor -> directory mapping */
+ CALLOC_ARRAY(w1, 1);
+ w1->wd = wd;
+ w1->dir = interned;
+ hashmap_entry_init(&w1->ent, memhash(&w1->wd, sizeof(int)));
+ hashmap_add(&data->watches, &w1->ent);
+
+ /* add directory -> watch descriptor mapping */
+ CALLOC_ARRAY(w2, 1);
+ w2->wd = wd;
+ w2->dir = interned;
+ hashmap_entry_init(&w2->ent, strhash(w2->dir));
+ hashmap_add(&data->revwatches, &w2->ent);
+
+ return 0;
+}
+
+/*
+ * Remove the inotify watch, the watch descriptor to path mapping
+ * and the reverse mapping.
+ */
+static void remove_watch(struct watch_entry *w, struct fsm_listen_data *data)
+{
+ struct watch_entry k1, k2, *w1, *w2;
+
+ /* remove watch, ignore error if kernel already did it */
+ if (inotify_rm_watch(data->fd_inotify, w->wd) && errno != EINVAL)
+ error_errno(_("inotify_rm_watch() failed"));
+
+ k1.wd = w->wd;
+ hashmap_entry_init(&k1.ent, memhash(&k1.wd, sizeof(int)));
+ w1 = hashmap_remove_entry(&data->watches, &k1, ent, NULL);
+ if (!w1)
+ BUG("Double remove of watch for '%s'", w->dir);
+
+ if (w1->cookie)
+ BUG("Removing watch for '%s' which has a pending rename", w1->dir);
+
+ k2.dir = w->dir;
+ hashmap_entry_init(&k2.ent, strhash(k2.dir));
+ w2 = hashmap_remove_entry(&data->revwatches, &k2, ent, NULL);
+ if (!w2)
+ BUG("Double remove of reverse watch for '%s'", w->dir);
+
+ /* w1->dir and w2->dir are interned strings, we don't own them */
+ free(w1);
+ free(w2);
+}
+
+/*
+ * Check for stale directory renames.
+ *
+ * https://man7.org/linux/man-pages/man7/inotify.7.html
+ *
+ * Allow for some small timeout to account for the fact that insertion of the
+ * IN_MOVED_FROM+IN_MOVED_TO event pair is not atomic, and the possibility that
+ * there may not be any IN_MOVED_TO event.
+ *
+ * If the IN_MOVED_TO event is not received within the timeout then events have
+ * been missed and the monitor is in an inconsistent state with respect to the
+ * filesystem.
+ */
+static int check_stale_dir_renames(struct hashmap *renames, time_t max_age)
+{
+ struct rename_entry *re;
+ struct hashmap_iter iter;
+
+ hashmap_for_each_entry(renames, &iter, re, ent) {
+ if (re->whence <= max_age)
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Track pending renames.
+ *
+ * Tracking is done via an event cookie to watch descriptor mapping.
+ *
+ * A rename is not complete until matching an IN_MOVED_TO event is received
+ * for a corresponding IN_MOVED_FROM event.
+ */
+static void add_dir_rename(uint32_t cookie, const char *path,
+ struct fsm_listen_data *data)
+{
+ struct watch_entry k, *w;
+ struct rename_entry *re;
+
+ /* lookup the watch descriptor for the given path */
+ k.dir = path;
+ hashmap_entry_init(&k.ent, strhash(path));
+ w = hashmap_get_entry(&data->revwatches, &k, ent, NULL);
+ if (!w) {
+ /*
+ * This can happen in rare cases where the directory was
+ * moved before we had a chance to add a watch on it.
+ * Just ignore this rename.
+ */
+ trace_printf_key(&trace_fsmonitor,
+ "no watch found for rename from '%s'", path);
+ return;
+ }
+ w->cookie = cookie;
+
+ /* add the pending rename to match against later */
+ CALLOC_ARRAY(re, 1);
+ re->dir = w->dir;
+ re->cookie = w->cookie;
+ re->whence = time(NULL);
+ hashmap_entry_init(&re->ent, memhash(&re->cookie, sizeof(uint32_t)));
+ hashmap_add(&data->renames, &re->ent);
+}
+
+/*
+ * Handle directory renames
+ *
+ * Once an IN_MOVED_TO event is received, lookup the rename tracking information
+ * via the event cookie and use this information to update the watch.
+ */
+static void rename_dir(uint32_t cookie, const char *path,
+ struct fsm_listen_data *data)
+{
+ struct rename_entry rek, *re;
+ struct watch_entry k, *w;
+
+ /* lookup a pending rename to match */
+ rek.cookie = cookie;
+ hashmap_entry_init(&rek.ent, memhash(&rek.cookie, sizeof(uint32_t)));
+ re = hashmap_get_entry(&data->renames, &rek, ent, NULL);
+ if (re) {
+ k.dir = re->dir;
+ hashmap_entry_init(&k.ent, strhash(k.dir));
+ w = hashmap_get_entry(&data->revwatches, &k, ent, NULL);
+ if (w) {
+ w->cookie = 0; /* rename handled */
+ remove_watch(w, data);
+ if (add_watch(path, data))
+ trace_printf_key(&trace_fsmonitor,
+ "failed to add watch for renamed dir '%s'",
+ path);
+ } else {
+ /* Directory was moved out of watch tree */
+ trace_printf_key(&trace_fsmonitor,
+ "No matching watch for rename to '%s'", path);
+ }
+ hashmap_remove_entry(&data->renames, &rek, ent, NULL);
+ free(re);
+ } else {
+ /* Directory was moved from outside the watch tree */
+ trace_printf_key(&trace_fsmonitor,
+ "No matching cookie for rename to '%s'", path);
+ }
+}
+
+/*
+ * Recursively add watches to every directory under path
+ */
+static int register_inotify(const char *path,
+ struct fsmonitor_daemon_state *state,
+ struct fsmonitor_batch *batch)
+{
+ DIR *dir;
+ const char *rel;
+ struct strbuf current = STRBUF_INIT;
+ struct dirent *de;
+ struct stat fs;
+ int ret = -1;
+
+ dir = opendir(path);
+ if (!dir) {
+ if (errno == ENOENT || errno == ENOTDIR)
+ return 0; /* directory was deleted */
+ return error_errno(_("opendir('%s') failed"), path);
+ }
+
+ while ((de = readdir_skip_dot_and_dotdot(dir)) != NULL) {
+ strbuf_reset(¤t);
+ strbuf_addf(¤t, "%s/%s", path, de->d_name);
+ if (lstat(current.buf, &fs)) {
+ if (errno == ENOENT)
+ continue; /* file was deleted */
+ error_errno(_("lstat('%s') failed"), current.buf);
+ goto failed;
+ }
+
+ /* recurse into directory */
+ if (S_ISDIR(fs.st_mode)) {
+ if (add_watch(current.buf, state->listen_data))
+ goto failed;
+ if (register_inotify(current.buf, state, batch))
+ goto failed;
+ } else if (batch) {
+ rel = current.buf + state->path_worktree_watch.len + 1;
+ trace_printf_key(&trace_fsmonitor, "explicitly adding '%s'", rel);
+ fsmonitor_batch__add_path(batch, rel);
+ }
+ }
+ ret = 0;
+
+failed:
+ strbuf_release(¤t);
+ if (closedir(dir) < 0)
+ return error_errno(_("closedir('%s') failed"), path);
+ return ret;
+}
+
+static int em_rename_dir_from(uint32_t mask)
+{
+ return ((mask & IN_ISDIR) && (mask & IN_MOVED_FROM));
+}
+
+static int em_rename_dir_to(uint32_t mask)
+{
+ return ((mask & IN_ISDIR) && (mask & IN_MOVED_TO));
+}
+
+static int em_remove_watch(uint32_t mask)
+{
+ return (mask & IN_DELETE_SELF);
+}
+
+static int em_dir_renamed(uint32_t mask)
+{
+ return ((mask & IN_ISDIR) && (mask & IN_MOVE));
+}
+
+static int em_dir_created(uint32_t mask)
+{
+ return ((mask & IN_ISDIR) && (mask & IN_CREATE));
+}
+
+static int em_dir_deleted(uint32_t mask)
+{
+ return ((mask & IN_ISDIR) && (mask & IN_DELETE));
+}
+
+static int em_force_shutdown(uint32_t mask)
+{
+ return (mask & IN_UNMOUNT) || (mask & IN_Q_OVERFLOW);
+}
+
+static int em_ignore(uint32_t mask)
+{
+ return (mask & IN_IGNORED) || (mask & IN_MOVE_SELF);
+}
+
+static void log_mask_set(const char *path, uint32_t mask)
+{
+ struct strbuf msg = STRBUF_INIT;
+
+ if (mask & IN_ACCESS)
+ strbuf_addstr(&msg, "IN_ACCESS|");
+ if (mask & IN_MODIFY)
+ strbuf_addstr(&msg, "IN_MODIFY|");
+ if (mask & IN_ATTRIB)
+ strbuf_addstr(&msg, "IN_ATTRIB|");
+ if (mask & IN_CLOSE_WRITE)
+ strbuf_addstr(&msg, "IN_CLOSE_WRITE|");
+ if (mask & IN_CLOSE_NOWRITE)
+ strbuf_addstr(&msg, "IN_CLOSE_NOWRITE|");
+ if (mask & IN_OPEN)
+ strbuf_addstr(&msg, "IN_OPEN|");
+ if (mask & IN_MOVED_FROM)
+ strbuf_addstr(&msg, "IN_MOVED_FROM|");
+ if (mask & IN_MOVED_TO)
+ strbuf_addstr(&msg, "IN_MOVED_TO|");
+ if (mask & IN_CREATE)
+ strbuf_addstr(&msg, "IN_CREATE|");
+ if (mask & IN_DELETE)
+ strbuf_addstr(&msg, "IN_DELETE|");
+ if (mask & IN_DELETE_SELF)
+ strbuf_addstr(&msg, "IN_DELETE_SELF|");
+ if (mask & IN_MOVE_SELF)
+ strbuf_addstr(&msg, "IN_MOVE_SELF|");
+ if (mask & IN_UNMOUNT)
+ strbuf_addstr(&msg, "IN_UNMOUNT|");
+ if (mask & IN_Q_OVERFLOW)
+ strbuf_addstr(&msg, "IN_Q_OVERFLOW|");
+ if (mask & IN_IGNORED)
+ strbuf_addstr(&msg, "IN_IGNORED|");
+ if (mask & IN_ISDIR)
+ strbuf_addstr(&msg, "IN_ISDIR|");
+
+ strbuf_strip_suffix(&msg, "|");
+
+ trace_printf_key(&trace_fsmonitor, "inotify_event: '%s', mask=%#8.8x %s",
+ path, mask, msg.buf);
+
+ strbuf_release(&msg);
+}
+
+int fsm_listen__ctor(struct fsmonitor_daemon_state *state)
+{
+ int fd;
+ int ret = 0;
+ struct fsm_listen_data *data;
+
+ CALLOC_ARRAY(data, 1);
+ state->listen_data = data;
+ state->listen_error_code = -1;
+ data->shutdown = SHUTDOWN_ERROR;
+
+ fd = inotify_init1(O_NONBLOCK);
+ if (fd < 0) {
+ FREE_AND_NULL(state->listen_data);
+ return error_errno(_("inotify_init1() failed"));
+ }
+
+ data->fd_inotify = fd;
+
+ hashmap_init(&data->watches, watch_entry_cmp, NULL, 0);
+ hashmap_init(&data->renames, rename_entry_cmp, NULL, 0);
+ hashmap_init(&data->revwatches, revwatches_entry_cmp, NULL, 0);
+
+ if (add_watch(state->path_worktree_watch.buf, data))
+ ret = -1;
+ else if (register_inotify(state->path_worktree_watch.buf, state, NULL))
+ ret = -1;
+ else if (state->nr_paths_watching > 1) {
+ if (add_watch(state->path_gitdir_watch.buf, data))
+ ret = -1;
+ else if (register_inotify(state->path_gitdir_watch.buf, state, NULL))
+ ret = -1;
+ }
+
+ if (!ret) {
+ state->listen_error_code = 0;
+ data->shutdown = SHUTDOWN_CONTINUE;
+ }
+
+ return ret;
+}
+
+void fsm_listen__dtor(struct fsmonitor_daemon_state *state)
+{
+ struct fsm_listen_data *data;
+ struct hashmap_iter iter;
+ struct watch_entry *w;
+ struct watch_entry **to_remove;
+ size_t nr_to_remove = 0, alloc_to_remove = 0;
+ size_t i;
+ int fd;
+
+ if (!state || !state->listen_data)
+ return;
+
+ data = state->listen_data;
+ fd = data->fd_inotify;
+
+ /*
+ * Collect all entries first, then remove them.
+ * We can't modify the hashmap while iterating over it.
+ */
+ to_remove = NULL;
+ hashmap_for_each_entry(&data->watches, &iter, w, ent) {
+ ALLOC_GROW(to_remove, nr_to_remove + 1, alloc_to_remove);
+ to_remove[nr_to_remove++] = w;
+ }
+
+ for (i = 0; i < nr_to_remove; i++) {
+ to_remove[i]->cookie = 0; /* ignore any pending renames */
+ remove_watch(to_remove[i], data);
+ }
+ free(to_remove);
+
+ hashmap_clear(&data->watches);
+
+ hashmap_clear(&data->revwatches); /* remove_watch freed the entries */
+
+ hashmap_clear_and_free(&data->renames, struct rename_entry, ent);
+
+ FREE_AND_NULL(state->listen_data);
+
+ if (fd && (close(fd) < 0))
+ error_errno(_("closing inotify file descriptor failed"));
+}
+
+void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)
+{
+ if (state && state->listen_data &&
+ state->listen_data->shutdown == SHUTDOWN_CONTINUE)
+ state->listen_data->shutdown = SHUTDOWN_STOP;
+}
+
+/*
+ * Process a single inotify event and queue for publication.
+ */
+static int process_event(const char *path,
+ const struct inotify_event *event,
+ struct fsmonitor_batch **batch,
+ struct string_list *cookie_list,
+ struct fsmonitor_daemon_state *state)
+{
+ const char *rel;
+ const char *last_sep;
+
+ switch (fsmonitor_classify_path_absolute(state, path)) {
+ case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:
+ case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
+ /* Use just the filename of the cookie file. */
+ last_sep = find_last_dir_sep(path);
+ string_list_append(cookie_list,
+ last_sep ? last_sep + 1 : path);
+ break;
+ case IS_INSIDE_DOT_GIT:
+ case IS_INSIDE_GITDIR:
+ break;
+ case IS_DOT_GIT:
+ case IS_GITDIR:
+ /*
+ * If .git directory is deleted or renamed away,
+ * we have to quit.
+ */
+ if (em_dir_deleted(event->mask)) {
+ trace_printf_key(&trace_fsmonitor,
+ "event: gitdir removed");
+ state->listen_data->shutdown = SHUTDOWN_FORCE;
+ goto done;
+ }
+
+ if (em_dir_renamed(event->mask)) {
+ trace_printf_key(&trace_fsmonitor,
+ "event: gitdir renamed");
+ state->listen_data->shutdown = SHUTDOWN_FORCE;
+ goto done;
+ }
+ break;
+ case IS_WORKDIR_PATH:
+ /* normal events in the working directory */
+ if (trace_pass_fl(&trace_fsmonitor))
+ log_mask_set(path, event->mask);
+
+ if (!*batch)
+ *batch = fsmonitor_batch__new();
+
+ rel = path + state->path_worktree_watch.len + 1;
+ fsmonitor_batch__add_path(*batch, rel);
+
+ if (em_dir_deleted(event->mask))
+ break;
+
+ /* received IN_MOVE_FROM, add tracking for expected IN_MOVE_TO */
+ if (em_rename_dir_from(event->mask))
+ add_dir_rename(event->cookie, path, state->listen_data);
+
+ /* received IN_MOVE_TO, update watch to reflect new path */
+ if (em_rename_dir_to(event->mask)) {
+ rename_dir(event->cookie, path, state->listen_data);
+ if (register_inotify(path, state, *batch)) {
+ state->listen_data->shutdown = SHUTDOWN_ERROR;
+ goto done;
+ }
+ }
+
+ if (em_dir_created(event->mask)) {
+ if (add_watch(path, state->listen_data)) {
+ state->listen_data->shutdown = SHUTDOWN_ERROR;
+ goto done;
+ }
+ if (register_inotify(path, state, *batch)) {
+ state->listen_data->shutdown = SHUTDOWN_ERROR;
+ goto done;
+ }
+ }
+ break;
+ case IS_OUTSIDE_CONE:
+ default:
+ trace_printf_key(&trace_fsmonitor,
+ "ignoring '%s'", path);
+ break;
+ }
+ return 0;
+done:
+ return -1;
+}
+
+/*
+ * Read the inotify event stream and pre-process events before further
+ * processing and eventual publishing.
+ */
+static void handle_events(struct fsmonitor_daemon_state *state)
+{
+ /* See https://man7.org/linux/man-pages/man7/inotify.7.html */
+ char buf[4096]
+ __attribute__ ((aligned(__alignof__(struct inotify_event))));
+
+ struct hashmap *watches = &state->listen_data->watches;
+ struct fsmonitor_batch *batch = NULL;
+ struct string_list cookie_list = STRING_LIST_INIT_DUP;
+ struct watch_entry k, *w;
+ struct strbuf path = STRBUF_INIT;
+ const struct inotify_event *event;
+ int fd = state->listen_data->fd_inotify;
+ ssize_t len;
+ char *ptr, *p;
+
+ for (;;) {
+ len = read(fd, buf, sizeof(buf));
+ if (len == -1) {
+ if (errno == EAGAIN || errno == EINTR)
+ goto done;
+ error_errno(_("reading inotify message stream failed"));
+ state->listen_data->shutdown = SHUTDOWN_ERROR;
+ goto done;
+ }
+
+ /* nothing to read */
+ if (len == 0)
+ goto done;
+
+ /* Loop over all events in the buffer. */
+ for (ptr = buf; ptr < buf + len;
+ ptr += sizeof(struct inotify_event) + event->len) {
+
+ event = (const struct inotify_event *)ptr;
+
+ if (em_ignore(event->mask))
+ continue;
+
+ /* File system was unmounted or event queue overflowed */
+ if (em_force_shutdown(event->mask)) {
+ if (trace_pass_fl(&trace_fsmonitor))
+ log_mask_set("Forcing shutdown", event->mask);
+ state->listen_data->shutdown = SHUTDOWN_FORCE;
+ goto done;
+ }
+
+ k.wd = event->wd;
+ hashmap_entry_init(&k.ent, memhash(&k.wd, sizeof(int)));
+
+ w = hashmap_get_entry(watches, &k, ent, NULL);
+ if (!w) {
+ /* Watch was removed, skip event */
+ continue;
+ }
+
+ /* directory watch was removed */
+ if (em_remove_watch(event->mask)) {
+ remove_watch(w, state->listen_data);
+ continue;
+ }
+
+ strbuf_reset(&path);
+ strbuf_addf(&path, "%s/%s", w->dir, event->name);
+
+ p = fsmonitor__resolve_alias(path.buf, &state->alias);
+ if (!p)
+ p = strbuf_detach(&path, NULL);
+
+ if (process_event(p, event, &batch, &cookie_list, state)) {
+ free(p);
+ goto done;
+ }
+ free(p);
+ }
+ strbuf_reset(&path);
+ fsmonitor_publish(state, batch, &cookie_list);
+ string_list_clear(&cookie_list, 0);
+ batch = NULL;
+ }
+done:
+ strbuf_release(&path);
+ fsmonitor_batch__free_list(batch);
+ string_list_clear(&cookie_list, 0);
+}
+
+/*
+ * Non-blocking read of the inotify events stream. The inotify fd is polled
+ * frequently to help minimize the number of queue overflows.
+ */
+void fsm_listen__loop(struct fsmonitor_daemon_state *state)
+{
+ int poll_num;
+ const int interval = 1000;
+ time_t checked = time(NULL);
+ struct pollfd fds[1];
+
+ fds[0].fd = state->listen_data->fd_inotify;
+ fds[0].events = POLLIN;
+
+ /*
+ * Our fs event listener is now running, so it's safe to start
+ * serving client requests.
+ */
+ ipc_server_start_async(state->ipc_server_data);
+
+ for (;;) {
+ switch (state->listen_data->shutdown) {
+ case SHUTDOWN_CONTINUE:
+ poll_num = poll(fds, 1, 1);
+ if (poll_num == -1) {
+ if (errno == EINTR)
+ continue;
+ error_errno(_("polling inotify message stream failed"));
+ state->listen_data->shutdown = SHUTDOWN_ERROR;
+ continue;
+ }
+
+ if ((time(NULL) - checked) >= interval) {
+ checked = time(NULL);
+ if (check_stale_dir_renames(&state->listen_data->renames,
+ checked - interval)) {
+ trace_printf_key(&trace_fsmonitor,
+ "Missed IN_MOVED_TO events, forcing shutdown");
+ state->listen_data->shutdown = SHUTDOWN_FORCE;
+ continue;
+ }
+ }
+
+ if (poll_num > 0 && (fds[0].revents & POLLIN))
+ handle_events(state);
+
+ continue;
+ case SHUTDOWN_ERROR:
+ state->listen_error_code = -1;
+ ipc_server_stop_async(state->ipc_server_data);
+ break;
+ case SHUTDOWN_FORCE:
+ state->listen_error_code = 0;
+ ipc_server_stop_async(state->ipc_server_data);
+ break;
+ case SHUTDOWN_STOP:
+ default:
+ state->listen_error_code = 0;
+ break;
+ }
+ return;
+ }
+}