-- Leo's gemini proxy

-- Connecting to thrig.me:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini

Permissions Mirror


Someone asked how to mirror the permissions and ownership from one file to another, so here's a provisional script to do that. The question concerned OpenBSD so extended ACL and other such fancy things (such as selinux making your life hell) are not considered.


    #!/usr/bin/env perl
    # mirror-permissions - (try to) copy the permissions and ownership
    # from the first file given to the rest. this could easily be
    # written in C for better efficiency (or sh, for less efficiency)
    use strict;
    use warnings;
    die "Usage: mirror-permissions source-from target ..\n"
      unless @ARGV > 1;

    my $source = shift;
    my @sattr  = ( stat($source) )[ 2, 4, 5 ]
      or die "cannot stat '$source': $!\n";
    $sattr[0] &= 07777;    # filter off the file type

    for my $target (@ARGV) {
        my @tattr = ( stat($target) )[ 2, 4, 5, 8, 9 ]
          or die "cannot stat '$target': $!\n";

        my $changed = chown @sattr[ 1, 2 ], $target;
        die "chown failed '$target': $!\n" if $changed != 1;

        $changed = chmod $sattr[0], $target;
        if ( $changed != 1 ) {
            # whoops, try to restore the original
            chown @tattr[ 1, 2 ], $target;
            utime @tattr[ 3, 4 ], $target;
            die "chmod failed '$target': $!\n";
        }
    }

mirror-permissions.pl


chown and chmod are distinct calls; if the second of these calls fails then one might want to rollback the changes, or otherwise some of the files will have the incorrect permissions or ownership depending on which call you made first. The rollback could fail, and regardless a manual intervention would be necessary. Yet another way would be to have configuration management such as Ansible set the correct ownership and permissions on the files. Probably the above script would be run as root to minimize permissions problems (or to even be able to change the file ownership), though a close encounter with a NFS mount may leave even root unable to fix a file.


Another problem is that the file being operated on may change between the chown and the chmod. That is, the chown could be applied to the file "foo" (inode 42), and then before the chown can happen some other process renames a new inode (say, 640) to the name "foo", which the chmod then acts on. Unlikely, but possible, especially on a very busy system where multiple processes are fiddling with the files at the same time. Try to avoid this when designing a file-wrangling workflow? Configuration management usually is run multiple times to eventually make the system more consistent, but it might be good to eliminate race conditions where possible. (With sh forking out to chmod or chown the race condition will be that much bigger. Hence me not using sh much if I can avoid it.)


    // mperm - mirror ownership and permissions from one file to others

    #include <sys/stat.h>

    #include <err.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sysexits.h>
    #include <unistd.h>

    void emit_help(void);

    int
    main(int argc, char *argv[])
    {
    #ifdef __OpenBSD__
        if (pledge("chown fattr rpath stdio wpath", NULL) == -1)
            err(1, "pledge failed");
    #endif

        if (argc < 2) emit_help();
        argv++;

        struct stat src;
        if (stat(*argv, &src) == -1) err(1, "cannot stat '%s'", *argv);
        argv++;
        src.st_mode &= 07777; // filter off the file type

        while (*argv) {
            int fd = open(*argv, O_RDONLY);
            if (fd == -1) err(1, "cannot open '%s'", *argv);
            if (fchown(fd, src.st_uid, src.st_gid) == -1)
                err(1, "chown failed '%s'", *argv);
            if (fchmod(fd, src.st_mode) == -1)
                err(1, "chmod failed '%s'", *argv);
            argv++;
        }
    }

    void
    emit_help(void)
    {
        fputs("Usage: mperm source target ..\n", stderr);
        exit(EX_USAGE);
    }

mperm.c


The race condition is avoid by using fchown and fchmod on a file descriptor for the file. The ownership is changed first on the assumption it is more likely to fail, in which case we do not bother with the chmod. The "revert to the old state" code has been removed as it is probably a needless complication. On OpenBSD, we restrict the system calls the program can make.


One might ignore the owner when the script is run as a non-root user and only attempt to change the group of the file, but that's more complicated, and again on a NFS mount root may be unable to change ownerships. Therefore the script fails quickly on any error and hopefully someone can figure out what went wrong.


tags #c #perl #unix

-- Response ended

-- Page fetched on Tue May 21 21:02:17 2024