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 = ". .. core RCSLOG tags TAGS RCS SCCS .make.state\
37  .nse_depinfo #* .#* cvslog.* ,* CVS CVS.adm .del-* *.a *.olb *.o *.obj\
38  *.so *.Z *~ *.old *.elc *.ln *.bak *.BAK *.orig *.rej *.exe _$* *$";
39 
40 #define IGN_GROW 16			/* grow the list by 16 elements at a
41 					 * time */
42 
43 /* Nonzero if we have encountered an -I ! directive, which means one should
44    no longer ask the server about what is in CVSROOTADM_IGNORE.  */
45 int ign_inhibit_server;
46 
47 
48 
49 /*
50  * To the "ignore list", add the hard-coded default ignored wildcards above,
51  * the wildcards found in $CVSROOT/CVSROOT/cvsignore, the wildcards found in
52  * ~/.cvsignore and the wildcards found in the CVSIGNORE environment
53  * variable.
54  */
55 void
ign_setup()56 ign_setup ()
57 {
58     char *home_dir;
59     char *tmp;
60 
61     ign_inhibit_server = 0;
62 
63     /* Start with default list and special case */
64     tmp = xstrdup (ign_default);
65     ign_add (tmp, 0);
66     free (tmp);
67 
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     {
74 	char *file = xmalloc (strlen (current_parsed_root->directory) + sizeof (CVSROOTADM)
75 			      + sizeof (CVSROOTADM_IGNORE) + 10);
76 	/* Then add entries found in repository, if it exists */
77 	(void) sprintf (file, "%s/%s/%s", current_parsed_root->directory,
78 			CVSROOTADM, CVSROOTADM_IGNORE);
79 	ign_add_file (file, 0);
80 	free (file);
81     }
82 
83     /* Then add entries found in home dir, (if user has one) and file exists */
84     home_dir = get_homedir ();
85     /* If we can't find a home directory, ignore ~/.cvsignore.  This may
86        make tracking down problems a bit of a pain, but on the other
87        hand it might be obnoxious to complain when CVS will function
88        just fine without .cvsignore (and many users won't even know what
89        .cvsignore is).  */
90     if (home_dir)
91     {
92 	char *file = strcat_filename_onto_homedir (home_dir, CVSDOTIGNORE);
93 	ign_add_file (file, 0);
94 	free (file);
95     }
96 
97     /* Then add entries found in CVSIGNORE environment variable. */
98     ign_add (getenv (IGNORE_ENV), 0);
99 
100     /* Later, add ignore entries found in -I arguments */
101 }
102 
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 (getline (&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 
171 
172 /* Parse a line of space-separated wildcards and add them to the list. */
173 void
ign_add(ign,hold)174 ign_add (ign, hold)
175     char *ign;
176     int hold;
177 {
178     if (!ign || !*ign)
179 	return;
180 
181     for (; *ign; ign++)
182     {
183 	char *mark;
184 	char save;
185 
186 	/* ignore whitespace before the token */
187 	if (isspace ((unsigned char) *ign))
188 	    continue;
189 
190 	/* If we have used up all the space, add some more.  Do this before
191 	   processing `!', since an "empty" list still contains the `CVS'
192 	   entry.  */
193 	if (ign_count >= ign_size)
194 	{
195 	    ign_size += IGN_GROW;
196 	    ign_list = (char **) xrealloc ((char *) ign_list,
197 					   (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 = xmalloc (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
ign_name(name)289 ign_name (name)
290     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(name)317 ign_dir_add (name)
318     char *name;
319 {
320     /* Make sure we've got the space for the entry.  */
321     if (dir_ign_current <= dir_ign_max)
322     {
323 	dir_ign_max += IGN_GROW;
324 	dir_ign_list =
325 	    (char **) xrealloc (dir_ign_list,
326 				(dir_ign_max + 1) * sizeof (char *));
327     }
328 
329     dir_ign_list[dir_ign_current++] = xstrdup (name);
330 }
331 
332 
333 /* Return nonzero if NAME is part of the list of directories to ignore.  */
334 
335 int
ignore_directory(name)336 ignore_directory (name)
337     const char *name;
338 {
339     int i;
340 
341     if (!dir_ign_list)
342 	return 0;
343 
344     i = dir_ign_current;
345     while (i--)
346     {
347 	if (strncmp (name, dir_ign_list[i], strlen (dir_ign_list[i])+1) == 0)
348 	    return 1;
349     }
350 
351     return 0;
352 }
353 
354 
355 
356 /*
357  * Process the current directory, looking for files not in ILIST and
358  * not on the global ignore list for this directory.  If we find one,
359  * call PROC passing it the name of the file and the update dir.
360  * ENTRIES is the entries list, which is used to identify known
361  * directories.  ENTRIES may be NULL, in which case we assume that any
362  * directory with a CVS administration directory is known.
363  */
364 void
ignore_files(ilist,entries,update_dir,proc)365 ignore_files (ilist, entries, update_dir, proc)
366     List *ilist;
367     List *entries;
368     const char *update_dir;
369     Ignore_proc proc;
370 {
371     int subdirs;
372     DIR *dirp;
373     struct dirent *dp;
374     struct stat sb;
375     char *file;
376     const char *xdir;
377     List *files;
378     Node *p;
379 
380     /* Set SUBDIRS if we have subdirectory information in ENTRIES.  */
381     if (entries == NULL)
382 	subdirs = 0;
383     else
384     {
385 	struct stickydirtag *sdtp = entries->list->data;
386 
387 	subdirs = sdtp == NULL || sdtp->subdirs;
388     }
389 
390     /* we get called with update_dir set to "." sometimes... strip it */
391     if (strcmp (update_dir, ".") == 0)
392 	xdir = "";
393     else
394 	xdir = update_dir;
395 
396     dirp = CVS_OPENDIR (".");
397     if (dirp == NULL)
398     {
399 	error (0, errno, "cannot open current directory");
400 	return;
401     }
402 
403     ign_add_file (CVSDOTIGNORE, 1);
404     wrap_add_file (CVSDOTWRAPPER, 1);
405 
406     /* Make a list for the files.  */
407     files = getlist ();
408 
409     while (errno = 0, (dp = CVS_READDIR (dirp)) != NULL)
410     {
411 	file = dp->d_name;
412 	if (strcmp (file, ".") == 0 || strcmp (file, "..") == 0)
413 	    continue;
414 	if (findnode_fn (ilist, file) != NULL)
415 	    continue;
416 	if (subdirs)
417 	{
418 	    Node *node;
419 
420 	    node = findnode_fn (entries, file);
421 	    if (node != NULL
422 		&& ((Entnode *) node->data)->type == ENT_SUBDIR)
423 	    {
424 		char *p;
425 		int dir;
426 
427 		/* For consistency with past behaviour, we only ignore
428 		   this directory if there is a CVS subdirectory.
429 		   This will normally be the case, but the user may
430 		   have messed up the working directory somehow.  */
431 		p = xmalloc (strlen (file) + sizeof CVSADM + 10);
432 		sprintf (p, "%s/%s", file, CVSADM);
433 		dir = isdir (p);
434 		free (p);
435 		if (dir)
436 		    continue;
437 	    }
438 	}
439 
440 	/* We could be ignoring FIFOs and other files which are neither
441 	   regular files nor directories here.  */
442 	if (ign_name (file))
443 	    continue;
444 
445 	if (
446 #ifdef DT_DIR
447 		dp->d_type != DT_UNKNOWN ||
448 #endif
449 		CVS_LSTAT (file, &sb) != -1)
450 	{
451 
452 	    if (
453 #ifdef DT_DIR
454 		dp->d_type == DT_DIR
455 		|| (dp->d_type == DT_UNKNOWN && S_ISDIR (sb.st_mode))
456 #else
457 		S_ISDIR (sb.st_mode)
458 #endif
459 		)
460 	    {
461 		if (! subdirs)
462 		{
463 		    char *temp;
464 
465 		    temp = xmalloc (strlen (file) + sizeof (CVSADM) + 10);
466 		    (void) sprintf (temp, "%s/%s", file, CVSADM);
467 		    if (isdir (temp))
468 		    {
469 			free (temp);
470 			continue;
471 		    }
472 		    free (temp);
473 		}
474 	    }
475 #ifdef S_ISLNK
476 	    else if (
477 #ifdef DT_DIR
478 		     dp->d_type == DT_LNK
479 		     || (dp->d_type == DT_UNKNOWN && S_ISLNK(sb.st_mode))
480 #else
481 		     S_ISLNK (sb.st_mode)
482 #endif
483 		     )
484 	    {
485 		continue;
486 	    }
487 #endif
488 	}
489 
490 	p = getnode ();
491 	p->type = FILES;
492 	p->key = xstrdup (file);
493 	(void) addnode (files, p);
494     }
495     if (errno != 0)
496 	error (0, errno, "error reading current directory");
497     (void) CVS_CLOSEDIR (dirp);
498 
499     sortlist (files, fsortcmp);
500     for (p = files->list->next; p != files->list; p = p->next)
501 	(*proc) (p->key, xdir);
502     dellist (&files);
503 }
504