git: ac6f924e1cd1 - main - mktemp: add -p/--tmpdir argument

From: Kyle Evans <kevans_at_FreeBSD.org>
Date: Mon, 31 Oct 2022 03:56:03 UTC
The branch main has been updated by kevans:

URL: https://cgit.FreeBSD.org/src/commit/?id=ac6f924e1cd1ebd9832227e906ebc03e3ba5eede

commit ac6f924e1cd1ebd9832227e906ebc03e3ba5eede
Author:     Kyle Evans <kevans@FreeBSD.org>
AuthorDate: 2022-10-31 03:55:46 +0000
Commit:     Kyle Evans <kevans@FreeBSD.org>
CommitDate: 2022-10-31 03:55:46 +0000

    mktemp: add -p/--tmpdir argument
    
    This matches other mktemp implementations, including OpenBSD and GNU.
    The -p option can be used to provide a tmpdir prefix for specified
    templates.  Precedence works out like so:
    
    -t flag:
    - $TMPDIR
    - -p directory
    - /tmp
    
    Implied -t flag (no arguments or only -d flag):
    - -p directory
    - $TMPDIR
    - /tmp
    
    Some tests have been added for mktemp(1) in the process.
    
    Reviewed by:    imp (earlier version), wosch
    Sponsored by:   Klara, Inc.
    Differential Revision:  https://reviews.freebsd.org/D37121
---
 etc/mtree/BSD.tests.dist            |   2 +
 usr.bin/mktemp/Makefile             |   5 ++
 usr.bin/mktemp/mktemp.1             |  35 ++++++++++-
 usr.bin/mktemp/mktemp.c             |  46 +++++++++++---
 usr.bin/mktemp/tests/Makefile       |   7 +++
 usr.bin/mktemp/tests/mktemp_test.sh | 118 ++++++++++++++++++++++++++++++++++++
 6 files changed, 203 insertions(+), 10 deletions(-)

diff --git a/etc/mtree/BSD.tests.dist b/etc/mtree/BSD.tests.dist
index 1a00c13f78ba..5fbcd3c31669 100644
--- a/etc/mtree/BSD.tests.dist
+++ b/etc/mtree/BSD.tests.dist
@@ -1060,6 +1060,8 @@
         ..
         mkimg
         ..
+        mktemp
+        ..
         ncal
         ..
         opensm
diff --git a/usr.bin/mktemp/Makefile b/usr.bin/mktemp/Makefile
index 0e23063431c2..72e3d7306bb8 100644
--- a/usr.bin/mktemp/Makefile
+++ b/usr.bin/mktemp/Makefile
@@ -1,7 +1,12 @@
 # $FreeBSD$
 
+.include <src.opts.mk>
+
 PACKAGE=	runtime
 
 PROG=	mktemp
 
+HAS_TESTS=
+SUBDIR.${MK_TESTS}+=	tests
+
 .include <bsd.prog.mk>
diff --git a/usr.bin/mktemp/mktemp.1 b/usr.bin/mktemp/mktemp.1
index 469d4f6097d0..5835cf4b1b3f 100644
--- a/usr.bin/mktemp/mktemp.1
+++ b/usr.bin/mktemp/mktemp.1
@@ -37,12 +37,14 @@
 .Sh SYNOPSIS
 .Nm
 .Op Fl d
+.Op Fl p Ar tmpdir
 .Op Fl q
 .Op Fl t Ar prefix
 .Op Fl u
 .Ar template ...
 .Nm
 .Op Fl d
+.Op Fl p Ar tmpdir
 .Op Fl q
 .Op Fl u
 .Fl t Ar prefix
@@ -91,10 +93,20 @@ will generate a template string based on the
 and the
 .Ev TMPDIR
 environment variable if set.
-The default location if
+If the
+.Fl p
+option is set, then the given
+.Ar tmpdir
+will be used if the
+.Ev TMPDIR
+environment variable is not set.
+Finally,
+.Pa /tmp
+will be used if neither
 .Ev TMPDIR
-is not set is
-.Pa /tmp .
+or
+.Fl p
+are set and used.
 Care should
 be taken to ensure that it is appropriate to use an environment variable
 potentially supplied by the user.
@@ -134,6 +146,23 @@ The available options are as follows:
 .Bl -tag -width indent
 .It Fl d , Fl -directory
 Make a directory instead of a file.
+.It Fl p Ar tmpdir , Fl -tmpdir Ns Oo = Ns Ar tmpdir Oc
+Use
+.Ar tmpdir
+for the
+.Fl t
+flag if the
+.Ev TMPDIR
+environment variable is not set.
+Additionally, any provided
+.Ar template
+arguments will be interpreted relative to the path specified as
+.Ar tmpdir .
+If
+.Ar tmpdir
+is either empty or omitted, then the
+.Ev TMPDIR
+environment variable will be used.
 .It Fl q , Fl -quiet
 Fail silently if an error occurs.
 This is useful if
diff --git a/usr.bin/mktemp/mktemp.c b/usr.bin/mktemp/mktemp.c
index 9240197e9129..a15e0d776c40 100644
--- a/usr.bin/mktemp/mktemp.c
+++ b/usr.bin/mktemp/mktemp.c
@@ -39,6 +39,7 @@
 #include <err.h>
 #include <getopt.h>
 #include <paths.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -53,6 +54,7 @@ static void usage(void);
 
 static const struct option long_opts[] = {
 	{"directory",	no_argument,	NULL,	'd'},
+	{"tmpdir",	optional_argument,	NULL,	'p'},
 	{"quiet",	no_argument,	NULL,	'q'},
 	{"dry-run",	no_argument,	NULL,	'u'},
 	{NULL,		no_argument,	NULL,	0},
@@ -62,21 +64,29 @@ int
 main(int argc, char **argv)
 {
 	int c, fd, ret;
-	char *tmpdir;
-	const char *prefix;
+	const char *prefix, *tmpdir;
 	char *name;
 	int dflag, qflag, tflag, uflag;
+	bool prefer_tmpdir;
 
 	ret = dflag = qflag = tflag = uflag = 0;
+	prefer_tmpdir = true;
 	prefix = "mktemp";
 	name = NULL;
+	tmpdir = NULL;
 
-	while ((c = getopt_long(argc, argv, "dqt:u", long_opts, NULL)) != -1)
+	while ((c = getopt_long(argc, argv, "dp:qt:u", long_opts, NULL)) != -1)
 		switch (c) {
 		case 'd':
 			dflag++;
 			break;
 
+		case 'p':
+			tmpdir = optarg;
+			if (tmpdir == NULL || *tmpdir == '\0')
+				tmpdir = getenv("TMPDIR");
+			break;
+
 		case 'q':
 			qflag++;
 			break;
@@ -100,10 +110,26 @@ main(int argc, char **argv)
 	if (!tflag && argc < 1) {
 		tflag = 1;
 		prefix = "tmp";
+
+		/*
+		 * For this implied -t mode, we actually want to swap the usual
+		 * order of precedence: -p, then TMPDIR, then /tmp.
+		 */
+		prefer_tmpdir = false;
 	}
 
 	if (tflag) {
-		tmpdir = getenv("TMPDIR");
+		const char *envtmp;
+
+		envtmp = NULL;
+
+		/*
+		 * $TMPDIR preferred over `-p` if specified, for compatibility.
+		 */
+		if (prefer_tmpdir || tmpdir == NULL)
+			envtmp = getenv("TMPDIR");
+		if (envtmp != NULL)
+			tmpdir = envtmp;
 		if (tmpdir == NULL)
 			asprintf(&name, "%s%s.XXXXXXXXXX", _PATH_TMP, prefix);
 		else
@@ -120,7 +146,12 @@ main(int argc, char **argv)
 	/* generate all requested files */
 	while (name != NULL || argc > 0) {
 		if (name == NULL) {
-			name = strdup(argv[0]);
+			if (!tflag && tmpdir != NULL)
+				asprintf(&name, "%s/%s", tmpdir, argv[0]);
+			else
+				name = strdup(argv[0]);
+			if (name == NULL)
+				err(1, "%s", argv[0]);
 			argv++;
 			argc--;
 		}
@@ -159,8 +190,9 @@ static void
 usage(void)
 {
 	fprintf(stderr,
-		"usage: mktemp [-d] [-q] [-t prefix] [-u] template ...\n");
+		"usage: mktemp [-d] [-p tmpdir] [-q] [-t prefix] [-u] template "
+		"...\n");
 	fprintf(stderr,
-		"       mktemp [-d] [-q] [-u] -t prefix \n");
+		"       mktemp [-d] [-p tmpdir] [-q] [-u] -t prefix \n");
 	exit (1);
 }
diff --git a/usr.bin/mktemp/tests/Makefile b/usr.bin/mktemp/tests/Makefile
new file mode 100644
index 000000000000..5141bf0faade
--- /dev/null
+++ b/usr.bin/mktemp/tests/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PACKAGE=	tests
+
+ATF_TESTS_SH+=	mktemp_test
+
+.include <bsd.test.mk>
diff --git a/usr.bin/mktemp/tests/mktemp_test.sh b/usr.bin/mktemp/tests/mktemp_test.sh
new file mode 100755
index 000000000000..1138c46ec1d0
--- /dev/null
+++ b/usr.bin/mktemp/tests/mktemp_test.sh
@@ -0,0 +1,118 @@
+#
+# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+#
+# Copyright (c) 2022 Klara Systems
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# $FreeBSD$
+
+atf_test_case tmpdir_env
+tmpdir_env_body()
+{
+
+	tmpdir="$PWD"
+
+	atf_check -o match:"^$tmpdir/foo\..+$" \
+	    env TMPDIR="$tmpdir" mktemp -t foo
+}
+
+atf_test_case tmpdir_pflag
+tmpdir_pflag_body()
+{
+
+	mkdir tmp_p tmp_env
+
+	tmpdir="$PWD/tmp_env"
+	export TMPDIR="$tmpdir"
+
+	pflag="$PWD/tmp_p"
+
+	# Basic usage: just -p specified
+	atf_check -o match:"^$pflag/tmp\..+$" \
+	    env -u TMPDIR mktemp -p "$pflag"
+	atf_check -o match:"^$pflag/tmp\..+$" \
+	    env TMPDIR="$tmpdir" mktemp -p "$pflag"
+
+	# -p with a list of names
+	atf_check -o ignore env -u TMPDIR mktemp -p "$pflag" x y z
+	atf_check test -f "$pflag/x"
+	atf_check test -f "$pflag/y"
+	atf_check test -f "$pflag/z"
+
+	# Checking --tmpdir usage, which should defer to $TMPDIR followed by
+	# /tmp with no value specified.
+	atf_check -o match:"^/tmp/foo\..+$" \
+	    env -u TMPDIR mktemp --tmpdir -t foo
+	atf_check -o match:"^$tmpdir/foo\..+$" \
+	    env TMPDIR="$tmpdir" mktemp --tmpdir -t foo
+
+	# Finally, combined -p -t
+	atf_check -o match:"^$pflag/foo\..+$" \
+	    env -u TMPDIR mktemp -p "$pflag" -t foo
+	atf_check -o match:"^$tmpdir/foo\..+$" \
+	    env TMPDIR="$tmpdir" mktemp -p "$pflag" -t foo
+}
+
+atf_test_case tmpdir_pflag_dir
+tmpdir_pflag_dir_body()
+{
+
+	tmpdir="$PWD"
+	atf_check -o save:tmpname \
+	    env -u TMPDIR mktemp -d -p "$tmpdir" -t foo
+
+	# Better diagnostics when using -o match: + cat rather than grep.
+	atf_check -o match:"^$tmpdir/foo\..+$" cat tmpname
+	cdir=$(cat tmpname)
+
+	atf_check test -d "$cdir"
+
+	atf_check -o match:"^$tmpdir/footmp$" \
+	    env -u TMPDIR mktemp -d -p "$tmpdir" footmp
+	atf_check test -d "$tmpdir/footmp"
+}
+
+atf_test_case tmpdir_pflag_noarg
+tmpdir_pflag_noarg_body()
+{
+
+	# Without -t, this time; this introduces $TMPDIR without having to use
+	# it.
+	tmpdir="$PWD"
+	atf_check -o save:tmpname \
+	    env TMPDIR="$tmpdir" mktemp --tmpdir foo.XXXXXXXX
+	atf_check -o match:"^$tmpdir/foo\..+$" cat tmpname
+
+	# An empty string gets the same treatment.
+	atf_check -o save:tmpname \
+	    env TMPDIR="$tmpdir" mktemp -p '' foo.XXXXXXXX
+	atf_check -o match:"^$tmpdir/foo\..+$" cat tmpname
+}
+
+atf_init_test_cases()
+{
+	atf_add_test_case tmpdir_env
+	atf_add_test_case tmpdir_pflag
+	atf_add_test_case tmpdir_pflag_dir
+	atf_add_test_case tmpdir_pflag_noarg
+}