+extern int unsort_ndx;
+extern int max_delete;
+extern int force_delete;
+extern int one_file_system;
+extern struct stats stats;
+extern dev_t filesystem_dev;
+extern mode_t orig_umask;
+extern uid_t our_uid;
+extern char *backup_dir;
+extern char *backup_suffix;
+extern int backup_suffix_len;
+extern char *basis_dir[MAX_BASIS_DIRS+1];
+extern struct file_list *cur_flist, *first_flist, *dir_flist;
+extern struct filter_list_struct daemon_filter_list;
+
+int ignore_perishable = 0;
+int non_perishable_cnt = 0;
+int maybe_ATTRS_REPORT = 0;
+
+static dev_t dev_zero;
+static int deletion_count = 0; /* used to implement --max-delete */
+static int deldelay_size = 0, deldelay_cnt = 0;
+static char *deldelay_buf = NULL;
+static int deldelay_fd = -1;
+static int loopchk_limit;
+static int dir_tweaking;
+static int symlink_timeset_failed_flags;
+static int need_retouch_dir_times;
+static int need_retouch_dir_perms;
+static const char *solo_file = NULL;
+
+/* For calling delete_item() and delete_dir_contents(). */
+#define DEL_NO_UID_WRITE (1<<0) /* file/dir has our uid w/o write perm */
+#define DEL_RECURSE (1<<1) /* if dir, delete all contents */
+#define DEL_DIR_IS_EMPTY (1<<2) /* internal delete_FUNCTIONS use only */
+#define DEL_FOR_FILE (1<<3) /* making room for a replacement file */
+#define DEL_FOR_DIR (1<<4) /* making room for a replacement dir */
+#define DEL_FOR_SYMLINK (1<<5) /* making room for a replacement symlink */
+#define DEL_FOR_DEVICE (1<<6) /* making room for a replacement device */
+#define DEL_FOR_SPECIAL (1<<7) /* making room for a replacement special */
+
+#define DEL_MAKE_ROOM (DEL_FOR_FILE|DEL_FOR_DIR|DEL_FOR_SYMLINK|DEL_FOR_DEVICE|DEL_FOR_SPECIAL)
+
+enum nonregtype {
+ TYPE_DIR, TYPE_SPECIAL, TYPE_DEVICE, TYPE_SYMLINK
+};
+
+enum delret {
+ DR_SUCCESS = 0, DR_FAILURE, DR_AT_LIMIT, DR_NOT_EMPTY
+};
+
+/* Forward declarations. */
+static enum delret delete_dir_contents(char *fname, uint16 flags);
+#ifdef SUPPORT_HARD_LINKS
+static void handle_skipped_hlink(struct file_struct *file, int itemizing,
+ enum logcode code, int f_out);
+#endif
+
+static int is_backup_file(char *fn)
+{
+ int k = strlen(fn) - backup_suffix_len;
+ return k > 0 && strcmp(fn+k, backup_suffix) == 0;
+}
+
+/* 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.)
+ */
+static 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)) {
+ int save_uid_ndx = uid_ndx;
+ /* This only happens on the first call to delete_item() since
+ * delete_dir_contents() always calls us w/DEL_DIR_IS_EMPTY. */
+ if (!uid_ndx)
+ uid_ndx = ++file_extra_cnt;
+ ignore_perishable = 1;
+ /* If DEL_RECURSE is not set, this just reports emptiness. */
+ ret = delete_dir_contents(fbuf, flags);
+ ignore_perishable = 0;
+ if (!save_uid_ndx) {
+ --file_extra_cnt;
+ uid_ndx = 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 && ++deletion_count > max_delete)
+ return DR_AT_LIMIT;
+
+ if (S_ISDIR(mode)) {
+ what = "rmdir";
+ ok = do_rmdir(fbuf) == 0;
+ } else if (make_backups > 0 && (backup_dir || !is_backup_file(fbuf))) {
+ what = "make_backup";
+ ok = make_backup(fbuf);
+ } else {
+ what = "unlink";
+ ok = robust_unlink(fbuf) == 0;
+ }
+
+ if (ok) {
+ if (!(flags & DEL_MAKE_ROOM))
+ log_delete(fbuf, mode);
+ 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, errno, "delete_file: %s(%s) failed",
+ what, fbuf);
+ ret = DR_FAILURE;
+ } else {
+ deletion_count--;
+ 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 new %s: %s\n",
+ desc, fbuf);
+ }
+ return ret;
+}
+
+/* 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 && (uid_t)F_OWNER(fp) == our_uid)
+ 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;
+}
+
+static int start_delete_delay_temp(void)
+{
+ char fnametmp[MAXPATHLEN];
+ int save_dry_run = dry_run;
+
+ dry_run = 0;
+ if (!get_tmpname(fnametmp, "deldelay")
+ || (deldelay_fd = do_mkstemp(fnametmp, 0600)) < 0) {
+ rprintf(FINFO, "NOTE: Unable to create delete-delay temp file%s.\n",
+ inc_recurse ? "" : " -- switching to --delete-after");
+ delete_during = 0;
+ delete_after = !inc_recurse;
+ dry_run = save_dry_run;
+ return 0;
+ }
+ unlink(fnametmp);
+ dry_run = save_dry_run;
+ return 1;
+}
+
+static int flush_delete_delay(void)
+{
+ if (deldelay_fd < 0 && !start_delete_delay_temp())
+ return 0;
+ if (write(deldelay_fd, deldelay_buf, deldelay_cnt) != deldelay_cnt) {
+ rsyserr(FERROR, errno, "flush of delete-delay buffer");
+ delete_during = 0;
+ delete_after = !inc_recurse;
+ close(deldelay_fd);
+ return 0;
+ }
+ deldelay_cnt = 0;
+ return 1;
+}
+
+static int remember_delete(struct file_struct *file, const char *fname, int flags)
+{
+ int len;
+
+ if (deldelay_cnt == deldelay_size && !flush_delete_delay())
+ return 0;
+
+ if (flags & DEL_NO_UID_WRITE)
+ deldelay_buf[deldelay_cnt++] = '!';
+
+ while (1) {
+ len = snprintf(deldelay_buf + deldelay_cnt,
+ deldelay_size - deldelay_cnt,
+ "%x %s%c",
+ (int)file->mode, fname, '\0');
+ if ((deldelay_cnt += len) <= deldelay_size)
+ break;
+ deldelay_cnt -= len;
+ if (!flush_delete_delay())
+ return 0;
+ }
+
+ return 1;
+}
+
+static int read_delay_line(char *buf, int *flags_p)
+{
+ static int read_pos = 0;
+ int j, len, mode;
+ char *bp, *past_space;
+
+ while (1) {
+ for (j = read_pos; j < deldelay_cnt && deldelay_buf[j]; j++) {}
+ if (j < deldelay_cnt)
+ break;
+ if (deldelay_fd < 0) {
+ if (j > read_pos)
+ goto invalid_data;
+ return -1;
+ }
+ deldelay_cnt -= read_pos;
+ if (deldelay_cnt == deldelay_size)
+ goto invalid_data;
+ if (deldelay_cnt && read_pos) {
+ memmove(deldelay_buf, deldelay_buf + read_pos,
+ deldelay_cnt);
+ }
+ len = read(deldelay_fd, deldelay_buf + deldelay_cnt,
+ deldelay_size - deldelay_cnt);
+ if (len == 0) {
+ if (deldelay_cnt) {
+ rprintf(FERROR,
+ "ERROR: unexpected EOF in delete-delay file.\n");
+ }
+ return -1;
+ }
+ if (len < 0) {
+ rsyserr(FERROR, errno,
+ "reading delete-delay file");
+ return -1;
+ }
+ deldelay_cnt += len;
+ read_pos = 0;
+ }
+
+ bp = deldelay_buf + read_pos;
+ if (*bp == '!') {
+ bp++;
+ *flags_p = DEL_NO_UID_WRITE;
+ } else
+ *flags_p = 0;
+
+ if (sscanf(bp, "%x ", &mode) != 1) {
+ invalid_data:
+ rprintf(FERROR, "ERROR: invalid data in delete-delay file.\n");
+ return -1;
+ }
+ past_space = strchr(bp, ' ') + 1;
+ len = j - read_pos - (past_space - bp) + 1; /* count the '\0' */
+ read_pos = j + 1;
+
+ if (len > MAXPATHLEN) {
+ rprintf(FERROR, "ERROR: filename too long in delete-delay file.\n");
+ return -1;
+ }
+
+ /* The caller needs the name in a MAXPATHLEN buffer, so we copy it
+ * instead of returning a pointer to our buffer. */
+ memcpy(buf, past_space, len);
+
+ return mode;
+}
+
+static void do_delayed_deletions(char *delbuf)
+{
+ int mode, flags;
+
+ if (deldelay_fd >= 0) {
+ if (deldelay_cnt && !flush_delete_delay())
+ return;
+ lseek(deldelay_fd, 0, 0);
+ }
+ while ((mode = read_delay_line(delbuf, &flags)) >= 0)
+ delete_item(delbuf, mode, flags | DEL_RECURSE);
+ if (deldelay_fd >= 0)
+ close(deldelay_fd);
+}
+
+/* This function is used to implement per-directory deletion, and is used by
+ * all the --delete-WHEN options. Note that the fbuf pointer must point to a
+ * MAXPATHLEN buffer with the name of the directory in it (the functions we
+ * call will append names onto the end, but the old dir value will be restored
+ * on exit). */
+static void delete_in_dir(char *fbuf, struct file_struct *file, dev_t *fs_dev)
+{
+ static int already_warned = 0;
+ struct file_list *dirlist;
+ char delbuf[MAXPATHLEN];
+ int dlen, i;
+ int save_uid_ndx = uid_ndx;
+
+ if (!fbuf) {
+ change_local_filter_dir(NULL, 0, 0);
+ return;
+ }
+
+ if (DEBUG_GTE(DEL, 2))
+ rprintf(FINFO, "delete_in_dir(%s)\n", fbuf);
+
+ if (allowed_lull)
+ maybe_send_keepalive();
+
+ if (io_error && !ignore_errors) {
+ if (already_warned)
+ return;
+ rprintf(FINFO,
+ "IO error encountered -- skipping file deletion\n");
+ already_warned = 1;
+ return;
+ }
+
+ dlen = strlen(fbuf);
+ change_local_filter_dir(fbuf, dlen, F_DEPTH(file));
+
+ if (one_file_system) {
+ if (file->flags & FLAG_TOP_DIR)
+ filesystem_dev = *fs_dev;
+ else if (filesystem_dev != *fs_dev)
+ return;
+ }
+
+ if (!uid_ndx)
+ uid_ndx = ++file_extra_cnt;
+
+ dirlist = get_dirlist(fbuf, dlen, 0);
+
+ /* If an item in dirlist is not found in flist, delete it
+ * from the filesystem. */
+ for (i = dirlist->used; i--; ) {
+ struct file_struct *fp = dirlist->files[i];
+ if (!F_IS_ACTIVE(fp))
+ continue;
+ if (fp->flags & FLAG_MOUNT_DIR && S_ISDIR(fp->mode)) {
+ if (INFO_GTE(MOUNT, 1))
+ rprintf(FINFO, "cannot delete mount point: %s\n",
+ f_name(fp, NULL));
+ continue;
+ }
+ if (flist_find(cur_flist, fp) < 0) {
+ int flags = DEL_RECURSE;
+ if (!(fp->mode & S_IWUSR) && !am_root && (uid_t)F_OWNER(fp) == our_uid)
+ flags |= DEL_NO_UID_WRITE;
+ f_name(fp, delbuf);
+ if (delete_during == 2) {
+ if (!remember_delete(fp, delbuf, flags))
+ break;
+ } else
+ delete_item(delbuf, fp->mode, flags);
+ }
+ }
+
+ flist_free(dirlist);
+
+ if (!save_uid_ndx) {
+ --file_extra_cnt;
+ uid_ndx = 0;
+ }
+}
+
+/* This deletes any files on the receiving side that are not present on the
+ * sending side. This is used by --delete-before and --delete-after. */
+static void do_delete_pass(void)
+{
+ char fbuf[MAXPATHLEN];
+ STRUCT_STAT st;
+ int j;
+
+ /* dry_run is incremented when the destination doesn't exist yet. */
+ if (dry_run > 1 || list_only)
+ return;
+
+ for (j = 0; j < cur_flist->used; j++) {
+ struct file_struct *file = cur_flist->sorted[j];
+
+ f_name(file, fbuf);
+
+ if (!(file->flags & FLAG_CONTENT_DIR)) {
+ change_local_filter_dir(fbuf, strlen(fbuf), F_DEPTH(file));
+ continue;
+ }
+
+ if (DEBUG_GTE(DEL, 1) && file->flags & FLAG_TOP_DIR)
+ rprintf(FINFO, "deleting in %s\n", fbuf);
+
+ if (link_stat(fbuf, &st, keep_dirlinks) < 0
+ || !S_ISDIR(st.st_mode))
+ continue;
+
+ delete_in_dir(fbuf, file, &st.st_dev);
+ }
+ delete_in_dir(NULL, NULL, &dev_zero);