root/usr.bin/runat/runat.c
/*
 * Copyright (c) 2025 Rick Macklem
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <sys/param.h>
#include <sys/wait.h>
#include <err.h>
#include <fcntl.h>
#include <getopt.h>
#include <paths.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static struct option longopts[] = {
        { "nofollow", no_argument, NULL, 'h' },
        { "-", no_argument, NULL, '-' },
        { NULL, 0, NULL, 0}
};

static void
usage(void)
{
        (void)fprintf(stderr, "usage: runat [-h/--nofollow] [--] <file> "
            "<shell command>\n");
        exit(1);
}

int
main(int argc, char *argv[])
{
        int ch, file_fd, flags, i, longindex, nameddir_fd, outsiz;
        char *buf;
        long named_enabled;
        size_t pos, siz;
        bool done_args;

        flags = O_RDONLY | O_CLOEXEC | O_PATH;
        done_args = false;
        while (!done_args && (ch = getopt_long(argc, argv, "h-", longopts,
            &longindex)) != -1)
                switch (ch) {
                case 'h':
                        flags |= O_NOFOLLOW;
                        break;
                case '-':
                        done_args = true;
                        break;
                default:
                        usage();
                }
        argv += optind;
        argc -= optind;
        if (argc < 2)
                usage();

        named_enabled = pathconf(argv[0], _PC_NAMEDATTR_ENABLED);
        if (named_enabled <= 0)
                errx(1, "Named attributes not enabled for %s", argv[0]);

        /* Generate the command string for "sh". */
        siz = 0;
        for (i = 1; i < argc; i++)
                siz += strlen(argv[i]) + 1;
        buf = malloc(siz);
        if (buf == NULL)
                errx(1, "Cannot malloc");
        pos = 0;
        for (i = 1; i < argc; i++) {
                outsiz = snprintf(&buf[pos], siz, "%s ", argv[i]);
                if (outsiz <= 0)
                        errx(1, "snprintf failed: returned %d", outsiz);
                if ((size_t)outsiz > siz)
                        errx(1, "Arguments too large");
                pos += outsiz;
                siz -= outsiz;
        }
        buf[pos - 1] = '\0';

        file_fd = open(argv[0], flags, 0);
        if (file_fd < 0)
                err(1, "Cannot open %s", argv[0]);
        nameddir_fd = openat(file_fd, ".", O_RDONLY | O_CLOEXEC | O_NAMEDATTR,
            0);
        if (nameddir_fd < 0)
                err(1, "Cannot open named attribute directory "
                    "for %s", argv[0]);

        if (fchdir(nameddir_fd) < 0)
                err(1, "Cannot fchdir to named attribute dir");

        execl(_PATH_BSHELL, "sh", "-c", buf, NULL);
        err(1, "Could not exec %s", _PATH_BSHELL);
}