-
Colin Walters authored
There are only two users of this in ostree, and one of them is fairly bogus; we can just use `fstat()`.
Colin Walters authoredThere are only two users of this in ostree, and one of them is fairly bogus; we can just use `fstat()`.
glnx-fdio.c 29.22 KiB
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* 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
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.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-dirfd.h>
#include <glnx-errors.h>
#include <glnx-xattrs.h>
#include <glnx-backport-autoptr.h>
#include <glnx-local-alloc.h>
#include <glnx-missing.h>
/* Returns the number of chars needed to format variables of the
* specified type as a decimal string. Adds in extra space for a
* negative '-' prefix (hence works correctly on signed
* types). Includes space for the trailing NUL. */
#define DECIMAL_STR_MAX(type) \
(2+(sizeof(type) <= 1 ? 3 : \
sizeof(type) <= 2 ? 5 : \
sizeof(type) <= 4 ? 10 : \
sizeof(type) <= 8 ? 20 : sizeof(int[-2*(sizeof(type) > 8)])))
gboolean
glnx_stdio_file_flush (FILE *f, GError **error)
{
if (fflush (f) != 0)
return glnx_throw_errno_prefix (error, "fflush");
if (ferror (f) != 0)
return glnx_throw_errno_prefix (error, "ferror");
return TRUE;
}
/* An implementation of renameat2(..., RENAME_NOREPLACE)
* with fallback to a non-atomic version.
*/
int
glnx_renameat2_noreplace (int olddirfd, const char *oldpath,
int newdirfd, const char *newpath)
{
#ifndef ENABLE_WRPSEUDO_COMPAT
if (renameat2 (olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE) < 0)
{
if (G_IN_SET(errno, EINVAL, ENOSYS))
{
/* Fall through */
}
else
{
return -1;
}
}
else
return TRUE;
#endif
if (linkat (olddirfd, oldpath, newdirfd, newpath, 0) < 0)
return -1;
if (unlinkat (olddirfd, oldpath, 0) < 0)
return -1;
return 0;
}
static gboolean
rename_file_noreplace_at (int olddirfd, const char *oldpath,
int newdirfd, const char *newpath,
gboolean ignore_eexist,
GError **error)
{
if (glnx_renameat2_noreplace (olddirfd, oldpath,
newdirfd, newpath) < 0)
{
if (errno == EEXIST && ignore_eexist)
{
(void) unlinkat (olddirfd, oldpath, 0);
return TRUE;
}
else
return glnx_throw_errno (error);
}
return TRUE;
}
/* An implementation of renameat2(..., RENAME_EXCHANGE)
* with fallback to a non-atomic version.
*/
int
glnx_renameat2_exchange (int olddirfd, const char *oldpath,
int newdirfd, const char *newpath)
{
#ifndef ENABLE_WRPSEUDO_COMPAT
if (renameat2 (olddirfd, oldpath, newdirfd, newpath, RENAME_EXCHANGE) == 0)
return 0;
else
{
if (G_IN_SET(errno, ENOSYS, EINVAL))
{
/* Fall through */
}
else
{
return -1;
}
}
#endif
/* Fallback */
{ const char *old_tmp_name = glnx_strjoina (oldpath, ".XXXXXX");
/* Move old out of the way */
if (renameat (olddirfd, oldpath, olddirfd, old_tmp_name) < 0)
return -1;
/* Now move new into its place */
if (renameat (newdirfd, newpath, olddirfd, oldpath) < 0)
return -1;
/* And finally old(tmp) into new */
if (renameat (olddirfd, old_tmp_name, newdirfd, newpath) < 0)
return -1;
}
return 0;
}
/* Deallocate a tmpfile, closing the fd and deleting the path, if any. This is
* normally called by default by the autocleanup attribute, but you can also
* invoke this directly.
*/
void
glnx_tmpfile_clear (GLnxTmpfile *tmpf)
{
/* Support being passed NULL so we work nicely in a GPtrArray */
if (!tmpf)
return;
if (!tmpf->initialized)
return;
if (tmpf->fd == -1)
return;
(void) close (tmpf->fd);
/* If ->path is set, we're likely aborting due to an error. Clean it up */
if (tmpf->path)
{
(void) unlinkat (tmpf->src_dfd, tmpf->path, 0);
g_free (tmpf->path);
}
tmpf->initialized = FALSE;
}
/* Allocate a temporary file, using Linux O_TMPFILE if available. The file mode
* will be 0600.
*
* The result will be stored in @out_tmpf, which is caller allocated
* so you can store it on the stack in common scenarios.
*
* The directory fd @dfd must live at least as long as the output @out_tmpf.
*/
gboolean
glnx_open_tmpfile_linkable_at (int dfd,
const char *subpath,
int flags,
GLnxTmpfile *out_tmpf,
GError **error)
{
const guint mode = 0600;
glnx_fd_close int fd = -1;
int count;
dfd = glnx_dirfd_canonicalize (dfd);
/* Don't allow O_EXCL, as that has a special meaning for O_TMPFILE */
g_return_val_if_fail ((flags & O_EXCL) == 0, FALSE);
/* Creates a temporary file, that shall be renamed to "target"
* later. If possible, this uses O_TMPFILE – in which case
* "ret_path" will be returned as NULL. If not possible a the
* tempoary path name used is returned in "ret_path". Use
* link_tmpfile() below to rename the result after writing the file
* in full. */
#if defined(O_TMPFILE) && !defined(DISABLE_OTMPFILE) && !defined(ENABLE_WRPSEUDO_COMPAT)
fd = openat (dfd, subpath, O_TMPFILE|flags, mode);
if (fd == -1 && !(G_IN_SET(errno, ENOSYS, EISDIR, EOPNOTSUPP)))
return glnx_throw_errno_prefix (error, "open(O_TMPFILE)");
if (fd != -1)
{
/* Workaround for https://sourceware.org/bugzilla/show_bug.cgi?id=17523
* See also https://github.com/ostreedev/ostree/issues/991
*/
if (fchmod (fd, mode) < 0)
return glnx_throw_errno_prefix (error, "fchmod");
out_tmpf->initialized = TRUE;
out_tmpf->src_dfd = dfd; /* Copied; caller must keep open */
out_tmpf->fd = glnx_steal_fd (&fd);
out_tmpf->path = NULL;
return TRUE;
}
/* Fallthrough */
#endif
{ g_autofree char *tmp = g_strconcat (subpath, "/tmp.XXXXXX", NULL);
const guint count_max = 100;
for (count = 0; count < count_max; count++)
{
glnx_gen_temp_name (tmp);
fd = openat (dfd, tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, mode);
if (fd < 0)
{
if (errno == EEXIST)
continue;
else
return glnx_throw_errno_prefix (error, "Creating temp file");
}
else
{
out_tmpf->initialized = TRUE;
out_tmpf->src_dfd = dfd; /* Copied; caller must keep open */
out_tmpf->fd = glnx_steal_fd (&fd);
out_tmpf->path = g_steal_pointer (&tmp);
return TRUE;
}
}
}
g_set_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS,
"Exhausted %u attempts to create temporary file", count);
return FALSE;
}
/* A variant of `glnx_open_tmpfile_linkable_at()` which doesn't support linking.
* Useful for true temporary storage. The fd will be allocated in /var/tmp to
* ensure maximum storage space.
*/
gboolean
glnx_open_anonymous_tmpfile (int flags,
GLnxTmpfile *out_tmpf,
GError **error)
{
if (!glnx_open_tmpfile_linkable_at (AT_FDCWD, "/var/tmp", flags, out_tmpf, error))
return FALSE;
if (out_tmpf->path)
{
(void) unlinkat (out_tmpf->src_dfd, out_tmpf->path, 0);
g_clear_pointer (&out_tmpf->path, g_free);
}
out_tmpf->anonymous = TRUE;
out_tmpf->src_dfd = -1;
return TRUE;
}
/* Use this after calling glnx_open_tmpfile_linkable_at() to give
* the file its final name (link into place).
*/
gboolean
glnx_link_tmpfile_at (GLnxTmpfile *tmpf,
GLnxLinkTmpfileReplaceMode mode,
int target_dfd,
const char *target,
GError **error)
{
const gboolean replace = (mode == GLNX_LINK_TMPFILE_REPLACE);
const gboolean ignore_eexist = (mode == GLNX_LINK_TMPFILE_NOREPLACE_IGNORE_EXIST);
g_return_val_if_fail (!tmpf->anonymous, FALSE);
g_return_val_if_fail (tmpf->fd >= 0, FALSE);
g_return_val_if_fail (tmpf->src_dfd == AT_FDCWD || tmpf->src_dfd >= 0, FALSE);
/* Unlike the original systemd code, this function also supports
* replacing existing files.
*/
/* We have `tmpfile_path` for old systems without O_TMPFILE. */
if (tmpf->path)
{
if (replace)
{
/* We have a regular tempfile, we're overwriting - this is a
* simple renameat().
*/
if (renameat (tmpf->src_dfd, tmpf->path, target_dfd, target) < 0)
return glnx_throw_errno_prefix (error, "renameat");
}
else
{
/* We need to use renameat2(..., NOREPLACE) or emulate it */
if (!rename_file_noreplace_at (tmpf->src_dfd, tmpf->path, target_dfd, target,
ignore_eexist,
error))
return FALSE;
}
/* Now, clear the pointer so we don't try to unlink it */
g_clear_pointer (&tmpf->path, g_free);
}
else
{
/* This case we have O_TMPFILE, so our reference to it is via /proc/self/fd */
char proc_fd_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(tmpf->fd) + 1];
sprintf (proc_fd_path, "/proc/self/fd/%i", tmpf->fd);
if (replace)
{
/* In this case, we had our temp file atomically hidden, but now
* we need to make it visible in the FS so we can do a rename.
* Ideally, linkat() would gain AT_REPLACE or so.
*/
/* TODO - avoid double alloca, we can just alloca a copy of
* the pathname plus space for tmp.XXXXX */
char *dnbuf = strdupa (target);
const char *dn = dirname (dnbuf);
char *tmpname_buf = glnx_strjoina (dn, "/tmp.XXXXXX");
guint count;
const guint count_max = 100;
for (count = 0; count < count_max; count++)
{
glnx_gen_temp_name (tmpname_buf);
if (linkat (AT_FDCWD, proc_fd_path, target_dfd, tmpname_buf, AT_SYMLINK_FOLLOW) < 0)
{
if (errno == EEXIST)
continue;
else
return glnx_throw_errno_prefix (error, "linkat");
}
else
break;
}
if (count == count_max)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS,
"Exhausted %u attempts to create temporary file", count);
return FALSE;
}
if (!glnx_renameat (target_dfd, tmpname_buf, target_dfd, target, error))
{
/* This is currently the only case where we need to have
* a cleanup unlinkat() still with O_TMPFILE.
*/
(void) unlinkat (target_dfd, tmpname_buf, 0);
return FALSE;
}
}
else
{
if (linkat (AT_FDCWD, proc_fd_path, target_dfd, target, AT_SYMLINK_FOLLOW) < 0)
{
if (errno == EEXIST && mode == GLNX_LINK_TMPFILE_NOREPLACE_IGNORE_EXIST)
;
else
return glnx_throw_errno_prefix (error, "linkat");
}
}
}
return TRUE;
}
static guint8*
glnx_fd_readall_malloc (int fd,
gsize *out_len,
gboolean nul_terminate,
GCancellable *cancellable,
GError **error)
{
const guint maxreadlen = 4096;
struct stat stbuf;
if (TEMP_FAILURE_RETRY (fstat (fd, &stbuf)) < 0)
return glnx_null_throw_errno (error);
gsize buf_allocated;
if (S_ISREG (stbuf.st_mode) && stbuf.st_size > 0)
buf_allocated = stbuf.st_size;
else
buf_allocated = 16;
g_autofree guint8* buf = g_malloc (buf_allocated);
gsize buf_size = 0;
while (TRUE)
{
gsize readlen = MIN (buf_allocated - buf_size, maxreadlen);
if (g_cancellable_set_error_if_cancelled (cancellable, error))
return FALSE;
gssize bytes_read;
do
bytes_read = read (fd, buf + buf_size, readlen);
while (G_UNLIKELY (bytes_read == -1 && errno == EINTR));
if (G_UNLIKELY (bytes_read == -1))
return glnx_null_throw_errno (error);
if (bytes_read == 0)
break;
buf_size += bytes_read;
if (buf_allocated - buf_size < maxreadlen)
buf = g_realloc (buf, buf_allocated *= 2);
}
if (nul_terminate)
{
if (buf_allocated - buf_size == 0)
buf = g_realloc (buf, buf_allocated + 1);
buf[buf_size] = '\0';
}
*out_len = buf_size;
return g_steal_pointer (&buf);
}
/**
* glnx_fd_readall_bytes:
* @fd: A file descriptor
* @cancellable: Cancellable:
* @error: Error
*
* Read all data from file descriptor @fd into a #GBytes. It's
* recommended to only use this for small files.
*
* Returns: (transfer full): A newly allocated #GBytes
*/
GBytes *
glnx_fd_readall_bytes (int fd,
GCancellable *cancellable,
GError **error)
{
gsize len;
guint8 *buf = glnx_fd_readall_malloc (fd, &len, FALSE, cancellable, error);
if (!buf)
return NULL;
return g_bytes_new_take (buf, len);
}
/**
* glnx_fd_readall_utf8:
* @fd: A file descriptor
* @out_len: (out): Returned length
* @cancellable: Cancellable:
* @error: Error
*
* Read all data from file descriptor @fd, validating
* the result as UTF-8.
*
* Returns: (transfer full): A string validated as UTF-8, or %NULL on error.
*/
char *
glnx_fd_readall_utf8 (int fd,
gsize *out_len,
GCancellable *cancellable,
GError **error)
{
gsize len;
g_autofree guint8 *buf = glnx_fd_readall_malloc (fd, &len, TRUE, cancellable, error);
if (!buf)
return FALSE;
if (!g_utf8_validate ((char*)buf, len, NULL))
{
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"Invalid UTF-8");
return FALSE;
}
if (out_len)
*out_len = len;
return (char*)g_steal_pointer (&buf);
}
/**
* glnx_file_get_contents_utf8_at:
* @dfd: Directory file descriptor
* @subpath: Path relative to @dfd
* @out_len: (out) (allow-none): Optional length
* @cancellable: Cancellable
* @error: Error
*
* Read the entire contents of the file referred
* to by @dfd and @subpath, validate the result as UTF-8.
* The length is optionally stored in @out_len.
*
* Returns: (transfer full): UTF-8 validated text, or %NULL on error
*/
char *
glnx_file_get_contents_utf8_at (int dfd,
const char *subpath,
gsize *out_len,
GCancellable *cancellable,
GError **error)
{
dfd = glnx_dirfd_canonicalize (dfd);
glnx_fd_close int fd = TEMP_FAILURE_RETRY (openat (dfd, subpath, O_RDONLY | O_NOCTTY | O_CLOEXEC));
if (G_UNLIKELY (fd == -1))
return glnx_null_throw_errno_prefix (error, "open(%s)", subpath);
gsize len;
g_autofree char *buf = glnx_fd_readall_utf8 (fd, &len, cancellable, error);
if (G_UNLIKELY(!buf))
return FALSE;
if (out_len)
*out_len = len;
return g_steal_pointer (&buf);
}
/**
* 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;
dfd = glnx_dirfd_canonicalize (dfd);
for (;;)
{
g_autofree char *c = NULL;
ssize_t n;
c = g_malloc (l);
n = TEMP_FAILURE_RETRY (readlinkat (dfd, subpath, c, l-1));
if (n < 0)
return glnx_null_throw_errno (error);
if ((size_t) n < l-1)
{
c[n] = 0;
return g_steal_pointer (&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)
{
g_autofree char *buf = glnx_readlinkat_malloc (src_dfd, src_subpath, cancellable, error);
if (!buf)
return FALSE;
if (TEMP_FAILURE_RETRY (symlinkat (buf, dest_dfd, dest_subpath)) != 0)
return glnx_throw_errno_prefix (error, "symlinkat");
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))
return FALSE;
if (!glnx_dfd_name_set_all_xattrs (dest_dfd, dest_subpath, xattrs,
cancellable, error))
return FALSE;
}
if (TEMP_FAILURE_RETRY (fchownat (dest_dfd, dest_subpath,
src_stbuf->st_uid, src_stbuf->st_gid,
AT_SYMLINK_NOFOLLOW)) != 0)
return glnx_throw_errno (error);
return TRUE;
}
#define COPY_BUFFER_SIZE (16*1024)
/* Most of the code below is from systemd, but has been reindented to GNU style,
* and changed to use POSIX error conventions (return -1, set errno) to more
* conveniently fit in with the rest of libglnx.
*/
static int btrfs_reflink(int infd, int outfd) {
g_return_val_if_fail(infd >= 0, -1);
g_return_val_if_fail(outfd >= 0, -1);
return ioctl (outfd, BTRFS_IOC_CLONE, infd);
}
/* Like write(), but loop until @nbytes are written, or an error
* occurs.
*
* On error, -1 is returned an @errno is set. NOTE: This is an
* API change from previous versions of this function.
*/
int
glnx_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 -1;
}
if (k == 0) /* Can't really happen */
{
errno = EIO;
return -1;
}
p += k;
nbytes -= k;
}
return 0;
}
/* Read from @fdf until EOF, writing to @fdt. If @try_reflink is %TRUE,
* attempt to use any "reflink" functionality; see e.g. https://lwn.net/Articles/331808/
*
* The file descriptor @fdf must refer to a regular file.
*
* If provided, @max_bytes specifies the maximum number of bytes to read from @fdf.
* On error, this function returns `-1` and @errno will be set.
*/
int
glnx_regfile_copy_bytes (int fdf, int fdt, off_t max_bytes, gboolean try_reflink)
{
bool try_sendfile = true;
int r;
g_return_val_if_fail (fdf >= 0, -1);
g_return_val_if_fail (fdt >= 0, -1);
g_return_val_if_fail (max_bytes >= -1, -1);
/* Try btrfs reflinks first. */
if (try_reflink && max_bytes == (off_t) -1)
{
r = btrfs_reflink(fdf, fdt);
if (r >= 0)
return 0;
/* Fall through */
}
while (TRUE)
{
size_t m = COPY_BUFFER_SIZE;
ssize_t n;
if (max_bytes != (off_t) -1)
{
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 -1;
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 -1;
if (n == 0) /* EOF */
break;
if (glnx_loop_write (fdt, buf, (size_t) n) < 0)
return -1;
}
next:
if (max_bytes != (off_t) -1)
{
g_assert(max_bytes >= n);
max_bytes -= n;
if (max_bytes == 0)
break;
}
}
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;
struct stat local_stbuf;
if (g_cancellable_set_error_if_cancelled (cancellable, error))
goto out;
src_dfd = glnx_dirfd_canonicalize (src_dfd);
dest_dfd = glnx_dirfd_canonicalize (dest_dfd);
/* Automatically do stat() if no stat buffer was supplied */
if (!src_stbuf)
{
if (fstatat (src_dfd, src_subpath, &local_stbuf, AT_SYMLINK_NOFOLLOW) != 0)
{
glnx_set_error_from_errno (error);
goto out;
}
src_stbuf = &local_stbuf;
}
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, src_stbuf->st_mode));
if (dest_fd == -1)
{
glnx_set_error_from_errno (error);
goto out;
}
r = glnx_regfile_copy_bytes (src_fd, dest_fd, (off_t) -1, TRUE);
if (r < 0)
{
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;
}
/**
* glnx_file_replace_contents_at:
* @dfd: Directory fd
* @subpath: Subpath
* @buf: (array len=len) (element-type guint8): File contents
* @len: Length (if `-1`, assume @buf is `NUL` terminated)
* @flags: Flags
* @cancellable: Cancellable
* @error: Error
*
* Create a new file, atomically replacing the contents of @subpath
* (relative to @dfd) with @buf. By default, if the file already
* existed, fdatasync() will be used before rename() to ensure stable
* contents. This and other behavior can be controlled via @flags.
*
* Note that no metadata from the existing file is preserved, such as
* uid/gid or extended attributes. The default mode will be `0666`,
* modified by umask.
*/
gboolean
glnx_file_replace_contents_at (int dfd,
const char *subpath,
const guint8 *buf,
gsize len,
GLnxFileReplaceFlags flags,
GCancellable *cancellable,
GError **error)
{
return glnx_file_replace_contents_with_perms_at (dfd, subpath, buf, len,
(mode_t) -1, (uid_t) -1, (gid_t) -1,
flags, cancellable, error);
}
/**
* glnx_file_replace_contents_with_perms_at:
* @dfd: Directory fd
* @subpath: Subpath
* @buf: (array len=len) (element-type guint8): File contents
* @len: Length (if `-1`, assume @buf is `NUL` terminated)
* @mode: File mode; if `-1`, use `0666 - umask`
* @flags: Flags
* @cancellable: Cancellable
* @error: Error
*
* Like glnx_file_replace_contents_at(), but also supports
* setting mode, and uid/gid.
*/
gboolean
glnx_file_replace_contents_with_perms_at (int dfd,
const char *subpath,
const guint8 *buf,
gsize len,
mode_t mode,
uid_t uid,
gid_t gid,
GLnxFileReplaceFlags flags,
GCancellable *cancellable,
GError **error)
{
char *dnbuf = strdupa (subpath);
const char *dn = dirname (dnbuf);
dfd = glnx_dirfd_canonicalize (dfd);
/* With O_TMPFILE we can't use umask, and we can't sanely query the
* umask...let's assume something relatively standard.
*/
if (mode == (mode_t) -1)
mode = 0644;
g_auto(GLnxTmpfile) tmpf = { 0, };
if (!glnx_open_tmpfile_linkable_at (dfd, dn, O_WRONLY | O_CLOEXEC,
&tmpf, error))
return FALSE;
if (len == -1)
len = strlen ((char*)buf);
if (!glnx_try_fallocate (tmpf.fd, 0, len, error))
return FALSE;
if (glnx_loop_write (tmpf.fd, buf, len) < 0)
return glnx_throw_errno (error);
if (!(flags & GLNX_FILE_REPLACE_NODATASYNC))
{
struct stat stbuf;
gboolean do_sync;
if (fstatat (dfd, subpath, &stbuf, AT_SYMLINK_NOFOLLOW) != 0)
{
if (errno != ENOENT)
return glnx_throw_errno (error);
do_sync = (flags & GLNX_FILE_REPLACE_DATASYNC_NEW) > 0;
}
else
do_sync = TRUE;
if (do_sync)
{
if (fdatasync (tmpf.fd) != 0)
return glnx_throw_errno_prefix (error, "fdatasync");
}
}
if (uid != (uid_t) -1)
{
if (fchown (tmpf.fd, uid, gid) != 0)
return glnx_throw_errno (error);
}
if (fchmod (tmpf.fd, mode) != 0)
return glnx_throw_errno (error);
if (!glnx_link_tmpfile_at (&tmpf, GLNX_LINK_TMPFILE_REPLACE,
dfd, subpath, error))
return FALSE;
return TRUE;
}