xref: /openbsd/usr.bin/lndir/lndir.c (revision 7b36286a)
1 /*	$OpenBSD: lndir.c,v 1.18 2006/11/15 22:23:11 miod Exp $	*/
2 /* $XConsortium: lndir.c /main/15 1995/08/30 10:56:18 gildea $ */
3 
4 /*
5  * Create shadow link tree (after X11R4 script of the same name)
6  * Mark Reinhold (mbr@lcs.mit.edu)/3 January 1990
7  */
8 
9 /*
10 Copyright (c) 1990,  X Consortium
11 
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18 
19 The above copyright notice and this permission notice shall be included in
20 all copies or substantial portions of the Software.
21 
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
25 X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
26 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
27 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 
29 Except as contained in this notice, the name of the X Consortium shall not be
30 used in advertising or otherwise to promote the sale, use or other dealings
31 in this Software without prior written authorization from the X Consortium.
32 
33 */
34 
35 /* From the original /bin/sh script:
36 
37   Used to create a copy of the a directory tree that has links for all
38   non-directories (except those named RCS, SCCS or CVS.adm).  If you are
39   building the distribution on more than one machine, you should use
40   this technique.
41 
42   If your master sources are located in /usr/local/src/X and you would like
43   your link tree to be in /usr/local/src/new-X, do the following:
44 
45 	%  mkdir /usr/local/src/new-X
46 	%  cd /usr/local/src/new-X
47 	%  lndir ../X
48 */
49 
50 #include <sys/param.h>
51 #include <sys/stat.h>
52 
53 #include <dirent.h>
54 #include <err.h>
55 #include <errno.h>
56 #include <stdarg.h>
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60 #include <unistd.h>
61 
62 extern char *__progname;
63 
64 int silent = 0;			/* -silent */
65 int ignore_links = 0;		/* -ignorelinks */
66 
67 char *rcurdir;
68 char *curdir;
69 
70 int equivalent(char *, char *);
71 void addexcept(char *);
72 int dodir(char *, struct stat *, struct stat *, int);
73 void usage(void);
74 
75 struct except {
76 	char *name;
77 	struct except *next;
78 };
79 
80 struct except *exceptions = (struct except *)NULL;
81 
82 int
83 main(int argc, char *argv[])
84 {
85 	struct stat fs, ts;
86 	char *fn, *tn;
87 
88 	while (++argv, --argc) {
89 		if ((strcmp(*argv, "-silent") == 0) ||
90 		    (strcmp(*argv, "-s") == 0))
91 			silent = 1;
92 		else if ((strcmp(*argv, "-ignorelinks") == 0) ||
93 		    (strcmp(*argv, "-i") == 0))
94 			ignore_links = 1;
95 		else if (strcmp(*argv, "-e") == 0) {
96 			++argv, --argc;
97 
98 			if (argc < 2)
99 				usage();
100 			addexcept(*argv);
101 		} else if (strcmp(*argv, "--") == 0) {
102 			++argv, --argc;
103 			break;
104 		} else
105 			break;
106 	}
107 
108 	if (argc < 1 || argc > 2)
109 		usage();
110 
111 	fn = argv[0];
112 	if (argc == 2)
113 		tn = argv[1];
114 	else
115 		tn = ".";
116 
117 	/* to directory */
118 	if (stat(tn, &ts) < 0)
119 		err(1, "%s", tn);
120 	if (!(S_ISDIR(ts.st_mode)))
121 		errx(2, "%s: %s", tn, strerror(ENOTDIR));
122 	if (chdir(tn) < 0)
123 		err(1, "%s", tn);
124 
125 	/* from directory */
126 	if (stat(fn, &fs) < 0)
127 		err(1, "%s", fn);
128 	if (!(S_ISDIR(fs.st_mode)))
129 		errx(2, "%s: %s", fn, strerror(ENOTDIR));
130 
131 	exit(dodir(fn, &fs, &ts, 0));
132 }
133 
134 int
135 equivalent(char *lname, char *rname)
136 {
137 	char *s, *ns;
138 
139 	if (strcmp(lname, rname) == 0)
140 		return(1);
141 	for (s = lname; *s && (s = strchr(s, '/')); s++) {
142 		if (s[1] == '/') {
143 			/* collapse multiple slashes in lname */
144 			for (ns = s + 1; *ns == '/'; ns++)
145 				;
146 			memmove(s + 1, ns, strlen(ns) + 1);
147 		}
148 	}
149 	return(strcmp(lname, rname) == 0);
150 }
151 
152 void
153 addexcept(char *name)
154 {
155 	struct except *new;
156 
157 	new = (struct except *)malloc(sizeof(struct except));
158 	if (new == (struct except *)NULL)
159 		err(1, NULL);
160 	new->name = strdup(name);
161 	if (new->name == (char *)NULL)
162 		err(1, NULL);
163 
164 	new->next = exceptions;
165 	exceptions = new;
166 }
167 
168 
169 /*
170  * Recursively create symbolic links from the current directory to the "from"
171  * directory.  Assumes that files described by fs and ts are directories.
172  */
173 #if 0
174 	char *fn;		/* name of "from" directory, either absolute or
175 				   relative to cwd */
176 	struct stat *fs, *ts;	/* stats for the "from" directory and cwd */
177 	int rel;		/* if true, prepend "../" to fn before using */
178 #endif
179 int
180 dodir(char *fn, struct stat *fs, struct stat *ts, int rel)
181 {
182 	char buf[MAXPATHLEN + 1], symbuf[MAXPATHLEN + 1], basesym[MAXPATHLEN + 1];
183 	int n_dirs, symlen, basesymlen = -1;
184 	struct stat sb, sc;
185 	struct except *cur;
186 	struct dirent *dp;
187 	char *ocurdir, *p;
188 	DIR *df;
189 
190 	if (fs->st_dev == ts->st_dev && fs->st_ino == ts->st_ino) {
191 		warnx("%s: From and to directories are identical!", fn);
192 		return(1);
193 	}
194 
195 	if (rel)
196 		strlcpy(buf, "../", sizeof(buf));
197 	else
198 		buf[0] = '\0';
199 	strlcat(buf, fn, sizeof(buf));
200 
201 	if (!(df = opendir(buf))) {
202 		warn("%s: Cannot opendir", buf);
203 		return(1);
204 	}
205 
206 	p = buf + strlen(buf);
207 	*p++ = '/';
208 	n_dirs = fs->st_nlink;
209 	while ((dp = readdir(df))) {
210 		if (dp->d_namlen == 0 || dp->d_name[dp->d_namlen - 1] == '~' ||
211 		    strncmp(dp->d_name, ".#", 2) == 0)
212 			continue;
213 		for (cur = exceptions; cur != NULL; cur = cur->next) {
214 			if (!strcmp(dp->d_name, cur->name))
215 				goto next;	/* can't continue */
216 		}
217 		strlcpy(p, dp->d_name, buf + sizeof(buf) - p);
218 
219 		if (n_dirs > 0) {
220 			if (stat(buf, &sb) < 0) {
221 				warn("%s", buf);
222 				continue;
223 			}
224 
225 			if (S_ISDIR(sb.st_mode)) {
226 				/* directory */
227 				n_dirs--;
228 				if (dp->d_name[0] == '.' &&
229 				    (dp->d_name[1] == '\0' || (dp->d_name[1] == '.' &&
230 				    dp->d_name[2] == '\0')))
231 					continue;
232 				if (!strcmp(dp->d_name, "RCS"))
233 					continue;
234 				if (!strcmp(dp->d_name, "SCCS"))
235 					continue;
236 				if (!strcmp(dp->d_name, "CVS"))
237 					continue;
238 				if (!strcmp(dp->d_name, "CVS.adm"))
239 					continue;
240 				ocurdir = rcurdir;
241 				rcurdir = buf;
242 				curdir = silent ? buf : NULL;
243 				if (!silent)
244 					printf("%s:\n", buf);
245 				if (stat(dp->d_name, &sc) < 0 && errno == ENOENT) {
246 					if (mkdir(dp->d_name, 0777) < 0 ||
247 					    stat(dp->d_name, &sc) < 0) {
248 						warn("%s", dp->d_name);
249 						curdir = rcurdir = ocurdir;
250 						continue;
251 					}
252 				}
253 				if (readlink(dp->d_name, symbuf,
254 				    sizeof(symbuf) - 1) >= 0) {
255 					fprintf(stderr,
256 					    "%s: is a link instead of a directory\n",
257 					    dp->d_name);
258 					curdir = rcurdir = ocurdir;
259 					continue;
260 				}
261 				if (chdir(dp->d_name) < 0) {
262 					warn("%s", dp->d_name);
263 					curdir = rcurdir = ocurdir;
264 					continue;
265 				}
266 				dodir(buf, &sb, &sc, (buf[0] != '/'));
267 				if (chdir("..") < 0)
268 					err(1, "..");
269 				curdir = rcurdir = ocurdir;
270 				continue;
271 			}
272 		}
273 
274 		/* non-directory */
275 		symlen = readlink(dp->d_name, symbuf, sizeof(symbuf) - 1);
276 		if (symlen >= 0)
277 			symbuf[symlen] = '\0';
278 
279 		/*
280 		 * The option to ignore links exists mostly because
281 		 * checking for them slows us down by 10-20%.
282 		 * But it is off by default because this really is a useful check.
283 		 */
284 		if (!ignore_links) {
285 			/* see if the file in the base tree was a symlink */
286 			basesymlen = readlink(buf, basesym, sizeof(basesym) - 1);
287 			if (basesymlen >= 0)
288 				basesym[basesymlen] = '\0';
289 		}
290 
291 		if (symlen >= 0) {
292 			/*
293 			 * Link exists in new tree.  Print message if
294 			 * it doesn't match.
295 			 */
296 			if (!equivalent(basesymlen>=0 ? basesym : buf, symbuf))
297 				fprintf(stderr,"%s: %s\n", dp->d_name, symbuf);
298 		} else {
299 			if (symlink(basesymlen>=0 ? basesym : buf, dp->d_name) < 0)
300 				warn("%s", dp->d_name);
301 		}
302 next:
303 	;
304 	}
305 
306 	closedir(df);
307 	return(0);
308 }
309 
310 void
311 usage(void)
312 {
313 	(void)fprintf(stderr, "usage: %s [-is] [-e exceptfile] fromdir [todir]\n",
314 	    __progname);
315 	exit(1);
316 }
317