diff options
Diffstat (limited to '')
-rw-r--r-- | delete.c | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/delete.c b/delete.c new file mode 100644 index 0000000..4a29485 --- /dev/null +++ b/delete.c @@ -0,0 +1,240 @@ +/* + * Deletion routines used in rsync. + * + * Copyright (C) 1996-2000 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2002 Martin Pool <mbp@samba.org> + * Copyright (C) 2003-2020 Wayne Davison + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, visit the http://fsf.org website. + */ + +#include "rsync.h" + +extern int am_root; +extern int make_backups; +extern int max_delete; +extern char *backup_dir; +extern char *backup_suffix; +extern int backup_suffix_len; +extern struct stats stats; + +int ignore_perishable = 0; +int non_perishable_cnt = 0; +int skipped_deletes = 0; + +static inline int is_backup_file(char *fn) +{ + int k = strlen(fn) - backup_suffix_len; + return k > 0 && strcmp(fn+k, backup_suffix) == 0; +} + +/* The directory is about to be deleted: if DEL_RECURSE is given, delete all + * its contents, otherwise just checks for content. Returns DR_SUCCESS or + * DR_NOT_EMPTY. Note that fname must point to a MAXPATHLEN buffer! (The + * buffer is used for recursion, but returned unchanged.) + */ +static enum delret delete_dir_contents(char *fname, uint16 flags) +{ + struct file_list *dirlist; + enum delret ret; + unsigned remainder; + void *save_filters; + int j, dlen; + char *p; + + if (DEBUG_GTE(DEL, 3)) { + rprintf(FINFO, "delete_dir_contents(%s) flags=%d\n", + fname, flags); + } + + dlen = strlen(fname); + save_filters = push_local_filters(fname, dlen); + + non_perishable_cnt = 0; + dirlist = get_dirlist(fname, dlen, 0); + ret = non_perishable_cnt ? DR_NOT_EMPTY : DR_SUCCESS; + + if (!dirlist->used) + goto done; + + if (!(flags & DEL_RECURSE)) { + ret = DR_NOT_EMPTY; + goto done; + } + + p = fname + dlen; + if (dlen != 1 || *fname != '/') + *p++ = '/'; + remainder = MAXPATHLEN - (p - fname); + + /* We do our own recursion, so make delete_item() non-recursive. */ + flags = (flags & ~(DEL_RECURSE|DEL_MAKE_ROOM|DEL_NO_UID_WRITE)) + | DEL_DIR_IS_EMPTY; + + for (j = dirlist->used; j--; ) { + struct file_struct *fp = dirlist->files[j]; + + if (fp->flags & FLAG_MOUNT_DIR && S_ISDIR(fp->mode)) { + if (DEBUG_GTE(DEL, 1)) { + rprintf(FINFO, + "mount point, %s, pins parent directory\n", + f_name(fp, NULL)); + } + ret = DR_NOT_EMPTY; + continue; + } + + strlcpy(p, fp->basename, remainder); + if (!(fp->mode & S_IWUSR) && !am_root && fp->flags & FLAG_OWNED_BY_US) + do_chmod(fname, fp->mode | S_IWUSR); + /* Save stack by recursing to ourself directly. */ + if (S_ISDIR(fp->mode)) { + if (delete_dir_contents(fname, flags | DEL_RECURSE) != DR_SUCCESS) + ret = DR_NOT_EMPTY; + } + if (delete_item(fname, fp->mode, flags) != DR_SUCCESS) + ret = DR_NOT_EMPTY; + } + + fname[dlen] = '\0'; + + done: + flist_free(dirlist); + pop_local_filters(save_filters); + + if (ret == DR_NOT_EMPTY) { + rprintf(FINFO, "cannot delete non-empty directory: %s\n", + fname); + } + return ret; +} + +/* Delete a file or directory. If DEL_RECURSE is set in the flags, this will + * delete recursively. + * + * Note that fbuf must point to a MAXPATHLEN buffer if the mode indicates it's + * a directory! (The buffer is used for recursion, but returned unchanged.) + */ +enum delret delete_item(char *fbuf, uint16 mode, uint16 flags) +{ + enum delret ret; + char *what; + int ok; + + if (DEBUG_GTE(DEL, 2)) { + rprintf(FINFO, "delete_item(%s) mode=%o flags=%d\n", + fbuf, (int)mode, (int)flags); + } + + if (flags & DEL_NO_UID_WRITE) + do_chmod(fbuf, mode | S_IWUSR); + + if (S_ISDIR(mode) && !(flags & DEL_DIR_IS_EMPTY)) { + /* This only happens on the first call to delete_item() since + * delete_dir_contents() always calls us w/DEL_DIR_IS_EMPTY. */ + ignore_perishable = 1; + /* If DEL_RECURSE is not set, this just reports emptiness. */ + ret = delete_dir_contents(fbuf, flags); + ignore_perishable = 0; + if (ret == DR_NOT_EMPTY || ret == DR_AT_LIMIT) + goto check_ret; + /* OK: try to delete the directory. */ + } + + if (!(flags & DEL_MAKE_ROOM) && max_delete >= 0 && stats.deleted_files >= max_delete) { + skipped_deletes++; + return DR_AT_LIMIT; + } + + if (S_ISDIR(mode)) { + what = "rmdir"; + ok = do_rmdir(fbuf) == 0; + } else { + if (make_backups > 0 && !(flags & DEL_FOR_BACKUP) && (backup_dir || !is_backup_file(fbuf))) { + what = "make_backup"; + ok = make_backup(fbuf, True); + if (ok == 2) { + what = "unlink"; + ok = robust_unlink(fbuf) == 0; + } + } else { + what = "unlink"; + ok = robust_unlink(fbuf) == 0; + } + } + + if (ok) { + if (!(flags & DEL_MAKE_ROOM)) { + log_delete(fbuf, mode); + stats.deleted_files++; + if (S_ISREG(mode)) { + /* Nothing more to count */ + } else if (S_ISDIR(mode)) + stats.deleted_dirs++; +#ifdef SUPPORT_LINKS + else if (S_ISLNK(mode)) + stats.deleted_symlinks++; +#endif + else if (IS_DEVICE(mode)) + stats.deleted_symlinks++; + else + stats.deleted_specials++; + } + ret = DR_SUCCESS; + } else { + if (S_ISDIR(mode) && errno == ENOTEMPTY) { + rprintf(FINFO, "cannot delete non-empty directory: %s\n", + fbuf); + ret = DR_NOT_EMPTY; + } else if (errno != ENOENT) { + rsyserr(FERROR_XFER, errno, "delete_file: %s(%s) failed", + what, fbuf); + ret = DR_FAILURE; + } else + ret = DR_SUCCESS; + } + + check_ret: + if (ret != DR_SUCCESS && flags & DEL_MAKE_ROOM) { + const char *desc; + switch (flags & DEL_MAKE_ROOM) { + case DEL_FOR_FILE: desc = "regular file"; break; + case DEL_FOR_DIR: desc = "directory"; break; + case DEL_FOR_SYMLINK: desc = "symlink"; break; + case DEL_FOR_DEVICE: desc = "device file"; break; + case DEL_FOR_SPECIAL: desc = "special file"; break; + default: exit_cleanup(RERR_UNSUPPORTED); /* IMPOSSIBLE */ + } + rprintf(FERROR_XFER, "could not make way for %s %s: %s\n", + flags & DEL_FOR_BACKUP ? "backup" : "new", + desc, fbuf); + } + return ret; +} + +uint16 get_del_for_flag(uint16 mode) +{ + if (S_ISREG(mode)) + return DEL_FOR_FILE; + if (S_ISDIR(mode)) + return DEL_FOR_DIR; + if (S_ISLNK(mode)) + return DEL_FOR_SYMLINK; + if (IS_DEVICE(mode)) + return DEL_FOR_DEVICE; + if (IS_SPECIAL(mode)) + return DEL_FOR_SPECIAL; + exit_cleanup(RERR_UNSUPPORTED); /* IMPOSSIBLE */ +} |