From: Masatake YAMATO Date: Wed, 6 Dec 2023 15:17:26 +0000 (+0900) Subject: exch: new command exchaging two files atomically X-Git-Tag: v2.40-rc1~101 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=cd094a05880cf1132762c5f9724c0945557c7638;p=thirdparty%2Futil-linux.git exch: new command exchaging two files atomically An example session: $ echo A > a $ echo B > b $ ./exch a b $ cat a B $ cat b A Signed-off-by: Masatake YAMATO --- diff --git a/bash-completion/Makemodule.am b/bash-completion/Makemodule.am index c836d30b81..b4a6db1962 100644 --- a/bash-completion/Makemodule.am +++ b/bash-completion/Makemodule.am @@ -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 index 0000000000..a3fc994771 --- /dev/null +++ b/bash-completion/exch @@ -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 diff --git a/configure.ac b/configure.ac index 6ed1f78228..8c1aae80e3 100644 --- a/configure.ac +++ b/configure.ac @@ -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]) diff --git a/meson.build b/meson.build index 74cf366ae2..3992846ae5 100644 --- a/meson.build +++ b/meson.build @@ -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() diff --git a/misc-utils/Makemodule.am b/misc-utils/Makemodule.am index 9e23c24e90..42e30d14d5 100644 --- a/misc-utils/Makemodule.am +++ b/misc-utils/Makemodule.am @@ -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 index 0000000000..fef78e8c08 --- /dev/null +++ b/misc-utils/exch.1.adoc @@ -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 index 0000000000..0181bd9e6c --- /dev/null +++ b/misc-utils/exch.c @@ -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 + * + * 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 +#include + +#ifndef HAVE_RENAMEAT2 + +#include +#include + +#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; +} diff --git a/misc-utils/meson.build b/misc-utils/meson.build index ee1957890f..f1a9346931 100644 --- a/misc-utils/meson.build +++ b/misc-utils/meson.build @@ -136,6 +136,10 @@ install_data( install_dir : docdir, install_mode: 'rwxr-xr-x') +exch_sources = files( + 'exch.c', +) + fincore_sources = files( 'fincore.c', ) diff --git a/tests/commands.sh b/tests/commands.sh index 1d76abe48d..adf9544945 100644 --- a/tests/commands.sh +++ b/tests/commands.sh @@ -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 index 0000000000..646ff6d010 --- /dev/null +++ b/tests/expected/exch/exch @@ -0,0 +1,4 @@ +A +B +B +A diff --git a/tests/ts/exch/exch b/tests/ts/exch/exch new file mode 100755 index 0000000000..1c6a28e875 --- /dev/null +++ b/tests/ts/exch/exch @@ -0,0 +1,40 @@ +#!/bin/bash +# +# Copyright (C) 2023 Masatake YAMATO +# +# 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