summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS1
-rw-r--r--ChangeLog22
-rw-r--r--NEWS2
-rw-r--r--README4
-rw-r--r--configure.ac2
-rw-r--r--man/.gitignore98
-rw-r--r--man/Makefile.am1
-rw-r--r--man/mktemp.x6
-rw-r--r--po/ChangeLog4
-rw-r--r--po/POTFILES.in1
-rw-r--r--src/.gitignore1
-rw-r--r--src/Makefile.am4
-rw-r--r--src/mktemp.c292
-rw-r--r--tests/Coreutils.pm23
-rw-r--r--tests/misc/Makefile.am1
-rwxr-xr-xtests/misc/mktemp121
16 files changed, 576 insertions, 7 deletions
diff --git a/AUTHORS b/AUTHORS
index 55e4f4307..200e141d8 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -46,6 +46,7 @@ md5sum: Ulrich Drepper, Scott Miller, David Madore
mkdir: David MacKenzie
mkfifo: David MacKenzie
mknod: David MacKenzie
+mktemp: Jim Meyering
mv: Mike Parker, David MacKenzie, Jim Meyering
nice: David MacKenzie
nl: Scott Bartram, David MacKenzie
diff --git a/ChangeLog b/ChangeLog
index cfd3f3983..fe382c1d1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,27 @@
2007-10-07 Jim Meyering <meyering@redhat.com>
+ New program: mktemp.
+ * NEWS: Mention this.
+ * README: Add mktemp to the list.
+ * AUTHORS: Add this: mktemp: Jim Meyering
+ * src/mktemp.c: New file.
+ * src/Makefile.am (bin_PROGRAMS): Add mktemp.
+ (mktemp_LDADD): Add $(LIB_GETHRXTIME).
+ * man/mktemp.x: New file.
+ * man/Makefile.am (dist_man_MANS): Add mktemp.1.
+ (mktemp.1): New dependency.
+ * man/.cvsignore: Add mktemp.1.
+ * man/.gitignore: New file.
+ * src/.cvsignore, src/.gitignore: Add mktemp.
+ * tests/misc/mktemp: New file.
+ * tests/misc/Makefile.am (TESTS): Add mktemp.
+ * tests/Coreutils.pm (run_tests): Give the POST-test function
+ access to stdout and stderr contents, so it can verify that
+ the named-on-stdout file/dir does indeed exist and has proper
+ permissions, etc.
+ [po/ChangeLog]
+ * POTFILES.in: Add src/mktemp.c.
+
Make tempname more random, via the randint module.
* gl/modules/tempname (Depends-on): Add randint and stdbool.
* gl/lib/tempname.c: Include randint.h and stdbool.h.
diff --git a/NEWS b/NEWS
index 35eefaaaf..295ef737f 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,8 @@ GNU coreutils NEWS -*- outline -*-
arch: equivalent to uname -m, not installed by default
But don't install this program on Solaris systems.
+ mktemp: create a temporary file or directory (or names)
+
** Programs no longer installed by default
hostname, su
diff --git a/README b/README
index f52e9c00c..fcac30f5e 100644
--- a/README
+++ b/README
@@ -9,8 +9,8 @@ The programs that can be built with this package are:
[ arch base64 basename cat chcon chgrp chmod chown chroot cksum comm cp
csplit cut date dd df dir dircolors dirname du echo env expand expr
- factor false fmt fold groups head hostid hostname id install join
- kill link ln logname ls md5sum mkdir mkfifo mknod mv nice nl nohup
+ factor false fmt fold groups head hostid hostname id install join kill
+ link ln logname ls md5sum mkdir mkfifo mknod mktemp mv nice nl nohup
od paste pathchk pinky pr printenv printf ptx pwd readlink rm rmdir
runcon seq sha1sum sha224sum sha256sum sha384sum sha512sum shred shuf
sleep sort split stat stty su sum sync tac tail tee test touch tr true
diff --git a/configure.ac b/configure.ac
index 59dc2853f..a16a4799f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -32,7 +32,7 @@ AC_CONFIG_AUX_DIR(build-aux)
AC_CONFIG_HEADERS([lib/config.h:lib/config.hin])
AB_INIT()
-AM_INIT_AUTOMAKE([1.10 dist-bzip2])
+AM_INIT_AUTOMAKE([1.10 dist-lzma])
AC_PROG_CC_STDC
AM_PROG_CC_C_O
diff --git a/man/.gitignore b/man/.gitignore
new file mode 100644
index 000000000..e9e270da9
--- /dev/null
+++ b/man/.gitignore
@@ -0,0 +1,98 @@
+Makefile
+Makefile.in
+base64.1
+basename.1
+cat.1
+chgrp.1
+chmod.1
+chown.1
+chroot.1
+cksum.1
+comm.1
+cp.1
+csplit.1
+cut.1
+date.1
+dd.1
+df.1
+dir.1
+dircolors.1
+dirname.1
+du.1
+echo.1
+env.1
+expand.1
+expr.1
+factor.1
+false.1
+fmt.1
+fold.1
+groups.1
+head.1
+hostid.1
+hostname.1
+id.1
+install.1
+join.1
+kill.1
+link.1
+ln.1
+logname.1
+ls.1
+md5sum.1
+mkdir.1
+mkfifo.1
+mknod.1
+mktemp.1
+mv.1
+nice.1
+nl.1
+nohup.1
+od.1
+paste.1
+pathchk.1
+pinky.1
+pr.1
+printenv.1
+printf.1
+ptx.1
+pwd.1
+readlink.1
+rm.1
+rmdir.1
+seq.1
+sha1sum.1
+sha224sum.1
+sha256sum.1
+sha384sum.1
+sha512sum.1
+shred.1
+shuf.1
+sleep.1
+sort.1
+split.1
+stat.1
+stty.1
+su.1
+sum.1
+sync.1
+tac.1
+tail.1
+tee.1
+test.1
+touch.1
+tr.1
+true.1
+tsort.1
+tty.1
+uname.1
+unexpand.1
+uniq.1
+unlink.1
+uptime.1
+users.1
+vdir.1
+wc.1
+who.1
+whoami.1
+yes.1
diff --git a/man/Makefile.am b/man/Makefile.am
index e102dbd85..a7b96d16e 100644
--- a/man/Makefile.am
+++ b/man/Makefile.am
@@ -75,6 +75,7 @@ md5sum.1: $(common_dep) $(srcdir)/md5sum.x ../src/md5sum.c
mkdir.1: $(common_dep) $(srcdir)/mkdir.x ../src/mkdir.c
mkfifo.1: $(common_dep) $(srcdir)/mkfifo.x ../src/mkfifo.c
mknod.1: $(common_dep) $(srcdir)/mknod.x ../src/mknod.c
+mktemp.1: $(common_dep) $(srcdir)/mktemp.x ../src/mktemp.c
mv.1: $(common_dep) $(srcdir)/mv.x ../src/mv.c
nice.1: $(common_dep) $(srcdir)/nice.x ../src/nice.c
nl.1: $(common_dep) $(srcdir)/nl.x ../src/nl.c
diff --git a/man/mktemp.x b/man/mktemp.x
new file mode 100644
index 000000000..393ca742b
--- /dev/null
+++ b/man/mktemp.x
@@ -0,0 +1,6 @@
+[NAME]
+mktemp \- create a temporary file or directory
+[DESCRIPTION]
+.\" Add any additional description here
+[SEE ALSO]
+mkstemp(3), mkdtemp(3), mktemp(3)
diff --git a/po/ChangeLog b/po/ChangeLog
index 247a76a2a..4a58cb1d6 100644
--- a/po/ChangeLog
+++ b/po/ChangeLog
@@ -11,6 +11,10 @@
* POTFILES.in: Remove lib/human.c.
+2007-05-19 Jim Meyering <jim@meyering.net>
+
+ * POTFILES.in: Add src/mktemp.c.
+
2007-02-02 Jim Meyering <jim@meyering.net>
* POTFILES.in: Add src/runcon.c.
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 7f4d5c2a5..70616bb56 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -74,6 +74,7 @@ src/md5sum.c
src/mkdir.c
src/mkfifo.c
src/mknod.c
+src/mktemp.c
src/mv.c
src/nice.c
src/nl.c
diff --git a/src/.gitignore b/src/.gitignore
index b38dc732a..c2e4c2376 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -47,6 +47,7 @@ md5sum
mkdir
mkfifo
mknod
+mktemp
mv
nice
nl
diff --git a/src/Makefile.am b/src/Makefile.am
index 1f3bcd8e6..fc3c7a1d4 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -31,7 +31,8 @@ EXTRA_PROGRAMS = \
$(build_if_possible__progs) \
[ chcon chgrp chown chmod cp dd dircolors du \
ginstall link ln dir vdir ls mkdir \
- mkfifo mknod mv nohup readlink rm rmdir shred stat sync touch unlink \
+ mkfifo mknod mktemp \
+ mv nohup readlink rm rmdir shred stat sync touch unlink \
cat cksum comm csplit cut expand fmt fold head join md5sum \
nl od paste pr ptx sha1sum sha224sum sha256sum sha384sum sha512sum \
shuf sort split sum tac tail tr tsort unexpand uniq wc \
@@ -96,6 +97,7 @@ ls_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME) $(LIB_SELINUX)
pr_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME)
shred_LDADD = $(LDADD) $(LIB_GETHRXTIME) $(LIB_FDATASYNC)
shuf_LDADD = $(LDADD) $(LIB_GETHRXTIME)
+mktemp_LDADD = $(LDADD) $(LIB_GETHRXTIME)
vdir_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME) $(LIB_SELINUX)
## If necessary, add -lm to resolve use of pow in lib/strtod.c.
diff --git a/src/mktemp.c b/src/mktemp.c
new file mode 100644
index 000000000..6580f3c75
--- /dev/null
+++ b/src/mktemp.c
@@ -0,0 +1,292 @@
+/* Create a temporary file or directory, safely.
+ Copyright (C) 2007 Free Software Foundation, Inc.
+
+ This program 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, or (at your option)
+ any later version.
+
+ This program 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, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+/* Jim Meyering */
+
+#include <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <getopt.h>
+
+#include "system.h"
+
+#include "error.h"
+#include "filenamecat.h"
+#include "long-options.h"
+#include "quote.h"
+#include "tempname.h"
+
+/* The official name of this program (e.g., no `g' prefix). */
+#define PROGRAM_NAME "mktemp"
+
+#define AUTHORS "Jim Meyering"
+
+static const char *default_template = "tmp.XXXXXXXXXX";
+
+/* The name this program was run with. */
+char *program_name;
+
+/* For long options that have no equivalent short option, use a
+ non-character as a pseudo short option, starting with CHAR_MAX + 1. */
+enum
+{
+ TMPDIR_OPTION = CHAR_MAX + 1
+};
+
+static struct option const longopts[] =
+{
+ {"directory", no_argument, NULL, 'd'},
+ {"quiet", no_argument, NULL, 'q'},
+ {"dry-run", no_argument, NULL, 'u'},
+ {"tmpdir", optional_argument, NULL, TMPDIR_OPTION},
+ {GETOPT_HELP_OPTION_DECL},
+ {GETOPT_VERSION_OPTION_DECL},
+ {NULL, 0, NULL, 0}
+};
+
+void
+usage (int status)
+{
+ if (status != EXIT_SUCCESS)
+ fprintf (stderr, _("Try `%s --help' for more information.\n"),
+ program_name);
+ else
+ {
+ printf (_("Usage: %s [OPTION]... [TEMPLATE]\n"), program_name);
+ fputs (_("\
+Create a temporary file or directory, safely, and print its name.\n\
+If TEMPLATE is not specified, use tmp.XXXXXXXXXX.\n\
+"), stdout);
+ fputs ("\n", stdout);
+ fputs (_("\
+ -d, --directory create a directory, not a file\n\
+"), stdout);
+ fputs (_("\
+ -q, --quiet suppress diagnostics about file/dir-creation failure\n\
+"), stdout);
+ fputs (_("\
+ -u, --dry-run do not create anything; merely print a name (unsafe)\n\
+"), stdout);
+ fputs (_("\
+ --tmpdir[=DIR] interpret TEMPLATE relative to DIR. If DIR is\n\
+ not specified, use $TMPDIR if set, else /tmp.\n\
+ With this option, TEMPLATE must not be an absolute name.\n\
+ Unlike with -t, TEMPLATE may contain slashes, but even\n\
+ here, mktemp still creates only the final component.\n\
+"), stdout);
+ fputs ("\n", stdout);
+ fputs (_("\
+ -p DIR use DIR as a prefix; implies -t [deprecated]\n\
+"), stdout);
+ fputs (_("\
+ -t interpret TEMPLATE as a single file name component,\n\
+ relative to a directory: $TMPDIR, if set; else the\n\
+ directory specified via -p; else /tmp [deprecated]\n\
+"), stdout);
+ fputs ("\n", stdout);
+ fputs (HELP_OPTION_DESCRIPTION, stdout);
+ fputs (VERSION_OPTION_DESCRIPTION, stdout);
+ emit_bug_reporting_address ();
+ }
+
+ exit (status);
+}
+
+static size_t
+count_trailing_X_s (const char *s)
+{
+ size_t len = strlen (s);
+ size_t n = 0;
+ for ( ; len && s[len-1] == 'X'; len--)
+ ++n;
+ return n;
+}
+
+static int
+mkstemp_len (char *tmpl, size_t suff_len, bool dry_run)
+{
+ return gen_tempname_len (tmpl, dry_run ? GT_NOCREATE : GT_FILE, suff_len);
+}
+
+static int
+mkdtemp_len (char *tmpl, size_t suff_len, bool dry_run)
+{
+ return gen_tempname_len (tmpl, dry_run ? GT_NOCREATE : GT_DIR, suff_len);
+}
+
+int
+main (int argc, char **argv)
+{
+ char *dest_dir;
+ char *dest_dir_arg = NULL;
+ bool suppress_stderr = false;
+ int c;
+ unsigned int n_args;
+ char *template;
+ bool use_dest_dir = false;
+ bool deprecated_t_option = false;
+ bool create_directory = false;
+ bool dry_run = false;
+ int status = EXIT_SUCCESS;
+ size_t x_count;
+ char *dest_name;
+
+ initialize_main (&argc, &argv);
+ program_name = argv[0];
+ setlocale (LC_ALL, "");
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+
+ atexit (close_stdout);
+
+ while ((c = getopt_long (argc, argv, "dp:qtuV", longopts, NULL)) != -1)
+ {
+ switch (c)
+ {
+ case 'd':
+ create_directory = true;
+ break;
+ case 'p':
+ dest_dir_arg = optarg;
+ use_dest_dir = true;
+ break;
+ case 'q':
+ suppress_stderr = true;
+ break;
+ case 't':
+ use_dest_dir = true;
+ deprecated_t_option = true;
+ break;
+ case 'u':
+ dry_run = true;
+ break;
+
+ case TMPDIR_OPTION:
+ use_dest_dir = true;
+ dest_dir_arg = optarg;
+ break;
+
+ case_GETOPT_HELP_CHAR;
+
+ case 'V':
+ case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
+ default:
+ usage (EXIT_FAILURE);
+ }
+ }
+
+ if (suppress_stderr)
+ {
+ /* From here on, redirect stderr to /dev/null.
+ A diagnostic from getopt_long, above, would still go to stderr. */
+ freopen ("/dev/null", "wb", stderr);
+ }
+
+ n_args = argc - optind;
+ if (2 <= n_args)
+ {
+ error (0, 0, _("too many templates"));
+ usage (EXIT_FAILURE);
+ }
+
+ if (n_args == 0)
+ {
+ use_dest_dir = true;
+ template = (char *) default_template;
+ }
+ else
+ {
+ template = argv[optind];
+ }
+
+ x_count = count_trailing_X_s (template);
+ if (x_count < 3)
+ error (EXIT_FAILURE, 0, _("too few X's in template %s"), quote (template));
+
+ if (use_dest_dir)
+ {
+ if (deprecated_t_option)
+ {
+ char *env = getenv ("TMPDIR");
+ dest_dir = (env && *env
+ ? env
+ : (dest_dir_arg ? dest_dir_arg : "/tmp"));
+
+ if (last_component (template) != template)
+ error (EXIT_FAILURE, 0,
+ _("invalid template, %s, contains directory separator"),
+ quote (template));
+ }
+ else
+ {
+ if (dest_dir_arg && *dest_dir_arg)
+ dest_dir = dest_dir_arg;
+ else
+ {
+ char *env = getenv ("TMPDIR");
+ dest_dir = (env && *env ? env : "/tmp");
+ }
+ if (IS_ABSOLUTE_FILE_NAME (template))
+ error (EXIT_FAILURE, 0,
+ _("invalid template, %s; with --tmpdir,"
+ " it may not be absolute"),
+ quote (template));
+ }
+
+ dest_name = file_name_concat (dest_dir, template, NULL);
+ }
+ else
+ {
+ dest_name = xstrdup (template);
+ }
+
+ if (create_directory)
+ {
+ int err = mkdtemp_len (dest_name, x_count, dry_run);
+ if (err != 0)
+ {
+ error (0, errno, _("failed to create directory via template %s"),
+ quote (dest_name));
+ status = EXIT_FAILURE;
+ }
+ }
+ else
+ {
+ int fd = mkstemp_len (dest_name, x_count, dry_run);
+ if (fd < 0 || (!dry_run && close (fd) != 0))
+ {
+ error (0, errno, _("failed to create file via template %s"),
+ quote (dest_name));
+ status = EXIT_FAILURE;
+ }
+ }
+
+ if (status == EXIT_SUCCESS)
+ puts (dest_name);
+
+#ifdef lint
+ free (dest_name);
+#endif
+
+ exit (status);
+}
+
+/*
+ * Local variables:
+ * indent-tabs-mode: nil
+ * End:
+ */
diff --git a/tests/Coreutils.pm b/tests/Coreutils.pm
index 74b5fbdbf..e506cc810 100644
--- a/tests/Coreutils.pm
+++ b/tests/Coreutils.pm
@@ -1,8 +1,7 @@
package Coreutils;
# This is a testing framework.
-# Copyright (C) 1998, 2000, 2001, 2002, 2004, 2005, 2006 Free Software
-# Foundation, Inc.
+# Copyright (C) 1998, 2000-2002, 2004-2007 Free Software Foundation, Inc.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -478,6 +477,23 @@ sub run_tests ($$$$$)
goto cleanup;
}
+ my %actual_data;
+ # Record actual stdout and stderr contents, if POST may need them.
+ if ($expect->{POST})
+ {
+ foreach my $eo (qw (OUT ERR))
+ {
+ my $out_file = $actual{$eo};
+ open IN, $out_file
+ or (warn "$program_name: cannot open $out_file for reading: $!\n"),
+ $fail = 1, next;
+ $actual_data{$eo} = <IN>;
+ close IN
+ or (warn "$program_name: failed to read $out_file: $!\n"),
+ $fail = 1;
+ }
+ }
+
foreach my $eo (qw (OUT ERR))
{
my $subst_expr = $expect->{RESULT_SUBST}->{$eo};
@@ -525,7 +541,8 @@ sub run_tests ($$$$$)
}
cleanup:
- &{$expect->{POST}} if $expect->{POST};
+ $expect->{POST}
+ and &{$expect->{POST}} ($actual_data{OUT}, $actual_data{ERR});
}
diff --git a/tests/misc/Makefile.am b/tests/misc/Makefile.am
index ec1a67c1c..04f1315fe 100644
--- a/tests/misc/Makefile.am
+++ b/tests/misc/Makefile.am
@@ -43,6 +43,7 @@ TESTS = \
date \
xstrtol \
od \
+ mktemp \
arch \
pr \
df-P \
diff --git a/tests/misc/mktemp b/tests/misc/mktemp
new file mode 100755
index 000000000..da576e8b2
--- /dev/null
+++ b/tests/misc/mktemp
@@ -0,0 +1,121 @@
+#!/bin/sh
+# Test "mktemp".
+
+# Copyright (C) 2007 Free Software Foundation, Inc.
+
+# This program 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.
+
+# This program 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+: ${PERL=perl}
+: ${srcdir=.}
+
+$PERL -e 1 > /dev/null 2>&1 || {
+ echo 1>&2 "$0: configure didn't find a usable version of Perl," \
+ "so can't run this test"
+ exit 77
+}
+
+me=`echo $0|sed 's,.*/,,'`
+exec $PERL -w -I$srcdir/.. -MCoreutils -M"CuTmpdir qw($me)" -- - <<\EOF
+require 5.003;
+use strict;
+
+(my $ME = $0) =~ s|.*/||;
+
+sub check_tmp($$)
+{
+ my ($file, $file_or_dir) = @_;
+
+ my (undef, undef, $mode, undef) = stat $file
+ or die "$ME: failed to stat $file: $!\n";
+ my $required_mode;
+ if ($file_or_dir eq 'D') {
+ -d $file or die "$ME: $file isn't a directory\n";
+ -x $file or die "$ME: $file isn't owner-searchable\n";
+ $required_mode = 0700;
+ } elsif ($file_or_dir eq 'F') {
+ -f $file or die "$ME: $file isn't a regular file\n";
+ $required_mode = 0600;
+ }
+ -r $file or die "$ME: $file isn't owner-readable\n";
+ -w $file or die "$ME: $file isn't owner-writable\n";
+ ($mode & 0777) == $required_mode
+ or die "$ME: $file doesn't have required permissions\n";
+
+ $file_or_dir eq 'D'
+ and do { rmdir $file or die "$ME: failed to rmdir $file: $!\n" };
+ $file_or_dir eq 'F'
+ and do { unlink $file or die "$ME: failed to unlink $file: $!\n" };
+}
+
+# Turn off localisation of executable's ouput.
+@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
+my $prog = 'mktemp';
+
+my @Tests =
+ (
+ # test-name, [option, option, ...] {OUT=>"expected-output"}
+ #
+ ['too-many', 'a b',
+ {ERR=>"$prog: too many templates\n"
+ . "Try `$prog --help' for more information.\n"}, {EXIT => 1} ],
+ ['too-many-q', '-q a b', {EXIT => 1} ],
+
+ ['too-few-x', 'foo.XX',
+ {ERR=>"$prog: too few X's in template `foo.XX'\n"}, {EXIT => 1} ],
+
+ ['1f', 'bar.XXXX', {OUT => "bar.ZZZZ\n"},
+ {OUT_SUBST => 's,\.....$,.ZZZZ,'},
+ {POST => sub { my ($f) = @_; defined $f or return; chomp $f;
+ check_tmp $f, 'F'; }}
+ ],
+
+ # Create a temporary directory.
+ ['1d', '-d f.XXXX', {OUT => "f.ZZZZ\n"},
+ {OUT_SUBST => 's,\.....$,.ZZZZ,'},
+ {POST => sub { my ($f) = @_; defined $f or return; chomp $f;
+ check_tmp $f, 'D'; }}
+ ],
+
+ # Use a template consisting solely of X's
+ ['1d-allX', '-d XXXX', {OUT => "ZZZZ\n"},
+ {OUT_SUBST => 's,^....$,ZZZZ,'},
+ {POST => sub { my ($f) = @_; defined $f or return; chomp $f;
+ check_tmp $f, 'D'; }}
+ ],
+
+ ['invalid-tmpl', '-t a/bXXXX',
+ {ERR=>"$prog: invalid template, `a/bXXXX', "
+ . "contains directory separator\n"}, {EXIT => 1} ],
+
+ ['invalid-t2', '--tmpdir=a /bXXXX',
+ {ERR=>"$prog: invalid template, `/bXXXX'; "
+ . "with --tmpdir, it may not be absolute\n"}, {EXIT => 1} ],
+
+ ['tmp-w-slash', '--tmpdir=. a/bXXXX',
+ {PRE => sub {mkdir 'a',0755 or die "a: $!\n"}},
+ {OUT_SUBST => 's,b....$,bZZZZ,'},
+ {OUT => "./a/bZZZZ\n"},
+ {POST => sub { my ($f) = @_; defined $f or return; chomp $f;
+ check_tmp $f, 'F'; unlink $f; rmdir 'a' or die "rmdir a: $!\n" }}
+ ],
+ );
+
+my $save_temps = $ENV{DEBUG};
+my $verbose = $ENV{VERBOSE};
+
+my $fail = run_tests ($ME, $prog, \@Tests, $save_temps, $verbose);
+exit $fail;
+EOF