cancellable, error);
}
+/**
+ * ostree_break_hardlink:
+ * @dfd: Directory fd
+ * @path: Path relative to @dfd
+ * @skip_xattrs: Do not copy extended attributes
+ * @error: error
+ *
+ * In many cases using libostree, a program may need to "break"
+ * hardlinks by performing a copy. For example, in order to
+ * logically append to a file.
+ *
+ * This function performs full copying, including e.g. extended
+ * attributes and permissions of both regular files and symbolic links.
+ *
+ * If the file is not hardlinked, this function does nothing and
+ * returns successfully.
+ *
+ * This function does not perform synchronization via `fsync()` or
+ * `fdatasync()`; the idea is this will commonly be done as part
+ * of an `ostree_repo_commit_transaction()`, which itself takes
+ * care of synchronization.
+ *
+ * Since: 2017.15
+ */
+gboolean ostree_break_hardlink (int dfd,
+ const char *path,
+ gboolean skip_xattrs,
+ GCancellable *cancellable,
+ GError **error)
+{
+ struct stat stbuf;
+
+ if (!glnx_fstatat (dfd, path, &stbuf, AT_SYMLINK_NOFOLLOW, error))
+ return FALSE;
+
+ if (!S_ISLNK (stbuf.st_mode) && !S_ISREG (stbuf.st_mode))
+ return glnx_throw (error, "Unsupported type for entry '%s'", path);
+
+ const GLnxFileCopyFlags copyflags =
+ skip_xattrs ? GLNX_FILE_COPY_NOXATTRS : 0;
+
+ if (stbuf.st_nlink > 1)
+ {
+ guint count;
+ gboolean copy_success = FALSE;
+ char *path_tmp = glnx_strjoina (path, ".XXXXXX");
+
+ for (count = 0; count < 100; count++)
+ {
+ g_autoptr(GError) tmp_error = NULL;
+
+ glnx_gen_temp_name (path_tmp);
+
+ if (!glnx_file_copy_at (dfd, path, &stbuf, dfd, path_tmp, copyflags,
+ 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));
+ return FALSE;
+ }
+
+ 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);
+ return FALSE;
+ }
+
+ if (!glnx_renameat (dfd, path_tmp, dfd, path, error))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
/**
* ostree_checksum_file_from_input:
* @file_info: File information
#include <stdlib.h>
#include <gio/gio.h>
#include <string.h>
+#include <err.h>
#include "libglnx.h"
#include "libostreetest.h"
}
}
+static gboolean
+impl_test_break_hardlink (int tmp_dfd,
+ const char *path,
+ GError **error)
+{
+ const char *linkedpath = glnx_strjoina (path, ".link");
+ struct stat orig_stbuf;
+ if (!glnx_fstatat (tmp_dfd, path, &orig_stbuf, AT_SYMLINK_NOFOLLOW, error))
+ return FALSE;
+
+ /* Calling ostree_break_hardlink() should be a noop */
+ struct stat stbuf;
+ if (!ostree_break_hardlink (tmp_dfd, path, TRUE, NULL, error))
+ return FALSE;
+ if (!glnx_fstatat (tmp_dfd, path, &stbuf, AT_SYMLINK_NOFOLLOW, error))
+ return FALSE;
+
+ g_assert_cmpint (orig_stbuf.st_dev, ==, stbuf.st_dev);
+ g_assert_cmpint (orig_stbuf.st_ino, ==, stbuf.st_ino);
+
+ if (linkat (tmp_dfd, path, tmp_dfd, linkedpath, 0) < 0)
+ return glnx_throw_errno_prefix (error, "linkat");
+
+ if (!ostree_break_hardlink (tmp_dfd, path, TRUE, NULL, error))
+ return FALSE;
+ if (!glnx_fstatat (tmp_dfd, path, &stbuf, AT_SYMLINK_NOFOLLOW, error))
+ return FALSE;
+ /* This file should be different */
+ g_assert_cmpint (orig_stbuf.st_dev, ==, stbuf.st_dev);
+ g_assert_cmpint (orig_stbuf.st_ino, !=, stbuf.st_ino);
+ /* But this one is still the same */
+ if (!glnx_fstatat (tmp_dfd, linkedpath, &stbuf, AT_SYMLINK_NOFOLLOW, error))
+ return FALSE;
+ g_assert_cmpint (orig_stbuf.st_dev, ==, stbuf.st_dev);
+ g_assert_cmpint (orig_stbuf.st_ino, ==, stbuf.st_ino);
+
+ (void) unlinkat (tmp_dfd, path, 0);
+ (void) unlinkat (tmp_dfd, linkedpath, 0);
+
+ return TRUE;
+}
+
+static void
+test_break_hardlink (void)
+{
+ int tmp_dfd = AT_FDCWD;
+ g_autoptr(GError) error = NULL;
+
+ /* Regular file */
+ const char hello_hardlinked_content[] = "hello hardlinked content";
+ glnx_file_replace_contents_at (tmp_dfd, "test-hardlink",
+ (guint8*)hello_hardlinked_content,
+ strlen (hello_hardlinked_content),
+ GLNX_FILE_REPLACE_NODATASYNC,
+ NULL, &error);
+ g_assert_no_error (error);
+ (void)impl_test_break_hardlink (tmp_dfd, "test-hardlink", &error);
+ g_assert_no_error (error);
+
+ /* Symlink */
+ if (symlinkat ("some-path", tmp_dfd, "test-symhardlink") < 0)
+ err (1, "symlinkat");
+ (void)impl_test_break_hardlink (tmp_dfd, "test-symhardlink", &error);
+ g_assert_no_error (error);
+}
+
static GVariant*
xattr_cb (OstreeRepo *repo,
const char *path,
g_test_add_data_func ("/raw-file-to-archive-stream", repo, test_raw_file_to_archive_stream);
g_test_add_data_func ("/objectwrites", repo, test_object_writes);
g_test_add_func ("/xattrs-devino-cache", test_devino_cache_xattrs);
+ g_test_add_func ("/break-hardlink", test_break_hardlink);
g_test_add_func ("/remotename", test_validate_remotename);
return g_test_run();