From: Andre Noll Date: Tue, 1 Jul 2008 19:26:26 +0000 (+0200) Subject: Merge commit 'fml/master' X-Git-Tag: v0.0.4~39 X-Git-Url: http://git.tuebingen.mpg.de/?a=commitdiff_plain;h=cecbe8525740b4a899cb1a6c91064e195ae1ada2;hp=ad33ad0384a415100220ca5edc32111037a1c0c0;p=adu.git Merge commit 'fml/master' Conflicts: Makefile --- diff --git a/.gitignore b/.gitignore index 743f60e..617d85d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ *.[oa] foo* bar* -cmdline.[ch] +*cmdline.[ch] GPATH GRTAGS GSYMS diff --git a/Makefile b/Makefile index 5e425da..b595c68 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,12 @@ -objects := adu.o string.o cmdline.o fd.o select.o create.o +objects := adu.o string.o cmdline.o fd.o select.o create.o interactive.o select.cmdline.o all: adu +version := 0.0.3 DEBUG_CPPFLAGS += -Wno-sign-compare -g -Wunused -Wundef -W DEBUG_CPPFLAGS += -Wredundant-decls -#CPPFLAGS += -Os +CPPFLAGS += -Os CPPFLAGS += -Wall -#CPPFLAGS += -Wuninitialized +CPPFLAGS += -Wuninitialized CPPFLAGS += -Wchar-subscripts CPPFLAGS += -Wformat-security CPPFLAGS += -Werror-implicit-function-declaration @@ -14,6 +15,7 @@ CPPFLAGS += -Wunused-macros CPPFLAGS += -Wbad-function-cast CPPFLAGS += -D_LARGEFILE64_SOURCE CPPFLAGS += $(shell getconf LFS64_CFLAGS) +CPPFLAGS += -DVERSION='"$(version)"' LDFLAGS += -D_LARGEFILE64_SOURCE LDFLAGS += $(shell getconf LFS64_LDFLAGS) @@ -38,5 +40,18 @@ cmdline.c cmdline.h: adu.ggo %.o: %.c Makefile $(CC) -c $(CPPFLAGS) $(DEBUG_CPPFLAGS) $< +select.cmdline.c select.cmdline.h: select.ggo + gengetopt --string-parser \ + --set-package=select \ + --no-handle-help \ + --no-handle-error \ + --no-handle-version \ + --arg-struct-name=select_args_info \ + --file-name=$(subst .ggo,,$<).cmdline \ + --func-name $(subst .ggo,,$<)_cmdline_parser < $< + +select.cmdline.o: select.cmdline.c select.cmdline.h + $(CC) -c $(CPPFLAGS) $< + clean: rm -f *.o adu diff --git a/adu.c b/adu.c index db7a564..02bfa30 100644 --- a/adu.c +++ b/adu.c @@ -8,6 +8,7 @@ #include "string.h" #include "error.h" #include "portable_io.h" +#include "select.cmdline.h" DEFINE_ERRLIST; int osl_errno; @@ -18,6 +19,9 @@ static int signum; /** Command line and config file options. */ struct gengetopt_args_info conf; +/** Options passed to --select-options. */ +struct select_args_info select_conf; + /** The number of different uids found so far. */ uint32_t num_uids = 0; @@ -48,12 +52,6 @@ static inline int ui_admissible(struct user_info *ui) */ struct osl_table *dir_table = NULL; -/** - * The array of all uid ranges that were given at the command line. - */ -struct uid_range *admissible_uids; - - /** * Compare the size of two directories * @@ -166,61 +164,6 @@ static struct osl_column_description user_table_cols[] = { }, }; -static int check_uid_arg(const char *arg, uint32_t *uid) -{ - const uint32_t max = ~0U; - /* - * we need an 64-bit int for string -> uid conversion because strtoll() - * returns a signed value. - */ - int64_t val; - int ret = atoi64(arg, &val); - - if (ret < 0) - return ret; - if (val < 0 || val > max) - return -ERRNO_TO_ERROR(EINVAL); - *uid = val; - return 1; -} - -static int parse_uid_range(const char *orig_arg, struct uid_range *ur) -{ - int ret; - char *arg = adu_strdup(orig_arg), *p = strchr(arg, '-'); - - if (!p || p == arg) { /* -42 or 42 */ - ret = check_uid_arg(p? p + 1 : arg, &ur->high); - if (ret < 0) - goto out; - ur->low = p? 0 : ur->high; - ret = 1; - goto out; - } - /* 42- or 42-4711 */ - *p = '\0'; - p++; - ret = check_uid_arg(arg, &ur->low); - if (ret < 0) - goto out; - ur->high = ~0U; - if (*p) { /* 42-4711 */ - ret = check_uid_arg(p, &ur->high); - if (ret < 0) - goto out; - } - if (ur->low > ur->high) - ret = -ERRNO_TO_ERROR(EINVAL); -out: - if (ret < 0) - ERROR_LOG("bad uid option: %s\n", orig_arg); - else - INFO_LOG("admissible uid range: %u - %u\n", ur->low, - ur->high); - free(arg); - return ret; -} - /** * The log function. * @@ -418,24 +361,25 @@ static uint32_t double_hash(uint32_t uid, uint32_t probe_num) % uid_hash_table_size; } -static int uid_is_admissible(uint32_t uid) +static int uid_is_admissible(uint32_t uid, struct uid_range *urs) { - int i; - - for (i = 0; i < conf.uid_given; i++) { - struct uid_range *ur = admissible_uids + i; + struct uid_range *ur; + int ret = 1; + if (!urs) /* empty array means all uids are allowed */ + return 1; + FOR_EACH_UID_RANGE(ur, urs) if (ur->low <= uid && ur->high >= uid) - break; - } - i = !conf.uid_given || i < conf.uid_given; + goto out; + ret = 0; +out: DEBUG_LOG("uid %u is %sadmissible\n", (unsigned)uid, - i? "" : "not "); - return i; + ret? "" : "not "); + return ret; } -int search_uid(uint32_t uid, enum search_uid_flags flags, - struct user_info **ui_ptr) +int search_uid(uint32_t uid, struct uid_range *urs, + enum search_uid_flags flags, struct user_info **ui_ptr) { uint32_t p; @@ -448,7 +392,7 @@ int search_uid(uint32_t uid, enum search_uid_flags flags, return -E_BAD_UID; ui->uid = uid; ui->flags |= UI_FL_SLOT_USED; - if (!uid_is_admissible(uid)) + if (!uid_is_admissible(uid, urs)) return 0; ui->flags |= UI_FL_ADMISSIBLE; ret = open_user_table(ui, flags & CREATE_USER_TABLE); @@ -495,9 +439,6 @@ int open_dir_table(int create) static int check_args(void) { - int i, ret; - - if (conf.create_given && !conf.base_dir_given) return -E_SYNTAX; @@ -514,19 +455,7 @@ static int check_args(void) conf.base_dir_arg[len] = '\0'; } } - if (!conf.uid_given) - return 0; - admissible_uids = adu_malloc(conf.uid_given * sizeof(*admissible_uids)); - for (i = 0; i < conf.uid_given; i++) { - ret = parse_uid_range(conf.uid_arg[i], admissible_uids + i); - if (ret < 0) - goto err; - } return 1; -err: - free(admissible_uids); - admissible_uids = NULL; - return ret; } int main(int argc, char **argv) @@ -550,12 +479,13 @@ int main(int argc, char **argv) ret = -E_SYNTAX; if (conf.select_given) ret = com_select(); - else + else if (conf.create_given) ret = com_create(); + else + ret = com_interactive(); if (ret < 0) goto out; out: - free(admissible_uids); if (ret < 0) { ERROR_LOG("%s\n", adu_strerror(-ret)); return -EXIT_FAILURE; diff --git a/adu.ggo b/adu.ggo index a46de25..11a5d00 100644 --- a/adu.ggo +++ b/adu.ggo @@ -3,7 +3,6 @@ # Licensed under the GPL v2. For licencing details see COPYING. package "adu" -version "0.0.2" purpose "advanced disk usage adu creates a database containing disk usage statistics of a given @@ -50,25 +49,6 @@ details=" goes to stdout. Lower values mean more verbose logging. " -option "uid" u -#~~~~~~~~~~~~~ -"user id(s) to take into account" -string typestr="uid_spec" -optional -multiple -details=" - An uid specifier may be a single number, or a range of uids. - Example: - - --uid 42 # only consider uid 42 - --uid 42- # only consider uids greater or equal than 42 - --uid 23-42 # only consider uids between 23 and 42, inclusively. - - This option may be given multiple times. An uid is taken into - account if it satisfies at least one --uid option. -" - - option "paths" p #~~~~~~~~~~~~~~~ "files to take into account" @@ -107,6 +87,14 @@ details=" that directory. " +groupoption "interactive" I +#~~~~~~~~~~~~~~~~~~~~~~~~~~ +"activate interactive mode" +group="mode" +details=" + In this mode, adu reads commands from stdin. +" + groupoption "select" S #~~~~~~~~~~~~~~~~~~~~~ "query a database previously created with --create" @@ -158,123 +146,12 @@ details=" users. Decreasing the value causes adu to use slightly less memory. " - -############################## -section "Options for --select" -############################## - -option "limit" L -#~~~~~~~~~~~~~~~ -"Limit output" -int typestr="num" -default="-1" -optional -dependon="select" -details=" - Only print num lines of output. If negative (the default), - print all lines. -" - -option "size-unit" - -#~~~~~~~~~~~~~~~~~~~ -"select output format for sizes" -enum typestr="format" -values="h","b","k","m","g","t" -default="h" -optional -dependon="select" -details=" - Print sizes in the given unit: human-readable, bytes, - kilobytes (2^10), megabytes (2^20), gigabytes (2^30), terabytes - (2^40). The default is \"h\", i.e. human-readable. -" - -option "count-unit" - -#~~~~~~~~~~~~~~~~~~~~ -"select output format for counted values" -enum typestr="format" -values="h","n","k","m","g","t" -default="h" -optional -dependon="select" -details=" - Print the number of files/directories in the given unit: - human-readable, number, number/10^3, number/10^6, number/10^12, - number/10^15. The default is to print numbers in human-readable - format. -" - -option "print-base-dir" - -#~~~~~~~~~~~~~~~~~~~~~~~~ -"whether to include the base-dir in the output" -flag off -details=" - If this flag is given, all directories printed are prefixed - with the base directory. The default is to print paths relative - to the base dir. -" - -option "no-headers" - -#~~~~~~~~~~~~~~~~~~~~ -"supress descriptions for listings/tables" -flag off -dependon="select" -details=" - This is mostly useful to feed the output of adu to scripts. -" - -option "global-list" - -#~~~~~~~~~~~~~~~~~~~~~ -"how to print global directory listings" -enum typestr="which" -values="size","file_count","both","none" -default="both" -optional -dependon="select" -details=" - By default adu prints two global directory listings: The - first prints the directory names ordered by the sum of the - sizes of the contained files while the second listing prints - them sorted by the number of files. This option can be used - to print only one or neither of these two listings. -" - -option "no-global-summary" - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~ -"do not print the summary line" -flag off -dependon="select" - -option "user-list" - -#~~~~~~~~~~~~~~~~~~~ -"how to print per-user directory listings" -enum typestr="which" -values="size","file_count","both","none" -default="both" -optional -dependon="select" -details=" - Similar to the global directory listings mentioned above, - adu can print two directory listings per user. This option - controls which of the these should be printed. -" - -option "no-user-summary" - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~ -"do not print the user summary table" -flag off -dependon="select" - - -option "user-summary-sort" - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~ -"how to sort the user-summary" -enum typestr="col_spec" -values="name","uid","dir_count","file_count","size" -default="size" +option "select-options" s +#~~~~~~~~~~~~~~~~~~~~~~~~~ +"Options for select mode" +string typestr="" optional dependon="select" details=" - It is enough to specify the first letter of the column specifier, - e.g. \"--user-summary-sort f\" sorts by file count. + Try --select-options \"-h\" " diff --git a/adu.h b/adu.h index debada6..2a15457 100644 --- a/adu.h +++ b/adu.h @@ -173,9 +173,12 @@ enum search_uid_flags { CREATE_USER_TABLE = 2, }; +#define FOR_EACH_UID_RANGE(ur, urs) for (ur = urs; ur->low <= ur->high; ur++) + extern uint32_t num_uids; extern struct osl_table *dir_table; extern struct gengetopt_args_info conf; +extern struct select_args_info select_conf; /* adu.c */ __printf_2_3 void __log(int, const char*, ...); @@ -184,14 +187,16 @@ void check_signals(void); void close_all_tables(void); char *get_uid_list_name(void); void create_hash_table(unsigned bits); -int search_uid(uint32_t uid, enum search_uid_flags flags, - struct user_info **ui_ptr); +int search_uid(uint32_t uid, struct uid_range *urs, + enum search_uid_flags flags, struct user_info **ui_ptr); int for_each_admissible_user(int (*func)(struct user_info *, void *), void *data); void sort_hash_table(int (*comp)(const void *, const void *)); /* select.c */ +int run_select_query(struct uid_range *admissible_uids); int com_select(void); /* create.h */ int com_create(void); +int com_interactive(void); diff --git a/create.c b/create.c index 47c7155..9b26f72 100644 --- a/create.c +++ b/create.c @@ -30,8 +30,6 @@ static int write_uid(struct user_info *ui, void *data) static int write_uid_list(void) { char *buf, *p, *filename; - uint32_t count = 0; - struct user_info *ui; size_t size = num_uids * sizeof(uint32_t); int ret; @@ -163,7 +161,7 @@ static int scan_dir(char *dirname, uint64_t *parent_dir_num) dir_size += size; dir_files++; uid = s.st_uid; - ret = search_uid(uid, CREATE_USER_TABLE | OPEN_USER_TABLE, &ui); + ret = search_uid(uid, NULL, CREATE_USER_TABLE | OPEN_USER_TABLE, &ui); if (ret < 0) goto out; ui->bytes += size; @@ -183,7 +181,7 @@ out: return ret; } -int com_create() +int com_create(void) { uint64_t zero = 0ULL; int ret; diff --git a/error.h b/error.h index 7c3f4aa..895f9f8 100644 --- a/error.h +++ b/error.h @@ -25,6 +25,7 @@ _ERROR(MMAP, "mmap error") \ _ERROR(OSL, "osl error") \ _ERROR(SIGNAL_SIG_ERR, "signal() returned SIG_ERR") \ + _ERROR(OUTPUT, "error writing output file") \ /** diff --git a/interactive.c b/interactive.c new file mode 100644 index 0000000..494a4f8 --- /dev/null +++ b/interactive.c @@ -0,0 +1,130 @@ +#include "adu.h" +#include "string.h" +#include "error.h" +#include "cmdline.h" +#include "select.cmdline.h" + +struct interactive_command { + const char *name; + int (*handler)(char *); + const char *desc; +}; + +static struct uid_range *admissible_uids; + +#define INTERACTIVE_COMMANDS \ + INTERACTIVE_COMMAND(dump, "dump the current configuration") \ + INTERACTIVE_COMMAND(set, "change the current configuration") \ + INTERACTIVE_COMMAND(reset, "reset configuration to defaults") \ + INTERACTIVE_COMMAND(help, "show list of commands and one-line descriptions") \ + INTERACTIVE_COMMAND(run, "start the query according to the current options") + + +#define INTERACTIVE_COMMAND(name, desc) \ + static int icom_ ## name (char *line); + +INTERACTIVE_COMMANDS + +#undef INTERACTIVE_COMMAND + +#define INTERACTIVE_COMMAND(_name, _desc) \ + { \ + .name = #_name, \ + .handler = icom_ ## _name, \ + .desc = _desc \ + }, + +struct interactive_command icmds[] = { + INTERACTIVE_COMMANDS + {.name = NULL} +}; + +#define FOR_EACH_COMMAND(c) for (c = icmds; c->name; c++) + +static int read_input_line(char *line, size_t size) +{ + return fgets(line, size, stdin)? 1 : -1; +} + +static int icom_run(__a_unused char *line) +{ + return run_select_query(admissible_uids); +} + +static int icom_help(__a_unused char *line) +{ + struct interactive_command *c; + + FOR_EACH_COMMAND(c) + fprintf(stdout, "%s\t%s\n", c->name, c->desc); + return 1; +} + +static int icom_reset(__a_unused char *line) +{ + select_cmdline_parser_init(&select_conf); + return 1; +} + +static int icom_set(char *line) +{ + struct select_cmdline_parser_params params = { + .override = 1, + .initialize = 0, + .check_required = 1, + .check_ambiguity = 0, + .print_errors = 1 + }; + if (select_cmdline_parser_string_ext(line, &select_conf, "select", + ¶ms)) + return -E_SYNTAX; + return parse_uid_arg(select_conf.uid_arg, &admissible_uids); +} + +static int icom_dump(__a_unused char *line) +{ + ERROR_LOG("dump: %s\n", select_conf.format_arg); + select_cmdline_parser_dump(stdout, &select_conf); + return 1; +} + +static int exec_interactive_command(char *line) +{ + const char const *delim = "\t\n "; + int i; + char *cmd = adu_strdup(line + strspn(line, delim)); + char *p = cmd + strcspn(cmd, delim); + int ret = -E_SYNTAX; + + *p = '\0'; + p++; + for (i = 0; icmds[i].name; i++) { + ERROR_LOG("name: %s, cmd: %s.\n", icmds[i].name, cmd); + if (strcmp(icmds[i].name, cmd)) + continue; + ERROR_LOG("exec cmd: %s, args: %s\n", cmd, p); + ret = icmds[i].handler(p); + break; + } + free(cmd); + return ret; +} + +int com_interactive(void) +{ + char line[255]; + int ret = 1; + + select_cmdline_parser_init(&select_conf); + while (read_input_line(line, sizeof(line)) >= 0) { + size_t len = strlen(line); + if (!len) + continue; + if (line[len - 1] == '\n') + line[len - 1] = '\0'; + ret = exec_interactive_command(line); + if (ret < 0) + printf("%s\n", adu_strerror(-ret)); + } + return ret; +} diff --git a/select.c b/select.c index 4c2f12c..0530257 100644 --- a/select.c +++ b/select.c @@ -14,6 +14,7 @@ #include "string.h" #include "error.h" #include "portable_io.h" +#include "select.cmdline.h" /** Global dir count. */ static uint64_t num_dirs; @@ -76,7 +77,6 @@ static const uint64_t count_unit_divisors[] = { static const char size_unit_abbrevs[] = " BKMGT"; static const char count_unit_abbrevs[] = " kmgt"; - static enum enum_size_unit format_size_value(enum enum_size_unit unit, uint64_t value, int print_unit, char *result) { @@ -111,6 +111,19 @@ static enum enum_count_unit format_count_value(enum enum_count_unit unit, return u; } +static FILE *output_file; + +__printf_1_2 static int output(const char const *fmt, ...) +{ + va_list argp; + int ret; + + va_start(argp, fmt); + ret = vfprintf(output_file, fmt, argp); + va_end(argp); + return ret < 0? -E_OUTPUT : 1; +} + static int get_dir_name_by_number(uint64_t *dirnum, char **name) { char *result = NULL, *tmp; @@ -133,7 +146,7 @@ again: ret = osl(osl_get_object(dir_table, row, DT_NAME, &obj)); if (ret < 0) goto out; - pfx = (conf.print_base_dir_given || val)? (char *)obj.data : "."; + pfx = (select_conf.print_base_dir_given || val)? (char *)obj.data : "."; tmp = make_message("%s/%s", pfx, result? result : ""); free(result); result = tmp; @@ -181,13 +194,14 @@ static int user_stats_loop_function(struct osl_row *row, void *data) goto err; files = *(uint64_t *)obj.data; if (usi->count && (usi->flags & USF_PRINT_FILES)) { - format_count_value(conf.count_unit_arg, files, - conf.count_unit_arg == count_unit_arg_h, + format_count_value(select_conf.count_unit_arg, files, + select_conf.count_unit_arg == count_unit_arg_h, formated_value); - printf("\t%s%s", formated_value, + ret = output("\t%s%s", formated_value, (usi->flags & (USF_PRINT_BYTES | USF_PRINT_DIRNAME))? - "\t" : "\n" - ); + "\t" : "\n"); + if (ret < 0) + goto err; } if (summary) usi->ui->files += files; @@ -199,14 +213,16 @@ static int user_stats_loop_function(struct osl_row *row, void *data) goto err; bytes = *(uint64_t *)obj.data; if (usi->count && (usi->flags & USF_PRINT_BYTES)) { - format_size_value(conf.size_unit_arg, bytes, - conf.size_unit_arg == size_unit_arg_h, + format_size_value(select_conf.size_unit_arg, bytes, + select_conf.size_unit_arg == size_unit_arg_h, formated_value); - printf("%s%s%s", + ret = output("%s%s%s", (usi->flags & USF_PRINT_FILES)? "" : "\t", formated_value, usi->flags & USF_PRINT_DIRNAME? "\t" : "\n" ); + if (ret < 0) + goto err; } if (summary) { usi->ui->bytes += bytes; @@ -222,10 +238,12 @@ static int user_stats_loop_function(struct osl_row *row, void *data) ret = get_dir_name_by_number((uint64_t *)obj.data, &dirname); if (ret < 0) goto err; - printf("%s%s\n", + ret = output("%s%s\n", (usi->flags & (USF_PRINT_BYTES | USF_PRINT_FILES))? "" : "\t", dirname); free(dirname); + if (ret < 0) + goto err; } if (usi->count > 0) usi->count--; @@ -255,12 +273,14 @@ static int global_stats_loop_function(struct osl_row *row, void *data) goto err; files = *(uint64_t *)obj.data; if (gsi->count && (gsi->flags & GSF_PRINT_FILES)) { - format_count_value(conf.count_unit_arg, files, - conf.count_unit_arg == count_unit_arg_h, + format_count_value(select_conf.count_unit_arg, files, + select_conf.count_unit_arg == count_unit_arg_h, formated_value); - printf("\t%s%s", formated_value, + ret = output("\t%s%s", formated_value, (gsi->flags & (GSF_PRINT_BYTES | GSF_PRINT_DIRNAME))? "\t" : "\n"); + if (ret < 0) + goto err; } if (summary) num_files += files; @@ -272,14 +292,16 @@ static int global_stats_loop_function(struct osl_row *row, void *data) goto err; bytes = *(uint64_t *)obj.data; if (gsi->count && (gsi->flags & GSF_PRINT_BYTES)) { - format_size_value(conf.size_unit_arg, bytes, - conf.size_unit_arg == size_unit_arg_h, + format_size_value(select_conf.size_unit_arg, bytes, + select_conf.size_unit_arg == size_unit_arg_h, formated_value); - printf("%s%s%s", + ret = output("%s%s%s", (gsi->flags & GSF_PRINT_FILES)? "" : "\t", formated_value, (gsi->flags & GSF_PRINT_DIRNAME)? "\t" : "\n" ); + if (ret < 0) + goto err; } if (summary) { num_bytes += bytes; @@ -290,10 +312,12 @@ static int global_stats_loop_function(struct osl_row *row, void *data) ret = get_dir_name_of_row(row, &dirname); if (ret < 0) goto err; - printf("%s%s\n", + ret = output("%s%s\n", (gsi->flags & (GSF_PRINT_BYTES | GSF_PRINT_FILES))? "" : "\t", dirname); free(dirname); + if (ret < 0) + goto err; } if (gsi->count > 0) gsi->count--; @@ -329,27 +353,31 @@ static int adu_loop_reverse(struct osl_table *t, unsigned col_num, void *private return check_loop_return(ret, *loop_ret, *loop_osl_errno); } -static void print_global_summary(void) +static int print_global_summary(void) { char d[FORMATED_VALUE_SIZE], f[FORMATED_VALUE_SIZE], s[FORMATED_VALUE_SIZE]; enum enum_count_unit ud, uf; enum enum_size_unit us; + int ret; - if (conf.no_global_summary_given) - return; - ud = format_count_value(conf.count_unit_arg, num_dirs, 0, d); - uf = format_count_value(conf.count_unit_arg, num_files, 0, f); - us = format_size_value(conf.size_unit_arg, num_bytes, 0, s); + if (select_conf.no_global_summary_given) + return 1; + ud = format_count_value(select_conf.count_unit_arg, num_dirs, 0, d); + uf = format_count_value(select_conf.count_unit_arg, num_files, 0, f); + us = format_size_value(select_conf.size_unit_arg, num_bytes, 0, s); - if (!conf.no_headers_given) - printf("Global summary " + if (!select_conf.no_headers_given) { + ret = output("Global summary " "(dirs(%c)/files(%c)/size(%c))\n", count_unit_abbrevs[ud], count_unit_abbrevs[uf], size_unit_abbrevs[us] ); - printf("\t%s\t%s\t%s\n\n", d, f, s); + if (ret < 0) + return ret; + } + return output("\t%s\t%s\t%s\n\n", d, f, s); } static int print_user_summary_line(struct user_info *ui, __a_unused void *data) @@ -358,23 +386,22 @@ static int print_user_summary_line(struct user_info *ui, __a_unused void *data) formated_file_count[FORMATED_VALUE_SIZE], formated_bytes[FORMATED_VALUE_SIZE ]; - format_count_value(conf.count_unit_arg, ui->dirs, - conf.count_unit_arg == count_unit_arg_h, + format_count_value(select_conf.count_unit_arg, ui->dirs, + select_conf.count_unit_arg == count_unit_arg_h, formated_dir_count); - format_count_value(conf.count_unit_arg, ui->files, - conf.count_unit_arg == count_unit_arg_h, + format_count_value(select_conf.count_unit_arg, ui->files, + select_conf.count_unit_arg == count_unit_arg_h, formated_file_count); - format_size_value(conf.size_unit_arg, ui->bytes, - conf.size_unit_arg == size_unit_arg_h, + format_size_value(select_conf.size_unit_arg, ui->bytes, + select_conf.size_unit_arg == size_unit_arg_h, formated_bytes); - printf("\t%s\t%u\t%s\t%s\t%s\n", + return output("\t%s\t%u\t%s\t%s\t%s\n", ui->pw_name? ui->pw_name : "?", (unsigned)ui->uid, formated_dir_count, formated_file_count, formated_bytes ); - return 1; } static int name_comp(const void *a, const void *b) @@ -427,56 +454,69 @@ static int (*summary_comparators[])(const void *, const void *) = { [user_summary_sort_arg_size] = size_comp, }; -static void print_user_summary(void) +static int print_user_summary(void) { - if (conf.no_user_summary_given) - return; - if (!conf.no_headers_given) - printf("User summary " + if (select_conf.no_user_summary_given) + return 1; + if (!select_conf.no_headers_given) { + int ret = output("User summary " "(pw_name/uid/dirs%s/files%s/size%s):\n", count_unit_buf, count_unit_buf, size_unit_buf); - sort_hash_table(summary_comparators[conf.user_summary_sort_arg]); - for_each_admissible_user(print_user_summary_line, NULL); + if (ret < 0) + return ret; + } + sort_hash_table(summary_comparators[select_conf.user_summary_sort_arg]); + return for_each_admissible_user(print_user_summary_line, NULL); } static int print_user_list(struct user_info *ui, __a_unused void *data) { int ret; struct user_stats_info usi; - enum enum_user_list ula = conf.user_list_arg; + enum enum_user_list ula = select_conf.user_list_arg; int print_size_list = (ula == user_list_arg_size || ula == user_list_arg_both); if (print_size_list) { - usi.count = conf.limit_arg; + usi.count = select_conf.limit_arg; usi.ui = ui; usi.flags = USF_PRINT_DIRNAME | USF_PRINT_BYTES | USF_COMPUTE_SUMMARY; - if (!conf.no_headers_given) - printf("%s (uid %u), by size%s:\n", + if (!select_conf.no_headers_given) { + ret = output("%s (uid %u), by size%s:\n", ui->pw_name? ui->pw_name : "?", (unsigned)ui->uid, size_unit_buf); + if (ret < 0) + return ret; + } ret = adu_loop_reverse(ui->table, UT_BYTES, &usi, user_stats_loop_function, &usi.ret, &usi.osl_errno); if (ret < 0) return ret; - printf("\n"); + ret = output("\n"); + if (ret < 0) + return ret; } if (ula == user_list_arg_file_count || ula == user_list_arg_both) { - if (!conf.no_headers_given) - printf("%s (uid %u), by file count%s:\n", + if (!select_conf.no_headers_given) { + ret = output("%s (uid %u), by file count%s:\n", ui->pw_name? ui->pw_name : "?", (unsigned)ui->uid, count_unit_buf); - usi.count = conf.limit_arg, + if (ret < 0) + return ret; + } + usi.count = select_conf.limit_arg, usi.ui = ui; usi.flags = USF_PRINT_DIRNAME | USF_PRINT_FILES; ret = adu_loop_reverse(ui->table, UT_FILES, &usi, user_stats_loop_function, &usi.ret, &usi.osl_errno); if (ret < 0) return ret; - printf("\n"); + ret = output("\n"); + if (ret < 0) + return ret; } - if (ula == user_list_arg_none && !conf.no_user_summary_given) { - usi.count = conf.limit_arg; + if (ula == user_list_arg_none && !select_conf.no_user_summary_given) { + usi.count = select_conf.limit_arg; usi.ui = ui; usi.flags = USF_COMPUTE_SUMMARY; ret = adu_loop_reverse(ui->table, UT_FILES, &usi, user_stats_loop_function, @@ -496,37 +536,47 @@ static int print_global_lists(void) { struct global_stats_info gsi; int ret; - enum enum_global_list gla = conf.global_list_arg; + enum enum_global_list gla = select_conf.global_list_arg; int print_size_list = (gla == global_list_arg_size || gla == global_list_arg_both); if (print_size_list) { - gsi.count = conf.limit_arg; + gsi.count = select_conf.limit_arg; gsi.flags = GSF_PRINT_DIRNAME | GSF_PRINT_BYTES | GSF_COMPUTE_SUMMARY; - if (!conf.no_headers_given) - printf("By size%s:\n", size_unit_buf); + if (!select_conf.no_headers_given) { + ret = output("By size%s:\n", size_unit_buf); + if (ret < 0) + return ret; + } ret = adu_loop_reverse(dir_table, DT_BYTES, &gsi, global_stats_loop_function, &gsi.ret, &gsi.osl_errno); if (ret < 0) return ret; - printf("\n"); + ret = output("\n"); + if (ret < 0) + return ret; } if (gla == global_list_arg_file_count || gla == global_list_arg_both) { - gsi.count = conf.limit_arg; + gsi.count = select_conf.limit_arg; gsi.flags = GSF_PRINT_DIRNAME | GSF_PRINT_FILES; if (!print_size_list) gsi.flags |= GSF_COMPUTE_SUMMARY; - if (!conf.no_headers_given) - printf("By file count%s:\n", count_unit_buf); + if (!select_conf.no_headers_given) { + ret = output("By file count%s:\n", count_unit_buf); + if (ret < 0) + return ret; + } ret = adu_loop_reverse(dir_table, DT_FILES, &gsi, global_stats_loop_function, &gsi.ret, &gsi.osl_errno); if (ret < 0) return ret; - printf("\n"); + ret = output("\n"); + if (ret < 0) + return ret; } - if (gla == global_list_arg_none && !conf.no_global_summary_given) { + if (gla == global_list_arg_none && !select_conf.no_global_summary_given) { /* must compute summary */ - gsi.count = conf.limit_arg; + gsi.count = select_conf.limit_arg; gsi.flags = GSF_COMPUTE_SUMMARY; ret = adu_loop_reverse(dir_table, DT_FILES, &gsi, global_stats_loop_function, &gsi.ret, &gsi.osl_errno); @@ -543,13 +593,19 @@ static int print_statistics(void) ret = print_global_lists(); if (ret < 0) return ret; - print_global_summary(); - print_user_lists(); - print_user_summary(); + ret = print_global_summary(); + if (ret < 0) + return ret; + ret = print_user_lists(); + if (ret < 0) + return ret; + ret = print_user_summary(); + if (ret < 0) + return ret; return 1; } -static int read_uid_file(void) +static int read_uid_file(struct uid_range *admissible_uids) { size_t size; uint32_t n; @@ -575,7 +631,7 @@ static int read_uid_file(void) create_hash_table(bits); for (n = 0; n < num_uids; n++) { uint32_t uid = read_u32(map + n * sizeof(uid)); - ret = search_uid(uid, OPEN_USER_TABLE, NULL); + ret = search_uid(uid, admissible_uids, OPEN_USER_TABLE, NULL); if (ret < 0) goto out; } @@ -584,28 +640,62 @@ out: return ret; } -int com_select(void) +int run_select_query(struct uid_range *admissible_uids) { int ret; - if (conf.count_unit_arg != count_unit_arg_h) - count_unit_buf[1] = count_unit_abbrevs[conf.count_unit_arg]; + if (select_conf.output_given && strcmp(select_conf.output_arg, "-")) { + output_file = fopen(select_conf.output_arg, "w"); + if (!output_file) + return -ERRNO_TO_ERROR(errno); + } else + output_file = stdout; + + if (select_conf.count_unit_arg != count_unit_arg_h) + count_unit_buf[1] = count_unit_abbrevs[select_conf.count_unit_arg]; else count_unit_buf[0] = '\0'; - if (conf.size_unit_arg != size_unit_arg_h) - size_unit_buf[1] = size_unit_abbrevs[conf.size_unit_arg]; + if (select_conf.size_unit_arg != size_unit_arg_h) + size_unit_buf[1] = size_unit_abbrevs[select_conf.size_unit_arg]; else size_unit_buf[0] = '\0'; ret = open_dir_table(0); if (ret < 0) - return ret; + goto out; check_signals(); - ret = read_uid_file(); + ret = read_uid_file(admissible_uids); if (ret < 0) - return ret; + goto out; check_signals(); ret = print_statistics(); +out: close_all_tables(); + if (output_file != stdout) + fclose(output_file); return ret; } + +int com_select(void) +{ + int ret; + struct uid_range *admissible_uids = NULL; + + if (conf.select_options_given) { + struct select_cmdline_parser_params params = { + .override = 1, + .initialize = 1, + .check_required = 1, + .check_ambiguity = 1, + .print_errors = 1 + }; + + if (select_cmdline_parser_string_ext(conf.select_options_arg, + &select_conf, "select", ¶ms)) + return -E_SYNTAX; + ret = parse_uid_arg(select_conf.uid_arg, &admissible_uids); + if (ret < 0) + return ret; + } + return run_select_query(admissible_uids); +} diff --git a/select.ggo b/select.ggo new file mode 100644 index 0000000..f4d090b --- /dev/null +++ b/select.ggo @@ -0,0 +1,156 @@ + +option "uid" u +#~~~~~~~~~~~~~ +"user id(s) to take into account" +string typestr="uid_spec" +optional +details=" + An uid specifier may be a single number, or a range of uids. + Example: + + --uid 42 # only consider uid 42 + --uid 42- # only consider uids greater or equal than 42 + --uid 23-42 # only consider uids between 23 and 42, inclusively. + --uid 23-42,666-777,88 # consider uids 23-42, 666-777 and 88. +" + +option "limit" L +#~~~~~~~~~~~~~~~ +"Limit output" +int typestr="num" +default="-1" +optional +details=" + Only print num lines of output. If negative (the default), + print all lines. +" + +option "no-headers" - +#~~~~~~~~~~~~~~~~~~~~ +"supress descriptions for listings/tables" +flag off +details=" + This is mostly useful to feed the output of adu to scripts. +" + +option "sort" s +#~~~~~~~~~~~~~~~ +"how to sort the output" +enum typestr="" +values="sizes","files","unsorted" +default="sizes" +optional +details=" + Sort by file size, file count or unsorted. +" + +option "format" f +#~~~~~~~~~~~~~~~~~ +"how to format the output" +string typestr="" +optional +details=" + %(basedir) -- the path given to --base-dir during create + %(dir) -- the name of the directory + %(dir_size) -- the size of the sum of all regular files in this directory + %(num_files) -- the number of regular files in this directory + %% -- interpolates to % + %xx -- interpolates to the character with hex code xx +" + +option "output" o +#~~~~~~~~~~~~~~~~ +"file to write output to" +string typestr="" +optional +default="-" +details=" + If empty, or not given, use stdout. +" + +option "size-unit" - +#~~~~~~~~~~~~~~~~~~~ +"select output format for sizes" +enum typestr="format" +values="h","b","k","m","g","t" +default="h" +optional +details=" + Print sizes in the given unit: human-readable, bytes, + kilobytes (2^10), megabytes (2^20), gigabytes (2^30), terabytes + (2^40). The default is \"h\", i.e. human-readable. +" + +option "count-unit" - +#~~~~~~~~~~~~~~~~~~~~ +"select output format for counted values" +enum typestr="format" +values="h","n","k","m","g","t" +default="h" +optional +details=" + Print the number of files/directories in the given unit: + human-readable, number, number/10^3, number/10^6, number/10^12, + number/10^15. The default is to print numbers in human-readable + format. +" + + +option "user-summary-sort" - +#~~~~~~~~~~~~~~~~~~~~~~~~~~~ +"how to sort the user-summary" +enum typestr="col_spec" +values="name","uid","dir_count","file_count","size" +default="size" +optional +details=" + It is enough to specify the first letter of the column specifier, + e.g. \"--user-summary-sort f\" sorts by file count. +" + +option "no-user-summary" - +#~~~~~~~~~~~~~~~~~~~~~~~~~~~ +"do not print the user summary table" +flag off + + +option "user-list" - +#~~~~~~~~~~~~~~~~~~~ +"how to print per-user directory listings" +enum typestr="which" +values="size","file_count","both","none" +default="both" +optional +details=" + Similar to the global directory listings mentioned above, + adu can print two directory listings per user. This option + controls which of the these should be printed. +" +option "no-global-summary" - +#~~~~~~~~~~~~~~~~~~~~~~~~~~~ +"do not print the summary line" +flag off + +option "global-list" - +#~~~~~~~~~~~~~~~~~~~~~ +"how to print global directory listings" +enum typestr="which" +values="size","file_count","both","none" +default="both" +optional +details=" + By default adu prints two global directory listings: The + first prints the directory names ordered by the sum of the + sizes of the contained files while the second listing prints + them sorted by the number of files. This option can be used + to print only one or neither of these two listings. +" +option "print-base-dir" - +#~~~~~~~~~~~~~~~~~~~~~~~~ +"whether to include the base-dir in the output" +flag off +details=" + If this flag is given, all directories printed are prefixed + with the base directory. The default is to print paths relative + to the base dir. +" diff --git a/string.c b/string.c index a0666d3..87f9c26 100644 --- a/string.c +++ b/string.c @@ -166,3 +166,137 @@ __must_check int atoi64(const char *str, int64_t *result) *result = tmp; return 1; } + +/** + * Split string and return pointers to its parts. + * + * \param args The string to be split. + * \param argv_ptr Pointer to the list of substrings. + * \param delim Delimiter. + * + * This function modifies \a args by replacing each occurance of \a delim by + * zero. A \p NULL-terminated array of pointers to char* is allocated dynamically + * and these pointers are initialized to point to the broken-up substrings + * within \a args. A pointer to this array is returned via \a argv_ptr. + * + * \return The number of substrings found in \a args. + */ +__must_check unsigned split_args(char *args, char *** const argv_ptr, const char *delim) +{ + char *p = args; + char **argv; + size_t n = 0, i, j; + + p = args + strspn(args, delim); + for (;;) { + i = strcspn(p, delim); + if (!i) + break; + p += i; + n++; + p += strspn(p, delim); + } + *argv_ptr = adu_malloc((n + 1) * sizeof(char *)); + argv = *argv_ptr; + i = 0; + p = args + strspn(args, delim); + while (p) { + argv[i] = p; + j = strcspn(p, delim); + if (!j) + break; + p += strcspn(p, delim); + if (*p) { + *p = '\0'; + p++; + p += strspn(p, delim); + } + i++; + } + argv[n] = NULL; + return n; +} + +static int check_uid_arg(const char *arg, uint32_t *uid) +{ + const uint32_t max = ~0U; + /* + * we need an 64-bit int for string -> uid conversion because strtoll() + * returns a signed value. + */ + int64_t val; + int ret = atoi64(arg, &val); + + if (ret < 0) + return ret; + if (val < 0 || val > max) + return -ERRNO_TO_ERROR(EINVAL); + *uid = val; + return 1; +} + +int parse_uid_range(const char *orig_arg, struct uid_range *ur) +{ + int ret; + char *arg = adu_strdup(orig_arg), *p = strchr(arg, '-'); + + if (!p || p == arg) { /* -42 or 42 */ + ret = check_uid_arg(p? p + 1 : arg, &ur->high); + if (ret < 0) + goto out; + ur->low = p? 0 : ur->high; + ret = 1; + goto out; + } + /* 42- or 42-4711 */ + *p = '\0'; + p++; + ret = check_uid_arg(arg, &ur->low); + if (ret < 0) + goto out; + ur->high = ~0U; + if (*p) { /* 42-4711 */ + ret = check_uid_arg(p, &ur->high); + if (ret < 0) + goto out; + } + if (ur->low > ur->high) + ret = -ERRNO_TO_ERROR(EINVAL); +out: + if (ret < 0) + ERROR_LOG("bad uid option: %s\n", orig_arg); + else + INFO_LOG("admissible uid range: %u - %u\n", ur->low, + ur->high); + free(arg); + return ret; +} + +int parse_uid_arg(const char *orig_arg, struct uid_range **ur) +{ + char *arg, **argv; + unsigned n; + int i, ret = 1; + + if (!orig_arg) + return 0; + arg = adu_strdup(orig_arg); + n = split_args(arg, &argv, ","); + if (!n) + return -E_SYNTAX; + *ur = adu_malloc((n + 1) * sizeof(struct uid_range)); + for (i = 0; i < n; i++) { + ret = parse_uid_range(argv[i], *ur + i); + if (ret < 0) + break; + } + free(arg); + if (ret < 0) { + free(*ur); + *ur = NULL; + } + /* an empty range indicates the end of the list */ + (*ur)[n].low = 1; + (*ur)[n].high = 0; + return n; +} diff --git a/string.h b/string.h index ebf3b17..7229b7d 100644 --- a/string.h +++ b/string.h @@ -12,3 +12,4 @@ __must_check __malloc void *adu_calloc(size_t size); __must_check __malloc char *adu_strdup(const char *s); __must_check __malloc __printf_1_2 char *make_message(const char *fmt, ...); __must_check int atoi64(const char *str, int64_t *result); +int parse_uid_arg(const char *orig_arg, struct uid_range **ur);