lib/core: Add a "break hardlink" API
This imports the code from rpm-ostree:
9ff9f6c997/src/libpriv/rpmostree-util.c (L742)
I plan to use this for rofiles-fuse to implement
copyup: https://github.com/ostreedev/ostree/issues/1377
But it's just obviously generally useful for projects using
libostree I think.
Closes: #1378
Approved by: jlebon
This commit is contained in:
parent
d340fe4060
commit
4a2e08148d
|
|
@ -133,6 +133,7 @@ ostree_content_file_parse_at
|
||||||
ostree_raw_file_to_archive_z2_stream
|
ostree_raw_file_to_archive_z2_stream
|
||||||
ostree_raw_file_to_archive_z2_stream_with_options
|
ostree_raw_file_to_archive_z2_stream_with_options
|
||||||
ostree_raw_file_to_content_stream
|
ostree_raw_file_to_content_stream
|
||||||
|
ostree_break_hardlink
|
||||||
ostree_checksum_file_from_input
|
ostree_checksum_file_from_input
|
||||||
ostree_checksum_file
|
ostree_checksum_file
|
||||||
ostree_checksum_file_at
|
ostree_checksum_file_at
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
LIBOSTREE_2017.15 {
|
LIBOSTREE_2017.15 {
|
||||||
ostree_repo_fsck_object;
|
ostree_repo_fsck_object;
|
||||||
ostree_repo_mark_commit_partial;
|
ostree_repo_mark_commit_partial;
|
||||||
|
ostree_break_hardlink;
|
||||||
} LIBOSTREE_2017.14;
|
} LIBOSTREE_2017.14;
|
||||||
|
|
||||||
/* Stub section for the stable release *after* this development one; don't
|
/* Stub section for the stable release *after* this development one; don't
|
||||||
|
|
|
||||||
|
|
@ -749,6 +749,86 @@ ostree_content_file_parse (gboolean compressed,
|
||||||
cancellable, error);
|
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:
|
* ostree_checksum_file_from_input:
|
||||||
* @file_info: File information
|
* @file_info: File information
|
||||||
|
|
|
||||||
|
|
@ -438,6 +438,13 @@ gboolean ostree_checksum_file (GFile *f,
|
||||||
GCancellable *cancellable,
|
GCancellable *cancellable,
|
||||||
GError **error);
|
GError **error);
|
||||||
|
|
||||||
|
_OSTREE_PUBLIC
|
||||||
|
gboolean ostree_break_hardlink (int dfd,
|
||||||
|
const char *path,
|
||||||
|
gboolean skip_xattrs,
|
||||||
|
GCancellable *cancellable,
|
||||||
|
GError **error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OstreeChecksumFlags:
|
* OstreeChecksumFlags:
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <gio/gio.h>
|
#include <gio/gio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <err.h>
|
||||||
|
|
||||||
#include "libglnx.h"
|
#include "libglnx.h"
|
||||||
#include "libostreetest.h"
|
#include "libostreetest.h"
|
||||||
|
|
@ -236,6 +237,72 @@ test_object_writes (gconstpointer data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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*
|
static GVariant*
|
||||||
xattr_cb (OstreeRepo *repo,
|
xattr_cb (OstreeRepo *repo,
|
||||||
const char *path,
|
const char *path,
|
||||||
|
|
@ -376,6 +443,7 @@ int main (int argc, char **argv)
|
||||||
g_test_add_data_func ("/raw-file-to-archive-stream", repo, test_raw_file_to_archive_stream);
|
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_data_func ("/objectwrites", repo, test_object_writes);
|
||||||
g_test_add_func ("/xattrs-devino-cache", test_devino_cache_xattrs);
|
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);
|
g_test_add_func ("/remotename", test_validate_remotename);
|
||||||
|
|
||||||
return g_test_run();
|
return g_test_run();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue