/* SPDX-License-Identifier: GPL-2.0 */ /** \file lsu.c Utilities related to the lopsub library. */ #include #include "para.h" #include "error.h" #include "string.h" #include "lsu.h" #include "fd.h" static void lsu_error(const char *etype, char **errctx, char **result, unsigned *num_chars) { /* *errctx is NULL if the lopsub function failed to allocate memory. */ unsigned n = xasprintf(result, "lopsub %s error: %s\n", etype, *errctx? *errctx : "out of memory"); free(*errctx); *errctx = NULL; if (num_chars) *num_chars = n; } static void lsu_get_subcommand_summary(bool long_summary, const struct lls_suite *suite, const char *(*aux_info_cb)(unsigned cmd_num, bool verbose), char **result, unsigned *num_chars) { int i; const struct lls_command *cmd; const char *name, *aux_info = NULL; struct para_buffer pb = {.max_size = 0 /* unlimited */}; para_printf(&pb, "Available subcommands:\n"); if (long_summary) { int maxname = 0, max_aux_info = 0; for (i = 1; (cmd = lls_cmd(i, suite)); i++) { maxname = PARA_MAX(maxname, (int)strlen(lls_command_name(cmd))); if (aux_info_cb) { aux_info = aux_info_cb(i, false); if (!aux_info) continue; max_aux_info = PARA_MAX(max_aux_info, (int)strlen(aux_info)); } } for (i = 1; (cmd = lls_cmd(i, suite)); i++) { if (aux_info_cb) aux_info = aux_info_cb(i, false); para_printf(&pb, "%-*s %-*s %s\n", maxname, lls_command_name(cmd), max_aux_info, aux_info? aux_info : "", lls_purpose(cmd)); } } else { unsigned n = 8; para_printf(&pb, "\t"); for (i = 1; (cmd = lls_cmd(i, suite)); i++) { name = lls_command_name(cmd); if (i > 1) n += para_printf(&pb, ", "); if (n > 70) { para_printf(&pb, "\n\t"); n = 8; } n += para_printf(&pb, "%s", name); } para_printf(&pb, "\n"); } *result = pb.buf; if (num_chars) *num_chars = pb.offset; } /** * A generic implementation of the help subcommand. * * This function returns the help text for the given subcommand, or the list of * all subcommands if no non-option argument is given. The function is generic * in that it works for arbitrary lopsub suites. * * \param long_help Applies to both command list and command help. * \param suite The supercommand, if any, is omitted. * \param lpr Used to determine whether a non-option argument is given. * \param aux_info_cb Optional callback, may return NULL, static memory. * \param result Must be freed by the caller. * \param num_chars Initialized to the length of the returned string, optional. * * If the optional aux_info_cb function pointer is not NULL, the callback * function must return the string representation of the aux_info structure of * the given command, or NULL to indicate that this command has no aux info * structure. * * The function fails if lpr has more than one non-option argument, or if there * is exactly one non-option argument, but this argument is not the name of a * subcommand in the given lopsub suite. * * \return Standard. In the failure case a suitable error message is returned * via the result pointer and num_chars is set accordingly. */ int lsu_com_help(bool long_help, const struct lls_parse_result *lpr, const struct lls_suite *suite, const char *(*aux_info_cb)(unsigned cmd_num, bool verbose), char **result, unsigned *num_chars) { int ret; unsigned n; char *errctx, *tmp; const char *arg, *aux_info = NULL; const struct lls_command *cmd; ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx)); if (ret < 0) { lsu_error("arg count", &errctx, result, num_chars); return ret; } if (lls_num_inputs(lpr) == 0) { lsu_get_subcommand_summary(long_help, suite, aux_info_cb, result, num_chars); return 0; } arg = lls_input(0, lpr); ret = lls(lls_lookup_subcmd(arg, suite, &errctx)); if (ret < 0) { lsu_error("lookup", &errctx, result, num_chars); return ret; } cmd = lls_cmd(ret, suite); tmp = long_help? lls_long_help(cmd) : lls_short_help(cmd); if (aux_info_cb) aux_info = aux_info_cb(ret, true); n = xasprintf(result, "%s%s%s", tmp, aux_info? aux_info : "", aux_info? "\n" : ""); free(tmp); if (num_chars) *num_chars = n; return 1; } /** * Merge command line options and config file options. * * This function parses the options stored in the configuration file and merges * them with the currently effective options. If the application supports * config files, it is supposed to call this after the command line options * have been parsed. If the application also supports config file reloading, * the function will be called for that purpose. * * \param path Config file path, usually the argument to --config-file. * \param dflt Relative to ~/.paraslash, ignored if path is not NULL. * \param lpr Value-result pointer. * \param cmd Passed to lls_parse() and lls_merge(). * \param suite Needed to tell whether cmd is the supercommand. * \param flags See enum \ref lsu_merge_cf_flags. * * The function does nothing if path is NULL and the default config file does * not exist, or if path is an empty file. Otherwise, the options of the config * file are parsed, the parse result is merged with lpr, and the merged parse * result is returned via lpr. * * By default, lpr is freed if the merge was done, but this can be changed by * including MCF_DONT_FREE flags. * * \return Zero if there was nothing to do, one if the config file options were * merged successfully, negative error code on failure. It is considered an error * if path is given, but the file does not exist. */ int lsu_merge_config_file_options(const char *path, const char *dflt, struct lls_parse_result **lpr, const struct lls_command *cmd, const struct lls_suite *suite, unsigned flags) { int ret; void *map; size_t sz; int cf_argc; char *cf, **cf_argv, *errctx = NULL; struct lls_parse_result *old_lpr = *lpr, *cf_lpr, *merged_lpr; const char *subcmd_name; if (path) cf = para_strdup(path); else { char *home = para_homedir(); cf = make_message("%s/.paraslash/%s", home, dflt); free(home); } ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL); if (ret < 0) { if (ret == -E_EMPTY) ret = 0; else if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) && !path) ret = 0; else PARA_ERROR_LOG("failed to mmap config file %s\n", cf); goto free_cf; } subcmd_name = (lls_cmd(0, suite) == cmd)? NULL : lls_command_name(cmd); ret = lls(lls_convert_config(map, sz, subcmd_name, &cf_argv, &errctx)); para_munmap(map, sz); if (ret < 0) { PARA_ERROR_LOG("failed to convert config file %s\n", cf); goto lopsub_error; } cf_argc = ret; ret = lls(lls_parse(cf_argc, cf_argv, cmd, &cf_lpr, &errctx)); lls_free_argv(cf_argv); if (ret < 0) { PARA_ERROR_LOG("failed to parse config file %s\n", cf); goto lopsub_error; } if (flags & MCF_OVERRIDE) ret = lls(lls_merge(cf_lpr, old_lpr, cmd, &merged_lpr, &errctx)); else ret = lls(lls_merge(old_lpr, cf_lpr, cmd, &merged_lpr, &errctx)); lls_free_parse_result(cf_lpr, cmd); if (ret < 0) { PARA_ERROR_LOG("could not merge options in %s\n", cf); goto lopsub_error; } if (!(flags & MCF_DONT_FREE)) lls_free_parse_result(old_lpr, cmd); *lpr = merged_lpr; ret = 1; goto free_cf; lopsub_error: assert(ret < 0); if (errctx) PARA_ERROR_LOG("lopsub error: %s\n", errctx); free(errctx); free_cf: free(cf); return ret; }