This is the fixed, reformatted patch to add the -m option to openrsync Thanks to j...@openbsd.org for feedback on style and for reviewing. Looking forward for feedback.
Index: extern.h =================================================================== RCS file: /cvs/src/usr.bin/rsync/extern.h,v retrieving revision 1.44 diff -u -p -u -p -r1.44 extern.h --- extern.h 2 Aug 2022 18:09:20 -0000 1.44 +++ extern.h 22 Mar 2023 10:19:28 -0000 @@ -134,6 +134,7 @@ struct opts { int server; /* --server */ int recursive; /* -r */ int dry_run; /* -n */ + int prune_empty_dirs; /* -m */ int preserve_times; /* -t */ int preserve_perms; /* -p */ int preserve_links; /* -l */ @@ -359,7 +360,8 @@ int rsync_set_metadata(struct sess *, in const char *); int rsync_set_metadata_at(struct sess *, int, int, const struct flist *, const char *); -int rsync_uploader(struct upload *, int *, struct sess *, int *); +int rsync_uploader(struct upload *, int *, struct sess *, int *, + char **, int); int rsync_uploader_tail(struct upload *, struct sess *); struct download *download_alloc(struct sess *, int, const struct flist *, Index: flist.c =================================================================== RCS file: /cvs/src/usr.bin/rsync/flist.c,v retrieving revision 1.37 diff -u -p -u -p -r1.37 flist.c --- flist.c 26 Dec 2022 19:16:02 -0000 1.37 +++ flist.c 22 Mar 2023 10:19:36 -0000 @@ -247,6 +247,42 @@ flist_free(struct flist *f, size_t sz) } /* + * Inspect a filetree for empty directory chains. + * 0 for the detection of a non-directory. + * 1 for the detection of an empty chain. + * 2 for errors. + */ + +static int +flist_emptychain(FTSENT *ent) +{ + FTS *prunefts; + FTSENT *prunent; + char *prune_cargv[2]; + int empty_chain = 1; + + prune_cargv[0] = ent->fts_accpath; + prune_cargv[1] = NULL; + + if ((prunefts = fts_open(prune_cargv, + FTS_PHYSICAL, NULL)) == NULL) { + ERR("fts_open"); + return 2; + } + + while ((prunent = fts_read(prunefts)) != NULL) { + if (prunent->fts_info != FTS_D && + prunent->fts_info != FTS_DP) { + empty_chain = 0; + fts_close(prunefts); + return empty_chain; + } + } + fts_close(prunefts); + return empty_chain; +} + +/* * Serialise our file list (which may be zero-length) to the wire. * Makes sure that the receiver isn't going to block on sending us * return messages on the log channel. @@ -803,7 +839,7 @@ flist_gen_dirent(struct sess *sess, char size_t *max) { char *cargv[2], *cp; - int rc = 0, flag; + int rc = 0, ec = 1, flag; FTS *fts; FTSENT *ent; struct flist *f; @@ -905,6 +941,21 @@ flist_gen_dirent(struct sess *sess, char if (!flist_fts_check(sess, ent)) { errno = 0; continue; + } + + /* + * If -m (prune empty dirs) is enabled, create a new fts + * to independently traverse directories at once and determine + * whether we are dealing with a hierarchy of empty + * directories, if so, skip. + */ + + if (sess->opts->prune_empty_dirs && ent->fts_info == FTS_D) { + ec = flist_emptychain(ent); + if (ec == 2) + return 0; + if (ec == 1) + continue; } /* We don't allow symlinks without -l. */ Index: main.c =================================================================== RCS file: /cvs/src/usr.bin/rsync/main.c,v retrieving revision 1.66 diff -u -p -u -p -r1.66 main.c --- main.c 14 Feb 2023 17:15:15 -0000 1.66 +++ main.c 22 Mar 2023 10:19:37 -0000 @@ -326,6 +326,7 @@ const struct option lopts[] = { { "perms", no_argument, &opts.preserve_perms, 1 }, { "no-perms", no_argument, &opts.preserve_perms, 0 }, { "port", required_argument, NULL, OP_PORT }, + { "prune-empty-dirs", no_argument, &opts.prune_empty_dirs, 1 }, { "recursive", no_argument, &opts.recursive, 1 }, { "no-recursive", no_argument, &opts.recursive, 0 }, { "rsh", required_argument, NULL, 'e' }, @@ -362,7 +363,7 @@ main(int argc, char *argv[]) opts.max_size = opts.min_size = -1; - while ((c = getopt_long(argc, argv, "Dae:ghlnoprtvxz", lopts, &lidx)) + while ((c = getopt_long(argc, argv, "Dae:ghlmnoprtvxz", lopts, &lidx)) != -1) { switch (c) { case 'D': @@ -388,6 +389,9 @@ main(int argc, char *argv[]) case 'l': opts.preserve_links = 1; break; + case 'm': + opts.prune_empty_dirs = 1; + break; case 'n': opts.dry_run = 1; break; @@ -633,7 +637,7 @@ basedir: exit(rc); usage: fprintf(stderr, "usage: %s" - " [-aDglnoprtvx] [-e program] [--address=sourceaddr]\n" + " [-aDglmnoprtvx] [-e program] [--address=sourceaddr]\n" "\t[--contimeout=seconds] [--compare-dest=dir] [--del] [--exclude]\n" "\t[--exclude-from=file] [--include] [--include-from=file]\n" "\t[--no-motd] [--numeric-ids] [--port=portnumber]\n" Index: receiver.c =================================================================== RCS file: /cvs/src/usr.bin/rsync/receiver.c,v retrieving revision 1.31 diff -u -p -u -p -r1.31 receiver.c --- receiver.c 24 Oct 2021 21:24:17 -0000 1.31 +++ receiver.c 22 Mar 2023 10:19:38 -0000 @@ -24,6 +24,7 @@ #include <errno.h> #include <fcntl.h> #include <inttypes.h> +#include <libgen.h> #include <math.h> #include <poll.h> #include <stdio.h> @@ -165,6 +166,69 @@ rsync_set_metadata_at(struct sess *sess, } /* +* Inspect flist for empty directory hierarchies +* while resolving directory sizes, filling out +* a list of directories to be pruned and its size. +*/ + +static char ** +rsync_empty_dirchains(struct flist *fl, size_t flsz, size_t *prunetracker) +{ + size_t i, j, exists, prunesz = 0, fpi = 0; + struct flist *ff = &fl[flsz - 1]; + char *parent, *ffc; + char *fp2[flsz], **ffprune = NULL; + + for (i = 0; i < flsz; i++) { + if (!S_ISDIR(ff->st.mode)) { + ffc = ff->path; + for(;;) { + parent = dirname(ffc); + if (!strcmp(parent, ".")) + break; + exists = 0; + for(j = 0; j < fpi; j++) { + if (!strcmp(parent, fp2[j])) { + exists = 1; + break; + } + } + ffc = parent; + if (!exists) { + fp2[fpi] = strdup(parent); + fpi++; + } + } + fp2[fpi] = strdup(ff->path); + fpi++; + } + ff--; + } + + if ((ffprune = malloc(flsz * sizeof(char**))) == NULL) + err(1, NULL); + + for (i = 0; i < flsz; i++) { + ff++; + exists = 0; + for(j = 0; j < fpi; j++) { + if (!strcmp(fp2[j], ff->path)) { + exists = 1; + break; + } + } + if (exists == 0) { + ffprune[prunesz] = strdup(ff->path); + prunesz++; + } + } + + *prunetracker = prunesz; + return ffprune; + +} + +/* * Pledges: unveil, unix, rpath, cpath, wpath, stdio, fattr, chown. * Pledges (dry-run): -unix, -cpath, -wpath, -fattr, -chown. */ @@ -172,7 +236,7 @@ int rsync_receiver(struct sess *sess, int fdin, int fdout, const char *root) { struct flist *fl = NULL, *dfl = NULL; - size_t i, flsz = 0, dflsz = 0; + size_t i, prunesz = 0, flsz = 0, dflsz = 0; char *tofree; int rc = 0, dfd = -1, phase = 0, c; int32_t ioerror; @@ -180,6 +244,7 @@ rsync_receiver(struct sess *sess, int fd struct download *dl = NULL; struct upload *ul = NULL; mode_t oumask; + char **flprune = NULL; if (pledge("stdio unix rpath wpath cpath dpath fattr chown getpw unveil", NULL) == -1) err(ERR_IPC, "pledge"); @@ -303,6 +368,11 @@ rsync_receiver(struct sess *sess, int fd pfd[PFD_DOWNLOADER_IN].events = POLLIN; pfd[PFD_SENDER_OUT].events = POLLOUT; + + + if (sess->opts->prune_empty_dirs) + flprune = rsync_empty_dirchains(fl, flsz, &prunesz); + ul = upload_alloc(root, dfd, fdout, CSUM_LENGTH_PHASE1, fl, flsz, oumask); @@ -366,7 +436,7 @@ rsync_receiver(struct sess *sess, int fd (pfd[PFD_SENDER_OUT].revents & POLLOUT)) { c = rsync_uploader(ul, &pfd[PFD_UPLOADER_IN].fd, - sess, &pfd[PFD_SENDER_OUT].fd); + sess, &pfd[PFD_SENDER_OUT].fd, flprune, prunesz); if (c < 0) { ERRX1("rsync_uploader"); goto out; Index: rsync.1 =================================================================== RCS file: /cvs/src/usr.bin/rsync/rsync.1,v retrieving revision 1.30 diff -u -p -u -p -r1.30 rsync.1 --- rsync.1 2 Aug 2022 18:09:20 -0000 1.30 +++ rsync.1 22 Mar 2023 10:19:38 -0000 @@ -150,6 +150,8 @@ bytes. See .Fl -max-size on the definition of size. +.It Fl m , -prune-empty-dirs +Prune empty directory chains from the file list. .It Fl n , -dry-run Do not actually modify the destination. Mainly useful in combination with Index: uploader.c =================================================================== RCS file: /cvs/src/usr.bin/rsync/uploader.c,v retrieving revision 1.33 diff -u -p -u -p -r1.33 uploader.c --- uploader.c 3 Nov 2021 14:42:12 -0000 1.33 +++ uploader.c 22 Mar 2023 10:19:42 -0000 @@ -515,10 +515,11 @@ pre_sock(struct upload *p, struct sess * * Return <0 on failure 0 on success. */ static int -pre_dir(const struct upload *p, struct sess *sess) +pre_dir(const struct upload *p, struct sess *sess, + char **flprune, int prunesz) { struct stat st; - int rc; + int rc, i; const struct flist *f; f = &p->fl[p->idx]; @@ -528,6 +529,17 @@ pre_dir(const struct upload *p, struct s WARNX("%s: ignoring directory", f->path); return 0; } + if (sess->opts->prune_empty_dirs) { + for (i = 0; i < prunesz; i++) { + if (!strcmp(".", flprune[i])) + continue; + if (!strcmp(f->path, flprune[i])) { + LOG3("%s: Pruning enabled, skipping directory", + f->path); + return 0; + } + } + } if (sess->opts->dry_run) { log_dir(sess, f); return 0; @@ -855,7 +867,7 @@ upload_free(struct upload *p) */ int rsync_uploader(struct upload *u, int *fileinfd, - struct sess *sess, int *fileoutfd) + struct sess *sess, int *fileoutfd, char **flprune, int prunesz) { struct blkset blk; void *mbuf, *bufp; @@ -926,7 +938,7 @@ rsync_uploader(struct upload *u, int *fi for ( ; u->idx < u->flsz; u->idx++) { if (S_ISDIR(u->fl[u->idx].st.mode)) - c = pre_dir(u, sess); + c = pre_dir(u, sess, flprune, prunesz); else if (S_ISLNK(u->fl[u->idx].st.mode)) c = pre_symlink(u, sess); else if (S_ISREG(u->fl[u->idx].st.mode))