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
This commit is contained in:
Kyle Evans 2022-10-30 22:55:46 -05:00
parent cfc57d7dbe
commit ac6f924e1c
6 changed files with 203 additions and 10 deletions

View File

@ -1060,6 +1060,8 @@
..
mkimg
..
mktemp
..
ncal
..
opensm

View File

@ -1,7 +1,12 @@
# $FreeBSD$
.include <src.opts.mk>
PACKAGE= runtime
PROG= mktemp
HAS_TESTS=
SUBDIR.${MK_TESTS}+= tests
.include <bsd.prog.mk>

View File

@ -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
is not set is
.Pa /tmp .
environment variable is not set.
Finally,
.Pa /tmp
will be used if neither
.Ev TMPDIR
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

View File

@ -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);
}

View File

@ -0,0 +1,7 @@
# $FreeBSD$
PACKAGE= tests
ATF_TESTS_SH+= mktemp_test
.include <bsd.test.mk>

View File

@ -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
}