diff --git a/glnx-fdio.c b/glnx-fdio.c index cdbb69fe1abb8c460c99240d87ffc0b4233ac4f0..c15639d5810cc2bdd92ae71c9a78abc6cdc19049 100644 --- a/glnx-fdio.c +++ b/glnx-fdio.c @@ -37,10 +37,247 @@ #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. */ +#ifdef O_TMPFILE + 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) + { + 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)) + 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 (dfd, tmpname_buf, target_dfd, target) < 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, @@ -658,20 +895,24 @@ glnx_file_replace_contents_with_perms_at (int dfd, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; int r; - /* We use the /proc/self trick as there's no mkostemp_at() yet */ - g_autofree char *tmppath = g_strdup_printf ("/proc/self/fd/%d/.tmpXXXXXX", dfd); + 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); - if ((fd = g_mkstemp_full (tmppath, O_WRONLY | O_CLOEXEC, - mode == (mode_t) -1 ? 0666 : mode)) == -1) - { - glnx_set_error_from_errno (error); - goto out; - } + /* 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); @@ -682,14 +923,14 @@ glnx_file_replace_contents_with_perms_at (int dfd, { errno = r; glnx_set_error_from_errno (error); - goto out; + return FALSE; } if ((r = glnx_loop_write (fd, buf, len)) != 0) { errno = -r; glnx_set_error_from_errno (error); - goto out; + return FALSE; } if (!(flags & GLNX_FILE_REPLACE_NODATASYNC)) @@ -702,7 +943,7 @@ glnx_file_replace_contents_with_perms_at (int dfd, if (errno != ENOENT) { glnx_set_error_from_errno (error); - goto out; + return FALSE; } do_sync = (flags & GLNX_FILE_REPLACE_DATASYNC_NEW) > 0; } @@ -714,7 +955,7 @@ glnx_file_replace_contents_with_perms_at (int dfd, if (fdatasync (fd) != 0) { glnx_set_error_from_errno (error); - goto out; + return FALSE; } } } @@ -724,31 +965,21 @@ glnx_file_replace_contents_with_perms_at (int dfd, if (fchown (fd, uid, gid) != 0) { glnx_set_error_from_errno (error); - goto out; - } - } - - /* If a mode was forced, override umask */ - if (mode != (mode_t) -1) - { - if (fchmod (fd, mode) != 0) - { - glnx_set_error_from_errno (error); - goto out; + return FALSE; } } - if (renameat (dfd, tmppath, dfd, subpath) != 0) + if (fchmod (fd, mode) != 0) { glnx_set_error_from_errno (error); - goto out; + return FALSE; } - ret = TRUE; - out: - if (!ret) - (void) unlink (tmppath); - return ret; + if (!glnx_link_tmpfile_at (dfd, GLNX_LINK_TMPFILE_REPLACE, + fd, tmpfile_path, dfd, subpath, error)) + return FALSE; + + return TRUE; } /** diff --git a/glnx-fdio.h b/glnx-fdio.h index 3ca1a660a8954bedeec46d267a62bc55793e5298..982545ab73b675fc828c32cd75cec2ce70558b6d 100644 --- a/glnx-fdio.h +++ b/glnx-fdio.h @@ -46,6 +46,29 @@ const char *glnx_basename (const char *path) return (basename) (path); } +gboolean +glnx_open_tmpfile_linkable_at (int dfd, + const char *subpath, + int flags, + int *out_fd, + char **out_path, + GError **error); + +typedef enum { + GLNX_LINK_TMPFILE_REPLACE, + GLNX_LINK_TMPFILE_NOREPLACE, + GLNX_LINK_TMPFILE_NOREPLACE_IGNORE_EXIST +} GLnxLinkTmpfileReplaceMode; + +gboolean +glnx_link_tmpfile_at (int dfd, + GLnxLinkTmpfileReplaceMode flags, + int fd, + const char *tmpfile_path, + int target_dfd, + const char *target, + GError **error); + GBytes * glnx_fd_readall_bytes (int fd, GCancellable *cancellable, diff --git a/glnx-missing-syscall.h b/glnx-missing-syscall.h new file mode 100644 index 0000000000000000000000000000000000000000..2c583c6d8990010de6dd7824aad7cf5457e493cf --- /dev/null +++ b/glnx-missing-syscall.h @@ -0,0 +1,54 @@ +/*** + This file was originally part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2016 Zbigniew Jędrzejewski-Szmek + + systemd 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.1 of the License, or + (at your option) any later version. + + systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +/* Missing glibc definitions to access certain kernel APIs */ + +#if !HAVE_DECL_RENAMEAT2 +# ifndef __NR_renameat2 +# if defined __x86_64__ +# define __NR_renameat2 316 +# elif defined __arm__ +# define __NR_renameat2 382 +# elif defined _MIPS_SIM +# if _MIPS_SIM == _MIPS_SIM_ABI32 +# define __NR_renameat2 4351 +# endif +# if _MIPS_SIM == _MIPS_SIM_NABI32 +# define __NR_renameat2 6315 +# endif +# if _MIPS_SIM == _MIPS_SIM_ABI64 +# define __NR_renameat2 5311 +# endif +# elif defined __i386__ +# define __NR_renameat2 353 +# else +# warning "__NR_renameat2 unknown for your architecture" +# endif +# endif + +static inline int renameat2(int oldfd, const char *oldname, int newfd, const char *newname, unsigned flags) { +# ifdef __NR_renameat2 + return syscall(__NR_renameat2, oldfd, oldname, newfd, newname, flags); +# else + errno = ENOSYS; + return -1; +# endif +} +#endif diff --git a/glnx-missing.h b/glnx-missing.h new file mode 100644 index 0000000000000000000000000000000000000000..fa80d3e86d7e94e32099a78a2a09c9ebe7a3e315 --- /dev/null +++ b/glnx-missing.h @@ -0,0 +1,52 @@ +#pragma once + +/*** + This file was originally part of systemd. + + Copyright 2010 Lennart Poettering + + systemd 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.1 of the License, or + (at your option) any later version. + + systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +/* Missing glibc definitions to access certain kernel APIs */ + +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <sys/resource.h> +#include <sys/syscall.h> +#include <uchar.h> +#include <unistd.h> + +#if defined(__i386__) || defined(__x86_64__) + +/* The precise definition of __O_TMPFILE is arch specific, so let's + * just define this on x86 where we know the value. */ + +#ifndef __O_TMPFILE +#define __O_TMPFILE 020000000 +#endif + +/* a horrid kludge trying to make sure that this will fail on old kernels */ +#ifndef O_TMPFILE +#define O_TMPFILE (__O_TMPFILE | O_DIRECTORY) +#endif + +#endif + +#ifndef RENAME_NOREPLACE +#define RENAME_NOREPLACE (1 << 0) +#endif + +#include "glnx-missing-syscall.h" diff --git a/libglnx.m4 b/libglnx.m4 new file mode 100644 index 0000000000000000000000000000000000000000..6603c0922fb1b3c1bbd57905eef9e8018323c577 --- /dev/null +++ b/libglnx.m4 @@ -0,0 +1,15 @@ +AC_DEFUN([LIBGLNX_CONFIGURE], +[ +AC_CHECK_DECLS([ + renameat2, + ], + [], [], [[ +#include <sys/types.h> +#include <unistd.h> +#include <sys/mount.h> +#include <fcntl.h> +#include <sched.h> +#include <linux/loop.h> +#include <linux/random.h> +]]) +])