From a2bd5eb03e299de04e63eb469ce3d3d3f092942f Mon Sep 17 00:00:00 2001
From: Wayne Davison <wayne@opencoder.net>
Date: Tue, 22 Nov 2022 21:00:04 -0800
Subject: [PATCH] Duplicate argv data before poptFreeContext().

Reference:https://github.com/RsyncProject/rsync/commit/8990ad96de881f7332d16d32485f9d8b841a87d2
Conflict:NA
---
 main.c    |  13 -----
 options.c | 160 ++++++++++++++++++++++++++++++------------------------
 2 files changed, 90 insertions(+), 83 deletions(-)

diff --git a/main.c b/main.c
index 9ebfbea..5f9a7a9 100644
--- a/main.c
+++ b/main.c
@@ -1372,15 +1372,6 @@ int client_run(int f_in, int f_out, pid_t pid, int argc, char *argv[])
 	return MAX(exit_code, exit_code2);
 }
 
-static void dup_argv(char *argv[])
-{
-	int i;
-
-	for (i = 0; argv[i]; i++)
-		argv[i] = strdup(argv[i]);
-}
-
-
 /* Start a client for either type of remote connection.  Work out
  * whether the arguments request a remote shell or rsyncd connection,
  * and call the appropriate connection function, then run_client.
@@ -1396,10 +1387,6 @@ static int start_client(int argc, char *argv[])
 	int ret;
 	pid_t pid;
 
-	/* Don't clobber argv[] so that ps(1) can still show the right
-	 * command line. */
-	dup_argv(argv);
-
 	if (!read_batch) { /* for read_batch, NO source is specified */
 		char *path = check_for_hostspec(argv[0], &shell_machine, &rsync_port);
 		if (path) { /* source is remote */
diff --git a/options.c b/options.c
index 4feeb7e..62558ef 100644
--- a/options.c
+++ b/options.c
@@ -200,6 +200,7 @@ int remote_option_cnt = 0;
 const char **remote_options = NULL;
 const char *checksum_choice = NULL;
 const char *compress_choice = NULL;
+static const char *empty_argv[1];
 
 int quiet = 0;
 int output_motd = 1;
@@ -1345,7 +1346,7 @@ char *alt_dest_opt(int type)
  **/
 int parse_arguments(int *argc_p, const char ***argv_p)
 {
-	static poptContext pc;
+	poptContext pc;
 	const char *arg, **argv = *argv_p;
 	int argc = *argc_p;
 	int opt, want_dest_type;
@@ -1365,10 +1366,6 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 
 	/* TODO: Call poptReadDefaultConfig; handle errors. */
 
-	/* The context leaks in case of an error, but if there's a
-	 * problem we always exit anyhow. */
-	if (pc)
-		poptFreeContext(pc);
 	pc = poptGetContext(RSYNC_NAME, argc, argv, long_options, 0);
 	if (!am_server) {
 		poptReadDefaultConfig(pc, 0);
@@ -1411,7 +1408,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 				strlcpy(err_buf,
 					"Attempt to hack rsync thwarted!\n",
 					sizeof err_buf);
-				return 0;
+				goto cleanup;
 			}
 #ifdef ICONV_OPTION
 			iconv_opt = NULL;
@@ -1457,7 +1454,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 			if (tmpdir && strlen(tmpdir) >= MAXPATHLEN - 10) {
 				snprintf(err_buf, sizeof err_buf,
 					 "the --temp-dir path is WAY too long.\n");
-				return 0;
+				goto cleanup;
 			}
 
 			if (!daemon_opt) {
@@ -1467,8 +1464,16 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 				exit_cleanup(RERR_SYNTAX);
 			}
 
-			*argv_p = argv = poptGetArgs(pc);
-			*argc_p = argc = count_args(argv);
+			argv = poptGetArgs(pc);
+			argc = count_args(argv);
+			if (!argc) {
+				*argv_p = empty_argv;
+				*argc_p = 0;
+			} else if (poptDupArgv(argc, argv, argc_p, argv_p) != 0)
+				out_of_memory("parse_arguments");
+			argv = *argv_p;
+			poptFreeContext(pc);
+
 			am_starting_up = 0;
 			daemon_opt = 0;
 			am_daemon = 1;
@@ -1523,7 +1528,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 		case 'a':
 			if (refused_archive_part) {
 				create_refuse_error(refused_archive_part);
-				return 0;
+				goto cleanup;
 			}
 			if (!recurse) /* preserve recurse == 2 */
 				recurse = 1;
@@ -1593,7 +1598,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 		case 'P':
 			if (refused_partial || refused_progress) {
 				create_refuse_error(refused_partial ? refused_partial : refused_progress);
-				return 0;
+				goto cleanup;
 			}
 			do_progress = 1;
 			keep_partial = 1;
@@ -1628,7 +1633,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 			if (*arg != '-') {
 				snprintf(err_buf, sizeof err_buf,
 					"Remote option must start with a dash: %s\n", arg);
-				return 0;
+				goto cleanup;
 			}
 			if (remote_option_cnt+2 >= remote_option_alloc) {
 				remote_option_alloc += 16;
@@ -1670,27 +1675,27 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 			ssize_t size;
 			arg = poptGetOptArg(pc);
 			if ((size = parse_size_arg(arg, 'b', "block-size", 0, max_blength, False)) < 0)
-				return 0;
+				goto cleanup;
 			block_size = (int32)size;
 			break;
 		}
 
 		case OPT_MAX_SIZE:
 			if ((max_size = parse_size_arg(max_size_arg, 'b', "max-size", 0, -1, False)) < 0)
-				return 0;
+				goto cleanup;
 			max_size_arg = strdup(do_big_num(max_size, 0, NULL));
 			break;
 
 		case OPT_MIN_SIZE:
 			if ((min_size = parse_size_arg(min_size_arg, 'b', "min-size", 0, -1, False)) < 0)
-				return 0;
+				goto cleanup;
 			min_size_arg = strdup(do_big_num(min_size, 0, NULL));
 			break;
 
 		case OPT_BWLIMIT: {
 			ssize_t size = parse_size_arg(bwlimit_arg, 'K', "bwlimit", 512, -1, True);
 			if (size < 0)
-				return 0;
+				goto cleanup;
 			bwlimit_arg = strdup(do_big_num(size, 0, NULL));
 			bwlimit = (size + 512) / 1024;
 			break;
@@ -1719,7 +1724,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 				snprintf(err_buf, sizeof err_buf,
 					"ERROR: the %s option conflicts with the %s option\n",
 					alt_dest_opt(want_dest_type), alt_dest_opt(0));
-				return 0;
+				goto cleanup;
 			}
 			alt_dest_type = want_dest_type;
 
@@ -1727,7 +1732,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 				snprintf(err_buf, sizeof err_buf,
 					"ERROR: at most %d %s args may be specified\n",
 					MAX_BASIS_DIRS, alt_dest_opt(0));
-				return 0;
+				goto cleanup;
 			}
 			/* We defer sanitizing this arg until we know what
 			 * our destination directory is going to be. */
@@ -1740,7 +1745,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 				snprintf(err_buf, sizeof err_buf,
 					"Invalid argument passed to --chmod (%s)\n",
 					arg);
-				return 0;
+				goto cleanup;
 			}
 			break;
 
@@ -1759,11 +1764,11 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 				if (usermap_via_chown) {
 					snprintf(err_buf, sizeof err_buf,
 						"--usermap conflicts with prior --chown.\n");
-					return 0;
+					goto cleanup;
 				}
 				snprintf(err_buf, sizeof err_buf,
 					"You can only specify --usermap once.\n");
-				return 0;
+				goto cleanup;
 			}
 			usermap = (char *)poptGetOptArg(pc);
 			usermap_via_chown = False;
@@ -1775,11 +1780,11 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 				if (groupmap_via_chown) {
 					snprintf(err_buf, sizeof err_buf,
 						"--groupmap conflicts with prior --chown.\n");
-					return 0;
+					goto cleanup;
 				}
 				snprintf(err_buf, sizeof err_buf,
 					"You can only specify --groupmap once.\n");
-				return 0;
+				goto cleanup;
 			}
 			groupmap = (char *)poptGetOptArg(pc);
 			groupmap_via_chown = False;
@@ -1798,11 +1803,11 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 					if (!usermap_via_chown) {
 						snprintf(err_buf, sizeof err_buf,
 							"--chown conflicts with prior --usermap.\n");
-						return 0;
+						goto cleanup;
 					}
 					snprintf(err_buf, sizeof err_buf,
 						"You can only specify a user-affecting --chown once.\n");
-					return 0;
+					goto cleanup;
 				}
 				if (asprintf(&usermap, "*:%.*s", len, chown) < 0)
 					out_of_memory("parse_arguments");
@@ -1814,11 +1819,11 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 					if (!groupmap_via_chown) {
 						snprintf(err_buf, sizeof err_buf,
 							"--chown conflicts with prior --groupmap.\n");
-						return 0;
+						goto cleanup;
 					}
 					snprintf(err_buf, sizeof err_buf,
 						"You can only specify a group-affecting --chown once.\n");
-					return 0;
+					goto cleanup;
 				}
 				if (asprintf(&groupmap, "*:%s", arg) < 0)
 					out_of_memory("parse_arguments");
@@ -1846,7 +1851,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 			snprintf(err_buf,sizeof(err_buf),
 				 "ACLs are not supported on this %s\n",
 				 am_server ? "server" : "client");
-			return 0;
+			goto cleanup;
 #endif
 
 		case 'X':
@@ -1857,7 +1862,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 			snprintf(err_buf,sizeof(err_buf),
 				 "extended attributes are not supported on this %s\n",
 				 am_server ? "server" : "client");
-			return 0;
+			goto cleanup;
 #endif
 
 		case OPT_STOP_AFTER: {
@@ -1866,7 +1871,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 			stop_at_utime = time(NULL);
 			if ((val = atol(arg) * 60) <= 0 || LONG_MAX - val < stop_at_utime || (long)(time_t)val != val) {
 				snprintf(err_buf, sizeof err_buf, "invalid --stop-after value: %s\n", arg);
-				return 0;
+				goto cleanup;
 			}
 			stop_at_utime += val;
 			break;
@@ -1877,11 +1882,11 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 			arg = poptGetOptArg(pc);
 			if ((stop_at_utime = parse_time(arg)) == (time_t)-1) {
 				snprintf(err_buf, sizeof err_buf, "invalid --stop-at format: %s\n", arg);
-				return 0;
+				goto cleanup;
 			}
 			if (stop_at_utime <= time(NULL)) {
 				snprintf(err_buf, sizeof err_buf, "--stop-at time is not in the future: %s\n", arg);
-				return 0;
+				goto cleanup;
 			}
 			break;
 #endif
@@ -1899,7 +1904,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 			else {
 				snprintf(err_buf, sizeof err_buf,
 					"--stderr mode \"%s\" is not one of errors, all, or client\n", arg);
-				return 0;
+				goto cleanup;
 			}
 			saw_stderr_opt = 1;
 			break;
@@ -1910,13 +1915,13 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 			 * turned this option off. */
 			if (opt >= OPT_REFUSED_BASE) {
 				create_refuse_error(opt);
-				return 0;
+				goto cleanup;
 			}
 			snprintf(err_buf, sizeof err_buf, "%s%s: %s\n",
 				 am_server ? "on remote machine: " : "",
 				 poptBadOption(pc, POPT_BADOPTION_NOALIAS),
 				 poptStrerror(opt));
-			return 0;
+			goto cleanup;
 		}
 	}
 
@@ -1936,7 +1941,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 	if (max_alloc_arg) {
 		ssize_t size = parse_size_arg(max_alloc_arg, 'B', "max-alloc", 1024*1024, -1, True);
 		if (size < 0)
-			return 0;
+			goto cleanup;
 		max_alloc = size;
 	}
 
@@ -1950,7 +1955,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 		if (protect_args > 0) {
 			snprintf(err_buf, sizeof err_buf,
 				 "--protect-args conflicts with --old-args.\n");
-			return 0;
+			goto cleanup;
 		}
 		protect_args = 0;
 	}
@@ -1995,7 +2000,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 			do_compression = CPRES_AUTO;
 		if (do_compression && refused_compress) {
 			create_refuse_error(refused_compress);
-			return 0;
+			goto cleanup;
 		}
 	}
 
@@ -2020,7 +2025,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 		default:
 			snprintf(err_buf, sizeof err_buf,
 				"Invalid --outbuf setting -- specify N, L, or B.\n");
-			return 0;
+			goto cleanup;
 		}
 		setvbuf(stdout, (char *)NULL, mode, 0);
 	}
@@ -2048,7 +2053,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 	}
 	if (refused_no_iconv && !iconv_opt) {
 		create_refuse_error(refused_no_iconv);
-		return 0;
+		goto cleanup;
 	}
 #endif
 
@@ -2059,18 +2064,30 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 	if (orig_protect_args == 2 && am_server)
 		protect_args = orig_protect_args;
 
-	if (protect_args == 1 && am_server)
+	if (protect_args == 1 && am_server) {
+		poptFreeContext(pc);
 		return 1;
+	}
 
-	*argv_p = argv = poptGetArgs(pc);
-	*argc_p = argc = count_args(argv);
+	/* Because popt 1.19 has started to free the returned args data, we now
+	 * make a copy of the array and then do an immediate cleanup. */
+	argv = poptGetArgs(pc);
+	argc = count_args(argv);
+	if (!argc) {
+		*argv_p = empty_argv;
+		*argc_p = 0;
+	} else if (poptDupArgv(argc, argv, argc_p, argv_p) != 0)
+		out_of_memory("parse_arguments");
+	argv = *argv_p;
+	poptFreeContext(pc);
+	pc = NULL;
 
 #ifndef SUPPORT_LINKS
 	if (preserve_links && !am_sender) {
 		snprintf(err_buf, sizeof err_buf,
 			 "symlinks are not supported on this %s\n",
 			 am_server ? "server" : "client");
-		return 0;
+		goto cleanup;
 	}
 #endif
 
@@ -2079,7 +2096,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 		snprintf(err_buf, sizeof err_buf,
 			 "hard links are not supported on this %s\n",
 			 am_server ? "server" : "client");
-		return 0;
+		goto cleanup;
 	}
 #endif
 
@@ -2087,20 +2104,20 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 	if (am_root < 0 && preserve_xattrs > 1) {
 		snprintf(err_buf, sizeof err_buf,
 			 "--fake-super conflicts with -XX\n");
-		return 0;
+		goto cleanup;
 	}
 #else
 	if (am_root < 0) {
 		snprintf(err_buf, sizeof err_buf,
 			 "--fake-super requires an rsync with extended attributes enabled\n");
-		return 0;
+		goto cleanup;
 	}
 #endif
 
 	if (write_batch && read_batch) {
 		snprintf(err_buf, sizeof err_buf,
 			"--write-batch and --read-batch can not be used together\n");
-		return 0;
+		goto cleanup;
 	}
 	if (write_batch > 0 || read_batch) {
 		if (am_server) {
@@ -2119,25 +2136,25 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 	if (read_batch && files_from) {
 		snprintf(err_buf, sizeof err_buf,
 			"--read-batch cannot be used with --files-from\n");
-		return 0;
+		goto cleanup;
 	}
 	if (read_batch && remove_source_files) {
 		snprintf(err_buf, sizeof err_buf,
 			"--read-batch cannot be used with --remove-%s-files\n",
 			remove_source_files == 1 ? "source" : "sent");
-		return 0;
+		goto cleanup;
 	}
 	if (batch_name && strlen(batch_name) > MAX_BATCH_NAME_LEN) {
 		snprintf(err_buf, sizeof err_buf,
 			"the batch-file name must be %d characters or less.\n",
 			MAX_BATCH_NAME_LEN);
-		return 0;
+		goto cleanup;
 	}
 
 	if (tmpdir && strlen(tmpdir) >= MAXPATHLEN - 10) {
 		snprintf(err_buf, sizeof err_buf,
 			 "the --temp-dir path is WAY too long.\n");
-		return 0;
+		goto cleanup;
 	}
 
 	if (max_delete < 0 && max_delete != INT_MIN) {
@@ -2171,7 +2188,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 	if (delete_before + !!delete_during + delete_after > 1) {
 		snprintf(err_buf, sizeof err_buf,
 			"You may not combine multiple --delete-WHEN options.\n");
-		return 0;
+		goto cleanup;
 	}
 	if (delete_before || delete_during || delete_after)
 		delete_mode = 1;
@@ -2182,7 +2199,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 				delete_during = 1;
 			else {
 				create_refuse_error(refused_delete_before);
-				return 0;
+				goto cleanup;
 			}
 		} else if (refused_delete_during)
 			delete_before = 1;
@@ -2191,14 +2208,14 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 	if (!xfer_dirs && delete_mode) {
 		snprintf(err_buf, sizeof err_buf,
 			"--delete does not work without --recursive (-r) or --dirs (-d).\n");
-		return 0;
+		goto cleanup;
 	}
 
 	if (missing_args == 3) /* simplify if both options were specified */
 		missing_args = 2;
 	if (refused_delete && (delete_mode || missing_args == 2)) {
 		create_refuse_error(refused_delete);
-		return 0;
+		goto cleanup;
 	}
 
 	if (remove_source_files) {
@@ -2207,7 +2224,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 		 * options. */
 		if (refused_delete && am_sender) {
 			create_refuse_error(refused_delete);
-			return 0;
+			goto cleanup;
 		}
 		need_messages_from_generator = 1;
 	}
@@ -2261,7 +2278,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 		snprintf(err_buf, sizeof err_buf,
 			"--suffix cannot contain slashes: %s\n",
 			backup_suffix);
-		return 0;
+		goto cleanup;
 	}
 	if (backup_dir) {
 		size_t len;
@@ -2274,7 +2291,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 		if (len > sizeof backup_dir_buf - 128) {
 			snprintf(err_buf, sizeof err_buf,
 				"the --backup-dir path is WAY too long.\n");
-			return 0;
+			goto cleanup;
 		}
 		backup_dir_len = (int)len;
 		if (!backup_dir_len) {
@@ -2293,7 +2310,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 			"--suffix cannot be empty %s\n", backup_dir_len < 0
 			? "when --backup-dir is the same as the dest dir"
 			: "without a --backup-dir");
-		return 0;
+		goto cleanup;
 	} else if (make_backups && delete_mode && !delete_excluded && !am_server) {
 		snprintf(backup_dir_buf, sizeof backup_dir_buf,
 			"P *%s", backup_suffix);
@@ -2362,11 +2379,11 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 		if (whole_file > 0) {
 			snprintf(err_buf, sizeof err_buf,
 				 "--append cannot be used with --whole-file\n");
-			return 0;
+			goto cleanup;
 		}
 		if (refused_inplace) {
 			create_refuse_error(refused_inplace);
-			return 0;
+			goto cleanup;
 		}
 		inplace = 1;
 	}
@@ -2374,7 +2391,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 	if (write_devices) {
 		if (refused_inplace) {
 			create_refuse_error(refused_inplace);
-			return 0;
+			goto cleanup;
 		}
 		inplace = 1;
 	}
@@ -2389,13 +2406,13 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 				 "--%s cannot be used with --%s\n",
 				 append_mode ? "append" : "inplace",
 				 delay_updates ? "delay-updates" : "partial-dir");
-			return 0;
+			goto cleanup;
 		}
 		/* --inplace implies --partial for refusal purposes, but we
 		 * clear the keep_partial flag for internal logic purposes. */
 		if (refused_partial) {
 			create_refuse_error(refused_partial);
-			return 0;
+			goto cleanup;
 		}
 		keep_partial = 0;
 #else
@@ -2403,7 +2420,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 			 "--%s is not supported on this %s\n",
 			 append_mode ? "append" : "inplace",
 			 am_server ? "server" : "client");
-		return 0;
+		goto cleanup;
 #endif
 	} else {
 		if (keep_partial && !partial_dir && !am_server) {
@@ -2417,7 +2434,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 				partial_dir = NULL;
 			if (!partial_dir && refused_partial) {
 				create_refuse_error(refused_partial);
-				return 0;
+				goto cleanup;
 			}
 			keep_partial = 1;
 		}
@@ -2438,14 +2455,14 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 			if (am_server) {
 				snprintf(err_buf, sizeof err_buf,
 					"The --files-from sent to the server cannot specify a host.\n");
-				return 0;
+				goto cleanup;
 			}
 			files_from = p;
 			filesfrom_host = h;
 			if (strcmp(files_from, "-") == 0) {
 				snprintf(err_buf, sizeof err_buf,
 					"Invalid --files-from remote filename\n");
-				return 0;
+				goto cleanup;
 			}
 		} else {
 			if (sanitize_paths)
@@ -2464,7 +2481,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 				snprintf(err_buf, sizeof err_buf,
 					"failed to open files-from file %s: %s\n",
 					files_from, strerror(errno));
-				return 0;
+				goto cleanup;
 			}
 		}
 	}
@@ -2481,6 +2498,9 @@ int parse_arguments(int *argc_p, const char ***argv_p)
   options_rejected:
 	snprintf(err_buf, sizeof err_buf,
 		"Your options have been rejected by the server.\n");
+  cleanup:
+	if (pc)
+		poptFreeContext(pc);
 	return 0;
 }
 
-- 
2.33.0

