aboutsummaryrefslogblamecommitdiffstats
path: root/makeenv.c
blob: 2f88f2d377a096323782ea5d8461972aa47205e4 (plain) (tree)
































                                                                                                          
                                





                       








                                                                                                  
















































                                                                                                          
                              













                                                                                                          
                                
























































































                                                                                                 
             

             








                                    
 
 




                                            
 


                                        
 

                             
 


                               
 
 




                                      















                            

























































                                                                                                                  
                                          


                                                        
                                                         



                                                                                                               
                                                        
                                 












                                                          






                                                                   


                                           
                                             





































































                                                                                                                      
                             




                                   
                                                           




                                                                                           
/* See LICENSE file for copyright and license details. */
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>


static const char *argv0 = "makeenv";
static const char **options = NULL;
static const char **macros = NULL;
static const char **targets = NULL;
static size_t noptions = 0;
static size_t nmacros = 0;
static size_t ntargets = 0;
static size_t options_size = 0;
static size_t macros_size = 0;
static size_t targets_size = 0;


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;
}


static void
put_option_short(int c)
{
	char *opt = malloc(3);
	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);
}


static void
put_option_prefix_dash(const char *s)
{
	size_t len = strlen(s);
	char *arg = malloc(len + 2);
	if (!arg) {
		fprintf(stderr, "%s: failed to allocate enough memory to load .makeenv\n", argv0);
		exit(125);
	}
	stpcpy(stpcpy(arg, "-"), s);
	put_option(arg);
}


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);
}


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;
}


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;
}


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);
}

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;
	}
}



#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);
}


static const char **
create_cmdline(void)
{
	size_t i, j = 1, n = noptions + nmacros + ntargets + 2;
	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];
	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;
}


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;
}


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)
		stpcpy(p, val);
}


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;

	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;
	}

	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 (!envlen) {
		close(fd);
		goto exec;
	}

	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';

	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;
		}
	}

	close(fd);

	unarged_opts = get("MAKEENV_OPTS_NO_ARG", "eiknpqrSst");
	arged_opts = get("MAKEENV_OPTS_ARG", "f");
	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", "");

	for (; *argv; argv++) {
		if (!strcmp(*argv, "--")) {
			put_operand(*argv++);
			break;
		} else if ((*argv)[0] != '-' || !(*argv)[1]) {
			/* do not break, as make(1) allows mixing options and operands */
			put_operand(*argv);
			operand_found = 1;
			continue;
		}

		if (operand_found && !warned_reordered) {
			warned_reordered = 1;
			fprintf(stderr, "%s: warning: reordering operands to after options\n", argv0);
		}

		if ((*argv)[1] == '-') {
			arg = *argv;
			if (strchr(arg, '=') || contains(unarged_longopts, arg)) {
				put_option(arg);
			} else if (contains(arged_longopts, arg)) {
				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)) {
				put_option(arg);
				if (argv[1] && argv[1][0] != '-')
					put_option(*++argv);
			} else {
				fprintf(stderr, "%s: option %s not recognised\n", argv0, arg);
				return 125;
			}
		} else {
			arg = &(*argv)[1];
			while (*arg) {
				if (strchr(unarged_opts, *arg)) {
					put_option_short(*arg++);
				} else if (strchr(arged_opts, *arg)) {
					if (arg[1]) {
						put_option_short(*arg++);
						put_option(arg);
						break;
					} else if (argv[1]) {
						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)) {
					put_option_prefix_dash(arg);
					break;
				} else if (strchr(optarged_opts, *arg)) {
					if (arg[1]) {
						put_option_prefix_dash(arg);
					} else {
						put_option_short(*arg++);
						if (argv[1] && argv[1][0] != '-')
							put_option(*++argv);
					}
					break;
				} else {
					fprintf(stderr, "%s: option -%c not recognised\n", argv0, *arg);
					return 125;
				}
			}
		}
	}

	for (; *argv; argv++)
		put_operand(*argv);

	args = create_cmdline();

exec:
	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;
}