My implementation of the ". insert-file" idiom for includes/excludes.
[rsync/rsync-patches.git] / filter.diff
1 This patch adds the ability to use ". INSERT" files in excludes and
2 includes.  If you specify a name without slashes, that filename will be
3 looked for in every directory and its rules will effect that directory
4 and its subdirectories.  Insert rules found inside a per-directory
5 include file are always just read in (they don't create any new per-dir
6 include/exclude files).
7
8 For example:
9
10   rsync -av --exclude='. .excl' from/ to
11
12 The above will look for a file named ".excl" in every directory of the
13 sender and will exclude (by default) files based on the rules found
14 therein.  If a file contains this:
15
16   + *.c
17   . another.file
18   - *.o
19
20 Then the file "another.file" will also be read (from that one dir) and
21 its rules inserted in between the surrounding lines.
22
23 Additionally, you can affect where the -C option's inclusion of the
24 .cvsignore file gets inserted into your rules by mentioning it as an
25 insertion.  For instance, specifying this:
26
27   rsync -avC --include=foo --exclude='. .cvsignore' --include='*.c' a/ b
28
29 This will insert all the .cvsignore rules in the middle of your rules
30 rather than at the end.  This allows their dir-specific rules to
31 supersede your general rules instead of being subservient to them.
32
33 ..wayne..
34
35 --- exclude.c   22 Apr 2004 22:17:15 -0000      1.71
36 +++ exclude.c   22 Apr 2004 23:45:51 -0000
37 @@ -30,31 +30,60 @@ extern int verbose;
38  extern int eol_nulls;
39  extern int list_only;
40  extern int recurse;
41 +extern int io_error;
42 +extern int sanitize_paths;
43  
44  extern char curr_dir[];
45  
46 -struct exclude_list_struct exclude_list = { 0, 0, "" };
47 -struct exclude_list_struct local_exclude_list = { 0, 0, "local-cvsignore " };
48 -struct exclude_list_struct server_exclude_list = { 0, 0, "server " };
49 +struct exclude_list_struct exclude_list = { 0, 0, 0, 0, "" };
50 +struct exclude_list_struct server_exclude_list = { 0, 0, 0, 0, "server " };
51  char *exclude_path_prefix = NULL;
52  
53 +static struct exclude_list_struct *local_exclude_lists;
54 +static int local_exclude_list_cnt;
55 +static char dirbuf[MAXPATHLEN];
56 +static unsigned int dirbuf_offset = 0;
57 +
58 +static void clear_exclude_list(struct exclude_list_struct *listp,
59 +                       struct exclude_struct *extra)
60 +{
61 +       listp->head = listp->extra = extra;
62 +       listp->tail = NULL;
63 +}
64 +
65  /** Build an exclude structure given a exclude pattern */
66  static void make_exclude(struct exclude_list_struct *listp, const char *pattern,
67 -                        int pat_len, int include)
68 +                        unsigned int pat_len, int mflags)
69  {
70         struct exclude_struct *ret;
71         const char *cp;
72 -       int ex_len;
73 +       unsigned int ex_len;
74 +
75 +       if (mflags & MATCHFLG_INSERT_FILE) {
76 +               struct exclude_struct *ex;
77 +               /* If the local include file was already mentioned, don't
78 +                * insert it again. */
79 +               for (ex = listp->head; ex; ex = ex->next) {
80 +                       if ((ex->match_flags & MATCHFLG_INSERT_FILE)
81 +                           && strlen(ex->pattern) == pat_len
82 +                           && strncmp(ex->pattern, pattern, pat_len) == 0)
83 +                               return;
84 +               }
85 +               if (pat_len == 10 && strncmp(pattern, ".cvsignore", 10) == 0) {
86 +                       mflags |= MATCHFLG_CVSIGNORE;
87 +                       mflags &= ~MATCHFLG_INCLUDE;
88 +               } else
89 +                       mflags &= ~MATCHFLG_CVSIGNORE;
90 +       }
91  
92         ret = new(struct exclude_struct);
93         if (!ret)
94                 out_of_memory("make_exclude");
95  
96         memset(ret, 0, sizeof ret[0]);
97 -       ret->include = include;
98  
99         if (exclude_path_prefix)
100 -               ret->match_flags |= MATCHFLG_ABS_PATH;
101 +               mflags |= MATCHFLG_ABS_PATH;
102         if (exclude_path_prefix && *pattern == '/')
103                 ex_len = strlen(exclude_path_prefix);
104         else
105 @@ -68,29 +97,49 @@ static void make_exclude(struct exclude_
106         pat_len += ex_len;
107  
108         if (strpbrk(ret->pattern, "*[?")) {
109 -               ret->match_flags |= MATCHFLG_WILD;
110 +               mflags |= MATCHFLG_WILD;
111                 if ((cp = strstr(ret->pattern, "**")) != NULL) {
112 -                       ret->match_flags |= MATCHFLG_WILD2;
113 +                       mflags |= MATCHFLG_WILD2;
114                         /* If the pattern starts with **, note that. */
115                         if (cp == ret->pattern)
116 -                               ret->match_flags |= MATCHFLG_WILD2_PREFIX;
117 +                               mflags |= MATCHFLG_WILD2_PREFIX;
118                 }
119         }
120  
121         if (pat_len > 1 && ret->pattern[pat_len-1] == '/') {
122                 ret->pattern[pat_len-1] = 0;
123 -               ret->directory = 1;
124 +               mflags |= MATCHFLG_DIRECTORY;
125         }
126  
127         for (cp = ret->pattern; (cp = strchr(cp, '/')) != NULL; cp++)
128                 ret->slash_cnt++;
129  
130 +       ret->next = listp->extra;
131 +
132         if (!listp->tail)
133                 listp->head = listp->tail = ret;
134         else {
135                 listp->tail->next = ret;
136                 listp->tail = ret;
137         }
138 +
139 +       if (mflags & MATCHFLG_INSERT_FILE) {
140 +               struct exclude_list_struct *lp;
141 +               int ndx = local_exclude_list_cnt++;
142 +               local_exclude_lists = realloc_array(local_exclude_lists,
143 +                   struct exclude_list_struct, local_exclude_list_cnt);
144 +               if (!local_exclude_lists)
145 +                       out_of_memory("make_exclude");
146 +               lp = &local_exclude_lists[ndx];
147 +               clear_exclude_list(lp, NULL);
148 +               if (asprintf(&lp->debug_type, "local %s ",
149 +                   ret->pattern) < 0)
150 +                       out_of_memory("make_exclude");
151 +               lp->parent = ret;
152 +               ret->slash_cnt = ndx;
153 +       }
154 +
155 +       ret->match_flags = mflags;
156  }
157  
158  static void free_exclude(struct exclude_struct *ex)
159 @@ -99,7 +148,7 @@ static void free_exclude(struct exclude_
160         free(ex);
161  }
162  
163 -void free_exclude_list(struct exclude_list_struct *listp)
164 +static void free_exclude_list(struct exclude_list_struct *listp)
165  {
166         struct exclude_struct *ent, *next;
167  
168 @@ -108,12 +157,72 @@ void free_exclude_list(struct exclude_li
169                         who_am_i(), listp->debug_type);
170         }
171  
172 +       if (listp->extra) {
173 +               if (listp->tail)
174 +                       listp->tail->next = NULL;
175 +               else
176 +                       listp->head = NULL;
177 +       }
178 +
179         for (ent = listp->head; ent; ent = next) {
180                 next = ent->next;
181                 free_exclude(ent);
182         }
183  
184 -       listp->head = listp->tail = NULL;
185 +       clear_exclude_list(listp, NULL);
186 +}
187 +
188 +void *push_local_excludes(char *fname, unsigned int offset)
189 +{
190 +       int i;
191 +       struct exclude_list_struct *mem = new_array(struct exclude_list_struct,
192 +                                                   local_exclude_list_cnt);
193 +       if (!mem)
194 +               out_of_memory("push_local_excludes");
195 +
196 +       memcpy(mem, local_exclude_lists,
197 +           sizeof (struct exclude_list_struct) * local_exclude_list_cnt);
198 +
199 +       memcpy(dirbuf, fname, offset);
200 +       dirbuf_offset = offset;
201 +
202 +       for (i = 0; i < local_exclude_list_cnt; i++) {
203 +               struct exclude_list_struct *listp = &local_exclude_lists[i];
204 +               struct exclude_struct *extra;
205 +               char *file = listp->parent->pattern;
206 +               int flags;
207 +               if (listp->parent->match_flags & MATCHFLG_CVSIGNORE) {
208 +                       flags = XFLG_WORD_SPLIT | XFLG_NO_PREFIXES;
209 +                       extra = NULL;
210 +               } else {
211 +                       flags = 0;
212 +                       extra = listp->head; /* subdirs inherit our rules */
213 +               }
214 +               clear_exclude_list(listp, extra);
215 +               if (strlcpy(fname +  offset, file, MAXPATHLEN - offset)
216 +                   < MAXPATHLEN - offset) {
217 +                       add_exclude_file(listp, fname, flags);
218 +               } else {
219 +                       io_error |= IOERR_GENERAL;
220 +                       rprintf(FINFO,
221 +                           "cannot add local excludes in long-named directory %s\n",
222 +                           full_fname(fname));
223 +               }
224 +       }
225 +
226 +       return (void *) mem;
227 +}
228 +
229 +void pop_local_excludes(void *mem)
230 +{
231 +       int i;
232 +       for (i = 0; i < local_exclude_list_cnt; i++) {
233 +               struct exclude_list_struct *listp = &local_exclude_lists[i];
234 +               free_exclude_list(listp);
235 +       }
236 +       memcpy(local_exclude_lists, mem,
237 +           sizeof (struct exclude_list_struct) * local_exclude_list_cnt);
238 +       free(mem);
239  }
240  
241  static int check_one_exclude(char *name, struct exclude_struct *ex,
242 @@ -139,7 +248,8 @@ static int check_one_exclude(char *name,
243  
244         if (!name[0]) return 0;
245  
246 -       if (ex->directory && !name_is_dir) return 0;
247 +       if ((ex->match_flags & MATCHFLG_DIRECTORY) && !name_is_dir)
248 +               return 0;
249  
250         if (*pattern == '/') {
251                 match_start = 1;
252 @@ -206,9 +316,11 @@ static void report_exclude_result(char c
253  
254         if (verbose >= 2) {
255                 rprintf(FINFO, "[%s] %scluding %s %s because of %spattern %s%s\n",
256 -                       who_am_i(), ent->include ? "in" : "ex",
257 +                       who_am_i(),
258 +                       ent->match_flags & MATCHFLG_INCLUDE ? "in" : "ex",
259                         name_is_dir ? "directory" : "file", name, type,
260 -                       ent->pattern, ent->directory ? "/" : "");
261 +                       ent->pattern,
262 +                       ent->match_flags & MATCHFLG_DIRECTORY ? "/" : "");
263         }
264  }
265  
266 @@ -223,10 +335,18 @@ int check_exclude(struct exclude_list_st
267         struct exclude_struct *ent;
268  
269         for (ent = listp->head; ent; ent = ent->next) {
270 +               if (ent->match_flags & MATCHFLG_INSERT_FILE) {
271 +                       struct exclude_list_struct *lp
272 +                           = &local_exclude_lists[ent->slash_cnt];
273 +                       int rc = check_exclude(lp, name, name_is_dir);
274 +                       if (rc)
275 +                               return rc;
276 +                       continue;
277 +               }
278                 if (check_one_exclude(name, ent, name_is_dir)) {
279                         report_exclude_result(name, ent, name_is_dir,
280                                               listp->debug_type);
281 -                       return ent->include ? 1 : -1;
282 +                       return (ent->match_flags & MATCHFLG_INCLUDE) ? 1 : -1;
283                 }
284         }
285  
286 @@ -242,11 +362,11 @@ int check_exclude(struct exclude_list_st
287   * *incl_ptr value will be 1 for an include, 0 for an exclude, and -1 for
288   * the list-clearing "!" token.
289   */
290 -static const char *get_exclude_tok(const char *p, int *len_ptr, int *incl_ptr,
291 +static const char *get_exclude_tok(const char *p, int *len_ptr, int *flag_ptr,
292                                    int xflags)
293  {
294         const unsigned char *s = (const unsigned char *)p;
295 -       int len;
296 +       int len, mflags = 0;
297  
298         if (xflags & XFLG_WORD_SPLIT) {
299                 /* Skip over any initial whitespace. */
300 @@ -256,13 +376,19 @@ static const char *get_exclude_tok(const
301                 p = (const char *)s;
302         }
303  
304 -       /* Is this a '+' or '-' followed by a space (not whitespace)? */
305 +       /* Is this a +/-/. followed by a space (not whitespace)? */
306         if (!(xflags & XFLG_NO_PREFIXES)
307 -           && (*s == '-' || *s == '+') && s[1] == ' ') {
308 -               *incl_ptr = *s == '+';
309 +           && (*s == '-' || *s == '+' || *s == '.') && s[1] == ' ') {
310 +               if (*s == '+')
311 +                       mflags |= MATCHFLG_INCLUDE;
312 +               else if (*s == '.') {
313 +                       mflags |= MATCHFLG_INSERT_FILE;
314 +                       if (xflags & XFLG_DEF_INCLUDE)
315 +                               mflags |= MATCHFLG_INCLUDE;
316 +               }
317                 s += 2;
318 -       } else
319 -               *incl_ptr = xflags & XFLG_DEF_INCLUDE;
320 +       } else if (xflags & XFLG_DEF_INCLUDE)
321 +               mflags |= MATCHFLG_INCLUDE;
322  
323         if (xflags & XFLG_WORD_SPLIT) {
324                 const unsigned char *cp = s;
325 @@ -274,9 +400,10 @@ static const char *get_exclude_tok(const
326                 len = strlen(s);
327  
328         if (*p == '!' && len == 1 && !(xflags & XFLG_NO_PREFIXES))
329 -               *incl_ptr = -1;
330 +               mflags |= MATCHFLG_CLEAR_LIST;
331  
332         *len_ptr = len;
333 +       *flag_ptr = mflags;
334         return (const char *)s;
335  }
336  
337 @@ -284,7 +411,7 @@ static const char *get_exclude_tok(const
338  void add_exclude(struct exclude_list_struct *listp, const char *pattern,
339                  int xflags)
340  {
341 -       int pat_len, incl;
342 +       int pat_len, mflags;
343         const char *cp;
344  
345         if (!pattern)
346 @@ -293,22 +420,44 @@ void add_exclude(struct exclude_list_str
347         cp = pattern;
348         pat_len = 0;
349         while (1) {
350 -               cp = get_exclude_tok(cp + pat_len, &pat_len, &incl, xflags);
351 +               cp = get_exclude_tok(cp + pat_len, &pat_len, &mflags, xflags);
352                 if (!pat_len)
353                         break;
354                 /* If we got the special "!" token, clear the list. */
355 -               if (incl < 0)
356 +               if (mflags & MATCHFLG_CLEAR_LIST) {
357                         free_exclude_list(listp);
358 -               else {
359 -                       make_exclude(listp, cp, pat_len, incl);
360 -
361 -                       if (verbose > 2) {
362 -                               rprintf(FINFO, "[%s] add_exclude(%.*s, %s%s)\n",
363 -                                       who_am_i(), pat_len, cp,
364 -                                       listp->debug_type,
365 -                                       incl ? "include" : "exclude");
366 +                       continue;
367 +               }
368 +               if (mflags & MATCHFLG_INSERT_FILE) {
369 +                       char name[MAXPATHLEN];
370 +                       if ((unsigned) pat_len >= sizeof name)
371 +                               continue; // XXX complain?
372 +                       strlcpy(name, cp, pat_len+1);
373 +                       if (listp->parent || strchr(name, '/') != NULL) {
374 +                               if (sanitize_paths)
375 +                                       sanitize_path(name, curr_dir);
376 +                               if (*name == '/')
377 +                                       cp = name;
378 +                               else {
379 +                                       if (strlcpy(dirbuf + dirbuf_offset,
380 +                                           name, MAXPATHLEN - dirbuf_offset)
381 +                                           >= MAXPATHLEN - dirbuf_offset)
382 +                                               continue; // XXX complain?
383 +                                       cp = dirbuf;
384 +                               }
385 +                               add_exclude_file(listp, cp,
386 +                                   xflags | XFLG_FATAL_ERRORS);
387 +                               continue;
388                         }
389                 }
390 +               make_exclude(listp, cp, pat_len, mflags);
391 +
392 +               if (verbose > 2) {
393 +                       rprintf(FINFO, "[%s] add_exclude(%.*s, %s%s%sclude)\n",
394 +                               who_am_i(), pat_len, cp, listp->debug_type,
395 +                               mflags & MATCHFLG_INSERT_FILE ? "FILE " : "",
396 +                               mflags & MATCHFLG_INCLUDE ? "in" : "ex");
397 +               }
398         }
399  }
400  
401 @@ -384,15 +533,19 @@ void send_exclude_list(int f)
402                 l = strlcpy(p, ent->pattern, sizeof p);
403                 if (l == 0 || l >= MAXPATHLEN)
404                         continue;
405 -               if (ent->directory) {
406 +               if (ent->match_flags & MATCHFLG_DIRECTORY) {
407                         p[l++] = '/';
408                         p[l] = '\0';
409                 }
410  
411 -               if (ent->include) {
412 +               if (ent->match_flags & MATCHFLG_INCLUDE) {
413                         write_int(f, l + 2);
414                         write_buf(f, "+ ", 2);
415 -               } else if ((*p == '-' || *p == '+') && p[1] == ' ') {
416 +               } else if (ent->match_flags & MATCHFLG_INSERT_FILE) {
417 +                       write_int(f, l + 2);
418 +                       write_buf(f, ". ", 2);
419 +               } else if ((*p == '-' || *p == '+' || *p == '.')
420 +                   && p[1] == ' ') {
421                         write_int(f, l + 2);
422                         write_buf(f, "- ", 2);
423                 } else
424 @@ -433,6 +586,7 @@ void add_cvs_excludes(void)
425         char fname[MAXPATHLEN];
426         char *p;
427  
428 +       add_exclude(&exclude_list, ". .cvsignore", 0);
429         add_exclude(&exclude_list, default_cvsignore,
430                     XFLG_WORD_SPLIT | XFLG_NO_PREFIXES);
431  
432 --- flist.c     22 Apr 2004 22:17:15 -0000      1.216
433 +++ flist.c     22 Apr 2004 23:45:51 -0000
434 @@ -39,8 +39,6 @@ extern int module_id;
435  extern int ignore_errors;
436  extern int numeric_ids;
437  
438 -extern int cvs_exclude;
439 -
440  extern int recurse;
441  extern char curr_dir[MAXPATHLEN];
442  extern char *files_from;
443 @@ -66,7 +64,6 @@ extern int write_batch;
444  
445  extern struct exclude_list_struct exclude_list;
446  extern struct exclude_list_struct server_exclude_list;
447 -extern struct exclude_list_struct local_exclude_list;
448  
449  int io_error;
450  
451 @@ -211,8 +208,6 @@ int link_stat(const char *path, STRUCT_S
452   */
453  static int check_exclude_file(char *fname, int is_dir, int exclude_level)
454  {
455 -       int rc;
456 -
457  #if 0 /* This currently never happens, so avoid a useless compare. */
458         if (exclude_level == NO_EXCLUDES)
459                 return 0;
460 @@ -234,10 +229,7 @@ static int check_exclude_file(char *fnam
461         if (exclude_level != ALL_EXCLUDES)
462                 return 0;
463         if (exclude_list.head
464 -           && (rc = check_exclude(&exclude_list, fname, is_dir)) != 0)
465 -               return rc < 0;
466 -       if (local_exclude_list.head
467 -           && check_exclude(&local_exclude_list, fname, is_dir) < 0)
468 +           && check_exclude(&exclude_list, fname, is_dir) < 0)
469                 return 1;
470         return 0;
471  }
472 @@ -946,11 +938,7 @@ void send_file_name(int f, struct file_l
473  
474         if (recursive && S_ISDIR(file->mode)
475             && !(file->flags & FLAG_MOUNT_POINT)) {
476 -               struct exclude_list_struct last_list = local_exclude_list;
477 -               local_exclude_list.head = local_exclude_list.tail = NULL;
478                 send_directory(f, flist, f_name_to(file, fbuf));
479 -               free_exclude_list(&local_exclude_list);
480 -               local_exclude_list = last_list;
481         }
482  }
483  
484 @@ -961,6 +949,7 @@ static void send_directory(int f, struct
485         struct dirent *di;
486         char fname[MAXPATHLEN];
487         unsigned int offset;
488 +       void *save_excludes;
489         char *p;
490  
491         d = opendir(dir);
492 @@ -985,18 +974,7 @@ static void send_directory(int f, struct
493                 offset++;
494         }
495  
496 -       if (cvs_exclude) {
497 -               if (strlcpy(p, ".cvsignore", MAXPATHLEN - offset)
498 -                   < MAXPATHLEN - offset) {
499 -                       add_exclude_file(&local_exclude_list, fname,
500 -                                        XFLG_WORD_SPLIT | XFLG_NO_PREFIXES);
501 -               } else {
502 -                       io_error |= IOERR_GENERAL;
503 -                       rprintf(FINFO,
504 -                               "cannot cvs-exclude in long-named directory %s\n",
505 -                               full_fname(fname));
506 -               }
507 -       }
508 +       save_excludes = push_local_excludes(fname, offset);
509  
510         for (errno = 0, di = readdir(d); di; errno = 0, di = readdir(d)) {
511                 char *dname = d_name(di);
512 @@ -1017,6 +995,8 @@ static void send_directory(int f, struct
513                 rprintf(FERROR, "readdir(%s): (%d) %s\n",
514                         dir, errno, strerror(errno));
515         }
516 +
517 +       pop_local_excludes(save_excludes);
518  
519         closedir(d);
520  }
521 --- proto.h     22 Apr 2004 09:58:09 -0000      1.189
522 +++ proto.h     22 Apr 2004 23:45:51 -0000
523 @@ -51,7 +51,8 @@ int start_daemon(int f_in, int f_out);
524  int daemon_main(void);
525  void setup_protocol(int f_out,int f_in);
526  int claim_connection(char *fname,int max_connections);
527 -void free_exclude_list(struct exclude_list_struct *listp);
528 +void *push_local_excludes(char *fname, unsigned int offset);
529 +void pop_local_excludes(void *mem);
530  int check_exclude(struct exclude_list_struct *listp, char *name, int name_is_dir);
531  void add_exclude(struct exclude_list_struct *listp, const char *pattern,
532                  int xflags);
533 --- rsync.h     22 Apr 2004 09:58:24 -0000      1.198
534 +++ rsync.h     22 Apr 2004 23:45:52 -0000
535 @@ -490,18 +490,21 @@ struct map_struct {
536  #define MATCHFLG_WILD2         (1<<1) /* pattern has '**' */
537  #define MATCHFLG_WILD2_PREFIX  (1<<2) /* pattern starts with '**' */
538  #define MATCHFLG_ABS_PATH      (1<<3) /* path-match on absolute path */
539 +#define MATCHFLG_INCLUDE       (1<<4) /* this is an include, not an exclude */
540 +#define MATCHFLG_CLEAR_LIST    (1<<5) /* this item is the "!" token */
541 +#define MATCHFLG_DIRECTORY     (1<<6) /* this matches only directories */
542 +#define MATCHFLG_INSERT_FILE   (1<<7) /* specifies a file to insert */
543 +#define MATCHFLG_CVSIGNORE     (1<<8) /* parse this as a .cvsignore file */
544  struct exclude_struct {
545         struct exclude_struct *next;
546         char *pattern;
547         int match_flags;
548 -       int include;
549 -       int directory;
550         int slash_cnt;
551  };
552  
553  struct exclude_list_struct {
554 -       struct exclude_struct *head;
555 -       struct exclude_struct *tail;
556 +       struct exclude_struct *head, *tail;
557 +       struct exclude_struct *extra, *parent;
558         char *debug_type;
559  };
560