Index: /trunk/src/kmk/function.c
===================================================================
--- /trunk/src/kmk/function.c	(revision 3318)
+++ /trunk/src/kmk/function.c	(revision 3319)
@@ -5799,4 +5799,1460 @@
 }
 
+/* Helper for performer GNU make style quoting of one filename. */
+
+static char *helper_quote_make (char *o, const char *name, int is_dep, int is_tgt,
+                                int quote_trailing_slashes, const char *funcname)
+{
+  unsigned const map_flags = MAP_NUL
+                           | 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;
+  if (*cur)
+    {
+      unsigned long len_out = 0;
+      const char *prev = cur;
+      for (;;)
+        {
+          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;
+                }
+
+              if (flags & MAP_NUL)
+                break;
+
+              /* Dollar is quoted by duplicating the dollar: */
+              if (flags & MAP_VARIABLE)
+                {
+                  prev = cur++;
+                  o = variable_buffer_output (o, "$", 1);
+                }
+              /* 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++;
+                }
+            }
+        }
+
+      /* 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 (message, 0, "%s: cannot quote empty string", funcname);
+  return o;
+}
+
+/* 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';
+  unsigned const map_flags = MAP_NUL
+                           | MAP_BLANK
+                           | MAP_NEWLINE
+                           | MAP_COMMENT
+                           | MAP_VARIABLE
+                           | MAP_SEMI
+                           | MAP_EQUALS
+                           | (is_dep ? MAP_PIPE :
+                              is_tgt ? MAP_COLON : 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, 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, is_dep, is_tgt, qts, funcname);
+            }
+        }
+      else
+          OS (message, 0, "%s: cannot munge empty string", funcname);
+    }
+
+  return o;
+}
+
+/* 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;
+}
+
+/* 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]));
+}
+
+
+/* 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;
+}
+
+/* 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]));
+}
+
+/* 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;
+}
+
+/* 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);
+    }
+}
+
+
+#define Q_RET_MASK              0x000f
+#define Q_RET_UNQUOTED          0x0000
+#define Q_RET_QUOTED            0x0001
+#define Q_RET_QUOTED_DEP        0x0002
+#define Q_RET_QUOTED_DEP_END    0x0003
+#define Q_RET_QUOTED_TGT        0x0004
+#define Q_RET_QUOTED_TGT_END    0x0005
+#define Q_RET_SHELL             0x0006
+#define Q_RET_SHELL_IN_DQ       0x0007
+#define Q_RET_SHELL_IN_SQ       0x0008
+
+#define Q_IN_MASK               0x0030
+#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_SEP_COMMA          0x0040  /* 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              0x0000
+#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
+
+
+/* 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_return_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;
+              else if (MATCH ("shell-in-sq")    || MATCH ("sh-i-s"))
+                intstyle = (intstyle & ~Q_RET_MASK) | Q_RET_SHELL_IN_SQ;
+              /* 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-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 = (intstyle & ~Q_SEP_MASK) | 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 return style: %s", start);
+                  *style = savedch;
+                }
+#undef MATCH
+            }
+          else
+            break;
+        }
+    }
+  return intstyle;
+}
+
+/* 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);
+}
+
+/* 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 (file[len] == '\0');
+  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, 0 /*is_dep*/, 0 /*is_tgt*/,
+                               !is_last /*quote_trailing_slashes*/, NULL);
+        break;
+      case Q_RET_QUOTED_DEP:
+        o = helper_quote_make (o, file, 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, 1 /*is_dep*/, 0 /*is_tgt*/,
+                               0 /*quote_trailing_slashes*/, NULL);
+        break;
+      case Q_RET_QUOTED_TGT:
+        o = helper_quote_make (o, file, 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, 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);
+}
+
+/* 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. */
+
+static struct nameseq *
+helper_parse_file_list (char *filelist, unsigned int style, int glob)
+{
+  if (filelist && *filelist != '\0')
+    switch (style & (Q_IN_MASK | Q_IN_SEP_COMMA))
+      {
+        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);
+         }
+
+        /* Following works recursively. Mainly for VMS. */
+        case Q_IN_SEP_COMMA | Q_IN_UNQUOTED:
+        case Q_IN_SEP_COMMA | Q_IN_QUOTED:
+        case Q_IN_SEP_COMMA | Q_IN_QUOTED_DEP: /** @todo ?? */
+        case Q_IN_SEP_COMMA | Q_IN_QUOTED_TGT: /** @todo ?? */
+          {
+            size_t len = strlen (filelist);
+            char *comma = (char *)memchr (filelist, ',', len);
+            struct nameseq *chain;
+            if (!comma)
+              chain = helper_parse_file_list (filelist, style & ~Q_IN_SEP_COMMA, 0);
+            else
+              {
+                char *copy;
+                char *start;
+                start = copy = xmalloc (len + 1);
+                memcpy (copy, filelist, len + 1);
+                comma = copy + (comma - filelist);
+                do
+                  {
+                    *comma = ' ';
+                    len -= comma - start - 1;
+                    if (len)
+                      {
+                        start = comma + 1;
+                        comma = (char *)memchr (start, ',', len);
+                      }
+                    else
+                      break;
+                  }
+                while (comma != NULL);
+
+                chain = helper_parse_file_list (filelist, style & ~Q_IN_SEP_COMMA, 0);
+
+                free (copy);
+              }
+            return chain;
+          }
+
+        default:
+          assert (0);
+          return NULL;
+      }
+  return NULL;
+}
+
+/* $(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)
+{
+  unsigned int const style = helper_file_return_style (argv[1], Q_QDEFAULT);
+  char *line = argv[0];
+  if (line && *line != '\0')
+    {
+      struct nameseq *chain = helper_parse_file_list (line, style, 0);
+      if (chain)
+        {
+          o = helper_return_file (o, chain->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)
+{
+  unsigned int const style = helper_file_return_style (argv[1], Q_QDEFAULT);
+  char *line = argv[0];
+  if (line && *line != '\0')
+    {
+      struct nameseq *chain = helper_parse_file_list (line, 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;
+}
+
+/* $(filelist start, end, file1..fileN [, style]) - 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)
+{
+  unsigned int const style = helper_file_return_style (argv[3], Q_QDEFAULT);
+  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;
+}
+
+/* $(countfiles file1 file2 ... fileN[,style]) - 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)
+{
+  unsigned int const style = helper_file_return_style (argv[1], Q_QDEFAULT); /* simpler */
+  char retval[32];
+  unsigned int files = 0;
+  char *line = argv[0];
+  if (line && *line != '\0')
+    {
+      struct nameseq *cur;
+      struct nameseq *chain = helper_parse_file_list (line, style, 0);
+      for (cur = chain; cur; cur = cur->next)
+        files++;
+      free_ns_chain_no_strcache (chain);
+    }
+
+  return variable_buffer_output (o, retval, sprintf (retval, "%u", files));
+}
+
+/* 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
+}
+
+/* $(foreachfile var, filelist, body [, style]) - same as $(foreach ), except
+   for file rather than word tokens and flexible variable value encoding.
+   See also func_foreach(). */
+
+static char *
+func_foreachfile (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
+
+  unsigned int const style = helper_file_return_style (argv[3], Q_QDEFAULT);
+  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;
+}
+
+/* $(sortfiles file1 ... fileN [,style]) and
+   $(rsortfiles file1 ... fileN [,style]) - same to $(sort ) and $(rsort ),
+   except for files rather than word tokens.  See func_sort(). */
+
+static char *func_sortfiles (char *o, char **argv, const char *funcname UNUSED)
+{
+  unsigned int const style = helper_file_return_style (argv[1], Q_QDEFAULT);
+  char *line = argv[0];
+  if (line && *line != '\0')
+    {
+      unsigned int num_files = 0;
+      struct nameseq *cur;
+      struct nameseq *chain = helper_parse_file_list (line, style, 0);
+      for (cur = chain; cur; cur = cur->next)
+        num_files++;
+      if (num_files > 0)
+        {
+          char *prev_file;
+          char **files = (char **)xmalloc (num_files * sizeof (char *));
+          unsigned int idx = 0;
+          for (cur = chain; cur; cur = cur->next)
+            files[idx++] = (char *)cur->name;
+
+          qsort (files, num_files, sizeof (char *), alpha_compare);
+
+          prev_file = NULL;
+          if (funcname[0] == 'r')
+            {
+              idx = num_files;
+              while (idx-- > 0)
+                if (prev_file == NULL || strcmp (files[idx], prev_file) != 0)
+                  {
+                    prev_file = files[idx];
+                    o = helper_return_file (o, files[idx], style, idx == 0);
+                  }
+            }
+          else
+            for (idx = 0; idx < num_files; idx++)
+              if (prev_file == NULL || strcmp (files[idx], prev_file) != 0)
+                {
+                  prev_file = files[idx];
+                  o = helper_return_file(o, files[idx], style,
+                                         idx + 1 == num_files);
+                }
+
+          free (files);
+        }
+      free_ns_chain_no_strcache (chain);
+    }
+
+  return o;
+}
+
+/* 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
+  (void)len;
+  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);
+      int doneany = 0;
+      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';
+                }
+
+              if (abspath (cur->name, outbuf))
+                {
+                  free ((char *)cur->name);
+                  cur->name = xstrdup (outbuf);
+                  prev = 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 file1 file2 ... fileN [, style]) - same to $(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[0], NULL, 0,
+                         helper_file_return_style (argv[1], Q_QDEFAULT));
+}
+
+# ifdef CONFIG_WITH_ABSPATHEX
+/* $(qabspathex file1 file2 ... fileN [,cwd [, style]]) - same to $(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)
+{
+  char *cwd = argv[1];
+  char *style = cwd ? argv[2] : NULL;
+
+  /* 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 = NULL;
+    }
+
+  return worker_abspath (o, argv[0], cwd, cwd ? strlen (cwd) : 0,
+                         helper_file_return_style (style, Q_QDEFAULT));
+}
+# endif
+
+/* $(qaddprefix prefix, file1 ... fileN [, style]) and
+   $(qaddsuffix prefix, file1 ... fileN [, style]) - same to $(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_return_style (argv[1], Q_QDEFAULT);
+  const char * const fix = argv[0];
+  size_t const fixlen = strlen (fix);
+  char *line = argv[0];
+  if (line && *line != '\0')
+    {
+      size_t tmpsize = (fixlen + 512) & ~(size_t)63;
+      char *tmp = (char *)xmalloc (tmpsize);
+      struct nameseq *cur;
+      struct nameseq *chain = helper_parse_file_list (line, style, 0);
+
+      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 path1 .. pathN[, style]) and $(qdir path1 .. pathN[, style])
+   - same to $(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_return_style (argv[1], Q_QDEFAULT_VMS_TRICKS);
+  struct nameseq *chain = helper_parse_file_list (argv[0], 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 path1 ... pathN[, style]) - 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_return_style (argv[1], Q_QDEFAULT_VMS_TRICKS);
+  struct nameseq *chain = helper_parse_file_list (argv[0], style, 0);
+  struct nameseq *cur;
+  int const stop = MAP_DIRSEP;
+
+  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 path1 ... pathN[, style]) - 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_return_style (argv[1], Q_QDEFAULT_VMS_TRICKS);
+  struct nameseq *chain = helper_parse_file_list (argv[0], style, 0);
+  struct nameseq *prev;
+  struct nameseq *cur;
+  int const stop = MAP_DIRSEP | MAP_DOT;
+
+  /* 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 path...pathN [,style]) - 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_return_style (argv[0] ? argv[1] : NULL, Q_QDEFAULT);
+  struct nameseq *chain = helper_parse_file_list (argv[0], 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 path1 .. pathN [, style]) - 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_return_style (argv[0] ? argv[1] : NULL, Q_QDEFAULT);
+  struct nameseq *chain = helper_parse_file_list (argv[0], 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 path1 .. pathN [, style]) - 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_return_style (argv[1], Q_QDEFAULT);
+  struct nameseq *chain = helper_parse_file_list (argv[0], 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_return_style (argv[1], Q_QDEFAULT);
+  struct nameseq *chain = helper_parse_file_list (argv[0], 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);
+}
+
 #endif /* KMK */
 
@@ -5994,4 +7450,41 @@
   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),
+  /* Quoted input and maybe output variants of functions typically
+     working with files: */
+  FT_ENTRY ("firstfile",     0, 1+1, 1,  func_firstfile),
+  FT_ENTRY ("lastfile",      0, 1+1, 1,  func_lastfile),
+  FT_ENTRY ("filelist",      3, 3+1, 1,  func_filelist),
+  FT_ENTRY ("countfiles",    0, 1+1, 1,  func_countfiles),
+  FT_ENTRY ("foreachfile",   3, 3+1, 0,  func_foreachfile),
+  FT_ENTRY ("sortfiles",     0, 1+1, 1,  func_sortfiles),
+# ifdef CONFIG_WITH_RSORT
+  FT_ENTRY ("rsortfiles",    0, 1+1, 1,  func_sortfiles),
+# endif
+  FT_ENTRY ("qabspath",      0, 1+1, 1,  func_q_abspath),
+  FT_ENTRY ("qaddprefix",    2, 2+1, 1,  func_q_addsuffix_addprefix),
+  FT_ENTRY ("qaddsuffix",    2, 2+1, 1,  func_q_addsuffix_addprefix),
+  FT_ENTRY ("qbasename",     0, 1+1, 1,  func_q_basename_dir),
+  FT_ENTRY ("qdir",          0, 1+1, 1,  func_q_basename_dir),
+  FT_ENTRY ("qnotdir",       0, 1+1, 1,  func_q_notdir),
+///@todo # ifdef CONFIG_WITH_ROOT_FUNC
+///@todo   FT_ENTRY ("qroot",         0, 1+1, 1,  func_q_root),
+///@todo   FT_ENTRY ("qnotroot",      0, 1+1, 1,  func_q_notroot),
+///@todo # endif
+  FT_ENTRY ("qsuffix",       0, 1+1, 1,  func_q_suffix),
+  FT_ENTRY ("qrealpath",     0, 1+1, 1,  func_q_realpath),
+# ifdef CONFIG_WITH_ABSPATHEX
+  FT_ENTRY ("qabspathex",    0, 2+1, 1,  func_q_abspathex),
+# endif
+  FT_ENTRY ("qwildcard",     0, 1+1, 1,  func_q_wildcard),
 #endif
 };
Index: /trunk/src/kmk/variable.c
===================================================================
--- /trunk/src/kmk/variable.c	(revision 3318)
+++ /trunk/src/kmk/variable.c	(revision 3319)
@@ -1765,5 +1765,5 @@
   && defined (KMK_HELPERS)
   define_variable_cname ("KMK_FEATURES",
-                         "append-dash-n abspath includedep-queue install-hard-linking umask"
+                         "append-dash-n abspath includedep-queue install-hard-linking umask quote"
                          " kBuild-define"
                          " rsort"
@@ -1796,5 +1796,5 @@
 # else /* MSC can't deal with strings mixed with #if/#endif, thus the slow way. */
 #  error "All features should be enabled by default!"
-  strcpy (buf, "append-dash-n abspath includedep-queue install-hard-linking umask"
+  strcpy (buf, "append-dash-n abspath includedep-queue install-hard-linking umask quote"
                " kBuild-define");
 #  if defined (CONFIG_WITH_RSORT)
