chiark / gitweb /
journal: rework directory enumeration/watch logic
authorLennart Poettering <lennart@poettering.net>
Tue, 10 Jul 2012 23:08:38 +0000 (01:08 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 10 Jul 2012 23:08:38 +0000 (01:08 +0200)
There's now sd_journal_new_directory() for watching specific journal
directories. This is exposed in journalctl -D.

sd_journal_wait() and sd_journal_process() now return whether changes in
the journal are invalidating or just appending.

We now create inotify kernel watches only when we actually need them

TODO
man/journalctl.xml
src/journal/journal-internal.h
src/journal/journalctl.c
src/journal/libsystemd-journal.sym
src/journal/sd-journal.c
src/systemd/sd-journal.h

diff --git a/TODO b/TODO
index 3f5c3e0..5df72cd 100644 (file)
--- a/TODO
+++ b/TODO
@@ -34,6 +34,8 @@ Bugfixes:
 
 Features:
 
+* compile libsystemd-journal statically into journalctl so that we can share util.c and suchlike
+
 * replace BindTo= by BindsTo=, but keep old name for compat
 
 * switch-root: sockets need relabelling
@@ -50,8 +52,6 @@ Features:
 
 * .device aliases need to be implemented with the "following" logic, probably.
 
-* add sd_journal_wait() to make things easier for sync programs that just want to wait for changes
-
 * refuse taking lower-case variable names in sd_journal_send() and friends.
 
 * when running as user instance: implicitly default to WorkingDirectory=$HOME for all services.
@@ -206,9 +206,6 @@ Features:
 
 * support container_ttys=
 
-* journald: make configurable "store-on-var", "store-on-run", "dont-store", "auto"
-  (store-persistent, store-volatile?)
-
 * introduce mix of BindTo and Requisite
 
 * journalctl: show multiline log messages sanely, expand tabs, and show all valid utf8 messages
index bbc9b34..ffe988a 100644 (file)
                         </varlistentry>
 
                         <varlistentry>
+                                <term><option>--directory=</option></term>
+                                <term><option>-D</option></term>
+
+                                <listitem><para>Takes an absolute
+                                directory path as argument. If
+                                specified will opearte on the
+                                specified journal directory instead of
+                                the default runtime and system journal
+                                paths.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
                                 <term><option>--new-id128</option></term>
 
                                 <listitem><para>Instead of showing
index bcffa35..929dfcd 100644 (file)
 #include "list.h"
 
 typedef struct Match Match;
+typedef struct Location Location;
+typedef struct Directory Directory;
+
+typedef enum location_type {
+        LOCATION_HEAD,
+        LOCATION_TAIL,
+        LOCATION_DISCRETE
+} location_type_t;
 
 struct Match {
         char *data;
@@ -40,13 +48,7 @@ struct Match {
         LIST_FIELDS(Match, matches);
 };
 
-typedef enum location_type {
-        LOCATION_HEAD,
-        LOCATION_TAIL,
-        LOCATION_DISCRETE
-} location_type_t;
-
-typedef struct Location {
+struct Location {
         location_type_t type;
 
         uint64_t seqnum;
@@ -62,7 +64,13 @@ typedef struct Location {
 
         uint64_t xor_hash;
         bool xor_hash_set;
-} Location;
+};
+
+struct Directory {
+        char *path;
+        int wd;
+        bool is_root;
+};
 
 struct sd_journal {
         int flags;
@@ -73,12 +81,15 @@ struct sd_journal {
         JournalFile *current_file;
         uint64_t current_field;
 
+        Hashmap *directories_by_path;
+        Hashmap *directories_by_wd;
+
         int inotify_fd;
-        Hashmap *inotify_wd_dirs;
-        Hashmap *inotify_wd_roots;
 
         LIST_HEAD(Match, matches);
         unsigned n_matches;
+
+        unsigned current_invalidate_counter, last_invalidate_counter;
 };
 
 #endif
index 7d8b8e5..4c975d3 100644 (file)
@@ -39,6 +39,7 @@
 #include "build.h"
 #include "pager.h"
 #include "logs-show.h"
+#include "strv.h"
 
 static OutputMode arg_output = OUTPUT_SHORT;
 static bool arg_follow = false;
@@ -50,6 +51,7 @@ static bool arg_new_id128 = false;
 static bool arg_quiet = false;
 static bool arg_local = false;
 static bool arg_this_boot = false;
+static const char *arg_directory = NULL;
 
 static int help(void) {
 
@@ -67,6 +69,7 @@ static int help(void) {
                "  -q --quiet          Don't show privilege warning\n"
                "  -l --local          Only local entries\n"
                "  -b --this-boot      Show data only from current boot\n"
+               "  -D --directory=PATH Show journal files from directory\n"
                "     --new-id128      Generate a new 128 Bit id\n",
                program_invocation_short_name);
 
@@ -95,6 +98,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "quiet",     no_argument,       NULL, 'q'           },
                 { "local",     no_argument,       NULL, 'l'           },
                 { "this-boot", no_argument,       NULL, 'b'           },
+                { "directory", required_argument, NULL, 'D'           },
                 { NULL,        0,                 NULL, 0             }
         };
 
@@ -103,7 +107,7 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
-        while ((c = getopt_long(argc, argv, "hfo:an:qlb", options, NULL)) >= 0) {
+        while ((c = getopt_long(argc, argv, "hfo:an:qlbD:", options, NULL)) >= 0) {
 
                 switch (c) {
 
@@ -166,6 +170,10 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_this_boot = true;
                         break;
 
+                case 'D':
+                        arg_directory = optarg;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -209,68 +217,26 @@ static int generate_new_id128(void) {
         return 0;
 }
 
-int main(int argc, char *argv[]) {
-        int r, i;
-        sd_journal *j = NULL;
-        unsigned line = 0;
-        bool need_seek = false;
-        struct stat st;
-
-        log_parse_environment();
-        log_open();
-
-        r = parse_argv(argc, argv);
-        if (r <= 0)
-                goto finish;
-
-        if (arg_new_id128) {
-                r = generate_new_id128();
-                goto finish;
-        }
-
-#ifdef HAVE_ACL
-        if (!arg_quiet && geteuid() != 0 && in_group("adm") <= 0)
-                log_warning("Showing user generated messages only. Users in the group 'adm' can see all messages. Pass -q to turn this message off.");
-#endif
-
-        r = sd_journal_open(&j, arg_local ? SD_JOURNAL_LOCAL_ONLY : 0);
-        if (r < 0) {
-                log_error("Failed to open journal: %s", strerror(-r));
-                goto finish;
-        }
-
-        if (arg_this_boot) {
-                char match[9+32+1] = "_BOOT_ID=";
-                sd_id128_t boot_id;
+static int add_matches(sd_journal *j, char **args) {
+        char **i;
+        int r;
 
-                r = sd_id128_get_boot(&boot_id);
-                if (r < 0) {
-                        log_error("Failed to get boot id: %s", strerror(-r));
-                        goto finish;
-                }
+        assert(j);
 
-                sd_id128_to_string(boot_id, match + 9);
+        STRV_FOREACH(i, args) {
 
-                r = sd_journal_add_match(j, match, strlen(match));
-                if (r < 0) {
-                        log_error("Failed to add match: %s", strerror(-r));
-                        goto finish;
-                }
-        }
-
-        for (i = optind; i < argc; i++) {
-                if (path_is_absolute(argv[i])) {
-                        char *p = NULL;
+                if (path_is_absolute(*i)) {
+                        char *p;
                         const char *path;
+                        struct stat st;
 
-                        p = canonicalize_file_name(argv[i]);
-                        path = p ? p : argv[i];
+                        p = canonicalize_file_name(*i);
+                        path = p ? p : *i;
 
                         if (stat(path, &st) < 0)  {
                                 free(p);
                                 log_error("Couldn't stat file: %m");
-                                r = -errno;
-                                goto finish;
+                                return -errno;
                         }
 
                         if (S_ISREG(st.st_mode) && (0111 & st.st_mode)) {
@@ -280,27 +246,95 @@ int main(int argc, char *argv[]) {
                                 if (!t) {
                                         free(p);
                                         log_error("Out of memory");
-                                        goto finish;
+                                        return -ENOMEM;
                                 }
 
                                 r = sd_journal_add_match(j, t, strlen(t));
                                 free(t);
                         } else {
                                 free(p);
-                                log_error("File is not a regular file or is not executable: %s", argv[i]);
-                                goto finish;
+                                log_error("File is not a regular file or is not executable: %s", *i);
+                                return -EINVAL;
                         }
 
                         free(p);
                 } else
-                        r = sd_journal_add_match(j, argv[i], strlen(argv[i]));
+                        r = sd_journal_add_match(j, *i, strlen(*i));
 
                 if (r < 0) {
                         log_error("Failed to add match: %s", strerror(-r));
-                        goto finish;
+                        return r;
                 }
         }
 
+        return 0;
+}
+
+static int add_this_boot(sd_journal *j) {
+        char match[9+32+1] = "_BOOT_ID=";
+        sd_id128_t boot_id;
+        int r;
+
+        if (!arg_this_boot)
+                return 0;
+
+        r = sd_id128_get_boot(&boot_id);
+        if (r < 0) {
+                log_error("Failed to get boot id: %s", strerror(-r));
+                return r;
+        }
+
+        sd_id128_to_string(boot_id, match + 9);
+        r = sd_journal_add_match(j, match, strlen(match));
+        if (r < 0) {
+                log_error("Failed to add match: %s", strerror(-r));
+                return r;
+        }
+
+        return 0;
+}
+
+int main(int argc, char *argv[]) {
+        int r;
+        sd_journal *j = NULL;
+        unsigned line = 0;
+        bool need_seek = false;
+
+        log_parse_environment();
+        log_open();
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                goto finish;
+
+        if (arg_new_id128) {
+                r = generate_new_id128();
+                goto finish;
+        }
+
+#ifdef HAVE_ACL
+        if (!arg_quiet && geteuid() != 0 && in_group("adm") <= 0)
+                log_warning("Showing user generated messages only. Users in the group 'adm' can see all messages. Pass -q to turn this message off.");
+#endif
+
+        if (arg_directory)
+                r = sd_journal_open_directory(&j, arg_directory, 0);
+        else
+                r = sd_journal_open(&j, arg_local ? SD_JOURNAL_LOCAL_ONLY : 0);
+
+        if (r < 0) {
+                log_error("Failed to open journal: %s", strerror(-r));
+                goto finish;
+        }
+
+        r = add_this_boot(j);
+        if (r < 0)
+                goto finish;
+
+        r = add_matches(j, argv + optind);
+        if (r < 0)
+                goto finish;
+
         if (!arg_quiet) {
                 usec_t start, end;
                 char start_buf[FORMAT_TIMESTAMP_MAX], end_buf[FORMAT_TIMESTAMP_MAX];
index d1ba9e8..fa4519a 100644 (file)
@@ -61,4 +61,5 @@ global:
 LIBSYSTEMD_JOURNAL_187 {
 global:
         sd_journal_wait;
+        sd_journal_open_directory;
 } LIBSYSTEMD_JOURNAL_184;
index 149dc10..6331f04 100644 (file)
@@ -935,8 +935,8 @@ _public_ int sd_journal_seek_tail(sd_journal *j) {
         return 0;
 }
 
-static int add_file(sd_journal *j, const char *prefix, const char *dir, const char *filename) {
-        char *fn;
+static int add_file(sd_journal *j, const char *prefix, const char *filename) {
+        char *path;
         int r;
         JournalFile *f;
 
@@ -949,27 +949,23 @@ static int add_file(sd_journal *j, const char *prefix, const char *dir, const ch
              (startswith(filename, "system@") && endswith(filename, ".journal"))))
                 return 0;
 
-        if (dir)
-                fn = join(prefix, "/", dir, "/", filename, NULL);
-        else
-                fn = join(prefix, "/", filename, NULL);
-
-        if (!fn)
+        path = join(prefix, "/", filename, NULL);
+        if (!path)
                 return -ENOMEM;
 
-        if (hashmap_get(j->files, fn)) {
-                free(fn);
+        if (hashmap_get(j->files, path)) {
+                free(path);
                 return 0;
         }
 
         if (hashmap_size(j->files) >= JOURNAL_FILES_MAX) {
-                log_debug("Too many open journal files, not adding %s, ignoring.", fn);
-                free(fn);
+                log_debug("Too many open journal files, not adding %s, ignoring.", path);
+                free(path);
                 return 0;
         }
 
-        r = journal_file_open(fn, O_RDONLY, 0, NULL, &f);
-        free(fn);
+        r = journal_file_open(path, O_RDONLY, 0, NULL, &f);
+        free(path);
 
         if (r < 0) {
                 if (errno == ENOENT)
@@ -986,166 +982,302 @@ static int add_file(sd_journal *j, const char *prefix, const char *dir, const ch
                 return r;
         }
 
+        j->current_invalidate_counter ++;
+
         log_debug("File %s got added.", f->path);
 
         return 0;
 }
 
-static int remove_file(sd_journal *j, const char *prefix, const char *dir, const char *filename) {
-        char *fn;
+static int remove_file(sd_journal *j, const char *prefix, const char *filename) {
+        char *path;
         JournalFile *f;
 
         assert(j);
         assert(prefix);
         assert(filename);
 
-        if (dir)
-                fn = join(prefix, "/", dir, "/", filename, NULL);
-        else
-                fn = join(prefix, "/", filename, NULL);
-
-        if (!fn)
+        path = join(prefix, "/", filename, NULL);
+        if (!path)
                 return -ENOMEM;
 
-        f = hashmap_get(j->files, fn);
-        free(fn);
-
+        f = hashmap_get(j->files, path);
+        free(path);
         if (!f)
                 return 0;
 
         hashmap_remove(j->files, f->path);
         journal_file_close(f);
 
+        j->current_invalidate_counter ++;
+
         log_debug("File %s got removed.", f->path);
         return 0;
 }
 
-static int add_directory(sd_journal *j, const char *prefix, const char *dir) {
-        char *fn;
+static int add_directory(sd_journal *j, const char *prefix, const char *dirname) {
+        char *path;
         int r;
         DIR *d;
-        int wd;
         sd_id128_t id, mid;
+        Directory *m;
 
         assert(j);
         assert(prefix);
-        assert(dir);
+        assert(dirname);
 
         if ((j->flags & SD_JOURNAL_LOCAL_ONLY) &&
-            (sd_id128_from_string(dir, &id) < 0 ||
+            (sd_id128_from_string(dirname, &id) < 0 ||
              sd_id128_get_machine(&mid) < 0 ||
              !sd_id128_equal(id, mid)))
             return 0;
 
-        fn = join(prefix, "/", dir, NULL);
-        if (!fn)
+        path = join(prefix, "/", dirname, NULL);
+        if (!path)
                 return -ENOMEM;
 
-        d = opendir(fn);
-
+        d = opendir(path);
         if (!d) {
-                free(fn);
+                log_debug("Failed to open %s: %m", path);
+                free(path);
+
                 if (errno == ENOENT)
                         return 0;
-
                 return -errno;
         }
 
-        wd = inotify_add_watch(j->inotify_fd, fn,
-                               IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE|
-                               IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT|
-                               IN_DONT_FOLLOW|IN_ONLYDIR);
-        if (wd > 0) {
-                if (hashmap_put(j->inotify_wd_dirs, INT_TO_PTR(wd), fn) < 0)
-                        inotify_rm_watch(j->inotify_fd, wd);
-                else
-                        fn = NULL;
+        m = hashmap_get(j->directories_by_path, path);
+        if (!m) {
+                m = new0(Directory, 1);
+                if (!m) {
+                        closedir(d);
+                        free(path);
+                        return -ENOMEM;
+                }
+
+                m->is_root = false;
+                m->path = path;
+
+                if (hashmap_put(j->directories_by_path, m->path, m) < 0) {
+                        closedir(d);
+                        free(m->path);
+                        free(m);
+                        return -ENOMEM;
+                }
+
+                j->current_invalidate_counter ++;
+
+                log_debug("Directory %s got added.", m->path);
+
+        } else if (m->is_root) {
+                free (path);
+                closedir(d);
+                return 0;
+        }  else
+                free(path);
+
+        if (m->wd <= 0 && j->inotify_fd >= 0) {
+
+                m->wd = inotify_add_watch(j->inotify_fd, m->path,
+                                          IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE|
+                                          IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT|
+                                          IN_DONT_FOLLOW|IN_ONLYDIR);
+
+                if (m->wd > 0 && hashmap_put(j->directories_by_wd, INT_TO_PTR(m->wd), m) < 0)
+                        inotify_rm_watch(j->inotify_fd, m->wd);
+        }
+
+        for (;;) {
+                struct dirent buf, *de;
+
+                r = readdir_r(d, &buf, &de);
+                if (r != 0 || !de)
+                        break;
+
+                if (dirent_is_file_with_suffix(de, ".journal")) {
+                        r = add_file(j, m->path, de->d_name);
+                        if (r < 0)
+                                log_debug("Failed to add file %s/%s: %s", m->path, de->d_name, strerror(-r));
+                }
+        }
+
+        closedir(d);
+
+        return 0;
+}
+
+static int add_root_directory(sd_journal *j, const char *p) {
+        DIR *d;
+        Directory *m;
+        int r;
+
+        assert(j);
+        assert(p);
+
+        if ((j->flags & SD_JOURNAL_RUNTIME_ONLY) &&
+            !path_startswith(p, "/run"))
+                return -EINVAL;
+
+        d = opendir(p);
+        if (!d)
+                return -errno;
+
+        m = hashmap_get(j->directories_by_path, p);
+        if (!m) {
+                m = new0(Directory, 1);
+                if (!m) {
+                        closedir(d);
+                        return -ENOMEM;
+                }
+
+                m->is_root = true;
+                m->path = strdup(p);
+                if (!m->path) {
+                        closedir(d);
+                        free(m);
+                        return -ENOMEM;
+                }
+
+                if (hashmap_put(j->directories_by_path, m->path, m) < 0) {
+                        closedir(d);
+                        free(m->path);
+                        free(m);
+                        return -ENOMEM;
+                }
+
+                j->current_invalidate_counter ++;
+
+                log_debug("Root directory %s got added.", m->path);
+
+        } else if (!m->is_root) {
+                closedir(d);
+                return 0;
         }
 
-        free(fn);
+        if (m->wd <= 0 && j->inotify_fd >= 0) {
+
+                m->wd = inotify_add_watch(j->inotify_fd, m->path,
+                                          IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE|
+                                          IN_DONT_FOLLOW|IN_ONLYDIR);
+
+                if (m->wd > 0 && hashmap_put(j->directories_by_wd, INT_TO_PTR(m->wd), m) < 0)
+                        inotify_rm_watch(j->inotify_fd, m->wd);
+        }
 
         for (;;) {
                 struct dirent buf, *de;
+                sd_id128_t id;
 
                 r = readdir_r(d, &buf, &de);
                 if (r != 0 || !de)
                         break;
 
-                if (!dirent_is_file_with_suffix(de, ".journal"))
-                        continue;
+                if (dirent_is_file_with_suffix(de, ".journal")) {
+                        r = add_file(j, m->path, de->d_name);
+                        if (r < 0)
+                                log_debug("Failed to add file %s/%s: %s", m->path, de->d_name, strerror(-r));
 
-                r = add_file(j, prefix, dir, de->d_name);
-                if (r < 0)
-                        log_debug("Failed to add file %s/%s/%s: %s", prefix, dir, de->d_name, strerror(-r));
+                } else if ((de->d_type == DT_DIR || de->d_type == DT_UNKNOWN) &&
+                           sd_id128_from_string(de->d_name, &id) >= 0) {
+
+                        r = add_directory(j, m->path, de->d_name);
+                        if (r < 0)
+                                log_debug("Failed to add directory %s/%s: %s", m->path, de->d_name, strerror(-r));
+                }
         }
 
         closedir(d);
 
-        log_debug("Directory %s/%s got added.", prefix, dir);
+        return 0;
+}
+
+static int remove_directory(sd_journal *j, Directory *d) {
+        assert(j);
+
+        if (d->wd > 0) {
+                hashmap_remove(j->directories_by_wd, INT_TO_PTR(d->wd));
+
+                if (j->inotify_fd >= 0)
+                        inotify_rm_watch(j->inotify_fd, d->wd);
+        }
+
+        hashmap_remove(j->directories_by_path, d->path);
+
+        if (d->is_root)
+                log_debug("Root directory %s got removed.", d->path);
+        else
+                log_debug("Directory %s got removed.", d->path);
+
+        free(d->path);
+        free(d);
 
         return 0;
 }
 
-static void remove_directory_wd(sd_journal *j, int wd) {
-        char *p;
+static int add_search_paths(sd_journal *j) {
+
+        const char search_paths[] =
+                "/run/log/journal\0"
+                "/var/log/journal\0";
+        const char *p;
 
         assert(j);
-        assert(wd > 0);
 
-        if (j->inotify_fd >= 0)
-                inotify_rm_watch(j->inotify_fd, wd);
+        /* We ignore most errors here, since the idea is to only open
+         * what's actually accessible, and ignore the rest. */
 
-        p = hashmap_remove(j->inotify_wd_dirs, INT_TO_PTR(wd));
+        NULSTR_FOREACH(p, search_paths)
+                add_root_directory(j, p);
 
-        if (p) {
-                log_debug("Directory %s got removed.", p);
-                free(p);
-        }
+        return 0;
 }
 
-static void add_root_wd(sd_journal *j, const char *p) {
-        int wd;
-        char *k;
-
+static int allocate_inotify(sd_journal *j) {
         assert(j);
-        assert(p);
 
-        wd = inotify_add_watch(j->inotify_fd, p,
-                               IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE|
-                               IN_DONT_FOLLOW|IN_ONLYDIR);
-        if (wd <= 0)
-                return;
+        if (j->inotify_fd < 0) {
+                j->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
+                if (j->inotify_fd < 0)
+                        return -errno;
+        }
 
-        k = strdup(p);
-        if (!k || hashmap_put(j->inotify_wd_roots, INT_TO_PTR(wd), k) < 0) {
-                inotify_rm_watch(j->inotify_fd, wd);
-                free(k);
+        if (!j->directories_by_wd) {
+                j->directories_by_wd = hashmap_new(trivial_hash_func, trivial_compare_func);
+                if (!j->directories_by_wd)
+                        return -ENOMEM;
         }
+
+        return 0;
 }
 
-static void remove_root_wd(sd_journal *j, int wd) {
-        char *p;
+static sd_journal *journal_new(int flags) {
+        sd_journal *j;
 
-        assert(j);
-        assert(wd > 0);
+        j = new0(sd_journal, 1);
+        if (!j)
+                return NULL;
 
-        if (j->inotify_fd >= 0)
-                inotify_rm_watch(j->inotify_fd, wd);
+        j->inotify_fd = -1;
+        j->flags = flags;
 
-        p = hashmap_remove(j->inotify_wd_roots, INT_TO_PTR(wd));
+        j->files = hashmap_new(string_hash_func, string_compare_func);
+        if (!j->files) {
+                free(j);
+                return NULL;
+        }
 
-        if (p) {
-                log_debug("Root %s got removed.", p);
-                free(p);
+        j->directories_by_path = hashmap_new(string_hash_func, string_compare_func);
+        if (!j->directories_by_path) {
+                hashmap_free(j->files);
+                free(j);
+                return NULL;
         }
+
+        return j;
 }
 
 _public_ int sd_journal_open(sd_journal **ret, int flags) {
         sd_journal *j;
-        const char *p;
-        const char search_paths[] =
-                "/run/log/journal\0"
-                "/var/log/journal\0";
         int r;
 
         if (!ret)
@@ -1156,75 +1288,43 @@ _public_ int sd_journal_open(sd_journal **ret, int flags) {
                       SD_JOURNAL_SYSTEM_ONLY))
                 return -EINVAL;
 
-        j = new0(sd_journal, 1);
+        j = journal_new(flags);
         if (!j)
                 return -ENOMEM;
 
-        j->flags = flags;
-
-        j->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
-        if (j->inotify_fd < 0) {
-                r = -errno;
-                goto fail;
-        }
-
-        j->files = hashmap_new(string_hash_func, string_compare_func);
-        if (!j->files) {
-                r = -ENOMEM;
-                goto fail;
-        }
-
-        j->inotify_wd_dirs = hashmap_new(trivial_hash_func, trivial_compare_func);
-        j->inotify_wd_roots = hashmap_new(trivial_hash_func, trivial_compare_func);
-
-        if (!j->inotify_wd_dirs || !j->inotify_wd_roots) {
-                r = -ENOMEM;
+        r = add_search_paths(j);
+        if (r < 0)
                 goto fail;
-        }
 
-        /* We ignore most errors here, since the idea is to only open
-         * what's actually accessible, and ignore the rest. */
-
-        NULSTR_FOREACH(p, search_paths) {
-                DIR *d;
-
-                if ((flags & SD_JOURNAL_RUNTIME_ONLY) &&
-                    !path_startswith(p, "/run"))
-                        continue;
+        *ret = j;
+        return 0;
 
-                d = opendir(p);
-                if (!d) {
-                        if (errno != ENOENT)
-                                log_debug("Failed to open %s: %m", p);
-                        continue;
-                }
+fail:
+        sd_journal_close(j);
 
-                add_root_wd(j, p);
+        return r;
+}
 
-                for (;;) {
-                        struct dirent buf, *de;
-                        sd_id128_t id;
+_public_ int sd_journal_open_directory(sd_journal **ret, const char *path, int flags) {
+        sd_journal *j;
+        int r;
 
-                        r = readdir_r(d, &buf, &de);
-                        if (r != 0 || !de)
-                                break;
+        if (!ret)
+                return -EINVAL;
 
-                        if (dirent_is_file_with_suffix(de, ".journal")) {
-                                r = add_file(j, p, NULL, de->d_name);
-                                if (r < 0)
-                                        log_debug("Failed to add file %s/%s: %s", p, de->d_name, strerror(-r));
+        if (!path || !path_is_absolute(path))
+                return -EINVAL;
 
-                        } else if ((de->d_type == DT_DIR || de->d_type == DT_UNKNOWN) &&
-                                   sd_id128_from_string(de->d_name, &id) >= 0) {
+        if (flags != 0)
+                return -EINVAL;
 
-                                r = add_directory(j, p, de->d_name);
-                                if (r < 0)
-                                        log_debug("Failed to add directory %s/%s: %s", p, de->d_name, strerror(-r));
-                        }
-                }
+        j = journal_new(flags);
+        if (!j)
+                return -ENOMEM;
 
-                closedir(d);
-        }
+        r = add_root_directory(j, path);
+        if (r < 0)
+                goto fail;
 
         *ret = j;
         return 0;
@@ -1233,44 +1333,34 @@ fail:
         sd_journal_close(j);
 
         return r;
-};
+}
 
 _public_ void sd_journal_close(sd_journal *j) {
+        Directory *d;
+        JournalFile *f;
+
         if (!j)
                 return;
 
-        if (j->inotify_wd_dirs) {
-                void *k;
-
-                while ((k = hashmap_first_key(j->inotify_wd_dirs)))
-                        remove_directory_wd(j, PTR_TO_INT(k));
-
-                hashmap_free(j->inotify_wd_dirs);
-        }
-
-        if (j->inotify_wd_roots) {
-                void *k;
-
-                while ((k = hashmap_first_key(j->inotify_wd_roots)))
-                        remove_root_wd(j, PTR_TO_INT(k));
-
-                hashmap_free(j->inotify_wd_roots);
-        }
+        while ((f = hashmap_steal_first(j->files)))
+                journal_file_close(f);
 
-        if (j->files) {
-                JournalFile *f;
+        hashmap_free(j->files);
 
-                while ((f = hashmap_steal_first(j->files)))
-                        journal_file_close(f);
+        while ((d = hashmap_first(j->directories_by_path)))
+                remove_directory(j, d);
 
-                hashmap_free(j->files);
-        }
+        while ((d = hashmap_first(j->directories_by_wd)))
+                remove_directory(j, d);
 
-        sd_journal_flush_matches(j);
+        hashmap_free(j->directories_by_path);
+        hashmap_free(j->directories_by_wd);
 
         if (j->inotify_fd >= 0)
                 close_nointr_nofail(j->inotify_fd);
 
+        sd_journal_flush_matches(j);
+
         free(j);
 }
 
@@ -1506,78 +1596,74 @@ _public_ void sd_journal_restart_data(sd_journal *j) {
 }
 
 _public_ int sd_journal_get_fd(sd_journal *j) {
+        int r;
+
         if (!j)
                 return -EINVAL;
 
+        if (j->inotify_fd >= 0)
+                return j->inotify_fd;
+
+        r = allocate_inotify(j);
+        if (r < 0)
+                return r;
+
+        /* Iterate through all dirs again, to add them to the
+         * inotify */
+        r = add_search_paths(j);
+        if (r < 0)
+                return r;
+
         return j->inotify_fd;
 }
 
 static void process_inotify_event(sd_journal *j, struct inotify_event *e) {
-        char *p;
+        Directory *d;
         int r;
 
         assert(j);
         assert(e);
 
         /* Is this a subdirectory we watch? */
-        p = hashmap_get(j->inotify_wd_dirs, INT_TO_PTR(e->wd));
-        if (p) {
+        d = hashmap_get(j->directories_by_wd, INT_TO_PTR(e->wd));
+        if (d) {
+                sd_id128_t id;
 
                 if (!(e->mask & IN_ISDIR) && e->len > 0 && endswith(e->name, ".journal")) {
 
                         /* Event for a journal file */
 
                         if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
-                                r = add_file(j, p, NULL, e->name);
+                                r = add_file(j, d->path, e->name);
                                 if (r < 0)
-                                        log_debug("Failed to add file %s/%s: %s", p, e->name, strerror(-r));
+                                        log_debug("Failed to add file %s/%s: %s", d->path, e->name, strerror(-r));
+
                         } else if (e->mask & (IN_DELETE|IN_UNMOUNT)) {
 
-                                r = remove_file(j, p, NULL, e->name);
+                                r = remove_file(j, d->path, e->name);
                                 if (r < 0)
-                                        log_debug("Failed to remove file %s/%s: %s", p, e->name, strerror(-r));
+                                        log_debug("Failed to remove file %s/%s: %s", d->path, e->name, strerror(-r));
                         }
 
-                } else if (e->len == 0) {
+                } else if (!d->is_root && e->len == 0) {
 
-                        /* Event for the directory itself */
-
-                        if (e->mask & (IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT))
-                                remove_directory_wd(j, e->wd);
-                }
-
-                return;
-        }
-
-        /* Must be the root directory then? */
-        p = hashmap_get(j->inotify_wd_roots, INT_TO_PTR(e->wd));
-        if (p) {
-                sd_id128_t id;
+                        /* Event for a subdirectory */
 
-                if (!(e->mask & IN_ISDIR) && e->len > 0 && endswith(e->name, ".journal")) {
-
-                        /* Event for a journal file */
-
-                        if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
-                                r = add_file(j, p, NULL, e->name);
-                                if (r < 0)
-                                        log_debug("Failed to add file %s/%s: %s", p, e->name, strerror(-r));
-                        } else if (e->mask & (IN_DELETE|IN_UNMOUNT)) {
-
-                                r = remove_file(j, p, NULL, e->name);
+                        if (e->mask & (IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT)) {
+                                r = remove_directory(j, d);
                                 if (r < 0)
-                                        log_debug("Failed to remove file %s/%s: %s", p, e->name, strerror(-r));
+                                        log_debug("Failed to remove directory %s: %s", d->path, strerror(-r));
                         }
 
-                } else if ((e->mask & IN_ISDIR) && e->len > 0 && sd_id128_from_string(e->name, &id) >= 0) {
 
-                        /* Event for subdirectory */
+                } else if (d->is_root && (e->mask & IN_ISDIR) && e->len > 0 && sd_id128_from_string(e->name, &id) >= 0) {
 
-                        if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
+                        /* Event for root directory */
 
-                                r = add_directory(j, p, e->name);
+                        if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
+                                r = add_directory(j, d->path, e->name);
                                 if (r < 0)
-                                        log_debug("Failed to add directory %s/%s: %s", p, e->name, strerror(-r));
+                                        log_debug("Failed to add directory %s/%s: %s", d->path, e->name, strerror(-r));
                         }
                 }
 
@@ -1590,8 +1676,20 @@ static void process_inotify_event(sd_journal *j, struct inotify_event *e) {
         log_warning("Unknown inotify event.");
 }
 
+static int determine_change(sd_journal *j) {
+        bool b;
+
+        assert(j);
+
+        b = j->current_invalidate_counter != j->last_invalidate_counter;
+        j->last_invalidate_counter = j->current_invalidate_counter;
+
+        return b ? SD_JOURNAL_INVALIDATE : SD_JOURNAL_APPEND;
+}
+
 _public_ int sd_journal_process(sd_journal *j) {
         uint8_t buffer[sizeof(struct inotify_event) + FILENAME_MAX];
+        bool got_something = false;
 
         if (!j)
                 return -EINVAL;
@@ -1602,12 +1700,14 @@ _public_ int sd_journal_process(sd_journal *j) {
 
                 l = read(j->inotify_fd, buffer, sizeof(buffer));
                 if (l < 0) {
-                        if (errno == EINTR || errno == EAGAIN)
-                                return 0;
+                        if (errno == EAGAIN || errno == EINTR)
+                                return got_something ? determine_change(j) : SD_JOURNAL_NOP;
 
                         return -errno;
                 }
 
+                got_something = true;
+
                 e = (struct inotify_event*) buffer;
                 while (l > 0) {
                         size_t step;
@@ -1621,20 +1721,38 @@ _public_ int sd_journal_process(sd_journal *j) {
                         l -= step;
                 }
         }
+
+        return determine_change(j);
 }
 
 _public_ int sd_journal_wait(sd_journal *j, uint64_t timeout_usec) {
-        int r, k;
+        int r;
 
         assert(j);
 
-        r = fd_wait_for_event(j->inotify_fd, POLLIN, timeout_usec);
-        k = sd_journal_process(j);
+        if (j->inotify_fd < 0) {
+
+                /* This is the first invocation, hence create the
+                 * inotify watch */
+                r = sd_journal_get_fd(j);
+                if (r < 0)
+                        return r;
+
+                /* The journal might have changed since the context
+                 * object was created and we weren't watching before,
+                 * hence don't wait for anything, and return
+                 * immediately. */
+                return determine_change(j);
+        }
+
+        do {
+                r = fd_wait_for_event(j->inotify_fd, POLLIN, timeout_usec);
+        } while (r == -EINTR);
 
         if (r < 0)
                 return r;
 
-        return k;
+        return sd_journal_process(j);
 }
 
 _public_ int sd_journal_get_cutoff_realtime_usec(sd_journal *j, uint64_t *from, uint64_t *to) {
index ee4acff..ab968b9 100644 (file)
@@ -72,6 +72,7 @@ enum {
 };
 
 int sd_journal_open(sd_journal **ret, int flags);
+int sd_journal_open_directory(sd_journal **ret, const char *path, int flags);
 void sd_journal_close(sd_journal *j);
 
 int sd_journal_previous(sd_journal *j);
@@ -107,8 +108,7 @@ int sd_journal_get_cutoff_monotonic_usec(sd_journal *j, const sd_id128_t boot_id
 enum {
         SD_JOURNAL_NOP,
         SD_JOURNAL_APPEND,
-        SD_JOURNAL_INVALIDATE_ADD,
-        SD_JOURNAL_INVALIDATE_REMOVE
+        SD_JOURNAL_INVALIDATE
 };
 
 int sd_journal_get_fd(sd_journal *j);