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