Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lib/core: Add a "break hardlink" API #1378

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apidoc/ostree-sections.txt
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ ostree_content_file_parse_at
ostree_raw_file_to_archive_z2_stream
ostree_raw_file_to_archive_z2_stream_with_options
ostree_raw_file_to_content_stream
ostree_break_hardlink
ostree_checksum_file_from_input
ostree_checksum_file
ostree_checksum_file_at
Expand Down
1 change: 1 addition & 0 deletions src/libostree/libostree-devel.sym
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
LIBOSTREE_2017.15 {
ostree_repo_fsck_object;
ostree_repo_mark_commit_partial;
ostree_break_hardlink;
} LIBOSTREE_2017.14;

/* Stub section for the stable release *after* this development one; don't
Expand Down
101 changes: 101 additions & 0 deletions src/libostree/ostree-core.c
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,107 @@ ostree_content_file_parse (gboolean compressed,
cancellable, error);
}

static gboolean
break_symhardlink (int dfd,
const char *path,
struct stat *stbuf,
GLnxFileCopyFlags copyflags,
GCancellable *cancellable,
GError **error)
{
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_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 (stbuf.st_nlink <= 1)
return TRUE; /* Note early return */

const GLnxFileCopyFlags copyflags = skip_xattrs ? GLNX_FILE_COPY_NOXATTRS : 0;

if (S_ISREG (stbuf.st_mode))
/* Note it's now completely safe to copy a file to itself,
* as glnx_file_copy_at() is documented to do an O_TMPFILE + rename()
* with GLNX_FILE_COPY_OVERWRITE.
*/
return glnx_file_copy_at (dfd, path, &stbuf, dfd, path,
copyflags | GLNX_FILE_COPY_OVERWRITE,
cancellable, error);
else if (S_ISLNK (stbuf.st_mode))
return break_symhardlink (dfd, path, &stbuf, copyflags,
cancellable, error);
else
return glnx_throw (error, "Unsupported type for entry '%s'", path);

return TRUE;
}

/**
* ostree_checksum_file_from_input:
* @file_info: File information
Expand Down
7 changes: 7 additions & 0 deletions src/libostree/ostree-core.h
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,13 @@ gboolean ostree_checksum_file (GFile *f,
GCancellable *cancellable,
GError **error);

_OSTREE_PUBLIC
gboolean ostree_break_hardlink (int dfd,
const char *path,
gboolean skip_xattrs,
GCancellable *cancellable,
GError **error);

/**
* OstreeChecksumFlags:
*
Expand Down
68 changes: 68 additions & 0 deletions tests/test-basic-c.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <stdlib.h>
#include <gio/gio.h>
#include <string.h>
#include <err.h>

#include "libglnx.h"
#include "libostreetest.h"
Expand Down Expand Up @@ -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*
xattr_cb (OstreeRepo *repo,
const char *path,
Expand Down Expand Up @@ -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 ("/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();
Expand Down