Merged revisions 342277 via svnmerge from
authorKinsey Moore <kmoore@digium.com>
Tue, 25 Oct 2011 16:08:52 +0000 (16:08 +0000)
committerKinsey Moore <kmoore@digium.com>
Tue, 25 Oct 2011 16:08:52 +0000 (16:08 +0000)
https://origsvn.digium.com/svn/asterisk/branches/10

................
  r342277 | kmoore | 2011-10-25 11:08:04 -0500 (Tue, 25 Oct 2011) | 25 lines

  Merged revisions 342276 via svnmerge from
  https://origsvn.digium.com/svn/asterisk/branches/1.8

  ........
    r342276 | kmoore | 2011-10-25 11:06:57 -0500 (Tue, 25 Oct 2011) | 18 lines

    Fix spool handling to allow call files to be hardlinked into place

    This fixes the inotify code to handle call files being hardlinked into the
    spool directory.

    The smsq utility does this, instead of rename(), to ensure that it cannot
    accidentally overwrite an existing spool file. A rename() might do that, but
    link() will definitely not.

    The inotify code had broken this, because it would wait for an IN_CLOSE_WRITE
    event on the file... which was never forthcoming, since it was never opened.
    Now we look for IN_OPEN events following the IN_CREATE event, and only wait
    for an IN_CLOSE_WRITE if the file was actually opened.

    Patch-by: dwmw2
    (closes issue ASTERISK-18331)
    Review: https://reviewboard.asterisk.org/r/1391/
  ........
................

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@342278 65c4cc65-6c06-0410-ace0-fbb531ad65f3

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) {