]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/sd-journal.c
macro: fix ALIGN_TO macro definition
[thirdparty/systemd.git] / src / journal / sd-journal.c
CommitLineData
87d2c1ff
LP
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
87d2c1ff 22#include <errno.h>
87d2c1ff 23#include <fcntl.h>
3fbf9cbb 24#include <stddef.h>
87d2c1ff
LP
25
26#include "sd-journal.h"
27#include "journal-def.h"
cec736d2 28#include "journal-file.h"
260a2be4 29#include "hashmap.h"
cec736d2 30#include "list.h"
de7b95cd 31#include "lookup3.h"
87d2c1ff 32
cec736d2 33typedef struct Match Match;
87d2c1ff 34
cec736d2
LP
35struct Match {
36 char *data;
37 size_t size;
de7b95cd 38 uint64_t le_hash;
87d2c1ff 39
cec736d2
LP
40 LIST_FIELDS(Match, matches);
41};
87d2c1ff 42
87d2c1ff 43struct sd_journal {
260a2be4 44 Hashmap *files;
87d2c1ff 45
cec736d2 46 JournalFile *current_file;
7210bfb3 47 uint64_t current_field;
87d2c1ff 48
cec736d2 49 LIST_HEAD(Match, matches);
de7b95cd 50 unsigned n_matches;
cec736d2 51};
87d2c1ff 52
8f9b6cd9
LP
53static void reset_location(sd_journal *j) {
54 Iterator i;
55 JournalFile *f;
56
57 assert(j);
58
59 j->current_file = NULL;
60 j->current_field = 0;
61
62 HASHMAP_FOREACH(f, j->files, i)
63 f->current_offset = 0;
64}
65
1cc101f1 66int sd_journal_add_match(sd_journal *j, const void *data, size_t size) {
cec736d2 67 Match *m;
87d2c1ff 68
cec736d2 69 assert(j);
1cc101f1
LP
70
71 if (size <= 0)
72 return -EINVAL;
73
74 assert(data);
87d2c1ff 75
cec736d2
LP
76 m = new0(Match, 1);
77 if (!m)
78 return -ENOMEM;
87d2c1ff 79
1cc101f1
LP
80 m->size = size;
81
cec736d2
LP
82 m->data = malloc(m->size);
83 if (!m->data) {
84 free(m);
85 return -ENOMEM;
87d2c1ff
LP
86 }
87
1cc101f1 88 memcpy(m->data, data, size);
de7b95cd 89 m->le_hash = hash64(m->data, size);
87d2c1ff 90
cec736d2 91 LIST_PREPEND(Match, matches, j->matches, m);
de7b95cd
LP
92 j->n_matches ++;
93
8f9b6cd9
LP
94 reset_location(j);
95
87d2c1ff
LP
96 return 0;
97}
98
cec736d2
LP
99void sd_journal_flush_matches(sd_journal *j) {
100 assert(j);
87d2c1ff 101
cec736d2
LP
102 while (j->matches) {
103 Match *m = j->matches;
87d2c1ff 104
cec736d2
LP
105 LIST_REMOVE(Match, matches, j->matches, m);
106 free(m->data);
107 free(m);
87d2c1ff 108 }
de7b95cd
LP
109
110 j->n_matches = 0;
8f9b6cd9
LP
111
112 reset_location(j);
87d2c1ff
LP
113}
114
cec736d2 115static int compare_order(JournalFile *af, Object *ao, uint64_t ap,
ae2cc8ef 116 JournalFile *bf, Object *bo, uint64_t bp) {
87d2c1ff 117
cec736d2 118 uint64_t a, b;
87d2c1ff 119
ae2cc8ef 120 /* We operate on two different files here, hence we can access
1cc101f1
LP
121 * two objects at the same time, which we normally can't.
122 *
123 * If contents and timestamps match, these entries are
124 * identical, even if the seqnum does not match */
125
126 if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id) &&
127 ao->entry.monotonic == bo->entry.monotonic &&
128 ao->entry.realtime == bo->entry.realtime &&
129 ao->entry.xor_hash == bo->entry.xor_hash)
130 return 0;
ae2cc8ef 131
cec736d2 132 if (sd_id128_equal(af->header->seqnum_id, bf->header->seqnum_id)) {
87d2c1ff 133
cec736d2
LP
134 /* If this is from the same seqnum source, compare
135 * seqnums */
136 a = le64toh(ao->entry.seqnum);
137 b = le64toh(bo->entry.seqnum);
87d2c1ff 138
ae2cc8ef
LP
139 if (a < b)
140 return -1;
141 if (a > b)
142 return 1;
1cc101f1
LP
143
144 /* Wow! This is weird, different data but the same
145 * seqnums? Something is borked, but let's make the
146 * best of it and compare by time. */
ae2cc8ef 147 }
87d2c1ff 148
ae2cc8ef 149 if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id)) {
87d2c1ff 150
cec736d2
LP
151 /* If the boot id matches compare monotonic time */
152 a = le64toh(ao->entry.monotonic);
153 b = le64toh(bo->entry.monotonic);
87d2c1ff 154
ae2cc8ef
LP
155 if (a < b)
156 return -1;
157 if (a > b)
158 return 1;
87d2c1ff
LP
159 }
160
ae2cc8ef
LP
161 /* Otherwise compare UTC time */
162 a = le64toh(ao->entry.realtime);
163 b = le64toh(ao->entry.realtime);
164
165 if (a < b)
166 return -1;
167 if (a > b)
168 return 1;
169
170 /* Finally, compare by contents */
171 a = le64toh(ao->entry.xor_hash);
172 b = le64toh(ao->entry.xor_hash);
173
174 if (a < b)
175 return -1;
176 if (a > b)
177 return 1;
178
179 return 0;
87d2c1ff
LP
180}
181
e892bd17 182static int move_to_next_with_matches(sd_journal *j, JournalFile *f, direction_t direction, Object **o, uint64_t *p) {
de7b95cd
LP
183 int r;
184 uint64_t cp;
185 Object *c;
186
187 assert(j);
188 assert(f);
189 assert(o);
190 assert(p);
191
192 if (!j->matches) {
193 /* No matches is easy, just go on to the next entry */
194
195 if (f->current_offset > 0) {
196 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &c);
197 if (r < 0)
198 return r;
199 } else
200 c = NULL;
201
e892bd17 202 return journal_file_next_entry(f, c, direction, o, p);
de7b95cd
LP
203 }
204
205 /* So there are matches we have to adhere to, let's find the
206 * first entry that matches all of them */
207
208 if (f->current_offset > 0)
209 cp = f->current_offset;
210 else {
e892bd17 211 r = journal_file_find_first_entry(f, j->matches->data, j->matches->size, direction, &c, &cp);
de7b95cd
LP
212 if (r <= 0)
213 return r;
214
215 /* We can shortcut this if there's only one match */
216 if (j->n_matches == 1) {
217 *o = c;
218 *p = cp;
219 return r;
220 }
221 }
222
223 for (;;) {
224 uint64_t np, n;
225 bool found;
226 Match *m;
227
228 r = journal_file_move_to_object(f, cp, OBJECT_ENTRY, &c);
229 if (r < 0)
230 return r;
231
232 n = journal_file_entry_n_items(c);
233
234 /* Make sure we don't match the entry we are starting
235 * from. */
236 found = f->current_offset != cp;
237
238 np = 0;
239 LIST_FOREACH(matches, m, j->matches) {
240 uint64_t q, k;
241
242 for (k = 0; k < n; k++)
243 if (c->entry.items[k].hash == m->le_hash)
244 break;
245
246 if (k >= n) {
247 /* Hmm, didn't find any field that matched, so ignore
248 * this match. Go on with next match */
249
250 found = false;
251 continue;
252 }
253
254 /* Hmm, so, this field matched, let's remember
255 * where we'd have to try next, in case the other
256 * matches are not OK */
e892bd17
LP
257
258 if (direction == DIRECTION_DOWN) {
259 q = le64toh(c->entry.items[k].next_entry_offset);
260
261 if (q > np)
262 np = q;
263 } else {
264 q = le64toh(c->entry.items[k].prev_entry_offset);
265
266 if (q != 0 && (np == 0 || q < np))
267 np = q;
268 }
de7b95cd
LP
269 }
270
271 /* Did this entry match against all matches? */
272 if (found) {
273 *o = c;
274 *p = cp;
275 return 1;
276 }
277
278 /* Did we find a subsequent entry? */
279 if (np == 0)
280 return 0;
281
282 /* Hmm, ok, this entry only matched partially, so
283 * let's try another one */
284 cp = np;
285 }
286}
287
e892bd17 288static int real_journal_next(sd_journal *j, direction_t direction) {
cec736d2
LP
289 JournalFile *f, *new_current = NULL;
290 Iterator i;
87d2c1ff 291 int r;
cec736d2
LP
292 uint64_t new_offset = 0;
293 Object *new_entry = NULL;
87d2c1ff 294
cec736d2 295 assert(j);
87d2c1ff 296
cec736d2
LP
297 HASHMAP_FOREACH(f, j->files, i) {
298 Object *o;
299 uint64_t p;
87d2c1ff 300
e892bd17 301 r = move_to_next_with_matches(j, f, direction, &o, &p);
87d2c1ff
LP
302 if (r < 0)
303 return r;
cec736d2
LP
304 else if (r == 0)
305 continue;
87d2c1ff 306
ae2cc8ef
LP
307 if (!new_current ||
308 compare_order(new_current, new_entry, new_offset, f, o, p) > 0) {
cec736d2
LP
309 new_current = f;
310 new_entry = o;
311 new_offset = p;
87d2c1ff 312 }
87d2c1ff
LP
313 }
314
cec736d2
LP
315 if (new_current) {
316 j->current_file = new_current;
3fbf9cbb 317 j->current_file->current_offset = new_offset;
7210bfb3 318 j->current_field = 0;
ae2cc8ef
LP
319
320 /* Skip over any identical entries in the other files too */
321
322 HASHMAP_FOREACH(f, j->files, i) {
323 Object *o;
324 uint64_t p;
325
326 if (j->current_file == f)
327 continue;
328
e892bd17 329 r = move_to_next_with_matches(j, f, direction, &o, &p);
ae2cc8ef
LP
330 if (r < 0)
331 return r;
332 else if (r == 0)
333 continue;
334
7210bfb3 335 if (compare_order(new_current, new_entry, new_offset, f, o, p) == 0)
ae2cc8ef 336 f->current_offset = p;
ae2cc8ef
LP
337 }
338
cec736d2 339 return 1;
87d2c1ff
LP
340 }
341
87d2c1ff
LP
342 return 0;
343}
344
e892bd17
LP
345int sd_journal_next(sd_journal *j) {
346 return real_journal_next(j, DIRECTION_DOWN);
347}
87d2c1ff 348
e892bd17
LP
349int sd_journal_previous(sd_journal *j) {
350 return real_journal_next(j, DIRECTION_UP);
87d2c1ff
LP
351}
352
3fbf9cbb 353int sd_journal_get_cursor(sd_journal *j, char **cursor) {
cec736d2 354 Object *o;
87d2c1ff 355 int r;
3fbf9cbb 356 char bid[33], sid[33];
87d2c1ff 357
cec736d2
LP
358 assert(j);
359 assert(cursor);
87d2c1ff 360
3fbf9cbb
LP
361 if (!j->current_file || j->current_file->current_offset <= 0)
362 return -EADDRNOTAVAIL;
87d2c1ff 363
cec736d2 364 r = journal_file_move_to_object(j->current_file, j->current_file->current_offset, OBJECT_ENTRY, &o);
87d2c1ff
LP
365 if (r < 0)
366 return r;
367
3fbf9cbb
LP
368 sd_id128_to_string(j->current_file->header->seqnum_id, sid);
369 sd_id128_to_string(o->entry.boot_id, bid);
87d2c1ff 370
3fbf9cbb
LP
371 if (asprintf(cursor,
372 "s=%s;i=%llx;b=%s;m=%llx;t=%llx;x=%llx;p=%s",
373 sid, (unsigned long long) le64toh(o->entry.seqnum),
374 bid, (unsigned long long) le64toh(o->entry.monotonic),
375 (unsigned long long) le64toh(o->entry.realtime),
376 (unsigned long long) le64toh(o->entry.xor_hash),
377 file_name_from_path(j->current_file->path)) < 0)
378 return -ENOMEM;
87d2c1ff
LP
379
380 return 1;
381}
382
3fbf9cbb 383int sd_journal_set_cursor(sd_journal *j, const char *cursor) {
cec736d2 384 return -EINVAL;
87d2c1ff
LP
385}
386
3fbf9cbb
LP
387static int add_file(sd_journal *j, const char *prefix, const char *dir, const char *filename) {
388 char *fn;
389 int r;
390 JournalFile *f;
391
392 assert(j);
393 assert(prefix);
394 assert(filename);
395
396 if (dir)
397 fn = join(prefix, "/", dir, "/", filename, NULL);
398 else
399 fn = join(prefix, "/", filename, NULL);
400
401 if (!fn)
402 return -ENOMEM;
403
404 r = journal_file_open(fn, O_RDONLY, 0, NULL, &f);
405 free(fn);
406
407 if (r < 0) {
408 if (errno == ENOENT)
409 return 0;
410
411 return r;
412 }
413
414 r = hashmap_put(j->files, f->path, f);
415 if (r < 0) {
416 journal_file_close(f);
417 return r;
418 }
419
420 return 0;
421}
422
423static int add_directory(sd_journal *j, const char *prefix, const char *dir) {
424 char *fn;
425 int r;
426 DIR *d;
427
428 assert(j);
429 assert(prefix);
430 assert(dir);
431
432 fn = join(prefix, "/", dir, NULL);
433 if (!fn)
434 return -ENOMEM;
435
436 d = opendir(fn);
437 free(fn);
438
439 if (!d) {
440 if (errno == ENOENT)
441 return 0;
442
443 return -errno;
444 }
445
446 for (;;) {
447 struct dirent buf, *de;
448
449 r = readdir_r(d, &buf, &de);
450 if (r != 0 || !de)
451 break;
452
453 if (!dirent_is_file_with_suffix(de, ".journal"))
454 continue;
455
456 r = add_file(j, prefix, dir, de->d_name);
457 if (r < 0)
458 log_debug("Failed to add file %s/%s/%s: %s", prefix, dir, de->d_name, strerror(-r));
459 }
460
461 closedir(d);
462
463 return 0;
464}
465
87d2c1ff
LP
466int sd_journal_open(sd_journal **ret) {
467 sd_journal *j;
87d2c1ff 468 const char *p;
87d2c1ff
LP
469 const char search_paths[] =
470 "/run/log/journal\0"
471 "/var/log/journal\0";
3fbf9cbb 472 int r;
87d2c1ff
LP
473
474 assert(ret);
475
476 j = new0(sd_journal, 1);
477 if (!j)
478 return -ENOMEM;
479
260a2be4 480 j->files = hashmap_new(string_hash_func, string_compare_func);
3fbf9cbb
LP
481 if (!j->files) {
482 r = -ENOMEM;
260a2be4 483 goto fail;
3fbf9cbb
LP
484 }
485
486 /* We ignore most errors here, since the idea is to only open
487 * what's actually accessible, and ignore the rest. */
260a2be4 488
87d2c1ff
LP
489 NULSTR_FOREACH(p, search_paths) {
490 DIR *d;
491
492 d = opendir(p);
493 if (!d) {
3fbf9cbb
LP
494 if (errno != ENOENT)
495 log_debug("Failed to open %s: %m", p);
87d2c1ff
LP
496 continue;
497 }
498
499 for (;;) {
500 struct dirent buf, *de;
3fbf9cbb 501 sd_id128_t id;
87d2c1ff 502
3fbf9cbb
LP
503 r = readdir_r(d, &buf, &de);
504 if (r != 0 || !de)
87d2c1ff
LP
505 break;
506
3fbf9cbb
LP
507 if (dirent_is_file_with_suffix(de, ".journal")) {
508 r = add_file(j, p, NULL, de->d_name);
509 if (r < 0)
510 log_debug("Failed to add file %s/%s: %s", p, de->d_name, strerror(-r));
87d2c1ff 511
3fbf9cbb
LP
512 } else if ((de->d_type == DT_DIR || de->d_type == DT_UNKNOWN) &&
513 sd_id128_from_string(de->d_name, &id) >= 0) {
87d2c1ff 514
3fbf9cbb
LP
515 r = add_directory(j, p, de->d_name);
516 if (r < 0)
517 log_debug("Failed to add directory %s/%s: %s", p, de->d_name, strerror(-r));
260a2be4
LP
518 }
519 }
3fbf9cbb
LP
520
521 closedir(d);
87d2c1ff
LP
522 }
523
524 *ret = j;
525 return 0;
526
527fail:
528 sd_journal_close(j);
529
530 return r;
531};
532
533void sd_journal_close(sd_journal *j) {
534 assert(j);
535
260a2be4
LP
536 if (j->files) {
537 JournalFile *f;
538
539 while ((f = hashmap_steal_first(j->files)))
540 journal_file_close(f);
541
542 hashmap_free(j->files);
543 }
87d2c1ff 544
1cc101f1
LP
545 sd_journal_flush_matches(j);
546
87d2c1ff
LP
547 free(j);
548}
3fbf9cbb
LP
549
550int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) {
551 Object *o;
552 JournalFile *f;
553 int r;
554
555 assert(j);
556 assert(ret);
557
558 f = j->current_file;
559 if (!f)
560 return 0;
561
562 if (f->current_offset <= 0)
563 return 0;
564
565 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
566 if (r < 0)
567 return r;
568
569 *ret = le64toh(o->entry.realtime);
570 return 1;
571}
572
573int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret) {
574 Object *o;
575 JournalFile *f;
576 int r;
577 sd_id128_t id;
578
579 assert(j);
580 assert(ret);
581
582 f = j->current_file;
583 if (!f)
584 return 0;
585
586 if (f->current_offset <= 0)
587 return 0;
588
de7b95cd 589 r = sd_id128_get_boot(&id);
3fbf9cbb
LP
590 if (r < 0)
591 return r;
592
593 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
594 if (r < 0)
595 return r;
596
597 if (!sd_id128_equal(id, o->entry.boot_id))
598 return 0;
599
600 *ret = le64toh(o->entry.monotonic);
601 return 1;
602
603}
604
8725d60a 605int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *size) {
3fbf9cbb
LP
606 JournalFile *f;
607 uint64_t i, n;
608 size_t field_length;
609 int r;
610 Object *o;
611
612 assert(j);
613 assert(field);
614 assert(data);
615 assert(size);
616
617 if (isempty(field) || strchr(field, '='))
618 return -EINVAL;
619
620 f = j->current_file;
621 if (!f)
622 return 0;
623
624 if (f->current_offset <= 0)
625 return 0;
626
627 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
628 if (r < 0)
629 return r;
630
631 field_length = strlen(field);
632
633 n = journal_file_entry_n_items(o);
634 for (i = 0; i < n; i++) {
de7b95cd 635 uint64_t p, l, h;
3fbf9cbb
LP
636 size_t t;
637
638 p = le64toh(o->entry.items[i].object_offset);
de7b95cd 639 h = o->entry.items[j->current_field].hash;
3fbf9cbb
LP
640 r = journal_file_move_to_object(f, p, OBJECT_DATA, &o);
641 if (r < 0)
642 return r;
643
de7b95cd
LP
644 if (h != o->data.hash)
645 return -EBADMSG;
646
3fbf9cbb
LP
647 l = le64toh(o->object.size) - offsetof(Object, data.payload);
648
161e54f8
LP
649 if (l >= field_length+1 &&
650 memcmp(o->data.payload, field, field_length) == 0 &&
651 o->data.payload[field_length] == '=') {
3fbf9cbb 652
161e54f8 653 t = (size_t) l;
3fbf9cbb 654
161e54f8
LP
655 if ((uint64_t) t != l)
656 return -E2BIG;
3fbf9cbb 657
161e54f8
LP
658 *data = o->data.payload;
659 *size = t;
3fbf9cbb 660
161e54f8
LP
661 return 1;
662 }
3fbf9cbb 663
161e54f8
LP
664 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
665 if (r < 0)
666 return r;
3fbf9cbb
LP
667 }
668
669 return 0;
670}
671
8725d60a 672int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *size) {
3fbf9cbb 673 JournalFile *f;
de7b95cd 674 uint64_t p, l, n, h;
3fbf9cbb
LP
675 size_t t;
676 int r;
677 Object *o;
678
679 assert(j);
680 assert(data);
681 assert(size);
682
683 f = j->current_file;
684 if (!f)
685 return 0;
686
687 if (f->current_offset <= 0)
688 return 0;
689
690 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
691 if (r < 0)
692 return r;
693
694 n = journal_file_entry_n_items(o);
7210bfb3 695 if (j->current_field >= n)
3fbf9cbb
LP
696 return 0;
697
7210bfb3 698 p = le64toh(o->entry.items[j->current_field].object_offset);
de7b95cd 699 h = o->entry.items[j->current_field].hash;
3fbf9cbb
LP
700 r = journal_file_move_to_object(f, p, OBJECT_DATA, &o);
701 if (r < 0)
702 return r;
703
de7b95cd
LP
704 if (h != o->data.hash)
705 return -EBADMSG;
706
3fbf9cbb
LP
707 l = le64toh(o->object.size) - offsetof(Object, data.payload);
708 t = (size_t) l;
709
710 /* We can't read objects larger than 4G on a 32bit machine */
711 if ((uint64_t) t != l)
712 return -E2BIG;
713
714 *data = o->data.payload;
715 *size = t;
716
7210bfb3 717 j->current_field ++;
3fbf9cbb
LP
718
719 return 1;
720}
c2373f84 721
8725d60a 722void sd_journal_start_data(sd_journal *j) {
c2373f84 723 assert(j);
8725d60a
LP
724
725 j->current_field = 0;
c2373f84
LP
726}
727
8f9b6cd9 728int sd_journal_seek_head(sd_journal *j) {
c2373f84 729 assert(j);
8725d60a 730
8f9b6cd9 731 reset_location(j);
8725d60a 732
8f9b6cd9 733 return real_journal_next(j, DIRECTION_DOWN);
8725d60a
LP
734}
735
736int sd_journal_seek_tail(sd_journal *j) {
8f9b6cd9
LP
737 assert(j);
738
739 reset_location(j);
740
741 return real_journal_next(j, DIRECTION_UP);
c2373f84 742}