Index: rsync.h =================================================================== RCS file: /cvsroot/rsync/rsync.h,v retrieving revision 1.294 diff -u -r1.294 rsync.h --- rsync.h 5 Jun 2006 16:52:34 -0000 1.294 +++ rsync.h 11 Jun 2006 19:55:50 -0000 @@ -67,7 +67,7 @@ #define FLAG_MISSING (1<<6) /* generator */ /* update this if you make incompatible changes */ -#define PROTOCOL_VERSION 29 +#define PROTOCOL_VERSION 30 /* We refuse to interoperate with versions that are not in this range. * Note that we assume we'll work with later versions: the onus is on @@ -153,6 +153,8 @@ /* These are outside the range of the transmitted flags. */ #define ITEM_MISSING_DATA (1<<16) /* used by log_formatted() */ #define ITEM_DELETED (1<<17) /* used by log_formatted() */ +#define ITEM_PINNED (1<<18) /* used by log_formatted() */ +#define ITEM_HIT_MAX_DELETE (1<<19) /* used by log_formatted() */ #define SIGNIFICANT_ITEM_FLAGS (~(\ ITEM_BASIS_TYPE_FOLLOWS | ITEM_XNAME_FOLLOWS | ITEM_LOCAL_CHANGE)) @@ -608,6 +610,7 @@ #define MATCHFLG_SENDER_SIDE (1<<16)/* rule applies to the sending side */ #define MATCHFLG_RECEIVER_SIDE (1<<17)/* rule applies to the receiving side */ #define MATCHFLG_CLEAR_LIST (1<<18)/* this item is the "!" token */ +#define MATCHFLG_PARENT_PROTECT (1<<19)/* matching files perish with their parents (modifies P) */ #define MATCHFLGS_FROM_CONTAINER (MATCHFLG_ABS_PATH | MATCHFLG_INCLUDE \ | MATCHFLG_DIRECTORY | MATCHFLG_SENDER_SIDE \ Index: generator.c =================================================================== RCS file: /cvsroot/rsync/generator.c,v retrieving revision 1.283 diff -u -r1.283 generator.c --- generator.c 5 Jun 2006 16:56:50 -0000 1.283 +++ generator.c 11 Jun 2006 19:55:47 -0000 @@ -94,9 +94,10 @@ static int deletion_count = 0; /* used to implement --max-delete */ -/* For calling delete_file() */ -#define DEL_FORCE_RECURSE (1<<1) /* recurse even w/o --force */ -#define DEL_TERSE (1<<3) +/* For calling delete_item() */ +#define DEL_PARENT_EXTRANEOUS (1<<0) /* parent-protect filters include */ +#define DEL_RECURSE (1<<1) /* recurse */ +#define DEL_REPLACE (1<<3) /* file will be replaced */ static int is_backup_file(char *fn) @@ -105,73 +106,36 @@ return k > 0 && strcmp(fn+k, backup_suffix) == 0; } +/* Declare here so empty_directory can call it */ +static int delete_item(char *fname, int mode, int flags); -/* Delete a file or directory. If DEL_FORCE_RECURSE is set in the flags, or if - * force_delete is set, this will delete recursively. - * - * Note that fname must point to a MAXPATHLEN buffer if the mode indicates it's - * a directory! (The buffer is used for recursion, but returned unchanged.) +/* The directory should be deleted. If DEL_RECURSE is given, delete unpinned + * files recursively and then signal whether the directory is pinned. If not, + * merely signal whether the directory is empty. + * + * Returns: 0 if OK, -3 if pinned, -4 if nonempty. */ -static int delete_item(char *fname, int mode, int flags) +static int empty_directory(char *fname, int flags) { struct file_list *dirlist; - int j, dlen, zap_dir, ok; + int j, dlen, result; unsigned remainder; void *save_filters; char *p; - - if (!S_ISDIR(mode)) { - if (max_delete && ++deletion_count > max_delete) - return 0; - if (make_backups && (backup_dir || !is_backup_file(fname))) - ok = make_backup(fname); - else - ok = robust_unlink(fname) == 0; - if (ok) { - if (!(flags & DEL_TERSE)) - log_delete(fname, mode); - return 0; - } - if (errno == ENOENT) { - deletion_count--; - return 0; - } - rsyserr(FERROR, errno, "delete_file: unlink %s failed", - full_fname(fname)); - return -1; - } - - zap_dir = flags & DEL_FORCE_RECURSE || force_delete; - if ((max_delete && ++deletion_count > max_delete) - || (dry_run && zap_dir)) { - ok = 0; - errno = ENOTEMPTY; - } else if (make_backups && !backup_dir && !is_backup_file(fname) - && !(flags & DEL_FORCE_RECURSE)) - ok = make_backup(fname); - else - ok = do_rmdir(fname) == 0; - if (ok) { - if (!(flags & DEL_TERSE)) - log_delete(fname, mode); - return 0; - } - if (errno == ENOENT) { - deletion_count--; - return 0; - } - if (!zap_dir) { - rsyserr(FERROR, errno, "delete_file: rmdir %s failed", - full_fname(fname)); - return -1; - } - flags |= DEL_FORCE_RECURSE; /* mark subdir dels as not "in the way" */ - deletion_count--; - + BOOL pinned = False; + dlen = strlen(fname); save_filters = push_local_filters(fname, dlen); - dirlist = get_dirlist(fname, dlen, 0); + dirlist = get_dirlist(fname, dlen, 0, &pinned); + + if (!pinned && dirlist->count == 0) { + result = 0; + goto out; + } else if (!(flags & DEL_RECURSE)) { + result = -4; + goto out; + } p = fname + dlen; if (dlen != 1 || *fname != '/') @@ -181,31 +145,105 @@ for (j = dirlist->count; j--; ) { struct file_struct *fp = dirlist->files[j]; - if (fp->flags & FLAG_MOUNT_POINT) + if (fp->flags & FLAG_MOUNT_POINT) { + pinned = True; + if (verbose > 1) + rprintf(FINFO, "mount point %s pins parent directory\n", + f_name(fp, NULL)); continue; + } strlcpy(p, fp->basename, remainder); - delete_item(fname, fp->mode, flags & ~DEL_TERSE); + result = delete_item(fname, fp->mode, + (flags | DEL_PARENT_EXTRANEOUS) & ~DEL_REPLACE); + /* as long as nothing is pinned, we can ignore deletion errors; + * if the filesystem lets us rmdir anyway, great, otherwise we + * get ENOTEMPTY and can handle it there */ + if (result == -3) { + pinned = True; + if (verbose > 1) + rprintf(FINFO, "pinned directory %s pins parent directory\n", + f_name(fp, NULL)); + } } - flist_free(dirlist); fname[dlen] = '\0'; + + if (pinned) + result = -3; + else + result = 0; +out: pop_local_filters(save_filters); + flist_free(dirlist); + return result; +} - if (max_delete && ++deletion_count > max_delete) - return 0; +/* Delete a file or directory. If DEL_RECURSE is set in the flags, this will + * delete recursively. + * + * Note that fname must point to a MAXPATHLEN buffer if the mode indicates it's + * a directory! (The buffer is used for recursion, but returned unchanged.) + * + * Returns: 0 on success, -1 on failure, -2 if deletion limit hit, -3 if pinned, + * -4 if nonempty. + */ +static int delete_item(char *fname, int mode, int flags) +{ + int ok, result; + char *what; + + if (verbose > 2) + rprintf(FINFO, "delete_item %s mode %o flags %d\n", + fname, mode, flags); + + if (S_ISDIR(mode)) { + result = empty_directory(fname, flags); + if (result == -3) { + if (!(flags & DEL_REPLACE)) + log_pinned(fname, mode); + return -3; + } else if (result == -4) { + rprintf(FINFO, "nonempty directory %s not deleted " + "because --force not given\n", fname); + return -4; + } + /* otherwise try to delete the directory */ + } + + if (!(flags & DEL_REPLACE)) { + if (max_delete >= 0 && ++deletion_count > max_delete) { + log_hit_max_delete(fname, mode); + return -2; + } + } - if (do_rmdir(fname) == 0) { - if (!(flags & DEL_TERSE)) + if (S_ISDIR(mode)) { + what = "rmdir"; + ok = do_rmdir(fname) == 0; + } else { + if (make_backups && (backup_dir || !is_backup_file(fname))) { + what = "make_backup"; + ok = make_backup(fname); + } else { + what = "unlink"; + ok = robust_unlink(fname) == 0; + } + } + + if (ok) { + if (!(flags & DEL_REPLACE)) log_delete(fname, mode); - } else if (errno != ENOTEMPTY && errno != EEXIST && errno != ENOENT) { - rsyserr(FERROR, errno, "delete_file: rmdir %s failed", - full_fname(fname)); + return 0; + } else if (errno == ENOENT) { + --deletion_count; + return 0; + } else { + rsyserr(FERROR, errno, "delete_file: %s %s failed", + what, full_fname(fname)); return -1; } - - return 0; } @@ -265,17 +303,23 @@ return; } - dirlist = get_dirlist(fbuf, dlen, 0); + dirlist = get_dirlist(fbuf, dlen, 0, NULL); /* If an item in dirlist is not found in flist, delete it * from the filesystem. */ for (i = dirlist->count; i--; ) { struct file_struct *fp = dirlist->files[i]; - if (!fp->basename || fp->flags & FLAG_MOUNT_POINT) + if (!fp->basename) continue; + if (fp->flags & FLAG_MOUNT_POINT) { + if (verbose > 1) + rprintf(FINFO, "mount point %s not deleted\n", + f_name(fp, NULL)); + continue; + } if (flist_find(flist, fp) < 0) { f_name(fp, delbuf); - delete_item(delbuf, fp->mode, DEL_FORCE_RECURSE); + delete_item(delbuf, fp->mode, DEL_RECURSE); } } @@ -802,7 +846,8 @@ char *fnamecmp, *partialptr, *backupptr = NULL; char fnamecmpbuf[MAXPATHLEN]; uchar fnamecmp_type; - int del_opts = DEL_TERSE | (delete_mode ? DEL_FORCE_RECURSE : 0); + int del_opts = DEL_REPLACE | + (delete_mode || force_delete ? DEL_RECURSE : 0); if (list_only) return; @@ -875,7 +920,7 @@ parent_dirname = dn; if (need_fuzzy_dirlist && S_ISREG(file->mode)) { - fuzzy_dirlist = get_dirlist(dn, -1, 1); + fuzzy_dirlist = get_dirlist(dn, -1, 1, NULL); need_fuzzy_dirlist = 0; } @@ -908,8 +953,10 @@ * we need to delete it. If it doesn't exist, then * (perhaps recursively) create it. */ if (statret == 0 && !S_ISDIR(st.st_mode)) { - if (delete_item(fname, st.st_mode, del_opts) < 0) + if (delete_item(fname, st.st_mode, del_opts) < 0) { + rprintf(FERROR, "could not make way for new directory at %s\n", fname); return; + } statret = -1; } if (dry_run && statret != 0 && missing_below < 0) { @@ -991,8 +1038,10 @@ } /* Not the right symlink (or not a symlink), so * delete it. */ - if (delete_item(fname, st.st_mode, del_opts) < 0) + if (delete_item(fname, st.st_mode, del_opts) < 0) { + rprintf(FERROR, "could not make way for new symlink at %s\n", fname); return; + } if (!S_ISLNK(st.st_mode)) statret = -1; } else if (basis_dir[0] != NULL) { @@ -1056,8 +1105,10 @@ || (st.st_mode & ~CHMOD_BITS) != (file->mode & ~CHMOD_BITS) || st.st_rdev != file->u.rdev) { if (statret == 0 - && delete_item(fname, st.st_mode, del_opts) < 0) + && delete_item(fname, st.st_mode, del_opts) < 0) { + rprintf(FERROR, "could not make way for new device or special at %s\n", fname); return; + } if (preserve_hard_links && file->link_u.links && hard_link_check(file, ndx, fname, -1, &st, itemizing, code, HL_SKIP)) @@ -1137,8 +1188,10 @@ fnamecmp_type = FNAMECMP_FNAME; if (statret == 0 && !S_ISREG(st.st_mode)) { - if (delete_item(fname, st.st_mode, del_opts) != 0) + if (delete_item(fname, st.st_mode, del_opts) < 0) { + rprintf(FERROR, "could not make way for new regular file at %s\n", fname); return; + } statret = -1; stat_errno = ENOENT; } @@ -1254,7 +1307,7 @@ close(fd); return; } - if (!(back_file = make_file(fname, NULL, NULL, 0, NO_FILTERS))) { + if (!(back_file = make_file(fname, NULL, NULL, 0, NO_FILTERS, NULL))) { close(fd); goto pretend_missing; } @@ -1515,7 +1568,7 @@ } recv_generator(NULL, NULL, 0, 0, 0, code, -1); - if (max_delete > 0 && deletion_count > max_delete) { + if (max_delete >= 0 && deletion_count > max_delete) { rprintf(FINFO, "Deletions stopped due to --max-delete limit (%d skipped)\n", deletion_count - max_delete); Index: options.c =================================================================== RCS file: /cvsroot/rsync/options.c,v retrieving revision 1.346 diff -u -r1.346 options.c --- options.c 7 Jun 2006 23:05:24 -0000 1.346 +++ options.c 11 Jun 2006 19:55:49 -0000 @@ -106,7 +106,7 @@ int ignore_existing = 0; int ignore_non_existing = 0; int need_messages_from_generator = 0; -int max_delete = 0; +int max_delete = -1; OFF_T max_size = 0; OFF_T min_size = 0; int ignore_errors = 0; @@ -1612,7 +1612,7 @@ args[ac++] = arg; } - if (max_delete && am_sender) { + if (max_delete >= 0 && am_sender) { if (asprintf(&arg, "--max-delete=%d", max_delete) < 0) goto oom; args[ac++] = arg; Index: compat.c =================================================================== RCS file: /cvsroot/rsync/compat.c,v retrieving revision 1.34 diff -u -r1.34 compat.c --- compat.c 25 Apr 2006 23:51:12 -0000 1.34 +++ compat.c 11 Jun 2006 19:55:44 -0000 @@ -25,6 +25,7 @@ int remote_protocol = 0; extern int verbose; +extern int am_sender; extern int am_server; extern int inplace; extern int fuzzy_basis; @@ -32,6 +33,7 @@ extern int checksum_seed; extern int basis_dir_cnt; extern int prune_empty_dirs; +extern int max_delete; extern int protocol_version; extern char *dest_option; @@ -75,6 +77,16 @@ exit_cleanup(RERR_PROTOCOL); } + if (protocol_version < 30) { + if (max_delete == 0 && am_sender) { + rprintf(FERROR, + "--max-delete=0 requires protocol 30 or higher" + " (negotiated %d).\n", + protocol_version); + exit_cleanup(RERR_PROTOCOL); + } + } + if (protocol_version < 29) { if (fuzzy_basis) { rprintf(FERROR, Index: flist.c =================================================================== RCS file: /cvsroot/rsync/flist.c,v retrieving revision 1.349 diff -u -r1.349 flist.c --- flist.c 9 May 2006 18:31:13 -0000 1.349 +++ flist.c 11 Jun 2006 19:55:46 -0000 @@ -201,13 +201,16 @@ /* This function is used to check if a file should be included/excluded * from the list of files based on its name and type etc. The value of - * filter_level is set to either SERVER_FILTERS or ALL_FILTERS. */ -static int is_excluded(char *fname, int is_dir, int filter_level) + * filter_level is set to either SERVER_FILTERS or ALL_FILTERS. If + * parent_extraneous is true, parent-protect counts as included. */ +static int is_excluded(char *fname, int is_dir, int filter_level, + BOOL parent_extraneous) { #if 0 /* This currently never happens, so avoid a useless compare. */ if (filter_level == NO_FILTERS) return 0; #endif + int fresult; if (fname) { /* never exclude '.', even if somebody does --exclude '*' */ if (fname[0] == '.' && !fname[1]) @@ -220,12 +223,14 @@ } } if (server_filter_list.head - && check_filter(&server_filter_list, fname, is_dir) < 0) + && (fresult = check_filter(&server_filter_list, fname, is_dir)) < 0 + && (!parent_extraneous || fresult != -2)) return 1; if (filter_level != ALL_FILTERS) return 0; if (filter_list.head - && check_filter(&filter_list, fname, is_dir) < 0) + && (fresult = check_filter(&filter_list, fname, is_dir)) < 0 + && (!parent_extraneous || fresult != -2)) return 1; return 0; } @@ -247,7 +252,7 @@ } static void send_directory(int f, struct file_list *flist, - char *fbuf, int len); + char *fbuf, int len, BOOL *pinned); static char *flist_dir; static int flist_dir_len; @@ -713,7 +718,7 @@ **/ struct file_struct *make_file(char *fname, struct file_list *flist, STRUCT_STAT *stp, unsigned short flags, - int filter_level) + int filter_level, BOOL *protected_p) { static char *lastdir; static int lastdir_len = -1; @@ -724,6 +729,7 @@ char linkname[MAXPATHLEN]; int alloc_len, basename_len, dirname_len, linkname_len, sum_len; char *basename, *dirname, *bp; + BOOL excluded; if (!flist || !flist->count) /* Ignore lastdir when invalid. */ lastdir_len = -1; @@ -745,8 +751,12 @@ int save_errno = errno; /* See if file is excluded before reporting an error. */ if (filter_level != NO_FILTERS - && is_excluded(thisname, 0, filter_level)) + && is_excluded(thisname, 0, filter_level, protected_p != NULL) + && is_excluded(thisname, 1, filter_level, protected_p != NULL)) { + if (protected_p) + *protected_p = True; return NULL; + } if (save_errno == ENOENT) { #ifdef SUPPORT_LINKS /* Avoid "vanished" error if symlink points nowhere. */ @@ -796,7 +806,11 @@ flags |= FLAG_MOUNT_POINT; } - if (is_excluded(thisname, S_ISDIR(st.st_mode) != 0, filter_level)) + excluded = is_excluded(thisname, S_ISDIR(st.st_mode) != 0, filter_level, + protected_p != NULL); + if (protected_p) + *protected_p = excluded; + if (excluded) return NULL; if (lp_ignore_nonreadable(module_id)) { @@ -939,12 +953,12 @@ static struct file_struct *send_file_name(int f, struct file_list *flist, char *fname, STRUCT_STAT *stp, - unsigned short flags) + unsigned short flags, BOOL *protected_p) { struct file_struct *file; file = make_file(fname, flist, stp, flags, - f == -2 ? SERVER_FILTERS : ALL_FILTERS); + f == -2 ? SERVER_FILTERS : ALL_FILTERS, protected_p); if (!file) return NULL; @@ -981,7 +995,7 @@ return; } save_filters = push_local_filters(fbuf, len); - send_directory(f, flist, fbuf, len); + send_directory(f, flist, fbuf, len, NULL); pop_local_filters(save_filters); fbuf[ol] = '\0'; if (is_dot_dir) @@ -995,13 +1009,14 @@ * might call this with f set to -2, which also indicates that local filter * rules should be ignored. */ static void send_directory(int f, struct file_list *flist, - char *fbuf, int len) + char *fbuf, int len, BOOL *pinned_p) { struct dirent *di; unsigned remainder; char *p; DIR *d; int start = flist->count; + BOOL protected; if (!(d = opendir(fbuf))) { io_error |= IOERR_GENERAL; @@ -1015,6 +1030,9 @@ *p = '\0'; remainder = MAXPATHLEN - (p - fbuf); + if (pinned_p) + *pinned_p = False; + for (errno = 0, di = readdir(d); di; errno = 0, di = readdir(d)) { char *dname = d_name(di); if (dname[0] == '.' && (dname[1] == '\0' @@ -1028,7 +1046,17 @@ continue; } - send_file_name(f, flist, fbuf, NULL, 0); + if (pinned_p == NULL) + send_file_name(f, flist, fbuf, NULL, 0, NULL); + else { + send_file_name(f, flist, fbuf, NULL, 0, &protected); + if (protected) { + *pinned_p = True; + if (verbose > 1) + rprintf(FINFO, "protected file %s pins parent directory\n", + fbuf); + } + } } fbuf[len] = '\0'; @@ -1245,7 +1273,7 @@ xfer_dirs = 1; while ((slash = strchr(slash+1, '/')) != 0) { *slash = '\0'; - send_file_name(f, flist, fbuf, NULL, 0); + send_file_name(f, flist, fbuf, NULL, 0, NULL); *slash = '/'; } copy_links = save_copy_links; @@ -1261,11 +1289,11 @@ if (recurse || (xfer_dirs && is_dot_dir)) { struct file_struct *file; - file = send_file_name(f, flist, fbuf, &st, FLAG_TOP_DIR); + file = send_file_name(f, flist, fbuf, &st, FLAG_TOP_DIR, NULL); if (file) send_if_directory(f, flist, file, fbuf, len); } else - send_file_name(f, flist, fbuf, &st, 0); + send_file_name(f, flist, fbuf, &st, 0, NULL); if (olddir[0]) { flist_dir = NULL; @@ -1637,7 +1665,7 @@ } prev_depth = file->dir.depth; if (is_excluded(f_name(file, fbuf), 1, - ALL_FILTERS)) { + ALL_FILTERS, False)) { /* Keep dirs through this dir. */ for (j = prev_depth-1; ; j--) { fp = flist->files[prev_i]; @@ -1889,9 +1917,10 @@ * exclude rules except for the daemon's. If "dlen" is >=0, it is the length * of the dirname string, and also indicates that "dirname" is a MAXPATHLEN * buffer (the functions we call will append names onto the end, but the old - * dir value will be restored on exit). */ + * dir value will be restored on exit). If pinned is not NULL, store in + * *pinned whether the directory is pinned by a protected file. */ struct file_list *get_dirlist(char *dirname, int dlen, - int ignore_filter_rules) + int ignore_filter_rules, BOOL *pinned) { struct file_list *dirlist; char dirbuf[MAXPATHLEN]; @@ -1909,7 +1938,7 @@ recurse = 0; xfer_dirs = 1; - send_directory(ignore_filter_rules ? -2 : -1, dirlist, dirname, dlen); + send_directory(ignore_filter_rules ? -2 : -1, dirlist, dirname, dlen, pinned); xfer_dirs = save_xfer_dirs; recurse = save_recurse; if (do_progress) Index: backup.c =================================================================== RCS file: /cvsroot/rsync/backup.c,v retrieving revision 1.56 diff -u -r1.56 backup.c --- backup.c 25 Apr 2006 23:51:12 -0000 1.56 +++ backup.c 11 Jun 2006 19:55:44 -0000 @@ -180,7 +180,7 @@ if (do_lstat(fname, &st) < 0) return 1; - if (!(file = make_file(fname, NULL, NULL, 0, NO_FILTERS))) + if (!(file = make_file(fname, NULL, NULL, 0, NO_FILTERS, NULL))) return 1; /* the file could have disappeared */ if (!(buf = get_backup_name(fname))) Index: log.c =================================================================== RCS file: /cvsroot/rsync/log.c,v retrieving revision 1.155 diff -u -r1.155 log.c --- log.c 7 Jun 2006 23:05:17 -0000 1.155 +++ log.c 11 Jun 2006 19:55:48 -0000 @@ -581,6 +581,14 @@ n = "*deleting"; break; } + if (iflags & ITEM_PINNED) { + n = "*pinned "; + break; + } + if (iflags & ITEM_HIT_MAX_DELETE) { + n = "*maxdel "; + break; + } n = buf2 + MAXPATHLEN - 32; n[0] = iflags & ITEM_LOCAL_CHANGE ? iflags & ITEM_XNAME_FOLLOWS ? 'h' : 'c' @@ -742,6 +750,52 @@ log_formatted(FLOG, fmt, "del.", &file, &stats, ITEM_DELETED, NULL); } +void log_pinned(char *fname, int mode) +{ + static struct file_struct file; + char *fmt; + + file.mode = mode; + file.basename = fname; + + if (!verbose && !stdout_format) + ; + else { + fmt = stdout_format_has_o_or_i ? stdout_format : "%n is pinned"; + log_formatted(FCLIENT, fmt, "pin ", &file, &stats, + ITEM_PINNED, NULL); + } + + if (!logfile_name || dry_run || !logfile_format) + return; + + fmt = logfile_format_has_o_or_i ? logfile_format : "%n is pinned"; + log_formatted(FLOG, fmt, "pin ", &file, &stats, ITEM_PINNED, NULL); +} + +void log_hit_max_delete(char *fname, int mode) +{ + static struct file_struct file; + char *fmt; + + file.mode = mode; + file.basename = fname; + + if (!verbose && !stdout_format) + ; + else { + fmt = stdout_format_has_o_or_i ? stdout_format : "%n is over --max-delete"; + log_formatted(FCLIENT, fmt, "mdel", &file, &stats, + ITEM_HIT_MAX_DELETE, NULL); + } + + if (!logfile_name || dry_run || !logfile_format) + return; + + fmt = logfile_format_has_o_or_i ? logfile_format : "%n is over --max-delete"; + log_formatted(FLOG, fmt, "mdel", &file, &stats, ITEM_HIT_MAX_DELETE, NULL); +} + /* * Called when the transfer is interrupted for some reason. * Index: proto.h =================================================================== RCS file: /cvsroot/rsync/proto.h,v retrieving revision 1.307 diff -u -r1.307 proto.h --- proto.h 7 Jun 2006 23:05:11 -0000 1.307 +++ proto.h 11 Jun 2006 19:55:49 -0000 @@ -72,7 +72,7 @@ void flist_expand(struct file_list *flist); struct file_struct *make_file(char *fname, struct file_list *flist, STRUCT_STAT *stp, unsigned short flags, - int filter_level); + int filter_level, BOOL *protected_p); struct file_list *send_file_list(int f, int argc, char *argv[]); struct file_list *recv_file_list(int f); int flist_find(struct file_list *flist, struct file_struct *f); @@ -82,7 +82,7 @@ int f_name_cmp(struct file_struct *f1, struct file_struct *f2); char *f_name(struct file_struct *f, char *fbuf); struct file_list *get_dirlist(char *dirname, int dlen, - int ignore_filter_rules); + int ignore_filter_rules, BOOL *pinned); int unchanged_attrs(struct file_struct *file, STRUCT_STAT *st); void itemize(struct file_struct *file, int ndx, int statret, STRUCT_STAT *st, int32 iflags, uchar fnamecmp_type, char *xname); @@ -197,6 +197,8 @@ void maybe_log_item(struct file_struct *file, int iflags, int itemizing, char *buf); void log_delete(char *fname, int mode); +void log_pinned(char *fname, int mode); +void log_hit_max_delete(char *fname, int mode); void log_exit(int code, const char *file, int line); pid_t wait_process(pid_t pid, int *status_ptr, int flags); int child_main(int argc, char *argv[]); Index: exclude.c =================================================================== RCS file: /cvsroot/rsync/exclude.c,v retrieving revision 1.134 diff -u -r1.134 exclude.c --- exclude.c 11 May 2006 07:54:33 -0000 1.134 +++ exclude.c 11 Jun 2006 19:55:45 -0000 @@ -51,8 +51,8 @@ #define MAX_RULE_PREFIX (16) #define MODIFIERS_MERGE_FILE "-+Cenw" -#define MODIFIERS_INCL_EXCL "/!Crs" -#define MODIFIERS_HIDE_PROTECT "/!" +#define MODIFIERS_INCL_EXCL "/!Crsp" +#define MODIFIERS_HIDE_PROTECT "/!p" /* The dirbuf is set by push_local_filters() to the current subdirectory * relative to curr_dir that is being processed. The path always has a @@ -595,7 +595,8 @@ /* * Return -1 if file "name" is defined to be excluded by the specified - * exclude list, 1 if it is included, and 0 if it was not matched. + * exclude list, -2 if it is excluded but parent protected, 1 if it is included, + * and 0 if it was not matched. */ int check_filter(struct filter_list_struct *listp, char *name, int name_is_dir) { @@ -619,7 +620,8 @@ if (rule_matches(name, ent, name_is_dir)) { report_filter_result(name, ent, name_is_dir, listp->debug_type); - return ent->match_flags & MATCHFLG_INCLUDE ? 1 : -1; + return ent->match_flags & MATCHFLG_PARENT_PROTECT ? -2 : + ent->match_flags & MATCHFLG_INCLUDE ? 1 : -1; } } @@ -809,6 +811,13 @@ case 'n': new_mflags |= MATCHFLG_NO_INHERIT; break; + case 'p': + if (new_mflags & MATCHFLG_INCLUDE + || new_mflags & MATCHFLG_SENDER_SIDE + || (delete_excluded && !(new_mflags & MATCHFLG_RECEIVER_SIDE))) + goto invalid; + new_mflags |= MATCHFLG_PARENT_PROTECT; + break; case 'r': new_mflags |= MATCHFLG_RECEIVER_SIDE; break; @@ -1079,6 +1088,12 @@ && (!for_xfer || protocol_version >= 29 || (delete_excluded && am_sender))) *op++ = 'r'; + if (match_flags & MATCHFLG_PARENT_PROTECT) { + if (!for_xfer || protocol_version >= 30) + *op++ = 'p'; + else if (am_sender) + return NULL; + } if (op - buf > legal_len) return NULL; if (legal_len) @@ -1124,7 +1139,7 @@ if (!p) { rprintf(FERROR, "filter rules are too modern for remote rsync.\n"); - exit_cleanup(RERR_SYNTAX); + exit_cleanup(RERR_PROTOCOL); } if (f_out < 0) continue;