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