1 /* $XConsortium: mkshadow.c /main/2 1996/12/04 10:11:51 swick $ */
2 /* mkshadow.c - make a "shadow copy" of a directory tree with symlinks.
3 Copyright 1990, 1993 Free Software Foundation, Inc.
5 Permission to use, copy, modify, and distribute this program for
6 any purpose and without fee is hereby granted, provided that this
7 copyright and permission notice appear on all copies, and that
8 notice be given that copying and distribution is by permission of
9 the Free Software Foundation. The Free Software Foundation makes
10 no representations about the suitability of this software for any
11 purpose. It is provided "as is" without expressed or implied
14 (The FSF has modified its usual distribution terms, for this file,
15 as a courtesy to the X project.) */
18 * Usage: mkshadow [-X exclude_file] [-x exclude_pattern] ... MASTER [SHADOW]
19 * Makes SHADOW be a "shadow copy" of MASTER. SHADOW defaults to the current
20 * directory. Sort of like a recursive copy of MASTER to SHADOW.
21 * However, symbolic links are used instead of actually
22 * copying (non-directory) files.
23 * Also, directories named RCS or SCCS are shared (with a symbolic link).
24 * Warning messages are printed for files (and directories) in .
25 * that don't match a corresponding file in MASTER (though
26 * symbolic links are silently removed).
27 * Also, a warning message is printed for non-directory files
28 * under SHADOW that are not symbolic links.
30 * Files and directories can be excluded from the sharing
31 * with the -X and -x flags. The flag `-x pattern' (or `-xpattern')
32 * means that mkshadow should ignore any file whose name matches
33 * the pattern. The pattern is a "globbing" pattern, i.e. the
34 * characters *?[^-] are interpreted as by the shell.
35 * If the pattern contains a '/' is is matched against the complete
36 * current path (relative to '.'); otherwise, it is matched
37 * against the last component of the path.
38 * A `-X filename' flag means to read a set of exclusion patterns
39 * from the named file, one pattern to a line.
41 * Originally written by Per Bothner at University of Wisconsin-Madison,
42 * inspired by the lndir script distributed with X11.
43 * Modified by Per Bothner <bothner@cygnus.com> November 1993
44 * to more-or-less follow Posix.
47 #include <sys/types.h>
56 #if defined(S_IFDIR) && !defined(S_ISDIR)
57 #define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
59 #if defined(S_IFLNK) && !defined(S_ISLNK)
60 #define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK)
66 #define MAXPATHLEN 1024
73 extern char * savedir();
78 if (errno) perror(msg ? msg : "");
79 else if (msg) fprintf(stderr, "mkshadow: %s\n", msg);
83 /* When handling symbolic links to relative directories,
84 * we need to prepend "../" to the "source".
85 * We preallocate MAX_DEPTH repetations of "../" using a simple trick.
88 #define PREPEND_BUFFER_SIZE (MAX_DEPTH*3)
89 char master_buffer[MAXPATHLEN+PREPEND_BUFFER_SIZE] =
90 "../../../../../../../../../../../../../../../../../../../../";
91 /* The logical start of the master_buffer is defined by
92 * master_start, which skips the fixed prepend area.
94 #define master_start (master_buffer+PREPEND_BUFFER_SIZE)
95 char shadow_buffer[MAXPATHLEN];
99 if (msg) fprintf(stderr, "%s\n", msg);
100 fprintf (stderr, "usage: mkshadow [-X exclude_file] [-x exclude_pattern]");
101 fprintf (stderr, " master [shadow]\n");
105 int exclude_count = 0;
106 char **exclude_patterns = NULL;
107 int exclude_limit = 0;
109 void add_exclude(pattern)
112 if (exclude_limit == 0) {
114 exclude_patterns = (char**)malloc(exclude_limit * sizeof(char*));
115 } else if (exclude_count + 1 >= exclude_limit) {
116 exclude_limit += 100;
117 exclude_patterns = (char**)realloc(exclude_patterns,
118 exclude_limit * sizeof(char*));
120 exclude_patterns[exclude_count] = pattern;
124 void add_exclude_file(name)
127 char buf[MAXPATHLEN];
128 FILE *file = fopen(name, "r");
129 if (file == NULL) fatal("failed to find -X (exclude) file");
132 char *str = fgets(buf, MAXPATHLEN, file);
133 if (str == NULL) break;
135 if (len && str[len-1] == '\n') str[--len] = 0;
137 str = (char*)malloc(len+1);
147 char *master_name = NULL;
148 char *shadow_name = NULL;
150 for (i = 1; i < argc; i++) {
151 if (argv[i][0] == '-') {
154 if (argv[i][2]) add_exclude_file(&argv[i][2]);
155 else if (++i >= argc) bad_args(NULL);
156 else add_exclude_file(argv[i]);
159 if (argv[i][2]) add_exclude(&argv[i][2]);
160 else if (++i >= argc) bad_args(NULL);
161 else add_exclude(argv[i]);
166 } else if (master_name == NULL)
167 master_name = argv[i];
168 else if (shadow_name == NULL)
169 shadow_name = argv[i];
170 else bad_args (NULL);
173 if (master_name == NULL) bad_args(NULL);
174 if (shadow_name == NULL)
176 else if ((shadow_name[0] != '.' || shadow_name[1])
177 && master_name[0] != '/') {
178 fprintf(stderr, "Shadowing a relative directory pathname to a \n");
179 fprintf(stderr, "shadow other than '.' is not supported!\n");
182 strcpy(shadow_buffer, shadow_name);
183 strcpy(master_start, master_name);
184 DoCopy(master_start, shadow_buffer, 0);
188 int compare_strings(ptr1, ptr2)
191 return strcmp(*ptr1, *ptr2);
194 void MakeLink(master, current, depth)
199 if (master[0] != '/') {
200 /* Source directory was specified with a relative pathname. */
201 if (master != master_start) {
202 fatal("Internal bug: bad string buffer use");
204 /* Pre-pend "../" depth times. This compensates for
205 * the directories we've entered. */
208 if (symlink(master, current)) {
209 fprintf(stderr, "Failed to create symbolic link %s->%s\n",
216 /* Get a sorted NULL_terminator array of (char*) using 'names'
217 * (created by save_dir) as data.
219 char ** get_name_pointers(names)
223 int names_buf_size = 64;
225 char ** pointers = (char**)malloc(names_buf_size * sizeof(char*));
226 if (!names || !pointers) fatal("virtual memory exhausted");
228 for (namep = names; *namep; namep += strlen(namep) + 1) {
229 if (n_names + 1 >= names_buf_size) {
231 pointers = (char**)realloc(pointers,
232 names_buf_size * sizeof(char*));
233 if (!pointers) fatal("virtual memory exhausted");
235 pointers[n_names++] = namep;
237 pointers[n_names] = 0;
238 qsort(pointers, n_names, sizeof(char*), compare_strings);
242 /* Recursively shadow the directory whose name is in MASTER
243 * (which is == MASTER_START) into the destination directory named CURRENT.
246 DoCopy(master, current, depth)
247 char *master; /* The source directory. */
248 char *current; /* The destination directory. */
251 struct stat stat_master, stat_current;
252 char **master_pointer, **current_pointer;
253 char **master_names, **current_names;
254 char *master_end, *current_end;
255 char *master_name_buf, *current_name_buf;
256 master_end = master + strlen(master);
257 current_end = current + strlen(current);
259 /* Get rid of terminal '/' */
260 if (master_end[-1] == '/' && master != master_end - 1)
262 if (current_end[-1] == '/' && current != current_end - 1)
265 if (depth >= MAX_DEPTH) {
267 "Nesting too deep (depth %d at %s). Probable circularity.\n",
272 master_name_buf = savedir(master, 500);
273 if (master_name_buf == NULL) {
274 fprintf(stderr, "Not enough memory or no such directory: %s\n",
278 current_name_buf = savedir(current, 500);
279 if (current_name_buf == NULL) {
280 fprintf(stderr, "Not enough memory or no such directory: %s\n",
285 master_names = get_name_pointers(master_name_buf);
286 current_names = get_name_pointers(current_name_buf);
288 master_pointer = master_names;
289 current_pointer = current_names;
292 int in_master, in_current;
294 if (*master_pointer == NULL && *current_pointer == NULL)
296 if (*master_pointer == NULL) cmp = 1;
297 else if (*current_pointer == NULL) cmp = -1;
298 else cmp = strcmp(*master_pointer, *current_pointer);
299 if (cmp < 0) { /* file only exists in master directory */
300 in_master = 1; in_current = 0;
301 } else if (cmp == 0) { /* file exists in both directories */
302 in_master = 1; in_current = 1;
303 } else { /* file only exists in current directory */
304 in_current = 1; in_master = 0;
306 cur_name = in_master ? *master_pointer : *current_pointer;
307 sprintf(master_end, "/%s", cur_name);
308 sprintf(current_end, "/%s", cur_name);
309 for (ipat = 0; ipat < exclude_count; ipat++) {
310 char *pat = exclude_patterns[ipat];
312 if (strchr(pat, '/')) cur = current + 2; /* Skip initial "./" */
314 if (wildmat(cur, pat)) goto skip;
317 if (lstat(master, &stat_master) != 0) fatal("stat failed");
319 if (lstat(current, &stat_current) != 0) fatal("stat failed");
320 if (in_current && !in_master) {
321 if (S_ISLNK(stat_current.st_mode))
322 if (unlink(current)) {
323 fprintf(stderr, "Failed to remove symbolic link %s.\n",
327 fprintf(stderr, "Removed symbolic link %s.\n",
331 "The file %s does not exist in the master tree.\n",
335 else if (S_ISDIR(stat_master.st_mode)
336 && strcmp(cur_name, "RCS") != 0
337 && strcmp(cur_name, "SCCS") != 0) {
339 if (mkdir(current, 0775)) fatal("mkdir failed");
341 else if (stat(current, &stat_current)) fatal("stat failed");
342 if (!in_current || stat_current.st_dev != stat_master.st_dev
343 || stat_current.st_ino != stat_master.st_ino)
344 DoCopy(master, current, depth+1);
346 fprintf(stderr, "Link %s is the same as directory %s.\n",
351 MakeLink(master, current, depth);
352 else if (!S_ISLNK(stat_current.st_mode)) {
353 fprintf(stderr, "Existing file %s is not a symbolc link.\n",
356 if (stat(current, &stat_current) || stat(master, &stat_master))
357 fatal("stat failed");
358 if (stat_current.st_dev != stat_master.st_dev
359 || stat_current.st_ino != stat_master.st_ino) {
360 fprintf(stderr, "Fixing incorrect symbolic link %s.\n",
362 if (unlink(current)) {
363 fprintf(stderr, "Failed to remove symbolic link %s.\n",
367 MakeLink(master, current, depth);
372 if (in_master) master_pointer++;
373 if (in_current) current_pointer++;
376 free(master_names); free(current_names);
377 free(master_name_buf); free(current_name_buf);