| /* |
| * Copyright 2012-2014 Google Inc. All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* |
| * A simple program that exits as soon as any of the files specified on |
| * the command line exists. (It might be nice to have it exit as soon as |
| * any of the files *change*, but there's no good way to do that without |
| * a race condition; the file might change while this program is starting up.) |
| */ |
| #include <errno.h> |
| #include <libgen.h> |
| #include <signal.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <sys/inotify.h> |
| #include <unistd.h> |
| |
| |
| static void close_inotify(int inotify) { |
| // TODO(apenwarr): This fork() is silly, but helps on my workstation. |
| // For some reason, on my Ubuntu workstation, closing the inotify socket |
| // takes about 300ms for no good reason. On a Debian kernel and another |
| // kernel I tried, there is (correctly) no delay. This quick hack causes |
| // the fd to actually be closed in a child process instead of the parent |
| // process, so the child has to suffer the delay, but nobody cares, and |
| // the parent can exit quickly. |
| if (fork() == 0) { |
| close(inotify); |
| _exit(0); |
| } |
| sleep(0); |
| close(inotify); |
| } |
| |
| |
| static void die(const char *tag) { |
| perror(tag); |
| exit(1); |
| } |
| |
| |
| static void close_and_die(int inotify, const char *tag) { |
| close_inotify(inotify); |
| die(tag); |
| } |
| |
| |
| // TODO(apenwarr): this is also only needed because of the weird ubuntu bug. |
| // Remove it in case my workstation kernel gets fixed, because normal |
| // people probably don't have this problem. |
| static int close_on_signal = -1; |
| static void on_signal(int signum) { |
| if (close_on_signal >= 0) |
| close_inotify(close_on_signal); |
| signal(signum, SIG_DFL); |
| kill(getpid(), signum); |
| _exit(99); |
| } |
| |
| |
| int main(int argc, const char **argv) { |
| const int want = IN_MOVE | IN_CREATE | IN_DELETE; |
| int inotify, i, err, len, *descriptors = NULL; |
| char buf[4096], *ptr; |
| struct inotify_event *event; |
| |
| if (argc < 2) { |
| fprintf(stderr, |
| "usage: %s <filenames...>\n" |
| " Waits until any of the given files has been created.\n", |
| argv[0]); |
| exit(2); |
| } |
| |
| signal(SIGTERM, on_signal); |
| signal(SIGINT, on_signal); |
| |
| while (1) { |
| close_on_signal = inotify = inotify_init(); |
| if (inotify < 0) |
| die("inotify_init"); |
| |
| descriptors = calloc(argc, sizeof(int)); |
| |
| for (i = 1; i < argc; ++i) { |
| char *fn = strdup(argv[i]); |
| const char *dir = dirname(fn); |
| int error_printed = 0; |
| do { |
| err = inotify_add_watch(inotify, dir, want); |
| if (err < 0) { |
| if (errno == ENOENT) { |
| if (!error_printed) { |
| perror(dir); |
| fprintf(stderr, "Sleeping until directory exists...\n"); |
| error_printed = 1; |
| } |
| sleep(1); |
| } else { |
| close_and_die(inotify, dir); |
| } |
| } |
| } while (err < 0); |
| descriptors[i] = err; |
| free(fn); |
| } |
| |
| for (i = 1; i < argc; ++i) { |
| // Avoid race condition: it's possible a file was created *before* |
| // we registered for its watch. |
| if (0 == access(argv[i], F_OK)) |
| goto success; |
| } |
| |
| while (1) { |
| len = read(inotify, buf, sizeof(buf)); |
| if (len == 0) |
| close_and_die(inotify, "inotify read EOF"); |
| if (len < 0) { |
| if (errno == EINTR || errno == EAGAIN) |
| continue; |
| close_and_die(inotify, "inotify read"); |
| } |
| for (ptr = buf; ptr < buf + len; ptr += event->len + sizeof(*event)) { |
| event = (struct inotify_event *)ptr; |
| for (i = 1; i < argc; ++i) { |
| char *fn = strdup(argv[i]); |
| const char *file = basename(fn); |
| if (event->mask & (IN_IGNORED | IN_Q_OVERFLOW | IN_UNMOUNT)) { |
| fprintf(stderr, "inotify: unexpected flag %X; try again.\n", |
| event->mask); |
| free(fn); |
| goto try_again; |
| } |
| if (event->wd == descriptors[i] && |
| event->len > 0 && |
| 0 == strncmp(event->name, file, event->len)) { |
| if ((event->mask & want) && !(event->mask & ~want)) { |
| free(fn); |
| goto success; |
| } else { |
| fprintf(stderr, "%s: expected mask 0 < 0x%X <= 0x%X; try again.\n", |
| argv[i], event->mask, want); |
| free(fn); |
| goto try_again; // something unexpected happened |
| } |
| } |
| free(fn); |
| } |
| } |
| } |
| try_again: |
| free(descriptors); |
| close_on_signal = -1; |
| close_inotify(inotify); |
| } |
| success: |
| free(descriptors); |
| close_on_signal = -1; |
| close_inotify(inotify); |
| return 0; |
| } |