|  |  |  | @ -1,10 +1,12 @@ | 
		
	
		
			
				|  |  |  |  | use crate::util::KernelVersion; | 
		
	
		
			
				|  |  |  |  | use libc::pid_t; | 
		
	
		
			
				|  |  |  |  | use std::{ | 
		
	
		
			
				|  |  |  |  |     ffi::{OsStr, OsString}, | 
		
	
		
			
				|  |  |  |  |     fmt::Write as _, | 
		
	
		
			
				|  |  |  |  |     fs::{self, OpenOptions}, | 
		
	
		
			
				|  |  |  |  |     io::{self, Write}, | 
		
	
		
			
				|  |  |  |  |     os::fd::{AsFd as _, AsRawFd as _, OwnedFd}, | 
		
	
		
			
				|  |  |  |  |     path::Path, | 
		
	
		
			
				|  |  |  |  |     path::{Path, PathBuf}, | 
		
	
		
			
				|  |  |  |  |     process, | 
		
	
		
			
				|  |  |  |  |     sync::atomic::{AtomicUsize, Ordering}, | 
		
	
		
			
				|  |  |  |  | }; | 
		
	
	
		
			
				
					|  |  |  | @ -42,16 +44,64 @@ impl ProbeKind { | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | pub(crate) fn lines(bytes: &[u8]) -> impl Iterator<Item = &OsStr> { | 
		
	
		
			
				|  |  |  |  |     use std::os::unix::ffi::OsStrExt as _; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     bytes.as_ref().split(|b| b == &b'\n').map(|mut line| { | 
		
	
		
			
				|  |  |  |  |         while let [stripped @ .., c] = line { | 
		
	
		
			
				|  |  |  |  |             if c.is_ascii_whitespace() { | 
		
	
		
			
				|  |  |  |  |                 line = stripped; | 
		
	
		
			
				|  |  |  |  |                 continue; | 
		
	
		
			
				|  |  |  |  |             } | 
		
	
		
			
				|  |  |  |  |             break; | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |         OsStr::from_bytes(line) | 
		
	
		
			
				|  |  |  |  |     }) | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | pub(crate) trait OsStringExt { | 
		
	
		
			
				|  |  |  |  |     fn starts_with(&self, needle: &OsStr) -> bool; | 
		
	
		
			
				|  |  |  |  |     fn ends_with(&self, needle: &OsStr) -> bool; | 
		
	
		
			
				|  |  |  |  |     fn strip_prefix(&self, prefix: &OsStr) -> Option<&OsStr>; | 
		
	
		
			
				|  |  |  |  |     fn strip_suffix(&self, suffix: &OsStr) -> Option<&OsStr>; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | impl OsStringExt for OsStr { | 
		
	
		
			
				|  |  |  |  |     fn starts_with(&self, needle: &OsStr) -> bool { | 
		
	
		
			
				|  |  |  |  |         use std::os::unix::ffi::OsStrExt as _; | 
		
	
		
			
				|  |  |  |  |         self.as_bytes().starts_with(needle.as_bytes()) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     fn ends_with(&self, needle: &OsStr) -> bool { | 
		
	
		
			
				|  |  |  |  |         use std::os::unix::ffi::OsStrExt as _; | 
		
	
		
			
				|  |  |  |  |         self.as_bytes().ends_with(needle.as_bytes()) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     fn strip_prefix(&self, prefix: &OsStr) -> Option<&OsStr> { | 
		
	
		
			
				|  |  |  |  |         use std::os::unix::ffi::OsStrExt as _; | 
		
	
		
			
				|  |  |  |  |         self.as_bytes() | 
		
	
		
			
				|  |  |  |  |             .strip_prefix(prefix.as_bytes()) | 
		
	
		
			
				|  |  |  |  |             .map(Self::from_bytes) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     fn strip_suffix(&self, suffix: &OsStr) -> Option<&OsStr> { | 
		
	
		
			
				|  |  |  |  |         use std::os::unix::ffi::OsStrExt as _; | 
		
	
		
			
				|  |  |  |  |         self.as_bytes() | 
		
	
		
			
				|  |  |  |  |             .strip_suffix(suffix.as_bytes()) | 
		
	
		
			
				|  |  |  |  |             .map(Self::from_bytes) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | #[derive(Debug)] | 
		
	
		
			
				|  |  |  |  | pub(crate) struct ProbeEvent { | 
		
	
		
			
				|  |  |  |  |     kind: ProbeKind, | 
		
	
		
			
				|  |  |  |  |     event_alias: String, | 
		
	
		
			
				|  |  |  |  |     event_alias: OsString, | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | pub(crate) fn attach<T: Link + From<PerfLinkInner>>( | 
		
	
		
			
				|  |  |  |  |     program_data: &mut ProgramData<T>, | 
		
	
		
			
				|  |  |  |  |     kind: ProbeKind, | 
		
	
		
			
				|  |  |  |  |     fn_name: &str, | 
		
	
		
			
				|  |  |  |  |     fn_name: &Path, | 
		
	
		
			
				|  |  |  |  |     offset: u64, | 
		
	
		
			
				|  |  |  |  |     pid: Option<pid_t>, | 
		
	
		
			
				|  |  |  |  | ) -> Result<T::Id, ProgramError> { | 
		
	
	
		
			
				
					|  |  |  | @ -90,7 +140,7 @@ pub(crate) fn detach_debug_fs(event: ProbeEvent) -> Result<(), ProgramError> { | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | fn create_as_probe( | 
		
	
		
			
				|  |  |  |  |     kind: ProbeKind, | 
		
	
		
			
				|  |  |  |  |     fn_name: &str, | 
		
	
		
			
				|  |  |  |  |     fn_name: &Path, | 
		
	
		
			
				|  |  |  |  |     offset: u64, | 
		
	
		
			
				|  |  |  |  |     pid: Option<pid_t>, | 
		
	
		
			
				|  |  |  |  | ) -> Result<OwnedFd, ProgramError> { | 
		
	
	
		
			
				
					|  |  |  | @ -126,10 +176,10 @@ fn create_as_probe( | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | fn create_as_trace_point( | 
		
	
		
			
				|  |  |  |  |     kind: ProbeKind, | 
		
	
		
			
				|  |  |  |  |     name: &str, | 
		
	
		
			
				|  |  |  |  |     name: &Path, | 
		
	
		
			
				|  |  |  |  |     offset: u64, | 
		
	
		
			
				|  |  |  |  |     pid: Option<pid_t>, | 
		
	
		
			
				|  |  |  |  | ) -> Result<(OwnedFd, String), ProgramError> { | 
		
	
		
			
				|  |  |  |  | ) -> Result<(OwnedFd, OsString), ProgramError> { | 
		
	
		
			
				|  |  |  |  |     use ProbeKind::*; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     let tracefs = find_tracefs_path()?; | 
		
	
	
		
			
				
					|  |  |  | @ -142,7 +192,7 @@ fn create_as_trace_point( | 
		
	
		
			
				|  |  |  |  |     }; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     let category = format!("{}s", kind.pmu()); | 
		
	
		
			
				|  |  |  |  |     let tpid = read_sys_fs_trace_point_id(tracefs, &category, &event_alias)?; | 
		
	
		
			
				|  |  |  |  |     let tpid = read_sys_fs_trace_point_id(tracefs, &category, event_alias.as_ref())?; | 
		
	
		
			
				|  |  |  |  |     let fd = perf_event_open_trace_point(tpid, pid).map_err(|(_code, io_error)| SyscallError { | 
		
	
		
			
				|  |  |  |  |         call: "perf_event_open", | 
		
	
		
			
				|  |  |  |  |         io_error, | 
		
	
	
		
			
				
					|  |  |  | @ -154,9 +204,10 @@ fn create_as_trace_point( | 
		
	
		
			
				|  |  |  |  | fn create_probe_event( | 
		
	
		
			
				|  |  |  |  |     tracefs: &Path, | 
		
	
		
			
				|  |  |  |  |     kind: ProbeKind, | 
		
	
		
			
				|  |  |  |  |     fn_name: &str, | 
		
	
		
			
				|  |  |  |  |     fn_name: &Path, | 
		
	
		
			
				|  |  |  |  |     offset: u64, | 
		
	
		
			
				|  |  |  |  | ) -> Result<String, (String, io::Error)> { | 
		
	
		
			
				|  |  |  |  | ) -> Result<OsString, (PathBuf, io::Error)> { | 
		
	
		
			
				|  |  |  |  |     use std::os::unix::ffi::OsStrExt as _; | 
		
	
		
			
				|  |  |  |  |     use ProbeKind::*; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     let events_file_name = tracefs.join(format!("{}_events", kind.pmu())); | 
		
	
	
		
			
				
					|  |  |  | @ -165,93 +216,129 @@ fn create_probe_event( | 
		
	
		
			
				|  |  |  |  |         KRetProbe | URetProbe => 'r', | 
		
	
		
			
				|  |  |  |  |     }; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     let fixed_fn_name = fn_name.replace(['.', '/', '-'], "_"); | 
		
	
		
			
				|  |  |  |  |     let fn_name = fn_name.as_os_str(); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     let event_alias = format!( | 
		
	
		
			
				|  |  |  |  |         "aya_{}_{}_{}_{:#x}_{}", | 
		
	
		
			
				|  |  |  |  |     let mut event_alias = OsString::new(); | 
		
	
		
			
				|  |  |  |  |     write!( | 
		
	
		
			
				|  |  |  |  |         &mut event_alias, | 
		
	
		
			
				|  |  |  |  |         "aya_{}_{}_", | 
		
	
		
			
				|  |  |  |  |         process::id(), | 
		
	
		
			
				|  |  |  |  |         probe_type_prefix, | 
		
	
		
			
				|  |  |  |  |         fixed_fn_name, | 
		
	
		
			
				|  |  |  |  |     ) | 
		
	
		
			
				|  |  |  |  |     .unwrap(); | 
		
	
		
			
				|  |  |  |  |     for b in fn_name.as_bytes() { | 
		
	
		
			
				|  |  |  |  |         let b = match *b { | 
		
	
		
			
				|  |  |  |  |             b'.' | b'/' | b'-' => b'_', | 
		
	
		
			
				|  |  |  |  |             b => b, | 
		
	
		
			
				|  |  |  |  |         }; | 
		
	
		
			
				|  |  |  |  |         event_alias.push(OsStr::from_bytes(&[b])); | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     write!( | 
		
	
		
			
				|  |  |  |  |         &mut event_alias, | 
		
	
		
			
				|  |  |  |  |         "_{:#x}_{}", | 
		
	
		
			
				|  |  |  |  |         offset, | 
		
	
		
			
				|  |  |  |  |         PROBE_NAME_INDEX.fetch_add(1, Ordering::AcqRel) | 
		
	
		
			
				|  |  |  |  |     ); | 
		
	
		
			
				|  |  |  |  |     let offset_suffix = match kind { | 
		
	
		
			
				|  |  |  |  |         KProbe => format!("+{offset}"), | 
		
	
		
			
				|  |  |  |  |         UProbe | URetProbe => format!(":{offset:#x}"), | 
		
	
		
			
				|  |  |  |  |         _ => String::new(), | 
		
	
		
			
				|  |  |  |  |     ) | 
		
	
		
			
				|  |  |  |  |     .unwrap(); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     let mut probe = OsString::new(); | 
		
	
		
			
				|  |  |  |  |     write!(&mut probe, "{}:{}s/", probe_type_prefix, kind.pmu(),).unwrap(); | 
		
	
		
			
				|  |  |  |  |     probe.push(&event_alias); | 
		
	
		
			
				|  |  |  |  |     probe.push(" "); | 
		
	
		
			
				|  |  |  |  |     probe.push(fn_name); | 
		
	
		
			
				|  |  |  |  |     match kind { | 
		
	
		
			
				|  |  |  |  |         KProbe => write!(&mut probe, "+{offset}").unwrap(), | 
		
	
		
			
				|  |  |  |  |         UProbe | URetProbe => write!(&mut probe, ":{offset:#x}").unwrap(), | 
		
	
		
			
				|  |  |  |  |         _ => {} | 
		
	
		
			
				|  |  |  |  |     }; | 
		
	
		
			
				|  |  |  |  |     let probe = format!( | 
		
	
		
			
				|  |  |  |  |         "{}:{}s/{} {}{}\n", | 
		
	
		
			
				|  |  |  |  |         probe_type_prefix, | 
		
	
		
			
				|  |  |  |  |         kind.pmu(), | 
		
	
		
			
				|  |  |  |  |         event_alias, | 
		
	
		
			
				|  |  |  |  |         fn_name, | 
		
	
		
			
				|  |  |  |  |         offset_suffix | 
		
	
		
			
				|  |  |  |  |     ); | 
		
	
		
			
				|  |  |  |  |     probe.push("\n"); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     let mut events_file = OpenOptions::new() | 
		
	
		
			
				|  |  |  |  |     OpenOptions::new() | 
		
	
		
			
				|  |  |  |  |         .append(true) | 
		
	
		
			
				|  |  |  |  |         .open(&events_file_name) | 
		
	
		
			
				|  |  |  |  |         .map_err(|e| (events_file_name.display().to_string(), e))?; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     events_file | 
		
	
		
			
				|  |  |  |  |         .write_all(probe.as_bytes()) | 
		
	
		
			
				|  |  |  |  |         .map_err(|e| (events_file_name.display().to_string(), e))?; | 
		
	
		
			
				|  |  |  |  |         .and_then(|mut events_file| events_file.write_all(probe.as_bytes())) | 
		
	
		
			
				|  |  |  |  |         .map_err(|e| (events_file_name, e))?; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     Ok(event_alias) | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | fn delete_probe_event(tracefs: &Path, event: ProbeEvent) -> Result<(), (String, io::Error)> { | 
		
	
		
			
				|  |  |  |  | fn delete_probe_event(tracefs: &Path, event: ProbeEvent) -> Result<(), (PathBuf, io::Error)> { | 
		
	
		
			
				|  |  |  |  |     use std::os::unix::ffi::OsStrExt as _; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     let ProbeEvent { kind, event_alias } = event; | 
		
	
		
			
				|  |  |  |  |     let events_file_name = tracefs.join(format!("{}_events", kind.pmu())); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     let events = fs::read_to_string(&events_file_name) | 
		
	
		
			
				|  |  |  |  |         .map_err(|e| (events_file_name.display().to_string(), e))?; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     let found = events.lines().any(|line| line.contains(&event_alias)); | 
		
	
		
			
				|  |  |  |  |     fs::read(&events_file_name) | 
		
	
		
			
				|  |  |  |  |         .and_then(|events| { | 
		
	
		
			
				|  |  |  |  |             let found = lines(&events).any(|line| { | 
		
	
		
			
				|  |  |  |  |                 let mut line = line.as_bytes(); | 
		
	
		
			
				|  |  |  |  |                 // See [`create_probe_event`] and the documentation:
 | 
		
	
		
			
				|  |  |  |  |                 //
 | 
		
	
		
			
				|  |  |  |  |                 // https://docs.kernel.org/trace/kprobetrace.html
 | 
		
	
		
			
				|  |  |  |  |                 //
 | 
		
	
		
			
				|  |  |  |  |                 // https://docs.kernel.org/trace/uprobetracer.html
 | 
		
	
		
			
				|  |  |  |  |                 loop { | 
		
	
		
			
				|  |  |  |  |                     match line.split_first() { | 
		
	
		
			
				|  |  |  |  |                         None => break false, | 
		
	
		
			
				|  |  |  |  |                         Some((b, rest)) => { | 
		
	
		
			
				|  |  |  |  |                             line = rest; | 
		
	
		
			
				|  |  |  |  |                             if *b == b'/' { | 
		
	
		
			
				|  |  |  |  |                                 break line.starts_with(event_alias.as_bytes()); | 
		
	
		
			
				|  |  |  |  |                             } | 
		
	
		
			
				|  |  |  |  |                         } | 
		
	
		
			
				|  |  |  |  |                     } | 
		
	
		
			
				|  |  |  |  |                 } | 
		
	
		
			
				|  |  |  |  |             }); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |             if found { | 
		
	
		
			
				|  |  |  |  |         let mut events_file = OpenOptions::new() | 
		
	
		
			
				|  |  |  |  |                 OpenOptions::new() | 
		
	
		
			
				|  |  |  |  |                     .append(true) | 
		
	
		
			
				|  |  |  |  |                     .open(&events_file_name) | 
		
	
		
			
				|  |  |  |  |             .map_err(|e| (events_file_name.display().to_string(), e))?; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         let rm = format!("-:{event_alias}\n"); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         events_file | 
		
	
		
			
				|  |  |  |  |             .write_all(rm.as_bytes()) | 
		
	
		
			
				|  |  |  |  |             .map_err(|e| (events_file_name.display().to_string(), e))?; | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |                     .and_then(|mut events_file| { | 
		
	
		
			
				|  |  |  |  |                         let mut rm = OsString::new(); | 
		
	
		
			
				|  |  |  |  |                         rm.push("-:"); | 
		
	
		
			
				|  |  |  |  |                         rm.push(event_alias); | 
		
	
		
			
				|  |  |  |  |                         rm.push("\n"); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |                         events_file.write_all(rm.as_bytes()) | 
		
	
		
			
				|  |  |  |  |                     }) | 
		
	
		
			
				|  |  |  |  |             } else { | 
		
	
		
			
				|  |  |  |  |                 Ok(()) | 
		
	
		
			
				|  |  |  |  |             } | 
		
	
		
			
				|  |  |  |  |         }) | 
		
	
		
			
				|  |  |  |  |         .map_err(|e| (events_file_name, e)) | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | fn read_sys_fs_perf_type(pmu: &str) -> Result<u32, (String, io::Error)> { | 
		
	
		
			
				|  |  |  |  |     let file = format!("/sys/bus/event_source/devices/{pmu}/type"); | 
		
	
		
			
				|  |  |  |  | fn read_sys_fs_perf_type(pmu: &str) -> Result<u32, (PathBuf, io::Error)> { | 
		
	
		
			
				|  |  |  |  |     let file = Path::new("/sys/bus/event_source/devices") | 
		
	
		
			
				|  |  |  |  |         .join(pmu) | 
		
	
		
			
				|  |  |  |  |         .join("type"); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     let perf_ty = fs::read_to_string(&file).map_err(|e| (file.clone(), e))?; | 
		
	
		
			
				|  |  |  |  |     let perf_ty = perf_ty | 
		
	
		
			
				|  |  |  |  |     fs::read_to_string(&file) | 
		
	
		
			
				|  |  |  |  |         .and_then(|perf_ty| { | 
		
	
		
			
				|  |  |  |  |             perf_ty | 
		
	
		
			
				|  |  |  |  |                 .trim() | 
		
	
		
			
				|  |  |  |  |                 .parse::<u32>() | 
		
	
		
			
				|  |  |  |  |         .map_err(|e| (file, io::Error::new(io::ErrorKind::Other, e)))?; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     Ok(perf_ty) | 
		
	
		
			
				|  |  |  |  |                 .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) | 
		
	
		
			
				|  |  |  |  |         }) | 
		
	
		
			
				|  |  |  |  |         .map_err(|e| (file, e)) | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | fn read_sys_fs_perf_ret_probe(pmu: &str) -> Result<u32, (String, io::Error)> { | 
		
	
		
			
				|  |  |  |  |     let file = format!("/sys/bus/event_source/devices/{pmu}/format/retprobe"); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     let data = fs::read_to_string(&file).map_err(|e| (file.clone(), e))?; | 
		
	
		
			
				|  |  |  |  | fn read_sys_fs_perf_ret_probe(pmu: &str) -> Result<u32, (PathBuf, io::Error)> { | 
		
	
		
			
				|  |  |  |  |     let file = Path::new("/sys/bus/event_source/devices") | 
		
	
		
			
				|  |  |  |  |         .join(pmu) | 
		
	
		
			
				|  |  |  |  |         .join("format/retprobe"); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     fs::read_to_string(&file) | 
		
	
		
			
				|  |  |  |  |         .and_then(|data| { | 
		
	
		
			
				|  |  |  |  |             let mut parts = data.trim().splitn(2, ':').skip(1); | 
		
	
		
			
				|  |  |  |  |     let config = parts.next().ok_or_else(|| { | 
		
	
		
			
				|  |  |  |  |         ( | 
		
	
		
			
				|  |  |  |  |             file.clone(), | 
		
	
		
			
				|  |  |  |  |             io::Error::new(io::ErrorKind::Other, "invalid format"), | 
		
	
		
			
				|  |  |  |  |         ) | 
		
	
		
			
				|  |  |  |  |     })?; | 
		
	
		
			
				|  |  |  |  |             let config = parts | 
		
	
		
			
				|  |  |  |  |                 .next() | 
		
	
		
			
				|  |  |  |  |                 .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid format"))?; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |             config | 
		
	
		
			
				|  |  |  |  |                 .parse::<u32>() | 
		
	
		
			
				|  |  |  |  |         .map_err(|e| (file, io::Error::new(io::ErrorKind::Other, e))) | 
		
	
		
			
				|  |  |  |  |                 .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) | 
		
	
		
			
				|  |  |  |  |         }) | 
		
	
		
			
				|  |  |  |  |         .map_err(|e| (file, e)) | 
		
	
		
			
				|  |  |  |  | } | 
		
	
	
		
			
				
					|  |  |  | 
 |