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