Merged revisions 342277 via svnmerge from
[asterisk/asterisk.git] / pbx / pbx_spool.c
index b45ff6a..327c995 100644 (file)
@@ -471,6 +471,7 @@ static AST_LIST_HEAD_STATIC(dirlist, direntry);
 #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)
@@ -551,14 +552,47 @@ static void queue_file_create(const char *filename)
                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);
@@ -605,7 +639,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 */
@@ -641,14 +675,35 @@ 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) {
@@ -679,6 +734,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) {