diff options
Diffstat (limited to 'src/kmk/function.c')
-rw-r--r-- | src/kmk/function.c | 8250 |
1 files changed, 8250 insertions, 0 deletions
diff --git a/src/kmk/function.c b/src/kmk/function.c new file mode 100644 index 0000000..5ad8586 --- /dev/null +++ b/src/kmk/function.c @@ -0,0 +1,8250 @@ +/* Builtin function expansion for GNU Make. +Copyright (C) 1988-2016 Free Software Foundation, Inc. +This file is part of GNU Make. + +GNU Make 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 3 of the License, or (at your option) any later +version. + +GNU Make 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 <http://www.gnu.org/licenses/>. */ + +#include "makeint.h" +#include "filedef.h" +#include "variable.h" +#include "dep.h" +#include "job.h" +#include "commands.h" +#include "debug.h" + +#ifdef _AMIGA +#include "amiga.h" +#endif + +#ifdef WINDOWS32 /* bird */ +# include "pathstuff.h" +# ifdef CONFIG_NEW_WIN_CHILDREN +# include "w32/winchildren.h" +# endif +#endif + +#ifdef KMK_HELPERS +# include "kbuild.h" +#endif +#ifdef CONFIG_WITH_PRINTF +# include "kmkbuiltin.h" +#endif +#ifdef CONFIG_WITH_XARGS /* bird */ +# ifdef HAVE_LIMITS_H +# include <limits.h> +# endif +#endif +#ifdef CONFIG_WITH_COMPILER +# include "kmk_cc_exec.h" +#endif +#include <assert.h> /* bird */ + +#if defined (CONFIG_WITH_MATH) || defined (CONFIG_WITH_NANOTS) || defined (CONFIG_WITH_FILE_SIZE) /* bird */ +# include <ctype.h> +typedef big_int math_int; +static char *math_int_to_variable_buffer (char *, math_int); +static math_int math_int_from_string (const char *str); +#endif + +#ifdef CONFIG_WITH_NANOTS /* bird */ +# ifdef WINDOWS32 +# include <Windows.h> +# endif +#endif + +#ifdef __OS2__ +# define CONFIG_WITH_OS2_LIBPATH 1 +#endif +#ifdef CONFIG_WITH_OS2_LIBPATH +# define INCL_BASE +# define INCL_ERRROS +# include <os2.h> + +# define QHINF_EXEINFO 1 /* NE exeinfo. */ +# define QHINF_READRSRCTBL 2 /* Reads from the resource table. */ +# define QHINF_READFILE 3 /* Reads from the executable file. */ +# define QHINF_LIBPATHLENGTH 4 /* Gets the libpath length. */ +# define QHINF_LIBPATH 5 /* Gets the entire libpath. */ +# define QHINF_FIXENTRY 6 /* NE only */ +# define QHINF_STE 7 /* NE only */ +# define QHINF_MAPSEL 8 /* NE only */ + extern APIRET APIENTRY DosQueryHeaderInfo(HMODULE hmod, ULONG ulIndex, PVOID pvBuffer, ULONG cbBuffer, ULONG ulSubFunction); +#endif /* CONFIG_WITH_OS2_LIBPATH */ + +#if defined(KMK) || defined(CONFIG_WITH_LAZY_DEPS_VARS) +/** Checks if the @a_cch characters (bytes) in @a a_psz equals @a a_szConst. */ +# define STR_N_EQUALS(a_psz, a_cch, a_szConst) \ + ( (a_cch) == sizeof (a_szConst) - 1 && !strncmp ((a_psz), (a_szConst), sizeof (a_szConst) - 1) ) +#endif + +#ifdef KMK +# ifdef _MSC_VER +# include "kmkbuiltin/mscfakes.h" +# endif +# include "version_compare.h" +#endif + + +struct function_table_entry + { + union { + char *(*func_ptr) (char *output, char **argv, const char *fname); + gmk_func_ptr alloc_func_ptr; + } fptr; + const char *name; + unsigned char len; + unsigned char minimum_args; + unsigned char maximum_args; + unsigned char expand_args:1; + unsigned char alloc_fn:1; + }; + +static unsigned long +function_table_entry_hash_1 (const void *keyv) +{ + const struct function_table_entry *key = keyv; + return_STRING_N_HASH_1 (key->name, key->len); +} + +static unsigned long +function_table_entry_hash_2 (const void *keyv) +{ + const struct function_table_entry *key = keyv; + return_STRING_N_HASH_2 (key->name, key->len); +} + +static int +function_table_entry_hash_cmp (const void *xv, const void *yv) +{ + const struct function_table_entry *x = xv; + const struct function_table_entry *y = yv; + int result = x->len - y->len; + if (result) + return result; + return_STRING_N_COMPARE (x->name, y->name, x->len); +} + +static struct hash_table function_table; + +#ifdef CONFIG_WITH_MAKE_STATS +long make_stats_allocations = 0; +long make_stats_reallocations = 0; +unsigned long make_stats_allocated = 0; +unsigned long make_stats_ht_lookups = 0; +unsigned long make_stats_ht_collisions = 0; +#endif + + +/* Store into VARIABLE_BUFFER at O the result of scanning TEXT and replacing + each occurrence of SUBST with REPLACE. TEXT is null-terminated. SLEN is + the length of SUBST and RLEN is the length of REPLACE. If BY_WORD is + nonzero, substitutions are done only on matches which are complete + whitespace-delimited words. */ + +char * +subst_expand (char *o, const char *text, const char *subst, const char *replace, + unsigned int slen, unsigned int rlen, int by_word) +{ + const char *t = text; + const char *p; + + if (slen == 0 && !by_word) + { + /* The first occurrence of "" in any string is its end. */ + o = variable_buffer_output (o, t, strlen (t)); + if (rlen > 0) + o = variable_buffer_output (o, replace, rlen); + return o; + } + + do + { + if (by_word && slen == 0) + /* When matching by words, the empty string should match + the end of each word, rather than the end of the whole text. */ + p = end_of_token (next_token (t)); + else + { + p = strstr (t, subst); + if (p == 0) + { + /* No more matches. Output everything left on the end. */ + o = variable_buffer_output (o, t, strlen (t)); + return o; + } + } + + /* Output everything before this occurrence of the string to replace. */ + if (p > t) + o = variable_buffer_output (o, t, p - t); + + /* If we're substituting only by fully matched words, + or only at the ends of words, check that this case qualifies. */ + if (by_word + && ((p > text && !ISSPACE (p[-1])) + || ! STOP_SET (p[slen], MAP_SPACE|MAP_NUL))) + /* Struck out. Output the rest of the string that is + no longer to be replaced. */ + o = variable_buffer_output (o, subst, slen); + else if (rlen > 0) + /* Output the replacement string. */ + o = variable_buffer_output (o, replace, rlen); + + /* Advance T past the string to be replaced. */ + t = p + slen; + } while (*t != '\0'); + + return o; +} + + +/* Store into VARIABLE_BUFFER at O the result of scanning TEXT + and replacing strings matching PATTERN with REPLACE. + If PATTERN_PERCENT is not nil, PATTERN has already been + run through find_percent, and PATTERN_PERCENT is the result. + If REPLACE_PERCENT is not nil, REPLACE has already been + run through find_percent, and REPLACE_PERCENT is the result. + Note that we expect PATTERN_PERCENT and REPLACE_PERCENT to point to the + character _AFTER_ the %, not to the % itself. +*/ + +char * +patsubst_expand_pat (char *o, const char *text, + const char *pattern, const char *replace, + const char *pattern_percent, const char *replace_percent) +{ + unsigned int pattern_prepercent_len, pattern_postpercent_len; + unsigned int replace_prepercent_len, replace_postpercent_len; + const char *t; + unsigned int len; + int doneany = 0; + + /* Record the length of REPLACE before and after the % so we don't have to + compute these lengths more than once. */ + if (replace_percent) + { + replace_prepercent_len = replace_percent - replace - 1; + replace_postpercent_len = strlen (replace_percent); + } + else + { + replace_prepercent_len = strlen (replace); + replace_postpercent_len = 0; + } + + if (!pattern_percent) + /* With no % in the pattern, this is just a simple substitution. */ + return subst_expand (o, text, pattern, replace, + strlen (pattern), strlen (replace), 1); + + /* Record the length of PATTERN before and after the % + so we don't have to compute it more than once. */ + pattern_prepercent_len = pattern_percent - pattern - 1; + pattern_postpercent_len = strlen (pattern_percent); + + while ((t = find_next_token (&text, &len)) != 0) + { + int fail = 0; + + /* Is it big enough to match? */ + if (len < pattern_prepercent_len + pattern_postpercent_len) + fail = 1; + + /* Does the prefix match? */ + if (!fail && pattern_prepercent_len > 0 + && (*t != *pattern + || t[pattern_prepercent_len - 1] != pattern_percent[-2] + || !strneq (t + 1, pattern + 1, pattern_prepercent_len - 1))) + fail = 1; + + /* Does the suffix match? */ + if (!fail && pattern_postpercent_len > 0 + && (t[len - 1] != pattern_percent[pattern_postpercent_len - 1] + || t[len - pattern_postpercent_len] != *pattern_percent + || !strneq (&t[len - pattern_postpercent_len], + pattern_percent, pattern_postpercent_len - 1))) + fail = 1; + + if (fail) + /* It didn't match. Output the string. */ + o = variable_buffer_output (o, t, len); + else + { + /* It matched. Output the replacement. */ + + /* Output the part of the replacement before the %. */ + o = variable_buffer_output (o, replace, replace_prepercent_len); + + if (replace_percent != 0) + { + /* Output the part of the matched string that + matched the % in the pattern. */ + o = variable_buffer_output (o, t + pattern_prepercent_len, + len - (pattern_prepercent_len + + pattern_postpercent_len)); + /* Output the part of the replacement after the %. */ + o = variable_buffer_output (o, replace_percent, + replace_postpercent_len); + } + } + + /* Output a space, but not if the replacement is "". */ + if (fail || replace_prepercent_len > 0 + || (replace_percent != 0 && len + replace_postpercent_len > 0)) + { + o = variable_buffer_output (o, " ", 1); + doneany = 1; + } + } +#ifndef CONFIG_WITH_VALUE_LENGTH + if (doneany) + /* Kill the last space. */ + --o; +#else + /* Kill the last space and make sure there is a terminator there + so that strcache_add_len doesn't have to do a lot of exacty work + when expand_deps sends the output its way. */ + if (doneany) + *--o = '\0'; + else + o = variable_buffer_output (o, "\0", 1) - 1; +#endif + + return o; +} + +/* Store into VARIABLE_BUFFER at O the result of scanning TEXT + and replacing strings matching PATTERN with REPLACE. + If PATTERN_PERCENT is not nil, PATTERN has already been + run through find_percent, and PATTERN_PERCENT is the result. + If REPLACE_PERCENT is not nil, REPLACE has already been + run through find_percent, and REPLACE_PERCENT is the result. + Note that we expect PATTERN_PERCENT and REPLACE_PERCENT to point to the + character _AFTER_ the %, not to the % itself. +*/ + +char * +patsubst_expand (char *o, const char *text, char *pattern, char *replace) +{ + const char *pattern_percent = find_percent (pattern); + const char *replace_percent = find_percent (replace); + + /* If there's a percent in the pattern or replacement skip it. */ + if (replace_percent) + ++replace_percent; + if (pattern_percent) + ++pattern_percent; + + return patsubst_expand_pat (o, text, pattern, replace, + pattern_percent, replace_percent); +} + +#if defined (CONFIG_WITH_OPTIMIZATION_HACKS) || defined (CONFIG_WITH_VALUE_LENGTH) + +/* Char map containing the valid function name characters. */ +char func_char_map[256]; + +/* Do the hash table lookup. */ + +MY_INLINE const struct function_table_entry * +lookup_function_in_hash_tab (const char *s, unsigned char len) +{ + struct function_table_entry function_table_entry_key; + function_table_entry_key.name = s; + function_table_entry_key.len = len; + + return hash_find_item (&function_table, &function_table_entry_key); +} + +/* Look up a function by name. */ + +MY_INLINE const struct function_table_entry * +lookup_function (const char *s, unsigned int len) +{ + unsigned char ch; +# if 0 /* insane loop unroll */ + + if (len > MAX_FUNCTION_LENGTH) + len = MAX_FUNCTION_LENGTH + 1; + +# define X(idx) \ + if (!func_char_map[ch = s[idx]]) \ + { \ + if (ISBLANK (ch)) \ + return lookup_function_in_hash_tab (s, idx); \ + return 0; \ + } +# define Z(idx) \ + return lookup_function_in_hash_tab (s, idx); + + switch (len) + { + default: + assert (0); + case 0: return 0; + case 1: return 0; + case 2: X(0); X(1); Z(2); + case 3: X(0); X(1); X(2); Z(3); + case 4: X(0); X(1); X(2); X(3); Z(4); + case 5: X(0); X(1); X(2); X(3); X(4); Z(5); + case 6: X(0); X(1); X(2); X(3); X(4); X(5); Z(6); + case 7: X(0); X(1); X(2); X(3); X(4); X(5); X(6); Z(7); + case 8: X(0); X(1); X(2); X(3); X(4); X(5); X(6); X(7); Z(8); + case 9: X(0); X(1); X(2); X(3); X(4); X(5); X(6); X(7); X(8); Z(9); + case 10: X(0); X(1); X(2); X(3); X(4); X(5); X(6); X(7); X(8); X(9); Z(10); + case 11: X(0); X(1); X(2); X(3); X(4); X(5); X(6); X(7); X(8); X(9); X(10); Z(11); + case 12: X(0); X(1); X(2); X(3); X(4); X(5); X(6); X(7); X(8); X(9); X(10); X(11); Z(12); + case 13: X(0); X(1); X(2); X(3); X(4); X(5); X(6); X(7); X(8); X(9); X(10); X(11); X(12); + if ((ch = s[12]) == '\0' || ISBLANK (ch)) + return lookup_function_in_hash_tab (s, 12); + return 0; + } +# undef Z +# undef X + +# else /* normal loop */ + const char *e = s; + if (len > MAX_FUNCTION_LENGTH) + len = MAX_FUNCTION_LENGTH; + while (func_char_map[ch = *e]) + { + if (!len--) + return 0; + e++; + } + if (ch == '\0' || ISBLANK (ch)) + return lookup_function_in_hash_tab (s, e - s); + return 0; +# endif /* normal loop */ +} + +#else /* original code */ +/* Look up a function by name. */ + +static const struct function_table_entry * +lookup_function (const char *s) +{ + struct function_table_entry function_table_entry_key; + const char *e = s; + while (STOP_SET (*e, MAP_USERFUNC)) + e++; + + if (e == s || !STOP_SET(*e, MAP_NUL|MAP_SPACE)) + return NULL; + + function_table_entry_key.name = s; + function_table_entry_key.len = e - s; + + return hash_find_item (&function_table, &function_table_entry_key); +} +#endif /* original code */ + + +/* Return 1 if PATTERN matches STR, 0 if not. */ + +int +pattern_matches (const char *pattern, const char *percent, const char *str) +{ + unsigned int sfxlen, strlength; + + if (percent == 0) + { + unsigned int len = strlen (pattern) + 1; + char *new_chars = alloca (len); + memcpy (new_chars, pattern, len); + percent = find_percent (new_chars); + if (percent == 0) + return streq (new_chars, str); + pattern = new_chars; + } + + sfxlen = strlen (percent + 1); + strlength = strlen (str); + + if (strlength < (percent - pattern) + sfxlen + || !strneq (pattern, str, percent - pattern)) + return 0; + + return !strcmp (percent + 1, str + (strlength - sfxlen)); +} + +#ifdef KMK +/* Return 1 if PATTERN matches STR, 0 if not. + + PATTERN is the pattern to match against. PERCENT points to the '%' wildcard + inside PATTERN. SFXLEN is the length of pattern following PERCENT. */ + +static int +pattern_matches_ex (const char *pattern, const char *percent, + unsigned int sfxlen, + const char *str, unsigned int strlength) +{ + if (strlength < (percent - pattern) + sfxlen + || !strneq (pattern, str, percent - pattern) + || strcmp (percent + 1, str + (strlength - sfxlen))) + return 0; + return 1; +} +#endif + + +/* Find the next comma or ENDPAREN (counting nested STARTPAREN and + ENDPARENtheses), starting at PTR before END. Return a pointer to + next character. + + If no next argument is found, return NULL. +*/ + +static char * +find_next_argument (char startparen, char endparen, + const char *ptr, const char *end) +{ + int count = 0; + + for (; ptr < end; ++ptr) + if (*ptr == startparen) + ++count; + + else if (*ptr == endparen) + { + --count; + if (count < 0) + return NULL; + } + + else if (*ptr == ',' && !count) + return (char *)ptr; + + /* We didn't find anything. */ + return NULL; +} + + +/* Glob-expand LINE. The returned pointer is + only good until the next call to string_glob. */ + +static char * +string_glob (char *line) +{ + static char *result = 0; + static unsigned int length; + struct nameseq *chain; + unsigned int idx; + + chain = PARSE_FILE_SEQ (&line, struct nameseq, MAP_NUL, NULL, + /* We do not want parse_file_seq to strip './'s. + That would break examples like: + $(patsubst ./%.c,obj/%.o,$(wildcard ./?*.c)). */ + PARSEFS_NOSTRIP|PARSEFS_NOCACHE|PARSEFS_EXISTS); + + if (result == 0) + { + length = 100; + result = xmalloc (100); + } + + idx = 0; + while (chain != 0) + { + struct nameseq *next = chain->next; + unsigned int len = strlen (chain->name); + + if (idx + len + 1 > length) + { + length += (len + 1) * 2; + result = xrealloc (result, length); + } + memcpy (&result[idx], chain->name, len); + idx += len; + result[idx++] = ' '; + + /* Because we used PARSEFS_NOCACHE above, we have to free() NAME. */ + free ((char *)chain->name); +#ifndef CONFIG_WITH_ALLOC_CACHES + free (chain); +#else + alloccache_free (&nameseq_cache, chain); +#endif + chain = next; + } + + /* Kill the last space and terminate the string. */ + if (idx == 0) + result[0] = '\0'; + else + result[idx - 1] = '\0'; + + return result; +} + +/* + Builtin functions + */ + +static char * +func_patsubst (char *o, char **argv, const char *funcname UNUSED) +{ + o = patsubst_expand (o, argv[2], argv[0], argv[1]); + return o; +} + + +static char * +func_join (char *o, char **argv, const char *funcname UNUSED) +{ + int doneany = 0; + + /* Write each word of the first argument directly followed + by the corresponding word of the second argument. + If the two arguments have a different number of words, + the excess words are just output separated by blanks. */ + const char *tp; + const char *pp; + const char *list1_iterator = argv[0]; + const char *list2_iterator = argv[1]; + do + { + unsigned int len1, len2; + + tp = find_next_token (&list1_iterator, &len1); + if (tp != 0) + o = variable_buffer_output (o, tp, len1); + + pp = find_next_token (&list2_iterator, &len2); + if (pp != 0) + o = variable_buffer_output (o, pp, len2); + + if (tp != 0 || pp != 0) + { + o = variable_buffer_output (o, " ", 1); + doneany = 1; + } + } + while (tp != 0 || pp != 0); + if (doneany) + /* Kill the last blank. */ + --o; + + return o; +} + + +static char * +func_origin (char *o, char **argv, const char *funcname UNUSED) +{ + /* Expand the argument. */ + struct variable *v = lookup_variable (argv[0], strlen (argv[0])); + if (v == 0) + o = variable_buffer_output (o, "undefined", 9); + else + switch (v->origin) + { + default: + case o_invalid: + abort (); + break; + case o_default: + o = variable_buffer_output (o, "default", 7); + break; + case o_env: + o = variable_buffer_output (o, "environment", 11); + break; + case o_file: + o = variable_buffer_output (o, "file", 4); + break; + case o_env_override: + o = variable_buffer_output (o, "environment override", 20); + break; + case o_command: + o = variable_buffer_output (o, "command line", 12); + break; + case o_override: + o = variable_buffer_output (o, "override", 8); + break; + case o_automatic: + o = variable_buffer_output (o, "automatic", 9); + break; +#ifdef CONFIG_WITH_LOCAL_VARIABLES + case o_local: + o = variable_buffer_output (o, "local", 5); + break; +#endif + } + + return o; +} + +static char * +func_flavor (char *o, char **argv, const char *funcname UNUSED) +{ + struct variable *v = lookup_variable (argv[0], strlen (argv[0])); + + if (v == 0) + o = variable_buffer_output (o, "undefined", 9); + else + if (v->recursive) + o = variable_buffer_output (o, "recursive", 9); + else + o = variable_buffer_output (o, "simple", 6); + + return o; +} + +#ifdef CONFIG_WITH_WHERE_FUNCTION +static char * +func_where (char *o, char **argv, const char *funcname UNUSED) +{ + struct variable *v = lookup_variable (argv[0], strlen (argv[0])); + char buf[64]; + + if (v == 0) + o = variable_buffer_output (o, "undefined", 9); + else + if (v->fileinfo.filenm) + { + o = variable_buffer_output (o, v->fileinfo.filenm, strlen(v->fileinfo.filenm)); + sprintf (buf, ":%lu", v->fileinfo.lineno); + o = variable_buffer_output (o, buf, strlen(buf)); + } + else + o = variable_buffer_output (o, "no-location", 11); + + return o; +} +#endif /* CONFIG_WITH_WHERE_FUNCTION */ + +static char * +func_notdir_suffix (char *o, char **argv, const char *funcname) +{ + /* Expand the argument. */ + const char *list_iterator = argv[0]; + const char *p2; + int doneany =0; + unsigned int len=0; + + int is_suffix = funcname[0] == 's'; + int is_notdir = !is_suffix; + int stop = MAP_DIRSEP | (is_suffix ? MAP_DOT : 0); +#ifdef VMS + /* For VMS list_iterator points to a comma separated list. To use the common + [find_]next_token, create a local copy and replace the commas with + spaces. Obviously, there is a problem if there is a ',' in the VMS filename + (can only happen on ODS5), the same problem as with spaces in filenames, + which seems to be present in make on all platforms. */ + char *vms_list_iterator = alloca(strlen(list_iterator) + 1); + int i; + for (i = 0; list_iterator[i]; i++) + if (list_iterator[i] == ',') + vms_list_iterator[i] = ' '; + else + vms_list_iterator[i] = list_iterator[i]; + vms_list_iterator[i] = list_iterator[i]; + while ((p2 = find_next_token((const char**) &vms_list_iterator, &len)) != 0) +#else + while ((p2 = find_next_token (&list_iterator, &len)) != 0) +#endif + { + const char *p = p2 + len - 1; + + while (p >= p2 && ! STOP_SET (*p, stop)) + --p; + + if (p >= p2) + { + if (is_notdir) + ++p; + else if (*p != '.') + continue; + o = variable_buffer_output (o, p, len - (p - p2)); + } +#ifdef HAVE_DOS_PATHS + /* Handle the case of "d:foo/bar". */ + else if (is_notdir && p2[0] && p2[1] == ':') + { + p = p2 + 2; + o = variable_buffer_output (o, p, len - (p - p2)); + } +#endif + else if (is_notdir) + o = variable_buffer_output (o, p2, len); + + if (is_notdir || p >= p2) + { +#ifdef VMS + if (vms_comma_separator) + o = variable_buffer_output (o, ",", 1); + else +#endif + o = variable_buffer_output (o, " ", 1); + + doneany = 1; + } + } + + if (doneany) + /* Kill last space. */ + --o; + + return o; +} + + +static char * +func_basename_dir (char *o, char **argv, const char *funcname) +{ + /* Expand the argument. */ + const char *p3 = argv[0]; + const char *p2; + int doneany = 0; + unsigned int len = 0; + + int is_basename = funcname[0] == 'b'; + int is_dir = !is_basename; + int stop = MAP_DIRSEP | (is_basename ? MAP_DOT : 0) | MAP_NUL; +#ifdef VMS + /* As in func_notdir_suffix ... */ + char *vms_p3 = alloca (strlen(p3) + 1); + int i; + for (i = 0; p3[i]; i++) + if (p3[i] == ',') + vms_p3[i] = ' '; + else + vms_p3[i] = p3[i]; + vms_p3[i] = p3[i]; + while ((p2 = find_next_token((const char**) &vms_p3, &len)) != 0) +#else + while ((p2 = find_next_token (&p3, &len)) != 0) +#endif + { + const char *p = p2 + len - 1; + while (p >= p2 && ! STOP_SET (*p, stop)) + --p; + + if (p >= p2 && (is_dir)) + o = variable_buffer_output (o, p2, ++p - p2); + else if (p >= p2 && (*p == '.')) + o = variable_buffer_output (o, p2, p - p2); +#ifdef HAVE_DOS_PATHS + /* Handle the "d:foobar" case */ + else if (p2[0] && p2[1] == ':' && is_dir) + o = variable_buffer_output (o, p2, 2); +#endif + else if (is_dir) +#ifdef VMS + { + extern int vms_report_unix_paths; + if (vms_report_unix_paths) + o = variable_buffer_output (o, "./", 2); + else + o = variable_buffer_output (o, "[]", 2); + } +#else +#ifndef _AMIGA + o = variable_buffer_output (o, "./", 2); +#else + ; /* Just a nop... */ +#endif /* AMIGA */ +#endif /* !VMS */ + else + /* The entire name is the basename. */ + o = variable_buffer_output (o, p2, len); + +#ifdef VMS + if (vms_comma_separator) + o = variable_buffer_output (o, ",", 1); + else +#endif + o = variable_buffer_output (o, " ", 1); + doneany = 1; + } + + if (doneany) + /* Kill last space. */ + --o; + + return o; +} + +#if 1 /* rewrite to new MAP stuff? */ +# ifdef VMS +# define IS_PATHSEP(c) ((c) == ']') +# else +# ifdef HAVE_DOS_PATHS +# define IS_PATHSEP(c) ((c) == '/' || (c) == '\\') +# else +# define IS_PATHSEP(c) ((c) == '/') +# endif +# endif +#endif + +#ifdef CONFIG_WITH_ROOT_FUNC + +/* + $(root path) + + This is mainly for dealing with drive letters and UNC paths on Windows + and OS/2. + */ +static char * +func_root (char *o, char **argv, const char *funcname UNUSED) +{ + const char *paths = argv[0] ? argv[0] : ""; + int doneany = 0; + const char *p; + unsigned int len; + + while ((p = find_next_token (&paths, &len)) != 0) + { + const char *p2 = p; + +# ifdef HAVE_DOS_PATHS + if ( len >= 2 + && p2[1] == ':' + && ( (p2[0] >= 'A' && p2[0] <= 'Z') + || (p2[0] >= 'a' && p2[0] <= 'z'))) + { + p2 += 2; + len -= 2; + } + else if (len >= 4 && IS_PATHSEP(p2[0]) && IS_PATHSEP(p2[1]) + && !IS_PATHSEP(p2[2])) + { + /* Min recognized UNC: "//./" - find the next slash + Typical root: "//srv/shr/" */ + /* XXX: Check if //./ needs special handling. */ + + p2 += 3; + len -= 3; + while (len > 0 && !IS_PATHSEP(*p2)) + p2++, len--; + + if (len && IS_PATHSEP(p2[0]) && (len == 1 || !IS_PATHSEP(p2[1]))) + { + p2++; + len--; + + if (len) /* optional share */ + while (len > 0 && !IS_PATHSEP(*p2)) + p2++, len--; + } + else + p2 = NULL; + } + else if (IS_PATHSEP(*p2)) + { + p2++; + len--; + } + else + p2 = NULL; + +# elif defined (VMS) || defined (AMGIA) + /* XXX: VMS and AMGIA */ + O (fatal, NILF, _("$(root ) is not implemented on this platform")); +# else + if (IS_PATHSEP(*p2)) + { + p2++; + len--; + } + else + p2 = NULL; +# endif + if (p2 != NULL) + { + /* Include all subsequent path separators. */ + + while (len > 0 && IS_PATHSEP(*p2)) + p2++, len--; + o = variable_buffer_output (o, p, p2 - p); + o = variable_buffer_output (o, " ", 1); + doneany = 1; + } + } + + if (doneany) + /* Kill last space. */ + --o; + + return o; +} + +/* + $(notroot path) + + This is mainly for dealing with drive letters and UNC paths on Windows + and OS/2. + */ +static char * +func_notroot (char *o, char **argv, const char *funcname UNUSED) +{ + const char *paths = argv[0] ? argv[0] : ""; + int doneany = 0; + const char *p; + unsigned int len; + + while ((p = find_next_token (&paths, &len)) != 0) + { + const char *p2 = p; + +# ifdef HAVE_DOS_PATHS + if ( len >= 2 + && p2[1] == ':' + && ( (p2[0] >= 'A' && p2[0] <= 'Z') + || (p2[0] >= 'a' && p2[0] <= 'z'))) + { + p2 += 2; + len -= 2; + } + else if (len >= 4 && IS_PATHSEP(p2[0]) && IS_PATHSEP(p2[1]) + && !IS_PATHSEP(p2[2])) + { + /* Min recognized UNC: "//./" - find the next slash + Typical root: "//srv/shr/" */ + /* XXX: Check if //./ needs special handling. */ + unsigned int saved_len = len; + + p2 += 3; + len -= 3; + while (len > 0 && !IS_PATHSEP(*p2)) + p2++, len--; + + if (len && IS_PATHSEP(p2[0]) && (len == 1 || !IS_PATHSEP(p2[1]))) + { + p2++; + len--; + + if (len) /* optional share */ + while (len > 0 && !IS_PATHSEP(*p2)) + p2++, len--; + } + else + { + p2 = p; + len = saved_len; + } + } + +# elif defined (VMS) || defined (AMGIA) + /* XXX: VMS and AMGIA */ + O (fatal, NILF, _("$(root ) is not implemented on this platform")); +# endif + + /* Exclude all subsequent / leading path separators. */ + + while (len > 0 && IS_PATHSEP(*p2)) + p2++, len--; + if (len > 0) + o = variable_buffer_output (o, p2, len); + else + o = variable_buffer_output (o, ".", 1); + o = variable_buffer_output (o, " ", 1); + doneany = 1; + } + + if (doneany) + /* Kill last space. */ + --o; + + return o; +} + +#endif /* CONFIG_WITH_ROOT_FUNC */ + +static char * +func_addsuffix_addprefix (char *o, char **argv, const char *funcname) +{ + int fixlen = strlen (argv[0]); + const char *list_iterator = argv[1]; + int is_addprefix = funcname[3] == 'p'; + int is_addsuffix = !is_addprefix; + + int doneany = 0; + const char *p; + unsigned int len; + + while ((p = find_next_token (&list_iterator, &len)) != 0) + { + if (is_addprefix) + o = variable_buffer_output (o, argv[0], fixlen); + o = variable_buffer_output (o, p, len); + if (is_addsuffix) + o = variable_buffer_output (o, argv[0], fixlen); + o = variable_buffer_output (o, " ", 1); + doneany = 1; + } + + if (doneany) + /* Kill last space. */ + --o; + + return o; +} + +static char * +func_subst (char *o, char **argv, const char *funcname UNUSED) +{ + o = subst_expand (o, argv[2], argv[0], argv[1], strlen (argv[0]), + strlen (argv[1]), 0); + + return o; +} + +#ifdef CONFIG_WITH_DEFINED_FUNCTIONS + +/* Used by func_firstdefined and func_lastdefined to parse the optional last + argument. Returns 0 if the variable name is to be returned and 1 if it's + the variable value value. */ +static int +parse_value_name_argument (const char *arg1, const char *funcname) +{ + const char *end; + int rc; + + if (arg1 == NULL) + return 0; + + end = strchr (arg1, '\0'); + strip_whitespace (&arg1, &end); + + if (!strncmp (arg1, "name", end - arg1)) + rc = 0; + else if (!strncmp (arg1, "value", end - arg1)) + rc = 1; + else +# if 1 /* FIXME: later */ + OSS (fatal, *expanding_var, + _("second argument to `%s' function must be `name' or `value', not `%s'"), + funcname, arg1); +# else + { + /* check the expanded form */ + char *exp = expand_argument (arg1, strchr (arg1, '\0')); + arg1 = exp; + end = strchr (arg1, '\0'); + strip_whitespace (&arg1, &end); + + if (!strncmp (arg1, "name", end - arg1)) + rc = 0; + else if (!strncmp (arg1, "value", end - arg1)) + rc = 1; + else + OSS (fatal, *expanding_var, + _("second argument to `%s' function must be `name' or `value', not `%s'"), + funcname, exp); + free (exp); + } +# endif + + return rc; +} + +/* Given a list of variable names (ARGV[0]), returned the first variable which + is defined (i.e. value is not empty). ARGV[1] indicates whether to return + the variable name or its value. */ +static char * +func_firstdefined (char *o, char **argv, const char *funcname) +{ + unsigned int i; + const char *words = argv[0]; /* Use a temp variable for find_next_token */ + const char *p; + int ret_value = parse_value_name_argument (argv[1], funcname); + + /* FIXME: Optimize by not expanding the arguments, but instead expand them + one by one here. This will require a find_next_token variant which + takes `$(' and `)' into account. */ + while ((p = find_next_token (&words, &i)) != NULL) + { + struct variable *v = lookup_variable (p, i); + if (v && v->value_length) + { + if (ret_value) + variable_expand_string_2 (o, v->value, v->value_length, &o); + else + o = variable_buffer_output (o, p, i); + break; + } + } + + return o; +} + +/* Given a list of variable names (ARGV[0]), returned the last variable which + is defined (i.e. value is not empty). ARGV[1] indicates whether to return + the variable name or its value. */ +static char * +func_lastdefined (char *o, char **argv, const char *funcname) +{ + struct variable *last_v = NULL; + unsigned int i; + const char *words = argv[0]; /* Use a temp variable for find_next_token */ + const char *p; + int ret_value = parse_value_name_argument (argv[1], funcname); + + /* FIXME: Optimize this. Walk from the end on unexpanded arguments. */ + while ((p = find_next_token (&words, &i)) != NULL) + { + struct variable *v = lookup_variable (p, i); + if (v && v->value_length) + { + last_v = v; + break; + } + } + + if (last_v != NULL) + { + if (ret_value) + variable_expand_string_2 (o, last_v->value, last_v->value_length, &o); + else + o = variable_buffer_output (o, last_v->name, last_v->length); + } + return o; +} + +#endif /* CONFIG_WITH_DEFINED_FUNCTIONS */ + +static char * +func_firstword (char *o, char **argv, const char *funcname UNUSED) +{ + unsigned int i; + const char *words = argv[0]; /* Use a temp variable for find_next_token */ + const char *p = find_next_token (&words, &i); + + if (p != 0) + o = variable_buffer_output (o, p, i); + + return o; +} + +static char * +func_lastword (char *o, char **argv, const char *funcname UNUSED) +{ + unsigned int i; + const char *words = argv[0]; /* Use a temp variable for find_next_token */ + const char *p = NULL; + const char *t; + + while ((t = find_next_token (&words, &i))) + p = t; + + if (p != 0) + o = variable_buffer_output (o, p, i); + + return o; +} + +static char * +func_words (char *o, char **argv, const char *funcname UNUSED) +{ + int i = 0; + const char *word_iterator = argv[0]; + char buf[20]; + + while (find_next_token (&word_iterator, NULL) != 0) + ++i; + + sprintf (buf, "%d", i); + o = variable_buffer_output (o, buf, strlen (buf)); + + return o; +} + +/* Set begpp to point to the first non-whitespace character of the string, + * and endpp to point to the last non-whitespace character of the string. + * If the string is empty or contains nothing but whitespace, endpp will be + * begpp-1. + */ +char * +strip_whitespace (const char **begpp, const char **endpp) +{ + while (*begpp <= *endpp && ISSPACE (**begpp)) + (*begpp) ++; + while (*endpp >= *begpp && ISSPACE (**endpp)) + (*endpp) --; + return (char *)*begpp; +} + +static void +check_numeric (const char *s, const char *msg) +{ + const char *end = s + strlen (s) - 1; + const char *beg = s; + strip_whitespace (&s, &end); + + for (; s <= end; ++s) + if (!ISDIGIT (*s)) /* ISDIGIT only evals its arg once: see makeint.h. */ + break; + + if (s <= end || end - beg < 0) + OSS (fatal, *expanding_var, "%s: '%s'", msg, beg); +} + + + +static char * +func_word (char *o, char **argv, const char *funcname UNUSED) +{ + const char *end_p; + const char *p; + int i; + + /* Check the first argument. */ + check_numeric (argv[0], _("non-numeric first argument to 'word' function")); + i = atoi (argv[0]); + + if (i == 0) + O (fatal, *expanding_var, + _("first argument to 'word' function must be greater than 0")); + + end_p = argv[1]; + while ((p = find_next_token (&end_p, 0)) != 0) + if (--i == 0) + break; + + if (i == 0) + o = variable_buffer_output (o, p, end_p - p); + + return o; +} + +static char * +func_wordlist (char *o, char **argv, const char *funcname UNUSED) +{ + int start, count; + + /* Check the arguments. */ + check_numeric (argv[0], + _("non-numeric first argument to 'wordlist' function")); + check_numeric (argv[1], + _("non-numeric second argument to 'wordlist' function")); + + start = atoi (argv[0]); + if (start < 1) + ON (fatal, *expanding_var, + "invalid first argument to 'wordlist' function: '%d'", start); + + count = atoi (argv[1]) - start + 1; + + if (count > 0) + { + const char *p; + const char *end_p = argv[2]; + + /* Find the beginning of the "start"th word. */ + while (((p = find_next_token (&end_p, 0)) != 0) && --start) + ; + + if (p) + { + /* Find the end of the "count"th word from start. */ + while (--count && (find_next_token (&end_p, 0) != 0)) + ; + + /* Return the stuff in the middle. */ + o = variable_buffer_output (o, p, end_p - p); + } + } + + return o; +} + +static char * +func_findstring (char *o, char **argv, const char *funcname UNUSED) +{ + /* Find the first occurrence of the first string in the second. */ + if (strstr (argv[1], argv[0]) != 0) + o = variable_buffer_output (o, argv[0], strlen (argv[0])); + + return o; +} + +static char * +func_foreach (char *o, char **argv, const char *funcname UNUSED) +{ + /* expand only the first two. */ + char *varname = expand_argument (argv[0], NULL); + char *list = expand_argument (argv[1], NULL); + const char *body = argv[2]; +#ifdef CONFIG_WITH_VALUE_LENGTH + long body_len = strlen (body); +#endif + + int doneany = 0; + const char *list_iterator = list; + const char *p; + unsigned int len; + struct variable *var; + + /* Clean up the variable name by removing whitespace. */ + char *vp = next_token (varname); + end_of_token (vp)[0] = '\0'; + + push_new_variable_scope (); + var = define_variable (vp, strlen (vp), "", o_automatic, 0); + + /* loop through LIST, put the value in VAR and expand BODY */ + while ((p = find_next_token (&list_iterator, &len)) != 0) + { +#ifndef CONFIG_WITH_VALUE_LENGTH + char *result = 0; + + free (var->value); + var->value = xstrndup (p, len); + + result = allocated_variable_expand (body); + + o = variable_buffer_output (o, result, strlen (result)); + o = variable_buffer_output (o, " ", 1); + doneany = 1; + free (result); +#else /* CONFIG_WITH_VALUE_LENGTH */ + if (len >= var->value_alloc_len) + { +# ifdef CONFIG_WITH_RDONLY_VARIABLE_VALUE + if (var->rdonly_val) + var->rdonly_val = 0; + else +# endif + free (var->value); + var->value_alloc_len = VAR_ALIGN_VALUE_ALLOC (len + 1); + var->value = xmalloc (var->value_alloc_len); + } + memcpy (var->value, p, len); + var->value[len] = '\0'; + var->value_length = len; + VARIABLE_CHANGED (var); + + variable_expand_string_2 (o, body, body_len, &o); + o = variable_buffer_output (o, " ", 1); + doneany = 1; +#endif /* CONFIG_WITH_VALUE_LENGTH */ + } + + if (doneany) + /* Kill the last space. */ + --o; + + pop_variable_scope (); + free (varname); + free (list); + + return o; +} + +#ifdef CONFIG_WITH_LOOP_FUNCTIONS + +/* Helper for func_for that evaluates the INIT and NEXT parts. */ +static void +helper_eval (char *text, size_t text_len) +{ + unsigned int buf_len; + char *buf; + + install_variable_buffer (&buf, &buf_len); + eval_buffer (text, NULL, text + text_len); + restore_variable_buffer (buf, buf_len); +} + +/* + $(for init,condition,next,body) + */ +static char * +func_for (char *o, char **argv, const char *funcname UNUSED) +{ + char *init = argv[0]; + const char *cond = argv[1]; + const char *next = argv[2]; + size_t next_len = strlen (next); + char *next_buf = xmalloc (next_len + 1); + const char *body = argv[3]; + size_t body_len = strlen (body); + unsigned int doneany = 0; + + push_new_variable_scope (); + + /* Evaluate INIT. */ + + helper_eval (init, strlen (init)); + + /* Loop till COND is false. */ + + while (expr_eval_if_conditionals (cond, NULL) == 0 /* true */) + { + /* Expand BODY. */ + + if (!doneany) + doneany = 1; + else + o = variable_buffer_output (o, " ", 1); + variable_expand_string_2 (o, body, body_len, &o); + + /* Evaluate NEXT. */ + + memcpy (next_buf, next, next_len + 1); + helper_eval (next_buf, next_len); + } + + pop_variable_scope (); + free (next_buf); + + return o; +} + +/* + $(while condition,body) + */ +static char * +func_while (char *o, char **argv, const char *funcname UNUSED) +{ + const char *cond = argv[0]; + const char *body = argv[1]; + size_t body_len = strlen (body); + unsigned int doneany = 0; + + push_new_variable_scope (); + + while (expr_eval_if_conditionals (cond, NULL) == 0 /* true */) + { + if (!doneany) + doneany = 1; + else + o = variable_buffer_output (o, " ", 1); + variable_expand_string_2 (o, body, body_len, &o); + } + + pop_variable_scope (); + + return o; +} + +#endif /* CONFIG_WITH_LOOP_FUNCTIONS */ + +struct a_word +{ + struct a_word *next; + struct a_word *chain; + char *str; + int length; + int matched; +}; + +static unsigned long +a_word_hash_1 (const void *key) +{ + return_STRING_HASH_1 (((struct a_word const *) key)->str); +} + +static unsigned long +a_word_hash_2 (const void *key) +{ + return_STRING_HASH_2 (((struct a_word const *) key)->str); +} + +static int +a_word_hash_cmp (const void *x, const void *y) +{ + int result = ((struct a_word const *) x)->length - ((struct a_word const *) y)->length; + if (result) + return result; + return_STRING_COMPARE (((struct a_word const *) x)->str, + ((struct a_word const *) y)->str); +} + +struct a_pattern +{ + struct a_pattern *next; + char *str; + char *percent; + int length; +#ifdef KMK + unsigned int sfxlen; +#endif +}; + +static char * +func_filter_filterout (char *o, char **argv, const char *funcname) +{ + struct a_word *wordhead; + struct a_word **wordtail; + struct a_word *wp; + struct a_pattern *pathead; + struct a_pattern **pattail; + struct a_pattern *pp; + + struct hash_table a_word_table; + int is_filter = funcname[CSTRLEN ("filter")] == '\0'; + const char *pat_iterator = argv[0]; + const char *word_iterator = argv[1]; + int literals = 0; + int words = 0; + int hashing = 0; + char *p; + unsigned int len; + + /* Chop ARGV[0] up into patterns to match against the words. + We don't need to preserve it because our caller frees all the + argument memory anyway. */ + + pattail = &pathead; + while ((p = find_next_token (&pat_iterator, &len)) != 0) + { + struct a_pattern *pat = alloca (sizeof (struct a_pattern)); + + *pattail = pat; + pattail = &pat->next; + + if (*pat_iterator != '\0') + ++pat_iterator; + + pat->str = p; + p[len] = '\0'; + pat->percent = find_percent (p); + if (pat->percent == 0) + literals++; +#ifdef KMK + pat->sfxlen = pat->percent ? strlen(pat->percent + 1) : 0; +#endif + + /* find_percent() might shorten the string so LEN is wrong. */ + pat->length = strlen (pat->str); + } + *pattail = 0; + + /* Chop ARGV[1] up into words to match against the patterns. */ + + wordtail = &wordhead; + while ((p = find_next_token (&word_iterator, &len)) != 0) + { + struct a_word *word = alloca (sizeof (struct a_word)); + + *wordtail = word; + wordtail = &word->next; + + if (*word_iterator != '\0') + ++word_iterator; + + p[len] = '\0'; + word->str = p; + word->length = len; + word->matched = 0; + word->chain = 0; + words++; + } + *wordtail = 0; + + /* Only use a hash table if arg list lengths justifies the cost. */ + hashing = (literals >= 2 && (literals * words) >= 10); + if (hashing) + { + hash_init (&a_word_table, words, a_word_hash_1, a_word_hash_2, + a_word_hash_cmp); + for (wp = wordhead; wp != 0; wp = wp->next) + { + struct a_word *owp = hash_insert (&a_word_table, wp); + if (owp) + wp->chain = owp; + } + } + + if (words) + { + int doneany = 0; + + /* Run each pattern through the words, killing words. */ + for (pp = pathead; pp != 0; pp = pp->next) + { + if (pp->percent) + for (wp = wordhead; wp != 0; wp = wp->next) +#ifdef KMK + wp->matched |= pattern_matches_ex (pp->str, pp->percent, pp->sfxlen, + wp->str, wp->length); +#else + wp->matched |= pattern_matches (pp->str, pp->percent, wp->str); +#endif + else if (hashing) + { + struct a_word a_word_key; + a_word_key.str = pp->str; + a_word_key.length = pp->length; + wp = hash_find_item (&a_word_table, &a_word_key); + while (wp) + { + wp->matched |= 1; + wp = wp->chain; + } + } + else + for (wp = wordhead; wp != 0; wp = wp->next) + wp->matched |= (wp->length == pp->length + && strneq (pp->str, wp->str, wp->length)); + } + + /* Output the words that matched (or didn't, for filter-out). */ + for (wp = wordhead; wp != 0; wp = wp->next) + if (is_filter ? wp->matched : !wp->matched) + { + o = variable_buffer_output (o, wp->str, strlen (wp->str)); + o = variable_buffer_output (o, " ", 1); + doneany = 1; + } + + if (doneany) + /* Kill the last space. */ + --o; + } + + if (hashing) + hash_free (&a_word_table, 0); + + return o; +} + + +static char * +func_strip (char *o, char **argv, const char *funcname UNUSED) +{ + const char *p = argv[0]; + int doneany = 0; + + while (*p != '\0') + { + int i=0; + const char *word_start; + + NEXT_TOKEN (p); + word_start = p; + for (i=0; *p != '\0' && !ISSPACE (*p); ++p, ++i) + {} + if (!i) + break; + o = variable_buffer_output (o, word_start, i); + o = variable_buffer_output (o, " ", 1); + doneany = 1; + } + + if (doneany) + /* Kill the last space. */ + --o; + + return o; +} + +/* + Print a warning or fatal message. +*/ +static char * +func_error (char *o, char **argv, const char *funcname) +{ + char **argvp; + char *msg, *p; + int len; + + /* The arguments will be broken on commas. Rather than create yet + another special case where function arguments aren't broken up, + just create a format string that puts them back together. */ + for (len=0, argvp=argv; *argvp != 0; ++argvp) + len += strlen (*argvp) + 2; + + p = msg = alloca (len + 1); + + for (argvp=argv; argvp[1] != 0; ++argvp) + { + strcpy (p, *argvp); + p += strlen (*argvp); + *(p++) = ','; + *(p++) = ' '; + } + strcpy (p, *argvp); + + switch (*funcname) + { + case 'e': + OS (fatal, reading_file, "%s", msg); + + case 'w': + OS (error, reading_file, "%s", msg); + break; + + case 'i': + outputs (0, msg); + outputs (0, "\n"); + break; + + default: + OS (fatal, *expanding_var, "Internal error: func_error: '%s'", funcname); + } + + /* The warning function expands to the empty string. */ + return o; +} + +#ifdef KMK +/* Compare strings *S1 and *S2. + Return negative if the first is less, positive if it is greater, + zero if they are equal. */ + +static int +version_compare_wrapper (const void *v1, const void *v2) +{ + const char *s1 = *((char **)v1); + const char *s2 = *((char **)v2); + return version_compare (s1, s2); +} +#endif /* KMK */ + +/* + chop argv[0] into words, and sort them. + */ +static char * +func_sort (char *o, char **argv, const char *funcname UNUSED) +{ + const char *t; + char **words; + int wordi; + char *p; + unsigned int len; + + /* Find the maximum number of words we'll have. */ + t = argv[0]; + wordi = 0; + while ((p = find_next_token (&t, NULL)) != 0) + { + ++t; + ++wordi; + } + + words = xmalloc ((wordi == 0 ? 1 : wordi) * sizeof (char *)); + + /* Now assign pointers to each string in the array. */ + t = argv[0]; + wordi = 0; + while ((p = find_next_token (&t, &len)) != 0) + { + if (*t != '\0') /* bird: Fixes access beyond end of string and overflowing words array. */ + ++t; + p[len] = '\0'; + words[wordi++] = p; + } + + if (wordi) + { + int i; + + /* Now sort the list of words. */ +#ifdef KMK + if (funcname[0] == 'v' || funcname[1] == 'v') + qsort (words, wordi, sizeof (char *), version_compare_wrapper); + else + qsort (words, wordi, sizeof (char *), alpha_compare); +#else + qsort (words, wordi, sizeof (char *), alpha_compare); +#endif + + /* Now write the sorted list, uniquified. */ +#ifdef CONFIG_WITH_RSORT + if (*funcname != 'r') + { + /* sort */ +#endif + for (i = 0; i < wordi; ++i) + { + len = strlen (words[i]); + if (i == wordi - 1 || strlen (words[i + 1]) != len + || strcmp (words[i], words[i + 1])) + { + o = variable_buffer_output (o, words[i], len); + o = variable_buffer_output (o, " ", 1); + } + } +#ifdef CONFIG_WITH_RSORT + } + else + { + /* rsort - reverse the result */ + i = wordi; + while (i-- > 0) + { + len = strlen (words[i]); + if (i == 0 || strlen (words[i - 1]) != len + || strcmp (words[i], words[i - 1])) + { + o = variable_buffer_output (o, words[i], len); + o = variable_buffer_output (o, " ", 1); + } + } + } +#endif + + /* Kill the last space. */ + --o; + } + + free (words); + + return o; +} + +/* + $(if condition,true-part[,false-part]) + + CONDITION is false iff it evaluates to an empty string. White + space before and after condition are stripped before evaluation. + + If CONDITION is true, then TRUE-PART is evaluated, otherwise FALSE-PART is + evaluated (if it exists). Because only one of the two PARTs is evaluated, + you can use $(if ...) to create side-effects (with $(shell ...), for + example). +*/ + +static char * +func_if (char *o, char **argv, const char *funcname UNUSED) +{ + const char *begp = argv[0]; + const char *endp = begp + strlen (argv[0]) - 1; + int result = 0; + + /* Find the result of the condition: if we have a value, and it's not + empty, the condition is true. If we don't have a value, or it's the + empty string, then it's false. */ + + strip_whitespace (&begp, &endp); + + if (begp <= endp) + { + char *expansion = expand_argument (begp, endp+1); + + result = strlen (expansion); + free (expansion); + } + + /* If the result is true (1) we want to eval the first argument, and if + it's false (0) we want to eval the second. If the argument doesn't + exist we do nothing, otherwise expand it and add to the buffer. */ + + argv += 1 + !result; + + if (*argv) + { + char *expansion = expand_argument (*argv, NULL); + + o = variable_buffer_output (o, expansion, strlen (expansion)); + + free (expansion); + } + + return o; +} + +/* + $(or condition1[,condition2[,condition3[...]]]) + + A CONDITION is false iff it evaluates to an empty string. White + space before and after CONDITION are stripped before evaluation. + + CONDITION1 is evaluated. If it's true, then this is the result of + expansion. If it's false, CONDITION2 is evaluated, and so on. If none of + the conditions are true, the expansion is the empty string. + + Once a CONDITION is true no further conditions are evaluated + (short-circuiting). +*/ + +static char * +func_or (char *o, char **argv, const char *funcname UNUSED) +{ + for ( ; *argv ; ++argv) + { + const char *begp = *argv; + const char *endp = begp + strlen (*argv) - 1; + char *expansion; + int result = 0; + + /* Find the result of the condition: if it's false keep going. */ + + strip_whitespace (&begp, &endp); + + if (begp > endp) + continue; + + expansion = expand_argument (begp, endp+1); + result = strlen (expansion); + + /* If the result is false keep going. */ + if (!result) + { + free (expansion); + continue; + } + + /* It's true! Keep this result and return. */ + o = variable_buffer_output (o, expansion, result); + free (expansion); + break; + } + + return o; +} + +/* + $(and condition1[,condition2[,condition3[...]]]) + + A CONDITION is false iff it evaluates to an empty string. White + space before and after CONDITION are stripped before evaluation. + + CONDITION1 is evaluated. If it's false, then this is the result of + expansion. If it's true, CONDITION2 is evaluated, and so on. If all of + the conditions are true, the expansion is the result of the last condition. + + Once a CONDITION is false no further conditions are evaluated + (short-circuiting). +*/ + +static char * +func_and (char *o, char **argv, const char *funcname UNUSED) +{ + char *expansion; + + while (1) + { + const char *begp = *argv; + const char *endp = begp + strlen (*argv) - 1; + int result; + + /* An empty condition is always false. */ + strip_whitespace (&begp, &endp); + if (begp > endp) + return o; + + expansion = expand_argument (begp, endp+1); + result = strlen (expansion); + + /* If the result is false, stop here: we're done. */ + if (!result) + break; + + /* Otherwise the result is true. If this is the last one, keep this + result and quit. Otherwise go on to the next one! */ + + if (*(++argv)) + free (expansion); + else + { + o = variable_buffer_output (o, expansion, result); + break; + } + } + + free (expansion); + + return o; +} + +static char * +func_wildcard (char *o, char **argv, const char *funcname UNUSED) +{ +#ifdef _AMIGA + o = wildcard_expansion (argv[0], o); +#else + char *p = string_glob (argv[0]); + o = variable_buffer_output (o, p, strlen (p)); +#endif + return o; +} + +/* + $(eval <makefile string>) + + Always resolves to the empty string. + + Treat the arguments as a segment of makefile, and parse them. +*/ + +static char * +func_eval (char *o, char **argv, const char *funcname UNUSED) +{ + char *buf; + unsigned int len; + + /* Eval the buffer. Pop the current variable buffer setting so that the + eval'd code can use its own without conflicting. */ + + install_variable_buffer (&buf, &len); + +#ifndef CONFIG_WITH_VALUE_LENGTH + eval_buffer (argv[0], NULL); +#else + eval_buffer (argv[0], NULL, strchr (argv[0], '\0')); +#endif + + restore_variable_buffer (buf, len); + + return o; +} + + +#ifdef CONFIG_WITH_EVALPLUS +/* Same as func_eval except that we push and pop the local variable + context before evaluating the buffer. */ +static char * +func_evalctx (char *o, char **argv, const char *funcname UNUSED) +{ + char *buf; + unsigned int len; + + /* Eval the buffer. Pop the current variable buffer setting so that the + eval'd code can use its own without conflicting. */ + + install_variable_buffer (&buf, &len); + + push_new_variable_scope (); + + eval_buffer (argv[0], NULL, strchr (argv[0], '\0')); + + pop_variable_scope (); + + restore_variable_buffer (buf, len); + + return o; +} + +/* A mix of func_eval and func_value, saves memory for the expansion. + This implements both evalval and evalvalctx, the latter has its own + variable context just like evalctx. */ +static char * +func_evalval (char *o, char **argv, const char *funcname) +{ + /* Look up the variable. */ + struct variable *v = lookup_variable (argv[0], strlen (argv[0])); + if (v) + { + char *buf; + unsigned int len; + int var_ctx; + size_t off; + const floc *reading_file_saved = reading_file; +# ifdef CONFIG_WITH_MAKE_STATS + unsigned long long uStartTick = CURRENT_CLOCK_TICK(); +# ifndef CONFIG_WITH_COMPILER + MAKE_STATS_2(v->evalval_count++); +# endif +# endif + + var_ctx = !strcmp (funcname, "evalvalctx"); + if (var_ctx) + push_new_variable_scope (); + if (v->fileinfo.filenm) + reading_file = &v->fileinfo; + +# ifdef CONFIG_WITH_COMPILER + /* If this variable has been evaluated more than a few times, it make + sense to compile it to speed up the processing. */ + + v->evalval_count++; + if ( v->evalprog + || (v->evalval_count == 3 && kmk_cc_compile_variable_for_eval (v))) + { + install_variable_buffer (&buf, &len); /* Really necessary? */ + kmk_exec_eval_variable (v); + restore_variable_buffer (buf, len); + } + else +# endif + { + /* Make a copy of the value to the variable buffer first since + eval_buffer will make changes to its input. */ + + off = o - variable_buffer; + variable_buffer_output (o, v->value, v->value_length + 1); + o = variable_buffer + off; + assert (!o[v->value_length]); + + install_variable_buffer (&buf, &len); /* Really necessary? */ + eval_buffer (o, NULL, o + v->value_length); + restore_variable_buffer (buf, len); + } + + reading_file = reading_file_saved; + if (var_ctx) + pop_variable_scope (); + + MAKE_STATS_2(v->cTicksEvalVal += CURRENT_CLOCK_TICK() - uStartTick); + } + + return o; +} + +/* Optimizes the content of one or more variables to save time in + the eval functions. This function will collapse line continuations + and remove comments. */ +static char * +func_eval_optimize_variable (char *o, char **argv, const char *funcname) +{ + unsigned int i; + + for (i = 0; argv[i]; i++) + { + struct variable *v = lookup_variable (argv[i], strlen (argv[i])); +# ifdef CONFIG_WITH_RDONLY_VARIABLE_VALUE + if (v && v->origin != o_automatic && !v->rdonly_val) +# else + if (v && v->origin != o_automatic) +# endif + { + char *eos, *src; + + eos = collapse_continuations (v->value, v->value_length); + v->value_length = eos - v->value; + + /* remove comments */ + + src = memchr (v->value, '#', v->value_length); + if (src) + { + unsigned char ch = '\0'; + char *dst = src; + do + { + /* drop blanks preceeding the comment */ + while (dst > v->value) + { + ch = (unsigned char)dst[-1]; + if (!ISBLANK (ch)) + break; + dst--; + } + + /* advance SRC to eol / eos. */ + src = memchr (src, '\n', eos - src); + if (!src) + break; + + /* drop a preceeding newline if possible (full line comment) */ + if (dst > v->value && dst[-1] == '\n') + dst--; + + /* copy till next comment or eol. */ + while (src < eos) + { + ch = *src++; + if (ch == '#') + break; + *dst++ = ch; + } + } + while (ch == '#' && src < eos); + + *dst = '\0'; + v->value_length = dst - v->value; + } + + VARIABLE_CHANGED (v); + +# ifdef CONFIG_WITH_COMPILER + /* Compile the variable for evalval, evalctx and expansion. */ + + if ( v->recursive + && !IS_VARIABLE_RECURSIVE_WITHOUT_DOLLAR (v)) + kmk_cc_compile_variable_for_expand (v); + kmk_cc_compile_variable_for_eval (v); +# endif + } + else if (v) + OSS (error, NILF, _("$(%s ): variable `%s' is of the wrong type\n"), funcname, v->name); + } + + return o; +} + +#endif /* CONFIG_WITH_EVALPLUS */ + +static char * +func_value (char *o, char **argv, const char *funcname UNUSED) +{ + /* Look up the variable. */ + struct variable *v = lookup_variable (argv[0], strlen (argv[0])); + + /* Copy its value into the output buffer without expanding it. */ + if (v) +#ifdef CONFIG_WITH_VALUE_LENGTH + { + assert (v->value_length == strlen (v->value)); + o = variable_buffer_output (o, v->value, v->value_length); + } +#else + o = variable_buffer_output (o, v->value, strlen (v->value)); +#endif + + return o; +} + +/* + \r is replaced on UNIX as well. Is this desirable? + */ +static void +fold_newlines (char *buffer, unsigned int *length, int trim_newlines) +{ + char *dst = buffer; + char *src = buffer; + char *last_nonnl = buffer - 1; + src[*length] = 0; + for (; *src != '\0'; ++src) + { + if (src[0] == '\r' && src[1] == '\n') + continue; + if (*src == '\n') + { + *dst++ = ' '; + } + else + { + last_nonnl = dst; + *dst++ = *src; + } + } + + if (!trim_newlines && (last_nonnl < (dst - 2))) + last_nonnl = dst - 2; + + *(++last_nonnl) = '\0'; + *length = last_nonnl - buffer; +} + +pid_t shell_function_pid = 0; +static int shell_function_completed; + +void +shell_completed (int exit_code, int exit_sig) +{ + char buf[256]; + + shell_function_pid = 0; + if (exit_sig == 0 && exit_code == 127) + shell_function_completed = -1; + else + shell_function_completed = 1; + + sprintf (buf, "%d", exit_code); + define_variable_cname (".SHELLSTATUS", buf, o_override, 0); +} + +#ifdef WINDOWS32 +/*untested*/ + +# ifndef CONFIG_NEW_WIN_CHILDREN +#include <windows.h> +#include <io.h> +#include "sub_proc.h" + +int +windows32_openpipe (int *pipedes, int errfd, pid_t *pid_p, char **command_argv, char **envp) +{ + SECURITY_ATTRIBUTES saAttr; + HANDLE hIn = INVALID_HANDLE_VALUE; + HANDLE hErr = INVALID_HANDLE_VALUE; + HANDLE hChildOutRd; + HANDLE hChildOutWr; + HANDLE hProcess, tmpIn, tmpErr; + DWORD e; + + /* Set status for return. */ + pipedes[0] = pipedes[1] = -1; + *pid_p = (pid_t)-1; + + saAttr.nLength = sizeof (SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + /* Standard handles returned by GetStdHandle can be NULL or + INVALID_HANDLE_VALUE if the parent process closed them. If that + happens, we open the null device and pass its handle to + process_begin below as the corresponding handle to inherit. */ + tmpIn = GetStdHandle (STD_INPUT_HANDLE); + if (DuplicateHandle (GetCurrentProcess (), tmpIn, + GetCurrentProcess (), &hIn, + 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE) + { + e = GetLastError (); + if (e == ERROR_INVALID_HANDLE) + { + tmpIn = CreateFile ("NUL", GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (tmpIn != INVALID_HANDLE_VALUE + && DuplicateHandle (GetCurrentProcess (), tmpIn, + GetCurrentProcess (), &hIn, + 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE) + CloseHandle (tmpIn); + } + if (hIn == INVALID_HANDLE_VALUE) + { + ON (error, NILF, + _("windows32_openpipe: DuplicateHandle(In) failed (e=%ld)\n"), e); + return -1; + } + } + tmpErr = (HANDLE)_get_osfhandle (errfd); + if (DuplicateHandle (GetCurrentProcess (), tmpErr, + GetCurrentProcess (), &hErr, + 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE) + { + e = GetLastError (); + if (e == ERROR_INVALID_HANDLE) + { + tmpErr = CreateFile ("NUL", GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (tmpErr != INVALID_HANDLE_VALUE + && DuplicateHandle (GetCurrentProcess (), tmpErr, + GetCurrentProcess (), &hErr, + 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE) + CloseHandle (tmpErr); + } + if (hErr == INVALID_HANDLE_VALUE) + { + ON (error, NILF, + _("windows32_openpipe: DuplicateHandle(Err) failed (e=%ld)\n"), e); + return -1; + } + } + + if (! CreatePipe (&hChildOutRd, &hChildOutWr, &saAttr, 0)) + { + ON (error, NILF, _("CreatePipe() failed (e=%ld)\n"), GetLastError()); + return -1; + } + + hProcess = process_init_fd (hIn, hChildOutWr, hErr); + + if (!hProcess) + { + O (error, NILF, _("windows32_openpipe(): process_init_fd() failed\n")); + return -1; + } + + /* make sure that CreateProcess() has Path it needs */ + sync_Path_environment (); + /* 'sync_Path_environment' may realloc 'environ', so take note of + the new value. */ + envp = environ; + + if (! process_begin (hProcess, command_argv, envp, command_argv[0], NULL)) + { + /* register process for wait */ + process_register (hProcess); + + /* set the pid for returning to caller */ + *pid_p = (pid_t) hProcess; + + /* set up to read data from child */ + pipedes[0] = _open_osfhandle ((intptr_t) hChildOutRd, O_RDONLY); + + /* this will be closed almost right away */ + pipedes[1] = _open_osfhandle ((intptr_t) hChildOutWr, O_APPEND); + return 0; + } + else + { + /* reap/cleanup the failed process */ + process_cleanup (hProcess); + + /* close handles which were duplicated, they weren't used */ + if (hIn != INVALID_HANDLE_VALUE) + CloseHandle (hIn); + if (hErr != INVALID_HANDLE_VALUE) + CloseHandle (hErr); + + /* close pipe handles, they won't be used */ + CloseHandle (hChildOutRd); + CloseHandle (hChildOutWr); + + return -1; + } +} +# endif /* !CONFIG_NEW_WIN_CHILDREN */ +#endif + + +#ifdef __MSDOS__ +FILE * +msdos_openpipe (int* pipedes, int *pidp, char *text) +{ + FILE *fpipe=0; + /* MSDOS can't fork, but it has 'popen'. */ + struct variable *sh = lookup_variable ("SHELL", 5); + int e; + extern int dos_command_running, dos_status; + + /* Make sure not to bother processing an empty line. */ + NEXT_TOKEN (text); + if (*text == '\0') + return 0; + + if (sh) + { + char buf[PATH_MAX + 7]; + /* This makes sure $SHELL value is used by $(shell), even + though the target environment is not passed to it. */ + sprintf (buf, "SHELL=%s", sh->value); + putenv (buf); + } + + e = errno; + errno = 0; + dos_command_running = 1; + dos_status = 0; + /* If dos_status becomes non-zero, it means the child process + was interrupted by a signal, like SIGINT or SIGQUIT. See + fatal_error_signal in commands.c. */ + fpipe = popen (text, "rt"); + dos_command_running = 0; + if (!fpipe || dos_status) + { + pipedes[0] = -1; + *pidp = -1; + if (dos_status) + errno = EINTR; + else if (errno == 0) + errno = ENOMEM; + if (fpipe) + pclose (fpipe); + shell_completed (127, 0); + } + else + { + pipedes[0] = fileno (fpipe); + *pidp = 42; /* Yes, the Meaning of Life, the Universe, and Everything! */ + errno = e; + } + return fpipe; +} +#endif + +/* + Do shell spawning, with the naughty bits for different OSes. + */ + +#ifdef VMS + +/* VMS can't do $(shell ...) */ + +char * +func_shell_base (char *o, char **argv, int trim_newlines) +{ + fprintf (stderr, "This platform does not support shell\n"); + die (MAKE_TROUBLE); + return NULL; +} + +#define func_shell 0 + +#else +#ifndef _AMIGA +char * +func_shell_base (char *o, char **argv, int trim_newlines) +{ + char *batch_filename = NULL; + int errfd; +#ifdef __MSDOS__ + FILE *fpipe; +#endif + char **command_argv; + const char * volatile error_prefix; /* bird: this volatile ~~and the 'o' one~~, is for shutting up gcc warnings */ + char **envp; + int pipedes[2]; + pid_t pid; + +#ifndef __MSDOS__ +#ifdef WINDOWS32 + /* Reset just_print_flag. This is needed on Windows when batch files + are used to run the commands, because we normally refrain from + creating batch files under -n. */ + int j_p_f = just_print_flag; + just_print_flag = 0; +#endif + + /* Construct the argument list. */ + command_argv = construct_command_argv (argv[0], NULL, NULL, 0, + &batch_filename); + if (command_argv == 0) + { +#ifdef WINDOWS32 + just_print_flag = j_p_f; +#endif + return o; + } +#endif /* !__MSDOS__ */ + + /* Using a target environment for 'shell' loses in cases like: + export var = $(shell echo foobie) + bad := $(var) + because target_environment hits a loop trying to expand $(var) to put it + in the environment. This is even more confusing when 'var' was not + explicitly exported, but just appeared in the calling environment. + + See Savannah bug #10593. + + envp = target_environment (NULL); + */ + + envp = environ; + + /* For error messages. */ + if (reading_file && reading_file->filenm) + { + char *p = alloca (strlen (reading_file->filenm)+11+4); + sprintf (p, "%s:%lu: ", reading_file->filenm, + reading_file->lineno + reading_file->offset); + error_prefix = p; + } + else + error_prefix = ""; + + /* Set up the output in case the shell writes something. */ + output_start (); + +#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY + errfd = -1; /** @todo fixme */ +#else + errfd = (output_context && output_context->err >= 0 + ? output_context->err : FD_STDERR); +#endif + +#if defined(__MSDOS__) + fpipe = msdos_openpipe (pipedes, &pid, argv[0]); + if (pipedes[0] < 0) + { + perror_with_name (error_prefix, "pipe"); + return o; + } + +#elif defined(WINDOWS32) +# ifdef CONFIG_NEW_WIN_CHILDREN + pipedes[1] = -1; + MkWinChildCreateWithStdOutPipe (command_argv, envp, errfd, &pid, &pipedes[0]); +# else + windows32_openpipe (pipedes, errfd, &pid, command_argv, envp); +# endif + /* Restore the value of just_print_flag. */ + just_print_flag = j_p_f; + + if (pipedes[0] < 0) + { + /* Open of the pipe failed, mark as failed execution. */ + shell_completed (127, 0); + perror_with_name (error_prefix, "pipe"); + return o; + } + +#else + if (pipe (pipedes) < 0) + { + perror_with_name (error_prefix, "pipe"); + return o; + } + + /* Close handles that are unnecessary for the child process. */ + CLOSE_ON_EXEC(pipedes[1]); + CLOSE_ON_EXEC(pipedes[0]); + + { + struct output out; + out.syncout = 1; + out.out = pipedes[1]; + out.err = errfd; + + pid = child_execute_job (&out, 1, command_argv, envp); + } + + if (pid < 0) + { + perror_with_name (error_prefix, "fork"); + return o; + } +#endif + + { + char *buffer; + unsigned int maxlen, i; + int cc; + + /* Record the PID for reap_children. */ + shell_function_pid = pid; +#ifndef __MSDOS__ + shell_function_completed = 0; + + /* Free the storage only the child needed. */ + free (command_argv[0]); + free (command_argv); + + /* Close the write side of the pipe. We test for -1, since + pipedes[1] is -1 on MS-Windows, and some versions of MS + libraries barf when 'close' is called with -1. */ + if (pipedes[1] >= 0) + close (pipedes[1]); +#endif + + /* Set up and read from the pipe. */ + + maxlen = 200; + buffer = xmalloc (maxlen + 1); + + /* Read from the pipe until it gets EOF. */ + for (i = 0; ; i += cc) + { + if (i == maxlen) + { + maxlen += 512; + buffer = xrealloc (buffer, maxlen + 1); + } + + EINTRLOOP (cc, read (pipedes[0], &buffer[i], maxlen - i)); + if (cc <= 0) + break; + } + buffer[i] = '\0'; + + /* Close the read side of the pipe. */ +#ifdef __MSDOS__ + if (fpipe) + { + int st = pclose (fpipe); + shell_completed (st, 0); + } +#else +# ifdef _MSC_VER /* Avoid annoying msvcrt when debugging. (bird) */ + if (pipedes[0] != -1) +# endif + (void) close (pipedes[0]); +#endif + + /* Loop until child_handler or reap_children() sets + shell_function_completed to the status of our child shell. */ + while (shell_function_completed == 0) + reap_children (1, 0); + + if (batch_filename) + { + DB (DB_VERBOSE, (_("Cleaning up temporary batch file %s\n"), + batch_filename)); + remove (batch_filename); + free (batch_filename); + } + shell_function_pid = 0; + + /* shell_completed() will set shell_function_completed to 1 when the + child dies normally, or to -1 if it dies with status 127, which is + most likely an exec fail. */ + + if (shell_function_completed == -1) + { + /* This likely means that the execvp failed, so we should just + write the error message in the pipe from the child. */ + fputs (buffer, stderr); + fflush (stderr); + } + else + { + /* The child finished normally. Replace all newlines in its output + with spaces, and put that in the variable output buffer. */ + fold_newlines (buffer, &i, trim_newlines); + o = variable_buffer_output (o, buffer, i); + } + + free (buffer); + } + + return o; +} + +#else /* _AMIGA */ + +/* Do the Amiga version of func_shell. */ + +char * +func_shell_base (char *o, char **argv, int trim_newlines) +{ + /* Amiga can't fork nor spawn, but I can start a program with + redirection of my choice. However, this means that we + don't have an opportunity to reopen stdout to trap it. Thus, + we save our own stdout onto a new descriptor and dup a temp + file's descriptor onto our stdout temporarily. After we + spawn the shell program, we dup our own stdout back to the + stdout descriptor. The buffer reading is the same as above, + except that we're now reading from a file. */ + +#include <dos/dos.h> +#include <proto/dos.h> + + BPTR child_stdout; + char tmp_output[FILENAME_MAX]; + unsigned int maxlen = 200, i; + int cc; + char * buffer, * ptr; + char ** aptr; + int len = 0; + char* batch_filename = NULL; + + /* Construct the argument list. */ + command_argv = construct_command_argv (argv[0], NULL, NULL, 0, + &batch_filename); + if (command_argv == 0) + return o; + + /* Note the mktemp() is a security hole, but this only runs on Amiga. + Ideally we would use output_tmpfile(), but this uses a special + Open(), not fopen(), and I'm not familiar enough with the code to mess + with it. */ + strcpy (tmp_output, "t:MakeshXXXXXXXX"); + mktemp (tmp_output); + child_stdout = Open (tmp_output, MODE_NEWFILE); + + for (aptr=command_argv; *aptr; aptr++) + len += strlen (*aptr) + 1; + + buffer = xmalloc (len + 1); + ptr = buffer; + + for (aptr=command_argv; *aptr; aptr++) + { + strcpy (ptr, *aptr); + ptr += strlen (ptr) + 1; + *ptr ++ = ' '; + *ptr = 0; + } + + ptr[-1] = '\n'; + + Execute (buffer, NULL, child_stdout); + free (buffer); + + Close (child_stdout); + + child_stdout = Open (tmp_output, MODE_OLDFILE); + + buffer = xmalloc (maxlen); + i = 0; + do + { + if (i == maxlen) + { + maxlen += 512; + buffer = xrealloc (buffer, maxlen + 1); + } + + cc = Read (child_stdout, &buffer[i], maxlen - i); + if (cc > 0) + i += cc; + } while (cc > 0); + + Close (child_stdout); + + fold_newlines (buffer, &i, trim_newlines); + o = variable_buffer_output (o, buffer, i); + free (buffer); + return o; +} +#endif /* _AMIGA */ + +static char * +func_shell (char *o, char **argv, const char *funcname UNUSED) +{ + return func_shell_base (o, argv, 1); +} +#endif /* !VMS */ + +#ifdef EXPERIMENTAL + +/* + equality. Return is string-boolean, i.e., the empty string is false. + */ +static char * +func_eq (char *o, char **argv, const char *funcname UNUSED) +{ + int result = ! strcmp (argv[0], argv[1]); + o = variable_buffer_output (o, result ? "1" : "", result); + return o; +} + + +/* + string-boolean not operator. + */ +static char * +func_not (char *o, char **argv, const char *funcname UNUSED) +{ + const char *s = argv[0]; + int result = 0; + NEXT_TOKEN (s); + result = ! (*s); + o = variable_buffer_output (o, result ? "1" : "", result); + return o; +} +#endif + + +#ifdef HAVE_DOS_PATHS +# ifdef __CYGWIN__ +# define IS_ABSOLUTE(n) ((n[0] && n[1] == ':') || STOP_SET (n[0], MAP_DIRSEP)) +# else +# define IS_ABSOLUTE(n) (n[0] && n[1] == ':') +# endif +# define ROOT_LEN 3 +#else +#define IS_ABSOLUTE(n) (n[0] == '/') +#define ROOT_LEN 1 +#endif + +/* Return the absolute name of file NAME which does not contain any '.', + '..' components nor any repeated path separators ('/'). */ +#ifdef KMK +char * +#else +static char * +#endif +abspath (const char *name, char *apath) +{ + char *dest; + const char *start, *end, *apath_limit; + unsigned long root_len = ROOT_LEN; + + if (name[0] == '\0' || apath == NULL) + return NULL; + +#ifdef WINDOWS32 /* bird */ + dest = unix_slashes_resolved (name, apath, GET_PATH_MAX); + if (!dest) + return NULL; + dest = strchr(apath, '\0'); + + (void)end; (void)start; (void)apath_limit; + +#elif defined __OS2__ /* bird */ + if (_abspath(apath, name, GET_PATH_MAX)) + return NULL; + dest = strchr(apath, '\0'); + + (void)end; (void)start; (void)apath_limit; (void)dest; + +#else /* !WINDOWS32 && !__OS2__ */ + apath_limit = apath + GET_PATH_MAX; + + if (!IS_ABSOLUTE(name)) + { + /* It is unlikely we would make it until here but just to make sure. */ + if (!starting_directory) + return NULL; + + strcpy (apath, starting_directory); + +#ifdef HAVE_DOS_PATHS + if (STOP_SET (name[0], MAP_DIRSEP)) + { + if (STOP_SET (name[1], MAP_DIRSEP)) + { + /* A UNC. Don't prepend a drive letter. */ + apath[0] = name[0]; + apath[1] = name[1]; + root_len = 2; + } + /* We have /foo, an absolute file name except for the drive + letter. Assume the missing drive letter is the current + drive, which we can get if we remove from starting_directory + everything past the root directory. */ + apath[root_len] = '\0'; + } +#endif + + dest = strchr (apath, '\0'); + } + else + { +#if defined(__CYGWIN__) && defined(HAVE_DOS_PATHS) + if (STOP_SET (name[0], MAP_DIRSEP)) + root_len = 1; +#endif +#ifdef KMK + memcpy (apath, name, root_len); + apath[root_len] = '\0'; + assert (strlen (apath) == root_len); +#else + strncpy (apath, name, root_len); + apath[root_len] = '\0'; +#endif + dest = apath + root_len; + /* Get past the root, since we already copied it. */ + name += root_len; +#ifdef HAVE_DOS_PATHS + if (! STOP_SET (apath[root_len - 1], MAP_DIRSEP)) + { + /* Convert d:foo into d:./foo and increase root_len. */ + apath[2] = '.'; + apath[3] = '/'; + dest++; + root_len++; + /* strncpy above copied one character too many. */ + name--; + } + else + apath[root_len - 1] = '/'; /* make sure it's a forward slash */ +#endif + } + + for (start = end = name; *start != '\0'; start = end) + { + unsigned long len; + + /* Skip sequence of multiple path-separators. */ + while (STOP_SET (*start, MAP_DIRSEP)) + ++start; + + /* Find end of path component. */ + for (end = start; ! STOP_SET (*end, MAP_DIRSEP|MAP_NUL); ++end) + ; + + len = end - start; + + if (len == 0) + break; + else if (len == 1 && start[0] == '.') + /* nothing */; + else if (len == 2 && start[0] == '.' && start[1] == '.') + { + /* Back up to previous component, ignore if at root already. */ + if (dest > apath + root_len) + for (--dest; ! STOP_SET (dest[-1], MAP_DIRSEP); --dest) + ; + } + else + { + if (! STOP_SET (dest[-1], MAP_DIRSEP)) + *dest++ = '/'; + + if (dest + len >= apath_limit) + return NULL; + + dest = memcpy (dest, start, len); + dest += len; + *dest = '\0'; + } + } +#endif /* !WINDOWS32 && !__OS2__ */ + + /* Unless it is root strip trailing separator. */ + if (dest > apath + root_len && STOP_SET (dest[-1], MAP_DIRSEP)) + --dest; + + *dest = '\0'; + + return apath; +} + + +static char * +func_realpath (char *o, char **argv, const char *funcname UNUSED) +{ + /* Expand the argument. */ + const char *p = argv[0]; + const char *path = 0; + int doneany = 0; + unsigned int len = 0; + + while ((path = find_next_token (&p, &len)) != 0) + { + if (len < GET_PATH_MAX) + { + char *rp; + struct stat st; + PATH_VAR (in); + PATH_VAR (out); + + strncpy (in, path, len); + in[len] = '\0'; + +#ifdef HAVE_REALPATH + ENULLLOOP (rp, realpath (in, out)); +#else + rp = abspath (in, out); +#endif + + if (rp) + { + int r; + EINTRLOOP (r, stat (out, &st)); + if (r == 0) + { + o = variable_buffer_output (o, out, strlen (out)); + o = variable_buffer_output (o, " ", 1); + doneany = 1; + } + } + } + } + + /* Kill last space. */ + if (doneany) + --o; + + return o; +} + +static char * +func_file (char *o, char **argv, const char *funcname UNUSED) +{ + char *fn = argv[0]; + + if (fn[0] == '>') + { + FILE *fp; +#ifdef KMK_FOPEN_NO_INHERIT_MODE + const char *mode = "w" KMK_FOPEN_NO_INHERIT_MODE; +#else + const char *mode = "w"; +#endif + + /* We are writing a file. */ + ++fn; + if (fn[0] == '>') + { +#ifdef KMK_FOPEN_NO_INHERIT_MODE + mode = "a" KMK_FOPEN_NO_INHERIT_MODE; +#else + mode = "a"; +#endif + ++fn; + } + NEXT_TOKEN (fn); + + if (fn[0] == '\0') + O (fatal, *expanding_var, _("file: missing filename")); + + ENULLLOOP (fp, fopen (fn, mode)); + if (fp == NULL) + OSS (fatal, reading_file, _("open: %s: %s"), fn, strerror (errno)); + + if (argv[1]) + { + int l = strlen (argv[1]); + int nl = l == 0 || argv[1][l-1] != '\n'; + + if (fputs (argv[1], fp) == EOF || (nl && fputc ('\n', fp) == EOF)) + OSS (fatal, reading_file, _("write: %s: %s"), fn, strerror (errno)); + } + if (fclose (fp)) + OSS (fatal, reading_file, _("close: %s: %s"), fn, strerror (errno)); + } + else if (fn[0] == '<') + { + char *preo = o; + FILE *fp; + + ++fn; + NEXT_TOKEN (fn); + if (fn[0] == '\0') + O (fatal, *expanding_var, _("file: missing filename")); + + if (argv[1]) + O (fatal, *expanding_var, _("file: too many arguments")); + +#ifdef KMK_FOPEN_NO_INHERIT_MODE + ENULLLOOP (fp, fopen (fn, "r" KMK_FOPEN_NO_INHERIT_MODE)); +#else + ENULLLOOP (fp, fopen (fn, "r")); +#endif + if (fp == NULL) + { + if (errno == ENOENT) + return o; + OSS (fatal, reading_file, _("open: %s: %s"), fn, strerror (errno)); + } + + while (1) + { + char buf[1024]; + size_t l = fread (buf, 1, sizeof (buf), fp); + if (l > 0) + o = variable_buffer_output (o, buf, l); + + if (ferror (fp)) + if (errno != EINTR) + OSS (fatal, reading_file, _("read: %s: %s"), fn, strerror (errno)); + if (feof (fp)) + break; + } + if (fclose (fp)) + OSS (fatal, reading_file, _("close: %s: %s"), fn, strerror (errno)); + + /* Remove trailing newline. */ + if (o > preo && o[-1] == '\n') + if (--o > preo && o[-1] == '\r') + --o; + } + else + OS (fatal, *expanding_var, _("file: invalid file operation: %s"), fn); + + return o; +} + +static char * +func_abspath (char *o, char **argv, const char *funcname UNUSED) +{ + /* Expand the argument. */ + const char *p = argv[0]; + const char *path = 0; + int doneany = 0; + unsigned int len = 0; + + while ((path = find_next_token (&p, &len)) != 0) + { + if (len < GET_PATH_MAX) + { + PATH_VAR (in); + PATH_VAR (out); + + strncpy (in, path, len); + in[len] = '\0'; + + if (abspath (in, out)) + { + o = variable_buffer_output (o, out, strlen (out)); + o = variable_buffer_output (o, " ", 1); + doneany = 1; + } + } + } + + /* Kill last space. */ + if (doneany) + --o; + + return o; +} + +#ifdef CONFIG_WITH_ABSPATHEX +/* Same as abspath except that the current path may be given as the + 2nd argument. */ +static char * +func_abspathex (char *o, char **argv, const char *funcname UNUSED) +{ + char *cwd = argv[1]; + + /* cwd needs leading spaces chopped and may be optional, + in which case we're exactly like $(abspath ). */ + if (cwd) + while (ISBLANK (*cwd)) + cwd++; + if (!cwd || !*cwd) + o = func_abspath (o, argv, funcname); + else + { + /* Expand the argument. */ + const char *p = argv[0]; + unsigned int cwd_len = ~0U; + char *path = 0; + int doneany = 0; + unsigned int len = 0; + PATH_VAR (in); + PATH_VAR (out); + + while ((path = find_next_token (&p, &len)) != 0) + { + if (len < GET_PATH_MAX) + { +#ifdef HAVE_DOS_PATHS + if (path[0] != '/' && path[0] != '\\' && (len < 2 || path[1] != ':') && cwd) +#else + if (path[0] != '/' && cwd) +#endif + { + /* relative path, prefix with cwd. */ + if (cwd_len == ~0U) + cwd_len = strlen (cwd); + if (cwd_len + len + 1 >= GET_PATH_MAX) + continue; + memcpy (in, cwd, cwd_len); + in[cwd_len] = '/'; + memcpy (in + cwd_len + 1, path, len); + in[cwd_len + len + 1] = '\0'; + } + else + { + /* absolute path pass it as-is. */ + memcpy (in, path, len); + in[len] = '\0'; + } + + if (abspath (in, out)) + { + o = variable_buffer_output (o, out, strlen (out)); + o = variable_buffer_output (o, " ", 1); + doneany = 1; + } + } + } + + /* Kill last space. */ + if (doneany) + --o; + } + + return o; +} +#endif + +#ifdef CONFIG_WITH_XARGS +/* Create one or more command lines avoiding the max argument + length restriction of the host OS. + + The last argument is the list of arguments that the normal + xargs command would be fed from stdin. + + The first argument is initial command and it's arguments. + + If there are three or more arguments, the 2nd argument is + the command and arguments to be used on subsequent + command lines. Defaults to the initial command. + + If there are four or more arguments, the 3rd argument is + the command to be used at the final command line. Defaults + to the sub sequent or initial command . + + A future version of this function may define more arguments + and therefor anyone specifying six or more arguments will + cause fatal errors. + + Typical usage is: + $(xargs ar cas mylib.a,$(objects)) + or + $(xargs ar cas mylib.a,ar as mylib.a,$(objects)) + + It will then create one or more "ar mylib.a ..." command + lines with proper \n\t separation so it can be used when + writing rules. */ +static char * +func_xargs (char *o, char **argv, const char *funcname UNUSED) +{ + int argc; + const char *initial_cmd; + size_t initial_cmd_len; + const char *subsequent_cmd; + size_t subsequent_cmd_len; + const char *final_cmd; + size_t final_cmd_len; + const char *args; + size_t max_args; + int i; + +#ifdef ARG_MAX + /* ARG_MAX is a bit unreliable (environment), so drop 25% of the max. */ +# define XARGS_MAX (ARG_MAX - (ARG_MAX / 4)) +#else /* FIXME: update configure with a command line length test. */ +# define XARGS_MAX 10240 +#endif + + argc = 0; + while (argv[argc]) + argc++; + if (argc > 4) + O (fatal, NILF, _("Too many arguments for $(xargs)!\n")); + + /* first: the initial / default command.*/ + initial_cmd = argv[0]; + while (ISSPACE (*initial_cmd)) + initial_cmd++; + max_args = initial_cmd_len = strlen (initial_cmd); + + /* second: the command for the subsequent command lines. defaults to the initial cmd. */ + subsequent_cmd = argc > 2 && argv[1][0] != '\0' ? argv[1] : "\0"; + while (ISSPACE (*subsequent_cmd)) + subsequent_cmd++; /* gcc 7.3.0 complains "offset ‘1’ outside bounds of constant string" if constant is "" rather than "\0". */ + if (*subsequent_cmd) + { + subsequent_cmd_len = strlen (subsequent_cmd); + if (subsequent_cmd_len > max_args) + max_args = subsequent_cmd_len; + } + else + { + subsequent_cmd = initial_cmd; + subsequent_cmd_len = initial_cmd_len; + } + + /* third: the final command. defaults to the subseq cmd. */ + final_cmd = argc > 3 && argv[2][0] != '\0' ? argv[2] : "\0"; + while (ISSPACE (*final_cmd)) + final_cmd++; /* gcc 7.3.0: same complaint as for subsequent_cmd++ */ + if (*final_cmd) + { + final_cmd_len = strlen (final_cmd); + if (final_cmd_len > max_args) + max_args = final_cmd_len; + } + else + { + final_cmd = subsequent_cmd; + final_cmd_len = subsequent_cmd_len; + } + + /* last: the arguments to split up into sensible portions. */ + args = argv[argc - 1]; + + /* calc the max argument length. */ + if (XARGS_MAX <= max_args + 2) + ONN (fatal, NILF, _("$(xargs): the commands are longer than the max exec argument length. (%lu <= %lu)\n"), + (unsigned long)XARGS_MAX, (unsigned long)max_args + 2); + max_args = XARGS_MAX - max_args - 1; + + /* generate the commands. */ + i = 0; + for (i = 0; ; i++) + { + unsigned int len; + const char *iterator = args; + const char *end = args; + const char *cur; + const char *tmp; + + /* scan the arguments till we reach the end or the max length. */ + while ((cur = find_next_token(&iterator, &len)) + && (size_t)((cur + len) - args) < max_args) + end = cur + len; + if (cur && end == args) + O (fatal, NILF, _("$(xargs): command + one single arg is too much. giving up.\n")); + + /* emit the command. */ + if (i == 0) + { + o = variable_buffer_output (o, (char *)initial_cmd, initial_cmd_len); + o = variable_buffer_output (o, " ", 1); + } + else if (cur) + { + o = variable_buffer_output (o, "\n\t", 2); + o = variable_buffer_output (o, (char *)subsequent_cmd, subsequent_cmd_len); + o = variable_buffer_output (o, " ", 1); + } + else + { + o = variable_buffer_output (o, "\n\t", 2); + o = variable_buffer_output (o, (char *)final_cmd, final_cmd_len); + o = variable_buffer_output (o, " ", 1); + } + + tmp = end; + while (tmp > args && ISSPACE (tmp[-1])) /* drop trailing spaces. */ + tmp--; + o = variable_buffer_output (o, (char *)args, tmp - args); + + + /* next */ + if (!cur) + break; + args = end; + while (ISSPACE (*args)) + args++; + } + + return o; +} +#endif + +#ifdef CONFIG_WITH_STRING_FUNCTIONS +/* + $(length string) + + XXX: This doesn't take multibyte locales into account. + */ +static char * +func_length (char *o, char **argv, const char *funcname UNUSED) +{ + size_t len = strlen (argv[0]); + return math_int_to_variable_buffer (o, len); +} + +/* + $(length-var var) + + XXX: This doesn't take multibyte locales into account. + */ +static char * +func_length_var (char *o, char **argv, const char *funcname UNUSED) +{ + struct variable *var = lookup_variable (argv[0], strlen (argv[0])); + return math_int_to_variable_buffer (o, var ? var->value_length : 0); +} + +/* func_insert and func_substr helper. */ +static char * +helper_pad (char *o, size_t to_add, const char *pad, size_t pad_len) +{ + while (to_add > 0) + { + size_t size = to_add > pad_len ? pad_len : to_add; + o = variable_buffer_output (o, pad, size); + to_add -= size; + } + return o; +} + +/* + $(insert in, str[, n[, length[, pad]]]) + + XXX: This doesn't take multibyte locales into account. + */ +static char * +func_insert (char *o, char **argv, const char *funcname UNUSED) +{ + const char *in = argv[0]; + math_int in_len = (math_int)strlen (in); + const char *str = argv[1]; + math_int str_len = (math_int)strlen (str); + math_int n = 0; + math_int length = str_len; + const char *pad = " "; + size_t pad_len = 16; + size_t i; + + if (argv[2] != NULL) + { + n = math_int_from_string (argv[2]); + if (n > 0) + n--; /* one-origin */ + else if (n == 0) + n = str_len; /* append */ + else + { /* n < 0: from the end */ + n = str_len + n; + if (n < 0) + n = 0; + } + if (n > 16*1024*1024) /* 16MB */ + OS (fatal, NILF, _("$(insert ): n=%s is out of bounds\n"), argv[2]); + + if (argv[3] != NULL) + { + length = math_int_from_string (argv[3]); + if (length < 0 || length > 16*1024*1024 /* 16MB */) + OS (fatal, NILF, _("$(insert ): length=%s is out of bounds\n"), argv[3]); + + if (argv[4] != NULL) + { + const char *tmp = argv[4]; + for (i = 0; tmp[i] == ' '; i++) + /* nothing */; + if (tmp[i] != '\0') + { + pad = argv[4]; + pad_len = strlen (pad); + } + /* else: it was all default spaces. */ + } + } + } + + /* the head of the original string */ + if (n > 0) + { + if (n <= str_len) + o = variable_buffer_output (o, str, n); + else + { + o = variable_buffer_output (o, str, str_len); + o = helper_pad (o, n - str_len, pad, pad_len); + } + } + + /* insert the string */ + if (length <= in_len) + o = variable_buffer_output (o, in, length); + else + { + o = variable_buffer_output (o, in, in_len); + o = helper_pad (o, length - in_len, pad, pad_len); + } + + /* the tail of the original string */ + if (n < str_len) + o = variable_buffer_output (o, str + n, str_len - n); + + return o; +} + +/* + $(pos needle, haystack[, start]) + $(lastpos needle, haystack[, start]) + + XXX: This doesn't take multibyte locales into account. + */ +static char * +func_pos (char *o, char **argv, const char *funcname UNUSED) +{ + const char *needle = *argv[0] ? argv[0] : " "; + size_t needle_len = strlen (needle); + const char *haystack = argv[1]; + size_t haystack_len = strlen (haystack); + math_int start = 0; + const char *hit; + + if (argv[2] != NULL) + { + start = math_int_from_string (argv[2]); + if (start > 0) + start--; /* one-origin */ + else if (start < 0) + start = haystack_len + start; /* from the end */ + if (start < 0 || start + needle_len > haystack_len) + return math_int_to_variable_buffer (o, 0); + } + else if (funcname[0] == 'l') + start = haystack_len - 1; + + /* do the searching */ + if (funcname[0] != 'l') + { /* pos */ + if (needle_len == 1) + hit = strchr (haystack + start, *needle); + else + hit = strstr (haystack + start, needle); + } + else + { /* last pos */ + int ch = *needle; + size_t off = start + 1; + + hit = NULL; + while (off-- > 0) + { + if ( haystack[off] == ch + && ( needle_len == 1 + || strncmp (&haystack[off], needle, needle_len) == 0)) + { + hit = haystack + off; + break; + } + } + } + + return math_int_to_variable_buffer (o, hit ? hit - haystack + 1 : 0); +} + +/* + $(substr str, start[, length[, pad]]) + + XXX: This doesn't take multibyte locales into account. + */ +static char * +func_substr (char *o, char **argv, const char *funcname UNUSED) +{ + const char *str = argv[0]; + math_int str_len = (math_int)strlen (str); + math_int start = math_int_from_string (argv[1]); + math_int length = 0; + const char *pad = NULL; + size_t pad_len = 0; + + if (argv[2] != NULL) + { + if (argv[3] != NULL) + { + pad = argv[3]; + for (pad_len = 0; pad[pad_len] == ' '; pad_len++) + /* nothing */; + if (pad[pad_len] != '\0') + pad_len = strlen (pad); + else + { + pad = " "; + pad_len = 16; + } + } + length = math_int_from_string (argv[2]); + if (pad != NULL && length > 16*1024*1024 /* 16MB */) + OS (fatal, NILF, _("$(substr ): length=%s is out of bounds\n"), argv[2]); + if (pad != NULL && length < 0) + OS (fatal, NILF, _("$(substr ): negative length (%s) and padding doesn't mix.\n"), argv[2]); + if (length == 0) + return o; + } + + /* Note that negative start and length are used for referencing from the + end of the string. */ + if (pad == NULL) + { + if (start > 0) + start--; /* one-origin */ + else + { + start = str_len + start; + if (start <= 0) + { + if (length < 0) + return o; + start += length; + if (start <= 0) + return o; + length = start; + start = 0; + } + } + + if (start >= str_len) + return o; + if (length == 0) + length = str_len - start; + else if (length < 0) + { + if (str_len <= -length) + return o; + length += str_len; + if (length <= start) + return o; + length -= start; + } + else if (start + length > str_len) + length = str_len - start; + + o = variable_buffer_output (o, str + start, length); + } + else + { + if (start > 0) + { + start--; /* one-origin */ + if (start >= str_len) + return length ? helper_pad (o, length, pad, pad_len) : o; + if (length == 0) + length = str_len - start; + } + else + { + start = str_len + start; + if (start <= 0) + { + if (start + length <= 0) + return length ? helper_pad (o, length, pad, pad_len) : o; + o = helper_pad (o, -start, pad, pad_len); + return variable_buffer_output (o, str, length + start); + } + if (length == 0) + length = str_len - start; + } + if (start + length <= str_len) + o = variable_buffer_output (o, str + start, length); + else + { + o = variable_buffer_output (o, str + start, str_len - start); + o = helper_pad (o, start + length - str_len, pad, pad_len); + } + } + + return o; +} + +/* + $(translate string, from-set[, to-set[, pad-char]]) + + XXX: This doesn't take multibyte locales into account. + */ +static char * +func_translate (char *o, char **argv, const char *funcname UNUSED) +{ + const unsigned char *str = (const unsigned char *)argv[0]; + const unsigned char *from_set = (const unsigned char *)argv[1]; + const char *to_set = argv[2] != NULL ? argv[2] : ""; + char trans_tab[1 << CHAR_BIT]; + int i; + char ch; + + /* init the array. */ + for (i = 0; i < (1 << CHAR_BIT); i++) + trans_tab[i] = i; + + while ( (i = *from_set) != '\0' + && (ch = *to_set) != '\0') + { + trans_tab[i] = ch; + from_set++; + to_set++; + } + + if (i != '\0') + { + ch = '\0'; /* no padding == remove char */ + if (argv[2] != NULL && argv[3] != NULL) + { + ch = argv[3][0]; + if (ch && argv[3][1]) + OS (fatal, NILF, _("$(translate ): pad=`%s' expected a single char\n"), argv[3]); + if (ch == '\0') /* no char == space */ + ch = ' '; + } + while ((i = *from_set++) != '\0') + trans_tab[i] = ch; + } + + /* do the translation */ + while ((i = *str++) != '\0') + { + ch = trans_tab[i]; + if (ch) + o = variable_buffer_output (o, &ch, 1); + } + + return o; +} +#endif /* CONFIG_WITH_STRING_FUNCTIONS */ + +#ifdef CONFIG_WITH_DEFINED +/* Similar to ifdef. */ +static char * +func_defined (char *o, char **argv, const char *funcname UNUSED) +{ + struct variable *v = lookup_variable (argv[0], strlen (argv[0])); + int result = v != NULL && *v->value != '\0'; + o = variable_buffer_output (o, result ? "1" : "", result); + return o; +} +#endif /* CONFIG_WITH_DEFINED*/ + +#ifdef CONFIG_WITH_TOUPPER_TOLOWER +static char * +func_toupper_tolower (char *o, char **argv, const char *funcname) +{ + /* Expand the argument. */ + const char *p = argv[0]; + while (*p) + { + /* convert to temporary buffer */ + char tmp[256]; + unsigned int i; + if (!strcmp(funcname, "toupper")) + for (i = 0; i < sizeof(tmp) && *p; i++, p++) + tmp[i] = toupper(*p); + else + for (i = 0; i < sizeof(tmp) && *p; i++, p++) + tmp[i] = tolower(*p); + o = variable_buffer_output (o, tmp, i); + } + + return o; +} +#endif /* CONFIG_WITH_TOUPPER_TOLOWER */ + +#if defined(CONFIG_WITH_VALUE_LENGTH) && defined(CONFIG_WITH_COMPARE) + +/* Strip leading spaces and other things off a command. */ +static const char * +comp_cmds_strip_leading (const char *s, const char *e) +{ + while (s < e) + { + const char ch = *s; + if (!ISBLANK (ch) + && ch != '@' +#ifdef CONFIG_WITH_COMMANDS_FUNC + && ch != '%' +#endif + && ch != '+' + && ch != '-') + break; + s++; + } + return s; +} + +/* Worker for func_comp_vars() which is called if the comparision failed. + It will do the slow command by command comparision of the commands + when there invoked as comp-cmds. */ +static char * +comp_vars_ne (char *o, const char *s1, const char *e1, const char *s2, const char *e2, + char *ne_retval, const char *funcname) +{ + /* give up at once if not comp-cmds or comp-cmds-ex. */ + if (strcmp (funcname, "comp-cmds") != 0 + && strcmp (funcname, "comp-cmds-ex") != 0) + o = variable_buffer_output (o, ne_retval, strlen (ne_retval)); + else + { + const char * const s1_start = s1; + int new_cmd = 1; + int diff; + for (;;) + { + /* if it's a new command, strip leading stuff. */ + if (new_cmd) + { + s1 = comp_cmds_strip_leading (s1, e1); + s2 = comp_cmds_strip_leading (s2, e2); + new_cmd = 0; + } + if (s1 >= e1 || s2 >= e2) + break; + + /* + * Inner compare loop which compares one line. + * FIXME: parse quoting! + */ + for (;;) + { + const char ch1 = *s1; + const char ch2 = *s2; + diff = ch1 - ch2; + if (diff) + break; + if (ch1 == '\n') + break; + assert (ch1 != '\r'); + + /* next */ + s1++; + s2++; + if (s1 >= e1 || s2 >= e2) + break; + } + + /* + * If we exited because of a difference try to end-of-command + * comparision, e.g. ignore trailing spaces. + */ + if (diff) + { + /* strip */ + while (s1 < e1 && ISBLANK (*s1)) + s1++; + while (s2 < e2 && ISBLANK (*s2)) + s2++; + if (s1 >= e1 || s2 >= e2) + break; + + /* compare again and check that it's a newline. */ + if (*s2 != '\n' || *s1 != '\n') + break; + } + /* Break out if we exited because of EOS. */ + else if (s1 >= e1 || s2 >= e2) + break; + + /* + * Detect the end of command lines. + */ + if (*s1 == '\n') + new_cmd = s1 == s1_start || s1[-1] != '\\'; + s1++; + s2++; + } + + /* + * Ignore trailing empty lines. + */ + if (s1 < e1 || s2 < e2) + { + while (s1 < e1 && (ISBLANK (*s1) || *s1 == '\n')) + if (*s1++ == '\n') + s1 = comp_cmds_strip_leading (s1, e1); + while (s2 < e2 && (ISBLANK (*s2) || *s2 == '\n')) + if (*s2++ == '\n') + s2 = comp_cmds_strip_leading (s2, e2); + } + + /* emit the result. */ + if (s1 == e1 && s2 == e2) + o = variable_buffer_output (o, "", 1) - 1; /** @todo check why this was necessary back the... */ + else + o = variable_buffer_output (o, ne_retval, strlen (ne_retval)); + } + return o; +} + +/* + $(comp-vars var1,var2,not-equal-return) + or + $(comp-cmds cmd-var1,cmd-var2,not-equal-return) + + Compares the two variables (that's given by name to avoid unnecessary + expanding) and return the string in the third argument if not equal. + If equal, nothing is returned. + + comp-vars will to an exact comparision only stripping leading and + trailing spaces. + + comp-cmds will compare command by command, ignoring not only leading + and trailing spaces on each line but also leading one leading '@', + '-', '+' and '%' +*/ +static char * +func_comp_vars (char *o, char **argv, const char *funcname) +{ + const char *s1, *e1, *x1, *s2, *e2, *x2; + char *a1 = NULL, *a2 = NULL; + size_t l, l1, l2; + struct variable *var1 = lookup_variable (argv[0], strlen (argv[0])); + struct variable *var2 = lookup_variable (argv[1], strlen (argv[1])); + + /* the simple cases */ + if (var1 == var2) + return variable_buffer_output (o, "", 0); /* eq */ + if (!var1 || !var2) + return variable_buffer_output (o, argv[2], strlen(argv[2])); + if (var1->value == var2->value) + return variable_buffer_output (o, "", 0); /* eq */ + if ( (!var1->recursive || IS_VARIABLE_RECURSIVE_WITHOUT_DOLLAR (var1)) + && (!var2->recursive || IS_VARIABLE_RECURSIVE_WITHOUT_DOLLAR (var2)) ) + { + if ( var1->value_length == var2->value_length + && !memcmp (var1->value, var2->value, var1->value_length)) + return variable_buffer_output (o, "", 0); /* eq */ + + /* ignore trailing and leading blanks */ + s1 = var1->value; + e1 = s1 + var1->value_length; + while (ISBLANK (*s1)) + s1++; + while (e1 > s1 && ISBLANK (e1[-1])) + e1--; + + s2 = var2->value; + e2 = s2 + var2->value_length; + while (ISBLANK (*s2)) + s2++; + while (e2 > s2 && ISBLANK (e2[-1])) + e2--; + + if (e1 - s1 != e2 - s2) + return comp_vars_ne (o, s1, e1, s2, e2, argv[2], funcname); + if (!memcmp (s1, s2, e1 - s1)) + return variable_buffer_output (o, "", 0); /* eq */ + return comp_vars_ne (o, s1, e1, s2, e2, argv[2], funcname); + } + + /* ignore trailing and leading blanks */ + s1 = var1->value; + e1 = s1 + var1->value_length; + while (ISBLANK (*s1)) + s1++; + while (e1 > s1 && ISBLANK (e1[-1])) + e1--; + + s2 = var2->value; + e2 = s2 + var2->value_length; + while (ISBLANK (*s2)) + s2++; + while (e2 > s2 && ISBLANK (e2[-1])) + e2--; + + /* both empty after stripping? */ + if (s1 == e1 && s2 == e2) + return variable_buffer_output (o, "", 0); /* eq */ + + /* optimist. */ + if ( e1 - s1 == e2 - s2 + && !memcmp(s1, s2, e1 - s1)) + return variable_buffer_output (o, "", 0); /* eq */ + + /* compare up to the first '$' or the end. */ + x1 = var1->recursive ? memchr (s1, '$', e1 - s1) : NULL; + x2 = var2->recursive ? memchr (s2, '$', e2 - s2) : NULL; + if (!x1 && !x2) + return comp_vars_ne (o, s1, e1, s2, e2, argv[2], funcname); + + l1 = x1 ? x1 - s1 : e1 - s1; + l2 = x2 ? x2 - s2 : e2 - s2; + l = l1 <= l2 ? l1 : l2; + if (l && memcmp (s1, s2, l)) + return comp_vars_ne (o, s1, e1, s2, e2, argv[2], funcname); + + /* one or both buffers now require expanding. */ + if (!x1) + s1 += l; + else + { + s1 = a1 = allocated_variable_expand ((char *)s1 + l); + if (!l) + while (ISBLANK (*s1)) + s1++; + e1 = strchr (s1, '\0'); + while (e1 > s1 && ISBLANK (e1[-1])) + e1--; + } + + if (!x2) + s2 += l; + else + { + s2 = a2 = allocated_variable_expand ((char *)s2 + l); + if (!l) + while (ISBLANK (*s2)) + s2++; + e2 = strchr (s2, '\0'); + while (e2 > s2 && ISBLANK (e2[-1])) + e2--; + } + + /* the final compare */ + if ( e1 - s1 != e2 - s2 + || memcmp (s1, s2, e1 - s1)) + o = comp_vars_ne (o, s1, e1, s2, e2, argv[2], funcname); + else + o = variable_buffer_output (o, "", 1) - 1; /* eq */ /** @todo check why this was necessary back the... */ + if (a1) + free (a1); + if (a2) + free (a2); + return o; +} + +/* + $(comp-cmds-ex cmds1,cmds2,not-equal-return) + + Compares the two strings and return the string in the third argument + if not equal. If equal, nothing is returned. + + The comparision will be performed command by command, ignoring not + only leading and trailing spaces on each line but also leading one + leading '@', '-', '+' and '%'. +*/ +static char * +func_comp_cmds_ex (char *o, char **argv, const char *funcname) +{ + const char *s1, *e1, *s2, *e2; + size_t l1, l2; + + /* the simple cases */ + s1 = argv[0]; + s2 = argv[1]; + if (s1 == s2) + return variable_buffer_output (o, "", 0); /* eq */ + l1 = strlen (argv[0]); + l2 = strlen (argv[1]); + + if ( l1 == l2 + && !memcmp (s1, s2, l1)) + return variable_buffer_output (o, "", 0); /* eq */ + + /* ignore trailing and leading blanks */ + e1 = s1 + l1; + s1 = comp_cmds_strip_leading (s1, e1); + + e2 = s2 + l2; + s2 = comp_cmds_strip_leading (s2, e2); + + if (e1 - s1 != e2 - s2) + return comp_vars_ne (o, s1, e1, s2, e2, argv[2], funcname); + if (!memcmp (s1, s2, e1 - s1)) + return variable_buffer_output (o, "", 0); /* eq */ + return comp_vars_ne (o, s1, e1, s2, e2, argv[2], funcname); +} +#endif + +#ifdef CONFIG_WITH_DATE +# if defined (_MSC_VER) /* FIXME: !defined (HAVE_STRPTIME) */ +char *strptime(const char *s, const char *format, struct tm *tm) +{ + return (char *)"strptime is not implemented"; +} +# endif +/* Check if the string is all blanks or not. */ +static int +all_blanks (const char *s) +{ + if (!s) + return 1; + while (ISSPACE (*s)) + s++; + return *s == '\0'; +} + +/* The first argument is the strftime format string, a iso + timestamp is the default if nothing is given. + + The second argument is a time value if given. The format + is either the format from the first argument or given as + an additional third argument. */ +static char * +func_date (char *o, char **argv, const char *funcname) +{ + char *p; + char *buf; + size_t buf_size; + struct tm t; + const char *format; + + /* determin the format - use a single word as the default. */ + format = !strcmp (funcname, "date-utc") + ? "%Y-%m-%dT%H:%M:%SZ" + : "%Y-%m-%dT%H:%M:%S"; + if (!all_blanks (argv[0])) + format = argv[0]; + + /* get the time. */ + memset (&t, 0, sizeof(t)); + if (argv[0] && !all_blanks (argv[1])) + { + const char *input_format = !all_blanks (argv[2]) ? argv[2] : format; + p = strptime (argv[1], input_format, &t); + if (!p || *p != '\0') + { + OSSSS (error, NILF, _("$(%s): strptime(%s,%s,) -> %s\n"), funcname, + argv[1], input_format, p ? p : "<null>"); + return variable_buffer_output (o, "", 0); + } + } + else + { + time_t tval; + time (&tval); + if (!strcmp (funcname, "date-utc")) + t = *gmtime (&tval); + else + t = *localtime (&tval); + } + + /* format it. note that zero isn't necessarily an error, so we'll + have to keep shut about failures. */ + buf_size = 64; + buf = xmalloc (buf_size); + while (strftime (buf, buf_size, format, &t) == 0) + { + if (buf_size >= 4096) + { + *buf = '\0'; + break; + } + buf = xrealloc (buf, buf_size <<= 1); + } + o = variable_buffer_output (o, buf, strlen (buf)); + free (buf); + return o; +} +#endif + +#ifdef CONFIG_WITH_FILE_SIZE +/* Prints the size of the specified file. Only one file is + permitted, notthing is stripped. -1 is returned if stat + fails. */ +static char * +func_file_size (char *o, char **argv, const char *funcname UNUSED) +{ + struct stat st; + if (stat (argv[0], &st)) + return variable_buffer_output (o, "-1", 2); + return math_int_to_variable_buffer (o, st.st_size); +} +#endif + +#ifdef CONFIG_WITH_WHICH +/* Checks if the specified file exists an is executable. + On systems employing executable extensions, the name may + be modified to include the extension. */ +static int func_which_test_x (char *file) +{ + struct stat st; +# if defined(WINDOWS32) || defined(__OS2__) + char *ext; + char *slash; + + /* fix slashes first. */ + slash = file; + while ((slash = strchr (slash, '\\')) != NULL) + *slash++ = '/'; + + /* straight */ + if (stat (file, &st) == 0 + && S_ISREG (st.st_mode)) + return 1; + + /* don't try add an extension if there already is one */ + ext = strchr (file, '\0'); + if (ext - file >= 4 + && ( !stricmp (ext - 4, ".exe") + || !stricmp (ext - 4, ".cmd") + || !stricmp (ext - 4, ".bat") + || !stricmp (ext - 4, ".com"))) + return 0; + + /* try the extensions. */ + strcpy (ext, ".exe"); + if (stat (file, &st) == 0 + && S_ISREG (st.st_mode)) + return 1; + + strcpy (ext, ".cmd"); + if (stat (file, &st) == 0 + && S_ISREG (st.st_mode)) + return 1; + + strcpy (ext, ".bat"); + if (stat (file, &st) == 0 + && S_ISREG (st.st_mode)) + return 1; + + strcpy (ext, ".com"); + if (stat (file, &st) == 0 + && S_ISREG (st.st_mode)) + return 1; + + return 0; + +# else + + return access (file, X_OK) == 0 + && stat (file, &st) == 0 + && S_ISREG (st.st_mode); +# endif +} + +/* Searches for the specified programs in the PATH and print + their full location if found. Prints nothing if not found. */ +static char * +func_which (char *o, char **argv, const char *funcname UNUSED) +{ + const char *path; + struct variable *path_var; + unsigned i; + int first = 1; + PATH_VAR (buf); + + path_var = lookup_variable ("PATH", 4); + if (path_var) + path = path_var->value; + else + path = "."; + + /* iterate input */ + for (i = 0; argv[i]; i++) + { + unsigned int len; + const char *iterator = argv[i]; + char *cur; + + while ((cur = find_next_token (&iterator, &len))) + { + /* if there is a separator, don't walk the path. */ + if (memchr (cur, '/', len) +#ifdef HAVE_DOS_PATHS + || memchr (cur, '\\', len) + || memchr (cur, ':', len) +#endif + ) + { + if (len + 1 + 4 < GET_PATH_MAX) /* +4 for .exe */ + { + memcpy (buf, cur, len); + buf[len] = '\0'; + if (func_which_test_x (buf)) + o = variable_buffer_output (o, buf, strlen (buf)); + } + } + else + { + const char *comp = path; + for (;;) + { + const char *src = comp; + const char *end = strchr (comp, PATH_SEPARATOR_CHAR); + size_t src_len = end ? (size_t)(end - comp) : strlen (comp); + if (!src_len) + { + src_len = 1; + src = "."; + } + if (len + src_len + 2 + 4 < GET_PATH_MAX) /* +4 for .exe */ + { + memcpy (buf, src, src_len); + buf [src_len] = '/'; + memcpy (&buf[src_len + 1], cur, len); + buf[src_len + 1 + len] = '\0'; + + if (func_which_test_x (buf)) + { + if (!first) + o = variable_buffer_output (o, " ", 1); + o = variable_buffer_output (o, buf, strlen (buf)); + first = 0; + break; + } + } + + /* next */ + if (!end) + break; + comp = end + 1; + } + } + } + } + + return variable_buffer_output (o, "", 0); +} +#endif /* CONFIG_WITH_WHICH */ + +#ifdef CONFIG_WITH_IF_CONDITIONALS + +/* Evaluates the expression given in the argument using the + same evaluator as for the new 'if' statements, except now + we don't force the result into a boolean like for 'if' and + '$(if-expr ,,)'. */ +static char * +func_expr (char *o, char **argv, const char *funcname UNUSED) +{ + o = expr_eval_to_string (o, argv[0]); + return o; +} + +/* Same as '$(if ,,)' except the first argument is evaluated + using the same evaluator as for the new 'if' statements. */ +static char * +func_if_expr (char *o, char **argv, const char *funcname UNUSED) +{ + int rc; + char *to_expand; + + /* Evaluate the condition in argv[0] and expand the 2nd or + 3rd (optional) argument according to the result. */ + rc = expr_eval_if_conditionals (argv[0], NULL); + to_expand = rc == 0 ? argv[1] : argv[2]; + if (to_expand && *to_expand) + variable_expand_string_2 (o, to_expand, -1, &o); + + return o; +} + +/* + $(select when1-cond, when1-body[,whenN-cond, whenN-body]). + */ +static char * +func_select (char *o, char **argv, const char *funcname UNUSED) +{ + int i; + + /* Test WHEN-CONDs until one matches. The check for 'otherwise[:]' + and 'default[:]' make this a bit more fun... */ + + for (i = 0; argv[i] != NULL; i += 2) + { + const char *cond = argv[i]; + int is_otherwise = 0; + + if (argv[i + 1] == NULL) + O (fatal, NILF, _("$(select ): not an even argument count\n")); + + while (ISSPACE (*cond)) + cond++; + if ( (*cond == 'o' && strncmp (cond, "otherwise", 9) == 0) + || (*cond == 'd' && strncmp (cond, "default", 7) == 0)) + { + const char *end = cond + (*cond == 'o' ? 9 : 7); + while (ISSPACE (*end)) + end++; + if (*end == ':') + do end++; + while (ISSPACE (*end)); + is_otherwise = *end == '\0'; + } + + if ( is_otherwise + || expr_eval_if_conditionals (cond, NULL) == 0 /* true */) + { + variable_expand_string_2 (o, argv[i + 1], -1, &o); + break; + } + } + + return o; +} + +#endif /* CONFIG_WITH_IF_CONDITIONALS */ + +#ifdef CONFIG_WITH_SET_CONDITIONALS +static char * +func_set_intersects (char *o, char **argv, const char *funcname UNUSED) +{ + const char *s1_cur; + unsigned int s1_len; + const char *s1_iterator = argv[0]; + + while ((s1_cur = find_next_token (&s1_iterator, &s1_len)) != 0) + { + const char *s2_cur; + unsigned int s2_len; + const char *s2_iterator = argv[1]; + while ((s2_cur = find_next_token (&s2_iterator, &s2_len)) != 0) + if (s2_len == s1_len + && strneq (s2_cur, s1_cur, s1_len) ) + return variable_buffer_output (o, "1", 1); /* found intersection */ + } + + return o; /* no intersection */ +} +#endif /* CONFIG_WITH_SET_CONDITIONALS */ + +#ifdef CONFIG_WITH_STACK + +/* Push an item (string without spaces). */ +static char * +func_stack_push (char *o, char **argv, const char *funcname UNUSED) +{ + do_variable_definition(NILF, argv[0], argv[1], o_file, f_append, 0 /* !target_var */); + return o; +} + +/* Pops an item off the stack / get the top stack element. + (This is what's tricky to do in pure GNU make syntax.) */ +static char * +func_stack_pop_top (char *o, char **argv, const char *funcname) +{ + struct variable *stack_var; + const char *stack = argv[0]; + + stack_var = lookup_variable (stack, strlen (stack) ); + if (stack_var) + { + unsigned int len; + const char *iterator = stack_var->value; + char *lastitem = NULL; + char *cur; + + while ((cur = find_next_token (&iterator, &len))) + lastitem = cur; + + if (lastitem != NULL) + { + if (strcmp (funcname, "stack-popv") != 0) + o = variable_buffer_output (o, lastitem, len); + if (strcmp (funcname, "stack-top") != 0) + { + *lastitem = '\0'; + while (lastitem > stack_var->value && ISSPACE (lastitem[-1])) + *--lastitem = '\0'; +#ifdef CONFIG_WITH_VALUE_LENGTH + stack_var->value_length = lastitem - stack_var->value; +#endif + VARIABLE_CHANGED (stack_var); + } + } + } + return o; +} +#endif /* CONFIG_WITH_STACK */ + +#if defined (CONFIG_WITH_MATH) || defined (CONFIG_WITH_NANOTS) || defined (CONFIG_WITH_FILE_SIZE) +/* outputs the number (as a string) into the variable buffer. */ +static char * +math_int_to_variable_buffer (char *o, math_int num) +{ + static const char xdigits[17] = "0123456789abcdef"; + int negative; + char strbuf[24]; /* 16 hex + 2 prefix + sign + term => 20 + or 20 dec + sign + term => 22 */ + char *str = &strbuf[sizeof (strbuf) - 1]; + + negative = num < 0; + if (negative) + num = -num; + + *str = '\0'; + + do + { +#ifdef HEX_MATH_NUMBERS + *--str = xdigits[num & 0xf]; + num >>= 4; +#else + *--str = xdigits[num % 10]; + num /= 10; +#endif + } + while (num); + +#ifdef HEX_MATH_NUMBERS + *--str = 'x'; + *--str = '0'; +#endif + + if (negative) + *--str = '-'; + + return variable_buffer_output (o, str, &strbuf[sizeof (strbuf) - 1] - str); +} +#endif /* CONFIG_WITH_MATH || CONFIG_WITH_NANOTS */ + +#ifdef CONFIG_WITH_MATH + +/* Converts a string to an integer, causes an error if the format is invalid. */ +static math_int +math_int_from_string (const char *str) +{ + const char *start; + unsigned base = 0; + int negative = 0; + math_int num = 0; + + /* strip spaces */ + while (ISSPACE (*str)) + str++; + if (!*str) + { + O (error, NILF, _("bad number: empty\n")); + return 0; + } + start = str; + + /* check for +/- */ + while (*str == '+' || *str == '-' || ISSPACE (*str)) + if (*str++ == '-') + negative = !negative; + + /* check for prefix - we do not accept octal numbers, sorry. */ + if (*str == '0' && (str[1] == 'x' || str[1] == 'X')) + { + base = 16; + str += 2; + } + else + { + /* look for a hex digit, if not found treat it as decimal */ + const char *p2 = str; + for ( ; *p2; p2++) + if (isxdigit (*p2) && !isdigit (*p2) && isascii (*p2) ) + { + base = 16; + break; + } + if (base == 0) + base = 10; + } + + /* must have at least one digit! */ + if ( !isascii (*str) + || !(base == 16 ? isxdigit (*str) : isdigit (*str)) ) + { + OS (error, NILF, _("bad number: '%s'\n"), start); + return 0; + } + + /* convert it! */ + while (*str && !ISSPACE (*str)) + { + int ch = *str++; + if (ch >= '0' && ch <= '9') + ch -= '0'; + else if (base == 16 && ch >= 'a' && ch <= 'f') + ch -= 'a' - 10; + else if (base == 16 && ch >= 'A' && ch <= 'F') + ch -= 'A' - 10; + else + { + OSNN (error, NILF, _("bad number: '%s' (base=%u, pos=%lu)\n"), start, base, (unsigned long)(str - start)); + return 0; + } + num *= base; + num += ch; + } + + /* check trailing spaces. */ + while (ISSPACE (*str)) + str++; + if (*str) + { + OS (error, NILF, _("bad number: '%s'\n"), start); + return 0; + } + + return negative ? -num : num; +} + +/* Add two or more integer numbers. */ +static char * +func_int_add (char *o, char **argv, const char *funcname UNUSED) +{ + math_int num; + int i; + + num = math_int_from_string (argv[0]); + for (i = 1; argv[i]; i++) + num += math_int_from_string (argv[i]); + + return math_int_to_variable_buffer (o, num); +} + +/* Subtract two or more integer numbers. */ +static char * +func_int_sub (char *o, char **argv, const char *funcname UNUSED) +{ + math_int num; + int i; + + num = math_int_from_string (argv[0]); + for (i = 1; argv[i]; i++) + num -= math_int_from_string (argv[i]); + + return math_int_to_variable_buffer (o, num); +} + +/* Multiply two or more integer numbers. */ +static char * +func_int_mul (char *o, char **argv, const char *funcname UNUSED) +{ + math_int num; + int i; + + num = math_int_from_string (argv[0]); + for (i = 1; argv[i]; i++) + num *= math_int_from_string (argv[i]); + + return math_int_to_variable_buffer (o, num); +} + +/* Divide an integer number by one or more divisors. */ +static char * +func_int_div (char *o, char **argv, const char *funcname UNUSED) +{ + math_int num; + math_int divisor; + int i; + + num = math_int_from_string (argv[0]); + for (i = 1; argv[i]; i++) + { + divisor = math_int_from_string (argv[i]); + if (!divisor) + { + OS (error, NILF, _("divide by zero ('%s')\n"), argv[i]); + return math_int_to_variable_buffer (o, 0); + } + num /= divisor; + } + + return math_int_to_variable_buffer (o, num); +} + + +/* Divide and return the remainder. */ +static char * +func_int_mod (char *o, char **argv, const char *funcname UNUSED) +{ + math_int num; + math_int divisor; + + num = math_int_from_string (argv[0]); + divisor = math_int_from_string (argv[1]); + if (!divisor) + { + OS (error, NILF, _("divide by zero ('%s')\n"), argv[1]); + return math_int_to_variable_buffer (o, 0); + } + num %= divisor; + + return math_int_to_variable_buffer (o, num); +} + +/* 2-complement. */ +static char * +func_int_not (char *o, char **argv, const char *funcname UNUSED) +{ + math_int num; + + num = math_int_from_string (argv[0]); + num = ~num; + + return math_int_to_variable_buffer (o, num); +} + +/* Bitwise AND (two or more numbers). */ +static char * +func_int_and (char *o, char **argv, const char *funcname UNUSED) +{ + math_int num; + int i; + + num = math_int_from_string (argv[0]); + for (i = 1; argv[i]; i++) + num &= math_int_from_string (argv[i]); + + return math_int_to_variable_buffer (o, num); +} + +/* Bitwise OR (two or more numbers). */ +static char * +func_int_or (char *o, char **argv, const char *funcname UNUSED) +{ + math_int num; + int i; + + num = math_int_from_string (argv[0]); + for (i = 1; argv[i]; i++) + num |= math_int_from_string (argv[i]); + + return math_int_to_variable_buffer (o, num); +} + +/* Bitwise XOR (two or more numbers). */ +static char * +func_int_xor (char *o, char **argv, const char *funcname UNUSED) +{ + math_int num; + int i; + + num = math_int_from_string (argv[0]); + for (i = 1; argv[i]; i++) + num ^= math_int_from_string (argv[i]); + + return math_int_to_variable_buffer (o, num); +} + +/* Compare two integer numbers. Returns make boolean (true="1"; false=""). */ +static char * +func_int_cmp (char *o, char **argv, const char *funcname) +{ + math_int num1; + math_int num2; + int rc; + + num1 = math_int_from_string (argv[0]); + num2 = math_int_from_string (argv[1]); + + funcname += sizeof ("int-") - 1; + if (!strcmp (funcname, "eq")) + rc = num1 == num2; + else if (!strcmp (funcname, "ne")) + rc = num1 != num2; + else if (!strcmp (funcname, "gt")) + rc = num1 > num2; + else if (!strcmp (funcname, "ge")) + rc = num1 >= num2; + else if (!strcmp (funcname, "lt")) + rc = num1 < num2; + else /*if (!strcmp (funcname, "le"))*/ + rc = num1 <= num2; + + return variable_buffer_output (o, rc ? "1" : "", rc); +} + +#endif /* CONFIG_WITH_MATH */ + +#ifdef CONFIG_WITH_NANOTS +/* Returns the current timestamp as nano seconds. The time + source is a high res monotone one if the platform provides + this (and we know about it). + + Tip. Use this with int-sub to profile makefile reading + and similar. */ +static char * +func_nanots (char *o, char **argv UNUSED, const char *funcname UNUSED) +{ + return math_int_to_variable_buffer (o, nano_timestamp ()); +} +#endif + +#ifdef CONFIG_WITH_OS2_LIBPATH +/* Sets or gets the OS/2 libpath variables. + + The first argument indicates which variable - BEGINLIBPATH, + ENDLIBPATH, LIBPATHSTRICT or LIBPATH. + + The second indicates whether this is a get (not present) or + set (present) operation. When present it is the new value for + the variable. */ +static char * +func_os2_libpath (char *o, char **argv, const char *funcname UNUSED) +{ + char buf[4096]; + ULONG fVar; + APIRET rc; + + /* translate variable name (first arg) */ + if (!strcmp (argv[0], "BEGINLIBPATH")) + fVar = BEGIN_LIBPATH; + else if (!strcmp (argv[0], "ENDLIBPATH")) + fVar = END_LIBPATH; + else if (!strcmp (argv[0], "LIBPATHSTRICT")) + fVar = LIBPATHSTRICT; + else if (!strcmp (argv[0], "LIBPATH")) + fVar = 0; + else + { + OS (error, NILF, _("$(libpath): unknown variable `%s'"), argv[0]); + return variable_buffer_output (o, "", 0); + } + + if (!argv[1]) + { + /* get the variable value. */ + if (fVar != 0) + { + buf[0] = buf[1] = buf[2] = buf[3] = '\0'; + rc = DosQueryExtLIBPATH (buf, fVar); + } + else + rc = DosQueryHeaderInfo (NULLHANDLE, 0, buf, sizeof(buf), QHINF_LIBPATH); + if (rc != NO_ERROR) + { + OSN (error, NILF, _("$(libpath): failed to query `%s', rc=%d"), argv[0], rc); + return variable_buffer_output (o, "", 0); + } + o = variable_buffer_output (o, buf, strlen (buf)); + } + else + { + /* set the variable value. */ + size_t len; + size_t len_max = sizeof (buf) < 2048 ? sizeof (buf) : 2048; + const char *val; + const char *end; + + if (fVar == 0) + { + O (error, NILF, _("$(libpath): LIBPATH is read-only")); + return variable_buffer_output (o, "", 0); + } + + /* strip leading and trailing spaces and check for max length. */ + val = argv[1]; + while (ISSPACE (*val)) + val++; + end = strchr (val, '\0'); + while (end > val && ISSPACE (end[-1])) + end--; + + len = end - val; + if (len >= len_max) + { + OSNN (error, NILF, _("$(libpath): The new `%s' value is too long (%d bytes, max %d)"), + argv[0], len, len_max); + return variable_buffer_output (o, "", 0); + } + + /* make a stripped copy in low memory and try set it. */ + memcpy (buf, val, len); + buf[len] = '\0'; + rc = DosSetExtLIBPATH (buf, fVar); + if (rc != NO_ERROR) + { + OSSN (error, NILF, _("$(libpath): failed to set `%s' to `%s', rc=%d"), argv[0], buf, rc); + return variable_buffer_output (o, "", 0); + } + + o = variable_buffer_output (o, "", 0); + } + return o; +} +#endif /* CONFIG_WITH_OS2_LIBPATH */ + +#if defined (CONFIG_WITH_MAKE_STATS) || defined (CONFIG_WITH_MINIMAL_STATS) +/* Retrieve make statistics. */ +static char * +func_make_stats (char *o, char **argv, const char *funcname UNUSED) +{ + char buf[512]; + int len; + + if (!argv[0] || (!argv[0][0] && !argv[1])) + { +# ifdef CONFIG_WITH_MAKE_STATS + len = sprintf (buf, "alloc-cur: %5ld/%3ld %3luMB hash: %5lu %2lu%%", + make_stats_allocations, + make_stats_reallocations, + make_stats_allocated / (1024*1024), + make_stats_ht_lookups, + (make_stats_ht_collisions * 100) / make_stats_ht_lookups); + o = variable_buffer_output (o, buf, len); +#endif + } + else + { + /* selective */ + int i; + for (i = 0; argv[i]; i++) + { + unsigned long val; + if (i != 0) + o = variable_buffer_output (o, " ", 1); + if (0) + continue; +# ifdef CONFIG_WITH_MAKE_STATS + else if (!strcmp(argv[i], "allocations")) + val = make_stats_allocations; + else if (!strcmp(argv[i], "reallocations")) + val = make_stats_reallocations; + else if (!strcmp(argv[i], "allocated")) + val = make_stats_allocated; + else if (!strcmp(argv[i], "ht_lookups")) + val = make_stats_ht_lookups; + else if (!strcmp(argv[i], "ht_collisions")) + val = make_stats_ht_collisions; + else if (!strcmp(argv[i], "ht_collisions_pct")) + val = (make_stats_ht_collisions * 100) / make_stats_ht_lookups; +#endif + else + { + o = variable_buffer_output (o, argv[i], strlen (argv[i])); + continue; + } + + len = sprintf (buf, "%ld", val); + o = variable_buffer_output (o, buf, len); + } + } + + return o; +} +#endif /* CONFIG_WITH_MAKE_STATS */ + +#ifdef CONFIG_WITH_COMMANDS_FUNC +/* Gets all the commands for a target, separated by newlines. + + This is useful when creating and checking target dependencies since + it reduces the amount of work and the memory consuption. A new prefix + character '%' has been introduced for skipping certain lines, like + for instance the one calling this function and pushing to a dep file. + Blank lines are also skipped. + + The commands function takes exactly one argument, which is the name of + the target which commands should be returned. + + The commands-sc is identical to commands except that it uses a ';' to + separate the commands. + + The commands-usr is similar to commands except that it takes a 2nd + argument that is used to separate the commands. */ +char * +func_commands (char *o, char **argv, const char *funcname) +{ + struct file *file; + static int recursive = 0; + + if (recursive) + { + OS (error, reading_file, _("$(%s ) was invoked recursivly"), funcname); + return variable_buffer_output (o, "recursive", sizeof ("recursive") - 1); + } + if (*argv[0] == '\0') + { + OS (error, reading_file, _("$(%s ) was invoked with an empty target name"), funcname); + return o; + } + recursive = 1; + + file = lookup_file (argv[0]); + if (file && file->cmds) + { + unsigned int i; + int cmd_sep_len; + struct commands *cmds = file->cmds; + const char *cmd_sep; + + if (!strcmp (funcname, "commands")) + { + cmd_sep = "\n"; + cmd_sep_len = 1; + } + else if (!strcmp (funcname, "commands-sc")) + { + cmd_sep = ";"; + cmd_sep_len = 1; + } + else /*if (!strcmp (funcname, "commands-usr"))*/ + { + cmd_sep = argv[1]; + cmd_sep_len = strlen (cmd_sep); + } + + initialize_file_variables (file, 1 /* don't search for pattern vars */); + set_file_variables (file, 1 /* early call */); + chop_commands (cmds); + + for (i = 0; i < cmds->ncommand_lines; i++) + { + char *p; + char *in, *out, *ref; + + /* Skip it if it has a '%' prefix or is blank. */ + if (cmds->lines_flags[i] & COMMAND_GETTER_SKIP_IT) + continue; + p = cmds->command_lines[i]; + while (ISBLANK (*p)) + p++; + if (*p == '\0') + continue; + + /* --- copied from new_job() in job.c --- */ + + /* Collapse backslash-newline combinations that are inside variable + or function references. These are left alone by the parser so + that they will appear in the echoing of commands (where they look + nice); and collapsed by construct_command_argv when it tokenizes. + But letting them survive inside function invocations loses because + we don't want the functions to see them as part of the text. */ + + /* IN points to where in the line we are scanning. + OUT points to where in the line we are writing. + When we collapse a backslash-newline combination, + IN gets ahead of OUT. */ + + in = out = p; + while ((ref = strchr (in, '$')) != 0) + { + ++ref; /* Move past the $. */ + + if (out != in) + /* Copy the text between the end of the last chunk + we processed (where IN points) and the new chunk + we are about to process (where REF points). */ + memmove (out, in, ref - in); + + /* Move both pointers past the boring stuff. */ + out += ref - in; + in = ref; + + if (*ref == '(' || *ref == '{') + { + char openparen = *ref; + char closeparen = openparen == '(' ? ')' : '}'; + int count; + char *p2; + + *out++ = *in++; /* Copy OPENPAREN. */ + /* IN now points past the opening paren or brace. + Count parens or braces until it is matched. */ + count = 0; + while (*in != '\0') + { + if (*in == closeparen && --count < 0) + break; + else if (*in == '\\' && in[1] == '\n') + { + /* We have found a backslash-newline inside a + variable or function reference. Eat it and + any following whitespace. */ + + int quoted = 0; + for (p2 = in - 1; p2 > ref && *p2 == '\\'; --p2) + quoted = !quoted; + + if (quoted) + /* There were two or more backslashes, so this is + not really a continuation line. We don't collapse + the quoting backslashes here as is done in + collapse_continuations, because the line will + be collapsed again after expansion. */ + *out++ = *in++; + else + { + /* Skip the backslash, newline and + any following whitespace. */ + in = next_token (in + 2); + + /* Discard any preceding whitespace that has + already been written to the output. */ + while (out > ref + && ISBLANK (out[-1])) + --out; + + /* Replace it all with a single space. */ + *out++ = ' '; + } + } + else + { + if (*in == openparen) + ++count; + + *out++ = *in++; + } + } + } + /* Some of these can be amended ($< perhaps), but we're likely to be called while the + dep expansion happens, so it would have to be on a hackish basis. sad... */ + else if (*ref == '<' || *ref == '*' || *ref == '%' || *ref == '^' || *ref == '+') + OSN (error, reading_file, _("$(%s ) does not work reliably with $%c in all cases"), funcname, *ref); + } + + /* There are no more references in this line to worry about. + Copy the remaining uninteresting text to the output. */ + if (out != in) + strcpy (out, in); + + /* --- copied from new_job() in job.c --- */ + + /* Finally, expand the line. */ + if (i) + o = variable_buffer_output (o, cmd_sep, cmd_sep_len); + o = variable_expand_for_file_2 (o, cmds->command_lines[i], ~0U, file, NULL); + + /* Skip it if it has a '%' prefix or is blank. */ + p = o; + while (ISBLANK (*o) + || *o == '@' + || *o == '-' + || *o == '+') + o++; + if (*o != '\0' && *o != '%') + o = strchr (o, '\0'); + else if (i) + o = p - cmd_sep_len; + else + o = p; + } /* for each command line */ + } + /* else FIXME: bitch about it? */ + + recursive = 0; + return o; +} +#endif /* CONFIG_WITH_COMMANDS_FUNC */ +#ifdef KMK + +/* Useful when debugging kmk and/or makefiles. */ +char * +func_breakpoint (char *o, char **argv UNUSED, const char *funcname UNUSED) +{ +#ifdef _MSC_VER + __debugbreak(); +#elif defined(__i386__) || defined(__x86__) || defined(__X86__) || defined(_M_IX86) || defined(__i386) \ + || defined(__amd64__) || defined(__x86_64__) || defined(__AMD64__) || defined(_M_X64) || defined(__amd64) +# ifdef __sun__ + __asm__ __volatile__ ("int $3\n\t"); +# else + __asm__ __volatile__ ("int3\n\t"); +# endif +#else + char *p = (char *)0; + *p = '\0'; +#endif + return o; +} + +/* umask | umask -S. */ +char * +func_get_umask (char *o, char **argv UNUSED, const char *funcname UNUSED) +{ + char sz[80]; + int off; + mode_t u; + int symbolic = 0; + const char *psz = argv[0]; + + if (psz) + { + const char *pszEnd = strchr (psz, '\0'); + strip_whitespace (&psz, &pszEnd); + + if (pszEnd != psz) + { + if ( STR_N_EQUALS (psz, pszEnd - pszEnd, "S") + || STR_N_EQUALS (psz, pszEnd - pszEnd, "-S") + || STR_N_EQUALS (psz, pszEnd - pszEnd, "symbolic") ) + symbolic = 1; + else + OSS (error, reading_file, _("$(%s ) invalid argument `%s'"), + funcname, argv[0]); + } + } + + u = g_fUMask; + assert (u == umask (g_fUMask)); + + if (symbolic) + { + off = 0; + sz[off++] = 'u'; + sz[off++] = '='; + if ((u & S_IRUSR) == 0) + sz[off++] = 'r'; + if ((u & S_IWUSR) == 0) + sz[off++] = 'w'; + if ((u & S_IXUSR) == 0) + sz[off++] = 'x'; + sz[off++] = ','; + sz[off++] = 'g'; + sz[off++] = '='; + if ((u & S_IRGRP) == 0) + sz[off++] = 'r'; + if ((u & S_IWGRP) == 0) + sz[off++] = 'w'; + if ((u & S_IXGRP) == 0) + sz[off++] = 'x'; + sz[off++] = ','; + sz[off++] = 'o'; + sz[off++] = '='; + if ((u & S_IROTH) == 0) + sz[off++] = 'r'; + if ((u & S_IWOTH) == 0) + sz[off++] = 'w'; + if ((u & S_IXOTH) == 0) + sz[off++] = 'x'; + } + else + off = sprintf (sz, "%.4o", u); + + return variable_buffer_output (o, sz, off); +} + + +/* umask 0002 | umask u=rwx,g=rwx,o=rx. */ +char * +func_set_umask (char *o, char **argv UNUSED, const char *funcname UNUSED) +{ + mode_t u; + const char *psz; + + /* Figure what kind of input this is. */ + psz = argv[0]; + while (ISBLANK (*psz)) + psz++; + + if (isdigit ((unsigned char)*psz)) + { + u = 0; + while (*psz) + { + u <<= 3; + if (*psz < '0' || *psz >= '8') + { + OSS (error, reading_file, _("$(%s ) illegal number `%s'"), funcname, argv[0]); + break; + } + u += *psz - '0'; + psz++; + } + + if (argv[1] != NULL) + OS (error, reading_file, _("$(%s ) too many arguments for octal mode"), funcname); + + umask (u); + g_fUMask = umask (u); /* Must get it again as windows modifies it. */ + } + else + { + OS (error, reading_file, _("$(%s ) symbol mode is not implemented"), funcname); + } + + return o; +} + + +/* Controls the cache in dir-bird-nt.c. */ + +char * +func_dircache_ctl (char *o, char **argv UNUSED, const char *funcname UNUSED) +{ +# ifdef KBUILD_OS_WINDOWS + const char *cmd = argv[0]; + while (ISBLANK (*cmd)) + cmd++; + if (strcmp (cmd, "invalidate") == 0) + { + if (argv[1] != NULL) + O (error, reading_file, "$(dircache-ctl invalidate) takes no parameters"); + dir_cache_invalid_all (); + } + else if (strcmp (cmd, "invalidate-and-close-dirs") == 0) + { + if (argv[1] != NULL) + O (error, reading_file, "$(dircache-ctl invalidate) takes no parameters"); + dir_cache_invalid_all_and_close_dirs (0 /*including_root*/); + } + else if (strcmp (cmd, "invalidate-missing") == 0) + { + if (argv[1] != NULL) + O (error, reading_file, "$(dircache-ctl invalidate-missing) takes no parameters"); + dir_cache_invalid_missing (); + } + else if (strcmp (cmd, "volatile") == 0) + { + size_t i; + for (i = 1; argv[i] != NULL; i++) + { + const char *dir = argv[i]; + while (ISBLANK (*dir)) + dir++; + if (*dir) + dir_cache_volatile_dir (dir); + } + } + else if (strcmp (cmd, "deleted") == 0) + { + size_t i; + for (i = 1; argv[i] != NULL; i++) + { + const char *dir = argv[i]; + while (ISBLANK (*dir)) + dir++; + if (*dir) + dir_cache_deleted_directory (dir); + } + } + else + OS (error, reading_file, "Unknown $(dircache-ctl ) command: '%s'", cmd); +# endif + return o; +} + +#endif /* KMK */ +#if defined (KMK) || defined (CONFIG_WITH_LAZY_DEPS_VARS) + +/* Helper for performer GNU make style quoting of one filename. */ + +static char * +helper_quote_make (char *o, const char *name, size_t len, int is_dep, + int is_tgt, int quote_trailing_slashes, + const char *funcname) +{ + unsigned const map_flags = MAP_BLANK + | MAP_NEWLINE + | MAP_COMMENT + | MAP_VARIABLE + | MAP_SEMI + | MAP_EQUALS + | (is_dep ? MAP_PIPE : + is_tgt ? MAP_COLON : 0); + const char *cur = name; + assert (memchr (name, '\0', len) == NULL); + if (len > 0) + { + const char * const end = &name[len]; + unsigned long len_out = 0; + const char *prev = cur; + do + { + char ch = *cur; + unsigned int flags = stopchar_map[(unsigned int)ch] & map_flags; + if (!flags) + cur++; /* likely */ + else + { + /* Flush pending output. */ + if (prev != cur) + { + o = variable_buffer_output (o, prev, cur - prev); + len_out += cur - prev; + } + + /* Dollar is quoted by duplicating the dollar: */ + if (flags & MAP_VARIABLE) + { + o = variable_buffer_output (o, "$", 1); + prev = cur++; + } + /* The rest is quoted by '\': */ + else + { + size_t const max_slashes = cur - prev; + size_t slashes = 0; + while (slashes < max_slashes && cur[1 - slashes] == '\\') + slashes++; + if (slashes) + { + o = variable_buffer_output (o, &cur[0 - slashes], slashes); + len_out += slashes; + } + o = variable_buffer_output (o, "\\", 1); + prev = cur++; + } + } + } + while ((uintptr_t)cur < (uintptr_t)end); + + /* Flush pending output. */ + if (prev != cur) + { + o = variable_buffer_output (o, prev, cur - prev); + len_out += cur - prev; + } + + /* Escape trailing slashes when needed. */ + if ( o[-1] == '\\' + && quote_trailing_slashes) + { + size_t slashes = 1; + while (slashes < len_out && o[-1 - slashes] == '\\') + slashes++; + while (slashes-- > 0) + o = variable_buffer_output (o, "\\", 1); + } + } + else + OS (error, reading_file, "%s: cannot quote empty string", funcname); + return o; +} + +# ifdef KMK + +/* Helper for func_quote_make that checks if there are more arguments + that produces output or not. */ + +static int func_quote_make_has_more_non_empty_args (char **argv) +{ + for (;;) + { + char *arg = *argv; + if (!arg) + return 0; + if (*arg) + return 1; + argv++; + } +} + +/* Takes zero or more plain strings and escapes (quotes) spaces and other + problematic characters, GNU make style. + + There is one slightly problematic aspect of using this, if the input ends + with backslashes whether or not they will be reduced or taken as-is depends + on whether they appear at the end of a line or not. They are taken as-is + when at the end of a line, otherwise they'll be subject to unescaping + (unquoting) and reduced by half. + + In addition, the quoting style differs for files on the left side and + right side of the recipe colon. Colons aren't escaped are only escaped + on the left side (target), and the pipe character is only escaped on the + right side (deps). + + For this reason there are four variants of this function. */ + +static char *func_quote_make (char *o, char **argv, const char *funcname) +{ + int const is_dep = funcname[5] == '-' && funcname[6] == 'd'; + int const is_tgt = funcname[5] == '-' && funcname[6] == 't'; + int const quote_trailing_slashes = funcname[5] == '\0' || funcname[9] == '\0'; + char * const o_initial = o; + int i; + + assert ( quote_trailing_slashes + == (!strcmp (funcname, "quote") || !strcmp (funcname, "quote-dep") || !strcmp (funcname, "quote-tgt"))); + assert (is_dep == !strncmp (funcname, "quote-dep", sizeof ("quote-dep") - 1)); + assert (is_tgt == !strncmp (funcname, "quote-tgt", sizeof ("quote-tgt") - 1)); + + for (i = 0; argv[i]; i++) + { + char *arg = argv[i]; + if (*arg) + { + /* Add space separator. */ + if (o != o_initial) + o = variable_buffer_output (o, " ", 1); + + /* Output the quoted argument: */ + if (quote_trailing_slashes) + o = helper_quote_make (o, arg, strlen (arg), is_dep, is_tgt, + quote_trailing_slashes, funcname); + else + { + char *end = strchr (arg, '\0'); + int qts = end != arg && end[-1] == '\\' + && func_quote_make_has_more_non_empty_args (&argv[i + 1]); + o = helper_quote_make (o, arg, strlen (arg), is_dep, is_tgt, + qts, funcname); + } + } + else + OS (error, reading_file, "%s: cannot munge empty string", funcname); + } + + return o; +} + +# endif /* KMK */ + +/* Worker for func_quote_shell() for escaping a string that's inside + double quotes. */ + +static char *func_escape_shell_in_dq (char *o, const char *arg, size_t len) +{ + const char *prev = arg; + while (len-- > 0) + { + char const ch = *arg; + switch (ch) + { + default: + arg++; + break; + case '!': + case '$': + case '`': + case '"': + case '\\': + case '\n': + if (prev != arg) + o = variable_buffer_output (o, prev, arg - prev); + o = variable_buffer_output (o, "\\", 1); + prev = arg; + arg++; + break; + } + } + if (prev != arg) + o = variable_buffer_output (o, prev, arg - prev); + return o; +} + +# ifdef KMK +/* quote-sh-dq */ + +static char *func_quote_shell_dq (char *o, char **argv, const char *funcname UNUSED) +{ + return func_escape_shell_in_dq (o, argv[0], strlen (argv[0])); +} +# endif + +/* Worker for func_quote_shell() for escaping a string that's inside + single quotes. */ + +static char *func_escape_shell_in_sq (char *o, const char *arg, size_t len) +{ + while (len > 0) + { + char *sq = memchr (arg, '\'', len); + if (!sq) + return variable_buffer_output (o, arg, len); + if (sq != arg) + o = variable_buffer_output (o, arg, sq - arg); + o = variable_buffer_output (o, "'\\''", 4); + + /* advance */ + sq++; + len -= sq - arg; + arg = sq; + } + return o; +} + +# ifdef KMK +/* quote-sh-dq */ + +static char *func_quote_shell_sq (char *o, char **argv, const char *funcname UNUSED) +{ + return func_escape_shell_in_sq (o, argv[0], strlen (argv[0])); +} +#endif + +/* Output a shell argument with quoting as needed. */ +static char *helper_quote_shell (char *o, const char *arg, size_t len, + int leading_space) +{ + if ( memchr (arg, '$', len) != NULL + || memchr (arg, '*', len) != NULL + || memchr (arg, '?', len) != NULL + || memchr (arg, '[', len) != NULL) + { + if (leading_space) + o = variable_buffer_output (o, " '", 2); + else + o = variable_buffer_output (o, "'", 1); + o = func_escape_shell_in_sq (o, arg, len); + o = variable_buffer_output (o, "'", 1); + } + else if ( memchr (arg, ' ', len) != NULL + || memchr (arg, '\t', len) != NULL + || memchr (arg, '\\', len) != NULL + || memchr (arg, '"', len) != NULL + || memchr (arg, '`', len) != NULL + || memchr (arg, '!', len) != NULL + || memchr (arg, '|', len) != NULL + || memchr (arg, '<', len) != NULL + || memchr (arg, '>', len) != NULL + || memchr (arg, '&', len) != NULL + || memchr (arg, ';', len) != NULL + || memchr (arg, '(', len) != NULL + || memchr (arg, ')', len) != NULL + || memchr (arg, '\n', len) != NULL) + { + if (leading_space) + o = variable_buffer_output (o, " \"", 2); + else + o = variable_buffer_output (o, "\"", 1); + o = func_escape_shell_in_dq (o, arg, len); + o = variable_buffer_output (o, "\"", 1); + } + else + { + if (leading_space) + o = variable_buffer_output (o, " ", 1); + o = variable_buffer_output (o, arg, len); + } + return o; +} + +# ifdef KMK + +/* Takes zero or more plain strings and escapes/quotes spaces and other + problematic characters, bourne make style. + + The quote-sh-dq and quote-sh-sq variants is for escaping strings that + going to be put into double quotes and single quotes respectively. + + The normal quote-sh variant assumes it's free to do open and close + quotes as it pleases. */ + +static char *func_quote_shell (char *o, char **argv, const char *funcname UNUSED) +{ + int i; + for (i = 0; argv[i]; i++) + o = helper_quote_shell (o, argv[i], strlen (argv[i]), + i > 0 /*need_leading_space*/); + return o; +} + +/* Unlinks CUR from *CHAINP and frees it, returning the next element. */ + +static struct nameseq * +helper_unlink_and_free_ns (struct nameseq *cur, struct nameseq *prev, + struct nameseq **chainp) +{ + struct nameseq *freeit = cur; \ + free ((char *)cur->name); + if (prev) + prev->next = cur = cur->next; + else + *chainp = cur = cur->next; + free_ns (freeit); + return cur; +} + +/* Frees a chain returned by helper_parse_file_list. */ + +static void free_ns_chain_no_strcache (struct nameseq *ns) +{ + while (ns != 0) + { + struct nameseq *t = ns; + free ((char *)ns->name); + ns = ns->next; + free_ns (t); + } +} + +# endif /* KMK */ + +/* Decoded style options for the $(q* ) and $(*file* ) functions. */ +#define Q_RET_MASK 0x000f +#define Q_RET_QUOTED 0x0000 +#define Q_RET_QUOTED_DEP 0x0001 +#define Q_RET_QUOTED_DEP_END 0x0002 +#define Q_RET_QUOTED_TGT 0x0003 +#define Q_RET_QUOTED_TGT_END 0x0004 +#define Q_RET_UNQUOTED 0x0005 +#define Q_RET_SHELL 0x0006 +#define Q_RET_SHELL_IN_DQ 0x0007 +#define Q_RET_SHELL_IN_SQ 0x0008 + +#define Q_IN_MASK 0x0070 +#define Q_IN_QUOTED 0x0000 +#define Q_IN_UNQUOTED 0x0010 +#define Q_IN_QUOTED_DEP 0x0020 /** @todo needed? */ +#define Q_IN_QUOTED_TGT 0x0030 /** @todo needed? */ +#define Q_IN_UNQUOTED_SINGLE 0x0040 +#define Q_IN_SEP_COMMA 0x0080 /* for VMS hacks, file lists only */ + +#define Q_SEP_MASK 0x0700 +#define Q_SEP_SHIFT 8 +#define Q_SEP_SPACE 0x0000 +#define Q_SEP_TAB 0x0100 +#define Q_SEP_NL 0x0200 +#define Q_SEP_NL_TAB 0x0300 +#define Q_SEP_COMMA 0x0400 /* for VMS, output only */ + +#define Q_QDEFAULT (Q_IN_QUOTED | Q_RET_QUOTED | Q_SEP_SPACE) +#ifndef VMS +# define Q_QDEFAULT_VMS_TRICKS Q_QDEFAULT +#else /* VMS: Treat ',' as file separators in input, maybe output too. */ +# define Q_QDEFAULT_VMS_TRICKS (Q_IN_SEP_COMMA | \ + (!vms_comma_separator ? Q_QDEFAULT \ + : (Q_QDEFAULT & ~Q_SEP_MASK) | Q_SEP_COMMA) +#endif + +# ifdef KMK +/* Decodes the optional style argument. This is chiefly for the return + style, but can also pick the input and space styles (just because we can). */ + +static unsigned int helper_file_quoting_style (char *style, unsigned int intstyle) +{ + if (style != NULL) + { + for (;;) + { + /* Skip blanks: */ + while (ISBLANK (*style)) + style++; + if (*style != '\0') + { + /* Find the end of the current word: */ + char * const start = style; + size_t len; + char ch; + while (!ISBLANK ((ch = *style)) && ch != '\0') + style++; + len = style - start; + + /* String "switch" to decode the word: */ + +#define MATCH(a_str) (len == sizeof (a_str) - 1 && memcmp (start, a_str, sizeof (a_str)) == 0) + /* return styles: */ + if (MATCH ("quoted") || MATCH ("q")) + intstyle = (intstyle & ~Q_RET_MASK) | Q_RET_QUOTED; + else if (MATCH ("unquoted") || MATCH ("unq") || MATCH ("u")) + intstyle = (intstyle & ~Q_RET_MASK) | Q_RET_UNQUOTED; + else if (MATCH ("quoted-dep") || MATCH ("q-dep") || MATCH ("q-d")) + intstyle = (intstyle & ~Q_RET_MASK) | Q_RET_QUOTED_DEP; + else if (MATCH ("quoted-dep-end") || MATCH ("q-dep-end") || MATCH ("q-d-e")) + intstyle = (intstyle & ~Q_RET_MASK) | Q_RET_QUOTED_DEP_END; + else if (MATCH ("quoted-tgt") || MATCH ("q-tgt") || MATCH ("q-t")) + intstyle = (intstyle & ~Q_RET_MASK) | Q_RET_QUOTED_TGT; + else if (MATCH ("quoted-tgt-end") || MATCH ("q-tgt-end") || MATCH ("q-t-e")) + intstyle = (intstyle & ~Q_RET_MASK) | Q_RET_QUOTED_TGT_END; + else if (MATCH ("shell") || MATCH ("sh")) + intstyle = (intstyle & ~Q_RET_MASK) | Q_RET_SHELL; + else if (MATCH ("shell-in-dq") || MATCH ("sh-i-d")) + intstyle = (intstyle & ~Q_RET_MASK) | Q_RET_SHELL_IN_DQ; /* returned string is used inside double shell quotes */ + else if (MATCH ("shell-in-sq") || MATCH ("sh-i-s")) + intstyle = (intstyle & ~Q_RET_MASK) | Q_RET_SHELL_IN_SQ; /* returned string is used inside single shell quotes */ + /* input styles: */ + else if (MATCH ("in-quoted") || MATCH ("i-q")) + intstyle = (intstyle & ~Q_IN_MASK) | Q_IN_QUOTED; + else if (MATCH ("in-unquoted") || MATCH ("i-unq") || MATCH ("i-u")) + intstyle = (intstyle & ~Q_IN_MASK) | Q_IN_UNQUOTED; + else if (MATCH ("in-unquoted-single")|| MATCH ("i-unq-s") || MATCH ("i-u-s") + || MATCH ("in-unquoted-file") || MATCH ("i-unq-f") || MATCH ("i-u-f")) + intstyle = (intstyle & ~Q_IN_MASK) | Q_IN_UNQUOTED_SINGLE; + else if (MATCH ("in-quoted-dep") || MATCH ("i-q-dep") || MATCH ("i-q-d")) + intstyle = (intstyle & ~Q_IN_MASK) | Q_IN_QUOTED_DEP; + else if (MATCH ("in-quoted-tgt") || MATCH ("i-q-tgt") || MATCH ("i-q-t")) + intstyle = (intstyle & ~Q_IN_MASK) | Q_IN_QUOTED_TGT; + else if (MATCH ("in-sep-comma") || MATCH ("i-s-com") || MATCH ("i-s-c")) + intstyle |= Q_IN_SEP_COMMA; /*?*/ + /* separator styles (output): */ + else if (MATCH ("sep-space") || MATCH ("s-space") || MATCH ("s-s")) + intstyle = (intstyle & ~Q_SEP_MASK) | Q_SEP_SPACE; + else if (MATCH ("sep-tab") || MATCH ("s-tab") || MATCH ("s-t")) + intstyle = (intstyle & ~Q_SEP_MASK) | Q_SEP_TAB; + else if (MATCH ("sep-nl") || MATCH ("s-nl") || MATCH ("s-n")) + intstyle = (intstyle & ~Q_SEP_MASK) | Q_SEP_NL; + else if (MATCH ("sep-nl-tab") || MATCH ("s-nl-tab") || MATCH ("s-n-t")) + intstyle = (intstyle & ~Q_SEP_MASK) | Q_SEP_NL_TAB; + else if (MATCH ("sep-comma") || MATCH ("s-comma") || MATCH ("s-c")) + intstyle = (intstyle & ~Q_SEP_MASK) | Q_SEP_COMMA; + else + { + char savedch = *style; + *style = '\0'; + OS (error, reading_file, "Unknown file style: %s", start); + *style = savedch; + } +#undef MATCH + } + else + break; + } + } + return intstyle; +} +# endif /* KMK */ + +/* Output (returns) a separator according to STYLE. */ + +static char *helper_return_sep (char *o, unsigned int style) +{ + /* Note! Must match Q_SEP_MASK! */ + static struct + { + const char *sep; + size_t len; + } const seps[8] = + { + { " ", 1 }, + { "\t", 1 }, + { "\n", 1 }, + { "\n\t", 2 }, + { ",", 1 }, + { " ", 1 }, + { " ", 1 }, + { " ", 1 } + }; + + return variable_buffer_output(o, + seps[(style & Q_SEP_MASK) >> Q_SEP_SHIFT].sep, + seps[(style & Q_SEP_MASK) >> Q_SEP_SHIFT].len); +} + +/* Removes one separator from the output. + +This is typically used when outputting lists where there is no simple way of +telling whether an entry is the last one or not. So, is_last is always false +and the last separator is removed afterwards by this function. */ + +MY_INLINE char *helper_drop_separator (char *o, unsigned int style) +{ + o -= (style & Q_SEP_MASK) != Q_SEP_NL_TAB ? 1 : 2; + *o = '\0'; /* not strictly necessary */ + return o; +} + + +/* Outputs (returns) the given file. */ + +static char *helper_return_file_len (char *o, const char *file, size_t len, + unsigned int style, int is_last) +{ + assert (memchr (file, '\0', len) == NULL); + switch (style & Q_RET_MASK) + { + case Q_RET_UNQUOTED: + o = variable_buffer_output (o, file, len); + break; + case Q_RET_QUOTED: + o = helper_quote_make (o, file, len, 0 /*is_dep*/, 0 /*is_tgt*/, + !is_last /*quote_trailing_slashes*/, NULL); + break; + case Q_RET_QUOTED_DEP: + o = helper_quote_make (o, file, len, 1 /*is_dep*/, 0 /*is_tgt*/, + !is_last /*quote_trailing_slashes*/, NULL); + break; + case Q_RET_QUOTED_DEP_END: + o = helper_quote_make (o, file, len, 1 /*is_dep*/, 0 /*is_tgt*/, + 0 /*quote_trailing_slashes*/, NULL); + break; + case Q_RET_QUOTED_TGT: + o = helper_quote_make (o, file, len, 0 /*is_dep*/, 1 /*is_tgt*/, + !is_last /*quote_trailing_slashes*/, NULL); + break; + case Q_RET_QUOTED_TGT_END: + o = helper_quote_make (o, file, len, 0 /*is_dep*/, 1 /*is_tgt*/, + 0 /*quote_trailing_slashes*/, NULL); + break; + case Q_RET_SHELL: + o = helper_quote_shell (o, file, len, 0 /*need_leading_space*/); + break; + case Q_RET_SHELL_IN_DQ: + o = func_escape_shell_in_dq (o, file, len); + break; + case Q_RET_SHELL_IN_SQ: + o = func_escape_shell_in_sq (o, file, len); + break; + default: + assert (0); + } + + /* Add separator space if not last. */ + if (!is_last) + o = helper_return_sep (o, style); + return o; +} + +/* Outputs (returns) the given file. */ + +static char *helper_return_file (char *o, const char *file, + unsigned int style, int is_last) +{ + return helper_return_file_len (o,file, strlen (file), style, is_last); +} + +#endif /* KMK || CONFIG_WITH_LAZY_DEPS_VARS */ +#ifdef KMK + +/* Outputs (returns) the given CHAIN and frees it. */ + +static char *helper_return_and_free_chain (char *o, struct nameseq *chain, unsigned int style) +{ + struct nameseq *cur; + for (cur = chain; cur; cur = cur->next) + o = helper_return_file (o, cur->name, style, cur->next == NULL); + free_ns_chain_no_strcache (chain); + return o; +} + + +/* Helper for helper_parse_file_list that globs a name sequence. */ +static struct nameseq * +helper_glob_chain (struct nameseq *chain) +{ + struct nameseq *prev = NULL; + struct nameseq *cur = chain; + glob_t gl; + dir_setup_glob (&gl); + + /** @todo XXX: !NO_ARCHIVES */ + while (cur) + { + switch (glob (cur->name, GLOB_NOSORT | GLOB_ALTDIRFUNC, NULL, &gl)) + { + case 0: /* Replace CUR with the names found. */ + { + struct nameseq *subchain = NULL; + struct nameseq **ppnext = &subchain; + const char ** const names = (const char **)gl.gl_pathv; + size_t const num_names = gl.gl_pathc; + size_t idx; + + cur = helper_unlink_and_free_ns (cur, prev, &chain); + + for (idx = 0; idx < num_names; idx++) + { +#ifndef CONFIG_WITH_ALLOC_CACHES + struct nameseq *newp = xcalloc (sizeof (*newp)); +#else + struct nameseq *newp = alloccache_calloc (&nameseq_cache); +#endif + newp->name = xstrdup (names[idx]); + newp->next = NULL; + *ppnext = newp; + ppnext = &newp->next; + } + + if (subchain) /* parnaoia */ + { + *ppnext = cur; + if (prev) + prev->next = subchain; + else + chain = subchain; + } + break; + } + + case GLOB_NOMATCH: /* doesn't exist, remove */ + cur = helper_unlink_and_free_ns (cur, prev, &chain); + break; + + default: /* Keep it. */ + prev = cur; + cur = cur->next; + break; + + case GLOB_NOSPACE: + OUT_OF_MEM(); + } + globfree (&gl); + } + return chain; +} + +/* Parses a file/word list according to STYLE and returns a name list. + + The FILELIST parameter may be modified! + + The GLOB param is for wildcard/qwildcard. + + TODO/XXX: Unquote and split up the FILELIST directly. All function + arguments are heap copies already, so we are free to modify + them. Would be nice to ditch the nameseq in favor of something + which also includes the length. */ + +static struct nameseq * +helper_parse_file_list (char *filelist, unsigned int style, int glob) +{ + if (filelist && *filelist != '\0') + { + /* Q_IN_SEP_COMMA: VMS tricks for qbasename, qdir, qnotdir and qsuffix + where commas are treated as separtors in FILELIST. We simply + replace commas in the FILELIST before doing the regular parsing. */ + if (!(style & Q_IN_SEP_COMMA)) + { /* typical */ } + else + { + size_t len = strlen (filelist); + char *start = filelist; + char *comma = (char *)memchr (filelist, ',', len); + while (comma) + { + *comma = ' '; + len -= comma - start - 1; + if (len) + { + start = comma + 1; + comma = (char *)memchr (start, ',', len); + } + else + break; + } + } + + switch (style & Q_IN_MASK) + { + case Q_IN_QUOTED: + case Q_IN_QUOTED_DEP: /** @todo ?? */ + case Q_IN_QUOTED_TGT: /** @todo ?? */ + return PARSE_FILE_SEQ(&filelist, struct nameseq, MAP_NUL, NULL, + !glob + ? PARSEFS_NOGLOB | PARSEFS_NOSTRIP | PARSEFS_NOCACHE + : PARSEFS_NOSTRIP | PARSEFS_NOCACHE | PARSEFS_EXISTS); + + case Q_IN_UNQUOTED: + { + struct nameseq *chain = NULL; + struct nameseq **ppnext = &chain; + const char *it = filelist; + const char *cur; + unsigned int curlen; + while ((cur = find_next_token (&it, &curlen)) != NULL) + { +#ifndef CONFIG_WITH_ALLOC_CACHES + struct nameseq *newp = xcalloc (sizeof (*newp)); +#else + struct nameseq *newp = alloccache_calloc (&nameseq_cache); +#endif + newp->name = xstrndup (cur, curlen); + newp->next = NULL; + *ppnext = newp; + ppnext = &newp->next; + } + if (!glob) + return chain; + return helper_glob_chain (chain); + } + + case Q_IN_UNQUOTED_SINGLE: + if (*filelist != '\0') + { +#ifndef CONFIG_WITH_ALLOC_CACHES + struct nameseq *newp = xcalloc (sizeof (*newp)); +#else + struct nameseq *newp = alloccache_calloc (&nameseq_cache); +#endif + newp->name = xstrdup (filelist); + newp->next = NULL; + if (!glob) + return newp; + return helper_glob_chain (newp); + } + return NULL; + + default: + assert (0); + return NULL; + } + } + return NULL; +} + +/* $(requote style, file1 file2 ... fileN). */ + +static char *func_requote (char *o, char **argv, const char *funcname UNUSED) +{ + unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT); + struct nameseq *chain = helper_parse_file_list (argv[1], style, 0); + return helper_return_and_free_chain (o, chain, style); +} + +/* Common worker for func_firstfile() and func_qfirstfile(). */ + +static char *common_firstfile (char *o, char **argv, unsigned int style) +{ + struct nameseq *chain = helper_parse_file_list (argv[0], style, 0); + if (chain) + { + o = helper_return_file (o, chain->name, style, 1); + free_ns_chain_no_strcache (chain); + } + return o; +} + +/* $(firstfile file1 file2 ... fileN) - same as $(firstfile ), except + for files rather than word tokens. See func_firstword(). */ + +static char *func_firstfile (char *o, char **argv, const char *funcname UNUSED) +{ + return common_firstfile(o, argv, Q_IN_QUOTED | Q_RET_QUOTED | Q_SEP_SPACE); +} + +/* $(qfirstfile style, file1 file2 ... fileN) - same as $(firstfile ), except + for files rather than word tokens. See func_firstword(). */ + +static char *func_q_firstfile (char *o, char **argv, const char *funcname UNUSED) +{ + unsigned int style = helper_file_quoting_style (argv[0], Q_QDEFAULT); + return common_firstfile(o, &argv[1], style); +} + +/* Common worker for func_lastfile() and func_q_lastfile(). */ + +static char *common_lastfile (char *o, char **argv, unsigned int style) +{ + struct nameseq *chain = helper_parse_file_list (argv[0], style, 0); + if (chain) + { + struct nameseq *last = chain; + while (last->next) + last = last->next; + o = helper_return_file (o, last->name, style, 1); + free_ns_chain_no_strcache (chain); + } + return o; +} + +/* $(lastfile file1 file2 ... fileN) - same as $(lastfile ), except + for files rather than word tokens. See func_lastword(). */ + +static char *func_lastfile (char *o, char **argv, const char *funcname UNUSED) +{ + return common_lastfile (o, argv, Q_IN_QUOTED | Q_RET_QUOTED | Q_SEP_SPACE); +} + +/* $(qlastfile style, file1 file2 ... fileN) - same as $(lastfile ), except + for files rather than word tokens. See func_lastword(). */ + +static char *func_q_lastfile (char *o, char **argv, const char *funcname UNUSED) +{ + unsigned int style = helper_file_quoting_style (argv[0], Q_QDEFAULT); + return common_lastfile (o, &argv[1], style); +} + +/* Common worker for func_filelist() and func_q_filelist(). */ + +static char *common_filelist (char *o, char **argv, unsigned int style) +{ + int start; + int count; + + /* Check the arguments. */ + check_numeric (argv[0], + _("non-numeric first argument to 'filelist' function")); + check_numeric (argv[1], + _("non-numeric second argument to 'filelist' function")); + + start = atoi (argv[0]); + if (start < 1) + ON (fatal, *expanding_var, + "invalid first argument to 'filelist' function: '%d'", start); + start--; /* make zero based */ + + count = atoi (argv[1]) - start; + + if (count > 0) + { + char *line = argv[2]; + struct nameseq *cur; + struct nameseq *chain; + chain = helper_parse_file_list (line, style, 0); + + /* Find the beginning of the "start"th word (1-based). */ + for (cur = chain; cur && start > 1; cur = cur->next) + start--; + + /* Output the requested count */ + while (cur && count-- > 0) + o = helper_return_file (o, cur->name, style, count > 0 && cur->next); + + free_ns_chain_no_strcache (chain); + } + + return o; +} + +/* $(filelist start, end, file1..fileN) - same as $(wordlist), + except for files rather than word tokens. See func_wordlist(). */ + +static char *func_filelist (char *o, char **argv, const char *funcname UNUSED) +{ + return common_filelist (o, argv, Q_IN_QUOTED | Q_RET_QUOTED | Q_SEP_SPACE); +} + +/* $(qfilelist style, start, end, file1..fileN) - same as $(wordlist), + except for files rather than word tokens. See func_wordlist(). */ + +static char *func_q_filelist (char *o, char **argv, const char *funcname UNUSED) +{ + unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT); + return common_filelist (o, &argv[1], style); +} + +/* Common worker for func_countfiles() and func_q_countfiles(). */ + +static char *common_countfiles (char *o, char **argv, unsigned int style) +{ + struct nameseq *chain = helper_parse_file_list (argv[0], style, 0); + struct nameseq *cur; + unsigned int files = 0; + char retval[32]; + + for (cur = chain; cur; cur = cur->next) + files++; + free_ns_chain_no_strcache (chain); + + return variable_buffer_output (o, retval, sprintf (retval, "%u", files)); +} + +/* $(countfiles file1 file2 ... fileN) - same as $(words ), except for + files rather than word tokens. See func_words(). */ + +static char *func_countfiles (char *o, char **argv, const char *funcname UNUSED) +{ + return common_countfiles (o, argv, Q_IN_QUOTED | Q_RET_QUOTED | Q_SEP_SPACE); +} + +/* $(qcountfiles style, file1 file2 ... fileN) - same as $(words ), except for + files rather than word tokens and the STYLE argument. See func_words(). */ + +static char *func_q_countfiles (char *o, char **argv, const char *funcname UNUSED) +{ + unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT); + return common_countfiles (o, &argv[1], style); +} + +/* Helper that sets the variable value. */ + +static void +helper_set_var_value (struct variable *var, const char *value, size_t len) +{ +#ifndef CONFIG_WITH_VALUE_LENGTH + free (var->value); + var->value = xstrndup (value, len); +#else + if (len >= var->value_alloc_len) + { +# ifdef CONFIG_WITH_RDONLY_VARIABLE_VALUE + if (var->rdonly_val) + var->rdonly_val = 0; + else +# endif + free (var->value); + var->value_alloc_len = VAR_ALIGN_VALUE_ALLOC (len + 1); + var->value = xmalloc (var->value_alloc_len); + } + memcpy (var->value, value, len); + var->value[len] = '\0'; + var->value_length = len; + VARIABLE_CHANGED (var); +#endif +} + +/* Common worker for func_foreachfile and func_qforeachfile. */ + +static char * +common_foreachfile (char *o, char **argv, unsigned int style) +{ + /* expand only the first two. */ + char *varname = expand_argument (argv[0], NULL); + char *list = expand_argument (argv[1], NULL); + const char *body = argv[2]; +#ifdef CONFIG_WITH_VALUE_LENGTH + long body_len = strlen (body); +#endif + + struct nameseq *chain = helper_parse_file_list (list, style, 0); + struct nameseq *cur; + + /* Clean up the variable name by removing whitespace. */ + struct variable *var; + char *vp = next_token (varname); + char *vp_end = end_of_token (vp); + vp_end[0] = '\0'; + + push_new_variable_scope (); + var = define_variable (vp, vp_end - vp, "", o_automatic, 0); + + /* Don't need the list any more. */ + free (list); + list = NULL; + + /* Loop through the chain. */ + for (cur = chain; cur; cur = cur->next) + { + /* Update the variable value: */ + unsigned int const len = strlen (cur->name); + switch (style & Q_RET_MASK) + { + case Q_RET_UNQUOTED: + helper_set_var_value (var, cur->name, len); + break; + default: + { /* Use the output buffer as temporary storage. */ + size_t const saved_off = o - variable_buffer; + size_t quoted_len; + char *quoted; + o = helper_return_file_len (o, cur->name, len, style, 1 /*is_last*/); + quoted = &variable_buffer[saved_off]; + quoted_len = o - quoted; + helper_set_var_value (var, quoted, quoted_len); + o = quoted; + break; + } + } + + /* Expand the body: */ +#ifndef CONFIG_WITH_VALUE_LENGTH + { + char *result = allocated_variable_expand (body); + o = variable_buffer_output (o, result, strlen (result)); + free (result); + } +#else + variable_expand_string_2 (o, body, body_len, &o); +#endif + + /* Add separator: */ + if (cur->next) + o = helper_return_sep (o, style); + } + + pop_variable_scope (); + free (varname); + + return o; +} + +/* $(foreachfile var, filelist, body) - same as $(foreach ), except + for file rather than word tokens. + See also func_foreach(). */ + +static char * +func_foreachfile (char *o, char **argv, const char *funcname UNUSED) +{ + return common_foreachfile (o, argv, Q_IN_QUOTED | Q_RET_QUOTED | Q_SEP_SPACE); +} + +/* $(qforeachfile style, var, filelist, body) - same as $(foreach ), except + for file rather than word tokens and flexible variable value encoding. + See also func_foreach(). */ + +static char * +func_q_foreachfile (char *o, char **argv, const char *funcname UNUSED) +{ + unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT); + return common_foreachfile (o, &argv[1], style); +} + +/* Common worker for func_sortfiles() and func_q_sortfiles(). */ + +static char *common_sortfiles (char *o, char **argv, unsigned int style, + int ascending, int version) +{ + struct nameseq *chain = helper_parse_file_list (argv[0], style, 0); + + /* Count the number of files to determin the array size and whether we've + got anything to sort. */ + struct nameseq *cur; + unsigned int num_files = 0; + for (cur = chain; cur; cur = cur->next) + num_files++; + + if (num_files <= 1) + o = helper_return_and_free_chain (o, chain, style); + else + { + /* Create array of string pointers from the chain. */ + char **files = (char **)xmalloc (num_files * sizeof (char *)); + unsigned int idx = 0; + for (cur = chain; cur; cur = cur->next) + files[idx++] = (char *)cur->name; + + /* Sort it. */ + if (version) + qsort(files, num_files, sizeof(files[0]), version_compare_wrapper); + else + qsort(files, num_files, sizeof(files[0]), alpha_compare); + + /* Output. We skip equal files. */ + if (ascending) + for (idx = 0; idx < num_files; idx++) + { + const char *curfile = files[idx]; + while (idx + 1 < num_files && strcmp(files[idx + 1], curfile) == 0) + idx++; + o = helper_return_file(o, files[idx], style, idx + 1 >= num_files); + } + else + { + idx = num_files; + while (idx-- > 0) + { + const char *curfile = files[idx]; + while (idx > 0 && strcmp(files[idx - 1], curfile) == 0) + idx--; + o = helper_return_file (o, curfile, style, idx == 0); + } + } + + free (files); + free_ns_chain_no_strcache (chain); + } + return o; +} + +/* $(sortfiles file1 ... fileN) and + $(rsortfiles file1 ... fileN) - same as $(sort ) and $(rsort ), + except for files rather than word tokens. See func_sort(). */ + +static char *func_sortfiles (char *o, char **argv, const char *funcname) +{ + return common_sortfiles (o, argv, Q_IN_QUOTED | Q_RET_QUOTED | Q_SEP_SPACE, + funcname[0] != 'r', + funcname[0] == 'v' || funcname[1] == 'v'); +} + +/* $(qsortfiles style, file1 ... fileN) and + $(qrsortfiles style, file1 ... fileN) - same as $(sort ) and $(rsort ), + except for files rather than word tokens and the flexible style. + See func_sort(). */ + +static char *func_q_sortfiles (char *o, char **argv, const char *funcname) +{ + unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT); + return common_sortfiles (o, &argv[1], style, funcname[1] != 'r', + funcname[1] == 'v' || funcname[2] == 'v'); +} + + +/* Helper for determining whether the given path is absolute or not. */ + +static int helper_is_abs (const char *path) +{ +#ifdef HAVE_DOS_PATHS + return path[0] == '/' + || path[0] == '\\' + || (isalpha(path[0]) && path[1] == ':'); +#else + return path[0] == '/'; +#endif +} + +/* Worker for func_q_abspath and func_q_abspath_ex. */ + +static char *worker_abspath (char *o, char *line, const char *cwd, + size_t cwd_len, unsigned int style) +{ + if (line && *line != '\0') + { + PATH_VAR (outbuf); + struct nameseq *chain = helper_parse_file_list (line, style, 0); + + /* Special case: single path, no cwd - no is_last path trouble */ + if (chain && !chain->next && !cwd) + { + if (abspath (chain->name, outbuf)) + o = helper_return_file(o, outbuf, style, 1); + free_ns_chain_no_strcache (chain); + } + else if (chain) + { + /* Pass one: replace names by absolute names */ + struct nameseq *prev = NULL; + struct nameseq *cur = chain; + while (cur) + { + /* If relative path and we've got cwd, join cwd and it. */ + if (cwd && !helper_is_abs (cur->name)) + { + size_t len_name = strlen (cur->name); + char *name = xrealloc ((char *)cur->name, cwd_len + 1 + len_name + 1); + memmove (&name[cwd_len + 1], &name[0], len_name); + memcpy (name, cwd, cwd_len); + name[cwd_len] = '/'; + name[cwd_len + 1 + len_name] = '\0'; + cur->name = name; + } + + if (abspath (cur->name, outbuf)) + { + free ((char *)cur->name); + cur->name = xstrdup (outbuf); + prev = cur; + cur = cur->next; + } + else /* remove it */ + cur = helper_unlink_and_free_ns (cur, prev, &chain); + } + + /* Pass two: output */ + o = helper_return_and_free_chain (o, chain, style); + } + } + return o; +} + +/* $(qabspath style, file1 file2 ... fileN) - same as $(abspath ), except + for files rather than word tokens. See func_abspath(). */ + +static char *func_q_abspath (char *o, char **argv, const char *funcname UNUSED) +{ + return worker_abspath (o, argv[1], NULL, 0, + helper_file_quoting_style (argv[0], Q_QDEFAULT)); +} + +# ifdef CONFIG_WITH_ABSPATHEX +/* $(qabspathex style, file1 file2 ... fileN [,cwd]) - same as $(abspathex ), + except for files rather than word tokens. See func_abspath_ex(). */ + +static char * +func_q_abspathex (char *o, char **argv, const char *funcname UNUSED) +{ + /* cwd needs leading spaces chopped and may be optional, + in which case we're exactly like $(abspath ). */ + char *cwd = argv[2]; + if (cwd) + { + while (ISBLANK (*cwd)) + cwd++; + if (*cwd == '\0') + cwd = NULL; + } + + return worker_abspath (o, argv[1], cwd, cwd ? strlen (cwd) : 0, + helper_file_quoting_style (argv[0], Q_QDEFAULT)); +} +# endif + +/* $(qaddprefix style, prefix, file1 ... fileN) and + $(qaddsuffix style, prefix, file1 ... fileN) - same as $(addprefix ) + and $(addsuffix ) except for files rather than word tokens. + The suffix/prefix is unquoted on input and subjected to the same quoting + styling as the file names. + See func_addsuffix_addprefix(). */ + +static char *func_q_addsuffix_addprefix (char *o, char **argv, const char *funcname UNUSED) +{ + unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT); + const char * const fix = argv[1]; + size_t const fixlen = strlen (fix); + struct nameseq *chain = helper_parse_file_list (argv[2], style, 0); + if (chain) + { + size_t tmpsize = (fixlen + 512) & ~(size_t)63; + char *tmp = (char *)xmalloc (tmpsize); + struct nameseq *cur; + + if (funcname[4] == 'p') + { + memcpy (tmp, fix, fixlen); + for (cur = chain; cur; cur = cur->next) + { + size_t curlen = strlen (cur->name); + if (fixlen + curlen + 1 <= tmpsize) + { /* likely */ } + else + { + tmpsize = (fixlen + curlen + 63) & ~(size_t)63; + tmp = (char *)xrealloc (tmp, tmpsize); + } + memcpy (&tmp[fixlen], cur->name, curlen + 1); + o = helper_return_file_len (o, tmp, fixlen + curlen, + style, cur->next == NULL); + } + } + else + for (cur = chain; cur; cur = cur->next) + { + size_t curlen = strlen (cur->name); + if (fixlen + curlen + 1 <= tmpsize) + { /* likely */ } + else + { + tmpsize = (fixlen + curlen + 63) & ~(size_t)63; + tmp = (char *)xrealloc (tmp, tmpsize); + } + memcpy (tmp, cur->name, curlen); + memcpy (&tmp[curlen], fix, fixlen + 1); + + o = helper_return_file_len (o, tmp, fixlen + curlen, + style, cur->next == NULL); + } + free_ns_chain_no_strcache (chain); + } + return o; +} + +/* $(qbasename style, path1 .. pathN) and $(qdir style, path1 .. pathN) + - same as $(basename ) and $(dir ), except for files rather than word tokens. + See func_basename_dir(). */ + +static char * +func_q_basename_dir (char *o, char **argv, const char *funcname) +{ + unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT_VMS_TRICKS); + struct nameseq *chain = helper_parse_file_list (argv[1], style, 0); + struct nameseq *cur; + + int const is_basename = funcname[1] == 'b'; + int const is_dir = !is_basename; + int const stop = MAP_DIRSEP | (is_basename ? MAP_DOT : 0) | MAP_NUL; + + for (cur = chain; cur; cur = cur->next) + { + int const is_last = cur->next == NULL; + const char * const path = cur->name; + const char * const end = strchr (path, '\0'); + + /* Locate the last dot or path separator (P): */ + const char *p = path != end ? end - 1 : end; + while (p >= path && !STOP_SET (*p, stop)) + --p; + + /* Do the outputting: */ + if (p >= path && (is_dir)) + o = helper_return_file_len (o, path, ++p - path, style, is_last); + else if (p >= path && *p == '.') + o = helper_return_file_len (o, path, p - path, style, is_last); +#ifdef HAVE_DOS_PATHS + /* Handle the "d:foobar" case */ + else if (path[0] && path[1] == ':' && is_dir) + o = helper_return_file_len (o, path, 2, style, is_last); +#endif + else if (is_dir) +#ifdef VMS + { + extern int vms_report_unix_paths; + o = helper_return_file_len (o, vms_report_unix_paths ? "./" : "[]", + 2, style, is_last); + } +#else +# ifndef _AMIGA + o = helper_return_file_len (o, "./", 2, style, is_last); +# else + ; /* Just a nop... */ +# endif /* AMIGA */ +#endif /* !VMS */ + else + /* The entire name is the basename. */ + o = helper_return_file_len (o, path, end - path, style, is_last); + } + + free_ns_chain_no_strcache (chain); + return o; +} + +/* $(qnotdir style, path1 ... pathN) - same as $(notdir ), except for + files rather than word tokens. See func_notdir_suffix(). */ + +static char * +func_q_notdir (char *o, char **argv, const char *funcname) +{ + unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT_VMS_TRICKS); + struct nameseq *chain = helper_parse_file_list (argv[1], style, 0); + struct nameseq *cur; + int const stop = MAP_DIRSEP; + (void)funcname; + + for (cur = chain; cur; cur = cur->next) + { + int const is_last = cur->next == NULL; + const char * const path = cur->name; + const char * const end = strchr(path, '\0'); + + /* Locate the last dot or path separator (P): */ + const char *p = path != end ? end - 1 : end; + while (p >= path && ! STOP_SET (*p, stop)) + --p; + + if ((uintptr_t)p >= (uintptr_t)path) + o = helper_return_file_len (o, p + 1, end - p - 1, style, is_last); +#ifdef HAVE_DOS_PATHS + else if (path[0] && path[1] == ':') /* "d:foo/bar" -> "foo/bar" */ + o = helper_return_file_len (o, path + 2, end - path - 2, style, is_last); +#endif + else + o = helper_return_file_len (o, path, end - path, style, is_last); + } + + free_ns_chain_no_strcache (chain); + return o; +} + +/* $(qsuffix style, path1 ... pathN) - same as $(suffix ), except for + files rather than word tokens. See func_notdir_suffix(). */ + +static char * +func_q_suffix (char *o, char **argv, const char *funcname) +{ + unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT_VMS_TRICKS); + struct nameseq *chain = helper_parse_file_list (argv[1], style, 0); + struct nameseq *prev; + struct nameseq *cur; + int const stop = MAP_DIRSEP | MAP_DOT; + (void)funcname; + + /* For suffixes we do a pre-pass that removes elements without suffixes. + This simplifies the handling of end-quoting. */ + prev = NULL; + cur = chain; + while (cur) + { + const char * const path = cur->name; + if (strchr (path, '.') != NULL) + { + const char * const end = strchr (path, '\0'); + const char *p = end - 1; + while ((uintptr_t)p >= (uintptr_t)path && ! STOP_SET (*p, stop)) + --p; + if ((uintptr_t)p >= (uintptr_t)path && *p == '.') + { + if (p != path) + memmove ((char *)path, p, end - p + 1); + prev = cur; + cur = cur->next; + } + else /* remove it */ + cur = helper_unlink_and_free_ns (cur, prev, &chain); + } + else /* remove it */ + cur = helper_unlink_and_free_ns (cur, prev, &chain); + } + + /* Output pass: */ + return helper_return_and_free_chain (o, chain, style); +} + +# ifdef CONFIG_WITH_ROOT_FUNC +/* + $(qroot style, path...pathN) - same as $(root ), except files rather + than space delimited word tokens. See func_root(). + + This is mainly for dealing with drive letters and UNC paths on Windows + and OS/2. + */ +static char * +func_q_root (char *o, char **argv, const char *funcname UNUSED) +{ + unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT); + struct nameseq *chain = helper_parse_file_list (argv[1], style, 0); + struct nameseq *prev; + struct nameseq *cur; + + /* First pass: Strip non-root components and remove rootless elements. */ + prev = NULL; + cur = chain; + while (cur) + { + const char *path = cur->name; + const char *end = NULL; + char ch; + +# ifdef HAVE_DOS_PATHS + if (isalpha(path[0]) && path[1] == ':') + end = path + 2; + else if ( IS_PATHSEP(path[0]) + && IS_PATHSEP(path[1]) + && !IS_PATHSEP(path[2]) && path[2] + && path[3]) + { + /* Min recognized UNC: "//./" - find the next slash + Typical root: "//srv/shr/" */ + /* XXX: Check if //./ needs special handling. */ + end = path + 3; + while ((ch = *end) != '\0' && !IS_PATHSEP(ch)) + end++; + + if (IS_PATHSEP(ch) && !IS_PATHSEP(end[1])) + { + end++; + while ((ch = *end) != '\0' && !IS_PATHSEP(ch)) + end++; + } + else + end = NULL; /* invalid */ + } + else if (IS_PATHSEP(*end)) + end = path + 1; + else + end = NULL; + +# elif defined (VMS) || defined (AMGIA) + /* XXX: VMS and AMGIA */ + OS (fatal, NILF, _("$(%s ) is not implemented on this platform"), funcname); +# else + if (IS_PATHSEP(*path)) + end = path + 1; +# endif + if (end != NULL) + { + /* Include all subsequent path separators. */ + + while ((ch = *end) != '\0' && IS_PATHSEP(ch)) + end++; + *(char *)end = '\0'; + + prev = cur; + cur = cur->next; + } + else + cur = helper_unlink_and_free_ns(cur, prev, &chain); + } + + /* Second pass: Output */ + return helper_return_and_free_chain (o, chain, style); +} + +/* + $(qnotroot style, path1 .. pathN) - same as $(notroot ), except files + rather than space delimited word tokens. See func_notroot(). + + This is mainly for dealing with drive letters and UNC paths on Windows + and OS/2. + */ +static char * +func_q_notroot (char *o, char **argv, const char *funcname UNUSED) +{ + unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT); + struct nameseq *chain = helper_parse_file_list (argv[1], style, 0); + struct nameseq *cur; + + for (cur = chain; cur; cur = cur->next) + { + const char *start = cur->name; + char ch; + +# ifdef HAVE_DOS_PATHS + if (isalpha(start[0]) && start[1] == ':') + start += 2; + else if ( IS_PATHSEP(start[0]) + && IS_PATHSEP(start[1]) + && !IS_PATHSEP(start[2]) && start[2] != '\0' + && start[3] != '\0') + { + /* Min recognized UNC: "//./" - find the next slash + Typical root: "//srv/shr/" */ + /* XXX: Check if //./ needs special handling. */ + start += 3; + while ((ch = *start) != '\0' && !IS_PATHSEP(ch)) + start++; + + if (IS_PATHSEP(ch) && !IS_PATHSEP(start[1])) + { + start++; + while ((ch = *start) != '\0' && !IS_PATHSEP(ch)) + start++; + } + else + start = cur->name; /* invalid UNC, pretend it's a couple unixy root slashes. */ + } + +# elif defined (VMS) || defined (AMGIA) + /* XXX: VMS and AMGIA */ + OS (fatal, NILF, _("$(%s) is not implemented on this platform"), funcname); +# endif + + /* Exclude all subsequent / leading path separators. */ + while ((ch = *start) != '\0' && IS_PATHSEP(ch)) + start++; + + if (ch != '\0') + o = helper_return_file(o, start, style, cur->next == NULL); + else + o = helper_return_file_len (o, ".", 1, style, cur->next == NULL); + } + + free_ns_chain_no_strcache (chain); + return o; +} + +# endif + +/* $(qrealpath style, path1 .. pathN) - same as $(realpath ), except files + rather than space delimited word tokens. See func_realpath(). */ + +static char * +func_q_realpath (char *o, char **argv, const char *funcname UNUSED) +{ + PATH_VAR (outbuf); + unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT); + struct nameseq *chain = helper_parse_file_list (argv[1], style, 0); + + /* Pass one: Do the realpath/abspath thing and remove anything that fails + or doesn't exists. */ + struct nameseq *cur = chain; + struct nameseq *prev = NULL; + while (cur) + { + char *result; +#ifdef HAVE_REALPATH + ENULLLOOP (result, realpath (cur->name, outbuf)); +#else + result = abspath (cur->name, outbuf); +#endif + if (result) + { + struct stat st; + int r; + EINTRLOOP (r, stat (outbuf, &st)); + if (r == 0) + { + free ((char *)cur->name); + cur->name = xstrdup (result); + prev = cur; + cur = cur->next; + } + else + cur = helper_unlink_and_free_ns(cur, prev, &chain); + } + else + cur = helper_unlink_and_free_ns(cur, prev, &chain); + } + + /* Pass two: Output. */ + return helper_return_and_free_chain (o, chain, style); +} + +/* $(qwildcard path1 .. pathN [, style]) - same as $(wildcard ), except files + rather than space delimited word tokens. See func_wildcard(). */ + +static char * +func_q_wildcard (char *o, char **argv, const char *funcname UNUSED) +{ + unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT); + struct nameseq *chain = helper_parse_file_list (argv[1], style, 1 /*glob*/); +#ifdef _AMIGA + OS (fatal, NILF, _("$(%s ) is not implemented on this platform"), funcname); +#endif + return helper_return_and_free_chain (o, chain, style); +} + +static char * +worker_filter_filterout (char *o, char **argv, unsigned style, int is_filter) +{ + struct nameseq *wordchain; + struct a_word *wordhead; + struct a_word **wordtail; + struct a_word *wp; + struct nameseq *patchain; + struct a_pattern *pathead; + struct a_pattern **pattail; + struct a_pattern *pp; + struct nameseq *cur; + struct hash_table a_word_table; + int literals; + int words; + int hashing; + unsigned int words_len; /* for output estimation */ + + /* Chop ARGV[0] up into patterns to match against the words. */ + /** @todo this very inefficient as we convert between two list format and + * duplicates the patterns on the heap. We could just modify argv[0] + * directly. */ + patchain = helper_parse_file_list (argv[0], style, 0 /*glob*/); + pattail = &pathead; + for (cur = patchain, literals = 0; cur; cur = cur->next) + { + struct a_pattern *pat = alloca (sizeof (struct a_pattern)); + + *pattail = pat; + pattail = &pat->next; + + pat->str = (char *)cur->name; /* (safe - PARSEFS_NOCACHE) */ + pat->percent = find_percent (pat->str); /* may modify name */ + if (pat->percent == 0) + literals++; + pat->sfxlen = pat->percent ? strlen(pat->percent + 1) : 0; + pat->length = strlen (pat->str); + } + *pattail = NULL; + + /* Chop ARGV[1] up into words to match against the patterns. */ + /** @todo this very inefficient as we convert between two list format and + * duplicates the words on the heap. We could just modify argv[1] + * directly. */ + wordchain = helper_parse_file_list (argv[1], style, 0 /*glob*/); + wordtail = &wordhead; + for (cur = wordchain, words = 0, words_len = 0; cur; cur = cur->next) + { + struct a_word *word = alloca (sizeof (struct a_word)); + + *wordtail = word; + wordtail = &word->next; + + word->str = (char *)cur->name; /* (safe - PARSEFS_NOCACHE) */ + word->length = strlen (cur->name); + words_len += word->length + 1; + word->matched = 0; + word->chain = NULL; + words++; + } + *wordtail = NULL; + + /* Only use a hash table if arg list lengths justifies the cost. */ + hashing = (literals >= 2 && (literals * words) >= 10); + if (hashing) + { + hash_init (&a_word_table, words, a_word_hash_1, a_word_hash_2, + a_word_hash_cmp); + for (wp = wordhead; wp != 0; wp = wp->next) + { + struct a_word *owp = hash_insert (&a_word_table, wp); + if (owp) + wp->chain = owp; + } + } + + if (words) + { + int doneany = 0; + + if (is_filter) + words_len = 0; + + /* Run each pattern through the words, killing words. */ + for (pp = pathead; pp != 0; pp = pp->next) + { + if (pp->percent) + { + for (wp = wordhead; wp != 0; wp = wp->next) + if (!wp->matched + && pattern_matches_ex (pp->str, pp->percent, pp->sfxlen, + wp->str, wp->length)) + { + wp->matched = 1; + if (is_filter) + words_len += wp->length + 1; + else + words_len -= wp->length + 1; + } + } + else if (hashing) + { + struct a_word a_word_key; + a_word_key.str = pp->str; + a_word_key.length = pp->length; + wp = hash_find_item (&a_word_table, &a_word_key); + while (wp) + { + if (!wp->matched) + { + wp->matched = 1; + if (is_filter) + words_len += wp->length + 1; + else + words_len -= wp->length + 1; + } + wp = wp->chain; + } + } + else + for (wp = wordhead; wp != 0; wp = wp->next) + if (!wp->matched + && wp->length == pp->length + && strneq (pp->str, wp->str, wp->length)) + { + wp->matched = 1; + if (is_filter) + words_len += wp->length + 1; + else + words_len -= wp->length + 1; + } + } + + /* Output the words that matched (or didn't, for filter-out). */ + o = ensure_variable_buffer_space (o, words_len); + + for (wp = wordhead; wp != 0; wp = wp->next) + if (wp->matched == is_filter) + { + o = helper_return_file_len (o, wp->str, wp->length, + style, 0 /*is_last*/); + doneany = 1; + } + + /* Kill the last separator. */ + if (doneany) + o = helper_drop_separator (o, style); + } + + /* Clean up. */ + if (hashing) + hash_free (&a_word_table, 0); + free_ns_chain_no_strcache (wordchain); + free_ns_chain_no_strcache (patchain); + + return o; +} + +/* Implements $(qfilter ) and $(qfilter-out ). */ +static char * +func_q_filter_filterout (char *o, char **argv, const char *funcname) +{ + unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT); + return worker_filter_filterout (o, &argv[1], style, funcname[7] == '\0'); +} + +#endif /* KMK */ + +#ifdef CONFIG_WITH_LAZY_DEPS_VARS + +/* Helper that parses the index argument for the $(deps* ) and $(qdeps* ) + functions. */ +static unsigned int parse_dep_index (const char *index, const char *funcname) +{ + unsigned int idx = 0; + if (index) + { + while (ISSPACE (*index)) + index++; + if (*index != '\0') + { + char *next = (char *)index; + long l = strtol (index, &next, 0); + while (ISSPACE (*next)) + next++; + idx = (unsigned int)l; + if (*next != '\0' || l < 0 || (long)idx != l) + OSS (fatal, NILF, _("%s: invalid index value: `%s'\n"), funcname, index); + } + } + return idx; +} + +/* Helper that calculates the output length for a dependency */ + +MY_INLINE unsigned int helper_dep_output_len (struct dep *d) +{ + const char *c = dep_name (d); +#ifndef NO_ARCHIVES + if (ar_name (c)) + return strlen (strchr (c, '(') + 1); +#endif +#ifdef CONFIG_WITH_STRCACHE2 + if (!d->need_2nd_expansion) + return strcache2_get_len (&file_strcache, c) + 1; +#endif + return strlen (c) + 1; +} + +/* Helper that outputs a depndency. */ + +MY_INLINE char *helper_dep_output_one (char *o, struct dep *d, + unsigned int style, int is_last) +{ + const char *c = dep_name (d); +#ifndef NO_ARCHIVES + if (ar_name (c)) + { + c = strchr(c, '(') + 1; + o = helper_return_file_len (o, c, strlen(c) - 1, style, is_last); + } + else +#endif +#ifdef CONFIG_WITH_STRCACHE2 + if (!d->need_2nd_expansion) + { + unsigned int len = strcache2_get_len (&file_strcache, c); + o = helper_return_file_len (o, c, len, style, is_last); + } + else +#endif + o = helper_return_file (o, c, style, is_last); + return o; +} + +/* Implements $^/$(deps )/$(qdeps ) and $+/$(deps-all )/$(qdeps-all ). + + If no second argument is given, or if it's empty, or if it's zero, + all dependencies will be returned. If the second argument is non-zero + the dependency at that position will be returned. If the argument is + negative a fatal error is thrown. */ +static char * +worker_deps (char *o, char **argv, const char *funcname, unsigned int style, + int all_deps) +{ + unsigned int idx = parse_dep_index (argv[1], funcname); + struct file *file; + + /* Find the file and select the list corresponding to FUNCNAME. */ + + file = lookup_file (argv[0]); + if (file) + { + struct dep *deps; + struct dep *d; + if (!all_deps) + { + deps = file->deps_no_dupes; + if (!deps && file->deps) + deps = file->deps_no_dupes = create_uniqute_deps_chain (file->deps); + } + else + deps = file->deps; + + if ( file->double_colon + && ( file->double_colon != file + || file->last != file)) + OSS (error, NILF, _("$(%s ) cannot be used on files with multiple double colon rules like `%s'\n"), + funcname, file->name); + + if (idx == 0 /* all */) + { + /* Since this may be a long list, calculate the output space needed if + no quoting takes place and no multichar separators are used. */ + + unsigned int total_len = 0; + for (d = deps; d; d = d->next) + if (!d->ignore_mtime) + total_len += helper_dep_output_len (d); + if (total_len > 0) + { + o = ensure_variable_buffer_space (o, total_len); + + for (d = deps; d; d = d->next) + if (!d->ignore_mtime) + o = helper_dep_output_one (o, d, style, 0 /*is_last*/); + + /* nuke the last list separator */ + o = helper_drop_separator (o, style); + } + } + else + { + /* Dependency given by index. */ + + for (d = deps; d; d = d->next) + if (!d->ignore_mtime) + if (--idx == 0) /* 1 based indexing */ + return helper_dep_output_one (o, d, style, 1 /*is_last*/); + } + } + + return o; +} + +/* Implements $? / $(deps-newer ) / $(qdeps-newer ). + + If no second argument is given, or if it's empty, or if it's zero, + all dependencies will be returned. If the second argument is non-zero + the dependency at that position will be returned. If the argument is + negative a fatal error is thrown. */ +static char * +worker_deps_newer (char *o, char **argv, const char *funcname, unsigned int style) +{ + unsigned int idx = parse_dep_index (argv[1], funcname); + struct file *file; + + /* Find the file. */ + + file = lookup_file (argv[0]); + if (file) + { + struct dep *deps = file->deps; + struct dep *d; + + if ( file->double_colon + && ( file->double_colon != file + || file->last != file)) + OSS (error, NILF, _("$(%s ) cannot be used on files with multiple double colon rules like `%s'\n"), + funcname, file->name); + + if (idx == 0 /* all */) + { + unsigned int total_len = 0; + + /* calc the result length. */ + + for (d = deps; d; d = d->next) + if (!d->ignore_mtime && d->changed) + total_len += helper_dep_output_len (d); + if (total_len) + { + o = ensure_variable_buffer_space (o, total_len); + + for (d = deps; d; d = d->next) + if (!d->ignore_mtime && d->changed) + o = helper_dep_output_one (o, d, style, 0 /*is_last*/); + + /* nuke the last list separator */ + o = helper_drop_separator (o, style); + } + } + else + { + /* Dependency given by index. */ + + for (d = deps; d; d = d->next) + if (!d->ignore_mtime && d->changed) + if (--idx == 0) /* 1 based indexing */ + return helper_dep_output_one (o, d, style, 1 /*is_last*/); + } + } + + return o; +} + +/* Implements $|, the order only dependency list. + + If no second argument is given, or if it's empty, or if it's zero, + all dependencies will be returned. If the second argument is non-zero + the dependency at that position will be returned. If the argument is + negative a fatal error is thrown. */ +static char * +worker_deps_order_only (char *o, char **argv, const char *funcname, unsigned int style) +{ + unsigned int idx = parse_dep_index (argv[1], funcname); + struct file *file; + + /* Find the file. */ + + file = lookup_file (argv[0]); + if (file) + { + struct dep *deps = file->deps; + struct dep *d; + + if ( file->double_colon + && ( file->double_colon != file + || file->last != file)) + OSS (error, NILF, _("$(%s ) cannot be used on files with multiple double colon rules like `%s'\n"), + funcname, file->name); + + if (idx == 0 /* all */) + { + unsigned int total_len = 0; + + /* calc the result length. */ + + for (d = deps; d; d = d->next) + if (d->ignore_mtime) + total_len += helper_dep_output_len (d); + if (total_len) + { + o = ensure_variable_buffer_space (o, total_len); + + for (d = deps; d; d = d->next) + if (d->ignore_mtime) + o = helper_dep_output_one (o, d, style, 0 /*is_last*/); + + /* nuke the last list separator */ + o = helper_drop_separator (o, style); + } + } + else + { + /* Dependency given by index. */ + + for (d = deps; d; d = d->next) + if (d->ignore_mtime) + if (--idx == 0) /* 1 based indexing */ + return helper_dep_output_one (o, d, style, 1 /*is_last*/); + } + } + + return o; +} + +/* Implements $^ and $+. + + The first comes with FUNCNAME 'deps', the second as 'deps-all'. + + If no second argument is given, or if it's empty, or if it's zero, + all dependencies will be returned. If the second argument is non-zero + the dependency at that position will be returned. If the argument is + negative a fatal error is thrown. */ +static char * +func_deps (char *o, char **argv, const char *funcname) +{ +# ifdef VMS + return worker_deps (o, argv, funcname, Q_RET_UNQUOTED | Q_SEP_COMMA, +# else + return worker_deps (o, argv, funcname, Q_RET_UNQUOTED | Q_SEP_SPACE, +# endif + funcname[4] != '\0'); +} + +/* Implements $?. + + If no second argument is given, or if it's empty, or if it's zero, + all dependencies will be returned. If the second argument is non-zero + the dependency at that position will be returned. If the argument is + negative a fatal error is thrown. */ +static char * +func_deps_newer (char *o, char **argv, const char *funcname) +{ +# ifdef VMS + return worker_deps_newer (o, argv, funcname, Q_RET_UNQUOTED | Q_SEP_COMMA); +# else + return worker_deps_newer (o, argv, funcname, Q_RET_UNQUOTED | Q_SEP_SPACE); +# endif +} + +/* Implements $|, the order only dependency list. + + If no second argument is given, or if it's empty, or if it's zero, + all dependencies will be returned. If the second argument is non-zero + the dependency at that position will be returned. If the argument is + negative a fatal error is thrown. */ +static char * +func_deps_order_only (char *o, char **argv, const char *funcname) +{ +# ifdef VMS + return worker_deps_order_only (o, argv, funcname, Q_RET_UNQUOTED | Q_SEP_COMMA); +# else + return worker_deps_order_only (o, argv, funcname, Q_RET_UNQUOTED | Q_SEP_SPACE); +# endif +} + +#endif /* CONFIG_WITH_LAZY_DEPS_VARS */ +#ifdef KMK + +/* Implements $(qdeps ) and $(qdeps-all ) + + If no third argument is given, or if it's empty, or if it's zero, + all dependencies will be returned. If the third argument is non-zero + the dependency at that position will be returned. If the argument is + negative a fatal error is thrown. */ +static char * +func_q_deps (char *o, char **argv, const char *funcname) +{ + unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT); + return worker_deps (o, &argv[1], funcname, style, funcname[5] != '\0'); +} + +/* Implements $(qdeps-newer ). + + If no third argument is given, or if it's empty, or if it's zero, + all dependencies will be returned. If the third argument is non-zero + the dependency at that position will be returned. If the argument is + negative a fatal error is thrown. */ +static char * +func_q_deps_newer (char *o, char **argv, const char *funcname) +{ + unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT); + return worker_deps_newer (o, &argv[1], funcname, style); +} + +/* Implements $(qdeps-oo ), the order only dependency list. + + If no third argument is given, or if it's empty, or if it's zero, + all dependencies will be returned. If the third argument is non-zero + the dependency at that position will be returned. If the argument is + negative a fatal error is thrown. */ +static char * +func_q_deps_order_only (char *o, char **argv, const char *funcname) +{ + unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT); + return worker_deps_order_only (o, &argv[1], funcname, style); +} + +/* Implements $(qtarget ), converting a single unquoted file to the given + quoting style. + + Typically used like this: + $(qtarget sh,$@) + $(qone-unquoted sh,$@) */ +static char * +func_q_one_unquoted (char *o, char **argv, const char *funcname) +{ + unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT); + return helper_return_file (o, argv[1], style, 1 /*is_last*/); +} + +#endif /* KMK */ + +/* Lookup table for builtin functions. + + This doesn't have to be sorted; we use a straight lookup. We might gain + some efficiency by moving most often used functions to the start of the + table. + + If MAXIMUM_ARGS is 0, that means there is no maximum and all + comma-separated values are treated as arguments. + + EXPAND_ARGS means that all arguments should be expanded before invocation. + Functions that do namespace tricks (foreach) don't automatically expand. */ + +static char *func_call (char *o, char **argv, const char *funcname); + +#define FT_ENTRY(_name, _min, _max, _exp, _func) \ + { { (_func) }, STRING_SIZE_TUPLE(_name), (_min), (_max), (_exp), 0 } + +static struct function_table_entry function_table_init[] = +{ + /* Name MIN MAX EXP? Function */ + FT_ENTRY ("abspath", 0, 1, 1, func_abspath), + FT_ENTRY ("addprefix", 2, 2, 1, func_addsuffix_addprefix), + FT_ENTRY ("addsuffix", 2, 2, 1, func_addsuffix_addprefix), + FT_ENTRY ("basename", 0, 1, 1, func_basename_dir), + FT_ENTRY ("dir", 0, 1, 1, func_basename_dir), + FT_ENTRY ("notdir", 0, 1, 1, func_notdir_suffix), +#ifdef CONFIG_WITH_ROOT_FUNC + FT_ENTRY ("root", 0, 1, 1, func_root), + FT_ENTRY ("notroot", 0, 1, 1, func_notroot), +#endif + FT_ENTRY ("subst", 3, 3, 1, func_subst), + FT_ENTRY ("suffix", 0, 1, 1, func_notdir_suffix), + FT_ENTRY ("filter", 2, 2, 1, func_filter_filterout), + FT_ENTRY ("filter-out", 2, 2, 1, func_filter_filterout), + FT_ENTRY ("findstring", 2, 2, 1, func_findstring), +#ifdef CONFIG_WITH_DEFINED_FUNCTIONS + FT_ENTRY ("firstdefined", 0, 2, 1, func_firstdefined), +#endif + FT_ENTRY ("firstword", 0, 1, 1, func_firstword), + FT_ENTRY ("flavor", 0, 1, 1, func_flavor), + FT_ENTRY ("join", 2, 2, 1, func_join), +#ifdef CONFIG_WITH_DEFINED_FUNCTIONS + FT_ENTRY ("lastdefined", 0, 2, 1, func_lastdefined), +#endif + FT_ENTRY ("lastword", 0, 1, 1, func_lastword), + FT_ENTRY ("patsubst", 3, 3, 1, func_patsubst), + FT_ENTRY ("realpath", 0, 1, 1, func_realpath), +#ifdef CONFIG_WITH_RSORT + FT_ENTRY ("rsort", 0, 1, 1, func_sort), +# ifdef KMK + FT_ENTRY ("rversort", 0, 1, 1, func_sort), +# endif +#endif + FT_ENTRY ("shell", 0, 1, 1, func_shell), + FT_ENTRY ("sort", 0, 1, 1, func_sort), +# ifdef KMK + FT_ENTRY ("versort", 0, 1, 1, func_sort), +# endif + FT_ENTRY ("strip", 0, 1, 1, func_strip), +#ifdef CONFIG_WITH_WHERE_FUNCTION + FT_ENTRY ("where", 0, 1, 1, func_where), +#endif + FT_ENTRY ("wildcard", 0, 1, 1, func_wildcard), + FT_ENTRY ("word", 2, 2, 1, func_word), + FT_ENTRY ("wordlist", 3, 3, 1, func_wordlist), + FT_ENTRY ("words", 0, 1, 1, func_words), + FT_ENTRY ("origin", 0, 1, 1, func_origin), + FT_ENTRY ("foreach", 3, 3, 0, func_foreach), +#ifdef CONFIG_WITH_LOOP_FUNCTIONS + FT_ENTRY ("for", 4, 4, 0, func_for), + FT_ENTRY ("while", 2, 2, 0, func_while), +#endif + FT_ENTRY ("call", 1, 0, 1, func_call), + FT_ENTRY ("info", 0, 1, 1, func_error), + FT_ENTRY ("error", 0, 1, 1, func_error), + FT_ENTRY ("warning", 0, 1, 1, func_error), + FT_ENTRY ("if", 2, 3, 0, func_if), + FT_ENTRY ("or", 1, 0, 0, func_or), + FT_ENTRY ("and", 1, 0, 0, func_and), + FT_ENTRY ("value", 0, 1, 1, func_value), +#ifdef EXPERIMENTAL + FT_ENTRY ("eq", 2, 2, 1, func_eq), + FT_ENTRY ("not", 0, 1, 1, func_not), +#endif + FT_ENTRY ("eval", 0, 1, 1, func_eval), +#ifdef CONFIG_WITH_EVALPLUS + FT_ENTRY ("evalctx", 0, 1, 1, func_evalctx), + FT_ENTRY ("evalval", 1, 1, 1, func_evalval), + FT_ENTRY ("evalvalctx", 1, 1, 1, func_evalval), + FT_ENTRY ("evalcall", 1, 0, 1, func_call), + FT_ENTRY ("evalcall2", 1, 0, 1, func_call), + FT_ENTRY ("eval-opt-var", 1, 0, 1, func_eval_optimize_variable), +#endif + FT_ENTRY ("file", 1, 2, 1, func_file), +#ifdef CONFIG_WITH_STRING_FUNCTIONS + FT_ENTRY ("length", 1, 1, 1, func_length), + FT_ENTRY ("length-var", 1, 1, 1, func_length_var), + FT_ENTRY ("insert", 2, 5, 1, func_insert), + FT_ENTRY ("pos", 2, 3, 1, func_pos), + FT_ENTRY ("lastpos", 2, 3, 1, func_pos), + FT_ENTRY ("substr", 2, 4, 1, func_substr), + FT_ENTRY ("translate", 2, 4, 1, func_translate), +#endif +#ifdef CONFIG_WITH_PRINTF + FT_ENTRY ("printf", 1, 0, 1, kmk_builtin_func_printf), +#endif +#ifdef CONFIG_WITH_LAZY_DEPS_VARS + FT_ENTRY ("deps", 1, 2, 1, func_deps), + FT_ENTRY ("deps-all", 1, 2, 1, func_deps), + FT_ENTRY ("deps-newer", 1, 2, 1, func_deps_newer), + FT_ENTRY ("deps-oo", 1, 2, 1, func_deps_order_only), +#endif +#ifdef CONFIG_WITH_DEFINED + FT_ENTRY ("defined", 1, 1, 1, func_defined), +#endif +#ifdef CONFIG_WITH_TOUPPER_TOLOWER + FT_ENTRY ("toupper", 0, 1, 1, func_toupper_tolower), + FT_ENTRY ("tolower", 0, 1, 1, func_toupper_tolower), +#endif +#ifdef CONFIG_WITH_ABSPATHEX + FT_ENTRY ("abspathex", 0, 2, 1, func_abspathex), +#endif +#ifdef CONFIG_WITH_XARGS + FT_ENTRY ("xargs", 2, 0, 1, func_xargs), +#endif +#if defined(CONFIG_WITH_VALUE_LENGTH) && defined(CONFIG_WITH_COMPARE) + FT_ENTRY ("comp-vars", 3, 3, 1, func_comp_vars), + FT_ENTRY ("comp-cmds", 3, 3, 1, func_comp_vars), + FT_ENTRY ("comp-cmds-ex", 3, 3, 1, func_comp_cmds_ex), +#endif +#ifdef CONFIG_WITH_DATE + FT_ENTRY ("date", 0, 1, 1, func_date), + FT_ENTRY ("date-utc", 0, 3, 1, func_date), +#endif +#ifdef CONFIG_WITH_FILE_SIZE + FT_ENTRY ("file-size", 1, 1, 1, func_file_size), +#endif +#ifdef CONFIG_WITH_WHICH + FT_ENTRY ("which", 0, 0, 1, func_which), +#endif +#ifdef CONFIG_WITH_IF_CONDITIONALS + FT_ENTRY ("expr", 1, 1, 0, func_expr), + FT_ENTRY ("if-expr", 2, 3, 0, func_if_expr), + FT_ENTRY ("select", 2, 0, 0, func_select), +#endif +#ifdef CONFIG_WITH_SET_CONDITIONALS + FT_ENTRY ("intersects", 2, 2, 1, func_set_intersects), +#endif +#ifdef CONFIG_WITH_STACK + FT_ENTRY ("stack-push", 2, 2, 1, func_stack_push), + FT_ENTRY ("stack-pop", 1, 1, 1, func_stack_pop_top), + FT_ENTRY ("stack-popv", 1, 1, 1, func_stack_pop_top), + FT_ENTRY ("stack-top", 1, 1, 1, func_stack_pop_top), +#endif +#ifdef CONFIG_WITH_MATH + FT_ENTRY ("int-add", 2, 0, 1, func_int_add), + FT_ENTRY ("int-sub", 2, 0, 1, func_int_sub), + FT_ENTRY ("int-mul", 2, 0, 1, func_int_mul), + FT_ENTRY ("int-div", 2, 0, 1, func_int_div), + FT_ENTRY ("int-mod", 2, 2, 1, func_int_mod), + FT_ENTRY ("int-not", 1, 1, 1, func_int_not), + FT_ENTRY ("int-and", 2, 0, 1, func_int_and), + FT_ENTRY ("int-or", 2, 0, 1, func_int_or), + FT_ENTRY ("int-xor", 2, 0, 1, func_int_xor), + FT_ENTRY ("int-eq", 2, 2, 1, func_int_cmp), + FT_ENTRY ("int-ne", 2, 2, 1, func_int_cmp), + FT_ENTRY ("int-gt", 2, 2, 1, func_int_cmp), + FT_ENTRY ("int-ge", 2, 2, 1, func_int_cmp), + FT_ENTRY ("int-lt", 2, 2, 1, func_int_cmp), + FT_ENTRY ("int-le", 2, 2, 1, func_int_cmp), +#endif +#ifdef CONFIG_WITH_NANOTS + FT_ENTRY ("nanots", 0, 0, 0, func_nanots), +#endif +#ifdef CONFIG_WITH_OS2_LIBPATH + FT_ENTRY ("libpath", 1, 2, 1, func_os2_libpath), +#endif +#if defined (CONFIG_WITH_MAKE_STATS) || defined (CONFIG_WITH_MINIMAL_STATS) + FT_ENTRY ("make-stats", 0, 0, 0, func_make_stats), +#endif +#ifdef CONFIG_WITH_COMMANDS_FUNC + FT_ENTRY ("commands", 1, 1, 1, func_commands), + FT_ENTRY ("commands-sc", 1, 1, 1, func_commands), + FT_ENTRY ("commands-usr", 2, 2, 1, func_commands), +#endif +#ifdef KMK_HELPERS + FT_ENTRY ("kb-src-tool", 1, 2, 0, func_kbuild_source_tool), + FT_ENTRY ("kb-obj-base", 1, 2, 0, func_kbuild_object_base), + FT_ENTRY ("kb-obj-suff", 1, 2, 0, func_kbuild_object_suffix), + FT_ENTRY ("kb-src-prop", 3, 4, 0, func_kbuild_source_prop), + FT_ENTRY ("kb-src-one", 0, 1, 0, func_kbuild_source_one), + FT_ENTRY ("kb-exp-tmpl", 6, 6, 1, func_kbuild_expand_template), +#endif +#ifdef KMK + FT_ENTRY ("dircache-ctl", 1, 0, 1, func_dircache_ctl), + FT_ENTRY ("breakpoint", 0, 0, 0, func_breakpoint), + FT_ENTRY ("set-umask", 1, 3, 1, func_set_umask), + FT_ENTRY ("get-umask", 0, 0, 0, func_get_umask), +#endif +#ifdef KMK + FT_ENTRY ("quote", 1, 0, 1, func_quote_make), + FT_ENTRY ("quote-dep", 1, 0, 1, func_quote_make), + FT_ENTRY ("quote-tgt", 1, 0, 1, func_quote_make), + FT_ENTRY ("quote-depend", 1, 0, 1, func_quote_make), + FT_ENTRY ("quote-tgtend", 1, 0, 1, func_quote_make), + FT_ENTRY ("quote-sh", 1, 0, 1, func_quote_shell), + FT_ENTRY ("quote-sh-dq", 1, 1, 1, func_quote_shell_dq), + FT_ENTRY ("quote-sh-sq", 1, 1, 1, func_quote_shell_sq), + FT_ENTRY ("requote", 1, 0, 1, func_requote), + /* Quoted input and maybe output variants of functions typically + working with files: */ + FT_ENTRY ("firstfile", 0, 1, 1, func_firstfile), + FT_ENTRY ("lastfile", 0, 1, 1, func_lastfile), + FT_ENTRY ("filelist", 3, 3, 1, func_filelist), + FT_ENTRY ("countfiles", 0, 1, 1, func_countfiles), + FT_ENTRY ("foreachfile", 3, 3, 0, func_foreachfile), + FT_ENTRY ("sortfiles", 0, 1, 1, func_sortfiles), + FT_ENTRY ("versortfiles", 0, 1, 1, func_sortfiles), +# ifdef CONFIG_WITH_RSORT + FT_ENTRY ("rsortfiles", 0, 1, 1, func_sortfiles), + FT_ENTRY ("rversortfiles", 0, 1, 1, func_sortfiles), +# endif + /* Function variants with preceding style argument and quoting by default. */ + FT_ENTRY ("qfirstfile", 1+0, 1+1, 1, func_q_firstfile), + FT_ENTRY ("qlastfile", 1+0, 1+1, 1, func_q_lastfile), + FT_ENTRY ("qfilelist", 1+3, 1+3, 1, func_q_filelist), + FT_ENTRY ("qcountfiles", 1+0, 1+1, 1, func_q_countfiles), + FT_ENTRY ("qforeachfile", 1+3, 1+3, 0, func_q_foreachfile), + FT_ENTRY ("qsortfiles", 1+0, 1+1, 1, func_q_sortfiles), + FT_ENTRY ("qversortfiles",1+0, 1+1, 1, func_q_sortfiles), +# ifdef CONFIG_WITH_RSORT + FT_ENTRY ("qrsortfiles", 1+0, 1+1, 1, func_q_sortfiles), + FT_ENTRY ("qrversortfiles",1+0,1+1, 1, func_q_sortfiles), +# endif + FT_ENTRY ("qabspath", 1+0, 1+1, 1, func_q_abspath), + FT_ENTRY ("qaddprefix", 1+2, 1+2, 1, func_q_addsuffix_addprefix), + FT_ENTRY ("qaddsuffix", 1+2, 1+2, 1, func_q_addsuffix_addprefix), + FT_ENTRY ("qbasename", 1+0, 1+1, 1, func_q_basename_dir), + FT_ENTRY ("qdir", 1+0, 1+1, 1, func_q_basename_dir), + FT_ENTRY ("qnotdir", 1+0, 1+1, 1, func_q_notdir), +# ifdef CONFIG_WITH_ROOT_FUNC + FT_ENTRY ("qroot", 1+0, 1+1, 1, func_q_root), + FT_ENTRY ("qnotroot", 1+0, 1+1, 1, func_q_notroot), +# endif + FT_ENTRY ("qsuffix", 1+0, 1+1, 1, func_q_suffix), + FT_ENTRY ("qrealpath", 1+0, 1+1, 1, func_q_realpath), +# ifdef CONFIG_WITH_ABSPATHEX + FT_ENTRY ("qabspathex", 1+0, 1+2, 1, func_q_abspathex), +# endif + FT_ENTRY ("qwildcard", 1+0, 1+1, 1, func_q_wildcard), + FT_ENTRY ("qone-unquoted",1+1, 1+1, 1, func_q_one_unquoted), + FT_ENTRY ("qtarget", 1+1, 1+1, 1, func_q_one_unquoted), /* For requoting plain $@ to given style. */ + FT_ENTRY ("qdeps", 1+1, 1+2, 1, func_q_deps), /* $^ with quoting style */ + FT_ENTRY ("qdeps-all", 1+1, 1+2, 1, func_q_deps), /* $+ with quoting style */ + FT_ENTRY ("qdeps-newer", 1+1, 1+2, 1, func_q_deps_newer), /* $? with quoting style */ + FT_ENTRY ("qdeps-oo", 1+1, 1+2, 1, func_q_deps_order_only), /* $| with quoting style */ + FT_ENTRY ("qfilter", 1+2, 1+2, 1, func_q_filter_filterout), + FT_ENTRY ("qfilter-out", 1+2, 1+2, 1, func_q_filter_filterout), + /** @todo XXX: Add more qxxxx variants. */ +#endif +}; + +#define FUNCTION_TABLE_ENTRIES (sizeof (function_table_init) / sizeof (struct function_table_entry)) + + +/* These must come after the definition of function_table. */ + +static char * +expand_builtin_function (char *o, int argc, char **argv, + const struct function_table_entry *entry_p) +{ + char *p; + + if (argc < (int)entry_p->minimum_args) + fatal (*expanding_var, strlen (entry_p->name), + _("insufficient number of arguments (%d) to function '%s'"), + argc, entry_p->name); + + /* I suppose technically some function could do something with no arguments, + but so far no internal ones do, so just test it for all functions here + rather than in each one. We can change it later if necessary. */ + + if (!argc && !entry_p->alloc_fn) + return o; + + if (!entry_p->fptr.func_ptr) + OS (fatal, *expanding_var, + _("unimplemented on this platform: function '%s'"), entry_p->name); + + if (!entry_p->alloc_fn) + return entry_p->fptr.func_ptr (o, argv, entry_p->name); + + /* This function allocates memory and returns it to us. + Write it to the variable buffer, then free it. */ + + p = entry_p->fptr.alloc_func_ptr (entry_p->name, argc, argv); + if (p) + { + o = variable_buffer_output (o, p, strlen (p)); + free (p); + } + + return o; +} + +/* Check for a function invocation in *STRINGP. *STRINGP points at the + opening ( or { and is not null-terminated. If a function invocation + is found, expand it into the buffer at *OP, updating *OP, incrementing + *STRINGP past the reference and returning nonzero. If not, return zero. */ + +static int +handle_function2 (const struct function_table_entry *entry_p, char **op, const char **stringp) /* bird split it up. */ +{ + char openparen = (*stringp)[0]; + char closeparen = openparen == '(' ? ')' : '}'; + const char *beg; + const char *end; + int count = 0; + char *abeg = NULL; + char **argv, **argvp; + int nargs; + + beg = *stringp + 1; + + /* We found a builtin function. Find the beginning of its arguments (skip + whitespace after the name). */ + + beg += entry_p->len; + NEXT_TOKEN (beg); + + /* Find the end of the function invocation, counting nested use of + whichever kind of parens we use. Since we're looking, count commas + to get a rough estimate of how many arguments we might have. The + count might be high, but it'll never be low. */ + + for (nargs=1, end=beg; *end != '\0'; ++end) + if (*end == ',') + ++nargs; + else if (*end == openparen) + ++count; + else if (*end == closeparen && --count < 0) + break; + + if (count >= 0) + fatal (*expanding_var, strlen (entry_p->name), + _("unterminated call to function '%s': missing '%c'"), + entry_p->name, closeparen); + + *stringp = end; + + /* Get some memory to store the arg pointers. */ + argvp = argv = alloca (sizeof (char *) * (nargs + 2)); + + /* Chop the string into arguments, then a nul. As soon as we hit + MAXIMUM_ARGS (if it's >0) assume the rest of the string is part of the + last argument. + + If we're expanding, store pointers to the expansion of each one. If + not, make a duplicate of the string and point into that, nul-terminating + each argument. */ + + if (entry_p->expand_args) + { + const char *p; + for (p=beg, nargs=0; p <= end; ++argvp) + { + const char *next; + + ++nargs; + + if (nargs == entry_p->maximum_args + || (! (next = find_next_argument (openparen, closeparen, p, end)))) + next = end; + + *argvp = expand_argument (p, next); + p = next + 1; + } + } + else + { + int len = end - beg; + char *p, *aend; + + abeg = xmalloc (len+1); + memcpy (abeg, beg, len); + abeg[len] = '\0'; + aend = abeg + len; + + for (p=abeg, nargs=0; p <= aend; ++argvp) + { + char *next; + + ++nargs; + + if (nargs == entry_p->maximum_args + || (! (next = find_next_argument (openparen, closeparen, p, aend)))) + next = aend; + + *argvp = p; + *next = '\0'; + p = next + 1; + } + } + *argvp = NULL; + + /* Finally! Run the function... */ + *op = expand_builtin_function (*op, nargs, argv, entry_p); + + /* Free memory. */ + if (entry_p->expand_args) + for (argvp=argv; *argvp != 0; ++argvp) + free (*argvp); + else + free (abeg); + + return 1; +} + + +int /* bird split it up and hacked it. */ +#ifndef CONFIG_WITH_VALUE_LENGTH +handle_function (char **op, const char **stringp) +{ + const struct function_table_entry *entry_p = lookup_function (*stringp + 1); + if (!entry_p) + return 0; + return handle_function2 (entry_p, op, stringp); +} +#else /* CONFIG_WITH_VALUE_LENGTH */ +handle_function (char **op, const char **stringp, const char *nameend, const char *eol UNUSED) +{ + const char *fname = *stringp + 1; + const struct function_table_entry *entry_p = + lookup_function_in_hash_tab (fname, nameend - fname); + if (!entry_p) + return 0; + return handle_function2 (entry_p, op, stringp); +} +#endif /* CONFIG_WITH_VALUE_LENGTH */ + +#ifdef CONFIG_WITH_COMPILER +/* Used by the "compiler" to get all info about potential functions. */ +make_function_ptr_t +lookup_function_for_compiler (const char *name, unsigned int len, + unsigned char *minargsp, unsigned char *maxargsp, + char *expargsp, const char **funcnamep) +{ + const struct function_table_entry *entry_p = lookup_function (name, len); + if (!entry_p) + return 0; + *minargsp = entry_p->minimum_args; + *maxargsp = entry_p->maximum_args; + *expargsp = entry_p->expand_args; + *funcnamep = entry_p->name; + return entry_p->func_ptr; +} +#endif /* CONFIG_WITH_COMPILER */ + + +/* User-defined functions. Expand the first argument as either a builtin + function or a make variable, in the context of the rest of the arguments + assigned to $1, $2, ... $N. $0 is the name of the function. */ + +static char * +func_call (char *o, char **argv, const char *funcname UNUSED) +{ + static int max_args = 0; + char *fname; + char *body; + int flen; + int i; + int saved_args; + const struct function_table_entry *entry_p; + struct variable *v; +#ifdef CONFIG_WITH_EVALPLUS + char *buf; + unsigned int len; +#endif +#ifdef CONFIG_WITH_VALUE_LENGTH + char *fname_end; +#endif +#if defined (CONFIG_WITH_EVALPLUS) || defined (CONFIG_WITH_VALUE_LENGTH) + char num[11]; +#endif + + /* Clean up the name of the variable to be invoked. */ + fname = next_token (argv[0]); +#ifndef CONFIG_WITH_VALUE_LENGTH + end_of_token (fname)[0] = '\0'; +#else + fname_end = end_of_token (fname); + *fname_end = '\0'; +#endif + + /* Calling nothing is a no-op */ +#ifndef CONFIG_WITH_VALUE_LENGTH + if (*fname == '\0') +#else + if (fname == fname_end) +#endif + return o; + + /* Are we invoking a builtin function? */ + +#ifndef CONFIG_WITH_VALUE_LENGTH + entry_p = lookup_function (fname); +#else + entry_p = lookup_function (fname, fname_end - fname); +#endif + if (entry_p) + { + /* How many arguments do we have? */ + for (i=0; argv[i+1]; ++i) + ; + return expand_builtin_function (o, i, argv+1, entry_p); + } + + /* Not a builtin, so the first argument is the name of a variable to be + expanded and interpreted as a function. Find it. */ + flen = strlen (fname); + + v = lookup_variable (fname, flen); + + if (v == 0) + warn_undefined (fname, flen); + + if (v == 0 || *v->value == '\0') + return o; + + body = alloca (flen + 4); + body[0] = '$'; + body[1] = '('; + memcpy (body + 2, fname, flen); + body[flen+2] = ')'; + body[flen+3] = '\0'; + + /* Set up arguments $(1) .. $(N). $(0) is the function name. */ + + push_new_variable_scope (); + + for (i=0; *argv; ++i, ++argv) +#ifdef CONFIG_WITH_VALUE_LENGTH + define_variable (num, sprintf (num, "%d", i), *argv, o_automatic, 0); +#else + { + char num[11]; + + sprintf (num, "%d", i); + define_variable (num, strlen (num), *argv, o_automatic, 0); + } +#endif + +#ifdef CONFIG_WITH_EVALPLUS + /* $(.ARGC) is the argument count. */ + + len = sprintf (num, "%d", i - 1); + define_variable_vl (".ARGC", sizeof (".ARGC") - 1, num, len, + 1 /* dup val */, o_automatic, 0); +#endif + + /* If the number of arguments we have is < max_args, it means we're inside + a recursive invocation of $(call ...). Fill in the remaining arguments + in the new scope with the empty value, to hide them from this + invocation. */ + + for (; i < max_args; ++i) +#ifdef CONFIG_WITH_VALUE_LENGTH + define_variable (num, sprintf (num, "%d", i), "", o_automatic, 0); +#else + { + char num[11]; + + sprintf (num, "%d", i); + define_variable (num, strlen (num), "", o_automatic, 0); + } +#endif + + saved_args = max_args; + max_args = i; + +#ifdef CONFIG_WITH_EVALPLUS + if (!strcmp (funcname, "call")) + { +#endif + /* Expand the body in the context of the arguments, adding the result to + the variable buffer. */ + + v->exp_count = EXP_COUNT_MAX; +#ifndef CONFIG_WITH_VALUE_LENGTH + o = variable_expand_string (o, body, flen+3); + v->exp_count = 0; + + o += strlen (o); +#else /* CONFIG_WITH_VALUE_LENGTH */ + variable_expand_string_2 (o, body, flen+3, &o); + v->exp_count = 0; +#endif /* CONFIG_WITH_VALUE_LENGTH */ +#ifdef CONFIG_WITH_EVALPLUS + } + else + { + const floc *reading_file_saved = reading_file; + char *eos; + + if (!strcmp (funcname, "evalcall")) + { + /* Evaluate the variable value without expanding it. We + need a copy since eval_buffer is destructive. */ + + size_t off = o - variable_buffer; + eos = variable_buffer_output (o, v->value, v->value_length + 1) - 1; + o = variable_buffer + off; + if (v->fileinfo.filenm) + reading_file = &v->fileinfo; + } + else + { + /* Expand the body first and then evaluate the output. */ + + v->exp_count = EXP_COUNT_MAX; + o = variable_expand_string_2 (o, body, flen+3, &eos); + v->exp_count = 0; + } + + install_variable_buffer (&buf, &len); + eval_buffer (o, NULL, eos); + restore_variable_buffer (buf, len); + reading_file = reading_file_saved; + + /* Deal with the .RETURN value if present. */ + + v = lookup_variable_in_set (".RETURN", sizeof (".RETURN") - 1, + current_variable_set_list->set); + if (v && v->value_length) + { + if (v->recursive && !IS_VARIABLE_RECURSIVE_WITHOUT_DOLLAR (v)) + { + v->exp_count = EXP_COUNT_MAX; + variable_expand_string_2 (o, v->value, v->value_length, &o); + v->exp_count = 0; + } + else + o = variable_buffer_output (o, v->value, v->value_length); + } + } +#endif /* CONFIG_WITH_EVALPLUS */ + + max_args = saved_args; + + pop_variable_scope (); + + return o; +} + +void +define_new_function (const floc *flocp, const char *name, + unsigned int min, unsigned int max, unsigned int flags, + gmk_func_ptr func) +{ + const char *e = name; + struct function_table_entry *ent; + size_t len; + + while (STOP_SET (*e, MAP_USERFUNC)) + e++; + len = e - name; + + if (len == 0) + O (fatal, flocp, _("Empty function name")); + if (*name == '.' || *e != '\0') + OS (fatal, flocp, _("Invalid function name: %s"), name); + if (len > 255) + OS (fatal, flocp, _("Function name too long: %s"), name); + if (min > 255) + ONS (fatal, flocp, + _("Invalid minimum argument count (%u) for function %s"), min, name); + if (max > 255 || (max && max < min)) + ONS (fatal, flocp, + _("Invalid maximum argument count (%u) for function %s"), max, name); + + ent = xmalloc (sizeof (struct function_table_entry)); + ent->name = name; + ent->len = len; + ent->minimum_args = min; + ent->maximum_args = max; + ent->expand_args = ANY_SET(flags, GMK_FUNC_NOEXPAND) ? 0 : 1; + ent->alloc_fn = 1; + ent->fptr.alloc_func_ptr = func; + + hash_insert (&function_table, ent); +} + +void +hash_init_function_table (void) +{ + hash_init (&function_table, FUNCTION_TABLE_ENTRIES * 2, + function_table_entry_hash_1, function_table_entry_hash_2, + function_table_entry_hash_cmp); + hash_load (&function_table, function_table_init, + FUNCTION_TABLE_ENTRIES, sizeof (struct function_table_entry)); +#if defined (CONFIG_WITH_OPTIMIZATION_HACKS) || defined (CONFIG_WITH_VALUE_LENGTH) + { + unsigned int i; + for (i = 0; i < FUNCTION_TABLE_ENTRIES; i++) + { + const char *fn = function_table_init[i].name; + while (*fn) + { + func_char_map[(int)*fn] = 1; + fn++; + } + assert (function_table_init[i].len <= MAX_FUNCTION_LENGTH); + assert (function_table_init[i].len >= MIN_FUNCTION_LENGTH); + } + } +#endif +} |