]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/sd-journal.c
journal: fix reverse traversing of entries
[thirdparty/systemd.git] / src / journal / sd-journal.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2011 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
12
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 General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <stddef.h>
25 #include <unistd.h>
26 #include <sys/inotify.h>
27
28 #include "sd-journal.h"
29 #include "journal-def.h"
30 #include "journal-file.h"
31 #include "hashmap.h"
32 #include "list.h"
33 #include "lookup3.h"
34 #include "compress.h"
35 #include "journal-internal.h"
36
37 #define JOURNAL_FILES_MAX 1024
38
39 static void detach_location(sd_journal *j) {
40 Iterator i;
41 JournalFile *f;
42
43 assert(j);
44
45 j->current_file = NULL;
46 j->current_field = 0;
47
48 HASHMAP_FOREACH(f, j->files, i)
49 f->current_offset = 0;
50 }
51
52 static void reset_location(sd_journal *j) {
53 assert(j);
54
55 detach_location(j);
56 zero(j->current_location);
57 }
58
59 static void init_location(Location *l, JournalFile *f, Object *o) {
60 assert(l);
61 assert(f);
62 assert(o->object.type == OBJECT_ENTRY);
63
64 l->type = LOCATION_DISCRETE;
65 l->seqnum = le64toh(o->entry.seqnum);
66 l->seqnum_id = f->header->seqnum_id;
67 l->realtime = le64toh(o->entry.realtime);
68 l->monotonic = le64toh(o->entry.monotonic);
69 l->boot_id = le64toh(o->entry.boot_id);
70 l->xor_hash = le64toh(o->entry.xor_hash);
71
72 l->seqnum_set = l->realtime_set = l->monotonic_set = l->xor_hash_set = true;
73 }
74
75 static void set_location(sd_journal *j, JournalFile *f, Object *o, uint64_t offset) {
76 assert(j);
77 assert(f);
78 assert(o);
79
80 init_location(&j->current_location, f, o);
81
82 j->current_file = f;
83 j->current_field = 0;
84
85 f->current_offset = offset;
86 }
87
88 static int same_field(const void *_a, size_t s, const void *_b, size_t t) {
89 const uint8_t *a = _a, *b = _b;
90 size_t j;
91 bool a_good = false, b_good = false, different = false;
92
93 for (j = 0; j < s && j < t; j++) {
94
95 if (a[j] == '=')
96 a_good = true;
97 if (b[j] == '=')
98 b_good = true;
99 if (a[j] != b[j])
100 different = true;
101
102 if (a_good && b_good)
103 return different ? 0 : 1;
104 }
105
106 return -EINVAL;
107 }
108
109 _public_ int sd_journal_add_match(sd_journal *j, const void *data, size_t size) {
110 Match *m, *after = NULL;
111 uint64_t le_hash;
112
113 if (!j)
114 return -EINVAL;
115 if (!data)
116 return -EINVAL;
117 if (size <= 0)
118 return -EINVAL;
119
120 le_hash = htole64(hash64(data, size));
121
122 LIST_FOREACH(matches, m, j->matches) {
123 int r;
124
125 if (m->le_hash == le_hash &&
126 m->size == size &&
127 memcmp(m->data, data, size) == 0)
128 return 0;
129
130 r = same_field(data, size, m->data, m->size);
131 if (r < 0)
132 return r;
133 else if (r > 0)
134 after = m;
135 }
136
137 m = new0(Match, 1);
138 if (!m)
139 return -ENOMEM;
140
141 m->size = size;
142
143 m->data = malloc(m->size);
144 if (!m->data) {
145 free(m);
146 return -ENOMEM;
147 }
148
149 memcpy(m->data, data, size);
150 m->le_hash = le_hash;
151
152 /* Matches for the same fields we order adjacent to each
153 * other */
154 LIST_INSERT_AFTER(Match, matches, j->matches, after, m);
155 j->n_matches ++;
156
157 detach_location(j);
158
159 return 0;
160 }
161
162 _public_ void sd_journal_flush_matches(sd_journal *j) {
163 if (!j)
164 return;
165
166 while (j->matches) {
167 Match *m = j->matches;
168
169 LIST_REMOVE(Match, matches, j->matches, m);
170 free(m->data);
171 free(m);
172 }
173
174 j->n_matches = 0;
175
176 detach_location(j);
177 }
178
179 static int compare_order(JournalFile *af, Object *ao,
180 JournalFile *bf, Object *bo) {
181
182 uint64_t a, b;
183
184 assert(af);
185 assert(ao);
186 assert(bf);
187 assert(bo);
188
189 /* We operate on two different files here, hence we can access
190 * two objects at the same time, which we normally can't.
191 *
192 * If contents and timestamps match, these entries are
193 * identical, even if the seqnum does not match */
194
195 if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id) &&
196 ao->entry.monotonic == bo->entry.monotonic &&
197 ao->entry.realtime == bo->entry.realtime &&
198 ao->entry.xor_hash == bo->entry.xor_hash)
199 return 0;
200
201 if (sd_id128_equal(af->header->seqnum_id, bf->header->seqnum_id)) {
202
203 /* If this is from the same seqnum source, compare
204 * seqnums */
205 a = le64toh(ao->entry.seqnum);
206 b = le64toh(bo->entry.seqnum);
207
208 if (a < b)
209 return -1;
210 if (a > b)
211 return 1;
212
213 /* Wow! This is weird, different data but the same
214 * seqnums? Something is borked, but let's make the
215 * best of it and compare by time. */
216 }
217
218 if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id)) {
219
220 /* If the boot id matches compare monotonic time */
221 a = le64toh(ao->entry.monotonic);
222 b = le64toh(bo->entry.monotonic);
223
224 if (a < b)
225 return -1;
226 if (a > b)
227 return 1;
228 }
229
230 /* Otherwise compare UTC time */
231 a = le64toh(ao->entry.realtime);
232 b = le64toh(ao->entry.realtime);
233
234 if (a < b)
235 return -1;
236 if (a > b)
237 return 1;
238
239 /* Finally, compare by contents */
240 a = le64toh(ao->entry.xor_hash);
241 b = le64toh(ao->entry.xor_hash);
242
243 if (a < b)
244 return -1;
245 if (a > b)
246 return 1;
247
248 return 0;
249 }
250
251 static int compare_with_location(JournalFile *af, Object *ao, Location *l) {
252 uint64_t a;
253
254 assert(af);
255 assert(ao);
256 assert(l);
257 assert(l->type == LOCATION_DISCRETE);
258
259 if (l->monotonic_set &&
260 sd_id128_equal(ao->entry.boot_id, l->boot_id) &&
261 l->realtime_set &&
262 le64toh(ao->entry.realtime) == l->realtime &&
263 l->xor_hash_set &&
264 le64toh(ao->entry.xor_hash) == l->xor_hash)
265 return 0;
266
267 if (l->seqnum_set &&
268 sd_id128_equal(af->header->seqnum_id, l->seqnum_id)) {
269
270 a = le64toh(ao->entry.seqnum);
271
272 if (a < l->seqnum)
273 return -1;
274 if (a > l->seqnum)
275 return 1;
276 }
277
278 if (l->monotonic_set &&
279 sd_id128_equal(ao->entry.boot_id, l->boot_id)) {
280
281 a = le64toh(ao->entry.monotonic);
282
283 if (a < l->monotonic)
284 return -1;
285 if (a > l->monotonic)
286 return 1;
287 }
288
289 if (l->realtime_set) {
290
291 a = le64toh(ao->entry.realtime);
292
293 if (a < l->realtime)
294 return -1;
295 if (a > l->realtime)
296 return 1;
297 }
298
299 if (l->xor_hash_set) {
300 a = le64toh(ao->entry.xor_hash);
301
302 if (a < l->xor_hash)
303 return -1;
304 if (a > l->xor_hash)
305 return 1;
306 }
307
308 return 0;
309 }
310
311 static int find_location(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) {
312 Object *o = NULL;
313 uint64_t p = 0;
314 int r;
315
316 assert(j);
317
318 if (!j->matches) {
319 /* No matches is simple */
320
321 if (j->current_location.type == LOCATION_HEAD)
322 r = journal_file_next_entry(f, NULL, 0, DIRECTION_DOWN, &o, &p);
323 else if (j->current_location.type == LOCATION_TAIL)
324 r = journal_file_next_entry(f, NULL, 0, DIRECTION_UP, &o, &p);
325 else if (j->current_location.seqnum_set &&
326 sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id))
327 r = journal_file_move_to_entry_by_seqnum(f, j->current_location.seqnum, direction, &o, &p);
328 else if (j->current_location.monotonic_set)
329 r = journal_file_move_to_entry_by_monotonic(f, j->current_location.boot_id, j->current_location.monotonic, direction, &o, &p);
330 else if (j->current_location.realtime_set)
331 r = journal_file_move_to_entry_by_realtime(f, j->current_location.realtime, direction, &o, &p);
332 else
333 r = journal_file_next_entry(f, NULL, 0, direction, &o, &p);
334
335 if (r <= 0)
336 return r;
337
338 } else {
339 Match *m, *term_match = NULL;
340 Object *to = NULL;
341 uint64_t tp = 0;
342
343 /* We have matches, first, let's jump to the monotonic
344 * position if we have any, since it implies a
345 * match. */
346
347 if (j->current_location.type == LOCATION_DISCRETE &&
348 j->current_location.monotonic_set) {
349
350 r = journal_file_move_to_entry_by_monotonic(f, j->current_location.boot_id, j->current_location.monotonic, direction, &o, &p);
351 if (r <= 0)
352 return r == -ENOENT ? 0 : r;
353 }
354
355 LIST_FOREACH(matches, m, j->matches) {
356 Object *c, *d;
357 uint64_t cp, dp;
358
359 r = journal_file_find_data_object_with_hash(f, m->data, m->size, m->le_hash, &d, &dp);
360 if (r <= 0)
361 return r;
362
363 if (j->current_location.type == LOCATION_HEAD)
364 r = journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_DOWN, &c, &cp);
365 else if (j->current_location.type == LOCATION_TAIL)
366 r = journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_UP, &c, &cp);
367 else if (j->current_location.seqnum_set &&
368 sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id))
369 r = journal_file_move_to_entry_by_seqnum_for_data(f, dp, j->current_location.seqnum, direction, &c, &cp);
370 else if (j->current_location.realtime_set)
371 r = journal_file_move_to_entry_by_realtime_for_data(f, dp, j->current_location.realtime, direction, &c, &cp);
372 else
373 r = journal_file_next_entry_for_data(f, NULL, 0, dp, direction, &c, &cp);
374
375 if (r < 0)
376 return r;
377
378 if (!term_match) {
379 term_match = m;
380
381 if (r > 0) {
382 to = c;
383 tp = cp;
384 }
385 } else if (same_field(term_match->data, term_match->size, m->data, m->size)) {
386
387 /* Same field as previous match... */
388 if (r > 0) {
389
390 /* Find the earliest of the OR matches */
391
392 if (!to ||
393 (direction == DIRECTION_DOWN && cp < tp) ||
394 (direction == DIRECTION_UP && cp > tp)) {
395 to = c;
396 tp = cp;
397 }
398
399 }
400
401 } else {
402
403 /* Previous term is finished, did anything match? */
404 if (!to)
405 return 0;
406
407 /* Find the last of the AND matches */
408 if (!o ||
409 (direction == DIRECTION_DOWN && tp > p) ||
410 (direction == DIRECTION_UP && tp < p)) {
411 o = to;
412 p = tp;
413 }
414
415 term_match = m;
416
417 if (r > 0) {
418 to = c;
419 tp = cp;
420 } else {
421 to = NULL;
422 tp = 0;
423 }
424 }
425 }
426
427 /* Last term is finished, did anything match? */
428 if (!to)
429 return 0;
430
431 if (!o ||
432 (direction == DIRECTION_DOWN && tp > p) ||
433 (direction == DIRECTION_UP && tp < p)) {
434 o = to;
435 p = tp;
436 }
437
438 if (!o)
439 return 0;
440 }
441
442 if (ret)
443 *ret = o;
444
445 if (offset)
446 *offset = p;
447
448 return 1;
449 }
450
451 static int next_with_matches(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) {
452 int r;
453 uint64_t cp;
454 Object *c;
455
456 assert(j);
457 assert(f);
458 assert(ret);
459 assert(offset);
460
461 c = *ret;
462 cp = *offset;
463
464 if (!j->matches) {
465 /* No matches is easy */
466
467 r = journal_file_next_entry(f, c, cp, direction, &c, &cp);
468 if (r <= 0)
469 return r;
470
471 if (ret)
472 *ret = c;
473 if (offset)
474 *offset = cp;
475 return 1;
476 }
477
478 /* So there are matches we have to adhere to, let's find the
479 * first entry that matches all of them */
480
481 for (;;) {
482 uint64_t np, n;
483 bool found, term_result = false;
484 Match *m, *term_match = NULL;
485 Object *npo = NULL;
486
487 n = journal_file_entry_n_items(c);
488
489 /* Make sure we don't match the entry we are starting
490 * from. */
491 found = cp != *offset;
492
493 np = 0;
494 LIST_FOREACH(matches, m, j->matches) {
495 uint64_t q, k;
496 Object *qo = NULL;
497
498 /* Let's check if this is the beginning of a
499 * new term, i.e. has a different field prefix
500 * as the preceeding match. */
501 if (!term_match) {
502 term_match = m;
503 term_result = false;
504 } else if (!same_field(term_match->data, term_match->size, m->data, m->size)) {
505 if (!term_result)
506 found = false;
507
508 term_match = m;
509 term_result = false;
510 }
511
512 for (k = 0; k < n; k++)
513 if (c->entry.items[k].hash == m->le_hash)
514 break;
515
516 if (k >= n) {
517 /* Hmm, didn't find any field that
518 * matched this rule, so ignore this
519 * match. Go on with next match */
520 continue;
521 }
522
523 term_result = true;
524
525 /* Hmm, so, this field matched, let's remember
526 * where we'd have to try next, in case the other
527 * matches are not OK */
528
529 r = journal_file_next_entry_for_data(f, c, cp, le64toh(c->entry.items[k].object_offset), direction, &qo, &q);
530 if (r < 0)
531 return r;
532
533 if (r > 0) {
534
535 if (direction == DIRECTION_DOWN) {
536 if (q > np) {
537 np = q;
538 npo = qo;
539 }
540 } else {
541 if (np == 0 || q < np) {
542 np = q;
543 npo = qo;
544 }
545 }
546 }
547 }
548
549 /* Check the last term */
550 if (term_match && !term_result)
551 found = false;
552
553 /* Did this entry match against all matches? */
554 if (found) {
555 if (ret)
556 *ret = c;
557 if (offset)
558 *offset = cp;
559 return 1;
560 }
561
562 /* Did we find a subsequent entry? */
563 if (np == 0)
564 return 0;
565
566 /* Hmm, ok, this entry only matched partially, so
567 * let's try another one */
568 cp = np;
569 c = npo;
570 }
571 }
572
573 static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) {
574 Object *c;
575 uint64_t cp;
576 int compare_value, r;
577
578 assert(j);
579 assert(f);
580
581 if (f->current_offset > 0) {
582 cp = f->current_offset;
583
584 r = journal_file_move_to_object(f, OBJECT_ENTRY, cp, &c);
585 if (r < 0)
586 return r;
587
588 r = next_with_matches(j, f, direction, &c, &cp);
589 if (r <= 0)
590 return r;
591
592 compare_value = 1;
593 } else {
594 r = find_location(j, f, direction, &c, &cp);
595 if (r <= 0)
596 return r;
597
598 compare_value = 0;
599 }
600
601 for (;;) {
602 bool found;
603
604 if (j->current_location.type == LOCATION_DISCRETE) {
605 int k;
606
607 k = compare_with_location(f, c, &j->current_location);
608 if (direction == DIRECTION_DOWN)
609 found = k >= compare_value;
610 else
611 found = k <= -compare_value;
612 } else
613 found = true;
614
615 if (found) {
616 if (ret)
617 *ret = c;
618 if (offset)
619 *offset = cp;
620 return 1;
621 }
622
623 r = next_with_matches(j, f, direction, &c, &cp);
624 if (r <= 0)
625 return r;
626 }
627 }
628
629 static int real_journal_next(sd_journal *j, direction_t direction) {
630 JournalFile *f, *new_current = NULL;
631 Iterator i;
632 int r;
633 uint64_t new_offset = 0;
634 Object *new_entry = NULL;
635
636 if (!j)
637 return -EINVAL;
638
639 HASHMAP_FOREACH(f, j->files, i) {
640 Object *o;
641 uint64_t p;
642 bool found;
643
644 r = next_beyond_location(j, f, direction, &o, &p);
645 if (r < 0)
646 return r;
647 else if (r == 0)
648 continue;
649
650 if (!new_current)
651 found = true;
652 else {
653 int k;
654
655 k = compare_order(f, o, new_current, new_entry);
656
657 if (direction == DIRECTION_DOWN)
658 found = k < 0;
659 else
660 found = k > 0;
661 }
662
663 if (found) {
664 new_current = f;
665 new_entry = o;
666 new_offset = p;
667 }
668 }
669
670 if (!new_current)
671 return 0;
672
673 set_location(j, new_current, new_entry, new_offset);
674
675 return 1;
676 }
677
678 _public_ int sd_journal_next(sd_journal *j) {
679 return real_journal_next(j, DIRECTION_DOWN);
680 }
681
682 _public_ int sd_journal_previous(sd_journal *j) {
683 return real_journal_next(j, DIRECTION_UP);
684 }
685
686 _public_ int sd_journal_next_skip(sd_journal *j, uint64_t skip) {
687 int c = 0, r;
688
689 if (!j)
690 return -EINVAL;
691
692 while (skip > 0) {
693 r = sd_journal_next(j);
694 if (r < 0)
695 return r;
696
697 if (r == 0)
698 return c;
699
700 skip--;
701 c++;
702 }
703
704 return c;
705 }
706
707 _public_ int sd_journal_previous_skip(sd_journal *j, uint64_t skip) {
708 int c = 0, r;
709
710 if (!j)
711 return -EINVAL;
712
713 while (skip > 0) {
714 r = sd_journal_previous(j);
715 if (r < 0)
716 return r;
717
718 if (r == 0)
719 return c;
720
721 skip--;
722 c++;
723 }
724
725 return 1;
726 }
727
728 _public_ int sd_journal_get_cursor(sd_journal *j, char **cursor) {
729 Object *o;
730 int r;
731 char bid[33], sid[33];
732
733 if (!j)
734 return -EINVAL;
735 if (!cursor)
736 return -EINVAL;
737
738 if (!j->current_file || j->current_file->current_offset <= 0)
739 return -EADDRNOTAVAIL;
740
741 r = journal_file_move_to_object(j->current_file, OBJECT_ENTRY, j->current_file->current_offset, &o);
742 if (r < 0)
743 return r;
744
745 sd_id128_to_string(j->current_file->header->seqnum_id, sid);
746 sd_id128_to_string(o->entry.boot_id, bid);
747
748 if (asprintf(cursor,
749 "s=%s;i=%llx;b=%s;m=%llx;t=%llx;x=%llx;p=%s",
750 sid, (unsigned long long) le64toh(o->entry.seqnum),
751 bid, (unsigned long long) le64toh(o->entry.monotonic),
752 (unsigned long long) le64toh(o->entry.realtime),
753 (unsigned long long) le64toh(o->entry.xor_hash),
754 file_name_from_path(j->current_file->path)) < 0)
755 return -ENOMEM;
756
757 return 1;
758 }
759
760 _public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) {
761 char *w;
762 size_t l;
763 char *state;
764 unsigned long long seqnum, monotonic, realtime, xor_hash;
765 bool
766 seqnum_id_set = false,
767 seqnum_set = false,
768 boot_id_set = false,
769 monotonic_set = false,
770 realtime_set = false,
771 xor_hash_set = false;
772 sd_id128_t seqnum_id, boot_id;
773
774 if (!j)
775 return -EINVAL;
776 if (!cursor)
777 return -EINVAL;
778
779 FOREACH_WORD_SEPARATOR(w, l, cursor, ";", state) {
780 char *item;
781 int k = 0;
782
783 if (l < 2 || w[1] != '=')
784 return -EINVAL;
785
786 item = strndup(w, l);
787 if (!item)
788 return -ENOMEM;
789
790 switch (w[0]) {
791
792 case 's':
793 seqnum_id_set = true;
794 k = sd_id128_from_string(w+2, &seqnum_id);
795 break;
796
797 case 'i':
798 seqnum_set = true;
799 if (sscanf(w+2, "%llx", &seqnum) != 1)
800 k = -EINVAL;
801 break;
802
803 case 'b':
804 boot_id_set = true;
805 k = sd_id128_from_string(w+2, &boot_id);
806 break;
807
808 case 'm':
809 monotonic_set = true;
810 if (sscanf(w+2, "%llx", &monotonic) != 1)
811 k = -EINVAL;
812 break;
813
814 case 't':
815 realtime_set = true;
816 if (sscanf(w+2, "%llx", &realtime) != 1)
817 k = -EINVAL;
818 break;
819
820 case 'x':
821 xor_hash_set = true;
822 if (sscanf(w+2, "%llx", &xor_hash) != 1)
823 k = -EINVAL;
824 break;
825 }
826
827 free(item);
828
829 if (k < 0)
830 return k;
831 }
832
833 if ((!seqnum_set || !seqnum_id_set) &&
834 (!monotonic_set || !boot_id_set) &&
835 !realtime_set)
836 return -EINVAL;
837
838 reset_location(j);
839
840 j->current_location.type = LOCATION_DISCRETE;
841
842 if (realtime_set) {
843 j->current_location.realtime = (uint64_t) realtime;
844 j->current_location.realtime_set = true;
845 }
846
847 if (seqnum_set && seqnum_id_set) {
848 j->current_location.seqnum = (uint64_t) seqnum;
849 j->current_location.seqnum_id = seqnum_id;
850 j->current_location.seqnum_set = true;
851 }
852
853 if (monotonic_set && boot_id_set) {
854 j->current_location.monotonic = (uint64_t) monotonic;
855 j->current_location.boot_id = boot_id;
856 j->current_location.monotonic_set = true;
857 }
858
859 if (xor_hash_set) {
860 j->current_location.xor_hash = (uint64_t) xor_hash;
861 j->current_location.xor_hash_set = true;
862 }
863
864 return 0;
865 }
866
867 _public_ int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec) {
868 if (!j)
869 return -EINVAL;
870
871 reset_location(j);
872 j->current_location.type = LOCATION_DISCRETE;
873 j->current_location.boot_id = boot_id;
874 j->current_location.monotonic = usec;
875 j->current_location.monotonic_set = true;
876
877 return 0;
878 }
879
880 _public_ int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec) {
881 if (!j)
882 return -EINVAL;
883
884 reset_location(j);
885 j->current_location.type = LOCATION_DISCRETE;
886 j->current_location.realtime = usec;
887 j->current_location.realtime_set = true;
888
889 return 0;
890 }
891
892 _public_ int sd_journal_seek_head(sd_journal *j) {
893 if (!j)
894 return -EINVAL;
895
896 reset_location(j);
897 j->current_location.type = LOCATION_HEAD;
898
899 return 0;
900 }
901
902 _public_ int sd_journal_seek_tail(sd_journal *j) {
903 if (!j)
904 return -EINVAL;
905
906 reset_location(j);
907 j->current_location.type = LOCATION_TAIL;
908
909 return 0;
910 }
911
912 static int add_file(sd_journal *j, const char *prefix, const char *dir, const char *filename) {
913 char *fn;
914 int r;
915 JournalFile *f;
916
917 assert(j);
918 assert(prefix);
919 assert(filename);
920
921 if ((j->flags & SD_JOURNAL_SYSTEM_ONLY) &&
922 !startswith(filename, "system.journal"))
923 return 0;
924
925 if (dir)
926 fn = join(prefix, "/", dir, "/", filename, NULL);
927 else
928 fn = join(prefix, "/", filename, NULL);
929
930 if (!fn)
931 return -ENOMEM;
932
933 if (hashmap_get(j->files, fn)) {
934 free(fn);
935 return 0;
936 }
937
938 if (hashmap_size(j->files) >= JOURNAL_FILES_MAX) {
939 log_debug("Too many open journal files, not adding %s, ignoring.", fn);
940 free(fn);
941 return 0;
942 }
943
944 r = journal_file_open(fn, O_RDONLY, 0, NULL, &f);
945 free(fn);
946
947 if (r < 0) {
948 if (errno == ENOENT)
949 return 0;
950
951 return r;
952 }
953
954 /* journal_file_dump(f); */
955
956 r = hashmap_put(j->files, f->path, f);
957 if (r < 0) {
958 journal_file_close(f);
959 return r;
960 }
961
962 log_debug("File %s got added.", f->path);
963
964 return 0;
965 }
966
967 static int remove_file(sd_journal *j, const char *prefix, const char *dir, const char *filename) {
968 char *fn;
969 JournalFile *f;
970
971 assert(j);
972 assert(prefix);
973 assert(filename);
974
975 if (dir)
976 fn = join(prefix, "/", dir, "/", filename, NULL);
977 else
978 fn = join(prefix, "/", filename, NULL);
979
980 if (!fn)
981 return -ENOMEM;
982
983 f = hashmap_get(j->files, fn);
984 free(fn);
985
986 if (!f)
987 return 0;
988
989 hashmap_remove(j->files, f->path);
990 journal_file_close(f);
991
992 log_debug("File %s got removed.", f->path);
993 return 0;
994 }
995
996 static int add_directory(sd_journal *j, const char *prefix, const char *dir) {
997 char *fn;
998 int r;
999 DIR *d;
1000 int wd;
1001 sd_id128_t id, mid;
1002
1003 assert(j);
1004 assert(prefix);
1005 assert(dir);
1006
1007 if ((j->flags & SD_JOURNAL_LOCAL_ONLY) &&
1008 (sd_id128_from_string(dir, &id) < 0 ||
1009 sd_id128_get_machine(&mid) < 0 ||
1010 !sd_id128_equal(id, mid)))
1011 return 0;
1012
1013 fn = join(prefix, "/", dir, NULL);
1014 if (!fn)
1015 return -ENOMEM;
1016
1017 d = opendir(fn);
1018
1019 if (!d) {
1020 free(fn);
1021 if (errno == ENOENT)
1022 return 0;
1023
1024 return -errno;
1025 }
1026
1027 wd = inotify_add_watch(j->inotify_fd, fn,
1028 IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE|
1029 IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT|
1030 IN_DONT_FOLLOW|IN_ONLYDIR);
1031 if (wd > 0) {
1032 if (hashmap_put(j->inotify_wd_dirs, INT_TO_PTR(wd), fn) < 0)
1033 inotify_rm_watch(j->inotify_fd, wd);
1034 else
1035 fn = NULL;
1036 }
1037
1038 free(fn);
1039
1040 for (;;) {
1041 struct dirent buf, *de;
1042
1043 r = readdir_r(d, &buf, &de);
1044 if (r != 0 || !de)
1045 break;
1046
1047 if (!dirent_is_file_with_suffix(de, ".journal"))
1048 continue;
1049
1050 r = add_file(j, prefix, dir, de->d_name);
1051 if (r < 0)
1052 log_debug("Failed to add file %s/%s/%s: %s", prefix, dir, de->d_name, strerror(-r));
1053 }
1054
1055 closedir(d);
1056
1057 log_debug("Directory %s/%s got added.", prefix, dir);
1058
1059 return 0;
1060 }
1061
1062 static void remove_directory_wd(sd_journal *j, int wd) {
1063 char *p;
1064
1065 assert(j);
1066 assert(wd > 0);
1067
1068 if (j->inotify_fd >= 0)
1069 inotify_rm_watch(j->inotify_fd, wd);
1070
1071 p = hashmap_remove(j->inotify_wd_dirs, INT_TO_PTR(wd));
1072
1073 if (p) {
1074 log_debug("Directory %s got removed.", p);
1075 free(p);
1076 }
1077 }
1078
1079 static void add_root_wd(sd_journal *j, const char *p) {
1080 int wd;
1081 char *k;
1082
1083 assert(j);
1084 assert(p);
1085
1086 wd = inotify_add_watch(j->inotify_fd, p,
1087 IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE|
1088 IN_DONT_FOLLOW|IN_ONLYDIR);
1089 if (wd <= 0)
1090 return;
1091
1092 k = strdup(p);
1093 if (!k || hashmap_put(j->inotify_wd_roots, INT_TO_PTR(wd), k) < 0) {
1094 inotify_rm_watch(j->inotify_fd, wd);
1095 free(k);
1096 }
1097 }
1098
1099 static void remove_root_wd(sd_journal *j, int wd) {
1100 char *p;
1101
1102 assert(j);
1103 assert(wd > 0);
1104
1105 if (j->inotify_fd >= 0)
1106 inotify_rm_watch(j->inotify_fd, wd);
1107
1108 p = hashmap_remove(j->inotify_wd_roots, INT_TO_PTR(wd));
1109
1110 if (p) {
1111 log_debug("Root %s got removed.", p);
1112 free(p);
1113 }
1114 }
1115
1116 _public_ int sd_journal_open(sd_journal **ret, int flags) {
1117 sd_journal *j;
1118 const char *p;
1119 const char search_paths[] =
1120 "/run/log/journal\0"
1121 "/var/log/journal\0";
1122 int r;
1123
1124 if (!ret)
1125 return -EINVAL;
1126
1127 if (flags & ~(SD_JOURNAL_LOCAL_ONLY|
1128 SD_JOURNAL_RUNTIME_ONLY|
1129 SD_JOURNAL_SYSTEM_ONLY))
1130 return -EINVAL;
1131
1132 j = new0(sd_journal, 1);
1133 if (!j)
1134 return -ENOMEM;
1135
1136 j->flags = flags;
1137
1138 j->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
1139 if (j->inotify_fd < 0) {
1140 r = -errno;
1141 goto fail;
1142 }
1143
1144 j->files = hashmap_new(string_hash_func, string_compare_func);
1145 if (!j->files) {
1146 r = -ENOMEM;
1147 goto fail;
1148 }
1149
1150 j->inotify_wd_dirs = hashmap_new(trivial_hash_func, trivial_compare_func);
1151 j->inotify_wd_roots = hashmap_new(trivial_hash_func, trivial_compare_func);
1152
1153 if (!j->inotify_wd_dirs || !j->inotify_wd_roots) {
1154 r = -ENOMEM;
1155 goto fail;
1156 }
1157
1158 /* We ignore most errors here, since the idea is to only open
1159 * what's actually accessible, and ignore the rest. */
1160
1161 NULSTR_FOREACH(p, search_paths) {
1162 DIR *d;
1163
1164 if ((flags & SD_JOURNAL_RUNTIME_ONLY) &&
1165 !path_startswith(p, "/run"))
1166 continue;
1167
1168 d = opendir(p);
1169 if (!d) {
1170 if (errno != ENOENT)
1171 log_debug("Failed to open %s: %m", p);
1172 continue;
1173 }
1174
1175 add_root_wd(j, p);
1176
1177 for (;;) {
1178 struct dirent buf, *de;
1179 sd_id128_t id;
1180
1181 r = readdir_r(d, &buf, &de);
1182 if (r != 0 || !de)
1183 break;
1184
1185 if (dirent_is_file_with_suffix(de, ".journal")) {
1186 r = add_file(j, p, NULL, de->d_name);
1187 if (r < 0)
1188 log_debug("Failed to add file %s/%s: %s", p, de->d_name, strerror(-r));
1189
1190 } else if ((de->d_type == DT_DIR || de->d_type == DT_UNKNOWN) &&
1191 sd_id128_from_string(de->d_name, &id) >= 0) {
1192
1193 r = add_directory(j, p, de->d_name);
1194 if (r < 0)
1195 log_debug("Failed to add directory %s/%s: %s", p, de->d_name, strerror(-r));
1196 }
1197 }
1198
1199 closedir(d);
1200 }
1201
1202 *ret = j;
1203 return 0;
1204
1205 fail:
1206 sd_journal_close(j);
1207
1208 return r;
1209 };
1210
1211 _public_ void sd_journal_close(sd_journal *j) {
1212 if (!j)
1213 return;
1214
1215 if (j->inotify_wd_dirs) {
1216 void *k;
1217
1218 while ((k = hashmap_first_key(j->inotify_wd_dirs)))
1219 remove_directory_wd(j, PTR_TO_INT(k));
1220
1221 hashmap_free(j->inotify_wd_dirs);
1222 }
1223
1224 if (j->inotify_wd_roots) {
1225 void *k;
1226
1227 while ((k = hashmap_first_key(j->inotify_wd_roots)))
1228 remove_root_wd(j, PTR_TO_INT(k));
1229
1230 hashmap_free(j->inotify_wd_roots);
1231 }
1232
1233 if (j->files) {
1234 JournalFile *f;
1235
1236 while ((f = hashmap_steal_first(j->files)))
1237 journal_file_close(f);
1238
1239 hashmap_free(j->files);
1240 }
1241
1242 sd_journal_flush_matches(j);
1243
1244 if (j->inotify_fd >= 0)
1245 close_nointr_nofail(j->inotify_fd);
1246
1247 free(j);
1248 }
1249
1250 _public_ int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) {
1251 Object *o;
1252 JournalFile *f;
1253 int r;
1254
1255 if (!j)
1256 return -EINVAL;
1257 if (!ret)
1258 return -EINVAL;
1259
1260 f = j->current_file;
1261 if (!f)
1262 return -EADDRNOTAVAIL;
1263
1264 if (f->current_offset <= 0)
1265 return -EADDRNOTAVAIL;
1266
1267 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1268 if (r < 0)
1269 return r;
1270
1271 *ret = le64toh(o->entry.realtime);
1272 return 0;
1273 }
1274
1275 _public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id128_t *ret_boot_id) {
1276 Object *o;
1277 JournalFile *f;
1278 int r;
1279 sd_id128_t id;
1280
1281 if (!j)
1282 return -EINVAL;
1283 if (!ret)
1284 return -EINVAL;
1285
1286 f = j->current_file;
1287 if (!f)
1288 return -EADDRNOTAVAIL;
1289
1290 if (f->current_offset <= 0)
1291 return -EADDRNOTAVAIL;
1292
1293 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1294 if (r < 0)
1295 return r;
1296
1297 if (ret_boot_id)
1298 *ret_boot_id = o->entry.boot_id;
1299 else {
1300 r = sd_id128_get_boot(&id);
1301 if (r < 0)
1302 return r;
1303
1304 if (!sd_id128_equal(id, o->entry.boot_id))
1305 return -ENOENT;
1306 }
1307
1308 *ret = le64toh(o->entry.monotonic);
1309 return 0;
1310 }
1311
1312 _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *size) {
1313 JournalFile *f;
1314 uint64_t i, n;
1315 size_t field_length;
1316 int r;
1317 Object *o;
1318
1319 if (!j)
1320 return -EINVAL;
1321 if (!field)
1322 return -EINVAL;
1323 if (!data)
1324 return -EINVAL;
1325 if (!size)
1326 return -EINVAL;
1327
1328 if (isempty(field) || strchr(field, '='))
1329 return -EINVAL;
1330
1331 f = j->current_file;
1332 if (!f)
1333 return -EADDRNOTAVAIL;
1334
1335 if (f->current_offset <= 0)
1336 return -EADDRNOTAVAIL;
1337
1338 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1339 if (r < 0)
1340 return r;
1341
1342 field_length = strlen(field);
1343
1344 n = journal_file_entry_n_items(o);
1345 for (i = 0; i < n; i++) {
1346 uint64_t p, l, le_hash;
1347 size_t t;
1348
1349 p = le64toh(o->entry.items[i].object_offset);
1350 le_hash = o->entry.items[i].hash;
1351 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
1352 if (r < 0)
1353 return r;
1354
1355 if (le_hash != o->data.hash)
1356 return -EBADMSG;
1357
1358 l = le64toh(o->object.size) - offsetof(Object, data.payload);
1359
1360 if (o->object.flags & OBJECT_COMPRESSED) {
1361
1362 #ifdef HAVE_XZ
1363 if (uncompress_startswith(o->data.payload, l,
1364 &f->compress_buffer, &f->compress_buffer_size,
1365 field, field_length, '=')) {
1366
1367 uint64_t rsize;
1368
1369 if (!uncompress_blob(o->data.payload, l,
1370 &f->compress_buffer, &f->compress_buffer_size, &rsize))
1371 return -EBADMSG;
1372
1373 *data = f->compress_buffer;
1374 *size = (size_t) rsize;
1375
1376 return 0;
1377 }
1378 #else
1379 return -EPROTONOSUPPORT;
1380 #endif
1381
1382 } else if (l >= field_length+1 &&
1383 memcmp(o->data.payload, field, field_length) == 0 &&
1384 o->data.payload[field_length] == '=') {
1385
1386 t = (size_t) l;
1387
1388 if ((uint64_t) t != l)
1389 return -E2BIG;
1390
1391 *data = o->data.payload;
1392 *size = t;
1393
1394 return 0;
1395 }
1396
1397 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1398 if (r < 0)
1399 return r;
1400 }
1401
1402 return -ENOENT;
1403 }
1404
1405 _public_ int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *size) {
1406 JournalFile *f;
1407 uint64_t p, l, n, le_hash;
1408 int r;
1409 Object *o;
1410 size_t t;
1411
1412 if (!j)
1413 return -EINVAL;
1414 if (!data)
1415 return -EINVAL;
1416 if (!size)
1417 return -EINVAL;
1418
1419 f = j->current_file;
1420 if (!f)
1421 return -EADDRNOTAVAIL;
1422
1423 if (f->current_offset <= 0)
1424 return -EADDRNOTAVAIL;
1425
1426 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1427 if (r < 0)
1428 return r;
1429
1430 n = journal_file_entry_n_items(o);
1431 if (j->current_field >= n)
1432 return 0;
1433
1434 p = le64toh(o->entry.items[j->current_field].object_offset);
1435 le_hash = o->entry.items[j->current_field].hash;
1436 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
1437 if (r < 0)
1438 return r;
1439
1440 if (le_hash != o->data.hash)
1441 return -EBADMSG;
1442
1443 l = le64toh(o->object.size) - offsetof(Object, data.payload);
1444 t = (size_t) l;
1445
1446 /* We can't read objects larger than 4G on a 32bit machine */
1447 if ((uint64_t) t != l)
1448 return -E2BIG;
1449
1450 if (o->object.flags & OBJECT_COMPRESSED) {
1451 #ifdef HAVE_XZ
1452 uint64_t rsize;
1453
1454 if (!uncompress_blob(o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize))
1455 return -EBADMSG;
1456
1457 *data = f->compress_buffer;
1458 *size = (size_t) rsize;
1459 #else
1460 return -EPROTONOSUPPORT;
1461 #endif
1462 } else {
1463 *data = o->data.payload;
1464 *size = t;
1465 }
1466
1467 j->current_field ++;
1468
1469 return 1;
1470 }
1471
1472 _public_ void sd_journal_restart_data(sd_journal *j) {
1473 if (!j)
1474 return;
1475
1476 j->current_field = 0;
1477 }
1478
1479 _public_ int sd_journal_get_fd(sd_journal *j) {
1480 if (!j)
1481 return -EINVAL;
1482
1483 return j->inotify_fd;
1484 }
1485
1486 static void process_inotify_event(sd_journal *j, struct inotify_event *e) {
1487 char *p;
1488 int r;
1489
1490 assert(j);
1491 assert(e);
1492
1493 /* Is this a subdirectory we watch? */
1494 p = hashmap_get(j->inotify_wd_dirs, INT_TO_PTR(e->wd));
1495 if (p) {
1496
1497 if (!(e->mask & IN_ISDIR) && e->len > 0 && endswith(e->name, ".journal")) {
1498
1499 /* Event for a journal file */
1500
1501 if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
1502 r = add_file(j, p, NULL, e->name);
1503 if (r < 0)
1504 log_debug("Failed to add file %s/%s: %s", p, e->name, strerror(-r));
1505 } else if (e->mask & (IN_DELETE|IN_UNMOUNT)) {
1506
1507 r = remove_file(j, p, NULL, e->name);
1508 if (r < 0)
1509 log_debug("Failed to remove file %s/%s: %s", p, e->name, strerror(-r));
1510 }
1511
1512 } else if (e->len == 0) {
1513
1514 /* Event for the directory itself */
1515
1516 if (e->mask & (IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT))
1517 remove_directory_wd(j, e->wd);
1518 }
1519
1520 return;
1521 }
1522
1523 /* Must be the root directory then? */
1524 p = hashmap_get(j->inotify_wd_roots, INT_TO_PTR(e->wd));
1525 if (p) {
1526 sd_id128_t id;
1527
1528 if (!(e->mask & IN_ISDIR) && e->len > 0 && endswith(e->name, ".journal")) {
1529
1530 /* Event for a journal file */
1531
1532 if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
1533 r = add_file(j, p, NULL, e->name);
1534 if (r < 0)
1535 log_debug("Failed to add file %s/%s: %s", p, e->name, strerror(-r));
1536 } else if (e->mask & (IN_DELETE|IN_UNMOUNT)) {
1537
1538 r = remove_file(j, p, NULL, e->name);
1539 if (r < 0)
1540 log_debug("Failed to remove file %s/%s: %s", p, e->name, strerror(-r));
1541 }
1542
1543 } else if ((e->mask & IN_ISDIR) && e->len > 0 && sd_id128_from_string(e->name, &id) >= 0) {
1544
1545 /* Event for subdirectory */
1546
1547 if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
1548
1549 r = add_directory(j, p, e->name);
1550 if (r < 0)
1551 log_debug("Failed to add directory %s/%s: %s", p, e->name, strerror(-r));
1552 }
1553 }
1554
1555 return;
1556 }
1557
1558 if (e->mask & IN_IGNORED)
1559 return;
1560
1561 log_warning("Unknown inotify event.");
1562 }
1563
1564 _public_ int sd_journal_process(sd_journal *j) {
1565 uint8_t buffer[sizeof(struct inotify_event) + FILENAME_MAX];
1566
1567 if (!j)
1568 return -EINVAL;
1569
1570 for (;;) {
1571 struct inotify_event *e;
1572 ssize_t l;
1573
1574 l = read(j->inotify_fd, buffer, sizeof(buffer));
1575 if (l < 0) {
1576 if (errno == EINTR || errno == EAGAIN)
1577 return 0;
1578
1579 return -errno;
1580 }
1581
1582 e = (struct inotify_event*) buffer;
1583 while (l > 0) {
1584 size_t step;
1585
1586 process_inotify_event(j, e);
1587
1588 step = sizeof(struct inotify_event) + e->len;
1589 assert(step <= (size_t) l);
1590
1591 e = (struct inotify_event*) ((uint8_t*) e + step);
1592 l -= step;
1593 }
1594 }
1595 }
1596
1597 _public_ int sd_journal_query_unique(sd_journal *j, const char *field) {
1598 if (!j)
1599 return -EINVAL;
1600 if (!field)
1601 return -EINVAL;
1602
1603 return -ENOTSUP;
1604 }
1605
1606 _public_ int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_t *l) {
1607 if (!j)
1608 return -EINVAL;
1609 if (!data)
1610 return -EINVAL;
1611 if (!l)
1612 return -EINVAL;
1613
1614 return -ENOTSUP;
1615 }
1616
1617 _public_ void sd_journal_restart_unique(sd_journal *j) {
1618 if (!j)
1619 return;
1620 }