+ if ((preserve_devices && IS_DEVICE(mode))
+ || (preserve_specials && IS_SPECIAL(mode))) {
+ if (protocol_version < 28) {
+ if (!(xflags & XMIT_SAME_RDEV_pre28))
+ rdev = (dev_t)read_int(f);
+ } else {
+ uint32 rdev_minor;
+ if (!(xflags & XMIT_SAME_RDEV_MAJOR))
+ rdev_major = read_varint30(f);
+ if (protocol_version >= 30)
+ rdev_minor = read_varint(f);
+ else if (xflags & XMIT_RDEV_MINOR_8_pre30)
+ rdev_minor = read_byte(f);
+ else
+ rdev_minor = read_int(f);
+ rdev = MAKEDEV(rdev_major, rdev_minor);
+ }
+ extra_len += DEV_EXTRA_CNT * EXTRA_LEN;
+ file_length = 0;
+ } else if (protocol_version < 28)
+ rdev = MAKEDEV(0, 0);
+
+#ifdef SUPPORT_LINKS
+ if (preserve_links && S_ISLNK(mode)) {
+ linkname_len = read_varint30(f) + 1; /* count the '\0' */
+ if (linkname_len <= 0 || linkname_len > MAXPATHLEN) {
+ rprintf(FERROR, "overflow: linkname_len=%d\n",
+ linkname_len - 1);
+ overflow_exit("recv_file_entry");
+ }
+ }
+ else
+#endif
+ linkname_len = 0;
+
+#ifdef SUPPORT_HARD_LINKS
+ create_object:
+ if (preserve_hard_links) {
+ if (protocol_version < 28 && S_ISREG(mode))
+ xflags |= XMIT_HLINKED;
+ if (xflags & XMIT_HLINKED)
+ extra_len += (inc_recurse+1) * EXTRA_LEN;
+ }
+#endif
+
+#ifdef SUPPORT_ACLS
+ /* We need one or two index int32s when we're preserving ACLs. */
+ if (preserve_acls)
+ extra_len += (S_ISDIR(mode) ? 2 : 1) * EXTRA_LEN;
+#endif
+
+ if (always_checksum && S_ISREG(mode))
+ extra_len += SUM_EXTRA_CNT * EXTRA_LEN;
+
+ if (file_length > 0xFFFFFFFFu && S_ISREG(mode))
+ extra_len += EXTRA_LEN;
+
+#if EXTRA_ROUNDING > 0
+ if (extra_len & (EXTRA_ROUNDING * EXTRA_LEN))
+ extra_len = (extra_len | (EXTRA_ROUNDING * EXTRA_LEN)) + EXTRA_LEN;
+#endif
+
+ if (inc_recurse && S_ISDIR(mode)) {
+ if (one_file_system) {
+ /* Room to save the dir's device for -x */
+ extra_len += DEV_EXTRA_CNT * EXTRA_LEN;
+ }
+ pool = dir_flist->file_pool;
+ } else
+ pool = flist->file_pool;
+
+ alloc_len = FILE_STRUCT_LEN + extra_len + basename_len
+ + linkname_len;
+ bp = pool_alloc(pool, alloc_len, "recv_file_entry");
+
+ memset(bp, 0, extra_len + FILE_STRUCT_LEN);
+ bp += extra_len;
+ file = (struct file_struct *)bp;
+ bp += FILE_STRUCT_LEN;
+
+ memcpy(bp, basename, basename_len);
+ bp += basename_len + linkname_len; /* skip space for symlink too */
+
+#ifdef SUPPORT_HARD_LINKS
+ if (xflags & XMIT_HLINKED)
+ file->flags |= FLAG_HLINKED;
+#endif
+ file->modtime = (time_t)modtime;
+ file->len32 = (uint32)file_length;
+ if (file_length > 0xFFFFFFFFu && S_ISREG(mode)) {
+ file->flags |= FLAG_LENGTH64;
+ OPT_EXTRA(file, 0)->unum = (uint32)(file_length >> 32);
+ }
+ file->mode = mode;
+ if (uid_ndx)
+ F_OWNER(file) = uid;
+ if (gid_ndx) {
+ F_GROUP(file) = gid;
+ file->flags |= gid_flags;
+ }
+#ifdef ICONV_OPTION
+ if (ic_ndx)
+ F_NDX(file) = flist->used + flist->ndx_start;
+#endif
+
+ if (basename != thisname) {
+ file->dirname = lastdir;
+ F_DEPTH(file) = lastdir_depth + 1;
+ } else
+ F_DEPTH(file) = 1;
+
+ if (S_ISDIR(mode)) {
+ if (basename_len == 1+1 && *basename == '.') /* +1 for '\0' */
+ F_DEPTH(file)--;
+ if (xflags & XMIT_TOP_DIR) {
+ in_del_hier = recurse;
+ del_hier_name_len = F_DEPTH(file) == 0 ? 0 : l1 + l2;
+ if (relative_paths && del_hier_name_len > 2
+ && lastname[del_hier_name_len-1] == '.'
+ && lastname[del_hier_name_len-2] == '/')
+ del_hier_name_len -= 2;
+ file->flags |= FLAG_TOP_DIR | FLAG_XFER_DIR;
+ } else if (protocol_version >= 30) {
+ if (!(xflags & XMIT_NON_XFER_DIR))
+ file->flags |= FLAG_XFER_DIR;
+ } else if (in_del_hier) {
+ if (!relative_paths || !del_hier_name_len
+ || (l1 >= del_hier_name_len
+ && lastname[del_hier_name_len] == '/'))
+ file->flags |= FLAG_XFER_DIR;
+ else
+ in_del_hier = 0;
+ }
+ }
+
+ if ((preserve_devices && IS_DEVICE(mode))
+ || (preserve_specials && IS_SPECIAL(mode))) {
+ uint32 *devp = F_RDEV_P(file);
+ DEV_MAJOR(devp) = major(rdev);
+ DEV_MINOR(devp) = minor(rdev);
+ }
+
+#ifdef SUPPORT_LINKS
+ if (linkname_len) {
+ bp = (char*)file->basename + basename_len;
+ if (first_hlink_ndx >= flist->ndx_start) {
+ struct file_struct *first = flist->files[first_hlink_ndx - flist->ndx_start];
+ memcpy(bp, F_SYMLINK(first), linkname_len);
+ } else
+ read_sbuf(f, bp, linkname_len - 1);
+ if (sanitize_paths)
+ sanitize_path(bp, bp, "", lastdir_depth, NULL);
+ }
+#endif
+
+#ifdef SUPPORT_HARD_LINKS
+ if (preserve_hard_links && xflags & XMIT_HLINKED) {
+ if (protocol_version >= 30) {
+ F_HL_GNUM(file) = xflags & XMIT_HLINK_FIRST
+ ? flist->ndx_start + flist->used
+ : first_hlink_ndx;
+ } else {
+ static int32 cnt = 0;
+ struct ht_int64_node *np;
+ int64 ino;
+ int32 ndx;
+ if (protocol_version < 26) {
+ dev = read_int(f);
+ ino = read_int(f);
+ } else {
+ if (!(xflags & XMIT_SAME_DEV_pre30))
+ dev = read_longint(f);
+ ino = read_longint(f);
+ }
+ np = idev_find(dev, ino);
+ ndx = (int32)(long)np->data - 1;
+ if (ndx < 0) {
+ ndx = cnt++;
+ np->data = (void*)(long)cnt;
+ }
+ F_HL_GNUM(file) = ndx;
+ }
+ }
+#endif
+
+ if (always_checksum && (S_ISREG(mode) || protocol_version < 28)) {
+ if (S_ISREG(mode))
+ bp = F_SUM(file);
+ else {
+ /* Prior to 28, we get a useless set of nulls. */
+ bp = tmp_sum;
+ }
+ if (first_hlink_ndx >= flist->ndx_start) {
+ struct file_struct *first = flist->files[first_hlink_ndx - flist->ndx_start];
+ memcpy(bp, F_SUM(first), checksum_len);
+ } else
+ read_buf(f, bp, checksum_len);
+ }
+
+#ifdef SUPPORT_ACLS
+ if (preserve_acls && !S_ISLNK(mode))
+ receive_acl(file, f);
+#endif
+#ifdef SUPPORT_XATTRS
+ if (preserve_xattrs)
+ receive_xattr(file, f );
+#endif
+
+ if (S_ISREG(mode) || S_ISLNK(mode))
+ stats.total_size += file_length;
+
+ return file;
+}
+
+/* Create a file_struct for a named file by reading its stat() information
+ * and performing extensive checks against global options.
+ *
+ * Returns a pointer to the new file struct, or NULL if there was an error
+ * or this file should be excluded. */
+struct file_struct *make_file(const char *fname, struct file_list *flist,
+ STRUCT_STAT *stp, int flags, int filter_level)
+{
+ static char *lastdir;
+ static int lastdir_len = -1;
+ struct file_struct *file;
+ char thisname[MAXPATHLEN];
+ char linkname[MAXPATHLEN];
+ int alloc_len, basename_len, linkname_len;
+ int extra_len = file_extra_cnt * EXTRA_LEN;
+ const char *basename;
+ alloc_pool_t *pool;
+ STRUCT_STAT st;
+ char *bp;
+
+ if (strlcpy(thisname, fname, sizeof thisname) >= sizeof thisname) {
+ rprintf(FINFO, "skipping overly long name: %s\n", fname);
+ return NULL;
+ }
+ clean_fname(thisname, 0);
+ if (sanitize_paths)
+ sanitize_path(thisname, thisname, "", 0, NULL);
+
+ if (stp && S_ISDIR(stp->st_mode)) {
+ st = *stp; /* Needed for "symlink/." with --relative. */
+ *linkname = '\0'; /* make IBM code checker happy */
+ } else if (readlink_stat(thisname, &st, linkname) != 0) {
+ 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, 1, filter_level))) {
+ if (ignore_perishable && save_errno != ENOENT)
+ non_perishable_cnt++;
+ return NULL;
+ }
+ if (save_errno == ENOENT) {
+#ifdef SUPPORT_LINKS
+ /* Avoid "vanished" error if symlink points nowhere. */
+ if (copy_links && x_lstat(thisname, &st, NULL) == 0
+ && S_ISLNK(st.st_mode)) {
+ io_error |= IOERR_GENERAL;
+ rprintf(FERROR, "symlink has no referent: %s\n",
+ full_fname(thisname));
+ } else
+#endif
+ {
+ enum logcode c = am_daemon && protocol_version < 28
+ ? FERROR : FINFO;
+ io_error |= IOERR_VANISHED;
+ rprintf(c, "file has vanished: %s\n",
+ full_fname(thisname));
+ }
+ } else {
+ io_error |= IOERR_GENERAL;
+ rsyserr(FERROR, save_errno, "readlink %s failed",
+ full_fname(thisname));
+ }
+ return NULL;
+ }
+
+ /* backup.c calls us with filter_level set to NO_FILTERS. */
+ if (filter_level == NO_FILTERS)
+ goto skip_filters;
+
+ if (S_ISDIR(st.st_mode)) {
+ if (!xfer_dirs) {
+ rprintf(FINFO, "skipping directory %s\n", thisname);
+ return NULL;
+ }
+ } else
+ flags &= ~FLAG_XFER_DIR;
+
+ /* -x only affects directories because we need to avoid recursing
+ * into a mount-point directory, not to avoid copying a symlinked
+ * file if -L (or similar) was specified. */
+ if (one_file_system && st.st_dev != filesystem_dev
+ && S_ISDIR(st.st_mode)) {
+ if (one_file_system > 1) {
+ if (verbose > 2) {
+ rprintf(FINFO, "skipping mount-point dir %s\n",
+ thisname);
+ }
+ return NULL;
+ }
+ flags |= FLAG_MOUNT_DIR;
+ }
+
+ if (is_excluded(thisname, S_ISDIR(st.st_mode) != 0, filter_level)) {
+ if (ignore_perishable)
+ non_perishable_cnt++;
+ return NULL;
+ }
+
+ if (lp_ignore_nonreadable(module_id)) {
+#ifdef SUPPORT_LINKS
+ if (!S_ISLNK(st.st_mode))
+#endif
+ if (access(thisname, R_OK) != 0)
+ return NULL;
+ }
+
+ skip_filters:
+
+ /* Only divert a directory in the main transfer. */
+ if (flist) {
+ if (flist->prev && S_ISDIR(st.st_mode)
+ && flags & FLAG_DIVERT_DIRS) {
+ /* Room for parent/sibling/next-child info. */
+ extra_len += DIRNODE_EXTRA_CNT * EXTRA_LEN;
+ dir_count++;
+ pool = dir_flist->file_pool;
+ } else
+ pool = flist->file_pool;
+ } else
+ pool = NULL;
+
+ if (verbose > 2) {
+ rprintf(FINFO, "[%s] make_file(%s,*,%d)\n",
+ who_am_i(), thisname, filter_level);
+ }
+
+ if ((basename = strrchr(thisname, '/')) != NULL) {
+ int len = basename++ - thisname;
+ if (len != lastdir_len || memcmp(thisname, lastdir, len) != 0) {
+ lastdir = new_array(char, len + 1);
+ memcpy(lastdir, thisname, len);
+ lastdir[len] = '\0';
+ lastdir_len = len;
+ }
+ } else
+ basename = thisname;
+ basename_len = strlen(basename) + 1; /* count the '\0' */
+
+#ifdef SUPPORT_LINKS
+ linkname_len = S_ISLNK(st.st_mode) ? strlen(linkname) + 1 : 0;
+#else
+ linkname_len = 0;
+#endif
+
+ if (st.st_size > 0xFFFFFFFFu && S_ISREG(st.st_mode))
+ extra_len += EXTRA_LEN;
+
+#if EXTRA_ROUNDING > 0
+ if (extra_len & (EXTRA_ROUNDING * EXTRA_LEN))
+ extra_len = (extra_len | (EXTRA_ROUNDING * EXTRA_LEN)) + EXTRA_LEN;
+#endif
+
+ alloc_len = FILE_STRUCT_LEN + extra_len + basename_len
+ + linkname_len;
+ if (pool)
+ bp = pool_alloc(pool, alloc_len, "make_file");
+ else {
+ if (!(bp = new_array(char, alloc_len)))
+ out_of_memory("make_file");
+ }
+
+ memset(bp, 0, extra_len + FILE_STRUCT_LEN);
+ bp += extra_len;
+ file = (struct file_struct *)bp;
+ bp += FILE_STRUCT_LEN;
+
+ memcpy(bp, basename, basename_len);
+ bp += basename_len + linkname_len; /* skip space for symlink too */
+
+#ifdef SUPPORT_HARD_LINKS
+ if (preserve_hard_links && flist && flist->prev) {
+ if (protocol_version >= 28
+ ? (!S_ISDIR(st.st_mode) && st.st_nlink > 1)
+ : S_ISREG(st.st_mode)) {
+ tmp_dev = st.st_dev;
+ tmp_ino = st.st_ino;
+ } else
+ tmp_dev = 0;
+ }
+#endif
+
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ if (IS_DEVICE(st.st_mode) || IS_SPECIAL(st.st_mode)) {
+ tmp_rdev = st.st_rdev;
+ st.st_size = 0;
+ }
+#endif
+
+ file->flags = flags;
+ file->modtime = st.st_mtime;
+ file->len32 = (uint32)st.st_size;
+ if (st.st_size > 0xFFFFFFFFu && S_ISREG(st.st_mode)) {
+ file->flags |= FLAG_LENGTH64;
+ OPT_EXTRA(file, 0)->unum = (uint32)(st.st_size >> 32);
+ }
+ file->mode = st.st_mode;
+ if (uid_ndx)
+ F_OWNER(file) = st.st_uid;
+ if (gid_ndx)
+ F_GROUP(file) = st.st_gid;
+
+ if (basename != thisname)
+ file->dirname = lastdir;
+
+#ifdef SUPPORT_LINKS
+ if (linkname_len) {
+ bp = (char*)file->basename + basename_len;
+ memcpy(bp, linkname, linkname_len);
+ }
+#endif
+
+ if (always_checksum && am_sender && S_ISREG(st.st_mode))
+ file_checksum(thisname, tmp_sum, st.st_size);
+
+ F_PATHNAME(file) = pathname;
+
+ /* This code is only used by the receiver when it is building
+ * a list of files for a delete pass. */
+ if (keep_dirlinks && linkname_len && flist) {
+ STRUCT_STAT st2;
+ int save_mode = file->mode;
+ file->mode = S_IFDIR; /* Find a directory with our name. */
+ if (flist_find(dir_flist, file) >= 0
+ && x_stat(thisname, &st2, NULL) == 0 && S_ISDIR(st2.st_mode)) {
+ file->modtime = st2.st_mtime;
+ file->len32 = 0;
+ file->mode = st2.st_mode;
+ if (uid_ndx)
+ F_OWNER(file) = st2.st_uid;
+ if (gid_ndx)
+ F_GROUP(file) = st2.st_gid;
+ } else
+ file->mode = save_mode;
+ }
+
+ if (basename_len == 0+1)
+ return NULL;
+
+#ifdef ICONV_OPTION
+ if (ic_ndx)
+ F_NDX(file) = dir_count - 1;
+#endif
+
+ return file;
+}
+
+/* Only called for temporary file_struct entries created by make_file(). */
+void unmake_file(struct file_struct *file)
+{
+ int extra_cnt = file_extra_cnt + LEN64_BUMP(file);
+#if EXTRA_ROUNDING > 0
+ if (extra_cnt & EXTRA_ROUNDING)
+ extra_cnt = (extra_cnt | EXTRA_ROUNDING) + 1;
+#endif
+ free(REQ_EXTRA(file, extra_cnt));
+}
+
+static struct file_struct *send_file_name(int f, struct file_list *flist,
+ char *fname, STRUCT_STAT *stp,
+ int flags, int filter_level)
+{
+ struct file_struct *file;
+#if defined SUPPORT_ACLS || defined SUPPORT_XATTRS
+ statx sx;
+#endif
+
+ file = make_file(fname, flist, stp, flags, filter_level);
+ if (!file)
+ return NULL;
+
+ if (chmod_modes && !S_ISLNK(file->mode))
+ file->mode = tweak_mode(file->mode, chmod_modes);
+
+#ifdef SUPPORT_ACLS
+ if (preserve_acls && !S_ISLNK(file->mode) && f >= 0) {
+ sx.st.st_mode = file->mode;
+ sx.acc_acl = sx.def_acl = NULL;
+ if (get_acl(fname, &sx) < 0)
+ return NULL;
+ }
+#endif
+#ifdef SUPPORT_XATTRS
+ if (preserve_xattrs && f >= 0) {
+ sx.xattr = NULL;
+ if (get_xattr(fname, &sx) < 0)
+ return NULL;
+ }
+#endif
+
+ maybe_emit_filelist_progress(flist->used + flist_count_offset);
+
+ flist_expand(flist, 1);
+ flist->files[flist->used++] = file;
+ if (f >= 0) {
+ send_file_entry(f, file, flist->used - 1, flist->ndx_start);
+#ifdef SUPPORT_ACLS
+ if (preserve_acls && !S_ISLNK(file->mode)) {
+ send_acl(&sx, f);
+ free_acl(&sx);
+ }
+#endif
+#ifdef SUPPORT_XATTRS
+ if (preserve_xattrs) {
+ F_XATTR(file) = send_xattr(&sx, f);
+ free_xattr(&sx);
+ }
+#endif
+ }
+ return file;
+}
+
+static void send_if_directory(int f, struct file_list *flist,
+ struct file_struct *file,
+ char *fbuf, unsigned int ol,
+ int flags)
+{
+ char is_dot_dir = fbuf[ol-1] == '.' && (ol == 1 || fbuf[ol-2] == '/');
+
+ if (S_ISDIR(file->mode)
+ && !(file->flags & FLAG_MOUNT_DIR) && f_name(file, fbuf)) {
+ void *save_filters;
+ unsigned int len = strlen(fbuf);
+ if (len > 1 && fbuf[len-1] == '/')
+ fbuf[--len] = '\0';
+ if (len >= MAXPATHLEN - 1) {
+ io_error |= IOERR_GENERAL;
+ rprintf(FERROR, "skipping long-named directory: %s\n",
+ full_fname(fbuf));
+ return;
+ }
+ save_filters = push_local_filters(fbuf, len);
+ send_directory(f, flist, fbuf, len, flags);
+ pop_local_filters(save_filters);
+ fbuf[ol] = '\0';
+ if (is_dot_dir)
+ fbuf[ol-1] = '.';
+ }
+}
+
+static int file_compare(const void *file1, const void *file2)
+{
+ return f_name_cmp(*(struct file_struct **)file1,
+ *(struct file_struct **)file2);
+}
+
+/* The guts of a merge-sort algorithm. This was derived from the glibc
+ * version, but I (Wayne) changed the merge code to do less copying and
+ * to require only half the amount of temporary memory. */
+static void fsort_tmp(struct file_struct **fp, size_t num,
+ struct file_struct **tmp)
+{
+ struct file_struct **f1, **f2, **t;
+ size_t n1, n2;
+
+ n1 = num / 2;
+ n2 = num - n1;
+ f1 = fp;
+ f2 = fp + n1;
+
+ if (n1 > 1)
+ fsort_tmp(f1, n1, tmp);
+ if (n2 > 1)
+ fsort_tmp(f2, n2, tmp);
+
+ while (f_name_cmp(*f1, *f2) <= 0) {
+ if (!--n1)
+ return;
+ f1++;
+ }
+
+ t = tmp;
+ memcpy(t, f1, n1 * PTR_SIZE);
+
+ *f1++ = *f2++, n2--;
+
+ while (n1 > 0 && n2 > 0) {
+ if (f_name_cmp(*t, *f2) <= 0)
+ *f1++ = *t++, n1--;
+ else
+ *f1++ = *f2++, n2--;
+ }
+
+ if (n1 > 0)
+ memcpy(f1, t, n1 * PTR_SIZE);
+}
+
+/* This file-struct sorting routine makes sure that any identical names in
+ * the file list stay in the same order as they were in the original list.
+ * This is particularly vital in inc_recurse mode where we expect a sort
+ * on the flist to match the exact order of a sort on the dir_flist. */
+static void fsort(struct file_struct **fp, size_t num)
+{
+ if (num <= 1)
+ return;
+
+ if (use_qsort)
+ qsort(fp, num, PTR_SIZE, file_compare);
+ else {
+ struct file_struct **tmp = new_array(struct file_struct *,
+ (num+1) / 2);
+ fsort_tmp(fp, num, tmp);
+ free(tmp);
+ }
+}
+
+/* We take an entire set of sibling dirs from the sorted flist and link them
+ * into the tree, setting the appropriate parent/child/sibling pointers. */
+static void add_dirs_to_tree(int parent_ndx, struct file_list *from_flist,
+ int dir_cnt)
+{
+ int i;
+ int32 *dp = NULL;
+ int32 *parent_dp = parent_ndx < 0 ? NULL
+ : F_DIR_NODE_P(dir_flist->sorted[parent_ndx]);
+
+ flist_expand(dir_flist, dir_cnt);
+ dir_flist->sorted = dir_flist->files;
+
+ for (i = 0; dir_cnt; i++) {
+ struct file_struct *file = from_flist->sorted[i];
+
+ if (!S_ISDIR(file->mode))
+ continue;