lndir.c revision baaedd75
1/* $Xorg: lndir.c,v 1.5 2001/02/09 02:03:17 xorgcvs Exp $ */
2/* Create shadow link tree (after X11R4 script of the same name)
3   Mark Reinhold (mbr@lcs.mit.edu)/3 January 1990 */
4
5/*
6Copyright (c) 1990, 1998 The Open Group
7
8Permission to use, copy, modify, distribute, and sell this software and its
9documentation for any purpose is hereby granted without fee, provided that
10the above copyright notice appear in all copies and that both that
11copyright notice and this permission notice appear in supporting
12documentation.
13
14The above copyright notice and this permission notice shall be included in
15all copies or substantial portions of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
20OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
21AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
24Except as contained in this notice, the name of The Open Group shall not be
25used in advertising or otherwise to promote the sale, use or other dealings
26in this Software without prior written authorization from The Open Group.
27
28*/
29/* $XFree86: xc/config/util/lndir.c,v 3.18 2003/06/24 15:44:45 eich Exp $ */
30
31/* From the original /bin/sh script:
32
33  Used to create a copy of the a directory tree that has links for all
34  non-directories (except, by default, those named BitKeeper, .git, .hg,
35  RCS, SCCS, .svn, CVS or CVS.adm).
36
37  If you are building the distribution on more than one machine,
38  you should use this technique.
39
40  If your master sources are located in /usr/local/src/X and you would like
41  your link tree to be in /usr/local/src/new-X, do the following:
42
43   	%  mkdir /usr/local/src/new-X
44	%  cd /usr/local/src/new-X
45   	%  lndir ../X
46*/
47
48#ifdef HAVE_CONFIG_H
49#include "config.h"
50#endif
51
52#include <X11/Xos.h>
53#include <X11/Xfuncproto.h>
54#include <stdio.h>
55#include <stdlib.h>
56#include <sys/stat.h>
57#include <limits.h>
58#include <sys/param.h>
59#include <errno.h>
60#include <dirent.h>
61#include <string.h>
62
63#ifndef MAXPATHLEN
64#define MAXPATHLEN 2048
65#endif
66
67#include <stdarg.h>
68
69static int silent = 0;			/* -silent */
70static int ignore_links = 0;		/* -ignorelinks */
71static int with_revinfo = 0;		/* -withrevinfo */
72
73static char *rcurdir;
74static char *curdir;
75
76static void _X_ATTRIBUTE_PRINTF(2,3) _X_NORETURN
77quit (int code, const char * fmt, ...)
78{
79    va_list args;
80    va_start(args, fmt);
81    vfprintf (stderr, fmt, args);
82    va_end(args);
83    putc ('\n', stderr);
84    exit (code);
85}
86
87static void _X_NORETURN
88quiterr (int code, const char *s)
89{
90    perror (s);
91    exit (code);
92}
93
94static void _X_ATTRIBUTE_PRINTF(1,2)
95msg (const char * fmt, ...)
96{
97    va_list args;
98    if (curdir) {
99	fprintf (stderr, "%s:\n", curdir);
100	curdir = 0;
101    }
102    va_start(args, fmt);
103    vfprintf (stderr, fmt, args);
104    va_end(args);
105    putc ('\n', stderr);
106}
107
108static void
109mperror (const char *s)
110{
111    if (curdir) {
112	fprintf (stderr, "%s:\n", curdir);
113	curdir = 0;
114    }
115    perror (s);
116}
117
118
119static int
120equivalent(char *lname, const char *rname, char **p)
121{
122    char *s;
123
124    if (!strcmp(lname, rname))
125	return 1;
126    for (s = lname; *s && (s = strchr(s, '/')); s++) {
127	while (s[1] == '/') {
128	    size_t len = strlen(s+2);
129	    memmove(s+1, s+2, len+1);
130	    if (p && *p) (*p)--;
131	}
132    }
133    return !strcmp(lname, rname);
134}
135
136
137/* Recursively create symbolic links from the current directory to the "from"
138   directory.  Assumes that files described by fs and ts are directories. */
139static int
140dodir (const char *fn,		/* name of "from" directory, either absolute or
141				   relative to cwd */
142       struct stat *fs,
143       struct stat *ts,		/* stats for the "from" directory and cwd */
144       int rel)			/* if true, prepend "../" to fn before using */
145{
146    DIR *df;
147    struct dirent *dp;
148    char buf[MAXPATHLEN + 1], *p;
149    char symbuf[MAXPATHLEN + 1];
150    char basesym[MAXPATHLEN + 1];
151    struct stat sb, sc;
152    unsigned int n_dirs;
153    ssize_t symlen;
154    ssize_t basesymlen = -1;
155    char *ocurdir;
156
157    if ((fs->st_dev == ts->st_dev) && (fs->st_ino == ts->st_ino)) {
158	msg ("%s: From and to directories are identical!", fn);
159	return 1;
160    }
161
162    if (rel)
163#ifdef HAVE_STRLCPY
164	strlcpy (buf, "../", sizeof(buf));
165#else
166	strcpy (buf, "../");
167#endif
168    else
169	buf[0] = '\0';
170#ifdef HAVE_STRLCAT
171    if (strlcat (buf, fn, sizeof(buf)) >= sizeof(buf))
172        quit(1, "Pathname too long: %s", fn);
173#else
174    strcat (buf, fn);
175#endif
176
177    if (!(df = opendir (buf))) {
178	msg ("%s: Cannot opendir", buf);
179	return 1;
180    }
181
182    p = buf + strlen (buf);
183    if (*(p - 1) != '/')
184	*p++ = '/';
185    n_dirs = fs->st_nlink;
186    if (n_dirs == 1)
187	n_dirs = INT_MAX;
188    while ((dp = readdir (df))) {
189	if (dp->d_name[strlen(dp->d_name) - 1] == '~')
190	    continue;
191#ifdef __APPLE__
192	/* Ignore these Mac OS X Finder data files */
193	if (!strcmp(dp->d_name, ".DS_Store") ||
194	    !strcmp(dp->d_name, "._.DS_Store"))
195	    continue;
196#endif
197
198#ifdef HAVE_STRLCAT
199	*p = '\0';
200	if (strlcat (buf, dp->d_name, sizeof(buf)) >= sizeof(buf)) {
201	    *p = '\0';
202	    quit(1, "Pathname too long: %s%s", buf, dp->d_name);
203	}
204#else
205	strcpy (p, dp->d_name);
206#endif
207
208	if (n_dirs > 0) {
209	    if (lstat (buf, &sb) < 0) {
210		mperror (buf);
211		continue;
212	    }
213
214	    if (S_ISDIR(sb.st_mode))
215	    {
216		/* directory */
217		n_dirs--;
218		if (dp->d_name[0] == '.' &&
219		    (dp->d_name[1] == '\0' || (dp->d_name[1] == '.' &&
220					       dp->d_name[2] == '\0')))
221		    continue;
222		if (!with_revinfo) {
223                    if (dp->d_name[0] == '.') {
224                        if (!strcmp (dp->d_name, ".git"))
225                            continue;
226                        if (!strcmp (dp->d_name, ".hg"))
227                            continue;
228                        if (!strcmp (dp->d_name, ".svn"))
229                            continue;
230                    } else {
231                        if (!strcmp (dp->d_name, "BitKeeper"))
232                            continue;
233                        if (!strcmp (dp->d_name, "RCS"))
234                            continue;
235                        if (!strcmp (dp->d_name, "SCCS"))
236                            continue;
237                        if (!strcmp (dp->d_name, "CVS"))
238                            continue;
239                        if (!strcmp (dp->d_name, "CVS.adm"))
240                            continue;
241                    }
242                }
243		ocurdir = rcurdir;
244		rcurdir = buf;
245		curdir = silent ? buf : (char *)0;
246		if (!silent)
247		    printf ("%s:\n", buf);
248		if ((stat (dp->d_name, &sc) < 0) && (errno == ENOENT)) {
249		    if (mkdir (dp->d_name, 0777) < 0 ||
250			stat (dp->d_name, &sc) < 0) {
251			mperror (dp->d_name);
252			curdir = rcurdir = ocurdir;
253			continue;
254		    }
255		}
256		if (readlink (dp->d_name, symbuf, sizeof(symbuf) - 1) >= 0) {
257		    msg ("%s: is a link instead of a directory", dp->d_name);
258		    curdir = rcurdir = ocurdir;
259		    continue;
260		}
261		if (chdir (dp->d_name) < 0) {
262		    mperror (dp->d_name);
263		    curdir = rcurdir = ocurdir;
264		    continue;
265		}
266		dodir (buf, &sb, &sc, (buf[0] != '/'));
267		if (chdir ("..") < 0)
268		    quiterr (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	/* The option to ignore links exists mostly because
280	   checking for them slows us down by 10-20%.
281	   But it is off by default because this really is a useful check. */
282	if (!ignore_links) {
283	    /* see if the file in the base tree was a symlink */
284	    basesymlen = readlink(buf, basesym, sizeof(basesym) - 1);
285	    if (basesymlen >= 0)
286		basesym[basesymlen] = '\0';
287	}
288
289	if (symlen >= 0) {
290	    /* Link exists in new tree.  Print message if it doesn't match. */
291	    if (!equivalent (basesymlen>=0 ? basesym : buf, symbuf,
292			     basesymlen>=0 ? (char **) 0 : &p))
293		msg ("%s: Keeping existing link to %s", dp->d_name, symbuf);
294	} else {
295	    char *sympath;
296
297	    if (basesymlen>=0) {
298		if ((buf[0] == '.') && (buf[1] == '.') && (buf[2] == '/') &&
299		    (basesym[0] == '.') && (basesym[1] == '.') &&
300		    (basesym[2] == '/')) {
301		    /* It becomes very tricky here. We have
302		       ../../bar/foo symlinked to ../xxx/yyy. We
303		       can't just use ../xxx/yyy. We have to use
304		       ../../bar/foo/../xxx/yyy.  */
305
306		    int i;
307		    char *start, *end;
308
309#ifdef HAVE_STRLCPY
310		    if (strlcpy (symbuf, buf, sizeof(symbuf)) >= sizeof(symbuf))
311			quit(1, "Pathname too long: %s", buf);
312#else
313		    strcpy (symbuf, buf);
314#endif
315		    /* Find the first char after "../" in symbuf.  */
316		    start = symbuf;
317		    do {
318			start += 3;
319		    } while ((start[0] == '.') && (start[1] == '.') &&
320			     (start[2] == '/'));
321
322		    /* Then try to eliminate "../"s in basesym.  */
323		    i = 0;
324		    end = strrchr (symbuf, '/');
325		    if (start < end) {
326			do {
327			    i += 3;
328			    end--;
329			    while ((*end != '/') && (end != start))
330				end--;
331			    if (end == start)
332				break;
333			} while ((basesym[i] == '.') &&
334				 (basesym[i + 1] == '.') &&
335				 (basesym[i + 2] == '/'));
336		    }
337		    if (*end == '/')
338			end++;
339#ifdef HAVE_STRLCPY
340		    *end = '\0';
341		    if (strlcat (symbuf, &basesym[i], sizeof(symbuf)) >=
342			sizeof(symbuf)) {
343			*end = '\0';
344			quit(1, "Pathname too long: %s%s", symbuf, &basesym[i]);
345		    }
346#else
347		    strcpy (end, &basesym[i]);
348#endif
349		    sympath = symbuf;
350		}
351		else
352		    sympath = basesym;
353	    }
354	    else
355		sympath = buf;
356	    if (symlink (sympath, dp->d_name) < 0)
357		mperror (dp->d_name);
358	}
359    }
360
361    closedir (df);
362    return 0;
363}
364
365int
366main (int argc, char *argv[])
367{
368    char *prog_name = argv[0];
369    const char *fn, *tn;
370    struct stat fs, ts;
371
372    while (++argv, --argc) {
373	if (strcmp(*argv, "-silent") == 0)
374	    silent = 1;
375	else if (strcmp(*argv, "-ignorelinks") == 0)
376	    ignore_links = 1;
377	else if (strcmp(*argv, "-withrevinfo") == 0)
378	    with_revinfo = 1;
379	else if (strcmp(*argv, "--") == 0) {
380	    ++argv, --argc;
381	    break;
382	}
383	else
384	    break;
385    }
386
387    if (argc < 1 || argc > 2)
388	quit (1,
389	      "usage: %s [-silent] [-ignorelinks] [-withrevinfo] fromdir [todir]",
390	      prog_name);
391
392    fn = argv[0];
393    if (argc == 2)
394	tn = argv[1];
395    else
396	tn = ".";
397
398    /* to directory */
399    if (stat (tn, &ts) < 0)
400	quiterr (1, tn);
401    if (!(S_ISDIR(ts.st_mode)))
402	quit (2, "%s: Not a directory", tn);
403    if (chdir (tn) < 0)
404	quiterr (1, tn);
405
406    /* from directory */
407    if (stat (fn, &fs) < 0)
408	quiterr (1, fn);
409    if (!(S_ISDIR(fs.st_mode)))
410	quit (2, "%s: Not a directory", fn);
411
412    exit (dodir (fn, &fs, &ts, 0));
413}
414