/* * Copyright (C) 2019 - Vito Caputo - * * 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 . */ #define FUSE_USE_VERSION 26 #include #include #include #include #include #include #include #include #ifndef MIN #define MIN(_a, _b) ((_a) < (_b)) ? (_a) : (_b) #endif static sd_journal *sdj; static int codex_getattr(const char *path, struct stat *stbuf) { int res = 0; memset(stbuf, 0, sizeof(struct stat)); if (strcmp(path, "/") == 0) { stbuf->st_mode = S_IFDIR | 0755; stbuf->st_nlink = 2; } else if (!strcmp(path, "/by-field")) { stbuf->st_mode = S_IFDIR | 0755; stbuf->st_nlink = 2; } else if (!strncmp(path, "/by-field/", 10)) { const char *value = strchr(&path[10], '/'); if (!value) { stbuf->st_mode = S_IFDIR | 0755; stbuf->st_nlink = 2; } else { stbuf->st_mode = S_IFREG | 0444; stbuf->st_nlink = 1; } } else if (!strcmp(path, "/by-boot")) { stbuf->st_mode = S_IFDIR | 0755; stbuf->st_nlink = 2; } else res = -ENOENT; return res; } /* TODO: this is a temp hack, there's no unescaping, it's the bare minimum * to avoid producing dentry names with slashes. */ static void escape(char *buf, size_t size) { if (!size) size = strlen(buf); for (size_t i = 0; i < size; i++) { if (buf[i] == '/') buf[i] = '-'; } } static void unescape(char *buf, size_t size) { if (!size) size = strlen(buf); for (size_t i = 0; i < size; i++) { if (buf[i] == '-') buf[i] = '/'; } } static int codex_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { if (!strcmp(path, "/by-field")) { const char *field; filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); SD_JOURNAL_FOREACH_FIELD(sdj, field) filler(buf, field, NULL, 0); } else if (!strncmp(path, "/by-field/", 10)) { char name[NAME_MAX]; const void *data; size_t size; filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); sd_journal_query_unique(sdj, &path[10]); SD_JOURNAL_FOREACH_UNIQUE(sdj, data, size) { const void *eq; eq = memchr(data, '=', size); if (eq) snprintf(name, sizeof(name), "%.*s", (int) (size - ((const uint8_t*) eq - (const uint8_t*) data + 1)), (const char*) eq + 1); else snprintf(name, sizeof(name), "%.*s", (int) size, (const char*) data); /* TODO: use proper escaping */ escape(name, strlen(name)); filler(buf, name, NULL, 0); } } else if (!strcmp(path, "/by-boot")) { filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); /* TODO: enumerate boots in a more readable fashion like journalctl --list-boots */ filler(buf, "IMPLEMENT ME", NULL, 0); } else if (!strcmp(path, "/")) { filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); filler(buf, "by-field", NULL, 0); filler(buf, "by-boot", NULL, 0); } else { return -ENOENT; } return 0; } static int codex_open(const char *path, struct fuse_file_info *fi) { if ((fi->flags & 3) != O_RDONLY) return -EACCES; return 0; } static int codex_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { static size_t data_len, data_off; static int needs_nl; const char *field, *value; char *data_ptr; int cnt = 0; if (strncmp(path, "/by-field/", 10)) return -ENOENT; field = &path[10]; value = strchr(field, '/'); if (!value) return -ENOENT; value++; /* This is all a dirty hack, zero offset is assumed to be new open. * XXX: no support for concurrent multiple file accesses or threding etc. */ if (!offset) { char buf[1024]; int r; sd_journal_flush_matches(sdj); sd_journal_seek_head(sdj); snprintf(buf, sizeof(buf), "%.*s=%s", (int)(value - field - 1), field, value); /* TODO: use proper escaping */ unescape(&buf[value - field], 0); /* XXX: overflow risk */ r = sd_journal_add_match(sdj, buf, 0); if (r < 0) return r; sd_journal_next(sdj); needs_nl = data_len = data_off = 0; } while (cnt < size) { size_t len; if (data_off >= data_len) { data_off = 0; while (!sd_journal_enumerate_data(sdj, (void *)&data_ptr, &data_len) || !data_len) { if (!sd_journal_next(sdj)) { data_len = 0; if (needs_nl) break; return cnt; } } } if (needs_nl) { buf[cnt++] = '\n'; needs_nl = 0; continue; } len = MIN(size - cnt, data_len - data_off); memcpy(&buf[cnt], &data_ptr[data_off], len); cnt += len; data_off += len; if (data_off == data_len) needs_nl = 1; } return cnt; } static struct fuse_operations codex_ops = { .getattr = codex_getattr, .readdir = codex_readdir, .open = codex_open, .read = codex_read, }; int main(int argc, char *argv[]) { int r; r = sd_journal_open(&sdj, 0); if (r < 0) { fprintf(stderr, "Unable to open sd-journal: %s", strerror(-r)); return EXIT_FAILURE; } puts("TODO/FIXME: Note these flags are required to work: -o direct_io -f -s"); return fuse_main(argc, argv, &codex_ops, NULL); }