/* Create a temporary file or directory, safely. Copyright (C) 2007-2009 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, see . */ /* Written by Jim Meyering. */ #include #include #include #include #include "system.h" #include "close-stream.h" #include "error.h" #include "filenamecat.h" #include "quote.h" #include "tempname.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "mktemp" #define AUTHORS proper_name ("Jim Meyering") static const char *default_template = "tmp.XXXXXXXXXX"; /* 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\ TEMPLATE must end in at least 3 consecutive `X's.\n\ If TEMPLATE is not specified, use tmp.XXXXXXXXXX, and --tmpdir is implied.\n\ "), stdout); fputs ("\n", stdout); fputs (_("\ -d, --directory create a directory, not a file\n\ -u, --dry-run do not create anything; merely print a name (unsafe)\n\ -q, --quiet suppress diagnostics about file/dir-creation failure\n\ "), stdout); fputs (_("\ --tmpdir[=DIR] interpret TEMPLATE relative to DIR. If DIR is not\n\ specified, use $TMPDIR if set, else /tmp. With\n\ this option, TEMPLATE must not be an absolute name.\n\ Unlike with -t, TEMPLATE may contain slashes, but\n\ mktemp creates only the final component.\n\ "), stdout); fputs ("\n", stdout); fputs (_("\ -p DIR use DIR as a prefix; implies -t [deprecated]\n\ -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_ancillary_info (); } 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, 0, 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, 0, dry_run ? GT_NOCREATE : GT_DIR, suff_len); } int main (int argc, char **argv) { char const *dest_dir; char const *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); set_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': /* Undocumented alias. FIXME: remove in 2011. */ 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. */ if (!freopen ("/dev/null", "wb", stderr)) error (EXIT_FAILURE, errno, _("failed to redirect stderr to /dev/null")); } 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)); } template = file_name_concat (dest_dir, template, NULL); } else { template = xstrdup (template); } /* Make a copy to be used in case of diagnostic, since failing mkstemp may leave the buffer in an undefined state. */ 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 (template)); 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 (template)); status = EXIT_FAILURE; } } if (status == EXIT_SUCCESS) { puts (dest_name); /* If we created a file, but then failed to output the file name, we should clean up the mess before failing. */ if (!dry_run && close_stream (stdout)) { int saved_errno = errno; remove (dest_name); error (EXIT_FAILURE, saved_errno, _("write error")); } } #ifdef lint free (dest_name); free (template); #endif exit (status); }