xref: /netbsd-src/external/gpl2/xcvs/dist/src/ignore.c (revision a5847cc334d9a7029f6352b847e9e8d71a0f9e0c)
1 /* This program is free software; you can redistribute it and/or modify
2    it under the terms of the GNU General Public License as published by
3    the Free Software Foundation; either version 2, or (at your option)
4    any later version.
5 
6    This program is distributed in the hope that it will be useful,
7    but WITHOUT ANY WARRANTY; without even the implied warranty of
8    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9    GNU General Public License for more details.  */
10 
11 /*
12  * .cvsignore file support contributed by David G. Grubbs <dgg@odi.com>
13  */
14 
15 #include "cvs.h"
16 #include "getline.h"
17 #include "lstat.h"
18 
19 /*
20  * Ignore file section.
21  *
22  *	"!" may be included any time to reset the list (i.e. ignore nothing);
23  *	"*" may be specified to ignore everything.  It stays as the first
24  *	    element forever, unless a "!" clears it out.
25  */
26 
27 static char **ign_list;			/* List of files to ignore in update
28 					 * and import */
29 static char **s_ign_list = NULL;
30 static int ign_count;			/* Number of active entries */
31 static int s_ign_count = 0;
32 static int ign_size;			/* This many slots available (plus
33 					 * one for a NULL) */
34 static int ign_hold = -1;		/* Index where first "temporary" item
35 					 * is held */
36 
37 const char *ign_default = ". .. core RCSLOG tags TAGS RCS SCCS .make.state\
38  .nse_depinfo #* .#* cvslog.* ,* CVS.adm .del-* *.a *.olb *.o *.obj\
39  *.so *.Z *~ *.old *.elc *.ln *.bak *.BAK *.orig *.rej *.exe _$* *$";
40 extern const char *cvsDir;
41 
42 #define IGN_GROW 16			/* grow the list by 16 elements at a
43 					 * time */
44 
45 /* Nonzero if we have encountered an -I ! directive, which means one should
46    no longer ask the server about what is in CVSROOTADM_IGNORE.  */
47 int ign_inhibit_server;
48 
49 
50 
51 /*
52  * To the "ignore list", add the hard-coded default ignored wildcards above,
53  * the wildcards found in $CVSROOT/CVSROOT/cvsignore, the wildcards found in
54  * ~/.cvsignore and the wildcards found in the CVSIGNORE environment
55  * variable.
56  */
57 void
58 ign_setup (void)
59 {
60     char *home_dir;
61     char *tmp;
62 
63     ign_inhibit_server = 0;
64 
65     /* Start with default list and special case */
66     tmp = xstrdup (ign_default);
67     ign_add (tmp, 0);
68     free (tmp);
69     tmp = xstrdup (cvsDir);
70     ign_add (tmp, 0);
71     free (tmp);
72 
73     /* The client handles another way, by (after it does its own ignore file
74        processing, and only if !ign_inhibit_server), letting the server
75        know about the files and letting it decide whether to ignore
76        them based on CVSROOOTADM_IGNORE.  */
77     if (!current_parsed_root->isremote)
78     {
79 	char *file = Xasprintf ("%s/%s/%s", current_parsed_root->directory,
80 				CVSROOTADM, CVSROOTADM_IGNORE);
81 	/* Then add entries found in repository, if it exists */
82 	ign_add_file (file, 0);
83 	free (file);
84     }
85 
86     /* Then add entries found in home dir, (if user has one) and file exists */
87     home_dir = get_homedir ();
88     /* If we can't find a home directory, ignore ~/.cvsignore.  This may
89        make tracking down problems a bit of a pain, but on the other
90        hand it might be obnoxious to complain when CVS will function
91        just fine without .cvsignore (and many users won't even know what
92        .cvsignore is).  */
93     if (home_dir)
94     {
95 	char *file = strcat_filename_onto_homedir (home_dir, CVSDOTIGNORE);
96 	ign_add_file (file, 0);
97 	free (file);
98     }
99 
100     /* Then add entries found in CVSIGNORE environment variable. */
101     ign_add (getenv (IGNORE_ENV), 0);
102 
103     /* Later, add ignore entries found in -I arguments */
104 }
105 
106 
107 
108 /*
109  * Open a file and read lines, feeding each line to a line parser. Arrange
110  * for keeping a temporary list of wildcards at the end, if the "hold"
111  * argument is set.
112  */
113 void
114 ign_add_file (char *file, int hold)
115 {
116     FILE *fp;
117     char *line = NULL;
118     size_t line_allocated = 0;
119 
120     /* restore the saved list (if any) */
121     if (s_ign_list != NULL)
122     {
123 	int i;
124 
125 	for (i = 0; i < s_ign_count; i++)
126 	    ign_list[i] = s_ign_list[i];
127 	ign_count = s_ign_count;
128 	ign_list[ign_count] = NULL;
129 
130 	s_ign_count = 0;
131 	free (s_ign_list);
132 	s_ign_list = NULL;
133     }
134 
135     /* is this a temporary ignore file? */
136     if (hold)
137     {
138 	/* re-set if we had already done a temporary file */
139 	if (ign_hold >= 0)
140 	{
141 	    int i;
142 
143 	    for (i = ign_hold; i < ign_count; i++)
144 		free (ign_list[i]);
145 	    ign_count = ign_hold;
146 	    ign_list[ign_count] = NULL;
147 	}
148 	else
149 	{
150 	    ign_hold = ign_count;
151 	}
152     }
153 
154     /* load the file */
155     fp = CVS_FOPEN (file, "r");
156     if (fp == NULL)
157     {
158 	if (! existence_error (errno))
159 	    error (0, errno, "cannot open %s", file);
160 	return;
161     }
162     while (getline (&line, &line_allocated, fp) >= 0)
163 	ign_add (line, hold);
164     if (ferror (fp))
165 	error (0, errno, "cannot read %s", file);
166     if (fclose (fp) < 0)
167 	error (0, errno, "cannot close %s", file);
168     free (line);
169 }
170 
171 
172 
173 /* Parse a line of space-separated wildcards and add them to the list. */
174 void
175 ign_add (char *ign, int hold)
176 {
177     if (!ign || !*ign)
178 	return;
179 
180     for (; *ign; ign++)
181     {
182 	char *mark;
183 	char save;
184 
185 	/* ignore whitespace before the token */
186 	if (isspace ((unsigned char) *ign))
187 	    continue;
188 
189 	/* If we have used up all the space, add some more.  Do this before
190 	   processing `!', since an "empty" list still contains the `CVS'
191 	   entry.  */
192 	if (ign_count >= ign_size)
193 	{
194 	    ign_size += IGN_GROW;
195 	    ign_list = xnrealloc (ign_list, ign_size + 1, sizeof (char *));
196 	}
197 
198 	/*
199 	 * if we find a single character !, we must re-set the ignore list
200 	 * (saving it if necessary).  We also catch * as a special case in a
201 	 * global ignore file as an optimization
202 	 */
203 	if ((!*(ign+1) || isspace ((unsigned char) *(ign+1)))
204 	    && (*ign == '!' || *ign == '*'))
205 	{
206 	    if (!hold)
207 	    {
208 		/* permanently reset the ignore list */
209 		int i;
210 
211 		for (i = 0; i < ign_count; i++)
212 		    free (ign_list[i]);
213 		ign_count = 1;
214 		/* Always ignore the "CVS" directory.  */
215 		ign_list[0] = xstrdup ("CVS");
216 		ign_list[1] = NULL;
217 
218 		/* if we are doing a '!', continue; otherwise add the '*' */
219 		if (*ign == '!')
220 		{
221 		    ign_inhibit_server = 1;
222 		    continue;
223 		}
224 	    }
225 	    else if (*ign == '!')
226 	    {
227 		/* temporarily reset the ignore list */
228 		int i;
229 
230 		if (ign_hold >= 0)
231 		{
232 		    for (i = ign_hold; i < ign_count; i++)
233 			free (ign_list[i]);
234 		    ign_hold = -1;
235 		}
236 		if (s_ign_list)
237 		{
238 		    /* Don't save the ignore list twice - if there are two
239 		     * bangs in a local .cvsignore file then we don't want to
240 		     * save the new list the first bang created.
241 		     *
242 		     * We still need to free the "new" ignore list.
243 		     */
244 		    for (i = 0; i < ign_count; i++)
245 			free (ign_list[i]);
246 		}
247 		else
248 		{
249 		    /* Save the ignore list for later.  */
250 		    s_ign_list = xnmalloc (ign_count, sizeof (char *));
251 		    for (i = 0; i < ign_count; i++)
252 			s_ign_list[i] = ign_list[i];
253 		    s_ign_count = ign_count;
254 		}
255 		ign_count = 1;
256 		    /* Always ignore the "CVS" directory.  */
257 		ign_list[0] = xstrdup ("CVS");
258 		ign_list[1] = NULL;
259 		continue;
260 	    }
261 	}
262 
263 	/* find the end of this token */
264 	for (mark = ign; *mark && !isspace ((unsigned char) *mark); mark++)
265 	     /* do nothing */ ;
266 
267 	save = *mark;
268 	*mark = '\0';
269 
270 	ign_list[ign_count++] = xstrdup (ign);
271 	ign_list[ign_count] = NULL;
272 
273 	*mark = save;
274 	if (save)
275 	    ign = mark;
276 	else
277 	    ign = mark - 1;
278     }
279 }
280 
281 
282 
283 /* Return true if the given filename should be ignored by update or import,
284  * else return false.
285  */
286 int
287 ign_name (char *name)
288 {
289     char **cpp = ign_list;
290 
291     if (cpp == NULL)
292 	return 0;
293 
294     while (*cpp)
295 	if (CVS_FNMATCH (*cpp++, name, 0) == 0)
296 	    return 1;
297 
298     return 0;
299 }
300 
301 
302 
303 /* FIXME: This list of dirs to ignore stuff seems not to be used.
304    Really?  send_dirent_proc and update_dirent_proc both call
305    ignore_directory and do_module calls ign_dir_add.  No doubt could
306    use some documentation/testsuite work.  */
307 
308 static char **dir_ign_list = NULL;
309 static int dir_ign_max = 0;
310 static int dir_ign_current = 0;
311 
312 /* Add a directory to list of dirs to ignore.  */
313 void
314 ign_dir_add (char *name)
315 {
316     /* Make sure we've got the space for the entry.  */
317     if (dir_ign_current <= dir_ign_max)
318     {
319 	dir_ign_max += IGN_GROW;
320 	dir_ign_list = xnrealloc (dir_ign_list,
321 				  dir_ign_max + 1, sizeof (char *));
322     }
323 
324     dir_ign_list[dir_ign_current++] = xstrdup (name);
325 }
326 
327 
328 /* Return nonzero if NAME is part of the list of directories to ignore.  */
329 
330 int
331 ignore_directory (const char *name)
332 {
333     int i;
334 
335     if (!dir_ign_list)
336 	return 0;
337 
338     i = dir_ign_current;
339     while (i--)
340     {
341 	if (strncmp (name, dir_ign_list[i], strlen (dir_ign_list[i])+1) == 0)
342 	    return 1;
343     }
344 
345     return 0;
346 }
347 
348 
349 
350 /*
351  * Process the current directory, looking for files not in ILIST and
352  * not on the global ignore list for this directory.  If we find one,
353  * call PROC passing it the name of the file and the update dir.
354  * ENTRIES is the entries list, which is used to identify known
355  * directories.  ENTRIES may be NULL, in which case we assume that any
356  * directory with a CVS administration directory is known.
357  */
358 void
359 ignore_files (List *ilist, List *entries, const char *update_dir,
360               Ignore_proc proc)
361 {
362     int subdirs;
363     DIR *dirp;
364     struct dirent *dp;
365     struct stat sb;
366     char *file;
367     const char *xdir;
368     List *files;
369     Node *p;
370 
371     /* Set SUBDIRS if we have subdirectory information in ENTRIES.  */
372     if (entries == NULL)
373 	subdirs = 0;
374     else
375     {
376 	struct stickydirtag *sdtp = entries->list->data;
377 
378 	subdirs = sdtp == NULL || sdtp->subdirs;
379     }
380 
381     /* we get called with update_dir set to "." sometimes... strip it */
382     if (strcmp (update_dir, ".") == 0)
383 	xdir = "";
384     else
385 	xdir = update_dir;
386 
387     dirp = CVS_OPENDIR (".");
388     if (dirp == NULL)
389     {
390 	error (0, errno, "cannot open current directory");
391 	return;
392     }
393 
394     ign_add_file (CVSDOTIGNORE, 1);
395     wrap_add_file (CVSDOTWRAPPER, 1);
396 
397     /* Make a list for the files.  */
398     files = getlist ();
399 
400     while (errno = 0, (dp = CVS_READDIR (dirp)) != NULL)
401     {
402 	file = dp->d_name;
403 	if (strcmp (file, ".") == 0 || strcmp (file, "..") == 0)
404 	    continue;
405 	if (findnode_fn (ilist, file) != NULL)
406 	    continue;
407 	if (subdirs)
408 	{
409 	    Node *node;
410 
411 	    node = findnode_fn (entries, file);
412 	    if (node != NULL
413 		&& ((Entnode *) node->data)->type == ENT_SUBDIR)
414 	    {
415 		char *p;
416 		int dir;
417 
418 		/* For consistency with past behaviour, we only ignore
419 		   this directory if there is a CVS subdirectory.
420 		   This will normally be the case, but the user may
421 		   have messed up the working directory somehow.  */
422 		p = Xasprintf ("%s/%s", file, CVSADM);
423 		dir = isdir (p);
424 		free (p);
425 		if (dir)
426 		    continue;
427 	    }
428 	}
429 
430 	/* We could be ignoring FIFOs and other files which are neither
431 	   regular files nor directories here.  */
432 	if (ign_name (file))
433 	    continue;
434 
435 	if (
436 #ifdef DT_DIR
437 	    dp->d_type != DT_UNKNOWN ||
438 #endif
439 	    lstat (file, &sb) != -1)
440 	{
441 
442 	    if (
443 #ifdef DT_DIR
444 		dp->d_type == DT_DIR
445 		|| (dp->d_type == DT_UNKNOWN && S_ISDIR (sb.st_mode))
446 #else
447 		S_ISDIR (sb.st_mode)
448 #endif
449 		)
450 	    {
451 		if (!subdirs)
452 		{
453 		    char *temp = Xasprintf ("%s/%s", file, CVSADM);
454 		    if (isdir (temp))
455 		    {
456 			free (temp);
457 			continue;
458 		    }
459 		    free (temp);
460 		}
461 	    }
462 #ifdef S_ISLNK
463 	    else if (
464 #ifdef DT_DIR
465 		     dp->d_type == DT_LNK
466 		     || (dp->d_type == DT_UNKNOWN && S_ISLNK (sb.st_mode))
467 #else
468 		     S_ISLNK (sb.st_mode)
469 #endif
470 		     )
471 	    {
472 		continue;
473 	    }
474 #endif
475 	}
476 
477 	p = getnode ();
478 	p->type = FILES;
479 	p->key = xstrdup (file);
480 	(void) addnode (files, p);
481     }
482     if (errno != 0)
483 	error (0, errno, "error reading current directory");
484     (void) CVS_CLOSEDIR (dirp);
485 
486     sortlist (files, fsortcmp);
487     for (p = files->list->next; p != files->list; p = p->next)
488 	(*proc) (p->key, xdir);
489     dellist (&files);
490 }
491