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