Skip to content
Snippets Groups Projects
Commit 162d1f6b authored by Colin Walters's avatar Colin Walters
Browse files

fdio: Add glnx_file_copy_at()

This will allow deleting some code from OSTree for the config file
merging.  We're reusing some code from systemd, which a nice modern
clean codebase, and among other things this gets us BTRFS reflinking
(if available) again.
parent 9a727788
No related branches found
No related tags found
No related merge requests found
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
* *
* Copyright (C) 2014,2015 Colin Walters <walters@verbum.org>. * Copyright (C) 2014,2015 Colin Walters <walters@verbum.org>.
* *
* Portions derived from systemd:
* Copyright 2010 Lennart Poettering
*
* This library is free software; you can redistribute it and/or * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public * modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either * License as published by the Free Software Foundation; either
...@@ -22,9 +25,19 @@ ...@@ -22,9 +25,19 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <sys/ioctl.h>
#include <sys/sendfile.h>
#include <errno.h>
/* See linux.git/fs/btrfs/ioctl.h */
#define BTRFS_IOCTL_MAGIC 0x94
#define BTRFS_IOC_CLONE _IOW(BTRFS_IOCTL_MAGIC, 9, int)
#include <glnx-fdio.h> #include <glnx-fdio.h>
#include <glnx-errors.h> #include <glnx-errors.h>
#include <glnx-xattrs.h>
#include <glnx-backport-autoptr.h>
#include <glnx-local-alloc.h> #include <glnx-local-alloc.h>
static guint8* static guint8*
...@@ -223,3 +236,343 @@ glnx_file_get_contents_utf8_at (int dfd, ...@@ -223,3 +236,343 @@ glnx_file_get_contents_utf8_at (int dfd,
g_free (buf); g_free (buf);
return NULL; return NULL;
} }
/**
* glnx_readlinkat_malloc:
* @dfd: Directory file descriptor
* @subpath: Subpath
* @cancellable: Cancellable
* @error: Error
*
* Read the value of a symlink into a dynamically
* allocated buffer.
*/
char *
glnx_readlinkat_malloc (int dfd,
const char *subpath,
GCancellable *cancellable,
GError **error)
{
size_t l = 100;
for (;;)
{
char *c;
ssize_t n;
c = g_malloc (l);
n = TEMP_FAILURE_RETRY (readlinkat (dfd, subpath, c, l-1));
if (n < 0)
{
glnx_set_error_from_errno (error);
g_free (c);
return FALSE;
}
if ((size_t) n < l-1)
{
c[n] = 0;
return c;
}
g_free (c);
l *= 2;
}
g_assert_not_reached ();
}
static gboolean
copy_symlink_at (int src_dfd,
const char *src_subpath,
const struct stat *src_stbuf,
int dest_dfd,
const char *dest_subpath,
GLnxFileCopyFlags copyflags,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
g_autofree char *buf = NULL;
buf = glnx_readlinkat_malloc (src_dfd, src_subpath, cancellable, error);
if (!buf)
goto out;
if (TEMP_FAILURE_RETRY (symlinkat (buf, dest_dfd, dest_subpath)) != 0)
{
glnx_set_error_from_errno (error);
goto out;
}
if (!(copyflags & GLNX_FILE_COPY_NOXATTRS))
{
g_autoptr(GVariant) xattrs = NULL;
if (!glnx_dfd_name_get_all_xattrs (src_dfd, src_subpath, &xattrs,
cancellable, error))
goto out;
if (!glnx_dfd_name_set_all_xattrs (dest_dfd, dest_subpath, xattrs,
cancellable, error))
goto out;
}
if (TEMP_FAILURE_RETRY (fchownat (dest_dfd, dest_subpath,
src_stbuf->st_uid, src_stbuf->st_gid,
AT_SYMLINK_NOFOLLOW)) != 0)
{
glnx_set_error_from_errno (error);
goto out;
}
ret = TRUE;
out:
return ret;
}
#define COPY_BUFFER_SIZE (16*1024)
/* From systemd */
static int btrfs_reflink(int infd, int outfd) {
int r;
g_return_val_if_fail(infd >= 0, -1);
g_return_val_if_fail(outfd >= 0, -1);
r = ioctl(outfd, BTRFS_IOC_CLONE, infd);
if (r < 0)
return -errno;
return 0;
}
static int loop_write(int fd, const void *buf, size_t nbytes) {
const uint8_t *p = buf;
g_return_val_if_fail(fd >= 0, -1);
g_return_val_if_fail(buf, -1);
errno = 0;
while (nbytes > 0) {
ssize_t k;
k = write(fd, p, nbytes);
if (k < 0) {
if (errno == EINTR)
continue;
return -errno;
}
if (k == 0) /* Can't really happen */
return -EIO;
p += k;
nbytes -= k;
}
return 0;
}
static int copy_bytes(int fdf, int fdt, off_t max_bytes, bool try_reflink) {
bool try_sendfile = true;
int r;
g_return_val_if_fail (fdf >= 0, -1);
g_return_val_if_fail (fdt >= 0, -1);
/* Try btrfs reflinks first. */
if (try_reflink && max_bytes == (off_t) -1) {
r = btrfs_reflink(fdf, fdt);
if (r >= 0)
return r;
}
for (;;) {
size_t m = COPY_BUFFER_SIZE;
ssize_t n;
if (max_bytes != (off_t) -1) {
if (max_bytes <= 0)
return -EFBIG;
if ((off_t) m > max_bytes)
m = (size_t) max_bytes;
}
/* First try sendfile(), unless we already tried */
if (try_sendfile) {
n = sendfile(fdt, fdf, NULL, m);
if (n < 0) {
if (errno != EINVAL && errno != ENOSYS)
return -errno;
try_sendfile = false;
/* use fallback below */
} else if (n == 0) /* EOF */
break;
else if (n > 0)
/* Succcess! */
goto next;
}
/* As a fallback just copy bits by hand */
{
char buf[m];
n = read(fdf, buf, m);
if (n < 0)
return -errno;
if (n == 0) /* EOF */
break;
r = loop_write(fdt, buf, (size_t) n);
if (r < 0)
return r;
}
next:
if (max_bytes != (off_t) -1) {
g_assert(max_bytes >= n);
max_bytes -= n;
}
}
return 0;
}
/**
* glnx_file_copy_at:
* @src_dfd: Source directory fd
* @src_subpath: Subpath relative to @src_dfd
* @dest_dfd: Target directory fd
* @dest_subpath: Destination name
* @copyflags: Flags
* @cancellable: cancellable
* @error: Error
*
* Perform a full copy of the regular file or
* symbolic link from @src_subpath to @dest_subpath.
*
* If @src_subpath is anything other than a regular
* file or symbolic link, an error will be returned.
*/
gboolean
glnx_file_copy_at (int src_dfd,
const char *src_subpath,
struct stat *src_stbuf,
int dest_dfd,
const char *dest_subpath,
GLnxFileCopyFlags copyflags,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
int r;
int dest_open_flags;
struct timespec ts[2];
glnx_fd_close int src_fd = -1;
glnx_fd_close int dest_fd = -1;
if (g_cancellable_set_error_if_cancelled (cancellable, error))
goto out;
if (S_ISLNK (src_stbuf->st_mode))
{
return copy_symlink_at (src_dfd, src_subpath, src_stbuf,
dest_dfd, dest_subpath,
copyflags,
cancellable, error);
}
else if (!S_ISREG (src_stbuf->st_mode))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"Cannot copy non-regular/non-symlink file: %s", src_subpath);
goto out;
}
src_fd = TEMP_FAILURE_RETRY (openat (src_dfd, src_subpath, O_RDONLY | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW));
if (src_fd == -1)
{
glnx_set_error_from_errno (error);
goto out;
}
dest_open_flags = O_WRONLY | O_CREAT | O_CLOEXEC | O_NOCTTY;
if (!(copyflags & GLNX_FILE_COPY_OVERWRITE))
dest_open_flags |= O_EXCL;
else
dest_open_flags |= O_TRUNC;
dest_fd = TEMP_FAILURE_RETRY (openat (dest_dfd, dest_subpath, dest_open_flags));
if (dest_fd == -1)
{
glnx_set_error_from_errno (error);
goto out;
}
r = copy_bytes (src_fd, dest_fd, (off_t) -1, TRUE);
if (r < 0)
{
errno = -r;
glnx_set_error_from_errno (error);
goto out;
}
if (fchown (dest_fd, src_stbuf->st_uid, src_stbuf->st_gid) != 0)
{
glnx_set_error_from_errno (error);
goto out;
}
if (fchmod (dest_fd, src_stbuf->st_mode & 07777) != 0)
{
glnx_set_error_from_errno (error);
goto out;
}
ts[0] = src_stbuf->st_atim;
ts[1] = src_stbuf->st_mtim;
(void) futimens (dest_fd, ts);
if (!(copyflags & GLNX_FILE_COPY_NOXATTRS))
{
g_autoptr(GVariant) xattrs = NULL;
if (!glnx_fd_get_all_xattrs (src_fd, &xattrs,
cancellable, error))
goto out;
if (!glnx_fd_set_all_xattrs (dest_fd, xattrs,
cancellable, error))
goto out;
}
if (copyflags & GLNX_FILE_COPY_DATASYNC)
{
if (fdatasync (dest_fd) < 0)
{
glnx_set_error_from_errno (error);
goto out;
}
}
r = close (dest_fd);
dest_fd = -1;
if (r < 0)
{
glnx_set_error_from_errno (error);
goto out;
}
ret = TRUE;
out:
if (!ret)
(void) unlinkat (dest_dfd, dest_subpath, 0);
return ret;
}
...@@ -47,4 +47,26 @@ glnx_file_get_contents_utf8_at (int dfd, ...@@ -47,4 +47,26 @@ glnx_file_get_contents_utf8_at (int dfd,
GCancellable *cancellable, GCancellable *cancellable,
GError **error); GError **error);
char *
glnx_readlinkat_malloc (int dfd,
const char *subpath,
GCancellable *cancellable,
GError **error);
typedef enum {
GLNX_FILE_COPY_OVERWRITE,
GLNX_FILE_COPY_NOXATTRS,
GLNX_FILE_COPY_DATASYNC
} GLnxFileCopyFlags;
gboolean
glnx_file_copy_at (int src_dfd,
const char *src_subpath,
struct stat *src_stbuf,
int dest_dfd,
const char *dest_subpath,
GLnxFileCopyFlags copyflags,
GCancellable *cancellable,
GError **error);
G_END_DECLS G_END_DECLS
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment