xref: /illumos-gate/usr/src/tools/manlink/manlink.c (revision 78a75454a34d2b5e9b2c2967ecdaf9c5d3e6b030)
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