1pub mod errors;
2pub mod file_watcher;
3pub mod process_watcher;
4pub mod systeminfo_watcher;
5pub mod telemetry_formatter;
6pub mod telemetry_layer;
7
8pub use errors::{into_fatal, into_recoverable, TelemetryError, WatcherError};
9pub use fuel_telemetry_macros::{new, new_with_watchers, new_with_watchers_and_init};
10pub use telemetry_formatter::TelemetryFormatter;
11pub use telemetry_layer::TelemetryLayer;
12pub use tracing::{debug, error, event, info, span, trace, warn, Level};
13pub use tracing_appender::non_blocking::WorkerGuard;
14
15pub mod prelude {
16 pub use crate::{
17 debug, debug_telemetry, error, error_telemetry, event, info, info_telemetry, span,
18 span_telemetry, trace, trace_telemetry, warn, warn_telemetry, Level, TelemetryLayer,
19 };
20}
21
22pub use tracing as __reexport_tracing;
24pub use tracing_subscriber as __reexport_tracing_subscriber;
25pub use tracing_subscriber::filter::EnvFilter as __reexport_EnvFilter;
26pub use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt as __reexport_tracing_subscriber_SubscriberExt;
27pub use tracing_subscriber::util::SubscriberInitExt as __reexport_SubscriberInitExt;
28pub use tracing_subscriber::Layer as __reexport_Layer;
29
30use dirs::home_dir;
31use libc::{c_int, c_long};
32use nix::{
33 errno::Errno,
34 fcntl::{Flock, FlockArg},
35 sys::stat,
36 unistd::{
37 chdir, close, dup2, fork, getpid, pipe, read, setsid, sysconf, write, ForkResult, Pid,
38 SysconfVar,
39 },
40};
41use std::{
42 env::{current_exe, var, var_os},
43 fs::{create_dir_all, File, OpenOptions},
44 io::{stderr, stdout, Write},
45 os::fd::{AsRawFd, OwnedFd},
46 path::{Path, PathBuf},
47 process::exit,
48 sync::LazyLock,
49};
50
51const FIRST_NON_STDIO_FD: i32 = 3;
54
55const MIN_OPEN_MAX: i32 = 1024;
57
58pub type Result<T> = std::result::Result<T, TelemetryError>;
60
61pub type WatcherResult<T> = std::result::Result<T, WatcherError>;
63
64pub struct EnvSetting {
70 name: &'static str,
72 default: &'static str,
74}
75
76impl EnvSetting {
77 pub fn new(name: &'static str, default: &'static str) -> Self {
87 Self { name, default }
88 }
89
90 pub fn get(&self) -> String {
101 var(self.name).unwrap_or_else(|_| self.default.to_string())
102 }
103}
104
105pub struct TelemetryConfig {
110 fuelup_tmp: String,
112 fuelup_log: String,
114}
115
116pub fn telemetry_config() -> Result<&'static TelemetryConfig> {
126 pub static TELEMETRY_CONFIG: LazyLock<Result<TelemetryConfig>> = LazyLock::new(|| {
129 let fuelup_home_env = EnvSetting {
130 name: "FUELUP_HOME",
131 default: ".fuelup",
132 };
133
134 let fuelup_tmp_env = EnvSetting {
135 name: "FUELUP_TMP",
136 default: "tmp",
137 };
138
139 let fuelup_log_env = EnvSetting {
140 name: "FUELUP_LOG",
141 default: "log",
142 };
143
144 let fuelup_home = var_os(fuelup_home_env.name)
147 .map(PathBuf::from)
148 .or_else(|| home_dir().map(|dir| dir.join(fuelup_home_env.default)))
149 .ok_or(TelemetryError::UnreachableHomeDir)?
150 .into_os_string()
151 .into_string()
152 .map_err(|e| TelemetryError::InvalidHomeDir(e.to_string_lossy().into()))?;
153
154 let fuelup_tmp = var_os(fuelup_tmp_env.name)
157 .unwrap_or_else(|| {
158 PathBuf::from(fuelup_home.clone())
159 .join(fuelup_tmp_env.default)
160 .into_os_string()
161 })
162 .into_string()
163 .map_err(|e| TelemetryError::InvalidTmpDir(e.to_string_lossy().into()))?;
164
165 let fuelup_log = var_os(fuelup_log_env.name)
168 .unwrap_or_else(|| {
169 PathBuf::from(fuelup_home.clone())
170 .join(fuelup_log_env.default)
171 .into_os_string()
172 })
173 .into_string()
174 .map_err(|e| TelemetryError::InvalidLogDir(e.to_string_lossy().into()))?;
175
176 create_dir_all(&fuelup_tmp)?;
178 create_dir_all(&fuelup_log)?;
179
180 Ok(TelemetryConfig {
181 fuelup_tmp,
182 fuelup_log,
183 })
184 });
185
186 TELEMETRY_CONFIG
187 .as_ref()
188 .map_err(|e| TelemetryError::InvalidConfig(e.to_string()))
189}
190
191#[macro_export]
207macro_rules! span_telemetry {
208 ($level:expr, $($arg:tt)*) => {
209 $crate::__reexport_tracing::span!($level, "auto", telemetry = true).in_scope(|| {
210 $crate::__reexport_tracing::event!($level, $($arg)*)
211 })
212 }
213}
214
215#[macro_export]
223macro_rules! error_telemetry {
224 ($($arg:tt)*) => {{
225 span_telemetry!($crate::__reexport_tracing::Level::ERROR, $($arg)*);
226 }}
227}
228
229#[macro_export]
237macro_rules! warn_telemetry {
238 ($($arg:tt)*) => {{
239 span_telemetry!($crate::__reexport_tracing::Level::WARN, $($arg)*);
240 }}
241}
242
243#[macro_export]
251macro_rules! info_telemetry {
252 ($($arg:tt)*) => {{
253 span_telemetry!($crate::__reexport_tracing::Level::INFO, $($arg)*);
254 }}
255}
256
257#[macro_export]
265macro_rules! debug_telemetry {
266 ($($arg:tt)*) => {{
267 span_telemetry!($crate::__reexport_tracing::Level::DEBUG, $($arg)*);
268 }}
269}
270
271#[macro_export]
279macro_rules! trace_telemetry {
280 ($($arg:tt)*) => {{
281 span_telemetry!($crate::__reexport_tracing::Level::TRACE, $($arg)*);
282 }}
283}
284
285pub fn get_process_name() -> String {
287 let mut exe_name = String::from("unknown");
288
289 if let Ok(exe) = current_exe() {
290 if let Some(name) = exe.file_name() {
291 if let Some(name_str) = name.to_str() {
292 exe_name = name_str.to_string().replace(':', "_");
293 }
294 }
295 }
296
297 exe_name
298}
299
300pub(crate) fn enforce_singleton(filename: &Path) -> Result<Flock<File>> {
305 enforce_singleton_with_helpers(filename, &mut DefaultEnforceSingletonHelpers)
306}
307
308fn enforce_singleton_with_helpers(
309 filename: &Path,
310 helpers: &mut impl EnforceSingletonHelpers,
311) -> Result<Flock<File>> {
312 let lockfile = helpers.open(filename)?;
313
314 let lock = match helpers.lock(lockfile) {
315 Ok(lock) => lock,
316 Err((_, Errno::EWOULDBLOCK)) => {
317 helpers.exit(0);
319 }
320 Err((_, e)) => return Err(TelemetryError::from(e)),
321 };
322
323 Ok(lock)
324}
325
326trait EnforceSingletonHelpers {
327 fn open(&self, filename: &Path) -> std::result::Result<File, std::io::Error> {
328 OpenOptions::new().create(true).append(true).open(filename)
329 }
330
331 fn lock(&self, file: File) -> std::result::Result<Flock<File>, (File, Errno)> {
332 Flock::lock(file, FlockArg::LockExclusiveNonblock)
333 }
334
335 fn exit(&self, status: i32) -> ! {
336 exit(status)
337 }
338}
339
340struct DefaultEnforceSingletonHelpers;
341impl EnforceSingletonHelpers for DefaultEnforceSingletonHelpers {}
342
343pub(crate) fn daemonise(log_filename: &PathBuf) -> WatcherResult<Option<Pid>> {
348 let mut helpers = DefaultDaemoniseHelpers;
349 daemonise_with_helpers(log_filename, &mut helpers)
350}
351
352fn daemonise_with_helpers(
353 log_filename: &PathBuf,
354 helpers: &mut impl DaemoniseHelpers,
355) -> WatcherResult<Option<Pid>> {
356 helpers.flush(&mut stdout()).map_err(into_recoverable)?;
361 helpers.flush(&mut stderr()).map_err(into_recoverable)?;
362
363 let (read_fd, write_fd) = helpers.pipe().map_err(into_recoverable)?;
364
365 if helpers.fork().map_err(into_recoverable)?.is_parent() {
367 drop(write_fd);
368
369 let mut pid_bytes = [0u8; std::mem::size_of::<Pid>()];
370 helpers
371 .read_pipe(read_fd, &mut pid_bytes)
372 .map_err(into_recoverable)?;
373
374 return Ok(Some(Pid::from_raw(i32::from_ne_bytes(pid_bytes))));
375 };
376
377 drop(read_fd);
378
379 if helpers.fork().map_err(into_fatal)?.is_parent() {
390 drop(write_fd);
391 exit(0);
392 }
393
394 helpers.setsid().map_err(into_fatal)?;
397
398 if helpers.fork().map_err(into_fatal)?.is_parent() {
401 drop(write_fd);
402 exit(0);
403 }
404
405 let pid = getpid();
406 helpers.write_pipe(write_fd, pid).map_err(into_fatal)?;
407
408 let fuelup_tmp = helpers
411 .telemetry_config()
412 .map_err(into_fatal)?
413 .fuelup_tmp
414 .clone();
415
416 helpers.setup_stdio(
417 Path::new(&fuelup_tmp)
418 .join(log_filename)
419 .to_str()
420 .ok_or(TelemetryError::InvalidLogFile(
421 fuelup_tmp.clone(),
422 log_filename.clone(),
423 ))
424 .map_err(into_fatal)?,
425 )?;
426
427 helpers.chdir(Path::new("/")).map_err(into_fatal)?;
431
432 let max_fd = helpers
436 .sysconf(SysconfVar::OPEN_MAX)
437 .map_err(into_fatal)?
438 .unwrap_or(MIN_OPEN_MAX.into()) as i32;
439
440 for fd in FIRST_NON_STDIO_FD..=max_fd {
441 match helpers.close(fd) {
442 Ok(()) | Err(Errno::EBADF) => {}
443 Err(e) => Err(into_fatal(e))?,
444 }
445 }
446
447 stat::umask(stat::Mode::empty());
449
450 Ok(None)
451}
452
453trait DaemoniseHelpers {
454 fn flush<T: Write + std::os::fd::AsRawFd>(
455 &mut self,
456 stream: &mut T,
457 ) -> std::result::Result<(), std::io::Error> {
458 stream.flush()
459 }
460
461 fn pipe(&mut self) -> nix::Result<(OwnedFd, OwnedFd)> {
462 pipe()
463 }
464
465 fn read_pipe(&mut self, read_fd: OwnedFd, pid_bytes: &mut [u8]) -> nix::Result<usize> {
466 read(read_fd.as_raw_fd(), pid_bytes)
467 }
468
469 fn fork(&mut self) -> nix::Result<ForkResult> {
470 unsafe { fork() }
471 }
472
473 fn setsid(&self) -> nix::Result<Pid> {
474 setsid()
475 }
476
477 fn write_pipe(&mut self, write_fd: OwnedFd, pid: Pid) -> nix::Result<usize> {
478 write(write_fd, &pid.as_raw().to_ne_bytes())
479 }
480
481 fn telemetry_config(&mut self) -> Result<&'static TelemetryConfig> {
482 telemetry_config()
483 }
484
485 fn setup_stdio(&self, log_filename: &str) -> std::result::Result<(), TelemetryError> {
486 setup_stdio(log_filename)
487 }
488
489 fn chdir(&self, path: &Path) -> nix::Result<()> {
490 chdir(path)
491 }
492
493 fn sysconf(&self, var: SysconfVar) -> nix::Result<Option<c_long>> {
494 sysconf(var)
495 }
496
497 fn close(&self, fd: c_int) -> nix::Result<()> {
498 close(fd)
499 }
500}
501
502struct DefaultDaemoniseHelpers;
503impl DaemoniseHelpers for DefaultDaemoniseHelpers {}
504
505trait SetupStdioHelpers {
506 fn create_append(&self, log_filename: &str) -> std::result::Result<File, std::io::Error> {
507 OpenOptions::new()
508 .create(true)
509 .append(true)
510 .open(log_filename)
511 }
512
513 fn dup2(&mut self, fd: c_int, fd2: c_int) -> std::result::Result<c_int, nix::errno::Errno> {
514 dup2(fd, fd2)
515 }
516
517 fn read_write(&self, path: &str) -> std::result::Result<File, std::io::Error> {
518 OpenOptions::new().read(true).write(true).open(path)
519 }
520}
521
522struct DefaultSetupStdioHelpers;
523impl SetupStdioHelpers for DefaultSetupStdioHelpers {}
524
525pub(crate) fn setup_stdio(log_filename: &str) -> std::result::Result<(), TelemetryError> {
530 let mut helpers = DefaultSetupStdioHelpers;
531 setup_stdio_with_helpers(log_filename, &mut helpers)
532}
533
534fn setup_stdio_with_helpers(
535 log_filename: &str,
536 helpers: &mut impl SetupStdioHelpers,
537) -> std::result::Result<(), TelemetryError> {
538 let log_file = helpers.create_append(log_filename)?;
539
540 helpers.dup2(log_file.as_raw_fd(), 2)?;
542
543 let dev_null = helpers.read_write("/dev/null")?;
545
546 helpers.dup2(dev_null.as_raw_fd(), 0)?;
548 helpers.dup2(dev_null.as_raw_fd(), 1)?;
549
550 Ok(())
551}
552
553#[cfg(test)]
554fn setup_fuelup_home() {
555 let tmp_dir = std::env::temp_dir().join(format!("fuelup-test-{}", uuid::Uuid::new_v4()));
556 std::fs::create_dir_all(&tmp_dir).unwrap();
557 std::env::set_var("FUELUP_HOME", tmp_dir.to_str().unwrap());
558}
559
560#[cfg(test)]
561mod env_setting {
562 use super::*;
563 use std::env::set_var;
564
565 #[test]
566 fn unset() {
567 let env_setting = EnvSetting::new("does_not_exist", "default_value");
568 assert_eq!(env_setting.get(), "default_value");
569 }
570
571 #[test]
572 fn set() {
573 set_var("existing_variable", "existing_value");
574
575 let env_setting = EnvSetting::new("existing_variable", "default_value");
576 assert_eq!(env_setting.get(), "existing_value");
577 }
578}
579
580#[cfg(test)]
581mod telemetry_config {
582 use super::*;
583 use rusty_fork::rusty_fork_test;
584 use std::{env::set_var, path::Path};
585
586 rusty_fork_test! {
587 #[test]
588 fn fuelup_all_unset() {
589 let telemetry_config = telemetry_config().unwrap();
590 let fuelup_home = home_dir().unwrap();
591
592 assert_eq!(
593 telemetry_config.fuelup_tmp,
594 fuelup_home.join(".fuelup/tmp").to_str().unwrap()
595 );
596
597 assert_eq!(
598 telemetry_config.fuelup_log,
599 fuelup_home.join(".fuelup/log").to_str().unwrap()
600 );
601
602 assert!(Path::new(&telemetry_config.fuelup_tmp).is_dir());
603 assert!(Path::new(&telemetry_config.fuelup_log).is_dir());
604 }
605
606 #[test]
607 fn fuelup_home_set() {
608 setup_fuelup_home();
609
610 let tempdir = var("FUELUP_HOME").unwrap();
611 let telemetry_config = telemetry_config().unwrap();
612
613 assert_eq!(telemetry_config.fuelup_tmp, format!("{}/tmp", tempdir));
614 assert_eq!(telemetry_config.fuelup_log, format!("{}/log", tempdir));
615
616 assert!(Path::new(&telemetry_config.fuelup_tmp).is_dir());
617 assert!(Path::new(&telemetry_config.fuelup_log).is_dir());
618 }
619
620 #[test]
621 fn fuelup_tmp_set() {
622 let tmpdir = std::env::temp_dir().join(format!("fuelup-test-{}", uuid::Uuid::new_v4()));
623 set_var("FUELUP_TMP", tmpdir.to_str().unwrap());
624 std::fs::create_dir_all(&tmpdir).unwrap();
625
626 let telemetry_config = telemetry_config().unwrap();
627
628 assert_eq!(telemetry_config.fuelup_tmp, tmpdir.to_str().unwrap());
629
630 assert_eq!(
631 telemetry_config.fuelup_log,
632 home_dir().unwrap().join(".fuelup/log").to_str().unwrap()
633 );
634
635 assert!(Path::new(&telemetry_config.fuelup_tmp).is_dir());
636 assert!(Path::new(&telemetry_config.fuelup_log).is_dir());
637 }
638
639 #[test]
640 fn fuelup_log_set() {
641 let tmpdir = std::env::temp_dir().join(format!("fuelup-test-{}", uuid::Uuid::new_v4()));
642 std::fs::create_dir_all(&tmpdir).unwrap();
643 set_var("FUELUP_LOG", tmpdir.to_str().unwrap());
644
645 let telemetry_config = telemetry_config().unwrap();
646
647 assert_eq!(
648 telemetry_config.fuelup_tmp,
649 home_dir().unwrap().join(".fuelup/tmp").to_str().unwrap()
650 );
651
652 assert_eq!(telemetry_config.fuelup_log, tmpdir.to_str().unwrap());
653
654 assert!(Path::new(&telemetry_config.fuelup_tmp).is_dir());
655 assert!(Path::new(&telemetry_config.fuelup_log).is_dir());
656 }
657 }
658}
659
660#[cfg(test)]
661mod enforce_singleton {
662 use super::*;
663 use nix::unistd::ForkResult;
664 use rusty_fork::rusty_fork_test;
665 use std::os::fd::OwnedFd;
666
667 fn setup_lockfile() -> PathBuf {
668 let lockfile = format!("{}/test.lock", telemetry_config().unwrap().fuelup_tmp);
669
670 File::create(&lockfile).unwrap();
671 PathBuf::from(lockfile)
672 }
673
674 rusty_fork_test! {
675 #[test]
676 fn lockfile_open_failed() {
677 struct LockfileOpenFailed;
678
679 impl EnforceSingletonHelpers for LockfileOpenFailed {
680 fn open(&self, _filename: &Path) -> std::result::Result<File, std::io::Error> {
681 Err(std::io::Error::new(
682 std::io::ErrorKind::NotFound,
683 "Mock error",
684 ))
685 }
686 }
687
688 assert_eq!(
689 enforce_singleton_with_helpers(Path::new("test.lock"), &mut LockfileOpenFailed)
690 .err(),
691 Some(TelemetryError::IO("Mock error".to_string()))
692 );
693 }
694
695 #[test]
696 fn flock_ewouldblock() {
697 setup_fuelup_home();
698 let lockfile = setup_lockfile();
699
700 struct FlockEWouldblock {
701 write_fd: OwnedFd,
702 }
703
704 impl EnforceSingletonHelpers for FlockEWouldblock {
705 fn lock(&self, file: File) -> std::result::Result<Flock<File>, (File, Errno)> {
706 Err((file, Errno::EWOULDBLOCK))
707 }
708
709 fn exit(&self, _status: i32) -> ! {
710 let pid = getpid();
712 write(&self.write_fd, &pid.as_raw().to_ne_bytes()).unwrap();
713
714 exit(0);
715 }
716 }
717
718 let (read_fd, write_fd) = pipe().unwrap();
719
720 match unsafe { fork() }.unwrap() {
721 ForkResult::Parent { child } => {
722 drop(write_fd);
723
724 let mut pid_bytes = [0u8; std::mem::size_of::<Pid>()];
725 read(read_fd.as_raw_fd(), &mut pid_bytes).unwrap();
726
727 assert_eq!(pid_bytes, child.as_raw().to_ne_bytes());
728 }
729 ForkResult::Child => {
730 drop(read_fd);
731
732 let mut flock_ewouldblock = FlockEWouldblock { write_fd };
733
734 enforce_singleton_with_helpers(&lockfile, &mut flock_ewouldblock).unwrap();
735
736 exit(99);
738 }
739 }
740 }
741
742 #[test]
743 fn flock_other_error() {
744 setup_fuelup_home();
745 let lockfile = setup_lockfile();
746
747 struct FlockOtherError;
748
749 impl EnforceSingletonHelpers for FlockOtherError {
750 fn lock(&self, file: File) -> std::result::Result<Flock<File>, (File, Errno)> {
751 Err((file, Errno::EOWNERDEAD))
752 }
753 }
754
755 let result = enforce_singleton_with_helpers(&lockfile, &mut FlockOtherError);
756
757 let expected = TelemetryError::Nix(Errno::EOWNERDEAD.to_string());
758 assert_eq!(result.err(), Some(expected));
759 }
760 }
761}
762
763#[cfg(test)]
764mod daemonise {
765 use super::*;
766 use nix::{
767 errno::Errno,
768 sys::wait::{waitpid, WaitStatus},
769 unistd::ForkResult,
770 };
771 use rusty_fork::rusty_fork_test;
772 use std::io::{Error, ErrorKind, Result, Write};
773
774 rusty_fork_test! {
775 #[test]
776 fn stdout_flush_failed() {
777 setup_fuelup_home();
778
779 struct StdoutFlushFailed;
780
781 impl DaemoniseHelpers for StdoutFlushFailed {
782 fn flush<T: Write + std::os::fd::AsRawFd>(&mut self, stream: &mut T) -> Result<()> {
783 assert_eq!(stream.as_raw_fd(), 1);
784 Err(Error::new(ErrorKind::Other, "Error flushing stdout"))
785 }
786 }
787
788 let result = daemonise_with_helpers(&PathBuf::from("test.log"), &mut StdoutFlushFailed);
789 assert!(matches!(result, Err(WatcherError::Recoverable(_))));
790 }
791
792 #[test]
793 fn stderr_flush_failed() {
794 setup_fuelup_home();
795
796 #[derive(Default)]
797 struct StderrFlushFailed {
798 call_counter: usize,
799 }
800
801 impl DaemoniseHelpers for StderrFlushFailed {
802 fn flush<T: Write + std::os::fd::AsRawFd>(&mut self, stream: &mut T) -> Result<()> {
803 self.call_counter += 1;
804
805 if self.call_counter == 1 {
806 Ok(())
807 } else {
808 assert_eq!(stream.as_raw_fd(), 2);
809 Err(Error::new(ErrorKind::Other, "Error flushing stderr"))
810 }
811 }
812 }
813
814 let result = daemonise_with_helpers(
815 &PathBuf::from("test.log"),
816 &mut StderrFlushFailed::default(),
817 );
818
819 assert!(matches!(result, Err(WatcherError::Recoverable(_))));
820 }
821
822 #[test]
823 fn pipe_failed() {
824 setup_fuelup_home();
825
826 struct PipeFailed;
827
828 impl DaemoniseHelpers for PipeFailed {
829 fn pipe(&mut self) -> nix::Result<(OwnedFd, OwnedFd)> {
830 Err(Errno::EOWNERDEAD)
831 }
832 }
833
834 let result = daemonise_with_helpers(&PathBuf::from("test.log"), &mut PipeFailed);
835 let expected_error = TelemetryError::Nix(Errno::EOWNERDEAD.to_string());
836
837 assert_eq!(
838 result.err(),
839 Some(WatcherError::Recoverable(expected_error))
840 );
841 }
842
843 #[test]
844 fn first_fork_failed() {
845 setup_fuelup_home();
846
847 struct FirstForkFailed;
848
849 impl DaemoniseHelpers for FirstForkFailed {
850 fn fork(&mut self) -> nix::Result<ForkResult> {
851 Err(Errno::EOWNERDEAD)
852 }
853 }
854
855 assert_eq!(
856 daemonise_with_helpers(&PathBuf::from("test.log"), &mut FirstForkFailed),
857 Err(WatcherError::Recoverable(TelemetryError::Nix(
858 Errno::EOWNERDEAD.to_string()
859 )))
860 );
861 }
862
863 #[test]
864 fn first_fork_is_parent() {
865 setup_fuelup_home();
866
867 struct FirstForkIsParent;
868
869 impl DaemoniseHelpers for FirstForkIsParent {
870 fn fork(&mut self) -> nix::Result<ForkResult> {
871 Ok(ForkResult::Parent {
872 child: Pid::from_raw(1),
873 })
874 }
875 }
876
877 let result = daemonise_with_helpers(&PathBuf::from("test.log"), &mut FirstForkIsParent);
878 assert!(matches!(result, Ok(Some(_))));
879 }
880
881 #[test]
882 fn second_fork_failed() {
883 setup_fuelup_home();
884
885 #[derive(Default)]
886 struct SecondForkFailed {
887 call_counter: usize,
888 }
889
890 impl DaemoniseHelpers for SecondForkFailed {
891 fn fork(&mut self) -> nix::Result<ForkResult> {
892 self.call_counter += 1;
893
894 if self.call_counter == 2 {
895 Err(Errno::EOWNERDEAD)
896 } else {
897 Ok(ForkResult::Child)
898 }
899 }
900 }
901
902 let result = daemonise_with_helpers(
903 &PathBuf::from("test.log"),
904 &mut SecondForkFailed::default(),
905 );
906
907 assert!(matches!(result, Err(WatcherError::Fatal(_))));
908 }
909
910 #[test]
911 fn second_fork_is_parent() {
912 setup_fuelup_home();
913
914 #[derive(Default)]
915 struct SecondForkIsParent {
916 call_counter: usize,
917 }
918
919 impl DaemoniseHelpers for SecondForkIsParent {
920 fn fork(&mut self) -> nix::Result<ForkResult> {
921 self.call_counter += 1;
922
923 if self.call_counter == 2 {
924 Ok(ForkResult::Parent {
925 child: Pid::from_raw(1),
926 })
927 } else {
928 Ok(ForkResult::Child)
929 }
930 }
931 }
932
933 match unsafe { fork() }.unwrap() {
935 ForkResult::Parent { child } => match waitpid(child, None).unwrap() {
936 WaitStatus::Exited(_, code) => {
937 assert_eq!(code, 0);
938 }
939 _ => panic!("Child did not exit normally"),
940 },
941 ForkResult::Child => {
942 let _ = daemonise_with_helpers(
943 &PathBuf::from("test.log"),
944 &mut SecondForkIsParent::default(),
945 );
946
947 exit(99);
949 }
950 }
951 }
952
953 #[test]
954 fn setsid_failed() {
955 setup_fuelup_home();
956
957 struct SetsidFailed;
958
959 impl DaemoniseHelpers for SetsidFailed {
960 fn setsid(&self) -> nix::Result<Pid> {
961 Err(Errno::EOWNERDEAD)
962 }
963
964 fn fork(&mut self) -> nix::Result<ForkResult> {
966 Ok(ForkResult::Child)
967 }
968 }
969
970 let result = daemonise_with_helpers(&PathBuf::from("test.log"), &mut SetsidFailed);
971
972 let expected_error = TelemetryError::Nix(Errno::EOWNERDEAD.to_string());
973 assert_eq!(result.err(), Some(WatcherError::Fatal(expected_error)));
974 }
975
976 #[test]
977 fn third_fork_failed() {
978 setup_fuelup_home();
979
980 #[derive(Default)]
981 struct ThirdForkFailed {
982 call_counter: usize,
983 }
984
985 impl DaemoniseHelpers for ThirdForkFailed {
986 fn fork(&mut self) -> nix::Result<ForkResult> {
987 self.call_counter += 1;
988
989 if self.call_counter == 3 {
990 Err(Errno::EOWNERDEAD)
991 } else {
992 Ok(ForkResult::Child)
993 }
994 }
995 }
996
997 let result =
998 daemonise_with_helpers(&PathBuf::from("test.log"), &mut ThirdForkFailed::default());
999
1000 let expected_error = TelemetryError::Nix(Errno::EOWNERDEAD.to_string());
1001 assert_eq!(result.err(), Some(WatcherError::Fatal(expected_error)));
1002 }
1003
1004 #[test]
1005 fn third_fork_is_parent() {
1006 setup_fuelup_home();
1007
1008 #[derive(Default)]
1009 struct ThirdForkIsParent {
1010 call_counter: usize,
1011 }
1012
1013 impl DaemoniseHelpers for ThirdForkIsParent {
1014 fn fork(&mut self) -> nix::Result<ForkResult> {
1015 self.call_counter += 1;
1016
1017 if self.call_counter == 3 {
1018 Ok(ForkResult::Parent {
1019 child: Pid::from_raw(1),
1020 })
1021 } else {
1022 Ok(ForkResult::Child)
1023 }
1024 }
1025 }
1026
1027 match unsafe { fork() }.unwrap() {
1029 ForkResult::Parent { child } => match waitpid(child, None).unwrap() {
1030 WaitStatus::Exited(_, code) => {
1031 assert_eq!(code, 0);
1032 }
1033 _ => panic!("Child did not exit normally"),
1034 },
1035 ForkResult::Child => {
1036 let _ = daemonise_with_helpers(
1037 &PathBuf::from("test.log"),
1038 &mut ThirdForkIsParent::default(),
1039 );
1040
1041 exit(99);
1043 }
1044 }
1045 }
1046
1047 #[test]
1048 fn write_pipe_failed() {
1049 setup_fuelup_home();
1050
1051 struct WritePipeFailed;
1052
1053 impl DaemoniseHelpers for WritePipeFailed {
1054 fn write_pipe(&mut self, _write_fd: OwnedFd, _pid: Pid) -> nix::Result<usize> {
1055 Err(Errno::EOWNERDEAD)
1056 }
1057
1058 fn fork(&mut self) -> nix::Result<ForkResult> {
1059 Ok(ForkResult::Child)
1060 }
1061 }
1062
1063 let result = daemonise_with_helpers(&PathBuf::from("test.log"), &mut WritePipeFailed);
1064
1065 let expected_error = TelemetryError::Nix(Errno::EOWNERDEAD.to_string());
1066 assert_eq!(result.err(), Some(WatcherError::Fatal(expected_error)));
1067 }
1068
1069 #[test]
1070 fn telemetry_config_failed() {
1071 setup_fuelup_home();
1072
1073 struct TelemetryConfigFailed;
1074
1075 impl DaemoniseHelpers for TelemetryConfigFailed {
1076 fn telemetry_config(
1077 &mut self,
1078 ) -> std::result::Result<&'static TelemetryConfig, errors::TelemetryError>
1079 {
1080 Err(TelemetryError::Mock)
1081 }
1082
1083 fn fork(&mut self) -> nix::Result<ForkResult> {
1084 let original_parent = getpid();
1087
1088 match unsafe { fork() }.unwrap() {
1089 ForkResult::Parent { child: _child } => Ok(ForkResult::Child),
1090 ForkResult::Child => Ok(ForkResult::Parent {
1091 child: original_parent,
1092 }),
1093 }
1094 }
1095 }
1096
1097 if let Err(e) =
1098 daemonise_with_helpers(&PathBuf::from("test.log"), &mut TelemetryConfigFailed)
1099 {
1100 assert_eq!(e, WatcherError::Fatal(TelemetryError::Mock));
1101 }
1102 }
1103
1104 #[test]
1105 fn setup_stdio_failed() {
1106 setup_fuelup_home();
1107
1108 struct SetupStdioFailed;
1109
1110 impl DaemoniseHelpers for SetupStdioFailed {
1111 fn setup_stdio(
1112 &self,
1113 _log_filename: &str,
1114 ) -> std::result::Result<(), TelemetryError> {
1115 Err(TelemetryError::IO("Error setting up stdio".to_string()))
1116 }
1117
1118 fn fork(&mut self) -> nix::Result<ForkResult> {
1119 let original_parent = getpid();
1122
1123 match unsafe { fork() }.unwrap() {
1124 ForkResult::Parent { child: _child } => Ok(ForkResult::Child),
1125 ForkResult::Child => Ok(ForkResult::Parent {
1126 child: original_parent,
1127 }),
1128 }
1129 }
1130 }
1131
1132 if let Err(e) =
1133 daemonise_with_helpers(&PathBuf::from("test.log"), &mut SetupStdioFailed)
1134 {
1135 let expected_error = TelemetryError::IO("Error setting up stdio".to_string());
1136 assert_eq!(e, WatcherError::Fatal(expected_error));
1137 }
1138 }
1139
1140 #[test]
1141 fn join_failed() {
1142 setup_fuelup_home();
1143
1144 struct JoinFailed;
1145
1146 impl DaemoniseHelpers for JoinFailed {
1147 fn telemetry_config(
1148 &mut self,
1149 ) -> std::result::Result<&'static TelemetryConfig, errors::TelemetryError>
1150 {
1151 pub static _TELEMETRY_CONFIG: LazyLock<Result<TelemetryConfig>> =
1152 LazyLock::new(|| {
1153 Ok(TelemetryConfig {
1154 fuelup_tmp: unsafe { String::from_utf8_unchecked(vec![0xFF]) },
1156 fuelup_log: "".to_string(),
1157 })
1158 });
1159
1160 _TELEMETRY_CONFIG.as_ref().map_err(|_| {
1161 TelemetryError::InvalidConfig("Error getting telemetry config".to_string())
1162 })
1163 }
1164
1165 fn setup_stdio(
1166 &self,
1167 _log_filename: &str,
1168 ) -> std::result::Result<(), TelemetryError> {
1169 Ok(())
1170 }
1171
1172 fn fork(&mut self) -> nix::Result<ForkResult> {
1173 let original_parent = getpid();
1176
1177 match unsafe { fork() }.unwrap() {
1178 ForkResult::Parent { child: _child } => Ok(ForkResult::Child),
1179 ForkResult::Child => Ok(ForkResult::Parent {
1180 child: original_parent,
1181 }),
1182 }
1183 }
1184 }
1185
1186 if let Err(e) = daemonise_with_helpers(&PathBuf::from("test.log"), &mut JoinFailed) {
1187 assert!(matches!(
1188 e,
1189 WatcherError::Fatal(TelemetryError::InvalidLogFile(_, _))
1190 ));
1191 }
1192 }
1193
1194 #[test]
1195 fn chdir_failed() {
1196 setup_fuelup_home();
1197
1198 struct ChdirFailed;
1199
1200 impl DaemoniseHelpers for ChdirFailed {
1201 fn chdir(&self, _path: &Path) -> nix::Result<()> {
1202 Err(Errno::EOWNERDEAD)
1203 }
1204
1205 fn setup_stdio(
1206 &self,
1207 _log_filename: &str,
1208 ) -> std::result::Result<(), TelemetryError> {
1209 Ok(())
1210 }
1211
1212 fn fork(&mut self) -> nix::Result<ForkResult> {
1213 let original_parent = getpid();
1216
1217 match unsafe { fork() }.unwrap() {
1218 ForkResult::Parent { child: _child } => Ok(ForkResult::Child),
1219 ForkResult::Child => Ok(ForkResult::Parent {
1220 child: original_parent,
1221 }),
1222 }
1223 }
1224 }
1225
1226 if let Err(e) = daemonise_with_helpers(&PathBuf::from("test.log"), &mut ChdirFailed) {
1227 assert_eq!(
1228 e,
1229 WatcherError::Fatal(TelemetryError::Nix(Errno::EOWNERDEAD.to_string()))
1230 );
1231 }
1232 }
1233
1234 #[test]
1235 fn sysconf_failed() {
1236 setup_fuelup_home();
1237
1238 struct SysconfFailed;
1239
1240 impl DaemoniseHelpers for SysconfFailed {
1241 fn sysconf(&self, _var: SysconfVar) -> nix::Result<Option<c_long>> {
1242 Err(Errno::EOWNERDEAD)
1243 }
1244
1245 fn setup_stdio(
1246 &self,
1247 _log_filename: &str,
1248 ) -> std::result::Result<(), TelemetryError> {
1249 Ok(())
1250 }
1251
1252 fn fork(&mut self) -> nix::Result<ForkResult> {
1253 let original_parent = getpid();
1256
1257 match unsafe { fork() }.unwrap() {
1258 ForkResult::Parent { child: _child } => Ok(ForkResult::Child),
1259 ForkResult::Child => Ok(ForkResult::Parent {
1260 child: original_parent,
1261 }),
1262 }
1263 }
1264 }
1265
1266 if let Err(e) = daemonise_with_helpers(&PathBuf::from("test.log"), &mut SysconfFailed) {
1267 assert_eq!(
1268 e,
1269 WatcherError::Fatal(TelemetryError::Nix(Errno::EOWNERDEAD.to_string()))
1270 );
1271 }
1272 }
1273
1274 #[test]
1275 fn close_failed_with_ebadf() {
1276 setup_fuelup_home();
1277
1278 struct CloseFailed;
1279
1280 impl DaemoniseHelpers for CloseFailed {
1281 fn close(&self, _fd: c_int) -> nix::Result<()> {
1282 Err(Errno::EBADF)
1283 }
1284
1285 fn setup_stdio(
1286 &self,
1287 _log_filename: &str,
1288 ) -> std::result::Result<(), TelemetryError> {
1289 Ok(())
1290 }
1291
1292 fn fork(&mut self) -> nix::Result<ForkResult> {
1293 let original_parent = getpid();
1296
1297 match unsafe { fork() }.unwrap() {
1298 ForkResult::Parent { child: _child } => Ok(ForkResult::Child),
1299 ForkResult::Child => Ok(ForkResult::Parent {
1300 child: original_parent,
1301 }),
1302 }
1303 }
1304 }
1305
1306 if let Err(e) = daemonise_with_helpers(&PathBuf::from("test.log"), &mut CloseFailed) {
1307 assert_eq!(
1308 e,
1309 WatcherError::Fatal(TelemetryError::Nix(Errno::EBADF.to_string()))
1310 );
1311 }
1312 }
1313
1314 #[test]
1315 fn close_failed_with_other_error() {
1316 setup_fuelup_home();
1317
1318 struct CloseFailed;
1319
1320 impl DaemoniseHelpers for CloseFailed {
1321 fn close(&self, _fd: c_int) -> nix::Result<()> {
1322 Err(Errno::EOWNERDEAD)
1323 }
1324
1325 fn setup_stdio(
1326 &self,
1327 _log_filename: &str,
1328 ) -> std::result::Result<(), TelemetryError> {
1329 Ok(())
1330 }
1331
1332 fn fork(&mut self) -> nix::Result<ForkResult> {
1333 let original_parent = getpid();
1336
1337 match unsafe { fork() }.unwrap() {
1338 ForkResult::Parent { child: _child } => Ok(ForkResult::Child),
1339 ForkResult::Child => Ok(ForkResult::Parent {
1340 child: original_parent,
1341 }),
1342 }
1343 }
1344 }
1345
1346 if let Err(e) = daemonise_with_helpers(&PathBuf::from("test.log"), &mut CloseFailed) {
1347 assert_eq!(
1348 e,
1349 WatcherError::Fatal(TelemetryError::Nix(Errno::EOWNERDEAD.to_string()))
1350 );
1351 }
1352 }
1353
1354 #[test]
1355 fn ok() {
1356 setup_fuelup_home();
1357
1358 struct AOk;
1359
1360 impl DaemoniseHelpers for AOk {
1361 fn setup_stdio(
1362 &self,
1363 _log_filename: &str,
1364 ) -> std::result::Result<(), TelemetryError> {
1365 Ok(())
1366 }
1367
1368 fn fork(&mut self) -> nix::Result<ForkResult> {
1369 let original_parent = getpid();
1372
1373 match unsafe { fork() }.unwrap() {
1374 ForkResult::Parent { child: _child } => Ok(ForkResult::Child),
1375 ForkResult::Child => Ok(ForkResult::Parent {
1376 child: original_parent,
1377 }),
1378 }
1379 }
1380 }
1381
1382 let parent_pid = getpid();
1383 let result = daemonise_with_helpers(&PathBuf::from("test.log"), &mut AOk);
1384
1385 if getpid() == parent_pid {
1387 assert_eq!(result, Ok(None));
1388 }
1389 }
1390 }
1391}
1392
1393#[cfg(test)]
1394mod setup_stdio {
1395 use super::*;
1396 use rusty_fork::rusty_fork_test;
1397
1398 rusty_fork_test! {
1399 #[test]
1400 fn create_append_failed() {
1401 setup_fuelup_home();
1402
1403 struct CreateAppendFailed;
1404
1405 impl SetupStdioHelpers for CreateAppendFailed {
1406 fn create_append(
1407 &self,
1408 _log_filename: &str,
1409 ) -> std::result::Result<File, std::io::Error> {
1410 Err(std::io::Error::new(
1411 std::io::ErrorKind::Other,
1412 "Error creating append",
1413 ))
1414 }
1415 }
1416
1417 let result = setup_stdio_with_helpers(
1418 &format!("{}/test.log", telemetry_config().unwrap().fuelup_log),
1419 &mut CreateAppendFailed,
1420 );
1421
1422 assert!(matches!(result, Err(TelemetryError::IO(_))));
1423 }
1424
1425 #[test]
1426 fn first_dup2_failed() {
1427 setup_fuelup_home();
1428
1429 struct FirstDup2Failed;
1430
1431 impl SetupStdioHelpers for FirstDup2Failed {
1432 fn dup2(
1433 &mut self,
1434 _fd: c_int,
1435 fd2: c_int,
1436 ) -> std::result::Result<c_int, nix::errno::Errno> {
1437 assert_eq!(fd2, 2);
1438 Err(nix::errno::Errno::EOWNERDEAD)
1439 }
1440 }
1441
1442 let result = setup_stdio_with_helpers(
1443 &format!("{}/test.log", telemetry_config().unwrap().fuelup_log),
1444 &mut FirstDup2Failed,
1445 );
1446
1447 assert!(matches!(result, Err(TelemetryError::Nix(_))));
1448 }
1449
1450 #[test]
1451 fn read_write_failed() {
1452 setup_fuelup_home();
1453
1454 struct ReadWriteFailed;
1455
1456 impl SetupStdioHelpers for ReadWriteFailed {
1457 fn read_write(&self, _path: &str) -> std::result::Result<File, std::io::Error> {
1458 Err(std::io::Error::new(
1459 std::io::ErrorKind::Other,
1460 "Error reading write",
1461 ))
1462 }
1463 }
1464
1465 let result = setup_stdio_with_helpers(
1466 &format!("{}/test.log", telemetry_config().unwrap().fuelup_log),
1467 &mut ReadWriteFailed,
1468 );
1469
1470 assert!(matches!(result, Err(TelemetryError::IO(_))));
1471 }
1472
1473 #[test]
1474 fn second_dup2_failed() {
1475 setup_fuelup_home();
1476
1477 #[derive(Default)]
1478 struct SecondDup2Failed {
1479 call_counter: usize,
1480 }
1481
1482 impl SetupStdioHelpers for SecondDup2Failed {
1483 fn dup2(
1484 &mut self,
1485 _fd: c_int,
1486 fd2: c_int,
1487 ) -> std::result::Result<c_int, nix::errno::Errno> {
1488 self.call_counter += 1;
1489
1490 if self.call_counter == 2 {
1491 assert_eq!(fd2, 0);
1492 Err(nix::errno::Errno::EOWNERDEAD)
1493 } else {
1494 Ok(0)
1495 }
1496 }
1497 }
1498
1499 let result = setup_stdio_with_helpers(
1500 &format!("{}/test.log", telemetry_config().unwrap().fuelup_log),
1501 &mut SecondDup2Failed::default(),
1502 );
1503
1504 assert!(matches!(result, Err(TelemetryError::Nix(_))));
1505 }
1506
1507 #[test]
1508 fn third_dup2_failed() {
1509 setup_fuelup_home();
1510
1511 #[derive(Default)]
1512 struct ThirdDup2Failed {
1513 call_counter: usize,
1514 }
1515
1516 impl SetupStdioHelpers for ThirdDup2Failed {
1517 fn dup2(
1518 &mut self,
1519 _fd: c_int,
1520 fd2: c_int,
1521 ) -> std::result::Result<c_int, nix::errno::Errno> {
1522 self.call_counter += 1;
1523
1524 if self.call_counter == 3 {
1525 assert_eq!(fd2, 1);
1526 Err(nix::errno::Errno::EOWNERDEAD)
1527 } else {
1528 Ok(0)
1529 }
1530 }
1531 }
1532
1533 let result = setup_stdio_with_helpers(
1534 &format!("{}/test.log", telemetry_config().unwrap().fuelup_log),
1535 &mut ThirdDup2Failed::default(),
1536 );
1537
1538 assert!(matches!(result, Err(TelemetryError::Nix(_))));
1539 }
1540
1541 #[test]
1542 fn ok() {
1543 setup_fuelup_home();
1544
1545 let result = setup_stdio_with_helpers(
1546 &format!("{}/test.log", telemetry_config().unwrap().fuelup_log),
1547 &mut DefaultSetupStdioHelpers,
1548 );
1549
1550 assert!(matches!(result, Ok(())));
1551 }
1552 }
1553}