-
Notifications
You must be signed in to change notification settings - Fork 7
Add functions to get original file name and loop device info #58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
24f3117
f500303
0048775
37146b5
4729bdf
0714ab2
7aeae6d
38a1f6f
5e3aa8b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,8 +38,9 @@ | |
| //! ld.detach().unwrap(); | ||
| //! ``` | ||
| use crate::bindings::{ | ||
| loop_info64, LOOP_CLR_FD, LOOP_CTL_ADD, LOOP_CTL_GET_FREE, LOOP_SET_CAPACITY, LOOP_SET_FD, | ||
| LOOP_SET_STATUS64, LO_FLAGS_AUTOCLEAR, LO_FLAGS_PARTSCAN, LO_FLAGS_READ_ONLY, | ||
| loop_info64, LOOP_CLR_FD, LOOP_CTL_ADD, LOOP_CTL_GET_FREE, LOOP_GET_STATUS64, | ||
| LOOP_SET_CAPACITY, LOOP_SET_FD, LOOP_SET_STATUS64, LO_FLAGS_AUTOCLEAR, LO_FLAGS_PARTSCAN, | ||
| LO_FLAGS_READ_ONLY, | ||
| }; | ||
| #[cfg(feature = "direct_io")] | ||
| use bindings::LOOP_SET_DIRECT_IO; | ||
|
|
@@ -52,13 +53,17 @@ use std::{ | |
| path::{Path, PathBuf}, | ||
| }; | ||
|
|
||
| use loname::Name; | ||
|
|
||
| #[allow(non_camel_case_types)] | ||
| #[allow(dead_code)] | ||
| #[allow(non_snake_case)] | ||
| mod bindings { | ||
| include!(concat!(env!("OUT_DIR"), "/bindings.rs")); | ||
| } | ||
|
|
||
| mod loname; | ||
|
|
||
| #[cfg(all(not(target_os = "android"), not(target_env = "musl")))] | ||
| type IoctlRequest = libc::c_ulong; | ||
| #[cfg(any(target_os = "android", target_env = "musl"))] | ||
|
|
@@ -243,9 +248,15 @@ impl LoopDevice { | |
| fn attach_with_loop_info( | ||
| &self, // TODO should be mut? - but changing it is a breaking change | ||
| backing_file: impl AsRef<Path>, | ||
| info: loop_info64, | ||
| mut info: loop_info64, | ||
| ) -> io::Result<()> { | ||
| let write_access = (info.lo_flags & LO_FLAGS_READ_ONLY) == 0; | ||
|
|
||
| // store backing file name in the info | ||
| let name = loname::Name::from_path(&backing_file).unwrap_or_default(); | ||
| info.lo_file_name = name.0; | ||
| info.lo_crypt_name = name.0; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would you consider only setting
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't use
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm personally okay with mimicking |
||
|
|
||
| let bf = OpenOptions::new() | ||
| .read(true) | ||
| .write(write_access) | ||
|
|
@@ -312,6 +323,19 @@ impl LoopDevice { | |
| std::fs::read_link(&p).ok() | ||
| } | ||
|
|
||
| /// Try to obtain the original file path used on mapping | ||
| /// The path is expected to be stored in the loop_info64. | ||
| /// The method ignores ioctl errors on requesting info. | ||
| /// | ||
| /// # Return | ||
| /// None - path field is empty, not a valid path or ioctl error was encountered | ||
| /// Some(PathBuf) - otherwise | ||
| pub fn original_path(&self) -> Option<PathBuf> { | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that this has to be replaced by this: but would like to see a thumb-up on this version |
||
| self.info() | ||
| .ok() | ||
| .and_then(|info| Name(info.lo_file_name).try_into().ok()) | ||
| } | ||
|
|
||
| /// Get the device major number | ||
| /// | ||
| /// # Errors | ||
|
|
@@ -390,6 +414,24 @@ impl LoopDevice { | |
| Ok(()) | ||
| } | ||
|
|
||
| /// Obtain loop_info64 struct for the loop device | ||
| /// # Return | ||
| /// Ok(loop_info64) - successfully obtained info | ||
| /// Err(std::io::Error) - error from ioctl | ||
| pub fn info(&self) -> Result<loop_info64, std::io::Error> { | ||
| let mut loop_info = loop_info64::default(); | ||
|
|
||
| let ret_code = unsafe { | ||
| libc::ioctl( | ||
| self.device.as_raw_fd(), | ||
| LOOP_GET_STATUS64 as IoctlRequest, | ||
| &mut loop_info, | ||
| ) | ||
| }; | ||
|
|
||
| ioctl_to_error(ret_code).map(|_| loop_info) | ||
| } | ||
|
|
||
| /// Enable or disable direct I/O for the backing file. | ||
| /// | ||
| /// # Errors | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| //! Configuration structures for loop device | ||
|
|
||
| use std::{ | ||
| ffi::CStr, | ||
| path::{Path, PathBuf}, | ||
| }; | ||
|
|
||
| use crate::bindings::LO_NAME_SIZE; | ||
|
|
||
| /// Loop device name | ||
| #[repr(C)] | ||
| #[derive(Debug)] | ||
| pub struct Name(pub [libc::__u8; LO_NAME_SIZE as usize]); | ||
|
|
||
| /// Allow to construct the config easily | ||
| impl Default for Name { | ||
| fn default() -> Self { | ||
| Self([0; LO_NAME_SIZE as usize]) | ||
| } | ||
| } | ||
|
|
||
| /// Conversions simplifiers | ||
| impl Name { | ||
| pub fn from_path<Y: AsRef<Path>>(path: &Y) -> Result<Self, String> { | ||
| let s = path.as_ref().as_os_str().as_encoded_bytes(); | ||
| if s.len() > LO_NAME_SIZE as usize { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm concerned that this use of
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Generally, I believe that |
||
| return Err(format!( | ||
| "too many bytes in the provided loop dev source file path: {}, max {LO_NAME_SIZE}", | ||
| s.len() | ||
| )); | ||
| } | ||
| let mut data = [0; LO_NAME_SIZE as usize]; | ||
| for (idx, byte) in s.iter().enumerate() { | ||
| data[idx] = *byte; | ||
| } | ||
| Ok(Self(data)) | ||
| } | ||
| } | ||
|
|
||
| impl TryInto<PathBuf> for Name { | ||
| type Error = NameToPathBufError; | ||
| fn try_into(self) -> Result<PathBuf, Self::Error> { | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm exposing possible errors here, but the outer scope supresses it, as I think it's a meaningful action on that level. |
||
| // cut trailing \0 | ||
| let c_str = CStr::from_bytes_until_nul(&self.0) | ||
| .map_err(|_e| Self::Error::FromBytesUntilNulError)?; | ||
| // UTF-8 decode | ||
| let string = c_str.to_str().map_err(|_e| Self::Error::CStrToStr)?; | ||
| if string.is_empty() { | ||
| Err(Self::Error::EmptyName) | ||
| } else { | ||
| Ok(PathBuf::from(string)) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[derive(Debug)] | ||
| pub enum NameToPathBufError { | ||
| EmptyName, | ||
| FromBytesUntilNulError, | ||
| CStrToStr, | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod test { | ||
| use std::path::PathBuf; | ||
|
|
||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn test_name_empty() { | ||
| let name = Name::default(); | ||
| for num in name.0 { | ||
| assert_eq!(0, num); | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_name_from_to() { | ||
| let path = PathBuf::from("/a/b/some-file/かっこいい имя"); | ||
| let name = Name::from_path(&path); | ||
|
|
||
| assert_eq!( | ||
| path, | ||
| <Name as TryInto<PathBuf>>::try_into(name.unwrap()).expect("path should not be empty") | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_name_too_long() { | ||
| let path_string = "/too-long/too-long/too-long/too-long/too-long/too-long/too-long--"; | ||
| let path = PathBuf::from(&path_string); | ||
| let name = Name::from_path(&path); | ||
|
|
||
| assert_eq!( | ||
| "too many bytes in the provided loop dev source file path: 65, max 64", | ||
| name.unwrap_err() | ||
| ) | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_name_is_empty() { | ||
| let name = Name::default(); | ||
|
|
||
| assert!(matches!( | ||
| <Name as TryInto<PathBuf>>::try_into(name), | ||
| Err(NameToPathBufError::EmptyName) | ||
| )); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_name_non_utf8() { | ||
| let name = Name([ | ||
| 'H' as u8, 'e' as u8, 'l' as u8, 'l' as u8, 'o' as u8, 159, 146, 150, 0, 0, 0, 0, 0, 0, | ||
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
| ]); | ||
|
|
||
| assert!(matches!( | ||
| <Name as TryInto<PathBuf>>::try_into(name), | ||
| Err(NameToPathBufError::CStrToStr) | ||
| )); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand the purpose of this but I'm unsure why you need the special Name struct for marshalling and unmarshalling. I feel that it would be better to use existing Path and PathBuf methods,
as_os_str()or something. Can you tell me why the concern w/ null terminators in theto_stringmethod? Thanks.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My concern was compatibility with C code.
I found that kernel actually takes care of the terminator here.
I did a brief check that name setup through
as_os_stris correctly displayed bylosetup -l, but that's not a reliable source, because ...losetup actually leverages
cat /sys/block/loop0/loop/backing_fileon my system. It isn't limited at 64 chars and a way more straight-forward path.I think I need to get rid of
lo_file_namelogic but keepinfoaround for future use.Do you think it's reasonable ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Getting the full name from
/sys/block/loop0/loop/backing_fileisn't that simple, as it requires to know theloop0part, which, in its turn, requires aninfocall. The only real advantage is that it allows to get the full name in all cases.LMK if you want to see the version with
as_os_stranywaysThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I would be for removing some of the more complicated handling that @mulkieran mentioned. I can see something like a length check potentially being necessary, but I believe that if we're doing FFI, it's better to use standard ways of null-encoding the string. Custom logic with null handling has caused me a lot of pain in the past.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see the point of removing the custom logic for handling \0, let me find a way to make it with OsString.
However, I would rather keep the naming helper separated from the rest of the code so it doesn't pollute the
lib.rs, which is nearing the limit of a good code unit.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ended-up with casting
&[u8]->CStr(because it's actually a CStr) ->OsStr(because it's needed to make a PathBuf) ->PathBuf.So, there's no
Tostringanymore, although the code is even more complicated.