diff --git a/cleanup.c b/cleanup.c index 7f1864ccb..59d092edb 100644 --- a/cleanup.c +++ b/cleanup.c @@ -34,6 +34,7 @@ extern int protocol_version; extern int output_needs_newline; extern char *partial_dir; extern char *logfile_name; +extern int data_transfer_limit_reached; int called_from_signal_handler = 0; BOOL shutting_down = False; @@ -210,6 +211,8 @@ NORETURN void _exit_cleanup(int code, const char *file, int line) if (exit_code == 0) { if (code) exit_code = code; + if (data_transfer_limit_reached) + exit_code = RERR_DATA_LIMIT; if (io_error & IOERR_DEL_LIMIT) exit_code = RERR_DEL_LIMIT; if (io_error & IOERR_VANISHED) diff --git a/configure b/configure deleted file mode 100755 index 51c3fee58..000000000 --- a/configure +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/sh -e -# This configure script ensures that the configure.sh script exists, and -# if not, it tries to fetch rsync's generated files or build them. We -# then transfer control to the configure.sh script to do the real work. - -dir=`dirname $0` -if test x"$dir" = x; then - dir=. -fi - -if test "$dir" = '.'; then - branch=`packaging/prep-auto-dir` || exit 1 - if test x"$branch" != x; then - cd build || exit 1 - dir=.. - fi -fi - -if test ! -f configure.sh; then - if ! "$dir/prepare-source" build; then - echo 'Failed to build configure.sh and/or config.h.in -- giving up.' >&2 - rm -f configure.sh - exit 1 - fi -fi - -exec ./configure.sh --srcdir="$dir" "${@}" diff --git a/errcode.h b/errcode.h index 9824a34d6..f338d69b8 100644 --- a/errcode.h +++ b/errcode.h @@ -43,6 +43,7 @@ #define RERR_PARTIAL 23 /* partial transfer */ #define RERR_VANISHED 24 /* file(s) vanished on sender side */ #define RERR_DEL_LIMIT 25 /* skipped some deletes due to --max-delete */ +#define RERR_DATA_LIMIT 26 /* data transfer limit reached */ #define RERR_TIMEOUT 30 /* timeout in data send/receive */ #define RERR_CONTIMEOUT 35 /* timeout waiting for daemon connection */ diff --git a/flist.c b/flist.c index 346540fbf..abfe7972e 100644 --- a/flist.c +++ b/flist.c @@ -78,6 +78,7 @@ extern uid_t our_uid; extern struct stats stats; extern char *filesfrom_host; extern char *usermap, *groupmap; +extern int data_transfer_limit_reached; extern struct name_num_item *file_sum_nni; @@ -1714,6 +1715,9 @@ static void send_if_directory(int f, struct file_list *flist, { char is_dot_dir = fbuf[ol-1] == '.' && (ol == 1 || fbuf[ol-2] == '/'); + if (data_transfer_limit_reached) + return; + if (S_ISDIR(file->mode) && !(file->flags & FLAG_MOUNT_DIR) && f_name(file, fbuf)) { void *save_filters; @@ -1866,6 +1870,9 @@ static void send_directory(int f, struct file_list *flist, char *fbuf, int len, assert(flist != NULL); + if (data_transfer_limit_reached) + return; + if (!(d = opendir(fbuf))) { if (errno == ENOENT) { if (am_sender) /* Can abuse this for vanished error w/ENOENT: */ @@ -1892,6 +1899,10 @@ static void send_directory(int f, struct file_list *flist, char *fbuf, int len, for (errno = 0, di = readdir(d); di; errno = 0, di = readdir(d)) { unsigned name_len; char *dname = d_name(di); + + if (data_transfer_limit_reached) + break; + if (dname[0] == '.' && (dname[1] == '\0' || (dname[1] == '.' && dname[2] == '\0'))) continue; @@ -2050,6 +2061,9 @@ static void send1extra(int f, struct file_struct *file, struct file_list *flist) int len, dlen, flags = FLAG_DIVERT_DIRS | FLAG_CONTENT_DIR; size_t j; + if (data_transfer_limit_reached) + return; + f_name(file, fbuf); dlen = strlen(fbuf); @@ -2082,6 +2096,9 @@ static void send1extra(int f, struct file_struct *file, struct file_list *flist) relnamecache *rnp = ((relnamecache**)relname_list->items)[j]; char name_type = rnp->name_type; + if (data_transfer_limit_reached) + break; + fbuf[dlen] = '/'; len = strlcpy(fbuf + dlen + 1, rnp->fname, sizeof fbuf - dlen - 1); free(rnp); @@ -2121,6 +2138,17 @@ static void write_end_of_flist(int f, int send_io_error) write_byte(f, 0); } +static void send_data_limit_flist_eof(int f) +{ + if (!flist_eof) { + write_ndx(f, NDX_FLIST_EOF); + flist_eof = 1; + if (DEBUG_GTE(FLIST, 3)) + rprintf(FINFO, "[%s] flist_eof=1 due to data transfer limit\n", who_am_i()); + change_local_filter_dir(NULL, 0, 0); + } +} + void send_extra_file_list(int f, int at_least) { struct file_list *flist; @@ -2131,6 +2159,11 @@ void send_extra_file_list(int f, int at_least) if (flist_eof) return; + if (data_transfer_limit_reached) { + send_data_limit_flist_eof(f); + return; + } + if (at_least < 0) at_least = file_total - file_old_total + 1; @@ -2142,6 +2175,11 @@ void send_extra_file_list(int f, int at_least) const char *pathname = F_PATHNAME(file); int32 *dp; + if (data_transfer_limit_reached) { + send_data_limit_flist_eof(f); + goto finish; + } + flist = flist_new(0, "send_extra_file_list"); start_write = stats.total_written; @@ -2293,6 +2331,9 @@ struct file_list *send_file_list(int f, int argc, char *argv[]) while (1) { char fbuf[MAXPATHLEN], *fn, name_type; + if (data_transfer_limit_reached) + break; + if (use_ff_fd) { if (read_line(filesfrom_fd, fbuf, sizeof fbuf, rl_flags) == 0) break; diff --git a/generator.c b/generator.c index 8642236e7..f79497fed 100644 --- a/generator.c +++ b/generator.c @@ -100,6 +100,21 @@ extern char *tmpdir; extern char *basis_dir[MAX_BASIS_DIRS+1]; extern struct file_list *cur_flist, *first_flist, *dir_flist; extern filter_rule_list filter_list, daemon_filter_list; +static int64 total_transferred_data = 0; +extern int data_transfer_limit_reached; +static int data_transfer_limit_msg_sent = 0; + +static int file_will_exceed_data_transfer_limit(struct file_struct *file) +{ + if (data_transfer_limit < 0) + return 0; + + if (delay_transfer_limit_check) + return total_transferred_data >= data_transfer_limit; + + return total_transferred_data + F_LENGTH(file) > data_transfer_limit; +} + int maybe_ATTRS_REPORT = 0; int maybe_ATTRS_ACCURATE_TIME = 0; @@ -1245,6 +1260,15 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, if (DEBUG_GTE(GENR, 1)) rprintf(FINFO, "recv_generator(%s,%d)\n", fname, ndx); + + if (data_transfer_limit_reached) { +#ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && F_IS_HLINKED(file)) + handle_skipped_hlink(file, itemizing, code, f_out); +#endif + return; + } + if (list_only) { if (is_dir < 0 || (is_dir && !implied_dirs && file->flags & FLAG_IMPLIED_DIR)) @@ -1929,7 +1953,19 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, if (DEBUG_GTE(DELTASUM, 2)) rprintf(FINFO, "generating and sending sums for %d\n", ndx); - notify_others: +notify_others: + if (file_will_exceed_data_transfer_limit(file)) { + rprintf(FINFO, + "recv_generator: transferring %s would exceed data transfer limit. Not transferred; stopping all remaining transfers.\n", + fname); + data_transfer_limit_reached = 1; + if (!data_transfer_limit_msg_sent) { + send_msg_int(MSG_DATA_LIMIT_REACHED, 0); + io_flush(MSG_FLUSH); + data_transfer_limit_msg_sent = 1; + } + goto cleanup; + } if (remove_source_files && !delay_updates && !phase && !dry_run) increment_active_files(ndx, itemizing, code); if (inc_recurse && (!dry_run || write_batch < 0)) @@ -1952,6 +1988,8 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, free_stat_x(&real_sx); } + total_transferred_data += F_LENGTH(file); + if (!do_xfers) { #ifdef SUPPORT_HARD_LINKS if (preserve_hard_links && F_IS_HLINKED(file)) @@ -2174,8 +2212,7 @@ void check_for_finished_files(int itemizing, enum logcode code, int check_redo) continue; } #endif - - if (check_redo && (ndx = get_redo_num()) != -1) { + if (!data_transfer_limit_reached && check_redo && (ndx = get_redo_num()) != -1) { OFF_T save_max_size = max_size; OFF_T save_min_size = min_size; csum_length = SUM_LENGTH; @@ -2370,7 +2407,7 @@ void generate_files(int f_out, const char *local_name) } } while ((cur_flist = cur_flist->next) != NULL); - if (delete_during) + if (delete_during && !data_transfer_limit_reached) delete_in_dir(NULL, NULL, dev_zero); phase++; if (DEBUG_GTE(GENR, 1)) @@ -2425,9 +2462,9 @@ void generate_files(int f_out, const char *local_name) info_levels[INFO_FLIST] = save_info_flist; info_levels[INFO_PROGRESS] = save_info_progress; - if (delete_during == 2) + if (delete_during == 2 && !data_transfer_limit_reached) do_delayed_deletions(fbuf); - if (delete_after && !solo_file && file_total > 0) + if (delete_after && !solo_file && file_total > 0 && !data_transfer_limit_reached) do_delete_pass(); if (max_delete >= 0 && skipped_deletes) { diff --git a/install-sh b/install-sh deleted file mode 100755 index 8c409fbb9..000000000 --- a/install-sh +++ /dev/null @@ -1,238 +0,0 @@ -#! /bin/sh -# -# install - install a program, script, or datafile -# This comes from X11R5. -# -# Calling this script install-sh is preferred over install.sh, to prevent -# `make' implicit rules from creating a file called install from it -# when there is no Makefile. -# -# This script is compatible with the BSD install script, but was written -# from scratch. -# - - -# set DOITPROG to echo to test this script - -# Don't use :- since 4.3BSD and earlier shells don't like it. -doit="${DOITPROG-}" - - -# put in absolute paths if you don't have them in your path; or use env. vars. - -mvprog="${MVPROG-mv}" -cpprog="${CPPROG-cp}" -chmodprog="${CHMODPROG-chmod}" -chownprog="${CHOWNPROG-chown}" -chgrpprog="${CHGRPPROG-chgrp}" -stripprog="${STRIPPROG-strip}" -rmprog="${RMPROG-rm}" -mkdirprog="${MKDIRPROG-mkdir}" - -transformbasename="" -transform_arg="" -instcmd="$mvprog" -chmodcmd="$chmodprog 0755" -chowncmd="" -chgrpcmd="" -stripcmd="" -rmcmd="$rmprog -f" -mvcmd="$mvprog" -src="" -dst="" -dir_arg="" - -while [ x"$1" != x ]; do - case $1 in - -c) instcmd="$cpprog" - shift - continue;; - - -d) dir_arg=true - shift - continue;; - - -m) chmodcmd="$chmodprog $2" - shift - shift - continue;; - - -o) chowncmd="$chownprog $2" - shift - shift - continue;; - - -g) chgrpcmd="$chgrpprog $2" - shift - shift - continue;; - - -s) stripcmd="$stripprog" - shift - continue;; - - -t=*) transformarg=`echo $1 | sed 's/-t=//'` - shift - continue;; - - -b=*) transformbasename=`echo $1 | sed 's/-b=//'` - shift - continue;; - - *) if [ x"$src" = x ] - then - src=$1 - else - # this colon is to work around a 386BSD /bin/sh bug - : - dst=$1 - fi - shift - continue;; - esac -done - -if [ x"$src" = x ] -then - echo "install: no input file specified" - exit 1 -else - true -fi - -if [ x"$dir_arg" != x ]; then - dst=$src - src="" - - if [ -d $dst ]; then - instcmd=: - else - instcmd=mkdir - fi -else - -# Waiting for this to be detected by the "$instcmd $src $dsttmp" command -# might cause directories to be created, which would be especially bad -# if $src (and thus $dsttmp) contains '*'. - - if [ -f $src ] || [ -d $src ] - then - true - else - echo "install: $src does not exist" - exit 1 - fi - - if [ x"$dst" = x ] - then - echo "install: no destination specified" - exit 1 - else - true - fi - -# If destination is a directory, append the input filename; if your system -# does not like double slashes in filenames, you may need to add some logic - - if [ -d $dst ] - then - dst="$dst"/`basename $src` - else - true - fi -fi - -## this sed command emulates the dirname command -dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` - -# Make sure that the destination directory exists. -# this part is taken from Noah Friedman's mkinstalldirs script - -# Skip lots of stat calls in the usual case. -if [ ! -d "$dstdir" ]; then -defaultIFS=' -' -IFS="${IFS-${defaultIFS}}" - -oIFS="${IFS}" -# Some sh's can't handle IFS=/ for some reason. -IFS='%' -set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` -IFS="${oIFS}" - -pathcomp='' - -while [ $# -ne 0 ] ; do - pathcomp="${pathcomp}${1}" - shift - - if [ ! -d "${pathcomp}" ] ; - then - $mkdirprog "${pathcomp}" - else - true - fi - - pathcomp="${pathcomp}/" -done -fi - -if [ x"$dir_arg" != x ] -then - $doit $instcmd $dst && - - if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && - if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && - if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && - if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi -else - -# If we're going to rename the final executable, determine the name now. - - if [ x"$transformarg" = x ] - then - dstfile=`basename $dst` - else - dstfile=`basename $dst $transformbasename | - sed $transformarg`$transformbasename - fi - -# don't allow the sed command to completely eliminate the filename - - if [ x"$dstfile" = x ] - then - dstfile=`basename $dst` - else - true - fi - -# Make a temp file name in the proper directory. - - dsttmp=$dstdir/_inst.$$_ - -# Move or copy the file name to the temp name - - $doit $instcmd $src $dsttmp && - - trap "rm -f ${dsttmp}" 0 && - -# and set any options; do chmod last to preserve setuid bits - -# If any of these fail, we abort the whole thing. If we want to -# ignore errors from any of these, just make sure not to ignore -# errors from the above "$doit $instcmd $src $dsttmp" command. - - if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && - if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && - if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && - if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && - -# Now rename the file to the real destination. - - $doit $rmcmd -f $dstdir/$dstfile && - $doit $mvcmd $dsttmp $dstdir/$dstfile - -fi && - - -exit 0 diff --git a/io.c b/io.c index 2dfeeaa34..c98f8ab6b 100644 --- a/io.c +++ b/io.c @@ -65,6 +65,7 @@ extern BOOL flush_ok_after_signal; extern struct stats stats; extern time_t stop_at_utime; extern struct file_list *cur_flist; +extern int data_transfer_limit_reached; #ifdef ICONV_OPTION extern int filesfrom_convert; extern iconv_t ic_send, ic_recv; @@ -1681,6 +1682,13 @@ static void read_a_msg(void) first_message = 0; } break; + case MSG_DATA_LIMIT_REACHED: + if (msg_bytes != 4) + goto invalid_msg; + raw_read_int(); + iobuf.in_multiplexed = 1; + data_transfer_limit_reached = 1; + break; case MSG_ERROR_EXIT: if (msg_bytes == 4) val = raw_read_int(); diff --git a/log.c b/log.c index 3e9a2edcf..eac34931e 100644 --- a/log.c +++ b/log.c @@ -99,6 +99,7 @@ struct { { RERR_VANISHED , "some files vanished before they could be transferred" }, { RERR_DEL_LIMIT , "the --max-delete limit stopped deletions" }, { RERR_TIMEOUT , "timeout in data send/receive" }, + { RERR_DATA_LIMIT , "the --data-transfer-limit stopped transfers" }, { RERR_CONTIMEOUT , "timeout waiting for daemon connection" }, { RERR_CMD_FAILED , "remote shell failed" }, { RERR_CMD_KILLED , "remote shell killed" }, diff --git a/options.c b/options.c index 8568af2b2..503eb6126 100644 --- a/options.c +++ b/options.c @@ -151,6 +151,11 @@ char *skip_compress = NULL; char *copy_as = NULL; item_list dparam_list = EMPTY_ITEM_LIST; +int64 data_transfer_limit = -1; +int delay_transfer_limit_check = 0; +int data_transfer_limit_reached = 0; +static char *data_transfer_limit_arg = NULL; + /** Network address family. **/ int default_af_hint #ifdef INET6 @@ -853,6 +858,8 @@ static struct poptOption long_options[] = { {"dparam", 0, POPT_ARG_STRING, 0, OPT_DAEMON, 0, 0 }, {"detach", 0, POPT_ARG_NONE, 0, OPT_DAEMON, 0, 0 }, {"no-detach", 0, POPT_ARG_NONE, 0, OPT_DAEMON, 0, 0 }, + {"data-transfer-limit", 0, POPT_ARG_STRING, &data_transfer_limit_arg, 0, 0, 0}, + {"delay-transfer-limit-check", 0, POPT_ARG_NONE, &delay_transfer_limit_check, 0, 0, 0}, {0,0,0,0, 0, 0, 0} }; @@ -2507,6 +2514,14 @@ int parse_arguments(int *argc_p, const char ***argv_p) } } + if (data_transfer_limit_arg) { + /* parse_size_arg converts strings like "10G" into a 64-bit integer */ + data_transfer_limit = parse_size_arg(data_transfer_limit_arg, 'b', + "data-transfer-limit", 0, -1, False); + if (data_transfer_limit < 0) + goto cleanup; + } + if (trust_sender || am_server || read_batch) trust_sender_args = trust_sender_filter = 1; else if (old_style_args || filesfrom_host != NULL) @@ -2801,6 +2816,12 @@ void server_options(char **args, int *argc_p) args[ac++] = arg; } + if (data_transfer_limit >= 0) { + args[ac++] = safe_arg("--data-transfer-limit", data_transfer_limit_arg); + if (delay_transfer_limit_check) + args[ac++] = "--delay-transfer-limit-check"; + } + if (backup_dir) { /* This split idiom allows for ~/path expansion via the shell. */ args[ac++] = "--backup-dir"; diff --git a/rsync.h b/rsync.h index cdc2d2c0d..48b750da5 100644 --- a/rsync.h +++ b/rsync.h @@ -299,6 +299,7 @@ enum msgcode { MSG_SUCCESS=100,/* successfully updated indicated flist index */ MSG_DELETED=101,/* successfully deleted a file on receiving side */ MSG_NO_SEND=102,/* sender failed to open a file we wanted */ + MSG_DATA_LIMIT_REACHED=62, /* transfer limit reached */ }; enum filetype { @@ -845,6 +846,8 @@ extern int gid_ndx; extern int acls_ndx; extern int xattrs_ndx; extern int file_sum_extra_cnt; +extern int64 data_transfer_limit; +extern int delay_transfer_limit_check; #ifdef USE_FLEXIBLE_ARRAY #define FILE_STRUCT_LEN (sizeof (struct file_struct)) diff --git a/testsuite/COVERAGE.md b/testsuite/COVERAGE.md index 6f6e37cf3..d33f17eb1 100644 --- a/testsuite/COVERAGE.md +++ b/testsuite/COVERAGE.md @@ -93,17 +93,19 @@ Status legend: ✓ property asserted · `~` shallow / by an existing ported test | --max-alloc | — | — | — | ✗ | ### Filtering -| option | test(s) | depth | x-dir | notes / gap | -|---|---|---|---|---| +| option | test(s) | depth | x-dir | notes / gap | +|---|---------------------|--|---|---| | -f, --filter / -F | filter-depth*new*, merge | Y | — | ✓ deep per-dir merge | | --exclude / --include | filter-depth*new*, exclude, exclude-lsh | Y | — | ✓ | | --exclude-from / --include-from | files-from-depth*new* | Y | — | ✓ | -| -C, --cvs-exclude | cvs-exclude*new* | Y | — | ✓ incl. deep .cvsignore | +| -C, --cvs-exclude | cvs-exclude*new* | Y | — | ✓ incl. deep .cvsignore | | --files-from | files-from-depth*new* | Y | — | ✓ | | -0, --from0 | files-from-depth*new* | Y | — | ✓ | -| --max-size / --min-size | size-filter*new* | Y | — | ✓ | -| --existing / --ignore-existing | delete-deep*new* | Y | — | ✓ | -| --ignore-missing-args / --delete-missing-args | — | — | — | ✗ | +| --max-size / --min-size | size-filter*new* | Y | — | ✓ | +| --data-transfer-limit | transfer-limit*new* | — | — | ✓ | +| --delay-transfer-limit-check | delay-transfer-limit-check*new* | — | — | ✓ | +| --existing / --ignore-existing | delete-deep*new* | Y | — | ✓ | +| --ignore-missing-args / --delete-missing-args | — | — | — | ✗ | ### Deletion | option | test(s) | depth | x-dir | notes / gap | diff --git a/testsuite/delay-transfer-limit-check_test.py b/testsuite/delay-transfer-limit-check_test.py new file mode 100644 index 000000000..79a1f3c20 --- /dev/null +++ b/testsuite/delay-transfer-limit-check_test.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +"""Coverage of --delay-transfer-limit-check. + +--data-transfer-limit specifies a maximum amount of data to transfer. This does not take delta-sync into consideration; +only "whole file" transfer size is counted. If the next file to be transferred would exceed the limit specified for this +execution, rsync will exit with code 26 and no further transfers will occur. However, if --delay-transfer-limit-check is +specified, transfers will continue until the limit has been exceeded. If the limit has not yet been reached, the next +file will be transferred even if doing so would exceed the limit specified for this execution. The next file after that +will not be transferred and rsync will exit with code 26. +""" + +from rsyncfns import ( + FROMDIR, TODIR, + make_data_file, makepath, rmtree, run_rsync, test_fail, +) + +src = FROMDIR +dest = TODIR +TESTFILE1 = 'testfile1' +TESTFILE2 = 'testfile2' +TESTFILE3 = 'testfile3' +FILE1_SIZE = 400 * 1024 # 400KB +FILE2_SIZE = 200 * 1024 # 200KB +FILE3_SIZE = 100 * 1024 # 100KB +LIMIT_KB = '500K' + +rmtree(src) +rmtree(dest) +makepath(src) +makepath(dest) +make_data_file(src / TESTFILE1, FILE1_SIZE) +make_data_file(src / TESTFILE2, FILE2_SIZE) +make_data_file(src / TESTFILE3, FILE3_SIZE) + +# Test --delay-transfer-limit-check allows transfers to continue as long as the transfer limit has not yet been reached. +# Transfers will stop only after the limit has been reached. +proc = run_rsync( + '-a', + '--whole-file', + f'--data-transfer-limit={LIMIT_KB}', + f'--delay-transfer-limit-check', + f'{src}/', + f'{dest}/', + check=False, # Don't fail on non-zero exit code + capture_output=True +) + +if proc.returncode != 26: + test_fail(f'Exit status 26 expected; got: {proc.returncode} instead.') + +# The first file should be transferred +dest_file1 = dest / TESTFILE1 +if not dest_file1.exists() or dest_file1.stat().st_size != FILE1_SIZE: + test_fail('The first file was not transferred.') + +# The second file should be transferred +dest_file2 = dest / TESTFILE2 +if not dest_file2.exists(): + test_fail('The second file was not transferred') + +# The third file should not be transferred and rsync should exit with code 26 +dest_file3 = dest / TESTFILE3 +if dest_file3.exists(): + test_fail('The third file was transferred, but it should have been skipped.') + +print('transfer_limit_test: --delay-transfer-limit-check verified') diff --git a/testsuite/transfer-limit_test.py b/testsuite/transfer-limit_test.py new file mode 100644 index 000000000..7e1912302 --- /dev/null +++ b/testsuite/transfer-limit_test.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +"""Coverage of --data-transfer-limit. + +--data-transfer-limit specifies a maximum amount of data to transfer. This does not take delta-sync into consideration; +only "whole file" transfer size is counted. If the next file to be transferred would exceed the limit specified for this +execution, rsync will exit with code 26 and no further transfers will occur. +""" + +from rsyncfns import ( + FROMDIR, TODIR, + make_data_file, makepath, rmtree, run_rsync, test_fail, +) + +src = FROMDIR +dest = TODIR +TESTFILE1 = 'testfile1' +TESTFILE2 = 'testfile2' +FILE1_SIZE = 400 * 1024 # 400KB +FILE2_SIZE = 200 * 1024 # 200KB +LIMIT_KB = '500K' + +rmtree(src) +rmtree(dest) +makepath(src) +makepath(dest) +make_data_file(src / TESTFILE1, FILE1_SIZE) +make_data_file(src / TESTFILE2, FILE2_SIZE) + +# Test --data-transfer-limit stops the transfer of files if the next file would exceed the specified limit. +proc = run_rsync( + '-a', + '--whole-file', + f'--data-transfer-limit={LIMIT_KB}', + f'{src}/', + f'{dest}/', + check=False, # Don't fail on non-zero exit code + capture_output=True +) + +if proc.returncode != 26: + test_fail(f'Exit status 26 expected; got: {proc.returncode} instead.') + +# The first file should be transferred +dest_file1 = dest / TESTFILE1 +if not dest_file1.exists() or dest_file1.stat().st_size != FILE1_SIZE: + test_fail('The first file was not transferred.') + +# The second file should not be transferred and rsync should exit with status 26 +dest_file2 = dest / TESTFILE2 +if dest_file2.exists(): + test_fail('The second file was transferred, but it should have been skipped.') + +print('transfer_limit_test: --data-transfer-limit verified')