From 99b039fead8d72936d0d558198235eee03016904 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Sat, 2 Jun 2007 23:04:10 +0200 Subject: [PATCH] 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. --- AUTHORS | 1 + ChangeLog | 22 ++++ NEWS | 2 + README | 4 +- configure.ac | 2 +- man/.gitignore | 98 +++++++++++++++++ man/Makefile.am | 1 + man/mktemp.x | 6 + po/ChangeLog | 4 + po/POTFILES.in | 1 + src/.gitignore | 1 + src/Makefile.am | 4 +- src/mktemp.c | 292 +++++++++++++++++++++++++++++++++++++++++++++++++ tests/Coreutils.pm | 23 +++- tests/misc/Makefile.am | 1 + tests/misc/mktemp | 121 ++++++++++++++++++++ 16 files changed, 576 insertions(+), 7 deletions(-) create mode 100644 man/.gitignore create mode 100644 man/mktemp.x create mode 100644 src/mktemp.c create mode 100755 tests/misc/mktemp diff --git a/AUTHORS b/AUTHORS index 55e4f43..200e141 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 cfd3f39..fe382c1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,27 @@ 2007-10-07 Jim Meyering + 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 35eefaa..295ef73 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 f52e9c0..fcac30f 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 59dc285..a16a479 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 0000000..e9e270d --- /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 e102dbd..a7b96d1 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 0000000..393ca74 --- /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 247a76a..4a58cb1 100644 --- a/po/ChangeLog +++ b/po/ChangeLog @@ -11,6 +11,10 @@ * POTFILES.in: Remove lib/human.c. +2007-05-19 Jim Meyering + + * POTFILES.in: Add src/mktemp.c. + 2007-02-02 Jim Meyering * POTFILES.in: Add src/runcon.c. diff --git a/po/POTFILES.in b/po/POTFILES.in index 7f4d5c2..70616bb 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 b38dc73..c2e4c23 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 1f3bcd8..fc3c7a1 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 0000000..6580f3c --- /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 +#include +#include +#include + +#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 74b5fbd..e506cc8 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} = ; + 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 ec1a67c..04f1315 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 0000000..da576e8 --- /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 -- 2.7.4