/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include #include /** * The name of the process, used in error messages */ static const char *argv0 = "makeenv"; /** * Options found in .makeenv and in the command line */ static const char **options = NULL; /** * Macros found in .makeenv and in the command line */ static const char **macros = NULL; /** * Targets found in .makeenv and in the command line */ static const char **targets = NULL; /** * Number of elements in `.options` */ static size_t noptions = 0; /** * Number of elements in `.macros` */ static size_t nmacros = 0; /** * Number of elements in `.targets` */ static size_t ntargets = 0; /** * Number of elements allocated to `.options` */ static size_t options_size = 0; /** * Number of elements allocated to `.macros` */ static size_t macros_size = 0; /** * Number of elements allocated to `.targets` */ static size_t targets_size = 0; /** * Add an option to the list of options * * @param s The option to add */ static void put_option(const char *s) { if (noptions == options_size) { options_size += 16; options = realloc(options, options_size * sizeof(*options)); if (!options) { fprintf(stderr, "%s: failed to allocate enough memory to load .makeenv\n", argv0); exit(125); } } options[noptions++] = s; } /** * Add an option to the list of options * * @param c The letter of the option to add, for example 'x' for "-x" */ static void put_option_short(int c) { char *opt = malloc(sizeof("-x")); if (!opt) { fprintf(stderr, "%s: failed to allocate enough memory to load .makeenv\n", argv0); exit(125); } opt[0] = '-'; opt[1] = (char)c; opt[2] = '\0'; put_option(opt); } /** * Add an option to the list of options * * @param s The option to add, it will be prefixed with a dash ("-") */ static void put_option_prefix_dash(const char *s) { size_t len = strlen(s); char *arg = malloc(sizeof("-") + len); if (!arg) { fprintf(stderr, "%s: failed to allocate enough memory to load .makeenv\n", argv0); exit(125); } stpcpy(stpcpy(arg, "-"), s); put_option(arg); } /** * Add all options from a list to the list of options * * @parm s The list of options to add; elements are separated by spaces */ static void put_options(char *s) { const char *word = s; for (; *s; s++) { if (isspace(*s)) { *s = '\0'; if (*word) put_option(word); word = &s[1]; } } if (*word) put_option(word); } /** * Add a macro to the list of macros and to environ(3) * * This function should not be called directly when adding * macros from the command line; use `put_operand` instead * as it will detect and warn about mixing targets and macros * * @param s The macro to add */ static void put_macro(const char *s) { if (putenv(*(char **)(void *)&s)) { fprintf(stderr, "%s: failed to update environment: %s\n", argv0, strerror(errno)); exit(125); } if (nmacros == macros_size) { macros_size += 16; macros = realloc(macros, macros_size * sizeof(*macros)); if (!macros) { fprintf(stderr, "%s: failed to allocate enough memory to load .makeenv\n", argv0); exit(125); } } macros[nmacros++] = s; } /** * Add a target to the list of targets * * This function should not be called directly when adding * targets from the command line; use `put_operand` instead * as it will detect and warn about mixing targets and macros * * @param s The target to add */ static void put_target(const char *s) { if (ntargets == targets_size) { targets_size += 16; targets = realloc(targets, targets_size * sizeof(*targets)); if (!targets) { fprintf(stderr, "%s: failed to allocate enough memory to load .makeenv\n", argv0); exit(125); } } targets[ntargets++] = s; } /** * Add all targets from a list to the list of targets * * @param s The list of targets to add; elements are separated by spaces */ static void put_targets(char *s) { const char *word = s; for (; *s; s++) { if (isspace(*s)) { *s = '\0'; if (*word) put_target(word); word = &s[1]; } } if (*word) put_target(word); } /** * Add an operand to either the list of macros or the list of targets, * depending on the classification of the operand * * @param s The operand to add */ static void put_operand(char *s) { /* * Behaviour is unspecified if any target is specified before * a macro, however make(1) shall allow options to be mixed in * with both macros and targets */ static int warned_mixed = 0; static int found_targets = 0; if (strchr(s, '=')) { /* * Adding macro as a target as we don't want it to be * but into the environment and we don't want to reorder * them if the user mixes macros with targets (targets * ared pulled into the command line after macros so * this will not cause any problems) */ put_target(s); if (found_targets && !warned_mixed) { warned_mixed = 1; fprintf(stderr, "%s: warning: mixing targets and macros in the " "command line results in unspecified behaviour\n", argv0); } } else { put_target(s); found_targets = 1; } } /** * Check if a string appears as a word in another string * * @param set The string to search in; words are separated by spaces * @param sought The string to search for * @return 1 if the string is found, 0 otherwise */ #if defined(__GNUC__) __attribute__((__pure__)) #endif static int contains(const char *set, const char *sought) { size_t m, n = strlen(sought); const char *p, *q; for (p = set; (q = strchr(p, ' ')); p = &q[1]) { m = (size_t)(q - p); if (m == n && !strncmp(p, sought, n)) return 1; } return !strcmp(p, sought); } /** * Create the argument list for for make(1) * * The arguments will be listed in the order required by make(1posix): * targets follow macros, however also, with POSIX.1-2017, Section 12.2, * Utility Syntax Guidelines, except for Guideline 9 enforced: operands * (macros and targets) follow options. The list will also begin with * and unset element where the process name for make(1) can be placed. * * The function will insert a "--" argument to separate options from * operands, to ensure it is put in a position, which does not cause * any problems, due to argument reordering, when the user has added * a "--" argument. The "--" argument will be added regardless of * whether the user added a "--" argument; in fact any "--" argument * from the user shall be skipped unless it can be determined that * it is a target ranther than and option-list ender. * * @return `NULL`-terminated list of arguments */ static const char ** create_cmdline(void) { size_t i, j = 1, n = noptions + nmacros + ntargets + 3; const char **args = malloc(n * sizeof(char *)); if (!args) { fprintf(stderr, "%s: failed to allocate enough memory to execute make\n", argv0); exit(125); } for (i = 0; i < noptions; i++) args[j++] = options[i]; args[j++] = "--"; for (i = 0; i < nmacros; i++) args[j++] = macros[i]; for (i = 0; i < ntargets; i++) args[j++] = targets[i]; args[j] = NULL; return args; } /** * Trim leading and trailing whitespace from a string * * @param s The string to trim * @return The trimmed string */ static char * trim(char *s) { size_t i, last = (size_t)-1; while (isspace(*s)) s++; for (i = 0; s[i]; i++) if (!isspace(s[i])) last = i; s[last + 1] = '\0'; return s; } /** * Trim whitespace around an equals sign in a string * * @param s The string to trim, will be updated */ static void trim_around_equals(char *s) { char *equals = strchr(s, '='); char *p = equals, *val = &equals[1]; while (p != s && isspace(p[-1])) p--; *p++ = '='; while (isspace(*val)) val++; if (val != p) memmove(p, val, strlen(val) + 1U); } /** * Get an environment variable or a default value * * @param var The name of the environment variable * @param def The default value to return if the environment variable is not set * @return The value of the environment variable, or the default value if unset */ static const char * get(const char *var, const char *def) { const char *ret = getenv(var); return ret ? ret : def; } int main(int argc, char *argv[]) { int exitstatus, fd; char *env = NULL; size_t envsize = 0; size_t envlen = 0; ssize_t r; size_t i; char *line; int has_equals; const char **args; const char *arg; const char *unarged_opts; const char *arged_opts; const char *optatarged_opts; const char *optarged_opts; const char *unarged_longopts; const char *arged_longopts; const char *optarged_longopts; int operand_found = 0; int warned_reordered = 0; args = (void *)argv; argv0 = *argv++; (void) argc; /* Open .makeenv */ fd = open(".makeenv", O_RDONLY); if (fd < 0) { if (errno == ENOENT) goto exec; fprintf(stderr, "%s: failed to open .makeenv for reading: %s\n", argv0, strerror(errno)); return 125; } /* Read .makeenv */ for (;;) { if (envlen == envsize) { envsize += 8096; env = realloc(env, envsize); if (!env) { fprintf(stderr, "%s: failed to allocate enough memory to load .makeenv\n", argv0); return 125; } } r = read(fd, &env[envlen], envsize - envlen); if (r <= 0) { if (!r) break; fprintf(stderr, "%s: failed to read .makeenv\n", argv0); return 125; } envlen += (size_t)r; } /* If .makeenv is empty, use make(1) without any additional arguments */ if (!envlen) { close(fd); goto exec; } /* Ensure .makeenv is LF-terminated */ env = realloc(env, envlen + 1); if (!env) { fprintf(stderr, "%s: failed to allocate enough memory to load .makeenv\n", argv0); return 125; } env[envlen++] = '\n'; /* Close .makeenv */ close(fd); /* Add options, macros, and targets from .makeenv */ line = env; has_equals = 0; for (i = 0; i < envlen; i++) { if (env[i] == '\n') { env[i] = '\0'; line = trim(line); if (line[0] == '-' && line[1]) { put_options(line); } else if (has_equals) { trim_around_equals(line); if (*line == '=') { fprintf(stderr, "%s: empty variable name listed in .makeenv\n", argv0); return 125; } else if (*line != '#') { put_macro(line); } } else { if (*line && *line != '#') put_targets(line); } line = &env[i + 1]; has_equals = 0; } else if (env[i] == '=') { has_equals = 1; } } /* Get option syntax from the environment */ unarged_opts = get("MAKEENV_OPTS_NO_ARG", "eiknpqrSst"); arged_opts = get("MAKEENV_OPTS_ARG", "fW"); optatarged_opts = get("MAKEENV_OPTS_OPT_ATTACHED_ARG", ""); optarged_opts = get("MAKEENV_OPTS_OPT_ARG", ""); unarged_longopts = get("MAKEENV_LONG_OPTS_NO_ARG", ""); arged_longopts = get("MAKEENV_LONG_OPTS_ARG", ""); optarged_longopts = get("MAKEENV_LONG_OPTS_OPT_ARG", ""); /* Add arguments from the command line */ for (; *argv; argv++) { /* Stop processing options when "--" is found */ if (!strcmp(*argv, "--")) { argv++; break; } /* Add operands listed before "--", but do not stop processing subsequent arguments as options */ if ((*argv)[0] != '-' || !(*argv)[1]) { /* do not break, as make(1) allows mixing options and operands */ put_operand(*argv); operand_found = 1; continue; } /* Warn when reordering operands to be placed after options */ if (operand_found && !warned_reordered) { warned_reordered = 1; fprintf(stderr, "%s: warning: reordering operands to after options\n", argv0); } /* Add options */ if ((*argv)[1] == '-') { /* Long option */ arg = *argv; if (strchr(arg, '=') || contains(unarged_longopts, arg)) { /* The option either has an attached argument (so it's always safe to * add) or it is recognised to never have an argument */ put_option(arg); } else if (contains(arged_longopts, arg)) { /* Long option with detached argument */ put_option(arg); if (!argv[1]) { fprintf(stderr, "%s: argument for option %s missing\n", argv0, arg); return 125; } put_option(*++argv); } else if (contains(optarged_longopts, arg)) { /* Long option with either detached argument or no argument */ put_option(arg); if (argv[1] && argv[1][0] != '-') { /* The option has a detached argument */ put_option(*++argv); } } else { fprintf(stderr, "%s: option %s not recognised\n", argv0, arg); return 125; } } else { /* Short options */ arg = &(*argv)[1]; while (*arg) { if (strchr(unarged_opts, *arg)) { /* Option cannot have an argument */ put_option_short(*arg++); } else if (strchr(arged_opts, *arg)) { /* Option must have an argument */ if (arg[1]) { /* Argument is attached to option */ put_option_short(*arg++); put_option(arg); break; } else if (argv[1]) { /* Argument is detached from option*/ put_option_short(*arg++); put_option(*++argv); break; } else { fprintf(stderr, "%s: argument for option -%c missing\n", argv0, *arg); return 125; } } else if (strchr(optatarged_opts, *arg)) { /* Option may have an attached argument, but it cannot have a detached argument */ put_option_prefix_dash(arg); break; } else if (strchr(optarged_opts, *arg)) { /* Option may have an attached or detached argument */ if (arg[1]) { /* Argument is attached to option */ put_option_prefix_dash(arg); } else { /* Either there is no argument, or it is detached from the option */ put_option_short(*arg++); if (argv[1] && argv[1][0] != '-') { /* Argument exist and is detached. We assume that if the next * argument in the command line is the option's argument unless * it starts with '-'; however some implementations can be more * intelligent about determining what is an option argument and * what is an operands. This means that in some cases makeenv(1) * would require the user to attach the argument even when their * implementation of make(1) does not require this. */ put_option(*++argv); } } break; } else { fprintf(stderr, "%s: option -%c not recognised\n", argv0, *arg); return 125; } } } } /* Add operands listed after "--" */ for (; *argv; argv++) put_operand(*argv); /* Create the arguments for make(1); the zeroth argument will be unset and to be filled in later */ args = create_cmdline(); exec: /* Execute make(1) */ args[0] = get("MAKEENV_MAKE", get("MAKE", "make")); execvp(args[0], (void *)args); exitstatus = errno == ENOENT ? 127 : 126; fprintf(stderr, "%s: failed to execute %s: %s\n", argv0, args[0], strerror(errno)); return exitstatus; }