Skip to content

Commit b02315a

Browse files
sylvestrecakebaker
andcommitted
chmod dedup some code
Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com>
1 parent 78d3604 commit b02315a

File tree

2 files changed

+121
-144
lines changed

2 files changed

+121
-144
lines changed

src/uu/chmod/src/chmod.rs

Lines changed: 117 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,104 @@ struct Chmoder {
269269
}
270270

271271
impl Chmoder {
272+
/// Calculate the new mode based on the current mode and the chmod specification.
273+
/// Returns (`new_mode`, `naively_expected_new_mode`) for symbolic modes, or (`new_mode`, `new_mode`) for numeric/reference modes.
274+
fn calculate_new_mode(&self, current_mode: u32, is_dir: bool) -> UResult<(u32, u32)> {
275+
match self.fmode {
276+
Some(mode) => Ok((mode, mode)),
277+
None => {
278+
let cmode_unwrapped = self.cmode.clone().unwrap();
279+
let mut new_mode = current_mode;
280+
let mut naively_expected_new_mode = current_mode;
281+
282+
for mode in cmode_unwrapped.split(',') {
283+
let result = if mode.chars().any(|c| c.is_ascii_digit()) {
284+
mode::parse_numeric(new_mode, mode, is_dir).map(|v| (v, v))
285+
} else {
286+
mode::parse_symbolic(new_mode, mode, mode::get_umask(), is_dir).map(|m| {
287+
// calculate the new mode as if umask was 0
288+
let naive_mode =
289+
mode::parse_symbolic(naively_expected_new_mode, mode, 0, is_dir)
290+
.unwrap(); // we know that mode must be valid, so this cannot fail
291+
(m, naive_mode)
292+
})
293+
};
294+
295+
match result {
296+
Ok((mode, naive_mode)) => {
297+
new_mode = mode;
298+
naively_expected_new_mode = naive_mode;
299+
}
300+
Err(f) => {
301+
return if self.quiet {
302+
Err(ExitCode::new(1))
303+
} else {
304+
Err(USimpleError::new(1, f))
305+
};
306+
}
307+
}
308+
}
309+
Ok((new_mode, naively_expected_new_mode))
310+
}
311+
}
312+
}
313+
314+
/// Report permission changes based on verbose and changes flags
315+
fn report_permission_change(&self, file_path: &Path, old_mode: u32, new_mode: u32) {
316+
if self.verbose || self.changes {
317+
let current_permissions = display_permissions_unix(old_mode as mode_t, false);
318+
let new_permissions = display_permissions_unix(new_mode as mode_t, false);
319+
320+
if new_mode != old_mode {
321+
println!(
322+
"mode of {} changed from {:04o} ({}) to {:04o} ({})",
323+
file_path.quote(),
324+
old_mode,
325+
current_permissions,
326+
new_mode,
327+
new_permissions
328+
);
329+
} else if self.verbose {
330+
println!(
331+
"mode of {} retained as {:04o} ({})",
332+
file_path.quote(),
333+
old_mode,
334+
current_permissions
335+
);
336+
}
337+
}
338+
}
339+
340+
/// Handle symlinks during directory traversal based on traversal mode
341+
#[cfg(not(target_os = "linux"))]
342+
fn handle_symlink_during_traversal(
343+
&self,
344+
path: &Path,
345+
is_command_line_arg: bool,
346+
) -> UResult<()> {
347+
let should_follow_symlink = match self.traverse_symlinks {
348+
TraverseSymlinks::All => true,
349+
TraverseSymlinks::First => is_command_line_arg,
350+
TraverseSymlinks::None => false,
351+
};
352+
353+
if !should_follow_symlink {
354+
return self.chmod_file_internal(path, false);
355+
}
356+
357+
match fs::metadata(path) {
358+
Ok(meta) if meta.is_dir() => self.walk_dir_with_context(path, false),
359+
Ok(_) => {
360+
// It's a file symlink, chmod it
361+
self.chmod_file(path)
362+
}
363+
Err(_) => {
364+
// Dangling symlink, chmod it without dereferencing
365+
self.chmod_file_internal(path, false)
366+
}
367+
}
368+
}
369+
272370
fn chmod(&self, files: &[OsString]) -> UResult<()> {
273371
let mut r = Ok(());
274372

@@ -371,9 +469,7 @@ impl Chmoder {
371469
if (!file_path.is_symlink() || should_follow_symlink) && file_path.is_dir() {
372470
match DirFd::open(file_path) {
373471
Ok(dir_fd) => {
374-
r = self
375-
.safe_traverse_dir(&dir_fd, file_path, is_command_line_arg)
376-
.and(r);
472+
r = self.safe_traverse_dir(&dir_fd, file_path).and(r);
377473
}
378474
Err(err) => {
379475
// Handle permission denied errors with proper file path context
@@ -392,21 +488,10 @@ impl Chmoder {
392488
}
393489

394490
#[cfg(target_os = "linux")]
395-
fn safe_traverse_dir(
396-
&self,
397-
dir_fd: &DirFd,
398-
dir_path: &Path,
399-
_is_command_line_arg: bool,
400-
) -> UResult<()> {
491+
fn safe_traverse_dir(&self, dir_fd: &DirFd, dir_path: &Path) -> UResult<()> {
401492
let mut r = Ok(());
402493

403-
// Read directory entries
404-
let entries = match dir_fd.read_dir() {
405-
Ok(entries) => entries,
406-
Err(e) => {
407-
return Err(e.into());
408-
}
409-
};
494+
let entries = dir_fd.read_dir()?;
410495

411496
for entry_name in entries {
412497
let entry_path = dir_path.join(&entry_name);
@@ -489,35 +574,8 @@ impl Chmoder {
489574
entry_name: &std::ffi::OsStr,
490575
current_mode: u32,
491576
) -> UResult<()> {
492-
// Determine the new permissions to apply
493-
let new_mode = match self.fmode {
494-
Some(mode) => mode,
495-
None => {
496-
let cmode_unwrapped = self.cmode.clone().unwrap();
497-
let mut new_mode = current_mode;
498-
for mode in cmode_unwrapped.split(',') {
499-
let result = if mode.chars().any(|c| c.is_ascii_digit()) {
500-
mode::parse_numeric(new_mode, mode, file_path.is_dir())
501-
} else {
502-
mode::parse_symbolic(new_mode, mode, mode::get_umask(), file_path.is_dir())
503-
};
504-
505-
match result {
506-
Ok(parsed_mode) => {
507-
new_mode = parsed_mode;
508-
}
509-
Err(f) => {
510-
return if self.quiet {
511-
Err(ExitCode::new(1))
512-
} else {
513-
Err(USimpleError::new(1, f))
514-
};
515-
}
516-
}
517-
}
518-
new_mode
519-
}
520-
};
577+
// Calculate the new mode using the helper method
578+
let (new_mode, _) = self.calculate_new_mode(current_mode, file_path.is_dir())?;
521579

522580
// Use safe traversal to change the mode
523581
let follow_symlinks = self.dereference;
@@ -534,67 +592,24 @@ impl Chmoder {
534592
);
535593
}
536594

537-
// Report the change if verbose or changes mode
538-
if self.verbose || self.changes {
539-
let current_permissions = display_permissions_unix(current_mode, false);
540-
let new_permissions = display_permissions_unix(new_mode, false);
541-
if new_mode != current_mode {
542-
if self.changes || self.verbose {
543-
println!(
544-
"mode of {} changed from {} ({:04o}) to {} ({:04o})",
545-
file_path.quote(),
546-
current_permissions,
547-
current_mode,
548-
new_permissions,
549-
new_mode
550-
);
551-
}
552-
} else if self.verbose {
553-
println!(
554-
"mode of {} retained as {} ({:04o})",
555-
file_path.quote(),
556-
current_permissions,
557-
current_mode
558-
);
559-
}
560-
}
595+
// Report the change using the helper method
596+
self.report_permission_change(file_path, current_mode, new_mode);
561597

562598
Ok(())
563599
}
564600

565601
#[cfg(not(target_os = "linux"))]
566602
fn handle_symlink_during_recursion(&self, path: &Path) -> UResult<()> {
567-
// During recursion, determine behavior based on traversal mode
568-
match self.traverse_symlinks {
569-
TraverseSymlinks::All => {
570-
// Follow all symlinks during recursion
571-
// Check if the symlink target is a directory, but handle dangling symlinks gracefully
572-
match fs::metadata(path) {
573-
Ok(meta) if meta.is_dir() => self.walk_dir_with_context(path, false),
574-
Ok(_) => {
575-
// It's a file symlink, chmod it
576-
self.chmod_file(path)
577-
}
578-
Err(_) => {
579-
// Dangling symlink, chmod it without dereferencing
580-
self.chmod_file_internal(path, false)
581-
}
582-
}
583-
}
584-
TraverseSymlinks::First | TraverseSymlinks::None => {
585-
// Don't follow symlinks encountered during recursion
586-
// For these symlinks, don't dereference them even if dereference is normally true
587-
self.chmod_file_internal(path, false)
588-
}
589-
}
603+
// Use the common symlink handling logic
604+
self.handle_symlink_during_traversal(path, false)
590605
}
591606

592607
fn chmod_file(&self, file: &Path) -> UResult<()> {
593608
self.chmod_file_internal(file, self.dereference)
594609
}
595610

596611
fn chmod_file_internal(&self, file: &Path, dereference: bool) -> UResult<()> {
597-
use uucore::{mode::get_umask, perms::get_metadata};
612+
use uucore::perms::get_metadata;
598613

599614
let metadata = get_metadata(file, dereference);
600615

@@ -620,45 +635,14 @@ impl Chmoder {
620635
}
621636
};
622637

623-
// Determine the new permissions to apply
638+
// Calculate the new mode using the helper method
639+
let (new_mode, naively_expected_new_mode) =
640+
self.calculate_new_mode(fperm, file.is_dir())?;
641+
642+
// Determine how to apply the permissions
624643
match self.fmode {
625644
Some(mode) => self.change_file(fperm, mode, file)?,
626645
None => {
627-
let cmode_unwrapped = self.cmode.clone().unwrap();
628-
let mut new_mode = fperm;
629-
let mut naively_expected_new_mode = new_mode;
630-
for mode in cmode_unwrapped.split(',') {
631-
let result = if mode.chars().any(|c| c.is_ascii_digit()) {
632-
mode::parse_numeric(new_mode, mode, file.is_dir()).map(|v| (v, v))
633-
} else {
634-
mode::parse_symbolic(new_mode, mode, get_umask(), file.is_dir()).map(|m| {
635-
// calculate the new mode as if umask was 0
636-
let naive_mode = mode::parse_symbolic(
637-
naively_expected_new_mode,
638-
mode,
639-
0,
640-
file.is_dir(),
641-
)
642-
.unwrap(); // we know that mode must be valid, so this cannot fail
643-
(m, naive_mode)
644-
})
645-
};
646-
647-
match result {
648-
Ok((mode, naive_mode)) => {
649-
new_mode = mode;
650-
naively_expected_new_mode = naive_mode;
651-
}
652-
Err(f) => {
653-
return if self.quiet {
654-
Err(ExitCode::new(1))
655-
} else {
656-
Err(USimpleError::new(1, f))
657-
};
658-
}
659-
}
660-
}
661-
662646
// Special handling for symlinks when not dereferencing
663647
if file.is_symlink() && !dereference {
664648
// TODO: On most Unix systems, symlink permissions are ignored by the kernel,
@@ -690,13 +674,8 @@ impl Chmoder {
690674

691675
fn change_file(&self, fperm: u32, mode: u32, file: &Path) -> Result<(), i32> {
692676
if fperm == mode {
693-
if self.verbose && !self.changes {
694-
println!(
695-
"mode of {} retained as {fperm:04o} ({})",
696-
file.quote(),
697-
display_permissions_unix(fperm as mode_t, false),
698-
);
699-
}
677+
// Use the helper method for consistent reporting
678+
self.report_permission_change(file, fperm, mode);
700679
Ok(())
701680
} else if let Err(err) = fs::set_permissions(file, fs::Permissions::from_mode(mode)) {
702681
if !self.quiet {
@@ -712,14 +691,8 @@ impl Chmoder {
712691
}
713692
Err(1)
714693
} else {
715-
if self.verbose || self.changes {
716-
println!(
717-
"mode of {} changed from {fperm:04o} ({}) to {mode:04o} ({})",
718-
file.quote(),
719-
display_permissions_unix(fperm as mode_t, false),
720-
display_permissions_unix(mode as mode_t, false)
721-
);
722-
}
694+
// Use the helper method for consistent reporting
695+
self.report_permission_change(file, fperm, mode);
723696
Ok(())
724697
}
725698
}

tests/by-util/test_chmod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,10 @@ fn test_chmod_recursive() {
391391
make_file(&at.plus_as_string("a/b/b"), 0o100444);
392392
make_file(&at.plus_as_string("a/b/c/c"), 0o100444);
393393
make_file(&at.plus_as_string("z/y"), 0o100444);
394+
#[cfg(not(target_os = "linux"))]
395+
let err_msg = "chmod: Permission denied\n";
396+
#[cfg(target_os = "linux")]
397+
let err_msg = "chmod: 'z': Permission denied\n";
394398

395399
// only the permissions of folder `a` and `z` are changed
396400
// folder can't be read after read permission is removed

0 commit comments

Comments
 (0)