ostree/libglnx/glnx-fdio.c

1023 lines
28 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -*- 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-alloca.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)])))
static gboolean
rename_file_noreplace_at (int olddirfd, const char *oldpath,
int newdirfd, const char *newpath,
gboolean ignore_eexist,
GError **error)
{
if (renameat2 (olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE) < 0)
{
if (errno == EINVAL || errno == ENOSYS)
{
/* Fall through */
;
}
else if (errno == EEXIST && ignore_eexist)
{
(void) unlinkat (olddirfd, oldpath, 0);
return TRUE;
}
else
{
glnx_set_error_from_errno (error);
return FALSE;
}
}
else
return TRUE;
if (linkat (olddirfd, oldpath, newdirfd, newpath, 0) < 0)
{
if (errno == EEXIST && ignore_eexist)
/* Fall through */
;
else
{
glnx_set_error_from_errno (error);
return FALSE;
}
}
if (unlinkat (olddirfd, oldpath, 0) < 0)
{
glnx_set_error_from_errno (error);
return FALSE;
}
return TRUE;
}
gboolean
glnx_open_tmpfile_linkable_at (int dfd,
const char *subpath,
int flags,
int *out_fd,
char **out_path,
GError **error)
{
glnx_fd_close int fd = -1;
int count;
/* 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)
fd = openat (dfd, subpath, O_TMPFILE|flags, 0600);
if (fd == -1 && !(errno == ENOSYS || errno == EISDIR || errno == EOPNOTSUPP))
{
glnx_set_prefix_error_from_errno (error, "%s", "open(O_TMPFILE)");
return FALSE;
}
if (fd != -1)
{
*out_fd = fd;
fd = -1;
*out_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, 0600);
if (fd < 0)
{
if (errno == EEXIST)
continue;
else
{
glnx_set_prefix_error_from_errno (error, "%s", "Creating temp file");
return FALSE;
}
}
else
{
*out_fd = fd;
fd = -1;
*out_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;
}
gboolean
glnx_link_tmpfile_at (int dfd,
GLnxLinkTmpfileReplaceMode mode,
int fd,
const char *tmpfile_path,
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 (fd >= 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 (tmpfile_path)
{
if (replace)
{
/* We have a regular tempfile, we're overwriting - this is a
* simple renameat().
*/
if (renameat (dfd, tmpfile_path, target_dfd, target) < 0)
{
(void) unlinkat (dfd, tmpfile_path, 0);
glnx_set_error_from_errno (error);
return FALSE;
}
}
else
{
/* We need to use renameat2(..., NOREPLACE) or emulate it */
if (!rename_file_noreplace_at (dfd, tmpfile_path, target_dfd, target,
ignore_eexist,
error))
{
(void) unlinkat (dfd, tmpfile_path, 0);
return FALSE;
}
}
}
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(fd) + 1];
sprintf (proc_fd_path, "/proc/self/fd/%i", 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
{
glnx_set_error_from_errno (error);
return FALSE;
}
}
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);
}
if (renameat (target_dfd, tmpname_buf, target_dfd, target) < 0)
{
/* 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);
glnx_set_error_from_errno (error);
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
{
glnx_set_error_from_errno (error);
return FALSE;
}
}
}
}
return TRUE;
}
static guint8*
glnx_fd_readall_malloc (int fd,
gsize *out_len,
gboolean nul_terminate,
GCancellable *cancellable,
GError **error)
{
gboolean success = FALSE;
const guint maxreadlen = 4096;
int res;
struct stat stbuf;
guint8* buf = NULL;
gsize buf_allocated;
gsize buf_size = 0;
gssize bytes_read;
do
res = fstat (fd, &stbuf);
while (G_UNLIKELY (res == -1 && errno == EINTR));
if (res == -1)
{
glnx_set_error_from_errno (error);
goto out;
}
if (S_ISREG (stbuf.st_mode) && stbuf.st_size > 0)
buf_allocated = stbuf.st_size;
else
buf_allocated = 16;
buf = g_malloc (buf_allocated);
while (TRUE)
{
gsize readlen = MIN (buf_allocated - buf_size, maxreadlen);
if (g_cancellable_set_error_if_cancelled (cancellable, error))
goto out;
do
bytes_read = read (fd, buf + buf_size, readlen);
while (G_UNLIKELY (bytes_read == -1 && errno == EINTR));
if (G_UNLIKELY (bytes_read == -1))
{
glnx_set_error_from_errno (error);
goto out;
}
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';
}
success = TRUE;
out:
if (success)
{
*out_len = buf_size;
return buf;
}
g_free (buf);
return NULL;
}
/**
* 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)
{
guint8 *buf;
gsize len;
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)
{
gboolean success = FALSE;
guint8 *buf;
gsize len;
buf = glnx_fd_readall_malloc (fd, &len, TRUE, cancellable, error);
if (!buf)
goto out;
if (!g_utf8_validate ((char*)buf, len, NULL))
{
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"Invalid UTF-8");
goto out;
}
success = TRUE;
out:
if (success)
{
if (out_len)
*out_len = len;
return (char*)buf;
}
g_free (buf);
return NULL;
}
/**
* 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)
{
gboolean success = FALSE;
glnx_fd_close int fd = -1;
char *buf = NULL;
gsize len;
dfd = glnx_dirfd_canonicalize (dfd);
do
fd = openat (dfd, subpath, O_RDONLY | O_NOCTTY | O_CLOEXEC);
while (G_UNLIKELY (fd == -1 && errno == EINTR));
if (G_UNLIKELY (fd == -1))
{
glnx_set_error_from_errno (error);
goto out;
}
buf = glnx_fd_readall_utf8 (fd, &len, cancellable, error);
if (G_UNLIKELY(!buf))
goto out;
success = TRUE;
out:
if (success)
{
if (out_len)
*out_len = len;
return buf;
}
g_free (buf);
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;
dfd = glnx_dirfd_canonicalize (dfd);
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;
}
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 -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 = glnx_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;
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 = 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;
}
/**
* 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)
{
int r;
char *dnbuf = strdupa (subpath);
const char *dn = dirname (dnbuf);
g_autofree char *tmpfile_path = NULL;
glnx_fd_close int fd = -1;
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;
if (!glnx_open_tmpfile_linkable_at (dfd, dn, O_WRONLY | O_CLOEXEC,
&fd, &tmpfile_path,
error))
return FALSE;
if (len == -1)
len = strlen ((char*)buf);
/* Note that posix_fallocate does *not* set errno but returns it. */
if (len > 0)
{
r = posix_fallocate (fd, 0, len);
if (r != 0)
{
errno = r;
glnx_set_error_from_errno (error);
return FALSE;
}
}
if ((r = glnx_loop_write (fd, buf, len)) != 0)
{
errno = -r;
glnx_set_error_from_errno (error);
return FALSE;
}
if (!(flags & GLNX_FILE_REPLACE_NODATASYNC))
{
struct stat stbuf;
gboolean do_sync;
if (fstatat (dfd, subpath, &stbuf, AT_SYMLINK_NOFOLLOW) != 0)
{
if (errno != ENOENT)
{
glnx_set_error_from_errno (error);
return FALSE;
}
do_sync = (flags & GLNX_FILE_REPLACE_DATASYNC_NEW) > 0;
}
else
do_sync = TRUE;
if (do_sync)
{
if (fdatasync (fd) != 0)
{
glnx_set_error_from_errno (error);
return FALSE;
}
}
}
if (uid != (uid_t) -1)
{
if (fchown (fd, uid, gid) != 0)
{
glnx_set_error_from_errno (error);
return FALSE;
}
}
if (fchmod (fd, mode) != 0)
{
glnx_set_error_from_errno (error);
return FALSE;
}
if (!glnx_link_tmpfile_at (dfd, GLNX_LINK_TMPFILE_REPLACE,
fd, tmpfile_path, dfd, subpath, error))
return FALSE;
return TRUE;
}
/**
* glnx_stream_fstat:
* @stream: A stream containing a Unix file descriptor
* @stbuf: Memory location to write stat buffer
* @error:
*
* Some streams created via libgsystem are #GUnixInputStream; these do
* not support e.g. g_file_input_stream_query_info(). This function
* allows dropping to the raw unix fstat() call for these types of
* streams, while still conveniently wrapped with the normal GLib
* handling of @error.
*/
gboolean
glnx_stream_fstat (GFileDescriptorBased *stream,
struct stat *stbuf,
GError **error)
{
int fd = g_file_descriptor_based_get_fd (stream);
if (fstat (fd, stbuf) == -1)
{
glnx_set_prefix_error_from_errno (error, "%s", "fstat");
return FALSE;
}
return TRUE;
}