Skip to content

Instantly share code, notes, and snippets.

@eatnumber1
Last active August 18, 2024 22:47
Show Gist options
  • Save eatnumber1/f97ac7dad7b1f5a9721f to your computer and use it in GitHub Desktop.
Save eatnumber1/f97ac7dad7b1f5a9721f to your computer and use it in GitHub Desktop.
Command-line tool to call renameat2(2)
/*
* Copyright (c) 2023 Russell Harmon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>
#include <getopt.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdarg.h>
#include <errno.h>
#include <unistd.h>
#include <sys/syscall.h>
#ifndef RENAME_NOREPLACE
#define RENAME_NOREPLACE (1 << 0) /* Don't overwrite target */ // from include/uapi/linux/fs.h
#endif
#ifndef RENAME_EXCHANGE
#define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */ // from include/uapi/linux/fs.h
#endif
#ifndef SYS_renameat2
#if defined(__x86_64__)
#define SYS_renameat2 314 // from arch/x86/syscalls/syscall_64.tbl
#elif defined(__i386__)
#define SYS_renameat2 353 // from arch/x86/syscalls/syscall_32.tbl
#else
#error Architecture unsupported
#endif
#endif // ifndef SYS_renameat2
static void fatal_fprintf(FILE *out, const char * restrict fmt, ...) {
va_list ap;
va_start(ap, fmt);
int ret = vfprintf(out, fmt, ap);
int saved_errno = errno;
va_end(ap);
if (ret < 0) {
errno = saved_errno;
perror("vfprintf");
exit(EXIT_FAILURE);
}
}
static void print_usage(FILE *out, char *progname) {
fatal_fprintf(out, "Usage: %s [options] SOURCE DEST\n", progname);
fatal_fprintf(out, "Call the renameat2(2) system call.\n");
fatal_fprintf(out, "\n");
fatal_fprintf(out, " -h, --help This help message\n");
fatal_fprintf(out, " -e, --exchange Atomically exchange SOURCE and DEST\n");
fatal_fprintf(out, " -n, --noreplace Don't overwrite DEST if it already exists\n");
}
int main(int argc, char *argv[]) {
int flags = 0;
while (true) {
static const struct option long_options[] = {
{"exchange", no_argument, NULL, 'e'},
{"noreplace", no_argument, NULL, 'n'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
int c = getopt_long(argc, argv, "enh", long_options, NULL);
if (c == -1) {
break;
}
switch (c) {
case 'n':
flags |= RENAME_NOREPLACE;
break;
case 'e':
flags |= RENAME_EXCHANGE;
break;
case 'h':
print_usage(stdout, argv[0]);
exit(EXIT_SUCCESS);
case '?':
print_usage(stderr, argv[0]);
exit(EXIT_FAILURE);
default:
fprintf(stderr, "?? getopt returned character code 0%o ??\n", c);
exit(EXIT_FAILURE);
}
}
if (argc - optind != 2) {
print_usage(stderr, argv[0]);
exit(EXIT_FAILURE);
}
char *source = argv[optind], *dest = argv[optind + 1];
if (syscall(SYS_renameat2, AT_FDCWD, source, AT_FDCWD, dest, flags) != 0) {
perror("renameat2");
exit(EXIT_FAILURE);
}
}
@ailabs-software
Copy link

Thank you. I am finding this very useful a deploy script.

@Fedora-Core
Copy link

Super ! Saved my day ! Thank you !

@ianrrees
Copy link

Thanks!

@sijanec
Copy link

sijanec commented Jan 12, 2021

You could maybe try to submit this to coreutils or to debian ...

@eatnumber1
Copy link
Author

Considered it, but I'd rather see a flag added to mv

@sijanec
Copy link

sijanec commented Jan 13, 2021 via email

@martinetd
Copy link

@Apteryks
Copy link

@eatnumber1 Hi! Could you please add a free software license on this file? I'd like to package it in GNU Guix. Thanks!

@doug-gilbert
Copy link

Writing a program to clone syfs in Linux. The default is to clone /sys to /tmp/sys . Most ls* utilities that do data-mining in /sys do not have an option for an alternative to /sys (for sysfs access); examples: lsusb and lspci. So I thought what if I can swap /sys with /tmp/sys and google brought me here. Tried it (as root):

  # ./renameat2 -e /sys /tmp/sys
renameat2: Device or resource busy

Can anything be done or am I stretching things too far?

My lsscsi utiity can take an alternative to /sys and it shows my clone is (partially) working:

  # lsscsi -y /tmp/sys
[0:0:0:0]    disk    Linux    scsi_debug       0191  /dev/sda 
[1:0:0:0]    disk    Linux    scsi_debug       0191  /dev/sdb 
[2:0:0:0]    disk    Linux    scsi_debug       0191  /dev/sdc 
[N:0:1:1]    disk    SKHynix_HFS512GDE9X081N__1      /dev/nvme0n1

@eatnumber1
Copy link
Author

@Apteryks added an MIT license. Happy to dual-license it under the GPL too if you need.

@eatnumber1
Copy link
Author

@doug-gilbert from rename(2), it looks like renameat2 can return EBUSY when:

   EBUSY  The rename fails because oldpath or newpath is a directory
          that is in use by some process (perhaps as current working
          directory, or as root directory, or because it was open
          for reading) or is in use by the system (for example as a
          mount point), while the system considers this an error.
          (Note that there is no requirement to return EBUSY in such
          cases—there is nothing wrong with doing the rename anyway—
          but it is allowed to return EBUSY if the system cannot
          otherwise handle such situations.)

It's possible you can get it to work at boot with a custom initrd that sets up /sys in the way you want, but I suspect you'll have a lot of trouble getting it working after boot. However you could also potentially try and figure out if anything has /sys open (lsof -r /sys) and kill whatever has open fds into /sys and try again.

@Apteryks
Copy link

@Apteryks added an MIT license. Happy to dual-license it under the GPL too if you need.

I prefer GPL myself, as I like free software to remain free, but MIT being compatible with GPL, if that's your preference, it's OK! Thank you.

@doug-gilbert
Copy link

It's possible you can get it to work at boot with a custom initrd that sets up /sys in the way you want, but I suspect you'll have a lot of trouble getting it working after boot. However you could also potentially try and figure out if anything has /sys open (lsof -r /sys) and kill whatever has open fds into /sys and try again.

Thinking about it, lots of programs could be monitoring events in sysfs including systemd, udev, etc. But a clean swap could just carry all open fd_s with it, but obviously not without upsetting this system call. So I'm looking at plan "b": to start with making lsusb take a new command line option: --sysfs-root=<alternate_to_slash_sys> . So far can only fool lsusb -t because the rest of lsusb uses libusb which is carrying 30 years of crud making it hard to decipher. As soon as I write a manage, I'll push clone_pseudo_fs . It can clone /sys , /dev and /proc (when /proc/kmsg is excluded).

@martinetd
Copy link

@doug-gilbert I think mount --move should do what you need, but you'll need to unshare mounts first (mount --make-rprivate /); and hope there's no other mount namespace around using it (containers etc...)

@eatnumber1 thanks for the license from me as well, also using this!

@doug-gilbert
Copy link

@doug-gilbert I think mount --move should do what you need, but you'll need to unshare mounts first (mount --make-rprivate /); and hope there's no other mount namespace around using it (containers etc...)

mount --move /sys /tmp/ssys/
mount: /tmp/ssys: bad option; moving a mount residing under a shared mount is unsupported.
       dmesg(1) may have more information after failed mount system call.

Tried to move /tmp and got the same error message. Nothing was added to /var/log/syslog when those mounts were run (on Debian 12.0). Looks like another dead-end . Pushed https://github.com/doug-gilbert/clone_pseudo_fs but its README is wrong. Basically works with some rough edges.

@martinetd
Copy link

Try reading (or actually running commands) until the end:

you'll need to unshare mounts first (mount --make-rprivate /); and hope there's no other mount namespace around using it (containers etc...)

(if you want to do that just for some specific command, unshare -m is a great way to manipulate mount points locally without disturbing anyone as well; you could even just umount the old /sys or mount something else over it and nobody would care)

Anyway, it's off topic for renameat; will stop spamming everyone on this topic.

@mralusw
Copy link

mralusw commented May 12, 2024

Good news, util-linux starting with this 2023-12-14 commit has an exch command. It will take a while for it to trickle down though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment