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