1use anyhow::Context;
105use std::io::IsTerminal;
106use tracing::instrument;
107
108mod auto_meta;
109pub mod chmod;
110pub mod cli;
111pub mod cmp;
112pub mod config;
113pub mod copy;
114pub mod copy_data;
115pub mod delete;
116pub mod dry_run;
117pub mod error;
118pub mod error_collector;
119pub mod filegen;
120pub mod filter;
121pub mod histogram_logger;
122pub mod histogram_panel;
123pub mod link;
124pub mod observability;
125pub mod preserve;
126pub mod remote_tracing;
127pub mod rm;
128mod runtime_setup;
129pub mod safedir;
130mod settings_parse;
131pub mod version;
132
133pub mod filecmp;
134pub mod progress;
135mod testutils;
136pub mod toctou_check;
137pub mod walk;
138pub mod walk_driver;
139
140pub use config::{
141 AutoMetaThrottleConfig, ConcurrencyLimit, DryRunMode, DryRunWarnings, FilesInFlightSource,
142 MIN_DEFAULT_FILES_IN_FLIGHT, OutputConfig, ResolvedFilesInFlight, RuntimeConfig,
143 ThrottleConfig, TracingConfig,
144};
145pub use congestion::{MetadataOp, Side};
150pub use progress::{RcpdProgressPrinter, SerializableProgress};
151pub use runtime_setup::{
155 NOTICE_TARGET, collect_runtime_stats, generate_debug_log_filename, generate_trace_filename,
156};
157pub use settings_parse::{
158 parse_compare_settings, parse_metadata_cmp_settings, parse_preserve_settings,
159 validate_update_compare_vs_preserve,
160};
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, enum_map::Enum)]
164pub enum RcpdType {
165 Source,
166 Destination,
167}
168
169impl std::fmt::Display for RcpdType {
170 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171 match self {
172 RcpdType::Source => write!(f, "source"),
173 RcpdType::Destination => write!(f, "destination"),
174 }
175 }
176}
177
178pub type ProgressSnapshot<T> = enum_map::EnumMap<RcpdType, T>;
180
181#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, PartialEq, Eq)]
183pub struct RuntimeStats {
184 pub cpu_time_user_ms: u64,
186 pub cpu_time_kernel_ms: u64,
188 pub peak_rss_bytes: u64,
190}
191
192#[derive(Debug, Default)]
194pub struct RemoteRuntimeStats {
195 pub source_host: String,
196 pub source_stats: RuntimeStats,
197 pub dest_host: String,
198 pub dest_stats: RuntimeStats,
199}
200
201#[must_use]
204pub fn is_localhost(host: &str) -> bool {
205 if host == "localhost" || host == "127.0.0.1" || host == "::1" || host == "[::1]" {
206 return true;
207 }
208 let mut buf = [0u8; 256];
210 let result = unsafe { libc::gethostname(buf.as_mut_ptr() as *mut libc::c_char, buf.len()) };
212 if result == 0
213 && let Ok(hostname_cstr) = std::ffi::CStr::from_bytes_until_nul(&buf)
214 && let Ok(hostname) = hostname_cstr.to_str()
215 && host == hostname
216 {
217 return true;
218 }
219 false
220}
221
222pub(crate) static PROGRESS: std::sync::LazyLock<progress::Progress> =
223 std::sync::LazyLock::new(progress::Progress::new);
224pub(crate) static PBAR: std::sync::LazyLock<indicatif::ProgressBar> =
225 std::sync::LazyLock::new(indicatif::ProgressBar::new_spinner);
226pub(crate) static REMOTE_RUNTIME_STATS: std::sync::LazyLock<
227 std::sync::Mutex<Option<RemoteRuntimeStats>>,
228> = std::sync::LazyLock::new(|| std::sync::Mutex::new(None));
229static HISTOGRAM_LOGGER_CANCEL: std::sync::Mutex<Option<tokio::sync::watch::Sender<bool>>> =
230 std::sync::Mutex::new(None);
231static HISTOGRAM_LOGGER_HANDLE: std::sync::Mutex<Option<tokio::task::JoinHandle<()>>> =
232 std::sync::Mutex::new(None);
233
234pub(crate) fn store_logger_cancel(tx: tokio::sync::watch::Sender<bool>) {
235 *HISTOGRAM_LOGGER_CANCEL
236 .lock()
237 .expect("histogram logger cancel mutex poisoned") = Some(tx);
238}
239
240pub(crate) fn store_logger_handle(handle: tokio::task::JoinHandle<()>) {
241 *HISTOGRAM_LOGGER_HANDLE
242 .lock()
243 .expect("histogram logger handle mutex poisoned") = Some(handle);
244}
245
246fn take_logger_handle() -> Option<tokio::task::JoinHandle<()>> {
247 HISTOGRAM_LOGGER_HANDLE
248 .lock()
249 .expect("histogram logger handle mutex poisoned")
250 .take()
251}
252
253fn signal_logger_cancel() {
254 if let Some(tx) = HISTOGRAM_LOGGER_CANCEL
255 .lock()
256 .expect("histogram logger cancel mutex poisoned")
257 .take()
258 && let Err(err) = tx.send(true)
259 {
260 tracing::debug!("histogram-logger cancel send failed (already gone): {err:#}");
261 }
262}
263
264#[must_use]
265pub fn get_progress() -> &'static progress::Progress {
266 &PROGRESS
267}
268
269pub fn set_remote_runtime_stats(stats: RemoteRuntimeStats) {
271 *REMOTE_RUNTIME_STATS.lock().unwrap() = Some(stats);
272}
273
274struct ProgressTracker {
275 lock_cvar: std::sync::Arc<(std::sync::Mutex<bool>, std::sync::Condvar)>,
276 pbar_thread: Option<std::thread::JoinHandle<()>>,
277}
278
279#[derive(Copy, Clone, Debug, Default, clap::ValueEnum)]
280pub enum ProgressType {
281 #[default]
282 #[value(name = "auto", alias = "Auto")]
283 Auto,
284 #[value(name = "ProgressBar", alias = "progress-bar")]
285 ProgressBar,
286 #[value(name = "TextUpdates", alias = "text-updates")]
287 TextUpdates,
288}
289
290pub enum GeneralProgressType {
291 User {
292 progress_type: ProgressType,
293 kind: progress::LocalProgressKind,
294 },
295 Remote(tokio::sync::mpsc::UnboundedSender<remote_tracing::TracingMessage>),
296 RemoteMaster {
297 progress_type: ProgressType,
298 get_progress_snapshot:
299 Box<dyn Fn() -> ProgressSnapshot<SerializableProgress> + Send + 'static>,
300 },
301}
302
303impl std::fmt::Debug for GeneralProgressType {
304 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
305 match self {
306 GeneralProgressType::User {
307 progress_type,
308 kind,
309 } => write!(f, "User(progress_type: {progress_type:?}, kind: {kind:?})"),
310 GeneralProgressType::Remote(_) => write!(f, "Remote(<sender>)"),
311 GeneralProgressType::RemoteMaster { progress_type, .. } => {
312 write!(
313 f,
314 "RemoteMaster(progress_type: {progress_type:?}, <function>)"
315 )
316 }
317 }
318 }
319}
320
321#[derive(Debug)]
322pub struct ProgressSettings {
323 pub progress_type: GeneralProgressType,
324 pub progress_delay: Option<String>,
325}
326
327fn progress_bar(
328 lock: &std::sync::Mutex<bool>,
329 cvar: &std::sync::Condvar,
330 delay_opt: &Option<std::time::Duration>,
331 kind: progress::LocalProgressKind,
332) {
333 let delay = delay_opt.unwrap_or(std::time::Duration::from_millis(200));
334 PBAR.set_style(
335 indicatif::ProgressStyle::with_template("{spinner:.cyan} {msg}")
336 .unwrap()
337 .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]),
338 );
339 let mut prog_printer = progress::make_local_printer(kind, &PROGRESS);
340 let mut is_done = lock.lock().unwrap();
341 loop {
342 PBAR.set_position(PBAR.position() + 1); let mut msg = prog_printer.print().unwrap();
344 msg.push_str(&observability::render_lines());
345 msg.push_str(&render_panel_from_registry());
346 PBAR.set_message(msg);
347 let result = cvar.wait_timeout(is_done, delay).unwrap();
348 is_done = result.0;
349 if *is_done {
350 break;
351 }
352 }
353 PBAR.finish_and_clear();
354}
355
356fn get_datetime_prefix() -> String {
357 chrono::Local::now()
358 .format("%Y-%m-%dT%H:%M:%S%.3f%:z")
359 .to_string()
360}
361
362fn text_updates(
363 lock: &std::sync::Mutex<bool>,
364 cvar: &std::sync::Condvar,
365 delay_opt: &Option<std::time::Duration>,
366 kind: progress::LocalProgressKind,
367) {
368 let delay = delay_opt.unwrap_or(std::time::Duration::from_secs(10));
369 let mut prog_printer = progress::make_local_printer(kind, &PROGRESS);
370 let mut is_done = lock.lock().unwrap();
371 loop {
372 eprintln!("=======================");
373 eprintln!(
374 "{}\n--{}{}{}",
375 get_datetime_prefix(),
376 prog_printer.print().unwrap(),
377 observability::render_lines(),
378 render_panel_from_registry(),
379 );
380 let result = cvar.wait_timeout(is_done, delay).unwrap();
381 is_done = result.0;
382 if *is_done {
383 break;
384 }
385 }
386}
387
388fn rcpd_updates(
389 lock: &std::sync::Mutex<bool>,
390 cvar: &std::sync::Condvar,
391 delay_opt: &Option<std::time::Duration>,
392 sender: tokio::sync::mpsc::UnboundedSender<remote_tracing::TracingMessage>,
393) {
394 tracing::debug!("Starting rcpd progress updates");
395 let delay = delay_opt.unwrap_or(std::time::Duration::from_millis(200));
396 let mut is_done = lock.lock().unwrap();
397 loop {
398 if remote_tracing::send_progress_update(&sender, &PROGRESS).is_err() {
399 tracing::debug!("Progress update channel closed, stopping progress updates");
401 break;
402 }
403 let result = cvar.wait_timeout(is_done, delay).unwrap();
404 is_done = result.0;
405 if *is_done {
406 break;
407 }
408 }
409}
410
411fn remote_master_updates<F>(
412 lock: &std::sync::Mutex<bool>,
413 cvar: &std::sync::Condvar,
414 delay_opt: &Option<std::time::Duration>,
415 get_progress_snapshot: F,
416 progress_type: ProgressType,
417) where
418 F: Fn() -> ProgressSnapshot<SerializableProgress> + Send + 'static,
419{
420 let interactive = match progress_type {
421 ProgressType::Auto => std::io::stderr().is_terminal(),
422 ProgressType::ProgressBar => true,
423 ProgressType::TextUpdates => false,
424 };
425 if interactive {
426 PBAR.set_style(
427 indicatif::ProgressStyle::with_template("{spinner:.cyan} {msg}")
428 .unwrap()
429 .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]),
430 );
431 let delay = delay_opt.unwrap_or(std::time::Duration::from_millis(200));
432 let mut printer = RcpdProgressPrinter::new();
433 let mut is_done = lock.lock().unwrap();
434 loop {
435 let progress_map = get_progress_snapshot();
436 let source_progress = &progress_map[RcpdType::Source];
437 let destination_progress = &progress_map[RcpdType::Destination];
438 PBAR.set_position(PBAR.position() + 1); let mut msg = printer
440 .print(source_progress, destination_progress)
441 .unwrap();
442 msg.push_str(&render_panel_from_registry());
443 PBAR.set_message(msg);
444 let result = cvar.wait_timeout(is_done, delay).unwrap();
445 is_done = result.0;
446 if *is_done {
447 break;
448 }
449 }
450 PBAR.finish_and_clear();
451 } else {
452 let delay = delay_opt.unwrap_or(std::time::Duration::from_secs(10));
453 let mut printer = RcpdProgressPrinter::new();
454 let mut is_done = lock.lock().unwrap();
455 loop {
456 let progress_map = get_progress_snapshot();
457 let source_progress = &progress_map[RcpdType::Source];
458 let destination_progress = &progress_map[RcpdType::Destination];
459 eprintln!("=======================");
460 eprintln!(
461 "{}\n--{}{}",
462 get_datetime_prefix(),
463 printer
464 .print(source_progress, destination_progress)
465 .unwrap(),
466 render_panel_from_registry(),
467 );
468 let result = cvar.wait_timeout(is_done, delay).unwrap();
469 is_done = result.0;
470 if *is_done {
471 break;
472 }
473 }
474 }
475}
476
477impl ProgressTracker {
478 pub fn new(progress_type: GeneralProgressType, delay_opt: Option<std::time::Duration>) -> Self {
479 let lock_cvar =
480 std::sync::Arc::new((std::sync::Mutex::new(false), std::sync::Condvar::new()));
481 let lock_cvar_clone = lock_cvar.clone();
482 let pbar_thread = std::thread::spawn(move || {
483 let (lock, cvar) = &*lock_cvar_clone;
484 match progress_type {
485 GeneralProgressType::Remote(sender) => {
486 rcpd_updates(lock, cvar, &delay_opt, sender);
487 }
488 GeneralProgressType::RemoteMaster {
489 progress_type,
490 get_progress_snapshot,
491 } => {
492 remote_master_updates(
493 lock,
494 cvar,
495 &delay_opt,
496 get_progress_snapshot,
497 progress_type,
498 );
499 }
500 GeneralProgressType::User {
501 progress_type,
502 kind,
503 } => {
504 let interactive = match progress_type {
505 ProgressType::Auto => std::io::stderr().is_terminal(),
506 ProgressType::ProgressBar => true,
507 ProgressType::TextUpdates => false,
508 };
509 if interactive {
510 progress_bar(lock, cvar, &delay_opt, kind);
511 } else {
512 text_updates(lock, cvar, &delay_opt, kind);
513 }
514 }
515 }
516 });
517 Self {
518 lock_cvar,
519 pbar_thread: Some(pbar_thread),
520 }
521 }
522}
523
524impl Drop for ProgressTracker {
525 fn drop(&mut self) {
526 let (lock, cvar) = &*self.lock_cvar;
527 let mut is_done = lock.lock().unwrap();
528 *is_done = true;
529 cvar.notify_one();
530 drop(is_done);
531 if let Some(pbar_thread) = self.pbar_thread.take() {
532 pbar_thread.join().unwrap();
533 }
534 }
535}
536
537pub async fn cmp(
538 src: &std::path::Path,
539 dst: &std::path::Path,
540 log: &cmp::LogWriter,
541 settings: &cmp::Settings,
542) -> Result<cmp::Summary, anyhow::Error> {
543 cmp::cmp(&PROGRESS, src, dst, log, settings).await
544}
545
546pub async fn copy(
547 src: &std::path::Path,
548 dst: &std::path::Path,
549 settings: ©::Settings,
550 preserve: &preserve::Settings,
551) -> Result<copy::Summary, copy::Error> {
552 copy::copy(&PROGRESS, src, dst, settings, preserve, false).await
553}
554
555pub async fn rm(path: &std::path::Path, settings: &rm::Settings) -> Result<rm::Summary, rm::Error> {
556 rm::rm(&PROGRESS, path, settings).await
557}
558
559pub async fn chmod(
560 path: &std::path::Path,
561 settings: &chmod::Settings,
562) -> Result<chmod::Summary, chmod::Error> {
563 chmod::chmod(&PROGRESS, path, settings).await
564}
565
566pub async fn link(
567 src: &std::path::Path,
568 dst: &std::path::Path,
569 update: &Option<std::path::PathBuf>,
570 settings: &link::Settings,
571) -> Result<link::Summary, link::Error> {
572 let cwd = std::env::current_dir()
573 .with_context(|| "failed to get current working directory")
574 .map_err(|err| link::Error::new(err, link::Summary::default()))?;
575 link::link(&PROGRESS, &cwd, src, dst, update, settings, false).await
576}
577
578fn render_panel_from_registry() -> String {
579 let entries = observability::registered_histograms();
580 if entries.is_empty() {
581 return String::new();
582 }
583 let snapshots: Vec<hdrhistogram::Histogram<u64>> = entries
584 .iter()
585 .map(|e| (*e.snapshot_rx.borrow()).clone())
586 .collect();
587 let units: Vec<histogram_panel::PanelUnit> = entries
588 .iter()
589 .zip(snapshots.iter())
590 .map(|(e, snap)| histogram_panel::PanelUnit {
591 label: e.label,
592 histogram: snap,
593 interval: e.interval,
594 })
595 .collect();
596 histogram_panel::render_histogram_panel(&units)
597}
598
599#[must_use]
601pub fn format_startup_diagnostic(prefix: Option<&str>, diagnostic: &str) -> String {
602 let one_line = diagnostic
603 .lines()
604 .map(str::trim)
605 .filter(|line| !line.is_empty())
606 .collect::<Vec<_>>()
607 .join(" ");
608 format!("{}{one_line}", prefix.unwrap_or_default())
609}
610
611fn print_startup_configuration_error(
612 startup_error_prefix: Option<&str>,
613 error: &impl std::fmt::Display,
614) {
615 eprintln!(
616 "{}",
617 format_startup_diagnostic(
618 startup_error_prefix,
619 &format!("Configuration error: {error:#}"),
620 )
621 );
622}
623
624#[instrument(skip(func))] pub fn run<Fut, Summary, Error>(
626 progress: Option<ProgressSettings>,
627 output: OutputConfig,
628 runtime_config: RuntimeConfig,
629 throttle_config: ThrottleConfig,
630 tracing_config: TracingConfig,
631 func: impl FnOnce() -> Fut,
632) -> Option<Summary>
633where
635 Summary: std::fmt::Display,
636 Error: std::fmt::Display + std::fmt::Debug,
637 Fut: std::future::Future<Output = Result<Summary, Error>>,
638{
639 let _ = get_progress();
643 let OutputConfig {
644 quiet,
645 verbose,
646 print_summary,
647 suppress_runtime_stats,
648 startup_error_prefix,
649 } = output;
650 if let Err(error) = throttle_config.validate() {
651 print_startup_configuration_error(startup_error_prefix, &error);
652 return None;
653 }
654 let trace_identifier = tracing_config.trace_identifier.clone();
657 if let Err(error) =
658 runtime_setup::validate_histogram_log_target(&throttle_config, &trace_identifier)
659 {
660 print_startup_configuration_error(startup_error_prefix, &error);
661 return None;
662 }
663 let _tracing_guards =
664 match runtime_setup::install_tracing_subscriber(quiet, verbose, tracing_config) {
665 Ok(guards) => guards,
666 Err(error) => {
667 print_startup_configuration_error(startup_error_prefix, &error);
668 return None;
669 }
670 };
671 if let Some(warning) = throttle_config.deprecated_max_open_files_warning() {
672 tracing::warn!(target: NOTICE_TARGET, "{warning}");
673 }
674 let res = {
675 let runtime = match runtime_setup::build_tokio_runtime(&runtime_config, &throttle_config) {
676 Ok(runtime) => runtime,
677 Err(error) => {
678 print_startup_configuration_error(startup_error_prefix, &error);
679 return None;
680 }
681 };
682 runtime_setup::spawn_throttle_replenishers(&runtime, &throttle_config, &trace_identifier);
683 let res = {
684 let _progress_tracker = progress.map(|settings| {
685 tracing::debug!("Requesting progress updates {settings:?}");
686 let delay = settings.progress_delay.map(|delay_str| {
687 humantime::parse_duration(&delay_str)
688 .expect("Couldn't parse duration out of --progress-delay")
689 });
690 ProgressTracker::new(settings.progress_type, delay)
691 });
692 runtime.block_on(func())
693 };
694 match &res {
695 Ok(summary) => {
696 if print_summary || verbose > 0 {
697 println!("{summary}");
698 }
699 }
700 Err(err) => {
701 if !quiet {
702 println!("{err:?}");
703 }
704 }
705 }
706 if (print_summary || verbose > 0)
707 && !suppress_runtime_stats
708 && let Err(err) = runtime_setup::print_runtime_stats()
709 {
710 println!("Failed to print runtime stats: {err:?}");
711 }
712 signal_logger_cancel();
716 if let Some(handle) = take_logger_handle() {
717 let _ = runtime.block_on(async {
720 tokio::time::timeout(std::time::Duration::from_secs(1), handle).await
721 });
722 }
723 runtime.shutdown_timeout(std::time::Duration::from_secs(10));
731 res
732 };
733 reset_process_throttle_state();
738 res.ok()
739}
740
741fn reset_process_throttle_state() {
747 congestion::clear_sample_sink();
748 observability::clear();
749 for &side in &throttle::Side::ALL {
750 for &op in &throttle::MetadataOp::ALL {
751 throttle::set_max_ops_in_flight(throttle::Resource::meta(side, op), 0);
752 }
753 }
754 throttle::disable_ops_throttle();
755 throttle::set_admission_limits(None);
759 throttle::init_iops_tokens(0);
760}