]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/sd-journal.c
relicense to LGPLv2.1 (with exceptions)
[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 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.
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 Lesser General Public License for more details.
17
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/>.
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 = 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 le64_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
331 if (r == -ENOENT) {
332 /* boot id unknown in this file */
333 if (j->current_location.realtime_set)
334 r = journal_file_move_to_entry_by_realtime(f, j->current_location.realtime, direction, &o, &p);
335 else
336 r = journal_file_next_entry(f, NULL, 0, direction, &o, &p);
337 }
338 } else if (j->current_location.realtime_set)
339 r = journal_file_move_to_entry_by_realtime(f, j->current_location.realtime, direction, &o, &p);
340 else
341 r = journal_file_next_entry(f, NULL, 0, direction, &o, &p);
342
343 if (r <= 0)
344 return r;
345
346 } else {
347 Match *m, *term_match = NULL;
348 Object *to = NULL;
349 uint64_t tp = 0;
350
351 /* We have matches, first, let's jump to the monotonic
352 * position if we have any, since it implies a
353 * match. */
354
355 if (j->current_location.type == LOCATION_DISCRETE &&
356 j->current_location.monotonic_set) {
357
358 r = journal_file_move_to_entry_by_monotonic(f, j->current_location.boot_id, j->current_location.monotonic, direction, &o, &p);
359 if (r <= 0)
360 return r == -ENOENT ? 0 : r;
361 }
362
363 LIST_FOREACH(matches, m, j->matches) {
364 Object *c, *d;
365 uint64_t cp, dp;
366
367 r = journal_file_find_data_object_with_hash(f, m->data, m->size, le64toh(m->le_hash), &d, &dp);
368 if (r <= 0)
369 return r;
370
371 if (j->current_location.type == LOCATION_HEAD)
372 r = journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_DOWN, &c, &cp);
373 else if (j->current_location.type == LOCATION_TAIL)
374 r = journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_UP, &c, &cp);
375 else if (j->current_location.seqnum_set &&
376 sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id))
377 r = journal_file_move_to_entry_by_seqnum_for_data(f, dp, j->current_location.seqnum, direction, &c, &cp);
378 else if (j->current_location.realtime_set)
379 r = journal_file_move_to_entry_by_realtime_for_data(f, dp, j->current_location.realtime, direction, &c, &cp);
380 else
381 r = journal_file_next_entry_for_data(f, NULL, 0, dp, direction, &c, &cp);
382
383 if (r < 0)
384 return r;
385
386 if (!term_match) {
387 term_match = m;
388
389 if (r > 0) {
390 to = c;
391 tp = cp;
392 }
393 } else if (same_field(term_match->data, term_match->size, m->data, m->size)) {
394
395 /* Same field as previous match... */
396 if (r > 0) {
397
398 /* Find the earliest of the OR matches */
399
400 if (!to ||
401 (direction == DIRECTION_DOWN && cp < tp) ||
402 (direction == DIRECTION_UP && cp > tp)) {
403 to = c;
404 tp = cp;
405 }
406
407 }
408
409 } else {
410
411 /* Previous term is finished, did anything match? */
412 if (!to)
413 return 0;
414
415 /* Find the last of the AND matches */
416 if (!o ||
417 (direction == DIRECTION_DOWN && tp > p) ||
418 (direction == DIRECTION_UP && tp < p)) {
419 o = to;
420 p = tp;
421 }
422
423 term_match = m;
424
425 if (r > 0) {
426 to = c;
427 tp = cp;
428 } else {
429 to = NULL;
430 tp = 0;
431 }
432 }
433 }
434
435 /* Last term is finished, did anything match? */
436 if (!to)
437 return 0;
438
439 if (!o ||
440 (direction == DIRECTION_DOWN && tp > p) ||
441 (direction == DIRECTION_UP && tp < p)) {
442 o = to;
443 p = tp;
444 }
445
446 if (!o)
447 return 0;
448 }
449
450 if (ret)
451 *ret = o;
452
453 if (offset)
454 *offset = p;
455
456 return 1;
457 }
458
459 static int next_with_matches(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) {
460 int r;
461 uint64_t cp;
462 Object *c;
463
464 assert(j);
465 assert(f);
466 assert(ret);
467 assert(offset);
468
469 c = *ret;
470 cp = *offset;
471
472 if (!j->matches) {
473 /* No matches is easy */
474
475 r = journal_file_next_entry(f, c, cp, direction, &c, &cp);
476 if (r <= 0)
477 return r;
478
479 if (ret)
480 *ret = c;
481 if (offset)
482 *offset = cp;
483 return 1;
484 }
485
486 /* So there are matches we have to adhere to, let's find the
487 * first entry that matches all of them */
488
489 for (;;) {
490 uint64_t np, n;
491 bool found, term_result = false;
492 Match *m, *term_match = NULL;
493 Object *npo = NULL;
494
495 n = journal_file_entry_n_items(c);
496
497 /* Make sure we don't match the entry we are starting
498 * from. */
499 found = cp != *offset;
500
501 np = 0;
502 LIST_FOREACH(matches, m, j->matches) {
503 uint64_t q, k;
504 Object *qo = NULL;
505
506 /* Let's check if this is the beginning of a
507 * new term, i.e. has a different field prefix
508 * as the preceeding match. */
509 if (!term_match) {
510 term_match = m;
511 term_result = false;
512 } else if (!same_field(term_match->data, term_match->size, m->data, m->size)) {
513 if (!term_result)
514 found = false;
515
516 term_match = m;
517 term_result = false;
518 }
519
520 for (k = 0; k < n; k++)
521 if (c->entry.items[k].hash == m->le_hash)
522 break;
523
524 if (k >= n) {
525 /* Hmm, didn't find any field that
526 * matched this rule, so ignore this
527 * match. Go on with next match */
528 continue;
529 }
530
531 term_result = true;
532
533 /* Hmm, so, this field matched, let's remember
534 * where we'd have to try next, in case the other
535 * matches are not OK */
536
537 r = journal_file_next_entry_for_data(f, c, cp, le64toh(c->entry.items[k].object_offset), direction, &qo, &q);
538 /* This pointer is invalidated if the window was
539 * remapped. May need to re-fetch it later */
540 c = NULL;
541 if (r < 0)
542 return r;
543
544 if (r > 0) {
545
546 if (direction == DIRECTION_DOWN) {
547 if (q > np) {
548 np = q;
549 npo = qo;
550 }
551 } else {
552 if (np == 0 || q < np) {
553 np = q;
554 npo = qo;
555 }
556 }
557 }
558 }
559
560 /* Check the last term */
561 if (term_match && !term_result)
562 found = false;
563
564 /* Did this entry match against all matches? */
565 if (found) {
566 if (ret) {
567 if (c == NULL) {
568 /* Re-fetch the entry */
569 r = journal_file_move_to_object(f, OBJECT_ENTRY, cp, &c);
570 if (r < 0)
571 return r;
572 }
573 *ret = c;
574 }
575 if (offset)
576 *offset = cp;
577 return 1;
578 }
579
580 /* Did we find a subsequent entry? */
581 if (np == 0)
582 return 0;
583
584 /* Hmm, ok, this entry only matched partially, so
585 * let's try another one */
586 cp = np;
587 c = npo;
588 }
589 }
590
591 static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) {
592 Object *c;
593 uint64_t cp;
594 int compare_value, r;
595
596 assert(j);
597 assert(f);
598
599 if (f->current_offset > 0) {
600 cp = f->current_offset;
601
602 r = journal_file_move_to_object(f, OBJECT_ENTRY, cp, &c);
603 if (r < 0)
604 return r;
605
606 r = next_with_matches(j, f, direction, &c, &cp);
607 if (r <= 0)
608 return r;
609
610 compare_value = 1;
611 } else {
612 r = find_location(j, f, direction, &c, &cp);
613 if (r <= 0)
614 return r;
615
616 compare_value = 0;
617 }
618
619 for (;;) {
620 bool found;
621
622 if (j->current_location.type == LOCATION_DISCRETE) {
623 int k;
624
625 k = compare_with_location(f, c, &j->current_location);
626 if (direction == DIRECTION_DOWN)
627 found = k >= compare_value;
628 else
629 found = k <= -compare_value;
630 } else
631 found = true;
632
633 if (found) {
634 if (ret)
635 *ret = c;
636 if (offset)
637 *offset = cp;
638 return 1;
639 }
640
641 r = next_with_matches(j, f, direction, &c, &cp);
642 if (r <= 0)
643 return r;
644 }
645 }
646
647 static int real_journal_next(sd_journal *j, direction_t direction) {
648 JournalFile *f, *new_current = NULL;
649 Iterator i;
650 int r;
651 uint64_t new_offset = 0;
652 Object *new_entry = NULL;
653
654 if (!j)
655 return -EINVAL;
656
657 HASHMAP_FOREACH(f, j->files, i) {
658 Object *o;
659 uint64_t p;
660 bool found;
661
662 r = next_beyond_location(j, f, direction, &o, &p);
663 if (r < 0)
664 return r;
665 else if (r == 0)
666 continue;
667
668 if (!new_current)
669 found = true;
670 else {
671 int k;
672
673 k = compare_order(f, o, new_current, new_entry);
674
675 if (direction == DIRECTION_DOWN)
676 found = k < 0;
677 else
678 found = k > 0;
679 }
680
681 if (found) {
682 new_current = f;
683 new_entry = o;
684 new_offset = p;
685 }
686 }
687
688 if (!new_current)
689 return 0;
690
691 set_location(j, new_current, new_entry, new_offset);
692
693 return 1;
694 }
695
696 _public_ int sd_journal_next(sd_journal *j) {
697 return real_journal_next(j, DIRECTION_DOWN);
698 }
699
700 _public_ int sd_journal_previous(sd_journal *j) {
701 return real_journal_next(j, DIRECTION_UP);
702 }
703
704 static int real_journal_next_skip(sd_journal *j, direction_t direction, uint64_t skip) {
705 int c = 0, r;
706
707 if (!j)
708 return -EINVAL;
709
710 if (skip == 0) {
711 /* If this is not a discrete skip, then at least
712 * resolve the current location */
713 if (j->current_location.type != LOCATION_DISCRETE)
714 return real_journal_next(j, direction);
715
716 return 0;
717 }
718
719 do {
720 r = real_journal_next(j, direction);
721 if (r < 0)
722 return r;
723
724 if (r == 0)
725 return c;
726
727 skip--;
728 c++;
729 } while (skip > 0);
730
731 return c;
732 }
733
734 _public_ int sd_journal_next_skip(sd_journal *j, uint64_t skip) {
735 return real_journal_next_skip(j, DIRECTION_DOWN, skip);
736 }
737
738 _public_ int sd_journal_previous_skip(sd_journal *j, uint64_t skip) {
739 return real_journal_next_skip(j, DIRECTION_UP, skip);
740 }
741
742 _public_ int sd_journal_get_cursor(sd_journal *j, char **cursor) {
743 Object *o;
744 int r;
745 char bid[33], sid[33];
746
747 if (!j)
748 return -EINVAL;
749 if (!cursor)
750 return -EINVAL;
751
752 if (!j->current_file || j->current_file->current_offset <= 0)
753 return -EADDRNOTAVAIL;
754
755 r = journal_file_move_to_object(j->current_file, OBJECT_ENTRY, j->current_file->current_offset, &o);
756 if (r < 0)
757 return r;
758
759 sd_id128_to_string(j->current_file->header->seqnum_id, sid);
760 sd_id128_to_string(o->entry.boot_id, bid);
761
762 if (asprintf(cursor,
763 "s=%s;i=%llx;b=%s;m=%llx;t=%llx;x=%llx;p=%s",
764 sid, (unsigned long long) le64toh(o->entry.seqnum),
765 bid, (unsigned long long) le64toh(o->entry.monotonic),
766 (unsigned long long) le64toh(o->entry.realtime),
767 (unsigned long long) le64toh(o->entry.xor_hash),
768 file_name_from_path(j->current_file->path)) < 0)
769 return -ENOMEM;
770
771 return 1;
772 }
773
774 _public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) {
775 char *w;
776 size_t l;
777 char *state;
778 unsigned long long seqnum, monotonic, realtime, xor_hash;
779 bool
780 seqnum_id_set = false,
781 seqnum_set = false,
782 boot_id_set = false,
783 monotonic_set = false,
784 realtime_set = false,
785 xor_hash_set = false;
786 sd_id128_t seqnum_id, boot_id;
787
788 if (!j)
789 return -EINVAL;
790 if (!cursor)
791 return -EINVAL;
792
793 FOREACH_WORD_SEPARATOR(w, l, cursor, ";", state) {
794 char *item;
795 int k = 0;
796
797 if (l < 2 || w[1] != '=')
798 return -EINVAL;
799
800 item = strndup(w, l);
801 if (!item)
802 return -ENOMEM;
803
804 switch (w[0]) {
805
806 case 's':
807 seqnum_id_set = true;
808 k = sd_id128_from_string(w+2, &seqnum_id);
809 break;
810
811 case 'i':
812 seqnum_set = true;
813 if (sscanf(w+2, "%llx", &seqnum) != 1)
814 k = -EINVAL;
815 break;
816
817 case 'b':
818 boot_id_set = true;
819 k = sd_id128_from_string(w+2, &boot_id);
820 break;
821
822 case 'm':
823 monotonic_set = true;
824 if (sscanf(w+2, "%llx", &monotonic) != 1)
825 k = -EINVAL;
826 break;
827
828 case 't':
829 realtime_set = true;
830 if (sscanf(w+2, "%llx", &realtime) != 1)
831 k = -EINVAL;
832 break;
833
834 case 'x':
835 xor_hash_set = true;
836 if (sscanf(w+2, "%llx", &xor_hash) != 1)
837 k = -EINVAL;
838 break;
839 }
840
841 free(item);
842
843 if (k < 0)
844 return k;
845 }
846
847 if ((!seqnum_set || !seqnum_id_set) &&
848 (!monotonic_set || !boot_id_set) &&
849 !realtime_set)
850 return -EINVAL;
851
852 reset_location(j);
853
854 j->current_location.type = LOCATION_DISCRETE;
855
856 if (realtime_set) {
857 j->current_location.realtime = (uint64_t) realtime;
858 j->current_location.realtime_set = true;
859 }
860
861 if (seqnum_set && seqnum_id_set) {
862 j->current_location.seqnum = (uint64_t) seqnum;
863 j->current_location.seqnum_id = seqnum_id;
864 j->current_location.seqnum_set = true;
865 }
866
867 if (monotonic_set && boot_id_set) {
868 j->current_location.monotonic = (uint64_t) monotonic;
869 j->current_location.boot_id = boot_id;
870 j->current_location.monotonic_set = true;
871 }
872
873 if (xor_hash_set) {
874 j->current_location.xor_hash = (uint64_t) xor_hash;
875 j->current_location.xor_hash_set = true;
876 }
877
878 return 0;
879 }
880
881 _public_ int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec) {
882 if (!j)
883 return -EINVAL;
884
885 reset_location(j);
886 j->current_location.type = LOCATION_DISCRETE;
887 j->current_location.boot_id = boot_id;
888 j->current_location.monotonic = usec;
889 j->current_location.monotonic_set = true;
890
891 return 0;
892 }
893
894 _public_ int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec) {
895 if (!j)
896 return -EINVAL;
897
898 reset_location(j);
899 j->current_location.type = LOCATION_DISCRETE;
900 j->current_location.realtime = usec;
901 j->current_location.realtime_set = true;
902
903 return 0;
904 }
905
906 _public_ int sd_journal_seek_head(sd_journal *j) {
907 if (!j)
908 return -EINVAL;
909
910 reset_location(j);
911 j->current_location.type = LOCATION_HEAD;
912
913 return 0;
914 }
915
916 _public_ int sd_journal_seek_tail(sd_journal *j) {
917 if (!j)
918 return -EINVAL;
919
920 reset_location(j);
921 j->current_location.type = LOCATION_TAIL;
922
923 return 0;
924 }
925
926 static int add_file(sd_journal *j, const char *prefix, const char *dir, const char *filename) {
927 char *fn;
928 int r;
929 JournalFile *f;
930
931 assert(j);
932 assert(prefix);
933 assert(filename);
934
935 if ((j->flags & SD_JOURNAL_SYSTEM_ONLY) &&
936 !startswith(filename, "system.journal"))
937 return 0;
938
939 if (dir)
940 fn = join(prefix, "/", dir, "/", filename, NULL);
941 else
942 fn = join(prefix, "/", filename, NULL);
943
944 if (!fn)
945 return -ENOMEM;
946
947 if (hashmap_get(j->files, fn)) {
948 free(fn);
949 return 0;
950 }
951
952 if (hashmap_size(j->files) >= JOURNAL_FILES_MAX) {
953 log_debug("Too many open journal files, not adding %s, ignoring.", fn);
954 free(fn);
955 return 0;
956 }
957
958 r = journal_file_open(fn, O_RDONLY, 0, NULL, &f);
959 free(fn);
960
961 if (r < 0) {
962 if (errno == ENOENT)
963 return 0;
964
965 return r;
966 }
967
968 /* journal_file_dump(f); */
969
970 r = hashmap_put(j->files, f->path, f);
971 if (r < 0) {
972 journal_file_close(f);
973 return r;
974 }
975
976 log_debug("File %s got added.", f->path);
977
978 return 0;
979 }
980
981 static int remove_file(sd_journal *j, const char *prefix, const char *dir, const char *filename) {
982 char *fn;
983 JournalFile *f;
984
985 assert(j);
986 assert(prefix);
987 assert(filename);
988
989 if (dir)
990 fn = join(prefix, "/", dir, "/", filename, NULL);
991 else
992 fn = join(prefix, "/", filename, NULL);
993
994 if (!fn)
995 return -ENOMEM;
996
997 f = hashmap_get(j->files, fn);
998 free(fn);
999
1000 if (!f)
1001 return 0;
1002
1003 hashmap_remove(j->files, f->path);
1004 journal_file_close(f);
1005
1006 log_debug("File %s got removed.", f->path);
1007 return 0;
1008 }
1009
1010 static int add_directory(sd_journal *j, const char *prefix, const char *dir) {
1011 char *fn;
1012 int r;
1013 DIR *d;
1014 int wd;
1015 sd_id128_t id, mid;
1016
1017 assert(j);
1018 assert(prefix);
1019 assert(dir);
1020
1021 if ((j->flags & SD_JOURNAL_LOCAL_ONLY) &&
1022 (sd_id128_from_string(dir, &id) < 0 ||
1023 sd_id128_get_machine(&mid) < 0 ||
1024 !sd_id128_equal(id, mid)))
1025 return 0;
1026
1027 fn = join(prefix, "/", dir, NULL);
1028 if (!fn)
1029 return -ENOMEM;
1030
1031 d = opendir(fn);
1032
1033 if (!d) {
1034 free(fn);
1035 if (errno == ENOENT)
1036 return 0;
1037
1038 return -errno;
1039 }
1040
1041 wd = inotify_add_watch(j->inotify_fd, fn,
1042 IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE|
1043 IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT|
1044 IN_DONT_FOLLOW|IN_ONLYDIR);
1045 if (wd > 0) {
1046 if (hashmap_put(j->inotify_wd_dirs, INT_TO_PTR(wd), fn) < 0)
1047 inotify_rm_watch(j->inotify_fd, wd);
1048 else
1049 fn = NULL;
1050 }
1051
1052 free(fn);
1053
1054 for (;;) {
1055 struct dirent buf, *de;
1056
1057 r = readdir_r(d, &buf, &de);
1058 if (r != 0 || !de)
1059 break;
1060
1061 if (!dirent_is_file_with_suffix(de, ".journal"))
1062 continue;
1063
1064 r = add_file(j, prefix, dir, de->d_name);
1065 if (r < 0)
1066 log_debug("Failed to add file %s/%s/%s: %s", prefix, dir, de->d_name, strerror(-r));
1067 }
1068
1069 closedir(d);
1070
1071 log_debug("Directory %s/%s got added.", prefix, dir);
1072
1073 return 0;
1074 }
1075
1076 static void remove_directory_wd(sd_journal *j, int wd) {
1077 char *p;
1078
1079 assert(j);
1080 assert(wd > 0);
1081
1082 if (j->inotify_fd >= 0)
1083 inotify_rm_watch(j->inotify_fd, wd);
1084
1085 p = hashmap_remove(j->inotify_wd_dirs, INT_TO_PTR(wd));
1086
1087 if (p) {
1088 log_debug("Directory %s got removed.", p);
1089 free(p);
1090 }
1091 }
1092
1093 static void add_root_wd(sd_journal *j, const char *p) {
1094 int wd;
1095 char *k;
1096
1097 assert(j);
1098 assert(p);
1099
1100 wd = inotify_add_watch(j->inotify_fd, p,
1101 IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE|
1102 IN_DONT_FOLLOW|IN_ONLYDIR);
1103 if (wd <= 0)
1104 return;
1105
1106 k = strdup(p);
1107 if (!k || hashmap_put(j->inotify_wd_roots, INT_TO_PTR(wd), k) < 0) {
1108 inotify_rm_watch(j->inotify_fd, wd);
1109 free(k);
1110 }
1111 }
1112
1113 static void remove_root_wd(sd_journal *j, int wd) {
1114 char *p;
1115
1116 assert(j);
1117 assert(wd > 0);
1118
1119 if (j->inotify_fd >= 0)
1120 inotify_rm_watch(j->inotify_fd, wd);
1121
1122 p = hashmap_remove(j->inotify_wd_roots, INT_TO_PTR(wd));
1123
1124 if (p) {
1125 log_debug("Root %s got removed.", p);
1126 free(p);
1127 }
1128 }
1129
1130 _public_ int sd_journal_open(sd_journal **ret, int flags) {
1131 sd_journal *j;
1132 const char *p;
1133 const char search_paths[] =
1134 "/run/log/journal\0"
1135 "/var/log/journal\0";
1136 int r;
1137
1138 if (!ret)
1139 return -EINVAL;
1140
1141 if (flags & ~(SD_JOURNAL_LOCAL_ONLY|
1142 SD_JOURNAL_RUNTIME_ONLY|
1143 SD_JOURNAL_SYSTEM_ONLY))
1144 return -EINVAL;
1145
1146 j = new0(sd_journal, 1);
1147 if (!j)
1148 return -ENOMEM;
1149
1150 j->flags = flags;
1151
1152 j->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
1153 if (j->inotify_fd < 0) {
1154 r = -errno;
1155 goto fail;
1156 }
1157
1158 j->files = hashmap_new(string_hash_func, string_compare_func);
1159 if (!j->files) {
1160 r = -ENOMEM;
1161 goto fail;
1162 }
1163
1164 j->inotify_wd_dirs = hashmap_new(trivial_hash_func, trivial_compare_func);
1165 j->inotify_wd_roots = hashmap_new(trivial_hash_func, trivial_compare_func);
1166
1167 if (!j->inotify_wd_dirs || !j->inotify_wd_roots) {
1168 r = -ENOMEM;
1169 goto fail;
1170 }
1171
1172 /* We ignore most errors here, since the idea is to only open
1173 * what's actually accessible, and ignore the rest. */
1174
1175 NULSTR_FOREACH(p, search_paths) {
1176 DIR *d;
1177
1178 if ((flags & SD_JOURNAL_RUNTIME_ONLY) &&
1179 !path_startswith(p, "/run"))
1180 continue;
1181
1182 d = opendir(p);
1183 if (!d) {
1184 if (errno != ENOENT)
1185 log_debug("Failed to open %s: %m", p);
1186 continue;
1187 }
1188
1189 add_root_wd(j, p);
1190
1191 for (;;) {
1192 struct dirent buf, *de;
1193 sd_id128_t id;
1194
1195 r = readdir_r(d, &buf, &de);
1196 if (r != 0 || !de)
1197 break;
1198
1199 if (dirent_is_file_with_suffix(de, ".journal")) {
1200 r = add_file(j, p, NULL, de->d_name);
1201 if (r < 0)
1202 log_debug("Failed to add file %s/%s: %s", p, de->d_name, strerror(-r));
1203
1204 } else if ((de->d_type == DT_DIR || de->d_type == DT_UNKNOWN) &&
1205 sd_id128_from_string(de->d_name, &id) >= 0) {
1206
1207 r = add_directory(j, p, de->d_name);
1208 if (r < 0)
1209 log_debug("Failed to add directory %s/%s: %s", p, de->d_name, strerror(-r));
1210 }
1211 }
1212
1213 closedir(d);
1214 }
1215
1216 *ret = j;
1217 return 0;
1218
1219 fail:
1220 sd_journal_close(j);
1221
1222 return r;
1223 };
1224
1225 _public_ void sd_journal_close(sd_journal *j) {
1226 if (!j)
1227 return;
1228
1229 if (j->inotify_wd_dirs) {
1230 void *k;
1231
1232 while ((k = hashmap_first_key(j->inotify_wd_dirs)))
1233 remove_directory_wd(j, PTR_TO_INT(k));
1234
1235 hashmap_free(j->inotify_wd_dirs);
1236 }
1237
1238 if (j->inotify_wd_roots) {
1239 void *k;
1240
1241 while ((k = hashmap_first_key(j->inotify_wd_roots)))
1242 remove_root_wd(j, PTR_TO_INT(k));
1243
1244 hashmap_free(j->inotify_wd_roots);
1245 }
1246
1247 if (j->files) {
1248 JournalFile *f;
1249
1250 while ((f = hashmap_steal_first(j->files)))
1251 journal_file_close(f);
1252
1253 hashmap_free(j->files);
1254 }
1255
1256 sd_journal_flush_matches(j);
1257
1258 if (j->inotify_fd >= 0)
1259 close_nointr_nofail(j->inotify_fd);
1260
1261 free(j);
1262 }
1263
1264 _public_ int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) {
1265 Object *o;
1266 JournalFile *f;
1267 int r;
1268
1269 if (!j)
1270 return -EINVAL;
1271 if (!ret)
1272 return -EINVAL;
1273
1274 f = j->current_file;
1275 if (!f)
1276 return -EADDRNOTAVAIL;
1277
1278 if (f->current_offset <= 0)
1279 return -EADDRNOTAVAIL;
1280
1281 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1282 if (r < 0)
1283 return r;
1284
1285 *ret = le64toh(o->entry.realtime);
1286 return 0;
1287 }
1288
1289 _public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id128_t *ret_boot_id) {
1290 Object *o;
1291 JournalFile *f;
1292 int r;
1293 sd_id128_t id;
1294
1295 if (!j)
1296 return -EINVAL;
1297 if (!ret)
1298 return -EINVAL;
1299
1300 f = j->current_file;
1301 if (!f)
1302 return -EADDRNOTAVAIL;
1303
1304 if (f->current_offset <= 0)
1305 return -EADDRNOTAVAIL;
1306
1307 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1308 if (r < 0)
1309 return r;
1310
1311 if (ret_boot_id)
1312 *ret_boot_id = o->entry.boot_id;
1313 else {
1314 r = sd_id128_get_boot(&id);
1315 if (r < 0)
1316 return r;
1317
1318 if (!sd_id128_equal(id, o->entry.boot_id))
1319 return -ESTALE;
1320 }
1321
1322 *ret = le64toh(o->entry.monotonic);
1323 return 0;
1324 }
1325
1326 _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *size) {
1327 JournalFile *f;
1328 uint64_t i, n;
1329 size_t field_length;
1330 int r;
1331 Object *o;
1332
1333 if (!j)
1334 return -EINVAL;
1335 if (!field)
1336 return -EINVAL;
1337 if (!data)
1338 return -EINVAL;
1339 if (!size)
1340 return -EINVAL;
1341
1342 if (isempty(field) || strchr(field, '='))
1343 return -EINVAL;
1344
1345 f = j->current_file;
1346 if (!f)
1347 return -EADDRNOTAVAIL;
1348
1349 if (f->current_offset <= 0)
1350 return -EADDRNOTAVAIL;
1351
1352 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1353 if (r < 0)
1354 return r;
1355
1356 field_length = strlen(field);
1357
1358 n = journal_file_entry_n_items(o);
1359 for (i = 0; i < n; i++) {
1360 uint64_t p, l;
1361 le64_t le_hash;
1362 size_t t;
1363
1364 p = le64toh(o->entry.items[i].object_offset);
1365 le_hash = o->entry.items[i].hash;
1366 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
1367 if (r < 0)
1368 return r;
1369
1370 if (le_hash != o->data.hash)
1371 return -EBADMSG;
1372
1373 l = le64toh(o->object.size) - offsetof(Object, data.payload);
1374
1375 if (o->object.flags & OBJECT_COMPRESSED) {
1376
1377 #ifdef HAVE_XZ
1378 if (uncompress_startswith(o->data.payload, l,
1379 &f->compress_buffer, &f->compress_buffer_size,
1380 field, field_length, '=')) {
1381
1382 uint64_t rsize;
1383
1384 if (!uncompress_blob(o->data.payload, l,
1385 &f->compress_buffer, &f->compress_buffer_size, &rsize))
1386 return -EBADMSG;
1387
1388 *data = f->compress_buffer;
1389 *size = (size_t) rsize;
1390
1391 return 0;
1392 }
1393 #else
1394 return -EPROTONOSUPPORT;
1395 #endif
1396
1397 } else if (l >= field_length+1 &&
1398 memcmp(o->data.payload, field, field_length) == 0 &&
1399 o->data.payload[field_length] == '=') {
1400
1401 t = (size_t) l;
1402
1403 if ((uint64_t) t != l)
1404 return -E2BIG;
1405
1406 *data = o->data.payload;
1407 *size = t;
1408
1409 return 0;
1410 }
1411
1412 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1413 if (r < 0)
1414 return r;
1415 }
1416
1417 return -ENOENT;
1418 }
1419
1420 _public_ int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *size) {
1421 JournalFile *f;
1422 uint64_t p, l, n;
1423 le64_t le_hash;
1424 int r;
1425 Object *o;
1426 size_t t;
1427
1428 if (!j)
1429 return -EINVAL;
1430 if (!data)
1431 return -EINVAL;
1432 if (!size)
1433 return -EINVAL;
1434
1435 f = j->current_file;
1436 if (!f)
1437 return -EADDRNOTAVAIL;
1438
1439 if (f->current_offset <= 0)
1440 return -EADDRNOTAVAIL;
1441
1442 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1443 if (r < 0)
1444 return r;
1445
1446 n = journal_file_entry_n_items(o);
1447 if (j->current_field >= n)
1448 return 0;
1449
1450 p = le64toh(o->entry.items[j->current_field].object_offset);
1451 le_hash = o->entry.items[j->current_field].hash;
1452 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
1453 if (r < 0)
1454 return r;
1455
1456 if (le_hash != o->data.hash)
1457 return -EBADMSG;
1458
1459 l = le64toh(o->object.size) - offsetof(Object, data.payload);
1460 t = (size_t) l;
1461
1462 /* We can't read objects larger than 4G on a 32bit machine */
1463 if ((uint64_t) t != l)
1464 return -E2BIG;
1465
1466 if (o->object.flags & OBJECT_COMPRESSED) {
1467 #ifdef HAVE_XZ
1468 uint64_t rsize;
1469
1470 if (!uncompress_blob(o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize))
1471 return -EBADMSG;
1472
1473 *data = f->compress_buffer;
1474 *size = (size_t) rsize;
1475 #else
1476 return -EPROTONOSUPPORT;
1477 #endif
1478 } else {
1479 *data = o->data.payload;
1480 *size = t;
1481 }
1482
1483 j->current_field ++;
1484
1485 return 1;
1486 }
1487
1488 _public_ void sd_journal_restart_data(sd_journal *j) {
1489 if (!j)
1490 return;
1491
1492 j->current_field = 0;
1493 }
1494
1495 _public_ int sd_journal_get_fd(sd_journal *j) {
1496 if (!j)
1497 return -EINVAL;
1498
1499 return j->inotify_fd;
1500 }
1501
1502 static void process_inotify_event(sd_journal *j, struct inotify_event *e) {
1503 char *p;
1504 int r;
1505
1506 assert(j);
1507 assert(e);
1508
1509 /* Is this a subdirectory we watch? */
1510 p = hashmap_get(j->inotify_wd_dirs, INT_TO_PTR(e->wd));
1511 if (p) {
1512
1513 if (!(e->mask & IN_ISDIR) && e->len > 0 && endswith(e->name, ".journal")) {
1514
1515 /* Event for a journal file */
1516
1517 if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
1518 r = add_file(j, p, NULL, e->name);
1519 if (r < 0)
1520 log_debug("Failed to add file %s/%s: %s", p, e->name, strerror(-r));
1521 } else if (e->mask & (IN_DELETE|IN_UNMOUNT)) {
1522
1523 r = remove_file(j, p, NULL, e->name);
1524 if (r < 0)
1525 log_debug("Failed to remove file %s/%s: %s", p, e->name, strerror(-r));
1526 }
1527
1528 } else if (e->len == 0) {
1529
1530 /* Event for the directory itself */
1531
1532 if (e->mask & (IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT))
1533 remove_directory_wd(j, e->wd);
1534 }
1535
1536 return;
1537 }
1538
1539 /* Must be the root directory then? */
1540 p = hashmap_get(j->inotify_wd_roots, INT_TO_PTR(e->wd));
1541 if (p) {
1542 sd_id128_t id;
1543
1544 if (!(e->mask & IN_ISDIR) && e->len > 0 && endswith(e->name, ".journal")) {
1545
1546 /* Event for a journal file */
1547
1548 if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
1549 r = add_file(j, p, NULL, e->name);
1550 if (r < 0)
1551 log_debug("Failed to add file %s/%s: %s", p, e->name, strerror(-r));
1552 } else if (e->mask & (IN_DELETE|IN_UNMOUNT)) {
1553
1554 r = remove_file(j, p, NULL, e->name);
1555 if (r < 0)
1556 log_debug("Failed to remove file %s/%s: %s", p, e->name, strerror(-r));
1557 }
1558
1559 } else if ((e->mask & IN_ISDIR) && e->len > 0 && sd_id128_from_string(e->name, &id) >= 0) {
1560
1561 /* Event for subdirectory */
1562
1563 if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
1564
1565 r = add_directory(j, p, e->name);
1566 if (r < 0)
1567 log_debug("Failed to add directory %s/%s: %s", p, e->name, strerror(-r));
1568 }
1569 }
1570
1571 return;
1572 }
1573
1574 if (e->mask & IN_IGNORED)
1575 return;
1576
1577 log_warning("Unknown inotify event.");
1578 }
1579
1580 _public_ int sd_journal_process(sd_journal *j) {
1581 uint8_t buffer[sizeof(struct inotify_event) + FILENAME_MAX];
1582
1583 if (!j)
1584 return -EINVAL;
1585
1586 for (;;) {
1587 struct inotify_event *e;
1588 ssize_t l;
1589
1590 l = read(j->inotify_fd, buffer, sizeof(buffer));
1591 if (l < 0) {
1592 if (errno == EINTR || errno == EAGAIN)
1593 return 0;
1594
1595 return -errno;
1596 }
1597
1598 e = (struct inotify_event*) buffer;
1599 while (l > 0) {
1600 size_t step;
1601
1602 process_inotify_event(j, e);
1603
1604 step = sizeof(struct inotify_event) + e->len;
1605 assert(step <= (size_t) l);
1606
1607 e = (struct inotify_event*) ((uint8_t*) e + step);
1608 l -= step;
1609 }
1610 }
1611 }
1612
1613 /* _public_ int sd_journal_query_unique(sd_journal *j, const char *field) { */
1614 /* if (!j) */
1615 /* return -EINVAL; */
1616 /* if (!field) */
1617 /* return -EINVAL; */
1618
1619 /* return -ENOTSUP; */
1620 /* } */
1621
1622 /* _public_ int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_t *l) { */
1623 /* if (!j) */
1624 /* return -EINVAL; */
1625 /* if (!data) */
1626 /* return -EINVAL; */
1627 /* if (!l) */
1628 /* return -EINVAL; */
1629
1630 /* return -ENOTSUP; */
1631 /* } */
1632
1633 /* _public_ void sd_journal_restart_unique(sd_journal *j) { */
1634 /* if (!j) */
1635 /* return; */
1636 /* } */