Add g_mkdtemp in the spirit of g_mkstemp

At the same time, also add g_mkdtemp_full and g_dir_make_tmp
variants. The patch also unifies the unique-name-generating
code for all variants of mkstemp and mkdtemp and adds tests
for the new functions.

Based on patches by Paolo Bonzini,
http://bugzilla.gnome.org/show_bug.cgi?id=118563
This commit is contained in:
Matthias Clasen 2011-08-14 14:09:58 -04:00
parent 8377a88685
commit b76bb6713b
5 changed files with 316 additions and 163 deletions

View File

@ -1174,6 +1174,9 @@ g_mkstemp_full
g_file_open_tmp g_file_open_tmp
g_file_read_link g_file_read_link
g_mkdir_with_parents g_mkdir_with_parents
g_mkdtemp
g_mkdtemp_full
g_dir_make_tmp
<SUBSECTION> <SUBSECTION>
GDir GDir

View File

@ -1173,39 +1173,17 @@ g_file_set_contents (const gchar *filename,
return retval; return retval;
} }
/**
* g_mkstemp_full:
* @tmpl: template filename
* @flags: flags to pass to an open() call in addition to O_EXCL and
* O_CREAT, which are passed automatically
* @mode: permissios to create the temporary file with
*
* Opens a temporary file. See the mkstemp() documentation
* on most UNIX-like systems.
*
* The parameter is a string that should follow the rules for
* mkstemp() templates, i.e. contain the string "XXXXXX".
* g_mkstemp_full() is slightly more flexible than mkstemp()
* in that the sequence does not have to occur at the very end of the
* template and you can pass a @mode and additional @flags. The X
* string will be modified to form the name of a file that didn't exist.
* The string should be in the GLib file name encoding. Most importantly,
* on Windows it should be in UTF-8.
*
* Return value: A file handle (as from open()) to the file
* opened for reading and writing. The file handle should be
* closed with close(). In case of errors, -1 is returned.
*
* Since: 2.22
*/
/* /*
* g_mkstemp_full based on the mkstemp implementation from the GNU C library. * get_tmp_file based on the mkstemp implementation from the GNU C library.
* Copyright (C) 1991,92,93,94,95,96,97,98,99 Free Software Foundation, Inc. * Copyright (C) 1991,92,93,94,95,96,97,98,99 Free Software Foundation, Inc.
*/ */
gint typedef gint (*GTmpFileCallback) (gchar *, gint, gint);
g_mkstemp_full (gchar *tmpl,
int flags, static gint
int mode) get_tmp_file (gchar *tmpl,
GTmpFileCallback f,
int flags,
int mode)
{ {
char *XXXXXX; char *XXXXXX;
int count, fd; int count, fd;
@ -1218,7 +1196,6 @@ g_mkstemp_full (gchar *tmpl,
g_return_val_if_fail (tmpl != NULL, -1); g_return_val_if_fail (tmpl != NULL, -1);
/* find the last occurrence of "XXXXXX" */ /* find the last occurrence of "XXXXXX" */
XXXXXX = g_strrstr (tmpl, "XXXXXX"); XXXXXX = g_strrstr (tmpl, "XXXXXX");
@ -1249,16 +1226,15 @@ g_mkstemp_full (gchar *tmpl,
v /= NLETTERS; v /= NLETTERS;
XXXXXX[5] = letters[v % NLETTERS]; XXXXXX[5] = letters[v % NLETTERS];
/* tmpl is in UTF-8 on Windows, thus use g_open() */ fd = f (tmpl, flags, mode);
fd = g_open (tmpl, flags | O_CREAT | O_EXCL, mode);
if (fd >= 0) if (fd >= 0)
return fd; return fd;
else if (errno != EEXIST) else if (errno != EEXIST)
/* Any other error will apply also to other names we might /* Any other error will apply also to other names we might
* try, and there are 2^32 or so of them, so give up now. * try, and there are 2^32 or so of them, so give up now.
*/ */
return -1; return -1;
} }
/* We got out of the loop because we ran out of combinations to try. */ /* We got out of the loop because we ran out of combinations to try. */
@ -1266,66 +1242,142 @@ g_mkstemp_full (gchar *tmpl,
return -1; return -1;
} }
gint
wrap_mkdir (gchar *tmpl,
int flags G_GNUC_UNUSED,
int mode)
{
/* tmpl is in UTF-8 on Windows, thus use g_mkdir() */
return g_mkdir (tmpl, mode);
}
/**
* g_mkdtemp_full:
* @tmpl: template directory name
* @mode: permissions to create the temporary directory with
*
* Creates a temporary directory. See the mkdtemp() documentation
* on most UNIX-like systems.
*
* The parameter is a string that should follow the rules for
* mkdtemp() templates, i.e. contain the string "XXXXXX".
* g_mkdtemp() is slightly more flexible than mkdtemp() in that the
* sequence does not have to occur at the very end of the template
* and you can pass a @mode. The X string will be modified to form
* the name of a directory that didn't exist. The string should be
* in the GLib file name encoding. Most importantly, on Windows it
* should be in UTF-8.
*
* Return value: A pointer to @tmpl, which has been modified
* to hold the directory name. In case of errors, %NULL is returned.
*
* Since: 2.26
*/
gchar *
g_mkdtemp_full (gchar *tmpl,
gint mode)
{
if (get_tmp_file (tmpl, wrap_mkdir, 0, mode) == -1)
return NULL;
else
return tmpl;
}
/**
* g_mkdtemp:
* @tmpl: template directory name
*
* Creates a temporary directory. See the mkdtemp() documentation
* on most UNIX-like systems.
*
* The parameter is a string that should follow the rules for
* mkdtemp() templates, i.e. contain the string "XXXXXX".
* g_mkdtemp() is slightly more flexible than mkdtemp() in that the
* sequence does not have to occur at the very end of the template
* and you can pass a @mode and additional @flags. The X string will
* be modified to form the name of a directory that didn't exist.
* The string should be in the GLib file name encoding. Most importantly,
* on Windows it should be in UTF-8.
*
* Return value: A pointer to @tmpl, which has been modified
* to hold the directory name. In case of errors, NULL is returned.
*
* Since: 2.26
*/
gchar *
g_mkdtemp (gchar *tmpl)
{
return g_mkdtemp_full (tmpl, 0700);
}
/**
* g_mkstemp_full:
* @tmpl: template filename
* @flags: flags to pass to an open() call in addition to O_EXCL
* and O_CREAT, which are passed automatically
* @mode: permissions to create the temporary file with
*
* Opens a temporary file. See the mkstemp() documentation
* on most UNIX-like systems.
*
* The parameter is a string that should follow the rules for
* mkstemp() templates, i.e. contain the string "XXXXXX".
* g_mkstemp_full() is slightly more flexible than mkstemp()
* in that the sequence does not have to occur at the very end of the
* template and you can pass a @mode and additional @flags. The X
* string will be modified to form the name of a file that didn't exist.
* The string should be in the GLib file name encoding. Most importantly,
* on Windows it should be in UTF-8.
*
* Return value: A file handle (as from open()) to the file
* opened for reading and writing. The file handle should be
* closed with close(). In case of errors, -1 is returned.
*
* Since: 2.22
*/
gint
g_mkstemp_full (gchar *tmpl,
gint flags,
gint mode)
{
/* tmpl is in UTF-8 on Windows, thus use g_open() */
return get_tmp_file (tmpl, (GTmpFileCallback) g_open,
flags | O_CREAT | O_EXCL, mode);
}
/** /**
* g_mkstemp: * g_mkstemp:
* @tmpl: template filename * @tmpl: template filename
* *
* Opens a temporary file. See the mkstemp() documentation * Opens a temporary file. See the mkstemp() documentation
* on most UNIX-like systems. * on most UNIX-like systems.
* *
* The parameter is a string that should follow the rules for * The parameter is a string that should follow the rules for
* mkstemp() templates, i.e. contain the string "XXXXXX". * mkstemp() templates, i.e. contain the string "XXXXXX".
* g_mkstemp() is slightly more flexible than mkstemp() * g_mkstemp() is slightly more flexible than mkstemp() in that the
* in that the sequence does not have to occur at the very end of the * sequence does not have to occur at the very end of the template.
* template. The X string will * The X string will be modified to form the name of a file that
* be modified to form the name of a file that didn't exist. * didn't exist. The string should be in the GLib file name encoding.
* The string should be in the GLib file name encoding. Most importantly, * Most importantly, on Windows it should be in UTF-8.
* on Windows it should be in UTF-8.
* *
* Return value: A file handle (as from open()) to the file * Return value: A file handle (as from open()) to the file
* opened for reading and writing. The file is opened in binary mode * opened for reading and writing. The file is opened in binary
* on platforms where there is a difference. The file handle should be * mode on platforms where there is a difference. The file handle
* closed with close(). In case of errors, -1 is returned. * should be closed with close(). In case of errors, -1 is returned.
*/ */
gint gint
g_mkstemp (gchar *tmpl) g_mkstemp (gchar *tmpl)
{ {
return g_mkstemp_full (tmpl, O_RDWR | O_BINARY, 0600); return g_mkstemp_full (tmpl, O_RDWR | O_BINARY, 0600);
} }
/** static gint
* g_file_open_tmp: g_get_tmp_name (const gchar *tmpl,
* @tmpl: Template for file name, as in g_mkstemp(), basename only, gchar **name_used,
* or %NULL, to a default template GTmpFileCallback f,
* @name_used: location to store actual name used, or %NULL gint flags,
* @error: return location for a #GError gint mode,
* GError **error)
* Opens a file for writing in the preferred directory for temporary
* files (as returned by g_get_tmp_dir()).
*
* @tmpl should be a string in the GLib file name encoding containing
* a sequence of six 'X' characters, as the parameter to g_mkstemp().
* However, unlike these functions, the template should only be a
* basename, no directory components are allowed. If template is
* %NULL, a default template is used.
*
* Note that in contrast to g_mkstemp() (and mkstemp())
* @tmpl is not modified, and might thus be a read-only literal string.
*
* The actual name used is returned in @name_used if non-%NULL. This
* string should be freed with g_free() when not needed any longer.
* The returned name is in the GLib file name encoding.
*
* Return value: A file handle (as from open()) to
* the file opened for reading and writing. The file is opened in binary
* mode on platforms where there is a difference. The file handle should be
* closed with close(). In case of errors, -1 is returned
* and @error will be set.
**/
gint
g_file_open_tmp (const gchar *tmpl,
gchar **name_used,
GError **error)
{ {
int retval; int retval;
const char *tmpdir; const char *tmpdir;
@ -1348,23 +1400,23 @@ g_file_open_tmp (const gchar *tmpl,
c[1] = '\0'; c[1] = '\0';
g_set_error (error, g_set_error (error,
G_FILE_ERROR, G_FILE_ERROR,
G_FILE_ERROR_FAILED, G_FILE_ERROR_FAILED,
_("Template '%s' invalid, should not contain a '%s'"), _("Template '%s' invalid, should not contain a '%s'"),
display_tmpl, c); display_tmpl, c);
g_free (display_tmpl); g_free (display_tmpl);
return -1; return -1;
} }
if (strstr (tmpl, "XXXXXX") == NULL) if (strstr (tmpl, "XXXXXX") == NULL)
{ {
gchar *display_tmpl = g_filename_display_name (tmpl); gchar *display_tmpl = g_filename_display_name (tmpl);
g_set_error (error, g_set_error (error,
G_FILE_ERROR, G_FILE_ERROR,
G_FILE_ERROR_FAILED, G_FILE_ERROR_FAILED,
_("Template '%s' doesn't contain XXXXXX"), _("Template '%s' doesn't contain XXXXXX"),
display_tmpl); display_tmpl);
g_free (display_tmpl); g_free (display_tmpl);
return -1; return -1;
} }
@ -1378,31 +1430,118 @@ g_file_open_tmp (const gchar *tmpl,
fulltemplate = g_strconcat (tmpdir, sep, tmpl, NULL); fulltemplate = g_strconcat (tmpdir, sep, tmpl, NULL);
retval = g_mkstemp (fulltemplate); retval = get_tmp_file (fulltemplate, f, flags, mode);
if (retval == -1) if (retval == -1)
{ {
int save_errno = errno; int save_errno = errno;
gchar *display_fulltemplate = g_filename_display_name (fulltemplate); gchar *display_fulltemplate = g_filename_display_name (fulltemplate);
g_set_error (error, g_set_error (error,
G_FILE_ERROR, G_FILE_ERROR,
g_file_error_from_errno (save_errno), g_file_error_from_errno (save_errno),
_("Failed to create file '%s': %s"), _("Failed to create file '%s': %s"),
display_fulltemplate, g_strerror (save_errno)); display_fulltemplate, g_strerror (save_errno));
g_free (display_fulltemplate); g_free (display_fulltemplate);
g_free (fulltemplate); g_free (fulltemplate);
return -1; return -1;
} }
if (name_used) *name_used = fulltemplate;
*name_used = fulltemplate;
else
g_free (fulltemplate);
return retval; return retval;
} }
/**
* g_file_open_tmp:
* @tmpl: Template for file name, as in g_mkstemp(), basename only,
* or %NULL, to a default template
* @name_used: location to store actual name used, or %NULL
* @error: return location for a #GError
*
* Opens a file for writing in the preferred directory for temporary
* files (as returned by g_get_tmp_dir()).
*
* @tmpl should be a string in the GLib file name encoding containing
* a sequence of six 'X' characters, as the parameter to g_mkstemp().
* However, unlike these functions, the template should only be a
* basename, no directory components are allowed. If template is
* %NULL, a default template is used.
*
* Note that in contrast to g_mkstemp() (and mkstemp()) @tmpl is not
* modified, and might thus be a read-only literal string.
*
* Upon success, and if @name_used is non-%NULL, the actual name used
* is returned in @name_used. This string should be freed with g_free()
* when not needed any longer. The returned name is in the GLib file
* name encoding.
*
* Return value: A file handle (as from open()) to the file opened for
* reading and writing. The file is opened in binary mode on platforms
* where there is a difference. The file handle should be closed with
* close(). In case of errors, -1 is returned and @error will be set.
*/
gint
g_file_open_tmp (const gchar *tmpl,
gchar **name_used,
GError **error)
{
gchar *fulltemplate;
gint result;
result = g_get_tmp_name (tmpl, &fulltemplate,
(GTmpFileCallback) g_open,
O_CREAT | O_EXCL | O_RDWR | O_BINARY,
0600,
error);
if (result != -1)
{
if (name_used)
*name_used = fulltemplate;
else
g_free (fulltemplate);
}
return result;
}
/**
* g_dir_make_tmp:
* @tmpl: Template for directory name, as in g_mkdtemp(), basename only,
* or %NULL, to a default template
* @name_used: location to store actual name used, or %NULL
* @error: return location for a #GError
*
* Creates a subdirectory in the preferred directory for temporary
* files (as returned by g_get_tmp_dir()).
*
* @tmpl should be a string in the GLib file name encoding containing
* a sequence of six 'X' characters, as the parameter to g_mkstemp().
* However, unlike these functions, the template should only be a
* basename, no directory components are allowed. If template is
* %NULL, a default template is used.
*
* Note that in contrast to g_mkdtemp() (and mkdtemp()) @tmpl is not
* modified, and might thus be a read-only literal string.
*
* Return value: The actual name used. This string should be freed
* with g_free() when not needed any longer and is is in the GLib
* file name encoding. In case of errors, %NULL is returned
* and @error will be set.
*
* Since: 2.30
*/
gchar *
g_dir_make_tmp (const gchar *tmpl,
GError **error)
{
gchar *fulltemplate;
if (g_get_tmp_name (tmpl, &fulltemplate, wrap_mkdir, 0, 0700, error) == -1)
return NULL;
else
return fulltemplate;
}
static gchar * static gchar *
g_build_path_va (const gchar *separator, g_build_path_va (const gchar *separator,
const gchar *first_element, const gchar *first_element,
@ -2143,62 +2282,11 @@ g_file_get_contents (const gchar *filename,
gint gint
g_mkstemp (gchar *tmpl) g_mkstemp (gchar *tmpl)
{ {
char *XXXXXX; /* This is the backward compatibility system codepage version,
int count, fd; * thus use normal open().
static const char letters[] = */
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; return get_tmp_file (tmpl, (GTmpFileCallback) open,
static const int NLETTERS = sizeof (letters) - 1; O_RDWR | O_CREAT | O_EXCL, 0600);
glong value;
GTimeVal tv;
static int counter = 0;
/* find the last occurrence of 'XXXXXX' */
XXXXXX = g_strrstr (tmpl, "XXXXXX");
if (!XXXXXX)
{
errno = EINVAL;
return -1;
}
/* Get some more or less random data. */
g_get_current_time (&tv);
value = (tv.tv_usec ^ tv.tv_sec) + counter++;
for (count = 0; count < 100; value += 7777, ++count)
{
glong v = value;
/* Fill in the random bits. */
XXXXXX[0] = letters[v % NLETTERS];
v /= NLETTERS;
XXXXXX[1] = letters[v % NLETTERS];
v /= NLETTERS;
XXXXXX[2] = letters[v % NLETTERS];
v /= NLETTERS;
XXXXXX[3] = letters[v % NLETTERS];
v /= NLETTERS;
XXXXXX[4] = letters[v % NLETTERS];
v /= NLETTERS;
XXXXXX[5] = letters[v % NLETTERS];
/* This is the backward compatibility system codepage version,
* thus use normal open().
*/
fd = open (tmpl, O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0600);
if (fd >= 0)
return fd;
else if (errno != EEXIST)
/* Any other error will apply also to other names we might
* try, and there are 2^32 or so of them, so give up now.
*/
return -1;
}
/* We got out of the loop because we ran out of combinations to try. */
errno = EEXIST;
return -1;
} }
#undef g_file_open_tmp #undef g_file_open_tmp

View File

@ -99,16 +99,23 @@ gboolean g_file_set_contents (const gchar *filename,
gchar *g_file_read_link (const gchar *filename, gchar *g_file_read_link (const gchar *filename,
GError **error); GError **error);
/* Wrapper / workalike for mkdtemp() */
gchar *g_mkdtemp (gchar *tmpl);
gchar *g_mkdtemp_full (gchar *tmpl,
gint mode);
/* Wrapper / workalike for mkstemp() */ /* Wrapper / workalike for mkstemp() */
gint g_mkstemp (gchar *tmpl); gint g_mkstemp (gchar *tmpl);
gint g_mkstemp_full (gchar *tmpl, gint g_mkstemp_full (gchar *tmpl,
int flags, gint flags,
int mode); gint mode);
/* Wrapper for g_mkstemp */ /* Wrappers for g_mkstemp and g_mkdtemp() */
gint g_file_open_tmp (const gchar *tmpl, gint g_file_open_tmp (const gchar *tmpl,
gchar **name_used, gchar **name_used,
GError **error); GError **error);
gchar *g_dir_make_tmp (const gchar *tmpl,
GError **error);
typedef enum typedef enum
{ {

View File

@ -342,6 +342,7 @@ g_file_error_quark
g_file_get_contents PRIVATE g_file_get_contents PRIVATE
#endif #endif
g_file_set_contents g_file_set_contents
g_dir_make_tmp
#ifndef _WIN64 #ifndef _WIN64
g_file_open_tmp PRIVATE g_file_open_tmp PRIVATE
g_file_test PRIVATE g_file_test PRIVATE
@ -353,6 +354,8 @@ g_format_size_for_display
#ifndef _WIN64 #ifndef _WIN64
g_mkstemp PRIVATE g_mkstemp PRIVATE
#endif #endif
g_mkdtemp
g_mkdtemp_full
g_mkstemp_full g_mkstemp_full
g_mkdir_with_parents g_mkdir_with_parents
#ifdef G_OS_WIN32 #ifdef G_OS_WIN32

View File

@ -43,6 +43,8 @@
#include <unistd.h> #include <unistd.h>
#endif #endif
#include <fcntl.h> /* For open() */
#ifdef G_OS_WIN32 #ifdef G_OS_WIN32
#include <io.h> /* For read(), write() etc */ #include <io.h> /* For read(), write() etc */
#endif #endif
@ -95,6 +97,55 @@ test_mkstemp (void)
remove (template); remove (template);
} }
static void
test_mkdtemp (void)
{
char template[32], *retval;
int fd;
int i;
strcpy (template, "foodir");
retval = g_mkdtemp (template);
if (retval != NULL)
{
g_warning ("g_mkdtemp works even if template doesn't contain XXXXXX");
g_rmdir (retval);
}
strcpy (template, "foodir");
retval = g_mkdtemp (template);
if (retval != NULL)
{
g_warning ("g_mkdtemp works even if template contains less than six X");
g_rmdir (retval);
}
strcpy (template, "fooXXXXXX");
retval = g_mkdtemp (template);
g_assert (retval != NULL && "g_mkdtemp didn't work for template fooXXXXXX");
g_assert (retval == template && "g_mkdtemp allocated the resulting string?");
g_assert (!g_file_test (template, G_FILE_TEST_IS_REGULAR));
g_assert (g_file_test (template, G_FILE_TEST_IS_DIR));
strcat (template, "/abc");
fd = g_open (template, O_WRONLY | O_CREAT, 0600);
g_assert (fd != -1 && "couldn't open file in temporary directory");
close (fd);
g_assert (g_file_test (template, G_FILE_TEST_IS_REGULAR));
i = g_unlink (template);
g_assert (i != -1 && "couldn't unlink file in temporary directory");
template[9] = '\0';
i = g_rmdir (template);
g_assert (i != -1 && "couldn't remove temporary directory");
strcpy (template, "fooXXXXXX.dir");
retval = g_mkdtemp (template);
g_assert (retval != NULL && "g_mkdtemp didn't work for template fooXXXXXX.dir");
g_assert (g_file_test (template, G_FILE_TEST_IS_DIR));
g_rmdir (template);
}
static void static void
test_readlink (void) test_readlink (void)
{ {
@ -173,6 +224,7 @@ int
main (int argc, char *argv[]) main (int argc, char *argv[])
{ {
test_mkstemp (); test_mkstemp ();
test_mkdtemp ();
test_readlink (); test_readlink ();
test_get_contents (); test_get_contents ();