HARDLINK_RESULT_LINKED
} HardlinkResult;
+/* Used for OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES. In
+ * order to atomically replace a target, we add a new link
+ * in self->tmp_dir_fd, with a name placed into the mutable
+ * buffer @tmpname.
+ */
+static gboolean
+hardlink_add_tmp_name (OstreeRepo *self,
+ int srcfd,
+ const char *loose_path,
+ char *tmpname,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const int max_attempts = 128;
+ guint i;
+
+ for (i = 0; i < max_attempts; i++)
+ {
+ glnx_gen_temp_name (tmpname);
+ if (linkat (srcfd, loose_path, self->tmp_dir_fd, tmpname, 0) < 0)
+ {
+ if (errno == EEXIST)
+ continue;
+ else
+ return glnx_throw_errno_prefix (error, "linkat");
+ }
+ else
+ break;
+ }
+ if (i == max_attempts)
+ return glnx_throw (error, "Exhausted attempts to make temporary hardlink");
+
+ return TRUE;
+}
+
static gboolean
checkout_file_hardlink (OstreeRepo *self,
OstreeRepoCheckoutAtOptions *options,
int srcfd = _ostree_repo_mode_is_bare (self->mode) ?
self->objects_dir_fd : self->uncompressed_objects_dir_fd;
- again:
if (linkat (srcfd, loose_path, destination_dfd, destination_name, 0) == 0)
ret_result = HARDLINK_RESULT_LINKED;
else if (!options->no_copy_fallback && (errno == EMLINK || errno == EXDEV || errno == EPERM))
ret_result = HARDLINK_RESULT_SKIP_EXISTED;
break;
case OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES:
- {
- /* Idiocy, from man rename(2)
- *
- * "If oldpath and newpath are existing hard links referring to
- * the same file, then rename() does nothing, and returns a
- * success status."
- *
- * So we can't make this atomic.
- */
- (void) unlinkat (destination_dfd, destination_name, 0);
- goto again;
- }
case OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_IDENTICAL:
{
- /* In this mode, we error out on EEXIST *unless* the files are already
- * hardlinked, which is what rpm-ostree wants for package layering.
- * https://github.com/projectatomic/rpm-ostree/issues/982
+ /* In both union-files and union-identical, see if the src/target are
+ * already hardlinked. If they are, we're done.
*
+ * If not, for union-identical we error out, which is what
+ * rpm-ostree wants for package layering.
+ * https://github.com/projectatomic/rpm-ostree/issues/982
* This should be similar to the librpm version:
* https://github.com/rpm-software-management/rpm/blob/e3cd2bc85e0578f158d14e6f9624eb955c32543b/lib/rpmfi.c#L921
* in rpmfilesCompare().
+ *
+ * For union-files, we make a temporary link, then rename() it
+ * into place.
*/
struct stat src_stbuf;
if (!glnx_fstatat (srcfd, loose_path, &src_stbuf,
const gboolean is_identical =
(src_stbuf.st_dev == dest_stbuf.st_dev &&
src_stbuf.st_ino == dest_stbuf.st_ino);
+
if (is_identical)
ret_result = HARDLINK_RESULT_SKIP_EXISTED;
+ else if (options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES)
+ {
+ char *tmpname = strdupa ("checkout-union-XXXXXX");
+ /* Make a link with a temp name */
+ if (!hardlink_add_tmp_name (self, srcfd, loose_path, tmpname, cancellable, error))
+ return FALSE;
+ /* Rename it into place */
+ if (!glnx_renameat (self->tmp_dir_fd, tmpname, destination_dfd, destination_name, error))
+ return FALSE;
+ ret_result = HARDLINK_RESULT_LINKED;
+ }
else
- return glnx_throw_errno_prefix (error, "Hardlinking %s to %s", loose_path, destination_name);
+ {
+ g_assert_cmpint (options->overwrite_mode, ==, OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_IDENTICAL);
+ return glnx_throw_errno_prefix (error, "Hardlinking %s to %s", loose_path, destination_name);
+ }
break;
}
}