blob: 6a8c7b62c7e4c21ba147dd83d1268e7bae334bfe [file] [log] [blame]
/*
* 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;
}