aboutsummaryrefslogblamecommitdiffstats
path: root/makeenv.c
blob: df940becaf83360fc9bc4ae660da8f0014c3040b (plain) (tree)
1
2
3
4
5
6
7
8
9
10









                                                         


                                                  
                                     



                                                    
                                   



                                                   
                                  



                                                    
                                   



                                   
                           



                                  
                          



                                   
                           



                                             
                               



                                            
                              



                                             


                               




                                       










                                                                                                          
                                


 




                                                                       


                       
                                         







                                                                                                  


 




                                                                      



                                     
                                              








                                                                                                  




                                                                         
















                                                 








                                                             














                                                                                                          
                              


 








                                                             










                                                                                                          
                                


 




                                                                          















                                                 






                                                                      































                                                                                          






                                                                          
















                                                        


















                                                                        


                    
                                                               






                                                                                                 
                         








                                       





                                                     
             

             








                                    
 
 




                                                    




                                            
 


                                        
 

                             
 
                     
                                                  
 
 
 






                                                                                     




                                      















                            













                                      
                           







                                                                                                         
                           


















                                                                                                                  
                                                                                




                          
                                              






                                                                                                  



                                                            




                                      
                                          


                                                        
                                                         



                                                                                                               
                                                        
                                 










                                                          
                                                    
                                                                
                                                   




                                                                   
 
                                                 
                               
                                                                
                                           
                              



                                                                                                                 





                                                                                         
                                                                              




                                                                                                      
                                 
                                        
                                         

                                                                                  

                                                                                                     

                                                                   
                                                                         






                                                                                                            
                                                                                               
                                                

                                                                                
                                                            
                                 




                                                                                              
                                           


                                                                 
                                                                            

                                                                      
                                                                          
                                                     
                                                                                    



                                                                         
                                                                                      







                                                                                                                      
                                                                                                                           


                                                                         
                                                                                               
                                                     
                                                                                    

                                                                            
                                                                                                                     
                                                                         







                                                                                                                        
                                                                            
                                                 









                                                                                                        
                                            
                             

                                   
                                                                                                           


                                
                             
                                                           




                                                                                           
/* 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>


/**
 * 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, "--")) {
			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 dettached 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 dettached 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 dettached 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 dettached argument */
					put_option_prefix_dash(arg);
					break;
				} else if (strchr(optarged_opts, *arg)) {
					/* Option may have an attached or dettached argument */
					if (arg[1]) {
						/* Argument is attached to option */
						put_option_prefix_dash(arg);
					} else {
						/* Either there is no argument, or it is dettached from the option */
						put_option_short(*arg++);
						if (argv[1] && argv[1][0] != '-') {
							/* Argument exist and is dettached. 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;
}