#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; #define FS_SHIFT 12 static const format_pair_t block_fs_units[] = { { "-si", UNIT_SI << FS_SHIFT }, { "-b", UNIT_B << FS_SHIFT }, { "-kib", UNIT_KB << FS_SHIFT }, { "-mib", UNIT_MB << FS_SHIFT }, { "-gib", UNIT_GB << FS_SHIFT }, { "-tib", UNIT_TB << FS_SHIFT }, { "-kb", (UNIT_KB | UNIT_SI) << FS_SHIFT }, { "-mb", (UNIT_MB | UNIT_SI) << FS_SHIFT }, { "-gb", (UNIT_GB | UNIT_SI) << FS_SHIFT }, { "-tb", (UNIT_TB | UNIT_SI) << FS_SHIFT }, { NULL }, }; static const format_option_t block_fs_options[] = { { { "total", FS_TOTAL }, .suffixes = block_fs_units }, { { "free", FS_FREE }, .suffixes = block_fs_units }, { { "used", FS_USED }, .suffixes = block_fs_units }, { { "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 info", "s:label", block->label, "s:path", fs->path, "i:errno", errno); return; } uint64_t total = sbuf.f_bsize * sbuf.f_blocks; uint64_t available = sbuf.f_bsize * sbuf.f_bavail; uint64_t used = sbuf.f_bsize * (sbuf.f_blocks - sbuf.f_bavail); uint64_t free_perc = (100 * sbuf.f_bavail) / sbuf.f_blocks; uint64_t used_perc = (100 * (sbuf.f_blocks - sbuf.f_bavail)) / sbuf.f_blocks; 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; unit_t mask = UNIT_MASK << FS_SHIFT; unit_t unit = (fs->format.parts[i].mark & mask) >> FS_SHIFT; switch (fs->format.parts[i].mark & ~mask) { case FS_STRING: start += snprintf(buffer + start, rest, "%s", fs->format.parts[i].string); break; case FS_TOTAL: start += snprintf_units(buffer + start, rest, total, unit); break; case FS_FREE: start += snprintf_units(buffer + start, rest, available, unit); break; case FS_USED: start += snprintf_units(buffer + start, rest, used, unit); break; case FS_FREE_PERC: start += snprintf(buffer + start, rest, "%ld", free_perc); break; case FS_USED_PERC: start += snprintf(buffer + start, rest, "%ld", used_perc); break; default: unreachable(); } } free(fs->block.text); fs->block.text = strcopy(buffer); assert(fs->block.text != NULL); } static void block_fs_init(block_t *block) { extern const block_scheme_t block_text_scheme; block_text_scheme.init_fn(block); block->update_interval = block_fs_interval; block->update_fn = block_fs_update; } 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 int block_fs_validate(block_t *block, config_t *config) { block_fs_t *fs = (block_fs_t *)block; int errors = 0; if (fs->block.text == NULL) { log_error("Block '%s' requires key '%s'", block->label, "text"); errors++; } if (!format_init(&fs->format, fs->block.text, '%')) { log_error("Block '%s' has an invalid format", block->label); errors++; } else { int marked = format_remark(&fs->format, block->label, block_fs_options); if (marked < 0) errors += -marked; else if (marked > 0) block->update_fn = block_fs_update; else { log_warn("Block '%s' does not use any 'fs' option", block->label); if (block->update_fn != NULL) { block->update_fn = NULL; log_debug("Disabled updates for block '%s'", block->label); } } } 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); errors++; } return errors; } 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, .size = sizeof(block_fs_t), .init_fn = block_fs_init, .clean_fn = block_fs_clean, .validate_fn = block_fs_validate, };