@@ -111,9 +111,56 @@ fn pad_and_print(result: &str, left: bool, width: usize, padding: Padding) {
111111 }
112112}
113113
114+ /// Pads and prints raw bytes (Unix-specific) or falls back to string printing
115+ ///
116+ /// On Unix systems, this preserves non-UTF8 data by printing raw bytes
117+ /// On other platforms, falls back to lossy string conversion
118+ fn pad_and_print_bytes < W : Write > (
119+ mut writer : W ,
120+ bytes : & [ u8 ] ,
121+ left : bool ,
122+ width : usize ,
123+ precision : Precision ,
124+ ) -> Result < ( ) , std:: io:: Error > {
125+ let display_bytes = match precision {
126+ Precision :: Number ( p) if p < bytes. len ( ) => & bytes[ ..p] ,
127+ _ => bytes,
128+ } ;
129+
130+ let display_len = display_bytes. len ( ) ;
131+ let padding_needed = width. saturating_sub ( display_len) ;
132+
133+ let ( left_pad, right_pad) = if left {
134+ ( 0 , padding_needed)
135+ } else {
136+ ( padding_needed, 0 )
137+ } ;
138+
139+ if left_pad > 0 {
140+ print_padding ( & mut writer, left_pad) ?;
141+ }
142+ writer. write_all ( display_bytes) ?;
143+ if right_pad > 0 {
144+ print_padding ( & mut writer, right_pad) ?;
145+ }
146+
147+ Ok ( ( ) )
148+ }
149+
150+ /// print padding based on a writer W and n size
151+ /// writer is genric to be any buffer like: `std::io::stdout`
152+ /// n is the calculated padding size
153+ fn print_padding < W : Write > ( writer : & mut W , n : usize ) -> Result < ( ) , std:: io:: Error > {
154+ for _ in 0 ..n {
155+ writer. write_all ( b" " ) ?;
156+ }
157+ Ok ( ( ) )
158+ }
159+
114160#[ derive( Debug ) ]
115- pub enum OutputType {
161+ pub enum OutputType < ' a > {
116162 Str ( String ) ,
163+ OsStr ( & ' a OsString ) ,
117164 Integer ( i64 ) ,
118165 Unsigned ( u64 ) ,
119166 UnsignedHex ( u64 ) ,
@@ -306,6 +353,7 @@ fn print_it(output: &OutputType, flags: Flags, width: usize, precision: Precisio
306353
307354 match output {
308355 OutputType :: Str ( s) => print_str ( s, & flags, width, precision) ,
356+ OutputType :: OsStr ( s) => print_os_str ( s, & flags, width, precision) ,
309357 OutputType :: Integer ( num) => print_integer ( * num, & flags, width, precision, padding_char) ,
310358 OutputType :: Unsigned ( num) => print_unsigned ( * num, & flags, width, precision, padding_char) ,
311359 OutputType :: UnsignedOct ( num) => {
@@ -354,6 +402,37 @@ fn print_str(s: &str, flags: &Flags, width: usize, precision: Precision) {
354402 pad_and_print ( s, flags. left , width, Padding :: Space ) ;
355403}
356404
405+ /// Prints a `OsString` value based on the provided flags, width, and precision.
406+ /// for unix it converts it to bytes then tries to print it if failed print the lossy string version
407+ /// for windows, `OsString` uses UTF-16 internally which doesn't map directly to bytes like Unix,
408+ /// so we fall back to lossy string conversion to handle invalid UTF-8 sequences gracefully
409+ ///
410+ /// # Arguments
411+ ///
412+ /// * `s` - The `OsString` to be printed.
413+ /// * `flags` - A reference to the Flags struct containing formatting flags.
414+ /// * `width` - The width of the field for the printed string.
415+ /// * `precision` - How many digits of precision, if any.
416+ fn print_os_str ( s : & OsString , flags : & Flags , width : usize , precision : Precision ) {
417+ #[ cfg( unix) ]
418+ {
419+ use std:: os:: unix:: ffi:: OsStrExt ;
420+
421+ let bytes = s. as_bytes ( ) ;
422+
423+ if pad_and_print_bytes ( std:: io:: stdout ( ) , bytes, flags. left , width, precision) . is_err ( ) {
424+ // if an error occurred while trying to print bytes fall back to normal lossy string so it can be printed
425+ let fallback_string = s. to_string_lossy ( ) ;
426+ print_str ( & fallback_string, flags, width, precision) ;
427+ }
428+ }
429+ #[ cfg( not( unix) ) ]
430+ {
431+ let lossy_string = s. to_string_lossy ( ) ;
432+ print_str ( & lossy_string, flags, width, precision) ;
433+ }
434+ }
435+
357436fn quote_file_name ( file_name : & str , quoting_style : & QuotingStyle ) -> String {
358437 match quoting_style {
359438 QuotingStyle :: Locale | QuotingStyle :: Shell => {
@@ -890,16 +969,12 @@ impl Stater {
890969 } )
891970 }
892971
893- fn find_mount_point < P : AsRef < Path > > ( & self , p : P ) -> Option < String > {
972+ fn find_mount_point < P : AsRef < Path > > ( & self , p : P ) -> Option < & OsString > {
894973 let path = p. as_ref ( ) . canonicalize ( ) . ok ( ) ?;
895-
896- for root in self . mount_list . as_ref ( ) ? {
897- if path. starts_with ( root) {
898- // TODO: This is probably wrong, we should pass the OsString
899- return Some ( root. to_string_lossy ( ) . into_owned ( ) ) ;
900- }
901- }
902- None
974+ self . mount_list
975+ . as_ref ( ) ?
976+ . iter ( )
977+ . find ( |root| path. starts_with ( root) )
903978 }
904979
905980 fn exec ( & self ) -> i32 {
@@ -993,8 +1068,11 @@ impl Stater {
9931068 'h' => OutputType :: Unsigned ( meta. nlink ( ) ) ,
9941069 // inode number
9951070 'i' => OutputType :: Unsigned ( meta. ino ( ) ) ,
996- // mount point: TODO: This should be an OsStr
997- 'm' => OutputType :: Str ( self . find_mount_point ( file) . unwrap ( ) ) ,
1071+ // mount point
1072+ 'm' => match self . find_mount_point ( file) {
1073+ Some ( s) => OutputType :: OsStr ( s) ,
1074+ None => OutputType :: Str ( String :: new ( ) ) ,
1075+ } ,
9981076 // file name
9991077 'n' => OutputType :: Str ( display_name. to_string ( ) ) ,
10001078 // quoted file name with dereference if symbolic link
@@ -1300,6 +1378,8 @@ fn pretty_time(meta: &Metadata, md_time_field: MetadataTimeField) -> String {
13001378
13011379#[ cfg( test) ]
13021380mod tests {
1381+ use crate :: { pad_and_print_bytes, print_padding} ;
1382+
13031383 use super :: { Flags , Precision , ScanUtil , Stater , Token , group_num, precision_trunc} ;
13041384
13051385 #[ test]
@@ -1421,4 +1501,32 @@ mod tests {
14211501 assert_eq ! ( precision_trunc( 123.456 , Precision :: Number ( 4 ) ) , "123.4560" ) ;
14221502 assert_eq ! ( precision_trunc( 123.456 , Precision :: Number ( 5 ) ) , "123.45600" ) ;
14231503 }
1504+
1505+ #[ test]
1506+ fn test_pad_and_print_bytes ( ) {
1507+ // testing non-utf8 with normal settings
1508+ let mut buffer = Vec :: new ( ) ;
1509+ let bytes = b"\x80 \xFF \x80 " ;
1510+ pad_and_print_bytes ( & mut buffer, bytes, false , 3 , Precision :: NotSpecified ) . unwrap ( ) ;
1511+ assert_eq ! ( & buffer, b"\x80 \xFF \x80 " ) ;
1512+
1513+ // testing left padding
1514+ let mut buffer = Vec :: new ( ) ;
1515+ let bytes = b"\x80 \xFF \x80 " ;
1516+ pad_and_print_bytes ( & mut buffer, bytes, false , 5 , Precision :: NotSpecified ) . unwrap ( ) ;
1517+ assert_eq ! ( & buffer, b" \x80 \xFF \x80 " ) ;
1518+
1519+ // testing right padding
1520+ let mut buffer = Vec :: new ( ) ;
1521+ let bytes = b"\x80 \xFF \x80 " ;
1522+ pad_and_print_bytes ( & mut buffer, bytes, true , 5 , Precision :: NotSpecified ) . unwrap ( ) ;
1523+ assert_eq ! ( & buffer, b"\x80 \xFF \x80 " ) ;
1524+ }
1525+
1526+ #[ test]
1527+ fn test_print_padding ( ) {
1528+ let mut buffer = Vec :: new ( ) ;
1529+ print_padding ( & mut buffer, 5 ) . unwrap ( ) ;
1530+ assert_eq ! ( & buffer, b" " ) ;
1531+ }
14241532}
0 commit comments