xref: /src/usr.bin/runat/runat.c (revision 4bfb7cfb70e62bc316de9e73cfd63a5c85541154)
1 /*
2  * Copyright (c) 2025 Rick Macklem
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  */
6 
7 #include <sys/param.h>
8 #include <sys/wait.h>
9 #include <err.h>
10 #include <fcntl.h>
11 #include <getopt.h>
12 #include <paths.h>
13 #include <signal.h>
14 #include <stdbool.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <unistd.h>
19 
20 static struct option longopts[] = {
21 	{ "nofollow", no_argument, NULL, 'h' },
22 	{ "-", no_argument, NULL, '-' },
23 	{ NULL, 0, NULL, 0}
24 };
25 
26 static void
usage(void)27 usage(void)
28 {
29 	(void)fprintf(stderr, "usage: runat [-h/--nofollow] [--] <file> "
30 	    "<shell command>\n");
31 	exit(1);
32 }
33 
34 int
main(int argc,char * argv[])35 main(int argc, char *argv[])
36 {
37 	int ch, file_fd, flags, i, longindex, nameddir_fd, outsiz;
38 	char *buf;
39 	long named_enabled;
40 	size_t pos, siz;
41 	bool done_args;
42 
43 	flags = O_RDONLY | O_CLOEXEC | O_PATH;
44 	done_args = false;
45 	while (!done_args && (ch = getopt_long(argc, argv, "h-", longopts,
46 	    &longindex)) != -1)
47 		switch (ch) {
48 		case 'h':
49 			flags |= O_NOFOLLOW;
50 			break;
51 		case '-':
52 			done_args = true;
53 			break;
54 		default:
55 			usage();
56 		}
57 	argv += optind;
58 	argc -= optind;
59 	if (argc < 2)
60 		usage();
61 
62 	named_enabled = pathconf(argv[0], _PC_NAMEDATTR_ENABLED);
63 	if (named_enabled <= 0)
64 		errx(1, "Named attributes not enabled for %s", argv[0]);
65 
66 	/* Generate the command string for "sh". */
67 	siz = 0;
68 	for (i = 1; i < argc; i++)
69 		siz += strlen(argv[i]) + 1;
70 	buf = malloc(siz);
71 	if (buf == NULL)
72 		errx(1, "Cannot malloc");
73 	pos = 0;
74 	for (i = 1; i < argc; i++) {
75 		outsiz = snprintf(&buf[pos], siz, "%s ", argv[i]);
76 		if (outsiz <= 0)
77 			errx(1, "snprintf failed: returned %d", outsiz);
78 		if ((size_t)outsiz > siz)
79 			errx(1, "Arguments too large");
80 		pos += outsiz;
81 		siz -= outsiz;
82 	}
83 	buf[pos - 1] = '\0';
84 
85 	file_fd = open(argv[0], flags, 0);
86 	if (file_fd < 0)
87 		err(1, "Cannot open %s", argv[0]);
88 	nameddir_fd = openat(file_fd, ".", O_RDONLY | O_CLOEXEC | O_NAMEDATTR,
89 	    0);
90 	if (nameddir_fd < 0)
91 		err(1, "Cannot open named attribute directory "
92 		    "for %s", argv[0]);
93 
94 	if (fchdir(nameddir_fd) < 0)
95 		err(1, "Cannot fchdir to named attribute dir");
96 
97 	execl(_PATH_BSHELL, "sh", "-c", buf, NULL);
98 	err(1, "Could not exec %s", _PATH_BSHELL);
99 }
100