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