@@ -256,68 +256,107 @@ impl LsColors {
256256 self . style_for_path_with_metadata ( path, metadata. as_ref ( ) )
257257 }
258258
259- /// Get the ANSI style for a path, given the corresponding `Metadata` struct.
260- ///
261- /// *Note:* The `Metadata` struct must have been acquired via `Path::symlink_metadata` in
262- /// order to colorize symbolic links correctly.
263- pub fn style_for_path_with_metadata < P : AsRef < Path > > (
264- & self ,
265- path : P ,
266- metadata : Option < & std:: fs:: Metadata > ,
267- ) -> Option < & Style > {
268- if let Some ( metadata) = metadata {
269- if metadata. is_dir ( ) {
270- return self . style_for_indicator ( Indicator :: Directory ) ;
271- }
259+ /// Check if an indicator has an associated color.
260+ fn has_color_for ( & self , indicator : Indicator ) -> bool {
261+ self . indicator_mapping . contains_key ( & indicator)
262+ }
272263
273- if metadata. file_type ( ) . is_symlink ( ) {
274- // This works because `Path::exists` traverses symlinks.
275- if path. as_ref ( ) . exists ( ) {
276- return self . style_for_indicator ( Indicator :: SymbolicLink ) ;
264+ /// Get the indicator type for a path with corresponding metadata.
265+ fn indicator_for ( & self , path : & Path , metadata : Option < & std:: fs:: Metadata > ) -> Indicator {
266+ if let Some ( metadata) = metadata {
267+ let file_type = metadata. file_type ( ) ;
268+
269+ if file_type. is_file ( ) {
270+ let mode = crate :: fs:: mode ( metadata) ;
271+ let nlink = crate :: fs:: nlink ( metadata) ;
272+
273+ if self . has_color_for ( Indicator :: Setuid ) && mode & 0o4000 != 0 {
274+ Indicator :: Setuid
275+ } else if self . has_color_for ( Indicator :: Setgid ) && mode & 0o2000 != 0 {
276+ Indicator :: Setgid
277+ } else if self . has_color_for ( Indicator :: ExecutableFile ) && mode & 0o0111 != 0 {
278+ Indicator :: ExecutableFile
279+ } else if self . has_color_for ( Indicator :: MultipleHardLinks ) && nlink > 1 {
280+ Indicator :: MultipleHardLinks
277281 } else {
278- return self . style_for_indicator ( Indicator :: OrphanedSymbolicLink ) ;
282+ Indicator :: RegularFile
279283 }
280- }
281-
282- # [ cfg ( unix ) ]
283- {
284- use std :: os :: unix :: fs :: FileTypeExt ;
285-
286- let filetype = metadata . file_type ( ) ;
287- if filetype . is_fifo ( ) {
288- return self . style_for_indicator ( Indicator :: FIFO ) ;
289- }
290- if filetype . is_socket ( ) {
291- return self . style_for_indicator ( Indicator :: Socket ) ;
284+ } else if file_type . is_dir ( ) {
285+ let mode = crate :: fs :: mode ( metadata ) ;
286+
287+ if self . has_color_for ( Indicator :: StickyAndOtherWritable ) && mode & 0o1002 == 0o1002
288+ {
289+ Indicator :: StickyAndOtherWritable
290+ } else if self . has_color_for ( Indicator :: OtherWritable ) && mode & 0o0002 != 0 {
291+ Indicator :: OtherWritable
292+ } else if self . has_color_for ( Indicator :: Sticky ) && mode & 0o1000 != 0 {
293+ Indicator :: Sticky
294+ } else {
295+ Indicator :: Directory
292296 }
293- if filetype. is_block_device ( ) {
294- return self . style_for_indicator ( Indicator :: BlockDevice ) ;
297+ } else if file_type. is_symlink ( ) {
298+ // This works because `Path::exists` traverses symlinks.
299+ if self . has_color_for ( Indicator :: OrphanedSymbolicLink ) && !path. exists ( ) {
300+ return Indicator :: OrphanedSymbolicLink ;
295301 }
296- if filetype. is_char_device ( ) {
297- return self . style_for_indicator ( Indicator :: CharacterDevice ) ;
302+
303+ Indicator :: SymbolicLink
304+ } else {
305+ #[ cfg( unix) ]
306+ {
307+ use std:: os:: unix:: fs:: FileTypeExt ;
308+
309+ if file_type. is_fifo ( ) {
310+ return Indicator :: FIFO ;
311+ }
312+ if file_type. is_socket ( ) {
313+ return Indicator :: Socket ;
314+ }
315+ if file_type. is_block_device ( ) {
316+ return Indicator :: BlockDevice ;
317+ }
318+ if file_type. is_char_device ( ) {
319+ return Indicator :: CharacterDevice ;
320+ }
298321 }
299- }
300322
301- if crate :: fs :: is_executable ( & metadata ) {
302- return self . style_for_indicator ( Indicator :: ExecutableFile ) ;
323+ // Treat files of unknown type as errors
324+ Indicator :: MissingFile
303325 }
326+ } else {
327+ // Default to a regular file, so we still try the suffix map when no metadata is available
328+ Indicator :: RegularFile
304329 }
330+ }
305331
306- // Note: using '.to_str()' here means that filename
307- // matching will not work with invalid-UTF-8 paths.
308- let filename = path. as_ref ( ) . file_name ( ) ?. to_str ( ) ?. to_ascii_lowercase ( ) ;
309-
310- // We need to traverse LS_COLORS from back to front
311- // to be consistent with `ls`:
312- for ( suffix, style) in self . suffix_mapping . iter ( ) . rev ( ) {
313- // Note: For some reason, 'ends_with' is much
314- // slower if we omit `.as_str()` here:
315- if filename. ends_with ( suffix. as_str ( ) ) {
316- return Some ( style) ;
332+ /// Get the ANSI style for a path, given the corresponding `Metadata` struct.
333+ ///
334+ /// *Note:* The `Metadata` struct must have been acquired via `Path::symlink_metadata` in
335+ /// order to colorize symbolic links correctly.
336+ pub fn style_for_path_with_metadata < P : AsRef < Path > > (
337+ & self ,
338+ path : P ,
339+ metadata : Option < & std:: fs:: Metadata > ,
340+ ) -> Option < & Style > {
341+ let indicator = self . indicator_for ( path. as_ref ( ) , metadata) ;
342+
343+ if indicator == Indicator :: RegularFile {
344+ // Note: using '.to_str()' here means that filename
345+ // matching will not work with invalid-UTF-8 paths.
346+ let filename = path. as_ref ( ) . file_name ( ) ?. to_str ( ) ?. to_ascii_lowercase ( ) ;
347+
348+ // We need to traverse LS_COLORS from back to front
349+ // to be consistent with `ls`:
350+ for ( suffix, style) in self . suffix_mapping . iter ( ) . rev ( ) {
351+ // Note: For some reason, 'ends_with' is much
352+ // slower if we omit `.as_str()` here:
353+ if filename. ends_with ( suffix. as_str ( ) ) {
354+ return Some ( style) ;
355+ }
317356 }
318357 }
319358
320- None
359+ self . style_for_indicator ( indicator )
321360 }
322361
323362 /// Get ANSI styles for each component of a given path. Components already include the path
@@ -337,13 +376,27 @@ impl LsColors {
337376 /// For example, the style for `mi` (missing file) falls back to `or` (orphaned symbolic link)
338377 /// if it has not been specified explicitly.
339378 pub fn style_for_indicator ( & self , indicator : Indicator ) -> Option < & Style > {
340- match indicator {
341- Indicator :: MissingFile => self
342- . indicator_mapping
343- . get ( & Indicator :: MissingFile )
344- . or_else ( || self . indicator_mapping . get ( & Indicator :: OrphanedSymbolicLink ) ) ,
345- _ => self . indicator_mapping . get ( & indicator) ,
346- }
379+ self . indicator_mapping
380+ . get ( & indicator)
381+ . or_else ( || {
382+ self . indicator_mapping . get ( & match indicator {
383+ Indicator :: Setuid
384+ | Indicator :: Setgid
385+ | Indicator :: ExecutableFile
386+ | Indicator :: MultipleHardLinks => Indicator :: RegularFile ,
387+
388+ Indicator :: StickyAndOtherWritable
389+ | Indicator :: OtherWritable
390+ | Indicator :: Sticky => Indicator :: Directory ,
391+
392+ Indicator :: OrphanedSymbolicLink => Indicator :: SymbolicLink ,
393+
394+ Indicator :: MissingFile => Indicator :: OrphanedSymbolicLink ,
395+
396+ _ => indicator,
397+ } )
398+ } )
399+ . or_else ( || self . indicator_mapping . get ( & Indicator :: Normal ) )
347400 }
348401}
349402
@@ -512,6 +565,63 @@ mod tests {
512565 assert_eq ! ( Some ( Color :: Yellow ) , style_missing. foreground) ;
513566 }
514567
568+ #[ cfg( unix) ]
569+ #[ test]
570+ fn style_for_setid ( ) {
571+ use std:: fs:: { set_permissions, Permissions } ;
572+ use std:: os:: unix:: fs:: PermissionsExt ;
573+
574+ let tmp_dir = temp_dir ( ) ;
575+ let tmp_file = create_file ( tmp_dir. path ( ) . join ( "setid" ) ) ;
576+ let perms = Permissions :: from_mode ( 0o6750 ) ;
577+ set_permissions ( & tmp_file, perms) . unwrap ( ) ;
578+
579+ let suid_style = get_default_style ( & tmp_file) . unwrap ( ) ;
580+ assert_eq ! ( Some ( Color :: Red ) , suid_style. background) ;
581+
582+ let lscolors = LsColors :: from_string ( "su=0" ) ;
583+ let sgid_style = lscolors. style_for_path ( & tmp_file) . unwrap ( ) ;
584+ assert_eq ! ( Some ( Color :: Yellow ) , sgid_style. background) ;
585+ }
586+
587+ #[ cfg( unix) ]
588+ #[ test]
589+ fn style_for_multi_hard_links ( ) {
590+ let tmp_dir = temp_dir ( ) ;
591+ let tmp_file = create_file ( tmp_dir. path ( ) . join ( "file1" ) ) ;
592+ std:: fs:: hard_link ( & tmp_file, tmp_dir. path ( ) . join ( "file2" ) ) . unwrap ( ) ;
593+
594+ let lscolors = LsColors :: from_string ( "mh=35" ) ;
595+ let style = lscolors. style_for_path ( & tmp_file) . unwrap ( ) ;
596+ assert_eq ! ( Some ( Color :: Magenta ) , style. foreground) ;
597+ }
598+
599+ #[ cfg( unix) ]
600+ #[ test]
601+ fn style_for_sticky_other_writable ( ) {
602+ use std:: fs:: { set_permissions, Permissions } ;
603+ use std:: os:: unix:: fs:: PermissionsExt ;
604+
605+ let tmp_root = temp_dir ( ) ;
606+ let tmp_dir = create_dir ( tmp_root. path ( ) . join ( "test-dir" ) ) ;
607+ let perms = Permissions :: from_mode ( 0o1777 ) ;
608+ set_permissions ( & tmp_dir, perms) . unwrap ( ) ;
609+
610+ let so_style = get_default_style ( & tmp_dir) . unwrap ( ) ;
611+ assert_eq ! ( Some ( Color :: Black ) , so_style. foreground) ;
612+ assert_eq ! ( Some ( Color :: Green ) , so_style. background) ;
613+
614+ let lscolors1 = LsColors :: from_string ( "tw=0" ) ;
615+ let ow_style = lscolors1. style_for_path ( & tmp_dir) . unwrap ( ) ;
616+ assert_eq ! ( Some ( Color :: Blue ) , ow_style. foreground) ;
617+ assert_eq ! ( Some ( Color :: Green ) , ow_style. background) ;
618+
619+ let lscolors2 = LsColors :: from_string ( "tw=0:ow=0" ) ;
620+ let st_style = lscolors2. style_for_path ( & tmp_dir) . unwrap ( ) ;
621+ assert_eq ! ( Some ( Color :: White ) , st_style. foreground) ;
622+ assert_eq ! ( Some ( Color :: Blue ) , st_style. background) ;
623+ }
624+
515625 #[ test]
516626 fn style_for_path_components ( ) {
517627 use std:: ffi:: OsString ;
0 commit comments