Skip to main content

s3sync/config/args/
mod.rs

1use crate::Config;
2use crate::callback::event_manager::EventManager;
3use crate::callback::filter_manager::FilterManager;
4use crate::callback::preprocess_manager::PreprocessManager;
5#[allow(unused_imports)]
6use crate::config::args::value_parser::{
7    canned_acl, checksum_algorithm, file_exist, human_bytes, metadata, sse, storage_class,
8    storage_path, tagging, url,
9};
10use crate::config::{
11    CLITimeoutConfig, ClientConfig, FilterConfig, ForceRetryConfig, RetryConfig, TracingConfig,
12    TransferConfig,
13};
14#[allow(unused_imports)]
15use crate::types::event_callback::EventType;
16use crate::types::{
17    AccessKeys, ClientConfigLocation, S3Credentials, SseCustomerKey, SseKmsKeyId, StoragePath,
18};
19use aws_sdk_s3::types::{
20    ChecksumAlgorithm, ChecksumMode, ObjectCannedAcl, RequestPayer, ServerSideEncryption,
21    StorageClass,
22};
23use aws_smithy_types::checksum_config::RequestChecksumCalculation;
24use cfg_if::cfg_if;
25use chrono::{DateTime, Utc};
26use clap::Parser;
27use clap::builder::{ArgPredicate, NonEmptyStringValueParser};
28use clap_verbosity_flag::{Verbosity, WarnLevel};
29use fancy_regex::Regex;
30#[cfg(feature = "version")]
31use shadow_rs::shadow;
32use std::ffi::OsString;
33use std::path::PathBuf;
34use std::str::FromStr;
35use std::sync::Arc;
36use tokio::sync::Semaphore;
37
38mod tests;
39mod value_parser;
40
41const EXPRESS_ONEZONE_STORAGE_SUFFIX: &str = "--x-s3";
42
43const DEFAULT_WORKER_SIZE: u16 = 16;
44const DEFAULT_AWS_MAX_ATTEMPTS: u32 = 10;
45const DEFAULT_FORCE_RETRY_COUNT: u32 = 5;
46const DEFAULT_FORCE_RETRY_INTERVAL_MILLISECONDS: u64 = 1000;
47const DEFAULT_INITIAL_BACKOFF_MILLISECONDS: u64 = 100;
48const DEFAULT_JSON_TRACING: bool = false;
49const DEFAULT_AWS_SDK_TRACING: bool = false;
50const DEFAULT_SPAN_EVENTS_TRACING: bool = false;
51const DEFAULT_DISABLE_COLOR_TRACING: bool = false;
52const DEFAULT_TRACING_STDERR: bool = false;
53const DEFAULT_MULTIPART_THRESHOLD: &str = "8MiB";
54const DEFAULT_MULTIPART_CHUNKSIZE: &str = "8MiB";
55const DEFAULT_AUTO_CHUNKSIZE: bool = false;
56const DEFAULT_NO_SYNC_SYSTEM_METADATA: bool = false;
57const DEFAULT_NO_SYNC_USER_DEFINED_METADATA: bool = false;
58const DEFAULT_WARN_AS_ERROR: bool = false;
59const DEFAULT_IGNORE_SYMLINKS: bool = false;
60const DEFAULT_FORCE_PATH_STYLE: bool = false;
61const DEFAULT_HEAD_EACH_TARGET: bool = false;
62const DEFAULT_ENABLE_VERSIONING: bool = false;
63const DEFAULT_REMOVE_MODIFIED_FILTER: bool = false;
64const DEFAULT_CHECK_SIZE: bool = false;
65const DEFAULT_CHECK_ETAG: bool = false;
66const DEFAULT_CHECK_MTIME_AND_ETAG: bool = false;
67const DEFAULT_SYNC_WITH_DELETE: bool = false;
68const DEFAULT_DISABLE_TAGGING: bool = false;
69const DEFAULT_SYNC_LATEST_TAGGING: bool = false;
70const DEFAULT_NO_GUESS_MIME_TYPE: bool = false;
71const DEFAULT_SERVER_SIDE_COPY: bool = false;
72const DEFAULT_DISABLE_MULTIPART_VERIFY: bool = false;
73const DEFAULT_DISABLE_ETAG_VERIFY: bool = false;
74const DEFAULT_DISABLE_ADDITIONAL_CHECKSUM_VERIFY: bool = false;
75const DEFAULT_ENABLE_ADDITIONAL_CHECKSUM: bool = false;
76const DEFAULT_DRY_RUN: bool = false;
77const DEFAULT_MAX_KEYS: i32 = 1000;
78const DEFAULT_PUT_LAST_MODIFIED_METADATA: bool = false;
79const DEFAULT_DISABLE_STALLED_STREAM_PROTECTION: bool = false;
80const DEFAULT_DISABLE_PAYLOAD_SIGNING: bool = false;
81const DEFAULT_DISABLE_CONTENT_MD5_HEADER: bool = false;
82const DEFAULT_DELETE_EXCLUDED: bool = false;
83const DEFAULT_FULL_OBJECT_CHECKSUM: bool = false;
84const DEFAULT_DISABLE_EXPRESS_ONE_ZONE_ADDITIONAL_CHECKSUM: bool = false;
85const DEFAULT_MAX_PARALLEL_MULTIPART_UPLOADS: u16 = 16;
86const DEFAULT_MAX_PARALLEL_LISTINGS: u16 = 16;
87const DEFAULT_OBJECT_LISTING_QUEUE_SIZE: u32 = 200000;
88const DEFAULT_PARALLEL_LISTING_MAX_DEPTH: u16 = 2;
89const DEFAULT_ALLOW_PARALLEL_LISTINGS_IN_EXPRESS_ONE_ZONE: bool = false;
90const DEFAULT_ACCELERATE: bool = false;
91const DEFAULT_REQUEST_PAYER: bool = false;
92const DEFAULT_REPORT_SYNC_STATUS: bool = false;
93const DEFAULT_REPORT_METADATA_SYNC_STATUS: bool = false;
94const DEFAULT_REPORT_TAGGING_SYNC_STATUS: bool = false;
95const DEFAULT_REPORT_ANNOTATIONS_SYNC_STATUS: bool = false;
96#[allow(dead_code)]
97const DEFAULT_ALLOW_LUA_OS_LIBRARY: bool = false;
98#[allow(dead_code)]
99const DEFAULT_ALLOW_LUA_UNSAFE_VM: bool = false;
100#[allow(dead_code)]
101const DEFAULT_LUA_VM_MEMORY_LIMIT: &str = "64MiB";
102#[allow(dead_code)]
103const DEFAULT_LUA_CALLBACK_TIMEOUT_MILLISECONDS: &str = "10000";
104const DEFAULT_SHOW_NO_PROGRESS: bool = false;
105const DEFAULT_IF_MATCH: bool = false;
106const DEFAULT_IF_NONE_MATCH: bool = false;
107const DEFAULT_COPY_SOURCE_IF_MATCH: bool = false;
108const DEFAULT_IGNORE_GLACIER_WARNINGS: bool = false;
109const DEFAULT_ENABLE_SYNC_OBJECT_ANNOTATION: bool = false;
110const DEFAULT_DISABLE_CHECK_ANNOTATION_ETAG: bool = false;
111const DEFAULT_SYNC_LATEST_OBJECT_ANNOTATION: bool = false;
112
113const NO_S3_STORAGE_SPECIFIED: &str = "either SOURCE or TARGET must be s3://\n";
114const LOCAL_STORAGE_SPECIFIED: &str = "with --enable-versioning/--sync-latest-tagging/--copy-source-if-match, both storage must be s3://\n";
115const LOCAL_STORAGE_SPECIFIED_FOR_OBJECT_ANNOTATION: &str = "with --enable-sync-object-annotations/--sync-latest-object-annotations/--report-annotations-sync-status, both storage must be s3://\n";
116const VERSIONING_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE: &str =
117    "--enable-versioning is not supported with express onezone storage class\n";
118const EXPRESS_ONEZONE_SPECIFIED_FOR_OBJECT_ANNOTATION: &str = "--enable-sync-object-annotations/--sync-latest-object-annotations/--report-annotations-sync-status are not supported with express onezone storage class\n";
119const LOCAL_STORAGE_SPECIFIED_WITH_STORAGE_CLASS: &str =
120    "with --storage-class, target storage must be s3://\n";
121const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_SSE: &str =
122    "with --sse/--sse-kms-key-id, target storage must be s3://\n";
123const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ACL: &str = "with --acl, target storage must be s3://\n";
124const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_ENABLE_ADDITIONAL_CHECKSUM: &str =
125    "with --enable-additional-checksum, source storage must be s3://\n";
126const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ADDITIONAL_CHECKSUM_ALGORITHM: &str =
127    "with --additional-checksum-algorithm, target storage must be s3://\n";
128const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_AUTO_CHUNKSIZE: &str =
129    "with --auto-chunksize, source storage must be s3://\n";
130const SOURCE_REMOTE_STORAGE_SPECIFIED_WITH_IGNORE_SYMLINKS: &str =
131    "with --ignore-symlinks, source storage must be local storage\n";
132const SOURCE_REMOTE_STORAGE_SPECIFIED_WITH_NO_GUESS_MIME_TYPE: &str =
133    "with --no-guess-mime-type, source storage must be local storage\n";
134const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_METADATA_OPTION: &str =
135    "with metadata related option, target storage must be s3://\n";
136const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_ENDPOINT_URL: &str =
137    "with --source-endpoint-url, source storage must be s3://\n";
138const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ENDPOINT_URL: &str =
139    "with --target-endpoint-url, target storage must be s3://\n";
140const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_NO_SIGN_REQUEST: &str =
141    "with --source-no-sign-request, source storage must be s3://\n";
142const CHECK_SIZE_CONFLICT: &str =
143    "--head-each-target is required for --check-size, or remove --remove-modified-filter\n";
144
145const CHECK_ETAG_CONFLICT: &str =
146    "--head-each-target is required for --check-etag, or remove --remove-modified-filter\n";
147const CHECK_ETAG_CONFLICT_SSE_KMS: &str =
148    "--check-etag is not supported with --sse aws:kms | aws:kms:dsse \n";
149const CHECK_ETAG_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE: &str =
150    "--check-etag is not supported with express onezone storage class\n";
151
152const CHECK_MTIME_AND_ETAG_CONFLICT_SSE_KMS: &str =
153    "--check-mtime-and-etag is not supported with --sse aws:kms | aws:kms:dsse \n";
154const CHECK_MTIME_AND_ETAG_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE: &str =
155    "--check-mtime-and-etag is not supported with express onezone storage class\n";
156const SOURCE_LOCAL_STORAGE_FILE_WITH_DELETE_OPTION: &str =
157    "with --delete, source storage must be directory\n";
158const SOURCE_LOCAL_STORAGE_PATH_NOT_FOUND: &str = "source file/directory not found\n";
159const TARGET_LOCAL_STORAGE_INVALID: &str = "invalid target path\n";
160const SSE_KMS_KEY_ID_ARGUMENTS_CONFLICT: &str =
161    "--sse-kms-key-id must be used with --sse aws:kms | aws:kms:dsse\n";
162const LOCAL_STORAGE_SPECIFIED_WITH_SSE_C: &str =
163    "with --source-sse-c/--target-sse-c, remote storage must be s3://\n";
164const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_DISABLE_PAYLOAD_SIGNING: &str =
165    "with --disable-payload-signing, target storage must be s3://\n";
166const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_DISABLE_CONTENT_MD5_HEADER: &str =
167    "with --disable-content-md5-header, target storage must be s3://\n";
168const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_FULL_OBJECT_CHECKSUM: &str =
169    "with --full-object-checksum, target storage must be s3://\n";
170const FULL_OBJECT_CHECKSUM_NOT_SUPPORTED: &str =
171    "Only CRC32/CRC32C/CRC64NVME supports full object checksum\n";
172const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_ACCELERATE: &str =
173    "with --source-accelerate, source storage must be s3://\n";
174const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ACCELERATE: &str =
175    "with --target-accelerate, target storage must be s3://\n";
176const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_REQUEST_PAYER: &str =
177    "with --source-request-payer, source storage must be s3://\n";
178const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_REQUEST_PAYER: &str =
179    "with --target-request-payer, target storage must be s3://\n";
180const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_SERVER_SIDE_COPY: &str =
181    "with --server-side-copy, source storage must be s3://\n";
182const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_SERVER_SIDE_COPY: &str =
183    "with --server-side-copy, target storage must be s3://\n";
184
185const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_INCLUDE_METADATA_REGEX: &str =
186    "with --filter-include-metadata-regex, source storage must be s3://\n";
187const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_EXCLUDE_METADATA_REGEX: &str =
188    "with --filter-exclude-metadata-regex, source storage must be s3://\n";
189
190const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_INCLUDE_TAG_REGEX: &str =
191    "with --filter-include-tag-regex, source storage must be s3://\n";
192const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_EXCLUDE_TAG_REGEX: &str =
193    "with --filter-exclude-tag-regex, source storage must be s3://\n";
194
195const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_SYSTEM_METADATA: &str =
196    "with --no-sync-system-metadata, source storage must be s3://\n";
197const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_SYSTEM_METADATA: &str =
198    "with --no-sync-system-metadata, target storage must be s3://\n";
199const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_USER_DEFINED_METADATA: &str =
200    "with --no-sync-user-defined-metadata, source storage must be s3://\n";
201const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_USER_DEFINED_METADATA: &str =
202    "with --no-sync-user-defined-metadata, target storage must be s3://\n";
203const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_POINT_IN_TIME: &str =
204    "with --point-in-time, source storage must be s3://\n";
205const POINT_IN_TIME_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE: &str =
206    "--point-in-time is not supported with express onezone storage class\n";
207const POINT_IN_TIME_VALUE_INVALID: &str = "--point-in-time value is later than current time\n";
208const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_METADATA_SYNC_STATUS: &str =
209    "with --report-metadata-sync-status, source storage must be s3://\n";
210const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_METADATA_SYNC_STATUS: &str =
211    "with --report-metadata-sync-status, target storage must be s3://\n";
212const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_TAGGING_SYNC_STATUS: &str =
213    "with --report-tagging-sync-status, source storage must be s3://\n";
214const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_TAGGING_SYNC_STATUS: &str =
215    "with --report-tagging-sync-status, target storage must be s3://\n";
216const NO_SOURCE_CREDENTIAL_REQUIRED: &str = "no source credential required\n";
217const NO_TARGET_CREDENTIAL_REQUIRED: &str = "no target credential required\n";
218#[allow(dead_code)]
219const LUA_SCRIPT_LOAD_ERROR: &str = "Failed to load and compile Lua script callback: ";
220const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_IF_MATCH: &str =
221    "with --if-match, target storage must be s3://\n";
222const IF_MATCH_CONFLICT: &str =
223    "--head-each-target is required for --if-match, or remove --remove-modified-filter\n";
224const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_IF_NONE_MATCH: &str =
225    "with --if-none-match, target storage must be s3://\n";
226
227#[cfg(feature = "version")]
228shadow!(build);
229
230#[derive(Parser, Clone, Debug)]
231#[cfg_attr(feature = "version", command(version=format!("{} ({} {}), {}", build::PKG_VERSION, build::SHORT_COMMIT, build::BUILD_TARGET, build::RUST_VERSION)))]
232pub struct CLIArgs {
233    #[arg(help = "s3://<BUCKET_NAME>[/prefix] or local path", value_parser = storage_path::check_storage_path, default_value_if("auto_complete_shell", ArgPredicate::IsPresent, "s3://ignored"), required = false)]
234    source: String,
235
236    #[arg(help = "s3://<BUCKET_NAME>[/prefix] or local path", value_parser = storage_path::check_storage_path, default_value_if("auto_complete_shell", ArgPredicate::IsPresent, "s3://ignored"), required = false)]
237    target: String,
238
239    /// A simulation mode. No actions will be performed.
240    #[arg(long, env, default_value_t = DEFAULT_DRY_RUN, help_heading = "General")]
241    dry_run: bool,
242
243    /// Don't show the progress bar.
244    #[arg(long, env, default_value_t = DEFAULT_SHOW_NO_PROGRESS, help_heading = "General")]
245    show_no_progress: bool,
246
247    #[arg(long, env, default_value_t = DEFAULT_SERVER_SIDE_COPY, help_heading = "General",
248    long_help = r#"Use server-side copy. This option is only available both source and target are S3 storage.
249It cannot work with between different object storages or regions."#)]
250    server_side_copy: bool,
251
252    /// Location of the file that the AWS CLI uses to store configuration profiles
253    #[arg(long, env, value_name = "FILE", help_heading = "AWS Configuration")]
254    aws_config_file: Option<PathBuf>,
255
256    /// Location of the file that the AWS CLI uses to store access keys
257    #[arg(long, env, value_name = "FILE", help_heading = "AWS Configuration")]
258    aws_shared_credentials_file: Option<PathBuf>,
259
260    /// Source AWS CLI profile
261    #[arg(long, env, conflicts_with_all = ["source_access_key", "source_secret_access_key", "source_session_token"], help_heading = "AWS Configuration")]
262    source_profile: Option<String>,
263
264    /// Source access key
265    #[arg(long, env, hide_env_values = true, conflicts_with_all = ["source_profile"], requires = "source_secret_access_key", help_heading = "AWS Configuration")]
266    source_access_key: Option<String>,
267
268    /// Source secret access key
269    #[arg(long, env, hide_env_values = true, conflicts_with_all = ["source_profile"], requires = "source_access_key", help_heading = "AWS Configuration")]
270    source_secret_access_key: Option<String>,
271
272    /// Source session token
273    #[arg(long, env, hide_env_values = true, conflicts_with_all = ["source_profile"], requires = "source_access_key", help_heading = "AWS Configuration")]
274    source_session_token: Option<String>,
275
276    /// Source region
277    #[arg(long, env, value_parser = NonEmptyStringValueParser::new(), help_heading = "Source Options")]
278    source_region: Option<String>,
279
280    /// Source endpoint url
281    #[arg(long, env, value_parser = url::check_scheme, help_heading = "Source Options")]
282    source_endpoint_url: Option<String>,
283
284    /// Use Amazon S3 Transfer Acceleration for the source bucket.
285    #[arg(long, env, default_value_t = DEFAULT_ACCELERATE, help_heading = "Source Options")]
286    source_accelerate: bool,
287
288    /// Use request payer for the source bucket.
289    #[arg(long, env, default_value_t = DEFAULT_REQUEST_PAYER, help_heading = "Source Options")]
290    source_request_payer: bool,
291
292    /// Do not sign requests for the source bucket (anonymous access for public buckets)
293    #[arg(
294        long, env, default_value_t = false,
295        conflicts_with_all = [
296            "source_profile",
297            "source_access_key",
298            "source_secret_access_key",
299            "source_session_token",
300            "source_request_payer",
301        ],
302        help_heading = "AWS Configuration",
303    )]
304    source_no_sign_request: bool,
305
306    /// Force path-style addressing for source endpoint.
307    #[arg(long, env, default_value_t = DEFAULT_FORCE_PATH_STYLE, help_heading = "Source Options")]
308    source_force_path_style: bool,
309
310    /// Target AWS CLI profile
311    #[arg(long, env, conflicts_with_all = ["target_access_key", "target_secret_access_key", "target_session_token"], help_heading = "AWS Configuration")]
312    target_profile: Option<String>,
313
314    /// Target access key
315    #[arg(long, env, hide_env_values = true, conflicts_with_all = ["target_profile"], requires = "target_secret_access_key", help_heading = "AWS Configuration")]
316    target_access_key: Option<String>,
317
318    /// Target secret access key
319    #[arg(long, env, hide_env_values = true, conflicts_with_all = ["target_profile"], requires = "target_access_key", help_heading = "AWS Configuration")]
320    target_secret_access_key: Option<String>,
321
322    /// Target session token
323    #[arg(long, env, hide_env_values = true, conflicts_with_all = ["target_profile"], requires = "target_access_key", help_heading = "AWS Configuration")]
324    target_session_token: Option<String>,
325
326    /// Target region
327    #[arg(long, env, value_parser = NonEmptyStringValueParser::new(), help_heading = "Target Options")]
328    target_region: Option<String>,
329
330    /// Target endpoint url
331    #[arg(long, env, value_parser = url::check_scheme, help_heading = "Target Options")]
332    target_endpoint_url: Option<String>,
333
334    /// Use Amazon S3 Transfer Acceleration for the target bucket.
335    #[arg(long, env, default_value_t = DEFAULT_ACCELERATE, help_heading = "Target Options")]
336    target_accelerate: bool,
337
338    /// Use request payer for the target bucket.
339    #[arg(long, env, default_value_t = DEFAULT_REQUEST_PAYER,help_heading = "Target Options")]
340    target_request_payer: bool,
341
342    /// Force path-style addressing for target endpoint.
343    #[arg(long, env, default_value_t = DEFAULT_FORCE_PATH_STYLE, help_heading = "Target Options")]
344    target_force_path_style: bool,
345
346    #[arg(long, env, value_parser = storage_class::parse_storage_class, help_heading = "Target Options",
347    long_help = r#"Type of storage to use for the target object.
348Valid choices: STANDARD | REDUCED_REDUNDANCY | STANDARD_IA | ONEZONE_IA | INTELLIGENT_TIERING | GLACIER |
349               DEEP_ARCHIVE | GLACIER_IR | EXPRESS_ONEZONE"#)]
350    storage_class: Option<String>,
351
352    #[arg(
353        long,
354        env,
355        help_heading = "Filtering",
356        long_help = r#"Sync only objects older than given time (RFC3339 datetime).
357Example: 2023-02-19T12:00:00Z"#
358    )]
359    filter_mtime_before: Option<DateTime<Utc>>,
360
361    #[arg(
362        long,
363        env,
364        help_heading = "Filtering",
365        long_help = r#"Sync only objects newer than OR EQUAL TO given time (RFC3339 datetime).
366Example: 2023-02-19T12:00:00Z"#
367    )]
368    filter_mtime_after: Option<DateTime<Utc>>,
369
370    #[arg(long, env, value_parser = human_bytes::check_human_bytes_without_limit, help_heading = "Filtering", long_help=r#"Sync only objects smaller than given size.
371Allow suffixes: KB, KiB, MB, MiB, GB, GiB, TB, TiB"#)]
372    filter_smaller_size: Option<String>,
373
374    #[arg(long, env, value_parser = human_bytes::check_human_bytes_without_limit, help_heading = "Filtering", long_help=r#"Sync only objects larger than OR EQUAL TO given size.
375Allow suffixes: KB, KiB, MB, MiB, GB, GiB, TB, TiB"#)]
376    filter_larger_size: Option<String>,
377
378    /// Sync only objects that match a given regular expression.
379    #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering")]
380    filter_include_regex: Option<String>,
381
382    /// Do not sync objects that match a given regular expression.
383    #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering")]
384    filter_exclude_regex: Option<String>,
385
386    #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering",
387    long_help = r#"Sync only objects that have Content-Type matching a given regular expression.
388If the source is local storage, Content-Type is guessed by the file extension,
389Unless --no-guess-mime-type is specified.
390It may take an extra API call to get Content-Type of the object.
391"#)]
392    filter_include_content_type_regex: Option<String>,
393
394    #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering",
395    long_help=r#"Do not sync objects that have Content-Type matching a given regular expression.
396If the source is local storage, Content-Type is guessed by the file extension,
397Unless --no-guess-mime-type is specified.
398It may take an extra API call to get Content-Type of the object.
399"#)]
400    filter_exclude_content_type_regex: Option<String>,
401
402    #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering",
403    long_help=r#"Sync only objects that have metadata matching a given regular expression.
404Keys(lowercase) must be sorted in alphabetical order, and comma separated.
405This filter is applied after all other filters(except tag filters).
406It may take an extra API call to get metadata of the object.
407
408Example: "key1=(value1|value2),key2=value2""#)]
409    filter_include_metadata_regex: Option<String>,
410
411    #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering",
412    long_help=r#"Do not sync objects that have metadata matching a given regular expression.
413Keys(lowercase) must be sorted in alphabetical order, and comma separated.
414This filter is applied after all other filters(except tag filters).
415It may take an extra API call to get metadata of the object.
416
417Example: "key1=(value1|value2),key2=value2""#)]
418    filter_exclude_metadata_regex: Option<String>,
419
420    #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering",
421    long_help=r#"Sync only objects that have tag matching a given regular expression.
422Keys must be sorted in alphabetical order, and '&' separated.
423This filter is applied after all other filters.
424It takes an extra API call to get tags of the object.
425
426Example: "key1=(value1|value2)&key2=value2""#)]
427    filter_include_tag_regex: Option<String>,
428
429    #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering",
430    long_help=r#"Do not sync objects that have tag matching a given regular expression.
431Keys must be sorted in alphabetical order, and '&' separated.
432This filter is applied after all other filters.
433It takes an extra API call to get tags of the object.
434
435Example: "key1=(value1|value2)&key2=value2""#)]
436    filter_exclude_tag_regex: Option<String>,
437
438    /// Do not update checking(ListObjectsV2) for modification in the target storage.
439    #[arg(long, env, conflicts_with_all = ["enable_versioning"], default_value_t = DEFAULT_REMOVE_MODIFIED_FILTER, help_heading = "Update Checking")]
440    remove_modified_filter: bool,
441
442    /// Use object size for update checking.
443    #[arg(long, env, conflicts_with_all = ["enable_versioning", "check_etag", "check_mtime_and_etag", "check_mtime_and_additional_checksum"], default_value_t = DEFAULT_CHECK_SIZE, help_heading = "Update Checking")]
444    check_size: bool,
445
446    /// Use ETag for update checking.
447    #[arg(long, env, conflicts_with_all = ["enable_versioning", "check_size", "check_mtime_and_etag", "check_mtime_and_additional_checksum", "source_sse_c_key", "target_sse_c_key"], default_value_t = DEFAULT_CHECK_ETAG, help_heading = "Update Checking",)]
448    check_etag: bool,
449
450    #[arg(long, env, conflicts_with_all = ["enable_versioning", "remove_modified_filter", "check_size", "check_etag", "source_sse_c_key", "target_sse_c_key"], default_value_t = DEFAULT_CHECK_MTIME_AND_ETAG, help_heading = "Update Checking",
451    long_help=r#"Use the modification time and ETag for update checking.
452If the source modification date is newer, check the ETag.
453"#)]
454    check_mtime_and_etag: bool,
455
456    /// Use additional checksum for update checking.
457    #[arg(long, env, conflicts_with_all = ["enable_versioning", "check_size", "check_etag", "check_mtime_and_etag", "check_mtime_and_additional_checksum"], value_parser = checksum_algorithm::parse_checksum_algorithm, help_heading = "Update Checking")]
458    check_additional_checksum: Option<String>,
459
460    #[arg(long, env, conflicts_with_all = ["enable_versioning", "remove_modified_filter", "check_size", "check_etag", "check_mtime_and_etag", "check_additional_checksum"], value_parser = checksum_algorithm::parse_checksum_algorithm, help_heading = "Update Checking",
461    long_help=r#"Use the modification time and additional checksum for update checking.
462If the source modification date is newer, check the additional checksum.
463"#)]
464    check_mtime_and_additional_checksum: Option<String>,
465
466    /// Additional checksum algorithm for upload
467    #[arg(long, env, value_parser = checksum_algorithm::parse_checksum_algorithm, help_heading = "Verification")]
468    additional_checksum_algorithm: Option<String>,
469
470    #[arg(long, env, default_value_t = DEFAULT_FULL_OBJECT_CHECKSUM, help_heading = "Verification", long_help=r#"Use full object checksum for verification. CRC64NVME automatically use full object checksum.
471This option cannot be used with SHA1/SHA256 additional checksum."#)]
472    full_object_checksum: bool,
473
474    /// Enable additional checksum for download
475    #[arg(long, env, default_value_t = DEFAULT_ENABLE_ADDITIONAL_CHECKSUM, help_heading = "Verification")]
476    enable_additional_checksum: bool,
477
478    /// Disable multipart upload verification with ETag/additional checksum.
479    #[arg(long, env, default_value_t = DEFAULT_DISABLE_MULTIPART_VERIFY, help_heading = "Verification")]
480    disable_multipart_verify: bool,
481
482    /// Disable ETag verification.
483    #[arg(long, env, default_value_t = DEFAULT_DISABLE_ETAG_VERIFY, help_heading = "Verification")]
484    disable_etag_verify: bool,
485
486    #[arg(long, env, requires = "additional_checksum_algorithm", default_value_t = DEFAULT_DISABLE_ADDITIONAL_CHECKSUM_VERIFY, help_heading = "Verification",
487    long_help=r#"Disable additional checksum verification 
488But use additional checksum for upload (The hash value is stored in the target object)."#)]
489    disable_additional_checksum_verify: bool,
490
491    /// Number of workers for synchronization
492    #[arg(long, env, default_value_t = DEFAULT_WORKER_SIZE, value_parser = clap::value_parser!(u16).range(1..), help_heading = "Performance")]
493    worker_size: u16,
494
495    /// Maximum number of parallel multipart uploads/downloads
496    #[arg(long, env, default_value_t = DEFAULT_MAX_PARALLEL_MULTIPART_UPLOADS, value_parser = clap::value_parser!(u16).range(1..), help_heading = "Performance")]
497    max_parallel_uploads: u16,
498
499    #[arg(long, env, default_value_t = DEFAULT_MAX_PARALLEL_LISTINGS, value_parser = clap::value_parser!(u16).range(1..), help_heading = "Performance", long_help=r#"Maximum number of parallel listings of objects."#)]
500    max_parallel_listings: u16,
501
502    #[arg(long, env, default_value_t = DEFAULT_PARALLEL_LISTING_MAX_DEPTH, value_parser = clap::value_parser!(u16).range(1..), help_heading = "Performance", long_help=r#"Maximum depth(sub directroy/prefix) of parallel listings."#)]
503    max_parallel_listing_max_depth: u16,
504
505    /// Queue size for object listings
506    #[arg(long, env, default_value_t = DEFAULT_OBJECT_LISTING_QUEUE_SIZE, value_parser = clap::value_parser!(u32).range(1..), help_heading = "Performance")]
507    object_listing_queue_size: u32,
508
509    #[arg(long, env, default_value_t = DEFAULT_ALLOW_PARALLEL_LISTINGS_IN_EXPRESS_ONE_ZONE, help_heading = "Performance", long_help=r#"Allow parallel listings in express one zone storage class.
510It may include multipart upload in progress objects in the listing result."#)]
511    allow_parallel_listings_in_express_one_zone: bool,
512
513    /// Rate limit objects per second
514    #[arg(long, env,  value_parser = clap::value_parser!(u32).range(10..), help_heading = "Performance")]
515    rate_limit_objects: Option<u32>,
516
517    /// Rate limit bandwidth(bytes per sec). Allow suffixes: MB, MiB, GB, GiB
518    #[arg(long, env, value_parser = human_bytes::check_human_bandwidth, help_heading = "Performance")]
519    rate_limit_bandwidth: Option<String>,
520
521    #[arg(long, env, conflicts_with_all = ["auto_chunksize"], default_value = DEFAULT_MULTIPART_THRESHOLD, value_parser = human_bytes::check_human_bytes, help_heading = "Multipart Settings",
522    long_help=r#"Object size threshold that s3sync uses for multipart upload
523Allow suffixes: MB, MiB, GB, GiB.
524The larger the size, the larger the memory usage."#)]
525    multipart_threshold: String,
526
527    #[arg(long, env, conflicts_with_all = ["auto_chunksize"], default_value = DEFAULT_MULTIPART_CHUNKSIZE, value_parser = human_bytes::check_human_bytes, help_heading = "Multipart Settings",
528    long_help=r#"Chunk size that s3sync uses for multipart upload of individual files
529Allow suffixes: MB, MiB, GB, GiB.
530The larger the size, the larger the memory usage."#)]
531    multipart_chunksize: String,
532
533    #[arg(long, env, conflicts_with_all = ["multipart_threshold", "multipart_chunksize"], default_value_t = DEFAULT_AUTO_CHUNKSIZE, help_heading = "Multipart Settings",
534    long_help=r#"Automatically adjusts a chunk size to match the source or target.
535It takes extra HEAD requests(1 API call per part)."#)]
536    auto_chunksize: bool,
537
538    /// Cache-Control HTTP header to set on the target object
539    #[arg(long, env, help_heading = "Metadata/Headers")]
540    cache_control: Option<String>,
541
542    /// Content-Disposition HTTP header to set on the target object
543    #[arg(long, env, help_heading = "Metadata/Headers")]
544    content_disposition: Option<String>,
545
546    /// Content-Encoding HTTP header to set on the target object
547    #[arg(long, env, help_heading = "Metadata/Headers")]
548    content_encoding: Option<String>,
549
550    /// Content-Language HTTP header to set on the target object
551    #[arg(long, env, help_heading = "Metadata/Headers")]
552    content_language: Option<String>,
553
554    /// Content-Type HTTP header to set on the target object
555    #[arg(long, env, help_heading = "Metadata/Headers")]
556    content_type: Option<String>,
557
558    #[arg(
559        long,
560        env,
561        help_heading = "Metadata/Headers",
562        long_help = r#"Expires HTTP header to set on the target object(RFC3339 datetime)
563Example: 2023-02-19T12:00:00Z"#
564    )]
565    expires: Option<DateTime<Utc>>,
566
567    #[arg(long, env, value_parser = metadata::check_metadata, help_heading = "Metadata/Headers", long_help=r#"Metadata to set on the target object
568Example: key1=value1,key2=value2"#)]
569    metadata: Option<String>,
570
571    /// x-amz-website-redirect-location header to set on the target object
572    #[arg(long, env, help_heading = "Metadata/Headers")]
573    website_redirect: Option<String>,
574
575    #[arg(long, env, default_value_t =  DEFAULT_NO_SYNC_SYSTEM_METADATA, help_heading = "Metadata/Headers",
576    long_help= r#"Do not sync system metadata
577System metadata: content-disposition, content-encoding, content-language, content-type,
578                 cache-control, expires, website-redirect"#)]
579    no_sync_system_metadata: bool,
580
581    /// Do not sync user-defined metadata.
582    #[arg(long, env, default_value_t =  DEFAULT_NO_SYNC_USER_DEFINED_METADATA, help_heading = "Metadata/Headers")]
583    no_sync_user_defined_metadata: bool,
584
585    #[arg(long, env, conflicts_with_all = ["disable_tagging", "sync_latest_tagging"], value_parser = tagging::parse_tagging, help_heading = "Tagging",
586    long_help=r#"Tagging to set on the target object.
587Key/value must be encoded as UTF-8 then URLEncoded URL query parameters without tag name duplicates.
588
589Example: key1=value1&key2=value2"#)]
590    tagging: Option<String>,
591
592    /// Do not copy tagging.
593    #[arg(long, env, default_value_t = DEFAULT_DISABLE_TAGGING, help_heading = "Tagging")]
594    disable_tagging: bool,
595
596    #[arg(long, env, conflicts_with_all = ["enable_versioning", "disable_tagging"], default_value_t = DEFAULT_SYNC_LATEST_TAGGING, help_heading = "Tagging",
597    long_help=r#"Copy the latest tagging from the source if necessary.
598If this option is enabled, the --remove-modified-filter and
599--head-each-target options are automatically enabled."#)]
600    sync_latest_tagging: bool,
601
602    #[arg(long, env, default_value_t = DEFAULT_ENABLE_SYNC_OBJECT_ANNOTATION, help_heading = "Object Annotation",
603    long_help=r#"Copy object annotations from the source if necessary.
604If this option is enabled, extra API calls are required."#)]
605    enable_sync_object_annotations: bool,
606
607    #[arg(long, env, default_value_t = DEFAULT_DISABLE_CHECK_ANNOTATION_ETAG, help_heading = "Object Annotation",
608    long_help=r#"Don't use ETag for update annotation checking"#)]
609    disable_check_annotation_etag: bool,
610
611    #[arg(long, env, default_value_t = DEFAULT_SYNC_LATEST_OBJECT_ANNOTATION, conflicts_with_all = ["enable_versioning", "enable_sync_object_annotations"], help_heading = "Object Annotation",
612    long_help=r#"Copy the latest object annotation from the source if necessary.
613If this option is enabled, the --remove-modified-filter and
614--head-each-target options are automatically enabled. And extra API calls are required."#)]
615    sync_latest_object_annotations: bool,
616
617    #[arg(long, env, conflicts_with_all = ["delete", "head_each_target", "remove_modified_filter"], default_value_t = DEFAULT_ENABLE_VERSIONING, help_heading = "Versioning",
618    long_help=r#"Sync all version objects in the source storage to the target versioning storage.
619 "#)]
620    enable_versioning: bool,
621
622    #[arg(
623        long,
624        env,
625        conflicts_with_all = ["delete", "head_each_target", "enable_versioning", "check_etag", "check_mtime_and_etag", "check_size", "check_mtime_and_additional_checksum"],
626        help_heading = "Versioning",
627        long_help = r#"Sync only objects at a specific point in time (RFC3339 datetime).
628The source storage must be a versioning enabled S3 bucket.
629By default, the target storage's objects will always be overwritten with the source's objects.
630
631Example: 2025-07-16T12:00:00Z"#
632    )]
633    point_in_time: Option<DateTime<Utc>>,
634
635    /// Server-side encryption. Valid choices: AES256 | aws:kms | aws:kms:dsse
636    #[arg(long, env, value_parser = sse::parse_sse, help_heading = "Encryption")]
637    sse: Option<String>,
638
639    /// SSE KMS ID key
640    #[arg(long, env, help_heading = "Encryption")]
641    sse_kms_key_id: Option<String>,
642
643    /// Source SSE-C algorithm. Valid choices: AES256
644    #[arg(long, env, conflicts_with_all = ["sse", "sse_kms_key_id"], requires = "source_sse_c_key", value_parser = sse::parse_sse_c, help_heading = "Encryption")]
645    source_sse_c: Option<String>,
646
647    /// Source SSE-C customer-provided encryption key(256bit key. must be base64 encoded)
648    #[arg(
649        long,
650        env,
651        hide_env_values = true,
652        requires = "source_sse_c_key_md5",
653        help_heading = "Encryption"
654    )]
655    source_sse_c_key: Option<String>,
656
657    /// Source base64 encoded MD5 digest of source_sse_c_key
658    #[arg(
659        long,
660        env,
661        hide_env_values = true,
662        requires = "source_sse_c",
663        help_heading = "Encryption"
664    )]
665    source_sse_c_key_md5: Option<String>,
666
667    /// Target SSE-C algorithm. Valid choices: AES256
668    #[arg(long, env, conflicts_with_all = ["sse", "sse_kms_key_id"], requires = "target_sse_c_key", value_parser = sse::parse_sse_c, help_heading = "Encryption")]
669    target_sse_c: Option<String>,
670
671    /// Target SSE-C customer-provided encryption key(256bit key. must be base64 encoded)
672    #[arg(
673        long,
674        env,
675        hide_env_values = true,
676        requires = "target_sse_c_key_md5",
677        help_heading = "Encryption"
678    )]
679    target_sse_c_key: Option<String>,
680
681    /// Target base64 encoded MD5 digest of target-sse-c-key
682    #[arg(
683        long,
684        env,
685        hide_env_values = true,
686        requires = "target_sse_c",
687        help_heading = "Encryption"
688    )]
689    target_sse_c_key_md5: Option<String>,
690
691    /// Trace verbosity(-v: show info, -vv: show debug, -vvv show trace)
692    #[clap(flatten)]
693    verbosity: Verbosity<WarnLevel>,
694
695    #[arg(
696        long,
697        env,
698        default_value_t = DEFAULT_REPORT_SYNC_STATUS,
699        conflicts_with_all = ["dry_run", "delete", "enable_versioning", "head_each_target", "point_in_time"],
700        help_heading = "Reporting",
701        long_help = r#"Report sync status to the target storage.
702Default verification is for ETag. For additional checksum, use --check-additional-checksum.
703For more precise control, use with --auto-chunksize."#
704    )]
705    report_sync_status: bool,
706
707    #[arg(
708        long,
709        env,
710        default_value_t = DEFAULT_REPORT_METADATA_SYNC_STATUS,
711        requires = "report_sync_status",
712        help_heading = "Reporting",
713        long_help = r#"Report metadata sync status to the target storage.
714It must be used with --report-sync-status.
715Note: s3sync generated user-defined metadata(s3sync_origin_version_id/s3sync_origin_last_modified) were ignored.
716      Because they are usually different from the source storage."#
717    )]
718    report_metadata_sync_status: bool,
719
720    #[arg(
721        long,
722        env,
723        default_value_t = DEFAULT_REPORT_TAGGING_SYNC_STATUS,
724        requires = "report_sync_status",
725        help_heading = "Reporting",
726        long_help = r#"Report tagging sync status to the target storage.
727It must be used with --report-sync-status."#
728    )]
729    report_tagging_sync_status: bool,
730
731    #[arg(
732        long,
733        env,
734        default_value_t = DEFAULT_REPORT_ANNOTATIONS_SYNC_STATUS,
735        requires = "report_sync_status",
736        help_heading = "Reporting",
737        long_help = r#"Report annotations sync status to the target storage.
738It must be used with --report-sync-status."#
739    )]
740    report_annotations_sync_status: bool,
741
742    /// Show trace as json format.
743    #[arg(long, env, default_value_t = DEFAULT_JSON_TRACING, help_heading = "Tracing/Logging")]
744    json_tracing: bool,
745
746    /// Enable aws sdk tracing.
747    #[arg(long, env, default_value_t = DEFAULT_AWS_SDK_TRACING, help_heading = "Tracing/Logging")]
748    aws_sdk_tracing: bool,
749
750    /// Show span event tracing.
751    #[arg(long, env, default_value_t = DEFAULT_SPAN_EVENTS_TRACING, help_heading = "Tracing/Logging")]
752    span_events_tracing: bool,
753
754    /// Disable ANSI terminal colors.
755    #[arg(long, env, default_value_t = DEFAULT_DISABLE_COLOR_TRACING, help_heading = "Tracing/Logging")]
756    disable_color_tracing: bool,
757
758    #[arg(
759        long,
760        env,
761        default_value_t = DEFAULT_TRACING_STDERR,
762        help_heading = "Tracing/Logging",
763        long_help = r#"Output all tracing to stderr. By default, tracing messages are written to stdout."#
764    )]
765    tracing_stderr: bool,
766
767    /// Maximum retry attempts that s3sync retry handler use
768    #[arg(long, env, default_value_t = DEFAULT_AWS_MAX_ATTEMPTS, value_name = "max_attempts", help_heading = "Retry Options")]
769    aws_max_attempts: u32,
770
771    #[arg(long, env, default_value_t = DEFAULT_INITIAL_BACKOFF_MILLISECONDS, value_name = "initial_backoff", help_heading = "Retry Options",
772    long_help=r#"A multiplier value used when calculating backoff times as part of an exponential backoff with jitter strategy.
773"#)]
774    initial_backoff_milliseconds: u64,
775
776    /// Maximum force retry attempts that s3sync retry handler use
777    #[arg(long, env, default_value_t = DEFAULT_FORCE_RETRY_COUNT, help_heading = "Retry Options")]
778    force_retry_count: u32,
779
780    #[arg(long, env, default_value_t = DEFAULT_FORCE_RETRY_INTERVAL_MILLISECONDS, value_name = "force_retry_interval", help_heading = "Retry Options",
781    long_help=r#"Sleep interval (milliseconds) between s3sync force retries on error.
782"#)]
783    force_retry_interval_milliseconds: u64,
784
785    #[arg(
786        long,
787        env,
788        value_name = "operation_timeout",
789        help_heading = "Timeout Options",
790        long_help = r#"Operation timeout (milliseconds). For details, see the AWS SDK for Rust TimeoutConfig documentation.
791The default has no timeout."#
792    )]
793    operation_timeout_milliseconds: Option<u64>,
794
795    #[arg(
796        long,
797        env,
798        value_name = "operation_attempt_timeout",
799        help_heading = "Timeout Options",
800        long_help = r#"Operation attempt timeout (milliseconds). For details, see the AWS SDK for Rust TimeoutConfig documentation.
801The default has no timeout."#
802    )]
803    operation_attempt_timeout_milliseconds: Option<u64>,
804
805    #[arg(
806        long,
807        env,
808        value_name = "connect_timeout",
809        help_heading = "Timeout Options",
810        long_help = r#"Connect timeout (milliseconds).The default has AWS SDK default timeout (Currently 3100 milliseconds).
811"#
812    )]
813    connect_timeout_milliseconds: Option<u64>,
814
815    #[arg(
816        long,
817        env,
818        value_name = "read_timeout",
819        help_heading = "Timeout Options",
820        long_help = r#"Read timeout (milliseconds). The default has no timeout."#
821    )]
822    read_timeout_milliseconds: Option<u64>,
823
824    /// Treat warnings as errors(except for the case of ETag/checksum mismatch, etc.).
825    #[arg(long, env, default_value_t = DEFAULT_WARN_AS_ERROR, help_heading = "Advanced")]
826    warn_as_error: bool,
827
828    /// Ignore symbolic links.
829    #[arg(long, env, default_value_t = DEFAULT_IGNORE_SYMLINKS, help_heading = "Advanced")]
830    ignore_symlinks: bool,
831
832    #[arg(long, env, conflicts_with_all = ["enable_versioning"], default_value_t = DEFAULT_HEAD_EACH_TARGET, help_heading = "Advanced",
833    long_help=r#"HeadObject is used to check whether an object has been modified in the target storage.
834It reduces the possibility of race condition issue"#)]
835    head_each_target: bool,
836
837    #[arg(long, env, value_parser = canned_acl::parse_canned_acl, help_heading = "Advanced",
838    long_help=r#"ACL for the objects
839Valid choices: private | public-read | public-read-write | authenticated-read | aws-exec-read |
840               bucket-owner-read | bucket-owner-full-control"#)]
841    acl: Option<String>,
842
843    /// Do not try to guess the mime type of local file.
844    #[arg(long, env, default_value_t = DEFAULT_NO_GUESS_MIME_TYPE, help_heading = "Advanced")]
845    no_guess_mime_type: bool,
846
847    /// Maximum number of objects returned in a single list object request
848    #[arg(long, env, default_value_t = DEFAULT_MAX_KEYS, value_parser = clap::value_parser!(i32).range(1..=32767), help_heading = "Advanced")]
849    max_keys: i32,
850
851    /// Put last modified of the source to metadata.
852    #[arg(long, env, default_value_t = DEFAULT_PUT_LAST_MODIFIED_METADATA, help_heading = "Advanced")]
853    put_last_modified_metadata: bool,
854
855    #[arg(long, env, value_name = "SHELL", value_parser = clap_complete::shells::Shell::from_str, help_heading = "Advanced",
856    long_help=r#"Generate a auto completions script.
857Valid choices: bash, fish, zsh, powershell, elvish."#)]
858    auto_complete_shell: Option<clap_complete::shells::Shell>,
859
860    /// Disable stalled stream protection.
861    #[arg(long, env, default_value_t = DEFAULT_DISABLE_STALLED_STREAM_PROTECTION, help_heading = "Advanced")]
862    disable_stalled_stream_protection: bool,
863
864    /// Disable payload signing for object uploads.
865    #[arg(long, env, default_value_t = DEFAULT_DISABLE_PAYLOAD_SIGNING, help_heading = "Advanced")]
866    disable_payload_signing: bool,
867
868    #[arg(long, env, default_value_t = DEFAULT_DISABLE_CONTENT_MD5_HEADER, help_heading = "Advanced",
869    long_help=r#"Disable Content-MD5 header for object uploads. It disables the ETag verification for the uploaded object.
870"#)]
871    disable_content_md5_header: bool,
872
873    #[arg(long, env, default_value_t = DEFAULT_DISABLE_EXPRESS_ONE_ZONE_ADDITIONAL_CHECKSUM, help_heading = "Advanced",
874    long_help=r#"Disable default additional checksum verification in Express One Zone storage class.
875 "#)]
876    disable_express_one_zone_additional_checksum: bool,
877
878    #[arg(long, env, requires = "delete", default_value_t = DEFAULT_DELETE_EXCLUDED, help_heading = "Advanced",
879    long_help=r#"When used in combination with --delete options, supplied --filter-exclude-regex patterns will not prevent an object from being deleted.
880"#)]
881    delete_excluded: bool,
882
883    #[arg(long, env, conflicts_with_all = ["enable_versioning", "point_in_time"], default_value_t = DEFAULT_IF_MATCH, help_heading = "Advanced", long_help=r#"Add an If-Match header for PutObject/CompleteMultipartUpload/DeleteObject requests.
884This is for like an optimistic lock."#)]
885    if_match: bool,
886
887    #[arg(long, env, conflicts_with_all = ["enable_versioning", "point_in_time"], requires = "server_side_copy", default_value_t = DEFAULT_COPY_SOURCE_IF_MATCH, help_heading = "Advanced", long_help=r#"Add an x-amz-copy-source-if-match header for CopyObject/UploadPartCopy requests.
888This is for like an optimistic lock."#)]
889    copy_source_if_match: bool,
890
891    #[arg(long, env, conflicts_with_all = ["enable_versioning", "point_in_time", "if_match"], default_value_t = DEFAULT_IF_NONE_MATCH, help_heading = "Advanced", long_help=r#"Uploads the object only if the object key name does not already exist in the specified bucket.
892This is for like an optimistic lock."#)]
893    if_none_match: bool,
894
895    /// Don't delete more than a specified number of objects
896    #[arg(long, env, requires = "delete", value_parser = clap::value_parser!(u64).range(1..), help_heading = "Advanced")]
897    max_delete: Option<u64>,
898
899    /// Suppress warnings related to Amazon S3 Glacier storage class objects during GetObject requests
900    #[arg(long, env, default_value_t = DEFAULT_IGNORE_GLACIER_WARNINGS, help_heading = "Advanced")]
901    ignore_glacier_warnings: bool,
902
903    #[cfg(feature = "lua_support")]
904    #[arg(
905        long,
906        env,
907        help_heading = "Lua scripting support",
908        value_parser = file_exist::is_file_exist,
909        long_help = r#"Path to the Lua script that is executed as preprocess callback"#
910    )]
911    preprocess_callback_lua_script: Option<String>,
912
913    #[cfg(feature = "lua_support")]
914    #[arg(
915        long,
916        env,
917        help_heading = "Lua scripting support",
918        value_parser = file_exist::is_file_exist,
919        long_help = r#"Path to the Lua script that is executed as event callback"#
920    )]
921    event_callback_lua_script: Option<String>,
922
923    #[cfg(feature = "lua_support")]
924    #[arg(
925        long,
926        env,
927        help_heading = "Lua scripting support",
928        value_parser = file_exist::is_file_exist,
929        long_help = r#"Path to the Lua script that is executed as filter callback"#
930    )]
931    filter_callback_lua_script: Option<String>,
932
933    #[cfg(feature = "lua_support")]
934    #[arg(long, env, conflicts_with_all = ["allow_lua_unsafe_vm"], default_value_t = DEFAULT_ALLOW_LUA_OS_LIBRARY, help_heading = "Lua scripting support", long_help="Allow Lua OS and I/O library functions in the Lua script.")]
935    allow_lua_os_library: bool,
936
937    #[cfg(feature = "lua_support")]
938    #[arg(long, env, default_value = DEFAULT_LUA_VM_MEMORY_LIMIT, value_parser = human_bytes::check_human_bytes_without_limit, help_heading = "Lua scripting support",
939    long_help=r#"Memory limit for the Lua VM. Allow suffixes: KB, KiB, MB, MiB, GB, GiB.
940Zero means no limit.
941If the memory limit is exceeded, the whole process will be terminated."#)]
942    lua_vm_memory_limit: String,
943
944    #[cfg(feature = "lua_support")]
945    #[arg(long, env, default_value = DEFAULT_LUA_CALLBACK_TIMEOUT_MILLISECONDS, value_parser = clap::value_parser!(u64), help_heading = "Lua scripting support",
946    long_help=r#"Timeout in milliseconds for Lua callback execution.
947Zero means no timeout.
948If the timeout is exceeded, the callback will be terminated with an error."#)]
949    lua_callback_timeout_milliseconds: u64,
950
951    #[cfg(feature = "lua_support")]
952    #[arg(long, env, conflicts_with_all = ["allow_lua_os_library"], default_value_t = DEFAULT_ALLOW_LUA_UNSAFE_VM, help_heading = "Dangerous",
953    long_help=r#"Allow unsafe Lua VM functions in the Lua script.
954It allows the Lua script to load unsafe standard libraries or C modules."#)]
955    allow_lua_unsafe_vm: bool,
956
957    #[arg(long, env, conflicts_with_all = ["enable_versioning", "sync_latest_tagging", "sync_latest_object_annotations"], default_value_t = DEFAULT_SYNC_WITH_DELETE, help_heading = "Dangerous",
958    long_help=r#"Delete objects that exist in the target but not in the source.
959Exclude filters other than --filter-exclude-regex will not prevent an object from being deleted.
960[Warning] Since this can cause data loss, test first with the --dry-run option.
961 "#)]
962    delete: bool,
963
964    /// Unit test purpose only
965    #[arg(long, hide = true, default_value_t = false, help_heading = "Dangerous")]
966    allow_both_local_storage: bool,
967
968    /// test purpose only
969    #[cfg(e2e_test_dangerous_simulations)]
970    #[arg(long, hide = true, default_value_t = false, help_heading = "Dangerous")]
971    test_user_defined_callback: bool,
972
973    /// [dangerous] Test purpose only
974    #[cfg(e2e_test_dangerous_simulations)]
975    #[arg(long, hide = true, default_value_t = false, help_heading = "Dangerous")]
976    allow_e2e_test_dangerous_simulation: bool,
977
978    /// [dangerous] Test purpose only
979    #[cfg(e2e_test_dangerous_simulations)]
980    #[arg(long, hide = true, help_heading = "Dangerous")]
981    cancellation_point: Option<String>,
982
983    /// [dangerous] Test purpose only
984    #[cfg(e2e_test_dangerous_simulations)]
985    #[arg(long, hide = true, help_heading = "Dangerous")]
986    panic_simulation_point: Option<String>,
987
988    /// [dangerous] Test purpose only
989    #[cfg(e2e_test_dangerous_simulations)]
990    #[arg(long, hide = true, help_heading = "Dangerous")]
991    error_simulation_point: Option<String>,
992}
993
994pub fn parse_from_args<I, T>(args: I) -> Result<CLIArgs, clap::Error>
995where
996    I: IntoIterator<Item = T>,
997    T: Into<OsString> + Clone,
998{
999    CLIArgs::try_parse_from(args)
1000}
1001
1002pub fn build_config_from_args<I, T>(args: I) -> Result<Config, String>
1003where
1004    I: IntoIterator<Item = T>,
1005    T: Into<OsString> + Clone,
1006{
1007    let config_args = CLIArgs::try_parse_from(args).map_err(|e| e.to_string())?;
1008    crate::Config::try_from(config_args)
1009}
1010
1011impl CLIArgs {
1012    // skipcq: RS-R1000
1013    fn validate_storage_config(&self) -> Result<(), String> {
1014        self.check_source_local_storage()?;
1015        self.check_target_local_storage()?;
1016        self.check_storage_conflict()?;
1017        self.check_versioning_option_conflict()?;
1018        self.check_tagging_option_conflict()?;
1019        self.check_storage_class_conflict()?;
1020        self.check_storage_credentials_conflict()?;
1021        self.check_sse_conflict()?;
1022        self.check_sse_c_conflict()?;
1023        self.check_acl_conflict()?;
1024        self.check_enable_additional_checksum_conflict()?;
1025        self.check_additional_checksum_algorithm_conflict()?;
1026        self.check_auto_chunksize_conflict()?;
1027        self.check_metadata_conflict()?;
1028        self.check_check_size_conflict()?;
1029        self.check_check_e_tag_conflict()?;
1030        self.check_check_mtime_and_e_tag_conflict()?;
1031        self.check_ignore_symlinks_conflict()?;
1032        self.check_no_guess_mime_type_conflict()?;
1033        self.check_endpoint_url_conflict()?;
1034        self.check_no_sign_request_conflict()?;
1035        self.check_disable_payload_signing_conflict()?;
1036        self.check_disable_content_md5_header_conflict()?;
1037        self.check_full_object_checksum_conflict()?;
1038        self.check_accelerate_conflict()?;
1039        self.check_request_payer_conflict()?;
1040        self.check_server_side_copy_conflict()?;
1041        self.check_filter_include_metadata_regex_conflict()?;
1042        self.check_filter_exclude_metadata_regex_conflict()?;
1043        self.check_filter_include_tag_regex_conflict()?;
1044        self.check_filter_exclude_tag_regex_conflict()?;
1045        self.check_server_no_sync_system_metadata_conflict()?;
1046        self.check_server_no_sync_user_defined_metadata_conflict()?;
1047        self.check_point_in_time_conflict()?;
1048        self.check_report_metadata_sync_status_conflict()?;
1049        self.check_report_tagging_sync_status_conflict()?;
1050        self.check_report_annotations_sync_status_conflict()?;
1051        self.check_if_match_conflict()?;
1052        self.check_if_none_match_conflict()?;
1053        self.check_copy_source_if_match_conflict()?;
1054        self.check_enable_sync_object_annotation_conflict()?;
1055        self.check_sync_latest_object_annotation_conflict()?;
1056
1057        Ok(())
1058    }
1059
1060    fn check_source_local_storage(&self) -> Result<(), String> {
1061        let source = storage_path::parse_storage_path(&self.source);
1062
1063        if let StoragePath::Local(path) = source {
1064            if !path.is_dir() && self.delete {
1065                return Err(SOURCE_LOCAL_STORAGE_FILE_WITH_DELETE_OPTION.to_string());
1066            }
1067            if !path.exists() {
1068                return Err(SOURCE_LOCAL_STORAGE_PATH_NOT_FOUND.to_string());
1069            }
1070        }
1071
1072        Ok(())
1073    }
1074
1075    fn check_target_local_storage(&self) -> Result<(), String> {
1076        let target = storage_path::parse_storage_path(&self.target);
1077
1078        if let StoragePath::Local(path) = target {
1079            let exist_result = path.try_exists();
1080            if exist_result.is_err() {
1081                return Err(TARGET_LOCAL_STORAGE_INVALID.to_string());
1082            }
1083        }
1084        Ok(())
1085    }
1086
1087    fn check_storage_conflict(&self) -> Result<(), String> {
1088        if self.allow_both_local_storage {
1089            return Ok(());
1090        }
1091
1092        let source = storage_path::parse_storage_path(&self.source);
1093        let target = storage_path::parse_storage_path(&self.target);
1094
1095        if storage_path::is_both_storage_local(&source, &target) {
1096            return Err(NO_S3_STORAGE_SPECIFIED.to_string());
1097        }
1098
1099        Ok(())
1100    }
1101
1102    fn check_versioning_option_conflict(&self) -> Result<(), String> {
1103        if !self.enable_versioning {
1104            return Ok(());
1105        }
1106
1107        let source = storage_path::parse_storage_path(&self.source);
1108        let target = storage_path::parse_storage_path(&self.target);
1109
1110        if !storage_path::is_both_storage_s3(&source, &target) {
1111            return Err(LOCAL_STORAGE_SPECIFIED.to_string());
1112        }
1113
1114        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.source) {
1115            if is_express_onezone_storage(&bucket) {
1116                return Err(VERSIONING_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE.to_string());
1117            }
1118        }
1119
1120        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.target) {
1121            if is_express_onezone_storage(&bucket) {
1122                return Err(VERSIONING_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE.to_string());
1123            }
1124        }
1125
1126        Ok(())
1127    }
1128
1129    fn check_tagging_option_conflict(&self) -> Result<(), String> {
1130        let source = storage_path::parse_storage_path(&self.source);
1131        let target = storage_path::parse_storage_path(&self.target);
1132
1133        if self.sync_latest_tagging && !storage_path::is_both_storage_s3(&source, &target) {
1134            return Err(LOCAL_STORAGE_SPECIFIED.to_string());
1135        }
1136
1137        Ok(())
1138    }
1139
1140    fn check_storage_class_conflict(&self) -> Result<(), String> {
1141        let target = storage_path::parse_storage_path(&self.target);
1142
1143        if self.storage_class.is_some() && matches!(target, StoragePath::Local(_)) {
1144            return Err(LOCAL_STORAGE_SPECIFIED_WITH_STORAGE_CLASS.to_string());
1145        }
1146
1147        Ok(())
1148    }
1149
1150    fn check_storage_credentials_conflict(&self) -> Result<(), String> {
1151        let source = storage_path::parse_storage_path(&self.source);
1152        let target = storage_path::parse_storage_path(&self.target);
1153
1154        if matches!(source, StoragePath::Local(_))
1155            && (self.source_profile.is_some() || self.source_access_key.is_some())
1156        {
1157            return Err(NO_SOURCE_CREDENTIAL_REQUIRED.to_string());
1158        }
1159
1160        if matches!(target, StoragePath::Local(_))
1161            && (self.target_profile.is_some() || self.target_access_key.is_some())
1162        {
1163            return Err(NO_TARGET_CREDENTIAL_REQUIRED.to_string());
1164        }
1165
1166        Ok(())
1167    }
1168
1169    fn check_sse_conflict(&self) -> Result<(), String> {
1170        if self.sse.is_none() && self.sse_kms_key_id.is_none() {
1171            return Ok(());
1172        }
1173
1174        let target = storage_path::parse_storage_path(&self.target);
1175        if matches!(target, StoragePath::Local(_)) {
1176            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_SSE.to_string());
1177        }
1178
1179        if self.sse_kms_key_id.is_some()
1180            && (self.sse.is_none()
1181                || (ServerSideEncryption::from_str(self.sse.as_ref().unwrap()).unwrap()
1182                    != ServerSideEncryption::AwsKms
1183                    && ServerSideEncryption::from_str(self.sse.as_ref().unwrap()).unwrap()
1184                        != ServerSideEncryption::AwsKmsDsse))
1185        {
1186            return Err(SSE_KMS_KEY_ID_ARGUMENTS_CONFLICT.to_string());
1187        }
1188
1189        Ok(())
1190    }
1191
1192    fn check_sse_c_conflict(&self) -> Result<(), String> {
1193        if self.source_sse_c.is_none() && self.target_sse_c.is_none() {
1194            return Ok(());
1195        }
1196
1197        if self.source_sse_c.is_some() {
1198            let source = storage_path::parse_storage_path(&self.source);
1199            if matches!(source, StoragePath::Local(_)) {
1200                return Err(LOCAL_STORAGE_SPECIFIED_WITH_SSE_C.to_string());
1201            }
1202        }
1203
1204        if self.target_sse_c.is_some() {
1205            let target = storage_path::parse_storage_path(&self.target);
1206            if matches!(target, StoragePath::Local(_)) {
1207                return Err(LOCAL_STORAGE_SPECIFIED_WITH_SSE_C.to_string());
1208            }
1209        }
1210
1211        Ok(())
1212    }
1213
1214    fn check_acl_conflict(&self) -> Result<(), String> {
1215        if self.acl.is_none() {
1216            return Ok(());
1217        }
1218
1219        let target = storage_path::parse_storage_path(&self.target);
1220        if matches!(target, StoragePath::Local(_)) {
1221            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ACL.to_string());
1222        }
1223
1224        Ok(())
1225    }
1226
1227    fn check_additional_checksum_algorithm_conflict(&self) -> Result<(), String> {
1228        if self.additional_checksum_algorithm.is_none() {
1229            return Ok(());
1230        }
1231
1232        let target = storage_path::parse_storage_path(&self.target);
1233        if matches!(target, StoragePath::Local(_)) {
1234            return Err(
1235                TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ADDITIONAL_CHECKSUM_ALGORITHM.to_string(),
1236            );
1237        }
1238
1239        Ok(())
1240    }
1241
1242    fn check_enable_additional_checksum_conflict(&self) -> Result<(), String> {
1243        if !self.enable_additional_checksum {
1244            return Ok(());
1245        }
1246
1247        let source = storage_path::parse_storage_path(&self.source);
1248        if matches!(source, StoragePath::Local(_)) {
1249            return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_ENABLE_ADDITIONAL_CHECKSUM.to_string());
1250        }
1251
1252        Ok(())
1253    }
1254
1255    fn check_auto_chunksize_conflict(&self) -> Result<(), String> {
1256        if !self.auto_chunksize {
1257            return Ok(());
1258        }
1259
1260        let source = storage_path::parse_storage_path(&self.source);
1261
1262        if !self.check_etag && !self.check_mtime_and_etag && matches!(source, StoragePath::Local(_))
1263        {
1264            return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_AUTO_CHUNKSIZE.to_string());
1265        }
1266
1267        Ok(())
1268    }
1269
1270    fn check_metadata_conflict(&self) -> Result<(), String> {
1271        if self.cache_control.is_none()
1272            && self.content_disposition.is_none()
1273            && self.content_encoding.is_none()
1274            && self.content_language.is_none()
1275            && self.content_type.is_none()
1276            && self.website_redirect.is_none()
1277            && self.expires.is_none()
1278            && self.tagging.is_none()
1279            && !self.put_last_modified_metadata
1280        {
1281            return Ok(());
1282        }
1283
1284        let target = storage_path::parse_storage_path(&self.target);
1285        if matches!(target, StoragePath::Local(_)) {
1286            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_METADATA_OPTION.to_string());
1287        }
1288
1289        Ok(())
1290    }
1291
1292    fn check_check_size_conflict(&self) -> Result<(), String> {
1293        if self.check_size && self.remove_modified_filter && !self.head_each_target {
1294            return Err(CHECK_SIZE_CONFLICT.to_string());
1295        }
1296
1297        Ok(())
1298    }
1299
1300    fn check_check_e_tag_conflict(&self) -> Result<(), String> {
1301        if !self.check_etag {
1302            return Ok(());
1303        }
1304        if self.remove_modified_filter && !self.head_each_target {
1305            return Err(CHECK_ETAG_CONFLICT.to_string());
1306        }
1307
1308        if self.sse.is_some() {
1309            let sse = ServerSideEncryption::from_str(self.sse.as_ref().unwrap()).unwrap();
1310            if sse == ServerSideEncryption::AwsKms || sse == ServerSideEncryption::AwsKmsDsse {
1311                return Err(CHECK_ETAG_CONFLICT_SSE_KMS.to_string());
1312            }
1313        }
1314
1315        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.source) {
1316            if is_express_onezone_storage(&bucket) {
1317                return Err(CHECK_ETAG_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE.to_string());
1318            }
1319        }
1320
1321        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.target) {
1322            if is_express_onezone_storage(&bucket) {
1323                return Err(CHECK_ETAG_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE.to_string());
1324            }
1325        }
1326
1327        Ok(())
1328    }
1329
1330    fn check_check_mtime_and_e_tag_conflict(&self) -> Result<(), String> {
1331        if !self.check_mtime_and_etag {
1332            return Ok(());
1333        }
1334
1335        if self.sse.is_some() {
1336            let sse = ServerSideEncryption::from_str(self.sse.as_ref().unwrap()).unwrap();
1337            if sse == ServerSideEncryption::AwsKms || sse == ServerSideEncryption::AwsKmsDsse {
1338                return Err(CHECK_MTIME_AND_ETAG_CONFLICT_SSE_KMS.to_string());
1339            }
1340        }
1341
1342        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.source) {
1343            if is_express_onezone_storage(&bucket) {
1344                return Err(CHECK_MTIME_AND_ETAG_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE.to_string());
1345            }
1346        }
1347
1348        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.target) {
1349            if is_express_onezone_storage(&bucket) {
1350                return Err(CHECK_MTIME_AND_ETAG_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE.to_string());
1351            }
1352        }
1353
1354        Ok(())
1355    }
1356
1357    fn check_ignore_symlinks_conflict(&self) -> Result<(), String> {
1358        if !self.ignore_symlinks {
1359            return Ok(());
1360        }
1361
1362        let source = storage_path::parse_storage_path(&self.source);
1363        if matches!(source, StoragePath::S3 { .. }) {
1364            return Err(SOURCE_REMOTE_STORAGE_SPECIFIED_WITH_IGNORE_SYMLINKS.to_string());
1365        }
1366
1367        Ok(())
1368    }
1369
1370    fn check_no_guess_mime_type_conflict(&self) -> Result<(), String> {
1371        if !self.no_guess_mime_type {
1372            return Ok(());
1373        }
1374
1375        let source = storage_path::parse_storage_path(&self.source);
1376        if matches!(source, StoragePath::S3 { .. }) {
1377            return Err(SOURCE_REMOTE_STORAGE_SPECIFIED_WITH_NO_GUESS_MIME_TYPE.to_string());
1378        }
1379
1380        Ok(())
1381    }
1382
1383    fn check_endpoint_url_conflict(&self) -> Result<(), String> {
1384        let source = storage_path::parse_storage_path(&self.source);
1385        if matches!(source, StoragePath::Local(_)) && self.source_endpoint_url.is_some() {
1386            return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_ENDPOINT_URL.to_string());
1387        }
1388
1389        let target = storage_path::parse_storage_path(&self.target);
1390        if matches!(target, StoragePath::Local(_)) && self.target_endpoint_url.is_some() {
1391            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ENDPOINT_URL.to_string());
1392        }
1393
1394        Ok(())
1395    }
1396
1397    fn check_no_sign_request_conflict(&self) -> Result<(), String> {
1398        if !self.source_no_sign_request {
1399            return Ok(());
1400        }
1401
1402        let source = storage_path::parse_storage_path(&self.source);
1403        if matches!(source, StoragePath::Local(_)) {
1404            return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_NO_SIGN_REQUEST.to_string());
1405        }
1406
1407        Ok(())
1408    }
1409
1410    fn check_disable_payload_signing_conflict(&self) -> Result<(), String> {
1411        if !self.disable_payload_signing {
1412            return Ok(());
1413        }
1414
1415        let target = storage_path::parse_storage_path(&self.target);
1416        if matches!(target, StoragePath::Local(_)) {
1417            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_DISABLE_PAYLOAD_SIGNING.to_string());
1418        }
1419
1420        Ok(())
1421    }
1422
1423    fn check_disable_content_md5_header_conflict(&self) -> Result<(), String> {
1424        if !self.disable_content_md5_header {
1425            return Ok(());
1426        }
1427
1428        let target = storage_path::parse_storage_path(&self.target);
1429        if matches!(target, StoragePath::Local(_)) {
1430            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_DISABLE_CONTENT_MD5_HEADER.to_string());
1431        }
1432
1433        Ok(())
1434    }
1435
1436    fn check_full_object_checksum_conflict(&self) -> Result<(), String> {
1437        if !self.full_object_checksum {
1438            return Ok(());
1439        }
1440
1441        let target = storage_path::parse_storage_path(&self.target);
1442        if matches!(target, StoragePath::Local(_)) {
1443            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_FULL_OBJECT_CHECKSUM.to_string());
1444        }
1445
1446        if let Some(additional_checksum_algorithm) = &self.additional_checksum_algorithm {
1447            if additional_checksum_algorithm == "SHA1" || additional_checksum_algorithm == "SHA256"
1448            {
1449                return Err(FULL_OBJECT_CHECKSUM_NOT_SUPPORTED.to_string());
1450            }
1451        }
1452
1453        Ok(())
1454    }
1455
1456    fn check_accelerate_conflict(&self) -> Result<(), String> {
1457        let source = storage_path::parse_storage_path(&self.source);
1458        if matches!(source, StoragePath::Local(_)) && self.source_accelerate {
1459            return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_ACCELERATE.to_string());
1460        }
1461
1462        let target = storage_path::parse_storage_path(&self.target);
1463        if matches!(target, StoragePath::Local(_)) && self.target_accelerate {
1464            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ACCELERATE.to_string());
1465        }
1466
1467        Ok(())
1468    }
1469
1470    fn check_request_payer_conflict(&self) -> Result<(), String> {
1471        let source = storage_path::parse_storage_path(&self.source);
1472        if matches!(source, StoragePath::Local(_)) && self.source_request_payer {
1473            return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_REQUEST_PAYER.to_string());
1474        }
1475
1476        let target = storage_path::parse_storage_path(&self.target);
1477        if matches!(target, StoragePath::Local(_)) && self.target_request_payer {
1478            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_REQUEST_PAYER.to_string());
1479        }
1480
1481        Ok(())
1482    }
1483
1484    fn check_server_side_copy_conflict(&self) -> Result<(), String> {
1485        let source = storage_path::parse_storage_path(&self.source);
1486        if matches!(source, StoragePath::Local(_)) && self.server_side_copy {
1487            return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_SERVER_SIDE_COPY.to_string());
1488        }
1489
1490        let target = storage_path::parse_storage_path(&self.target);
1491        if matches!(target, StoragePath::Local(_)) && self.server_side_copy {
1492            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_SERVER_SIDE_COPY.to_string());
1493        }
1494
1495        Ok(())
1496    }
1497
1498    fn check_filter_include_metadata_regex_conflict(&self) -> Result<(), String> {
1499        let source = storage_path::parse_storage_path(&self.source);
1500        if matches!(source, StoragePath::Local(_)) && self.filter_include_metadata_regex.is_some() {
1501            return Err(
1502                SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_INCLUDE_METADATA_REGEX.to_string(),
1503            );
1504        }
1505
1506        Ok(())
1507    }
1508
1509    fn check_filter_exclude_metadata_regex_conflict(&self) -> Result<(), String> {
1510        let source = storage_path::parse_storage_path(&self.source);
1511        if matches!(source, StoragePath::Local(_)) && self.filter_exclude_metadata_regex.is_some() {
1512            return Err(
1513                SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_EXCLUDE_METADATA_REGEX.to_string(),
1514            );
1515        }
1516
1517        Ok(())
1518    }
1519
1520    fn check_filter_include_tag_regex_conflict(&self) -> Result<(), String> {
1521        let source = storage_path::parse_storage_path(&self.source);
1522        if matches!(source, StoragePath::Local(_)) && self.filter_include_tag_regex.is_some() {
1523            return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_INCLUDE_TAG_REGEX.to_string());
1524        }
1525
1526        Ok(())
1527    }
1528
1529    fn check_filter_exclude_tag_regex_conflict(&self) -> Result<(), String> {
1530        let source = storage_path::parse_storage_path(&self.source);
1531        if matches!(source, StoragePath::Local(_)) && self.filter_exclude_tag_regex.is_some() {
1532            return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_EXCLUDE_TAG_REGEX.to_string());
1533        }
1534
1535        Ok(())
1536    }
1537
1538    fn check_server_no_sync_system_metadata_conflict(&self) -> Result<(), String> {
1539        let source = storage_path::parse_storage_path(&self.source);
1540        if matches!(source, StoragePath::Local(_)) && self.no_sync_system_metadata {
1541            return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_SYSTEM_METADATA.to_string());
1542        }
1543
1544        let target = storage_path::parse_storage_path(&self.target);
1545        if matches!(target, StoragePath::Local(_)) && self.no_sync_system_metadata {
1546            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_SYSTEM_METADATA.to_string());
1547        }
1548
1549        Ok(())
1550    }
1551
1552    fn check_server_no_sync_user_defined_metadata_conflict(&self) -> Result<(), String> {
1553        let source = storage_path::parse_storage_path(&self.source);
1554        if matches!(source, StoragePath::Local(_)) && self.no_sync_user_defined_metadata {
1555            return Err(
1556                SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_USER_DEFINED_METADATA.to_string(),
1557            );
1558        }
1559
1560        let target = storage_path::parse_storage_path(&self.target);
1561        if matches!(target, StoragePath::Local(_)) && self.no_sync_user_defined_metadata {
1562            return Err(
1563                TARGET_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_USER_DEFINED_METADATA.to_string(),
1564            );
1565        }
1566
1567        Ok(())
1568    }
1569    fn check_point_in_time_conflict(&self) -> Result<(), String> {
1570        if self.point_in_time.is_none() {
1571            return Ok(());
1572        }
1573
1574        let source = storage_path::parse_storage_path(&self.source);
1575        if matches!(source, StoragePath::Local(_)) {
1576            return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_POINT_IN_TIME.to_string());
1577        }
1578
1579        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.source) {
1580            if is_express_onezone_storage(&bucket) {
1581                return Err(POINT_IN_TIME_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE.to_string());
1582            }
1583        }
1584
1585        if Utc::now() < self.point_in_time.unwrap() {
1586            return Err(POINT_IN_TIME_VALUE_INVALID.to_string());
1587        }
1588
1589        Ok(())
1590    }
1591
1592    fn check_report_metadata_sync_status_conflict(&self) -> Result<(), String> {
1593        if !self.report_metadata_sync_status {
1594            return Ok(());
1595        }
1596
1597        let source = storage_path::parse_storage_path(&self.source);
1598        if matches!(source, StoragePath::Local(_)) {
1599            return Err(
1600                SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_METADATA_SYNC_STATUS.to_string(),
1601            );
1602        }
1603
1604        let target = storage_path::parse_storage_path(&self.target);
1605        if matches!(target, StoragePath::Local(_)) {
1606            return Err(
1607                TARGET_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_METADATA_SYNC_STATUS.to_string(),
1608            );
1609        }
1610
1611        Ok(())
1612    }
1613
1614    fn check_report_tagging_sync_status_conflict(&self) -> Result<(), String> {
1615        if !self.report_tagging_sync_status {
1616            return Ok(());
1617        }
1618
1619        let source = storage_path::parse_storage_path(&self.source);
1620        if matches!(source, StoragePath::Local(_)) {
1621            return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_TAGGING_SYNC_STATUS.to_string());
1622        }
1623
1624        let target = storage_path::parse_storage_path(&self.target);
1625        if matches!(target, StoragePath::Local(_)) {
1626            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_TAGGING_SYNC_STATUS.to_string());
1627        }
1628
1629        Ok(())
1630    }
1631
1632    fn check_report_annotations_sync_status_conflict(&self) -> Result<(), String> {
1633        if !self.report_annotations_sync_status {
1634            return Ok(());
1635        }
1636
1637        let source = storage_path::parse_storage_path(&self.source);
1638        let target = storage_path::parse_storage_path(&self.target);
1639
1640        if !storage_path::is_both_storage_s3(&source, &target) {
1641            return Err(LOCAL_STORAGE_SPECIFIED_FOR_OBJECT_ANNOTATION.to_string());
1642        }
1643
1644        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.source) {
1645            if is_express_onezone_storage(&bucket) {
1646                return Err(EXPRESS_ONEZONE_SPECIFIED_FOR_OBJECT_ANNOTATION.to_string());
1647            }
1648        }
1649
1650        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.target) {
1651            if is_express_onezone_storage(&bucket) {
1652                return Err(EXPRESS_ONEZONE_SPECIFIED_FOR_OBJECT_ANNOTATION.to_string());
1653            }
1654        }
1655
1656        Ok(())
1657    }
1658
1659    fn check_if_match_conflict(&self) -> Result<(), String> {
1660        if !self.if_match {
1661            return Ok(());
1662        }
1663
1664        let target = storage_path::parse_storage_path(&self.target);
1665        if matches!(target, StoragePath::Local(_)) {
1666            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_IF_MATCH.to_string());
1667        }
1668
1669        if self.remove_modified_filter && !self.head_each_target {
1670            return Err(IF_MATCH_CONFLICT.to_string());
1671        }
1672
1673        Ok(())
1674    }
1675
1676    fn check_if_none_match_conflict(&self) -> Result<(), String> {
1677        if !self.if_none_match {
1678            return Ok(());
1679        }
1680
1681        let target = storage_path::parse_storage_path(&self.target);
1682        if matches!(target, StoragePath::Local(_)) {
1683            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_IF_NONE_MATCH.to_string());
1684        }
1685
1686        Ok(())
1687    }
1688
1689    fn check_copy_source_if_match_conflict(&self) -> Result<(), String> {
1690        if !self.copy_source_if_match {
1691            return Ok(());
1692        }
1693
1694        let source = storage_path::parse_storage_path(&self.source);
1695        let target = storage_path::parse_storage_path(&self.target);
1696
1697        if !storage_path::is_both_storage_s3(&source, &target) {
1698            return Err(LOCAL_STORAGE_SPECIFIED.to_string());
1699        }
1700
1701        Ok(())
1702    }
1703
1704    fn check_enable_sync_object_annotation_conflict(&self) -> Result<(), String> {
1705        if !self.enable_sync_object_annotations {
1706            return Ok(());
1707        }
1708
1709        let source = storage_path::parse_storage_path(&self.source);
1710        let target = storage_path::parse_storage_path(&self.target);
1711
1712        if !storage_path::is_both_storage_s3(&source, &target) {
1713            return Err(LOCAL_STORAGE_SPECIFIED_FOR_OBJECT_ANNOTATION.to_string());
1714        }
1715
1716        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.source) {
1717            if is_express_onezone_storage(&bucket) {
1718                return Err(EXPRESS_ONEZONE_SPECIFIED_FOR_OBJECT_ANNOTATION.to_string());
1719            }
1720        }
1721
1722        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.target) {
1723            if is_express_onezone_storage(&bucket) {
1724                return Err(EXPRESS_ONEZONE_SPECIFIED_FOR_OBJECT_ANNOTATION.to_string());
1725            }
1726        }
1727
1728        Ok(())
1729    }
1730
1731    fn check_sync_latest_object_annotation_conflict(&self) -> Result<(), String> {
1732        if !self.sync_latest_object_annotations {
1733            return Ok(());
1734        }
1735
1736        let source = storage_path::parse_storage_path(&self.source);
1737        let target = storage_path::parse_storage_path(&self.target);
1738
1739        if !storage_path::is_both_storage_s3(&source, &target) {
1740            return Err(LOCAL_STORAGE_SPECIFIED_FOR_OBJECT_ANNOTATION.to_string());
1741        }
1742
1743        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.source) {
1744            if is_express_onezone_storage(&bucket) {
1745                return Err(EXPRESS_ONEZONE_SPECIFIED_FOR_OBJECT_ANNOTATION.to_string());
1746            }
1747        }
1748
1749        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.target) {
1750            if is_express_onezone_storage(&bucket) {
1751                return Err(EXPRESS_ONEZONE_SPECIFIED_FOR_OBJECT_ANNOTATION.to_string());
1752            }
1753        }
1754
1755        Ok(())
1756    }
1757
1758    fn build_client_configs(
1759        &self,
1760        request_checksum_calculation: RequestChecksumCalculation,
1761    ) -> (Option<ClientConfig>, Option<ClientConfig>) {
1762        let source_credential = if self.source_no_sign_request {
1763            Some(S3Credentials::NoSignRequest)
1764        } else if let Some(source_profile) = self.source_profile.clone() {
1765            Some(S3Credentials::Profile(source_profile))
1766        } else if self.source_access_key.is_some() {
1767            self.source_access_key
1768                .clone()
1769                .map(|access_key| S3Credentials::Credentials {
1770                    access_keys: AccessKeys {
1771                        access_key,
1772                        secret_access_key: self
1773                            .source_secret_access_key
1774                            .as_ref()
1775                            .unwrap()
1776                            .to_string(),
1777                        session_token: self.source_session_token.clone(),
1778                    },
1779                })
1780        } else {
1781            Some(S3Credentials::FromEnvironment)
1782        };
1783
1784        let target_credential = if let Some(target_profile) = self.target_profile.clone() {
1785            Some(S3Credentials::Profile(target_profile))
1786        } else if self.target_access_key.is_some() {
1787            self.target_access_key
1788                .clone()
1789                .map(|access_key| S3Credentials::Credentials {
1790                    access_keys: AccessKeys {
1791                        access_key,
1792                        secret_access_key: self
1793                            .target_secret_access_key
1794                            .as_ref()
1795                            .unwrap()
1796                            .to_string(),
1797                        session_token: self.target_session_token.clone(),
1798                    },
1799                })
1800        } else {
1801            Some(S3Credentials::FromEnvironment)
1802        };
1803
1804        let parallel_upload_semaphore =
1805            Arc::new(Semaphore::new(self.max_parallel_uploads as usize));
1806
1807        let source_request_payer = if self.source_request_payer {
1808            Some(RequestPayer::Requester)
1809        } else {
1810            None
1811        };
1812
1813        let source_client_config = source_credential.map(|source_credential| ClientConfig {
1814            client_config_location: ClientConfigLocation {
1815                aws_config_file: self.aws_config_file.clone(),
1816                aws_shared_credentials_file: self.aws_shared_credentials_file.clone(),
1817            },
1818            credential: source_credential,
1819            region: self.source_region.clone(),
1820            endpoint_url: self.source_endpoint_url.clone(),
1821            force_path_style: self.source_force_path_style,
1822            retry_config: RetryConfig {
1823                aws_max_attempts: self.aws_max_attempts,
1824                initial_backoff_milliseconds: self.initial_backoff_milliseconds,
1825            },
1826            cli_timeout_config: CLITimeoutConfig {
1827                operation_timeout_milliseconds: self.operation_timeout_milliseconds,
1828                operation_attempt_timeout_milliseconds: self.operation_attempt_timeout_milliseconds,
1829                connect_timeout_milliseconds: self.connect_timeout_milliseconds,
1830                read_timeout_milliseconds: self.read_timeout_milliseconds,
1831            },
1832            disable_stalled_stream_protection: self.disable_stalled_stream_protection,
1833            request_checksum_calculation: RequestChecksumCalculation::WhenRequired,
1834            parallel_upload_semaphore: parallel_upload_semaphore.clone(),
1835            accelerate: self.source_accelerate,
1836            request_payer: source_request_payer,
1837        });
1838
1839        let target_request_payer = if self.target_request_payer {
1840            Some(RequestPayer::Requester)
1841        } else {
1842            None
1843        };
1844
1845        let target_client_config = target_credential.map(|target_credential| ClientConfig {
1846            client_config_location: ClientConfigLocation {
1847                aws_config_file: self.aws_config_file.clone(),
1848                aws_shared_credentials_file: self.aws_shared_credentials_file.clone(),
1849            },
1850            credential: target_credential,
1851            region: self.target_region.clone(),
1852            endpoint_url: self.target_endpoint_url.clone(),
1853            force_path_style: self.target_force_path_style,
1854            retry_config: RetryConfig {
1855                aws_max_attempts: self.aws_max_attempts,
1856                initial_backoff_milliseconds: self.initial_backoff_milliseconds,
1857            },
1858            cli_timeout_config: CLITimeoutConfig {
1859                operation_timeout_milliseconds: self.operation_timeout_milliseconds,
1860                operation_attempt_timeout_milliseconds: self.operation_attempt_timeout_milliseconds,
1861                connect_timeout_milliseconds: self.connect_timeout_milliseconds,
1862                read_timeout_milliseconds: self.read_timeout_milliseconds,
1863            },
1864            disable_stalled_stream_protection: self.disable_stalled_stream_protection,
1865            request_checksum_calculation,
1866            parallel_upload_semaphore,
1867            accelerate: self.target_accelerate,
1868            request_payer: target_request_payer,
1869        });
1870
1871        (source_client_config, target_client_config)
1872    }
1873}
1874
1875impl TryFrom<CLIArgs> for Config {
1876    type Error = String;
1877
1878    fn try_from(value: CLIArgs) -> Result<Self, Self::Error> {
1879        value.validate_storage_config()?;
1880
1881        let original_cloned_value = value.clone();
1882
1883        let mut tracing_config = value.verbosity.log_level().map(|log_level| TracingConfig {
1884            tracing_level: log_level,
1885            json_tracing: value.json_tracing,
1886            aws_sdk_tracing: value.aws_sdk_tracing,
1887            span_events_tracing: value.span_events_tracing,
1888            disable_color_tracing: value.disable_color_tracing,
1889            stderr_tracing: value.tracing_stderr,
1890        });
1891
1892        let storage_class = value
1893            .storage_class
1894            .map(|storage_class| StorageClass::from_str(&storage_class).unwrap());
1895
1896        let sse = value
1897            .sse
1898            .map(|sse| ServerSideEncryption::from_str(&sse).unwrap());
1899
1900        let canned_acl = value
1901            .acl
1902            .map(|acl| ObjectCannedAcl::from_str(&acl).unwrap());
1903
1904        let include_regex = value
1905            .filter_include_regex
1906            .map(|regex| Regex::new(&regex).unwrap());
1907
1908        let exclude_regex = value
1909            .filter_exclude_regex
1910            .map(|regex| Regex::new(&regex).unwrap());
1911        let include_content_type_regex = value
1912            .filter_include_content_type_regex
1913            .map(|regex| Regex::new(&regex).unwrap());
1914        let exclude_content_type_regex = value
1915            .filter_exclude_content_type_regex
1916            .map(|regex| Regex::new(&regex).unwrap());
1917
1918        let include_metadata_regex = value
1919            .filter_include_metadata_regex
1920            .map(|regex| Regex::new(&regex).unwrap());
1921        let exclude_metadata_regex = value
1922            .filter_exclude_metadata_regex
1923            .map(|regex| Regex::new(&regex).unwrap());
1924        let include_tag_regex = value
1925            .filter_include_tag_regex
1926            .map(|regex| Regex::new(&regex).unwrap());
1927        let exclude_tag_regex = value
1928            .filter_exclude_tag_regex
1929            .map(|regex| Regex::new(&regex).unwrap());
1930
1931        let rate_limit_bandwidth = value
1932            .rate_limit_bandwidth
1933            .map(|bandwidth| human_bytes::parse_human_bandwidth(&bandwidth).unwrap());
1934
1935        let mut additional_checksum_algorithm = value
1936            .additional_checksum_algorithm
1937            .map(|algorithm| ChecksumAlgorithm::from(algorithm.as_str()));
1938
1939        let check_additional_checksum_algorithm = value
1940            .check_additional_checksum
1941            .map(|algorithm| ChecksumAlgorithm::from(algorithm.as_str()));
1942
1943        let check_mtime_and_additional_checksum = value
1944            .check_mtime_and_additional_checksum
1945            .map(|algorithm| ChecksumAlgorithm::from(algorithm.as_str()));
1946
1947        let mut checksum_mode = if value.enable_additional_checksum {
1948            Some(ChecksumMode::Enabled)
1949        } else {
1950            None
1951        };
1952
1953        let tagging = value
1954            .tagging
1955            .map(|tagging| tagging::parse_tagging(&tagging).unwrap());
1956        let filter_larger_size = value
1957            .filter_larger_size
1958            .map(|human_bytes| human_bytes::parse_human_bytes_without_limit(&human_bytes).unwrap());
1959        let filter_smaller_size = value
1960            .filter_smaller_size
1961            .map(|human_bytes| human_bytes::parse_human_bytes_without_limit(&human_bytes).unwrap());
1962
1963        let metadata = if value.metadata.is_some() {
1964            Some(metadata::parse_metadata(&value.metadata.unwrap())?)
1965        } else {
1966            None
1967        };
1968
1969        let mut full_object_checksum = if additional_checksum_algorithm
1970            .as_ref()
1971            .is_some_and(|algorithm| algorithm == &ChecksumAlgorithm::Crc64Nvme)
1972        {
1973            true
1974        } else {
1975            value.full_object_checksum
1976        };
1977
1978        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&value.source) {
1979            if is_express_onezone_storage(&bucket)
1980                && !value.disable_express_one_zone_additional_checksum
1981            {
1982                checksum_mode = Some(ChecksumMode::Enabled);
1983            }
1984        }
1985
1986        let mut request_checksum_calculation = RequestChecksumCalculation::WhenRequired;
1987        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&value.target) {
1988            if is_express_onezone_storage(&bucket)
1989                && additional_checksum_algorithm.is_none()
1990                && !value.disable_express_one_zone_additional_checksum
1991            {
1992                additional_checksum_algorithm = Some(ChecksumAlgorithm::Crc64Nvme);
1993                full_object_checksum = true;
1994                request_checksum_calculation = RequestChecksumCalculation::WhenSupported;
1995            } else if additional_checksum_algorithm.is_some() {
1996                request_checksum_calculation = RequestChecksumCalculation::WhenSupported;
1997            }
1998        }
1999
2000        let (source_client_config, target_client_config) =
2001            original_cloned_value.build_client_configs(request_checksum_calculation);
2002
2003        #[allow(unused_assignments)]
2004        #[allow(unused_mut)]
2005        let mut allow_e2e_test_dangerous_simulation = false;
2006
2007        #[allow(unused_assignments)]
2008        #[allow(unused_mut)]
2009        let mut cancellation_point = None;
2010        #[allow(unused_assignments)]
2011        #[allow(unused_mut)]
2012        let mut panic_simulation_point = None;
2013        #[allow(unused_assignments)]
2014        #[allow(unused_mut)]
2015        let mut error_simulation_point = None;
2016        #[cfg(e2e_test_dangerous_simulations)]
2017        {
2018            allow_e2e_test_dangerous_simulation = value.allow_e2e_test_dangerous_simulation;
2019            cancellation_point = value.cancellation_point;
2020            panic_simulation_point = value.panic_simulation_point;
2021            error_simulation_point = value.error_simulation_point;
2022        }
2023
2024        #[allow(unused_assignments)]
2025        #[allow(unused_mut)]
2026        let mut test_user_defined_callback = false;
2027        #[cfg(e2e_test_dangerous_simulations)]
2028        {
2029            test_user_defined_callback = value.test_user_defined_callback;
2030        }
2031
2032        // If point-in-time is specified, we need to remove the modified filter.
2033        // Because target objects may need to be modified.
2034        let mut remove_modified_filter = if value.point_in_time.is_some() {
2035            true
2036        } else {
2037            value.remove_modified_filter
2038        };
2039
2040        // Reporting need to be done in dry run mode.
2041        let dry_run = if value.report_sync_status {
2042            true
2043        } else {
2044            value.dry_run
2045        };
2046
2047        // If report_sync_status is true, we need to remove the modified filter.
2048        // And must warn as error.
2049        let warn_as_error;
2050        let check_etag;
2051        let head_each_target;
2052        if value.report_sync_status {
2053            remove_modified_filter = true;
2054            warn_as_error = true;
2055            head_each_target = true;
2056            check_etag = check_additional_checksum_algorithm.is_none();
2057        } else {
2058            warn_as_error = value.warn_as_error;
2059            check_etag = value.check_etag;
2060            head_each_target = value.head_each_target;
2061        }
2062
2063        if dry_run {
2064            if tracing_config.is_none() {
2065                tracing_config = Some(TracingConfig {
2066                    tracing_level: log::Level::Info,
2067                    json_tracing: DEFAULT_JSON_TRACING,
2068                    aws_sdk_tracing: DEFAULT_AWS_SDK_TRACING,
2069                    span_events_tracing: DEFAULT_SPAN_EVENTS_TRACING,
2070                    disable_color_tracing: DEFAULT_DISABLE_COLOR_TRACING,
2071                    stderr_tracing: value.tracing_stderr,
2072                });
2073            } else if tracing_config.unwrap().tracing_level < log::Level::Info {
2074                tracing_config = Some(TracingConfig {
2075                    tracing_level: log::Level::Info,
2076                    json_tracing: tracing_config.unwrap().json_tracing,
2077                    aws_sdk_tracing: tracing_config.unwrap().aws_sdk_tracing,
2078                    span_events_tracing: tracing_config.unwrap().span_events_tracing,
2079                    disable_color_tracing: tracing_config.unwrap().disable_color_tracing,
2080                    stderr_tracing: tracing_config.unwrap().stderr_tracing,
2081                });
2082            }
2083        }
2084
2085        cfg_if! {
2086            if #[cfg(feature = "lua_support")] {
2087                let preprocess_callback_lua_script =  value.preprocess_callback_lua_script.clone();
2088                let event_callback_lua_script = value.event_callback_lua_script.clone();
2089                let filter_callback_lua_script = value.filter_callback_lua_script.clone();
2090                let allow_lua_os_library = value.allow_lua_os_library;
2091                let allow_lua_unsafe_vm = value.allow_lua_unsafe_vm;
2092                let lua_vm_memory_limit = human_bytes::parse_human_bytes_without_low_limit(&value.lua_vm_memory_limit)? as usize;
2093                let lua_callback_timeout_milliseconds = value.lua_callback_timeout_milliseconds;
2094            } else {
2095                let preprocess_callback_lua_script =  None;
2096                let event_callback_lua_script = None;
2097                let filter_callback_lua_script = None;
2098                let allow_lua_os_library = false;
2099                let allow_lua_unsafe_vm = false;
2100                let lua_vm_memory_limit = 0;
2101                let lua_callback_timeout_milliseconds: u64 = 0;
2102            }
2103        }
2104
2105        #[allow(unused_mut)]
2106        let mut preprocess_manager = PreprocessManager::new();
2107        cfg_if! {
2108            if #[cfg(feature = "lua_support")] {
2109                if let Some(preprocess_callback_lua_script) = preprocess_callback_lua_script.as_ref() {
2110                    let mut lua_preprocess_callback = crate::callback::lua_preprocess_callback::LuaPreprocessCallback::new(
2111                        lua_vm_memory_limit,
2112                        allow_lua_os_library,
2113                        allow_lua_unsafe_vm,
2114                        lua_callback_timeout_milliseconds,
2115                    );
2116                    if let Err(e) =
2117                        lua_preprocess_callback.load_and_compile(preprocess_callback_lua_script.as_str())
2118                    {
2119                        return Err(LUA_SCRIPT_LOAD_ERROR.to_string() + &e.to_string());
2120                    }
2121                    preprocess_manager.register_callback(lua_preprocess_callback);
2122                }
2123            }
2124        }
2125
2126        #[allow(unused_mut)]
2127        let mut event_manager = EventManager::new();
2128        cfg_if! {
2129            if #[cfg(feature = "lua_support")] {
2130                if let Some(event_callback_lua_script) = event_callback_lua_script.as_ref() {
2131                    let mut lua_event_callback = crate::callback::lua_event_callback::LuaEventCallback::new(
2132                        lua_vm_memory_limit,
2133                        allow_lua_os_library,
2134                        allow_lua_unsafe_vm,
2135                        lua_callback_timeout_milliseconds,
2136                    );
2137                    if let Err(e) = lua_event_callback.load_and_compile(event_callback_lua_script.as_str())
2138                    {
2139                        return Err(LUA_SCRIPT_LOAD_ERROR.to_string() + &e.to_string());
2140                    }
2141                    // Lua event callback is registered for all events.
2142                    event_manager.register_callback(EventType::ALL_EVENTS, lua_event_callback, dry_run);
2143                }
2144            }
2145        }
2146
2147        #[allow(unused_mut)]
2148        let mut filter_manager = FilterManager::new();
2149        cfg_if! {
2150            if #[cfg(feature = "lua_support")] {
2151                if let Some(filter_callback_lua_script) = filter_callback_lua_script.as_ref() {
2152                    let mut lua_filter_callback = crate::callback::lua_filter_callback::LuaFilterCallback::new(
2153                        lua_vm_memory_limit,
2154                        allow_lua_os_library,
2155                        allow_lua_unsafe_vm,
2156                        lua_callback_timeout_milliseconds,
2157                    );
2158                    if let Err(e) =
2159                        lua_filter_callback.load_and_compile(filter_callback_lua_script.as_str())
2160                    {
2161                        return Err(LUA_SCRIPT_LOAD_ERROR.to_string() + &e.to_string());
2162                    }
2163                    filter_manager.register_callback(lua_filter_callback);
2164                }
2165            }
2166        }
2167
2168        Ok(Config {
2169            source: storage_path::parse_storage_path(&value.source),
2170            target: storage_path::parse_storage_path(&value.target),
2171
2172            show_no_progress: value.show_no_progress,
2173
2174            source_client_config,
2175            target_client_config,
2176
2177            tracing_config,
2178
2179            force_retry_config: ForceRetryConfig {
2180                force_retry_count: value.force_retry_count,
2181                force_retry_interval_milliseconds: value.force_retry_interval_milliseconds,
2182            },
2183
2184            transfer_config: TransferConfig {
2185                multipart_threshold: human_bytes::parse_human_bytes(&value.multipart_threshold)?,
2186                multipart_chunksize: human_bytes::parse_human_bytes(&value.multipart_chunksize)?,
2187                auto_chunksize: value.auto_chunksize,
2188            },
2189
2190            worker_size: value.worker_size,
2191
2192            warn_as_error,
2193            follow_symlinks: !value.ignore_symlinks,
2194            head_each_target,
2195            sync_with_delete: value.delete,
2196            disable_tagging: value.disable_tagging,
2197            sync_latest_tagging: value.sync_latest_tagging,
2198            server_side_copy: value.server_side_copy,
2199            no_guess_mime_type: value.no_guess_mime_type,
2200            disable_multipart_verify: value.disable_multipart_verify,
2201            disable_etag_verify: value.disable_etag_verify,
2202            disable_additional_checksum_verify: value.disable_additional_checksum_verify,
2203            enable_versioning: value.enable_versioning,
2204            point_in_time: value.point_in_time,
2205            storage_class,
2206            sse,
2207            sse_kms_key_id: SseKmsKeyId {
2208                id: value.sse_kms_key_id,
2209            },
2210            source_sse_c: value.source_sse_c,
2211            source_sse_c_key: SseCustomerKey {
2212                key: value.source_sse_c_key,
2213            },
2214            source_sse_c_key_md5: value.source_sse_c_key_md5,
2215            target_sse_c: value.target_sse_c,
2216            target_sse_c_key: SseCustomerKey {
2217                key: value.target_sse_c_key,
2218            },
2219            target_sse_c_key_md5: value.target_sse_c_key_md5,
2220            canned_acl,
2221            additional_checksum_algorithm,
2222            additional_checksum_mode: checksum_mode,
2223            dry_run,
2224            rate_limit_objects: value.rate_limit_objects,
2225            rate_limit_bandwidth,
2226            max_parallel_listings: value.max_parallel_listings,
2227            object_listing_queue_size: value.object_listing_queue_size,
2228            max_parallel_listing_max_depth: value.max_parallel_listing_max_depth,
2229            allow_parallel_listings_in_express_one_zone: value
2230                .allow_parallel_listings_in_express_one_zone,
2231            cache_control: value.cache_control,
2232            content_disposition: value.content_disposition,
2233            content_encoding: value.content_encoding,
2234            content_language: value.content_language,
2235            content_type: value.content_type,
2236            expires: value.expires,
2237            metadata,
2238            website_redirect: value.website_redirect,
2239            no_sync_system_metadata: value.no_sync_system_metadata,
2240            no_sync_user_defined_metadata: value.no_sync_user_defined_metadata,
2241            tagging,
2242            filter_config: FilterConfig {
2243                before_time: value.filter_mtime_before,
2244                after_time: value.filter_mtime_after,
2245                remove_modified_filter,
2246                check_size: value.check_size,
2247                check_etag,
2248                check_mtime_and_etag: value.check_mtime_and_etag,
2249                check_checksum_algorithm: check_additional_checksum_algorithm,
2250                check_mtime_and_additional_checksum,
2251                include_regex,
2252                exclude_regex,
2253                include_content_type_regex,
2254                exclude_content_type_regex,
2255                include_metadata_regex,
2256                exclude_metadata_regex,
2257                include_tag_regex,
2258                exclude_tag_regex,
2259                larger_size: filter_larger_size,
2260                smaller_size: filter_smaller_size,
2261                filter_manager,
2262            },
2263            max_keys: value.max_keys,
2264            put_last_modified_metadata: value.put_last_modified_metadata,
2265            auto_complete_shell: value.auto_complete_shell,
2266            disable_payload_signing: value.disable_payload_signing,
2267            disable_content_md5_header: value.disable_content_md5_header,
2268            delete_excluded: value.delete_excluded,
2269            full_object_checksum,
2270            allow_e2e_test_dangerous_simulation,
2271            test_user_defined_callback,
2272            cancellation_point,
2273            panic_simulation_point,
2274            error_simulation_point,
2275            source_accelerate: value.source_accelerate,
2276            target_accelerate: value.target_accelerate,
2277            source_request_payer: value.source_request_payer,
2278            target_request_payer: value.target_request_payer,
2279            report_sync_status: value.report_sync_status,
2280            report_metadata_sync_status: value.report_metadata_sync_status,
2281            report_tagging_sync_status: value.report_tagging_sync_status,
2282            report_annotations_sync_status: value.report_annotations_sync_status,
2283            event_manager,
2284            preprocess_manager,
2285            preprocess_callback_lua_script,
2286            event_callback_lua_script,
2287            filter_callback_lua_script,
2288            allow_lua_os_library,
2289            allow_lua_unsafe_vm,
2290            lua_vm_memory_limit,
2291            lua_callback_timeout_milliseconds,
2292            if_match: value.if_match,
2293            if_none_match: value.if_none_match,
2294            copy_source_if_match: value.copy_source_if_match,
2295            max_delete: value.max_delete,
2296            ignore_glacier_warnings: value.ignore_glacier_warnings,
2297            enable_sync_object_annotations: value.enable_sync_object_annotations,
2298            sync_latest_object_annotations: value.sync_latest_object_annotations,
2299            disable_check_annotation_etag: value.disable_check_annotation_etag,
2300        })
2301    }
2302}
2303
2304fn is_express_onezone_storage(bucket: &str) -> bool {
2305    bucket.ends_with(EXPRESS_ONEZONE_STORAGE_SUFFIX)
2306}