Copyright 2011 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
- under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
+ Lesser General Public License for more details.
- You should have received a copy of the GNU General Public License
+ You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
+#include <unistd.h>
+#include <sys/inotify.h>
#include "sd-journal.h"
#include "journal-def.h"
#include "journal-file.h"
#include "hashmap.h"
#include "list.h"
+#include "path-util.h"
#include "lookup3.h"
+#include "compress.h"
+#include "journal-internal.h"
-typedef struct Match Match;
-
-struct Match {
- char *data;
- size_t size;
- uint64_t le_hash;
-
- LIST_FIELDS(Match, matches);
-};
-
-typedef enum location_type {
- LOCATION_HEAD,
- LOCATION_TAIL,
- LOCATION_DISCRETE
-} location_type_t;
-
-typedef struct Location {
- location_type_t type;
-
- uint64_t seqnum;
- sd_id128_t seqnum_id;
- bool seqnum_set;
-
- uint64_t realtime;
- bool realtime_set;
-
- uint64_t monotonic;
- sd_id128_t boot_id;
- bool monotonic_set;
-
- uint64_t xor_hash;
- bool xor_hash_set;
-} Location;
-
-struct sd_journal {
- Hashmap *files;
-
- Location current_location;
- JournalFile *current_file;
- uint64_t current_field;
-
- LIST_HEAD(Match, matches);
- unsigned n_matches;
-};
+#define JOURNAL_FILES_MAX 1024
static void detach_location(sd_journal *j) {
Iterator i;
l->seqnum_id = f->header->seqnum_id;
l->realtime = le64toh(o->entry.realtime);
l->monotonic = le64toh(o->entry.monotonic);
- l->boot_id = le64toh(o->entry.boot_id);
+ l->boot_id = o->entry.boot_id;
l->xor_hash = le64toh(o->entry.xor_hash);
l->seqnum_set = l->realtime_set = l->monotonic_set = l->xor_hash_set = true;
return -EINVAL;
}
-int sd_journal_add_match(sd_journal *j, const void *data, size_t size) {
+_public_ int sd_journal_add_match(sd_journal *j, const void *data, size_t size) {
Match *m, *after = NULL;
- uint64_t le_hash;
-
- assert(j);
+ le64_t le_hash;
- if (size <= 0)
+ if (!j)
+ return -EINVAL;
+ if (!data)
+ return -EINVAL;
+ if (size <= 1)
+ return -EINVAL;
+ if (!memchr(data, '=', size))
+ return -EINVAL;
+ if (*(char*) data == '=')
return -EINVAL;
- assert(data);
+ /* FIXME: iterating with multiple matches is currently
+ * broken */
+ if (j->matches)
+ return -ENOTSUP;
le_hash = htole64(hash64(data, size));
return 0;
}
-void sd_journal_flush_matches(sd_journal *j) {
- assert(j);
+_public_ void sd_journal_flush_matches(sd_journal *j) {
+ if (!j)
+ return;
while (j->matches) {
Match *m = j->matches;
else if (j->current_location.seqnum_set &&
sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id))
r = journal_file_move_to_entry_by_seqnum(f, j->current_location.seqnum, direction, &o, &p);
- else if (j->current_location.monotonic_set)
+ else if (j->current_location.monotonic_set) {
r = journal_file_move_to_entry_by_monotonic(f, j->current_location.boot_id, j->current_location.monotonic, direction, &o, &p);
- else if (j->current_location.realtime_set)
+
+ if (r == -ENOENT) {
+ /* boot id unknown in this file */
+ if (j->current_location.realtime_set)
+ r = journal_file_move_to_entry_by_realtime(f, j->current_location.realtime, direction, &o, &p);
+ else
+ r = journal_file_next_entry(f, NULL, 0, direction, &o, &p);
+ }
+ } else if (j->current_location.realtime_set)
r = journal_file_move_to_entry_by_realtime(f, j->current_location.realtime, direction, &o, &p);
else
r = journal_file_next_entry(f, NULL, 0, direction, &o, &p);
r = journal_file_move_to_entry_by_monotonic(f, j->current_location.boot_id, j->current_location.monotonic, direction, &o, &p);
if (r <= 0)
- return r;
+ return r == -ENOENT ? 0 : r;
}
LIST_FOREACH(matches, m, j->matches) {
Object *c, *d;
uint64_t cp, dp;
- r = journal_file_find_data_object_with_hash(f, m->data, m->size, m->le_hash, &d, &dp);
+ r = journal_file_find_data_object_with_hash(f, m->data, m->size, le64toh(m->le_hash), &d, &dp);
if (r <= 0)
return r;
else
r = journal_file_next_entry_for_data(f, NULL, 0, dp, direction, &c, &cp);
+ if (r < 0)
+ return r;
+
if (!term_match) {
term_match = m;
(direction == DIRECTION_DOWN && cp < tp) ||
(direction == DIRECTION_UP && cp > tp)) {
to = c;
- tp = tp;
+ tp = cp;
}
}
uint64_t np, n;
bool found, term_result = false;
Match *m, *term_match = NULL;
+ Object *npo = NULL;
n = journal_file_entry_n_items(c);
/* Make sure we don't match the entry we are starting
* from. */
- found = cp > *offset;
+ found = cp != *offset;
np = 0;
LIST_FOREACH(matches, m, j->matches) {
uint64_t q, k;
+ Object *qo = NULL;
/* Let's check if this is the beginning of a
* new term, i.e. has a different field prefix
* where we'd have to try next, in case the other
* matches are not OK */
- r = journal_file_next_entry_for_data(f, c, cp, le64toh(c->entry.items[k].object_offset), direction, NULL, &q);
+ r = journal_file_next_entry_for_data(f, c, cp, le64toh(c->entry.items[k].object_offset), direction, &qo, &q);
+ /* This pointer is invalidated if the window was
+ * remapped. May need to re-fetch it later */
+ c = NULL;
+ if (r < 0)
+ return r;
+
if (r > 0) {
if (direction == DIRECTION_DOWN) {
- if (q > np)
+ if (q > np) {
np = q;
+ npo = qo;
+ }
} else {
- if (np == 0 || q < np)
+ if (np == 0 || q < np) {
np = q;
+ npo = qo;
+ }
}
}
}
/* Check the last term */
- if (term_match && term_result)
- found = true;
+ if (term_match && !term_result)
+ found = false;
/* Did this entry match against all matches? */
if (found) {
- if (ret)
+ if (ret) {
+ if (c == NULL) {
+ /* Re-fetch the entry */
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, cp, &c);
+ if (r < 0)
+ return r;
+ }
*ret = c;
+ }
if (offset)
*offset = cp;
return 1;
/* Hmm, ok, this entry only matched partially, so
* let's try another one */
cp = np;
+ c = npo;
}
}
assert(f);
if (f->current_offset > 0) {
- r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &c);
+ cp = f->current_offset;
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, cp, &c);
if (r < 0)
return r;
- cp = f->current_offset;
-
r = next_with_matches(j, f, direction, &c, &cp);
if (r <= 0)
return r;
uint64_t new_offset = 0;
Object *new_entry = NULL;
- assert(j);
+ if (!j)
+ return -EINVAL;
HASHMAP_FOREACH(f, j->files, i) {
Object *o;
bool found;
r = next_beyond_location(j, f, direction, &o, &p);
- if (r < 0)
- return r;
- else if (r == 0)
+ if (r < 0) {
+ log_debug("Can't iterate through %s, ignoring: %s", f->path, strerror(-r));
+ continue;
+ } else if (r == 0)
continue;
if (!new_current)
return 1;
}
-int sd_journal_next(sd_journal *j) {
+_public_ int sd_journal_next(sd_journal *j) {
return real_journal_next(j, DIRECTION_DOWN);
}
-int sd_journal_previous(sd_journal *j) {
+_public_ int sd_journal_previous(sd_journal *j) {
return real_journal_next(j, DIRECTION_UP);
}
-int sd_journal_next_skip(sd_journal *j, uint64_t skip) {
+static int real_journal_next_skip(sd_journal *j, direction_t direction, uint64_t skip) {
int c = 0, r;
- assert(j);
+ if (!j)
+ return -EINVAL;
+
+ if (skip == 0) {
+ /* If this is not a discrete skip, then at least
+ * resolve the current location */
+ if (j->current_location.type != LOCATION_DISCRETE)
+ return real_journal_next(j, direction);
+
+ return 0;
+ }
- while (skip > 0) {
- r = sd_journal_next(j);
+ do {
+ r = real_journal_next(j, direction);
if (r < 0)
return r;
skip--;
c++;
- }
+ } while (skip > 0);
return c;
}
-int sd_journal_previous_skip(sd_journal *j, uint64_t skip) {
- int c = 0, r;
-
- assert(j);
-
- while (skip > 0) {
- r = sd_journal_previous(j);
- if (r < 0)
- return r;
-
- if (r == 0)
- return c;
-
- skip--;
- c++;
- }
+_public_ int sd_journal_next_skip(sd_journal *j, uint64_t skip) {
+ return real_journal_next_skip(j, DIRECTION_DOWN, skip);
+}
- return 1;
+_public_ int sd_journal_previous_skip(sd_journal *j, uint64_t skip) {
+ return real_journal_next_skip(j, DIRECTION_UP, skip);
}
-int sd_journal_get_cursor(sd_journal *j, char **cursor) {
+_public_ int sd_journal_get_cursor(sd_journal *j, char **cursor) {
Object *o;
int r;
char bid[33], sid[33];
- assert(j);
- assert(cursor);
+ if (!j)
+ return -EINVAL;
+ if (!cursor)
+ return -EINVAL;
if (!j->current_file || j->current_file->current_offset <= 0)
return -EADDRNOTAVAIL;
bid, (unsigned long long) le64toh(o->entry.monotonic),
(unsigned long long) le64toh(o->entry.realtime),
(unsigned long long) le64toh(o->entry.xor_hash),
- file_name_from_path(j->current_file->path)) < 0)
+ path_get_file_name(j->current_file->path)) < 0)
return -ENOMEM;
return 1;
}
-int sd_journal_seek_cursor(sd_journal *j, const char *cursor) {
+_public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) {
char *w;
size_t l;
char *state;
xor_hash_set = false;
sd_id128_t seqnum_id, boot_id;
- assert(j);
- assert(cursor);
+ if (!j)
+ return -EINVAL;
+ if (!cursor)
+ return -EINVAL;
FOREACH_WORD_SEPARATOR(w, l, cursor, ";", state) {
char *item;
return 0;
}
-int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec) {
- assert(j);
+_public_ int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec) {
+ if (!j)
+ return -EINVAL;
reset_location(j);
j->current_location.type = LOCATION_DISCRETE;
return 0;
}
-int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec) {
- assert(j);
+_public_ int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec) {
+ if (!j)
+ return -EINVAL;
reset_location(j);
j->current_location.type = LOCATION_DISCRETE;
return 0;
}
-int sd_journal_seek_head(sd_journal *j) {
- assert(j);
+_public_ int sd_journal_seek_head(sd_journal *j) {
+ if (!j)
+ return -EINVAL;
reset_location(j);
j->current_location.type = LOCATION_HEAD;
return 0;
}
-int sd_journal_seek_tail(sd_journal *j) {
- assert(j);
+_public_ int sd_journal_seek_tail(sd_journal *j) {
+ if (!j)
+ return -EINVAL;
reset_location(j);
j->current_location.type = LOCATION_TAIL;
assert(prefix);
assert(filename);
+ if ((j->flags & SD_JOURNAL_SYSTEM_ONLY) &&
+ !(streq(filename, "system.journal") ||
+ (startswith(filename, "system@") && endswith(filename, ".journal"))))
+ return 0;
+
if (dir)
fn = join(prefix, "/", dir, "/", filename, NULL);
else
if (!fn)
return -ENOMEM;
+ if (hashmap_get(j->files, fn)) {
+ free(fn);
+ return 0;
+ }
+
+ if (hashmap_size(j->files) >= JOURNAL_FILES_MAX) {
+ log_debug("Too many open journal files, not adding %s, ignoring.", fn);
+ free(fn);
+ return 0;
+ }
+
r = journal_file_open(fn, O_RDONLY, 0, NULL, &f);
free(fn);
return r;
}
- journal_file_dump(f);
-
+ /* journal_file_dump(f); */
r = hashmap_put(j->files, f->path, f);
if (r < 0) {
return r;
}
+ 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;
+ JournalFile *f;
+
+ assert(j);
+ assert(prefix);
+ assert(filename);
+
+ if (dir)
+ fn = join(prefix, "/", dir, "/", filename, NULL);
+ else
+ fn = join(prefix, "/", filename, NULL);
+
+ if (!fn)
+ return -ENOMEM;
+
+ f = hashmap_get(j->files, fn);
+ free(fn);
+
+ if (!f)
+ return 0;
+
+ hashmap_remove(j->files, f->path);
+ journal_file_close(f);
+
+ log_debug("File %s got removed.", f->path);
return 0;
}
char *fn;
int r;
DIR *d;
+ int wd;
+ sd_id128_t id, mid;
assert(j);
assert(prefix);
assert(dir);
+ if ((j->flags & SD_JOURNAL_LOCAL_ONLY) &&
+ (sd_id128_from_string(dir, &id) < 0 ||
+ sd_id128_get_machine(&mid) < 0 ||
+ !sd_id128_equal(id, mid)))
+ return 0;
+
fn = join(prefix, "/", dir, NULL);
if (!fn)
return -ENOMEM;
d = opendir(fn);
- free(fn);
if (!d) {
+ free(fn);
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;
+ }
+
+ free(fn);
+
for (;;) {
struct dirent buf, *de;
closedir(d);
+ log_debug("Directory %s/%s got added.", prefix, dir);
+
return 0;
}
-int sd_journal_open(sd_journal **ret) {
+static void remove_directory_wd(sd_journal *j, int wd) {
+ char *p;
+
+ assert(j);
+ assert(wd > 0);
+
+ if (j->inotify_fd >= 0)
+ inotify_rm_watch(j->inotify_fd, wd);
+
+ p = hashmap_remove(j->inotify_wd_dirs, INT_TO_PTR(wd));
+
+ if (p) {
+ log_debug("Directory %s got removed.", p);
+ free(p);
+ }
+}
+
+static void add_root_wd(sd_journal *j, const char *p) {
+ int wd;
+ char *k;
+
+ 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;
+
+ 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);
+ }
+}
+
+static void remove_root_wd(sd_journal *j, int wd) {
+ char *p;
+
+ assert(j);
+ assert(wd > 0);
+
+ if (j->inotify_fd >= 0)
+ inotify_rm_watch(j->inotify_fd, wd);
+
+ p = hashmap_remove(j->inotify_wd_roots, INT_TO_PTR(wd));
+
+ if (p) {
+ log_debug("Root %s got removed.", p);
+ free(p);
+ }
+}
+
+_public_ int sd_journal_open(sd_journal **ret, int flags) {
sd_journal *j;
const char *p;
const char search_paths[] =
"/var/log/journal\0";
int r;
- assert(ret);
+ if (!ret)
+ return -EINVAL;
+
+ if (flags & ~(SD_JOURNAL_LOCAL_ONLY|
+ SD_JOURNAL_RUNTIME_ONLY|
+ SD_JOURNAL_SYSTEM_ONLY))
+ return -EINVAL;
j = new0(sd_journal, 1);
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;
+ 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;
+
d = opendir(p);
if (!d) {
if (errno != ENOENT)
continue;
}
+ add_root_wd(j, p);
+
for (;;) {
struct dirent buf, *de;
sd_id128_t id;
return r;
};
-void sd_journal_close(sd_journal *j) {
- assert(j);
+_public_ void sd_journal_close(sd_journal *j) {
+ 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);
+ }
if (j->files) {
JournalFile *f;
sd_journal_flush_matches(j);
+ if (j->inotify_fd >= 0)
+ close_nointr_nofail(j->inotify_fd);
+
free(j);
}
-int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) {
+_public_ int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) {
Object *o;
JournalFile *f;
int r;
- assert(j);
- assert(ret);
+ if (!j)
+ return -EINVAL;
+ if (!ret)
+ return -EINVAL;
f = j->current_file;
if (!f)
return 0;
}
-int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id128_t *ret_boot_id) {
+_public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id128_t *ret_boot_id) {
Object *o;
JournalFile *f;
int r;
sd_id128_t id;
- assert(j);
- assert(ret);
+ if (!j)
+ return -EINVAL;
+ if (!ret)
+ return -EINVAL;
f = j->current_file;
if (!f)
return r;
if (!sd_id128_equal(id, o->entry.boot_id))
- return -ENOENT;
+ return -ESTALE;
}
*ret = le64toh(o->entry.monotonic);
return 0;
}
-int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *size) {
+_public_ int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *size) {
JournalFile *f;
uint64_t i, n;
size_t field_length;
int r;
Object *o;
- assert(j);
- assert(field);
- assert(data);
- assert(size);
+ if (!j)
+ return -EINVAL;
+ if (!field)
+ return -EINVAL;
+ if (!data)
+ return -EINVAL;
+ if (!size)
+ return -EINVAL;
if (isempty(field) || strchr(field, '='))
return -EINVAL;
n = journal_file_entry_n_items(o);
for (i = 0; i < n; i++) {
- uint64_t p, l, le_hash;
+ uint64_t p, l;
+ le64_t le_hash;
size_t t;
p = le64toh(o->entry.items[i].object_offset);
- le_hash = o->entry.items[j->current_field].hash;
+ le_hash = o->entry.items[i].hash;
r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
if (r < 0)
return r;
l = le64toh(o->object.size) - offsetof(Object, data.payload);
- if (l >= field_length+1 &&
- memcmp(o->data.payload, field, field_length) == 0 &&
- o->data.payload[field_length] == '=') {
+ if (o->object.flags & OBJECT_COMPRESSED) {
+
+#ifdef HAVE_XZ
+ if (uncompress_startswith(o->data.payload, l,
+ &f->compress_buffer, &f->compress_buffer_size,
+ field, field_length, '=')) {
+
+ uint64_t rsize;
+
+ if (!uncompress_blob(o->data.payload, l,
+ &f->compress_buffer, &f->compress_buffer_size, &rsize))
+ return -EBADMSG;
+
+ *data = f->compress_buffer;
+ *size = (size_t) rsize;
+
+ return 0;
+ }
+#else
+ return -EPROTONOSUPPORT;
+#endif
+
+ } else if (l >= field_length+1 &&
+ memcmp(o->data.payload, field, field_length) == 0 &&
+ o->data.payload[field_length] == '=') {
t = (size_t) l;
return -ENOENT;
}
-int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *size) {
+_public_ int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *size) {
JournalFile *f;
- uint64_t p, l, n, le_hash;
+ uint64_t p, l, n;
+ le64_t le_hash;
int r;
Object *o;
size_t t;
- assert(j);
- assert(data);
- assert(size);
+ if (!j)
+ return -EINVAL;
+ if (!data)
+ return -EINVAL;
+ if (!size)
+ return -EINVAL;
f = j->current_file;
if (!f)
if ((uint64_t) t != l)
return -E2BIG;
- *data = o->data.payload;
- *size = t;
+ if (o->object.flags & OBJECT_COMPRESSED) {
+#ifdef HAVE_XZ
+ uint64_t rsize;
+
+ if (!uncompress_blob(o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize))
+ return -EBADMSG;
+
+ *data = f->compress_buffer;
+ *size = (size_t) rsize;
+#else
+ return -EPROTONOSUPPORT;
+#endif
+ } else {
+ *data = o->data.payload;
+ *size = t;
+ }
j->current_field ++;
return 1;
}
-void sd_journal_restart_data(sd_journal *j) {
- assert(j);
+_public_ void sd_journal_restart_data(sd_journal *j) {
+ if (!j)
+ return;
j->current_field = 0;
}
+
+_public_ int sd_journal_get_fd(sd_journal *j) {
+ if (!j)
+ return -EINVAL;
+
+ return j->inotify_fd;
+}
+
+static void process_inotify_event(sd_journal *j, struct inotify_event *e) {
+ char *p;
+ 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) {
+
+ 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 (r < 0)
+ log_debug("Failed to remove file %s/%s: %s", p, e->name, strerror(-r));
+ }
+
+ } else if (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;
+
+ 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 (r < 0)
+ log_debug("Failed to remove file %s/%s: %s", p, e->name, strerror(-r));
+ }
+
+ } else if ((e->mask & IN_ISDIR) && e->len > 0 && sd_id128_from_string(e->name, &id) >= 0) {
+
+ /* Event for subdirectory */
+
+ if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
+
+ r = add_directory(j, p, e->name);
+ if (r < 0)
+ log_debug("Failed to add directory %s/%s: %s", p, e->name, strerror(-r));
+ }
+ }
+
+ return;
+ }
+
+ if (e->mask & IN_IGNORED)
+ return;
+
+ log_warning("Unknown inotify event.");
+}
+
+_public_ int sd_journal_process(sd_journal *j) {
+ uint8_t buffer[sizeof(struct inotify_event) + FILENAME_MAX];
+
+ if (!j)
+ return -EINVAL;
+
+ for (;;) {
+ struct inotify_event *e;
+ ssize_t l;
+
+ l = read(j->inotify_fd, buffer, sizeof(buffer));
+ if (l < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ return 0;
+
+ return -errno;
+ }
+
+ e = (struct inotify_event*) buffer;
+ while (l > 0) {
+ size_t step;
+
+ process_inotify_event(j, e);
+
+ step = sizeof(struct inotify_event) + e->len;
+ assert(step <= (size_t) l);
+
+ e = (struct inotify_event*) ((uint8_t*) e + step);
+ l -= step;
+ }
+ }
+}
+
+_public_ int sd_journal_get_cutoff_realtime_usec(sd_journal *j, uint64_t *from, uint64_t *to) {
+ Iterator i;
+ JournalFile *f;
+ bool first = true;
+ int r;
+
+ if (!j)
+ return -EINVAL;
+ if (!from && !to)
+ return -EINVAL;
+
+ HASHMAP_FOREACH(f, j->files, i) {
+ usec_t fr, t;
+
+ r = journal_file_get_cutoff_realtime_usec(f, &fr, &t);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ if (first) {
+ if (from)
+ *from = fr;
+ if (to)
+ *to = t;
+ first = false;
+ } else {
+ if (from)
+ *from = MIN(fr, *from);
+ if (to)
+ *to = MIN(t, *to);
+ }
+ }
+
+ return first ? 0 : 1;
+}
+
+_public_ int sd_journal_get_cutoff_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t *from, uint64_t *to) {
+ Iterator i;
+ JournalFile *f;
+ bool first = true;
+ int r;
+
+ if (!j)
+ return -EINVAL;
+ if (!from && !to)
+ return -EINVAL;
+
+ HASHMAP_FOREACH(f, j->files, i) {
+ usec_t fr, t;
+
+ r = journal_file_get_cutoff_monotonic_usec(f, boot_id, &fr, &t);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ if (first) {
+ if (from)
+ *from = fr;
+ if (to)
+ *to = t;
+ first = false;
+ } else {
+ if (from)
+ *from = MIN(fr, *from);
+ if (to)
+ *to = MIN(t, *to);
+ }
+ }
+
+ return first ? 0 : 1;
+}
+
+
+/* _public_ int sd_journal_query_unique(sd_journal *j, const char *field) { */
+/* if (!j) */
+/* return -EINVAL; */
+/* if (!field) */
+/* return -EINVAL; */
+
+/* return -ENOTSUP; */
+/* } */
+
+/* _public_ int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_t *l) { */
+/* if (!j) */
+/* return -EINVAL; */
+/* if (!data) */
+/* return -EINVAL; */
+/* if (!l) */
+/* return -EINVAL; */
+
+/* return -ENOTSUP; */
+/* } */
+
+/* _public_ void sd_journal_restart_unique(sd_journal *j) { */
+/* if (!j) */
+/* return; */
+/* } */