99
1010mod common;
1111
12- use self :: common:: error_string;
12+ use self :: common:: { chown_traverse , error_string, ChangeOwnershipArgs } ;
1313use clap:: Parser ;
1414use gettextrs:: { bind_textdomain_codeset, gettext, setlocale, textdomain, LocaleCategory } ;
15- use std:: { cell :: RefCell , ffi:: CString , io, os :: unix :: fs :: MetadataExt } ;
15+ use std:: { ffi:: CString , io} ;
1616
1717/// chgrp - change file group ownership
1818#[ derive( Parser ) ]
1919#[ command( version, about, disable_help_flag = true ) ]
2020struct Args {
21- #[ arg( long, action = clap:: ArgAction :: HelpLong ) ] // Bec. help clashes with -h
22- help : Option < bool > ,
23-
24- /// Change symbolic links, rather than the files they point to
25- #[ arg( short = 'h' , long, default_value_t = false ) ]
26- no_derereference : bool ,
27-
28- /// Follow command line symlinks during -R recursion
29- #[ arg( short = 'H' , overrides_with_all = [ "follow_cli" , "follow_symlinks" , "follow_none" ] ) ]
30- follow_cli : bool ,
31-
32- /// Follow symlinks during -R recursion
33- #[ arg( short = 'L' , overrides_with_all = [ "follow_cli" , "follow_symlinks" , "follow_none" ] ) ]
34- follow_symlinks : bool ,
35-
36- /// Never follow symlinks during -R recursion
37- #[ arg( short = 'P' , overrides_with_all = [ "follow_cli" , "follow_symlinks" , "follow_none" ] , default_value_t = true ) ]
38- follow_none : bool ,
39-
40- /// Recursively change groups of directories and their contents
41- #[ arg( short, short_alias = 'R' , long) ]
42- recurse : bool ,
21+ #[ command( flatten) ]
22+ delegate : ChangeOwnershipArgs ,
4323
4424 /// A group name from the group database or a numeric group ID
4525 group : String ,
@@ -48,89 +28,6 @@ struct Args {
4828 files : Vec < String > ,
4929}
5030
51- fn chgrp_file ( filename : & str , gid : Option < u32 > , args : & Args ) -> bool {
52- let recurse = args. recurse ;
53- let no_derereference = args. no_derereference ;
54-
55- let terminate = RefCell :: new ( false ) ;
56-
57- ftw:: traverse_directory (
58- filename,
59- |entry| {
60- if * terminate. borrow ( ) {
61- return Ok ( false ) ;
62- }
63-
64- let md = entry. metadata ( ) . unwrap ( ) ;
65-
66- // According to the spec:
67- // "The user ID of the file shall be used as the owner argument."
68- let uid = md. uid ( ) ;
69-
70- // Don't change the group ID if the group argument is empty
71- let gid = gid. unwrap_or ( libc:: gid_t:: MAX ) ;
72-
73- let ret = unsafe {
74- libc:: fchownat (
75- entry. dir_fd ( ) ,
76- entry. file_name ( ) . as_ptr ( ) ,
77- uid,
78- gid,
79- // Default is to change the file that the symbolic link points to unless the
80- // -h flag is specified.
81- if no_derereference {
82- libc:: AT_SYMLINK_NOFOLLOW
83- } else {
84- 0
85- } ,
86- )
87- } ;
88- if ret != 0 {
89- let e = io:: Error :: last_os_error ( ) ;
90- let err_str = match e. kind ( ) {
91- io:: ErrorKind :: PermissionDenied => {
92- gettext ! ( "cannot access '{}': {}" , entry. path( ) , error_string( & e) )
93- }
94- _ => {
95- gettext ! ( "changing group of '{}': {}" , entry. path( ) , error_string( & e) )
96- }
97- } ;
98- eprintln ! ( "chgrp: {}" , err_str) ;
99- * terminate. borrow_mut ( ) = true ;
100- return Err ( ( ) ) ;
101- }
102-
103- Ok ( recurse)
104- } ,
105- |_| Ok ( ( ) ) , // Do nothing on `postprocess_dir`
106- |entry, error| {
107- let e = error. inner ( ) ;
108- let err_str = match e. kind ( ) {
109- io:: ErrorKind :: PermissionDenied => {
110- gettext ! (
111- "cannot read directory '{}': {}" ,
112- entry. path( ) ,
113- error_string( & e)
114- )
115- }
116- _ => {
117- gettext ! ( "changing group of '{}': {}" , entry. path( ) , error_string( & e) )
118- }
119- } ;
120- eprintln ! ( "chgrp: {}" , err_str) ;
121- * terminate. borrow_mut ( ) = true ;
122- } ,
123- ftw:: TraverseDirectoryOpts {
124- follow_symlinks_on_args : args. follow_cli ,
125- follow_symlinks : args. follow_symlinks ,
126- ..Default :: default ( )
127- } ,
128- ) ;
129-
130- let failed = * terminate. borrow ( ) ;
131- !failed
132- }
133-
13431// lookup string group by name, or parse numeric group ID
13532fn parse_group ( group : & str ) -> Result < Option < u32 > , String > {
13633 // empty strings are accepted without errors
@@ -155,13 +52,37 @@ fn parse_group(group: &str) -> Result<Option<u32>, String> {
15552 }
15653}
15754
55+ fn err_handler ( e : io:: Error , path : ftw:: DisplayablePath ) {
56+ let err_str = match e. kind ( ) {
57+ io:: ErrorKind :: PermissionDenied => {
58+ gettext ! ( "cannot read directory '{}': {}" , path, error_string( & e) )
59+ }
60+ _ => {
61+ gettext ! ( "changing group of '{}': {}" , path, error_string( & e) )
62+ }
63+ } ;
64+ eprintln ! ( "chgrp: {}" , err_str) ;
65+ }
66+
67+ fn chown_err_handler ( e : io:: Error , path : ftw:: DisplayablePath ) {
68+ let err_str = match e. kind ( ) {
69+ io:: ErrorKind :: PermissionDenied => {
70+ gettext ! ( "cannot access '{}': {}" , path, error_string( & e) )
71+ }
72+ _ => {
73+ gettext ! ( "changing group of '{}': {}" , path, error_string( & e) )
74+ }
75+ } ;
76+ eprintln ! ( "chgrp: {}" , err_str) ;
77+ }
78+
15879fn main ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
15980 // parse command line arguments
16081 let mut args = Args :: parse ( ) ;
16182
16283 // Enable `no_derereference` if `-R` is enabled without either `-H` or `-L`
163- if args. recurse && !( args. follow_cli || args. follow_symlinks ) {
164- args. no_derereference = true ;
84+ if args. delegate . recurse && !( args. delegate . follow_cli || args. delegate . follow_symlinks ) {
85+ args. delegate . no_dereference = true ;
16586 }
16687
16788 // initialize translations
@@ -182,7 +103,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
182103
183104 // apply the group to each file
184105 for filename in & args. files {
185- let success = chgrp_file ( filename, gid, & args) ;
106+ let success = chown_traverse (
107+ filename,
108+ None ,
109+ gid,
110+ & args. delegate ,
111+ err_handler,
112+ chown_err_handler,
113+ ) ;
186114 if !success {
187115 exit_code = 1 ;
188116 }
0 commit comments