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