--- pbx/pbx_spool.c~ 2010-11-10 23:26:39.000000000 +0000 +++ pbx/pbx_spool.c 2011-08-26 22:29:50.995622156 +0100 @@ -460,6 +460,7 @@ static AST_LIST_HEAD_STATIC(dirlist, dir #if defined(HAVE_INOTIFY) /* Only one thread is accessing this list, so no lock is necessary */ static AST_LIST_HEAD_NOLOCK_STATIC(createlist, direntry); +static AST_LIST_HEAD_NOLOCK_STATIC(openlist, direntry); #endif static void queue_file(const char *filename, time_t when) @@ -540,14 +541,46 @@ static void queue_file_create(const char return; } strcpy(cur->name, filename); + /* We'll handle this file unless an IN_OPEN event occurs within 2 seconds */ + cur->mtime = time(NULL) + 2; AST_LIST_INSERT_TAIL(&createlist, cur, list); } +static void queue_file_open(const char *filename) +{ + struct direntry *cur; + + AST_LIST_TRAVERSE_SAFE_BEGIN(&createlist, cur, list) { + if (!strcmp(cur->name, filename)) { + AST_LIST_REMOVE_CURRENT(list); + AST_LIST_INSERT_TAIL(&openlist, cur, list); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END +} + +static void queue_created_files(void) +{ + struct direntry *cur; + time_t now = time(NULL); + + AST_LIST_TRAVERSE_SAFE_BEGIN(&createlist, cur, list) { + if (cur->mtime > now) + break; + + AST_LIST_REMOVE_CURRENT(list); + queue_file(cur->name, 0); + ast_free(cur); + } + AST_LIST_TRAVERSE_SAFE_END +} + static void queue_file_write(const char *filename) { struct direntry *cur; /* Only queue entries where an IN_CREATE preceded the IN_CLOSE_WRITE */ - AST_LIST_TRAVERSE_SAFE_BEGIN(&createlist, cur, list) { + AST_LIST_TRAVERSE_SAFE_BEGIN(&openlist, cur, list) { if (!strcmp(cur->name, filename)) { AST_LIST_REMOVE_CURRENT(list); ast_free(cur); @@ -594,7 +627,7 @@ static void *scan_thread(void *unused) } #ifdef HAVE_INOTIFY - inotify_add_watch(inotify_fd, qdir, IN_CREATE | IN_CLOSE_WRITE | IN_MOVED_TO); + inotify_add_watch(inotify_fd, qdir, IN_CREATE | IN_OPEN | IN_CLOSE_WRITE | IN_MOVED_TO); #endif /* First, run through the directory and clear existing entries */ @@ -630,14 +663,34 @@ static void *scan_thread(void *unused) /* Convert from seconds to milliseconds, unless there's nothing * in the queue already, in which case, we wait forever. */ int waittime = next == INT_MAX ? -1 : (next - now) * 1000; + if (!AST_LIST_EMPTY(&createlist)) + waittime = 1000; /* When a file arrives, add it to the queue, in mtime order. */ if ((res = poll(&pfd, 1, waittime)) > 0 && (stage = 1) && (res = read(inotify_fd, &buf, sizeof(buf))) >= sizeof(*iev)) { ssize_t len = 0; /* File(s) added to directory, add them to my list */ for (iev = (void *) buf; res >= sizeof(*iev); iev = (struct inotify_event *) (((char *) iev) + len)) { + /* For an IN_MOVED_TO event, simply process the file. However, if + * we get an IN_CREATE event it *might* be an open(O_CREAT) or it + * might be a hardlink (like smsq does, since rename() might + * overwrite an existing file). So we have to see if we get a + * subsequent IN_OPEN event on the same file. If we do, keep it + * on the openlist and wait for the corresponding IN_CLOSE_WRITE. + * If we *don't* see an IN_OPEN event, then it was a hard link so + * it can be processed immediately. + * + * Unfortunately, although open(O_CREAT) is an atomic file system + * operation, the inotify subsystem doesn't give it to us in a + * single event with both IN_CREATE|IN_OPEN set. It's two separate + * events, and the kernel doesn't even give them to us at the same + * time. We can read() from inotify_fd after the IN_CREATE event, + * and get *nothing* from it. The IN_OPEN arrives only later! So + * we have a very short timeout of 2 seconds. */ if (iev->mask & IN_CREATE) { queue_file_create(iev->name); + } else if (iev->mask & IN_OPEN) { + queue_file_open(iev->name); } else if (iev->mask & IN_CLOSE_WRITE) { queue_file_write(iev->name); } else if (iev->mask & IN_MOVED_TO) { @@ -668,6 +721,7 @@ static void *scan_thread(void *unused) time(&now); } + queue_created_files(); /* Empty the list of all entries ready to be processed */ AST_LIST_LOCK(&dirlist); while (!AST_LIST_EMPTY(&dirlist) && AST_LIST_FIRST(&dirlist)->mtime <= now) {