1*78a75454SPatrick Mooney /*
2*78a75454SPatrick Mooney * This file and its contents are supplied under the terms of the
3*78a75454SPatrick Mooney * Common Development and Distribution License ("CDDL"), version 1.0.
4*78a75454SPatrick Mooney * You may only use this file in accordance with the terms of version
5*78a75454SPatrick Mooney * 1.0 of the CDDL.
6*78a75454SPatrick Mooney *
7*78a75454SPatrick Mooney * A full copy of the text of the CDDL should have accompanied this
8*78a75454SPatrick Mooney * source. A copy of the CDDL is also available via the Internet at
9*78a75454SPatrick Mooney * http://www.illumos.org/license/CDDL.
10*78a75454SPatrick Mooney */
11*78a75454SPatrick Mooney
12*78a75454SPatrick Mooney /*
13*78a75454SPatrick Mooney * Copyright 2025 Oxide Computer Company
14*78a75454SPatrick Mooney */
15*78a75454SPatrick Mooney
16*78a75454SPatrick Mooney /*
17*78a75454SPatrick Mooney * Parse a "Manlink" file and create the symlinks described within under a
18*78a75454SPatrick Mooney * specified destination directory.
19*78a75454SPatrick Mooney */
20*78a75454SPatrick Mooney
21*78a75454SPatrick Mooney
22*78a75454SPatrick Mooney #include <stdio.h>
23*78a75454SPatrick Mooney #include <stdlib.h>
24*78a75454SPatrick Mooney #include <unistd.h>
25*78a75454SPatrick Mooney #include <fcntl.h>
26*78a75454SPatrick Mooney #include <ctype.h>
27*78a75454SPatrick Mooney #include <err.h>
28*78a75454SPatrick Mooney #include <errno.h>
29*78a75454SPatrick Mooney #include <string.h>
30*78a75454SPatrick Mooney #include <stdbool.h>
31*78a75454SPatrick Mooney #include <libgen.h>
32*78a75454SPatrick Mooney #include <sys/debug.h>
33*78a75454SPatrick Mooney #include <sys/sysmacros.h>
34*78a75454SPatrick Mooney #include <sys/stat.h>
35*78a75454SPatrick Mooney
36*78a75454SPatrick Mooney static const char *progname = NULL;
37*78a75454SPatrick Mooney
38*78a75454SPatrick Mooney static void
usage(const char * fmt,...)39*78a75454SPatrick Mooney usage(const char *fmt, ...)
40*78a75454SPatrick Mooney {
41*78a75454SPatrick Mooney if (fmt != NULL) {
42*78a75454SPatrick Mooney va_list ap;
43*78a75454SPatrick Mooney
44*78a75454SPatrick Mooney va_start(ap, fmt);
45*78a75454SPatrick Mooney vwarnx(fmt, ap);
46*78a75454SPatrick Mooney va_end(ap);
47*78a75454SPatrick Mooney }
48*78a75454SPatrick Mooney
49*78a75454SPatrick Mooney (void) fprintf(stderr,
50*78a75454SPatrick Mooney "Usage: %s [opts] -d <destdir> <input(s)>\n\n"
51*78a75454SPatrick Mooney "Options:\n"
52*78a75454SPatrick Mooney "\t-n\tdry run\n",
53*78a75454SPatrick Mooney progname);
54*78a75454SPatrick Mooney }
55*78a75454SPatrick Mooney
56*78a75454SPatrick Mooney typedef struct manlink_iter {
57*78a75454SPatrick Mooney FILE *mi_fp;
58*78a75454SPatrick Mooney size_t mi_cap;
59*78a75454SPatrick Mooney char *mi_line;
60*78a75454SPatrick Mooney char *mi_tok_saveptr;
61*78a75454SPatrick Mooney char *mi_target;
62*78a75454SPatrick Mooney } manlink_iter_t;
63*78a75454SPatrick Mooney
64*78a75454SPatrick Mooney typedef struct manlink_iter_result {
65*78a75454SPatrick Mooney const char *mir_name;
66*78a75454SPatrick Mooney const char *mir_target;
67*78a75454SPatrick Mooney } manlink_iter_res_t;
68*78a75454SPatrick Mooney
69*78a75454SPatrick Mooney static bool
valid_name(const char * name)70*78a75454SPatrick Mooney valid_name(const char *name)
71*78a75454SPatrick Mooney {
72*78a75454SPatrick Mooney for (char c; (c = *name) != '\0'; name++) {
73*78a75454SPatrick Mooney if (c == '/') {
74*78a75454SPatrick Mooney /* Link names expected to be in base directory */
75*78a75454SPatrick Mooney return (false);
76*78a75454SPatrick Mooney }
77*78a75454SPatrick Mooney if (c == '#') {
78*78a75454SPatrick Mooney /* Should not contain comment character */
79*78a75454SPatrick Mooney return (false);
80*78a75454SPatrick Mooney }
81*78a75454SPatrick Mooney if (!isalnum(c) && !ispunct(c)) {
82*78a75454SPatrick Mooney /* Expect "normal" man page names */
83*78a75454SPatrick Mooney return (false);
84*78a75454SPatrick Mooney }
85*78a75454SPatrick Mooney }
86*78a75454SPatrick Mooney return (true);
87*78a75454SPatrick Mooney }
88*78a75454SPatrick Mooney
89*78a75454SPatrick Mooney static bool
valid_target(const char * target)90*78a75454SPatrick Mooney valid_target(const char *target)
91*78a75454SPatrick Mooney {
92*78a75454SPatrick Mooney for (char c; (c = *target) != '\0'; target++) {
93*78a75454SPatrick Mooney if (isalnum(c)) {
94*78a75454SPatrick Mooney continue;
95*78a75454SPatrick Mooney }
96*78a75454SPatrick Mooney switch (c) {
97*78a75454SPatrick Mooney case '.':
98*78a75454SPatrick Mooney case '_':
99*78a75454SPatrick Mooney case '-':
100*78a75454SPatrick Mooney case '/':
101*78a75454SPatrick Mooney break;
102*78a75454SPatrick Mooney default:
103*78a75454SPatrick Mooney return (false);
104*78a75454SPatrick Mooney }
105*78a75454SPatrick Mooney }
106*78a75454SPatrick Mooney return (true);
107*78a75454SPatrick Mooney }
108*78a75454SPatrick Mooney
109*78a75454SPatrick Mooney static void
link_iter_init(FILE * ifp,manlink_iter_t * itr)110*78a75454SPatrick Mooney link_iter_init(FILE *ifp, manlink_iter_t *itr)
111*78a75454SPatrick Mooney {
112*78a75454SPatrick Mooney itr->mi_fp = ifp;
113*78a75454SPatrick Mooney itr->mi_cap = 0;
114*78a75454SPatrick Mooney itr->mi_line = NULL;
115*78a75454SPatrick Mooney itr->mi_target = NULL;
116*78a75454SPatrick Mooney }
117*78a75454SPatrick Mooney
118*78a75454SPatrick Mooney static void
link_iter_fini(manlink_iter_t * itr)119*78a75454SPatrick Mooney link_iter_fini(manlink_iter_t *itr)
120*78a75454SPatrick Mooney {
121*78a75454SPatrick Mooney (void) fclose(itr->mi_fp);
122*78a75454SPatrick Mooney free(itr->mi_line);
123*78a75454SPatrick Mooney free(itr->mi_target);
124*78a75454SPatrick Mooney }
125*78a75454SPatrick Mooney
126*78a75454SPatrick Mooney static bool
link_iter_next(manlink_iter_t * itr,const char ** namep,const char ** targetp)127*78a75454SPatrick Mooney link_iter_next(manlink_iter_t *itr, const char **namep, const char **targetp)
128*78a75454SPatrick Mooney {
129*78a75454SPatrick Mooney ssize_t len;
130*78a75454SPatrick Mooney
131*78a75454SPatrick Mooney while ((len = getline(&itr->mi_line, &itr->mi_cap, itr->mi_fp)) >= 1) {
132*78a75454SPatrick Mooney char *line = itr->mi_line;
133*78a75454SPatrick Mooney
134*78a75454SPatrick Mooney /* Nuke the trailing newline (if any) */
135*78a75454SPatrick Mooney if (line[len - 1] == '\n') {
136*78a75454SPatrick Mooney line[len - 1] = '\0';
137*78a75454SPatrick Mooney }
138*78a75454SPatrick Mooney
139*78a75454SPatrick Mooney if (*line == '\0' || *line == '#') {
140*78a75454SPatrick Mooney /* Skip empty lines and comments */
141*78a75454SPatrick Mooney continue;
142*78a75454SPatrick Mooney } else if (*line == '\t') {
143*78a75454SPatrick Mooney const char *name = line + 1;
144*78a75454SPatrick Mooney
145*78a75454SPatrick Mooney if (!valid_name(name)) {
146*78a75454SPatrick Mooney err(EXIT_FAILURE,
147*78a75454SPatrick Mooney "Invalid link name: \"%s\"", name);
148*78a75454SPatrick Mooney } else if (itr->mi_target == NULL) {
149*78a75454SPatrick Mooney err(EXIT_FAILURE,
150*78a75454SPatrick Mooney "Link without preceding target");
151*78a75454SPatrick Mooney } else {
152*78a75454SPatrick Mooney *namep = name;
153*78a75454SPatrick Mooney *targetp = itr->mi_target;
154*78a75454SPatrick Mooney return (true);
155*78a75454SPatrick Mooney }
156*78a75454SPatrick Mooney } else {
157*78a75454SPatrick Mooney if (!valid_target(line)) {
158*78a75454SPatrick Mooney errx(EXIT_FAILURE,
159*78a75454SPatrick Mooney "Invalid link target \"%s\"", line);
160*78a75454SPatrick Mooney } else {
161*78a75454SPatrick Mooney free(itr->mi_target);
162*78a75454SPatrick Mooney itr->mi_target = strdup(line);
163*78a75454SPatrick Mooney continue;
164*78a75454SPatrick Mooney }
165*78a75454SPatrick Mooney }
166*78a75454SPatrick Mooney }
167*78a75454SPatrick Mooney
168*78a75454SPatrick Mooney return (false);
169*78a75454SPatrick Mooney }
170*78a75454SPatrick Mooney
171*78a75454SPatrick Mooney static void
do_links(const char * dest_dir,const char * input_file,bool dry_run)172*78a75454SPatrick Mooney do_links(const char *dest_dir, const char *input_file, bool dry_run)
173*78a75454SPatrick Mooney {
174*78a75454SPatrick Mooney int dfd = open(dest_dir, O_DIRECTORY | O_RDONLY, 0);
175*78a75454SPatrick Mooney if (dfd < 0) {
176*78a75454SPatrick Mooney err(EXIT_FAILURE, "Could not open destination dir %s",
177*78a75454SPatrick Mooney dest_dir);
178*78a75454SPatrick Mooney }
179*78a75454SPatrick Mooney
180*78a75454SPatrick Mooney FILE *ifp = fopen(input_file, "r");
181*78a75454SPatrick Mooney if (ifp == NULL) {
182*78a75454SPatrick Mooney err(EXIT_FAILURE, "Could not open input file %s", input_file);
183*78a75454SPatrick Mooney }
184*78a75454SPatrick Mooney
185*78a75454SPatrick Mooney manlink_iter_t iter;
186*78a75454SPatrick Mooney link_iter_init(ifp, &iter);
187*78a75454SPatrick Mooney
188*78a75454SPatrick Mooney const char *name, *target;
189*78a75454SPatrick Mooney while (link_iter_next(&iter, &name, &target)) {
190*78a75454SPatrick Mooney struct stat st;
191*78a75454SPatrick Mooney
192*78a75454SPatrick Mooney const int res = fstatat(dfd, name, &st, AT_SYMLINK_NOFOLLOW);
193*78a75454SPatrick Mooney if (res == 0) {
194*78a75454SPatrick Mooney if (S_ISLNK(st.st_mode)) {
195*78a75454SPatrick Mooney char buf[MAXPATHLEN];
196*78a75454SPatrick Mooney
197*78a75454SPatrick Mooney buf[0] = '\0';
198*78a75454SPatrick Mooney ssize_t len = readlinkat(dfd, name, buf,
199*78a75454SPatrick Mooney sizeof (buf));
200*78a75454SPatrick Mooney if (len > 0) {
201*78a75454SPatrick Mooney /* NUL terminate */
202*78a75454SPatrick Mooney buf[MIN(len, sizeof (buf) - 1)] = '\0';
203*78a75454SPatrick Mooney }
204*78a75454SPatrick Mooney
205*78a75454SPatrick Mooney if (strncmp(buf, target, sizeof (buf)) == 0) {
206*78a75454SPatrick Mooney continue;
207*78a75454SPatrick Mooney }
208*78a75454SPatrick Mooney }
209*78a75454SPatrick Mooney (void) printf("unlink %s/%s\n", dest_dir, name);
210*78a75454SPatrick Mooney if (!dry_run && unlinkat(dfd, name, 0) != 0) {
211*78a75454SPatrick Mooney err(EXIT_FAILURE,
212*78a75454SPatrick Mooney "Could not unlink conflicting file %s/%s",
213*78a75454SPatrick Mooney dest_dir, name);
214*78a75454SPatrick Mooney }
215*78a75454SPatrick Mooney } else if (errno != ENOENT) {
216*78a75454SPatrick Mooney err(EXIT_FAILURE, "stat() failure for link %s/%s",
217*78a75454SPatrick Mooney dest_dir, name);
218*78a75454SPatrick Mooney }
219*78a75454SPatrick Mooney
220*78a75454SPatrick Mooney (void) printf("link %s/%s -> %s\n", dest_dir, name, target);
221*78a75454SPatrick Mooney if (!dry_run && symlinkat(target, dfd, name) != 0) {
222*78a75454SPatrick Mooney err(EXIT_FAILURE, "failure to create link at %s/%s",
223*78a75454SPatrick Mooney dest_dir, name);
224*78a75454SPatrick Mooney }
225*78a75454SPatrick Mooney }
226*78a75454SPatrick Mooney link_iter_fini(&iter);
227*78a75454SPatrick Mooney }
228*78a75454SPatrick Mooney
229*78a75454SPatrick Mooney int
main(int argc,char * argv[])230*78a75454SPatrick Mooney main(int argc, char *argv[])
231*78a75454SPatrick Mooney {
232*78a75454SPatrick Mooney char *dest_dir = NULL;
233*78a75454SPatrick Mooney bool do_dry_run = false;
234*78a75454SPatrick Mooney progname = basename(argv[0]);
235*78a75454SPatrick Mooney
236*78a75454SPatrick Mooney int c;
237*78a75454SPatrick Mooney while ((c = getopt(argc, argv, "nd:")) != -1) {
238*78a75454SPatrick Mooney switch (c) {
239*78a75454SPatrick Mooney case 'd':
240*78a75454SPatrick Mooney dest_dir = optarg;
241*78a75454SPatrick Mooney break;
242*78a75454SPatrick Mooney case 'n':
243*78a75454SPatrick Mooney do_dry_run = true;
244*78a75454SPatrick Mooney break;
245*78a75454SPatrick Mooney case '?':
246*78a75454SPatrick Mooney usage("unknown option: -%c", optopt);
247*78a75454SPatrick Mooney exit(EXIT_FAILURE);
248*78a75454SPatrick Mooney }
249*78a75454SPatrick Mooney }
250*78a75454SPatrick Mooney argc -= optind;
251*78a75454SPatrick Mooney argv += optind;
252*78a75454SPatrick Mooney
253*78a75454SPatrick Mooney if (argc < 1) {
254*78a75454SPatrick Mooney usage("input file(s)");
255*78a75454SPatrick Mooney exit(EXIT_FAILURE);
256*78a75454SPatrick Mooney }
257*78a75454SPatrick Mooney for (uint_t i = 0; i < (uint_t)argc; i++) {
258*78a75454SPatrick Mooney do_links(dest_dir, argv[i], do_dry_run);
259*78a75454SPatrick Mooney }
260*78a75454SPatrick Mooney
261*78a75454SPatrick Mooney return (EXIT_SUCCESS);
262*78a75454SPatrick Mooney }
263