m4-patches
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

branch-1_4 maketemp cleanup


From: Eric Blake
Subject: branch-1_4 maketemp cleanup
Date: Thu, 19 Oct 2006 23:00:01 +0000 (UTC)
User-agent: Loom/3.14 (http://gmane.org/)

In the maketemp builtin, we were invoking undefined behavior.  For example, on 
cygwin:
$ ls
$ echo 'maketemp(foo)' | m4
$ ls
foo
$

whereas on mingw:
$ echo 'maketemp(bar)' | m4
m4.exe:stdin:1: cannot create tempfile `bar': Invalid argument
$


POSIX states that mkstemp(3) is undefined if there are less than 6 trailing 'X' 
characters.  I'd rather have well-defined semantics, not to mention more user-
friendly behavior, by having the builtin supply the missing `X' characters.

And while I was at it, I implemented the mkstemp macro, which is the Austin 
group's recommended solution to my aardvark that the POSIX semantics of 
maketemp are insecure.  For the branch, maketemp silently behaves like mkstemp 
in GNU mode, and warns when behaving in traditional mode (it has to be warning-
free in GNU mode to avoid breaking autom4te's m4_maketemp; although I provided 
a parallel patch to autoconf 2.61 to start the transition period so that 
autoconf will quit using maketemp).  But for head, we can go ahead and always 
warn that uses of maketemp are obsolete, and to use mkstemp instead.

Would it make sense to also implement a mkdtemp macro for head?

branch:
2006-10-19  Eric Blake  <address@hidden>

        * src/builtin.c (mkstemp_helper, m4_mkstemp): New functions.
        (m4_maketemp): Provide traditional behavior.
        * doc/m4.texinfo (Mkstemp): Rename from Maketemp.  Document the
        new `mkstemp' macro and the flaws of the old `maketemp'.
        (Incompatibilities): Move maketemp discussion to...
        (Extensions): ...here, since -G now supresses the GNU extension.
        * NEWS: Document this.

Index: NEWS
===================================================================
RCS file: /sources/m4/m4/NEWS,v
retrieving revision 1.1.1.1.2.72
diff -u -r1.1.1.1.2.72 NEWS
--- NEWS        13 Oct 2006 22:25:32 -0000      1.1.1.1.2.72
+++ NEWS        19 Oct 2006 20:37:24 -0000
@@ -26,6 +26,16 @@
   M4 must use temporary files, the environment variable $TMPDIR is now
   consulted, and a better effort is made to clean up those files in the
   event of a fatal signal.
+* The `mkstemp' builtin is added with the same GNU semantics as `maketemp',
+  based on the recommendation of POSIX to deprecate the POSIX semantics of
+  `maketemp' as inherently insecure.  In GNU mode (no -G supplied on the
+  command line), `maketemp' silently retains the secure GNU semantics, but
+  a future release of M4 will change this to emit a warning.  In
+  traditional mode (m4 -G), `maketemp' now uses the POSIX-mandated insecure
+  semantics, and issues a warning that you should convert your script to
+  use `mkstemp' instead.  Additionally, `mkstemp' and `maketemp' are now
+  well-defined even if the template argument does not end in six `X'
+  characters.
 
 Version 1.4.7 - 25 September 2006, by Eric Blake  (CVS version 1.4.6a)
 
Index: doc/m4.texinfo
===================================================================
RCS file: /sources/m4/m4/doc/m4.texinfo,v
retrieving revision 1.1.1.1.2.89
diff -u -r1.1.1.1.2.89 m4.texinfo
--- doc/m4.texinfo      19 Oct 2006 16:12:46 -0000      1.1.1.1.2.89
+++ doc/m4.texinfo      19 Oct 2006 20:37:24 -0000
@@ -242,7 +242,7 @@
 * Syscmd::                      Executing simple commands
 * Esyscmd::                     Reading the output of commands
 * Sysval::                      Exit status
-* Maketemp::                    Making temporary files
+* Mkstemp::                     Making temporary files
 
 Miscellaneous builtin macros
 
@@ -4283,7 +4283,7 @@
 * Syscmd::                      Executing simple commands
 * Esyscmd::                     Reading the output of commands
 * Sysval::                      Exit status
-* Maketemp::                    Making temporary files
+* Mkstemp::                     Making temporary files
 @end menu
 
 @node Platform macros
@@ -4520,45 +4520,83 @@
 @result{}2304
 @end example
 
address@hidden Maketemp
address@hidden Mkstemp
 @section Making temporary files
 
 @cindex temporary file names
 @cindex files, names of temporary
 Commands specified to @code{syscmd} or @code{esyscmd} might need a
 temporary file, for output or for some other purpose.
-There is a builtin macro, @code{maketemp}, for making temporary file
+There is a builtin macro, @code{mkstemp}, for making temporary file
 names:
 
address@hidden Builtin maketemp (@var{template})
address@hidden Builtin mkstemp (@var{template})
address@hidden Builtin maketemp (@var{template})
 Expands to a name of a new, empty file, made from the string
 @var{template}, which should end with the string @samp{XXXXXX}.  The six
address@hidden characters are then replaced with random data, in order to make
-the file name unique.
address@hidden characters are then replaced with random characters matching
+the regular expression @samp{[a-zA-Z0-9._-]}, in order to make the file
+name unique.  If fewer than six @samp{X} characters are found at the end
+of @code{template}, the result will be longer than the template.  The
+created file will have access permissions as if by @kbd{chmod +rw,go=},
+meaning that the current umask of the @code{m4} process is taken into
+account, and at most only the current user can read and write the file.
+
+The traditional behavior, standardized by @acronym{POSIX}, is that
address@hidden merely replaces the trailing @samp{X} with the process
+id, without creating a file, and without ensuring that the resulting
+string is a unique file name.  In part, this means that using the same
address@hidden twice in the same input file will result in the same
+expansion.  This behavior is a security hole, as it is very easy for
+another process to guess the name that will be generated, and thus
+interfere with a subsequent use of @code{syscmd} trying to manipulate
+that file name.  Hence, @acronym{POSIX} has recommended that all new
+implementations of @code{m4} provide the secure @code{mkstemp} builtin,
+and that users of @code{m4} check for its existence.
 
-The macro @code{maketemp} is recognized only with parameters.
+The macros @code{mkstemp} and @code{maketemp} are recognized only with
+parameters.
 @end deffn
 
+If you try this next example, you will most likely get different output
+for the two file names, since the replacement characters are randomly
+chosen:
+
 @comment ignore
 @example
 maketemp(`/tmp/fooXXXXXX')
 @result{}/tmp/fooa07346
+ifdef(`mkstemp', `define(`maketemp', defn(`mkstemp'))',
+      `define(`mkstemp', defn(`maketemp'))dnl
+errprint(`warning: potentially insecure maketemp implementation
+')')
address@hidden
+mkstemp(`doc')
address@hidden
address@hidden
 @end example
 
-Traditional implementations of @code{m4} replaced the trailing @samp{X}
-sequence with the process id, without creating the file; meaning you
-only get one result no matter how many times you use maketemp on the
-same string.  As of this release, @acronym{POSIX} is considering the
-addition of a new macro @code{mkstemp} that behaves like @acronym{GNU}
address@hidden, so a future version of @acronym{GNU} M4 may have
-changes in this area.
address@hidden @acronym{GNU} extensions
+Unless you use the @option{--traditional} command line option (or
address@hidden, @pxref{Limits control, , Invoking m4}), the @acronym{GNU}
+version of @code{maketemp} is secure.  This means that using the same
+template to multiple calls will generate multiple files.  However, we
+recommend that you use the new @code{mkstemp} macro, introduced in
address@hidden M4 1.4.8, which is secure even in traditional mode.
 
 @example
 define(`file1', maketemp(`fooXXXXXX'))dnl
-define(`file2', maketemp(`fooXXXXXX'))dnl
-ifelse(file1, file2, `same', `different')
address@hidden
-syscmd(`rm 'file1 file2)
+define(`file2', maketemp(`fooXX'))dnl
+define(`file3', mkstemp(`fooXXXXXX'))dnl
+ifelse(len(file1), len(file2), `same length', `different')
address@hidden length
+ifelse(file1, file2, `same', `different file')
address@hidden file
+ifelse(file2, file3, `same', `different file')
address@hidden file
+ifelse(file1, file3, `same', `different file')
address@hidden file
+syscmd(`rm 'file1 file2 file3)
 @result{}
 sysval
 @result{}0
@@ -5039,6 +5077,12 @@
 @item
 The destination of trace and debug output can be controlled with
 @code{debugfile} (@pxref{Debug Output}).
+
address@hidden
+The @code{maketemp} (@pxref{Mkstemp}) macro behaves like @code{mkstemp},
+creating a new file with a unique name on every invocation, rather than
+following the insecure behavior of replacing the trailing @samp{X}
+characters with the @code{m4} process id.
 @end itemize
 
 In addition to the above extensions, @acronym{GNU} @code{m4} implements the
@@ -5135,15 +5179,6 @@
 semantics.
 
 @item
address@hidden requires @code{maketemp} (@pxref{Maketemp}) to replace
-the trailing @samp{X} characters with the @code{m4} process id, giving
-the same result on identical input, without creating any files, which
-leaves the door open for a data race in which other processes can create
-a file by the same name.  @acronym{GNU} @code{m4} actually creates a temporary
-file for each invocation of @code{maketemp}, which means that the output
-of the macro is different even if the input is identical.
-
address@hidden
 @acronym{POSIX} requires @code{changequote(@var{arg})}
 (@pxref{Changequote}) to use newline as the close quote, but @acronym{GNU}
 @code{m4} uses @samp{'} as the close quote.  Meanwhile, some
Index: src/builtin.c
===================================================================
RCS file: /sources/m4/m4/src/Attic/builtin.c,v
retrieving revision 1.1.1.1.2.44
diff -u -r1.1.1.1.2.44 builtin.c
--- src/builtin.c       14 Oct 2006 14:14:54 -0000      1.1.1.1.2.44
+++ src/builtin.c       19 Oct 2006 20:37:24 -0000
@@ -73,6 +73,7 @@
 DECLARE (m4_m4exit);
 DECLARE (m4_m4wrap);
 DECLARE (m4_maketemp);
+DECLARE (m4_mkstemp);
 DECLARE (m4_patsubst);
 DECLARE (m4_popdef);
 DECLARE (m4_pushdef);
@@ -128,6 +129,7 @@
   { "m4exit",          FALSE,  FALSE,  FALSE,  m4_m4exit },
   { "m4wrap",          FALSE,  FALSE,  TRUE,   m4_m4wrap },
   { "maketemp",                FALSE,  FALSE,  TRUE,   m4_maketemp },
+  { "mkstemp",         FALSE,  FALSE,  TRUE,   m4_mkstemp },
   { "patsubst",                TRUE,   FALSE,  TRUE,   m4_patsubst },
   { "popdef",          FALSE,  FALSE,  TRUE,   m4_popdef },
   { "pushdef",         FALSE,  TRUE,   TRUE,   m4_pushdef },
@@ -1242,21 +1244,86 @@
 | Use the first argument as at template for a temporary file name.  |
 `------------------------------------------------------------------*/
 
+/* Add trailing 'X' to NAME if necessary, securely create the file,
+   and place the new file name on OBS.  */
 static void
-m4_maketemp (struct obstack *obs, int argc, token_data **argv)
+mkstemp_helper (struct obstack *obs, const char *name)
 {
   int fd;
+  int len;
+  int i;
+
+  /* Guarantee that there are six trailing 'X' characters, even if the
+     user forgot to supply them.  */
+  len = strlen (name);
+  obstack_grow (obs, name, len);
+  for (i = 0; len > 0 && i < 6; i++)
+    if (name[--len] != 'X')
+      break;
+  for (; i < 6; i++)
+    obstack_1grow (obs, 'X');
+  obstack_1grow (obs, '\0');
+
+  errno = 0;
+  fd = mkstemp (obstack_base (obs));
+  if (fd < 0)
+    {
+      M4ERROR ((0, errno, "cannot create tempfile `%s'", name));
+      obstack_free (obs, obstack_finish (obs));
+    }
+  else
+    close (fd);
+}
+
+static void
+m4_maketemp (struct obstack *obs, int argc, token_data **argv)
+{
   if (bad_argc (argv[0], argc, 2, 2))
     return;
-  errno = 0;
-  if ((fd = mkstemp (ARG (1))) < 0)
+  if (no_gnu_extensions)
     {
-      M4ERROR ((warning_status, errno, "cannot create tempfile `%s'",
-               ARG (1)));
-      return;
+      /* POSIX states "any trailing 'X' characters [are] replaced with
+        the current process ID as a string", without referencing the
+        file system.  Horribly insecure, but we have to do it when we
+        are in traditional mode.
+
+        For reference, Solaris m4 does:
+          maketemp() -> `'
+          maketemp(X) -> `X'
+          maketemp(XX) -> `Xn', where n is last digit of pid
+          maketemp(XXXXXXXX) -> `X00nnnnn', where nnnnn is 16-bit pid
+      */
+      const char *str = ARG (1);
+      int len = strlen (str);
+      int i;
+      int len2;
+
+      M4ERROR ((warning_status, 0, "recommend using mkstemp instead"));
+      for (i = len; i > 1; i--)
+       if (str[i - 1] != 'X')
+         break;
+      obstack_grow (obs, str, i);
+      str = ntoa ((eval_t) getpid (), 10);
+      len2 = strlen (str);
+      if (len2 > len - i)
+       obstack_grow0 (obs, str + len2 - (len - i), len - i);
+      else
+       {
+         while (i++ < len - len2)
+           obstack_1grow (obs, '0');
+         obstack_grow0 (obs, str, len2);
+       }
     }
-  close(fd);
-  obstack_grow (obs, ARG (1), strlen (ARG (1)));
+  else
+    mkstemp_helper (obs, ARG (1));
+}
+
+static void
+m4_mkstemp (struct obstack *obs, int argc, token_data **argv)
+{
+  if (bad_argc (argv[0], argc, 2, 2))
+    return;
+  mkstemp_helper (obs, ARG (1));
 }
 
 /*----------------------------------------.


head:
2006-10-19  Eric Blake  <address@hidden>

        * modules/m4.c (m4_make_temp, mkstemp): New functions.
        (maketemp): Add POSIX behavior and a warning.
        * tests/others.at (maketemp): Move this test...
        * tests/builtins.at (mkstemp): ...to here, and beef up.
        * tests/options.at (--safer): Update to new warning message.
        * doc/m4.texinfo (Mkstemp): Sync from branch.
        (Extensions): Update maketemp behavior.
        * NEWS: Document that maketemp now always warns.

Index: NEWS
===================================================================
RCS file: /sources/m4/m4/NEWS,v
retrieving revision 1.27
diff -u -r1.27 NEWS
--- NEWS        12 Oct 2006 02:44:26 -0000      1.27
+++ NEWS        19 Oct 2006 22:58:20 -0000
@@ -50,7 +50,12 @@
   the include path, rather than always searching `.' first.
 
 * New `--safer' command-line option cripples the potentially unsafe
-  macros `debugfile', `syscmd', `esyscmd', and `maketemp'.
+  macros `debugfile', `syscmd', `esyscmd', `maketemp', and `mkstemp'.
+
+* The `maketemp' builtin now always warns that it is obsolete, even in GNU
+  mode where it uses the same secure algorithm as `mkstemp', because of
+  the recommendation of POSIX to obsolete `maketemp' as inherently
+  insecure when obeying POSIX.
 
 * New `-b'/`--batch' command line option to force non-interactive mode.
   Also, in addition to `-e'/`--interactive' requesting interactive mode, m4
Index: doc/m4.texinfo
===================================================================
RCS file: /sources/m4/m4/doc/m4.texinfo,v
retrieving revision 1.67
diff -u -r1.67 m4.texinfo
--- doc/m4.texinfo      19 Oct 2006 16:19:20 -0000      1.67
+++ doc/m4.texinfo      19 Oct 2006 22:58:20 -0000
@@ -258,7 +258,7 @@
 * Syscmd::                      Executing simple commands
 * Esyscmd::                     Reading the output of commands
 * Sysval::                      Exit status
-* Maketemp::                    Making temporary files
+* Mkstemp::                     Making temporary files
 
 Miscellaneous builtin macros
 
@@ -644,7 +644,7 @@
 format and meaning of @var{RESYNTAX-SPEC}.
 
 @item --safer
-Cripple the builtins @code{maketemp} (@pxref{Maketemp}),
+Cripple the builtins @code{maketemp}, @code{mkstemp} (@pxref{Mkstemp}),
 @code{debugfile} (@pxref{Debugfile}), @code{syscmd} (@pxref{Syscmd}),
 and @code{esyscmd} (@pxref{Esyscmd}), since they can perform potentially
 unsafe actions.  An attempt to use these macros will result in an error.
@@ -5024,7 +5024,7 @@
 * Syscmd::                      Executing simple commands
 * Esyscmd::                     Reading the output of commands
 * Sysval::                      Exit status
-* Maketemp::                    Making temporary files
+* Mkstemp::                     Making temporary files
 @end menu
 
 @node Platform macros
@@ -5158,44 +5158,124 @@
 @result{}0
 @end example
 
address@hidden Maketemp
address@hidden Mkstemp
 @section Making temporary files
 
 @cindex temporary file names
 @cindex files, names of temporary
 Commands specified to @code{syscmd} or @code{esyscmd} might need a
-temporary file, for output or for some other purpose.
+temporary file, for output or for some other purpose.  There is a
+builtin macro, @code{mkstemp}, for making a temporary file:
 
address@hidden {Builtin (m4)} maketemp (@var{template})
-There is a builtin macro, @code{maketemp}, for making temporary file
-names, which expands to a name of a new, empty file, made from the
-string @var{template}, which should end with the string @samp{XXXXXX}.
-The six @code{X}'s are then replaced, usually with something that
-includes the process id of the @code{m4} process, in order to make the
-file name unique.
address@hidden {Builtin (m4)} mkstemp (@var{template})
address@hidden {Builtin (m4)} maketemp (@var{template})
+Expands to a name of a new, empty file, made from the string
address@hidden, which should end with the string @samp{XXXXXX}.  The six
address@hidden characters are then replaced with random characters matching
+the regular expression @samp{[a-zA-Z0-9._-]}, in order to make the file
+name unique.  If fewer than six @samp{X} characters are found at the end
+of @code{template}, the result will be longer than the template.  The
+created file will have access permissions as if by @kbd{chmod +rw,go=},
+meaning that the current umask of the @code{m4} process is taken into
+account, and at most only the current user can read and write the file.
+
+The traditional behavior, standardized by @acronym{POSIX}, is that
address@hidden merely replaces the trailing @samp{X} with the process
+id, without creating a file, and without ensuring that the resulting
+string is a unique file name.  In part, this means that using the same
address@hidden twice in the same input file will result in the same
+expansion.  This behavior is a security hole, as it is very easy for
+another process to guess the name that will be generated, and thus
+interfere with a subsequent use of @code{syscmd} trying to manipulate
+that file name.  Hence, @acronym{POSIX} has recommended that all new
+implementations of @code{m4} provide the secure @code{mkstemp} builtin,
+and that users of @code{m4} check for its existence.
+
+The expansion is void and an error issued if a temporary file could
+not be created.
+
+When the @option{--safer} option (@pxref{Operation modes, Invoking m4})
+is in effect, @code{mkstemp} and @acronym{GNU}-mode @code{maketemp}
+result in an error, since otherwise an input file could perform a mild
+denial-of-service attack by filling up a disk with multiple empty files.
+
+The macros @code{mkstemp} and @code{maketemp} are recognized only with
+parameters.
address@hidden deffn
+
+If you try this next example, you will most likely get different output
+for the two file names, since the replacement characters are randomly
+chosen:
 
 @comment ignore
 @example
+$ @kbd{m4}
 maketemp(`/tmp/fooXXXXXX')
address@hidden:stdin:1: Warning: maketemp: recommend using mkstemp instead
 @result{}/tmp/fooa07346
+ifdef(`mkstemp', `define(`maketemp', defn(`mkstemp'))',
+      `define(`mkstemp', defn(`maketemp'))dnl
+errprint(`warning: potentially insecure maketemp implementation
+')')
address@hidden
+mkstemp(`doc')
address@hidden
address@hidden
 @end example
 
-When the @option{--safer} option (@pxref{Operation modes, Invoking m4})
-is in effect, @code{maketemp} results in an error, since otherwise an
-input file could perform a mild denial-of-service attack by filling up a
-disk with multiple empty files.
-
-The builtin macro @code{maketemp} is recognized only when given
-arguments.
address@hidden deffn
-
 @comment options: --safer
 @comment status: 1
 @example
 $ @kbd{m4 --safer}
 maketemp(`/tmp/fooXXXXXX')
address@hidden:stdin:1: Warning: maketemp: recommend using mkstemp instead
 @error{}m4:stdin:1: maketemp: disabled by --safer
 @result{}
+mkstemp(`/tmp/fooXXXXXX')
address@hidden:stdin:2: mkstemp: disabled by --safer
address@hidden
address@hidden example
+
address@hidden @acronym{GNU} extensions
+Unless you use the @option{--traditional} command line option (or
address@hidden, @pxref{Limits control, , Invoking m4}), the @acronym{GNU}
+version of @code{maketemp} is secure.  This means that using the same
+template to multiple calls will generate multiple files.  However, we
+recommend that you use the new @code{mkstemp} macro, introduced in
address@hidden M4 1.4.8, which is secure even in traditional mode.
+
address@hidden
+$ @kbd{m4}
+define(`file1', maketemp(`fooXXXXXX'))dnl
address@hidden:stdin:1: Warning: maketemp: recommend using mkstemp instead
+define(`file2', maketemp(`fooXX'))dnl
address@hidden:stdin:2: Warning: maketemp: recommend using mkstemp instead
+define(`file3', mkstemp(`fooXXXXXX'))dnl
+ifelse(len(file1), len(file2), `same length', `different')
address@hidden length
+ifelse(file1, file2, `same', `different file')
address@hidden file
+ifelse(file2, file3, `same', `different file')
address@hidden file
+ifelse(file1, file3, `same', `different file')
address@hidden file
+syscmd(`rm 'file1 file2 file3)
address@hidden
+sysval
address@hidden
address@hidden example
+
address@hidden options: -G
address@hidden
+$ @kbd{m4 -G}
+define(`file1', maketemp(`fooXXXXXX'))dnl
address@hidden:stdin:1: Warning: maketemp: recommend using mkstemp instead
+define(`file2', maketemp(`fooXXXXXX'))dnl
address@hidden:stdin:2: Warning: maketemp: recommend using mkstemp instead
+ifelse(file1, file2, `same', `different file')
address@hidden
+syscmd(`echo foo??????')dnl
address@hidden
 @end example
 
 @node Miscellaneous
@@ -5591,7 +5671,7 @@
 @cindex GNU extensions
 @cindex @acronym{POSIX}
 @cindex @env{POSIXLY_CORRECT}
-This version of @code{m4} contains a few facilities, that do not exist
+This version of @code{m4} contains a few facilities that do not exist
 in System V @code{m4}.  These extra facilities are all suppressed by
 using the @samp{-G} command line option, unless overridden by other
 command line options.
@@ -5664,6 +5744,15 @@
 @item
 The destination of trace and debug output can be controlled with
 @code{debugfile} (@pxref{Debugfile}).
+
address@hidden
+The @code{maketemp} (@pxref{Mkstemp}) macro behaves like @code{mkstemp},
+creating a new file with a unique name on every invocation, rather than
+following the insecure behavior of replacing the trailing @samp{X}
+characters with the @code{m4} process id.  @acronym{POSIX} does not
+allow this extension, so @code{maketemp} is insecure if
address@hidden is set, but you should be using @code{mkstemp} in
+the first place.
 @end itemize
 
 Additionally, @acronym{POSIX} only requires support for the command line
Index: modules/m4.c
===================================================================
RCS file: /sources/m4/m4/modules/m4.c,v
retrieving revision 1.83
diff -u -r1.83 m4.c
--- modules/m4.c        14 Oct 2006 14:16:23 -0000      1.83
+++ modules/m4.c        19 Oct 2006 22:58:20 -0000
@@ -79,6 +79,7 @@
   BUILTIN (m4exit,     false,  false,  false,  0,      1  )    \
   BUILTIN (m4wrap,     false,  true,   false,  1,      -1 )    \
   BUILTIN (maketemp,   false,  true,   false,  1,      1  )    \
+  BUILTIN (mkstemp,    false,  true,   false,  1,      1  )    \
   BUILTIN (popdef,     false,  true,   false,  1,      -1 )    \
   BUILTIN (pushdef,    true,   true,   false,  1,      2  )    \
   BUILTIN (shift,      false,  true,   false,  1,      -1 )    \
@@ -669,10 +670,16 @@
 
 /* More miscellaneous builtins -- "maketemp", "errprint".  */
 
-/* Use the first argument as at template for a temporary file name.  */
-M4BUILTIN_HANDLER (maketemp)
+/* Use the first argument as at template for a temporary file name.
+   FIXME - should we add a mkdtemp builtin in the gnu module, then
+   export this function as a helper to that?  */
+static void
+m4_make_temp (m4 *context, m4_obstack *obs, int argc, m4_symbol_value **argv)
 {
   int fd;
+  int len;
+  int i;
+  const char *name = M4ARG (1);
 
   if (m4_get_safer_opt (context))
     {
@@ -680,15 +687,74 @@
       return;
     }
 
+  /* Guarantee that there are six trailing 'X' characters, even if the
+     user forgot to supply them.  */
+  assert (obstack_object_size (obs) == 0);
+  len = strlen (name);
+  obstack_grow (obs, name, len);
+  for (i = 0; len > 0 && i < 6; i++)
+    if (name[--len] != 'X')
+      break;
+  for (; i < 6; i++)
+    obstack_1grow (obs, 'X');
+  obstack_1grow (obs, '\0');
+
   errno = 0;
-  if ((fd = mkstemp (M4ARG(1))) < 0)
+  fd = mkstemp (obstack_base (obs));
+  if (fd < 0)
     {
       m4_error (context, 0, errno, _("%s: cannot create tempfile `%s'"),
-               M4ARG (0), M4ARG (1));
-      return;
+               M4ARG (0), name);
+      obstack_free (obs, obstack_finish (obs));
+    }
+  else
+     close (fd);
+}
+
+/* Use the first argument as at template for a temporary file name.  */
+M4BUILTIN_HANDLER (maketemp)
+{
+  m4_warn (context, 0, _("%s: recommend using mkstemp instead"), M4ARG (0));
+  if (m4_get_posixly_correct_opt (context))
+    {
+      /* POSIX states "any trailing 'X' characters [are] replaced with
+        the current process ID as a string", without referencing the
+        file system.  Horribly insecure, but we have to do it.
+
+        For reference, Solaris m4 does:
+          maketemp() -> `'
+          maketemp(X) -> `X'
+          maketemp(XX) -> `Xn', where n is last digit of pid
+          maketemp(XXXXXXXX) -> `X00nnnnn', where nnnnn is 16-bit pid
+      */
+      const char *str = M4ARG (1);
+      int len = strlen (str);
+      int i;
+      int len2;
+
+      for (i = len; i > 1; i--)
+       if (str[i - 1] != 'X')
+         break;
+      obstack_grow (obs, str, i);
+      str = ntoa ((number) getpid (), 10);
+      len2 = strlen (str);
+      if (len2 > len - i)
+       obstack_grow0 (obs, str + len2 - (len - i), len - i);
+      else
+       {
+         while (i++ < len - len2)
+           obstack_1grow (obs, '0');
+         obstack_grow0 (obs, str, len2);
+       }
     }
-  close (fd);
-  m4_shipout_string (context, obs, M4ARG (1), 0, false);
+  else
+    m4_make_temp (context, obs, argc, argv);
+}
+
+/* Use the first argument as a template for a temporary file name.  */
+M4BUILTIN_HANDLER (mkstemp)
+{
+  m4_make_temp (context, obs, argc, argv);
 }
 
 /* Print all arguments on standard error.  */
Index: tests/builtins.at
===================================================================
RCS file: /sources/m4/m4/tests/builtins.at,v
retrieving revision 1.24
diff -u -r1.24 builtins.at
--- tests/builtins.at   12 Oct 2006 21:14:50 -0000      1.24
+++ tests/builtins.at   19 Oct 2006 22:58:20 -0000
@@ -495,6 +495,52 @@
 AT_CLEANUP
 
 
+## -------- ##
+## maketemp ##
+## -------- ##
+
+AT_SETUP([mkstemp])
+
+dnl Check that on error, the expansion is void
+AT_DATA([[in]],
+[[mkstemp(`no_such_dir/m4-fooXXXXXX')
+]])
+AT_CHECK_M4([in], [1], [[
+]], [[m4:in:1: mkstemp: cannot create tempfile `no_such_dir/m4-fooXXXXXX': No 
such file or directory
+]])
+
+dnl Check for Solaris compatibility of maketemp.  Hopefully the pid is
+dnl less than 20 decimal digits.  Also check that --safer does not affect
+dnl traditional behavior of maketemp, which is textual only.
+AT_DATA([[stdin]],
+[[maketemp()
+maketemp(X)
+maketemp(XX)
+maketemp(XXXXXXXXXXXXXXXXXXXXX)
+maketemp(no_such_dir/XXXXXX)
+]])
+dnl Abuse our knowledge of AT_CHECK_M4 so that we can get stderr filtering...
+POSIXLY_CORRECT=1
+export POSIXLY_CORRECT
+AT_CHECK_M4([-G --safer], [0], [stdout],
+[[m4:stdin:1: Warning: maketemp: recommend using mkstemp instead
+m4:stdin:2: Warning: maketemp: recommend using mkstemp instead
+m4:stdin:3: Warning: maketemp: recommend using mkstemp instead
+m4:stdin:4: Warning: maketemp: recommend using mkstemp instead
+m4:stdin:5: Warning: maketemp: recommend using mkstemp instead
+]], [stdin& echo $! > pid; wait $!])
+pid=`cat pid`
+cat >expout <<EOF
+
+X
+X`sed -e 's/.*\(.\)$/\1/' pid`
+X`echo "$pid" | sed -e "s/.*/00000000000000000000&/" -e 's/.*\(.\{20\}$\)/\1/'`
+no_such_dir/`echo "$pid" | sed -e "s/.*/000000&/" -e 's/.*\(.\{6\}$\)/\1/'`
+EOF
+AT_CHECK([cat stdout], [0], [expout])
+
+AT_CLEANUP
+
 
 ## ----------- ##
 ## multiquotes ##
Index: tests/options.at
===================================================================
RCS file: /sources/m4/m4/tests/options.at,v
retrieving revision 1.21
diff -u -r1.21 options.at
--- tests/options.at    13 Oct 2006 16:46:47 -0000      1.21
+++ tests/options.at    19 Oct 2006 22:58:20 -0000
@@ -566,16 +566,16 @@
 m4trace: -1- foo -> `2'
 ]])
 
-dnl make sure builtin cannot bypass --safer, and that maketemp does not
+dnl make sure builtin cannot bypass --safer, and that mkstemp does not
 dnl create file
-AT_DATA([[in]], [[builtin(`maketemp', `./fooXXXXXX')
+AT_DATA([[in]], [[builtin(`mkstemp', `./fooXXXXXX')
 ]])
 
 AT_CHECK([echo foo*], [0], [foo*
 ])
 
 AT_CHECK_M4([--safer in], [1], [[
-]], [[m4:in:1: maketemp: disabled by --safer
+]], [[m4:in:1: mkstemp: disabled by --safer
 ]])
 
 AT_CHECK([echo foo*], [0], [foo*
Index: tests/others.at
===================================================================
RCS file: /sources/m4/m4/tests/others.at,v
retrieving revision 1.25
diff -u -r1.25 others.at
--- tests/others.at     14 Oct 2006 16:34:55 -0000      1.25
+++ tests/others.at     19 Oct 2006 22:58:20 -0000
@@ -285,36 +285,6 @@
 AT_CLEANUP
 
 
-
-## -------- ##
-## maketemp ##
-## -------- ##
-
-AT_SETUP([maketemp])
-
-[cat >expout <<EOF
-different
-
-0
-EOF]
-
-AT_DATA([[misc.m4]],
-[[dnl This test assumes /tmp is a valid directory name, which is not true
-dnl for native Windows.
-ifdef(`__unix__', , `m4exit(`77')')dnl
-define(`file1', maketemp(`/tmp/m4-fooXXXXXX'))dnl
-define(`file2', maketemp(`/tmp/m4-fooXXXXXX'))dnl
-ifelse(file1, file2, `same', `different')
-syscmd(`rm 'file1 file2)
-sysval
-]])
-
-AT_CHECK_M4([misc.m4], 0, expout)
-
-AT_CLEANUP
-
-
-
 ## ------- ##
 ## reverse ##
 ## ------- ##






reply via email to

[Prev in Thread] Current Thread [Next in Thread]