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