Last active
October 9, 2025 00:29
-
-
Save robertkirkman/31ae9a5992afd700e93ac15e374e6feb to your computer and use it in GitHub Desktop.
Standalone update-alternatives.c for termux-pacman
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * update-alternatives | |
| * | |
| * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk> | |
| * Copyright © 2000-2002 Wichert Akkerman <wakkerma@debian.org> | |
| * Copyright © 2006-2017 Guillem Jover <guillem@debian.org> | |
| * Copyright © 2008 Pierre Habouzit <madcoder@debian.org> | |
| * Copyright © 2009-2010 Raphaël Hertzog <hertzog@debian.org> | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License as published by | |
| * the Free Software Foundation; either version 2 of the License, or | |
| * (at your option) any later version. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program. If not, see <https://www.gnu.org/licenses/>. | |
| */ | |
| /* BEGIN: Termux-removed: dpkg/utils headers */ | |
| /* | |
| #include <config.h> | |
| #include <compat.h> | |
| */ | |
| /* END: Termux-removed: dpkg/utils headers */ | |
| #include <sys/types.h> | |
| #include <sys/time.h> | |
| #include <sys/stat.h> | |
| #include <sys/wait.h> | |
| #include <errno.h> | |
| #include <stdarg.h> | |
| #include <stdbool.h> | |
| #include <stdlib.h> | |
| #include <stdio.h> | |
| #include <unistd.h> | |
| #include <string.h> | |
| #include <dirent.h> | |
| #include <time.h> | |
| #include <setjmp.h> | |
| #include <assert.h> | |
| #include <locale.h> | |
| #include <ctype.h> | |
| #include <limits.h> | |
| /* BEGIN: Termux-removed: dpkg/lib/dpkg headers */ | |
| /* | |
| #include <dpkg/macros.h> | |
| #include <dpkg/i18n.h> | |
| */ | |
| /* END: Termux-removed: dpkg/lib/dpkg headers */ | |
| /* BEGIN: Termux-added: imported/rewritten macros copied and pasted from elsewhere */ | |
| /* absolute paths spelled out verbatim for clarity and so they can be easily */ | |
| /* replaced with '@TERMUX_PREFIX@', etc. when translating this .c file to a .c.in file */ | |
| #define SYSCONFDIR "/data/data/com.termux/files/usr/etc" | |
| #define _(str) ((const char *) (str)) | |
| #define VERSION "1.22.6-dirty" | |
| #define LIBCOMPAT_ATTR_NORET __attribute__((__noreturn__)) | |
| #define LIBCOMPAT_ATTR_FMT(t, f, a) __attribute__((__format__(t, f, a))) | |
| #define LIBCOMPAT_ATTR_PRINTF(n) LIBCOMPAT_ATTR_FMT(__printf__, n, n + 1) | |
| #define LIBCOMPAT_ATTR_VPRINTF(n) LIBCOMPAT_ATTR_FMT(__printf__, n, 0) | |
| #define INSTDIR_ENVVAR "DPKG_ROOT" | |
| /* (originally this was "/data/data/com.termux/files/usr/var/lib/dpkg") */ | |
| #define ADMINDIR "/data/data/com.termux/files/usr/var/lib/pacman" | |
| #define LIBCOMPAT_ATTR_ENUM_FLAGS __attribute__((__enum_extensibility__(closed),__flag_enum__)) | |
| #define ALT_TMP_EXT ".dpkg-tmp" | |
| #define max(a, b) ((a) > (b) ? (a) : (b)) | |
| #define P_(Msgid1, Msgid2, N) ((N) == 1 ? (const char *)(Msgid1) : (const char *)(Msgid2)) | |
| #define VARBUF_INIT { 0, 0, NULL } | |
| #define DPKG_ATTR_FMT(t, f, a) __attribute__((__format__(t, f, a))) | |
| #define DPKG_ATTR_PRINTF(n) DPKG_ATTR_FMT(__printf__, n, n + 1) | |
| #define DPKG_ATTR_VPRINTF(n) DPKG_ATTR_FMT(__printf__, n, 0) | |
| #define NCALLS 2 | |
| #define DPKG_ATTR_NORET __attribute__((__noreturn__)) | |
| #define COLOR_RESET "\e[0m" | |
| #define COLOR_BOLD "\e[1m" | |
| #define COLOR_BOLD_RED "\e[1;31m" | |
| #define COLOR_PROG COLOR_BOLD | |
| #define COLOR_ERROR COLOR_BOLD_RED | |
| #define internerr(...) do_internerr(__FILE__, __LINE__, __func__, __VA_ARGS__) | |
| #define varbuf_add_str(v, s) varbuf_add_buf(v, s, strlen(s)) | |
| #define DPKG_BIT(n) (1UL << (n)) | |
| #define DPKG_ATTR_ENUM_FLAGS __attribute__((__enum_extensibility__(closed),__flag_enum__)) | |
| #define array_count(a) (sizeof(a) / sizeof((a)[0])) | |
| #define LOGDIR "/data/data/com.termux/files/usr/var/log" | |
| #define ADMINDIR_ENVVAR "DPKG_ADMINDIR" | |
| #define bindtextdomain(Domainname, Dirname) ((void) (Domainname), (const char *) (Dirname)) | |
| #define textdomain(Domainname) ((const char *) (Domainname)) | |
| /* (originally this was "dpkg", also PACKAGE and LOCALEDIR do nothing in Termux) */ | |
| #define PACKAGE "update-alternatives-standalone" | |
| #define LOCALEDIR "/data/data/com.termux/files/usr/share/locale" | |
| /* END: Termux-added: imported/rewritten macros copied and pasted from elsewhere */ | |
| /* BEGIN: Termux-added: imported/rewritten types copied and pasted from elsewhere */ | |
| typedef void error_handler_func(void); | |
| typedef void error_printer_func(const char *emsg, const void *data); | |
| /* END: Termux-added: imported/rewritten types copied and pasted from elsewhere */ | |
| /* BEGIN: Termux-added: imported/rewritten enums copied and pasted from elsewhere */ | |
| enum color_mode { | |
| COLOR_MODE_UNKNOWN = -1, | |
| COLOR_MODE_NEVER, | |
| COLOR_MODE_ALWAYS, | |
| COLOR_MODE_AUTO, | |
| }; | |
| enum { | |
| ehflag_normaltidy = DPKG_BIT(0), | |
| ehflag_bombout = DPKG_BIT(1), | |
| ehflag_recursiveerror = DPKG_BIT(2), | |
| }; | |
| enum DPKG_ATTR_ENUM_FLAGS subproc_flags { | |
| SUBPROC_NORMAL = 0, | |
| SUBPROC_WARN = DPKG_BIT(0), | |
| SUBPROC_NOPIPE = DPKG_BIT(1), | |
| SUBPROC_NOCHECK = DPKG_BIT(2), | |
| SUBPROC_RETERROR = DPKG_BIT(3), | |
| SUBPROC_RETSIGNO = DPKG_BIT(3), | |
| }; | |
| /* END: Termux-added: imported/rewritten enums copied and pasted from elsewhere */ | |
| /* BEGIN: Termux-added: imported/rewritten structs copied and pasted from elsewhere */ | |
| struct varbuf { | |
| size_t used, size; | |
| char *buf; | |
| }; | |
| struct cleanup_entry { | |
| struct cleanup_entry *next; | |
| struct { | |
| int mask; | |
| void (*call)(int argc, void **argv); | |
| } calls[NCALLS]; | |
| int cpmask, cpvalue; | |
| int argc; | |
| void *argv[1]; | |
| }; | |
| struct error_context { | |
| struct error_context *next; | |
| enum { | |
| HANDLER_TYPE_FUNC, | |
| HANDLER_TYPE_JUMP, | |
| } handler_type; | |
| union { | |
| error_handler_func *func; | |
| jmp_buf *jump; | |
| } handler; | |
| struct { | |
| error_printer_func *func; | |
| const void *data; | |
| } printer; | |
| struct cleanup_entry *cleanups; | |
| char *errmsg; | |
| }; | |
| struct emergency_struct { | |
| struct cleanup_entry ce; | |
| void *args[20]; | |
| char errmsg[4096]; | |
| }; | |
| struct command { | |
| const char *name; | |
| const char *filename; | |
| int argc; | |
| int argv_size; | |
| const char **argv; | |
| }; | |
| /* END: Termux-added: imported/rewritten structs copied and pasted from elsewhere */ | |
| /* BEGIN: Termux-added: imported/rewritten globals copied and pasted from elsewhere */ | |
| static struct error_context *volatile econtext = NULL; | |
| static volatile int onerr_abort = 0; | |
| static enum color_mode color_mode = COLOR_MODE_UNKNOWN; | |
| static bool use_color = false; | |
| static const char *progname = "update-alternatives"; | |
| static struct emergency_struct emergency; | |
| /* END: Termux-added: imported/rewritten globals copied and pasted from elsewhere */ | |
| // Termux-added: forward declaration to use update-alternatives warning() in place of libdpkg warning() only | |
| static void LIBCOMPAT_ATTR_PRINTF(1) warning(char const *fmt, ...); | |
| /* BEGIN: Termux-added: imported/rewritten functions copied and pasted from elsewhere */ | |
| static bool | |
| file_is_exec(const char *filename) | |
| { | |
| struct stat st; | |
| if (stat(filename, &st) < 0) | |
| return false; | |
| if (!S_ISREG(st.st_mode)) | |
| return false; | |
| return st.st_mode & 0111; | |
| } | |
| static void | |
| error_context_errmsg_free(struct error_context *ec) | |
| { | |
| if (ec->errmsg != emergency.errmsg) | |
| free(ec->errmsg); | |
| ec->errmsg = NULL; | |
| } | |
| static void | |
| error_context_errmsg_set(struct error_context *ec, char *errmsg) | |
| { | |
| error_context_errmsg_free(ec); | |
| ec->errmsg = errmsg; | |
| } | |
| static int DPKG_ATTR_VPRINTF(1) | |
| error_context_errmsg_format(const char *fmt, va_list args) | |
| { | |
| va_list args_copy; | |
| char *errmsg = NULL; | |
| int rc; | |
| va_copy(args_copy, args); | |
| rc = vasprintf(&errmsg, fmt, args_copy); | |
| va_end(args_copy); | |
| if (rc >= 0) | |
| error_context_errmsg_set(econtext, errmsg); | |
| if (rc < 0) { | |
| rc = vsnprintf(emergency.errmsg, sizeof(emergency.errmsg), fmt, args); | |
| if (rc >= (int)sizeof(emergency.errmsg)) | |
| rc = -1; | |
| error_context_errmsg_set(econtext, emergency.errmsg); | |
| } | |
| return rc; | |
| } | |
| static bool | |
| color_set_mode(const char *mode) | |
| { | |
| if (strcmp(mode, "auto") == 0) { | |
| color_mode = COLOR_MODE_AUTO; | |
| use_color = isatty(STDOUT_FILENO); | |
| } else if (strcmp(mode, "always") == 0) { | |
| color_mode = COLOR_MODE_ALWAYS; | |
| use_color = true; | |
| } else { | |
| color_mode = COLOR_MODE_NEVER; | |
| use_color = false; | |
| } | |
| return use_color; | |
| } | |
| static bool | |
| color_enabled(void) | |
| { | |
| const char *mode; | |
| if (color_mode != COLOR_MODE_UNKNOWN) | |
| return use_color; | |
| mode = getenv("DPKG_COLORS"); | |
| if (mode == NULL) | |
| mode = "auto"; | |
| return color_set_mode(mode); | |
| } | |
| static const char * | |
| color_get(const char *color) | |
| { | |
| if (!color_enabled()) | |
| return ""; | |
| return color; | |
| } | |
| static const char * | |
| color_reset(void) | |
| { | |
| return color_get(COLOR_RESET); | |
| } | |
| static const char * | |
| dpkg_get_progname(void) | |
| { | |
| return progname; | |
| } | |
| static void | |
| print_abort_error(const char *etype, const char *emsg) | |
| { | |
| fprintf(stderr, _("%s%s%s: %s%s:%s\n %s\n"), | |
| color_get(COLOR_PROG), dpkg_get_progname(), color_reset(), | |
| color_get(COLOR_ERROR), etype, color_reset(), emsg); | |
| } | |
| static void | |
| set_error_printer(struct error_context *ec, error_printer_func *func, const void *data) | |
| { | |
| ec->printer.func = func; | |
| ec->printer.data = data; | |
| } | |
| static void | |
| set_jump_handler(struct error_context *ec, jmp_buf *jump) | |
| { | |
| ec->handler_type = HANDLER_TYPE_JUMP; | |
| ec->handler.jump = jump; | |
| } | |
| static void | |
| print_cleanup_error(const char *emsg, const void *data) | |
| { | |
| print_abort_error(_("error while cleaning up"), emsg); | |
| } | |
| static void | |
| do_internerr(const char *file, int line, const char *func, const char *fmt, ...) | |
| { | |
| va_list args; | |
| va_start(args, fmt); | |
| error_context_errmsg_format(fmt, args); | |
| va_end(args); | |
| fprintf(stderr, "%s%s:%s:%d:%s:%s %s%s:%s %s\n", color_get(COLOR_PROG), | |
| dpkg_get_progname(), file, line, func, color_reset(), | |
| color_get(COLOR_ERROR), _("internal error"), color_reset(), | |
| econtext->errmsg); | |
| error_context_errmsg_free(econtext); | |
| abort(); | |
| } | |
| static void DPKG_ATTR_NORET | |
| run_error_handler(void) | |
| { | |
| if (onerr_abort) { | |
| print_abort_error(_("unrecoverable fatal error, aborting"), econtext->errmsg); | |
| error_context_errmsg_free(econtext); | |
| exit(2); | |
| } | |
| if (econtext == NULL) { | |
| print_abort_error(_("outside error context, aborting"), | |
| _("an error occurred with no error handling in place")); | |
| exit(2); | |
| } else if (econtext->handler_type == HANDLER_TYPE_FUNC) { | |
| econtext->handler.func(); | |
| internerr("error handler returned unexpectedly!"); | |
| } else if (econtext->handler_type == HANDLER_TYPE_JUMP) { | |
| longjmp(*econtext->handler.jump, 1); | |
| } else { | |
| internerr("unknown error handler type %d!", econtext->handler_type); | |
| } | |
| } | |
| static void | |
| run_cleanups(struct error_context *econ, int flagsetin) | |
| { | |
| static volatile int preventrecurse= 0; | |
| struct cleanup_entry *volatile cep; | |
| struct cleanup_entry *ncep; | |
| struct error_context recurserr, *oldecontext; | |
| jmp_buf recurse_jump; | |
| volatile int i, flagset; | |
| if (econ->printer.func) | |
| econ->printer.func(econ->errmsg, econ->printer.data); | |
| if (++preventrecurse > 3) { | |
| onerr_abort++; | |
| print_cleanup_error(_("too many nested errors during error recovery"), NULL); | |
| flagset= 0; | |
| } else { | |
| flagset = flagsetin; | |
| } | |
| cep = econ->cleanups; | |
| oldecontext = econtext; | |
| while (cep) { | |
| for (i=0; i<NCALLS; i++) { | |
| if (cep->calls[i].call && cep->calls[i].mask & flagset) { | |
| if (setjmp(recurse_jump)) { | |
| run_cleanups(&recurserr, ehflag_bombout | ehflag_recursiveerror); | |
| error_context_errmsg_free(&recurserr); | |
| } else { | |
| memset(&recurserr, 0, sizeof(recurserr)); | |
| set_error_printer(&recurserr, print_cleanup_error, NULL); | |
| set_jump_handler(&recurserr, &recurse_jump); | |
| econtext= &recurserr; | |
| cep->calls[i].call(cep->argc,cep->argv); | |
| } | |
| econtext = oldecontext; | |
| } | |
| } | |
| flagset &= cep->cpmask; | |
| flagset |= cep->cpvalue; | |
| ncep= cep->next; | |
| if (cep != &emergency.ce) | |
| free(cep); | |
| cep = ncep; | |
| } | |
| preventrecurse--; | |
| } | |
| static void | |
| ohshit(const char *fmt, ...) | |
| { | |
| va_list args; | |
| va_start(args, fmt); | |
| error_context_errmsg_format(fmt, args); | |
| va_end(args); | |
| run_error_handler(); | |
| } | |
| static void | |
| ohshite(const char *fmt, ...) { | |
| int e, rc; | |
| va_list args; | |
| e=errno; | |
| va_start(args, fmt); | |
| rc = error_context_errmsg_format(fmt, args); | |
| va_end(args); | |
| if (rc > 0) { | |
| char *errmsg = NULL; | |
| rc = asprintf(&errmsg, "%s: %s", econtext->errmsg, strerror(e)); | |
| if (rc > 0) | |
| error_context_errmsg_set(econtext, errmsg); | |
| } | |
| run_error_handler(); | |
| } | |
| static struct error_context * | |
| error_context_new(void) | |
| { | |
| struct error_context *necp; | |
| necp = malloc(sizeof(*necp)); | |
| if (!necp) | |
| ohshite(_("out of memory for new error context")); | |
| necp->next= econtext; | |
| necp->cleanups= NULL; | |
| necp->errmsg = NULL; | |
| econtext= necp; | |
| return necp; | |
| } | |
| static void | |
| set_func_handler(struct error_context *ec, error_handler_func *func) | |
| { | |
| ec->handler_type = HANDLER_TYPE_FUNC; | |
| ec->handler.func = func; | |
| } | |
| static void | |
| push_error_context_func(error_handler_func *handler, | |
| error_printer_func *printer, | |
| const void *printer_data) | |
| { | |
| struct error_context *ec; | |
| ec = error_context_new(); | |
| set_error_printer(ec, printer, printer_data); | |
| set_func_handler(ec, handler); | |
| onerr_abort = 0; | |
| } | |
| static inline void * | |
| must_alloc(void *ptr) | |
| { | |
| if (ptr) | |
| return ptr; | |
| onerr_abort++; | |
| ohshite(_("failed to allocate memory")); | |
| } | |
| static void * | |
| m_malloc(size_t amount) { | |
| return must_alloc(malloc(amount)); | |
| } | |
| static void * | |
| m_realloc(void *r, size_t amount) { | |
| return must_alloc(realloc(r, amount)); | |
| } | |
| static void | |
| varbuf_reset(struct varbuf *v) | |
| { | |
| v->used = 0; | |
| } | |
| static void | |
| varbuf_grow(struct varbuf *v, size_t need_size) | |
| { | |
| size_t new_size; | |
| if (v->size < v->used) | |
| internerr("varbuf used(%zu) > size(%zu)", v->used, v->size); | |
| if ((v->size - v->used) >= need_size) | |
| return; | |
| new_size = (v->size + need_size) * 2; | |
| if (new_size < v->size) | |
| ohshit(_("cannot grow varbuf to size %zu; it would overflow"), | |
| need_size); | |
| v->size = new_size; | |
| v->buf = m_realloc(v->buf, v->size); | |
| } | |
| static void | |
| varbuf_add_buf(struct varbuf *v, const void *s, size_t size) | |
| { | |
| if (size == 0) | |
| return; | |
| varbuf_grow(v, size); | |
| memcpy(v->buf + v->used, s, size); | |
| v->used += size; | |
| } | |
| static void | |
| varbuf_add_char(struct varbuf *v, int c) | |
| { | |
| varbuf_grow(v, 1); | |
| v->buf[v->used++] = c; | |
| } | |
| static void | |
| varbuf_end_str(struct varbuf *v) | |
| { | |
| varbuf_grow(v, 1); | |
| v->buf[v->used] = '\0'; | |
| } | |
| static void | |
| varbuf_set_buf(struct varbuf *v, const void *buf, size_t size) | |
| { | |
| varbuf_reset(v); | |
| varbuf_add_buf(v, buf, size); | |
| varbuf_end_str(v); | |
| } | |
| static void | |
| varbuf_destroy(struct varbuf *v) | |
| { | |
| free(v->buf); | |
| v->buf = NULL; | |
| v->size = 0; | |
| v->used = 0; | |
| } | |
| static char * | |
| strchrnul(const char *s, int c) | |
| { | |
| char *match; | |
| match = strchr(s, c); | |
| if (match) | |
| return match; | |
| return (char *)s + strlen(s); | |
| } | |
| static bool | |
| command_in_path(const char *cmd) | |
| { | |
| struct varbuf filename = VARBUF_INIT; | |
| const char *path_list; | |
| const char *path, *path_end; | |
| if (cmd[0] == '/') | |
| return file_is_exec(cmd); | |
| path_list = getenv("PATH"); | |
| if (!path_list) | |
| ohshit(_("PATH is not set")); | |
| for (path = path_list; path; path = *path_end ? path_end + 1 : NULL) { | |
| size_t path_len; | |
| path_end = strchrnul(path, ':'); | |
| path_len = (size_t)(path_end - path); | |
| varbuf_set_buf(&filename, path, path_len); | |
| if (path_len) | |
| varbuf_add_char(&filename, '/'); | |
| varbuf_add_str(&filename, cmd); | |
| varbuf_end_str(&filename); | |
| if (file_is_exec(filename.buf)) { | |
| varbuf_destroy(&filename); | |
| return true; | |
| } | |
| } | |
| varbuf_destroy(&filename); | |
| return false; | |
| } | |
| static void | |
| pop_error_context(int flagset) | |
| { | |
| struct error_context *tecp; | |
| tecp = econtext; | |
| econtext = tecp->next; | |
| if (flagset & ehflag_normaltidy) | |
| set_error_printer(tecp, NULL, NULL); | |
| run_cleanups(tecp, flagset); | |
| error_context_errmsg_free(tecp); | |
| free(tecp); | |
| } | |
| static void | |
| catch_fatal_error(void) | |
| { | |
| pop_error_context(ehflag_bombout); | |
| exit(2); | |
| } | |
| static void | |
| print_subproc_error(const char *emsg, const void *data) | |
| { | |
| fprintf(stderr, _("%s (subprocess): %s\n"), dpkg_get_progname(), emsg); | |
| } | |
| static pid_t | |
| subproc_fork(void) | |
| { | |
| pid_t pid; | |
| pid = fork(); | |
| if (pid == -1) { | |
| onerr_abort++; | |
| ohshite(_("fork failed")); | |
| } | |
| if (pid > 0) | |
| return pid; | |
| /* Push a new error context, so that we don't do the other cleanups, | |
| * because they'll be done by/in the parent process. */ | |
| push_error_context_func(catch_fatal_error, print_subproc_error, NULL); | |
| return pid; | |
| } | |
| static int | |
| subproc_check(int status, const char *desc, enum subproc_flags flags) | |
| { | |
| void (*out)(const char *fmt, ...) DPKG_ATTR_PRINTF(1); | |
| int n; | |
| if (flags & SUBPROC_WARN) | |
| out = warning; | |
| else | |
| out = ohshit; | |
| if (WIFEXITED(status)) { | |
| n = WEXITSTATUS(status); | |
| if (!n) | |
| return 0; | |
| if (flags & SUBPROC_RETERROR) | |
| return n; | |
| out(_("%s subprocess returned error exit status %d"), desc, n); | |
| } else if (WIFSIGNALED(status)) { | |
| n = WTERMSIG(status); | |
| if (!n) | |
| return 0; | |
| if ((flags & SUBPROC_NOPIPE) && n == SIGPIPE) | |
| return 0; | |
| if (flags & SUBPROC_RETSIGNO) | |
| return n; | |
| if (n == SIGINT) | |
| out(_("%s subprocess was interrupted"), desc); | |
| else | |
| out(_("%s subprocess was killed by signal (%s)%s"), | |
| desc, strsignal(n), | |
| WCOREDUMP(status) ? _(", core dumped") : ""); | |
| } else { | |
| if (flags & SUBPROC_RETERROR) | |
| return -1; | |
| out(_("%s subprocess failed with wait status code %d"), desc, | |
| status); | |
| } | |
| return -1; | |
| } | |
| static int | |
| subproc_wait(pid_t pid, const char *desc) | |
| { | |
| pid_t dead_pid; | |
| int status; | |
| while ((dead_pid = waitpid(pid, &status, 0)) == -1 && errno == EINTR) ; | |
| if (dead_pid != pid) { | |
| onerr_abort++; | |
| ohshite(_("wait for %s subprocess failed"), desc); | |
| } | |
| return status; | |
| } | |
| static int | |
| subproc_reap(pid_t pid, const char *desc, enum subproc_flags flags) | |
| { | |
| int status, rc; | |
| status = subproc_wait(pid, desc); | |
| if (flags & SUBPROC_NOCHECK) | |
| rc = status; | |
| else | |
| rc = subproc_check(status, desc, flags); | |
| return rc; | |
| } | |
| static void | |
| command_init(struct command *cmd, const char *filename, const char *name) | |
| { | |
| cmd->filename = filename; | |
| cmd->name = name; | |
| cmd->argc = 0; | |
| cmd->argv_size = 10; | |
| cmd->argv = m_malloc(cmd->argv_size * sizeof(cmd->argv[0])); | |
| cmd->argv[0] = NULL; | |
| } | |
| static void | |
| command_grow_argv(struct command *cmd, int need) | |
| { | |
| need++; | |
| if ((cmd->argv_size - cmd->argc) >= need) | |
| return; | |
| cmd->argv_size = (cmd->argv_size + need) * 2; | |
| cmd->argv = m_realloc(cmd->argv, cmd->argv_size * sizeof(cmd->argv[0])); | |
| } | |
| static void | |
| command_add_arg(struct command *cmd, const char *arg) | |
| { | |
| command_grow_argv(cmd, 1); | |
| cmd->argv[cmd->argc++] = arg; | |
| cmd->argv[cmd->argc] = NULL; | |
| } | |
| static void | |
| command_exec(struct command *cmd) | |
| { | |
| execvp(cmd->filename, (char * const *)cmd->argv); | |
| ohshite(_("unable to execute %s (%s)"), cmd->name, cmd->filename); | |
| } | |
| /* END: Termux-added: imported/rewritten functions copied and pasted from elsewhere */ | |
| /* Global variables: */ | |
| #define PROGNAME "update-alternatives" | |
| static const char *altdir = SYSCONFDIR "/alternatives"; | |
| static char *admdir = NULL; | |
| static const char *instdir = ""; | |
| static size_t instdir_len; | |
| static const char *prog_path = "update-alternatives"; | |
| enum action { | |
| ACTION_NONE, | |
| ACTION_INSTALL, | |
| ACTION_SET, | |
| ACTION_SET_SELECTIONS, | |
| ACTION_GET_SELECTIONS, | |
| ACTION_AUTO, | |
| ACTION_CONFIG, | |
| ACTION_CONFIG_ALL, | |
| ACTION_REMOVE, | |
| ACTION_REMOVE_ALL, | |
| ACTION_LIST, | |
| ACTION_QUERY, | |
| ACTION_DISPLAY, | |
| }; | |
| static struct action_name { | |
| enum action action; | |
| const char *name; | |
| } action_names[] = { | |
| { ACTION_NONE, "" }, | |
| { ACTION_INSTALL, "install" }, | |
| { ACTION_SET, "set" }, | |
| { ACTION_SET_SELECTIONS, "set-selections" }, | |
| { ACTION_GET_SELECTIONS, "get-selections" }, | |
| { ACTION_AUTO, "auto" }, | |
| { ACTION_CONFIG, "config" }, | |
| { ACTION_CONFIG_ALL, "all" }, | |
| { ACTION_REMOVE, "remove" }, | |
| { ACTION_REMOVE_ALL, "remove-all" }, | |
| { ACTION_LIST, "list" }, | |
| { ACTION_QUERY, "query" }, | |
| { ACTION_DISPLAY, "display" }, | |
| }; | |
| enum output_mode { | |
| OUTPUT_QUIET = -1, | |
| OUTPUT_NORMAL = 0, | |
| OUTPUT_VERBOSE = 1, | |
| OUTPUT_DEBUG = 2, | |
| }; | |
| /* Action to perform */ | |
| static enum action action = ACTION_NONE; | |
| static char *log_file = NULL; | |
| static FILE *fh_log = NULL; | |
| /* Skip alternatives properly configured in auto mode (for --config) */ | |
| static int opt_skip_auto = 0; | |
| static int opt_verbose = OUTPUT_NORMAL; | |
| static int opt_force = 0; | |
| // Termux-added: mandoc update hook only | |
| static bool should_update_mandoc = false; | |
| /* | |
| * Functions. | |
| */ | |
| static void | |
| version(void) | |
| { | |
| printf(_("%s version %s.\n"), PROGNAME, VERSION); | |
| printf("\n"); | |
| printf(_( | |
| "This is free software; see the GNU General Public License version 2 or\n" | |
| "later for copying conditions. There is NO warranty.\n")); | |
| } | |
| static void | |
| usage(void) | |
| { | |
| printf(_( | |
| "Usage: %s [<option> ...] <command>\n" | |
| "\n"), PROGNAME); | |
| printf(_( | |
| "Commands:\n" | |
| " --install <link> <name> <path> <priority>\n" | |
| " [--slave <link> <name> <path>] ...\n" | |
| " add a group of alternatives to the system.\n" | |
| " --remove <name> <path> remove <path> from the <name> group alternative.\n" | |
| " --remove-all <name> remove <name> group from the alternatives system.\n" | |
| " --auto <name> switch the master link <name> to automatic mode.\n" | |
| " --display <name> display information about the <name> group.\n" | |
| " --query <name> machine parseable version of --display <name>.\n" | |
| " --list <name> display all targets of the <name> group.\n" | |
| " --get-selections list master alternative names and their status.\n" | |
| " --set-selections read alternative status from standard input.\n" | |
| " --config <name> show alternatives for the <name> group and ask the\n" | |
| " user to select which one to use.\n" | |
| " --set <name> <path> set <path> as alternative for <name>.\n" | |
| " --all call --config on all alternatives.\n" | |
| "\n")); | |
| printf(_( | |
| "<link> is the symlink pointing to %s/<name>.\n" | |
| " (e.g. /usr/bin/pager)\n" | |
| "<name> is the master name for this link group.\n" | |
| " (e.g. pager)\n" | |
| "<path> is the location of one of the alternative target files.\n" | |
| " (e.g. /usr/bin/less)\n" | |
| "<priority> is an integer; options with higher numbers have higher priority in\n" | |
| " automatic mode.\n" | |
| "\n"), altdir); | |
| printf(_( | |
| "Options:\n" | |
| " --altdir <directory> change the alternatives directory\n" | |
| " (default is %s).\n" | |
| " --admindir <directory> change the administrative directory\n" | |
| " (default is %s).\n" | |
| " --instdir <directory> change the installation directory.\n" | |
| " --root <directory> change the filesystem root directory.\n" | |
| " --log <file> change the log file.\n" | |
| " --force allow replacing files with alternative links.\n" | |
| " --skip-auto skip prompt for alternatives correctly configured\n" | |
| " in automatic mode (relevant for --config only)\n" | |
| " --quiet quiet operation, minimal output.\n" | |
| " --verbose verbose operation, more output.\n" | |
| " --debug debug output, way more output.\n" | |
| " --help show this help message.\n" | |
| " --version show the version.\n" | |
| ), altdir, admdir); | |
| } | |
| static void LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_PRINTF(1) | |
| error(char const *fmt, ...) | |
| { | |
| va_list args; | |
| fprintf(stderr, "%s: %s: ", PROGNAME, _("error")); | |
| va_start(args, fmt); | |
| vfprintf(stderr, fmt, args); | |
| va_end(args); | |
| fprintf(stderr, "\n"); | |
| exit(2); | |
| } | |
| static void LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_PRINTF(1) | |
| syserr(char const *fmt, ...) | |
| { | |
| va_list args; | |
| fprintf(stderr, "%s: %s: ", PROGNAME, _("error")); | |
| va_start(args, fmt); | |
| vfprintf(stderr, fmt, args); | |
| va_end(args); | |
| fprintf(stderr, ": %s\n", strerror(errno)); | |
| exit(2); | |
| } | |
| static void LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_PRINTF(1) | |
| badusage(char const *fmt, ...) | |
| { | |
| va_list args; | |
| fprintf(stderr, "%s: ", PROGNAME); | |
| va_start(args, fmt); | |
| vfprintf(stderr, fmt, args); | |
| va_end(args); | |
| fprintf(stderr, "\n\n"); | |
| fprintf(stderr, _("Use '%s --help' for program usage information."), | |
| PROGNAME); | |
| fprintf(stderr, "\n"); | |
| exit(2); | |
| } | |
| static void LIBCOMPAT_ATTR_PRINTF(1) | |
| warning(char const *fmt, ...) | |
| { | |
| va_list args; | |
| if (opt_verbose < OUTPUT_NORMAL) | |
| return; | |
| fprintf(stderr, "%s: %s: ", PROGNAME, _("warning")); | |
| va_start(args, fmt); | |
| vfprintf(stderr, fmt, args); | |
| va_end(args); | |
| fprintf(stderr, "\n"); | |
| } | |
| static void LIBCOMPAT_ATTR_PRINTF(1) | |
| debug(char const *fmt, ...) | |
| { | |
| va_list args; | |
| if (opt_verbose < OUTPUT_DEBUG) | |
| return; | |
| fprintf(stderr, "DEBUG: "); | |
| va_start(args, fmt); | |
| vfprintf(stderr, fmt, args); | |
| va_end(args); | |
| fprintf(stderr, "\n"); | |
| } | |
| static void LIBCOMPAT_ATTR_PRINTF(1) | |
| verbose(char const *fmt, ...) | |
| { | |
| va_list args; | |
| if (opt_verbose < OUTPUT_VERBOSE) | |
| return; | |
| printf("%s: ", PROGNAME); | |
| va_start(args, fmt); | |
| vprintf(fmt, args); | |
| va_end(args); | |
| printf("\n"); | |
| } | |
| static void LIBCOMPAT_ATTR_PRINTF(1) | |
| info(char const *fmt, ...) | |
| { | |
| va_list args; | |
| if (opt_verbose < OUTPUT_NORMAL) | |
| return; | |
| printf("%s: ", PROGNAME); | |
| va_start(args, fmt); | |
| vprintf(fmt, args); | |
| va_end(args); | |
| printf("\n"); | |
| } | |
| static void LIBCOMPAT_ATTR_PRINTF(1) | |
| pr(char const *fmt, ...) | |
| { | |
| va_list args; | |
| va_start(args, fmt); | |
| vprintf(fmt, args); | |
| va_end(args); | |
| printf("\n"); | |
| } | |
| static void * | |
| xmalloc(size_t size) | |
| { | |
| void *ptr; | |
| ptr = malloc(size); | |
| if (!ptr) | |
| error(_("malloc failed (%zu bytes)"), size); | |
| return ptr; | |
| } | |
| static char * | |
| xstrdup(const char *str) | |
| { | |
| char *new_str; | |
| if (!str) | |
| return NULL; | |
| new_str = strdup(str); | |
| if (!new_str) | |
| error(_("failed to allocate memory")); | |
| return new_str; | |
| } | |
| static char * | |
| xstrndup(const char *str, size_t n) | |
| { | |
| char *new_str; | |
| if (!str) | |
| return NULL; | |
| new_str = strndup(str, n); | |
| if (!new_str) | |
| error(_("failed to allocate memory")); | |
| return new_str; | |
| } | |
| static char * LIBCOMPAT_ATTR_VPRINTF(1) | |
| xvasprintf(const char *fmt, va_list args) | |
| { | |
| char *str; | |
| if (vasprintf(&str, fmt, args) < 0) | |
| error(_("failed to allocate memory")); | |
| return str; | |
| } | |
| static char * LIBCOMPAT_ATTR_PRINTF(1) | |
| xasprintf(const char *fmt, ...) | |
| { | |
| va_list args; | |
| char *str; | |
| va_start(args, fmt); | |
| str = xvasprintf(fmt, args); | |
| va_end(args); | |
| return str; | |
| } | |
| static char * | |
| areadlink(const char *linkname) | |
| { | |
| struct stat st; | |
| char *buf; | |
| ssize_t size; | |
| /* Allocate required memory to store the value of the symlink */ | |
| if (lstat(linkname, &st)) | |
| return NULL; | |
| if (!S_ISLNK(st.st_mode)) { | |
| errno = EINVAL; | |
| return NULL; | |
| } | |
| buf = xmalloc(st.st_size + 1); | |
| /* Read it and terminate the string properly */ | |
| size = readlink(linkname, buf, st.st_size); | |
| if (size == -1) { | |
| int saved_errno = errno; | |
| free(buf); | |
| errno = saved_errno; | |
| return NULL; | |
| } | |
| buf[size] = '\0'; | |
| return buf; | |
| } | |
| static int | |
| spawn(const char *prog, const char *args[]) | |
| { | |
| pid_t pid, dead_pid; | |
| int status; | |
| pid = fork(); | |
| if (pid == -1) | |
| error(_("fork failed")); | |
| if (pid == 0) { | |
| execvp(prog, (char *const *)args); | |
| syserr(_("unable to execute %s (%s)"), prog, prog); | |
| } | |
| while ((dead_pid = waitpid(pid, &status, 0)) == -1 && errno == EINTR) ; | |
| if (dead_pid != pid) | |
| error(_("wait for subprocess %s failed"), prog); | |
| return status; | |
| } | |
| static bool | |
| rename_mv(const char *src, const char *dst) | |
| { | |
| const char *args[] = { "mv", src, dst, NULL }; | |
| int rc; | |
| if (rename(src, dst) == 0) | |
| return true; | |
| if (errno == ENOENT) | |
| return false; | |
| rc = spawn("mv", args); | |
| if (WIFEXITED(rc) && WEXITSTATUS(rc) == 0) | |
| return true; | |
| return false; | |
| } | |
| static void | |
| xrename(const char *src, const char *dst) | |
| { | |
| if (!rename_mv(src, dst)) | |
| syserr(_("unable to install '%.250s' as '%.250s'"), src, dst); | |
| } | |
| static void LIBCOMPAT_ATTR_PRINTF(1) | |
| xunlink_args(const char *fmt, ...) | |
| { | |
| va_list args; | |
| char *path; | |
| va_start(args, fmt); | |
| path = xvasprintf(fmt, args); | |
| va_end(args); | |
| if (unlink(path) < 0 && errno != ENOENT) | |
| syserr(_("unable to remove '%s'"), path); | |
| free(path); | |
| } | |
| static char * | |
| xdirname(const char *pathname) | |
| { | |
| char *dirname, *slash; | |
| slash = strrchr(pathname, '/'); | |
| if (slash) | |
| dirname = xstrndup(pathname, slash - pathname); | |
| else | |
| dirname = xstrdup("."); | |
| return dirname; | |
| } | |
| static int | |
| make_path(const char *pathname, mode_t mode) | |
| { | |
| char *dirname, *slash; | |
| dirname = xstrdup(pathname); | |
| /* Find the first slash, and ignore it, as it will be either the | |
| * slash for the root directory, for the current directory in a | |
| * relative pathname or its parent. */ | |
| slash = strchr(dirname, '/'); | |
| while (slash != NULL) { | |
| slash = strchr(slash + 1, '/'); | |
| if (slash) | |
| *slash = '\0'; | |
| if (mkdir(dirname, mode) < 0 && errno != EEXIST) { | |
| free(dirname); | |
| return -1; | |
| } | |
| if (slash) | |
| *slash = '/'; | |
| } | |
| free(dirname); | |
| return 0; | |
| } | |
| static void LIBCOMPAT_ATTR_PRINTF(1) | |
| log_msg(const char *fmt, ...) | |
| { | |
| if (fh_log == NULL) { | |
| fh_log = fopen(log_file, "a"); | |
| if (fh_log == NULL && errno == ENOENT) { | |
| char *log_dir = xdirname(log_file); | |
| if (make_path(log_dir, 0755) < 0) | |
| syserr(_("cannot create log directory '%s'"), | |
| log_dir); | |
| free(log_dir); | |
| fh_log = fopen(log_file, "a"); | |
| } | |
| if (fh_log == NULL && errno != EACCES) | |
| syserr(_("cannot append to '%s'"), log_file); | |
| } | |
| if (fh_log) { | |
| va_list args; | |
| char timestamp[64]; | |
| time_t now; | |
| struct tm tm; | |
| time(&now); | |
| if (localtime_r(&now, &tm) == NULL) | |
| syserr(_("cannot get local time to log into '%s'"), log_file); | |
| strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", | |
| &tm); | |
| fprintf(fh_log, "%s %s: ", PROGNAME, timestamp); | |
| va_start(args, fmt); | |
| vfprintf(fh_log, fmt, args); | |
| va_end(args); | |
| fprintf(fh_log, "\n"); | |
| } | |
| } | |
| /* | |
| * Filesystem access for alternative handling. | |
| */ | |
| static char * | |
| fsys_get_path(const char *pathpart) | |
| { | |
| return xasprintf("%s%s", instdir, pathpart); | |
| } | |
| static const char * | |
| fsys_set_dir(const char *dir) | |
| { | |
| if (dir == NULL) { | |
| const char *instdir_env; | |
| instdir_env = getenv(INSTDIR_ENVVAR); | |
| if (instdir_env) | |
| dir = instdir_env; | |
| else | |
| dir = ""; | |
| } | |
| instdir_len = strlen(dir); | |
| return dir; | |
| } | |
| static char * | |
| fsys_gen_admindir(void) | |
| { | |
| return fsys_get_path(ADMINDIR "/alternatives"); | |
| } | |
| static bool | |
| fsys_pathname_is_missing(const char *pathname) | |
| { | |
| struct stat st; | |
| char *root_pathname; | |
| root_pathname = fsys_get_path(pathname); | |
| errno = 0; | |
| if (stat(root_pathname, &st) < 0 && errno != ENOENT) | |
| syserr(_("cannot stat file '%s'"), root_pathname); | |
| free(root_pathname); | |
| if (errno == ENOENT) | |
| return true; | |
| return false; | |
| } | |
| static int | |
| fsys_lstat(const char *linkname, struct stat *st) | |
| { | |
| char *root_linkname; | |
| int rc; | |
| root_linkname = fsys_get_path(linkname); | |
| errno = 0; | |
| rc = lstat(root_linkname, st); | |
| free(root_linkname); | |
| return rc; | |
| } | |
| static char * | |
| fsys_areadlink(const char *linkname) | |
| { | |
| char *root_linkname; | |
| char *target; | |
| root_linkname = fsys_get_path(linkname); | |
| target = areadlink(root_linkname); | |
| free(root_linkname); | |
| return target; | |
| } | |
| static char * | |
| fsys_xreadlink(const char *linkname) | |
| { | |
| char *buf; | |
| buf = fsys_areadlink(linkname); | |
| if (buf == NULL) | |
| syserr(_("unable to read link '%s%.255s'"), instdir, linkname); | |
| return buf; | |
| } | |
| static void | |
| fsys_set_ref_time(const char *linkname, const char *target) | |
| { | |
| #ifdef HAVE_LUTIMES | |
| /* If the symlink did not exist, then copy the timestamps | |
| * from the target. This is needed so we can get reproducible | |
| * installations, for programs that track these timestamps on | |
| * their databases. */ | |
| struct stat st; | |
| struct timeval tv[2]; | |
| char *root_linkname; | |
| if (fsys_lstat(target, &st) < 0) { | |
| if (errno != ENOENT) | |
| syserr(_("unable to get file '%s%s' metadata"), | |
| instdir, target); | |
| return; | |
| } | |
| tv[0].tv_sec = st.st_mtime; | |
| tv[0].tv_usec = 0; | |
| tv[1].tv_sec = st.st_mtime; | |
| tv[1].tv_usec = 0; | |
| root_linkname = fsys_get_path(linkname); | |
| if (lutimes(root_linkname, tv) < 0 && errno != ENOSYS) | |
| syserr(_("cannot set symlink '%s' timestamp"), root_linkname); | |
| free(root_linkname); | |
| #endif | |
| } | |
| static void | |
| fsys_symlink(const char *filename, const char *linkname) | |
| { | |
| char *root_linkname; | |
| root_linkname = fsys_get_path(linkname); | |
| if (unlink(root_linkname) < 0 && errno != ENOENT) | |
| syserr(_("unable to remove '%s'"), root_linkname); | |
| if (symlink(filename, root_linkname)) | |
| syserr(_("error creating symbolic link '%.255s'"), root_linkname); | |
| free(root_linkname); | |
| } | |
| static void | |
| fsys_mv(const char *src, const char *dst) | |
| { | |
| char *root_src; | |
| char *root_dst; | |
| root_src = fsys_get_path(src); | |
| root_dst = fsys_get_path(dst); | |
| xrename(root_src, root_dst); | |
| free(root_src); | |
| free(root_dst); | |
| } | |
| static void | |
| fsys_rm(const char *f) | |
| { | |
| char *root_f; | |
| root_f = fsys_get_path(f); | |
| if (unlink(root_f) < 0 && errno != ENOENT) | |
| syserr(_("unable to remove '%s'"), root_f); | |
| free(root_f); | |
| } | |
| static void LIBCOMPAT_ATTR_PRINTF(1) | |
| fsys_rm_args(const char *fmt, ...) | |
| { | |
| va_list args; | |
| char *path; | |
| va_start(args, fmt); | |
| path = xvasprintf(fmt, args); | |
| va_end(args); | |
| fsys_rm(path); | |
| free(path); | |
| } | |
| /* | |
| * OBJECTS | |
| */ | |
| struct fileset { | |
| struct fileset *next; | |
| char *master_file; | |
| int priority; | |
| struct slave_file { | |
| struct slave_file *next; | |
| char *name; | |
| char *file; | |
| } *slaves; | |
| }; | |
| static struct fileset * | |
| fileset_new(const char *master_file, int prio) | |
| { | |
| struct fileset *fs; | |
| fs = xmalloc(sizeof(*fs)); | |
| fs->next = NULL; | |
| fs->master_file = xstrdup(master_file); | |
| fs->priority = prio; | |
| fs->slaves = NULL; | |
| return fs; | |
| } | |
| static void | |
| fileset_free(struct fileset *fs) | |
| { | |
| struct slave_file *slave, *next; | |
| free(fs->master_file); | |
| for (slave = fs->slaves; slave; slave = next) { | |
| next = slave->next; | |
| free(slave->name); | |
| free(slave->file); | |
| free(slave); | |
| } | |
| free(fs); | |
| } | |
| static void | |
| fileset_add_slave(struct fileset *fs, const char *name, const char *file) | |
| { | |
| struct slave_file *sl, *cur, *prev = NULL; | |
| /* Replace existing first */ | |
| for (cur = fs->slaves; cur; cur = cur->next) { | |
| if (strcmp(cur->name, name) == 0) { | |
| free(cur->file); | |
| cur->file = xstrdup(file); | |
| return; | |
| } | |
| prev = cur; | |
| } | |
| /* Otherwise add new at the end */ | |
| sl = xmalloc(sizeof(*sl)); | |
| sl->next = NULL; | |
| sl->name = xstrdup(name); | |
| sl->file = xstrdup(file); | |
| if (prev) | |
| prev->next = sl; | |
| else | |
| fs->slaves = sl; | |
| } | |
| static const char * | |
| fileset_get_slave(struct fileset *fs, const char *name) | |
| { | |
| struct slave_file *slave; | |
| for (slave = fs->slaves; slave; slave = slave->next) { | |
| if (strcmp(slave->name, name) == 0) | |
| return slave->file; | |
| } | |
| return NULL; | |
| } | |
| static bool | |
| fileset_has_slave(struct fileset *fs, const char *name) | |
| { | |
| const char *file = fileset_get_slave(fs, name); | |
| if (file == NULL) | |
| return false; | |
| return file[0] != '\0'; | |
| } | |
| static bool | |
| fileset_can_install_slave(struct fileset *fs, const char *slave_name) | |
| { | |
| /* Decide whether the slave alternative must be setup */ | |
| if (fileset_has_slave(fs, slave_name)) { | |
| const char *slave = fileset_get_slave(fs, slave_name); | |
| if (!fsys_pathname_is_missing(slave)) | |
| return true; | |
| } | |
| return false; | |
| } | |
| struct slave_link { | |
| struct slave_link *next; | |
| char *name; | |
| char *link; | |
| bool updated; | |
| }; | |
| struct commit_operation { | |
| struct commit_operation *next; | |
| enum opcode { | |
| OPCODE_NOP, | |
| OPCODE_RM, | |
| OPCODE_MV, | |
| OPCODE_REF_TIME, | |
| } opcode; | |
| char *arg_a; | |
| char *arg_b; | |
| }; | |
| enum alternative_update_reason { | |
| ALT_UPDATE_NO, | |
| ALT_UPDATE_SLAVE_CHANGED, | |
| ALT_UPDATE_LINK_BROKEN, | |
| }; | |
| struct alternative { | |
| char *master_name; | |
| char *master_link; | |
| char *current; | |
| enum alternative_status { | |
| ALT_ST_UNKNOWN, | |
| ALT_ST_AUTO, | |
| ALT_ST_MANUAL, | |
| } status; | |
| struct slave_link *slaves; | |
| struct fileset *choices; | |
| struct commit_operation *commit_ops; | |
| int ref_count; | |
| bool modified; | |
| bool known_current; | |
| }; | |
| static void | |
| slave_link_free(struct slave_link *slave) | |
| { | |
| free(slave->name); | |
| free(slave->link); | |
| free(slave); | |
| } | |
| static void | |
| commit_operation_free(struct commit_operation *commit_op) | |
| { | |
| free(commit_op->arg_a); | |
| free(commit_op->arg_b); | |
| free(commit_op); | |
| } | |
| static struct alternative * | |
| alternative_new(const char *name) | |
| { | |
| struct alternative *alt; | |
| alt = xmalloc(sizeof(*alt)); | |
| alt->master_name = xstrdup(name); | |
| alt->master_link = NULL; | |
| alt->current = NULL; | |
| alt->status = ALT_ST_UNKNOWN; | |
| alt->slaves = NULL; | |
| alt->choices = NULL; | |
| alt->commit_ops = NULL; | |
| alt->modified = false; | |
| alt->known_current = false; | |
| alt->ref_count = 1; | |
| return alt; | |
| } | |
| static inline void | |
| alternative_ref(struct alternative *a) | |
| { | |
| a->ref_count++; | |
| } | |
| static inline bool | |
| alternative_unref(struct alternative *a) | |
| { | |
| return --a->ref_count == 0; | |
| } | |
| static void | |
| alternative_choices_free(struct alternative *a) | |
| { | |
| struct fileset *fs; | |
| if (a->choices) | |
| a->modified = true; | |
| while (a->choices) { | |
| fs = a->choices; | |
| a->choices = fs->next; | |
| fileset_free(fs); | |
| } | |
| } | |
| static void | |
| alternative_commit_operations_free(struct alternative *a) | |
| { | |
| struct commit_operation *op; | |
| while (a->commit_ops) { | |
| op = a->commit_ops; | |
| a->commit_ops = op->next; | |
| commit_operation_free(op); | |
| } | |
| } | |
| static void | |
| alternative_reset(struct alternative *alt) | |
| { | |
| struct slave_link *slave; | |
| free(alt->current); | |
| alt->current = NULL; | |
| free(alt->master_link); | |
| alt->master_link = NULL; | |
| while (alt->slaves) { | |
| slave = alt->slaves; | |
| alt->slaves = slave->next; | |
| slave_link_free(slave); | |
| } | |
| alternative_choices_free(alt); | |
| alternative_commit_operations_free(alt); | |
| alt->modified = false; | |
| alt->known_current = false; | |
| } | |
| static void | |
| alternative_free(struct alternative *alt) | |
| { | |
| if (!alternative_unref(alt)) | |
| return; | |
| alternative_reset(alt); | |
| free(alt->master_name); | |
| free(alt); | |
| } | |
| static int | |
| alternative_choices_count(struct alternative *alt) | |
| { | |
| struct fileset *fs; | |
| int count = 0; | |
| for (fs = alt->choices; fs; fs = fs->next) | |
| count++; | |
| return count; | |
| } | |
| static int | |
| alternative_slaves_count(struct alternative *alt) | |
| { | |
| struct slave_link *sl; | |
| int count = 0; | |
| for (sl = alt->slaves; sl; sl = sl->next) | |
| count++; | |
| return count; | |
| } | |
| static int | |
| compare_fileset(const void *va, const void *vb) | |
| { | |
| const struct fileset *a = *(const struct fileset **)va; | |
| const struct fileset *b = *(const struct fileset **)vb; | |
| assert(a && a->master_file); | |
| assert(b && b->master_file); | |
| return strcmp(a->master_file, b->master_file); | |
| } | |
| static int | |
| compare_slave_link(const void *va, const void *vb) | |
| { | |
| const struct slave_link *a = *(const struct slave_link **)va; | |
| const struct slave_link *b = *(const struct slave_link **)vb; | |
| assert(a && a->name); | |
| assert(b && b->name); | |
| return strcmp(a->name, b->name); | |
| } | |
| static void | |
| alternative_sort_choices(struct alternative *a) | |
| { | |
| int count, i; | |
| struct fileset **table, *fs; | |
| count = alternative_choices_count(a); | |
| if (count < 2) /* Nothing to sort */ | |
| return; | |
| /* Store objects in a table instead of a linked list */ | |
| table = xmalloc(sizeof(fs) * count); | |
| for (fs = a->choices, i = 0; fs; fs = fs->next) { | |
| assert(fs->master_file); | |
| table[i++] = fs; | |
| } | |
| qsort(table, count, sizeof(fs), compare_fileset); | |
| /* Rewrite the linked list from the sorted table */ | |
| a->choices = fs = table[0]; | |
| table[count - 1]->next = NULL; | |
| for (i = 1; i < count; fs = fs->next, i++) | |
| fs->next = table[i]; | |
| free(table); | |
| } | |
| static void | |
| alternative_sort_slaves(struct alternative *a) | |
| { | |
| int count, i; | |
| struct slave_link **table, *sl; | |
| count = alternative_slaves_count(a); | |
| if (count < 2) /* Nothing to sort */ | |
| return; | |
| /* Store objects in a table instead of a linked list */ | |
| table = xmalloc(sizeof(sl) * count); | |
| for (sl = a->slaves, i = 0; sl; sl = sl->next, i++) { | |
| table[i] = sl; | |
| } | |
| qsort(table, count, sizeof(sl), compare_slave_link); | |
| /* Rewrite the linked list from the sorted table */ | |
| a->slaves = sl = table[0]; | |
| table[count - 1]->next = NULL; | |
| for (i = 1; i < count; sl = sl->next, i++) | |
| sl->next = table[i]; | |
| free(table); | |
| } | |
| static struct fileset * | |
| alternative_get_fileset(struct alternative *a, const char *file) | |
| { | |
| struct fileset *fs; | |
| for (fs = a->choices; fs; fs = fs->next) | |
| if (strcmp(fs->master_file, file) == 0) | |
| return fs; | |
| return NULL; | |
| } | |
| static struct slave_link * | |
| alternative_get_slave(struct alternative *a, const char *name) | |
| { | |
| struct slave_link *sl; | |
| for (sl = a->slaves; sl; sl = sl->next) | |
| if (strcmp(sl->name, name) == 0) | |
| return sl; | |
| return NULL; | |
| } | |
| static bool | |
| alternative_has_slave(struct alternative *a, const char *name) | |
| { | |
| return alternative_get_slave(a, name) != NULL; | |
| } | |
| static bool | |
| alternative_has_choice(struct alternative *a, const char *file) | |
| { | |
| return alternative_get_fileset(a, file) != NULL; | |
| } | |
| static void | |
| alternative_add_choice(struct alternative *a, struct fileset *fs) | |
| { | |
| struct fileset *cur, *prev = NULL; | |
| /* Replace if already existing */ | |
| for (cur = a->choices; cur; cur = cur->next) { | |
| if (strcmp(cur->master_file, fs->master_file) == 0) { | |
| fs->next = cur->next; | |
| fileset_free(cur); | |
| if (prev) | |
| prev->next = fs; | |
| else | |
| a->choices = fs; | |
| /* XXX: Be smarter in detecting change? */ | |
| a->modified = true; | |
| return; | |
| } | |
| prev = cur; | |
| } | |
| /* Otherwise add at the end */ | |
| if (prev == NULL) | |
| a->choices = fs; | |
| else | |
| prev->next = fs; | |
| fs->next = NULL; | |
| a->modified = true; | |
| } | |
| static struct slave_link * | |
| alternative_add_slave(struct alternative *a, | |
| const char *slave_name, const char *slave_link) | |
| { | |
| struct slave_link *sl, *new; | |
| /* Replace if already existing */ | |
| for (sl = a->slaves; sl; sl = sl->next) { | |
| if (strcmp(sl->name, slave_name) == 0) { | |
| free(sl->link); | |
| sl->link = xstrdup(slave_link); | |
| return sl; | |
| } | |
| if (sl->next == NULL) | |
| break; | |
| } | |
| /* Otherwise create new and add at the end */ | |
| new = xmalloc(sizeof(*new)); | |
| new->name = xstrdup(slave_name); | |
| new->link = xstrdup(slave_link); | |
| new->updated = false; | |
| new->next = NULL; | |
| if (sl) | |
| sl->next = new; | |
| else | |
| a->slaves = new; | |
| return new; | |
| } | |
| static void | |
| alternative_copy_slave(struct alternative *a, struct slave_link *sl) | |
| { | |
| struct slave_link *sl_new; | |
| sl_new = alternative_add_slave(a, sl->name, sl->link); | |
| sl_new->updated = sl->updated; | |
| } | |
| static const char * | |
| alternative_status_string(enum alternative_status status) | |
| { | |
| return (status == ALT_ST_AUTO) ? "auto" : "manual"; | |
| } | |
| static const char * | |
| alternative_status_describe(enum alternative_status status) | |
| { | |
| return (status == ALT_ST_AUTO) ? _("auto mode") : _("manual mode"); | |
| } | |
| static void | |
| alternative_set_status(struct alternative *a, enum alternative_status status) | |
| { | |
| if (a->status == ALT_ST_UNKNOWN || status != a->status) | |
| a->modified = true; | |
| if (a->status != ALT_ST_UNKNOWN && status != a->status) | |
| log_msg("status of link group %s set to %s", a->master_link, | |
| alternative_status_string(status)); | |
| a->status = status; | |
| } | |
| static void | |
| alternative_set_link(struct alternative *a, const char *linkname) | |
| { | |
| if (a->master_link == NULL || strcmp(linkname, a->master_link) != 0) | |
| a->modified = true; | |
| free(a->master_link); | |
| a->master_link = xstrdup(linkname); | |
| } | |
| static bool | |
| alternative_remove_choice(struct alternative *a, const char *file) | |
| { | |
| struct fileset *fs, *fs_prev; | |
| fs_prev = NULL; | |
| for (fs = a->choices; fs; fs = fs->next) { | |
| if (strcmp(fs->master_file, file) != 0) { | |
| fs_prev = fs; | |
| continue; | |
| } | |
| if (fs_prev) | |
| fs_prev->next = fs->next; | |
| else | |
| a->choices = fs->next; | |
| fileset_free(fs); | |
| a->modified = true; | |
| return true; | |
| } | |
| return false; | |
| } | |
| /* | |
| * Alternatives Database Load/Store functions. | |
| */ | |
| enum LIBCOMPAT_ATTR_ENUM_FLAGS altdb_flags { | |
| ALTDB_LAX_PARSER = 1 << 0, | |
| ALTDB_WARN_PARSER = 1 << 1, | |
| }; | |
| struct altdb_context { | |
| FILE *fh; | |
| char *filename; | |
| enum altdb_flags flags; | |
| bool modified; | |
| void LIBCOMPAT_ATTR_NORET | |
| (*bad_format)(struct altdb_context *, const char *msg); | |
| jmp_buf on_error; | |
| }; | |
| static void | |
| altdb_context_free(struct altdb_context *ctx) | |
| { | |
| if (ctx->fh) | |
| fclose(ctx->fh); | |
| free(ctx->filename); | |
| } | |
| static void LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_PRINTF(2) | |
| altdb_bad_format(struct altdb_context *ctx, const char *format, ...) | |
| { | |
| va_list args; | |
| char *msg; | |
| va_start(args, format); | |
| msg = xvasprintf(format, args); | |
| va_end(args); | |
| ctx->bad_format(ctx, msg); | |
| /* This cannot happen, but just to make sure the bad_format() function | |
| * pointer is well implemented. */ | |
| error("(internal) non returning bad-format function returned"); | |
| } | |
| static int | |
| altdb_filter_namelist(const struct dirent *entry) | |
| { | |
| if (strcmp(entry->d_name, ".") == 0 || | |
| strcmp(entry->d_name, "..") == 0 || | |
| (strlen(entry->d_name) > strlen(ALT_TMP_EXT) && | |
| strcmp(entry->d_name + strlen(entry->d_name) - | |
| strlen(ALT_TMP_EXT), ALT_TMP_EXT) == 0)) | |
| return 0; | |
| return 1; | |
| } | |
| static int | |
| altdb_get_namelist(struct dirent ***table) | |
| { | |
| int count; | |
| count = scandir(admdir, table, altdb_filter_namelist, alphasort); | |
| if (count < 0) { | |
| if (errno != ENOENT) | |
| syserr(_("cannot scan directory '%.255s'"), admdir); | |
| /* The directory does not exist, proceed anyway. */ | |
| *table = NULL; | |
| count = 0; | |
| } | |
| return count; | |
| } | |
| static void | |
| altdb_free_namelist(struct dirent **table, int n) | |
| { | |
| while (n--) | |
| free(table[n]); | |
| free(table); | |
| } | |
| static char * | |
| altdb_get_line(struct altdb_context *ctx, const char *name) | |
| { | |
| char *buf, *line; | |
| size_t len, bufsz, i; | |
| bufsz = 1024; | |
| buf = xmalloc(bufsz); | |
| for (i = 0; true; i += strlen(line)) { | |
| errno = 0; | |
| line = fgets(buf + i, bufsz - i, ctx->fh); | |
| if (line) { | |
| if (strlen(buf) < bufsz - 1 || buf[bufsz - 2] == '\n') | |
| break; | |
| /* Need more space */ | |
| bufsz *= 2; | |
| buf = realloc(buf, bufsz); | |
| if (!buf) | |
| error(_("failed to allocate memory")); | |
| continue; | |
| } | |
| if (feof(ctx->fh)) | |
| altdb_bad_format(ctx, _("unexpected end of file while trying " | |
| "to read %s"), name); | |
| altdb_bad_format(ctx, _("while reading %s: %s"), | |
| name, strerror(errno)); | |
| } | |
| len = strlen(buf); | |
| if (len == 0 || buf[len - 1] != '\n') { | |
| altdb_bad_format(ctx, _("line not terminated while trying " | |
| "to read %s"), name); | |
| } | |
| line[len - 1] = '\0'; | |
| return buf; | |
| } | |
| static void LIBCOMPAT_ATTR_NORET | |
| altdb_parse_error(struct altdb_context *ctx, const char *msg) | |
| { | |
| error(_("%s corrupt: %s"), ctx->filename, msg); | |
| } | |
| static void LIBCOMPAT_ATTR_NORET | |
| altdb_parse_stop(struct altdb_context *ctx, const char *msg) | |
| { | |
| longjmp(ctx->on_error, 1); | |
| } | |
| static void | |
| altdb_print_line(struct altdb_context *ctx, const char *line) | |
| { | |
| if (strchr(line, '\n') != NULL) | |
| error(_("newlines prohibited in update-alternatives files (%s)"), | |
| line); | |
| if (fprintf(ctx->fh, "%s\n", line) < (int) strlen(line) + 1) | |
| syserr(_("unable to write file '%s'"), ctx->filename); | |
| } | |
| static bool | |
| alternative_parse_slave(struct alternative *a, struct altdb_context *ctx) | |
| { | |
| char *name, *linkname; | |
| struct slave_link *sl; | |
| name = altdb_get_line(ctx, _("slave name")); | |
| if (!strlen(name)) { /* End of list */ | |
| free(name); | |
| return false; | |
| } | |
| sl = alternative_get_slave(a, name); | |
| if (sl) { | |
| free(name); | |
| altdb_bad_format(ctx, _("duplicate slave name %s"), sl->name); | |
| } | |
| linkname = altdb_get_line(ctx, _("slave link")); | |
| if (strcmp(linkname, a->master_link) == 0) { | |
| free(linkname); | |
| free(name); | |
| altdb_bad_format(ctx, _("slave link same as main link %s"), | |
| a->master_link); | |
| } | |
| for (sl = a->slaves; sl; sl = sl->next) { | |
| if (strcmp(linkname, sl->link) == 0) { | |
| free(linkname); | |
| free(name); | |
| altdb_bad_format(ctx, _("duplicate slave link %s"), | |
| sl->link); | |
| } | |
| } | |
| alternative_add_slave(a, name, linkname); | |
| free(linkname); | |
| free(name); | |
| return true; | |
| } | |
| static bool | |
| alternative_parse_fileset(struct alternative *a, struct altdb_context *ctx) | |
| { | |
| struct fileset *fs; | |
| struct slave_link *sl; | |
| char *master_file; | |
| master_file = altdb_get_line(ctx, _("master file")); | |
| if (!strlen(master_file)) { /* End of list */ | |
| free(master_file); | |
| return false; | |
| } | |
| fs = alternative_get_fileset(a, master_file); | |
| if (fs) | |
| altdb_bad_format(ctx, _("duplicate path %s"), master_file); | |
| if (fsys_pathname_is_missing(master_file)) { | |
| char *junk; | |
| /* File not found - remove. */ | |
| if (ctx->flags & ALTDB_WARN_PARSER) | |
| warning(_("alternative %s (part of link group %s) " | |
| "doesn't exist; removing from list of " | |
| "alternatives"), master_file, a->master_name); | |
| junk = altdb_get_line(ctx, _("priority")); | |
| free(junk); | |
| for (sl = a->slaves; sl; sl = sl->next) { | |
| junk = altdb_get_line(ctx, _("slave file")); | |
| free(junk); | |
| } | |
| ctx->modified = true; | |
| } else { | |
| char *prio_str, *prio_end; | |
| long prio; | |
| prio_str = altdb_get_line(ctx, _("priority")); | |
| errno = 0; | |
| prio = strtol(prio_str, &prio_end, 10); | |
| /* XXX: Leak master_file/prio_str on non-fatal error */ | |
| if (prio_str == prio_end || *prio_end != '\0') | |
| altdb_bad_format(ctx, _("priority of %s: %s"), | |
| master_file, prio_str); | |
| if (prio < INT_MIN || prio > INT_MAX || errno == ERANGE) | |
| altdb_bad_format(ctx, | |
| _("priority of %s is out of range: %s"), | |
| master_file, prio_str); | |
| free(prio_str); | |
| fs = fileset_new(master_file, prio); | |
| for (sl = a->slaves; sl; sl = sl->next) { | |
| char *slave_file = altdb_get_line(ctx, _("slave file")); | |
| fileset_add_slave(fs, sl->name, slave_file); | |
| free(slave_file); | |
| } | |
| alternative_add_choice(a, fs); | |
| } | |
| free(master_file); | |
| return true; | |
| } | |
| static bool | |
| alternative_load(struct alternative *a, enum altdb_flags flags) | |
| { | |
| struct altdb_context ctx; | |
| struct stat st; | |
| char *status; | |
| char *master_link; | |
| /* Initialize parse context */ | |
| ctx.modified = false; | |
| ctx.flags = flags; | |
| if (flags & ALTDB_LAX_PARSER) | |
| ctx.bad_format = altdb_parse_stop; | |
| else | |
| ctx.bad_format = altdb_parse_error; | |
| ctx.filename = xasprintf("%s/%s", admdir, a->master_name); | |
| /* Open the alternative file. */ | |
| ctx.fh = fopen(ctx.filename, "r"); | |
| if (ctx.fh == NULL) { | |
| if (errno == ENOENT) { | |
| altdb_context_free(&ctx); | |
| return false; | |
| } | |
| syserr(_("unable to open file '%s'"), ctx.filename); | |
| } | |
| if (setjmp(ctx.on_error)) { | |
| altdb_context_free(&ctx); | |
| alternative_reset(a); | |
| return false; | |
| } | |
| /* Verify the alternative is not empty. */ | |
| if (fstat(fileno(ctx.fh), &st) == -1) | |
| syserr(_("cannot stat file '%s'"), ctx.filename); | |
| if (st.st_size == 0) { | |
| altdb_context_free(&ctx); | |
| alternative_reset(a); | |
| return false; | |
| } | |
| /* Start parsing mandatory attributes (link+status) of the alternative */ | |
| alternative_reset(a); | |
| status = altdb_get_line(&ctx, _("status")); | |
| if (strcmp(status, "auto") != 0 && strcmp(status, "manual") != 0) | |
| altdb_bad_format(&ctx, _("invalid status")); | |
| alternative_set_status(a, (strcmp(status, "auto") == 0) ? | |
| ALT_ST_AUTO : ALT_ST_MANUAL); | |
| free(status); | |
| master_link = altdb_get_line(&ctx, _("master link")); | |
| alternative_set_link(a, master_link); | |
| free(master_link); | |
| /* Parse the description of the slaves links of the alternative */ | |
| while (alternative_parse_slave(a, &ctx)); | |
| /* Parse the available choices in the alternative */ | |
| while (alternative_parse_fileset(a, &ctx)) ; | |
| /* Close database file */ | |
| if (fclose(ctx.fh)) | |
| syserr(_("unable to close file '%s'"), ctx.filename); | |
| free(ctx.filename); | |
| /* Initialize the modified field which has been erroneously changed | |
| * by the various alternative_(add|set)_* calls: | |
| * false unless a choice has been auto-cleaned */ | |
| a->modified = ctx.modified; | |
| return true; | |
| } | |
| static void | |
| alternative_save(struct alternative *a) | |
| { | |
| struct altdb_context ctx; | |
| struct slave_link *sl, *sl_prev; | |
| struct fileset *fs; | |
| char *filenew, *file; | |
| /* Cleanup unused slaves before writing admin file. */ | |
| sl_prev = NULL; | |
| sl = a->slaves; | |
| while (sl) { | |
| bool has_slave = false; | |
| for (fs = a->choices; fs; fs = fs->next) { | |
| if (fileset_has_slave(fs, sl->name)) { | |
| has_slave = true; | |
| break; | |
| } | |
| } | |
| if (!has_slave) { | |
| struct slave_link *sl_rm; | |
| verbose(_("discarding obsolete slave link %s (%s)"), | |
| sl->name, sl->link); | |
| if (sl_prev) | |
| sl_prev->next = sl->next; | |
| else | |
| a->slaves = sl->next; | |
| sl_rm = sl; | |
| sl = sl->next; | |
| slave_link_free(sl_rm); | |
| } else { | |
| sl_prev = sl; | |
| sl = sl->next; | |
| } | |
| } | |
| /* Sort entries */ | |
| alternative_sort_slaves(a); | |
| alternative_sort_choices(a); | |
| /* Write admin file. */ | |
| file = xasprintf("%s/%s", admdir, a->master_name); | |
| filenew = xasprintf("%s" ALT_TMP_EXT, file); | |
| ctx.filename = filenew; | |
| ctx.fh = fopen(ctx.filename, "w"); | |
| if (ctx.fh == NULL && errno == ENOENT) { | |
| if (make_path(admdir, 0755) < 0) | |
| syserr(_("cannot create administrative directory '%s'"), | |
| admdir); | |
| ctx.fh = fopen(ctx.filename, "w"); | |
| } | |
| if (ctx.fh == NULL) | |
| syserr(_("unable to create file '%s'"), ctx.filename); | |
| altdb_print_line(&ctx, alternative_status_string(a->status)); | |
| altdb_print_line(&ctx, a->master_link); | |
| for (sl = a->slaves; sl; sl = sl->next) { | |
| altdb_print_line(&ctx, sl->name); | |
| altdb_print_line(&ctx, sl->link); | |
| } | |
| altdb_print_line(&ctx, ""); | |
| for (fs = a->choices; fs; fs = fs->next) { | |
| char *prio; | |
| altdb_print_line(&ctx, fs->master_file); | |
| prio = xasprintf("%d", fs->priority); | |
| altdb_print_line(&ctx, prio); | |
| free(prio); | |
| for (sl = a->slaves; sl; sl = sl->next) { | |
| if (fileset_has_slave(fs, sl->name)) | |
| altdb_print_line(&ctx, | |
| fileset_get_slave(fs, sl->name)); | |
| else | |
| altdb_print_line(&ctx, ""); | |
| } | |
| } | |
| altdb_print_line(&ctx, ""); | |
| /* Close database file */ | |
| if (fflush(ctx.fh)) | |
| syserr(_("unable to flush file '%s'"), ctx.filename); | |
| if (fsync(fileno(ctx.fh))) | |
| syserr(_("unable to sync file '%s'"), ctx.filename); | |
| if (fclose(ctx.fh)) | |
| syserr(_("unable to close file '%s'"), ctx.filename); | |
| /* Put in place atomically. */ | |
| xrename(filenew, file); | |
| free(filenew); | |
| free(file); | |
| } | |
| static const char * | |
| alternative_set_current(struct alternative *a, char *new_choice) | |
| { | |
| a->known_current = true; | |
| a->current = new_choice; | |
| return new_choice; | |
| } | |
| static const char * | |
| alternative_get_current(struct alternative *a) | |
| { | |
| char *curlink; | |
| char *file; | |
| if (a->known_current) | |
| return a->current; | |
| curlink = xasprintf("%s/%s", altdir, a->master_name); | |
| file = fsys_areadlink(curlink); | |
| if (file == NULL && errno != ENOENT) | |
| syserr(_("cannot stat file '%s%s'"), instdir, curlink); | |
| free(curlink); | |
| return alternative_set_current(a, file); | |
| } | |
| static struct fileset * | |
| alternative_get_best(struct alternative *a) | |
| { | |
| struct fileset *fs, *best; | |
| const char *current; | |
| current = alternative_get_current(a); | |
| if (current) | |
| best = alternative_get_fileset(a, current); | |
| else | |
| best = NULL; | |
| if (best == NULL) | |
| best = a->choices; | |
| for (fs = a->choices; fs; fs = fs->next) | |
| if (fs->priority > best->priority) | |
| best = fs; | |
| return best; | |
| } | |
| static void | |
| alternative_display_query(struct alternative *a) | |
| { | |
| struct fileset *best, *fs; | |
| struct slave_link *sl; | |
| const char *current; | |
| pr("Name: %s", a->master_name); | |
| pr("Link: %s", a->master_link); | |
| if (alternative_slaves_count(a) > 0) { | |
| pr("Slaves:"); | |
| for (sl = a->slaves; sl; sl = sl->next) | |
| pr(" %s %s", sl->name, sl->link); | |
| } | |
| pr("Status: %s", alternative_status_string(a->status)); | |
| best = alternative_get_best(a); | |
| if (best) | |
| pr("Best: %s", best->master_file); | |
| current = alternative_get_current(a); | |
| pr("Value: %s", current ? current : "none"); | |
| for (fs = a->choices; fs; fs = fs->next) { | |
| printf("\n"); | |
| pr("Alternative: %s", fs->master_file); | |
| pr("Priority: %d", fs->priority); | |
| if (alternative_slaves_count(a) == 0) | |
| continue; | |
| pr("Slaves:"); | |
| for (sl = a->slaves; sl; sl = sl->next) { | |
| if (fileset_has_slave(fs, sl->name)) | |
| pr(" %s %s", sl->name, | |
| fileset_get_slave(fs, sl->name)); | |
| } | |
| } | |
| } | |
| static void | |
| alternative_display_user(struct alternative *a) | |
| { | |
| const char *current; | |
| struct fileset *fs; | |
| struct slave_link *sl; | |
| pr("%s - %s", a->master_name, alternative_status_describe(a->status)); | |
| fs = alternative_get_best(a); | |
| if (fs) | |
| pr(_(" link best version is %s"), fs->master_file); | |
| else | |
| pr(_(" link best version not available")); | |
| current = alternative_get_current(a); | |
| if (current) { | |
| pr(_(" link currently points to %s"), current); | |
| } else { | |
| pr(_(" link currently absent")); | |
| } | |
| pr(_(" link %s is %s"), a->master_name, a->master_link); | |
| for (sl = a->slaves; sl; sl = sl->next) | |
| pr(_(" slave %s is %s"), sl->name, sl->link); | |
| for (fs = a->choices; fs; fs = fs->next) { | |
| pr(_("%s - priority %d"), fs->master_file, fs->priority); | |
| for (sl = a->slaves; sl; sl = sl->next) { | |
| if (fileset_has_slave(fs, sl->name)) | |
| pr(_(" slave %s: %s"), sl->name, | |
| fileset_get_slave(fs, sl->name)); | |
| } | |
| } | |
| } | |
| static void | |
| alternative_display_list(struct alternative *a) | |
| { | |
| struct fileset *fs; | |
| for (fs = a->choices; fs; fs = fs->next) | |
| pr("%s", fs->master_file); | |
| } | |
| static void | |
| alternative_print_choice(struct alternative *a, enum alternative_status status, | |
| struct fileset *fs, int idx, int len) | |
| { | |
| const char *current = alternative_get_current(a); | |
| int mark; | |
| if (a->status == status && | |
| current && strcmp(current, fs->master_file) == 0) | |
| mark = '*'; | |
| else | |
| mark = ' '; | |
| pr("%c %-12d %-*s % -10d %s", mark, idx, len, | |
| fs->master_file, fs->priority, alternative_status_describe(status)); | |
| } | |
| static char * | |
| alternative_select_choice(struct alternative *a) | |
| { | |
| const char *current; | |
| char *ret, selection[_POSIX_PATH_MAX]; | |
| struct fileset *best, *fs; | |
| int n_choices; | |
| int len; | |
| n_choices = alternative_choices_count(a); | |
| current = alternative_get_current(a); | |
| best = alternative_get_best(a); | |
| assert(best); | |
| len = 15; | |
| for (fs = a->choices; fs; fs = fs->next) | |
| len = max(len, (int)strlen(fs->master_file) + 1); | |
| for (;;) { | |
| int idx; | |
| pr(P_("There is %d choice for the alternative %s (providing %s).", | |
| "There are %d choices for the alternative %s (providing %s).", | |
| n_choices), n_choices, a->master_name, a->master_link); | |
| printf("\n"); | |
| pr(" %-12.12s %-*.*s %-10.10s %s", _("Selection"), len, len, | |
| _("Path"), _("Priority"), _("Status")); | |
| pr("------------------------------------------------------------"); | |
| idx = 0; | |
| alternative_print_choice(a, ALT_ST_AUTO, best, idx++, len); | |
| for (fs = a->choices; fs; fs = fs->next, idx++) | |
| alternative_print_choice(a, ALT_ST_MANUAL, fs, idx, len); | |
| printf("\n"); | |
| printf(_("Press <enter> to keep the current choice[*], " | |
| "or type selection number: ")); | |
| ret = fgets(selection, sizeof(selection), stdin); | |
| if (ret == NULL || strlen(selection) == 0) { | |
| return NULL; | |
| } | |
| selection[strlen(selection) - 1] = '\0'; | |
| if (strlen(selection) == 0) | |
| return xstrdup(current); | |
| errno = 0; | |
| idx = strtol(selection, &ret, 10); | |
| if (idx >= 0 && errno == 0 && *ret == '\0') { | |
| /* Look up by index */ | |
| if (idx == 0) { | |
| alternative_set_status(a, ALT_ST_AUTO); | |
| return xstrdup(best->master_file); | |
| } | |
| idx--; | |
| for (fs = a->choices; idx && fs; idx--) | |
| fs = fs->next; | |
| if (fs) { | |
| alternative_set_status(a, ALT_ST_MANUAL); | |
| return xstrdup(fs->master_file); | |
| } | |
| } else { | |
| /* Look up by name */ | |
| fs = alternative_get_fileset(a, selection); | |
| if (fs) { | |
| alternative_set_status(a, ALT_ST_MANUAL); | |
| return xstrdup(selection); | |
| } | |
| } | |
| } | |
| } | |
| static char * | |
| alternative_config(struct alternative *a, const char *current_choice) | |
| { | |
| char *new_choice = NULL; | |
| if (alternative_choices_count(a) == 0) { | |
| pr(_("There is no program which provides %s."), | |
| a->master_name); | |
| pr(_("Nothing to configure.")); | |
| } else if (opt_skip_auto && a->status == ALT_ST_AUTO) { | |
| alternative_display_user(a); | |
| } else { | |
| new_choice = alternative_select_choice(a); | |
| } | |
| return new_choice; | |
| } | |
| static void | |
| alternative_add_commit_op(struct alternative *a, enum opcode opcode, | |
| const char *arg_a, const char *arg_b) | |
| { | |
| struct commit_operation *op, *cur; | |
| op = xmalloc(sizeof(*op)); | |
| op->opcode = opcode; | |
| op->arg_a = xstrdup(arg_a); | |
| op->arg_b = xstrdup(arg_b); | |
| op->next = NULL; | |
| /* Add at the end */ | |
| cur = a->commit_ops; | |
| while (cur && cur->next) | |
| cur = cur->next; | |
| if (cur) | |
| cur->next = op; | |
| else | |
| a->commit_ops = op; | |
| } | |
| static void | |
| alternative_commit(struct alternative *a) | |
| { | |
| struct commit_operation *op; | |
| for (op = a->commit_ops; op; op = op->next) { | |
| switch (op->opcode) { | |
| case OPCODE_NOP: | |
| break; | |
| case OPCODE_RM: | |
| fsys_rm(op->arg_a); | |
| break; | |
| case OPCODE_MV: | |
| fsys_mv(op->arg_a, op->arg_b); | |
| break; | |
| case OPCODE_REF_TIME: | |
| fsys_set_ref_time(op->arg_a, op->arg_b); | |
| break; | |
| } | |
| } | |
| alternative_commit_operations_free(a); | |
| } | |
| enum alternative_path_status { | |
| ALT_PATH_SYMLINK, | |
| ALT_PATH_MISSING, | |
| ALT_PATH_OTHER, | |
| }; | |
| static enum alternative_path_status | |
| alternative_path_classify(const char *linkname) | |
| { | |
| struct stat st; | |
| if (fsys_lstat(linkname, &st) == -1) { | |
| if (errno != ENOENT) | |
| syserr(_("cannot stat file '%s%s'"), instdir, linkname); | |
| return ALT_PATH_MISSING; | |
| } else if (S_ISLNK(st.st_mode)) { | |
| return ALT_PATH_SYMLINK; | |
| } else { | |
| return ALT_PATH_OTHER; | |
| } | |
| } | |
| static bool | |
| alternative_path_can_remove(const char *linkname) | |
| { | |
| if (opt_force) | |
| return true; | |
| if (alternative_path_classify(linkname) == ALT_PATH_OTHER) | |
| return false; | |
| else | |
| return true; | |
| } | |
| static bool | |
| alternative_path_needs_update(const char *linkname, const char *filename) | |
| { | |
| char *linktarget; | |
| bool update; | |
| if (opt_force) | |
| return true; | |
| switch (alternative_path_classify(linkname)) { | |
| case ALT_PATH_SYMLINK: | |
| linktarget = fsys_xreadlink(linkname); | |
| if (strcmp(linktarget, filename) == 0) | |
| update = false; | |
| else | |
| update = true; | |
| free(linktarget); | |
| return update; | |
| case ALT_PATH_OTHER: | |
| warning(_("not replacing %s with a link"), linkname); | |
| return false; | |
| case ALT_PATH_MISSING: | |
| default: | |
| return true; | |
| } | |
| } | |
| static void | |
| alternative_prepare_install_single(struct alternative *a, const char *name, | |
| const char *linkname, const char *file) | |
| { | |
| char *fntmp, *fn; | |
| /* Create alternatives directory (/etc/alternatives) if missing. */ | |
| if (fsys_pathname_is_missing(altdir)) { | |
| char *root_altdir = fsys_get_path(altdir); | |
| if (make_path(root_altdir, 0755) < 0) | |
| syserr(_("cannot create alternatives directory '%s'"), | |
| root_altdir); | |
| free(root_altdir); | |
| } | |
| fn = xasprintf("%s/%s", altdir, name); | |
| /* Create link in /etc/alternatives. */ | |
| fntmp = xasprintf("%s/%s" ALT_TMP_EXT, altdir, name); | |
| fsys_symlink(file, fntmp); | |
| alternative_add_commit_op(a, OPCODE_MV, fntmp, fn); | |
| if (fsys_pathname_is_missing(fn)) | |
| alternative_add_commit_op(a, OPCODE_REF_TIME, fn, file); | |
| free(fntmp); | |
| if (alternative_path_needs_update(linkname, fn)) { | |
| /* Create alternative link. */ | |
| fntmp = xasprintf("%s" ALT_TMP_EXT, linkname); | |
| fsys_symlink(fn, fntmp); | |
| alternative_add_commit_op(a, OPCODE_MV, fntmp, linkname); | |
| if (fsys_pathname_is_missing(linkname)) | |
| alternative_add_commit_op(a, OPCODE_REF_TIME, linkname, fn); | |
| free(fntmp); | |
| } | |
| free(fn); | |
| } | |
| static void | |
| alternative_prepare_install(struct alternative *a, const char *choice) | |
| { | |
| struct slave_link *sl; | |
| struct fileset *fs; | |
| fs = alternative_get_fileset(a, choice); | |
| if (fs == NULL) | |
| error(_("can't install unknown choice %s"), choice); | |
| /* Take care of master alternative */ | |
| alternative_prepare_install_single(a, a->master_name, a->master_link, | |
| choice); | |
| /* Take care of slaves alternatives */ | |
| for (sl = a->slaves; sl; sl = sl->next) { | |
| char *fn; | |
| if (fileset_can_install_slave(fs, sl->name)) { | |
| alternative_prepare_install_single(a, sl->name, | |
| sl->link, fileset_get_slave(fs, sl->name)); | |
| continue; | |
| } | |
| /* Slave can't be installed */ | |
| if (fileset_has_slave(fs, sl->name)) | |
| warning(_("skip creation of %s because associated " | |
| "file %s (of link group %s) doesn't exist"), | |
| sl->link, fileset_get_slave(fs, sl->name), | |
| a->master_name); | |
| /* Drop unused slave. */ | |
| fn = xasprintf("%s/%s", altdir, sl->name); | |
| if (alternative_path_can_remove(sl->link)) | |
| alternative_add_commit_op(a, OPCODE_RM, sl->link, NULL); | |
| else | |
| warning(_("not removing %s since it's not a symlink"), | |
| sl->link); | |
| alternative_add_commit_op(a, OPCODE_RM, fn, NULL); | |
| free(fn); | |
| } | |
| // Termux-added: mandoc update hook only | |
| should_update_mandoc = true; | |
| } | |
| static void | |
| alternative_remove_files(struct alternative *a) | |
| { | |
| struct slave_link *sl; | |
| fsys_rm_args("%s" ALT_TMP_EXT, a->master_link); | |
| if (alternative_path_can_remove(a->master_link)) | |
| fsys_rm(a->master_link); | |
| fsys_rm_args("%s/%s" ALT_TMP_EXT, altdir, a->master_name); | |
| fsys_rm_args("%s/%s", altdir, a->master_name); | |
| for (sl = a->slaves; sl; sl = sl->next) { | |
| fsys_rm_args("%s" ALT_TMP_EXT, sl->link); | |
| if (alternative_path_can_remove(sl->link)) | |
| fsys_rm(sl->link); | |
| fsys_rm_args("%s/%s" ALT_TMP_EXT, altdir, sl->name); | |
| fsys_rm_args("%s/%s", altdir, sl->name); | |
| } | |
| /* Drop admin file */ | |
| xunlink_args("%s/%s", admdir, a->master_name); | |
| } | |
| static char * | |
| alternative_remove(struct alternative *a, const char *current_choice, | |
| const char *path) | |
| { | |
| char *new_choice = NULL; | |
| if (alternative_has_choice(a, path)) | |
| alternative_remove_choice(a, path); | |
| else | |
| verbose(_("alternative %s for %s not registered; not removing"), | |
| path, a->master_name); | |
| if (current_choice && strcmp(current_choice, path) == 0) { | |
| struct fileset *best; | |
| /* Current choice is removed. */ | |
| if (a->status == ALT_ST_MANUAL) { | |
| /* And it was manual, switch to auto. */ | |
| info(_("removing manually selected alternative " | |
| "- switching %s to auto mode"), | |
| a->master_name); | |
| alternative_set_status(a, ALT_ST_AUTO); | |
| } | |
| best = alternative_get_best(a); | |
| if (best) | |
| new_choice = xstrdup(best->master_file); | |
| } | |
| return new_choice; | |
| } | |
| static bool | |
| alternative_has_broken_symlink(const char *linkname, const char *ref_target) | |
| { | |
| char *target; | |
| target = fsys_areadlink(linkname); | |
| if (!target) | |
| return true; | |
| if (strcmp(target, ref_target) != 0) { | |
| free(target); | |
| return true; | |
| } | |
| free(target); | |
| return false; | |
| } | |
| static bool | |
| alternative_has_broken_slave(struct slave_link *sl, struct fileset *fs) | |
| { | |
| if (fileset_can_install_slave(fs, sl->name)) { | |
| char *wanted; | |
| const char *sl_target; | |
| /* Verify link -> /etc/alternatives/foo */ | |
| wanted = xasprintf("%s/%s", altdir, sl->name); | |
| if (alternative_has_broken_symlink(sl->link, wanted)) { | |
| free(wanted); | |
| return true; | |
| } | |
| /* Verify /etc/alternatives/foo -> file */ | |
| sl_target = fileset_get_slave(fs, sl->name); | |
| if (alternative_has_broken_symlink(wanted, sl_target)) { | |
| free(wanted); | |
| return true; | |
| } | |
| free(wanted); | |
| } else { | |
| char *sl_altlnk; | |
| /* Slave link must not exist. */ | |
| if (alternative_path_classify(sl->link) != ALT_PATH_MISSING) | |
| return true; | |
| sl_altlnk = xasprintf("%s/%s", altdir, sl->name); | |
| if (alternative_path_classify(sl_altlnk) != ALT_PATH_MISSING) { | |
| free(sl_altlnk); | |
| return true; | |
| } | |
| free(sl_altlnk); | |
| } | |
| return false; | |
| } | |
| static enum alternative_update_reason | |
| alternative_needs_update(struct alternative *a) | |
| { | |
| enum alternative_update_reason reason = ALT_UPDATE_NO; | |
| const char *current; | |
| char *wanted; | |
| struct fileset *fs; | |
| struct slave_link *sl; | |
| /* Check master link */ | |
| wanted = xasprintf("%s/%s", altdir, a->master_name); | |
| if (alternative_has_broken_symlink(a->master_link, wanted)) { | |
| free(wanted); | |
| return ALT_UPDATE_LINK_BROKEN; | |
| } | |
| free(wanted); | |
| /* Stop if we have an unmanaged alternative */ | |
| current = alternative_get_current(a); | |
| if (current == NULL) | |
| return ALT_UPDATE_LINK_BROKEN; | |
| fs = alternative_get_fileset(a, current); | |
| /* Stop if we do not have the choice. */ | |
| if (fs == NULL) | |
| return ALT_UPDATE_NO; | |
| /* Check slaves */ | |
| for (sl = a->slaves; sl; sl = sl->next) { | |
| if (alternative_has_broken_slave(sl, fs)) { | |
| if (sl->updated) | |
| reason = ALT_UPDATE_SLAVE_CHANGED; | |
| else | |
| return ALT_UPDATE_LINK_BROKEN; | |
| } | |
| } | |
| return reason; | |
| } | |
| struct alternative_map { | |
| struct alternative_map *next; | |
| const char *key; | |
| struct alternative *item; | |
| }; | |
| static struct alternative_map * | |
| alternative_map_new(const char *key, struct alternative *a) | |
| { | |
| struct alternative_map *am; | |
| am = xmalloc(sizeof(*am)); | |
| am->next = NULL; | |
| am->key = key; | |
| am->item = a; | |
| return am; | |
| } | |
| static struct alternative * | |
| alternative_map_find(struct alternative_map *am, const char *key) | |
| { | |
| for (; am; am = am->next) | |
| if (am->key && strcmp(am->key, key) == 0) | |
| return am->item; | |
| return NULL; | |
| } | |
| static void | |
| alternative_map_add(struct alternative_map *am, const char *key, | |
| struct alternative *a) | |
| { | |
| alternative_ref(a); | |
| if (am->key == NULL) { | |
| am->key = key; | |
| am->item = a; | |
| } else { | |
| struct alternative_map *new = alternative_map_new(key, a); | |
| while (am->next) | |
| am = am->next; | |
| am->next = new; | |
| } | |
| } | |
| static void | |
| alternative_map_load_names(struct alternative_map *alt_map_obj) | |
| { | |
| struct dirent **table; | |
| int i, count; | |
| count = altdb_get_namelist(&table); | |
| for (i = 0; i < count; i++) { | |
| struct alternative *a_new = alternative_new(table[i]->d_name); | |
| if (!alternative_load(a_new, ALTDB_LAX_PARSER)) { | |
| alternative_free(a_new); | |
| continue; | |
| } | |
| alternative_map_add(alt_map_obj, a_new->master_name, a_new); | |
| alternative_unref(a_new); | |
| } | |
| altdb_free_namelist(table, count); | |
| } | |
| static void | |
| alternative_map_load_tree(struct alternative_map *alt_map_links, | |
| struct alternative_map *alt_map_parent) | |
| { | |
| struct dirent **table; | |
| int i, count; | |
| count = altdb_get_namelist(&table); | |
| for (i = 0; i < count; i++) { | |
| struct slave_link *sl; | |
| struct alternative *a_new = alternative_new(table[i]->d_name); | |
| if (!alternative_load(a_new, ALTDB_LAX_PARSER)) { | |
| alternative_free(a_new); | |
| continue; | |
| } | |
| alternative_map_add(alt_map_links, a_new->master_link, a_new); | |
| alternative_map_add(alt_map_parent, a_new->master_name, a_new); | |
| for (sl = a_new->slaves; sl; sl = sl->next) { | |
| alternative_map_add(alt_map_links, sl->link, a_new); | |
| alternative_map_add(alt_map_parent, sl->name, a_new); | |
| } | |
| alternative_unref(a_new); | |
| } | |
| altdb_free_namelist(table, count); | |
| } | |
| static void | |
| alternative_map_free(struct alternative_map *am) | |
| { | |
| struct alternative_map *am_next; | |
| while (am) { | |
| am_next = am->next; | |
| if (am->item) | |
| alternative_free(am->item); | |
| free(am); | |
| am = am_next; | |
| } | |
| } | |
| static char * | |
| alternative_set_manual(struct alternative *a, const char *path) | |
| { | |
| char *new_choice = NULL; | |
| if (alternative_has_choice(a, path)) | |
| new_choice = xstrdup(path); | |
| else | |
| error(_("alternative %s for %s not registered; " | |
| "not setting"), path, a->master_name); | |
| alternative_set_status(a, ALT_ST_MANUAL); | |
| return new_choice; | |
| } | |
| static char * | |
| alternative_set_auto(struct alternative *a) | |
| { | |
| char *new_choice = NULL; | |
| alternative_set_status(a, ALT_ST_AUTO); | |
| if (alternative_choices_count(a) == 0) | |
| info(_("there is no program which provides %s"), | |
| a->master_name); | |
| else | |
| new_choice = xstrdup(alternative_get_best(a)->master_file); | |
| return new_choice; | |
| } | |
| static const char * | |
| get_argv_string(int argc, char **argv) | |
| { | |
| static char string[2048]; | |
| size_t cur_len; | |
| int i; | |
| string[0] = '\0'; | |
| cur_len = 0; | |
| for (i = 1; i < argc; i++) { | |
| size_t arg_len = strlen(argv[i]); | |
| if (cur_len + arg_len + 2 > sizeof(string)) | |
| break; | |
| if (cur_len) { | |
| strcpy(string + cur_len, " "); | |
| cur_len++; | |
| } | |
| strcpy(string + cur_len, argv[i]); | |
| cur_len += arg_len; | |
| } | |
| return string; | |
| } | |
| static void | |
| alternative_select_mode(struct alternative *a, const char *current_choice) | |
| { | |
| if (current_choice) { | |
| /* Detect manually modified alternative, switch to manual. */ | |
| if (!alternative_has_choice(a, current_choice)) { | |
| if (fsys_pathname_is_missing(current_choice)) { | |
| warning(_("%s%s/%s is dangling; it will be updated " | |
| "with best choice"), instdir, altdir, | |
| a->master_name); | |
| alternative_set_status(a, ALT_ST_AUTO); | |
| } else if (a->status != ALT_ST_MANUAL) { | |
| warning(_("%s%s/%s has been changed (manually or by " | |
| "a script); switching to manual " | |
| "updates only"), instdir, altdir, | |
| a->master_name); | |
| alternative_set_status(a, ALT_ST_MANUAL); | |
| } | |
| } | |
| } else { | |
| /* Lack of alternative link => automatic mode. */ | |
| verbose(_("setting up automatic selection of %s"), | |
| a->master_name); | |
| alternative_set_status(a, ALT_ST_AUTO); | |
| } | |
| } | |
| static void | |
| alternative_evolve_slave(struct alternative *a, const char *cur_choice, | |
| struct slave_link *sl, struct fileset *fs) | |
| { | |
| struct slave_link *sl_old; | |
| char *new_file = NULL; | |
| const char *old, *new; | |
| sl_old = alternative_get_slave(a, sl->name); | |
| if (sl_old == NULL) { | |
| sl->updated = true; | |
| return; | |
| } | |
| old = sl_old->link; | |
| new = sl->link; | |
| if (cur_choice && strcmp(cur_choice, fs->master_file) == 0) { | |
| new_file = xstrdup(fileset_get_slave(fs, sl->name)); | |
| } else { | |
| char *lnk; | |
| lnk = xasprintf("%s/%s", altdir, sl->name); | |
| new_file = fsys_areadlink(lnk); | |
| free(lnk); | |
| } | |
| if (strcmp(old, new) != 0 && | |
| alternative_path_classify(old) == ALT_PATH_SYMLINK) { | |
| bool rename_link = false; | |
| if (new_file) | |
| rename_link = !fsys_pathname_is_missing(new_file); | |
| if (rename_link) { | |
| info(_("renaming %s slave link from %s%s to %s%s"), | |
| sl->name, instdir, old, instdir, new); | |
| fsys_mv(old, new); | |
| } else { | |
| fsys_rm(old); | |
| } | |
| sl->updated = true; | |
| } | |
| free(new_file); | |
| } | |
| static void | |
| alternative_evolve(struct alternative *a, struct alternative *b, | |
| const char *cur_choice, struct fileset *fs) | |
| { | |
| struct slave_link *sl; | |
| bool is_link; | |
| is_link = alternative_path_classify(a->master_link) == ALT_PATH_SYMLINK; | |
| if (is_link && strcmp(a->master_link, b->master_link) != 0) { | |
| info(_("renaming %s link from %s%s to %s%s"), b->master_name, | |
| instdir, a->master_link, instdir, b->master_link); | |
| fsys_mv(a->master_link, b->master_link); | |
| } | |
| alternative_set_link(a, b->master_link); | |
| /* Check if new slaves have been added, or existing | |
| * ones renamed. */ | |
| for (sl = b->slaves; sl; sl = sl->next) { | |
| alternative_evolve_slave(a, cur_choice, sl, fs); | |
| alternative_copy_slave(a, sl); | |
| } | |
| } | |
| static char * | |
| alternative_install(struct alternative **aptr, struct alternative *inst_alt, | |
| const char *current_choice, struct fileset *fileset) | |
| { | |
| struct alternative *a = *aptr; | |
| char *new_choice = NULL; | |
| if (a->master_link) { | |
| /* Alternative already exists, check if anything got | |
| * updated. */ | |
| alternative_evolve(a, inst_alt, current_choice, fileset); | |
| alternative_free(inst_alt); | |
| } else { | |
| /* Alternative doesn't exist, create from parameters. */ | |
| alternative_free(a); | |
| *aptr = a = inst_alt; | |
| } | |
| alternative_add_choice(a, fileset); | |
| if (a->status == ALT_ST_AUTO) { | |
| new_choice = xstrdup(alternative_get_best(a)->master_file); | |
| } else { | |
| verbose(_("automatic updates of %s/%s are disabled; " | |
| "leaving it alone"), altdir, a->master_name); | |
| verbose(_("to return to automatic updates use " | |
| "'%s --auto %s'"), PROGNAME, a->master_name); | |
| } | |
| return new_choice; | |
| } | |
| /* BEGIN: Termux-added: mandoc update hook */ | |
| /* Update mandoc database after creating symbolic links */ | |
| /* command launching logic copied from the extracthalf() function in src/deb/extract.c */ | |
| static void | |
| update_mandoc(void) { | |
| const char *cmd_string = "makewhatis"; | |
| if (!command_in_path(cmd_string)) { | |
| warning("skipping updating manpage database as '%s' command from 'mandoc' package is not installed", cmd_string); | |
| return; | |
| } | |
| log_msg("updating manpage database..."); | |
| pid_t current_process_is_parent = subproc_fork(); | |
| if (current_process_is_parent) { | |
| subproc_reap(current_process_is_parent, cmd_string, 0); | |
| return; | |
| } | |
| struct command cmd_struct; | |
| command_init(&cmd_struct, cmd_string, cmd_string); | |
| command_add_arg(&cmd_struct, cmd_string); | |
| command_exec(&cmd_struct); | |
| } | |
| /* END: Termux-added: mandoc update hook */ | |
| static void | |
| alternative_update(struct alternative *a, | |
| const char *current_choice, const char *new_choice) | |
| { | |
| enum alternative_update_reason reason; | |
| /* No choice left, remove everything. */ | |
| if (!alternative_choices_count(a)) { | |
| log_msg("link group %s fully removed", a->master_name); | |
| alternative_remove_files(a); | |
| return; | |
| } | |
| /* New choice wanted. */ | |
| if (new_choice && | |
| (!current_choice || strcmp(new_choice, current_choice) != 0)) { | |
| log_msg("link group %s updated to point to %s", a->master_name, | |
| new_choice); | |
| if (a->status == ALT_ST_AUTO) | |
| info(_("using %s to provide %s (%s) in auto mode"), | |
| new_choice, a->master_link, a->master_name); | |
| else | |
| info(_("using %s to provide %s (%s) in manual mode"), | |
| new_choice, a->master_link, a->master_name); | |
| debug("prepare_install(%s)", new_choice); | |
| alternative_prepare_install(a, new_choice); | |
| } else if ((reason = alternative_needs_update(a))) { | |
| if (reason == ALT_UPDATE_SLAVE_CHANGED) { | |
| log_msg("link group %s updated with changed slaves", | |
| a->master_name); | |
| info(_("updating alternative %s " | |
| "because link group %s has changed slave links"), | |
| current_choice, a->master_name); | |
| } else { | |
| log_msg("auto-repair link group %s", a->master_name); | |
| warning(_("forcing reinstallation of alternative %s " | |
| "because link group %s is broken"), | |
| current_choice, a->master_name); | |
| } | |
| if (current_choice && !alternative_has_choice(a, current_choice)) { | |
| struct fileset *best = alternative_get_best(a); | |
| warning(_("current alternative %s is unknown, " | |
| "switching to %s for link group %s"), | |
| current_choice, best->master_file, | |
| a->master_name); | |
| current_choice = best->master_file; | |
| alternative_set_status(a, ALT_ST_AUTO); | |
| } | |
| if (current_choice) | |
| alternative_prepare_install(a, current_choice); | |
| } | |
| /* Save administrative file if needed. */ | |
| if (a->modified) { | |
| debug("%s is modified and will be saved", a->master_name); | |
| alternative_save(a); | |
| } | |
| /* Replace all symlinks in one pass. */ | |
| alternative_commit(a); | |
| } | |
| static void | |
| alternative_config_all(void) | |
| { | |
| struct alternative_map *alt_map_obj; | |
| struct alternative_map *am; | |
| alt_map_obj = alternative_map_new(NULL, NULL); | |
| alternative_map_load_names(alt_map_obj); | |
| for (am = alt_map_obj; am && am->item; am = am->next) { | |
| const char *current_choice; | |
| char *new_choice; | |
| current_choice = alternative_get_current(am->item); | |
| alternative_select_mode(am->item, current_choice); | |
| new_choice = alternative_config(am->item, current_choice); | |
| alternative_update(am->item, current_choice, new_choice); | |
| free(new_choice); | |
| } | |
| alternative_map_free(alt_map_obj); | |
| } | |
| static void | |
| alternative_get_selections(void) | |
| { | |
| struct alternative_map *alt_map_obj; | |
| struct alternative_map *am; | |
| alt_map_obj = alternative_map_new(NULL, NULL); | |
| alternative_map_load_names(alt_map_obj); | |
| for (am = alt_map_obj; am && am->item; am = am->next) { | |
| const char *current; | |
| current = alternative_get_current(am->item); | |
| printf("%-30s %-8s %s\n", am->key, | |
| alternative_status_string(am->item->status), | |
| current ? current : ""); | |
| } | |
| alternative_map_free(alt_map_obj); | |
| } | |
| static void | |
| alternative_set_selection(struct alternative_map *all, const char *name, | |
| const char *status, const char *choice) | |
| { | |
| struct alternative *a; | |
| debug("set_selection(%s, %s, %s)", name, status, choice); | |
| a = alternative_map_find(all, name); | |
| if (a) { | |
| char *new_choice = NULL; | |
| if (strcmp(status, "auto") == 0) { | |
| info(_("selecting alternative %s as auto"), name); | |
| new_choice = alternative_set_auto(a); | |
| } else if (alternative_has_choice(a, choice)) { | |
| info(_("selecting alternative %s as choice %s"), name, | |
| choice); | |
| new_choice = alternative_set_manual(a, choice); | |
| } else { | |
| info(_("alternative %s unchanged because choice " | |
| "%s is not available"), name, choice); | |
| } | |
| if (new_choice) { | |
| const char *current_choice; | |
| current_choice = alternative_get_current(a); | |
| alternative_select_mode(a, current_choice); | |
| alternative_update(a, current_choice, new_choice); | |
| free(new_choice); | |
| } | |
| } else { | |
| info(_("skip unknown alternative %s"), name); | |
| } | |
| } | |
| static void | |
| alternative_set_selections(FILE *input, const char *desc) | |
| { | |
| struct alternative_map *alt_map_obj; | |
| alt_map_obj = alternative_map_new(NULL, NULL); | |
| alternative_map_load_names(alt_map_obj); | |
| for (;;) { | |
| char line[1024], *res, *name, *status, *choice; | |
| size_t len, i; | |
| errno = 0; | |
| /* Can't use scanf("%s %s %s") because choice can | |
| * contain a space */ | |
| res = fgets(line, sizeof(line), input); | |
| if (res == NULL && errno) { | |
| syserr(_("read error in %.250s"), desc); | |
| } else if (res == NULL) { | |
| break; | |
| } | |
| len = strlen(line); | |
| if (len == 0 || line[len - 1] != '\n') { | |
| error(_("line too long or not terminated while " | |
| "trying to read %s"), desc); | |
| } | |
| line[len - 1] = '\0'; | |
| len--; | |
| /* Delimit name string in line */ | |
| i = 0; | |
| name = line; | |
| while (i < len && !isblank(line[i])) | |
| i++; | |
| if (i >= len) { | |
| info(_("skip invalid selection line: %s"), line); | |
| continue; | |
| } | |
| line[i++] = '\0'; | |
| while (i < len && isblank(line[i])) | |
| i++; | |
| /* Delimit status string in line */ | |
| status = line + i; | |
| while (i < len && !isblank(line[i])) | |
| i++; | |
| if (i >= len) { | |
| info(_("skip invalid selection line: %s"), line); | |
| continue; | |
| } | |
| line[i++] = '\0'; | |
| while (i < len && isblank(line[i])) | |
| i++; | |
| /* Delimit choice string in the line */ | |
| if (i >= len) { | |
| info(_("skip invalid selection line: %s"), line); | |
| continue; | |
| } | |
| choice = line + i; | |
| alternative_set_selection(alt_map_obj, name, status, choice); | |
| } | |
| alternative_map_free(alt_map_obj); | |
| } | |
| static void | |
| alternative_check_name(const char *name) | |
| { | |
| if (strpbrk(name, "/ \t")) | |
| error(_("alternative name (%s) must not contain '/' " | |
| "and spaces"), name); | |
| } | |
| static void | |
| alternative_check_link(const char *linkname) | |
| { | |
| if (linkname[0] != '/') | |
| error(_("alternative link is not absolute as it should be: %s"), | |
| linkname); | |
| } | |
| static void | |
| alternative_check_path(const char *file) | |
| { | |
| if (!file || file[0] != '/') | |
| error(_("alternative path is not absolute as it should be: %s"), | |
| file); | |
| } | |
| /** | |
| * Check the alternative installation arguments. | |
| * | |
| * That the caller doesn't mix links between alternatives, doesn't mix | |
| * alternatives between slave/master, and that the various parameters | |
| * are fine. | |
| */ | |
| static void | |
| alternative_check_install_args(struct alternative *inst_alt, | |
| struct fileset *fileset) | |
| { | |
| struct alternative_map *alt_map_links, *alt_map_parent; | |
| struct alternative *found; | |
| struct slave_link *sl; | |
| alternative_check_name(inst_alt->master_name); | |
| alternative_check_link(inst_alt->master_link); | |
| alternative_check_path(fileset->master_file); | |
| /* Load information about all alternatives to check for mistakes. */ | |
| alt_map_links = alternative_map_new(NULL, NULL); | |
| alt_map_parent = alternative_map_new(NULL, NULL); | |
| alternative_map_load_tree(alt_map_links, alt_map_parent); | |
| found = alternative_map_find(alt_map_parent, inst_alt->master_name); | |
| if (found && strcmp(found->master_name, inst_alt->master_name) != 0) { | |
| error(_("alternative %s can't be master: it is a slave of %s"), | |
| inst_alt->master_name, found->master_name); | |
| } | |
| found = alternative_map_find(alt_map_links, inst_alt->master_link); | |
| if (found && strcmp(found->master_name, inst_alt->master_name) != 0) { | |
| found = alternative_map_find(alt_map_parent, | |
| found->master_name); | |
| error(_("alternative link %s is already managed by %s"), | |
| inst_alt->master_link, found->master_name); | |
| } | |
| if (fsys_pathname_is_missing(fileset->master_file)) | |
| error(_("alternative path %s%s doesn't exist"), | |
| instdir, fileset->master_file); | |
| for (sl = inst_alt->slaves; sl; sl = sl->next) { | |
| const char *file = fileset_get_slave(fileset, sl->name); | |
| alternative_check_name(sl->name); | |
| alternative_check_link(sl->link); | |
| alternative_check_path(file); | |
| found = alternative_map_find(alt_map_parent, sl->name); | |
| if (found && | |
| strcmp(found->master_name, inst_alt->master_name) != 0) { | |
| if (strcmp(found->master_name, sl->name) == 0) | |
| error(_("alternative %s can't be slave of %s: " | |
| "it is a master alternative"), | |
| sl->name, inst_alt->master_name); | |
| else | |
| error(_("alternative %s can't be slave of %s: " | |
| "it is a slave of %s"), | |
| sl->name, inst_alt->master_name, | |
| found->master_name); | |
| } | |
| found = alternative_map_find(alt_map_links, sl->link); | |
| if (found && | |
| strcmp(found->master_name, inst_alt->master_name) != 0) { | |
| error(_("alternative link %s is already " | |
| "managed by %s"), sl->link, | |
| found->master_name); | |
| } | |
| if (found) { | |
| struct slave_link *sl2; | |
| for (sl2 = found->slaves; sl2; sl2 = sl2->next) | |
| if (strcmp(sl2->link, sl->link) == 0) | |
| break; | |
| if (sl2 && strcmp(sl2->name, sl->name) != 0) | |
| error(_("alternative link %s is already " | |
| "managed by %s (slave of %s)"), | |
| sl->link, sl2->name, | |
| found->master_name); | |
| } | |
| } | |
| alternative_map_free(alt_map_links); | |
| alternative_map_free(alt_map_parent); | |
| } | |
| /* | |
| * Main program | |
| */ | |
| static void | |
| set_action(enum action new_action) | |
| { | |
| if (action) | |
| badusage(_("two commands specified: --%s and --%s"), | |
| action_names[action].name, action_names[new_action].name); | |
| action = new_action; | |
| } | |
| static void | |
| set_action_from_name(const char *new_action) | |
| { | |
| size_t i; | |
| for (i = 0; i < array_count(action_names); i++) { | |
| if (strcmp(new_action, action_names[i].name) == 0) { | |
| set_action(action_names[i].action); | |
| return; | |
| } | |
| } | |
| assert(!"unknown action name"); | |
| } | |
| static const char * | |
| set_rootdir(const char *dir) | |
| { | |
| instdir = fsys_set_dir(dir); | |
| free(log_file); | |
| log_file = fsys_get_path(LOGDIR "/alternatives.log"); | |
| altdir = SYSCONFDIR "/alternatives"; | |
| free(admdir); | |
| admdir = fsys_gen_admindir(); | |
| return instdir; | |
| } | |
| static char * | |
| admindir_init(void) | |
| { | |
| const char *basedir_env; | |
| /* Try to get the admindir from an environment variable, usually set | |
| * by the system package manager. */ | |
| basedir_env = getenv(ADMINDIR_ENVVAR); | |
| if (basedir_env) | |
| return xasprintf("%s%s", basedir_env, "/alternatives"); | |
| else | |
| return fsys_gen_admindir(); | |
| } | |
| #define MISSING_ARGS(nb) (argc < i + nb + 1) | |
| int | |
| main(int argc, char **argv) | |
| { | |
| /* Alternative worked on. */ | |
| struct alternative *a = NULL; | |
| /* Alternative to install. */ | |
| struct alternative *inst_alt = NULL; | |
| /* Set of files to install in the alternative. */ | |
| struct fileset *fileset = NULL; | |
| /* Path of alternative we are offering. */ | |
| const char *path = NULL; | |
| const char *current_choice = NULL; | |
| char *new_choice = NULL; | |
| bool modifies_alt = false; | |
| bool modifies_sys = false; | |
| int i = 0; | |
| setlocale(LC_ALL, ""); | |
| bindtextdomain(PACKAGE, LOCALEDIR); | |
| textdomain(PACKAGE); | |
| tzset(); | |
| umask(022); | |
| instdir = fsys_set_dir(NULL); | |
| admdir = admindir_init(); | |
| log_file = fsys_get_path(LOGDIR "/alternatives.log"); | |
| if (setvbuf(stdout, NULL, _IONBF, 0)) | |
| syserr("setvbuf failed"); | |
| prog_path = argv[0]; | |
| for (i = 1; i < argc; i++) { | |
| if (strstr(argv[i], "--") != argv[i]) { | |
| error(_("unknown argument '%s'"), argv[i]); | |
| } else if (strcmp("--help", argv[i]) == 0) { | |
| usage(); | |
| exit(0); | |
| } else if (strcmp("--version", argv[i]) == 0) { | |
| version(); | |
| exit(0); | |
| } else if (strcmp("--quiet", argv[i]) == 0) { | |
| opt_verbose = OUTPUT_QUIET; | |
| } else if (strcmp("--verbose", argv[i]) == 0) { | |
| opt_verbose = OUTPUT_VERBOSE; | |
| } else if (strcmp("--debug", argv[i]) == 0) { | |
| opt_verbose = OUTPUT_DEBUG; | |
| } else if (strcmp("--install", argv[i]) == 0) { | |
| const char *alink, *aname, *apath; | |
| char *prio_str, *prio_end; | |
| long prio; | |
| set_action(ACTION_INSTALL); | |
| if (MISSING_ARGS(4)) | |
| badusage(_("--%s needs <link> <name> <path> " | |
| "<priority>"), argv[i] + 2); | |
| alink = argv[i + 1]; | |
| aname = argv[i + 2]; | |
| apath = argv[i + 3]; | |
| prio_str = argv[i + 4]; | |
| if (strcmp(alink, apath) == 0) | |
| badusage(_("<link> '%s' is the same as <path>"), | |
| alink); | |
| errno = 0; | |
| prio = strtol(prio_str, &prio_end, 10); | |
| if (prio_str == prio_end || *prio_end != '\0') | |
| badusage(_("priority '%s' must be an integer"), | |
| prio_str); | |
| if (prio < INT_MIN || prio > INT_MAX || errno == ERANGE) | |
| badusage(_("priority '%s' is out of range"), | |
| prio_str); | |
| a = alternative_new(aname); | |
| inst_alt = alternative_new(aname); | |
| alternative_set_status(inst_alt, ALT_ST_AUTO); | |
| alternative_set_link(inst_alt, alink); | |
| fileset = fileset_new(apath, prio); | |
| i += 4; | |
| } else if (strcmp("--remove", argv[i]) == 0 || | |
| strcmp("--set", argv[i]) == 0) { | |
| set_action_from_name(argv[i] + 2); | |
| if (MISSING_ARGS(2)) | |
| badusage(_("--%s needs <name> <path>"), argv[i] + 2); | |
| a = alternative_new(argv[i + 1]); | |
| path = argv[i + 2]; | |
| alternative_check_name(a->master_name); | |
| alternative_check_path(path); | |
| i += 2; | |
| } else if (strcmp("--display", argv[i]) == 0 || | |
| strcmp("--query", argv[i]) == 0 || | |
| strcmp("--auto", argv[i]) == 0 || | |
| strcmp("--config", argv[i]) == 0 || | |
| strcmp("--list", argv[i]) == 0 || | |
| strcmp("--remove-all", argv[i]) == 0) { | |
| set_action_from_name(argv[i] + 2); | |
| if (MISSING_ARGS(1)) | |
| badusage(_("--%s needs <name>"), argv[i] + 2); | |
| a = alternative_new(argv[i + 1]); | |
| alternative_check_name(a->master_name); | |
| i++; | |
| } else if (strcmp("--all", argv[i]) == 0 || | |
| strcmp("--get-selections", argv[i]) == 0 || | |
| strcmp("--set-selections", argv[i]) == 0) { | |
| set_action_from_name(argv[i] + 2); | |
| } else if (strcmp("--slave", argv[i]) == 0) { | |
| const char *slink, *sname, *spath; | |
| struct slave_link *sl; | |
| if (action != ACTION_INSTALL) | |
| badusage(_("--%s only allowed with --%s"), | |
| argv[i] + 2, "install"); | |
| if (MISSING_ARGS(3)) | |
| badusage(_("--%s needs <link> <name> <path>"), | |
| argv[i] + 2); | |
| slink = argv[i + 1]; | |
| sname = argv[i + 2]; | |
| spath = argv[i + 3]; | |
| if (strcmp(slink, spath) == 0) | |
| badusage(_("<link> '%s' is the same as <path>"), | |
| slink); | |
| if (strcmp(inst_alt->master_name, sname) == 0) | |
| badusage(_("<name> '%s' is both primary and slave"), | |
| sname); | |
| if (strcmp(slink, inst_alt->master_link) == 0) | |
| badusage(_("<link> '%s' is both primary and slave"), | |
| slink); | |
| if (alternative_has_slave(inst_alt, sname)) | |
| badusage(_("duplicate slave <name> '%s'"), sname); | |
| for (sl = inst_alt->slaves; sl; sl = sl->next) { | |
| const char *linkname = sl->link; | |
| if (linkname == NULL) | |
| linkname = ""; | |
| if (strcmp(linkname, slink) == 0) | |
| badusage(_("duplicate slave <link> '%s'"), | |
| slink); | |
| } | |
| alternative_add_slave(inst_alt, sname, slink); | |
| fileset_add_slave(fileset, sname, spath); | |
| i+= 3; | |
| } else if (strcmp("--log", argv[i]) == 0) { | |
| if (MISSING_ARGS(1)) | |
| badusage(_("--%s needs a <file> argument"), | |
| argv[i] + 2); | |
| free(log_file); | |
| log_file = fsys_get_path(argv[i + 1]); | |
| i++; | |
| } else if (strcmp("--altdir", argv[i]) == 0) { | |
| if (MISSING_ARGS(1)) | |
| badusage(_("--%s needs a <directory> argument"), | |
| argv[i] + 2); | |
| altdir = argv[i + 1]; | |
| i++; | |
| /* If altdir is below instdir, convert it to a relative | |
| * path, as we will prepend instdir as needed. */ | |
| if (strncmp(altdir, instdir, instdir_len) == 0) | |
| altdir += instdir_len; | |
| } else if (strcmp("--admindir", argv[i]) == 0) { | |
| if (MISSING_ARGS(1)) | |
| badusage(_("--%s needs a <directory> argument"), | |
| argv[i] + 2); | |
| free(admdir); | |
| admdir = xstrdup(argv[i + 1]); | |
| i++; | |
| } else if (strcmp("--instdir", argv[i]) == 0) { | |
| if (MISSING_ARGS(1)) | |
| badusage(_("--%s needs a <directory> argument"), | |
| argv[i] + 2); | |
| fsys_set_dir(argv[i + 1]); | |
| i++; | |
| /* If altdir is below instdir, convert it to a relative | |
| * path, as we will prepend instdir as needed. */ | |
| if (strncmp(altdir, instdir, instdir_len) == 0) | |
| altdir += instdir_len; | |
| } else if (strcmp("--root", argv[i]) == 0) { | |
| if (MISSING_ARGS(1)) | |
| badusage(_("--%s needs a <directory> argument"), | |
| argv[i] + 2); | |
| set_rootdir(argv[i + 1]); | |
| i++; | |
| } else if (strcmp("--skip-auto", argv[i]) == 0) { | |
| opt_skip_auto = 1; | |
| } else if (strcmp("--force", argv[i]) == 0) { | |
| opt_force = 1; | |
| } else { | |
| badusage(_("unknown option '%s'"), argv[i]); | |
| } | |
| } | |
| if (action == ACTION_NONE) | |
| badusage(_("need --%s, --%s, --%s, --%s, --%s, --%s, --%s, " | |
| "--%s, --%s, --%s, --%s or --%s"), | |
| "display", "query", "list", "get-selections", | |
| "config", "set", "set-selections", "install", | |
| "remove", "all", "remove-all", "auto"); | |
| debug("root=%s admdir=%s altdir=%s", instdir, admdir, altdir); | |
| /* The following actions might modify the current alternative. */ | |
| if (action == ACTION_SET || | |
| action == ACTION_AUTO || | |
| action == ACTION_CONFIG || | |
| action == ACTION_REMOVE || | |
| action == ACTION_REMOVE_ALL || | |
| action == ACTION_INSTALL) | |
| modifies_alt = true; | |
| /* The following actions might modify the system somehow. */ | |
| if (modifies_alt || | |
| action == ACTION_CONFIG_ALL || | |
| action == ACTION_SET_SELECTIONS) | |
| modifies_sys = true; | |
| if (action == ACTION_INSTALL) | |
| alternative_check_install_args(inst_alt, fileset); | |
| if (action == ACTION_DISPLAY || | |
| action == ACTION_QUERY || | |
| action == ACTION_LIST || | |
| action == ACTION_SET || | |
| action == ACTION_AUTO || | |
| action == ACTION_CONFIG || | |
| action == ACTION_REMOVE_ALL) { | |
| /* Load the alternative info, stop on failure. */ | |
| if (!alternative_load(a, ALTDB_WARN_PARSER)) | |
| error(_("no alternatives for %s"), a->master_name); | |
| } else if (action == ACTION_REMOVE) { | |
| /* XXX: Be consistent for now with the case when we | |
| * try to remove a non-existing path from an existing | |
| * link group file. */ | |
| if (!alternative_load(a, ALTDB_WARN_PARSER)) { | |
| verbose(_("no alternatives for %s"), a->master_name); | |
| alternative_free(a); | |
| free(log_file); | |
| free(admdir); | |
| exit(0); | |
| } | |
| } else if (action == ACTION_INSTALL) { | |
| /* Load the alternative info, ignore failures. */ | |
| alternative_load(a, ALTDB_WARN_PARSER); | |
| } | |
| if (modifies_sys) | |
| log_msg("run with %s", get_argv_string(argc, argv)); | |
| if (modifies_alt) { | |
| current_choice = alternative_get_current(a); | |
| alternative_select_mode(a, current_choice); | |
| } | |
| /* Handle actions. */ | |
| if (action == ACTION_CONFIG_ALL) { | |
| alternative_config_all(); | |
| } else if (action == ACTION_GET_SELECTIONS) { | |
| alternative_get_selections(); | |
| } else if (action == ACTION_SET_SELECTIONS) { | |
| alternative_set_selections(stdin, _("<standard input>")); | |
| } else if (action == ACTION_DISPLAY) { | |
| alternative_display_user(a); | |
| } else if (action == ACTION_QUERY) { | |
| alternative_display_query(a); | |
| } else if (action == ACTION_LIST) { | |
| alternative_display_list(a); | |
| } else if (action == ACTION_SET) { | |
| new_choice = alternative_set_manual(a, path); | |
| } else if (action == ACTION_AUTO) { | |
| new_choice = alternative_set_auto(a); | |
| } else if (action == ACTION_CONFIG) { | |
| new_choice = alternative_config(a, current_choice); | |
| } else if (action == ACTION_REMOVE) { | |
| new_choice = alternative_remove(a, current_choice, path); | |
| } else if (action == ACTION_REMOVE_ALL) { | |
| alternative_choices_free(a); | |
| } else if (action == ACTION_INSTALL) { | |
| new_choice = alternative_install(&a, inst_alt, current_choice, | |
| fileset); | |
| } | |
| if (modifies_alt) | |
| alternative_update(a, current_choice, new_choice); | |
| if (a) | |
| alternative_free(a); | |
| free(new_choice); | |
| free(log_file); | |
| free(admdir); | |
| /* BEGIN: Termux-added: mandoc update hook */ | |
| if (should_update_mandoc) | |
| update_mandoc(); | |
| /* END: Termux-added: mandoc update hook */ | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment