]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/libsystemd/sd-journal/journal-file.c
macro: introduce u64_multiply_safe() to avoid overflow
[thirdparty/systemd.git] / src / libsystemd / sd-journal / journal-file.c
index 45971d3944dbdd97c43258566b4f5ead5b7ddada..d138e3809638d84cd951d5d372808c184ada0278 100644 (file)
 #define DEFAULT_COMPRESS_THRESHOLD (512ULL)
 #define MIN_COMPRESS_THRESHOLD (8ULL)
 
+#define U64_KB UINT64_C(1024)
+#define U64_MB (UINT64_C(1024) * U64_KB)
+#define U64_GB (UINT64_C(1024) * U64_MB)
+
 /* This is the minimum journal file size */
-#define JOURNAL_FILE_SIZE_MIN (512 * 1024ULL)             /* 512 KiB */
-#define JOURNAL_COMPACT_SIZE_MAX UINT32_MAX               /* 4 GiB */
+#define JOURNAL_FILE_SIZE_MIN (512 * U64_KB)             /* 512 KiB */
+#define JOURNAL_COMPACT_SIZE_MAX ((uint64_t) UINT32_MAX) /* 4 GiB */
 
-/* These are the lower and upper bounds if we deduce the max_use value
- * from the file system size */
-#define MAX_USE_LOWER (1 * 1024 * 1024ULL)                /* 1 MiB */
-#define MAX_USE_UPPER (4 * 1024 * 1024 * 1024ULL)         /* 4 GiB */
+/* These are the lower and upper bounds if we deduce the max_use value from the file system size */
+#define MAX_USE_LOWER (1 * U64_MB)                       /* 1 MiB */
+#define MAX_USE_UPPER (4 * U64_GB)                       /* 4 GiB */
 
 /* Those are the lower and upper bounds for the minimal use limit,
  * i.e. how much we'll use even if keep_free suggests otherwise. */
-#define MIN_USE_LOW (1 * 1024 * 1024ULL)                  /* 1 MiB */
-#define MIN_USE_HIGH (16 * 1024 * 1024ULL)                /* 16 MiB */
+#define MIN_USE_LOW  (1  * U64_MB)                       /* 1 MiB */
+#define MIN_USE_HIGH (16 * U64_MB)                       /* 16 MiB */
 
 /* This is the upper bound if we deduce max_size from max_use */
-#define MAX_SIZE_UPPER (128 * 1024 * 1024ULL)             /* 128 MiB */
+#define MAX_SIZE_UPPER (128 * U64_MB)                    /* 128 MiB */
 
-/* This is the upper bound if we deduce the keep_free value from the
- * file system size */
-#define KEEP_FREE_UPPER (4 * 1024 * 1024 * 1024ULL)       /* 4 GiB */
+/* This is the upper bound if we deduce the keep_free value from the file system size */
+#define KEEP_FREE_UPPER (4 * U64_GB)                     /* 4 GiB */
 
-/* This is the keep_free value when we can't determine the system
- * size */
-#define DEFAULT_KEEP_FREE (1024 * 1024ULL)                /* 1 MB */
+/* This is the keep_free value when we can't determine the system size */
+#define DEFAULT_KEEP_FREE (1 * U64_MB)                   /* 1 MB */
 
 /* This is the default maximum number of journal files to keep around. */
 #define DEFAULT_N_MAX_FILES 100
@@ -82,7 +83,7 @@
 #define CHAIN_CACHE_MAX 20
 
 /* How much to increase the journal file size at once each time we allocate something new. */
-#define FILE_SIZE_INCREASE (8 * 1024 * 1024ULL)          /* 8MB */
+#define FILE_SIZE_INCREASE (8 * U64_MB)                  /* 8MB */
 
 /* Reread fstat() of the file for detecting deletions at least this often */
 #define LAST_STAT_REFRESH_USEC (5*USEC_PER_SEC)
@@ -750,7 +751,7 @@ static int journal_file_allocate(JournalFile *f, uint64_t offset, uint64_t size)
         /* We assume that this file is not sparse, and we know that for sure, since we always call
          * posix_fallocate() ourselves */
 
-        if (size > PAGE_ALIGN_DOWN(UINT64_MAX) - offset)
+        if (size > PAGE_ALIGN_DOWN_U64(UINT64_MAX) - offset)
                 return -EINVAL;
 
         if (mmap_cache_fd_got_sigbus(f->cache_fd))
@@ -758,12 +759,12 @@ static int journal_file_allocate(JournalFile *f, uint64_t offset, uint64_t size)
 
         old_header_size = le64toh(READ_NOW(f->header->header_size));
         old_arena_size = le64toh(READ_NOW(f->header->arena_size));
-        if (old_arena_size > PAGE_ALIGN_DOWN(UINT64_MAX) - old_header_size)
+        if (old_arena_size > PAGE_ALIGN_DOWN_U64(UINT64_MAX) - old_header_size)
                 return -EBADMSG;
 
         old_size = old_header_size + old_arena_size;
 
-        new_size = MAX(PAGE_ALIGN(offset + size), old_header_size);
+        new_size = MAX(PAGE_ALIGN_U64(offset + size), old_header_size);
 
         if (new_size <= old_size) {
 
@@ -794,7 +795,7 @@ static int journal_file_allocate(JournalFile *f, uint64_t offset, uint64_t size)
                 if (fstatvfs(f->fd, &svfs) >= 0) {
                         uint64_t available;
 
-                        available = LESS_BY((uint64_t) svfs.f_bfree * (uint64_t) svfs.f_bsize, f->metrics.keep_free);
+                        available = LESS_BY(u64_multiply_safe(svfs.f_bfree, svfs.f_bsize), f->metrics.keep_free);
 
                         if (new_size - old_size > available)
                                 return -E2BIG;
@@ -3104,11 +3105,9 @@ static int generic_array_bisect_plus_one(
                 int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle),
                 direction_t direction,
                 Object **ret_object,
-                uint64_t *ret_offset,
-                uint64_t *ret_idx) {
+                uint64_t *ret_offset) {
 
         int r;
-        bool step_back = false;
 
         assert(f);
         assert(test_object);
@@ -3116,41 +3115,52 @@ static int generic_array_bisect_plus_one(
         if (n <= 0)
                 return 0;
 
-        /* This bisects the array in object 'first', but first checks
-         * an extra  */
+        /* This bisects the array in object 'first', but first checks an extra. */
         r = test_object(f, extra, needle);
         if (r < 0)
                 return r;
 
-        if (r == TEST_FOUND)
-                r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
-
-        /* if we are looking with DIRECTION_UP then we need to first
-           see if in the actual array there is a matching entry, and
-           return the last one of that. But if there isn't any we need
-           to return this one. Hence remember this, and return it
-           below. */
-        if (r == TEST_LEFT)
-                step_back = direction == DIRECTION_UP;
+        if (direction == DIRECTION_DOWN) {
+                /* If we are going downwards, then we need to return the first object that passes the test.
+                 * When there is no object that passes the test, we need to return the first object that
+                 * test_object() returns TEST_RIGHT for. */
+                if (IN_SET(r,
+                           TEST_FOUND,  /* The 'extra' object passes the test. Hence, this is the first
+                                         * object that passes the test. */
+                           TEST_RIGHT)) /* The 'extra' object is the first object that test_object() returns
+                                         * TEST_RIGHT for, and no object exists even in the chained arrays
+                                         * that passes the test. */
+                        goto use_extra; /* The 'extra' object is exactly the one we are looking for. It is
+                                         * not necessary to bisect the chained arrays. */
+
+                /* Otherwise, the 'extra' object is not the one we are looking for. Search in the arrays. */
 
-        if (r == TEST_RIGHT) {
-                if (direction == DIRECTION_DOWN)
-                        goto found;
-                else
-                        return 0;
+        } else {
+                /* If we are going upwards, then we need to return the last object that passes the test.
+                 * When there is no object that passes the test, we need to return the the last object that
+                 * test_object() returns TEST_LEFT for. */
+                if (r == TEST_RIGHT)
+                        return 0; /* Not only the 'extra' object, but also all objects in the chained arrays
+                                   * will never get TEST_FOUND or TEST_LEFT. The object we are looking for
+                                   * does not exist. */
+
+                /* Even if the 'extra' object passes the test, there may be multiple objects in the arrays
+                 * that also pass the test. Hence, we need to bisect the arrays for finding the last matching
+                 * object. */
         }
 
-        r = generic_array_bisect(f, first, n-1, needle, test_object, direction, ret_object, ret_offset, ret_idx);
-
-        if (r == 0 && step_back)
-                goto found;
-
-        if (r > 0 && ret_idx)
-                (*ret_idx)++;
+        r = generic_array_bisect(f, first, n-1, needle, test_object, direction, ret_object, ret_offset, NULL);
+        if (r != 0)
+                return r; /* When > 0, the found object is the first (or last, when DIRECTION_UP) object.
+                           * Hence, return the found object now. */
 
-        return r;
+        /* No matching object found in the chained arrays.
+         * DIRECTION_DOWN : the 'extra' object neither matches the condition. There is no matching object.
+         * DIRECTION_UP   : the 'extra' object matches the condition. So, return it. */
+        if (direction == DIRECTION_DOWN)
+                return 0;
 
-found:
+use_extra:
         if (ret_object) {
                 r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, ret_object);
                 if (r < 0)
@@ -3160,9 +3170,6 @@ found:
         if (ret_offset)
                 *ret_offset = extra;
 
-        if (ret_idx)
-                *ret_idx = 0;
-
         return 1;
 }
 
@@ -3340,7 +3347,7 @@ int journal_file_move_to_entry_by_monotonic(
                         monotonic,
                         test_object_monotonic,
                         direction,
-                        ret_object, ret_offset, NULL);
+                        ret_object, ret_offset);
 }
 
 void journal_file_reset_location(JournalFile *f) {
@@ -3391,7 +3398,8 @@ int journal_file_next_entry(
                 Object **ret_object,
                 uint64_t *ret_offset) {
 
-        uint64_t i, n, ofs;
+        uint64_t i, n, q;
+        Object *o;
         int r;
 
         assert(f);
@@ -3403,38 +3411,53 @@ int journal_file_next_entry(
         if (n <= 0)
                 return 0;
 
+        /* When the input offset 'p' is zero, return the first (or last on DIRECTION_UP) entry. */
         if (p == 0)
-                i = direction == DIRECTION_DOWN ? 0 : n - 1;
-        else {
-                r = generic_array_bisect(f,
+                return generic_array_get(f,
                                          le64toh(f->header->entry_array_offset),
-                                         le64toh(f->header->n_entries),
-                                         p,
-                                         test_object_offset,
-                                         DIRECTION_DOWN,
-                                         NULL, NULL,
-                                         &i);
-                if (r <= 0)
-                        return r;
+                                         direction == DIRECTION_DOWN ? 0 : n - 1,
+                                         direction,
+                                         ret_object, ret_offset);
+
+        /* Otherwise, first find the nearest entry object. */
+        r = generic_array_bisect(f,
+                                 le64toh(f->header->entry_array_offset),
+                                 le64toh(f->header->n_entries),
+                                 p,
+                                 test_object_offset,
+                                 direction,
+                                 ret_object ? &o : NULL, &q, &i);
+        if (r <= 0)
+                return r;
 
-                r = bump_array_index(&i, direction, n);
-                if (r <= 0)
-                        return r;
-        }
+        assert(direction == DIRECTION_DOWN ? p <= q : q <= p);
+
+        /* If the input offset 'p' points to an entry object, generic_array_bisect() should provides
+         * the same offset, and the index needs to be shifted. Otherwise, use the found object as is,
+         * as it is the nearest entry object from the input offset 'p'. */
+
+        if (p != q)
+                goto found;
+
+        r = bump_array_index(&i, direction, n);
+        if (r <= 0)
+                return r;
 
         /* And jump to it */
-        r = generic_array_get(f, le64toh(f->header->entry_array_offset), i, direction, ret_object, &ofs);
+        r = generic_array_get(f, le64toh(f->header->entry_array_offset), i, direction, ret_object ? &o : NULL, &q);
         if (r <= 0)
                 return r;
 
         /* Ensure our array is properly ordered. */
-        if (p > 0 && !check_properly_ordered(ofs, p, direction))
+        if (!check_properly_ordered(q, p, direction))
                 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
-                                       "%s: entry array not properly ordered at entry %" PRIu64,
+                                       "%s: entry array not properly ordered at entry index %" PRIu64,
                                        f->path, i);
-
+found:
+        if (ret_object)
+                *ret_object = o;
         if (ret_offset)
-                *ret_offset = ofs;
+                *ret_offset = q;
 
         return 1;
 }
@@ -3523,7 +3546,7 @@ int journal_file_move_to_entry_by_offset_for_data(
                         p,
                         test_object_offset,
                         direction,
-                        ret, ret_offset, NULL);
+                        ret, ret_offset);
 }
 
 int journal_file_move_to_entry_by_monotonic_for_data(
@@ -3535,8 +3558,8 @@ int journal_file_move_to_entry_by_monotonic_for_data(
                 Object **ret_object,
                 uint64_t *ret_offset) {
 
-        uint64_t b, z, entry_offset, entry_array_offset, n_entries;
-        Object *o;
+        uint64_t z, entry_offset, entry_array_offset, n_entries;
+        Object *o, *entry;
         int r;
 
         assert(f);
@@ -3549,7 +3572,7 @@ int journal_file_move_to_entry_by_monotonic_for_data(
         n_entries = le64toh(READ_NOW(d->data.n_entries));
 
         /* First, seek by time */
-        r = find_data_object_by_boot_id(f, boot_id, &o, &b);
+        r = find_data_object_by_boot_id(f, boot_id, &o, NULL);
         if (r <= 0)
                 return r;
 
@@ -3560,19 +3583,16 @@ int journal_file_move_to_entry_by_monotonic_for_data(
                                           monotonic,
                                           test_object_monotonic,
                                           direction,
-                                          NULL, &z, NULL);
+                                          NULL, &z);
         if (r <= 0)
                 return r;
 
-        /* And now, continue seeking until we find an entry that
-         * exists in both bisection arrays */
-
-        r = journal_file_move_to_object(f, OBJECT_DATA, b, &o);
-        if (r < 0)
-                return r;
-
+        /* And now, continue seeking until we find an entry that exists in both bisection arrays. */
         for (;;) {
-                uint64_t p, q;
+                uint64_t p;
+
+                /* The journal entry found by the above bisect_plus_one() may not have the specified data,
+                 * that is, it may not be linked in the data object. So, we need to check that. */
 
                 r = generic_array_bisect_plus_one(f,
                                                   entry_offset,
@@ -3581,9 +3601,15 @@ int journal_file_move_to_entry_by_monotonic_for_data(
                                                   z,
                                                   test_object_offset,
                                                   direction,
-                                                  NULL, &p, NULL);
+                                                  ret_object ? &entry : NULL, &p);
                 if (r <= 0)
                         return r;
+                if (p == z)
+                        break; /* The journal entry has the specified data. Yay! */
+
+                /* If the entry does not have the data, then move to the next (or previous, depends on the
+                 * 'direction') entry linked to the data object. But, the next entry may be in another boot.
+                 * So, we need to check that the entry has the matching boot ID. */
 
                 r = generic_array_bisect_plus_one(f,
                                                   le64toh(o->data.entry_offset),
@@ -3592,26 +3618,20 @@ int journal_file_move_to_entry_by_monotonic_for_data(
                                                   p,
                                                   test_object_offset,
                                                   direction,
-                                                  NULL, &q, NULL);
-
+                                                  ret_object ? &entry : NULL, &z);
                 if (r <= 0)
                         return r;
+                if (p == z)
+                        break; /* The journal entry has the specified boot ID. Yay! */
 
-                if (p == q) {
-                        if (ret_object) {
-                                r = journal_file_move_to_object(f, OBJECT_ENTRY, q, ret_object);
-                                if (r < 0)
-                                        return r;
-                        }
-
-                        if (ret_offset)
-                                *ret_offset = q;
-
-                        return 1;
-                }
-
-                z = q;
+                /* If not, let's try to the next entry... */
         }
+
+        if (ret_object)
+                *ret_object = entry;
+        if (ret_offset)
+                *ret_offset = z;
+        return 1;
 }
 
 int journal_file_move_to_entry_by_seqnum_for_data(
@@ -3634,7 +3654,7 @@ int journal_file_move_to_entry_by_seqnum_for_data(
                         seqnum,
                         test_object_seqnum,
                         direction,
-                        ret_object, ret_offset, NULL);
+                        ret_object, ret_offset);
 }
 
 int journal_file_move_to_entry_by_realtime_for_data(
@@ -3656,7 +3676,7 @@ int journal_file_move_to_entry_by_realtime_for_data(
                         realtime,
                         test_object_realtime,
                         direction,
-                        ret, ret_offset, NULL);
+                        ret, ret_offset);
 }
 
 void journal_file_dump(JournalFile *f) {
@@ -3861,19 +3881,19 @@ static void journal_default_metrics(JournalMetrics *m, int fd, bool compact) {
         assert(fd >= 0);
 
         if (fstatvfs(fd, &ss) >= 0)
-                fs_size = ss.f_frsize * ss.f_blocks;
+                fs_size = u64_multiply_safe(ss.f_frsize, ss.f_blocks);
         else
                 log_debug_errno(errno, "Failed to determine disk size: %m");
 
         if (m->max_use == UINT64_MAX) {
 
                 if (fs_size > 0)
-                        m->max_use = CLAMP(PAGE_ALIGN(fs_size / 10), /* 10% of file system size */
+                        m->max_use = CLAMP(PAGE_ALIGN_U64(fs_size / 10), /* 10% of file system size */
                                            MAX_USE_LOWER, MAX_USE_UPPER);
                 else
                         m->max_use = MAX_USE_LOWER;
         } else {
-                m->max_use = PAGE_ALIGN(m->max_use);
+                m->max_use = PAGE_ALIGN_U64(m->max_use);
 
                 if (m->max_use != 0 && m->max_use < JOURNAL_FILE_SIZE_MIN*2)
                         m->max_use = JOURNAL_FILE_SIZE_MIN*2;
@@ -3881,7 +3901,7 @@ static void journal_default_metrics(JournalMetrics *m, int fd, bool compact) {
 
         if (m->min_use == UINT64_MAX) {
                 if (fs_size > 0)
-                        m->min_use = CLAMP(PAGE_ALIGN(fs_size / 50), /* 2% of file system size */
+                        m->min_use = CLAMP(PAGE_ALIGN_U64(fs_size / 50), /* 2% of file system size */
                                            MIN_USE_LOW, MIN_USE_HIGH);
                 else
                         m->min_use = MIN_USE_LOW;
@@ -3891,10 +3911,10 @@ static void journal_default_metrics(JournalMetrics *m, int fd, bool compact) {
                 m->min_use = m->max_use;
 
         if (m->max_size == UINT64_MAX)
-                m->max_size = MIN(PAGE_ALIGN(m->max_use / 8), /* 8 chunks */
+                m->max_size = MIN(PAGE_ALIGN_U64(m->max_use / 8), /* 8 chunks */
                                   MAX_SIZE_UPPER);
         else
-                m->max_size = PAGE_ALIGN(m->max_size);
+                m->max_size = PAGE_ALIGN_U64(m->max_size);
 
         if (compact && m->max_size > JOURNAL_COMPACT_SIZE_MAX)
                 m->max_size = JOURNAL_COMPACT_SIZE_MAX;
@@ -3910,13 +3930,13 @@ static void journal_default_metrics(JournalMetrics *m, int fd, bool compact) {
         if (m->min_size == UINT64_MAX)
                 m->min_size = JOURNAL_FILE_SIZE_MIN;
         else
-                m->min_size = CLAMP(PAGE_ALIGN(m->min_size),
+                m->min_size = CLAMP(PAGE_ALIGN_U64(m->min_size),
                                     JOURNAL_FILE_SIZE_MIN,
                                     m->max_size ?: UINT64_MAX);
 
         if (m->keep_free == UINT64_MAX) {
                 if (fs_size > 0)
-                        m->keep_free = MIN(PAGE_ALIGN(fs_size / 20), /* 5% of file system size */
+                        m->keep_free = MIN(PAGE_ALIGN_U64(fs_size / 20), /* 5% of file system size */
                                            KEEP_FREE_UPPER);
                 else
                         m->keep_free = DEFAULT_KEEP_FREE;