diff options
author | Vito Caputo <vcaputo@pengaru.com> | 2020-11-25 17:58:50 -0800 |
---|---|---|
committer | Vito Caputo <vcaputo@pengaru.com> | 2020-11-25 18:07:05 -0800 |
commit | 8ec1588722809a35a7d149bff10de56424e2658a (patch) | |
tree | dac9aca6321b92f84505293227c5151193dd8ee6 /src | |
parent | 7b15a68d12e2df7f45c4311d0281ff9a78693e81 (diff) |
src: initial commit of jio WIP source
This is a very quick and dirty experimental hack written in some
sort of bastard continuation-passing style in C w/io_uring using
journal-file introspection and manipulation duty as an excuse for
its existence.
Consider this unfinished prototype quality code.
Diffstat (limited to 'src')
-rw-r--r-- | src/bootid.c | 99 | ||||
-rw-r--r-- | src/bootid.h | 10 | ||||
-rw-r--r-- | src/humane.c | 82 | ||||
-rw-r--r-- | src/humane.h | 12 | ||||
-rw-r--r-- | src/jio.c | 139 | ||||
-rw-r--r-- | src/journals.c | 731 | ||||
-rw-r--r-- | src/journals.h | 41 | ||||
-rw-r--r-- | src/machid.c | 72 | ||||
-rw-r--r-- | src/machid.h | 10 | ||||
-rw-r--r-- | src/op.h | 13 | ||||
-rw-r--r-- | src/readfile.c | 96 | ||||
-rw-r--r-- | src/readfile.h | 10 | ||||
-rw-r--r-- | src/reclaim-tail-waste.c | 169 | ||||
-rw-r--r-- | src/reclaim-tail-waste.h | 8 | ||||
-rw-r--r-- | src/report-tail-waste.c | 137 | ||||
-rw-r--r-- | src/report-tail-waste.h | 8 | ||||
-rw-r--r-- | src/report-usage.c | 131 | ||||
-rw-r--r-- | src/report-usage.h | 8 |
18 files changed, 1776 insertions, 0 deletions
diff --git a/src/bootid.c b/src/bootid.c new file mode 100644 index 0000000..c2f9d24 --- /dev/null +++ b/src/bootid.c @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <fcntl.h> +#include <liburing.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <iou.h> +#include <thunk.h> + +#include "bootid.h" +#include "readfile.h" + +/* Implements iou-based bootid retrieval. + * + * Note there's not really any reason for doing this async vio iou, it just + * served as the initial test/use case while iou was fleshed out, so here it + * is, no harm in using it. I guess it's kind of convenient to have a little + * kicking of the tires iou/io_uring user at the start of the program anyways, + * rather than having something more complex to debug io_uring issues out of + * the hole. At least if the bootid is retrieved there's some evidence of the + * basic machinery working. + */ + + +#define BOOTID_PATH "/proc/sys/kernel/random/boot_id" + + +/* perform in-place removal of hyphens from str */ +static void dehyphen(char *str) +{ + char *dest; + + for (dest = str; *str; str++) { + if (*str == '-') + continue; + + *dest++ = *str; + } + + *dest = '\0'; +} + +/* call user-supplied closure now that buf is populated, after some baking of the data */ +THUNK_DEFINE_STATIC(have_bootid, iou_t *, iou, char *, buf, size_t *, size, char **, res_ptr, thunk_t *, closure) +{ + assert(iou); + assert(closure); + + /* FIXME: I'm just assuming it's a proper nl-terminated bootid for now, null terminate it */ + buf[*size - 1] = '\0'; + + dehyphen(buf); /* replicating what systemd does with the bootid */ + + *res_ptr = strdup(buf); + if (!*res_ptr) + return -ENOMEM; + + return thunk_dispatch(closure); +} + + +/* get a bootid via iou, scheduling closure once gotten */ +/* returns < 0 on error, 0 on successful queueing of operation */ +THUNK_DEFINE(bootid_get, iou_t *, iou, char **, res_ptr, thunk_t *, closure) +{ + thunk_t *bootid_thunk; + struct { + char buf[4096]; + size_t len; + } *buf; + + /* we need a buffer for readfiles, it only needs to last until have_bootid, + * where it gets null terminated and strdup()d, so allocae it as part of + * the have_bootid thunk. The THUNK_ALLOC/THUNK_INIT interfaces are still + * evolving. + */ + bootid_thunk = THUNK_ALLOC(have_bootid, (void **)&buf, sizeof(*buf)); + buf->len = sizeof(buf->buf); + THUNK_INIT(have_bootid(bootid_thunk, iou, buf->buf, &buf->len, res_ptr, closure)); + + return readfile(iou, BOOTID_PATH, buf->buf, &buf->len, bootid_thunk); +} diff --git a/src/bootid.h b/src/bootid.h new file mode 100644 index 0000000..15ee9be --- /dev/null +++ b/src/bootid.h @@ -0,0 +1,10 @@ +#ifndef _JIO_BOOTID_H +#define _JIO_BOOTID_H + +#include "thunk.h" + +typedef struct iou_t iou_t; + +THUNK_DECLARE(bootid_get, iou_t *, iou, char **, res_ptr, thunk_t *, closure); + +#endif diff --git a/src/humane.c b/src/humane.c new file mode 100644 index 0000000..3fdce03 --- /dev/null +++ b/src/humane.c @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <inttypes.h> +#include <stdint.h> +#include <stdio.h> + +#include "humane.h" + +/* Print bytes as a "human-readable" string in Si storage units into humane->buf and return it. */ +char * humane_bytes(humane_t *humane, uint64_t bytes) +{ + double z = bytes; + + static const char * units[] = { + "B", + "KiB", + "MiB", + "GiB", + "TiB", + "PiB", + "EiB", + }; + int order = 0; + + while (z >= 1024) { + order++; + z /= 1024; + } + + /* FIXME: isn't there a format specifier for adaptive precision? where %.2 means + * use a maximum of two digits but only extend a non-zero fraction up to that limit, + * i.e. don't produce outputs like 1.00. but produce 1.1 or 1.01, but 1.00 should be 1. + * I can't remember if there's a double format to do that, and can't waste more time + * reading the printf(3) man page. + */ + snprintf(humane->buf, sizeof(humane->buf), "%.2f %s", z, units[order]); + + return humane->buf; +} + + +#if 0 +/* TODO: when/if unit tests become a thing in this tree, turn this into one of them and assert the + * stringified "humane" outputs match expectations. + */ + +#define U64(x) UINT64_C(x) +int main(int argc, char *argv[]) +{ + uint64_t nums[] = { + 0, + U64(1), + U64(512), + U64(1024), + U64(1024) + U64(512), + U64(1024) * U64(1024), + U64(1024) * U64(1024) * U64(1024), + U64(1024) * U64(1024) * U64(1024) * U64(1024), + U64(1024) * U64(1024) * U64(1024) * U64(1024) * U64(1024), + U64(1024) * U64(1024) * U64(1024) * U64(1024) * U64(1024) * U64(1024), + UINT64_MAX, + }; + humane_t humane = {}; + + for (int i = 0; i < sizeof(nums) / sizeof(*nums); i++) + printf("%"PRIu64" humane: %s\n", nums[i], humane_bytes(&humane, nums[i])); +} +#endif diff --git a/src/humane.h b/src/humane.h new file mode 100644 index 0000000..959589e --- /dev/null +++ b/src/humane.h @@ -0,0 +1,12 @@ +#ifndef _JIO_HUMANE_H +#define _JIO_HUMANE_H + +#include <stdint.h> + +typedef struct humane_t { + char buf[sizeof("1023.99 EiB")]; +} humane_t; + +char * humane_bytes(humane_t *humane, uint64_t bytes); + +#endif diff --git a/src/jio.c b/src/jio.c new file mode 100644 index 0000000..2e98545 --- /dev/null +++ b/src/jio.c @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <string.h> + +#include <iou.h> + +#include "reclaim-tail-waste.h" +#include "report-tail-waste.h" +#include "report-usage.h" + +#include "upstream/journal-def.h" + +/* jio - journal-file input/output tool */ + +/* Copyright (c) 2020 Vito Caputo <vcaputo@pengaru.com> */ + +/* XXX: This is a WIP experiment, use at your own risk! XXX */ + +int main(int argc, char *argv[]) +{ + iou_t *iou; + int r; + + if (argc < 2) { + printf("Usage: %s {help,reclaim,report} [subcommand-args]\n", argv[0]); + return 0; + } + + iou = iou_new(8); + if (!iou) + return 1; + + /* FIXME TODO This is ad-hoc open-coded jank for now */ + if (!strcmp(argv[1], "help")) { + printf( + "\n" + " help show this help\n" + " license print license header\n" + " reclaim [subcmd] reclaim space from journal files\n" + " tail-waste reclaim wasted space from tails of archives\n" + "\n" + " report [subcmd] report statistics about journal files\n" + " usage report space used by various object types\n" + " tail-waste report extra space allocated onto tails\n" + " version print jio version\n" + "\n" + ); + return 0; + } else if (!strcmp(argv[1], "license")) { + printf( + "\n" + " Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com>\n" + "\n" + " This program is free software: you can redistribute it and/or modify it\n" + " under the terms of the GNU General Public License version 3 as published\n" + " by the Free Software Foundation.\n" + "\n" + " This program is distributed in the hope that it will be useful,\n" + " but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + " GNU General Public License for more details.\n" + "\n" + " You should have received a copy of the GNU General Public License\n" + " along with this program. If not, see <http://www.gnu.org/licenses/>.\n" + "\n" + ); + return 0; + } else if (!strcmp(argv[1], "reclaim")) { + if (argc < 3) { + printf("Usage: %s reclaim {tail-waste}\n", argv[0]); + return 0; + } + + if (!strcmp(argv[2], "tail-waste")) { + r = jio_reclaim_tail_waste(iou, argc, argv); + if (r < 0) { + fprintf(stderr, "failed to reclaim tail waste: %s\n", strerror(-r)); + return 1; + } + } else { + fprintf(stderr, "Unsupported reclaim subcommand: \"%s\"\n", argv[2]); + return 1; + } + } else if (!strcmp(argv[1], "report")) { + if (argc < 3) { + printf("Usage: %s report {usage,tail-waste}\n", argv[0]); + return 0; + } + + if (!strcmp(argv[2], "tail-waste")) { + r = jio_report_tail_waste(iou, argc, argv); + if (r < 0) { + fprintf(stderr, "failed to report tail waste: %s\n", strerror(-r)); + return 1; + } + } else if (!strcmp(argv[2], "usage")) { + r = jio_report_usage(iou, argc, argv); + if (r < 0) { + fprintf(stderr, "failed to report usage: %s\n", strerror(-r)); + return 1; + } + } else { + fprintf(stderr, "Unsupported report subcommand: \"%s\"\n", argv[2]); + return 1; + } + } else if (!strcmp(argv[1], "version")) { + puts("jio version " VERSION); + return 0; + } else { + fprintf(stderr, "Unsupported subcommand: \"%s\"\n", argv[1]); + return 1; + } + + if (!r) { + /* XXX: note this is a successful noop if there's no outstanding io */ + r = iou_run(iou); + if (r < 0) + fprintf(stderr, "iou error: %s\n", strerror(-r)); + } + + iou_free(iou); + + return r != 0; +} diff --git a/src/journals.c b/src/journals.c new file mode 100644 index 0000000..7ad60d4 --- /dev/null +++ b/src/journals.c @@ -0,0 +1,731 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <dirent.h> +#include <fcntl.h> +#include <liburing.h> +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <iou.h> +#include <thunk.h> + +#include "journals.h" +#include "op.h" + +#include "upstream/journal-def.h" + + +#define PERSISTENT_PATH "/var/log/journal" + + +typedef struct journals_t { + int dirfd; + size_t n_journals, n_allocated, n_opened; + journal_t journals[]; +} journals_t; + + +/* an open on journal->name was attempted, result in op->result. + * bump *journals->n_opened, when it matches *journals->n_journals, dispatch closure + */ +THUNK_DEFINE_STATIC(opened_journal, iou_t *, iou, iou_op_t *, op, journals_t *, journals, journal_t *, journal, thunk_t *, closure) +{ + assert(iou); + assert(op); + assert(journals); + assert(journal); + assert(closure); + + /* note n_opened is a count of open calls, not successes, the closure only gets called + * when all the opens have been performed hence the need to count them. + */ + journals->n_opened++; + + if (op->result < 0 ) { + if (op->result != -EPERM && op->result != -EACCES) + return op->result; + + fprintf(stderr, "Permission denied opening \"%s\", ignoring\n", journal->name); + journal->fd = -1; + } else { + journal->fd = op->result; + } + + if (journals->n_opened == journals->n_journals) + return thunk_dispatch(closure); + + return 0; +} + + +THUNK_DEFINE_STATIC(get_journals, iou_t *, iou, iou_op_t *, op, journals_t **, journals, int, flags, thunk_t *, closure) +{ + struct dirent *dent; + DIR *jdir; + size_t n = 0; + journals_t *j; + int r; + + assert(iou); + assert(op); + assert(journals); + assert(closure); + + if (op->result < 0) + return op->result; + + /* I don't see any readdir/getdents ops for io_uring, so just do the opendir/readdir + * synchronously here before queueing the opening of all those paths. + */ + jdir = fdopendir(op->result); + if (jdir == NULL) { + int r = errno; + + close(op->result); /* only on successful fdopendir is the fd owned and closed by jdir */ + + return r; + } + + while ((dent = readdir(jdir))) { + if (dent->d_name[0] == '.') /* just skip dot files and "." ".." */ + continue; + + n++; + } + rewinddir(jdir); + + if (!n) /* no journals! */ + return 0; + + j = calloc(1, sizeof(journals_t) + sizeof(j->journals[0]) * n); + if (!j) { + closedir(jdir); + return -ENOMEM; + } + + j->n_allocated = n; + + while ((dent = readdir(jdir))) { + if (dent->d_name[0] == '.') /* just skip dot files and "." ".." */ + continue; + + j->journals[j->n_journals++].name = strdup(dent->d_name); + + assert(j->n_journals <= j->n_allocated);// FIXME this defends against the race, but the race + // is a normal thing and should be handled gracefully, + // or just stop doing this two pass dance. + } + + /* duplicate the dirfd for openat use later on, since closedir() will close op->result */ + j->dirfd = dup(op->result); + closedir(jdir); + + /* to keep things relatively simple, let's ensure there's enough queue space for at least one + * operation per journal. + */ + r = iou_resize(iou, j->n_journals); + if (r < 0) + return r; /* TODO: cleanup j */ + + /* we have a list of journal names, now queue requests to open them all */ + for (size_t i = 0; i < j->n_journals; i++) { + iou_op_t *oop; + + oop = iou_op_new(iou); + if (!oop) + return -ENOMEM; // FIXME: cleanup + + io_uring_prep_openat(oop->sqe, j->dirfd, j->journals[i].name, flags, 0); + op_queue(iou, oop, THUNK(opened_journal(iou, oop, j, &j->journals[i], closure))); + } + + iou_flush(iou); + + /* stow the journals where they can be found, but note they aren't opened yet. */ + *journals = j; + + return 0; +} + + +THUNK_DEFINE_STATIC(var_opened, iou_t *, iou, iou_op_t *, op, char **, machid, journals_t **, journals, int, flags, thunk_t *, closure) +{ + iou_op_t *mop; + + if (op->result < 0) + return op->result; + + /* PERSISTENT_PATH is opened, req open ./$machid */ + mop = iou_op_new(iou); + if (!mop) + return -ENOMEM; + + /* req iou to open dir, passing the req to read and open its contents */ + io_uring_prep_openat(mop->sqe, op->result, *machid, O_DIRECTORY|O_RDONLY, 0); + op_queue(iou, mop, THUNK(get_journals(iou, mop, journals, flags, closure))); + + return 0; +} + + +/* requesting opening the journals via iou, allocating the resulting journals_t @ *journals */ +/* returns < 0 on error, 0 on successful queueing of operation */ +THUNK_DEFINE(journals_open, iou_t *, iou, char **, machid, int, flags, journals_t **, journals, thunk_t *, closure) +{ + iou_op_t *op; + + assert(iou); + assert(machid); + assert(journals); + assert(closure); + + op = iou_op_new(iou); + if (!op) + return -ENOMEM; + + /* req iou to open PERSISTENT_PATH, passing on to var_opened */ + io_uring_prep_openat(op->sqe, 0, PERSISTENT_PATH, O_DIRECTORY|O_RDONLY, 0); + op_queue(iou, op, THUNK(var_opened(iou, op, machid, journals, flags, closure))); + + return 0; +} + + + +THUNK_DEFINE_STATIC(got_iter_object_header, iou_t *, iou, iou_op_t *, op, journal_t *, journal, uint64_t *, iter_offset, ObjectHeader *, iter_object_header, thunk_t *, closure) +{ + assert(iou); + assert(journal); + assert(iter_offset); + assert(iter_object_header); + assert(closure); + + if (op->result < 0) + return op->result; + + if (op->result != sizeof(ObjectHeader)) + return -EINVAL; + + iter_object_header->size = le64toh(iter_object_header->size); + + return thunk_dispatch_keep(closure); +} + + +/* Queues IO on iou for getting the next object from (*journal)->fd + * addressed by state in {iter_offset,iter_object_header} storing it @ + * *iter_object_header which must already have space allocated to accomodate + * sizeof(ObjectHeader), registering closure for dispatch when completed. This + * only performs a single step, advancing the offset in *iter_offset by + * *iter_object_header->size, ready for continuation in a subsequent call. + * + * When (*iter_offset == 0) it's considered the initial iteration, as this is + * an invalid object offset (0=the journal header), the first object of the + * journal will be loaded into *iter_object_header without advancing anything - + * ignoring the initial contents of *iter_object_header. + * + * When closure gets dispatched with (*iter_offset == 0), there's no more + * objects in journal, and *iter_object_header will have been unmodified. + * Hence closure should always check if (*iter_offset == 0) before accessing + * *iter_object_header, and do whatever is appropriate upon reaching the end of + * the journal. If journal_iter_next_object() recurs after reaching this + * point, it will restart iterating from the first object of the journal. + * + * Currently closures before the end of journal are dispatched w/the + * non-freeing variant thunk_dispatch_keep(). Only the last dispatch + * w/(*iter_offset == 0) is dispatched with the freeing thunk_dispatch(). This + * feels clunky, but it works for now. I might extend thunk.h to let closures + * control wether their dispatch should free or not via the return value. TODO + */ +THUNK_DEFINE(journal_iter_next_object, iou_t *, iou, journal_t **, journal, Header *, header, uint64_t *, iter_offset, ObjectHeader *, iter_object_header, thunk_t *, closure) +{ + iou_op_t *op; + + assert(iou); + assert(journal); + assert(iter_offset); + assert(iter_object_header); + assert(closure); + + /* restart iterating when entered with (*iter_offset == 0) */ + if (!(*iter_offset)) + *iter_offset = header->header_size; + else { + if (iter_object_header->size) + *iter_offset += ALIGN64(iter_object_header->size); + else { + fprintf(stderr, "Encountered zero-sized object, journal \"%s\" appears corrupt, ignoring remainder\n", (*journal)->name); + *iter_offset = header->tail_object_offset + 1; + } + } + + /* final dispatch if past tail object */ + if (*iter_offset > header->tail_object_offset) { + *iter_offset = 0; + return thunk_dispatch(closure); + } + + op = iou_op_new(iou); + if (!op) + return -ENOMEM; + + io_uring_prep_read(op->sqe, (*journal)->fd, iter_object_header, sizeof(ObjectHeader), *iter_offset); + op_queue(iou, op, THUNK(got_iter_object_header(iou, op, *journal, iter_offset, iter_object_header, closure))); + + return 0; +} + + +/* Helper for the journal_for_each() simple objects iteration. */ +THUNK_DEFINE_STATIC(journal_for_each_dispatch, iou_t *, iou, journal_t **, journal, Header *, header, uint64_t *, iter_offset, ObjectHeader *, iter_object_header, thunk_t *, closure) +{ + int r; + + if (!(*iter_offset)) + return thunk_dispatch(closure); + + r = thunk_dispatch_keep(closure); + if (r < 0) + return r; + + return journal_iter_next_object(iou, journal, header, iter_offset, iter_object_header, THUNK( + journal_for_each_dispatch(iou, journal, header, iter_offset, iter_object_header, closure))); +} + + +/* Convenience journal object iterator, dispatches thunk for every object in the journal. + * + * Note in this wrapper there's no way to control/defer the iterator, your + * closure is simply dispatched in a loop, no continuation is passed to it for + * resuming iteration, which tends to limit its use to simple situations. + */ +THUNK_DEFINE(journal_for_each, iou_t *, iou, journal_t **, journal, Header *, header, uint64_t *, iter_offset, ObjectHeader *, iter_object_header, thunk_t *, closure) +{ + assert(iter_offset); + + *iter_offset = 0; + + return journal_iter_next_object(iou, journal, header, iter_offset, iter_object_header, THUNK( + journal_for_each_dispatch(iou, journal, header, iter_offset, iter_object_header, closure))); +} + + +THUNK_DEFINE_STATIC(got_hash_table_iter_object_header, iou_t *, iou, iou_op_t *, op, journal_t *, journal, HashItem *, hash_table, uint64_t, nbuckets, uint64_t *, iter_bucket, uint64_t *, iter_offset, HashedObjectHeader *, iter_object_header, size_t, iter_object_size, thunk_t *, closure) +{ + assert(iou); + assert(journal); + assert(hash_table); + assert(iter_bucket && *iter_bucket < nbuckets); + assert(iter_offset); + assert(iter_object_header); + assert(closure); + + if (op->result < 0) + return op->result; + + if (op->result != iter_object_size) + return -EINVAL; + + iter_object_header->object.size = le64toh(iter_object_header->object.size); + iter_object_header->hash = le64toh(iter_object_header->hash); + iter_object_header->next_hash_offset = le64toh(iter_object_header->next_hash_offset); + + if (iter_object_size > sizeof(HashedObjectHeader)) { + /* The caller can iterate either the field or data hash tables, + * so just introspect and handle those two... using a size > + * than the minimum HashedObjectHeader as a heuristic. + * Scenarios that only need what's available in HashedObjectHeader + * can bypass all this and only read in the HashedObjectHeader + * while iterating. + */ + switch (iter_object_header->object.type) { + case OBJECT_DATA: { + DataObject *data_object = (DataObject *)iter_object_header; + + assert(iter_object_size >= sizeof(DataObject)); + + data_object->next_field_offset = le64toh(data_object->next_field_offset); + data_object->entry_offset = le64toh(data_object->entry_offset); + data_object->entry_array_offset = le64toh(data_object->entry_array_offset); + data_object->n_entries = le64toh(data_object->n_entries); + break; + } + case OBJECT_FIELD: { + FieldObject *field_object = (FieldObject *)iter_object_header; + + assert(iter_object_size >= sizeof(FieldObject)); + + field_object->head_data_offset = le64toh(field_object->head_data_offset); + break; + } + default: + assert(0); + } + } + + return thunk_dispatch_keep(closure); +} + + +/* Queues IO on iou for getting the next object from hash_table{_size} + * positioned by state {iter_bucket,iter_offset,iter_object_header} storing it + * @ *iter_object_header which must already have space allocated to accomodate + * iter_object_size, registering closure for dispatch when completed. This + * only performs a single step, advancing the state in + * {iter_bucket,iter_offset} appropriate for continuing via another call. + * + * When (*iter_offset == 0) it's considered the initial iteration, as this is + * an invalid object offset (0=the journal header), and the first object of the + * table will be loaded into *iter_object_header without advancing anything. + * + * When closure gets dispatched with (*iter_offset == 0), there's no more + * objects in hash_table, and *iter_object_header will have been unmodified. + * Hence closure should always check if (*iter_offset == 0) before using + * *iter_object_header, and do whatever is appropriate upon reaching the end of + * the hash table. If journal_hash_table_iter_next_object() recurs after + * reaching this point, it will restart iterating from the first object of the + * table. + * + * Currently closures before the end of hash_table are dispatched w/the + * non-freeing variant thunk_dispatch_keep(). Only the last dispatch + * w/(*iter_offset == 0) is dispatched with the freeing thunk_dispatch(). + * This feels clunky, but it works for now. I might extend thunk.h to let + * closures control wether their dispatch should free or not via the return + * value. TODO + */ +THUNK_DEFINE(journal_hash_table_iter_next_object, iou_t *, iou, journal_t **, journal, HashItem **, hash_table, uint64_t *, hash_table_size, uint64_t *, iter_bucket, uint64_t *, iter_offset, HashedObjectHeader *, iter_object_header, size_t, iter_object_size, thunk_t *, closure) +{ + size_t nbuckets; + iou_op_t *op; + + assert(iou); + assert(journal); + assert(hash_table); + assert(hash_table_size && *hash_table_size >= sizeof(HashItem)); + assert(iter_bucket); + assert(iter_offset); + assert(iter_object_header); + assert(iter_object_size >= sizeof(HashedObjectHeader)); + assert(closure); + + nbuckets = *hash_table_size / sizeof(HashItem); + + assert(*iter_bucket < nbuckets); + + /* restart iterating when entered with (*iter_offset == 0) */ + if (!*iter_offset) + *iter_bucket = 0; + + if (*iter_offset && *iter_offset != (*hash_table)[*iter_bucket].tail_hash_offset) { + *iter_offset = iter_object_header->next_hash_offset; + } else { + do { + (*iter_bucket)++; + if (*iter_bucket >= nbuckets) { + *iter_offset = 0; + return thunk_dispatch(closure); + } + (*iter_offset) = (*hash_table)[*iter_bucket].head_hash_offset; + } while (!(*iter_offset)); + } + + op = iou_op_new(iou); + if (!op) + return -ENOMEM; + + io_uring_prep_read(op->sqe, (*journal)->fd, iter_object_header, iter_object_size, *iter_offset); + op_queue(iou, op, THUNK(got_hash_table_iter_object_header(iou, op, *journal, *hash_table, *hash_table_size, iter_bucket, iter_offset, iter_object_header, iter_object_size, closure))); + + return 0; +} + + +/* Helper for the journal_hash_table_for_each() simple hash table iteration. */ +THUNK_DEFINE_STATIC(journal_hash_table_for_each_dispatch, iou_t *, iou, journal_t **, journal, HashItem **, hash_table, uint64_t *, hash_table_size, uint64_t *, iter_bucket, uint64_t *, iter_offset, HashedObjectHeader *, iter_object_header, size_t, iter_object_size, thunk_t *, closure) +{ + int r; + + if (!*iter_offset) + return thunk_dispatch(closure); + + r = thunk_dispatch_keep(closure); + if (r < 0) + return r; + + return journal_hash_table_iter_next_object(iou, journal, hash_table, hash_table_size, iter_bucket, iter_offset, iter_object_header, iter_object_size, THUNK( + journal_hash_table_for_each_dispatch(iou, journal, hash_table, hash_table_size, iter_bucket, iter_offset, iter_object_header, iter_object_size, closure))); +} + + +/* Convenience hash table iterator, dispatches thunk for every object in the hash table. + * iter_object_size would typically be sizeof(DataObject) or sizeof(FieldObject), as + * these are the two hash table types currently handled. The size is supplied rather + * than a type, because one may iterate by only loading the HashedObjectHeader portion. + * The size is introspected to determine if more than just the HashedObjectHeader is being + * loaded, and its type asserted to fit in the supplied space. + * + * Note in this wrapper there's no way to control/defer the iterator, your + * closure is simply dispatched in a loop, no continuation is passed to it for + * resuming iteration, which tends to limit its use to simple situations. + */ +THUNK_DEFINE(journal_hash_table_for_each, iou_t *, iou, journal_t **, journal, HashItem **, hash_table, uint64_t *, hash_table_size, uint64_t *, iter_bucket, uint64_t *, iter_offset, HashedObjectHeader *, iter_object_header, size_t, iter_object_size, thunk_t *, closure) +{ + assert(iter_offset); + + *iter_offset = 0; + + return journal_hash_table_iter_next_object(iou, journal, hash_table, hash_table_size, iter_bucket, iter_offset, iter_object_header, iter_object_size, THUNK( + journal_hash_table_for_each_dispatch(iou, journal, hash_table, hash_table_size, iter_bucket, iter_offset, iter_object_header, iter_object_size, closure))); +} + + +THUNK_DEFINE_STATIC(got_hashtable, iou_t *, iou, iou_op_t *, op, HashItem *, table, uint64_t, size, HashItem **, res_hash_table, thunk_t *, closure) +{ + assert(iou); + assert(op); + assert(table); + assert(res_hash_table); + assert(closure); + + if (op->result < 0) + return op->result; + + if (op->result != size) + return -EINVAL; + + for (uint64_t i = 0; i < size / sizeof(HashItem); i++) { + table[i].head_hash_offset = le64toh(table[i].head_hash_offset); + table[i].tail_hash_offset = le64toh(table[i].tail_hash_offset); + } + /* TODO: validation/sanity checks? */ + + *res_hash_table = table; + + return thunk_dispatch(closure); +} + + +/* Queue IO on iou for loading a journal's hash table from *journal into memory allocated and stored @ *res_hash_table. + * Registers closure for dispatch when completed. + */ +THUNK_DEFINE(journal_get_hash_table, iou_t *, iou, journal_t **, journal, uint64_t *, hash_table_offset, uint64_t *, hash_table_size, HashItem **, res_hash_table, thunk_t *, closure) +{ + iou_op_t *op; + HashItem *table; + + assert(iou); + assert(journal); + assert(hash_table_offset); + assert(hash_table_size); + assert(res_hash_table); + assert(closure); + + op = iou_op_new(iou); + if (!op) + return -ENOMEM; + + table = malloc(*hash_table_size); + if (!table) + return -ENOMEM; + + io_uring_prep_read(op->sqe, (*journal)->fd, table, *hash_table_size, *hash_table_offset); + op_queue(iou, op, THUNK(got_hashtable(iou, op, table, *hash_table_size, res_hash_table, closure))); + + return 0; +} + + +/* Validate and prepare journal header loaded via journal_get_header @ header, dispatch closure. */ +THUNK_DEFINE_STATIC(got_header, iou_t *, iou, iou_op_t *, op, journal_t *, journal, Header *, header, thunk_t *, closure) +{ + assert(iou); + assert(op); + assert(journal); + assert(header); + assert(closure); + + if (op->result < 0) + return op->result; + + if (op->result < sizeof(*header)) + return -EINVAL; + + header->compatible_flags = le32toh(header->compatible_flags); + header->incompatible_flags = le32toh(header->incompatible_flags); + header->header_size = le64toh(header->header_size); + header->arena_size = le64toh(header->arena_size); + header->data_hash_table_offset = le64toh(header->data_hash_table_offset); + header->data_hash_table_size = le64toh(header->data_hash_table_size); + header->field_hash_table_offset = le64toh(header->field_hash_table_offset); + header->field_hash_table_size = le64toh(header->field_hash_table_size); + header->tail_object_offset = le64toh(header->tail_object_offset); + header->n_objects = le64toh(header->n_objects); + header->n_entries = le64toh(header->n_entries); + header->tail_entry_seqnum = le64toh(header->tail_entry_seqnum); + header->head_entry_seqnum = le64toh(header->head_entry_seqnum); + header->entry_array_offset = le64toh(header->entry_array_offset); + header->head_entry_realtime = le64toh(header->head_entry_realtime); + header->tail_entry_realtime = le64toh(header->tail_entry_realtime); + header->tail_entry_monotonic = le64toh(header->tail_entry_monotonic); + header->n_data = le64toh(header->n_data); + header->n_fields = le64toh(header->n_fields); + header->n_tags = le64toh(header->n_tags); + header->n_entry_arrays = le64toh(header->n_entry_arrays); + header->data_hash_chain_depth = le64toh(header->data_hash_chain_depth); + header->field_hash_chain_depth = le64toh(header->field_hash_chain_depth); + /* TODO: validation/sanity checks? */ + + return thunk_dispatch(closure); +} + + +/* Queue IO on iou for loading a journal's header from *journal into *header. + * Registers closure for dispatch when completed. + */ +THUNK_DEFINE(journal_get_header, iou_t *, iou, journal_t **, journal, Header *, header, thunk_t *, closure) +{ + iou_op_t *op; + + assert(iou); + assert(journal); + assert(closure); + + op = iou_op_new(iou); + if (!op) + return -ENOMEM; + + io_uring_prep_read(op->sqe, (*journal)->fd, header, sizeof(*header), 0); + op_queue(iou, op, THUNK(got_header(iou, op, *journal, header, closure))); + + return 0; +} + + +/* Validate and prepare object header loaded via journal_get_object_header @ object_header, dispatch closure. */ +THUNK_DEFINE_STATIC(got_object_header, iou_t *, iou, iou_op_t *, op, ObjectHeader *, object_header, thunk_t *, closure) +{ + assert(iou); + assert(op); + assert(object_header); + assert(closure); + + if (op->result < 0) + return op->result; + + if (op->result < sizeof(*object_header)) + return -EINVAL; + + object_header->size = le64toh(object_header->size); + /* TODO: validation/sanity checks? */ + + return thunk_dispatch(closure); +} + + +/* Queue IO on iou for loading an object header from *journal @ offset *offset, into *object_header. + * Registers closure for dispatch on the io when completed. + */ +THUNK_DEFINE(journal_get_object_header, iou_t *, iou, journal_t **, journal, uint64_t *, offset, ObjectHeader *, object_header, thunk_t *, closure) +{ + iou_op_t *op; + + assert(iou); + assert(journal); + assert(offset); + assert(object_header); + assert(closure); + + op = iou_op_new(iou); + if (!op) + return -ENOMEM; + + io_uring_prep_read(op->sqe, (*journal)->fd, object_header, sizeof(*object_header), *offset); + op_queue(iou, op, THUNK(got_object_header(iou, op, object_header, closure))); + + return 0; +} + + +/* for every open journal in *journals, store the journal in *journal_iter and dispatch closure */ +/* closure must expect to be dispatched multiple times; once per journal, and will be freed once at end */ +THUNK_DEFINE(journals_for_each, journals_t **, journals, journal_t **, journal_iter, thunk_t *, closure) +{ + journals_t *j; + + assert(journals && *journals); + assert(journal_iter); + assert(closure); + + j = *journals; + + for (size_t i = 0; i < j->n_journals; i++) { + int r; + + if (j->journals[i].fd < 0) + continue; + + (*journal_iter) = &j->journals[i]; + + r = thunk_dispatch_keep(closure); + if (r < 0) { + free(closure); + return r; + } + } + + free(closure); + + return 0; +} + + +const char * journal_object_type_str(ObjectType type) +{ + static const char *names[] = { + "UNUSED", + "Data", + "Field", + "Entry", + "DataHashTable", + "FieldHashTable", + "EntryArray", + "Tag", + }; + + if (type < 0 || type >= sizeof(names) / sizeof(*names)) + return "UNKNOWN"; + + return names[type]; +} + + +const char * journal_state_str(JournalState state) +{ + static const char *names[] = { + "Offline", + "Online", + "Archived", + }; + + if (state < 0 || state >= sizeof(names) / sizeof(*names)) + return "UNKNOWN"; + + return names[state]; +} diff --git a/src/journals.h b/src/journals.h new file mode 100644 index 0000000..1cbe660 --- /dev/null +++ b/src/journals.h @@ -0,0 +1,41 @@ +#ifndef _JIO_JOURNALS_H +#define _JIO_JOURNALS_H + +#include <stdint.h> + +/* open() includes since journals_open() reuses open() flags */ +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + + +#include "thunk.h" + +#include "upstream/journal-def.h" + +typedef struct iou_t iou_t; + +typedef struct journal_t { + char *name; + int fd; +} journal_t; + +typedef struct journals_t journals_t; + +THUNK_DECLARE(journals_open, iou_t *, iou, char **, machid, int, flags, journals_t **, journals, thunk_t *, closure); +THUNK_DECLARE(journal_get_header, iou_t *, iou, journal_t **, journal, Header *, header, thunk_t *, closure); + +THUNK_DECLARE(journal_iter_next_object, iou_t *, iou, journal_t **, journal, Header *, header, uint64_t *, iter_offset, ObjectHeader *, iter_object_header, thunk_t *, closure); +THUNK_DECLARE(journal_for_each, iou_t *, iou, journal_t **, journal, Header *, header, uint64_t *, iter_offset, ObjectHeader *, iter_object_header, thunk_t *, closure); + +THUNK_DECLARE(journal_get_hash_table, iou_t *, iou, journal_t **, journal, uint64_t *, hash_table_offset, uint64_t *, hash_table_size, HashItem **, res_hash_table, thunk_t *, closure); +THUNK_DECLARE(journal_hash_table_iter_next_object, iou_t *, iou, journal_t **, journal, HashItem **, hash_table, uint64_t *, hash_table_size, uint64_t *, iter_bucket, uint64_t *, iter_offset, HashedObjectHeader *, iter_object_header, size_t, iter_object_size, thunk_t *, closure); +THUNK_DECLARE(journal_hash_table_for_each, iou_t *, iou, journal_t **, journal, HashItem **, hash_table, uint64_t *, hash_table_size, uint64_t *, iter_bucket, uint64_t *, iter_offset, HashedObjectHeader *, iter_object_header, size_t, iter_object_size, thunk_t *, closure); + +THUNK_DECLARE(journal_get_object_header, iou_t *, iou, journal_t **, journal, uint64_t *, offset, ObjectHeader *, object_header, thunk_t *, closure); +THUNK_DECLARE(journals_for_each, journals_t **, journals, journal_t **, journal_iter, thunk_t *, closure); + +const char * journal_object_type_str(ObjectType type); +const char * journal_state_str(JournalState state); + +#endif diff --git a/src/machid.c b/src/machid.c new file mode 100644 index 0000000..721a4ee --- /dev/null +++ b/src/machid.c @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <fcntl.h> +#include <liburing.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <iou.h> +#include <thunk.h> + +#include "machid.h" +#include "readfile.h" + +/* Implements iou-based machine-id retrieval. + * + * this is basically copy & paste from bootid.c, + * which suggests this should just be readfile.c + */ + + +#define MACHID_PATH "/etc/machine-id" + + +/* call user-supplied closure now that buf is populated */ +THUNK_DEFINE_STATIC(have_machid, iou_t *, iou, char *, buf, size_t *, size, char **, res_ptr, thunk_t *, closure) +{ + assert(iou); + assert(closure); + + /* FIXME: I'm just assuming it's a proper nl-terminated machid for now, null terminate it */ + buf[*size - 1] = '\0'; + + *res_ptr = strdup(buf); + if (!*res_ptr) + return -ENOMEM; + + return thunk_dispatch(closure); +} + + +/* get a machid via iou, scheduling closure once gotten */ +/* returns < 0 on error, 0 on successful queueing of operation */ +THUNK_DEFINE(machid_get, iou_t *, iou, char **, res_ptr, thunk_t *, closure) +{ + thunk_t *machid_thunk; + struct { + char buf[4096]; + size_t len; + } *buf; + + machid_thunk = THUNK_ALLOC(have_machid, (void **)&buf, sizeof(*buf)); + buf->len = sizeof(buf->buf); + THUNK_INIT(have_machid(machid_thunk, iou, buf->buf, &buf->len, res_ptr, closure)); + + return readfile(iou, MACHID_PATH, buf->buf, &buf->len, machid_thunk); +} diff --git a/src/machid.h b/src/machid.h new file mode 100644 index 0000000..6aa972b --- /dev/null +++ b/src/machid.h @@ -0,0 +1,10 @@ +#ifndef _JIO_MACHID_H +#define _JIO_MACHID_H + +#include "thunk.h" + +typedef struct iou_t iou_t; + +THUNK_DECLARE(machid_get, iou_t *, iou, char **, res_ptr, thunk_t *, closure); + +#endif diff --git a/src/op.h b/src/op.h new file mode 100644 index 0000000..ccb1932 --- /dev/null +++ b/src/op.h @@ -0,0 +1,13 @@ +#ifndef _JIO_OP_H +#define _JIO_OP_H + +#include <iou.h> +#include <thunk.h> + +/* ergonomic helper for submitting a thunk_t as a cb+cb_data pair to iou_op_queue() */ +static inline void op_queue(iou_t *iou, iou_op_t *op, thunk_t *thunk) +{ + return iou_op_queue(iou, op, (int (*)(void *))thunk->dispatch, thunk); +} + +#endif diff --git a/src/readfile.c b/src/readfile.c new file mode 100644 index 0000000..8af7438 --- /dev/null +++ b/src/readfile.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <fcntl.h> +#include <liburing.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <iou.h> +#include <thunk.h> + +#include "readfile.h" +#include "op.h" + +/* Implements iou-based file read into a supplied buffer. + */ + + +/* call user-supplied closure now that buf is populated */ +THUNK_DEFINE_STATIC(read_done, iou_t *, iou, iou_op_t *, op, int, fd, char *, buf, size_t *, size, thunk_t *, closure) +{ + assert(iou); + assert(op); + assert(closure); + + if (op->result < 0) + return op->result; + + (void) close(fd); + + *size = op->result; + + return thunk_dispatch(closure); +} + + +/* ask iou to read from the opened fd into buf */ +THUNK_DEFINE_STATIC(open_done, iou_t *, iou, iou_op_t *, op, char *, buf, size_t *, size, thunk_t *, closure) +{ + thunk_t *read_closure; + iou_op_t *read_op; + + assert(iou); + assert(op); + + if (op->result < 0) + return op->result; + + read_op = iou_op_new(iou); + if (!read_op) + return -ENOMEM; + + read_closure = THUNK(read_done(iou, read_op, op->result, buf, size, closure)); + if (!read_closure) + return -ENOMEM; + + io_uring_prep_read(read_op->sqe, op->result, buf, *size, 0); + op_queue(iou, read_op, read_closure); + + return 0; +} + + +/* read a file into a buffer via iou, dispatching closure when complete */ +/* size specifies the buf size, and will also get the length of what's read + * written to it upon completion. + */ +/* returns < 0 on error, 0 on successful queueing of operation */ +THUNK_DEFINE(readfile, iou_t *, iou, const char *, path, char *, buf, size_t *, size, thunk_t *, closure) +{ + iou_op_t *op; + + op = iou_op_new(iou); + if (!op) + return -ENOMEM; + + /* req iou to open path, passing the req to read its contents */ + io_uring_prep_openat(op->sqe, 0, path, 0, O_RDONLY); + op_queue(iou, op, THUNK(open_done(iou, op, buf, size, closure))); + + return 0; +} diff --git a/src/readfile.h b/src/readfile.h new file mode 100644 index 0000000..2e414b3 --- /dev/null +++ b/src/readfile.h @@ -0,0 +1,10 @@ +#ifndef _JIO_READFILE_H +#define _JIO_READFILE_H + +#include "thunk.h" + +typedef struct iou_t iou_t; + +THUNK_DECLARE(readfile, iou_t *, iou, const char *, path, char *, buf, size_t *, size, thunk_t *, closure); + +#endif diff --git a/src/reclaim-tail-waste.c b/src/reclaim-tail-waste.c new file mode 100644 index 0000000..fd3cb13 --- /dev/null +++ b/src/reclaim-tail-waste.c @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <errno.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> + +#include <iou.h> +#include <thunk.h> + +#include "bootid.h" +#include "humane.h" +#include "journals.h" +#include "machid.h" +#include "reclaim-tail-waste.h" + +#include "upstream/journal-def.h" + + +typedef struct tail_waste_t { + unsigned n_journals, n_reclaimed, n_ignored, n_errored, n_mules; + uint64_t reclaimed_bytes, ignored_bytes, errored_bytes; +} tail_waste_t; + + +THUNK_DEFINE_STATIC(reclaim_tail_waste, journal_t **, journal, Header *, journal_header, ObjectHeader *, tail_object_header, tail_waste_t *, tail_waste) +{ + uint64_t sz, tail; + struct stat st; + int r; + humane_t h1; + + assert(journal); + assert(journal_header); + assert(tail_object_header); + assert(tail_waste); + + r = fstat((*journal)->fd, &st); + if (r < 0) + return r; + + tail_waste->n_journals++; + + sz = st.st_size; + tail = journal_header->tail_object_offset + ALIGN64(tail_object_header->size); + + if (sz == tail) { + tail_waste->n_mules++; + + return 0; + } + + if (journal_header->state != STATE_ARCHIVED) { + printf("Ignoring %s of tail-waste on \"%s\" for not being archived (state=%s)\n", + humane_bytes(&h1, sz - tail), + (*journal)->name, + journal_state_str(journal_header->state)); + + tail_waste->n_ignored++; + tail_waste->ignored_bytes += sz - tail; + + return 0; + } + + if (ftruncate((*journal)->fd, tail) < 0) { + fprintf(stderr, "Unable to truncate \"%s\" to %"PRIu64", ignoring: %s\n", + (*journal)->name, + tail, + strerror(errno)); + + tail_waste->n_errored++; + tail_waste->errored_bytes += sz - tail; + + return 0; + } + + tail_waste->n_reclaimed++; + tail_waste->reclaimed_bytes += sz - tail; + + return 0; +} + + +THUNK_DEFINE_STATIC(per_journal_tail_waste, iou_t *, iou, journal_t **, journal_iter, tail_waste_t *, tail_waste) +{ + struct { + journal_t *journal; + Header header; + ObjectHeader tail_object_header; + } *foo; + + thunk_t *closure; + + assert(iou); + assert(journal_iter); + + closure = THUNK_ALLOC(reclaim_tail_waste, (void **)&foo, sizeof(*foo)); + foo->journal = *journal_iter; + + return journal_get_header(iou, &foo->journal, &foo->header, THUNK( + journal_get_object_header(iou, &foo->journal, &foo->header.tail_object_offset, &foo->tail_object_header, THUNK_INIT( + reclaim_tail_waste(closure, &foo->journal, &foo->header, &foo->tail_object_header, tail_waste))))); +} + + +/* print the size of wasted space between each journal's tail object and EOF, and a sum total. */ +int jio_reclaim_tail_waste(iou_t *iou, int argc, char *argv[]) +{ + char *machid; + journals_t *journals; + journal_t *journal_iter; + tail_waste_t tail_waste = {}; + int r; + humane_t h1; + + r = machid_get(iou, &machid, THUNK( + journals_open(iou, &machid, O_RDWR, &journals, THUNK( + journals_for_each(&journals, &journal_iter, THUNK( + per_journal_tail_waste(iou, &journal_iter, &tail_waste))))))); + if (r < 0) + return r; + + printf("\nReclaiming tail-waste...\n"); + r = iou_run(iou); + if (r < 0) + return r; + + printf("\nSummary:\n"); + if (!tail_waste.n_journals) + printf("\tNo journal files opened!\n"); + + if (tail_waste.n_mules) + printf("\tSkipped %u journal files free of tail-waste\n", + tail_waste.n_mules); + + if (tail_waste.n_ignored) + printf("\tIgnored %u unarchived journal files totalling %s of tail-waste\n", + tail_waste.n_ignored, + humane_bytes(&h1, tail_waste.ignored_bytes)); + + if (tail_waste.n_reclaimed) + printf("\tReclaimed %s from %u journal files\n", + humane_bytes(&h1, tail_waste.reclaimed_bytes), + tail_waste.n_reclaimed); + + if (tail_waste.n_errored) + printf("\tFailed to relcaim %s from %u journal files due to errors\n", + humane_bytes(&h1, tail_waste.errored_bytes), + tail_waste.n_errored); + + return 0; +} diff --git a/src/reclaim-tail-waste.h b/src/reclaim-tail-waste.h new file mode 100644 index 0000000..06e403e --- /dev/null +++ b/src/reclaim-tail-waste.h @@ -0,0 +1,8 @@ +#ifndef _JIO_RECLAIM_TAIL_WASTE +#define _JIO_RECLAIM_TAIL_WASTE + +typedef struct iou_t iou_t; + +int jio_reclaim_tail_waste(iou_t *iou, int argc, char *argv[]); + +#endif diff --git a/src/report-tail-waste.c b/src/report-tail-waste.c new file mode 100644 index 0000000..fdd52cb --- /dev/null +++ b/src/report-tail-waste.c @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <inttypes.h> +#include <stdio.h> +#include <stdint.h> + +#include <iou.h> +#include <thunk.h> + +#include "bootid.h" +#include "humane.h" +#include "journals.h" +#include "machid.h" +#include "report-tail-waste.h" + +#include "upstream/journal-def.h" + + +typedef struct tail_waste_t { + unsigned per_state_counts[_STATE_MAX]; + uint64_t per_state_bytes[_STATE_MAX]; + uint64_t total, total_file_size; + unsigned n_journals; +} tail_waste_t; + + +THUNK_DEFINE_STATIC(print_tail_waste, journal_t **, journal, Header *, journal_header, ObjectHeader *, tail_object_header, tail_waste_t *, tail_waste) +{ + uint64_t sz, tail; + struct stat st; + int r; + humane_t h1, h2; + + assert(journal); + assert(journal_header); + assert(tail_object_header); + assert(tail_waste); + + r = fstat((*journal)->fd, &st); + if (r < 0) + return r; + + sz = st.st_size; + tail = journal_header->tail_object_offset + ALIGN64(tail_object_header->size); + + printf("\t%s: %s, size: %s, tail-waste: %s\n", + journal_state_str(journal_header->state), + (*journal)->name, + humane_bytes(&h1, sz), + humane_bytes(&h2, sz - tail)); + + tail_waste->per_state_bytes[journal_header->state] += sz - tail; + tail_waste->total += sz - tail; + tail_waste->total_file_size += sz; + tail_waste->per_state_counts[journal_header->state]++; + tail_waste->n_journals++; + + return 0; +} + + +THUNK_DEFINE_STATIC(per_journal_tail_waste, iou_t *, iou, journal_t **, journal_iter, tail_waste_t *, tail_waste) +{ + struct { + journal_t *journal; + Header header; + ObjectHeader tail_object_header; + } *foo; + + thunk_t *closure; + + assert(iou); + assert(journal_iter); + + closure = THUNK_ALLOC(print_tail_waste, (void **)&foo, sizeof(*foo)); + foo->journal = *journal_iter; + + return journal_get_header(iou, &foo->journal, &foo->header, THUNK( + journal_get_object_header(iou, &foo->journal, &foo->header.tail_object_offset, &foo->tail_object_header, THUNK_INIT( + print_tail_waste(closure, &foo->journal, &foo->header, &foo->tail_object_header, tail_waste))))); +} + + +/* print the size of wasted space between each journal's tail object and EOF, and a sum total. */ +int jio_report_tail_waste(iou_t *iou, int argc, char *argv[]) +{ + tail_waste_t tail_waste = {}; + char *machid; + journals_t *journals; + journal_t *journal_iter; + humane_t h1, h2; + int r; + + r = machid_get(iou, &machid, THUNK( + journals_open(iou, &machid, O_RDONLY, &journals, THUNK( + journals_for_each(&journals, &journal_iter, THUNK( + per_journal_tail_waste(iou, &journal_iter, &tail_waste))))))); + if (r < 0) + return r; + + printf("\nPer-journal:\n"); + r = iou_run(iou); + if (r < 0) + return r; + + printf("\nTotals:\n"); + printf("\tTail-waste by state:\n"); + for (int i = 0; i < _STATE_MAX; i++) { + printf("\t\t%10s [%u]: %s, %"PRIu64"%% of all tail-waste\n", + journal_state_str(i), + tail_waste.per_state_counts[i], + humane_bytes(&h1, tail_waste.per_state_bytes[i]), + tail_waste.total >= 100 ? tail_waste.per_state_bytes[i] / (tail_waste.total / 100) : 0); + } + + printf("\n\tAggregate tail-waste: %s, %"PRIu64"%% of %s spanning %u journal files\n", + humane_bytes(&h1, tail_waste.total), + tail_waste.total_file_size >= 100 ? tail_waste.total / (tail_waste.total_file_size / 100) : 0, + humane_bytes(&h2, tail_waste.total_file_size), + tail_waste.n_journals); + + return 0; +} diff --git a/src/report-tail-waste.h b/src/report-tail-waste.h new file mode 100644 index 0000000..7810399 --- /dev/null +++ b/src/report-tail-waste.h @@ -0,0 +1,8 @@ +#ifndef _JIO_REPORT_TAIL_WASTE +#define _JIO_REPORT_TAIL_WASTE + +typedef struct iou_t iou_t; + +int jio_report_tail_waste(iou_t *iou, int argc, char *argv[]); + +#endif diff --git a/src/report-usage.c b/src/report-usage.c new file mode 100644 index 0000000..0cadeb7 --- /dev/null +++ b/src/report-usage.c @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <inttypes.h> +#include <stdint.h> +#include <stdio.h> + +#include <iou.h> +#include <thunk.h> + +#include "bootid.h" +#include "humane.h" +#include "journals.h" +#include "machid.h" +#include "report-usage.h" + +#include "upstream/journal-def.h" + + +typedef struct usage_t { + uint64_t count_per_type[_OBJECT_TYPE_MAX]; + uint64_t use_per_type[_OBJECT_TYPE_MAX]; + uint64_t use_total; + uint64_t file_size, file_count; +} usage_t; + + +THUNK_DEFINE_STATIC(per_data_object, ObjectHeader *, iter_object_header, usage_t *, usage, usage_t *, total_usage) +{ + assert(iter_object_header); + assert(usage); + assert(total_usage); + + usage->count_per_type[iter_object_header->type]++; + usage->use_per_type[iter_object_header->type] += iter_object_header->size; + usage->use_total += iter_object_header->size; + + total_usage->count_per_type[iter_object_header->type]++; + total_usage->use_per_type[iter_object_header->type] += iter_object_header->size; + total_usage->use_total += iter_object_header->size; + + return 0; +} + + +THUNK_DEFINE_STATIC(per_journal, iou_t *, iou, journal_t **, journal_iter, usage_t *, total_usage, unsigned *, n_journals) +{ + struct { + journal_t *journal; + Header header; + usage_t usage; + uint64_t iter_offset; + ObjectHeader iter_object_header; + } *foo; + + thunk_t *closure; + struct stat st; + int r; + + assert(iou); + assert(journal_iter); + assert(total_usage); + + /* XXX: io_uring has a STATX opcode, so this too could be async */ + r = fstat((*journal_iter)->fd, &st); + if (r < 0) + return r; + + closure = THUNK_ALLOC(per_data_object, (void **)&foo, sizeof(*foo)); + foo->journal = *journal_iter; + foo->usage.file_size = st.st_size; + + total_usage->file_size += st.st_size; + (*n_journals)++; + + return journal_get_header(iou, &foo->journal, &foo->header, THUNK( + journal_for_each(iou, &foo->journal, &foo->header, &foo->iter_offset, &foo->iter_object_header, THUNK_INIT( + per_data_object(closure, &foo->iter_object_header, &foo->usage, total_usage))))); +} + + +/* print the amount of space used by various objects per journal, and sum totals */ +int jio_report_usage(iou_t *iou, int argc, char *argv[]) +{ + usage_t aggregate_usage = {}; + char *machid; + journals_t *journals; + journal_t *journal_iter; + unsigned n_journals = 0; + humane_t h1, h2; + int r; + + r = machid_get(iou, &machid, THUNK( + journals_open(iou, &machid, O_RDONLY, &journals, THUNK( + journals_for_each(&journals, &journal_iter, THUNK( + per_journal(iou, &journal_iter, &aggregate_usage, &n_journals))))))); + if (r < 0) + return r; + + r = iou_run(iou); + if (r < 0) + return r; + + printf("Per-object-type usage:\n"); + for (int i = 0; i < _OBJECT_TYPE_MAX; i++) + printf("%16s: [%"PRIu64"] %s\n", + journal_object_type_str(i), + aggregate_usage.count_per_type[i], + humane_bytes(&h1, + aggregate_usage.use_per_type[i])); + + printf("Aggregate object usage: %s of %s spanning %u journal files\n", + humane_bytes(&h1, aggregate_usage.use_total), + humane_bytes(&h2, aggregate_usage.file_size), + n_journals); + + return 0; +} diff --git a/src/report-usage.h b/src/report-usage.h new file mode 100644 index 0000000..ce5a6e1 --- /dev/null +++ b/src/report-usage.h @@ -0,0 +1,8 @@ +#ifndef _JIO_REPORT_USAGE +#define _JIO_REPORT_USAGE + +typedef struct iou_t iou_t; + +int jio_report_usage(iou_t *iou, int argc, char *argv[]); + +#endif |