From abb596709b6fd4f1d020370f5f546db0d932f324 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 5 May 2022 18:33:59 +0200 Subject: [PATCH] add --dry-run mode REFMAIL: 20211130142121.xon5oygrpdfj5s2t@fastmail.com --- NEWS | 2 ++ src/common.h | 2 ++ src/drv_proxy.c | 82 +++++++++++++++++++++++++++++++++++++++----- src/drv_proxy_gen.pl | 34 ++++++++++++++++++ src/main.c | 6 ++++ src/main_sync.c | 8 +++-- src/mbsync.1 | 5 +++ src/sync_state.c | 13 +++++++ 8 files changed, 142 insertions(+), 10 deletions(-) diff --git a/NEWS b/NEWS index 9faa482..bbee611 100644 --- a/NEWS +++ b/NEWS @@ -22,6 +22,8 @@ Added new sync operation 'Old'. Added support for mirroring deletions more accurately. +Added --dry-run option. + [1.4.0] The 'isync' compatibility wrapper was removed. diff --git a/src/common.h b/src/common.h index c7ad08c..92d1e89 100644 --- a/src/common.h +++ b/src/common.h @@ -113,6 +113,8 @@ BIT_ENUM( PROGRESS, + DRYRUN, + ZERODELAY, KEEPJOURNAL, FORCEJOURNAL, diff --git a/src/drv_proxy.c b/src/drv_proxy.c index 0cca3a5..37dbc34 100644 --- a/src/drv_proxy.c +++ b/src/drv_proxy.c @@ -23,6 +23,8 @@ typedef union proxy_store { gen_cmd_t *pending_cmds, **pending_cmds_append; gen_cmd_t *check_cmds, **check_cmds_append; wakeup_t wakeup; + uint fake_nextuid; + char is_fake; // Was "created" by dry-run char force_async; void (*expunge_callback)( message_t *msg, void *aux ); @@ -139,8 +141,11 @@ static @type@proxy_@name@( store_t *gctx ) { proxy_store_t *ctx = (proxy_store_t *)gctx; - @type@rv = ctx->real_driver->@name@( ctx->real_store ); - debug( "%sCalled @name@, ret=@fmt@\n", ctx->label, rv ); + @type@rv; + @pre_invoke@ + @indent_invoke@rv = ctx->real_driver->@name@( ctx->real_store ); + @post_invoke@ + debug( "%sCalled @name@@print_fmt_dry@, ret=@fmt@\n", ctx->label@print_pass_dry@, rv ); return rv; } //# END @@ -151,9 +156,12 @@ static @type@proxy_@name@( store_t *gctx@decl_args@ ) proxy_store_t *ctx = (proxy_store_t *)gctx; @pre_print_args@ - debug( "%sEnter @name@@print_fmt_args@\n", ctx->label@print_pass_args@ ); + debug( "%sEnter @name@@print_fmt_dry@@print_fmt_args@\n", ctx->label@print_pass_dry@@print_pass_args@ ); @print_args@ - @type@rv = ctx->real_driver->@name@( ctx->real_store@pass_args@ ); + @type@rv; + @pre_invoke@ + @indent_invoke@rv = ctx->real_driver->@name@( ctx->real_store@pass_args@ ); + @post_invoke@ debug( "%sLeave @name@, ret=@print_fmt_ret@\n", ctx->label, @print_pass_ret@ ); return rv; } @@ -165,9 +173,11 @@ static @type@proxy_@name@( store_t *gctx@decl_args@ ) proxy_store_t *ctx = (proxy_store_t *)gctx; @pre_print_args@ - debug( "%sEnter @name@@print_fmt_args@\n", ctx->label@print_pass_args@ ); + debug( "%sEnter @name@@print_fmt_dry@@print_fmt_args@\n", ctx->label@print_pass_dry@@print_pass_args@ ); @print_args@ - ctx->real_driver->@name@( ctx->real_store@pass_args@ ); + @pre_invoke@ + @indent_invoke@ctx->real_driver->@name@( ctx->real_store@pass_args@ ); + @post_invoke@ debug( "%sLeave @name@\n", ctx->label ); @action@ } @@ -226,9 +236,11 @@ proxy_do_@name@( gen_cmd_t *gcmd ) proxy_store_t *ctx = cmd->ctx; @pre_print_args@ - debug( "%s[% 2d] Enter @name@@print_fmt_args@\n", ctx->label, cmd->tag@print_pass_args@ ); + debug( "%s[% 2d] Enter @name@@print_fmt_dry@@print_fmt_args@\n", ctx->label, cmd->tag@print_pass_dry@@print_pass_args@ ); @print_args@ - ctx->real_driver->@name@( ctx->real_store@pass_args@, proxy_@name@_cb, cmd ); + @pre_invoke@ + @indent_invoke@ctx->real_driver->@name@( ctx->real_store@pass_args@, proxy_@name@_cb, cmd ); + @post_invoke@ debug( "%s[% 2d] Leave @name@\n", ctx->label, cmd->tag ); } @@ -252,10 +264,51 @@ static @type@proxy_@name@( store_t *gctx@decl_args@, void (*cb)( @decl_cb_args@v debug( " %s\n", box->string ); //# END +//# DEFINE select_box_pre_invoke + ctx->is_fake = 0; +//# END + +//# DEFINE create_box_driable 1 +//# DEFINE create_box_fake_invoke + ctx->is_fake = 1; +//# END //# DEFINE create_box_counted 1 +//# DEFINE open_box_fakeable 1 +//# DEFINE open_box_fake_invoke + ctx->fake_nextuid = 1; +//# END +//# DEFINE open_box_fake_cb_args , 1 + +//# DEFINE get_uidnext_fakeable 1 +//# DEFINE get_uidnext_fake_invoke + rv = ctx->fake_nextuid; +//# END +//# DEFINE get_uidnext_post_real_invoke + ctx->fake_nextuid = rv; +//# END + +//# DEFINE get_supported_flags_fakeable 1 +//# DEFINE get_supported_flags_fake_invoke + rv = 255; +//# END + +//# DEFINE confirm_box_empty_fakeable 1 +//# DEFINE confirm_box_empty_fake_invoke + rv = 1; +//# END + +//# DEFINE delete_box_driable 1 +//# DEFINE delete_box_fake_invoke + ctx->is_fake = 0; +//# END //# DEFINE delete_box_counted 1 +//# DEFINE finish_delete_box_driable 1 +//# DEFINE finish_delete_box_fake_invoke + rv = DRV_OK; +//# END + //# DEFINE prepare_load_box_print_fmt_args , opts=%s //# DEFINE prepare_load_box_print_pass_args , fmt_opts( opts ).str //# DEFINE prepare_load_box_print_fmt_ret %s @@ -274,6 +327,8 @@ static @type@proxy_@name@( store_t *gctx@decl_args@, void (*cb)( @decl_cb_args@v debug( "\n" ); } //# END +//# DEFINE load_box_fakeable 1 +//# DEFINE load_box_fake_cb_args , NULL, 0, 0 //# DEFINE load_box_print_fmt_cb_args , total=%d, recent=%d //# DEFINE load_box_print_pass_cb_args , total_msgs, recent_msgs //# DEFINE load_box_print_cb_args @@ -297,6 +352,11 @@ static @type@proxy_@name@( store_t *gctx@decl_args@, void (*cb)( @decl_cb_args@v //# DEFINE fetch_msg_print_fmt_args , uid=%u, want_flags=%s, want_date=%s //# DEFINE fetch_msg_print_pass_args , cmd->msg->uid, !(cmd->msg->status & M_FLAGS) ? "yes" : "no", cmd->data->date ? "yes" : "no" +//# DEFINE fetch_msg_driable 1 +//# DEFINE fetch_msg_fake_invoke + cmd->data->data = strdup( "" ); + cmd->data->len = 0; +//# END //# DEFINE fetch_msg_print_fmt_cb_args , flags=%s, date=%lld, size=%u //# DEFINE fetch_msg_print_pass_cb_args , fmt_flags( cmd->data->flags ).str, (long long)cmd->data->date, cmd->data->len //# DEFINE fetch_msg_print_cb_args @@ -318,17 +378,23 @@ static @type@proxy_@name@( store_t *gctx@decl_args@, void (*cb)( @decl_cb_args@v fflush( stdout ); } //# END +//# DEFINE store_msg_driable 1 +//# DEFINE store_msg_fake_cb_args , cmd->to_trash ? 0 : ctx->fake_nextuid++ //# DEFINE store_msg_counted 1 //# DEFINE set_msg_flags_checked 1 //# DEFINE set_msg_flags_print_fmt_args , uid=%u, add=%s, del=%s //# DEFINE set_msg_flags_print_pass_args , cmd->uid, fmt_flags( cmd->add ).str, fmt_flags( cmd->del ).str +//# DEFINE set_msg_flags_driable 1 //# DEFINE set_msg_flags_counted 1 //# DEFINE trash_msg_print_fmt_args , uid=%u //# DEFINE trash_msg_print_pass_args , cmd->msg->uid +//# DEFINE trash_msg_driable 1 //# DEFINE trash_msg_counted 1 +//# DEFINE close_box_driable 1 +//# DEFINE close_box_fake_cb_args , 0 //# DEFINE close_box_counted 1 //# DEFINE commit_cmds_print_args diff --git a/src/drv_proxy_gen.pl b/src/drv_proxy_gen.pl index 3fda58c..6b9c868 100755 --- a/src/drv_proxy_gen.pl +++ b/src/drv_proxy_gen.pl @@ -175,15 +175,49 @@ for (@ptypes) { $replace{'print_pass_args'} = $replace{'pass_args'} = $pass_args; $replace{'print_fmt_args'} = make_format($cmd_args); } + my ($fake_cond, $fake_invoke, $fake_cb_args, $post_invoke) = (undef, "", "", ""); for (keys %defines) { next if (!/^${cmd_name}_(.*)$/); my ($key, $val) = ($1, delete $defines{$_}); if ($key eq 'counted') { $replace{count_step} = "countStep();\n"; + } elsif ($key eq 'fakeable') { + $fake_cond = "ctx->is_fake"; + $replace{print_pass_dry} = ', '.$fake_cond.' ? " [FAKE]" : ""'; + } elsif ($key eq 'driable') { + $fake_cond = "DFlags & DRYRUN"; + $replace{print_pass_dry} = ', ('.$fake_cond.') ? " [DRY]" : ""'; + } elsif ($key eq 'fake_invoke') { + $fake_invoke = $val; + } elsif ($key eq 'fake_cb_args') { + $fake_cb_args = $val; + } elsif ($key eq 'post_real_invoke') { + $post_invoke = $val; } else { $replace{$key} = $val; } } + if (defined($fake_cond)) { + $replace{print_fmt_dry} = '%s'; + if ($inc_tpl eq 'CALLBACK_STS') { + $fake_invoke .= "proxy_${cmd_name}_cb( DRV_OK${fake_cb_args}, cmd );\n"; + } elsif (length($fake_cb_args)) { + die("Unexpected fake callback arguments to $cmd_name\n"); + } + my $num_fake = $fake_invoke =~ s/^(?=.)/\t/gsm; + my $num_real = $post_invoke =~ s/^(?=.)/\t/gsm; + my $pre_invoke = "if (".$fake_cond.")"; + if ($num_fake > 1 || $num_real) { + $pre_invoke .= " {"; + $fake_invoke .= "} else {\n"; + $post_invoke .= "}\n"; + } else { + $fake_invoke .= "else\n"; + } + $replace{pre_invoke} = $pre_invoke."\n".$fake_invoke; + $replace{indent_invoke} = "\t"; + $replace{post_invoke} = $post_invoke; + } my %used; my $text = $templates{$template}; if ($inc_tpl) { diff --git a/src/main.c b/src/main.c index 37304fa..ba467ac 100644 --- a/src/main.c +++ b/src/main.c @@ -46,6 +46,7 @@ PACKAGE " " VERSION " - mailbox synchronizer\n" " -x, --expunge-solo expunge deleted messages that are not paired\n" " -c, --config CONFIG read an alternate config file (default: ~/." EXE "rc)\n" " -D, --debug debugging modes (see manual)\n" +" -y, --dry-run do not actually modify anything\n" " -V, --verbose display what is happening\n" " -q, --quiet don't display progress counters\n" " -v, --version display version\n" @@ -217,6 +218,8 @@ main( int argc, char **argv ) else goto badopt; DFlags |= op; + } else if (!strcmp( opt, "dry-run" )) { + DFlags |= DRYRUN; } else if (!strcmp( opt, "pull" )) { cops |= XOP_PULL, mvars->ops[F] |= XOP_HAVE_TYPE; } else if (!strcmp( opt, "push" )) { @@ -454,6 +457,9 @@ main( int argc, char **argv ) op = DEBUG_ALL; DFlags |= op; break; + case 'y': + DFlags |= DRYRUN; + break; case 'T': for (; *ochar; ) { switch (*ochar++) { diff --git a/src/main_sync.c b/src/main_sync.c index 0c7f6db..3d3d90f 100644 --- a/src/main_sync.c +++ b/src/main_sync.c @@ -50,10 +50,14 @@ summary( void ) printf( "Processed %d box(es) in %d channel(s)", boxes_done, chans_done ); for (int t = 2; --t >= 0; ) { if (ops_any[t]) - printf( ",\n%sed %d new message(s) and %d flag update(s)", + printf( (DFlags & DRYRUN) ? + ",\nwould %s %d new message(s) and %d flag update(s)" : + ",\n%sed %d new message(s) and %d flag update(s)", str_hl[t], new_done[t], flags_done[t] ); if (trash_any[t]) - printf( ",\nmoved %d %s message(s) to trash", + printf( (DFlags & DRYRUN) ? + ",\nwould move %d %s message(s) to trash" : + ",\nmoved %d %s message(s) to trash", trash_done[t], str_fn[t] ); } puts( "." ); diff --git a/src/mbsync.1 b/src/mbsync.1 index dd0ad98..79a6afb 100644 --- a/src/mbsync.1 +++ b/src/mbsync.1 @@ -74,6 +74,11 @@ Display a summary of command line options. \fB-v\fR, \fB--version\fR Display version information. .TP +\fB-y\fR, \fB--dry-run\fR +Enter simulation mode: the Channel status is queried and all required +operations are determined, but no modifications are actually made +to either the mailboxes or the state files. +.TP \fB-V\fR, \fB--verbose\fR Enable \fIverbose\fR mode, which displays what is currently happening. .TP diff --git a/src/sync_state.c b/src/sync_state.c index 3329efd..385d089 100644 --- a/src/sync_state.c +++ b/src/sync_state.c @@ -77,6 +77,9 @@ lock_state( sync_vars_t *svars ) { struct flock lck; + if (DFlags & DRYRUN) + return 1; + if (svars->lfd >= 0) return 1; memset( &lck, 0, sizeof(lck) ); @@ -451,6 +454,8 @@ jFprintf( sync_vars_t *svars, const char *msg, ... ) va_list va; if (!svars->jfp) { + if (DFlags & DRYRUN) + goto dryout; create_state( svars ); if (!(svars->jfp = fopen( svars->jname, svars->replayed ? "a" : "w" ))) { sys_error( "Error: cannot create journal %s", svars->jname ); @@ -463,6 +468,7 @@ jFprintf( sync_vars_t *svars, const char *msg, ... ) va_start( va, msg ); vFprintf( svars->jfp, msg, va ); va_end( va ); + dryout: countStep(); JCount++; } @@ -474,6 +480,10 @@ save_state( sync_vars_t *svars ) if (!svars->jfp && !svars->replayed) return; + // jfp is NULL in this case anyway, but we might have replayed. + if (DFlags & DRYRUN) + return; + if (!svars->nfp) create_state( svars ); Fprintf( svars->nfp, @@ -506,6 +516,9 @@ save_state( sync_vars_t *svars ) void delete_state( sync_vars_t *svars ) { + if (DFlags & DRYRUN) + return; + unlink( svars->nname ); unlink( svars->jname ); if (unlink( svars->dname ) || unlink( svars->lname )) {