]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
exch: new command exchaging two files atomically
authorMasatake YAMATO <yamato@redhat.com>
Wed, 6 Dec 2023 15:17:26 +0000 (00:17 +0900)
committerKarel Zak <kzak@redhat.com>
Thu, 14 Dec 2023 08:47:00 +0000 (09:47 +0100)
An example session:

$ echo A > a
$ echo B > b
$ ./exch a b
$ cat a
B
$ cat b
A

Signed-off-by: Masatake YAMATO <yamato@redhat.com>
bash-completion/Makemodule.am
bash-completion/exch [new file with mode: 0644]
configure.ac
meson.build
misc-utils/Makemodule.am
misc-utils/exch.1.adoc [new file with mode: 0644]
misc-utils/exch.c [new file with mode: 0644]
misc-utils/meson.build
tests/commands.sh
tests/expected/exch/exch [new file with mode: 0644]
tests/ts/exch/exch [new file with mode: 0755]

index c836d30b81b392a81893d7fa1d04c31de49c2e5c..b4a6db1962b5d221a345b6e4ea5ce341b042b21c 100644 (file)
@@ -18,6 +18,9 @@ endif
 if BUILD_COLUMN
 dist_bashcompletion_DATA += bash-completion/column
 endif
+if BUILD_EXCH
+dist_bashcompletion_DATA += bash-completion/exch
+endif
 if BUILD_FINCORE
 dist_bashcompletion_DATA += bash-completion/fincore
 endif
diff --git a/bash-completion/exch b/bash-completion/exch
new file mode 100644 (file)
index 0000000..a3fc994
--- /dev/null
@@ -0,0 +1,26 @@
+_exch_module()
+{
+    local cur prev OPTS
+    COMPREPLY=()
+    cur="${COMP_WORDS[COMP_CWORD]}"
+    prev="${COMP_WORDS[COMP_CWORD-1]}"
+    case $prev in
+       '-h'|'--help'|'-V'|'--version')
+           return 0
+           ;;
+    esac
+    case $cur in
+       -*)
+           OPTS='
+                 --help
+                 --version'
+           COMPREPLY=( $(compgen -W "${OPTS[*]}" -- $cur) )
+           return 0
+       ;;
+    esac
+    local IFS=$'\n'
+    compopt -o filenames
+    COMPREPLY=( $(compgen -f -- $cur) )
+    return 0
+}
+complete -F _exch_module exch
index 6ed1f7822803d7e55942534446019454cc67d6e3..8c1aae80e3f36166582be2565ae29d1d1cb74212 100644 (file)
@@ -611,6 +611,7 @@ AC_CHECK_FUNCS([ \
        prctl \
        qsort_r \
        reallocarray \
+       renameat2 \
        rpmatch \
        scandirat \
        sched_setattr \
@@ -662,6 +663,12 @@ UL_CHECK_SYSCALL([pidfd_open])
 UL_CHECK_SYSCALL([pidfd_send_signal])
 UL_CHECK_SYSCALL([close_range])
 
+have_renameat2_syscall="yes"
+UL_CHECK_SYSCALL([renameat2])
+AS_IF([test "x$ul_cv_syscall_renameat2" = xno], [
+   have_renameat2_syscall="no"
+])
+
 UL_CHECK_SYSCALL([fsconfig])
 UL_CHECK_SYSCALL([fsmount])
 UL_CHECK_SYSCALL([fsopen])
@@ -2110,6 +2117,11 @@ dnl earlier than 2.x.
 UL_REQUIRES_HAVE([ctrlaltdel], [reboot], [reboot function])
 AM_CONDITIONAL([BUILD_CTRLALTDEL], [test "x$build_ctrlaltdel" = xyes])
 
+enable_exch=$have_renameat2_syscall
+UL_BUILD_INIT([exch])
+UL_REQUIRES_LINUX([exch])
+AM_CONDITIONAL([BUILD_EXCH], [test "x$build_exch" = xyes])
+
 UL_BUILD_INIT([fincore], [check])
 UL_REQUIRES_LINUX([fincore])
 UL_REQUIRES_BUILD([fincore], [libsmartcols])
index 74cf366ae265b6d57ca0ac87858ab37ac7e26cb7..3992846ae5b1d75ca7df0ee29562b4ef4b4166e0 100644 (file)
@@ -562,6 +562,7 @@ funcs = '''
         prctl
         qsort_r
         reallocarray
+        renameat2
         rpmatch
         scandirat
         setprogname
@@ -2981,6 +2982,21 @@ if not is_disabler(exe)
   bashcompletions += ['lsclocks']
 endif
 
+if conf.get('HAVE_RENAMEAT2').to_string() == '1'
+exe = executable(
+  'exch',
+  exch_sources,
+  include_directories : includes,
+  link_with : [lib_common],
+  install_dir : usrbin_exec_dir,
+  install : true)
+if not is_disabler(exe)
+  exes += exe
+  manadocs += ['misc-utils/exch.1.adoc']
+  bashcompletions += ['exch']
+endif
+endif
+
 ############################################################
 
 opt = not get_option('build-schedutils').disabled()
index 9e23c24e90c95cd0153bb5a4c93813563d0ad4de..42e30d14d5a0f154a1a5ef8c9eb9605eedd13cb5 100644 (file)
@@ -230,6 +230,15 @@ dist_getoptexample_DATA = \
        misc-utils/getopt-example.tcsh
 endif
 
+if BUILD_EXCH
+usrbin_exec_PROGRAMS += exch
+MANPAGES += misc-utils/exch.1
+dist_noinst_DATA += misc-utils/exch.1.adoc
+exch_SOURCES = misc-utils/exch.c
+exch_LDADD = $(LDADD) libcommon.la
+exch_CFLAGS = $(AM_CFLAGS)
+endif
+
 if BUILD_FINCORE
 usrbin_exec_PROGRAMS += fincore
 MANPAGES += misc-utils/fincore.1
diff --git a/misc-utils/exch.1.adoc b/misc-utils/exch.1.adoc
new file mode 100644 (file)
index 0000000..fef78e8
--- /dev/null
@@ -0,0 +1,51 @@
+//po4a: entry man manual
+= exch(1)
+:doctype: manpage
+:man manual: User Commands
+:man source: util-linux {release-version}
+:page-layout: base
+:command: exch
+
+== NAME
+
+exch - a command line interface for RENAME_EXCHANGE of renameat2(2)
+
+== SYNOPSIS
+
+*exch* _oldpath_ _newpath_
+
+== DESCRIPTION
+
+*exch* atomically exchanges oldpath and newpath.
+*exch* is a simple command wrapping *RENAME_EXCHANGE* of *renameat2*
+system call.
+
+
+== OPTIONS
+
+include::man-common/help-version.adoc[]
+
+== EXIT STATUS
+
+*exch* has the following exit status values:
+
+*0*::
+success
+*1*::
+unspecified failure
+
+== AUTHORS
+
+mailto:yamato@redhat.com[Masatake YAMATO]
+
+== SEE ALSO
+
+*renameat2*(2)
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/misc-utils/exch.c b/misc-utils/exch.c
new file mode 100644 (file)
index 0000000..0181bd9
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * exch(1) - a command line interface for RENAME_EXCHANGE of renameat2(2).
+ *
+ * Copyright (C) 2023 Red Hat, Inc. All rights reserved.
+ * Written by Masatake YAMATO <yamato@redhat.com>
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would 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, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "c.h"
+#include "nls.h"
+
+#include <fcntl.h>
+#include <getopt.h>
+
+#ifndef HAVE_RENAMEAT2
+
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#ifndef RENAME_EXCHANGE
+#define RENAME_EXCHANGE (1 << 1)
+#endif
+
+static inline int renameat2(int olddirfd, const char *oldpath,
+                           int newdirfd, const char *newpath, unsigned int flags)
+{
+       return syscall (SYS_renameat2, olddirfd, oldpath, newdirfd, newpath, flags);
+}
+
+#endif
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+       FILE *out = stdout;
+
+       fputs(USAGE_HEADER, out);
+       fprintf(out, _(" %s [options] oldpath newpath\n"), program_invocation_short_name);
+
+       fputs(USAGE_OPTIONS, out);
+       fprintf(out, USAGE_HELP_OPTIONS(30));
+
+       fprintf(out, USAGE_MAN_TAIL("exch(1)"));
+
+       exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+       int c;
+       int rc;
+
+       static const struct option longopts[] = {
+               { "version",    no_argument, NULL, 'V' },
+               { "help",       no_argument, NULL, 'h' },
+       };
+
+       setlocale(LC_ALL, "");
+       bindtextdomain(PACKAGE, LOCALEDIR);
+       textdomain(PACKAGE);
+
+       while ((c = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1) {
+               switch (c) {
+               case 'V':
+                       print_version(EXIT_SUCCESS);
+               case 'h':
+                       usage();
+               default:
+                       errtryhelp(EXIT_FAILURE);
+               }
+       }
+
+       if (argc - optind < 2) {
+               warnx(_("too few arguments"));
+               errtryhelp(EXIT_FAILURE);
+       } else if (argc - optind > 2) {
+               warnx(_("too many arguments"));
+               errtryhelp(EXIT_FAILURE);
+       }
+
+       rc = renameat2(AT_FDCWD, argv[optind],
+                      AT_FDCWD, argv[optind + 1], RENAME_EXCHANGE);
+       if (rc)
+               warn(_("failed to exchange \"%s\" and \"%s\""),
+                    argv[optind], argv[optind + 1]);
+
+       return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
index ee1957890f1c38fd0a73791c50ef2353a7b4d48a..f1a9346931f02397eb8a2b77cb7d834cb0486e78 100644 (file)
@@ -136,6 +136,10 @@ install_data(
   install_dir : docdir,
   install_mode: 'rwxr-xr-x')
 
+exch_sources = files(
+  'exch.c',
+)
+
 fincore_sources = files(
   'fincore.c',
 )
index 1d76abe48d1e9b7536115bb3af1624f738952705..adf9544945410f4d63e2db98d84b02e4ace1b275 100644 (file)
@@ -65,6 +65,7 @@ TS_CMD_COL=${TS_CMD_COL:-"${ts_commandsdir}col"}
 TS_CMD_COLUMN=${TS_CMD_COLUMN:-"${ts_commandsdir}column"}
 TS_CMD_ENOSYS=${TS_CMD_ENOSYS-"${ts_commandsdir}enosys"}
 TS_CMD_EJECT=${TS_CMD_EJECT-"${ts_commandsdir}eject"}
+TS_CMD_EXCH=${TS_CMD_EXCH-"${ts_commandsdir}exch"}
 TS_CMD_FALLOCATE=${TS_CMD_FALLOCATE-"${ts_commandsdir}fallocate"}
 TS_CMD_FDISK=${TS_CMD_FDISK-"${ts_commandsdir}fdisk"}
 TS_CMD_FLOCK=${TS_CMD_FLOCK-"${ts_commandsdir}flock"}
diff --git a/tests/expected/exch/exch b/tests/expected/exch/exch
new file mode 100644 (file)
index 0000000..646ff6d
--- /dev/null
@@ -0,0 +1,4 @@
+A
+B
+B
+A
diff --git a/tests/ts/exch/exch b/tests/ts/exch/exch
new file mode 100755 (executable)
index 0000000..1c6a28e
--- /dev/null
@@ -0,0 +1,40 @@
+#!/bin/bash
+#
+# Copyright (C) 2023 Masatake YAMATO <yamato@redhat.com>
+#
+# This file is part of util-linux.
+#
+# This file 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 of the License, or
+# (at your option) any later version.
+#
+# This file 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.
+#
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="exch"
+
+. "$TS_TOPDIR"/functions.sh
+ts_init "$*"
+
+ts_check_test_command "$TS_CMD_EXCH"
+
+ts_cd "$TS_OUTDIR"
+
+{
+    echo A >  a
+    echo B >  b
+
+    cat a
+    cat b
+
+    "$TS_CMD_EXCH" a b
+
+    cat a
+    cat b
+} > "$TS_OUTPUT" 2>&1
+
+ts_finalize