This is the fixed, reformatted patch to add the -m option to openrsync
Thanks to [email protected] 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))