diff --git a/.gitignore b/.gitignore index 59a6d09eb..964463b1b 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,11 @@ aclocal.m4 .deps /*.exe *.dSYM/ +simdtest +t_chmod_secure +t_secure_relpath +testsuite/__pycache__/ +testsuite/chown-fake_test.py +testsuite/devices-fake_test.py +testsuite/exclude-lsh_test.py +testsuite/xattrs-hlink_test.py diff --git a/Makefile.in b/Makefile.in index 5216fb2f7..d697e8992 100644 --- a/Makefile.in +++ b/Makefile.in @@ -46,7 +46,7 @@ zlib_OBJS=zlib/deflate.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o \ OBJS1=flist.o rsync.o generator.o receiver.o cleanup.o sender.o exclude.o \ util1.o util2.o main.o checksum.o match.o syscall.o android.o log.o backup.o delete.o OBJS2=options.o io.o compat.o hlink.o token.o uidlist.o socket.o hashtable.o \ - usage.o fileio.o batch.o clientname.o chmod.o acls.o xattrs.o + usage.o fileio.o batch.o clientname.o chmod.o acls.o xattrs.o niceness.o OBJS3=progress.o pipe.o @MD5_ASM@ @ROLL_SIMD@ @ROLL_ASM@ DAEMON_OBJ = params.o loadparm.o clientserver.o access.o connection.o authenticate.o popt_OBJS= popt/popt.o popt/poptconfig.o \ diff --git a/configure.ac b/configure.ac index cda60405b..f6cfef994 100644 --- a/configure.ac +++ b/configure.ac @@ -13,7 +13,7 @@ AC_CHECK_HEADERS(sys/fcntl.h sys/select.h fcntl.h sys/time.h sys/unistd.h \ sys/acl.h acl/libacl.h attr/xattr.h sys/xattr.h sys/extattr.h dl.h \ popt.h popt/popt.h linux/falloc.h netinet/in_systm.h netgroup.h \ zlib.h xxhash.h openssl/md4.h openssl/md5.h zstd.h lz4.h sys/file.h \ - bsd/string.h) + bsd/string.h sys/resource.h linux/ioprio.h sys/syscall.h) AC_CHECK_HEADERS([netinet/ip.h], [], [], [[#include ]]) AC_HEADER_MAJOR_FIXED @@ -1479,6 +1479,8 @@ case "$CC" in ;; esac +AC_DEFINE_UNQUOTED([COMPILE_TARGET], ["$host"], [String describing the compiled target OS and architecture]) + AC_CONFIG_FILES([Makefile lib/dummy zlib/dummy popt/dummy shconfig]) AC_OUTPUT diff --git a/main.c b/main.c index 9b52bbe6a..8eed586de 100644 --- a/main.c +++ b/main.c @@ -107,6 +107,8 @@ extern char backup_dir_buf[MAXPATHLEN]; extern char *basis_dir[MAX_BASIS_DIRS+1]; extern struct file_list *first_flist; extern filter_rule_list daemon_filter_list, implied_filter_list; +extern int nice_local; +extern int ionice_local; uid_t our_uid; gid_t our_gid; @@ -1844,6 +1846,14 @@ int main(int argc,char *argv[]) if (write_batch < 0) dry_run = 1; + if (nice_local) { + renice_me(nice_local); + } + + if (ionice_local) { + ionice_me(ionice_local); + } + if (am_server) { #ifdef ICONV_CONST setup_iconv(); diff --git a/niceness.c b/niceness.c new file mode 100644 index 000000000..542542fd6 --- /dev/null +++ b/niceness.c @@ -0,0 +1,316 @@ +/* + * Renice or ionice the current process to reduce its impact on the system + * + * Copyright (C) 2026 Michael Mess + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, visit the http://fsf.org website. + */ + +#include "rsync.h" + +extern int am_server; + +/* + * Enum for ionice values. + * Constants with negative numbers need root permission, others can be set by any user. + * RT_X is realtime priority + * BE_X is best effort with the given level + * IDLE means the process gets served only when no other processes are using disk io. + * NONE means best effort with level calculated by the formula (cpu_nice + 20) / 5 + * and is the default that does not need to be set. + */ +enum Ionice_Values { + RT_0 =-8, + RT_1 =-7, + RT_2 =-6, + RT_3 =-5, + RT_4 =-4, + RT_5 =-3, + RT_6 =-2, + RT_7 =-1, + NONE = 0, + BE_0 = 1, + BE_1 = 2, + BE_2 = 3, + BE_3 = 4, + BE_4 = 5, + BE_5 = 6, + BE_6 = 7, + BE_7 = 8, + IDLE = 9 +}; + +/* + * String representation of ionice. All strings have to be 4 characters long. + */ +const char *Ionice_ValueStrings[] = { + "rt_0", + "rt_1", + "rt_2", + "rt_3", + "rt_4", + "rt_5", + "rt_6", + "rt_7", + "none", + "be_0", + "be_1", + "be_2", + "be_3", + "be_4", + "be_5", + "be_6", + "be_7", + "idle", + 0 +}; + +/* + * Get the string representation for the given ionice_value + */ +const char *intToIoniceValueString(int ionice_value) +{ + if (ionice_value<-8) return 0; + if (ionice_value>9) return 0; + return Ionice_ValueStrings[ionice_value+8]; +} + +/* + * Parse string into ionice_value. Returns 1 on success, 0 otherwise. + */ +int ioniceStringToInt(char * string, int *ionice_value) +{ + for (int i=0; Ionice_ValueStrings[i]; i++) + { + if (strncasecmp(string, Ionice_ValueStrings[i], 4) == 0) // All values are 4 chars long + { + *ionice_value=i-8; + return 1; + } + } + return 0; +} + +/* + * Try to parse the location and set the variables isLocal and isRemote. + * Return the pointer to the string after the found string. + */ +char *parse_location(char *str, int *isLocal, int *isRemote) +{ + if (strncasecmp(str, "local", 5) == 0) + { + *isLocal=1; + *isRemote=0; + return str+5; + } + if (strncasecmp(str, "remote", 6) == 0) + { + *isLocal=0; + *isRemote=1; + return str+6; + } + if (strncasecmp(str, "all", 3) == 0) + { + *isLocal=1; + *isRemote=1; + return str+3; + } + *isLocal=0; + *isRemote=0; + return str; +} + +char *parse_nice_value(char *str, int *nice_value) +{ + if (!str || !str[0]) return 0; // ERROR + char *pointer = str; + long nice_long = strtol(str, &pointer, 10); + if (nice_long<-20 || nice_long>20) return 0; + *nice_value=(int)nice_long; + return pointer; +} + +char *parse_ionice_value(char *str, int *ionice_value) +{ + if (ioniceStringToInt(str, ionice_value)) { + return str+4; + } + *ionice_value=0; + return str; +} + +char *parse_nice_and_ionice_values(char *str, int *nice_value, int *ionice_value) +{ + char *pointer = parse_ionice_value(str, ionice_value); + if (pointer != str) // ionice value has been given standalone, no nice + { + *nice_value=0; + return pointer; + } + pointer = parse_nice_value(pointer, nice_value); + if (pointer == 0) return 0; // ERROR + if (pointer[0] == '/') // nice/ionice value + { + pointer++; + pointer = parse_ionice_value(pointer, ionice_value); + } + return pointer; +} + +/* + * parse the configuration string and return a pointer to the still not processed remainder of the configuration string + */ +char *parse_setting(char *config_str, int *nice_local, int *ionice_local, int *nice_remote, int *ionice_remote) +{ + int isLocal=0; + int isRemote=0; + char *pointer=parse_location(config_str, &isLocal, &isRemote); + if (isLocal || isRemote) + { /* Location "local" or "remote" or "all" found */ + if (pointer[0] == ':') + { /* Location followed by specific setting */ + pointer++; /* Skip colon */ + /* Read nice/ionice value */ + int nice_value=0; + int ionice_value=0; + pointer = parse_nice_and_ionice_values(pointer, &nice_value, &ionice_value); + if (isLocal) { + *nice_local=nice_value; + *ionice_local=ionice_value; + } + if (isRemote) { + *nice_remote=nice_value; + *ionice_remote=ionice_value; + } + return pointer; + } else { + if (!pointer[0] || pointer[0] == ',') + { /* End of string or comma */ + /* Location without specific setting, use default */ + int nice_default=get_renice_default_prio(); + int ionice_default=get_ionice_default_prio(); + if (isLocal) { + *nice_local=nice_default; + *ionice_local=ionice_default; + } + if (isRemote) { + *nice_remote=nice_default; + *ionice_remote=ionice_default; + } + return pointer; + } else + { /* Unexpected characters */ + return 0; // ERROR + } + } + } else { + /* Read nice/ionice value */ + int nice_value=0; + int ionice_value=0; + pointer = parse_nice_and_ionice_values(pointer, &nice_value, &ionice_value); + *nice_local=nice_value; + *ionice_local=ionice_value; + *nice_remote=nice_value; + *ionice_remote=ionice_value; + return pointer; + } + return 0; // ERROR +} + +int parse_nice(const char *config_str, int *nice_local, int *ionice_local, int *nice_remote, int *ionice_remote) +{ + char *str = (char *) config_str; + while (str && str[0]) { // string is valid and not empty + str=parse_setting(str, nice_local, ionice_local, nice_remote, ionice_remote); + if (!str) return 0; // ERROR returned by parser + if (!str[0]) return 1; // End of string - everything has been parsed - finished successfully! + if (str[0] != ',') return 0; // ERROR: Unexpected character, we expect a comma here as separator. + str++; + } + return 0; // string was empty or not valid (maybe even after a comma) +} + +int get_renice_default_prio() +{ + return 19; // lowest CPU Priority +} + +int get_ionice_default_prio() +{ + return IDLE; // lowest IO Priority +} + +void renice_me(int prio) +{ +#ifdef SUPPORT_RENICE + int which = PRIO_PROCESS; // who specifies a Process ID + int who = 0; // 0 means current process + int result = setpriority(which, who, prio); + if ( result < 0 ) { + // Failed to set priority, inform user, but can be ignored (it's just not so nice). + rprintf(FWARNING, "renice %s to new priority %d failed (%s version %s): %s\n", + am_server ? "server" : "client", prio, RSYNC_NAME, rsync_version(), strerror(errno)); + } else { + if (DEBUG_GTE(CMD, 1)) + rprintf(FINFO, "successfully reniced %s to new priority %d\n", am_server ? "server" : "client", prio); + } +#else + rprintf(FWARNING, "renice %s to new priority %d failed (%s version %s): renice not supported for %s\n", + am_server ? "server" : "client", prio, RSYNC_NAME, rsync_version(), COMPILE_TARGET); +#endif +} + +void ionice_me(int ionice_value) +{ + const char *ionice_string = intToIoniceValueString(ionice_value); +#ifdef SUPPORT_IONICE + int which = IOPRIO_WHO_PROCESS; // who specifies a Process ID + int who = 0; // 0 means current process + int class; + int data; // Ignored when using the IOPRIO_CLASS_IDLE or IOPRIO_CLASS_NONE class + switch (ionice_string[0]) + { + case 'r': // Realtime + class = IOPRIO_CLASS_RT; + data = ionice_string[3]-'0'; // rt_X: X -> data + break; + case 'b': // Best effort + class = IOPRIO_CLASS_BE; + data = ionice_string[3]-'0'; // be_X: X -> data + break; + case 'i': // Idle + class = IOPRIO_CLASS_IDLE; + data = 0; + break; + case 'n': // None + default: + class = IOPRIO_CLASS_NONE; + data = 0; + break; + } + int ioprio = IOPRIO_PRIO_VALUE(class, data); + int result = syscall(SYS_ioprio_set, which, who, ioprio); + if ( result < 0 ) { + // Failed to set priority, inform user, but can be ignored (it's just not so ionice). + rprintf(FWARNING, "ionice %s to new priority %s failed (%s version %s): %s\n", + am_server ? "server" : "client", ionice_string, RSYNC_NAME, rsync_version(), strerror(errno)); + } else { + if (DEBUG_GTE(CMD, 1)) + rprintf(FINFO, "successfully ioniced %s to new priority %s\n", am_server ? "server" : "client", ionice_string); + } +#else + rprintf(FWARNING, "ionice %s to new priority %s failed (%s version %s): ionice not supported for %s\n", + am_server ? "server" : "client", ionice_string, RSYNC_NAME, rsync_version(), COMPILE_TARGET); +#endif +} diff --git a/options.c b/options.c index 8568af2b2..4dfee06e9 100644 --- a/options.c +++ b/options.c @@ -35,6 +35,19 @@ extern filter_rule_list daemon_filter_list; int make_backups = 0; +/* If set to other than 0, be nice on the local or remote site + * Contains the nice priority to be set on the process, or 0=feature is turned off, no priority will be set. + * I have to admit, that it is not possible to set a nice value of 0 with this code, + * but in most cases 0 should be the priority of the newly started rsync process already anyway. + */ +int nice_local = 0; +int nice_remote = 0; +/* If set to other than 0, be ionice on the local or remote site + * Currently only idle is supported for ionice when turned on. + */ +int ionice_local = 0; +int ionice_remote = 0; + /** * If 1, send the whole file as literal data rather than trying to * create an incremental diff. @@ -594,7 +607,7 @@ enum {OPT_SERVER = 1000, OPT_DAEMON, OPT_SENDER, OPT_EXCLUDE, OPT_EXCLUDE_FROM, OPT_NO_D, OPT_APPEND, OPT_NO_ICONV, OPT_INFO, OPT_DEBUG, OPT_BLOCK_SIZE, OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN, OPT_BWLIMIT, OPT_STDERR, OPT_OLD_COMPRESS, OPT_NEW_COMPRESS, OPT_NO_COMPRESS, OPT_OLD_ARGS, - OPT_STOP_AFTER, OPT_STOP_AT, + OPT_STOP_AFTER, OPT_STOP_AT, OPT_NICE, OPT_NO_NICE, OPT_REFUSED_BASE = 9000}; static struct poptOption long_options[] = { @@ -845,6 +858,9 @@ static struct poptOption long_options[] = { {"remote-option", 'M', POPT_ARG_STRING, 0, 'M', 0, 0 }, {"protocol", 0, POPT_ARG_INT, &protocol_version, 0, 0, 0 }, {"checksum-seed", 0, POPT_ARG_INT, &checksum_seed, 0, 0, 0 }, + {"nice", 0, POPT_ARG_STRING, 0, OPT_NICE, 0, 0 }, + {"no-nice", 0, POPT_ARG_NONE, 0, OPT_NO_NICE, 0, 0 }, + {0, 'Q', POPT_ARG_NONE, 0, 'Q', 0, 0 }, {"server", 0, POPT_ARG_NONE, 0, OPT_SERVER, 0, 0 }, {"sender", 0, POPT_ARG_NONE, 0, OPT_SENDER, 0, 0 }, /* All the following options switch us into daemon-mode option-parsing. */ @@ -1365,6 +1381,8 @@ int parse_arguments(int *argc_p, const char ***argv_p) int argc = *argc_p; int opt, want_dest_type; int orig_protect_args = protect_args; + int default_nice_prio = get_renice_default_prio(); // default nice priority to be used, if no explicit priority is given. + int default_ionice_prio = get_ionice_default_prio(); // default ionice priority to be used, if no explicit priority is given. if (argc == 0) { strlcpy(err_buf, "argc is zero!\n", sizeof err_buf); @@ -1597,6 +1615,43 @@ int parse_arguments(int *argc_p, const char ***argv_p) quiet++; break; + case 'Q': + /* + * Turn nice on with default prio and turn ionice on (idle). + */ + nice_local = default_nice_prio; + nice_remote = default_nice_prio; + ionice_local = default_ionice_prio; + ionice_remote = default_ionice_prio; + break; + + case OPT_NICE: + /* + * Parse parameters for nice and set the following fields: + - nice_local + - ionice_local + - nice_remote + - ionice_remote + */ + arg = poptGetOptArg(pc); + if (!parse_nice(arg, &nice_local, &ionice_local, &nice_remote, &ionice_remote)) { + snprintf(err_buf, sizeof err_buf, + "Invalid argument passed to --nice (%s)\n", + arg); + goto cleanup; + } + break; + + case OPT_NO_NICE: + /* + * Turn off nice and ionice + */ + nice_local = 0; + ionice_local = 0; + nice_remote = 0; + ionice_remote = 0; + break; + case 'x': one_file_system++; break; @@ -2995,6 +3050,14 @@ void server_options(char **args, int *argc_p) if (mkpath_dest_arg && am_sender) args[ac++] = "--mkpath"; + if (nice_remote || ionice_remote) { + const char *ionice_str = ionice_remote ? intToIoniceValueString(ionice_remote):""; + const char *slash_str = ionice_remote ? "/":""; + if (asprintf(&arg, "--nice=local:%d%s%s", nice_remote, slash_str, ionice_str) < 0) + goto oom; + args[ac++] = arg; + } + if (ac > MAX_SERVER_ARGS) { /* Not possible... */ rprintf(FERROR, "argc overflow in server_options().\n"); exit_cleanup(RERR_MALLOC); diff --git a/rsync.1.md b/rsync.1.md index fdf0b2e95..e4929dda4 100644 --- a/rsync.1.md +++ b/rsync.1.md @@ -565,6 +565,15 @@ has its own detailed description later in this manpage. --checksum-seed=NUM set block/file checksum seed (advanced) --ipv4, -4 prefer IPv4 --ipv6, -6 prefer IPv6 +--nice=(LOCATION|[LOCATION:]SETTING)[,...] Set prio of the rsync process + LOCATION: local|remote|all + SETTING: NICESETTING|IONICESETTING|NICESETTING/IONICESETTING + NICESETTING: -20...19 + IONICESETTING: RT_0|RT_1|RT_2|RT_3|RT_4|RT_5|RT_6|RT_7|NONE| + BE_0|BE_1|BE_2|BE_3|BE_4|BE_5|BE_6|BE_7|IDLE +--no-nice prepending "no-" turns the option off +--Q same as --nice=all:19/idle + LOCATION and IONICESETTING are not case sensitive --version, -V print the version + other info and exit --help, -h (*) show this help (* -h is help only on its own) ``` @@ -585,6 +594,7 @@ accepted: --log-file=FILE override the "log file" setting --log-file-format=FMT override the "log format" setting --sockopts=OPTIONS specify custom TCP options +--nice=... set priority of the local daemon process --verbose, -v increase verbosity --ipv4, -4 prefer IPv4 --ipv6, -6 prefer IPv6 @@ -3825,6 +3835,74 @@ expand it. user wants a more random checksum seed. Setting NUM to 0 causes rsync to use the default of **time**() for checksum seed. +0. `--nice=(LOCATION|[LOCATION:]SETTING)[,...]` + + LOCATION: `LOCAL`|`REMOTE`|`ALL` + + - `LOCAL` is the local rsync process that has been started by the user/script/etc. + + - `REMOTE` it the remote rsync process that has been started via SSH on the remote system + + - `ALL` means both, local and remote. + The constants are not case sensitive, thus e.g. `REMOTE` and `remote` are the same value. + If omitted, the default is: all + + SETTING: NICESETTING|IONICESETTING|NICESETTING/IONICESETTING + - If the whole setting is omitted, the default is `19/IDLE` + + NICESETTING: `-20`...`19` + - Higher number means lower priority, negative numbers require root permissions + - If not specified in the setting, the default is `0`. + - No system call is made to set the priority `0`. + - If the setting fails, e.g. due to the lack of permission to set negative nice + priority, a warning is issued and rsync will continue doing its work. + + IONICESETTING: RT_0|RT_1|RT_2|RT_3|RT_4|RT_5|RT_6|RT_7|NONE|BE_0|BE_1|BE_2|BE_3|BE_4|BE_5|BE_6|BE_7|IDLE + - `RT_X` is realtime priority, need root permission + - `BE_X` is best effort with the given level + - `IDLE` means the process gets served only when no other processes are using disk io. + - `NONE` means best effort with level calculated by the formula (cpu_nice + 20) / 5 + and is the default that does not need to be set. + - The constants are not case sensitive, thus e.g. `IDLE` and `idle` are the same value. + - If not specified in the setting, the default is `NONE`. + - No system call is made to set the priority `NONE`. + - If the setting fails, e.g. due to the lack of permission to set realtime + priority, a warning is issued and rsync will continue doing its work. + + Examples: + + Specifying a setting for both, local and remote: + --nice=19/idle = --nice=all:19/idle + --nice=19 = --nice=all:19 (no ionice -> NONE => only nice will be set) + --nice=idle = --nice=all:idle (no nice -> 0 => only ionice will be set) + --nice=0 = --nice=all:0 (nice value 0 and no ionice -> 0/NONE => none of them is set) + + Specifying a setting for the given location: + --nice=local:10 + --nice=remote:idle + --nice=local:19,remote:5 + --nice=local:19/idle,remote:5 + --nice=local:19/be_0,remote:idle + + No specific setting means default 19/idle: + --nice=local = --nice=local:19/idle + --nice=remote = --nice=remote:19/idle + --nice=all = --nice=19/idle + -Q = --nice=19/idle + --nice=local,remote + + Any specific setting set before may be overwritten by the following setting, e. g.: + --nice=local:5 --nice=all + --nice=local:6/idle,all + --nice=local:5 --nice=remote:6 -Q + would result in setting the default 19/idle for both, local and remote. + + nice and ionice are always set as a pair, even when only one of them is specified. + --nice=remote:5 --nice=remote:idle + therefore would result in an unset nice value (`5` will be overwritten with default `0`) + and effectively only ionice value `idle` beeing applied. + + ## DAEMON OPTIONS The options allowed when starting an rsync daemon are as follows: diff --git a/rsync.h b/rsync.h index cdc2d2c0d..0c9e935ec 100644 --- a/rsync.h +++ b/rsync.h @@ -18,6 +18,9 @@ * with this program; if not, visit the http://fsf.org website. */ +#ifndef RSYNC_H // Ensure that this header file is never included more than once, thus avoiding errors with duplicate definitions +#define RSYNC_H 1 + #define False 0 #define True 1 #define Unset (-1) /* Our BOOL values are always an int. */ @@ -1510,3 +1513,17 @@ const char *get_panic_action(void); #elif defined HAVE_MALLINFO #define MEM_ALLOC_INFO mallinfo #endif + + /* Some header files needed to be nice ;) */ +#ifdef HAVE_SYS_RESOURCE_H +#define SUPPORT_RENICE +#include +#endif + +#if defined HAVE_LINUX_IOPRIO_H && defined HAVE_SYS_SYSCALL_H +#define SUPPORT_IONICE +#include +#include +#endif + +#endif // ifndef RSYNC_H diff --git a/testsuite/niceness_test.py b/testsuite/niceness_test.py new file mode 100644 index 000000000..e73c67ddc --- /dev/null +++ b/testsuite/niceness_test.py @@ -0,0 +1,346 @@ +#!/usr/bin/env python3 +# Python handling of niceness options like --nice or --ionice. +# +# Foundational smoke test: --version / --info=help / --debug=help all +# work with --nice or --ionice without crash, +# plus test passing the correct options to the remote command + +import os + +from rsyncfns import ( + FROMDIR, RSYNC, SRCDIR, TODIR, + checkit, run_rsync, test_fail, test_skipped +) + +# Helper function +def fail(msg: str) -> 'None': + print('### TEST FAILURE ### ' + msg + '\n') + test_fail(msg) + +# Basic help dumps must not crash when using any niceness option. +if run_rsync('-Q', '--version', check=False).returncode != 0: + fail('-Q --version output failed') +if run_rsync('-Q', '--info=help', check=False).returncode != 0: + fail('-Q --info=help output failed') +if run_rsync('-Q', '--debug=help', check=False).returncode != 0: + fail('-Q --debug=help output failed') + +# We just want to test which parameters are handed over to the remote, +# but do not really want to run rsync on the remote site to copy files. +# For this we add "--rsh=echo" to replace ssh for the remote site with echo. +# Thus no remote connection is actually created, instead the echo will just +# exit quickly without any further rsync communication, letting the local +# rsync process fail fast, not wasting much time for the test execution. +# It is expected that the local rsync process will exit here with non zero. +# We then just check the log output here for any options that may or may not +# have been given for the remote rsync process by our local rsync process. +def test_remote_args(testname: str, args: list, expected_nice_value: str, expected_ionice_value: str, expected_remote_args: list) -> None: + print('### TEST BEGIN ### ' + testname + '\n') + probe = run_rsync(*['-nvv', '--rsh=echo', *args], check=False, capture_output=True) + if probe.returncode == 0: + fail('The command should not succeed here.') + actual_remote_args = 'not found' + nice_value="" + ionice_value="" + for remote_line in probe.stdout.splitlines(): + if 'opening connection using' in remote_line: + actual_remote_args = remote_line.strip() + elif 'client to new priority' in remote_line: + print("stdout: Found client info: "+remote_line); + if 'renice' in remote_line: + nice_value = remote_line.split("to new priority", 1)[1].strip() + print("Local nice value: "+nice_value); + else: + ionice_value = remote_line.split("to new priority", 1)[1].strip() + print("Local ionice value: "+ionice_value); + else: + print('ignoring line: ' + remote_line) + for remote_line in probe.stderr.splitlines(): + if 'client to new priority' in remote_line: + print("stderr: Found client info: "+remote_line); + if 'renice' in remote_line: + nice_value = remote_line.split("to new priority", 1)[1].split("failed", 1)[0].strip() + print("Local nice value: "+nice_value); + else: + ionice_value = remote_line.split("to new priority", 1)[1].split("failed", 1)[0].strip() + print("Local ionice value: "+ionice_value); + if 'not found' == actual_remote_args: + fail('No remote shell command has been started or it was not logged.') + if not nice_value == expected_nice_value: + fail('Local nice value does not match: expected: '+expected_nice_value+", but was: "+nice_value) + if not ionice_value == expected_ionice_value: + fail('Local ionice value does not match: expected: '+expected_ionice_value+", but was: "+ionice_value) + for string in expected_remote_args: + if string[0] == '!': + substring = string[1:] + if (substring+' ') in actual_remote_args or ('\"'+substring+'\"') in actual_remote_args: + fail('Remote command line contains unexpected ' + substring + ': ' + actual_remote_args) + else: + if not ((string+' ') in actual_remote_args or ('\"'+string+'\"') in actual_remote_args): + fail('Remote command line did not contain expected ' + string + ': ' + actual_remote_args) + print('### TEST END ### ' + testname + '\n') + +test_remote_args('Check with no flags', [f'localhost:{FROMDIR}', TODIR], "", "", ["--server", "--sender", "!--blahblah", + "!--nice", + "!--nice-local", + "!--nice-local-value=4", + "!--nice-remote", + "!--nice-remote-value=6", + "!--ionice", + "!--ionice-local", + "!--ionice-remote", + "!-Q" + ]) + +test_remote_args('Check with -Q', ['-Q', f'localhost:{FROMDIR}', TODIR], "19", "idle", ["--server", "--sender", "!--blahblah", + "!--nice", + "--nice=local:19/idle", + "!--nice-local-value=4", + "!--nice-remote", + "!--nice-remote-value=6", + "!--ionice", + "!--ionice-local", + "!--ionice-remote", + "!-Q" + ]) + +test_remote_args('Check with --nice=all', ['--nice=all', f'localhost:{FROMDIR}', TODIR], "19", "idle", ["--server", "--sender", "!--blahblah", + "!--nice", + "--nice=local:19/idle", + "!--nice-local-value=4", + "!--nice-remote", + "!--nice-remote-value=6", + "!--ionice", + "!--ionice-local", + "!--ionice-remote", + "!-Q" + ]) + +test_remote_args('Check with --nice=local', ['--nice=local', f'localhost:{FROMDIR}', TODIR], "19", "idle", ["--server", "--sender", "!--blahblah", + "!--nice", + "!--nice=local:19/idle", + "!--nice-local-value=4", + "!--nice-remote", + "!--nice-remote-value=6", + "!--ionice", + "!--ionice-local", + "!--ionice-remote", + "!-Q" + ]) + +test_remote_args('Check with --nice=remote', ['--nice=remote', f'localhost:{FROMDIR}', TODIR], "", "", ["--server", "--sender", "!--blahblah", + "!--nice", + "--nice=local:19/idle", + "!--nice-local-value=4", + "!--nice-remote", + "!--nice-remote-value=6", + "!--ionice", + "!--ionice-local", + "!--ionice-remote", + "!-Q" + ]) + +test_remote_args('Check with --nice=idle', ['--nice=idle', f'localhost:{FROMDIR}', TODIR], "", "idle", ["--server", "--sender", "!--blahblah", + "!--nice", + "--nice=local:0/idle", + "!--nice-local-value=4", + "!--nice-remote", + "!--nice-remote-value=6", + "!--ionice", + "!--ionice-local", + "!--ionice-remote", + "!-Q" + ]) + +test_remote_args('Check with --nice=be_2', ['--nice=be_2', f'localhost:{FROMDIR}', TODIR], "", "be_2", ["--server", "--sender", "!--blahblah", + "!--nice", + "--nice=local:0/be_2", + "!--nice-local-value=4", + "!--nice-remote", + "!--nice-remote-value=6", + "!--ionice", + "!--ionice-local", + "!--ionice-remote", + "!-Q" + ]) + +test_remote_args('Check with --nice=be_5', ['--nice=be_5', f'localhost:{FROMDIR}', TODIR], "", "be_5", ["--server", "--sender", "!--blahblah", + "!--nice", + "--nice=local:0/be_5", + "!--nice-local-value=4", + "!--nice-remote", + "!--nice-remote-value=6", + "!--ionice", + "!--ionice-local", + "!--ionice-remote", + "!-Q" + ]) + +test_remote_args('Check with --nice=rt_3', ['--nice=rt_3', f'localhost:{FROMDIR}', TODIR], "", "rt_3", ["--server", "--sender", "!--blahblah", + "!--nice", + "--nice=local:0/rt_3", + "!--nice-local-value=4", + "!--nice-remote", + "!--nice-remote-value=6", + "!--ionice", + "!--ionice-local", + "!--ionice-remote", + "!-Q" + ]) + +test_remote_args('Check with --nice=rt_7', ['--nice=rt_7', f'localhost:{FROMDIR}', TODIR], "", "rt_7", ["--server", "--sender", "!--blahblah", + "!--nice", + "--nice=local:0/rt_7", + "!--nice-local-value=4", + "!--nice-remote", + "!--nice-remote-value=6", + "!--ionice", + "!--ionice-local", + "!--ionice-remote", + "!-Q" + ]) + +test_remote_args('Check with --nice=RT_0', ['--nice=RT_0', f'localhost:{FROMDIR}', TODIR], "", "rt_0", ["--server", "--sender", "!--blahblah", + "!--nice", + "--nice=local:0/rt_0", + "!--nice-local-value=4", + "!--nice-remote", + "!--nice-remote-value=6", + "!--ionice", + "!--ionice-local", + "!--ionice-remote", + "!-Q" + ]) + +test_remote_args('Check with --nice=none', ['--nice=none', f'localhost:{FROMDIR}', TODIR], "", "", ["--server", "--sender", "!--blahblah", + "!--nice", + "!--nice=local:0/none", + "!--nice-local-value=4", + "!--nice-remote", + "!--nice-remote-value=6", + "!--ionice", + "!--ionice-local", + "!--ionice-remote", + "!-Q" + ]) + +test_remote_args('Check with --nice=NONE', ['--nice=NONE', f'localhost:{FROMDIR}', TODIR], "", "", ["--server", "--sender", "!--blahblah", + "!--nice", + "!--nice=local:0/none", + "!--nice-local-value=4", + "!--nice-remote", + "!--nice-remote-value=6", + "!--ionice", + "!--ionice-local", + "!--ionice-remote", + "!-Q" + ]) + +test_remote_args('Check with --nice=local:idle', ['--nice=local:idle', f'localhost:{FROMDIR}', TODIR], "", "idle", ["--server", "--sender", "!--blahblah", + "!--nice", + "!--nice=local:19/idle", + "!--nice-local-value=4", + "!--nice-remote", + "!--nice-remote-value=6", + "!--ionice", + "!--ionice-local", + "!--ionice-remote", + "!-Q" + ]) + +test_remote_args('Check with --nice=remote:idle', ['--nice=remote:idle', f'localhost:{FROMDIR}', TODIR], "", "", ["--server", "--sender", "!--blahblah", + "!--nice", + "--nice=local:0/idle", + "!--nice-local-value=4", + "!--nice-remote", + "!--nice-remote-value=6", + "!--ionice", + "!--ionice-local", + "!--ionice-remote", + "!-Q" + ]) + +test_remote_args('Check with --nice=local:4', ['--nice=local:4', f'localhost:{FROMDIR}', TODIR], "4", "", ["--server", "--sender", "!--blahblah", + "!--nice", + "!--nice=local:4", + "!--nice-local-value=4", + "!--nice-local-value=6", + "!--nice-remote", + "!--nice-remote-value=4", + "!--nice-remote-value=6", + "!--ionice", + "!--ionice-local", + "!--ionice-remote", + "!-Q" + ]) + +test_remote_args('Check with --nice=remote:6', ['--nice=remote:6', f'localhost:{FROMDIR}', TODIR], "", "", ["--server", "--sender", "!--blahblah", + "!--nice", + "--nice=local:6", + "!--nice-local-value=4", + "!--nice-local-value=6", + "!--nice-remote", + "!--nice-remote-value=4", + "!--nice-remote-value=6", + "!--ionice", + "!--ionice-local", + "!--ionice-remote", + "!-Q" + ]) + + +test_remote_args('Check with --nice=6', ['--nice=6', f'localhost:{FROMDIR}', TODIR], "6", "", ["--server", "--sender", "!--blahblah", + "!--nice", + "--nice=local:6", + "!--nice-local-value=4", + "!--nice-local-value=6", + "!--nice-remote", + "!--nice-remote-value=4", + "!--nice-remote-value=6", + "!--ionice", + "!--ionice-local", + "!--ionice-remote", + "!-Q" + ]) + + +test_remote_args('Check with --nice=-2/IDLE', ['--nice=-2/IDLE', f'localhost:{FROMDIR}', TODIR], "-2", "idle", ["--server", "--sender", "!--blahblah", + "!--nice", + "--nice=local:-2/idle", + "!--nice-local-value=4", + "!--nice-local-value=6", + "!--nice-remote", + "!--nice-remote-value=4", + "!--nice-remote-value=6", + "!--ionice", + "!--ionice-local", + "!--ionice-remote", + "!-Q" + ]) + +test_remote_args('Check with --nice=ALL:-2/IDLE', ['--nice=ALL:-2/IDLE', f'localhost:{FROMDIR}', TODIR], "-2", "idle", ["--server", "--sender", "!--blahblah", + "!--nice", + "--nice=local:-2/idle", + "!--nice-local-value=4", + "!--nice-local-value=6", + "!--nice-remote", + "!--nice-remote-value=4", + "!--nice-remote-value=6", + "!--ionice", + "!--ionice-local", + "!--ionice-remote", + "!-Q" + ]) + +test_remote_args('Check with --nice=local:10/BE_0,remote:19/IDLE', ['--nice=local:10/BE_0,remote:19/IDLE', f'localhost:{FROMDIR}', TODIR], "10", "be_0", ["--server", "--sender", "!--blahblah", + "!--nice", + "--nice=local:19/idle", + "!--nice-local-value=4", + "!--nice-remote", + "!--nice-remote-value=6", + "!--ionice", + "!--ionice-local", + "!--ionice-remote", + "!-Q" + ]) +