Github messages for voidlinux
 help / color / mirror / Atom feed
* [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR
@ 2021-04-28 21:30 loreb
  2021-04-28 21:38 ` [PR REVIEW] " ericonr
                   ` (22 more replies)
  0 siblings, 23 replies; 24+ messages in thread
From: loreb @ 2021-04-28 21:30 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 2208 bytes --]

There is a new pull request by loreb against master on the void-packages repository

https://github.com/loreb/void-packages master
https://github.com/void-linux/void-packages/pull/30567

recutils: patch to fix recins not working outside of $TMPDIR
Details at https://git.savannah.gnu.org/cgit/recutils.git/commit/utils/recutl.c?id=86f662a8202408134a235572ec60141d3082f975
Will no longer be necessary once 1.9 is released; fixes bug #30546
Thanks to ericonr for all the assistance.

note to self: bootstrap.conf is not shipped
add *temporary* dependency on automake
adding gnulib pieces is a real chore...

And then the GNU tools (rightfully) decided that the manpages were out
of date, because a file has been modified, and insisted on running
help2man - I simply told pre_configure to update the timestamps of the
manpages.

<!-- Mark items with [x] where applicable -->

#### General
- [ ] This is a new package and it conforms to the [quality requirements](https://github.com/void-linux/void-packages/blob/master/Manual.md#quality-requirements)

#### Have the results of the proposed changes been tested?
- [ ] I use the packages affected by the proposed changes on a regular basis and confirm this PR works for me
- [ ] I generally don't use the affected packages but briefly tested this PR

<!--
If GitHub CI cannot be used to validate the build result (for example, if the
build is likely to take several hours), make sure to
[skip CI](https://github.com/void-linux/void-packages/blob/master/CONTRIBUTING.md#continuous-integration).
When skipping CI, uncomment and fill out the following section.
Note: for builds that are likely to complete in less than 2 hours, it is not
acceptable to skip CI.
-->
<!-- 
#### Does it build and run successfully? 
(Please choose at least one native build and, if supported, at least one cross build. More are better.)
- [ ] I built this PR locally for my native architecture, (ARCH-LIBC)
- [ ] I built this PR locally for these architectures (if supported. mark crossbuilds):
  - [ ] aarch64-musl
  - [ ] armv7l
  - [ ] armv6l-musl
-->


A patch file from https://github.com/void-linux/void-packages/pull/30567.patch is attached

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: github-pr-master-30567.patch --]
[-- Type: text/x-diff, Size: 69177 bytes --]

From f6de7e727ed410b5d4ed782c464758c77147a336 Mon Sep 17 00:00:00 2001
From: Lorenzo Beretta <vc.net.loreb@gmail.com>
Date: Wed, 28 Apr 2021 14:20:53 +0200
Subject: [PATCH] recutils: patch to fix recins not working outside of $TMPDIR

Details at https://git.savannah.gnu.org/cgit/recutils.git/commit/utils/recutl.c?id=86f662a8202408134a235572ec60141d3082f975
Will no longer be necessary once 1.9 is released; fixes bug #30546
Thanks to ericonr for all the assistance.

note to self: bootstrap.conf is not shipped
add *temporary* dependency on automake
adding gnulib pieces is a real chore...

And then the GNU tools (rightfully) decided that the manpages were out
of date, because a file has been modified, and insisted on running
help2man - I simply told pre_configure to update the timestamps of the
manpages.
---
 srcpkgs/recutils/patches/copyfile.patch | 1929 +++++++++++++++++++++++
 srcpkgs/recutils/patches/exdev.patch    |   53 +
 srcpkgs/recutils/template               |   12 +-
 3 files changed, 1992 insertions(+), 2 deletions(-)
 create mode 100644 srcpkgs/recutils/patches/copyfile.patch
 create mode 100644 srcpkgs/recutils/patches/exdev.patch

diff --git a/srcpkgs/recutils/patches/copyfile.patch b/srcpkgs/recutils/patches/copyfile.patch
new file mode 100644
index 000000000000..c2f289f6f41d
--- /dev/null
+++ b/srcpkgs/recutils/patches/copyfile.patch
@@ -0,0 +1,1929 @@
+diff --git a/lib/copy-file.c b/lib/copy-file.c
+new file mode 100644
+index 0000000..15e17ae
+--- /dev/null
++++ b/lib/copy-file.c
+@@ -0,0 +1,221 @@
++/* Copying of files.
++   Copyright (C) 2001-2003, 2006-2007, 2009-2021 Free Software Foundation, Inc.
++   Written by Bruno Haible <haible@clisp.cons.org>, 2001.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++
++#include <config.h>
++
++/* Specification.  */
++#include "copy-file.h"
++
++#include <errno.h>
++#include <fcntl.h>
++#include <stddef.h>
++#include <stdlib.h>
++#include <sys/stat.h>
++#include <unistd.h>
++
++#include "error.h"
++#include "ignore-value.h"
++#include "safe-read.h"
++#include "full-write.h"
++#include "stat-time.h"
++#include "utimens.h"
++#include "acl.h"
++#include "binary-io.h"
++#include "quote.h"
++#include "gettext.h"
++
++#define _(str) gettext (str)
++
++enum { IO_SIZE = 32 * 1024 };
++
++int
++qcopy_file_preserving (const char *src_filename, const char *dest_filename)
++{
++  int err = 0;
++  int src_fd;
++  struct stat statbuf;
++  int mode;
++  int dest_fd;
++
++  src_fd = open (src_filename, O_RDONLY | O_BINARY | O_CLOEXEC);
++  if (src_fd < 0)
++    return GL_COPY_ERR_OPEN_READ;
++  if (fstat (src_fd, &statbuf) < 0)
++    {
++      err = GL_COPY_ERR_OPEN_READ;
++      goto error_src;
++    }
++
++  mode = statbuf.st_mode & 07777;
++  off_t inbytes = S_ISREG (statbuf.st_mode) ? statbuf.st_size : -1;
++  bool empty_regular_file = inbytes == 0;
++
++  dest_fd = open (dest_filename,
++                  O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_CLOEXEC,
++                  0600);
++  if (dest_fd < 0)
++    {
++      err = GL_COPY_ERR_OPEN_BACKUP_WRITE;
++      goto error_src;
++    }
++
++  /* Copy the file contents.  FIXME: Do not copy holes.  */
++  while (0 < inbytes)
++    {
++      size_t copy_max = -1;
++      copy_max -= copy_max % IO_SIZE;
++      size_t len = inbytes < copy_max ? inbytes : copy_max;
++      ssize_t copied = copy_file_range (src_fd, NULL, dest_fd, NULL, len, 0);
++      if (copied <= 0)
++        break;
++      inbytes -= copied;
++    }
++
++  /* Finish up with read/write, in case the file was not a regular
++     file, or the file shrank or had I/O errors (in which case find
++     whether it was a read or write error).  Read empty regular files
++     since they might be in /proc with their true sizes unknown until
++     they are read.  */
++  if (inbytes != 0 || empty_regular_file)
++    {
++      char smallbuf[1024];
++      int bufsize = IO_SIZE;
++      char *buf = malloc (bufsize);
++      if (!buf)
++        buf = smallbuf, bufsize = sizeof smallbuf;
++
++      while (true)
++        {
++          size_t n_read = safe_read (src_fd, buf, bufsize);
++          if (n_read == 0)
++            break;
++          if (n_read == SAFE_READ_ERROR)
++            {
++              err = GL_COPY_ERR_READ;
++              break;
++            }
++          if (full_write (dest_fd, buf, n_read) < n_read)
++            {
++              err = GL_COPY_ERR_WRITE;
++              break;
++            }
++        }
++
++      if (buf != smallbuf)
++        free (buf);
++      if (err)
++        goto error_src_dest;
++    }
++
++#if !USE_ACL
++  if (close (dest_fd) < 0)
++    {
++      err = GL_COPY_ERR_WRITE;
++      goto error_src;
++    }
++  if (close (src_fd) < 0)
++    return GL_COPY_ERR_AFTER_READ;
++#endif
++
++  /* Preserve the access and modification times.  */
++  {
++    struct timespec ts[2];
++
++    ts[0] = get_stat_atime (&statbuf);
++    ts[1] = get_stat_mtime (&statbuf);
++    utimens (dest_filename, ts);
++  }
++
++#if HAVE_CHOWN
++  /* Preserve the owner and group.  */
++  ignore_value (chown (dest_filename, statbuf.st_uid, statbuf.st_gid));
++#endif
++
++  /* Preserve the access permissions.  */
++#if USE_ACL
++  switch (qcopy_acl (src_filename, src_fd, dest_filename, dest_fd, mode))
++    {
++    case -2:
++      err = GL_COPY_ERR_GET_ACL;
++      goto error_src_dest;
++    case -1:
++      err = GL_COPY_ERR_SET_ACL;
++      goto error_src_dest;
++    }
++#else
++  chmod (dest_filename, mode);
++#endif
++
++#if USE_ACL
++  if (close (dest_fd) < 0)
++    {
++      err = GL_COPY_ERR_WRITE;
++      goto error_src;
++    }
++  if (close (src_fd) < 0)
++    return GL_COPY_ERR_AFTER_READ;
++#endif
++
++  return 0;
++
++ error_src_dest:
++  close (dest_fd);
++ error_src:
++  close (src_fd);
++  return err;
++}
++
++void
++copy_file_preserving (const char *src_filename, const char *dest_filename)
++{
++  switch (qcopy_file_preserving (src_filename, dest_filename))
++    {
++    case 0:
++      return;
++
++    case GL_COPY_ERR_OPEN_READ:
++      error (EXIT_FAILURE, errno, _("error while opening %s for reading"),
++             quote (src_filename));
++
++    case GL_COPY_ERR_OPEN_BACKUP_WRITE:
++      error (EXIT_FAILURE, errno, _("cannot open backup file %s for writing"),
++             quote (dest_filename));
++
++    case GL_COPY_ERR_READ:
++      error (EXIT_FAILURE, errno, _("error reading %s"),
++             quote (src_filename));
++
++    case GL_COPY_ERR_WRITE:
++      error (EXIT_FAILURE, errno, _("error writing %s"),
++             quote (dest_filename));
++
++    case GL_COPY_ERR_AFTER_READ:
++      error (EXIT_FAILURE, errno, _("error after reading %s"),
++             quote (src_filename));
++
++    case GL_COPY_ERR_GET_ACL:
++      error (EXIT_FAILURE, errno, "%s", quote (src_filename));
++
++    case GL_COPY_ERR_SET_ACL:
++      error (EXIT_FAILURE, errno, _("preserving permissions for %s"),
++             quote (dest_filename));
++
++    default:
++      abort ();
++    }
++}
+diff --git a/lib/copy-file.h b/lib/copy-file.h
+new file mode 100644
+index 0000000..09281d6
+--- /dev/null
++++ b/lib/copy-file.h
+@@ -0,0 +1,54 @@
++/* Copying of files.
++   Copyright (C) 2001-2003, 2009-2021 Free Software Foundation, Inc.
++   Written by Bruno Haible <haible@clisp.cons.org>, 2001.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++
++/* Error codes returned by qcopy_file_preserving.  */
++enum
++{
++  GL_COPY_ERR_OPEN_READ = -1,
++  GL_COPY_ERR_OPEN_BACKUP_WRITE = -2,
++  GL_COPY_ERR_READ = -3,
++  GL_COPY_ERR_WRITE = -4,
++  GL_COPY_ERR_AFTER_READ = -5,
++  GL_COPY_ERR_GET_ACL = -6,
++  GL_COPY_ERR_SET_ACL = -7
++};
++
++/* Copy a regular file: from src_filename to dest_filename.
++   The destination file is assumed to be a backup file.
++   Modification times, owner, group and access permissions are preserved as
++   far as possible.
++   Return 0 if successful, otherwise set errno and return one of the error
++   codes above.  */
++extern int qcopy_file_preserving (const char *src_filename, const char *dest_filename);
++
++/* Copy a regular file: from src_filename to dest_filename.
++   The destination file is assumed to be a backup file.
++   Modification times, owner, group and access permissions are preserved as
++   far as possible.
++   Exit upon failure.  */
++extern void copy_file_preserving (const char *src_filename, const char *dest_filename);
++
++
++#ifdef __cplusplus
++}
++#endif
+diff --git a/m4/copy-file.m4 b/m4/copy-file.m4
+new file mode 100644
+index 0000000..6211171
+--- /dev/null
++++ b/m4/copy-file.m4
+@@ -0,0 +1,11 @@
++# copy-file.m4 serial 5
++dnl Copyright (C) 2003, 2009-2021 Free Software Foundation, Inc.
++dnl This file is free software; the Free Software Foundation
++dnl gives unlimited permission to copy and/or distribute it,
++dnl with or without modifications, as long as this notice is preserved.
++
++AC_DEFUN([gl_COPY_FILE],
++[
++  AC_CHECK_HEADERS_ONCE([unistd.h])
++  AC_CHECK_FUNCS([chown])
++])
+
+diff --git a/lib/Makefile.am b/lib/Makefile.am
+index a6ec30b..f363b86 100644
+--- a/lib/Makefile.am
++++ b/lib/Makefile.am
+@@ -219,6 +225,12 @@
+ 
+ ## end   gnulib module base64
+ 
++## begin gnulib module binary-io
++
++librecutils_la_SOURCES += binary-io.h binary-io.c
++
++## end   gnulib module binary-io
++
+ ## begin gnulib module btowc
+ 
+ 
+@@ -289,6 +301,21 @@
+ 
+ ## end   gnulib module closeout
+ 
++## begin gnulib module copy-file
++
++librecutils_la_SOURCES += copy-file.h copy-file.c
++
++## end   gnulib module copy-file
++
++## begin gnulib module copy-file-range
++
++
++EXTRA_DIST += copy-file-range.c
++
++EXTRA_librecutils_la_SOURCES += copy-file-range.c
++
++## end   gnulib module copy-file-range
++
+ ## begin gnulib module crc
+ 
+ librecutils_la_SOURCES += crc.c
+@@ -605,6 +634,12 @@
+ 
+ ## end   gnulib module ftello
+ 
++## begin gnulib module full-write
++
++librecutils_la_SOURCES += full-write.h full-write.c
++
++## end   gnulib module full-write
++
+ ## begin gnulib module fwriting
+ 
+ 
+@@ -778,6 +813,13 @@
+ 
+ ## end   gnulib module havelib
+ 
++## begin gnulib module ignore-value
++
++
++EXTRA_DIST += ignore-value.h
++
++## end   gnulib module ignore-value
++
+ ## begin gnulib module intprops
+ 
+ 
+@@ -1761,6 +1827,32 @@
+ 
+ ## end   gnulib module root-uid
+ 
++## begin gnulib module safe-read
++
++librecutils_la_SOURCES += safe-read.c
++
++EXTRA_DIST += safe-read.h sys-limits.h
++
++## end   gnulib module safe-read
++
++## begin gnulib module safe-write
++
++librecutils_la_SOURCES += safe-write.c
++
++EXTRA_DIST += safe-read.c safe-write.h sys-limits.h
++
++EXTRA_librecutils_la_SOURCES += safe-read.c
++
++## end   gnulib module safe-write
++
++## begin gnulib module utimens
++
++librecutils_la_SOURCES += utimens.c
++
++EXTRA_DIST += utimens.h
++
++## end   gnulib module utimens
++
+ ## begin gnulib module same-inode
+ 
+ 
+diff --git a/lib/ignore-value.h b/lib/ignore-value.h
+new file mode 100644
+index 0000000..0a3cf1e
+--- /dev/null
++++ b/lib/ignore-value.h
+@@ -0,0 +1,51 @@
++/* ignore a function return without a compiler warning.  -*- coding: utf-8 -*-
++
++   Copyright (C) 2008-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++/* Written by Jim Meyering, Eric Blake and Pádraig Brady.  */
++
++/* Use "ignore_value" to avoid a warning when using a function declared with
++   gcc's warn_unused_result attribute, but for which you really do want to
++   ignore the result.  Traditionally, people have used a "(void)" cast to
++   indicate that a function's return value is deliberately unused.  However,
++   if the function is declared with __attribute__((warn_unused_result)),
++   gcc issues a warning even with the cast.
++
++   Caution: most of the time, you really should heed gcc's warning, and
++   check the return value.  However, in those exceptional cases in which
++   you're sure you know what you're doing, use this function.
++
++   For the record, here's one of the ignorable warnings:
++   "copy.c:233: warning: ignoring return value of 'fchown',
++   declared with attribute warn_unused_result".  */
++
++#ifndef _GL_IGNORE_VALUE_H
++#define _GL_IGNORE_VALUE_H
++
++/* Normally casting an expression to void discards its value, but GCC
++   versions 3.4 and newer have __attribute__ ((__warn_unused_result__))
++   which may cause unwanted diagnostics in that case.  Use __typeof__
++   and __extension__ to work around the problem, if the workaround is
++   known to be needed.
++   The workaround is not needed with clang.  */
++#if (3 < __GNUC__ + (4 <= __GNUC_MINOR__)) && !defined __clang__
++# define ignore_value(x) \
++    (__extension__ ({ __typeof__ (x) __x = (x); (void) __x; }))
++#else
++# define ignore_value(x) ((void) (x))
++#endif
++
++#endif
+diff --git a/lib/safe-read.c b/lib/safe-read.c
+new file mode 100644
+index 0000000..be9c33c
+--- /dev/null
++++ b/lib/safe-read.c
+@@ -0,0 +1,71 @@
++/* An interface to read and write that retries after interrupts.
++
++   Copyright (C) 1993-1994, 1998, 2002-2006, 2009-2021 Free Software
++   Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++#include <config.h>
++
++/* Specification.  */
++#ifdef SAFE_WRITE
++# include "safe-write.h"
++#else
++# include "safe-read.h"
++#endif
++
++/* Get ssize_t.  */
++#include <sys/types.h>
++#include <unistd.h>
++
++#include <errno.h>
++
++#ifdef EINTR
++# define IS_EINTR(x) ((x) == EINTR)
++#else
++# define IS_EINTR(x) 0
++#endif
++
++#include "sys-limits.h"
++
++#ifdef SAFE_WRITE
++# define safe_rw safe_write
++# define rw write
++#else
++# define safe_rw safe_read
++# define rw read
++# undef const
++# define const /* empty */
++#endif
++
++/* Read(write) up to COUNT bytes at BUF from(to) descriptor FD, retrying if
++   interrupted.  Return the actual number of bytes read(written), zero for EOF,
++   or SAFE_READ_ERROR(SAFE_WRITE_ERROR) upon error.  */
++size_t
++safe_rw (int fd, void const *buf, size_t count)
++{
++  for (;;)
++    {
++      ssize_t result = rw (fd, buf, count);
++
++      if (0 <= result)
++        return result;
++      else if (IS_EINTR (errno))
++        continue;
++      else if (errno == EINVAL && SYS_BUFSIZE_MAX < count)
++        count = SYS_BUFSIZE_MAX;
++      else
++        return result;
++    }
++}
+diff --git a/lib/safe-read.h b/lib/safe-read.h
+new file mode 100644
+index 0000000..5482906
+--- /dev/null
++++ b/lib/safe-read.h
+@@ -0,0 +1,47 @@
++/* An interface to read() that retries after interrupts.
++   Copyright (C) 2002, 2006, 2009-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++/* Some system calls may be interrupted and fail with errno = EINTR in the
++   following situations:
++     - The process is stopped and restarted (signal SIGSTOP and SIGCONT, user
++       types Ctrl-Z) on some platforms: Mac OS X.
++     - The process receives a signal for which a signal handler was installed
++       with sigaction() with an sa_flags field that does not contain
++       SA_RESTART.
++     - The process receives a signal for which a signal handler was installed
++       with signal() and for which no call to siginterrupt(sig,0) was done,
++       on some platforms: AIX, HP-UX, IRIX, OSF/1, Solaris.
++
++   This module provides a wrapper around read() that handles EINTR.  */
++
++#include <stddef.h>
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++
++#define SAFE_READ_ERROR ((size_t) -1)
++
++/* Read up to COUNT bytes at BUF from descriptor FD, retrying if interrupted.
++   Return the actual number of bytes read, zero for EOF, or SAFE_READ_ERROR
++   upon error.  */
++extern size_t safe_read (int fd, void *buf, size_t count);
++
++
++#ifdef __cplusplus
++}
++#endif
+diff --git a/lib/sys-limits.h b/lib/sys-limits.h
+new file mode 100644
+index 0000000..cd05a81
+--- /dev/null
++++ b/lib/sys-limits.h
+@@ -0,0 +1,42 @@
++/* System call limits
++
++   Copyright 2018-2021 Free Software Foundation, Inc.
++
++   This program is free software; you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 2, or (at your option)
++   any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program; if not, see <https://www.gnu.org/licenses/>.  */
++
++#ifndef _GL_SYS_LIMITS_H
++#define _GL_SYS_LIMITS_H
++
++#include <limits.h>
++
++/* Maximum number of bytes to read or write in a single system call.
++   This can be useful for system calls like sendfile on GNU/Linux,
++   which do not handle more than MAX_RW_COUNT bytes correctly.
++   The Linux kernel MAX_RW_COUNT is at least INT_MAX >> 20 << 20,
++   where the 20 comes from the Hexagon port with 1 MiB pages; use that
++   as an approximation, as the exact value may not be available to us.
++
++   Using this also works around a serious Linux bug before 2.6.16; see
++   <https://bugzilla.redhat.com/show_bug.cgi?id=612839>.
++
++   Using this also works around a Tru64 5.1 bug, where attempting
++   to read INT_MAX bytes fails with errno == EINVAL.  See
++   <https://lists.gnu.org/r/bug-gnu-utils/2002-04/msg00010.html>.
++
++   Using this is likely to work around similar bugs in other operating
++   systems.  */
++
++enum { SYS_BUFSIZE_MAX = INT_MAX >> 20 << 20 };
++
++#endif
+diff --git a/m4/safe-read.m4 b/m4/safe-read.m4
+new file mode 100644
+index 0000000..3cba288
+--- /dev/null
++++ b/m4/safe-read.m4
+@@ -0,0 +1,12 @@
++# safe-read.m4 serial 6
++dnl Copyright (C) 2002-2003, 2005-2006, 2009-2021 Free Software Foundation,
++dnl Inc.
++dnl This file is free software; the Free Software Foundation
++dnl gives unlimited permission to copy and/or distribute it,
++dnl with or without modifications, as long as this notice is preserved.
++
++# Prerequisites of lib/safe-read.c.
++AC_DEFUN([gl_PREREQ_SAFE_READ],
++[
++  AC_REQUIRE([gt_TYPE_SSIZE_T])
++])
+diff --git a/lib/full-write.c b/lib/full-write.c
+new file mode 100644
+index 0000000..2c0e06b
+--- /dev/null
++++ b/lib/full-write.c
+@@ -0,0 +1,79 @@
++/* An interface to read and write that retries (if necessary) until complete.
++
++   Copyright (C) 1993-1994, 1997-2006, 2009-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++#include <config.h>
++
++/* Specification.  */
++#ifdef FULL_READ
++# include "full-read.h"
++#else
++# include "full-write.h"
++#endif
++
++#include <errno.h>
++
++#ifdef FULL_READ
++# include "safe-read.h"
++# define safe_rw safe_read
++# define full_rw full_read
++# undef const
++# define const /* empty */
++#else
++# include "safe-write.h"
++# define safe_rw safe_write
++# define full_rw full_write
++#endif
++
++#ifdef FULL_READ
++/* Set errno to zero upon EOF.  */
++# define ZERO_BYTE_TRANSFER_ERRNO 0
++#else
++/* Some buggy drivers return 0 when one tries to write beyond
++   a device's end.  (Example: Linux 1.2.13 on /dev/fd0.)
++   Set errno to ENOSPC so they get a sensible diagnostic.  */
++# define ZERO_BYTE_TRANSFER_ERRNO ENOSPC
++#endif
++
++/* Write(read) COUNT bytes at BUF to(from) descriptor FD, retrying if
++   interrupted or if a partial write(read) occurs.  Return the number
++   of bytes transferred.
++   When writing, set errno if fewer than COUNT bytes are written.
++   When reading, if fewer than COUNT bytes are read, you must examine
++   errno to distinguish failure from EOF (errno == 0).  */
++size_t
++full_rw (int fd, const void *buf, size_t count)
++{
++  size_t total = 0;
++  const char *ptr = (const char *) buf;
++
++  while (count > 0)
++    {
++      size_t n_rw = safe_rw (fd, ptr, count);
++      if (n_rw == (size_t) -1)
++        break;
++      if (n_rw == 0)
++        {
++          errno = ZERO_BYTE_TRANSFER_ERRNO;
++          break;
++        }
++      total += n_rw;
++      ptr += n_rw;
++      count -= n_rw;
++    }
++
++  return total;
++}
+diff --git a/lib/full-write.h b/lib/full-write.h
+new file mode 100644
+index 0000000..6c4e28a
+--- /dev/null
++++ b/lib/full-write.h
+@@ -0,0 +1,34 @@
++/* An interface to write() that writes all it is asked to write.
++
++   Copyright (C) 2002-2003, 2009-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++#include <stddef.h>
++
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++
++/* Write COUNT bytes at BUF to descriptor FD, retrying if interrupted
++   or if partial writes occur.  Return the number of bytes successfully
++   written, setting errno if that is less than COUNT.  */
++extern size_t full_write (int fd, const void *buf, size_t count);
++
++
++#ifdef __cplusplus
++}
++#endif
+diff --git a/lib/utimens.c b/lib/utimens.c
+new file mode 100644
+index 0000000..06d1aa3
+--- /dev/null
++++ b/lib/utimens.c
+@@ -0,0 +1,647 @@
++/* Set file access and modification times.
++
++   Copyright (C) 2003-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify it
++   under the terms of the GNU General Public License as published by the
++   Free Software Foundation; either version 3 of the License, or any
++   later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++/* Written by Paul Eggert.  */
++
++/* derived from a function in touch.c */
++
++#include <config.h>
++
++#define _GL_UTIMENS_INLINE _GL_EXTERN_INLINE
++#include "utimens.h"
++
++#include <errno.h>
++#include <fcntl.h>
++#include <stdbool.h>
++#include <string.h>
++#include <sys/stat.h>
++#include <sys/time.h>
++#include <unistd.h>
++#include <utime.h>
++
++#include "stat-time.h"
++#include "timespec.h"
++
++/* On native Windows, use SetFileTime; but avoid this when compiling
++   GNU Emacs, which arranges for this in some other way and which
++   defines WIN32_LEAN_AND_MEAN itself.  */
++
++#if defined _WIN32 && ! defined __CYGWIN__ && ! defined EMACS_CONFIGURATION
++# define USE_SETFILETIME
++# define WIN32_LEAN_AND_MEAN
++# include <windows.h>
++# if GNULIB_MSVC_NOTHROW
++#  include "msvc-nothrow.h"
++# else
++#  include <io.h>
++# endif
++#endif
++
++/* Avoid recursion with rpl_futimens or rpl_utimensat.  */
++#undef futimens
++#if !HAVE_NEARLY_WORKING_UTIMENSAT
++# undef utimensat
++#endif
++
++/* Solaris 9 mistakenly succeeds when given a non-directory with a
++   trailing slash.  Force the use of rpl_stat for a fix.  */
++#ifndef REPLACE_FUNC_STAT_FILE
++# define REPLACE_FUNC_STAT_FILE 0
++#endif
++
++#if HAVE_UTIMENSAT || HAVE_FUTIMENS
++/* Cache variables for whether the utimensat syscall works; used to
++   avoid calling the syscall if we know it will just fail with ENOSYS,
++   and to avoid unnecessary work in massaging timestamps if the
++   syscall will work.  Multiple variables are needed, to distinguish
++   between the following scenarios on Linux:
++   utimensat doesn't exist, or is in glibc but kernel 2.6.18 fails with ENOSYS
++   kernel 2.6.22 and earlier rejects AT_SYMLINK_NOFOLLOW
++   kernel 2.6.25 and earlier reject UTIME_NOW/UTIME_OMIT with non-zero tv_sec
++   kernel 2.6.32 used with xfs or ntfs-3g fail to honor UTIME_OMIT
++   utimensat completely works
++   For each cache variable: 0 = unknown, 1 = yes, -1 = no.  */
++static int utimensat_works_really;
++static int lutimensat_works_really;
++#endif /* HAVE_UTIMENSAT || HAVE_FUTIMENS */
++
++/* Validate the requested timestamps.  Return 0 if the resulting
++   timespec can be used for utimensat (after possibly modifying it to
++   work around bugs in utimensat).  Return a positive value if the
++   timespec needs further adjustment based on stat results: 1 if any
++   adjustment is needed for utimes, and 2 if any adjustment is needed
++   for Linux utimensat.  Return -1, with errno set to EINVAL, if
++   timespec is out of range.  */
++static int
++validate_timespec (struct timespec timespec[2])
++{
++  int result = 0;
++  int utime_omit_count = 0;
++  if ((timespec[0].tv_nsec != UTIME_NOW
++       && timespec[0].tv_nsec != UTIME_OMIT
++       && ! (0 <= timespec[0].tv_nsec
++             && timespec[0].tv_nsec < TIMESPEC_HZ))
++      || (timespec[1].tv_nsec != UTIME_NOW
++          && timespec[1].tv_nsec != UTIME_OMIT
++          && ! (0 <= timespec[1].tv_nsec
++                && timespec[1].tv_nsec < TIMESPEC_HZ)))
++    {
++      errno = EINVAL;
++      return -1;
++    }
++  /* Work around Linux kernel 2.6.25 bug, where utimensat fails with
++     EINVAL if tv_sec is not 0 when using the flag values of tv_nsec.
++     Flag a Linux kernel 2.6.32 bug, where an mtime of UTIME_OMIT
++     fails to bump ctime.  */
++  if (timespec[0].tv_nsec == UTIME_NOW
++      || timespec[0].tv_nsec == UTIME_OMIT)
++    {
++      timespec[0].tv_sec = 0;
++      result = 1;
++      if (timespec[0].tv_nsec == UTIME_OMIT)
++        utime_omit_count++;
++    }
++  if (timespec[1].tv_nsec == UTIME_NOW
++      || timespec[1].tv_nsec == UTIME_OMIT)
++    {
++      timespec[1].tv_sec = 0;
++      result = 1;
++      if (timespec[1].tv_nsec == UTIME_OMIT)
++        utime_omit_count++;
++    }
++  return result + (utime_omit_count == 1);
++}
++
++/* Normalize any UTIME_NOW or UTIME_OMIT values in (*TS)[0] and (*TS)[1],
++   using STATBUF to obtain the current timestamps of the file.  If
++   both times are UTIME_NOW, set *TS to NULL (as this can avoid some
++   permissions issues).  If both times are UTIME_OMIT, return true
++   (nothing further beyond the prior collection of STATBUF is
++   necessary); otherwise return false.  */
++static bool
++update_timespec (struct stat const *statbuf, struct timespec **ts)
++{
++  struct timespec *timespec = *ts;
++  if (timespec[0].tv_nsec == UTIME_OMIT
++      && timespec[1].tv_nsec == UTIME_OMIT)
++    return true;
++  if (timespec[0].tv_nsec == UTIME_NOW
++      && timespec[1].tv_nsec == UTIME_NOW)
++    {
++      *ts = NULL;
++      return false;
++    }
++
++  if (timespec[0].tv_nsec == UTIME_OMIT)
++    timespec[0] = get_stat_atime (statbuf);
++  else if (timespec[0].tv_nsec == UTIME_NOW)
++    gettime (&timespec[0]);
++
++  if (timespec[1].tv_nsec == UTIME_OMIT)
++    timespec[1] = get_stat_mtime (statbuf);
++  else if (timespec[1].tv_nsec == UTIME_NOW)
++    gettime (&timespec[1]);
++
++  return false;
++}
++
++/* Set the access and modification timestamps of FD (a.k.a. FILE) to be
++   TIMESPEC[0] and TIMESPEC[1], respectively.
++   FD must be either negative -- in which case it is ignored --
++   or a file descriptor that is open on FILE.
++   If FD is nonnegative, then FILE can be NULL, which means
++   use just futimes (or equivalent) instead of utimes (or equivalent),
++   and fail if on an old system without futimes (or equivalent).
++   If TIMESPEC is null, set the timestamps to the current time.
++   Return 0 on success, -1 (setting errno) on failure.  */
++
++int
++fdutimens (int fd, char const *file, struct timespec const timespec[2])
++{
++  struct timespec adjusted_timespec[2];
++  struct timespec *ts = timespec ? adjusted_timespec : NULL;
++  int adjustment_needed = 0;
++  struct stat st;
++
++  if (ts)
++    {
++      adjusted_timespec[0] = timespec[0];
++      adjusted_timespec[1] = timespec[1];
++      adjustment_needed = validate_timespec (ts);
++    }
++  if (adjustment_needed < 0)
++    return -1;
++
++  /* Require that at least one of FD or FILE are potentially valid, to avoid
++     a Linux bug where futimens (AT_FDCWD, NULL) changes "." rather
++     than failing.  */
++  if (fd < 0 && !file)
++    {
++      errno = EBADF;
++      return -1;
++    }
++
++  /* Some Linux-based NFS clients are buggy, and mishandle timestamps
++     of files in NFS file systems in some cases.  We have no
++     configure-time test for this, but please see
++     <https://bugs.gentoo.org/show_bug.cgi?id=132673> for references to
++     some of the problems with Linux 2.6.16.  If this affects you,
++     compile with -DHAVE_BUGGY_NFS_TIME_STAMPS; this is reported to
++     help in some cases, albeit at a cost in performance.  But you
++     really should upgrade your kernel to a fixed version, since the
++     problem affects many applications.  */
++
++#if HAVE_BUGGY_NFS_TIME_STAMPS
++  if (fd < 0)
++    sync ();
++  else
++    fsync (fd);
++#endif
++
++  /* POSIX 2008 added two interfaces to set file timestamps with
++     nanosecond resolution; newer Linux implements both functions via
++     a single syscall.  We provide a fallback for ENOSYS (for example,
++     compiling against Linux 2.6.25 kernel headers and glibc 2.7, but
++     running on Linux 2.6.18 kernel).  */
++#if HAVE_UTIMENSAT || HAVE_FUTIMENS
++  if (0 <= utimensat_works_really)
++    {
++      int result;
++# if __linux__ || __sun
++      /* As recently as Linux kernel 2.6.32 (Dec 2009), several file
++         systems (xfs, ntfs-3g) have bugs with a single UTIME_OMIT,
++         but work if both times are either explicitly specified or
++         UTIME_NOW.  Work around it with a preparatory [f]stat prior
++         to calling futimens/utimensat; fortunately, there is not much
++         timing impact due to the extra syscall even on file systems
++         where UTIME_OMIT would have worked.
++
++         The same bug occurs in Solaris 11.1 (Apr 2013).
++
++         FIXME: Simplify this for Linux in 2016 and for Solaris in
++         2024, when file system bugs are no longer common.  */
++      if (adjustment_needed == 2)
++        {
++          if (fd < 0 ? stat (file, &st) : fstat (fd, &st))
++            return -1;
++          if (ts[0].tv_nsec == UTIME_OMIT)
++            ts[0] = get_stat_atime (&st);
++          else if (ts[1].tv_nsec == UTIME_OMIT)
++            ts[1] = get_stat_mtime (&st);
++          /* Note that st is good, in case utimensat gives ENOSYS.  */
++          adjustment_needed++;
++        }
++# endif
++# if HAVE_UTIMENSAT
++      if (fd < 0)
++        {
++#  if defined __APPLE__ && defined __MACH__
++          size_t len = strlen (file);
++          if (len > 0 && file[len - 1] == '/')
++            {
++              struct stat statbuf;
++              if (stat (file, &statbuf) < 0)
++                return -1;
++              if (!S_ISDIR (statbuf.st_mode))
++                {
++                  errno = ENOTDIR;
++                  return -1;
++                }
++            }
++#  endif
++          result = utimensat (AT_FDCWD, file, ts, 0);
++#  ifdef __linux__
++          /* Work around a kernel bug:
++             https://bugzilla.redhat.com/show_bug.cgi?id=442352
++             https://bugzilla.redhat.com/show_bug.cgi?id=449910
++             It appears that utimensat can mistakenly return 280 rather
++             than -1 upon ENOSYS failure.
++             FIXME: remove in 2010 or whenever the offending kernels
++             are no longer in common use.  */
++          if (0 < result)
++            errno = ENOSYS;
++#  endif /* __linux__ */
++          if (result == 0 || errno != ENOSYS)
++            {
++              utimensat_works_really = 1;
++              return result;
++            }
++        }
++# endif /* HAVE_UTIMENSAT */
++# if HAVE_FUTIMENS
++      if (0 <= fd)
++        {
++          result = futimens (fd, ts);
++#  ifdef __linux__
++          /* Work around the same bug as above.  */
++          if (0 < result)
++            errno = ENOSYS;
++#  endif /* __linux__ */
++          if (result == 0 || errno != ENOSYS)
++            {
++              utimensat_works_really = 1;
++              return result;
++            }
++        }
++# endif /* HAVE_FUTIMENS */
++    }
++  utimensat_works_really = -1;
++  lutimensat_works_really = -1;
++#endif /* HAVE_UTIMENSAT || HAVE_FUTIMENS */
++
++#ifdef USE_SETFILETIME
++  /* On native Windows, use SetFileTime(). See
++     <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfiletime>
++     <https://docs.microsoft.com/en-us/windows/desktop/api/minwinbase/ns-minwinbase-filetime>  */
++  if (0 <= fd)
++    {
++      HANDLE handle;
++      FILETIME current_time;
++      FILETIME last_access_time;
++      FILETIME last_write_time;
++
++      handle = (HANDLE) _get_osfhandle (fd);
++      if (handle == INVALID_HANDLE_VALUE)
++        {
++          errno = EBADF;
++          return -1;
++        }
++
++      if (ts == NULL || ts[0].tv_nsec == UTIME_NOW || ts[1].tv_nsec == UTIME_NOW)
++        {
++          /* GetSystemTimeAsFileTime
++             <https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getsystemtimeasfiletime>.
++             It would be overkill to use
++             GetSystemTimePreciseAsFileTime
++             <https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getsystemtimepreciseasfiletime>.  */
++          GetSystemTimeAsFileTime (&current_time);
++        }
++
++      if (ts == NULL || ts[0].tv_nsec == UTIME_NOW)
++        {
++          last_access_time = current_time;
++        }
++      else if (ts[0].tv_nsec == UTIME_OMIT)
++        {
++          last_access_time.dwLowDateTime = 0;
++          last_access_time.dwHighDateTime = 0;
++        }
++      else
++        {
++          ULONGLONG time_since_16010101 =
++            (ULONGLONG) ts[0].tv_sec * 10000000 + ts[0].tv_nsec / 100 + 116444736000000000LL;
++          last_access_time.dwLowDateTime = (DWORD) time_since_16010101;
++          last_access_time.dwHighDateTime = time_since_16010101 >> 32;
++        }
++
++      if (ts == NULL || ts[1].tv_nsec == UTIME_NOW)
++        {
++          last_write_time = current_time;
++        }
++      else if (ts[1].tv_nsec == UTIME_OMIT)
++        {
++          last_write_time.dwLowDateTime = 0;
++          last_write_time.dwHighDateTime = 0;
++        }
++      else
++        {
++          ULONGLONG time_since_16010101 =
++            (ULONGLONG) ts[1].tv_sec * 10000000 + ts[1].tv_nsec / 100 + 116444736000000000LL;
++          last_write_time.dwLowDateTime = (DWORD) time_since_16010101;
++          last_write_time.dwHighDateTime = time_since_16010101 >> 32;
++        }
++
++      if (SetFileTime (handle, NULL, &last_access_time, &last_write_time))
++        return 0;
++      else
++        {
++          DWORD sft_error = GetLastError ();
++          #if 0
++          fprintf (stderr, "fdutimens SetFileTime error 0x%x\n", (unsigned int) sft_error);
++          #endif
++          switch (sft_error)
++            {
++            case ERROR_ACCESS_DENIED: /* fd was opened without O_RDWR */
++              errno = EACCES; /* not specified by POSIX */
++              break;
++            default:
++              errno = EINVAL;
++              break;
++            }
++          return -1;
++        }
++    }
++#endif
++
++  /* The platform lacks an interface to set file timestamps with
++     nanosecond resolution, so do the best we can, discarding any
++     fractional part of the timestamp.  */
++
++  if (adjustment_needed || (REPLACE_FUNC_STAT_FILE && fd < 0))
++    {
++      if (adjustment_needed != 3
++          && (fd < 0 ? stat (file, &st) : fstat (fd, &st)))
++        return -1;
++      if (ts && update_timespec (&st, &ts))
++        return 0;
++    }
++
++  {
++#if HAVE_FUTIMESAT || HAVE_WORKING_UTIMES
++    struct timeval timeval[2];
++    struct timeval *t;
++    if (ts)
++      {
++        timeval[0].tv_sec = ts[0].tv_sec;
++        timeval[0].tv_usec = ts[0].tv_nsec / 1000;
++        timeval[1].tv_sec = ts[1].tv_sec;
++        timeval[1].tv_usec = ts[1].tv_nsec / 1000;
++        t = timeval;
++      }
++    else
++      t = NULL;
++
++    if (fd < 0)
++      {
++# if HAVE_FUTIMESAT
++        return futimesat (AT_FDCWD, file, t);
++# endif
++      }
++    else
++      {
++        /* If futimesat or futimes fails here, don't try to speed things
++           up by returning right away.  glibc can incorrectly fail with
++           errno == ENOENT if /proc isn't mounted.  Also, Mandrake 10.0
++           in high security mode doesn't allow ordinary users to read
++           /proc/self, so glibc incorrectly fails with errno == EACCES.
++           If errno == EIO, EPERM, or EROFS, it's probably safe to fail
++           right away, but these cases are rare enough that they're not
++           worth optimizing, and who knows what other messed-up systems
++           are out there?  So play it safe and fall back on the code
++           below.  */
++
++# if (HAVE_FUTIMESAT && !FUTIMESAT_NULL_BUG) || HAVE_FUTIMES
++#  if HAVE_FUTIMESAT && !FUTIMESAT_NULL_BUG
++#   undef futimes
++#   define futimes(fd, t) futimesat (fd, NULL, t)
++#  endif
++        if (futimes (fd, t) == 0)
++          {
++#  if __linux__ && __GLIBC__
++            /* Work around a longstanding glibc bug, still present as
++               of 2010-12-27.  On older Linux kernels that lack both
++               utimensat and utimes, glibc's futimes rounds instead of
++               truncating when falling back on utime.  The same bug
++               occurs in futimesat with a null 2nd arg.  */
++            if (t)
++              {
++                bool abig = 500000 <= t[0].tv_usec;
++                bool mbig = 500000 <= t[1].tv_usec;
++                if ((abig | mbig) && fstat (fd, &st) == 0)
++                  {
++                    /* If these two subtractions overflow, they'll
++                       track the overflows inside the buggy glibc.  */
++                    time_t adiff = st.st_atime - t[0].tv_sec;
++                    time_t mdiff = st.st_mtime - t[1].tv_sec;
++
++                    struct timeval *tt = NULL;
++                    struct timeval truncated_timeval[2];
++                    truncated_timeval[0] = t[0];
++                    truncated_timeval[1] = t[1];
++                    if (abig && adiff == 1 && get_stat_atime_ns (&st) == 0)
++                      {
++                        tt = truncated_timeval;
++                        tt[0].tv_usec = 0;
++                      }
++                    if (mbig && mdiff == 1 && get_stat_mtime_ns (&st) == 0)
++                      {
++                        tt = truncated_timeval;
++                        tt[1].tv_usec = 0;
++                      }
++                    if (tt)
++                      futimes (fd, tt);
++                  }
++              }
++#  endif
++
++            return 0;
++          }
++# endif
++      }
++#endif /* HAVE_FUTIMESAT || HAVE_WORKING_UTIMES */
++
++    if (!file)
++      {
++#if ! ((HAVE_FUTIMESAT && !FUTIMESAT_NULL_BUG)          \
++        || (HAVE_WORKING_UTIMES && HAVE_FUTIMES))
++        errno = ENOSYS;
++#endif
++        return -1;
++      }
++
++#ifdef USE_SETFILETIME
++    return _gl_utimens_windows (file, ts);
++#elif HAVE_WORKING_UTIMES
++    return utimes (file, t);
++#else
++    {
++      struct utimbuf utimbuf;
++      struct utimbuf *ut;
++      if (ts)
++        {
++          utimbuf.actime = ts[0].tv_sec;
++          utimbuf.modtime = ts[1].tv_sec;
++          ut = &utimbuf;
++        }
++      else
++        ut = NULL;
++
++      return utime (file, ut);
++    }
++#endif /* !HAVE_WORKING_UTIMES */
++  }
++}
++
++/* Set the access and modification timestamps of FILE to be
++   TIMESPEC[0] and TIMESPEC[1], respectively.  */
++int
++utimens (char const *file, struct timespec const timespec[2])
++{
++  return fdutimens (-1, file, timespec);
++}
++
++/* Set the access and modification timestamps of FILE to be
++   TIMESPEC[0] and TIMESPEC[1], respectively, without dereferencing
++   symlinks.  Fail with ENOSYS if the platform does not support
++   changing symlink timestamps, but FILE was a symlink.  */
++int
++lutimens (char const *file, struct timespec const timespec[2])
++{
++  struct timespec adjusted_timespec[2];
++  struct timespec *ts = timespec ? adjusted_timespec : NULL;
++  int adjustment_needed = 0;
++  struct stat st;
++
++  if (ts)
++    {
++      adjusted_timespec[0] = timespec[0];
++      adjusted_timespec[1] = timespec[1];
++      adjustment_needed = validate_timespec (ts);
++    }
++  if (adjustment_needed < 0)
++    return -1;
++
++  /* The Linux kernel did not support symlink timestamps until
++     utimensat, in version 2.6.22, so we don't need to mimic
++     fdutimens' worry about buggy NFS clients.  But we do have to
++     worry about bogus return values.  */
++
++#if HAVE_UTIMENSAT
++  if (0 <= lutimensat_works_really)
++    {
++      int result;
++# if __linux__ || __sun
++      /* As recently as Linux kernel 2.6.32 (Dec 2009), several file
++         systems (xfs, ntfs-3g) have bugs with a single UTIME_OMIT,
++         but work if both times are either explicitly specified or
++         UTIME_NOW.  Work around it with a preparatory lstat prior to
++         calling utimensat; fortunately, there is not much timing
++         impact due to the extra syscall even on file systems where
++         UTIME_OMIT would have worked.
++
++         The same bug occurs in Solaris 11.1 (Apr 2013).
++
++         FIXME: Simplify this for Linux in 2016 and for Solaris in
++         2024, when file system bugs are no longer common.  */
++      if (adjustment_needed == 2)
++        {
++          if (lstat (file, &st))
++            return -1;
++          if (ts[0].tv_nsec == UTIME_OMIT)
++            ts[0] = get_stat_atime (&st);
++          else if (ts[1].tv_nsec == UTIME_OMIT)
++            ts[1] = get_stat_mtime (&st);
++          /* Note that st is good, in case utimensat gives ENOSYS.  */
++          adjustment_needed++;
++        }
++# endif
++      result = utimensat (AT_FDCWD, file, ts, AT_SYMLINK_NOFOLLOW);
++# ifdef __linux__
++      /* Work around a kernel bug:
++         https://bugzilla.redhat.com/show_bug.cgi?id=442352
++         https://bugzilla.redhat.com/show_bug.cgi?id=449910
++         It appears that utimensat can mistakenly return 280 rather
++         than -1 upon ENOSYS failure.
++         FIXME: remove in 2010 or whenever the offending kernels
++         are no longer in common use.  */
++      if (0 < result)
++        errno = ENOSYS;
++# endif
++      if (result == 0 || errno != ENOSYS)
++        {
++          utimensat_works_really = 1;
++          lutimensat_works_really = 1;
++          return result;
++        }
++    }
++  lutimensat_works_really = -1;
++#endif /* HAVE_UTIMENSAT */
++
++  /* The platform lacks an interface to set file timestamps with
++     nanosecond resolution, so do the best we can, discarding any
++     fractional part of the timestamp.  */
++
++  if (adjustment_needed || REPLACE_FUNC_STAT_FILE)
++    {
++      if (adjustment_needed != 3 && lstat (file, &st))
++        return -1;
++      if (ts && update_timespec (&st, &ts))
++        return 0;
++    }
++
++  /* On Linux, lutimes is a thin wrapper around utimensat, so there is
++     no point trying lutimes if utimensat failed with ENOSYS.  */
++#if HAVE_LUTIMES && !HAVE_UTIMENSAT
++  {
++    struct timeval timeval[2];
++    struct timeval *t;
++    int result;
++    if (ts)
++      {
++        timeval[0].tv_sec = ts[0].tv_sec;
++        timeval[0].tv_usec = ts[0].tv_nsec / 1000;
++        timeval[1].tv_sec = ts[1].tv_sec;
++        timeval[1].tv_usec = ts[1].tv_nsec / 1000;
++        t = timeval;
++      }
++    else
++      t = NULL;
++
++    result = lutimes (file, t);
++    if (result == 0 || errno != ENOSYS)
++      return result;
++  }
++#endif /* HAVE_LUTIMES && !HAVE_UTIMENSAT */
++
++  /* Out of luck for symlinks, but we still handle regular files.  */
++  if (!(adjustment_needed || REPLACE_FUNC_STAT_FILE) && lstat (file, &st))
++    return -1;
++  if (!S_ISLNK (st.st_mode))
++    return fdutimens (-1, file, ts);
++  errno = ENOSYS;
++  return -1;
++}
+diff --git a/lib/utimens.h b/lib/utimens.h
+new file mode 100644
+index 0000000..295d3d7
+--- /dev/null
++++ b/lib/utimens.h
+@@ -0,0 +1,49 @@
++/* Set file access and modification times.
++
++   Copyright 2012-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify it
++   under the terms of the GNU General Public License as published by the
++   Free Software Foundation; either version 3 of the License, or any
++   later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++/* Written by Paul Eggert.  */
++
++#include <time.h>
++int fdutimens (int, char const *, struct timespec const [2]);
++int utimens (char const *, struct timespec const [2]);
++int lutimens (char const *, struct timespec const [2]);
++
++#if GNULIB_FDUTIMENSAT
++# include <fcntl.h>
++# include <sys/stat.h>
++
++#ifndef _GL_INLINE_HEADER_BEGIN
++ #error "Please include config.h first."
++#endif
++_GL_INLINE_HEADER_BEGIN
++#ifndef _GL_UTIMENS_INLINE
++# define _GL_UTIMENS_INLINE _GL_INLINE
++#endif
++
++int fdutimensat (int fd, int dir, char const *name, struct timespec const [2],
++                 int atflag);
++
++/* Using this function makes application code slightly more readable.  */
++_GL_UTIMENS_INLINE int
++lutimensat (int dir, char const *file, struct timespec const times[2])
++{
++  return utimensat (dir, file, times, AT_SYMLINK_NOFOLLOW);
++}
++
++_GL_INLINE_HEADER_END
++
++#endif
+diff --git a/m4/utimens.m4 b/m4/utimens.m4
+new file mode 100644
+index 0000000..2ee4ef9
+--- /dev/null
++++ b/m4/utimens.m4
+@@ -0,0 +1,52 @@
++dnl Copyright (C) 2003-2021 Free Software Foundation, Inc.
++dnl This file is free software; the Free Software Foundation
++dnl gives unlimited permission to copy and/or distribute it,
++dnl with or without modifications, as long as this notice is preserved.
++
++dnl serial 11
++
++AC_DEFUN([gl_UTIMENS],
++[
++  dnl Prerequisites of lib/utimens.c.
++  AC_REQUIRE([gl_FUNC_UTIMES])
++  AC_REQUIRE([gl_CHECK_TYPE_STRUCT_TIMESPEC])
++  AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
++  AC_CHECK_FUNCS_ONCE([futimes futimesat futimens utimensat lutimes])
++
++  if test $ac_cv_func_futimens = no && test $ac_cv_func_futimesat = yes; then
++    dnl FreeBSD 8.0-rc2 mishandles futimesat(fd,NULL,time).  It is not
++    dnl standardized, but Solaris implemented it first and uses it as
++    dnl its only means to set fd time.
++    AC_CACHE_CHECK([whether futimesat handles NULL file],
++      [gl_cv_func_futimesat_works],
++      [touch conftest.file
++       AC_RUN_IFELSE([AC_LANG_PROGRAM([[
++#include <stddef.h>
++#include <sys/times.h>
++#include <fcntl.h>
++]GL_MDA_DEFINES],
++        [[int fd = open ("conftest.file", O_RDWR);
++          if (fd < 0) return 1;
++          if (futimesat (fd, NULL, NULL)) return 2;
++        ]])],
++        [gl_cv_func_futimesat_works=yes],
++        [gl_cv_func_futimesat_works=no],
++        [case "$host_os" in
++                            # Guess yes on Linux systems.
++           linux-* | linux) gl_cv_func_futimesat_works="guessing yes" ;;
++                            # Guess yes on glibc systems.
++           *-gnu*)          gl_cv_func_futimesat_works="guessing yes" ;;
++                            # If we don't know, obey --enable-cross-guesses.
++           *)               gl_cv_func_futimesat_works="$gl_cross_guess_normal" ;;
++         esac
++        ])
++      rm -f conftest.file])
++    case "$gl_cv_func_futimesat_works" in
++      *yes) ;;
++      *)
++        AC_DEFINE([FUTIMESAT_NULL_BUG], [1],
++          [Define to 1 if futimesat mishandles a NULL file name.])
++        ;;
++    esac
++  fi
++])
+diff --git a/m4/utimes.m4 b/m4/utimes.m4
+new file mode 100644
+index 0000000..0440e78
+--- /dev/null
++++ b/m4/utimes.m4
+@@ -0,0 +1,161 @@
++# Detect some bugs in glibc's implementation of utimes.
++# serial 8
++
++dnl Copyright (C) 2003-2005, 2009-2021 Free Software Foundation, Inc.
++dnl This file is free software; the Free Software Foundation
++dnl gives unlimited permission to copy and/or distribute it,
++dnl with or without modifications, as long as this notice is preserved.
++
++# See if we need to work around bugs in glibc's implementation of
++# utimes from 2003-07-12 to 2003-09-17.
++# First, there was a bug that would make utimes set mtime
++# and atime to zero (1970-01-01) unconditionally.
++# Then, there was code to round rather than truncate.
++# Then, there was an implementation (sparc64, Linux-2.4.28, glibc-2.3.3)
++# that didn't honor the NULL-means-set-to-current-time semantics.
++# Finally, there was also a version of utimes that failed on read-only
++# files, while utime worked fine (linux-2.2.20, glibc-2.2.5).
++#
++# From Jim Meyering, with suggestions from Paul Eggert.
++
++AC_DEFUN([gl_FUNC_UTIMES],
++[
++  AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
++  AC_CACHE_CHECK([whether the utimes function works],
++                 [gl_cv_func_working_utimes],
++    [AC_RUN_IFELSE([AC_LANG_SOURCE([[
++#include <sys/types.h>
++#include <sys/stat.h>
++#include <fcntl.h>
++#include <sys/time.h>
++#include <time.h>
++#include <unistd.h>
++#include <stdlib.h>
++#include <stdio.h>
++#include <utime.h>
++#include <errno.h>
++]GL_MDA_DEFINES[
++
++static int
++inorder (time_t a, time_t b, time_t c)
++{
++  return a <= b && b <= c;
++}
++
++int
++main ()
++{
++  int result = 0;
++  char const *file = "conftest.utimes";
++  /* On OS/2, file timestamps must be on or after 1980 in local time,
++     with an even number of seconds.  */
++  static struct timeval timeval[2] = {{315620000 + 10, 10},
++                                      {315620000 + 1000000, 999998}};
++
++  /* Test whether utimes() essentially works.  */
++  {
++    struct stat sbuf;
++    FILE *f = fopen (file, "w");
++    if (f == NULL)
++      result |= 1;
++    else if (fclose (f) != 0)
++      result |= 1;
++    else if (utimes (file, timeval) != 0)
++      result |= 2;
++    else if (lstat (file, &sbuf) != 0)
++      result |= 1;
++    else if (!(sbuf.st_atime == timeval[0].tv_sec
++               && sbuf.st_mtime == timeval[1].tv_sec))
++      result |= 4;
++    if (unlink (file) != 0)
++      result |= 1;
++  }
++
++  /* Test whether utimes() with a NULL argument sets the file's timestamp
++     to the current time.  Use 'fstat' as well as 'time' to
++     determine the "current" time, to accommodate NFS file systems
++     if there is a time skew between the host and the NFS server.  */
++  {
++    int fd = open (file, O_WRONLY|O_CREAT, 0644);
++    if (fd < 0)
++      result |= 1;
++    else
++      {
++        time_t t0, t2;
++        struct stat st0, st1, st2;
++        if (time (&t0) == (time_t) -1)
++          result |= 1;
++        else if (fstat (fd, &st0) != 0)
++          result |= 1;
++        else if (utimes (file, timeval) != 0
++                 && (errno != EACCES
++                     /* OS/2 kLIBC utimes fails on opened files.  */
++                     || close (fd) != 0
++                     || utimes (file, timeval) != 0
++                     || (fd = open (file, O_WRONLY)) < 0))
++          result |= 2;
++        else if (utimes (file, NULL) != 0
++                 && (errno != EACCES
++                     /* OS/2 kLIBC utimes fails on opened files.  */
++                     || close (fd) != 0
++                     || utimes (file, NULL) != 0
++                     || (fd = open (file, O_WRONLY)) < 0))
++          result |= 8;
++        else if (fstat (fd, &st1) != 0)
++          result |= 1;
++        else if (write (fd, "\n", 1) != 1)
++          result |= 1;
++        else if (fstat (fd, &st2) != 0)
++          result |= 1;
++        else if (time (&t2) == (time_t) -1)
++          result |= 1;
++        else
++          {
++            int m_ok_POSIX = inorder (t0, st1.st_mtime, t2);
++            int m_ok_NFS = inorder (st0.st_mtime, st1.st_mtime, st2.st_mtime);
++            if (! (st1.st_atime == st1.st_mtime))
++              result |= 16;
++            if (! (m_ok_POSIX || m_ok_NFS))
++              result |= 32;
++          }
++        if (close (fd) != 0)
++          result |= 1;
++      }
++    if (unlink (file) != 0)
++      result |= 1;
++  }
++
++  /* Test whether utimes() with a NULL argument works on read-only files.  */
++  {
++    int fd = open (file, O_WRONLY|O_CREAT, 0444);
++    if (fd < 0)
++      result |= 1;
++    else if (close (fd) != 0)
++      result |= 1;
++    else if (utimes (file, NULL) != 0)
++      result |= 64;
++    if (unlink (file) != 0)
++      result |= 1;
++  }
++
++  return result;
++}
++  ]])],
++       [gl_cv_func_working_utimes=yes],
++       [gl_cv_func_working_utimes=no],
++       [case "$host_os" in
++                   # Guess yes on musl systems.
++          *-musl*) gl_cv_func_working_utimes="guessing yes" ;;
++                   # Guess no on native Windows.
++          mingw*)  gl_cv_func_working_utimes="guessing no" ;;
++          *)       gl_cv_func_working_utimes="$gl_cross_guess_normal" ;;
++        esac
++       ])
++    ])
++
++  case "$gl_cv_func_working_utimes" in
++    *yes)
++      AC_DEFINE([HAVE_WORKING_UTIMES], [1], [Define if utimes works properly.])
++      ;;
++  esac
++])
+diff --git a/lib/binary-io.c b/lib/binary-io.c
+new file mode 100644
+index 0000000..f267897
+--- /dev/null
++++ b/lib/binary-io.c
+@@ -0,0 +1,39 @@
++/* Binary mode I/O.
++   Copyright 2017-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++#include <config.h>
++
++#define BINARY_IO_INLINE _GL_EXTERN_INLINE
++#include "binary-io.h"
++
++#if defined __DJGPP__ || defined __EMX__
++# include <unistd.h>
++
++int
++set_binary_mode (int fd, int mode)
++{
++  if (isatty (fd))
++    /* If FD refers to a console (not a pipe, not a regular file),
++       O_TEXT is the only reasonable mode, both on input and on output.
++       Silently ignore the request.  If we were to return -1 here,
++       all programs that use xset_binary_mode would fail when run
++       with console input or console output.  */
++    return O_TEXT;
++  else
++    return __gl_setmode (fd, mode);
++}
++
++#endif
+diff --git a/lib/binary-io.h b/lib/binary-io.h
+new file mode 100644
+index 0000000..8654fd2
+--- /dev/null
++++ b/lib/binary-io.h
+@@ -0,0 +1,77 @@
++/* Binary mode I/O.
++   Copyright (C) 2001, 2003, 2005, 2008-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++#ifndef _BINARY_H
++#define _BINARY_H
++
++/* For systems that distinguish between text and binary I/O.
++   O_BINARY is guaranteed by the gnulib <fcntl.h>. */
++#include <fcntl.h>
++
++/* The MSVC7 <stdio.h> doesn't like to be included after '#define fileno ...',
++   so we include it here first.  */
++#include <stdio.h>
++
++#ifndef _GL_INLINE_HEADER_BEGIN
++ #error "Please include config.h first."
++#endif
++_GL_INLINE_HEADER_BEGIN
++#ifndef BINARY_IO_INLINE
++# define BINARY_IO_INLINE _GL_INLINE
++#endif
++
++#if O_BINARY
++# if defined __EMX__ || defined __DJGPP__ || defined __CYGWIN__
++#  include <io.h> /* declares setmode() */
++#  define __gl_setmode setmode
++# else
++#  define __gl_setmode _setmode
++#  undef fileno
++#  define fileno _fileno
++# endif
++#else
++  /* On reasonable systems, binary I/O is the only choice.  */
++  /* Use a function rather than a macro, to avoid gcc warnings
++     "warning: statement with no effect".  */
++BINARY_IO_INLINE int
++__gl_setmode (int fd _GL_UNUSED, int mode _GL_UNUSED)
++{
++  return O_BINARY;
++}
++#endif
++
++/* Set FD's mode to MODE, which should be either O_TEXT or O_BINARY.
++   Return the old mode if successful, -1 (setting errno) on failure.
++   Ordinarily this function would be called 'setmode', since that is
++   its old name on MS-Windows, but it is called 'set_binary_mode' here
++   to avoid colliding with a BSD function of another name.  */
++
++#if defined __DJGPP__ || defined __EMX__
++extern int set_binary_mode (int fd, int mode);
++#else
++BINARY_IO_INLINE int
++set_binary_mode (int fd, int mode)
++{
++  return __gl_setmode (fd, mode);
++}
++#endif
++
++/* This macro is obsolescent.  */
++#define SET_BINARY(fd) ((void) set_binary_mode (fd, O_BINARY))
++
++_GL_INLINE_HEADER_END
++
++#endif /* _BINARY_H */
+diff --git a/lib/safe-write.c b/lib/safe-write.c
+new file mode 100644
+index 0000000..a460dc9
+--- /dev/null
++++ b/lib/safe-write.c
+@@ -0,0 +1,18 @@
++/* An interface to write that retries after interrupts.
++   Copyright (C) 2002, 2009-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++#define SAFE_WRITE
++#include "safe-read.c"
+diff --git a/lib/safe-write.h b/lib/safe-write.h
+new file mode 100644
+index 0000000..4307ffe
+--- /dev/null
++++ b/lib/safe-write.h
+@@ -0,0 +1,37 @@
++/* An interface to write() that retries after interrupts.
++   Copyright (C) 2002, 2009-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++/* Some system calls may be interrupted and fail with errno = EINTR in the
++   following situations:
++     - The process is stopped and restarted (signal SIGSTOP and SIGCONT, user
++       types Ctrl-Z) on some platforms: Mac OS X.
++     - The process receives a signal for which a signal handler was installed
++       with sigaction() with an sa_flags field that does not contain
++       SA_RESTART.
++     - The process receives a signal for which a signal handler was installed
++       with signal() and for which no call to siginterrupt(sig,0) was done,
++       on some platforms: AIX, HP-UX, IRIX, OSF/1, Solaris.
++
++   This module provides a wrapper around write() that handles EINTR.  */
++
++#include <stddef.h>
++
++#define SAFE_WRITE_ERROR ((size_t) -1)
++
++/* Write up to COUNT bytes at BUF to descriptor FD, retrying if interrupted.
++   Return the actual number of bytes written, zero for EOF, or SAFE_WRITE_ERROR
++   upon error.  */
++extern size_t safe_write (int fd, const void *buf, size_t count);
+diff --git a/m4/safe-write.m4 b/m4/safe-write.m4
+new file mode 100644
+index 0000000..ef10d96
+--- /dev/null
++++ b/m4/safe-write.m4
+@@ -0,0 +1,11 @@
++# safe-write.m4 serial 4
++dnl Copyright (C) 2002, 2005-2006, 2009-2021 Free Software Foundation, Inc.
++dnl This file is free software; the Free Software Foundation
++dnl gives unlimited permission to copy and/or distribute it,
++dnl with or without modifications, as long as this notice is preserved.
++
++# Prerequisites of lib/safe-write.c.
++AC_DEFUN([gl_PREREQ_SAFE_WRITE],
++[
++  gl_PREREQ_SAFE_READ
++])
+
+
diff --git a/srcpkgs/recutils/patches/exdev.patch b/srcpkgs/recutils/patches/exdev.patch
new file mode 100644
index 000000000000..6d1f8bb213a1
--- /dev/null
+++ b/srcpkgs/recutils/patches/exdev.patch
@@ -0,0 +1,53 @@
+From 86f662a8202408134a235572ec60141d3082f975 Mon Sep 17 00:00:00 2001
+From: "Jose E. Marchesi" <jose.marchesi@oracle.com>
+Date: Tue, 28 Jan 2020 12:13:42 +0100
+Subject: utils: make utilitis to work with temporary files in a different
+ filesystem
+
+2020-01-28  Jose E. Marchesi  <jose.marchesi@oracle.com>
+
+	* bootstrap.conf (gnulib_modules): Import the modules copy-file
+	and remove.
+	* utils/recutl.c (recutl_write_db_to_file): Copy and remove
+	instead of rename temporary files.
+---
+ utils/recutl.c | 14 ++++++++------
+ 1 file changed, 8 insertions(+), 6 deletions(-)
+
+(limited to 'utils/recutl.c')
+
+diff --git a/utils/recutl.c b/utils/recutl.c
+index 9cf823d..a814fd8 100644
+--- a/utils/recutl.c
++++ b/utils/recutl.c
+@@ -58,6 +58,7 @@
+ #include <rec.h>
+ #include <recutl.h>
+ #include "read-file.h"
++#include "copy-file.h"
+ 
+ /*
+  * Global variables.
+@@ -432,12 +433,13 @@ recutl_write_db_to_file (rec_db_t db,
+ 
+   if (file_name)
+     {
+-      /* Rename the temporary file to file_name.  */
+-      if (rename (tmp_file_name, file_name) == -1)
+-        {
+-          remove (tmp_file_name);
+-          recutl_fatal (_("renaming file %s to %s\n"), tmp_file_name, file_name);
+-        }
++      /* Rename the temporary file to file_name.  We copy and remove
++         instead of renaming because the later doesn't work across
++         different mount points, and it is getting common for /tmp to
++         be mounted in its own filesystem.  */
++      if (qcopy_file_preserving (tmp_file_name, file_name) != 0)
++        recutl_fatal (_("renaming file %s to %s\n"), tmp_file_name, file_name);
++      remove (tmp_file_name);
+ 
+       /* Restore the attributes of the original file. */
+       if (stat_result != -1)
+-- 
+cgit v1.2.1
+
diff --git a/srcpkgs/recutils/template b/srcpkgs/recutils/template
index 3e71312c4e25..76e6da4c5848 100644
--- a/srcpkgs/recutils/template
+++ b/srcpkgs/recutils/template
@@ -1,17 +1,25 @@
 # Template file for 'recutils'
 pkgname=recutils
 version=1.8
-revision=1
+revision=2
 build_style=gnu-configure
 configure_args="--with-bash-headers --disable-rpath"
-hostmakedepends="pkg-config"
+# TODO remove automake in 1.9; only needed for copy-file
+hostmakedepends="automake pkg-config"
 makedepends="acl-devel libgcrypt-devel libuuid-devel libcurl-devel"
+pre_configure() {
+	# XXX horrible kludge to avoid help2man - remove in 1.9
+	touch man/*.1
+	# XXX ONLY needed for copy-file - remove in 1.9
+	aclocal && automake
+}
 short_desc="Utilities to deal with recfiles"
 maintainer="Orphaned <orphan@voidlinux.org>"
 license="GPL-3.0-or-later"
 homepage="https://www.gnu.org/software/recutils/"
 distfiles="${GNU_SITE}/recutils/recutils-${version}.tar.gz"
 checksum=df8eae69593fdba53e264cbf4b2307dfb82120c09b6fab23e2dad51a89a5b193
+patch_args=-Np1
 
 librec1_package() {
 	short_desc+=" - rec1 library"

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PR REVIEW] recutils: patch to fix recins not working outside of $TMPDIR
  2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
@ 2021-04-28 21:38 ` ericonr
  2021-04-28 21:46 ` [PR PATCH] [Updated] " loreb
                   ` (21 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: ericonr @ 2021-04-28 21:38 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 229 bytes --]

New review comment by ericonr on void-packages repository

https://github.com/void-linux/void-packages/pull/30567#discussion_r622581385

Comment:
This should go below the information block, above `librec1_package`librec1_package

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PR PATCH] [Updated] recutils: patch to fix recins not working outside of $TMPDIR
  2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
  2021-04-28 21:38 ` [PR REVIEW] " ericonr
@ 2021-04-28 21:46 ` loreb
  2021-04-28 21:48 ` [PR REVIEW] " loreb
                   ` (20 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: loreb @ 2021-04-28 21:46 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 2213 bytes --]

There is an updated pull request by loreb against master on the void-packages repository

https://github.com/loreb/void-packages master
https://github.com/void-linux/void-packages/pull/30567

recutils: patch to fix recins not working outside of $TMPDIR
Details at https://git.savannah.gnu.org/cgit/recutils.git/commit/utils/recutl.c?id=86f662a8202408134a235572ec60141d3082f975
Will no longer be necessary once 1.9 is released; fixes bug #30546
Thanks to ericonr for all the assistance.

note to self: bootstrap.conf is not shipped
add *temporary* dependency on automake
adding gnulib pieces is a real chore...

And then the GNU tools (rightfully) decided that the manpages were out
of date, because a file has been modified, and insisted on running
help2man - I simply told pre_configure to update the timestamps of the
manpages.

<!-- Mark items with [x] where applicable -->

#### General
- [ ] This is a new package and it conforms to the [quality requirements](https://github.com/void-linux/void-packages/blob/master/Manual.md#quality-requirements)

#### Have the results of the proposed changes been tested?
- [ ] I use the packages affected by the proposed changes on a regular basis and confirm this PR works for me
- [ ] I generally don't use the affected packages but briefly tested this PR

<!--
If GitHub CI cannot be used to validate the build result (for example, if the
build is likely to take several hours), make sure to
[skip CI](https://github.com/void-linux/void-packages/blob/master/CONTRIBUTING.md#continuous-integration).
When skipping CI, uncomment and fill out the following section.
Note: for builds that are likely to complete in less than 2 hours, it is not
acceptable to skip CI.
-->
<!-- 
#### Does it build and run successfully? 
(Please choose at least one native build and, if supported, at least one cross build. More are better.)
- [ ] I built this PR locally for my native architecture, (ARCH-LIBC)
- [ ] I built this PR locally for these architectures (if supported. mark crossbuilds):
  - [ ] aarch64-musl
  - [ ] armv7l
  - [ ] armv6l-musl
-->


A patch file from https://github.com/void-linux/void-packages/pull/30567.patch is attached

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: github-pr-master-30567.patch --]
[-- Type: text/x-diff, Size: 69456 bytes --]

From 1ce16148c8d5c4d79ec9b42e868523a264611f6a Mon Sep 17 00:00:00 2001
From: Lorenzo Beretta <vc.net.loreb@gmail.com>
Date: Wed, 28 Apr 2021 14:20:53 +0200
Subject: [PATCH] recutils: patch to fix recins not working outside of $TMPDIR

Details at https://git.savannah.gnu.org/cgit/recutils.git/commit/utils/recutl.c?id=86f662a8202408134a235572ec60141d3082f975
Will no longer be necessary once 1.9 is released; fixes bug #30546
Thanks to ericonr for all the assistance.

note to self: bootstrap.conf is not shipped
add *temporary* dependency on automake
adding gnulib pieces is a real chore...

And then the GNU tools (rightfully) decided that the manpages were out
of date, because a file has been modified, and insisted on running
help2man - I simply told pre_configure to update the timestamps of the
manpages.

Left a check in pre_configure to help remembering to remove automake
and the kludge
---
 srcpkgs/recutils/patches/copyfile.patch | 1929 +++++++++++++++++++++++
 srcpkgs/recutils/patches/exdev.patch    |   53 +
 srcpkgs/recutils/template               |   17 +-
 3 files changed, 1997 insertions(+), 2 deletions(-)
 create mode 100644 srcpkgs/recutils/patches/copyfile.patch
 create mode 100644 srcpkgs/recutils/patches/exdev.patch

diff --git a/srcpkgs/recutils/patches/copyfile.patch b/srcpkgs/recutils/patches/copyfile.patch
new file mode 100644
index 000000000000..c2f289f6f41d
--- /dev/null
+++ b/srcpkgs/recutils/patches/copyfile.patch
@@ -0,0 +1,1929 @@
+diff --git a/lib/copy-file.c b/lib/copy-file.c
+new file mode 100644
+index 0000000..15e17ae
+--- /dev/null
++++ b/lib/copy-file.c
+@@ -0,0 +1,221 @@
++/* Copying of files.
++   Copyright (C) 2001-2003, 2006-2007, 2009-2021 Free Software Foundation, Inc.
++   Written by Bruno Haible <haible@clisp.cons.org>, 2001.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++
++#include <config.h>
++
++/* Specification.  */
++#include "copy-file.h"
++
++#include <errno.h>
++#include <fcntl.h>
++#include <stddef.h>
++#include <stdlib.h>
++#include <sys/stat.h>
++#include <unistd.h>
++
++#include "error.h"
++#include "ignore-value.h"
++#include "safe-read.h"
++#include "full-write.h"
++#include "stat-time.h"
++#include "utimens.h"
++#include "acl.h"
++#include "binary-io.h"
++#include "quote.h"
++#include "gettext.h"
++
++#define _(str) gettext (str)
++
++enum { IO_SIZE = 32 * 1024 };
++
++int
++qcopy_file_preserving (const char *src_filename, const char *dest_filename)
++{
++  int err = 0;
++  int src_fd;
++  struct stat statbuf;
++  int mode;
++  int dest_fd;
++
++  src_fd = open (src_filename, O_RDONLY | O_BINARY | O_CLOEXEC);
++  if (src_fd < 0)
++    return GL_COPY_ERR_OPEN_READ;
++  if (fstat (src_fd, &statbuf) < 0)
++    {
++      err = GL_COPY_ERR_OPEN_READ;
++      goto error_src;
++    }
++
++  mode = statbuf.st_mode & 07777;
++  off_t inbytes = S_ISREG (statbuf.st_mode) ? statbuf.st_size : -1;
++  bool empty_regular_file = inbytes == 0;
++
++  dest_fd = open (dest_filename,
++                  O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_CLOEXEC,
++                  0600);
++  if (dest_fd < 0)
++    {
++      err = GL_COPY_ERR_OPEN_BACKUP_WRITE;
++      goto error_src;
++    }
++
++  /* Copy the file contents.  FIXME: Do not copy holes.  */
++  while (0 < inbytes)
++    {
++      size_t copy_max = -1;
++      copy_max -= copy_max % IO_SIZE;
++      size_t len = inbytes < copy_max ? inbytes : copy_max;
++      ssize_t copied = copy_file_range (src_fd, NULL, dest_fd, NULL, len, 0);
++      if (copied <= 0)
++        break;
++      inbytes -= copied;
++    }
++
++  /* Finish up with read/write, in case the file was not a regular
++     file, or the file shrank or had I/O errors (in which case find
++     whether it was a read or write error).  Read empty regular files
++     since they might be in /proc with their true sizes unknown until
++     they are read.  */
++  if (inbytes != 0 || empty_regular_file)
++    {
++      char smallbuf[1024];
++      int bufsize = IO_SIZE;
++      char *buf = malloc (bufsize);
++      if (!buf)
++        buf = smallbuf, bufsize = sizeof smallbuf;
++
++      while (true)
++        {
++          size_t n_read = safe_read (src_fd, buf, bufsize);
++          if (n_read == 0)
++            break;
++          if (n_read == SAFE_READ_ERROR)
++            {
++              err = GL_COPY_ERR_READ;
++              break;
++            }
++          if (full_write (dest_fd, buf, n_read) < n_read)
++            {
++              err = GL_COPY_ERR_WRITE;
++              break;
++            }
++        }
++
++      if (buf != smallbuf)
++        free (buf);
++      if (err)
++        goto error_src_dest;
++    }
++
++#if !USE_ACL
++  if (close (dest_fd) < 0)
++    {
++      err = GL_COPY_ERR_WRITE;
++      goto error_src;
++    }
++  if (close (src_fd) < 0)
++    return GL_COPY_ERR_AFTER_READ;
++#endif
++
++  /* Preserve the access and modification times.  */
++  {
++    struct timespec ts[2];
++
++    ts[0] = get_stat_atime (&statbuf);
++    ts[1] = get_stat_mtime (&statbuf);
++    utimens (dest_filename, ts);
++  }
++
++#if HAVE_CHOWN
++  /* Preserve the owner and group.  */
++  ignore_value (chown (dest_filename, statbuf.st_uid, statbuf.st_gid));
++#endif
++
++  /* Preserve the access permissions.  */
++#if USE_ACL
++  switch (qcopy_acl (src_filename, src_fd, dest_filename, dest_fd, mode))
++    {
++    case -2:
++      err = GL_COPY_ERR_GET_ACL;
++      goto error_src_dest;
++    case -1:
++      err = GL_COPY_ERR_SET_ACL;
++      goto error_src_dest;
++    }
++#else
++  chmod (dest_filename, mode);
++#endif
++
++#if USE_ACL
++  if (close (dest_fd) < 0)
++    {
++      err = GL_COPY_ERR_WRITE;
++      goto error_src;
++    }
++  if (close (src_fd) < 0)
++    return GL_COPY_ERR_AFTER_READ;
++#endif
++
++  return 0;
++
++ error_src_dest:
++  close (dest_fd);
++ error_src:
++  close (src_fd);
++  return err;
++}
++
++void
++copy_file_preserving (const char *src_filename, const char *dest_filename)
++{
++  switch (qcopy_file_preserving (src_filename, dest_filename))
++    {
++    case 0:
++      return;
++
++    case GL_COPY_ERR_OPEN_READ:
++      error (EXIT_FAILURE, errno, _("error while opening %s for reading"),
++             quote (src_filename));
++
++    case GL_COPY_ERR_OPEN_BACKUP_WRITE:
++      error (EXIT_FAILURE, errno, _("cannot open backup file %s for writing"),
++             quote (dest_filename));
++
++    case GL_COPY_ERR_READ:
++      error (EXIT_FAILURE, errno, _("error reading %s"),
++             quote (src_filename));
++
++    case GL_COPY_ERR_WRITE:
++      error (EXIT_FAILURE, errno, _("error writing %s"),
++             quote (dest_filename));
++
++    case GL_COPY_ERR_AFTER_READ:
++      error (EXIT_FAILURE, errno, _("error after reading %s"),
++             quote (src_filename));
++
++    case GL_COPY_ERR_GET_ACL:
++      error (EXIT_FAILURE, errno, "%s", quote (src_filename));
++
++    case GL_COPY_ERR_SET_ACL:
++      error (EXIT_FAILURE, errno, _("preserving permissions for %s"),
++             quote (dest_filename));
++
++    default:
++      abort ();
++    }
++}
+diff --git a/lib/copy-file.h b/lib/copy-file.h
+new file mode 100644
+index 0000000..09281d6
+--- /dev/null
++++ b/lib/copy-file.h
+@@ -0,0 +1,54 @@
++/* Copying of files.
++   Copyright (C) 2001-2003, 2009-2021 Free Software Foundation, Inc.
++   Written by Bruno Haible <haible@clisp.cons.org>, 2001.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++
++/* Error codes returned by qcopy_file_preserving.  */
++enum
++{
++  GL_COPY_ERR_OPEN_READ = -1,
++  GL_COPY_ERR_OPEN_BACKUP_WRITE = -2,
++  GL_COPY_ERR_READ = -3,
++  GL_COPY_ERR_WRITE = -4,
++  GL_COPY_ERR_AFTER_READ = -5,
++  GL_COPY_ERR_GET_ACL = -6,
++  GL_COPY_ERR_SET_ACL = -7
++};
++
++/* Copy a regular file: from src_filename to dest_filename.
++   The destination file is assumed to be a backup file.
++   Modification times, owner, group and access permissions are preserved as
++   far as possible.
++   Return 0 if successful, otherwise set errno and return one of the error
++   codes above.  */
++extern int qcopy_file_preserving (const char *src_filename, const char *dest_filename);
++
++/* Copy a regular file: from src_filename to dest_filename.
++   The destination file is assumed to be a backup file.
++   Modification times, owner, group and access permissions are preserved as
++   far as possible.
++   Exit upon failure.  */
++extern void copy_file_preserving (const char *src_filename, const char *dest_filename);
++
++
++#ifdef __cplusplus
++}
++#endif
+diff --git a/m4/copy-file.m4 b/m4/copy-file.m4
+new file mode 100644
+index 0000000..6211171
+--- /dev/null
++++ b/m4/copy-file.m4
+@@ -0,0 +1,11 @@
++# copy-file.m4 serial 5
++dnl Copyright (C) 2003, 2009-2021 Free Software Foundation, Inc.
++dnl This file is free software; the Free Software Foundation
++dnl gives unlimited permission to copy and/or distribute it,
++dnl with or without modifications, as long as this notice is preserved.
++
++AC_DEFUN([gl_COPY_FILE],
++[
++  AC_CHECK_HEADERS_ONCE([unistd.h])
++  AC_CHECK_FUNCS([chown])
++])
+
+diff --git a/lib/Makefile.am b/lib/Makefile.am
+index a6ec30b..f363b86 100644
+--- a/lib/Makefile.am
++++ b/lib/Makefile.am
+@@ -219,6 +225,12 @@
+ 
+ ## end   gnulib module base64
+ 
++## begin gnulib module binary-io
++
++librecutils_la_SOURCES += binary-io.h binary-io.c
++
++## end   gnulib module binary-io
++
+ ## begin gnulib module btowc
+ 
+ 
+@@ -289,6 +301,21 @@
+ 
+ ## end   gnulib module closeout
+ 
++## begin gnulib module copy-file
++
++librecutils_la_SOURCES += copy-file.h copy-file.c
++
++## end   gnulib module copy-file
++
++## begin gnulib module copy-file-range
++
++
++EXTRA_DIST += copy-file-range.c
++
++EXTRA_librecutils_la_SOURCES += copy-file-range.c
++
++## end   gnulib module copy-file-range
++
+ ## begin gnulib module crc
+ 
+ librecutils_la_SOURCES += crc.c
+@@ -605,6 +634,12 @@
+ 
+ ## end   gnulib module ftello
+ 
++## begin gnulib module full-write
++
++librecutils_la_SOURCES += full-write.h full-write.c
++
++## end   gnulib module full-write
++
+ ## begin gnulib module fwriting
+ 
+ 
+@@ -778,6 +813,13 @@
+ 
+ ## end   gnulib module havelib
+ 
++## begin gnulib module ignore-value
++
++
++EXTRA_DIST += ignore-value.h
++
++## end   gnulib module ignore-value
++
+ ## begin gnulib module intprops
+ 
+ 
+@@ -1761,6 +1827,32 @@
+ 
+ ## end   gnulib module root-uid
+ 
++## begin gnulib module safe-read
++
++librecutils_la_SOURCES += safe-read.c
++
++EXTRA_DIST += safe-read.h sys-limits.h
++
++## end   gnulib module safe-read
++
++## begin gnulib module safe-write
++
++librecutils_la_SOURCES += safe-write.c
++
++EXTRA_DIST += safe-read.c safe-write.h sys-limits.h
++
++EXTRA_librecutils_la_SOURCES += safe-read.c
++
++## end   gnulib module safe-write
++
++## begin gnulib module utimens
++
++librecutils_la_SOURCES += utimens.c
++
++EXTRA_DIST += utimens.h
++
++## end   gnulib module utimens
++
+ ## begin gnulib module same-inode
+ 
+ 
+diff --git a/lib/ignore-value.h b/lib/ignore-value.h
+new file mode 100644
+index 0000000..0a3cf1e
+--- /dev/null
++++ b/lib/ignore-value.h
+@@ -0,0 +1,51 @@
++/* ignore a function return without a compiler warning.  -*- coding: utf-8 -*-
++
++   Copyright (C) 2008-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++/* Written by Jim Meyering, Eric Blake and Pádraig Brady.  */
++
++/* Use "ignore_value" to avoid a warning when using a function declared with
++   gcc's warn_unused_result attribute, but for which you really do want to
++   ignore the result.  Traditionally, people have used a "(void)" cast to
++   indicate that a function's return value is deliberately unused.  However,
++   if the function is declared with __attribute__((warn_unused_result)),
++   gcc issues a warning even with the cast.
++
++   Caution: most of the time, you really should heed gcc's warning, and
++   check the return value.  However, in those exceptional cases in which
++   you're sure you know what you're doing, use this function.
++
++   For the record, here's one of the ignorable warnings:
++   "copy.c:233: warning: ignoring return value of 'fchown',
++   declared with attribute warn_unused_result".  */
++
++#ifndef _GL_IGNORE_VALUE_H
++#define _GL_IGNORE_VALUE_H
++
++/* Normally casting an expression to void discards its value, but GCC
++   versions 3.4 and newer have __attribute__ ((__warn_unused_result__))
++   which may cause unwanted diagnostics in that case.  Use __typeof__
++   and __extension__ to work around the problem, if the workaround is
++   known to be needed.
++   The workaround is not needed with clang.  */
++#if (3 < __GNUC__ + (4 <= __GNUC_MINOR__)) && !defined __clang__
++# define ignore_value(x) \
++    (__extension__ ({ __typeof__ (x) __x = (x); (void) __x; }))
++#else
++# define ignore_value(x) ((void) (x))
++#endif
++
++#endif
+diff --git a/lib/safe-read.c b/lib/safe-read.c
+new file mode 100644
+index 0000000..be9c33c
+--- /dev/null
++++ b/lib/safe-read.c
+@@ -0,0 +1,71 @@
++/* An interface to read and write that retries after interrupts.
++
++   Copyright (C) 1993-1994, 1998, 2002-2006, 2009-2021 Free Software
++   Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++#include <config.h>
++
++/* Specification.  */
++#ifdef SAFE_WRITE
++# include "safe-write.h"
++#else
++# include "safe-read.h"
++#endif
++
++/* Get ssize_t.  */
++#include <sys/types.h>
++#include <unistd.h>
++
++#include <errno.h>
++
++#ifdef EINTR
++# define IS_EINTR(x) ((x) == EINTR)
++#else
++# define IS_EINTR(x) 0
++#endif
++
++#include "sys-limits.h"
++
++#ifdef SAFE_WRITE
++# define safe_rw safe_write
++# define rw write
++#else
++# define safe_rw safe_read
++# define rw read
++# undef const
++# define const /* empty */
++#endif
++
++/* Read(write) up to COUNT bytes at BUF from(to) descriptor FD, retrying if
++   interrupted.  Return the actual number of bytes read(written), zero for EOF,
++   or SAFE_READ_ERROR(SAFE_WRITE_ERROR) upon error.  */
++size_t
++safe_rw (int fd, void const *buf, size_t count)
++{
++  for (;;)
++    {
++      ssize_t result = rw (fd, buf, count);
++
++      if (0 <= result)
++        return result;
++      else if (IS_EINTR (errno))
++        continue;
++      else if (errno == EINVAL && SYS_BUFSIZE_MAX < count)
++        count = SYS_BUFSIZE_MAX;
++      else
++        return result;
++    }
++}
+diff --git a/lib/safe-read.h b/lib/safe-read.h
+new file mode 100644
+index 0000000..5482906
+--- /dev/null
++++ b/lib/safe-read.h
+@@ -0,0 +1,47 @@
++/* An interface to read() that retries after interrupts.
++   Copyright (C) 2002, 2006, 2009-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++/* Some system calls may be interrupted and fail with errno = EINTR in the
++   following situations:
++     - The process is stopped and restarted (signal SIGSTOP and SIGCONT, user
++       types Ctrl-Z) on some platforms: Mac OS X.
++     - The process receives a signal for which a signal handler was installed
++       with sigaction() with an sa_flags field that does not contain
++       SA_RESTART.
++     - The process receives a signal for which a signal handler was installed
++       with signal() and for which no call to siginterrupt(sig,0) was done,
++       on some platforms: AIX, HP-UX, IRIX, OSF/1, Solaris.
++
++   This module provides a wrapper around read() that handles EINTR.  */
++
++#include <stddef.h>
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++
++#define SAFE_READ_ERROR ((size_t) -1)
++
++/* Read up to COUNT bytes at BUF from descriptor FD, retrying if interrupted.
++   Return the actual number of bytes read, zero for EOF, or SAFE_READ_ERROR
++   upon error.  */
++extern size_t safe_read (int fd, void *buf, size_t count);
++
++
++#ifdef __cplusplus
++}
++#endif
+diff --git a/lib/sys-limits.h b/lib/sys-limits.h
+new file mode 100644
+index 0000000..cd05a81
+--- /dev/null
++++ b/lib/sys-limits.h
+@@ -0,0 +1,42 @@
++/* System call limits
++
++   Copyright 2018-2021 Free Software Foundation, Inc.
++
++   This program is free software; you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 2, or (at your option)
++   any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program; if not, see <https://www.gnu.org/licenses/>.  */
++
++#ifndef _GL_SYS_LIMITS_H
++#define _GL_SYS_LIMITS_H
++
++#include <limits.h>
++
++/* Maximum number of bytes to read or write in a single system call.
++   This can be useful for system calls like sendfile on GNU/Linux,
++   which do not handle more than MAX_RW_COUNT bytes correctly.
++   The Linux kernel MAX_RW_COUNT is at least INT_MAX >> 20 << 20,
++   where the 20 comes from the Hexagon port with 1 MiB pages; use that
++   as an approximation, as the exact value may not be available to us.
++
++   Using this also works around a serious Linux bug before 2.6.16; see
++   <https://bugzilla.redhat.com/show_bug.cgi?id=612839>.
++
++   Using this also works around a Tru64 5.1 bug, where attempting
++   to read INT_MAX bytes fails with errno == EINVAL.  See
++   <https://lists.gnu.org/r/bug-gnu-utils/2002-04/msg00010.html>.
++
++   Using this is likely to work around similar bugs in other operating
++   systems.  */
++
++enum { SYS_BUFSIZE_MAX = INT_MAX >> 20 << 20 };
++
++#endif
+diff --git a/m4/safe-read.m4 b/m4/safe-read.m4
+new file mode 100644
+index 0000000..3cba288
+--- /dev/null
++++ b/m4/safe-read.m4
+@@ -0,0 +1,12 @@
++# safe-read.m4 serial 6
++dnl Copyright (C) 2002-2003, 2005-2006, 2009-2021 Free Software Foundation,
++dnl Inc.
++dnl This file is free software; the Free Software Foundation
++dnl gives unlimited permission to copy and/or distribute it,
++dnl with or without modifications, as long as this notice is preserved.
++
++# Prerequisites of lib/safe-read.c.
++AC_DEFUN([gl_PREREQ_SAFE_READ],
++[
++  AC_REQUIRE([gt_TYPE_SSIZE_T])
++])
+diff --git a/lib/full-write.c b/lib/full-write.c
+new file mode 100644
+index 0000000..2c0e06b
+--- /dev/null
++++ b/lib/full-write.c
+@@ -0,0 +1,79 @@
++/* An interface to read and write that retries (if necessary) until complete.
++
++   Copyright (C) 1993-1994, 1997-2006, 2009-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++#include <config.h>
++
++/* Specification.  */
++#ifdef FULL_READ
++# include "full-read.h"
++#else
++# include "full-write.h"
++#endif
++
++#include <errno.h>
++
++#ifdef FULL_READ
++# include "safe-read.h"
++# define safe_rw safe_read
++# define full_rw full_read
++# undef const
++# define const /* empty */
++#else
++# include "safe-write.h"
++# define safe_rw safe_write
++# define full_rw full_write
++#endif
++
++#ifdef FULL_READ
++/* Set errno to zero upon EOF.  */
++# define ZERO_BYTE_TRANSFER_ERRNO 0
++#else
++/* Some buggy drivers return 0 when one tries to write beyond
++   a device's end.  (Example: Linux 1.2.13 on /dev/fd0.)
++   Set errno to ENOSPC so they get a sensible diagnostic.  */
++# define ZERO_BYTE_TRANSFER_ERRNO ENOSPC
++#endif
++
++/* Write(read) COUNT bytes at BUF to(from) descriptor FD, retrying if
++   interrupted or if a partial write(read) occurs.  Return the number
++   of bytes transferred.
++   When writing, set errno if fewer than COUNT bytes are written.
++   When reading, if fewer than COUNT bytes are read, you must examine
++   errno to distinguish failure from EOF (errno == 0).  */
++size_t
++full_rw (int fd, const void *buf, size_t count)
++{
++  size_t total = 0;
++  const char *ptr = (const char *) buf;
++
++  while (count > 0)
++    {
++      size_t n_rw = safe_rw (fd, ptr, count);
++      if (n_rw == (size_t) -1)
++        break;
++      if (n_rw == 0)
++        {
++          errno = ZERO_BYTE_TRANSFER_ERRNO;
++          break;
++        }
++      total += n_rw;
++      ptr += n_rw;
++      count -= n_rw;
++    }
++
++  return total;
++}
+diff --git a/lib/full-write.h b/lib/full-write.h
+new file mode 100644
+index 0000000..6c4e28a
+--- /dev/null
++++ b/lib/full-write.h
+@@ -0,0 +1,34 @@
++/* An interface to write() that writes all it is asked to write.
++
++   Copyright (C) 2002-2003, 2009-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++#include <stddef.h>
++
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++
++/* Write COUNT bytes at BUF to descriptor FD, retrying if interrupted
++   or if partial writes occur.  Return the number of bytes successfully
++   written, setting errno if that is less than COUNT.  */
++extern size_t full_write (int fd, const void *buf, size_t count);
++
++
++#ifdef __cplusplus
++}
++#endif
+diff --git a/lib/utimens.c b/lib/utimens.c
+new file mode 100644
+index 0000000..06d1aa3
+--- /dev/null
++++ b/lib/utimens.c
+@@ -0,0 +1,647 @@
++/* Set file access and modification times.
++
++   Copyright (C) 2003-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify it
++   under the terms of the GNU General Public License as published by the
++   Free Software Foundation; either version 3 of the License, or any
++   later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++/* Written by Paul Eggert.  */
++
++/* derived from a function in touch.c */
++
++#include <config.h>
++
++#define _GL_UTIMENS_INLINE _GL_EXTERN_INLINE
++#include "utimens.h"
++
++#include <errno.h>
++#include <fcntl.h>
++#include <stdbool.h>
++#include <string.h>
++#include <sys/stat.h>
++#include <sys/time.h>
++#include <unistd.h>
++#include <utime.h>
++
++#include "stat-time.h"
++#include "timespec.h"
++
++/* On native Windows, use SetFileTime; but avoid this when compiling
++   GNU Emacs, which arranges for this in some other way and which
++   defines WIN32_LEAN_AND_MEAN itself.  */
++
++#if defined _WIN32 && ! defined __CYGWIN__ && ! defined EMACS_CONFIGURATION
++# define USE_SETFILETIME
++# define WIN32_LEAN_AND_MEAN
++# include <windows.h>
++# if GNULIB_MSVC_NOTHROW
++#  include "msvc-nothrow.h"
++# else
++#  include <io.h>
++# endif
++#endif
++
++/* Avoid recursion with rpl_futimens or rpl_utimensat.  */
++#undef futimens
++#if !HAVE_NEARLY_WORKING_UTIMENSAT
++# undef utimensat
++#endif
++
++/* Solaris 9 mistakenly succeeds when given a non-directory with a
++   trailing slash.  Force the use of rpl_stat for a fix.  */
++#ifndef REPLACE_FUNC_STAT_FILE
++# define REPLACE_FUNC_STAT_FILE 0
++#endif
++
++#if HAVE_UTIMENSAT || HAVE_FUTIMENS
++/* Cache variables for whether the utimensat syscall works; used to
++   avoid calling the syscall if we know it will just fail with ENOSYS,
++   and to avoid unnecessary work in massaging timestamps if the
++   syscall will work.  Multiple variables are needed, to distinguish
++   between the following scenarios on Linux:
++   utimensat doesn't exist, or is in glibc but kernel 2.6.18 fails with ENOSYS
++   kernel 2.6.22 and earlier rejects AT_SYMLINK_NOFOLLOW
++   kernel 2.6.25 and earlier reject UTIME_NOW/UTIME_OMIT with non-zero tv_sec
++   kernel 2.6.32 used with xfs or ntfs-3g fail to honor UTIME_OMIT
++   utimensat completely works
++   For each cache variable: 0 = unknown, 1 = yes, -1 = no.  */
++static int utimensat_works_really;
++static int lutimensat_works_really;
++#endif /* HAVE_UTIMENSAT || HAVE_FUTIMENS */
++
++/* Validate the requested timestamps.  Return 0 if the resulting
++   timespec can be used for utimensat (after possibly modifying it to
++   work around bugs in utimensat).  Return a positive value if the
++   timespec needs further adjustment based on stat results: 1 if any
++   adjustment is needed for utimes, and 2 if any adjustment is needed
++   for Linux utimensat.  Return -1, with errno set to EINVAL, if
++   timespec is out of range.  */
++static int
++validate_timespec (struct timespec timespec[2])
++{
++  int result = 0;
++  int utime_omit_count = 0;
++  if ((timespec[0].tv_nsec != UTIME_NOW
++       && timespec[0].tv_nsec != UTIME_OMIT
++       && ! (0 <= timespec[0].tv_nsec
++             && timespec[0].tv_nsec < TIMESPEC_HZ))
++      || (timespec[1].tv_nsec != UTIME_NOW
++          && timespec[1].tv_nsec != UTIME_OMIT
++          && ! (0 <= timespec[1].tv_nsec
++                && timespec[1].tv_nsec < TIMESPEC_HZ)))
++    {
++      errno = EINVAL;
++      return -1;
++    }
++  /* Work around Linux kernel 2.6.25 bug, where utimensat fails with
++     EINVAL if tv_sec is not 0 when using the flag values of tv_nsec.
++     Flag a Linux kernel 2.6.32 bug, where an mtime of UTIME_OMIT
++     fails to bump ctime.  */
++  if (timespec[0].tv_nsec == UTIME_NOW
++      || timespec[0].tv_nsec == UTIME_OMIT)
++    {
++      timespec[0].tv_sec = 0;
++      result = 1;
++      if (timespec[0].tv_nsec == UTIME_OMIT)
++        utime_omit_count++;
++    }
++  if (timespec[1].tv_nsec == UTIME_NOW
++      || timespec[1].tv_nsec == UTIME_OMIT)
++    {
++      timespec[1].tv_sec = 0;
++      result = 1;
++      if (timespec[1].tv_nsec == UTIME_OMIT)
++        utime_omit_count++;
++    }
++  return result + (utime_omit_count == 1);
++}
++
++/* Normalize any UTIME_NOW or UTIME_OMIT values in (*TS)[0] and (*TS)[1],
++   using STATBUF to obtain the current timestamps of the file.  If
++   both times are UTIME_NOW, set *TS to NULL (as this can avoid some
++   permissions issues).  If both times are UTIME_OMIT, return true
++   (nothing further beyond the prior collection of STATBUF is
++   necessary); otherwise return false.  */
++static bool
++update_timespec (struct stat const *statbuf, struct timespec **ts)
++{
++  struct timespec *timespec = *ts;
++  if (timespec[0].tv_nsec == UTIME_OMIT
++      && timespec[1].tv_nsec == UTIME_OMIT)
++    return true;
++  if (timespec[0].tv_nsec == UTIME_NOW
++      && timespec[1].tv_nsec == UTIME_NOW)
++    {
++      *ts = NULL;
++      return false;
++    }
++
++  if (timespec[0].tv_nsec == UTIME_OMIT)
++    timespec[0] = get_stat_atime (statbuf);
++  else if (timespec[0].tv_nsec == UTIME_NOW)
++    gettime (&timespec[0]);
++
++  if (timespec[1].tv_nsec == UTIME_OMIT)
++    timespec[1] = get_stat_mtime (statbuf);
++  else if (timespec[1].tv_nsec == UTIME_NOW)
++    gettime (&timespec[1]);
++
++  return false;
++}
++
++/* Set the access and modification timestamps of FD (a.k.a. FILE) to be
++   TIMESPEC[0] and TIMESPEC[1], respectively.
++   FD must be either negative -- in which case it is ignored --
++   or a file descriptor that is open on FILE.
++   If FD is nonnegative, then FILE can be NULL, which means
++   use just futimes (or equivalent) instead of utimes (or equivalent),
++   and fail if on an old system without futimes (or equivalent).
++   If TIMESPEC is null, set the timestamps to the current time.
++   Return 0 on success, -1 (setting errno) on failure.  */
++
++int
++fdutimens (int fd, char const *file, struct timespec const timespec[2])
++{
++  struct timespec adjusted_timespec[2];
++  struct timespec *ts = timespec ? adjusted_timespec : NULL;
++  int adjustment_needed = 0;
++  struct stat st;
++
++  if (ts)
++    {
++      adjusted_timespec[0] = timespec[0];
++      adjusted_timespec[1] = timespec[1];
++      adjustment_needed = validate_timespec (ts);
++    }
++  if (adjustment_needed < 0)
++    return -1;
++
++  /* Require that at least one of FD or FILE are potentially valid, to avoid
++     a Linux bug where futimens (AT_FDCWD, NULL) changes "." rather
++     than failing.  */
++  if (fd < 0 && !file)
++    {
++      errno = EBADF;
++      return -1;
++    }
++
++  /* Some Linux-based NFS clients are buggy, and mishandle timestamps
++     of files in NFS file systems in some cases.  We have no
++     configure-time test for this, but please see
++     <https://bugs.gentoo.org/show_bug.cgi?id=132673> for references to
++     some of the problems with Linux 2.6.16.  If this affects you,
++     compile with -DHAVE_BUGGY_NFS_TIME_STAMPS; this is reported to
++     help in some cases, albeit at a cost in performance.  But you
++     really should upgrade your kernel to a fixed version, since the
++     problem affects many applications.  */
++
++#if HAVE_BUGGY_NFS_TIME_STAMPS
++  if (fd < 0)
++    sync ();
++  else
++    fsync (fd);
++#endif
++
++  /* POSIX 2008 added two interfaces to set file timestamps with
++     nanosecond resolution; newer Linux implements both functions via
++     a single syscall.  We provide a fallback for ENOSYS (for example,
++     compiling against Linux 2.6.25 kernel headers and glibc 2.7, but
++     running on Linux 2.6.18 kernel).  */
++#if HAVE_UTIMENSAT || HAVE_FUTIMENS
++  if (0 <= utimensat_works_really)
++    {
++      int result;
++# if __linux__ || __sun
++      /* As recently as Linux kernel 2.6.32 (Dec 2009), several file
++         systems (xfs, ntfs-3g) have bugs with a single UTIME_OMIT,
++         but work if both times are either explicitly specified or
++         UTIME_NOW.  Work around it with a preparatory [f]stat prior
++         to calling futimens/utimensat; fortunately, there is not much
++         timing impact due to the extra syscall even on file systems
++         where UTIME_OMIT would have worked.
++
++         The same bug occurs in Solaris 11.1 (Apr 2013).
++
++         FIXME: Simplify this for Linux in 2016 and for Solaris in
++         2024, when file system bugs are no longer common.  */
++      if (adjustment_needed == 2)
++        {
++          if (fd < 0 ? stat (file, &st) : fstat (fd, &st))
++            return -1;
++          if (ts[0].tv_nsec == UTIME_OMIT)
++            ts[0] = get_stat_atime (&st);
++          else if (ts[1].tv_nsec == UTIME_OMIT)
++            ts[1] = get_stat_mtime (&st);
++          /* Note that st is good, in case utimensat gives ENOSYS.  */
++          adjustment_needed++;
++        }
++# endif
++# if HAVE_UTIMENSAT
++      if (fd < 0)
++        {
++#  if defined __APPLE__ && defined __MACH__
++          size_t len = strlen (file);
++          if (len > 0 && file[len - 1] == '/')
++            {
++              struct stat statbuf;
++              if (stat (file, &statbuf) < 0)
++                return -1;
++              if (!S_ISDIR (statbuf.st_mode))
++                {
++                  errno = ENOTDIR;
++                  return -1;
++                }
++            }
++#  endif
++          result = utimensat (AT_FDCWD, file, ts, 0);
++#  ifdef __linux__
++          /* Work around a kernel bug:
++             https://bugzilla.redhat.com/show_bug.cgi?id=442352
++             https://bugzilla.redhat.com/show_bug.cgi?id=449910
++             It appears that utimensat can mistakenly return 280 rather
++             than -1 upon ENOSYS failure.
++             FIXME: remove in 2010 or whenever the offending kernels
++             are no longer in common use.  */
++          if (0 < result)
++            errno = ENOSYS;
++#  endif /* __linux__ */
++          if (result == 0 || errno != ENOSYS)
++            {
++              utimensat_works_really = 1;
++              return result;
++            }
++        }
++# endif /* HAVE_UTIMENSAT */
++# if HAVE_FUTIMENS
++      if (0 <= fd)
++        {
++          result = futimens (fd, ts);
++#  ifdef __linux__
++          /* Work around the same bug as above.  */
++          if (0 < result)
++            errno = ENOSYS;
++#  endif /* __linux__ */
++          if (result == 0 || errno != ENOSYS)
++            {
++              utimensat_works_really = 1;
++              return result;
++            }
++        }
++# endif /* HAVE_FUTIMENS */
++    }
++  utimensat_works_really = -1;
++  lutimensat_works_really = -1;
++#endif /* HAVE_UTIMENSAT || HAVE_FUTIMENS */
++
++#ifdef USE_SETFILETIME
++  /* On native Windows, use SetFileTime(). See
++     <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfiletime>
++     <https://docs.microsoft.com/en-us/windows/desktop/api/minwinbase/ns-minwinbase-filetime>  */
++  if (0 <= fd)
++    {
++      HANDLE handle;
++      FILETIME current_time;
++      FILETIME last_access_time;
++      FILETIME last_write_time;
++
++      handle = (HANDLE) _get_osfhandle (fd);
++      if (handle == INVALID_HANDLE_VALUE)
++        {
++          errno = EBADF;
++          return -1;
++        }
++
++      if (ts == NULL || ts[0].tv_nsec == UTIME_NOW || ts[1].tv_nsec == UTIME_NOW)
++        {
++          /* GetSystemTimeAsFileTime
++             <https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getsystemtimeasfiletime>.
++             It would be overkill to use
++             GetSystemTimePreciseAsFileTime
++             <https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getsystemtimepreciseasfiletime>.  */
++          GetSystemTimeAsFileTime (&current_time);
++        }
++
++      if (ts == NULL || ts[0].tv_nsec == UTIME_NOW)
++        {
++          last_access_time = current_time;
++        }
++      else if (ts[0].tv_nsec == UTIME_OMIT)
++        {
++          last_access_time.dwLowDateTime = 0;
++          last_access_time.dwHighDateTime = 0;
++        }
++      else
++        {
++          ULONGLONG time_since_16010101 =
++            (ULONGLONG) ts[0].tv_sec * 10000000 + ts[0].tv_nsec / 100 + 116444736000000000LL;
++          last_access_time.dwLowDateTime = (DWORD) time_since_16010101;
++          last_access_time.dwHighDateTime = time_since_16010101 >> 32;
++        }
++
++      if (ts == NULL || ts[1].tv_nsec == UTIME_NOW)
++        {
++          last_write_time = current_time;
++        }
++      else if (ts[1].tv_nsec == UTIME_OMIT)
++        {
++          last_write_time.dwLowDateTime = 0;
++          last_write_time.dwHighDateTime = 0;
++        }
++      else
++        {
++          ULONGLONG time_since_16010101 =
++            (ULONGLONG) ts[1].tv_sec * 10000000 + ts[1].tv_nsec / 100 + 116444736000000000LL;
++          last_write_time.dwLowDateTime = (DWORD) time_since_16010101;
++          last_write_time.dwHighDateTime = time_since_16010101 >> 32;
++        }
++
++      if (SetFileTime (handle, NULL, &last_access_time, &last_write_time))
++        return 0;
++      else
++        {
++          DWORD sft_error = GetLastError ();
++          #if 0
++          fprintf (stderr, "fdutimens SetFileTime error 0x%x\n", (unsigned int) sft_error);
++          #endif
++          switch (sft_error)
++            {
++            case ERROR_ACCESS_DENIED: /* fd was opened without O_RDWR */
++              errno = EACCES; /* not specified by POSIX */
++              break;
++            default:
++              errno = EINVAL;
++              break;
++            }
++          return -1;
++        }
++    }
++#endif
++
++  /* The platform lacks an interface to set file timestamps with
++     nanosecond resolution, so do the best we can, discarding any
++     fractional part of the timestamp.  */
++
++  if (adjustment_needed || (REPLACE_FUNC_STAT_FILE && fd < 0))
++    {
++      if (adjustment_needed != 3
++          && (fd < 0 ? stat (file, &st) : fstat (fd, &st)))
++        return -1;
++      if (ts && update_timespec (&st, &ts))
++        return 0;
++    }
++
++  {
++#if HAVE_FUTIMESAT || HAVE_WORKING_UTIMES
++    struct timeval timeval[2];
++    struct timeval *t;
++    if (ts)
++      {
++        timeval[0].tv_sec = ts[0].tv_sec;
++        timeval[0].tv_usec = ts[0].tv_nsec / 1000;
++        timeval[1].tv_sec = ts[1].tv_sec;
++        timeval[1].tv_usec = ts[1].tv_nsec / 1000;
++        t = timeval;
++      }
++    else
++      t = NULL;
++
++    if (fd < 0)
++      {
++# if HAVE_FUTIMESAT
++        return futimesat (AT_FDCWD, file, t);
++# endif
++      }
++    else
++      {
++        /* If futimesat or futimes fails here, don't try to speed things
++           up by returning right away.  glibc can incorrectly fail with
++           errno == ENOENT if /proc isn't mounted.  Also, Mandrake 10.0
++           in high security mode doesn't allow ordinary users to read
++           /proc/self, so glibc incorrectly fails with errno == EACCES.
++           If errno == EIO, EPERM, or EROFS, it's probably safe to fail
++           right away, but these cases are rare enough that they're not
++           worth optimizing, and who knows what other messed-up systems
++           are out there?  So play it safe and fall back on the code
++           below.  */
++
++# if (HAVE_FUTIMESAT && !FUTIMESAT_NULL_BUG) || HAVE_FUTIMES
++#  if HAVE_FUTIMESAT && !FUTIMESAT_NULL_BUG
++#   undef futimes
++#   define futimes(fd, t) futimesat (fd, NULL, t)
++#  endif
++        if (futimes (fd, t) == 0)
++          {
++#  if __linux__ && __GLIBC__
++            /* Work around a longstanding glibc bug, still present as
++               of 2010-12-27.  On older Linux kernels that lack both
++               utimensat and utimes, glibc's futimes rounds instead of
++               truncating when falling back on utime.  The same bug
++               occurs in futimesat with a null 2nd arg.  */
++            if (t)
++              {
++                bool abig = 500000 <= t[0].tv_usec;
++                bool mbig = 500000 <= t[1].tv_usec;
++                if ((abig | mbig) && fstat (fd, &st) == 0)
++                  {
++                    /* If these two subtractions overflow, they'll
++                       track the overflows inside the buggy glibc.  */
++                    time_t adiff = st.st_atime - t[0].tv_sec;
++                    time_t mdiff = st.st_mtime - t[1].tv_sec;
++
++                    struct timeval *tt = NULL;
++                    struct timeval truncated_timeval[2];
++                    truncated_timeval[0] = t[0];
++                    truncated_timeval[1] = t[1];
++                    if (abig && adiff == 1 && get_stat_atime_ns (&st) == 0)
++                      {
++                        tt = truncated_timeval;
++                        tt[0].tv_usec = 0;
++                      }
++                    if (mbig && mdiff == 1 && get_stat_mtime_ns (&st) == 0)
++                      {
++                        tt = truncated_timeval;
++                        tt[1].tv_usec = 0;
++                      }
++                    if (tt)
++                      futimes (fd, tt);
++                  }
++              }
++#  endif
++
++            return 0;
++          }
++# endif
++      }
++#endif /* HAVE_FUTIMESAT || HAVE_WORKING_UTIMES */
++
++    if (!file)
++      {
++#if ! ((HAVE_FUTIMESAT && !FUTIMESAT_NULL_BUG)          \
++        || (HAVE_WORKING_UTIMES && HAVE_FUTIMES))
++        errno = ENOSYS;
++#endif
++        return -1;
++      }
++
++#ifdef USE_SETFILETIME
++    return _gl_utimens_windows (file, ts);
++#elif HAVE_WORKING_UTIMES
++    return utimes (file, t);
++#else
++    {
++      struct utimbuf utimbuf;
++      struct utimbuf *ut;
++      if (ts)
++        {
++          utimbuf.actime = ts[0].tv_sec;
++          utimbuf.modtime = ts[1].tv_sec;
++          ut = &utimbuf;
++        }
++      else
++        ut = NULL;
++
++      return utime (file, ut);
++    }
++#endif /* !HAVE_WORKING_UTIMES */
++  }
++}
++
++/* Set the access and modification timestamps of FILE to be
++   TIMESPEC[0] and TIMESPEC[1], respectively.  */
++int
++utimens (char const *file, struct timespec const timespec[2])
++{
++  return fdutimens (-1, file, timespec);
++}
++
++/* Set the access and modification timestamps of FILE to be
++   TIMESPEC[0] and TIMESPEC[1], respectively, without dereferencing
++   symlinks.  Fail with ENOSYS if the platform does not support
++   changing symlink timestamps, but FILE was a symlink.  */
++int
++lutimens (char const *file, struct timespec const timespec[2])
++{
++  struct timespec adjusted_timespec[2];
++  struct timespec *ts = timespec ? adjusted_timespec : NULL;
++  int adjustment_needed = 0;
++  struct stat st;
++
++  if (ts)
++    {
++      adjusted_timespec[0] = timespec[0];
++      adjusted_timespec[1] = timespec[1];
++      adjustment_needed = validate_timespec (ts);
++    }
++  if (adjustment_needed < 0)
++    return -1;
++
++  /* The Linux kernel did not support symlink timestamps until
++     utimensat, in version 2.6.22, so we don't need to mimic
++     fdutimens' worry about buggy NFS clients.  But we do have to
++     worry about bogus return values.  */
++
++#if HAVE_UTIMENSAT
++  if (0 <= lutimensat_works_really)
++    {
++      int result;
++# if __linux__ || __sun
++      /* As recently as Linux kernel 2.6.32 (Dec 2009), several file
++         systems (xfs, ntfs-3g) have bugs with a single UTIME_OMIT,
++         but work if both times are either explicitly specified or
++         UTIME_NOW.  Work around it with a preparatory lstat prior to
++         calling utimensat; fortunately, there is not much timing
++         impact due to the extra syscall even on file systems where
++         UTIME_OMIT would have worked.
++
++         The same bug occurs in Solaris 11.1 (Apr 2013).
++
++         FIXME: Simplify this for Linux in 2016 and for Solaris in
++         2024, when file system bugs are no longer common.  */
++      if (adjustment_needed == 2)
++        {
++          if (lstat (file, &st))
++            return -1;
++          if (ts[0].tv_nsec == UTIME_OMIT)
++            ts[0] = get_stat_atime (&st);
++          else if (ts[1].tv_nsec == UTIME_OMIT)
++            ts[1] = get_stat_mtime (&st);
++          /* Note that st is good, in case utimensat gives ENOSYS.  */
++          adjustment_needed++;
++        }
++# endif
++      result = utimensat (AT_FDCWD, file, ts, AT_SYMLINK_NOFOLLOW);
++# ifdef __linux__
++      /* Work around a kernel bug:
++         https://bugzilla.redhat.com/show_bug.cgi?id=442352
++         https://bugzilla.redhat.com/show_bug.cgi?id=449910
++         It appears that utimensat can mistakenly return 280 rather
++         than -1 upon ENOSYS failure.
++         FIXME: remove in 2010 or whenever the offending kernels
++         are no longer in common use.  */
++      if (0 < result)
++        errno = ENOSYS;
++# endif
++      if (result == 0 || errno != ENOSYS)
++        {
++          utimensat_works_really = 1;
++          lutimensat_works_really = 1;
++          return result;
++        }
++    }
++  lutimensat_works_really = -1;
++#endif /* HAVE_UTIMENSAT */
++
++  /* The platform lacks an interface to set file timestamps with
++     nanosecond resolution, so do the best we can, discarding any
++     fractional part of the timestamp.  */
++
++  if (adjustment_needed || REPLACE_FUNC_STAT_FILE)
++    {
++      if (adjustment_needed != 3 && lstat (file, &st))
++        return -1;
++      if (ts && update_timespec (&st, &ts))
++        return 0;
++    }
++
++  /* On Linux, lutimes is a thin wrapper around utimensat, so there is
++     no point trying lutimes if utimensat failed with ENOSYS.  */
++#if HAVE_LUTIMES && !HAVE_UTIMENSAT
++  {
++    struct timeval timeval[2];
++    struct timeval *t;
++    int result;
++    if (ts)
++      {
++        timeval[0].tv_sec = ts[0].tv_sec;
++        timeval[0].tv_usec = ts[0].tv_nsec / 1000;
++        timeval[1].tv_sec = ts[1].tv_sec;
++        timeval[1].tv_usec = ts[1].tv_nsec / 1000;
++        t = timeval;
++      }
++    else
++      t = NULL;
++
++    result = lutimes (file, t);
++    if (result == 0 || errno != ENOSYS)
++      return result;
++  }
++#endif /* HAVE_LUTIMES && !HAVE_UTIMENSAT */
++
++  /* Out of luck for symlinks, but we still handle regular files.  */
++  if (!(adjustment_needed || REPLACE_FUNC_STAT_FILE) && lstat (file, &st))
++    return -1;
++  if (!S_ISLNK (st.st_mode))
++    return fdutimens (-1, file, ts);
++  errno = ENOSYS;
++  return -1;
++}
+diff --git a/lib/utimens.h b/lib/utimens.h
+new file mode 100644
+index 0000000..295d3d7
+--- /dev/null
++++ b/lib/utimens.h
+@@ -0,0 +1,49 @@
++/* Set file access and modification times.
++
++   Copyright 2012-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify it
++   under the terms of the GNU General Public License as published by the
++   Free Software Foundation; either version 3 of the License, or any
++   later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++/* Written by Paul Eggert.  */
++
++#include <time.h>
++int fdutimens (int, char const *, struct timespec const [2]);
++int utimens (char const *, struct timespec const [2]);
++int lutimens (char const *, struct timespec const [2]);
++
++#if GNULIB_FDUTIMENSAT
++# include <fcntl.h>
++# include <sys/stat.h>
++
++#ifndef _GL_INLINE_HEADER_BEGIN
++ #error "Please include config.h first."
++#endif
++_GL_INLINE_HEADER_BEGIN
++#ifndef _GL_UTIMENS_INLINE
++# define _GL_UTIMENS_INLINE _GL_INLINE
++#endif
++
++int fdutimensat (int fd, int dir, char const *name, struct timespec const [2],
++                 int atflag);
++
++/* Using this function makes application code slightly more readable.  */
++_GL_UTIMENS_INLINE int
++lutimensat (int dir, char const *file, struct timespec const times[2])
++{
++  return utimensat (dir, file, times, AT_SYMLINK_NOFOLLOW);
++}
++
++_GL_INLINE_HEADER_END
++
++#endif
+diff --git a/m4/utimens.m4 b/m4/utimens.m4
+new file mode 100644
+index 0000000..2ee4ef9
+--- /dev/null
++++ b/m4/utimens.m4
+@@ -0,0 +1,52 @@
++dnl Copyright (C) 2003-2021 Free Software Foundation, Inc.
++dnl This file is free software; the Free Software Foundation
++dnl gives unlimited permission to copy and/or distribute it,
++dnl with or without modifications, as long as this notice is preserved.
++
++dnl serial 11
++
++AC_DEFUN([gl_UTIMENS],
++[
++  dnl Prerequisites of lib/utimens.c.
++  AC_REQUIRE([gl_FUNC_UTIMES])
++  AC_REQUIRE([gl_CHECK_TYPE_STRUCT_TIMESPEC])
++  AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
++  AC_CHECK_FUNCS_ONCE([futimes futimesat futimens utimensat lutimes])
++
++  if test $ac_cv_func_futimens = no && test $ac_cv_func_futimesat = yes; then
++    dnl FreeBSD 8.0-rc2 mishandles futimesat(fd,NULL,time).  It is not
++    dnl standardized, but Solaris implemented it first and uses it as
++    dnl its only means to set fd time.
++    AC_CACHE_CHECK([whether futimesat handles NULL file],
++      [gl_cv_func_futimesat_works],
++      [touch conftest.file
++       AC_RUN_IFELSE([AC_LANG_PROGRAM([[
++#include <stddef.h>
++#include <sys/times.h>
++#include <fcntl.h>
++]GL_MDA_DEFINES],
++        [[int fd = open ("conftest.file", O_RDWR);
++          if (fd < 0) return 1;
++          if (futimesat (fd, NULL, NULL)) return 2;
++        ]])],
++        [gl_cv_func_futimesat_works=yes],
++        [gl_cv_func_futimesat_works=no],
++        [case "$host_os" in
++                            # Guess yes on Linux systems.
++           linux-* | linux) gl_cv_func_futimesat_works="guessing yes" ;;
++                            # Guess yes on glibc systems.
++           *-gnu*)          gl_cv_func_futimesat_works="guessing yes" ;;
++                            # If we don't know, obey --enable-cross-guesses.
++           *)               gl_cv_func_futimesat_works="$gl_cross_guess_normal" ;;
++         esac
++        ])
++      rm -f conftest.file])
++    case "$gl_cv_func_futimesat_works" in
++      *yes) ;;
++      *)
++        AC_DEFINE([FUTIMESAT_NULL_BUG], [1],
++          [Define to 1 if futimesat mishandles a NULL file name.])
++        ;;
++    esac
++  fi
++])
+diff --git a/m4/utimes.m4 b/m4/utimes.m4
+new file mode 100644
+index 0000000..0440e78
+--- /dev/null
++++ b/m4/utimes.m4
+@@ -0,0 +1,161 @@
++# Detect some bugs in glibc's implementation of utimes.
++# serial 8
++
++dnl Copyright (C) 2003-2005, 2009-2021 Free Software Foundation, Inc.
++dnl This file is free software; the Free Software Foundation
++dnl gives unlimited permission to copy and/or distribute it,
++dnl with or without modifications, as long as this notice is preserved.
++
++# See if we need to work around bugs in glibc's implementation of
++# utimes from 2003-07-12 to 2003-09-17.
++# First, there was a bug that would make utimes set mtime
++# and atime to zero (1970-01-01) unconditionally.
++# Then, there was code to round rather than truncate.
++# Then, there was an implementation (sparc64, Linux-2.4.28, glibc-2.3.3)
++# that didn't honor the NULL-means-set-to-current-time semantics.
++# Finally, there was also a version of utimes that failed on read-only
++# files, while utime worked fine (linux-2.2.20, glibc-2.2.5).
++#
++# From Jim Meyering, with suggestions from Paul Eggert.
++
++AC_DEFUN([gl_FUNC_UTIMES],
++[
++  AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
++  AC_CACHE_CHECK([whether the utimes function works],
++                 [gl_cv_func_working_utimes],
++    [AC_RUN_IFELSE([AC_LANG_SOURCE([[
++#include <sys/types.h>
++#include <sys/stat.h>
++#include <fcntl.h>
++#include <sys/time.h>
++#include <time.h>
++#include <unistd.h>
++#include <stdlib.h>
++#include <stdio.h>
++#include <utime.h>
++#include <errno.h>
++]GL_MDA_DEFINES[
++
++static int
++inorder (time_t a, time_t b, time_t c)
++{
++  return a <= b && b <= c;
++}
++
++int
++main ()
++{
++  int result = 0;
++  char const *file = "conftest.utimes";
++  /* On OS/2, file timestamps must be on or after 1980 in local time,
++     with an even number of seconds.  */
++  static struct timeval timeval[2] = {{315620000 + 10, 10},
++                                      {315620000 + 1000000, 999998}};
++
++  /* Test whether utimes() essentially works.  */
++  {
++    struct stat sbuf;
++    FILE *f = fopen (file, "w");
++    if (f == NULL)
++      result |= 1;
++    else if (fclose (f) != 0)
++      result |= 1;
++    else if (utimes (file, timeval) != 0)
++      result |= 2;
++    else if (lstat (file, &sbuf) != 0)
++      result |= 1;
++    else if (!(sbuf.st_atime == timeval[0].tv_sec
++               && sbuf.st_mtime == timeval[1].tv_sec))
++      result |= 4;
++    if (unlink (file) != 0)
++      result |= 1;
++  }
++
++  /* Test whether utimes() with a NULL argument sets the file's timestamp
++     to the current time.  Use 'fstat' as well as 'time' to
++     determine the "current" time, to accommodate NFS file systems
++     if there is a time skew between the host and the NFS server.  */
++  {
++    int fd = open (file, O_WRONLY|O_CREAT, 0644);
++    if (fd < 0)
++      result |= 1;
++    else
++      {
++        time_t t0, t2;
++        struct stat st0, st1, st2;
++        if (time (&t0) == (time_t) -1)
++          result |= 1;
++        else if (fstat (fd, &st0) != 0)
++          result |= 1;
++        else if (utimes (file, timeval) != 0
++                 && (errno != EACCES
++                     /* OS/2 kLIBC utimes fails on opened files.  */
++                     || close (fd) != 0
++                     || utimes (file, timeval) != 0
++                     || (fd = open (file, O_WRONLY)) < 0))
++          result |= 2;
++        else if (utimes (file, NULL) != 0
++                 && (errno != EACCES
++                     /* OS/2 kLIBC utimes fails on opened files.  */
++                     || close (fd) != 0
++                     || utimes (file, NULL) != 0
++                     || (fd = open (file, O_WRONLY)) < 0))
++          result |= 8;
++        else if (fstat (fd, &st1) != 0)
++          result |= 1;
++        else if (write (fd, "\n", 1) != 1)
++          result |= 1;
++        else if (fstat (fd, &st2) != 0)
++          result |= 1;
++        else if (time (&t2) == (time_t) -1)
++          result |= 1;
++        else
++          {
++            int m_ok_POSIX = inorder (t0, st1.st_mtime, t2);
++            int m_ok_NFS = inorder (st0.st_mtime, st1.st_mtime, st2.st_mtime);
++            if (! (st1.st_atime == st1.st_mtime))
++              result |= 16;
++            if (! (m_ok_POSIX || m_ok_NFS))
++              result |= 32;
++          }
++        if (close (fd) != 0)
++          result |= 1;
++      }
++    if (unlink (file) != 0)
++      result |= 1;
++  }
++
++  /* Test whether utimes() with a NULL argument works on read-only files.  */
++  {
++    int fd = open (file, O_WRONLY|O_CREAT, 0444);
++    if (fd < 0)
++      result |= 1;
++    else if (close (fd) != 0)
++      result |= 1;
++    else if (utimes (file, NULL) != 0)
++      result |= 64;
++    if (unlink (file) != 0)
++      result |= 1;
++  }
++
++  return result;
++}
++  ]])],
++       [gl_cv_func_working_utimes=yes],
++       [gl_cv_func_working_utimes=no],
++       [case "$host_os" in
++                   # Guess yes on musl systems.
++          *-musl*) gl_cv_func_working_utimes="guessing yes" ;;
++                   # Guess no on native Windows.
++          mingw*)  gl_cv_func_working_utimes="guessing no" ;;
++          *)       gl_cv_func_working_utimes="$gl_cross_guess_normal" ;;
++        esac
++       ])
++    ])
++
++  case "$gl_cv_func_working_utimes" in
++    *yes)
++      AC_DEFINE([HAVE_WORKING_UTIMES], [1], [Define if utimes works properly.])
++      ;;
++  esac
++])
+diff --git a/lib/binary-io.c b/lib/binary-io.c
+new file mode 100644
+index 0000000..f267897
+--- /dev/null
++++ b/lib/binary-io.c
+@@ -0,0 +1,39 @@
++/* Binary mode I/O.
++   Copyright 2017-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++#include <config.h>
++
++#define BINARY_IO_INLINE _GL_EXTERN_INLINE
++#include "binary-io.h"
++
++#if defined __DJGPP__ || defined __EMX__
++# include <unistd.h>
++
++int
++set_binary_mode (int fd, int mode)
++{
++  if (isatty (fd))
++    /* If FD refers to a console (not a pipe, not a regular file),
++       O_TEXT is the only reasonable mode, both on input and on output.
++       Silently ignore the request.  If we were to return -1 here,
++       all programs that use xset_binary_mode would fail when run
++       with console input or console output.  */
++    return O_TEXT;
++  else
++    return __gl_setmode (fd, mode);
++}
++
++#endif
+diff --git a/lib/binary-io.h b/lib/binary-io.h
+new file mode 100644
+index 0000000..8654fd2
+--- /dev/null
++++ b/lib/binary-io.h
+@@ -0,0 +1,77 @@
++/* Binary mode I/O.
++   Copyright (C) 2001, 2003, 2005, 2008-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++#ifndef _BINARY_H
++#define _BINARY_H
++
++/* For systems that distinguish between text and binary I/O.
++   O_BINARY is guaranteed by the gnulib <fcntl.h>. */
++#include <fcntl.h>
++
++/* The MSVC7 <stdio.h> doesn't like to be included after '#define fileno ...',
++   so we include it here first.  */
++#include <stdio.h>
++
++#ifndef _GL_INLINE_HEADER_BEGIN
++ #error "Please include config.h first."
++#endif
++_GL_INLINE_HEADER_BEGIN
++#ifndef BINARY_IO_INLINE
++# define BINARY_IO_INLINE _GL_INLINE
++#endif
++
++#if O_BINARY
++# if defined __EMX__ || defined __DJGPP__ || defined __CYGWIN__
++#  include <io.h> /* declares setmode() */
++#  define __gl_setmode setmode
++# else
++#  define __gl_setmode _setmode
++#  undef fileno
++#  define fileno _fileno
++# endif
++#else
++  /* On reasonable systems, binary I/O is the only choice.  */
++  /* Use a function rather than a macro, to avoid gcc warnings
++     "warning: statement with no effect".  */
++BINARY_IO_INLINE int
++__gl_setmode (int fd _GL_UNUSED, int mode _GL_UNUSED)
++{
++  return O_BINARY;
++}
++#endif
++
++/* Set FD's mode to MODE, which should be either O_TEXT or O_BINARY.
++   Return the old mode if successful, -1 (setting errno) on failure.
++   Ordinarily this function would be called 'setmode', since that is
++   its old name on MS-Windows, but it is called 'set_binary_mode' here
++   to avoid colliding with a BSD function of another name.  */
++
++#if defined __DJGPP__ || defined __EMX__
++extern int set_binary_mode (int fd, int mode);
++#else
++BINARY_IO_INLINE int
++set_binary_mode (int fd, int mode)
++{
++  return __gl_setmode (fd, mode);
++}
++#endif
++
++/* This macro is obsolescent.  */
++#define SET_BINARY(fd) ((void) set_binary_mode (fd, O_BINARY))
++
++_GL_INLINE_HEADER_END
++
++#endif /* _BINARY_H */
+diff --git a/lib/safe-write.c b/lib/safe-write.c
+new file mode 100644
+index 0000000..a460dc9
+--- /dev/null
++++ b/lib/safe-write.c
+@@ -0,0 +1,18 @@
++/* An interface to write that retries after interrupts.
++   Copyright (C) 2002, 2009-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++#define SAFE_WRITE
++#include "safe-read.c"
+diff --git a/lib/safe-write.h b/lib/safe-write.h
+new file mode 100644
+index 0000000..4307ffe
+--- /dev/null
++++ b/lib/safe-write.h
+@@ -0,0 +1,37 @@
++/* An interface to write() that retries after interrupts.
++   Copyright (C) 2002, 2009-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++/* Some system calls may be interrupted and fail with errno = EINTR in the
++   following situations:
++     - The process is stopped and restarted (signal SIGSTOP and SIGCONT, user
++       types Ctrl-Z) on some platforms: Mac OS X.
++     - The process receives a signal for which a signal handler was installed
++       with sigaction() with an sa_flags field that does not contain
++       SA_RESTART.
++     - The process receives a signal for which a signal handler was installed
++       with signal() and for which no call to siginterrupt(sig,0) was done,
++       on some platforms: AIX, HP-UX, IRIX, OSF/1, Solaris.
++
++   This module provides a wrapper around write() that handles EINTR.  */
++
++#include <stddef.h>
++
++#define SAFE_WRITE_ERROR ((size_t) -1)
++
++/* Write up to COUNT bytes at BUF to descriptor FD, retrying if interrupted.
++   Return the actual number of bytes written, zero for EOF, or SAFE_WRITE_ERROR
++   upon error.  */
++extern size_t safe_write (int fd, const void *buf, size_t count);
+diff --git a/m4/safe-write.m4 b/m4/safe-write.m4
+new file mode 100644
+index 0000000..ef10d96
+--- /dev/null
++++ b/m4/safe-write.m4
+@@ -0,0 +1,11 @@
++# safe-write.m4 serial 4
++dnl Copyright (C) 2002, 2005-2006, 2009-2021 Free Software Foundation, Inc.
++dnl This file is free software; the Free Software Foundation
++dnl gives unlimited permission to copy and/or distribute it,
++dnl with or without modifications, as long as this notice is preserved.
++
++# Prerequisites of lib/safe-write.c.
++AC_DEFUN([gl_PREREQ_SAFE_WRITE],
++[
++  gl_PREREQ_SAFE_READ
++])
+
+
diff --git a/srcpkgs/recutils/patches/exdev.patch b/srcpkgs/recutils/patches/exdev.patch
new file mode 100644
index 000000000000..6d1f8bb213a1
--- /dev/null
+++ b/srcpkgs/recutils/patches/exdev.patch
@@ -0,0 +1,53 @@
+From 86f662a8202408134a235572ec60141d3082f975 Mon Sep 17 00:00:00 2001
+From: "Jose E. Marchesi" <jose.marchesi@oracle.com>
+Date: Tue, 28 Jan 2020 12:13:42 +0100
+Subject: utils: make utilitis to work with temporary files in a different
+ filesystem
+
+2020-01-28  Jose E. Marchesi  <jose.marchesi@oracle.com>
+
+	* bootstrap.conf (gnulib_modules): Import the modules copy-file
+	and remove.
+	* utils/recutl.c (recutl_write_db_to_file): Copy and remove
+	instead of rename temporary files.
+---
+ utils/recutl.c | 14 ++++++++------
+ 1 file changed, 8 insertions(+), 6 deletions(-)
+
+(limited to 'utils/recutl.c')
+
+diff --git a/utils/recutl.c b/utils/recutl.c
+index 9cf823d..a814fd8 100644
+--- a/utils/recutl.c
++++ b/utils/recutl.c
+@@ -58,6 +58,7 @@
+ #include <rec.h>
+ #include <recutl.h>
+ #include "read-file.h"
++#include "copy-file.h"
+ 
+ /*
+  * Global variables.
+@@ -432,12 +433,13 @@ recutl_write_db_to_file (rec_db_t db,
+ 
+   if (file_name)
+     {
+-      /* Rename the temporary file to file_name.  */
+-      if (rename (tmp_file_name, file_name) == -1)
+-        {
+-          remove (tmp_file_name);
+-          recutl_fatal (_("renaming file %s to %s\n"), tmp_file_name, file_name);
+-        }
++      /* Rename the temporary file to file_name.  We copy and remove
++         instead of renaming because the later doesn't work across
++         different mount points, and it is getting common for /tmp to
++         be mounted in its own filesystem.  */
++      if (qcopy_file_preserving (tmp_file_name, file_name) != 0)
++        recutl_fatal (_("renaming file %s to %s\n"), tmp_file_name, file_name);
++      remove (tmp_file_name);
+ 
+       /* Restore the attributes of the original file. */
+       if (stat_result != -1)
+-- 
+cgit v1.2.1
+
diff --git a/srcpkgs/recutils/template b/srcpkgs/recutils/template
index 3e71312c4e25..566f18dfa542 100644
--- a/srcpkgs/recutils/template
+++ b/srcpkgs/recutils/template
@@ -1,10 +1,11 @@
 # Template file for 'recutils'
 pkgname=recutils
 version=1.8
-revision=1
+revision=2
 build_style=gnu-configure
 configure_args="--with-bash-headers --disable-rpath"
-hostmakedepends="pkg-config"
+# TODO remove automake in 1.9; only needed for copy-file
+hostmakedepends="automake pkg-config"
 makedepends="acl-devel libgcrypt-devel libuuid-devel libcurl-devel"
 short_desc="Utilities to deal with recfiles"
 maintainer="Orphaned <orphan@voidlinux.org>"
@@ -12,7 +13,19 @@ license="GPL-3.0-or-later"
 homepage="https://www.gnu.org/software/recutils/"
 distfiles="${GNU_SITE}/recutils/recutils-${version}.tar.gz"
 checksum=df8eae69593fdba53e264cbf4b2307dfb82120c09b6fab23e2dad51a89a5b193
+patch_args=-Np1
 
+pre_configure() {
+	if test x"$version.$revision" != x1.8.2 ; then
+		echo hopefully you can remove the automake dependency
+		echo and the pre_configure kludge
+		exit 1
+	fi
+	# XXX horrible kludge to avoid help2man - remove in 1.9
+	touch man/*.1
+	# XXX ONLY needed for copy-file - remove in 1.9
+	aclocal && automake
+}
 librec1_package() {
 	short_desc+=" - rec1 library"
 	pkg_install() {

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PR REVIEW] recutils: patch to fix recins not working outside of $TMPDIR
  2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
  2021-04-28 21:38 ` [PR REVIEW] " ericonr
  2021-04-28 21:46 ` [PR PATCH] [Updated] " loreb
@ 2021-04-28 21:48 ` loreb
  2021-04-28 21:52 ` ericonr
                   ` (19 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: loreb @ 2021-04-28 21:48 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 178 bytes --]

New review comment by loreb on void-packages repository

https://github.com/void-linux/void-packages/pull/30567#discussion_r622588290

Comment:
Done and added the version check.

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PR REVIEW] recutils: patch to fix recins not working outside of $TMPDIR
  2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
                   ` (2 preceding siblings ...)
  2021-04-28 21:48 ` [PR REVIEW] " loreb
@ 2021-04-28 21:52 ` ericonr
  2021-04-28 21:55 ` loreb
                   ` (18 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: ericonr @ 2021-04-28 21:52 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 206 bytes --]

New review comment by ericonr on void-packages repository

https://github.com/void-linux/void-packages/pull/30567#discussion_r622590485

Comment:
I don't this is necessary, you should trust the comments...

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PR REVIEW] recutils: patch to fix recins not working outside of $TMPDIR
  2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
                   ` (3 preceding siblings ...)
  2021-04-28 21:52 ` ericonr
@ 2021-04-28 21:55 ` loreb
  2021-04-28 21:58 ` loreb
                   ` (17 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: loreb @ 2021-04-28 21:55 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 587 bytes --]

New review comment by loreb on void-packages repository

https://github.com/void-linux/void-packages/pull/30567#discussion_r622592173

Comment:
About the size of the patch, I was thinking that you could, more or less, clone the gnulib repo, then check for every file that is quoted in the patch that it's there semi-automatically, something like
```
$ fgrep -C3 "$(cat somefile.c | sed 's/^/+/g)" huge.patch
```

The "-C3" is to see that the patch begins and ends with the files; what is left is a few lines and most of them are trivial,
like adding the new files to the makefile.

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PR REVIEW] recutils: patch to fix recins not working outside of $TMPDIR
  2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
                   ` (4 preceding siblings ...)
  2021-04-28 21:55 ` loreb
@ 2021-04-28 21:58 ` loreb
  2021-04-28 21:58 ` [PR PATCH] [Updated] " loreb
                   ` (16 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: loreb @ 2021-04-28 21:58 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 494 bytes --]

New review comment by loreb on void-packages repository

https://github.com/void-linux/void-packages/pull/30567#discussion_r622593820

Comment:
Ok, it was just en enforceable reminder.
Btw, I just realized something - I did the "touch man/*.1" kludge because `xbps-query -Rvs help2man` showed nothing,
but I'm on my debian computer, and even `xbps-query -Rvs vim` shows nothing.
That being said, the manpages *should* stay unchanged, as this is just a bugfix with zero functionality change.

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PR PATCH] [Updated] recutils: patch to fix recins not working outside of $TMPDIR
  2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
                   ` (5 preceding siblings ...)
  2021-04-28 21:58 ` loreb
@ 2021-04-28 21:58 ` loreb
  2021-05-17 13:12 ` loreb
                   ` (15 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: loreb @ 2021-04-28 21:58 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 2213 bytes --]

There is an updated pull request by loreb against master on the void-packages repository

https://github.com/loreb/void-packages master
https://github.com/void-linux/void-packages/pull/30567

recutils: patch to fix recins not working outside of $TMPDIR
Details at https://git.savannah.gnu.org/cgit/recutils.git/commit/utils/recutl.c?id=86f662a8202408134a235572ec60141d3082f975
Will no longer be necessary once 1.9 is released; fixes bug #30546
Thanks to ericonr for all the assistance.

note to self: bootstrap.conf is not shipped
add *temporary* dependency on automake
adding gnulib pieces is a real chore...

And then the GNU tools (rightfully) decided that the manpages were out
of date, because a file has been modified, and insisted on running
help2man - I simply told pre_configure to update the timestamps of the
manpages.

<!-- Mark items with [x] where applicable -->

#### General
- [ ] This is a new package and it conforms to the [quality requirements](https://github.com/void-linux/void-packages/blob/master/Manual.md#quality-requirements)

#### Have the results of the proposed changes been tested?
- [ ] I use the packages affected by the proposed changes on a regular basis and confirm this PR works for me
- [ ] I generally don't use the affected packages but briefly tested this PR

<!--
If GitHub CI cannot be used to validate the build result (for example, if the
build is likely to take several hours), make sure to
[skip CI](https://github.com/void-linux/void-packages/blob/master/CONTRIBUTING.md#continuous-integration).
When skipping CI, uncomment and fill out the following section.
Note: for builds that are likely to complete in less than 2 hours, it is not
acceptable to skip CI.
-->
<!-- 
#### Does it build and run successfully? 
(Please choose at least one native build and, if supported, at least one cross build. More are better.)
- [ ] I built this PR locally for my native architecture, (ARCH-LIBC)
- [ ] I built this PR locally for these architectures (if supported. mark crossbuilds):
  - [ ] aarch64-musl
  - [ ] armv7l
  - [ ] armv6l-musl
-->


A patch file from https://github.com/void-linux/void-packages/pull/30567.patch is attached

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: github-pr-master-30567.patch --]
[-- Type: text/x-diff, Size: 69213 bytes --]

From a9d59d8d5c16001da29a8b06b4566d1d4a31a4b6 Mon Sep 17 00:00:00 2001
From: Lorenzo Beretta <vc.net.loreb@gmail.com>
Date: Wed, 28 Apr 2021 14:20:53 +0200
Subject: [PATCH] recutils: patch to fix recins not working outside of $TMPDIR

Details at https://git.savannah.gnu.org/cgit/recutils.git/commit/utils/recutl.c?id=86f662a8202408134a235572ec60141d3082f975
Will no longer be necessary once 1.9 is released; fixes bug #30546
Thanks to ericonr for all the assistance.

note to self: bootstrap.conf is not shipped
add *temporary* dependency on automake
adding gnulib pieces is a real chore...

And then the GNU tools (rightfully) decided that the manpages were out
of date, because a file has been modified, and insisted on running
help2man - I simply told pre_configure to update the timestamps of the
manpages.
---
 srcpkgs/recutils/patches/copyfile.patch | 1929 +++++++++++++++++++++++
 srcpkgs/recutils/patches/exdev.patch    |   53 +
 srcpkgs/recutils/template               |   12 +-
 3 files changed, 1992 insertions(+), 2 deletions(-)
 create mode 100644 srcpkgs/recutils/patches/copyfile.patch
 create mode 100644 srcpkgs/recutils/patches/exdev.patch

diff --git a/srcpkgs/recutils/patches/copyfile.patch b/srcpkgs/recutils/patches/copyfile.patch
new file mode 100644
index 000000000000..c2f289f6f41d
--- /dev/null
+++ b/srcpkgs/recutils/patches/copyfile.patch
@@ -0,0 +1,1929 @@
+diff --git a/lib/copy-file.c b/lib/copy-file.c
+new file mode 100644
+index 0000000..15e17ae
+--- /dev/null
++++ b/lib/copy-file.c
+@@ -0,0 +1,221 @@
++/* Copying of files.
++   Copyright (C) 2001-2003, 2006-2007, 2009-2021 Free Software Foundation, Inc.
++   Written by Bruno Haible <haible@clisp.cons.org>, 2001.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++
++#include <config.h>
++
++/* Specification.  */
++#include "copy-file.h"
++
++#include <errno.h>
++#include <fcntl.h>
++#include <stddef.h>
++#include <stdlib.h>
++#include <sys/stat.h>
++#include <unistd.h>
++
++#include "error.h"
++#include "ignore-value.h"
++#include "safe-read.h"
++#include "full-write.h"
++#include "stat-time.h"
++#include "utimens.h"
++#include "acl.h"
++#include "binary-io.h"
++#include "quote.h"
++#include "gettext.h"
++
++#define _(str) gettext (str)
++
++enum { IO_SIZE = 32 * 1024 };
++
++int
++qcopy_file_preserving (const char *src_filename, const char *dest_filename)
++{
++  int err = 0;
++  int src_fd;
++  struct stat statbuf;
++  int mode;
++  int dest_fd;
++
++  src_fd = open (src_filename, O_RDONLY | O_BINARY | O_CLOEXEC);
++  if (src_fd < 0)
++    return GL_COPY_ERR_OPEN_READ;
++  if (fstat (src_fd, &statbuf) < 0)
++    {
++      err = GL_COPY_ERR_OPEN_READ;
++      goto error_src;
++    }
++
++  mode = statbuf.st_mode & 07777;
++  off_t inbytes = S_ISREG (statbuf.st_mode) ? statbuf.st_size : -1;
++  bool empty_regular_file = inbytes == 0;
++
++  dest_fd = open (dest_filename,
++                  O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_CLOEXEC,
++                  0600);
++  if (dest_fd < 0)
++    {
++      err = GL_COPY_ERR_OPEN_BACKUP_WRITE;
++      goto error_src;
++    }
++
++  /* Copy the file contents.  FIXME: Do not copy holes.  */
++  while (0 < inbytes)
++    {
++      size_t copy_max = -1;
++      copy_max -= copy_max % IO_SIZE;
++      size_t len = inbytes < copy_max ? inbytes : copy_max;
++      ssize_t copied = copy_file_range (src_fd, NULL, dest_fd, NULL, len, 0);
++      if (copied <= 0)
++        break;
++      inbytes -= copied;
++    }
++
++  /* Finish up with read/write, in case the file was not a regular
++     file, or the file shrank or had I/O errors (in which case find
++     whether it was a read or write error).  Read empty regular files
++     since they might be in /proc with their true sizes unknown until
++     they are read.  */
++  if (inbytes != 0 || empty_regular_file)
++    {
++      char smallbuf[1024];
++      int bufsize = IO_SIZE;
++      char *buf = malloc (bufsize);
++      if (!buf)
++        buf = smallbuf, bufsize = sizeof smallbuf;
++
++      while (true)
++        {
++          size_t n_read = safe_read (src_fd, buf, bufsize);
++          if (n_read == 0)
++            break;
++          if (n_read == SAFE_READ_ERROR)
++            {
++              err = GL_COPY_ERR_READ;
++              break;
++            }
++          if (full_write (dest_fd, buf, n_read) < n_read)
++            {
++              err = GL_COPY_ERR_WRITE;
++              break;
++            }
++        }
++
++      if (buf != smallbuf)
++        free (buf);
++      if (err)
++        goto error_src_dest;
++    }
++
++#if !USE_ACL
++  if (close (dest_fd) < 0)
++    {
++      err = GL_COPY_ERR_WRITE;
++      goto error_src;
++    }
++  if (close (src_fd) < 0)
++    return GL_COPY_ERR_AFTER_READ;
++#endif
++
++  /* Preserve the access and modification times.  */
++  {
++    struct timespec ts[2];
++
++    ts[0] = get_stat_atime (&statbuf);
++    ts[1] = get_stat_mtime (&statbuf);
++    utimens (dest_filename, ts);
++  }
++
++#if HAVE_CHOWN
++  /* Preserve the owner and group.  */
++  ignore_value (chown (dest_filename, statbuf.st_uid, statbuf.st_gid));
++#endif
++
++  /* Preserve the access permissions.  */
++#if USE_ACL
++  switch (qcopy_acl (src_filename, src_fd, dest_filename, dest_fd, mode))
++    {
++    case -2:
++      err = GL_COPY_ERR_GET_ACL;
++      goto error_src_dest;
++    case -1:
++      err = GL_COPY_ERR_SET_ACL;
++      goto error_src_dest;
++    }
++#else
++  chmod (dest_filename, mode);
++#endif
++
++#if USE_ACL
++  if (close (dest_fd) < 0)
++    {
++      err = GL_COPY_ERR_WRITE;
++      goto error_src;
++    }
++  if (close (src_fd) < 0)
++    return GL_COPY_ERR_AFTER_READ;
++#endif
++
++  return 0;
++
++ error_src_dest:
++  close (dest_fd);
++ error_src:
++  close (src_fd);
++  return err;
++}
++
++void
++copy_file_preserving (const char *src_filename, const char *dest_filename)
++{
++  switch (qcopy_file_preserving (src_filename, dest_filename))
++    {
++    case 0:
++      return;
++
++    case GL_COPY_ERR_OPEN_READ:
++      error (EXIT_FAILURE, errno, _("error while opening %s for reading"),
++             quote (src_filename));
++
++    case GL_COPY_ERR_OPEN_BACKUP_WRITE:
++      error (EXIT_FAILURE, errno, _("cannot open backup file %s for writing"),
++             quote (dest_filename));
++
++    case GL_COPY_ERR_READ:
++      error (EXIT_FAILURE, errno, _("error reading %s"),
++             quote (src_filename));
++
++    case GL_COPY_ERR_WRITE:
++      error (EXIT_FAILURE, errno, _("error writing %s"),
++             quote (dest_filename));
++
++    case GL_COPY_ERR_AFTER_READ:
++      error (EXIT_FAILURE, errno, _("error after reading %s"),
++             quote (src_filename));
++
++    case GL_COPY_ERR_GET_ACL:
++      error (EXIT_FAILURE, errno, "%s", quote (src_filename));
++
++    case GL_COPY_ERR_SET_ACL:
++      error (EXIT_FAILURE, errno, _("preserving permissions for %s"),
++             quote (dest_filename));
++
++    default:
++      abort ();
++    }
++}
+diff --git a/lib/copy-file.h b/lib/copy-file.h
+new file mode 100644
+index 0000000..09281d6
+--- /dev/null
++++ b/lib/copy-file.h
+@@ -0,0 +1,54 @@
++/* Copying of files.
++   Copyright (C) 2001-2003, 2009-2021 Free Software Foundation, Inc.
++   Written by Bruno Haible <haible@clisp.cons.org>, 2001.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++
++/* Error codes returned by qcopy_file_preserving.  */
++enum
++{
++  GL_COPY_ERR_OPEN_READ = -1,
++  GL_COPY_ERR_OPEN_BACKUP_WRITE = -2,
++  GL_COPY_ERR_READ = -3,
++  GL_COPY_ERR_WRITE = -4,
++  GL_COPY_ERR_AFTER_READ = -5,
++  GL_COPY_ERR_GET_ACL = -6,
++  GL_COPY_ERR_SET_ACL = -7
++};
++
++/* Copy a regular file: from src_filename to dest_filename.
++   The destination file is assumed to be a backup file.
++   Modification times, owner, group and access permissions are preserved as
++   far as possible.
++   Return 0 if successful, otherwise set errno and return one of the error
++   codes above.  */
++extern int qcopy_file_preserving (const char *src_filename, const char *dest_filename);
++
++/* Copy a regular file: from src_filename to dest_filename.
++   The destination file is assumed to be a backup file.
++   Modification times, owner, group and access permissions are preserved as
++   far as possible.
++   Exit upon failure.  */
++extern void copy_file_preserving (const char *src_filename, const char *dest_filename);
++
++
++#ifdef __cplusplus
++}
++#endif
+diff --git a/m4/copy-file.m4 b/m4/copy-file.m4
+new file mode 100644
+index 0000000..6211171
+--- /dev/null
++++ b/m4/copy-file.m4
+@@ -0,0 +1,11 @@
++# copy-file.m4 serial 5
++dnl Copyright (C) 2003, 2009-2021 Free Software Foundation, Inc.
++dnl This file is free software; the Free Software Foundation
++dnl gives unlimited permission to copy and/or distribute it,
++dnl with or without modifications, as long as this notice is preserved.
++
++AC_DEFUN([gl_COPY_FILE],
++[
++  AC_CHECK_HEADERS_ONCE([unistd.h])
++  AC_CHECK_FUNCS([chown])
++])
+
+diff --git a/lib/Makefile.am b/lib/Makefile.am
+index a6ec30b..f363b86 100644
+--- a/lib/Makefile.am
++++ b/lib/Makefile.am
+@@ -219,6 +225,12 @@
+ 
+ ## end   gnulib module base64
+ 
++## begin gnulib module binary-io
++
++librecutils_la_SOURCES += binary-io.h binary-io.c
++
++## end   gnulib module binary-io
++
+ ## begin gnulib module btowc
+ 
+ 
+@@ -289,6 +301,21 @@
+ 
+ ## end   gnulib module closeout
+ 
++## begin gnulib module copy-file
++
++librecutils_la_SOURCES += copy-file.h copy-file.c
++
++## end   gnulib module copy-file
++
++## begin gnulib module copy-file-range
++
++
++EXTRA_DIST += copy-file-range.c
++
++EXTRA_librecutils_la_SOURCES += copy-file-range.c
++
++## end   gnulib module copy-file-range
++
+ ## begin gnulib module crc
+ 
+ librecutils_la_SOURCES += crc.c
+@@ -605,6 +634,12 @@
+ 
+ ## end   gnulib module ftello
+ 
++## begin gnulib module full-write
++
++librecutils_la_SOURCES += full-write.h full-write.c
++
++## end   gnulib module full-write
++
+ ## begin gnulib module fwriting
+ 
+ 
+@@ -778,6 +813,13 @@
+ 
+ ## end   gnulib module havelib
+ 
++## begin gnulib module ignore-value
++
++
++EXTRA_DIST += ignore-value.h
++
++## end   gnulib module ignore-value
++
+ ## begin gnulib module intprops
+ 
+ 
+@@ -1761,6 +1827,32 @@
+ 
+ ## end   gnulib module root-uid
+ 
++## begin gnulib module safe-read
++
++librecutils_la_SOURCES += safe-read.c
++
++EXTRA_DIST += safe-read.h sys-limits.h
++
++## end   gnulib module safe-read
++
++## begin gnulib module safe-write
++
++librecutils_la_SOURCES += safe-write.c
++
++EXTRA_DIST += safe-read.c safe-write.h sys-limits.h
++
++EXTRA_librecutils_la_SOURCES += safe-read.c
++
++## end   gnulib module safe-write
++
++## begin gnulib module utimens
++
++librecutils_la_SOURCES += utimens.c
++
++EXTRA_DIST += utimens.h
++
++## end   gnulib module utimens
++
+ ## begin gnulib module same-inode
+ 
+ 
+diff --git a/lib/ignore-value.h b/lib/ignore-value.h
+new file mode 100644
+index 0000000..0a3cf1e
+--- /dev/null
++++ b/lib/ignore-value.h
+@@ -0,0 +1,51 @@
++/* ignore a function return without a compiler warning.  -*- coding: utf-8 -*-
++
++   Copyright (C) 2008-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++/* Written by Jim Meyering, Eric Blake and Pádraig Brady.  */
++
++/* Use "ignore_value" to avoid a warning when using a function declared with
++   gcc's warn_unused_result attribute, but for which you really do want to
++   ignore the result.  Traditionally, people have used a "(void)" cast to
++   indicate that a function's return value is deliberately unused.  However,
++   if the function is declared with __attribute__((warn_unused_result)),
++   gcc issues a warning even with the cast.
++
++   Caution: most of the time, you really should heed gcc's warning, and
++   check the return value.  However, in those exceptional cases in which
++   you're sure you know what you're doing, use this function.
++
++   For the record, here's one of the ignorable warnings:
++   "copy.c:233: warning: ignoring return value of 'fchown',
++   declared with attribute warn_unused_result".  */
++
++#ifndef _GL_IGNORE_VALUE_H
++#define _GL_IGNORE_VALUE_H
++
++/* Normally casting an expression to void discards its value, but GCC
++   versions 3.4 and newer have __attribute__ ((__warn_unused_result__))
++   which may cause unwanted diagnostics in that case.  Use __typeof__
++   and __extension__ to work around the problem, if the workaround is
++   known to be needed.
++   The workaround is not needed with clang.  */
++#if (3 < __GNUC__ + (4 <= __GNUC_MINOR__)) && !defined __clang__
++# define ignore_value(x) \
++    (__extension__ ({ __typeof__ (x) __x = (x); (void) __x; }))
++#else
++# define ignore_value(x) ((void) (x))
++#endif
++
++#endif
+diff --git a/lib/safe-read.c b/lib/safe-read.c
+new file mode 100644
+index 0000000..be9c33c
+--- /dev/null
++++ b/lib/safe-read.c
+@@ -0,0 +1,71 @@
++/* An interface to read and write that retries after interrupts.
++
++   Copyright (C) 1993-1994, 1998, 2002-2006, 2009-2021 Free Software
++   Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++#include <config.h>
++
++/* Specification.  */
++#ifdef SAFE_WRITE
++# include "safe-write.h"
++#else
++# include "safe-read.h"
++#endif
++
++/* Get ssize_t.  */
++#include <sys/types.h>
++#include <unistd.h>
++
++#include <errno.h>
++
++#ifdef EINTR
++# define IS_EINTR(x) ((x) == EINTR)
++#else
++# define IS_EINTR(x) 0
++#endif
++
++#include "sys-limits.h"
++
++#ifdef SAFE_WRITE
++# define safe_rw safe_write
++# define rw write
++#else
++# define safe_rw safe_read
++# define rw read
++# undef const
++# define const /* empty */
++#endif
++
++/* Read(write) up to COUNT bytes at BUF from(to) descriptor FD, retrying if
++   interrupted.  Return the actual number of bytes read(written), zero for EOF,
++   or SAFE_READ_ERROR(SAFE_WRITE_ERROR) upon error.  */
++size_t
++safe_rw (int fd, void const *buf, size_t count)
++{
++  for (;;)
++    {
++      ssize_t result = rw (fd, buf, count);
++
++      if (0 <= result)
++        return result;
++      else if (IS_EINTR (errno))
++        continue;
++      else if (errno == EINVAL && SYS_BUFSIZE_MAX < count)
++        count = SYS_BUFSIZE_MAX;
++      else
++        return result;
++    }
++}
+diff --git a/lib/safe-read.h b/lib/safe-read.h
+new file mode 100644
+index 0000000..5482906
+--- /dev/null
++++ b/lib/safe-read.h
+@@ -0,0 +1,47 @@
++/* An interface to read() that retries after interrupts.
++   Copyright (C) 2002, 2006, 2009-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++/* Some system calls may be interrupted and fail with errno = EINTR in the
++   following situations:
++     - The process is stopped and restarted (signal SIGSTOP and SIGCONT, user
++       types Ctrl-Z) on some platforms: Mac OS X.
++     - The process receives a signal for which a signal handler was installed
++       with sigaction() with an sa_flags field that does not contain
++       SA_RESTART.
++     - The process receives a signal for which a signal handler was installed
++       with signal() and for which no call to siginterrupt(sig,0) was done,
++       on some platforms: AIX, HP-UX, IRIX, OSF/1, Solaris.
++
++   This module provides a wrapper around read() that handles EINTR.  */
++
++#include <stddef.h>
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++
++#define SAFE_READ_ERROR ((size_t) -1)
++
++/* Read up to COUNT bytes at BUF from descriptor FD, retrying if interrupted.
++   Return the actual number of bytes read, zero for EOF, or SAFE_READ_ERROR
++   upon error.  */
++extern size_t safe_read (int fd, void *buf, size_t count);
++
++
++#ifdef __cplusplus
++}
++#endif
+diff --git a/lib/sys-limits.h b/lib/sys-limits.h
+new file mode 100644
+index 0000000..cd05a81
+--- /dev/null
++++ b/lib/sys-limits.h
+@@ -0,0 +1,42 @@
++/* System call limits
++
++   Copyright 2018-2021 Free Software Foundation, Inc.
++
++   This program is free software; you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 2, or (at your option)
++   any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program; if not, see <https://www.gnu.org/licenses/>.  */
++
++#ifndef _GL_SYS_LIMITS_H
++#define _GL_SYS_LIMITS_H
++
++#include <limits.h>
++
++/* Maximum number of bytes to read or write in a single system call.
++   This can be useful for system calls like sendfile on GNU/Linux,
++   which do not handle more than MAX_RW_COUNT bytes correctly.
++   The Linux kernel MAX_RW_COUNT is at least INT_MAX >> 20 << 20,
++   where the 20 comes from the Hexagon port with 1 MiB pages; use that
++   as an approximation, as the exact value may not be available to us.
++
++   Using this also works around a serious Linux bug before 2.6.16; see
++   <https://bugzilla.redhat.com/show_bug.cgi?id=612839>.
++
++   Using this also works around a Tru64 5.1 bug, where attempting
++   to read INT_MAX bytes fails with errno == EINVAL.  See
++   <https://lists.gnu.org/r/bug-gnu-utils/2002-04/msg00010.html>.
++
++   Using this is likely to work around similar bugs in other operating
++   systems.  */
++
++enum { SYS_BUFSIZE_MAX = INT_MAX >> 20 << 20 };
++
++#endif
+diff --git a/m4/safe-read.m4 b/m4/safe-read.m4
+new file mode 100644
+index 0000000..3cba288
+--- /dev/null
++++ b/m4/safe-read.m4
+@@ -0,0 +1,12 @@
++# safe-read.m4 serial 6
++dnl Copyright (C) 2002-2003, 2005-2006, 2009-2021 Free Software Foundation,
++dnl Inc.
++dnl This file is free software; the Free Software Foundation
++dnl gives unlimited permission to copy and/or distribute it,
++dnl with or without modifications, as long as this notice is preserved.
++
++# Prerequisites of lib/safe-read.c.
++AC_DEFUN([gl_PREREQ_SAFE_READ],
++[
++  AC_REQUIRE([gt_TYPE_SSIZE_T])
++])
+diff --git a/lib/full-write.c b/lib/full-write.c
+new file mode 100644
+index 0000000..2c0e06b
+--- /dev/null
++++ b/lib/full-write.c
+@@ -0,0 +1,79 @@
++/* An interface to read and write that retries (if necessary) until complete.
++
++   Copyright (C) 1993-1994, 1997-2006, 2009-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++#include <config.h>
++
++/* Specification.  */
++#ifdef FULL_READ
++# include "full-read.h"
++#else
++# include "full-write.h"
++#endif
++
++#include <errno.h>
++
++#ifdef FULL_READ
++# include "safe-read.h"
++# define safe_rw safe_read
++# define full_rw full_read
++# undef const
++# define const /* empty */
++#else
++# include "safe-write.h"
++# define safe_rw safe_write
++# define full_rw full_write
++#endif
++
++#ifdef FULL_READ
++/* Set errno to zero upon EOF.  */
++# define ZERO_BYTE_TRANSFER_ERRNO 0
++#else
++/* Some buggy drivers return 0 when one tries to write beyond
++   a device's end.  (Example: Linux 1.2.13 on /dev/fd0.)
++   Set errno to ENOSPC so they get a sensible diagnostic.  */
++# define ZERO_BYTE_TRANSFER_ERRNO ENOSPC
++#endif
++
++/* Write(read) COUNT bytes at BUF to(from) descriptor FD, retrying if
++   interrupted or if a partial write(read) occurs.  Return the number
++   of bytes transferred.
++   When writing, set errno if fewer than COUNT bytes are written.
++   When reading, if fewer than COUNT bytes are read, you must examine
++   errno to distinguish failure from EOF (errno == 0).  */
++size_t
++full_rw (int fd, const void *buf, size_t count)
++{
++  size_t total = 0;
++  const char *ptr = (const char *) buf;
++
++  while (count > 0)
++    {
++      size_t n_rw = safe_rw (fd, ptr, count);
++      if (n_rw == (size_t) -1)
++        break;
++      if (n_rw == 0)
++        {
++          errno = ZERO_BYTE_TRANSFER_ERRNO;
++          break;
++        }
++      total += n_rw;
++      ptr += n_rw;
++      count -= n_rw;
++    }
++
++  return total;
++}
+diff --git a/lib/full-write.h b/lib/full-write.h
+new file mode 100644
+index 0000000..6c4e28a
+--- /dev/null
++++ b/lib/full-write.h
+@@ -0,0 +1,34 @@
++/* An interface to write() that writes all it is asked to write.
++
++   Copyright (C) 2002-2003, 2009-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++#include <stddef.h>
++
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++
++/* Write COUNT bytes at BUF to descriptor FD, retrying if interrupted
++   or if partial writes occur.  Return the number of bytes successfully
++   written, setting errno if that is less than COUNT.  */
++extern size_t full_write (int fd, const void *buf, size_t count);
++
++
++#ifdef __cplusplus
++}
++#endif
+diff --git a/lib/utimens.c b/lib/utimens.c
+new file mode 100644
+index 0000000..06d1aa3
+--- /dev/null
++++ b/lib/utimens.c
+@@ -0,0 +1,647 @@
++/* Set file access and modification times.
++
++   Copyright (C) 2003-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify it
++   under the terms of the GNU General Public License as published by the
++   Free Software Foundation; either version 3 of the License, or any
++   later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++/* Written by Paul Eggert.  */
++
++/* derived from a function in touch.c */
++
++#include <config.h>
++
++#define _GL_UTIMENS_INLINE _GL_EXTERN_INLINE
++#include "utimens.h"
++
++#include <errno.h>
++#include <fcntl.h>
++#include <stdbool.h>
++#include <string.h>
++#include <sys/stat.h>
++#include <sys/time.h>
++#include <unistd.h>
++#include <utime.h>
++
++#include "stat-time.h"
++#include "timespec.h"
++
++/* On native Windows, use SetFileTime; but avoid this when compiling
++   GNU Emacs, which arranges for this in some other way and which
++   defines WIN32_LEAN_AND_MEAN itself.  */
++
++#if defined _WIN32 && ! defined __CYGWIN__ && ! defined EMACS_CONFIGURATION
++# define USE_SETFILETIME
++# define WIN32_LEAN_AND_MEAN
++# include <windows.h>
++# if GNULIB_MSVC_NOTHROW
++#  include "msvc-nothrow.h"
++# else
++#  include <io.h>
++# endif
++#endif
++
++/* Avoid recursion with rpl_futimens or rpl_utimensat.  */
++#undef futimens
++#if !HAVE_NEARLY_WORKING_UTIMENSAT
++# undef utimensat
++#endif
++
++/* Solaris 9 mistakenly succeeds when given a non-directory with a
++   trailing slash.  Force the use of rpl_stat for a fix.  */
++#ifndef REPLACE_FUNC_STAT_FILE
++# define REPLACE_FUNC_STAT_FILE 0
++#endif
++
++#if HAVE_UTIMENSAT || HAVE_FUTIMENS
++/* Cache variables for whether the utimensat syscall works; used to
++   avoid calling the syscall if we know it will just fail with ENOSYS,
++   and to avoid unnecessary work in massaging timestamps if the
++   syscall will work.  Multiple variables are needed, to distinguish
++   between the following scenarios on Linux:
++   utimensat doesn't exist, or is in glibc but kernel 2.6.18 fails with ENOSYS
++   kernel 2.6.22 and earlier rejects AT_SYMLINK_NOFOLLOW
++   kernel 2.6.25 and earlier reject UTIME_NOW/UTIME_OMIT with non-zero tv_sec
++   kernel 2.6.32 used with xfs or ntfs-3g fail to honor UTIME_OMIT
++   utimensat completely works
++   For each cache variable: 0 = unknown, 1 = yes, -1 = no.  */
++static int utimensat_works_really;
++static int lutimensat_works_really;
++#endif /* HAVE_UTIMENSAT || HAVE_FUTIMENS */
++
++/* Validate the requested timestamps.  Return 0 if the resulting
++   timespec can be used for utimensat (after possibly modifying it to
++   work around bugs in utimensat).  Return a positive value if the
++   timespec needs further adjustment based on stat results: 1 if any
++   adjustment is needed for utimes, and 2 if any adjustment is needed
++   for Linux utimensat.  Return -1, with errno set to EINVAL, if
++   timespec is out of range.  */
++static int
++validate_timespec (struct timespec timespec[2])
++{
++  int result = 0;
++  int utime_omit_count = 0;
++  if ((timespec[0].tv_nsec != UTIME_NOW
++       && timespec[0].tv_nsec != UTIME_OMIT
++       && ! (0 <= timespec[0].tv_nsec
++             && timespec[0].tv_nsec < TIMESPEC_HZ))
++      || (timespec[1].tv_nsec != UTIME_NOW
++          && timespec[1].tv_nsec != UTIME_OMIT
++          && ! (0 <= timespec[1].tv_nsec
++                && timespec[1].tv_nsec < TIMESPEC_HZ)))
++    {
++      errno = EINVAL;
++      return -1;
++    }
++  /* Work around Linux kernel 2.6.25 bug, where utimensat fails with
++     EINVAL if tv_sec is not 0 when using the flag values of tv_nsec.
++     Flag a Linux kernel 2.6.32 bug, where an mtime of UTIME_OMIT
++     fails to bump ctime.  */
++  if (timespec[0].tv_nsec == UTIME_NOW
++      || timespec[0].tv_nsec == UTIME_OMIT)
++    {
++      timespec[0].tv_sec = 0;
++      result = 1;
++      if (timespec[0].tv_nsec == UTIME_OMIT)
++        utime_omit_count++;
++    }
++  if (timespec[1].tv_nsec == UTIME_NOW
++      || timespec[1].tv_nsec == UTIME_OMIT)
++    {
++      timespec[1].tv_sec = 0;
++      result = 1;
++      if (timespec[1].tv_nsec == UTIME_OMIT)
++        utime_omit_count++;
++    }
++  return result + (utime_omit_count == 1);
++}
++
++/* Normalize any UTIME_NOW or UTIME_OMIT values in (*TS)[0] and (*TS)[1],
++   using STATBUF to obtain the current timestamps of the file.  If
++   both times are UTIME_NOW, set *TS to NULL (as this can avoid some
++   permissions issues).  If both times are UTIME_OMIT, return true
++   (nothing further beyond the prior collection of STATBUF is
++   necessary); otherwise return false.  */
++static bool
++update_timespec (struct stat const *statbuf, struct timespec **ts)
++{
++  struct timespec *timespec = *ts;
++  if (timespec[0].tv_nsec == UTIME_OMIT
++      && timespec[1].tv_nsec == UTIME_OMIT)
++    return true;
++  if (timespec[0].tv_nsec == UTIME_NOW
++      && timespec[1].tv_nsec == UTIME_NOW)
++    {
++      *ts = NULL;
++      return false;
++    }
++
++  if (timespec[0].tv_nsec == UTIME_OMIT)
++    timespec[0] = get_stat_atime (statbuf);
++  else if (timespec[0].tv_nsec == UTIME_NOW)
++    gettime (&timespec[0]);
++
++  if (timespec[1].tv_nsec == UTIME_OMIT)
++    timespec[1] = get_stat_mtime (statbuf);
++  else if (timespec[1].tv_nsec == UTIME_NOW)
++    gettime (&timespec[1]);
++
++  return false;
++}
++
++/* Set the access and modification timestamps of FD (a.k.a. FILE) to be
++   TIMESPEC[0] and TIMESPEC[1], respectively.
++   FD must be either negative -- in which case it is ignored --
++   or a file descriptor that is open on FILE.
++   If FD is nonnegative, then FILE can be NULL, which means
++   use just futimes (or equivalent) instead of utimes (or equivalent),
++   and fail if on an old system without futimes (or equivalent).
++   If TIMESPEC is null, set the timestamps to the current time.
++   Return 0 on success, -1 (setting errno) on failure.  */
++
++int
++fdutimens (int fd, char const *file, struct timespec const timespec[2])
++{
++  struct timespec adjusted_timespec[2];
++  struct timespec *ts = timespec ? adjusted_timespec : NULL;
++  int adjustment_needed = 0;
++  struct stat st;
++
++  if (ts)
++    {
++      adjusted_timespec[0] = timespec[0];
++      adjusted_timespec[1] = timespec[1];
++      adjustment_needed = validate_timespec (ts);
++    }
++  if (adjustment_needed < 0)
++    return -1;
++
++  /* Require that at least one of FD or FILE are potentially valid, to avoid
++     a Linux bug where futimens (AT_FDCWD, NULL) changes "." rather
++     than failing.  */
++  if (fd < 0 && !file)
++    {
++      errno = EBADF;
++      return -1;
++    }
++
++  /* Some Linux-based NFS clients are buggy, and mishandle timestamps
++     of files in NFS file systems in some cases.  We have no
++     configure-time test for this, but please see
++     <https://bugs.gentoo.org/show_bug.cgi?id=132673> for references to
++     some of the problems with Linux 2.6.16.  If this affects you,
++     compile with -DHAVE_BUGGY_NFS_TIME_STAMPS; this is reported to
++     help in some cases, albeit at a cost in performance.  But you
++     really should upgrade your kernel to a fixed version, since the
++     problem affects many applications.  */
++
++#if HAVE_BUGGY_NFS_TIME_STAMPS
++  if (fd < 0)
++    sync ();
++  else
++    fsync (fd);
++#endif
++
++  /* POSIX 2008 added two interfaces to set file timestamps with
++     nanosecond resolution; newer Linux implements both functions via
++     a single syscall.  We provide a fallback for ENOSYS (for example,
++     compiling against Linux 2.6.25 kernel headers and glibc 2.7, but
++     running on Linux 2.6.18 kernel).  */
++#if HAVE_UTIMENSAT || HAVE_FUTIMENS
++  if (0 <= utimensat_works_really)
++    {
++      int result;
++# if __linux__ || __sun
++      /* As recently as Linux kernel 2.6.32 (Dec 2009), several file
++         systems (xfs, ntfs-3g) have bugs with a single UTIME_OMIT,
++         but work if both times are either explicitly specified or
++         UTIME_NOW.  Work around it with a preparatory [f]stat prior
++         to calling futimens/utimensat; fortunately, there is not much
++         timing impact due to the extra syscall even on file systems
++         where UTIME_OMIT would have worked.
++
++         The same bug occurs in Solaris 11.1 (Apr 2013).
++
++         FIXME: Simplify this for Linux in 2016 and for Solaris in
++         2024, when file system bugs are no longer common.  */
++      if (adjustment_needed == 2)
++        {
++          if (fd < 0 ? stat (file, &st) : fstat (fd, &st))
++            return -1;
++          if (ts[0].tv_nsec == UTIME_OMIT)
++            ts[0] = get_stat_atime (&st);
++          else if (ts[1].tv_nsec == UTIME_OMIT)
++            ts[1] = get_stat_mtime (&st);
++          /* Note that st is good, in case utimensat gives ENOSYS.  */
++          adjustment_needed++;
++        }
++# endif
++# if HAVE_UTIMENSAT
++      if (fd < 0)
++        {
++#  if defined __APPLE__ && defined __MACH__
++          size_t len = strlen (file);
++          if (len > 0 && file[len - 1] == '/')
++            {
++              struct stat statbuf;
++              if (stat (file, &statbuf) < 0)
++                return -1;
++              if (!S_ISDIR (statbuf.st_mode))
++                {
++                  errno = ENOTDIR;
++                  return -1;
++                }
++            }
++#  endif
++          result = utimensat (AT_FDCWD, file, ts, 0);
++#  ifdef __linux__
++          /* Work around a kernel bug:
++             https://bugzilla.redhat.com/show_bug.cgi?id=442352
++             https://bugzilla.redhat.com/show_bug.cgi?id=449910
++             It appears that utimensat can mistakenly return 280 rather
++             than -1 upon ENOSYS failure.
++             FIXME: remove in 2010 or whenever the offending kernels
++             are no longer in common use.  */
++          if (0 < result)
++            errno = ENOSYS;
++#  endif /* __linux__ */
++          if (result == 0 || errno != ENOSYS)
++            {
++              utimensat_works_really = 1;
++              return result;
++            }
++        }
++# endif /* HAVE_UTIMENSAT */
++# if HAVE_FUTIMENS
++      if (0 <= fd)
++        {
++          result = futimens (fd, ts);
++#  ifdef __linux__
++          /* Work around the same bug as above.  */
++          if (0 < result)
++            errno = ENOSYS;
++#  endif /* __linux__ */
++          if (result == 0 || errno != ENOSYS)
++            {
++              utimensat_works_really = 1;
++              return result;
++            }
++        }
++# endif /* HAVE_FUTIMENS */
++    }
++  utimensat_works_really = -1;
++  lutimensat_works_really = -1;
++#endif /* HAVE_UTIMENSAT || HAVE_FUTIMENS */
++
++#ifdef USE_SETFILETIME
++  /* On native Windows, use SetFileTime(). See
++     <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfiletime>
++     <https://docs.microsoft.com/en-us/windows/desktop/api/minwinbase/ns-minwinbase-filetime>  */
++  if (0 <= fd)
++    {
++      HANDLE handle;
++      FILETIME current_time;
++      FILETIME last_access_time;
++      FILETIME last_write_time;
++
++      handle = (HANDLE) _get_osfhandle (fd);
++      if (handle == INVALID_HANDLE_VALUE)
++        {
++          errno = EBADF;
++          return -1;
++        }
++
++      if (ts == NULL || ts[0].tv_nsec == UTIME_NOW || ts[1].tv_nsec == UTIME_NOW)
++        {
++          /* GetSystemTimeAsFileTime
++             <https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getsystemtimeasfiletime>.
++             It would be overkill to use
++             GetSystemTimePreciseAsFileTime
++             <https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getsystemtimepreciseasfiletime>.  */
++          GetSystemTimeAsFileTime (&current_time);
++        }
++
++      if (ts == NULL || ts[0].tv_nsec == UTIME_NOW)
++        {
++          last_access_time = current_time;
++        }
++      else if (ts[0].tv_nsec == UTIME_OMIT)
++        {
++          last_access_time.dwLowDateTime = 0;
++          last_access_time.dwHighDateTime = 0;
++        }
++      else
++        {
++          ULONGLONG time_since_16010101 =
++            (ULONGLONG) ts[0].tv_sec * 10000000 + ts[0].tv_nsec / 100 + 116444736000000000LL;
++          last_access_time.dwLowDateTime = (DWORD) time_since_16010101;
++          last_access_time.dwHighDateTime = time_since_16010101 >> 32;
++        }
++
++      if (ts == NULL || ts[1].tv_nsec == UTIME_NOW)
++        {
++          last_write_time = current_time;
++        }
++      else if (ts[1].tv_nsec == UTIME_OMIT)
++        {
++          last_write_time.dwLowDateTime = 0;
++          last_write_time.dwHighDateTime = 0;
++        }
++      else
++        {
++          ULONGLONG time_since_16010101 =
++            (ULONGLONG) ts[1].tv_sec * 10000000 + ts[1].tv_nsec / 100 + 116444736000000000LL;
++          last_write_time.dwLowDateTime = (DWORD) time_since_16010101;
++          last_write_time.dwHighDateTime = time_since_16010101 >> 32;
++        }
++
++      if (SetFileTime (handle, NULL, &last_access_time, &last_write_time))
++        return 0;
++      else
++        {
++          DWORD sft_error = GetLastError ();
++          #if 0
++          fprintf (stderr, "fdutimens SetFileTime error 0x%x\n", (unsigned int) sft_error);
++          #endif
++          switch (sft_error)
++            {
++            case ERROR_ACCESS_DENIED: /* fd was opened without O_RDWR */
++              errno = EACCES; /* not specified by POSIX */
++              break;
++            default:
++              errno = EINVAL;
++              break;
++            }
++          return -1;
++        }
++    }
++#endif
++
++  /* The platform lacks an interface to set file timestamps with
++     nanosecond resolution, so do the best we can, discarding any
++     fractional part of the timestamp.  */
++
++  if (adjustment_needed || (REPLACE_FUNC_STAT_FILE && fd < 0))
++    {
++      if (adjustment_needed != 3
++          && (fd < 0 ? stat (file, &st) : fstat (fd, &st)))
++        return -1;
++      if (ts && update_timespec (&st, &ts))
++        return 0;
++    }
++
++  {
++#if HAVE_FUTIMESAT || HAVE_WORKING_UTIMES
++    struct timeval timeval[2];
++    struct timeval *t;
++    if (ts)
++      {
++        timeval[0].tv_sec = ts[0].tv_sec;
++        timeval[0].tv_usec = ts[0].tv_nsec / 1000;
++        timeval[1].tv_sec = ts[1].tv_sec;
++        timeval[1].tv_usec = ts[1].tv_nsec / 1000;
++        t = timeval;
++      }
++    else
++      t = NULL;
++
++    if (fd < 0)
++      {
++# if HAVE_FUTIMESAT
++        return futimesat (AT_FDCWD, file, t);
++# endif
++      }
++    else
++      {
++        /* If futimesat or futimes fails here, don't try to speed things
++           up by returning right away.  glibc can incorrectly fail with
++           errno == ENOENT if /proc isn't mounted.  Also, Mandrake 10.0
++           in high security mode doesn't allow ordinary users to read
++           /proc/self, so glibc incorrectly fails with errno == EACCES.
++           If errno == EIO, EPERM, or EROFS, it's probably safe to fail
++           right away, but these cases are rare enough that they're not
++           worth optimizing, and who knows what other messed-up systems
++           are out there?  So play it safe and fall back on the code
++           below.  */
++
++# if (HAVE_FUTIMESAT && !FUTIMESAT_NULL_BUG) || HAVE_FUTIMES
++#  if HAVE_FUTIMESAT && !FUTIMESAT_NULL_BUG
++#   undef futimes
++#   define futimes(fd, t) futimesat (fd, NULL, t)
++#  endif
++        if (futimes (fd, t) == 0)
++          {
++#  if __linux__ && __GLIBC__
++            /* Work around a longstanding glibc bug, still present as
++               of 2010-12-27.  On older Linux kernels that lack both
++               utimensat and utimes, glibc's futimes rounds instead of
++               truncating when falling back on utime.  The same bug
++               occurs in futimesat with a null 2nd arg.  */
++            if (t)
++              {
++                bool abig = 500000 <= t[0].tv_usec;
++                bool mbig = 500000 <= t[1].tv_usec;
++                if ((abig | mbig) && fstat (fd, &st) == 0)
++                  {
++                    /* If these two subtractions overflow, they'll
++                       track the overflows inside the buggy glibc.  */
++                    time_t adiff = st.st_atime - t[0].tv_sec;
++                    time_t mdiff = st.st_mtime - t[1].tv_sec;
++
++                    struct timeval *tt = NULL;
++                    struct timeval truncated_timeval[2];
++                    truncated_timeval[0] = t[0];
++                    truncated_timeval[1] = t[1];
++                    if (abig && adiff == 1 && get_stat_atime_ns (&st) == 0)
++                      {
++                        tt = truncated_timeval;
++                        tt[0].tv_usec = 0;
++                      }
++                    if (mbig && mdiff == 1 && get_stat_mtime_ns (&st) == 0)
++                      {
++                        tt = truncated_timeval;
++                        tt[1].tv_usec = 0;
++                      }
++                    if (tt)
++                      futimes (fd, tt);
++                  }
++              }
++#  endif
++
++            return 0;
++          }
++# endif
++      }
++#endif /* HAVE_FUTIMESAT || HAVE_WORKING_UTIMES */
++
++    if (!file)
++      {
++#if ! ((HAVE_FUTIMESAT && !FUTIMESAT_NULL_BUG)          \
++        || (HAVE_WORKING_UTIMES && HAVE_FUTIMES))
++        errno = ENOSYS;
++#endif
++        return -1;
++      }
++
++#ifdef USE_SETFILETIME
++    return _gl_utimens_windows (file, ts);
++#elif HAVE_WORKING_UTIMES
++    return utimes (file, t);
++#else
++    {
++      struct utimbuf utimbuf;
++      struct utimbuf *ut;
++      if (ts)
++        {
++          utimbuf.actime = ts[0].tv_sec;
++          utimbuf.modtime = ts[1].tv_sec;
++          ut = &utimbuf;
++        }
++      else
++        ut = NULL;
++
++      return utime (file, ut);
++    }
++#endif /* !HAVE_WORKING_UTIMES */
++  }
++}
++
++/* Set the access and modification timestamps of FILE to be
++   TIMESPEC[0] and TIMESPEC[1], respectively.  */
++int
++utimens (char const *file, struct timespec const timespec[2])
++{
++  return fdutimens (-1, file, timespec);
++}
++
++/* Set the access and modification timestamps of FILE to be
++   TIMESPEC[0] and TIMESPEC[1], respectively, without dereferencing
++   symlinks.  Fail with ENOSYS if the platform does not support
++   changing symlink timestamps, but FILE was a symlink.  */
++int
++lutimens (char const *file, struct timespec const timespec[2])
++{
++  struct timespec adjusted_timespec[2];
++  struct timespec *ts = timespec ? adjusted_timespec : NULL;
++  int adjustment_needed = 0;
++  struct stat st;
++
++  if (ts)
++    {
++      adjusted_timespec[0] = timespec[0];
++      adjusted_timespec[1] = timespec[1];
++      adjustment_needed = validate_timespec (ts);
++    }
++  if (adjustment_needed < 0)
++    return -1;
++
++  /* The Linux kernel did not support symlink timestamps until
++     utimensat, in version 2.6.22, so we don't need to mimic
++     fdutimens' worry about buggy NFS clients.  But we do have to
++     worry about bogus return values.  */
++
++#if HAVE_UTIMENSAT
++  if (0 <= lutimensat_works_really)
++    {
++      int result;
++# if __linux__ || __sun
++      /* As recently as Linux kernel 2.6.32 (Dec 2009), several file
++         systems (xfs, ntfs-3g) have bugs with a single UTIME_OMIT,
++         but work if both times are either explicitly specified or
++         UTIME_NOW.  Work around it with a preparatory lstat prior to
++         calling utimensat; fortunately, there is not much timing
++         impact due to the extra syscall even on file systems where
++         UTIME_OMIT would have worked.
++
++         The same bug occurs in Solaris 11.1 (Apr 2013).
++
++         FIXME: Simplify this for Linux in 2016 and for Solaris in
++         2024, when file system bugs are no longer common.  */
++      if (adjustment_needed == 2)
++        {
++          if (lstat (file, &st))
++            return -1;
++          if (ts[0].tv_nsec == UTIME_OMIT)
++            ts[0] = get_stat_atime (&st);
++          else if (ts[1].tv_nsec == UTIME_OMIT)
++            ts[1] = get_stat_mtime (&st);
++          /* Note that st is good, in case utimensat gives ENOSYS.  */
++          adjustment_needed++;
++        }
++# endif
++      result = utimensat (AT_FDCWD, file, ts, AT_SYMLINK_NOFOLLOW);
++# ifdef __linux__
++      /* Work around a kernel bug:
++         https://bugzilla.redhat.com/show_bug.cgi?id=442352
++         https://bugzilla.redhat.com/show_bug.cgi?id=449910
++         It appears that utimensat can mistakenly return 280 rather
++         than -1 upon ENOSYS failure.
++         FIXME: remove in 2010 or whenever the offending kernels
++         are no longer in common use.  */
++      if (0 < result)
++        errno = ENOSYS;
++# endif
++      if (result == 0 || errno != ENOSYS)
++        {
++          utimensat_works_really = 1;
++          lutimensat_works_really = 1;
++          return result;
++        }
++    }
++  lutimensat_works_really = -1;
++#endif /* HAVE_UTIMENSAT */
++
++  /* The platform lacks an interface to set file timestamps with
++     nanosecond resolution, so do the best we can, discarding any
++     fractional part of the timestamp.  */
++
++  if (adjustment_needed || REPLACE_FUNC_STAT_FILE)
++    {
++      if (adjustment_needed != 3 && lstat (file, &st))
++        return -1;
++      if (ts && update_timespec (&st, &ts))
++        return 0;
++    }
++
++  /* On Linux, lutimes is a thin wrapper around utimensat, so there is
++     no point trying lutimes if utimensat failed with ENOSYS.  */
++#if HAVE_LUTIMES && !HAVE_UTIMENSAT
++  {
++    struct timeval timeval[2];
++    struct timeval *t;
++    int result;
++    if (ts)
++      {
++        timeval[0].tv_sec = ts[0].tv_sec;
++        timeval[0].tv_usec = ts[0].tv_nsec / 1000;
++        timeval[1].tv_sec = ts[1].tv_sec;
++        timeval[1].tv_usec = ts[1].tv_nsec / 1000;
++        t = timeval;
++      }
++    else
++      t = NULL;
++
++    result = lutimes (file, t);
++    if (result == 0 || errno != ENOSYS)
++      return result;
++  }
++#endif /* HAVE_LUTIMES && !HAVE_UTIMENSAT */
++
++  /* Out of luck for symlinks, but we still handle regular files.  */
++  if (!(adjustment_needed || REPLACE_FUNC_STAT_FILE) && lstat (file, &st))
++    return -1;
++  if (!S_ISLNK (st.st_mode))
++    return fdutimens (-1, file, ts);
++  errno = ENOSYS;
++  return -1;
++}
+diff --git a/lib/utimens.h b/lib/utimens.h
+new file mode 100644
+index 0000000..295d3d7
+--- /dev/null
++++ b/lib/utimens.h
+@@ -0,0 +1,49 @@
++/* Set file access and modification times.
++
++   Copyright 2012-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify it
++   under the terms of the GNU General Public License as published by the
++   Free Software Foundation; either version 3 of the License, or any
++   later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++/* Written by Paul Eggert.  */
++
++#include <time.h>
++int fdutimens (int, char const *, struct timespec const [2]);
++int utimens (char const *, struct timespec const [2]);
++int lutimens (char const *, struct timespec const [2]);
++
++#if GNULIB_FDUTIMENSAT
++# include <fcntl.h>
++# include <sys/stat.h>
++
++#ifndef _GL_INLINE_HEADER_BEGIN
++ #error "Please include config.h first."
++#endif
++_GL_INLINE_HEADER_BEGIN
++#ifndef _GL_UTIMENS_INLINE
++# define _GL_UTIMENS_INLINE _GL_INLINE
++#endif
++
++int fdutimensat (int fd, int dir, char const *name, struct timespec const [2],
++                 int atflag);
++
++/* Using this function makes application code slightly more readable.  */
++_GL_UTIMENS_INLINE int
++lutimensat (int dir, char const *file, struct timespec const times[2])
++{
++  return utimensat (dir, file, times, AT_SYMLINK_NOFOLLOW);
++}
++
++_GL_INLINE_HEADER_END
++
++#endif
+diff --git a/m4/utimens.m4 b/m4/utimens.m4
+new file mode 100644
+index 0000000..2ee4ef9
+--- /dev/null
++++ b/m4/utimens.m4
+@@ -0,0 +1,52 @@
++dnl Copyright (C) 2003-2021 Free Software Foundation, Inc.
++dnl This file is free software; the Free Software Foundation
++dnl gives unlimited permission to copy and/or distribute it,
++dnl with or without modifications, as long as this notice is preserved.
++
++dnl serial 11
++
++AC_DEFUN([gl_UTIMENS],
++[
++  dnl Prerequisites of lib/utimens.c.
++  AC_REQUIRE([gl_FUNC_UTIMES])
++  AC_REQUIRE([gl_CHECK_TYPE_STRUCT_TIMESPEC])
++  AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
++  AC_CHECK_FUNCS_ONCE([futimes futimesat futimens utimensat lutimes])
++
++  if test $ac_cv_func_futimens = no && test $ac_cv_func_futimesat = yes; then
++    dnl FreeBSD 8.0-rc2 mishandles futimesat(fd,NULL,time).  It is not
++    dnl standardized, but Solaris implemented it first and uses it as
++    dnl its only means to set fd time.
++    AC_CACHE_CHECK([whether futimesat handles NULL file],
++      [gl_cv_func_futimesat_works],
++      [touch conftest.file
++       AC_RUN_IFELSE([AC_LANG_PROGRAM([[
++#include <stddef.h>
++#include <sys/times.h>
++#include <fcntl.h>
++]GL_MDA_DEFINES],
++        [[int fd = open ("conftest.file", O_RDWR);
++          if (fd < 0) return 1;
++          if (futimesat (fd, NULL, NULL)) return 2;
++        ]])],
++        [gl_cv_func_futimesat_works=yes],
++        [gl_cv_func_futimesat_works=no],
++        [case "$host_os" in
++                            # Guess yes on Linux systems.
++           linux-* | linux) gl_cv_func_futimesat_works="guessing yes" ;;
++                            # Guess yes on glibc systems.
++           *-gnu*)          gl_cv_func_futimesat_works="guessing yes" ;;
++                            # If we don't know, obey --enable-cross-guesses.
++           *)               gl_cv_func_futimesat_works="$gl_cross_guess_normal" ;;
++         esac
++        ])
++      rm -f conftest.file])
++    case "$gl_cv_func_futimesat_works" in
++      *yes) ;;
++      *)
++        AC_DEFINE([FUTIMESAT_NULL_BUG], [1],
++          [Define to 1 if futimesat mishandles a NULL file name.])
++        ;;
++    esac
++  fi
++])
+diff --git a/m4/utimes.m4 b/m4/utimes.m4
+new file mode 100644
+index 0000000..0440e78
+--- /dev/null
++++ b/m4/utimes.m4
+@@ -0,0 +1,161 @@
++# Detect some bugs in glibc's implementation of utimes.
++# serial 8
++
++dnl Copyright (C) 2003-2005, 2009-2021 Free Software Foundation, Inc.
++dnl This file is free software; the Free Software Foundation
++dnl gives unlimited permission to copy and/or distribute it,
++dnl with or without modifications, as long as this notice is preserved.
++
++# See if we need to work around bugs in glibc's implementation of
++# utimes from 2003-07-12 to 2003-09-17.
++# First, there was a bug that would make utimes set mtime
++# and atime to zero (1970-01-01) unconditionally.
++# Then, there was code to round rather than truncate.
++# Then, there was an implementation (sparc64, Linux-2.4.28, glibc-2.3.3)
++# that didn't honor the NULL-means-set-to-current-time semantics.
++# Finally, there was also a version of utimes that failed on read-only
++# files, while utime worked fine (linux-2.2.20, glibc-2.2.5).
++#
++# From Jim Meyering, with suggestions from Paul Eggert.
++
++AC_DEFUN([gl_FUNC_UTIMES],
++[
++  AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
++  AC_CACHE_CHECK([whether the utimes function works],
++                 [gl_cv_func_working_utimes],
++    [AC_RUN_IFELSE([AC_LANG_SOURCE([[
++#include <sys/types.h>
++#include <sys/stat.h>
++#include <fcntl.h>
++#include <sys/time.h>
++#include <time.h>
++#include <unistd.h>
++#include <stdlib.h>
++#include <stdio.h>
++#include <utime.h>
++#include <errno.h>
++]GL_MDA_DEFINES[
++
++static int
++inorder (time_t a, time_t b, time_t c)
++{
++  return a <= b && b <= c;
++}
++
++int
++main ()
++{
++  int result = 0;
++  char const *file = "conftest.utimes";
++  /* On OS/2, file timestamps must be on or after 1980 in local time,
++     with an even number of seconds.  */
++  static struct timeval timeval[2] = {{315620000 + 10, 10},
++                                      {315620000 + 1000000, 999998}};
++
++  /* Test whether utimes() essentially works.  */
++  {
++    struct stat sbuf;
++    FILE *f = fopen (file, "w");
++    if (f == NULL)
++      result |= 1;
++    else if (fclose (f) != 0)
++      result |= 1;
++    else if (utimes (file, timeval) != 0)
++      result |= 2;
++    else if (lstat (file, &sbuf) != 0)
++      result |= 1;
++    else if (!(sbuf.st_atime == timeval[0].tv_sec
++               && sbuf.st_mtime == timeval[1].tv_sec))
++      result |= 4;
++    if (unlink (file) != 0)
++      result |= 1;
++  }
++
++  /* Test whether utimes() with a NULL argument sets the file's timestamp
++     to the current time.  Use 'fstat' as well as 'time' to
++     determine the "current" time, to accommodate NFS file systems
++     if there is a time skew between the host and the NFS server.  */
++  {
++    int fd = open (file, O_WRONLY|O_CREAT, 0644);
++    if (fd < 0)
++      result |= 1;
++    else
++      {
++        time_t t0, t2;
++        struct stat st0, st1, st2;
++        if (time (&t0) == (time_t) -1)
++          result |= 1;
++        else if (fstat (fd, &st0) != 0)
++          result |= 1;
++        else if (utimes (file, timeval) != 0
++                 && (errno != EACCES
++                     /* OS/2 kLIBC utimes fails on opened files.  */
++                     || close (fd) != 0
++                     || utimes (file, timeval) != 0
++                     || (fd = open (file, O_WRONLY)) < 0))
++          result |= 2;
++        else if (utimes (file, NULL) != 0
++                 && (errno != EACCES
++                     /* OS/2 kLIBC utimes fails on opened files.  */
++                     || close (fd) != 0
++                     || utimes (file, NULL) != 0
++                     || (fd = open (file, O_WRONLY)) < 0))
++          result |= 8;
++        else if (fstat (fd, &st1) != 0)
++          result |= 1;
++        else if (write (fd, "\n", 1) != 1)
++          result |= 1;
++        else if (fstat (fd, &st2) != 0)
++          result |= 1;
++        else if (time (&t2) == (time_t) -1)
++          result |= 1;
++        else
++          {
++            int m_ok_POSIX = inorder (t0, st1.st_mtime, t2);
++            int m_ok_NFS = inorder (st0.st_mtime, st1.st_mtime, st2.st_mtime);
++            if (! (st1.st_atime == st1.st_mtime))
++              result |= 16;
++            if (! (m_ok_POSIX || m_ok_NFS))
++              result |= 32;
++          }
++        if (close (fd) != 0)
++          result |= 1;
++      }
++    if (unlink (file) != 0)
++      result |= 1;
++  }
++
++  /* Test whether utimes() with a NULL argument works on read-only files.  */
++  {
++    int fd = open (file, O_WRONLY|O_CREAT, 0444);
++    if (fd < 0)
++      result |= 1;
++    else if (close (fd) != 0)
++      result |= 1;
++    else if (utimes (file, NULL) != 0)
++      result |= 64;
++    if (unlink (file) != 0)
++      result |= 1;
++  }
++
++  return result;
++}
++  ]])],
++       [gl_cv_func_working_utimes=yes],
++       [gl_cv_func_working_utimes=no],
++       [case "$host_os" in
++                   # Guess yes on musl systems.
++          *-musl*) gl_cv_func_working_utimes="guessing yes" ;;
++                   # Guess no on native Windows.
++          mingw*)  gl_cv_func_working_utimes="guessing no" ;;
++          *)       gl_cv_func_working_utimes="$gl_cross_guess_normal" ;;
++        esac
++       ])
++    ])
++
++  case "$gl_cv_func_working_utimes" in
++    *yes)
++      AC_DEFINE([HAVE_WORKING_UTIMES], [1], [Define if utimes works properly.])
++      ;;
++  esac
++])
+diff --git a/lib/binary-io.c b/lib/binary-io.c
+new file mode 100644
+index 0000000..f267897
+--- /dev/null
++++ b/lib/binary-io.c
+@@ -0,0 +1,39 @@
++/* Binary mode I/O.
++   Copyright 2017-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++#include <config.h>
++
++#define BINARY_IO_INLINE _GL_EXTERN_INLINE
++#include "binary-io.h"
++
++#if defined __DJGPP__ || defined __EMX__
++# include <unistd.h>
++
++int
++set_binary_mode (int fd, int mode)
++{
++  if (isatty (fd))
++    /* If FD refers to a console (not a pipe, not a regular file),
++       O_TEXT is the only reasonable mode, both on input and on output.
++       Silently ignore the request.  If we were to return -1 here,
++       all programs that use xset_binary_mode would fail when run
++       with console input or console output.  */
++    return O_TEXT;
++  else
++    return __gl_setmode (fd, mode);
++}
++
++#endif
+diff --git a/lib/binary-io.h b/lib/binary-io.h
+new file mode 100644
+index 0000000..8654fd2
+--- /dev/null
++++ b/lib/binary-io.h
+@@ -0,0 +1,77 @@
++/* Binary mode I/O.
++   Copyright (C) 2001, 2003, 2005, 2008-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++#ifndef _BINARY_H
++#define _BINARY_H
++
++/* For systems that distinguish between text and binary I/O.
++   O_BINARY is guaranteed by the gnulib <fcntl.h>. */
++#include <fcntl.h>
++
++/* The MSVC7 <stdio.h> doesn't like to be included after '#define fileno ...',
++   so we include it here first.  */
++#include <stdio.h>
++
++#ifndef _GL_INLINE_HEADER_BEGIN
++ #error "Please include config.h first."
++#endif
++_GL_INLINE_HEADER_BEGIN
++#ifndef BINARY_IO_INLINE
++# define BINARY_IO_INLINE _GL_INLINE
++#endif
++
++#if O_BINARY
++# if defined __EMX__ || defined __DJGPP__ || defined __CYGWIN__
++#  include <io.h> /* declares setmode() */
++#  define __gl_setmode setmode
++# else
++#  define __gl_setmode _setmode
++#  undef fileno
++#  define fileno _fileno
++# endif
++#else
++  /* On reasonable systems, binary I/O is the only choice.  */
++  /* Use a function rather than a macro, to avoid gcc warnings
++     "warning: statement with no effect".  */
++BINARY_IO_INLINE int
++__gl_setmode (int fd _GL_UNUSED, int mode _GL_UNUSED)
++{
++  return O_BINARY;
++}
++#endif
++
++/* Set FD's mode to MODE, which should be either O_TEXT or O_BINARY.
++   Return the old mode if successful, -1 (setting errno) on failure.
++   Ordinarily this function would be called 'setmode', since that is
++   its old name on MS-Windows, but it is called 'set_binary_mode' here
++   to avoid colliding with a BSD function of another name.  */
++
++#if defined __DJGPP__ || defined __EMX__
++extern int set_binary_mode (int fd, int mode);
++#else
++BINARY_IO_INLINE int
++set_binary_mode (int fd, int mode)
++{
++  return __gl_setmode (fd, mode);
++}
++#endif
++
++/* This macro is obsolescent.  */
++#define SET_BINARY(fd) ((void) set_binary_mode (fd, O_BINARY))
++
++_GL_INLINE_HEADER_END
++
++#endif /* _BINARY_H */
+diff --git a/lib/safe-write.c b/lib/safe-write.c
+new file mode 100644
+index 0000000..a460dc9
+--- /dev/null
++++ b/lib/safe-write.c
+@@ -0,0 +1,18 @@
++/* An interface to write that retries after interrupts.
++   Copyright (C) 2002, 2009-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++#define SAFE_WRITE
++#include "safe-read.c"
+diff --git a/lib/safe-write.h b/lib/safe-write.h
+new file mode 100644
+index 0000000..4307ffe
+--- /dev/null
++++ b/lib/safe-write.h
+@@ -0,0 +1,37 @@
++/* An interface to write() that retries after interrupts.
++   Copyright (C) 2002, 2009-2021 Free Software Foundation, Inc.
++
++   This program is free software: you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
++
++/* Some system calls may be interrupted and fail with errno = EINTR in the
++   following situations:
++     - The process is stopped and restarted (signal SIGSTOP and SIGCONT, user
++       types Ctrl-Z) on some platforms: Mac OS X.
++     - The process receives a signal for which a signal handler was installed
++       with sigaction() with an sa_flags field that does not contain
++       SA_RESTART.
++     - The process receives a signal for which a signal handler was installed
++       with signal() and for which no call to siginterrupt(sig,0) was done,
++       on some platforms: AIX, HP-UX, IRIX, OSF/1, Solaris.
++
++   This module provides a wrapper around write() that handles EINTR.  */
++
++#include <stddef.h>
++
++#define SAFE_WRITE_ERROR ((size_t) -1)
++
++/* Write up to COUNT bytes at BUF to descriptor FD, retrying if interrupted.
++   Return the actual number of bytes written, zero for EOF, or SAFE_WRITE_ERROR
++   upon error.  */
++extern size_t safe_write (int fd, const void *buf, size_t count);
+diff --git a/m4/safe-write.m4 b/m4/safe-write.m4
+new file mode 100644
+index 0000000..ef10d96
+--- /dev/null
++++ b/m4/safe-write.m4
+@@ -0,0 +1,11 @@
++# safe-write.m4 serial 4
++dnl Copyright (C) 2002, 2005-2006, 2009-2021 Free Software Foundation, Inc.
++dnl This file is free software; the Free Software Foundation
++dnl gives unlimited permission to copy and/or distribute it,
++dnl with or without modifications, as long as this notice is preserved.
++
++# Prerequisites of lib/safe-write.c.
++AC_DEFUN([gl_PREREQ_SAFE_WRITE],
++[
++  gl_PREREQ_SAFE_READ
++])
+
+
diff --git a/srcpkgs/recutils/patches/exdev.patch b/srcpkgs/recutils/patches/exdev.patch
new file mode 100644
index 000000000000..6d1f8bb213a1
--- /dev/null
+++ b/srcpkgs/recutils/patches/exdev.patch
@@ -0,0 +1,53 @@
+From 86f662a8202408134a235572ec60141d3082f975 Mon Sep 17 00:00:00 2001
+From: "Jose E. Marchesi" <jose.marchesi@oracle.com>
+Date: Tue, 28 Jan 2020 12:13:42 +0100
+Subject: utils: make utilitis to work with temporary files in a different
+ filesystem
+
+2020-01-28  Jose E. Marchesi  <jose.marchesi@oracle.com>
+
+	* bootstrap.conf (gnulib_modules): Import the modules copy-file
+	and remove.
+	* utils/recutl.c (recutl_write_db_to_file): Copy and remove
+	instead of rename temporary files.
+---
+ utils/recutl.c | 14 ++++++++------
+ 1 file changed, 8 insertions(+), 6 deletions(-)
+
+(limited to 'utils/recutl.c')
+
+diff --git a/utils/recutl.c b/utils/recutl.c
+index 9cf823d..a814fd8 100644
+--- a/utils/recutl.c
++++ b/utils/recutl.c
+@@ -58,6 +58,7 @@
+ #include <rec.h>
+ #include <recutl.h>
+ #include "read-file.h"
++#include "copy-file.h"
+ 
+ /*
+  * Global variables.
+@@ -432,12 +433,13 @@ recutl_write_db_to_file (rec_db_t db,
+ 
+   if (file_name)
+     {
+-      /* Rename the temporary file to file_name.  */
+-      if (rename (tmp_file_name, file_name) == -1)
+-        {
+-          remove (tmp_file_name);
+-          recutl_fatal (_("renaming file %s to %s\n"), tmp_file_name, file_name);
+-        }
++      /* Rename the temporary file to file_name.  We copy and remove
++         instead of renaming because the later doesn't work across
++         different mount points, and it is getting common for /tmp to
++         be mounted in its own filesystem.  */
++      if (qcopy_file_preserving (tmp_file_name, file_name) != 0)
++        recutl_fatal (_("renaming file %s to %s\n"), tmp_file_name, file_name);
++      remove (tmp_file_name);
+ 
+       /* Restore the attributes of the original file. */
+       if (stat_result != -1)
+-- 
+cgit v1.2.1
+
diff --git a/srcpkgs/recutils/template b/srcpkgs/recutils/template
index 3e71312c4e25..c8b48e356837 100644
--- a/srcpkgs/recutils/template
+++ b/srcpkgs/recutils/template
@@ -1,10 +1,11 @@
 # Template file for 'recutils'
 pkgname=recutils
 version=1.8
-revision=1
+revision=2
 build_style=gnu-configure
 configure_args="--with-bash-headers --disable-rpath"
-hostmakedepends="pkg-config"
+# TODO remove automake in 1.9; only needed for copy-file
+hostmakedepends="automake pkg-config"
 makedepends="acl-devel libgcrypt-devel libuuid-devel libcurl-devel"
 short_desc="Utilities to deal with recfiles"
 maintainer="Orphaned <orphan@voidlinux.org>"
@@ -12,7 +13,14 @@ license="GPL-3.0-or-later"
 homepage="https://www.gnu.org/software/recutils/"
 distfiles="${GNU_SITE}/recutils/recutils-${version}.tar.gz"
 checksum=df8eae69593fdba53e264cbf4b2307dfb82120c09b6fab23e2dad51a89a5b193
+patch_args=-Np1
 
+pre_configure() {
+	# XXX horrible kludge to avoid help2man - remove in 1.9
+	touch man/*.1
+	# XXX ONLY needed for copy-file - remove in 1.9
+	aclocal && automake
+}
 librec1_package() {
 	short_desc+=" - rec1 library"
 	pkg_install() {

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: recutils: patch to fix recins not working outside of $TMPDIR
  2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
                   ` (6 preceding siblings ...)
  2021-04-28 21:58 ` [PR PATCH] [Updated] " loreb
@ 2021-05-17 13:12 ` loreb
  2021-06-07 15:44 ` loreb
                   ` (14 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: loreb @ 2021-05-17 13:12 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 1045 bytes --]

New comment by loreb on void-packages repository

https://github.com/void-linux/void-packages/pull/30567#issuecomment-842312800

Comment:
After 10 days with no answer on the mailing list, since this only requires a single function to copy one file, how about shelling out to /bin/cp? eg
```
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static int qcopy_file_preserving(const char *src, const char *dst)
{
        int stat_loc;
        pid_t pid = fork();
        if (pid < 0) {
                perror("fork");
                return -1;
        }
        if (pid == 0) {
                execl("/bin/cp", "--", src, dst, (void *)0);
                exit(1);
        }
        if (pid != waitpid(pid, &stat_loc, 0)) {
                perror("waitpid");
                return -1;
        }
        if (!(WIFEXITED(stat_loc) && WEXITSTATUS(stat_loc) == 0)) {
                return -1;
        }
        return 0;
}
```

should work fine until upstream makes a new release, right?

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: recutils: patch to fix recins not working outside of $TMPDIR
  2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
                   ` (7 preceding siblings ...)
  2021-05-17 13:12 ` loreb
@ 2021-06-07 15:44 ` loreb
  2021-06-08  2:29 ` ericonr
                   ` (13 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: loreb @ 2021-06-07 15:44 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 1147 bytes --]

New comment by loreb on void-packages repository

https://github.com/void-linux/void-packages/pull/30567#issuecomment-856051789

Comment:
It's been one month since I asked upstream and still no response... to recap:
1. the fix is small, and most importantly the patch is literally copied from upstream
2. unfortunately it pull in a stupid amount of tiny dependencies, nearly 2kloc
3. I've given up generating number 2 in automatically
4. one month with no response means upstream will wait until 1.9 is out

`qcopy_file_preserving` above replaces all those stupid dependencies in the megapatch, at the cost of calling an external process; I tested it a couple of times to check for silly mistakes, then ran "make check" and all tests passed.
It's not even part of the shared libraries, so there should be no concerns like "what if something is using librec.{a,so} in a chroot without /bin/cp" - unless I'm mistaken of course.

TLDR: if you think it's fine, I'll replace the megapatch with qcopy_file_preserving above + any suggestion/modification etc,
otherwise just tell me and I'll close the PR, no big deal and no more time wasted :)

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: recutils: patch to fix recins not working outside of $TMPDIR
  2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
                   ` (8 preceding siblings ...)
  2021-06-07 15:44 ` loreb
@ 2021-06-08  2:29 ` ericonr
  2021-06-08 12:54 ` [PR PATCH] [Updated] " loreb
                   ` (12 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: ericonr @ 2021-06-08  2:29 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 256 bytes --]

New comment by ericonr on void-packages repository

https://github.com/void-linux/void-packages/pull/30567#issuecomment-856389828

Comment:
If you feel ok with doing it via execing `cp`, I think I'd rather we carry that, yes! Don't forget to do `cp --` ;)

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PR PATCH] [Updated] recutils: patch to fix recins not working outside of $TMPDIR
  2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
                   ` (9 preceding siblings ...)
  2021-06-08  2:29 ` ericonr
@ 2021-06-08 12:54 ` loreb
  2021-06-08 13:02 ` loreb
                   ` (11 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: loreb @ 2021-06-08 12:54 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 2213 bytes --]

There is an updated pull request by loreb against master on the void-packages repository

https://github.com/loreb/void-packages master
https://github.com/void-linux/void-packages/pull/30567

recutils: patch to fix recins not working outside of $TMPDIR
Details at https://git.savannah.gnu.org/cgit/recutils.git/commit/utils/recutl.c?id=86f662a8202408134a235572ec60141d3082f975
Will no longer be necessary once 1.9 is released; fixes bug #30546
Thanks to ericonr for all the assistance.

note to self: bootstrap.conf is not shipped
add *temporary* dependency on automake
adding gnulib pieces is a real chore...

And then the GNU tools (rightfully) decided that the manpages were out
of date, because a file has been modified, and insisted on running
help2man - I simply told pre_configure to update the timestamps of the
manpages.

<!-- Mark items with [x] where applicable -->

#### General
- [ ] This is a new package and it conforms to the [quality requirements](https://github.com/void-linux/void-packages/blob/master/Manual.md#quality-requirements)

#### Have the results of the proposed changes been tested?
- [ ] I use the packages affected by the proposed changes on a regular basis and confirm this PR works for me
- [x] I generally don't use the affected packages but briefly tested this PR

<!--
If GitHub CI cannot be used to validate the build result (for example, if the
build is likely to take several hours), make sure to
[skip CI](https://github.com/void-linux/void-packages/blob/master/CONTRIBUTING.md#continuous-integration).
When skipping CI, uncomment and fill out the following section.
Note: for builds that are likely to complete in less than 2 hours, it is not
acceptable to skip CI.
-->
<!-- 
#### Does it build and run successfully? 
(Please choose at least one native build and, if supported, at least one cross build. More are better.)
- [ ] I built this PR locally for my native architecture, (ARCH-LIBC)
- [ ] I built this PR locally for these architectures (if supported. mark crossbuilds):
  - [ ] aarch64-musl
  - [ ] armv7l
  - [ ] armv6l-musl
-->


A patch file from https://github.com/void-linux/void-packages/pull/30567.patch is attached

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: github-pr-master-30567.patch --]
[-- Type: text/x-diff, Size: 5571 bytes --]

From e002829cfd0851bd62b7a9fdef99b0de2d83f5a3 Mon Sep 17 00:00:00 2001
From: Lorenzo Beretta <vc.net.loreb@gmail.com>
Date: Wed, 28 Apr 2021 14:20:53 +0200
Subject: [PATCH] recutils: patch to fix recins not working outside of $TMPDIR

Details at https://git.savannah.gnu.org/cgit/recutils.git/commit/utils/recutl.c?id=86f662a8202408134a235572ec60141d3082f975
Will no longer be necessary once 1.9 is released; fixes bug #30546
Thanks to ericonr for all the assistance.

note to self: bootstrap.conf is not shipped
add *temporary* dependency on automake
adding gnulib pieces is a real chore...

And then the GNU tools (rightfully) decided that the manpages were out
of date, because a file has been modified, and insisted on running
help2man - I simply told pre_configure to update the timestamps of the
manpages.

---

Importing qcopy_file_preserving as described above results in a 2kloc
path, so just invoke cp(1) until 1.9 is released
---
 srcpkgs/recutils/patches/copyfile.patch | 37 +++++++++++++++++
 srcpkgs/recutils/patches/exdev.patch    | 54 +++++++++++++++++++++++++
 srcpkgs/recutils/template               | 12 +++++-
 3 files changed, 101 insertions(+), 2 deletions(-)
 create mode 100644 srcpkgs/recutils/patches/copyfile.patch
 create mode 100644 srcpkgs/recutils/patches/exdev.patch

diff --git a/srcpkgs/recutils/patches/copyfile.patch b/srcpkgs/recutils/patches/copyfile.patch
new file mode 100644
index 000000000000..c00805af2c6e
--- /dev/null
+++ b/srcpkgs/recutils/patches/copyfile.patch
@@ -0,0 +1,37 @@
+--- a/utils/recutl.c
++++ b/utils/recutl.c
+@@ -58,6 +58,33 @@
+ #include <recutl.h>
+ #include "read-file.h"
+ 
++#include <sys/wait.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <unistd.h>
++
++static int qcopy_file_preserving(const char *src, const char *dst)
++{
++        int stat_loc;
++        pid_t pid = fork();
++        if (pid < 0) {
++                perror("fork");
++                return -1;
++        }
++        if (pid == 0) {
++                execl("/bin/cp", "--", src, dst, (void *)0);
++                exit(1);
++        }
++        if (pid != waitpid(pid, &stat_loc, 0)) {
++                perror("waitpid");
++                return -1;
++        }
++        if (!(WIFEXITED(stat_loc) && WEXITSTATUS(stat_loc) == 0)) {
++                return -1;
++        }
++        return 0;
++}
++
+ /*
+  * Global variables.
+  */
+
diff --git a/srcpkgs/recutils/patches/exdev.patch b/srcpkgs/recutils/patches/exdev.patch
new file mode 100644
index 000000000000..38fb43498ad0
--- /dev/null
+++ b/srcpkgs/recutils/patches/exdev.patch
@@ -0,0 +1,54 @@
+From 86f662a8202408134a235572ec60141d3082f975 Mon Sep 17 00:00:00 2001
+From: "Jose E. Marchesi" <jose.marchesi@oracle.com>
+Date: Tue, 28 Jan 2020 12:13:42 +0100
+Subject: utils: make utilitis to work with temporary files in a different
+ filesystem
+
+2020-01-28  Jose E. Marchesi  <jose.marchesi@oracle.com>
+
+	* bootstrap.conf (gnulib_modules): Import the modules copy-file
+	and remove.
+	* utils/recutl.c (recutl_write_db_to_file): Copy and remove
+	instead of rename temporary files.
+---
+ utils/recutl.c | 14 ++++++++------
+ 1 file changed, 8 insertions(+), 6 deletions(-)
+
+(limited to 'utils/recutl.c')
+(copy-file.h commented out to use /bin/cp)
+
+diff --git a/utils/recutl.c b/utils/recutl.c
+index 9cf823d..a814fd8 100644
+--- a/utils/recutl.c
++++ b/utils/recutl.c
+@@ -58,6 +58,7 @@
+ #include <rec.h>
+ #include <recutl.h>
+ #include "read-file.h"
++/*#include "copy-file.h"*/
+ 
+ /*
+  * Global variables.
+@@ -432,12 +433,13 @@ recutl_write_db_to_file (rec_db_t db,
+ 
+   if (file_name)
+     {
+-      /* Rename the temporary file to file_name.  */
+-      if (rename (tmp_file_name, file_name) == -1)
+-        {
+-          remove (tmp_file_name);
+-          recutl_fatal (_("renaming file %s to %s\n"), tmp_file_name, file_name);
+-        }
++      /* Rename the temporary file to file_name.  We copy and remove
++         instead of renaming because the later doesn't work across
++         different mount points, and it is getting common for /tmp to
++         be mounted in its own filesystem.  */
++      if (qcopy_file_preserving (tmp_file_name, file_name) != 0)
++        recutl_fatal (_("renaming file %s to %s\n"), tmp_file_name, file_name);
++      remove (tmp_file_name);
+ 
+       /* Restore the attributes of the original file. */
+       if (stat_result != -1)
+-- 
+cgit v1.2.1
+
diff --git a/srcpkgs/recutils/template b/srcpkgs/recutils/template
index 3e71312c4e25..c8b48e356837 100644
--- a/srcpkgs/recutils/template
+++ b/srcpkgs/recutils/template
@@ -1,10 +1,11 @@
 # Template file for 'recutils'
 pkgname=recutils
 version=1.8
-revision=1
+revision=2
 build_style=gnu-configure
 configure_args="--with-bash-headers --disable-rpath"
-hostmakedepends="pkg-config"
+# TODO remove automake in 1.9; only needed for copy-file
+hostmakedepends="automake pkg-config"
 makedepends="acl-devel libgcrypt-devel libuuid-devel libcurl-devel"
 short_desc="Utilities to deal with recfiles"
 maintainer="Orphaned <orphan@voidlinux.org>"
@@ -12,7 +13,14 @@ license="GPL-3.0-or-later"
 homepage="https://www.gnu.org/software/recutils/"
 distfiles="${GNU_SITE}/recutils/recutils-${version}.tar.gz"
 checksum=df8eae69593fdba53e264cbf4b2307dfb82120c09b6fab23e2dad51a89a5b193
+patch_args=-Np1
 
+pre_configure() {
+	# XXX horrible kludge to avoid help2man - remove in 1.9
+	touch man/*.1
+	# XXX ONLY needed for copy-file - remove in 1.9
+	aclocal && automake
+}
 librec1_package() {
 	short_desc+=" - rec1 library"
 	pkg_install() {

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: recutils: patch to fix recins not working outside of $TMPDIR
  2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
                   ` (10 preceding siblings ...)
  2021-06-08 12:54 ` [PR PATCH] [Updated] " loreb
@ 2021-06-08 13:02 ` loreb
  2021-06-08 13:21 ` [PR REVIEW] " ericonr
                   ` (10 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: loreb @ 2021-06-08 13:02 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 486 bytes --]

New comment by loreb on void-packages repository

https://github.com/void-linux/void-packages/pull/30567#issuecomment-856747031

Comment:
Done; the patch declares the function as static, meaning that the patch should immediately fail to compile when 1.9 is released and qcopy_file_preserving is declared "extern int...", and slightly modifies the original patch by commenting out `#include "copy-file.h"` as everything is done in a single file.

If I screwed up anything let me know.

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PR REVIEW] recutils: patch to fix recins not working outside of $TMPDIR
  2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
                   ` (11 preceding siblings ...)
  2021-06-08 13:02 ` loreb
@ 2021-06-08 13:21 ` ericonr
  2021-06-08 13:21 ` ericonr
                   ` (9 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: ericonr @ 2021-06-08 13:21 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 267 bytes --]

New review comment by ericonr on void-packages repository

https://github.com/void-linux/void-packages/pull/30567#discussion_r647433120

Comment:
```suggestion
+                _exit(1);
```

Calling `exit` in a forked child can lead to all sorts of weirdness ;)

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PR REVIEW] recutils: patch to fix recins not working outside of $TMPDIR
  2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
                   ` (12 preceding siblings ...)
  2021-06-08 13:21 ` [PR REVIEW] " ericonr
@ 2021-06-08 13:21 ` ericonr
  2021-06-08 13:21 ` ericonr
                   ` (8 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: ericonr @ 2021-06-08 13:21 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 204 bytes --]

New review comment by ericonr on void-packages repository

https://github.com/void-linux/void-packages/pull/30567#discussion_r647434623

Comment:
Add a comment on top explaining the purpose of this patch

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PR REVIEW] recutils: patch to fix recins not working outside of $TMPDIR
  2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
                   ` (13 preceding siblings ...)
  2021-06-08 13:21 ` ericonr
@ 2021-06-08 13:21 ` ericonr
  2021-06-08 14:33 ` [PR PATCH] [Updated] " loreb
                   ` (7 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: ericonr @ 2021-06-08 13:21 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 171 bytes --]

New review comment by ericonr on void-packages repository

https://github.com/void-linux/void-packages/pull/30567#discussion_r647434247

Comment:
Is this still necessary?

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PR PATCH] [Updated] recutils: patch to fix recins not working outside of $TMPDIR
  2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
                   ` (14 preceding siblings ...)
  2021-06-08 13:21 ` ericonr
@ 2021-06-08 14:33 ` loreb
  2021-06-08 14:34 ` [PR REVIEW] " loreb
                   ` (6 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: loreb @ 2021-06-08 14:33 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 2213 bytes --]

There is an updated pull request by loreb against master on the void-packages repository

https://github.com/loreb/void-packages master
https://github.com/void-linux/void-packages/pull/30567

recutils: patch to fix recins not working outside of $TMPDIR
Details at https://git.savannah.gnu.org/cgit/recutils.git/commit/utils/recutl.c?id=86f662a8202408134a235572ec60141d3082f975
Will no longer be necessary once 1.9 is released; fixes bug #30546
Thanks to ericonr for all the assistance.

note to self: bootstrap.conf is not shipped
add *temporary* dependency on automake
adding gnulib pieces is a real chore...

And then the GNU tools (rightfully) decided that the manpages were out
of date, because a file has been modified, and insisted on running
help2man - I simply told pre_configure to update the timestamps of the
manpages.

<!-- Mark items with [x] where applicable -->

#### General
- [ ] This is a new package and it conforms to the [quality requirements](https://github.com/void-linux/void-packages/blob/master/Manual.md#quality-requirements)

#### Have the results of the proposed changes been tested?
- [ ] I use the packages affected by the proposed changes on a regular basis and confirm this PR works for me
- [x] I generally don't use the affected packages but briefly tested this PR

<!--
If GitHub CI cannot be used to validate the build result (for example, if the
build is likely to take several hours), make sure to
[skip CI](https://github.com/void-linux/void-packages/blob/master/CONTRIBUTING.md#continuous-integration).
When skipping CI, uncomment and fill out the following section.
Note: for builds that are likely to complete in less than 2 hours, it is not
acceptable to skip CI.
-->
<!-- 
#### Does it build and run successfully? 
(Please choose at least one native build and, if supported, at least one cross build. More are better.)
- [ ] I built this PR locally for my native architecture, (ARCH-LIBC)
- [ ] I built this PR locally for these architectures (if supported. mark crossbuilds):
  - [ ] aarch64-musl
  - [ ] armv7l
  - [ ] armv6l-musl
-->


A patch file from https://github.com/void-linux/void-packages/pull/30567.patch is attached

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: github-pr-master-30567.patch --]
[-- Type: text/x-diff, Size: 5570 bytes --]

From 54b2372c21a83ca68574423de0a5982f92fd8a5e Mon Sep 17 00:00:00 2001
From: Lorenzo Beretta <vc.net.loreb@gmail.com>
Date: Wed, 28 Apr 2021 14:20:53 +0200
Subject: [PATCH] recutils: patch to fix recins not working outside of $TMPDIR

Details at https://git.savannah.gnu.org/cgit/recutils.git/commit/utils/recutl.c?id=86f662a8202408134a235572ec60141d3082f975
Will no longer be necessary once 1.9 is released; fixes bug #30546
Thanks to ericonr for all the assistance.

note to self: bootstrap.conf is not shipped
add *temporary* dependency on automake
adding gnulib pieces is a real chore...

And then the GNU tools (rightfully) decided that the manpages were out
of date, because a file has been modified, and insisted on running
help2man - I simply told pre_configure to update the timestamps of the
manpages.

---

Importing qcopy_file_preserving as described above results in a 2kloc
path, so just invoke cp(1) until 1.9 is released
---
 srcpkgs/recutils/patches/copyfile.patch | 45 +++++++++++++++++++++
 srcpkgs/recutils/patches/exdev.patch    | 54 +++++++++++++++++++++++++
 srcpkgs/recutils/template               |  7 +++-
 3 files changed, 105 insertions(+), 1 deletion(-)
 create mode 100644 srcpkgs/recutils/patches/copyfile.patch
 create mode 100644 srcpkgs/recutils/patches/exdev.patch

diff --git a/srcpkgs/recutils/patches/copyfile.patch b/srcpkgs/recutils/patches/copyfile.patch
new file mode 100644
index 000000000000..2104680974b7
--- /dev/null
+++ b/srcpkgs/recutils/patches/copyfile.patch
@@ -0,0 +1,45 @@
+Implement qcopy_file_preserving using cp(1).
+
+Done because importing the original upstream fix resulted in a 2kloc patch;
+the function in upstream is "extern int",
+the one in this patch is static,
+so as soon as upstream publish a release that fixes this bug
+this patch will signal its obsolescence by failing to compile.
+
+--- a/utils/recutl.c
++++ b/utils/recutl.c
+@@ -58,6 +58,33 @@
+ #include <recutl.h>
+ #include "read-file.h"
+ 
++#include <sys/wait.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <unistd.h>
++
++static int qcopy_file_preserving(const char *src, const char *dst)
++{
++        int stat_loc;
++        pid_t pid = fork();
++        if (pid < 0) {
++                perror("fork");
++                return -1;
++        }
++        if (pid == 0) {
++                execl("/bin/cp", "--", src, dst, (void *)0);
++                exit(1);
++        }
++        if (pid != waitpid(pid, &stat_loc, 0)) {
++                perror("waitpid");
++                return -1;
++        }
++        if (!(WIFEXITED(stat_loc) && WEXITSTATUS(stat_loc) == 0)) {
++                return -1;
++        }
++        return 0;
++}
++
+ /*
+  * Global variables.
+  */
+
diff --git a/srcpkgs/recutils/patches/exdev.patch b/srcpkgs/recutils/patches/exdev.patch
new file mode 100644
index 000000000000..38fb43498ad0
--- /dev/null
+++ b/srcpkgs/recutils/patches/exdev.patch
@@ -0,0 +1,54 @@
+From 86f662a8202408134a235572ec60141d3082f975 Mon Sep 17 00:00:00 2001
+From: "Jose E. Marchesi" <jose.marchesi@oracle.com>
+Date: Tue, 28 Jan 2020 12:13:42 +0100
+Subject: utils: make utilitis to work with temporary files in a different
+ filesystem
+
+2020-01-28  Jose E. Marchesi  <jose.marchesi@oracle.com>
+
+	* bootstrap.conf (gnulib_modules): Import the modules copy-file
+	and remove.
+	* utils/recutl.c (recutl_write_db_to_file): Copy and remove
+	instead of rename temporary files.
+---
+ utils/recutl.c | 14 ++++++++------
+ 1 file changed, 8 insertions(+), 6 deletions(-)
+
+(limited to 'utils/recutl.c')
+(copy-file.h commented out to use /bin/cp)
+
+diff --git a/utils/recutl.c b/utils/recutl.c
+index 9cf823d..a814fd8 100644
+--- a/utils/recutl.c
++++ b/utils/recutl.c
+@@ -58,6 +58,7 @@
+ #include <rec.h>
+ #include <recutl.h>
+ #include "read-file.h"
++/*#include "copy-file.h"*/
+ 
+ /*
+  * Global variables.
+@@ -432,12 +433,13 @@ recutl_write_db_to_file (rec_db_t db,
+ 
+   if (file_name)
+     {
+-      /* Rename the temporary file to file_name.  */
+-      if (rename (tmp_file_name, file_name) == -1)
+-        {
+-          remove (tmp_file_name);
+-          recutl_fatal (_("renaming file %s to %s\n"), tmp_file_name, file_name);
+-        }
++      /* Rename the temporary file to file_name.  We copy and remove
++         instead of renaming because the later doesn't work across
++         different mount points, and it is getting common for /tmp to
++         be mounted in its own filesystem.  */
++      if (qcopy_file_preserving (tmp_file_name, file_name) != 0)
++        recutl_fatal (_("renaming file %s to %s\n"), tmp_file_name, file_name);
++      remove (tmp_file_name);
+ 
+       /* Restore the attributes of the original file. */
+       if (stat_result != -1)
+-- 
+cgit v1.2.1
+
diff --git a/srcpkgs/recutils/template b/srcpkgs/recutils/template
index 3e71312c4e25..942a871a6d4b 100644
--- a/srcpkgs/recutils/template
+++ b/srcpkgs/recutils/template
@@ -1,7 +1,7 @@
 # Template file for 'recutils'
 pkgname=recutils
 version=1.8
-revision=1
+revision=2
 build_style=gnu-configure
 configure_args="--with-bash-headers --disable-rpath"
 hostmakedepends="pkg-config"
@@ -12,7 +12,12 @@ license="GPL-3.0-or-later"
 homepage="https://www.gnu.org/software/recutils/"
 distfiles="${GNU_SITE}/recutils/recutils-${version}.tar.gz"
 checksum=df8eae69593fdba53e264cbf4b2307dfb82120c09b6fab23e2dad51a89a5b193
+patch_args=-Np1
 
+pre_configure() {
+	# XXX horrible kludge to avoid help2man - remove in 1.9
+	touch man/*.1
+}
 librec1_package() {
 	short_desc+=" - rec1 library"
 	pkg_install() {

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PR REVIEW] recutils: patch to fix recins not working outside of $TMPDIR
  2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
                   ` (15 preceding siblings ...)
  2021-06-08 14:33 ` [PR PATCH] [Updated] " loreb
@ 2021-06-08 14:34 ` loreb
  2021-06-08 14:35 ` loreb
                   ` (5 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: loreb @ 2021-06-08 14:34 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 222 bytes --]

New review comment by loreb on void-packages repository

https://github.com/void-linux/void-packages/pull/30567#discussion_r647503737

Comment:
Good catch; help2man is still called, but aclocal/acmake are no longer needed

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PR REVIEW] recutils: patch to fix recins not working outside of $TMPDIR
  2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
                   ` (16 preceding siblings ...)
  2021-06-08 14:34 ` [PR REVIEW] " loreb
@ 2021-06-08 14:35 ` loreb
  2021-06-08 15:17 ` [PR PATCH] [Updated] " loreb
                   ` (4 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: loreb @ 2021-06-08 14:35 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 159 bytes --]

New review comment by loreb on void-packages repository

https://github.com/void-linux/void-packages/pull/30567#discussion_r647505332

Comment:
Done, sorry :D

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PR PATCH] [Updated] recutils: patch to fix recins not working outside of $TMPDIR
  2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
                   ` (17 preceding siblings ...)
  2021-06-08 14:35 ` loreb
@ 2021-06-08 15:17 ` loreb
  2021-06-08 15:19 ` [PR REVIEW] " loreb
                   ` (3 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: loreb @ 2021-06-08 15:17 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 2213 bytes --]

There is an updated pull request by loreb against master on the void-packages repository

https://github.com/loreb/void-packages master
https://github.com/void-linux/void-packages/pull/30567

recutils: patch to fix recins not working outside of $TMPDIR
Details at https://git.savannah.gnu.org/cgit/recutils.git/commit/utils/recutl.c?id=86f662a8202408134a235572ec60141d3082f975
Will no longer be necessary once 1.9 is released; fixes bug #30546
Thanks to ericonr for all the assistance.

note to self: bootstrap.conf is not shipped
add *temporary* dependency on automake
adding gnulib pieces is a real chore...

And then the GNU tools (rightfully) decided that the manpages were out
of date, because a file has been modified, and insisted on running
help2man - I simply told pre_configure to update the timestamps of the
manpages.

<!-- Mark items with [x] where applicable -->

#### General
- [ ] This is a new package and it conforms to the [quality requirements](https://github.com/void-linux/void-packages/blob/master/Manual.md#quality-requirements)

#### Have the results of the proposed changes been tested?
- [ ] I use the packages affected by the proposed changes on a regular basis and confirm this PR works for me
- [x] I generally don't use the affected packages but briefly tested this PR

<!--
If GitHub CI cannot be used to validate the build result (for example, if the
build is likely to take several hours), make sure to
[skip CI](https://github.com/void-linux/void-packages/blob/master/CONTRIBUTING.md#continuous-integration).
When skipping CI, uncomment and fill out the following section.
Note: for builds that are likely to complete in less than 2 hours, it is not
acceptable to skip CI.
-->
<!-- 
#### Does it build and run successfully? 
(Please choose at least one native build and, if supported, at least one cross build. More are better.)
- [ ] I built this PR locally for my native architecture, (ARCH-LIBC)
- [ ] I built this PR locally for these architectures (if supported. mark crossbuilds):
  - [ ] aarch64-musl
  - [ ] armv7l
  - [ ] armv6l-musl
-->


A patch file from https://github.com/void-linux/void-packages/pull/30567.patch is attached

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: github-pr-master-30567.patch --]
[-- Type: text/x-diff, Size: 5571 bytes --]

From ecff3a6108ad8486efb2ac8ea5b83294a081f38f Mon Sep 17 00:00:00 2001
From: Lorenzo Beretta <vc.net.loreb@gmail.com>
Date: Wed, 28 Apr 2021 14:20:53 +0200
Subject: [PATCH] recutils: patch to fix recins not working outside of $TMPDIR

Details at https://git.savannah.gnu.org/cgit/recutils.git/commit/utils/recutl.c?id=86f662a8202408134a235572ec60141d3082f975
Will no longer be necessary once 1.9 is released; fixes bug #30546
Thanks to ericonr for all the assistance.

note to self: bootstrap.conf is not shipped
add *temporary* dependency on automake
adding gnulib pieces is a real chore...

And then the GNU tools (rightfully) decided that the manpages were out
of date, because a file has been modified, and insisted on running
help2man - I simply told pre_configure to update the timestamps of the
manpages.

---

Importing qcopy_file_preserving as described above results in a 2kloc
path, so just invoke cp(1) until 1.9 is released
---
 srcpkgs/recutils/patches/copyfile.patch | 45 +++++++++++++++++++++
 srcpkgs/recutils/patches/exdev.patch    | 54 +++++++++++++++++++++++++
 srcpkgs/recutils/template               |  7 +++-
 3 files changed, 105 insertions(+), 1 deletion(-)
 create mode 100644 srcpkgs/recutils/patches/copyfile.patch
 create mode 100644 srcpkgs/recutils/patches/exdev.patch

diff --git a/srcpkgs/recutils/patches/copyfile.patch b/srcpkgs/recutils/patches/copyfile.patch
new file mode 100644
index 000000000000..26fa6e6af0d6
--- /dev/null
+++ b/srcpkgs/recutils/patches/copyfile.patch
@@ -0,0 +1,45 @@
+Implement qcopy_file_preserving using cp(1).
+
+Done because importing the original upstream fix resulted in a 2kloc patch;
+the function in upstream is "extern int",
+the one in this patch is static,
+so as soon as upstream publish a release that fixes this bug
+this patch will signal its obsolescence by failing to compile.
+
+--- a/utils/recutl.c
++++ b/utils/recutl.c
+@@ -58,6 +58,33 @@
+ #include <recutl.h>
+ #include "read-file.h"
+ 
++#include <sys/wait.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <unistd.h>
++
++static int qcopy_file_preserving(const char *src, const char *dst)
++{
++        int stat_loc;
++        pid_t pid = fork();
++        if (pid < 0) {
++                perror("fork");
++                return -1;
++        }
++        if (pid == 0) {
++                execl("/bin/cp", "--", src, dst, (void *)0);
++                _exit(1);
++        }
++        if (pid != waitpid(pid, &stat_loc, 0)) {
++                perror("waitpid");
++                return -1;
++        }
++        if (!(WIFEXITED(stat_loc) && WEXITSTATUS(stat_loc) == 0)) {
++                return -1;
++        }
++        return 0;
++}
++
+ /*
+  * Global variables.
+  */
+
diff --git a/srcpkgs/recutils/patches/exdev.patch b/srcpkgs/recutils/patches/exdev.patch
new file mode 100644
index 000000000000..38fb43498ad0
--- /dev/null
+++ b/srcpkgs/recutils/patches/exdev.patch
@@ -0,0 +1,54 @@
+From 86f662a8202408134a235572ec60141d3082f975 Mon Sep 17 00:00:00 2001
+From: "Jose E. Marchesi" <jose.marchesi@oracle.com>
+Date: Tue, 28 Jan 2020 12:13:42 +0100
+Subject: utils: make utilitis to work with temporary files in a different
+ filesystem
+
+2020-01-28  Jose E. Marchesi  <jose.marchesi@oracle.com>
+
+	* bootstrap.conf (gnulib_modules): Import the modules copy-file
+	and remove.
+	* utils/recutl.c (recutl_write_db_to_file): Copy and remove
+	instead of rename temporary files.
+---
+ utils/recutl.c | 14 ++++++++------
+ 1 file changed, 8 insertions(+), 6 deletions(-)
+
+(limited to 'utils/recutl.c')
+(copy-file.h commented out to use /bin/cp)
+
+diff --git a/utils/recutl.c b/utils/recutl.c
+index 9cf823d..a814fd8 100644
+--- a/utils/recutl.c
++++ b/utils/recutl.c
+@@ -58,6 +58,7 @@
+ #include <rec.h>
+ #include <recutl.h>
+ #include "read-file.h"
++/*#include "copy-file.h"*/
+ 
+ /*
+  * Global variables.
+@@ -432,12 +433,13 @@ recutl_write_db_to_file (rec_db_t db,
+ 
+   if (file_name)
+     {
+-      /* Rename the temporary file to file_name.  */
+-      if (rename (tmp_file_name, file_name) == -1)
+-        {
+-          remove (tmp_file_name);
+-          recutl_fatal (_("renaming file %s to %s\n"), tmp_file_name, file_name);
+-        }
++      /* Rename the temporary file to file_name.  We copy and remove
++         instead of renaming because the later doesn't work across
++         different mount points, and it is getting common for /tmp to
++         be mounted in its own filesystem.  */
++      if (qcopy_file_preserving (tmp_file_name, file_name) != 0)
++        recutl_fatal (_("renaming file %s to %s\n"), tmp_file_name, file_name);
++      remove (tmp_file_name);
+ 
+       /* Restore the attributes of the original file. */
+       if (stat_result != -1)
+-- 
+cgit v1.2.1
+
diff --git a/srcpkgs/recutils/template b/srcpkgs/recutils/template
index 3e71312c4e25..942a871a6d4b 100644
--- a/srcpkgs/recutils/template
+++ b/srcpkgs/recutils/template
@@ -1,7 +1,7 @@
 # Template file for 'recutils'
 pkgname=recutils
 version=1.8
-revision=1
+revision=2
 build_style=gnu-configure
 configure_args="--with-bash-headers --disable-rpath"
 hostmakedepends="pkg-config"
@@ -12,7 +12,12 @@ license="GPL-3.0-or-later"
 homepage="https://www.gnu.org/software/recutils/"
 distfiles="${GNU_SITE}/recutils/recutils-${version}.tar.gz"
 checksum=df8eae69593fdba53e264cbf4b2307dfb82120c09b6fab23e2dad51a89a5b193
+patch_args=-Np1
 
+pre_configure() {
+	# XXX horrible kludge to avoid help2man - remove in 1.9
+	touch man/*.1
+}
 librec1_package() {
 	short_desc+=" - rec1 library"
 	pkg_install() {

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PR REVIEW] recutils: patch to fix recins not working outside of $TMPDIR
  2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
                   ` (18 preceding siblings ...)
  2021-06-08 15:17 ` [PR PATCH] [Updated] " loreb
@ 2021-06-08 15:19 ` loreb
  2022-05-18  2:09 ` github-actions
                   ` (2 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: loreb @ 2021-06-08 15:19 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 202 bytes --]

New review comment by loreb on void-packages repository

https://github.com/void-linux/void-packages/pull/30567#discussion_r647546964

Comment:
Ouch, there are a couple of calls to `atexit`, good call!

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: recutils: patch to fix recins not working outside of $TMPDIR
  2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
                   ` (19 preceding siblings ...)
  2021-06-08 15:19 ` [PR REVIEW] " loreb
@ 2022-05-18  2:09 ` github-actions
  2022-05-18 14:46 ` [PR PATCH] [Closed]: " loreb
  2022-05-18 14:46 ` loreb
  22 siblings, 0 replies; 24+ messages in thread
From: github-actions @ 2022-05-18  2:09 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 305 bytes --]

New comment by github-actions[bot] on void-packages repository

https://github.com/void-linux/void-packages/pull/30567#issuecomment-1129493126

Comment:
Pull Requests become stale 90 days after last activity and are closed 14 days after that.  If this pull request is still relevant bump it or assign it.

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: recutils: patch to fix recins not working outside of $TMPDIR
  2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
                   ` (21 preceding siblings ...)
  2022-05-18 14:46 ` [PR PATCH] [Closed]: " loreb
@ 2022-05-18 14:46 ` loreb
  22 siblings, 0 replies; 24+ messages in thread
From: loreb @ 2022-05-18 14:46 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 232 bytes --]

New comment by loreb on void-packages repository

https://github.com/void-linux/void-packages/pull/30567#issuecomment-1130111520

Comment:
Closing because https://github.com/void-linux/void-packages/pull/37184 made this unnecessary

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PR PATCH] [Closed]: recutils: patch to fix recins not working outside of $TMPDIR
  2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
                   ` (20 preceding siblings ...)
  2022-05-18  2:09 ` github-actions
@ 2022-05-18 14:46 ` loreb
  2022-05-18 14:46 ` loreb
  22 siblings, 0 replies; 24+ messages in thread
From: loreb @ 2022-05-18 14:46 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 2062 bytes --]

There's a closed pull request on the void-packages repository

recutils: patch to fix recins not working outside of $TMPDIR
https://github.com/void-linux/void-packages/pull/30567

Description:
Details at https://git.savannah.gnu.org/cgit/recutils.git/commit/utils/recutl.c?id=86f662a8202408134a235572ec60141d3082f975
Will no longer be necessary once 1.9 is released; fixes bug #30546
Thanks to ericonr for all the assistance.

note to self: bootstrap.conf is not shipped
add *temporary* dependency on automake
adding gnulib pieces is a real chore...

And then the GNU tools (rightfully) decided that the manpages were out
of date, because a file has been modified, and insisted on running
help2man - I simply told pre_configure to update the timestamps of the
manpages.

<!-- Mark items with [x] where applicable -->

#### General
- [ ] This is a new package and it conforms to the [quality requirements](https://github.com/void-linux/void-packages/blob/master/Manual.md#quality-requirements)

#### Have the results of the proposed changes been tested?
- [ ] I use the packages affected by the proposed changes on a regular basis and confirm this PR works for me
- [x] I generally don't use the affected packages but briefly tested this PR

<!--
If GitHub CI cannot be used to validate the build result (for example, if the
build is likely to take several hours), make sure to
[skip CI](https://github.com/void-linux/void-packages/blob/master/CONTRIBUTING.md#continuous-integration).
When skipping CI, uncomment and fill out the following section.
Note: for builds that are likely to complete in less than 2 hours, it is not
acceptable to skip CI.
-->
<!-- 
#### Does it build and run successfully? 
(Please choose at least one native build and, if supported, at least one cross build. More are better.)
- [ ] I built this PR locally for my native architecture, (ARCH-LIBC)
- [ ] I built this PR locally for these architectures (if supported. mark crossbuilds):
  - [ ] aarch64-musl
  - [ ] armv7l
  - [ ] armv6l-musl
-->


^ permalink raw reply	[flat|nested] 24+ messages in thread

end of thread, other threads:[~2022-05-18 14:46 UTC | newest]

Thread overview: 24+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-04-28 21:30 [PR PATCH] recutils: patch to fix recins not working outside of $TMPDIR loreb
2021-04-28 21:38 ` [PR REVIEW] " ericonr
2021-04-28 21:46 ` [PR PATCH] [Updated] " loreb
2021-04-28 21:48 ` [PR REVIEW] " loreb
2021-04-28 21:52 ` ericonr
2021-04-28 21:55 ` loreb
2021-04-28 21:58 ` loreb
2021-04-28 21:58 ` [PR PATCH] [Updated] " loreb
2021-05-17 13:12 ` loreb
2021-06-07 15:44 ` loreb
2021-06-08  2:29 ` ericonr
2021-06-08 12:54 ` [PR PATCH] [Updated] " loreb
2021-06-08 13:02 ` loreb
2021-06-08 13:21 ` [PR REVIEW] " ericonr
2021-06-08 13:21 ` ericonr
2021-06-08 13:21 ` ericonr
2021-06-08 14:33 ` [PR PATCH] [Updated] " loreb
2021-06-08 14:34 ` [PR REVIEW] " loreb
2021-06-08 14:35 ` loreb
2021-06-08 15:17 ` [PR PATCH] [Updated] " loreb
2021-06-08 15:19 ` [PR REVIEW] " loreb
2022-05-18  2:09 ` github-actions
2022-05-18 14:46 ` [PR PATCH] [Closed]: " loreb
2022-05-18 14:46 ` loreb

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).