1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2011 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
26 #include <sys/inotify.h>
29 #include "sd-journal.h"
30 #include "journal-def.h"
31 #include "journal-file.h"
34 #include "path-util.h"
37 #include "journal-internal.h"
39 #define JOURNAL_FILES_MAX 1024
41 static void detach_location(sd_journal
*j
) {
47 j
->current_file
= NULL
;
50 HASHMAP_FOREACH(f
, j
->files
, i
)
51 f
->current_offset
= 0;
54 static void reset_location(sd_journal
*j
) {
58 zero(j
->current_location
);
61 static void init_location(Location
*l
, JournalFile
*f
, Object
*o
) {
64 assert(o
->object
.type
== OBJECT_ENTRY
);
66 l
->type
= LOCATION_DISCRETE
;
67 l
->seqnum
= le64toh(o
->entry
.seqnum
);
68 l
->seqnum_id
= f
->header
->seqnum_id
;
69 l
->realtime
= le64toh(o
->entry
.realtime
);
70 l
->monotonic
= le64toh(o
->entry
.monotonic
);
71 l
->boot_id
= o
->entry
.boot_id
;
72 l
->xor_hash
= le64toh(o
->entry
.xor_hash
);
74 l
->seqnum_set
= l
->realtime_set
= l
->monotonic_set
= l
->xor_hash_set
= true;
77 static void set_location(sd_journal
*j
, JournalFile
*f
, Object
*o
, uint64_t offset
) {
82 init_location(&j
->current_location
, f
, o
);
87 f
->current_offset
= offset
;
90 static int same_field(const void *_a
, size_t s
, const void *_b
, size_t t
) {
91 const uint8_t *a
= _a
, *b
= _b
;
93 bool a_good
= false, b_good
= false, different
= false;
95 for (j
= 0; j
< s
&& j
< t
; j
++) {
104 if (a_good
&& b_good
)
105 return different
? 0 : 1;
111 _public_
int sd_journal_add_match(sd_journal
*j
, const void *data
, size_t size
) {
112 Match
*m
, *after
= NULL
;
121 if (!memchr(data
, '=', size
))
123 if (*(char*) data
== '=')
126 /* FIXME: iterating with multiple matches is currently
131 le_hash
= htole64(hash64(data
, size
));
133 LIST_FOREACH(matches
, m
, j
->matches
) {
136 if (m
->le_hash
== le_hash
&&
138 memcmp(m
->data
, data
, size
) == 0)
141 r
= same_field(data
, size
, m
->data
, m
->size
);
154 m
->data
= malloc(m
->size
);
160 memcpy(m
->data
, data
, size
);
161 m
->le_hash
= le_hash
;
163 /* Matches for the same fields we order adjacent to each
165 LIST_INSERT_AFTER(Match
, matches
, j
->matches
, after
, m
);
173 _public_
void sd_journal_flush_matches(sd_journal
*j
) {
178 Match
*m
= j
->matches
;
180 LIST_REMOVE(Match
, matches
, j
->matches
, m
);
190 static int compare_order(JournalFile
*af
, Object
*ao
,
191 JournalFile
*bf
, Object
*bo
) {
200 /* We operate on two different files here, hence we can access
201 * two objects at the same time, which we normally can't.
203 * If contents and timestamps match, these entries are
204 * identical, even if the seqnum does not match */
206 if (sd_id128_equal(ao
->entry
.boot_id
, bo
->entry
.boot_id
) &&
207 ao
->entry
.monotonic
== bo
->entry
.monotonic
&&
208 ao
->entry
.realtime
== bo
->entry
.realtime
&&
209 ao
->entry
.xor_hash
== bo
->entry
.xor_hash
)
212 if (sd_id128_equal(af
->header
->seqnum_id
, bf
->header
->seqnum_id
)) {
214 /* If this is from the same seqnum source, compare
216 a
= le64toh(ao
->entry
.seqnum
);
217 b
= le64toh(bo
->entry
.seqnum
);
224 /* Wow! This is weird, different data but the same
225 * seqnums? Something is borked, but let's make the
226 * best of it and compare by time. */
229 if (sd_id128_equal(ao
->entry
.boot_id
, bo
->entry
.boot_id
)) {
231 /* If the boot id matches compare monotonic time */
232 a
= le64toh(ao
->entry
.monotonic
);
233 b
= le64toh(bo
->entry
.monotonic
);
241 /* Otherwise compare UTC time */
242 a
= le64toh(ao
->entry
.realtime
);
243 b
= le64toh(bo
->entry
.realtime
);
250 /* Finally, compare by contents */
251 a
= le64toh(ao
->entry
.xor_hash
);
252 b
= le64toh(bo
->entry
.xor_hash
);
262 static int compare_with_location(JournalFile
*af
, Object
*ao
, Location
*l
) {
268 assert(l
->type
== LOCATION_DISCRETE
);
270 if (l
->monotonic_set
&&
271 sd_id128_equal(ao
->entry
.boot_id
, l
->boot_id
) &&
273 le64toh(ao
->entry
.realtime
) == l
->realtime
&&
275 le64toh(ao
->entry
.xor_hash
) == l
->xor_hash
)
279 sd_id128_equal(af
->header
->seqnum_id
, l
->seqnum_id
)) {
281 a
= le64toh(ao
->entry
.seqnum
);
289 if (l
->monotonic_set
&&
290 sd_id128_equal(ao
->entry
.boot_id
, l
->boot_id
)) {
292 a
= le64toh(ao
->entry
.monotonic
);
294 if (a
< l
->monotonic
)
296 if (a
> l
->monotonic
)
300 if (l
->realtime_set
) {
302 a
= le64toh(ao
->entry
.realtime
);
310 if (l
->xor_hash_set
) {
311 a
= le64toh(ao
->entry
.xor_hash
);
322 static int find_location(sd_journal
*j
, JournalFile
*f
, direction_t direction
, Object
**ret
, uint64_t *offset
) {
330 /* No matches is simple */
332 if (j
->current_location
.type
== LOCATION_HEAD
)
333 r
= journal_file_next_entry(f
, NULL
, 0, DIRECTION_DOWN
, &o
, &p
);
334 else if (j
->current_location
.type
== LOCATION_TAIL
)
335 r
= journal_file_next_entry(f
, NULL
, 0, DIRECTION_UP
, &o
, &p
);
336 else if (j
->current_location
.seqnum_set
&&
337 sd_id128_equal(j
->current_location
.seqnum_id
, f
->header
->seqnum_id
))
338 r
= journal_file_move_to_entry_by_seqnum(f
, j
->current_location
.seqnum
, direction
, &o
, &p
);
339 else if (j
->current_location
.monotonic_set
) {
340 r
= journal_file_move_to_entry_by_monotonic(f
, j
->current_location
.boot_id
, j
->current_location
.monotonic
, direction
, &o
, &p
);
343 /* boot id unknown in this file */
344 if (j
->current_location
.realtime_set
)
345 r
= journal_file_move_to_entry_by_realtime(f
, j
->current_location
.realtime
, direction
, &o
, &p
);
347 r
= journal_file_next_entry(f
, NULL
, 0, direction
, &o
, &p
);
349 } else if (j
->current_location
.realtime_set
)
350 r
= journal_file_move_to_entry_by_realtime(f
, j
->current_location
.realtime
, direction
, &o
, &p
);
352 r
= journal_file_next_entry(f
, NULL
, 0, direction
, &o
, &p
);
358 Match
*m
, *term_match
= NULL
;
362 /* We have matches, first, let's jump to the monotonic
363 * position if we have any, since it implies a
366 if (j
->current_location
.type
== LOCATION_DISCRETE
&&
367 j
->current_location
.monotonic_set
) {
369 r
= journal_file_move_to_entry_by_monotonic(f
, j
->current_location
.boot_id
, j
->current_location
.monotonic
, direction
, &o
, &p
);
371 return r
== -ENOENT
? 0 : r
;
374 LIST_FOREACH(matches
, m
, j
->matches
) {
378 r
= journal_file_find_data_object_with_hash(f
, m
->data
, m
->size
, le64toh(m
->le_hash
), &d
, &dp
);
382 if (j
->current_location
.type
== LOCATION_HEAD
)
383 r
= journal_file_next_entry_for_data(f
, NULL
, 0, dp
, DIRECTION_DOWN
, &c
, &cp
);
384 else if (j
->current_location
.type
== LOCATION_TAIL
)
385 r
= journal_file_next_entry_for_data(f
, NULL
, 0, dp
, DIRECTION_UP
, &c
, &cp
);
386 else if (j
->current_location
.seqnum_set
&&
387 sd_id128_equal(j
->current_location
.seqnum_id
, f
->header
->seqnum_id
))
388 r
= journal_file_move_to_entry_by_seqnum_for_data(f
, dp
, j
->current_location
.seqnum
, direction
, &c
, &cp
);
389 else if (j
->current_location
.realtime_set
)
390 r
= journal_file_move_to_entry_by_realtime_for_data(f
, dp
, j
->current_location
.realtime
, direction
, &c
, &cp
);
392 r
= journal_file_next_entry_for_data(f
, NULL
, 0, dp
, direction
, &c
, &cp
);
404 } else if (same_field(term_match
->data
, term_match
->size
, m
->data
, m
->size
)) {
406 /* Same field as previous match... */
409 /* Find the earliest of the OR matches */
412 (direction
== DIRECTION_DOWN
&& cp
< tp
) ||
413 (direction
== DIRECTION_UP
&& cp
> tp
)) {
422 /* Previous term is finished, did anything match? */
426 /* Find the last of the AND matches */
428 (direction
== DIRECTION_DOWN
&& tp
> p
) ||
429 (direction
== DIRECTION_UP
&& tp
< p
)) {
446 /* Last term is finished, did anything match? */
451 (direction
== DIRECTION_DOWN
&& tp
> p
) ||
452 (direction
== DIRECTION_UP
&& tp
< p
)) {
470 static int next_with_matches(sd_journal
*j
, JournalFile
*f
, direction_t direction
, Object
**ret
, uint64_t *offset
) {
484 /* No matches is easy */
486 r
= journal_file_next_entry(f
, c
, cp
, direction
, &c
, &cp
);
497 /* So there are matches we have to adhere to, let's find the
498 * first entry that matches all of them */
502 bool found
, term_result
= false;
503 Match
*m
, *term_match
= NULL
;
506 n
= journal_file_entry_n_items(c
);
508 /* Make sure we don't match the entry we are starting
510 found
= cp
!= *offset
;
513 LIST_FOREACH(matches
, m
, j
->matches
) {
517 /* Let's check if this is the beginning of a
518 * new term, i.e. has a different field prefix
519 * as the preceeding match. */
523 } else if (!same_field(term_match
->data
, term_match
->size
, m
->data
, m
->size
)) {
531 for (k
= 0; k
< n
; k
++)
532 if (c
->entry
.items
[k
].hash
== m
->le_hash
)
536 /* Hmm, didn't find any field that
537 * matched this rule, so ignore this
538 * match. Go on with next match */
544 /* Hmm, so, this field matched, let's remember
545 * where we'd have to try next, in case the other
546 * matches are not OK */
548 r
= journal_file_next_entry_for_data(f
, c
, cp
, le64toh(c
->entry
.items
[k
].object_offset
), direction
, &qo
, &q
);
549 /* This pointer is invalidated if the window was
550 * remapped. May need to re-fetch it later */
557 if (direction
== DIRECTION_DOWN
) {
563 if (np
== 0 || q
< np
) {
571 /* Check the last term */
572 if (term_match
&& !term_result
)
575 /* Did this entry match against all matches? */
579 /* Re-fetch the entry */
580 r
= journal_file_move_to_object(f
, OBJECT_ENTRY
, cp
, &c
);
591 /* Did we find a subsequent entry? */
595 /* Hmm, ok, this entry only matched partially, so
596 * let's try another one */
602 static int next_beyond_location(sd_journal
*j
, JournalFile
*f
, direction_t direction
, Object
**ret
, uint64_t *offset
) {
605 int compare_value
, r
;
610 if (f
->current_offset
> 0) {
611 cp
= f
->current_offset
;
613 r
= journal_file_move_to_object(f
, OBJECT_ENTRY
, cp
, &c
);
617 r
= next_with_matches(j
, f
, direction
, &c
, &cp
);
623 r
= find_location(j
, f
, direction
, &c
, &cp
);
633 if (j
->current_location
.type
== LOCATION_DISCRETE
) {
636 k
= compare_with_location(f
, c
, &j
->current_location
);
637 if (direction
== DIRECTION_DOWN
)
638 found
= k
>= compare_value
;
640 found
= k
<= -compare_value
;
652 r
= next_with_matches(j
, f
, direction
, &c
, &cp
);
658 static int real_journal_next(sd_journal
*j
, direction_t direction
) {
659 JournalFile
*f
, *new_current
= NULL
;
662 uint64_t new_offset
= 0;
663 Object
*new_entry
= NULL
;
668 HASHMAP_FOREACH(f
, j
->files
, i
) {
673 r
= next_beyond_location(j
, f
, direction
, &o
, &p
);
675 log_debug("Can't iterate through %s, ignoring: %s", f
->path
, strerror(-r
));
685 k
= compare_order(f
, o
, new_current
, new_entry
);
687 if (direction
== DIRECTION_DOWN
)
703 set_location(j
, new_current
, new_entry
, new_offset
);
708 _public_
int sd_journal_next(sd_journal
*j
) {
709 return real_journal_next(j
, DIRECTION_DOWN
);
712 _public_
int sd_journal_previous(sd_journal
*j
) {
713 return real_journal_next(j
, DIRECTION_UP
);
716 static int real_journal_next_skip(sd_journal
*j
, direction_t direction
, uint64_t skip
) {
723 /* If this is not a discrete skip, then at least
724 * resolve the current location */
725 if (j
->current_location
.type
!= LOCATION_DISCRETE
)
726 return real_journal_next(j
, direction
);
732 r
= real_journal_next(j
, direction
);
746 _public_
int sd_journal_next_skip(sd_journal
*j
, uint64_t skip
) {
747 return real_journal_next_skip(j
, DIRECTION_DOWN
, skip
);
750 _public_
int sd_journal_previous_skip(sd_journal
*j
, uint64_t skip
) {
751 return real_journal_next_skip(j
, DIRECTION_UP
, skip
);
754 _public_
int sd_journal_get_cursor(sd_journal
*j
, char **cursor
) {
757 char bid
[33], sid
[33];
764 if (!j
->current_file
|| j
->current_file
->current_offset
<= 0)
765 return -EADDRNOTAVAIL
;
767 r
= journal_file_move_to_object(j
->current_file
, OBJECT_ENTRY
, j
->current_file
->current_offset
, &o
);
771 sd_id128_to_string(j
->current_file
->header
->seqnum_id
, sid
);
772 sd_id128_to_string(o
->entry
.boot_id
, bid
);
775 "s=%s;i=%llx;b=%s;m=%llx;t=%llx;x=%llx;p=%s",
776 sid
, (unsigned long long) le64toh(o
->entry
.seqnum
),
777 bid
, (unsigned long long) le64toh(o
->entry
.monotonic
),
778 (unsigned long long) le64toh(o
->entry
.realtime
),
779 (unsigned long long) le64toh(o
->entry
.xor_hash
),
780 path_get_file_name(j
->current_file
->path
)) < 0)
786 _public_
int sd_journal_seek_cursor(sd_journal
*j
, const char *cursor
) {
790 unsigned long long seqnum
, monotonic
, realtime
, xor_hash
;
792 seqnum_id_set
= false,
795 monotonic_set
= false,
796 realtime_set
= false,
797 xor_hash_set
= false;
798 sd_id128_t seqnum_id
, boot_id
;
805 FOREACH_WORD_SEPARATOR(w
, l
, cursor
, ";", state
) {
809 if (l
< 2 || w
[1] != '=')
812 item
= strndup(w
, l
);
819 seqnum_id_set
= true;
820 k
= sd_id128_from_string(w
+2, &seqnum_id
);
825 if (sscanf(w
+2, "%llx", &seqnum
) != 1)
831 k
= sd_id128_from_string(w
+2, &boot_id
);
835 monotonic_set
= true;
836 if (sscanf(w
+2, "%llx", &monotonic
) != 1)
842 if (sscanf(w
+2, "%llx", &realtime
) != 1)
848 if (sscanf(w
+2, "%llx", &xor_hash
) != 1)
859 if ((!seqnum_set
|| !seqnum_id_set
) &&
860 (!monotonic_set
|| !boot_id_set
) &&
866 j
->current_location
.type
= LOCATION_DISCRETE
;
869 j
->current_location
.realtime
= (uint64_t) realtime
;
870 j
->current_location
.realtime_set
= true;
873 if (seqnum_set
&& seqnum_id_set
) {
874 j
->current_location
.seqnum
= (uint64_t) seqnum
;
875 j
->current_location
.seqnum_id
= seqnum_id
;
876 j
->current_location
.seqnum_set
= true;
879 if (monotonic_set
&& boot_id_set
) {
880 j
->current_location
.monotonic
= (uint64_t) monotonic
;
881 j
->current_location
.boot_id
= boot_id
;
882 j
->current_location
.monotonic_set
= true;
886 j
->current_location
.xor_hash
= (uint64_t) xor_hash
;
887 j
->current_location
.xor_hash_set
= true;
893 _public_
int sd_journal_seek_monotonic_usec(sd_journal
*j
, sd_id128_t boot_id
, uint64_t usec
) {
898 j
->current_location
.type
= LOCATION_DISCRETE
;
899 j
->current_location
.boot_id
= boot_id
;
900 j
->current_location
.monotonic
= usec
;
901 j
->current_location
.monotonic_set
= true;
906 _public_
int sd_journal_seek_realtime_usec(sd_journal
*j
, uint64_t usec
) {
911 j
->current_location
.type
= LOCATION_DISCRETE
;
912 j
->current_location
.realtime
= usec
;
913 j
->current_location
.realtime_set
= true;
918 _public_
int sd_journal_seek_head(sd_journal
*j
) {
923 j
->current_location
.type
= LOCATION_HEAD
;
928 _public_
int sd_journal_seek_tail(sd_journal
*j
) {
933 j
->current_location
.type
= LOCATION_TAIL
;
938 static int add_file(sd_journal
*j
, const char *prefix
, const char *filename
) {
947 if ((j
->flags
& SD_JOURNAL_SYSTEM_ONLY
) &&
948 !(streq(filename
, "system.journal") ||
949 (startswith(filename
, "system@") && endswith(filename
, ".journal"))))
952 path
= join(prefix
, "/", filename
, NULL
);
956 if (hashmap_get(j
->files
, path
)) {
961 if (hashmap_size(j
->files
) >= JOURNAL_FILES_MAX
) {
962 log_debug("Too many open journal files, not adding %s, ignoring.", path
);
967 r
= journal_file_open(path
, O_RDONLY
, 0, NULL
, &f
);
977 /* journal_file_dump(f); */
979 r
= hashmap_put(j
->files
, f
->path
, f
);
981 journal_file_close(f
);
985 j
->current_invalidate_counter
++;
987 log_debug("File %s got added.", f
->path
);
992 static int remove_file(sd_journal
*j
, const char *prefix
, const char *filename
) {
1000 path
= join(prefix
, "/", filename
, NULL
);
1004 f
= hashmap_get(j
->files
, path
);
1009 hashmap_remove(j
->files
, f
->path
);
1010 journal_file_close(f
);
1012 j
->current_invalidate_counter
++;
1014 log_debug("File %s got removed.", f
->path
);
1018 static int add_directory(sd_journal
*j
, const char *prefix
, const char *dirname
) {
1029 if ((j
->flags
& SD_JOURNAL_LOCAL_ONLY
) &&
1030 (sd_id128_from_string(dirname
, &id
) < 0 ||
1031 sd_id128_get_machine(&mid
) < 0 ||
1032 !sd_id128_equal(id
, mid
)))
1035 path
= join(prefix
, "/", dirname
, NULL
);
1041 log_debug("Failed to open %s: %m", path
);
1044 if (errno
== ENOENT
)
1049 m
= hashmap_get(j
->directories_by_path
, path
);
1051 m
= new0(Directory
, 1);
1061 if (hashmap_put(j
->directories_by_path
, m
->path
, m
) < 0) {
1068 j
->current_invalidate_counter
++;
1070 log_debug("Directory %s got added.", m
->path
);
1072 } else if (m
->is_root
) {
1079 if (m
->wd
<= 0 && j
->inotify_fd
>= 0) {
1081 m
->wd
= inotify_add_watch(j
->inotify_fd
, m
->path
,
1082 IN_CREATE
|IN_MOVED_TO
|IN_MODIFY
|IN_ATTRIB
|IN_DELETE
|
1083 IN_DELETE_SELF
|IN_MOVE_SELF
|IN_UNMOUNT
|
1084 IN_DONT_FOLLOW
|IN_ONLYDIR
);
1086 if (m
->wd
> 0 && hashmap_put(j
->directories_by_wd
, INT_TO_PTR(m
->wd
), m
) < 0)
1087 inotify_rm_watch(j
->inotify_fd
, m
->wd
);
1091 struct dirent buf
, *de
;
1093 r
= readdir_r(d
, &buf
, &de
);
1097 if (dirent_is_file_with_suffix(de
, ".journal")) {
1098 r
= add_file(j
, m
->path
, de
->d_name
);
1100 log_debug("Failed to add file %s/%s: %s", m
->path
, de
->d_name
, strerror(-r
));
1109 static int add_root_directory(sd_journal
*j
, const char *p
) {
1117 if ((j
->flags
& SD_JOURNAL_RUNTIME_ONLY
) &&
1118 !path_startswith(p
, "/run"))
1125 m
= hashmap_get(j
->directories_by_path
, p
);
1127 m
= new0(Directory
, 1);
1134 m
->path
= strdup(p
);
1141 if (hashmap_put(j
->directories_by_path
, m
->path
, m
) < 0) {
1148 j
->current_invalidate_counter
++;
1150 log_debug("Root directory %s got added.", m
->path
);
1152 } else if (!m
->is_root
) {
1157 if (m
->wd
<= 0 && j
->inotify_fd
>= 0) {
1159 m
->wd
= inotify_add_watch(j
->inotify_fd
, m
->path
,
1160 IN_CREATE
|IN_MOVED_TO
|IN_MODIFY
|IN_ATTRIB
|IN_DELETE
|
1161 IN_DONT_FOLLOW
|IN_ONLYDIR
);
1163 if (m
->wd
> 0 && hashmap_put(j
->directories_by_wd
, INT_TO_PTR(m
->wd
), m
) < 0)
1164 inotify_rm_watch(j
->inotify_fd
, m
->wd
);
1168 struct dirent buf
, *de
;
1171 r
= readdir_r(d
, &buf
, &de
);
1175 if (dirent_is_file_with_suffix(de
, ".journal")) {
1176 r
= add_file(j
, m
->path
, de
->d_name
);
1178 log_debug("Failed to add file %s/%s: %s", m
->path
, de
->d_name
, strerror(-r
));
1180 } else if ((de
->d_type
== DT_DIR
|| de
->d_type
== DT_UNKNOWN
) &&
1181 sd_id128_from_string(de
->d_name
, &id
) >= 0) {
1183 r
= add_directory(j
, m
->path
, de
->d_name
);
1185 log_debug("Failed to add directory %s/%s: %s", m
->path
, de
->d_name
, strerror(-r
));
1194 static int remove_directory(sd_journal
*j
, Directory
*d
) {
1198 hashmap_remove(j
->directories_by_wd
, INT_TO_PTR(d
->wd
));
1200 if (j
->inotify_fd
>= 0)
1201 inotify_rm_watch(j
->inotify_fd
, d
->wd
);
1204 hashmap_remove(j
->directories_by_path
, d
->path
);
1207 log_debug("Root directory %s got removed.", d
->path
);
1209 log_debug("Directory %s got removed.", d
->path
);
1217 static int add_search_paths(sd_journal
*j
) {
1219 const char search_paths
[] =
1220 "/run/log/journal\0"
1221 "/var/log/journal\0";
1226 /* We ignore most errors here, since the idea is to only open
1227 * what's actually accessible, and ignore the rest. */
1229 NULSTR_FOREACH(p
, search_paths
)
1230 add_root_directory(j
, p
);
1235 static int allocate_inotify(sd_journal
*j
) {
1238 if (j
->inotify_fd
< 0) {
1239 j
->inotify_fd
= inotify_init1(IN_NONBLOCK
|IN_CLOEXEC
);
1240 if (j
->inotify_fd
< 0)
1244 if (!j
->directories_by_wd
) {
1245 j
->directories_by_wd
= hashmap_new(trivial_hash_func
, trivial_compare_func
);
1246 if (!j
->directories_by_wd
)
1253 static sd_journal
*journal_new(int flags
) {
1256 j
= new0(sd_journal
, 1);
1263 j
->files
= hashmap_new(string_hash_func
, string_compare_func
);
1269 j
->directories_by_path
= hashmap_new(string_hash_func
, string_compare_func
);
1270 if (!j
->directories_by_path
) {
1271 hashmap_free(j
->files
);
1279 _public_
int sd_journal_open(sd_journal
**ret
, int flags
) {
1286 if (flags
& ~(SD_JOURNAL_LOCAL_ONLY
|
1287 SD_JOURNAL_RUNTIME_ONLY
|
1288 SD_JOURNAL_SYSTEM_ONLY
))
1291 j
= journal_new(flags
);
1295 r
= add_search_paths(j
);
1303 sd_journal_close(j
);
1308 _public_
int sd_journal_open_directory(sd_journal
**ret
, const char *path
, int flags
) {
1315 if (!path
|| !path_is_absolute(path
))
1321 j
= journal_new(flags
);
1325 r
= add_root_directory(j
, path
);
1333 sd_journal_close(j
);
1338 _public_
void sd_journal_close(sd_journal
*j
) {
1345 while ((f
= hashmap_steal_first(j
->files
)))
1346 journal_file_close(f
);
1348 hashmap_free(j
->files
);
1350 while ((d
= hashmap_first(j
->directories_by_path
)))
1351 remove_directory(j
, d
);
1353 while ((d
= hashmap_first(j
->directories_by_wd
)))
1354 remove_directory(j
, d
);
1356 hashmap_free(j
->directories_by_path
);
1357 hashmap_free(j
->directories_by_wd
);
1359 if (j
->inotify_fd
>= 0)
1360 close_nointr_nofail(j
->inotify_fd
);
1362 sd_journal_flush_matches(j
);
1367 _public_
int sd_journal_get_realtime_usec(sd_journal
*j
, uint64_t *ret
) {
1377 f
= j
->current_file
;
1379 return -EADDRNOTAVAIL
;
1381 if (f
->current_offset
<= 0)
1382 return -EADDRNOTAVAIL
;
1384 r
= journal_file_move_to_object(f
, OBJECT_ENTRY
, f
->current_offset
, &o
);
1388 *ret
= le64toh(o
->entry
.realtime
);
1392 _public_
int sd_journal_get_monotonic_usec(sd_journal
*j
, uint64_t *ret
, sd_id128_t
*ret_boot_id
) {
1401 f
= j
->current_file
;
1403 return -EADDRNOTAVAIL
;
1405 if (f
->current_offset
<= 0)
1406 return -EADDRNOTAVAIL
;
1408 r
= journal_file_move_to_object(f
, OBJECT_ENTRY
, f
->current_offset
, &o
);
1413 *ret_boot_id
= o
->entry
.boot_id
;
1415 r
= sd_id128_get_boot(&id
);
1419 if (!sd_id128_equal(id
, o
->entry
.boot_id
))
1424 *ret
= le64toh(o
->entry
.monotonic
);
1429 static bool field_is_valid(const char *field
) {
1437 if (startswith(field
, "__"))
1440 for (p
= field
; *p
; p
++) {
1445 if (*p
>= 'A' && *p
<= 'Z')
1448 if (*p
>= '0' && *p
<= '9')
1457 _public_
int sd_journal_get_data(sd_journal
*j
, const char *field
, const void **data
, size_t *size
) {
1460 size_t field_length
;
1473 if (!field_is_valid(field
))
1476 f
= j
->current_file
;
1478 return -EADDRNOTAVAIL
;
1480 if (f
->current_offset
<= 0)
1481 return -EADDRNOTAVAIL
;
1483 r
= journal_file_move_to_object(f
, OBJECT_ENTRY
, f
->current_offset
, &o
);
1487 field_length
= strlen(field
);
1489 n
= journal_file_entry_n_items(o
);
1490 for (i
= 0; i
< n
; i
++) {
1495 p
= le64toh(o
->entry
.items
[i
].object_offset
);
1496 le_hash
= o
->entry
.items
[i
].hash
;
1497 r
= journal_file_move_to_object(f
, OBJECT_DATA
, p
, &o
);
1501 if (le_hash
!= o
->data
.hash
)
1504 l
= le64toh(o
->object
.size
) - offsetof(Object
, data
.payload
);
1506 if (o
->object
.flags
& OBJECT_COMPRESSED
) {
1509 if (uncompress_startswith(o
->data
.payload
, l
,
1510 &f
->compress_buffer
, &f
->compress_buffer_size
,
1511 field
, field_length
, '=')) {
1515 if (!uncompress_blob(o
->data
.payload
, l
,
1516 &f
->compress_buffer
, &f
->compress_buffer_size
, &rsize
))
1519 *data
= f
->compress_buffer
;
1520 *size
= (size_t) rsize
;
1525 return -EPROTONOSUPPORT
;
1528 } else if (l
>= field_length
+1 &&
1529 memcmp(o
->data
.payload
, field
, field_length
) == 0 &&
1530 o
->data
.payload
[field_length
] == '=') {
1534 if ((uint64_t) t
!= l
)
1537 *data
= o
->data
.payload
;
1543 r
= journal_file_move_to_object(f
, OBJECT_ENTRY
, f
->current_offset
, &o
);
1551 _public_
int sd_journal_enumerate_data(sd_journal
*j
, const void **data
, size_t *size
) {
1566 f
= j
->current_file
;
1568 return -EADDRNOTAVAIL
;
1570 if (f
->current_offset
<= 0)
1571 return -EADDRNOTAVAIL
;
1573 r
= journal_file_move_to_object(f
, OBJECT_ENTRY
, f
->current_offset
, &o
);
1577 n
= journal_file_entry_n_items(o
);
1578 if (j
->current_field
>= n
)
1581 p
= le64toh(o
->entry
.items
[j
->current_field
].object_offset
);
1582 le_hash
= o
->entry
.items
[j
->current_field
].hash
;
1583 r
= journal_file_move_to_object(f
, OBJECT_DATA
, p
, &o
);
1587 if (le_hash
!= o
->data
.hash
)
1590 l
= le64toh(o
->object
.size
) - offsetof(Object
, data
.payload
);
1593 /* We can't read objects larger than 4G on a 32bit machine */
1594 if ((uint64_t) t
!= l
)
1597 if (o
->object
.flags
& OBJECT_COMPRESSED
) {
1601 if (!uncompress_blob(o
->data
.payload
, l
, &f
->compress_buffer
, &f
->compress_buffer_size
, &rsize
))
1604 *data
= f
->compress_buffer
;
1605 *size
= (size_t) rsize
;
1607 return -EPROTONOSUPPORT
;
1610 *data
= o
->data
.payload
;
1614 j
->current_field
++;
1619 _public_
void sd_journal_restart_data(sd_journal
*j
) {
1623 j
->current_field
= 0;
1626 _public_
int sd_journal_get_fd(sd_journal
*j
) {
1632 if (j
->inotify_fd
>= 0)
1633 return j
->inotify_fd
;
1635 r
= allocate_inotify(j
);
1639 /* Iterate through all dirs again, to add them to the
1641 r
= add_search_paths(j
);
1645 return j
->inotify_fd
;
1648 static void process_inotify_event(sd_journal
*j
, struct inotify_event
*e
) {
1655 /* Is this a subdirectory we watch? */
1656 d
= hashmap_get(j
->directories_by_wd
, INT_TO_PTR(e
->wd
));
1660 if (!(e
->mask
& IN_ISDIR
) && e
->len
> 0 && endswith(e
->name
, ".journal")) {
1662 /* Event for a journal file */
1664 if (e
->mask
& (IN_CREATE
|IN_MOVED_TO
|IN_MODIFY
|IN_ATTRIB
)) {
1665 r
= add_file(j
, d
->path
, e
->name
);
1667 log_debug("Failed to add file %s/%s: %s", d
->path
, e
->name
, strerror(-r
));
1669 } else if (e
->mask
& (IN_DELETE
|IN_UNMOUNT
)) {
1671 r
= remove_file(j
, d
->path
, e
->name
);
1673 log_debug("Failed to remove file %s/%s: %s", d
->path
, e
->name
, strerror(-r
));
1676 } else if (!d
->is_root
&& e
->len
== 0) {
1678 /* Event for a subdirectory */
1680 if (e
->mask
& (IN_DELETE_SELF
|IN_MOVE_SELF
|IN_UNMOUNT
)) {
1681 r
= remove_directory(j
, d
);
1683 log_debug("Failed to remove directory %s: %s", d
->path
, strerror(-r
));
1687 } else if (d
->is_root
&& (e
->mask
& IN_ISDIR
) && e
->len
> 0 && sd_id128_from_string(e
->name
, &id
) >= 0) {
1689 /* Event for root directory */
1691 if (e
->mask
& (IN_CREATE
|IN_MOVED_TO
|IN_MODIFY
|IN_ATTRIB
)) {
1692 r
= add_directory(j
, d
->path
, e
->name
);
1694 log_debug("Failed to add directory %s/%s: %s", d
->path
, e
->name
, strerror(-r
));
1701 if (e
->mask
& IN_IGNORED
)
1704 log_warning("Unknown inotify event.");
1707 static int determine_change(sd_journal
*j
) {
1712 b
= j
->current_invalidate_counter
!= j
->last_invalidate_counter
;
1713 j
->last_invalidate_counter
= j
->current_invalidate_counter
;
1715 return b
? SD_JOURNAL_INVALIDATE
: SD_JOURNAL_APPEND
;
1718 _public_
int sd_journal_process(sd_journal
*j
) {
1719 uint8_t buffer
[sizeof(struct inotify_event
) + FILENAME_MAX
];
1720 bool got_something
= false;
1726 struct inotify_event
*e
;
1729 l
= read(j
->inotify_fd
, buffer
, sizeof(buffer
));
1731 if (errno
== EAGAIN
|| errno
== EINTR
)
1732 return got_something
? determine_change(j
) : SD_JOURNAL_NOP
;
1737 got_something
= true;
1739 e
= (struct inotify_event
*) buffer
;
1743 process_inotify_event(j
, e
);
1745 step
= sizeof(struct inotify_event
) + e
->len
;
1746 assert(step
<= (size_t) l
);
1748 e
= (struct inotify_event
*) ((uint8_t*) e
+ step
);
1753 return determine_change(j
);
1756 _public_
int sd_journal_wait(sd_journal
*j
, uint64_t timeout_usec
) {
1761 if (j
->inotify_fd
< 0) {
1763 /* This is the first invocation, hence create the
1765 r
= sd_journal_get_fd(j
);
1769 /* The journal might have changed since the context
1770 * object was created and we weren't watching before,
1771 * hence don't wait for anything, and return
1773 return determine_change(j
);
1777 r
= fd_wait_for_event(j
->inotify_fd
, POLLIN
, timeout_usec
);
1778 } while (r
== -EINTR
);
1783 return sd_journal_process(j
);
1786 _public_
int sd_journal_get_cutoff_realtime_usec(sd_journal
*j
, uint64_t *from
, uint64_t *to
) {
1797 HASHMAP_FOREACH(f
, j
->files
, i
) {
1800 r
= journal_file_get_cutoff_realtime_usec(f
, &fr
, &t
);
1814 *from
= MIN(fr
, *from
);
1820 return first
? 0 : 1;
1823 _public_
int sd_journal_get_cutoff_monotonic_usec(sd_journal
*j
, sd_id128_t boot_id
, uint64_t *from
, uint64_t *to
) {
1834 HASHMAP_FOREACH(f
, j
->files
, i
) {
1837 r
= journal_file_get_cutoff_monotonic_usec(f
, boot_id
, &fr
, &t
);
1851 *from
= MIN(fr
, *from
);
1857 return first
? 0 : 1;
1861 /* _public_ int sd_journal_query_unique(sd_journal *j, const char *field) { */
1863 /* return -EINVAL; */
1865 /* return -EINVAL; */
1867 /* return -ENOTSUP; */
1870 /* _public_ int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_t *l) { */
1872 /* return -EINVAL; */
1874 /* return -EINVAL; */
1876 /* return -EINVAL; */
1878 /* return -ENOTSUP; */
1881 /* _public_ void sd_journal_restart_unique(sd_journal *j) { */