Annotation of embedaddon/rsync/patches/filter-attribute-mods.diff, revision 1.1

1.1     ! misho       1: From: Matt McCutchen <matt@mattmccutchen.net>
        !             2: 
        !             3: Implement the "m", "o", "g" include modifiers to tweak the permissions,
        !             4: owner, or group of matching files.
        !             5: 
        !             6: To use this patch, run these commands for a successful build:
        !             7: 
        !             8:     patch -p1 <patches/filter-attribute-mods.diff
        !             9:     ./configure                         (optional if already run)
        !            10:     make
        !            11: 
        !            12: based-on: e94bad1c156fc3910f24e2b3b71a81b0b0bdeb70
        !            13: diff --git a/exclude.c b/exclude.c
        !            14: --- a/exclude.c
        !            15: +++ b/exclude.c
        !            16: @@ -45,10 +45,13 @@ filter_rule_list filter_list = { .debug_type = "" };
        !            17:  filter_rule_list cvs_filter_list = { .debug_type = " [global CVS]" };
        !            18:  filter_rule_list daemon_filter_list = { .debug_type = " [daemon]" };
        !            19:  
        !            20: +filter_rule *last_hit_filter_rule;
        !            21: +
        !            22:  int saw_xattr_filter = 0;
        !            23:  
        !            24: -/* Need room enough for ":MODS " prefix plus some room to grow. */
        !            25: -#define MAX_RULE_PREFIX (16)
        !            26: +/* Need room enough for ":MODS " prefix, which can now include
        !            27: + * chmod/user/group values. */
        !            28: +#define MAX_RULE_PREFIX (256)
        !            29:  
        !            30:  #define SLASH_WILD3_SUFFIX "/***"
        !            31:  
        !            32: @@ -127,8 +130,27 @@ static void teardown_mergelist(filter_rule *ex)
        !            33:                mergelist_cnt--;
        !            34:  }
        !            35:  
        !            36: +static struct filter_chmod_struct *ref_filter_chmod(struct filter_chmod_struct *chmod)
        !            37: +{
        !            38: +      chmod->ref_cnt++;
        !            39: +      assert(chmod->ref_cnt != 0); /* Catch overflow. */
        !            40: +      return chmod;
        !            41: +}
        !            42: +
        !            43: +static void unref_filter_chmod(struct filter_chmod_struct *chmod)
        !            44: +{
        !            45: +      chmod->ref_cnt--;
        !            46: +      if (chmod->ref_cnt == 0) {
        !            47: +              free(chmod->modestr);
        !            48: +              free_chmod_mode(chmod->modes);
        !            49: +              free(chmod);
        !            50: +      }
        !            51: +}
        !            52: +
        !            53:  static void free_filter(filter_rule *ex)
        !            54:  {
        !            55: +      if (ex->rflags & FILTRULE_CHMOD)
        !            56: +              unref_filter_chmod(ex->chmod);
        !            57:        if (ex->rflags & FILTRULE_PERDIR_MERGE)
        !            58:                teardown_mergelist(ex);
        !            59:        free(ex->pattern);
        !            60: @@ -722,7 +744,9 @@ static void report_filter_result(enum logcode code, char const *name,
        !            61:  
        !            62:  /* This function is used to check if a file should be included/excluded
        !            63:   * from the list of files based on its name and type etc.  The value of
        !            64: - * filter_level is set to either SERVER_FILTERS or ALL_FILTERS. */
        !            65: + * filter_level is set to either SERVER_FILTERS or ALL_FILTERS.
        !            66: + * "last_hit_filter_rule" will be set to the operative filter, or NULL if none. */
        !            67: +
        !            68:  int name_is_excluded(const char *fname, int name_flags, int filter_level)
        !            69:  {
        !            70:        if (daemon_filter_list.head && check_filter(&daemon_filter_list, FLOG, fname, name_flags) < 0) {
        !            71: @@ -731,6 +755,9 @@ int name_is_excluded(const char *fname, int name_flags, int filter_level)
        !            72:                return 1;
        !            73:        }
        !            74:  
        !            75: +      /* Don't leave a daemon include in last_hit_filter_rule. */
        !            76: +      last_hit_filter_rule = NULL;
        !            77: +
        !            78:        if (filter_level != ALL_FILTERS)
        !            79:                return 0;
        !            80:  
        !            81: @@ -741,7 +768,8 @@ int name_is_excluded(const char *fname, int name_flags, int filter_level)
        !            82:  }
        !            83:  
        !            84:  /* Return -1 if file "name" is defined to be excluded by the specified
        !            85: - * exclude list, 1 if it is included, and 0 if it was not matched. */
        !            86: + * exclude list, 1 if it is included, and 0 if it was not matched.
        !            87: + * Sets last_hit_filter_rule to the filter that was hit, or NULL if none. */
        !            88:  int check_filter(filter_rule_list *listp, enum logcode code,
        !            89:                 const char *name, int name_flags)
        !            90:  {
        !            91: @@ -764,10 +792,12 @@ int check_filter(filter_rule_list *listp, enum logcode code,
        !            92:                }
        !            93:                if (rule_matches(name, ent, name_flags)) {
        !            94:                        report_filter_result(code, name, ent, name_flags, listp->debug_type);
        !            95: +                      last_hit_filter_rule = ent;
        !            96:                        return ent->rflags & FILTRULE_INCLUDE ? 1 : -1;
        !            97:                }
        !            98:        }
        !            99:  
        !           100: +      last_hit_filter_rule = NULL;
        !           101:        return 0;
        !           102:  }
        !           103:  
        !           104: @@ -784,9 +814,45 @@ static const uchar *rule_strcmp(const uchar *str, const char *rule, int rule_len
        !           105:        return NULL;
        !           106:  }
        !           107:  
        !           108: +static char *grab_paren_value(const uchar **s_ptr)
        !           109: +{
        !           110: +      const uchar *start, *end;
        !           111: +      int val_sz;
        !           112: +      char *val;
        !           113: +
        !           114: +      if ((*s_ptr)[1] != '(')
        !           115: +              return NULL;
        !           116: +      start = (*s_ptr) + 2;
        !           117: +
        !           118: +      for (end = start; *end != ')'; end++)
        !           119: +              if (!*end || *end == ' ' || *end == '_')
        !           120: +                      return NULL;
        !           121: +
        !           122: +      val_sz = end - start + 1;
        !           123: +      val = new_array(char, val_sz);
        !           124: +      strlcpy(val, (const char *)start, val_sz);
        !           125: +      *s_ptr = end; /* remember ++s in parse_rule_tok */
        !           126: +      return val;
        !           127: +}
        !           128: +
        !           129: +static struct filter_chmod_struct *make_chmod_struct(char *modestr)
        !           130: +{
        !           131: +      struct filter_chmod_struct *chmod;
        !           132: +      struct chmod_mode_struct *modes = NULL;
        !           133: +
        !           134: +      if (!parse_chmod(modestr, &modes))
        !           135: +              return NULL;
        !           136: +
        !           137: +      chmod = new(struct filter_chmod_struct);
        !           138: +      chmod->ref_cnt = 1;
        !           139: +      chmod->modestr = modestr;
        !           140: +      chmod->modes = modes;
        !           141: +      return chmod;
        !           142: +}
        !           143: +
        !           144:  #define FILTRULES_FROM_CONTAINER (FILTRULE_ABS_PATH | FILTRULE_INCLUDE \
        !           145:                                | FILTRULE_DIRECTORY | FILTRULE_NEGATE \
        !           146: -                              | FILTRULE_PERISHABLE)
        !           147: +                              | FILTRULE_PERISHABLE | FILTRULES_ATTRS)
        !           148:  
        !           149:  /* Gets the next include/exclude rule from *rulestr_ptr and advances
        !           150:   * *rulestr_ptr to point beyond it.  Stores the pattern's start (within
        !           151: @@ -801,6 +867,7 @@ static filter_rule *parse_rule_tok(const char **rulestr_ptr,
        !           152:                                   const char **pat_ptr, unsigned int *pat_len_ptr)
        !           153:  {
        !           154:        const uchar *s = (const uchar *)*rulestr_ptr;
        !           155: +      char *val;
        !           156:        filter_rule *rule;
        !           157:        unsigned int len;
        !           158:  
        !           159: @@ -819,6 +886,12 @@ static filter_rule *parse_rule_tok(const char **rulestr_ptr,
        !           160:        /* Inherit from the template.  Don't inherit FILTRULES_SIDES; we check
        !           161:         * that later. */
        !           162:        rule->rflags = template->rflags & FILTRULES_FROM_CONTAINER;
        !           163: +      if (template->rflags & FILTRULE_CHMOD)
        !           164: +              rule->chmod = ref_filter_chmod(template->chmod);
        !           165: +      if (template->rflags & FILTRULE_FORCE_OWNER)
        !           166: +              rule->force_uid = template->force_uid;
        !           167: +      if (template->rflags & FILTRULE_FORCE_GROUP)
        !           168: +              rule->force_gid = template->force_gid;
        !           169:  
        !           170:        /* Figure out what kind of a filter rule "s" is pointing at.  Note
        !           171:         * that if FILTRULE_NO_PREFIXES is set, the rule is either an include
        !           172: @@ -964,11 +1037,63 @@ static filter_rule *parse_rule_tok(const char **rulestr_ptr,
        !           173:                                        goto invalid;
        !           174:                                rule->rflags |= FILTRULE_EXCLUDE_SELF;
        !           175:                                break;
        !           176: +                      case 'g': {
        !           177: +                              gid_t gid;
        !           178: +
        !           179: +                              if (!(val = grab_paren_value(&s)))
        !           180: +                                      goto invalid;
        !           181: +                              if (group_to_gid(val, &gid, True)) {
        !           182: +                                      rule->rflags |= FILTRULE_FORCE_GROUP;
        !           183: +                                      rule->force_gid = gid;
        !           184: +                              } else {
        !           185: +                                      rprintf(FERROR,
        !           186: +                                              "unknown group '%s' in filter rule: %s\n",
        !           187: +                                              val, *rulestr_ptr);
        !           188: +                                      exit_cleanup(RERR_SYNTAX);
        !           189: +                              }
        !           190: +                              free(val);
        !           191: +                              break;
        !           192: +                      }
        !           193: +                      case 'm': {
        !           194: +                              struct filter_chmod_struct *chmod;
        !           195: +
        !           196: +                              if (!(val = grab_paren_value(&s)))
        !           197: +                                      goto invalid;
        !           198: +                              if ((chmod = make_chmod_struct(val))) {
        !           199: +                                      if (rule->rflags & FILTRULE_CHMOD)
        !           200: +                                              unref_filter_chmod(rule->chmod);
        !           201: +                                      rule->rflags |= FILTRULE_CHMOD;
        !           202: +                                      rule->chmod = chmod;
        !           203: +                              } else {
        !           204: +                                      rprintf(FERROR,
        !           205: +                                              "unparseable chmod string '%s' in filter rule: %s\n",
        !           206: +                                              val, *rulestr_ptr);
        !           207: +                                      exit_cleanup(RERR_SYNTAX);
        !           208: +                              }
        !           209: +                              break;
        !           210: +                      }
        !           211:                        case 'n':
        !           212:                                if (!(rule->rflags & FILTRULE_MERGE_FILE))
        !           213:                                        goto invalid;
        !           214:                                rule->rflags |= FILTRULE_NO_INHERIT;
        !           215:                                break;
        !           216: +                      case 'o': {
        !           217: +                              uid_t uid;
        !           218: +
        !           219: +                              if (!(val = grab_paren_value(&s)))
        !           220: +                                      goto invalid;
        !           221: +                              if (user_to_uid(val, &uid, True)) {
        !           222: +                                      rule->rflags |= FILTRULE_FORCE_OWNER;
        !           223: +                                      rule->force_uid = uid;
        !           224: +                              } else {
        !           225: +                                      rprintf(FERROR,
        !           226: +                                              "unknown user '%s' in filter rule: %s\n",
        !           227: +                                              val, *rulestr_ptr);
        !           228: +                                      exit_cleanup(RERR_SYNTAX);
        !           229: +                              }
        !           230: +                              free(val);
        !           231: +                              break;
        !           232: +                      }
        !           233:                        case 'p':
        !           234:                                rule->rflags |= FILTRULE_PERISHABLE;
        !           235:                                break;
        !           236: @@ -1282,6 +1407,23 @@ char *get_rule_prefix(filter_rule *rule, const char *pat, int for_xfer,
        !           237:                else if (am_sender)
        !           238:                        return NULL;
        !           239:        }
        !           240: +      if (rule->rflags & FILTRULES_ATTRS) {
        !           241: +              if (!for_xfer || protocol_version >= 31) {
        !           242: +                      if (rule->rflags & FILTRULE_CHMOD)
        !           243: +                              if (!snappendf(&op, (buf + sizeof buf) - op,
        !           244: +                                      "m(%s)", rule->chmod->modestr))
        !           245: +                                      return NULL;
        !           246: +                      if (rule->rflags & FILTRULE_FORCE_OWNER)
        !           247: +                              if (!snappendf(&op, (buf + sizeof buf) - op,
        !           248: +                                      "o(%u)", (unsigned)rule->force_uid))
        !           249: +                                      return NULL;
        !           250: +                      if (rule->rflags & FILTRULE_FORCE_GROUP)
        !           251: +                              if (!snappendf(&op, (buf + sizeof buf) - op,
        !           252: +                                      "g(%u)", (unsigned)rule->force_gid))
        !           253: +                                      return NULL;
        !           254: +              } else if (!am_sender)
        !           255: +                      return NULL;
        !           256: +      }
        !           257:        if (op - buf > legal_len)
        !           258:                return NULL;
        !           259:        if (legal_len)
        !           260: diff --git a/flist.c b/flist.c
        !           261: --- a/flist.c
        !           262: +++ b/flist.c
        !           263: @@ -84,6 +84,7 @@ extern struct chmod_mode_struct *chmod_modes;
        !           264:  
        !           265:  extern filter_rule_list filter_list;
        !           266:  extern filter_rule_list daemon_filter_list;
        !           267: +extern filter_rule *last_hit_filter_rule;
        !           268:  
        !           269:  #ifdef ICONV_OPTION
        !           270:  extern int filesfrom_convert;
        !           271: @@ -1229,7 +1230,7 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
        !           272:        } else if (readlink_stat(thisname, &st, linkname) != 0) {
        !           273:                int save_errno = errno;
        !           274:                /* See if file is excluded before reporting an error. */
        !           275: -              if (filter_level != NO_FILTERS
        !           276: +              if (filter_level != NO_FILTERS && filter_level != ALL_FILTERS_NO_EXCLUDE
        !           277:                 && (is_excluded(thisname, 0, filter_level)
        !           278:                  || is_excluded(thisname, 1, filter_level))) {
        !           279:                        if (ignore_perishable && save_errno != ENOENT)
        !           280: @@ -1274,6 +1275,12 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
        !           281:  
        !           282:        if (filter_level == NO_FILTERS)
        !           283:                goto skip_filters;
        !           284: +      if (filter_level == ALL_FILTERS_NO_EXCLUDE) {
        !           285: +              /* Call only for the side effect of setting last_hit_filter_rule to
        !           286: +               * any operative include filter, which might affect attributes. */
        !           287: +              is_excluded(thisname, S_ISDIR(st.st_mode) != 0, ALL_FILTERS);
        !           288: +              goto skip_filters;
        !           289: +      }
        !           290:  
        !           291:        if (S_ISDIR(st.st_mode)) {
        !           292:                if (!xfer_dirs) {
        !           293: @@ -1494,12 +1501,23 @@ static struct file_struct *send_file_name(int f, struct file_list *flist,
        !           294:                                          int flags, int filter_level)
        !           295:  {
        !           296:        struct file_struct *file;
        !           297: +      BOOL can_tweak_mode;
        !           298:  
        !           299:        file = make_file(fname, flist, stp, flags, filter_level);
        !           300:        if (!file)
        !           301:                return NULL;
        !           302:  
        !           303: -      if (chmod_modes && !S_ISLNK(file->mode) && file->mode)
        !           304: +      can_tweak_mode = !S_ISLNK(file->mode) && file->mode;
        !           305: +      if ((filter_level == ALL_FILTERS || filter_level == ALL_FILTERS_NO_EXCLUDE)
        !           306: +              && last_hit_filter_rule) {
        !           307: +              if ((last_hit_filter_rule->rflags & FILTRULE_CHMOD) && can_tweak_mode)
        !           308: +                      file->mode = tweak_mode(file->mode, last_hit_filter_rule->chmod->modes);
        !           309: +              if ((last_hit_filter_rule->rflags & FILTRULE_FORCE_OWNER) && uid_ndx)
        !           310: +                      F_OWNER(file) = last_hit_filter_rule->force_uid;
        !           311: +              if ((last_hit_filter_rule->rflags & FILTRULE_FORCE_GROUP) && gid_ndx)
        !           312: +                      F_GROUP(file) = last_hit_filter_rule->force_gid;
        !           313: +      }
        !           314: +      if (chmod_modes && can_tweak_mode)
        !           315:                file->mode = tweak_mode(file->mode, chmod_modes);
        !           316:  
        !           317:        if (f >= 0) {
        !           318: @@ -2399,7 +2417,7 @@ struct file_list *send_file_list(int f, int argc, char *argv[])
        !           319:                        struct file_struct *file;
        !           320:                        file = send_file_name(f, flist, fbuf, &st,
        !           321:                                              FLAG_TOP_DIR | FLAG_CONTENT_DIR | flags,
        !           322: -                                            NO_FILTERS);
        !           323: +                                            ALL_FILTERS_NO_EXCLUDE);
        !           324:                        if (!file)
        !           325:                                continue;
        !           326:                        if (inc_recurse) {
        !           327: @@ -2413,7 +2431,7 @@ struct file_list *send_file_list(int f, int argc, char *argv[])
        !           328:                        } else
        !           329:                                send_if_directory(f, flist, file, fbuf, len, flags);
        !           330:                } else
        !           331: -                      send_file_name(f, flist, fbuf, &st, flags, NO_FILTERS);
        !           332: +                      send_file_name(f, flist, fbuf, &st, flags, ALL_FILTERS_NO_EXCLUDE);
        !           333:        }
        !           334:  
        !           335:        if (reenable_multiplex >= 0)
        !           336: diff --git a/rsync.1.md b/rsync.1.md
        !           337: --- a/rsync.1.md
        !           338: +++ b/rsync.1.md
        !           339: @@ -1285,7 +1285,9 @@ your home directory (remove the '=' for that).
        !           340:      >     --chmod=D2775,F664
        !           341:  
        !           342:      It is also legal to specify multiple `--chmod` options, as each additional
        !           343: -    option is just appended to the list of changes to make.
        !           344: +    option is just appended to the list of changes to make.  To change
        !           345: +    permissions of files matching a pattern, use an include filter with the `m`
        !           346: +    modifier, which takes effect before any `--chmod` options.
        !           347:  
        !           348:      See the `--perms` and `--executability` options for how the resulting
        !           349:      permission value can be applied to the files in the transfer.
        !           350: @@ -2636,6 +2638,10 @@ your home directory (remove the '=' for that).
        !           351:      "`--usermap=*:foo --groupmap=*:bar`", only easier.  If your shell complains
        !           352:      about the wildcards, use `--protect-args` (`-s`).
        !           353:  
        !           354: +    To change ownership of files matching a pattern, use an include filter with
        !           355: +    a `o` or `g` modifier, which take effect before uid/gid mapping and
        !           356: +    therefore *can* be mixed with `--usermap` and `--groupmap`.
        !           357: +
        !           358:  0.  `--timeout=SECONDS`
        !           359:  
        !           360:      This option allows you to set a maximum I/O timeout in seconds.  If no data
        !           361: @@ -3633,6 +3639,15 @@ The following modifiers are accepted after a "`+`" or "`-`":
        !           362:    rules that exclude things like "CVS" and "`*.o`" are marked as perishable,
        !           363:    and will not prevent a directory that was removed on the source from being
        !           364:    deleted on the destination.
        !           365: +- An `m(CHMOD)` on an include rule tweaks the permissions of matching
        !           366: +  source files in the same way as `--chmod`.  This happens before any tweaks
        !           367: +  requested via `--chmod` options.
        !           368: +- An `o(USER)` on an include rule pretends that matching source files are
        !           369: +  owned by `USER` (a name or numeric uid).  This happens before any uid mapping
        !           370: +  by name or `--usermap`.
        !           371: +- A `g(GROUP)` on an include rule pretends that matching source files are
        !           372: +  owned by `GROUP` (a name or numeric gid).  This happens before any gid
        !           373: +  mapping by name or `--groupmap`.
        !           374:  - An `x` indicates that a rule affects xattr names in xattr copy/delete
        !           375:    operations (and is thus ignored when matching file/dir names).  If no
        !           376:    xattr-matching rules are specified, a default xattr filtering rule is used
        !           377: @@ -3690,6 +3705,12 @@ The following modifiers are accepted after a merge or dir-merge rule:
        !           378:    rules in the file must not specify sides (via a modifier or a rule prefix
        !           379:    such as `hide`).
        !           380:  
        !           381: +The attribute-affecting modifiers `m`, `o`, and `g` work only in client filters
        !           382: +(not in daemon filters), and only the modifiers of the first matching rule are
        !           383: +applied.  As an example, assuming `--super` is enabled, the rule
        !           384: +"`+o(root),g(root),m(go=) *~`" would ensure that all "backup"
        !           385: +files belong to root and are not accessible to anyone else.
        !           386: +
        !           387:  Per-directory rules are inherited in all subdirectories of the directory where
        !           388:  the merge-file was found unless the 'n' modifier was used.  Each subdirectory's
        !           389:  rules are prefixed to the inherited per-directory rules from its parents, which
        !           390: diff --git a/rsync.h b/rsync.h
        !           391: --- a/rsync.h
        !           392: +++ b/rsync.h
        !           393: @@ -171,6 +171,9 @@
        !           394:  #define NO_FILTERS    0
        !           395:  #define SERVER_FILTERS        1
        !           396:  #define ALL_FILTERS   2
        !           397: +/* Don't let the file be excluded, but check for a filter that might affect
        !           398: + * its attributes via FILTRULES_ATTRS. */
        !           399: +#define ALL_FILTERS_NO_EXCLUDE        3
        !           400:  
        !           401:  #define XFLG_FATAL_ERRORS     (1<<0)
        !           402:  #define XFLG_OLD_PREFIXES     (1<<1)
        !           403: @@ -966,6 +969,8 @@ struct map_struct {
        !           404:        int status;             /* first errno from read errors         */
        !           405:  };
        !           406:  
        !           407: +struct chmod_mode_struct;
        !           408: +
        !           409:  #define NAME_IS_FILE          (0)    /* filter name as a file */
        !           410:  #define NAME_IS_DIR           (1<<0) /* filter name as a dir */
        !           411:  #define NAME_IS_XATTR         (1<<2) /* filter name as an xattr */
        !           412: @@ -991,8 +996,18 @@ struct map_struct {
        !           413:  #define FILTRULE_CLEAR_LIST   (1<<18)/* this item is the "!" token */
        !           414:  #define FILTRULE_PERISHABLE   (1<<19)/* perishable if parent dir goes away */
        !           415:  #define FILTRULE_XATTR                (1<<20)/* rule only applies to xattr names */
        !           416: +#define FILTRULE_CHMOD                (1<<21)/* chmod-tweak matching files */
        !           417: +#define FILTRULE_FORCE_OWNER  (1<<22)/* force owner of matching files */
        !           418: +#define FILTRULE_FORCE_GROUP  (1<<23)/* force group of matching files */
        !           419:  
        !           420:  #define FILTRULES_SIDES (FILTRULE_SENDER_SIDE | FILTRULE_RECEIVER_SIDE)
        !           421: +#define FILTRULES_ATTRS (FILTRULE_CHMOD | FILTRULE_FORCE_OWNER | FILTRULE_FORCE_GROUP)
        !           422: +
        !           423: +struct filter_chmod_struct {
        !           424: +      unsigned int ref_cnt;
        !           425: +      char *modestr;
        !           426: +      struct chmod_mode_struct *modes;
        !           427: +};
        !           428:  
        !           429:  typedef struct filter_struct {
        !           430:        struct filter_struct *next;
        !           431: @@ -1002,6 +1017,11 @@ typedef struct filter_struct {
        !           432:                int slash_cnt;
        !           433:                struct filter_list_struct *mergelist;
        !           434:        } u;
        !           435: +      /* TODO: Use an "extras" mechanism to avoid
        !           436: +       * allocating this memory when we don't need it. */
        !           437: +      struct filter_chmod_struct *chmod;
        !           438: +      uid_t force_uid;
        !           439: +      gid_t force_gid;
        !           440:  } filter_rule;
        !           441:  
        !           442:  typedef struct filter_list_struct {
        !           443: diff --git a/util.c b/util.c
        !           444: --- a/util.c
        !           445: +++ b/util.c
        !           446: @@ -884,6 +884,25 @@ size_t stringjoin(char *dest, size_t destsize, ...)
        !           447:        return ret;
        !           448:  }
        !           449:  
        !           450: +/* Append formatted text at *dest_ptr up to a maximum of sz (like snprintf).
        !           451: + * On success, advance *dest_ptr and return True; on overflow, return False. */
        !           452: +BOOL snappendf(char **dest_ptr, size_t sz, const char *format, ...)
        !           453: +{
        !           454: +      va_list ap;
        !           455: +      size_t len;
        !           456: +
        !           457: +      va_start(ap, format);
        !           458: +      len = vsnprintf(*dest_ptr, sz, format, ap);
        !           459: +      va_end(ap);
        !           460: +
        !           461: +      if (len >= sz)
        !           462: +              return False;
        !           463: +      else {
        !           464: +              *dest_ptr += len;
        !           465: +              return True;
        !           466: +      }
        !           467: +}
        !           468: +
        !           469:  int count_dir_elements(const char *p)
        !           470:  {
        !           471:        int cnt = 0, new_component = 1;
        !           472: diff -Nurp a/rsync.1 b/rsync.1
        !           473: --- a/rsync.1
        !           474: +++ b/rsync.1
        !           475: @@ -1378,7 +1378,9 @@ Using octal mode numbers is also allowed
        !           476:  .RE
        !           477:  .IP
        !           478:  It is also legal to specify multiple \fB\-\-chmod\fP options, as each additional
        !           479: -option is just appended to the list of changes to make.
        !           480: +option is just appended to the list of changes to make.  To change
        !           481: +permissions of files matching a pattern, use an include filter with the \fBm\fP
        !           482: +modifier, which takes effect before any \fB\-\-chmod\fP options.
        !           483:  .IP
        !           484:  See the \fB\-\-perms\fP and \fB\-\-executability\fP options for how the resulting
        !           485:  permission value can be applied to the files in the transfer.
        !           486: @@ -2686,6 +2688,10 @@ USER is empty, a leading colon must be s
        !           487:  If you specify "\fB\-\-chown=foo:bar\fP", this is exactly the same as specifying
        !           488:  "\fB\-\-usermap=*:foo\ \-\-groupmap=*:bar\fP", only easier.  If your shell complains
        !           489:  about the wildcards, use \fB\-\-protect-args\fP (\fB\-s\fP).
        !           490: +.IP
        !           491: +To change ownership of files matching a pattern, use an include filter with
        !           492: +a \fBo\fP or \fBg\fP modifier, which take effect before uid/gid mapping and
        !           493: +therefore \fIcan\fP be mixed with \fB\-\-usermap\fP and \fB\-\-groupmap\fP.
        !           494:  .IP "\fB\-\-timeout=SECONDS\fP"
        !           495:  This option allows you to set a maximum I/O timeout in seconds.  If no data
        !           496:  is transferred for the specified time then rsync will exit.  The default is
        !           497: @@ -3704,6 +3710,18 @@ rules that exclude things like "CVS" and
        !           498:  and will not prevent a directory that was removed on the source from being
        !           499:  deleted on the destination.
        !           500:  .IP o
        !           501: +An \fBm(CHMOD)\fP on an include rule tweaks the permissions of matching
        !           502: +source files in the same way as \fB\-\-chmod\fP.  This happens before any tweaks
        !           503: +requested via \fB\-\-chmod\fP options.
        !           504: +.IP o
        !           505: +An \fBo(USER)\fP on an include rule pretends that matching source files are
        !           506: +owned by \fBUSER\fP (a name or numeric uid).  This happens before any uid mapping
        !           507: +by name or \fB\-\-usermap\fP.
        !           508: +.IP o
        !           509: +A \fBg(GROUP)\fP on an include rule pretends that matching source files are
        !           510: +owned by \fBGROUP\fP (a name or numeric gid).  This happens before any gid
        !           511: +mapping by name or \fB\-\-groupmap\fP.
        !           512: +.IP o
        !           513:  An \fBx\fP indicates that a rule affects xattr names in xattr copy/delete
        !           514:  operations (and is thus ignored when matching file/dir names).  If no
        !           515:  xattr-matching rules are specified, a default xattr filtering rule is used
        !           516: @@ -3772,6 +3790,12 @@ specifies sides to affect (via the \fBs\
        !           517:  rules in the file must not specify sides (via a modifier or a rule prefix
        !           518:  such as \fBhide\fP).
        !           519:  .P
        !           520: +The attribute-affecting modifiers \fBm\fP, \fBo\fP, and \fBg\fP work only in client filters
        !           521: +(not in daemon filters), and only the modifiers of the first matching rule are
        !           522: +applied.  As an example, assuming \fB\-\-super\fP is enabled, the rule
        !           523: +"\fB+o(root),g(root),m(go=)\ *~\fP" would ensure that all "backup"
        !           524: +files belong to root and are not accessible to anyone else.
        !           525: +.P
        !           526:  Per-directory rules are inherited in all subdirectories of the directory where
        !           527:  the merge-file was found unless the 'n' modifier was used.  Each subdirectory's
        !           528:  rules are prefixed to the inherited per-directory rules from its parents, which
        !           529: diff -Nurp a/rsync.1.html b/rsync.1.html
        !           530: --- a/rsync.1.html
        !           531: +++ b/rsync.1.html
        !           532: @@ -1242,7 +1242,9 @@ consistent executability across all bits
        !           533:  </code></pre>
        !           534:  </blockquote>
        !           535:  <p>It is also legal to specify multiple <code>--chmod</code> options, as each additional
        !           536: -option is just appended to the list of changes to make.</p>
        !           537: +option is just appended to the list of changes to make.  To change
        !           538: +permissions of files matching a pattern, use an include filter with the <code>m</code>
        !           539: +modifier, which takes effect before any <code>--chmod</code> options.</p>
        !           540:  <p>See the <code>--perms</code> and <code>--executability</code> options for how the resulting
        !           541:  permission value can be applied to the files in the transfer.</p>
        !           542:  </dd>
        !           543: @@ -2490,6 +2492,9 @@ USER is empty, a leading colon must be s
        !           544:  <p>If you specify &quot;<code>--chown=foo:bar</code>&quot;, this is exactly the same as specifying
        !           545:  &quot;<code>--usermap=*:foo --groupmap=*:bar</code>&quot;, only easier.  If your shell complains
        !           546:  about the wildcards, use <code>--protect-args</code> (<code>-s</code>).</p>
        !           547: +<p>To change ownership of files matching a pattern, use an include filter with
        !           548: +a <code>o</code> or <code>g</code> modifier, which take effect before uid/gid mapping and
        !           549: +therefore <u>can</u> be mixed with <code>--usermap</code> and <code>--groupmap</code>.</p>
        !           550:  </dd>
        !           551:  
        !           552:  <dt><code>--timeout=SECONDS</code></dt><dd>
        !           553: @@ -3431,6 +3436,15 @@ directories that are being deleted.  For
        !           554:  rules that exclude things like &quot;CVS&quot; and &quot;<code>*.o</code>&quot; are marked as perishable,
        !           555:  and will not prevent a directory that was removed on the source from being
        !           556:  deleted on the destination.</li>
        !           557: +<li>An <code>m(CHMOD)</code> on an include rule tweaks the permissions of matching
        !           558: +source files in the same way as <code>--chmod</code>.  This happens before any tweaks
        !           559: +requested via <code>--chmod</code> options.</li>
        !           560: +<li>An <code>o(USER)</code> on an include rule pretends that matching source files are
        !           561: +owned by <code>USER</code> (a name or numeric uid).  This happens before any uid mapping
        !           562: +by name or <code>--usermap</code>.</li>
        !           563: +<li>A <code>g(GROUP)</code> on an include rule pretends that matching source files are
        !           564: +owned by <code>GROUP</code> (a name or numeric gid).  This happens before any gid
        !           565: +mapping by name or <code>--groupmap</code>.</li>
        !           566:  <li>An <code>x</code> indicates that a rule affects xattr names in xattr copy/delete
        !           567:  operations (and is thus ignored when matching file/dir names).  If no
        !           568:  xattr-matching rules are specified, a default xattr filtering rule is used
        !           569: @@ -3486,6 +3500,11 @@ specifies sides to affect (via the <code
        !           570:  rules in the file must not specify sides (via a modifier or a rule prefix
        !           571:  such as <code>hide</code>).</li>
        !           572:  </ul>
        !           573: +<p>The attribute-affecting modifiers <code>m</code>, <code>o</code>, and <code>g</code> work only in client filters
        !           574: +(not in daemon filters), and only the modifiers of the first matching rule are
        !           575: +applied.  As an example, assuming <code>--super</code> is enabled, the rule
        !           576: +&quot;<code>+o(root),g(root),m(go=) *~</code>&quot; would ensure that all &quot;backup&quot;
        !           577: +files belong to root and are not accessible to anyone else.</p>
        !           578:  <p>Per-directory rules are inherited in all subdirectories of the directory where
        !           579:  the merge-file was found unless the 'n' modifier was used.  Each subdirectory's
        !           580:  rules are prefixed to the inherited per-directory rules from its parents, which

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>