#include #include #include #include #include #include #include "../block.h" #include "../format.h" #include "../any_log.h" typedef struct { block_text_t block; format_t format; char *path; } block_fs_t; typedef enum { FS_STRING = 0, FS_TOTAL, FS_FREE, FS_USED, FS_FREE_PERC, FS_USED_PERC, } block_fs_mark_t; static const format_option_t block_fs_options[] = { { { "total", FS_TOTAL } }, { { "free", FS_FREE } }, { { "used", FS_USED } }, { { "free-percentage", FS_FREE_PERC } }, { { "used-percentage", FS_USED_PERC } }, { { NULL } }, }; static const struct timespec block_fs_interval = { .tv_sec = 20, .tv_nsec = 0, }; static void block_fs_update(block_t *block) { block_fs_t *fs = (block_fs_t *)block; struct statvfs sbuf; if (statvfs(fs->path, &sbuf) < 0) { log_value_debug("Failed to read filesystem", "s:label", block->label, "s:path", fs->path, "i:errno", errno); return; } char buffer[256] = { 0 }; size_t start = 0, size = sizeof(buffer); for (size_t i = 0; i < fs->format.length; i++) { size_t rest = start >= size ? 0 : size - start; if (rest == 0) break; switch (fs->format.parts[i].mark) { case FS_STRING: start += snprintf(buffer + start, rest, "%s", fs->format.parts[i].string); break; case FS_TOTAL: start += snprintf(buffer + start, rest, "%ld", sbuf.f_bsize * (uint64_t)sbuf.f_blocks); break; case FS_FREE: start += snprintf(buffer + start, rest, "%ld", sbuf.f_bsize * (uint64_t)sbuf.f_bavail); break; case FS_USED: start += snprintf(buffer + start, rest, "%ld", sbuf.f_bsize * (sbuf.f_blocks - sbuf.f_bavail)); break; case FS_FREE_PERC: start += snprintf(buffer + start, rest, "%ld", (100 * sbuf.f_bavail) / sbuf.f_blocks); break; case FS_USED_PERC: start += snprintf(buffer + start, rest, "%ld", (100 * (sbuf.f_blocks - sbuf.f_bavail)) / sbuf.f_blocks); break; default: unreachable(); } } free(fs->block.text); fs->block.text = strcopy(buffer); assert(fs->block.text != NULL); } static block_t *block_fs_alloc(const block_scheme_t *scheme) { block_t *block = calloc(1, sizeof(block_fs_t)); block->type = BLOCK_TEXT; block->update_interval = block_fs_interval; block->update_fn = block_fs_update; return block; } static void block_fs_clean(block_t *block) { block_fs_t *fs = (block_fs_t *)block; format_free(&fs->format); free(fs->path); free(fs->block.text); } static bool block_fs_validate(block_t *block, const block_scheme_t *scheme) { block_fs_t *fs = (block_fs_t *)block; if (fs->block.text == NULL) { log_error("Block '%s' requires key '%s'", block->label, "text"); return false; } if (!format_init(&fs->format, fs->block.text, '%')) { log_error("Block '%s' has an invalid format", block->label); return false; } int marked = format_remark(&fs->format, block->label, block_fs_options); if (marked < 0) return false; if (marked == 0) { log_warn("Block '%s' does not use any fs option", block->label); block->update_fn = NULL; log_debug("Disabled updates for block '%s'", block->label); return true; } struct stat sbuf; if (fs->path == NULL || stat(fs->path, &sbuf) < 0) { log_trace("Failed to read file '%s': %s", fs->path, strerror(errno)); log_error("Block '%s' was given an invalid path '%s'", block->label, fs->path); return false; } return true; } static const config_entry_t block_fs_entries[] = { { "path", CONFIG_STRING, NULL, offsetof(block_fs_t, path) }, { 0 }, }; const block_scheme_t block_fs_scheme = { .name = "fs", .entries = block_fs_entries, .alloc_fn = block_fs_alloc, .clean_fn = block_fs_clean, .validate_fn = block_fs_validate, .resolve_fn = NULL, };