diff --git a/src/pacman/conf.h b/src/pacman/conf.h index 2c4fddf0..8c53f9cc 100644 --- a/src/pacman/conf.h +++ b/src/pacman/conf.h @@ -59,6 +59,7 @@ typedef struct __config_t { unsigned short color; unsigned short disable_dl_timeout; unsigned short disable_sandbox; + unsigned short retry_input; char *print_format; /* unfortunately, we have to keep track of paths both here and in the library * because they can come from both the command line or config file, and we @@ -214,7 +215,8 @@ enum { OP_REFRESH, OP_ASSUMEINSTALLED, OP_DISABLEDLTIMEOUT, - OP_DISABLESANDBOX + OP_DISABLESANDBOX, + OP_RETRYINPUT }; /* clean method */ diff --git a/src/pacman/pacman.c b/src/pacman/pacman.c index 2866fc98..a4c829f1 100644 --- a/src/pacman/pacman.c +++ b/src/pacman/pacman.c @@ -228,6 +228,7 @@ static void usage(int op, const char * const myname) " use relaxed timeouts for download\n")); addlist(_(" --disable-sandbox\n" " disable the sandbox used for the downloader process\n")); + addlist(_(" --retry-prompt repeat prompts until a valid answer is supplied\n")); } list = alpm_list_msort(list, alpm_list_count(list), options_cmp); for(i = list; i; i = alpm_list_next(i)) { @@ -499,6 +500,9 @@ static int parsearg_global(int opt) case 'v': (config->verbose)++; break; + case OP_RETRYINPUT: + config->retry_input = 1; + break; default: return 1; } @@ -982,6 +986,7 @@ static int parseargs(int argc, char *argv[]) {"color", required_argument, 0, OP_COLOR}, {"disable-download-timeout", no_argument, 0, OP_DISABLEDLTIMEOUT}, {"disable-sandbox", no_argument, 0, OP_DISABLESANDBOX}, + {"retry-prompt", no_argument, 0, OP_RETRYINPUT}, {0, 0, 0, 0} }; diff --git a/src/pacman/po/de.po b/src/pacman/po/de.po index 1d5d9e12..146b0b54 100644 --- a/src/pacman/po/de.po +++ b/src/pacman/po/de.po @@ -1317,6 +1317,13 @@ msgid "" " --disable-sandbox\n" " disable the sandbox used for the downloader process\n" msgstr "" +" --disable-sandbox\n" +" shaltet Sandbox für den Download-Prozess aus\n" + +#: src/pacman/pacman.c:231 +#, c-format +msgid " --retry-prompt repeat prompts until a valid answer is supplied\n" +msgstr " --retry-prompt wiederholt Frage, bis eine valide Antwort gegeben wird\n" #: src/pacman/pacman.c:249 #, c-format @@ -1866,6 +1873,11 @@ msgstr "[J/n]" msgid "[y/N]" msgstr "[j/N]" +#: src/pacman/util.c:1741 +#, c-format +msgid "vsnprintf failure: Failed to cache prompt: %s\n" +msgstr "vsnprintf-fehler: Kann Frage nicht cachen: %s\n" + #: src/pacman/util.c:1751 #, c-format msgid "Y" @@ -1886,6 +1898,11 @@ msgstr "N" msgid "NO" msgstr "NEIN" +#: src/pacman/util.c:1769 +#, c-format +msgid "vsnprintf failure: Failed to cache prompt: Required size changed: %zu -> %i bytes\n" +msgstr "vsnprintf-fehler: Kann Frage nicht cachen: Benötigte Größe hat sich geändert: %zu -> %i bytes\n" + #: src/pacman/util.c:1820 #, c-format msgid "failed to allocate string\n" diff --git a/src/pacman/po/en_GB.po b/src/pacman/po/en_GB.po index 11664de1..0189ec22 100644 --- a/src/pacman/po/en_GB.po +++ b/src/pacman/po/en_GB.po @@ -1232,6 +1232,11 @@ msgstr "" " --disable-sandbox\n" " disable the sandbox used for the downloader process\n" +#: src/pacman/pacman.c:231 +#, c-format +msgid " --retry-prompt repeat prompts until a valid answer is supplied\n" +msgstr " --retry-prompt repeat prompts until a valid answer is supplied\n" + #: src/pacman/pacman.c:249 #, c-format msgid "" @@ -1777,6 +1782,10 @@ msgstr "[Y/n]" msgid "[y/N]" msgstr "[y/N]" +#: src/pacman/util.c:1741 src/pacman/util.c:1759 +msgid "vsnprintf failure: Failed to cache prompt: %s\n" +msgstr "vsnprintf failure: Failed to cache prompt: %s\n" + #: src/pacman/util.c:1751 #, c-format msgid "Y" @@ -1797,6 +1806,11 @@ msgstr "N" msgid "NO" msgstr "NO" +#: src/pacman/util.c:1767 +#, c-format +msgid "vsnprintf failure: Failed to cache prompt: Required size changed: %zu -> %i bytes\n" +msgstr "vsnprintf failure: Failed to cache prompt: Required size changed: %zu -> %i bytes\n" + #: src/pacman/util.c:1820 #, c-format msgid "failed to allocate string\n" diff --git a/src/pacman/util.c b/src/pacman/util.c index 3b96568d..d8803cf6 100644 --- a/src/pacman/util.c +++ b/src/pacman/util.c @@ -1706,10 +1706,19 @@ static int mbscasecmp(const char *s1, const char *s2) __attribute__((format(printf, 2, 0))) static int question(short preset, const char *format, va_list args) { + int return_code = 0; char response[32]; FILE *stream; int fd_in = fileno(stdin); + va_list args_copy; + int format_len; + /* No need to initialize: + * the other calls also print a termination character */ + char prompt_cache_stack[256]; + char *prompt_cache = prompt_cache_stack; + size_t prompt_cache_size = sizeof(prompt_cache_stack); + if(config->noconfirm) { stream = stdout; } else { @@ -1717,48 +1726,110 @@ static int question(short preset, const char *format, va_list args) stream = stderr; } - /* ensure all text makes it to the screen before we prompt the user */ - fflush(stdout); - fflush(stderr); + /* Cache prompt, because we can't call v*printf() multiple times + * on the same arguments. */ + /* Copy args so we can retry when the buffer is too small */ + va_copy(args_copy, args); - fputs(config->colstr.colon, stream); - vfprintf(stream, format, args); + /* Don't ask me why v*printf() returns an Integer instead of a long */ + format_len = vsnprintf( + prompt_cache, + prompt_cache_size, + format, args); - if(preset) { - fprintf(stream, " %s ", _("[Y/n]")); - } else { - fprintf(stream, " %s ", _("[y/N]")); + /* Something has gone wrong */ + if (format_len < 1) { + pm_printf(ALPM_LOG_ERROR, _("vsnprintf failure: Failed to cache prompt: %s\n"), strerror(errno)); + + goto free_args_copy; } - fputs(config->colstr.nocolor, stream); - fflush(stream); + /* Buffer is too small */ + if ((size_t)format_len + 1 > prompt_cache_size) { + prompt_cache = malloc(format_len + 1); + prompt_cache_size = format_len + 1; + if (prompt_cache == 0) { + pm_printf(ALPM_LOG_ERROR, _("malloc failure: could not allocate %i bytes\n"), format_len + 1); - if(config->noconfirm) { - fprintf(stream, "\n"); - return preset; + goto free_args_copy; + } + + format_len = vsnprintf(prompt_cache, prompt_cache_size, format, args_copy); + /* Something has gone wrong */ + if (format_len < 1) { + pm_printf(ALPM_LOG_ERROR, _("vsnprintf failure: Failed to cache prompt: %s\n"), strerror(errno)); + + goto free_args_copy; + } + + /* Just in case */ + if ((size_t)format + 1 != prompt_cache_size) { + pm_printf(ALPM_LOG_ERROR, _("vsnprintf failure: Failed to cache prompt: Required size changed: %zu -> %i bytes\n"), prompt_cache_size, format_len + 1); + + goto free_args_copy; + } } - flush_term_input(fd_in); + /* We can free the argument-copy now */ + va_end(args_copy); - if(safe_fgets_stdin(response, sizeof(response))) { - size_t len = strtrim(response); - if(len == 0) { + /* Prompt the user at least once before exiting */ + do { + /* Ensure all text makes it to the screen before we prompt the user */ + fflush(stdout); + fflush(stderr); + + fputs(config->colstr.colon, stream); + fputs(prompt_cache, stream); + + if(preset) { + fprintf(stream, " %s ", _("[Y/n]")); + } else { + fprintf(stream, " %s ", _("[y/N]")); + } + + fputs(config->colstr.nocolor, stream); + fflush(stream); + + if(config->noconfirm) { + fprintf(stream, "\n"); return preset; } - /* if stdin is piped, response does not get printed out, and as a result - * a \n is missing, resulting in broken output */ - if(!isatty(fd_in)) { - fprintf(stream, "%s\n", response); - } + flush_term_input(fd_in); - if(mbscasecmp(response, _("Y")) == 0 || mbscasecmp(response, _("YES")) == 0) { - return 1; - } else if(mbscasecmp(response, _("N")) == 0 || mbscasecmp(response, _("NO")) == 0) { - return 0; + if(safe_fgets_stdin(response, sizeof(response))) { + size_t len = strtrim(response); + if(len == 0) { + return_code = preset; + goto opt_free_cache; + } + + /* if stdin is piped, response does not get printed out, and as a result + * a \n is missing, resulting in broken output */ + if(!isatty(fd_in)) { + fprintf(stream, "%s\n", response); + } + + if(mbscasecmp(response, _("Y")) == 0 || mbscasecmp(response, _("YES")) == 0) { + return_code = 1; + goto opt_free_cache; + } else if(mbscasecmp(response, _("N")) == 0 || mbscasecmp(response, _("NO")) == 0) { + goto opt_free_cache; + } else + /* Emulate same behaviour as multiselect_question() and select_question() */ + fprintf(stream, _("invalid input: prompt requires `%s' or `%s'\n\n"), _("Y"), _("N")); } - } - return 0; + } while (config->retry_input); + +opt_free_cache: + if (prompt_cache != prompt_cache_stack) + free(prompt_cache); + return return_code; + +free_args_copy: + va_end(args_copy); + return return_code; } int yesno(const char *format, ...)