1 /* Per-directory exclusion files for tar.
2 
3    Copyright 2014-2021 Free Software Foundation, Inc.
4 
5    This file is part of GNU tar.
6 
7    GNU tar is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 3 of the License, or
10    (at your option) any later version.
11 
12    GNU tar is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License
18    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 */
20 #include <system.h>
21 #include <quotearg.h>
22 #include <fnmatch.h>
23 #include <wordsplit.h>
24 #include "common.h"
25 
26 typedef void (*add_fn) (struct exclude *, char const *, int, void *);
27 
28 struct vcs_ignore_file
29 {
30   char const *filename;
31   int flags;
32   add_fn addfn;
33   void *(*initfn) (void *);
34   void *data;
35 };
36 
37 static struct vcs_ignore_file *get_vcs_ignore_file (const char *name);
38 
39 struct excfile
40 {
41   struct excfile *next;
42   int flags;
43   char name[1];
44 };
45 
46 static struct excfile *excfile_head, *excfile_tail;
47 
48 void
excfile_add(const char * name,int flags)49 excfile_add (const char *name, int flags)
50 {
51   struct excfile *p = xmalloc (sizeof (*p) + strlen (name));
52   p->next = NULL;
53   p->flags = flags;
54   strcpy (p->name, name);
55   if (excfile_tail)
56     excfile_tail->next = p;
57   else
58     excfile_head = p;
59   excfile_tail = p;
60 }
61 
62 struct exclist
63 {
64   struct exclist *next, *prev;
65   int flags;
66   struct exclude *excluded;
67 };
68 
69 void
info_attach_exclist(struct tar_stat_info * dir)70 info_attach_exclist (struct tar_stat_info *dir)
71 {
72   struct excfile *file;
73   struct exclist *head = NULL, *tail = NULL, *ent;
74   struct vcs_ignore_file *vcsfile;
75 
76   if (dir->exclude_list)
77     return;
78   for (file = excfile_head; file; file = file->next)
79     {
80       if (faccessat (dir ? dir->fd : chdir_fd, file->name, F_OK, 0) == 0)
81 	{
82 	  FILE *fp;
83 	  struct exclude *ex = NULL;
84 	  int fd = subfile_open (dir, file->name, O_RDONLY);
85 	  if (fd == -1)
86 	    {
87 	      open_error (file->name);
88 	      continue;
89 	    }
90 	  fp = fdopen (fd, "r");
91 	  if (!fp)
92 	    {
93 	      ERROR ((0, errno, _("%s: fdopen failed"), file->name));
94 	      close (fd);
95 	      continue;
96 	    }
97 
98 	  if (!ex)
99 	    ex = new_exclude ();
100 
101 	  vcsfile = get_vcs_ignore_file (file->name);
102 
103 	  if (vcsfile->initfn)
104 	    vcsfile->data = vcsfile->initfn (vcsfile->data);
105 
106 	  if (add_exclude_fp (vcsfile->addfn, ex, fp,
107 			      FNM_FILE_NAME|EXCLUDE_WILDCARDS|EXCLUDE_ANCHORED,
108 			      '\n',
109 			      vcsfile->data))
110 	    {
111 	      int e = errno;
112 	      FATAL_ERROR ((0, e, "%s", quotearg_colon (file->name)));
113 	    }
114 	  fclose (fp);
115 
116 	  ent = xmalloc (sizeof (*ent));
117 	  ent->excluded = ex;
118 	  ent->flags = file->flags == EXCL_DEFAULT
119 	               ? file->flags : vcsfile->flags;
120 	  ent->prev = tail;
121 	  ent->next = NULL;
122 
123 	  if (tail)
124 	    tail->next = ent;
125 	  else
126 	    head = ent;
127 	  tail = ent;
128 	}
129     }
130   dir->exclude_list = head;
131 }
132 
133 void
info_free_exclist(struct tar_stat_info * dir)134 info_free_exclist (struct tar_stat_info *dir)
135 {
136   struct exclist *ep = dir->exclude_list;
137 
138   while (ep)
139     {
140       struct exclist *next = ep->next;
141       free_exclude (ep->excluded);
142       free (ep);
143       ep = next;
144     }
145 
146   dir->exclude_list = NULL;
147 }
148 
149 
150 /* Return nonzero if file NAME is excluded.  */
151 bool
excluded_name(char const * name,struct tar_stat_info * st)152 excluded_name (char const *name, struct tar_stat_info *st)
153 {
154   struct exclist *ep;
155   const char *rname = NULL;
156   char *bname = NULL;
157   bool result;
158   int nr = 0;
159 
160   name += FILE_SYSTEM_PREFIX_LEN (name);
161 
162   /* Try global exclusion list first */
163   if (excluded_file_name (excluded, name))
164     return true;
165 
166   if (!st)
167     return false;
168 
169   for (result = false; st && !result; st = st->parent, nr = EXCL_NON_RECURSIVE)
170     {
171       for (ep = st->exclude_list; ep; ep = ep->next)
172 	{
173 	  if (ep->flags & nr)
174 	    continue;
175 	  if ((result = excluded_file_name (ep->excluded, name)))
176 	    break;
177 
178 	  if (!rname)
179 	    {
180 	      rname = name;
181 	      /* Skip leading ./ */
182 	      while (*rname == '.' && ISSLASH (rname[1]))
183 		rname += 2;
184 	    }
185 	  if ((result = excluded_file_name (ep->excluded, rname)))
186 	    break;
187 
188 	  if (!bname)
189 	    bname = base_name (name);
190 	  if ((result = excluded_file_name (ep->excluded, bname)))
191 	    break;
192 	}
193     }
194 
195   free (bname);
196 
197   return result;
198 }
199 
200 static void
cvs_addfn(struct exclude * ex,char const * pattern,int options,void * data)201 cvs_addfn (struct exclude *ex, char const *pattern, int options, void *data)
202 {
203   struct wordsplit ws;
204   size_t i;
205 
206   if (wordsplit (pattern, &ws,
207 		 WRDSF_NOVAR | WRDSF_NOCMD | WRDSF_SQUEEZE_DELIMS))
208     return;
209   for (i = 0; i < ws.ws_wordc; i++)
210     add_exclude (ex, ws.ws_wordv[i], options);
211   wordsplit_free (&ws);
212 }
213 
214 static void
git_addfn(struct exclude * ex,char const * pattern,int options,void * data)215 git_addfn (struct exclude *ex, char const *pattern, int options, void *data)
216 {
217   while (isspace (*pattern))
218     ++pattern;
219   if (*pattern == 0 || *pattern == '#')
220     return;
221   if (*pattern == '\\' && pattern[1] == '#')
222     ++pattern;
223   add_exclude (ex, pattern, options);
224 }
225 
226 static void
bzr_addfn(struct exclude * ex,char const * pattern,int options,void * data)227 bzr_addfn (struct exclude *ex, char const *pattern, int options, void *data)
228 {
229   while (isspace (*pattern))
230     ++pattern;
231   if (*pattern == 0 || *pattern == '#')
232     return;
233   if (*pattern == '!')
234     {
235       if (*++pattern == '!')
236 	++pattern;
237       else
238 	options |= EXCLUDE_INCLUDE;
239     }
240   /* FIXME: According to the docs, globbing patterns are rsync-style,
241             and regexps are perl-style. */
242   if (strncmp (pattern, "RE:", 3) == 0)
243     {
244       pattern += 3;
245       options &= ~EXCLUDE_WILDCARDS;
246       options |= EXCLUDE_REGEX;
247     }
248   add_exclude (ex, pattern, options);
249 }
250 
251 static void *
hg_initfn(void * data)252 hg_initfn (void *data)
253 {
254   static int hg_options;
255   int *hgopt = data ? data : &hg_options;
256   *hgopt = EXCLUDE_REGEX;
257   return hgopt;
258 }
259 
260 static void
hg_addfn(struct exclude * ex,char const * pattern,int options,void * data)261 hg_addfn (struct exclude *ex, char const *pattern, int options, void *data)
262 {
263   int *hgopt = data;
264   size_t len;
265 
266   while (isspace (*pattern))
267     ++pattern;
268   if (*pattern == 0 || *pattern == '#')
269     return;
270   if (strncmp (pattern, "syntax:", 7) == 0)
271     {
272       for (pattern += 7; isspace (*pattern); ++pattern)
273 	;
274       if (strcmp (pattern, "regexp") == 0)
275 	/* FIXME: Regexps must be perl-style */
276 	*hgopt = EXCLUDE_REGEX;
277       else if (strcmp (pattern, "glob") == 0)
278 	*hgopt = EXCLUDE_WILDCARDS;
279       /* Ignore unknown syntax */
280       return;
281     }
282 
283   len = strlen(pattern);
284   if (pattern[len-1] == '/')
285     {
286       char *p;
287 
288       --len;
289       p = xmalloc (len+1);
290       memcpy (p, pattern, len);
291       p[len] = 0;
292       pattern = p;
293       exclude_add_pattern_buffer (ex, p);
294       options |= FNM_LEADING_DIR|EXCLUDE_ALLOC;
295     }
296 
297   add_exclude (ex, pattern,
298 	       ((*hgopt == EXCLUDE_REGEX)
299 		? (options & ~EXCLUDE_WILDCARDS)
300 		: (options & ~EXCLUDE_REGEX)) | *hgopt);
301 }
302 
303 static struct vcs_ignore_file vcs_ignore_files[] = {
304   { ".cvsignore", EXCL_NON_RECURSIVE, cvs_addfn, NULL, NULL },
305   { ".gitignore", 0, git_addfn, NULL, NULL },
306   { ".bzrignore", 0, bzr_addfn, NULL, NULL },
307   { ".hgignore",  0, hg_addfn, hg_initfn, NULL },
308   { NULL, 0, git_addfn, NULL, NULL }
309 };
310 
311 static struct vcs_ignore_file *
get_vcs_ignore_file(const char * name)312 get_vcs_ignore_file (const char *name)
313 {
314   struct vcs_ignore_file *p;
315 
316   for (p = vcs_ignore_files; p->filename; p++)
317     if (strcmp (p->filename, name) == 0)
318       break;
319 
320   return p;
321 }
322 
323 void
exclude_vcs_ignores(void)324 exclude_vcs_ignores (void)
325 {
326   struct vcs_ignore_file *p;
327 
328   for (p = vcs_ignore_files; p->filename; p++)
329     excfile_add (p->filename, EXCL_DEFAULT);
330 }
331