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