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))

Reply via email to