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