core: Dedup hardlink/tempfile code

I wanted to avoid yet another copy of the "generate tempfile name"
code, so moved it to libglnx:

  https://github.com/GNOME/libglnx/pull/14

This also closes the TODO about deduping the "break one hardlink" code
with the "break all links in one dir" code.  The core observation here
is that it's simpler to copy to a tempfile and rename over the
existing, rather than rename, create, unlink.

Closes: #293
Approved by: jlebon
This commit is contained in:
Colin Walters 2016-05-29 12:36:43 -04:00 committed by Atomic Bot
parent e0637544bd
commit 93e4039f60
2 changed files with 46 additions and 146 deletions

@ -1 +1 @@
Subproject commit 40ef5f7400d4f8eed6a8f834917008b33ad4fb4e
Subproject commit afe3c3a86178c29ceaa3a5e46397ab2fa97202b3

View File

@ -1412,96 +1412,11 @@ ostree_checkout_package (OstreeRepo *repo,
return ret;
}
/* XXX: adapted from glnx_mkdtempat(). try to send upstream. */
/* Searches for a free filename matching the @dst_path_tmpl template and renames
* @src_path to that. */
static gboolean
renametempat (int src_dfd,
const char *src_path,
int dst_dfd,
char *dst_path_tmpl,
GError **error)
{
char *XXXXXX;
int count;
static const char letters[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
static const int NLETTERS = sizeof (letters) - 1;
glong value;
GTimeVal tv;
static int counter = 0;
g_return_val_if_fail (dst_path_tmpl != NULL, -1);
/* find the last occurrence of "XXXXXX" */
XXXXXX = g_strrstr (dst_path_tmpl, "XXXXXX");
if (!XXXXXX || strncmp (XXXXXX, "XXXXXX", 6))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
"Invalid temporary file template '%s'", dst_path_tmpl);
return FALSE;
}
/* 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];
/* I initially wrote this function thinking I could use renameat2, which
* would make the operation safer and simpler, but since it was introduced
* in 3.15, let's just do it in two steps for now (XXX: or maybe we should
* #ifdef it). */
if (TEMP_FAILURE_RETRY (faccessat (dst_dfd, dst_path_tmpl, F_OK,
AT_SYMLINK_NOFOLLOW)) == 0)
continue; /* file exists */
else
{
if (errno != ENOENT)
{
/* 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.
*/
glnx_set_prefix_error_from_errno (error, "%s", "faccessat");
return FALSE;
}
}
if (TEMP_FAILURE_RETRY (renameat (src_dfd, src_path,
dst_dfd, dst_path_tmpl) != 0))
{
glnx_set_prefix_error_from_errno (error, "%s", "renameat");
return FALSE;
}
return TRUE;
}
g_set_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS,
"renametempat ran out of combinations to try.");
return FALSE;
}
/* Given a path to a file/symlink, make a copy of it if it's a hard link. */
/* XXX: we should probably refactor this and the next two funcs so they share
* more code. */
/* Given a path to a file/symlink, make a copy (reflink if possible)
* of it if it's a hard link. We need this for two places right now:
* - The RPM database
* - SELinux policy "denormalization" where a label changes
*/
static gboolean
break_single_hardlink_at (int dfd,
const char *path,
@ -1526,17 +1441,39 @@ break_single_hardlink_at (int dfd,
if (stbuf.st_nlink > 1)
{
g_autofree char *path_tmp = g_strconcat (path, ".XXXXXX", NULL);
if (!renametempat (dfd, path, dfd, path_tmp, error))
goto out;
guint count;
gboolean copy_success = FALSE;
char *path_tmp = glnx_strjoina (path, ".XXXXXX");
if (!glnx_file_copy_at (dfd, path_tmp, &stbuf, dfd, path, 0,
cancellable, error))
goto out;
if (unlinkat (dfd, path_tmp, 0) < 0)
for (count = 0; count < 100; count++)
{
glnx_set_prefix_error_from_errno (error, "%s", "unlinkat");
g_autoptr(GError) tmp_error = NULL;
glnx_gen_temp_name (path_tmp);
if (!glnx_file_copy_at (dfd, path, &stbuf, dfd, path_tmp, 0,
cancellable, &tmp_error))
{
if (g_error_matches (tmp_error, G_IO_ERROR, G_IO_ERROR_EXISTS))
continue;
g_propagate_error (error, g_steal_pointer (&tmp_error));
goto out;
}
copy_success = TRUE;
break;
}
if (!copy_success)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS,
"Exceeded limit of %u file creation attempts", count);
goto out;
}
if (renameat (dfd, path_tmp, dfd, path) != 0)
{
glnx_set_prefix_error_from_errno (error, "Rename %s", path);
goto out;
}
}
@ -1546,34 +1483,6 @@ out:
return ret;
}
static gboolean
copy_dir_contents_nonrecurse_at (int src_dfd,
const char *srcpath,
int dest_dfd,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
g_auto(GLnxDirFdIterator) dfd_iter = { FALSE, };
struct dirent *dent = NULL;
if (!glnx_dirfd_iterator_init_at (src_dfd, srcpath, TRUE, &dfd_iter, error))
goto out;
while (glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, cancellable, error))
{
if (dent == NULL)
break;
if (!glnx_file_copy_at (dfd_iter.fd, dent->d_name, NULL, dest_dfd,
dent->d_name, 0, cancellable, error))
goto out;
}
ret = TRUE;
out:
return ret;
}
/* Given a directory referred to by @dfd and @dirpath, ensure that physical (or
* reflink'd) copies of all files are done. */
static gboolean
@ -1583,30 +1492,21 @@ break_hardlinks_at (int dfd,
GError **error)
{
gboolean ret = FALSE;
glnx_fd_close int dest_dfd = -1;
g_autofree char *dirpath_tmp = g_strconcat (dirpath, ".XXXXXX", NULL);
g_auto(GLnxDirFdIterator) dfd_iter = { FALSE, };
struct dirent *dent = NULL;
if (!renametempat (dfd, dirpath, dfd, dirpath_tmp, error))
if (!glnx_dirfd_iterator_init_at (dfd, dirpath, TRUE, &dfd_iter, error))
goto out;
/* We're not accurately copying the mode, but in reality modes don't
* matter since it's all immutable anyways.
*/
if (TEMP_FAILURE_RETRY (mkdirat (dfd, dirpath, 0755)) != 0)
while (glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, cancellable, error))
{
glnx_set_error_from_errno (error);
goto out;
if (dent == NULL)
break;
if (!break_single_hardlink_at (dfd_iter.fd, dent->d_name,
cancellable, error))
goto out;
}
if (!glnx_opendirat (dfd, dirpath, TRUE, &dest_dfd, error))
goto out;
if (!copy_dir_contents_nonrecurse_at (dfd, dirpath_tmp, dest_dfd, cancellable, error))
goto out;
if (!glnx_shutil_rm_rf_at (dfd, dirpath_tmp, cancellable, error))
goto out;
ret = TRUE;
out:
return ret;