From 99b039fead8d72936d0d558198235eee03016904 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Sat, 2 Jun 2007 23:04:10 +0200 Subject: 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. --- src/mktemp.c | 292 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 src/mktemp.c (limited to 'src/mktemp.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 +#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: + */ -- cgit v1.2.3-54-g00ecf