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