Skip to content

Commit bfa76ad

Browse files
committed
refactor(chmod): move from walker to walkdir, simplify the code and add tests
1 parent 71f959c commit bfa76ad

File tree

4 files changed

+69
-47
lines changed

4 files changed

+69
-47
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/uu/chmod/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ path = "src/chmod.rs"
1818
libc = "0.2.42"
1919
uucore = { version=">=0.0.4", package="uucore", path="../../uucore", features=["fs", "mode"] }
2020
uucore_procs = { version=">=0.0.4", package="uucore_procs", path="../../uucore_procs" }
21+
walkdir = "2.2"
2122
walker = "1.0.0"
2223

2324
[[bin]]

src/uu/chmod/src/chmod.rs

Lines changed: 31 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
#[cfg(unix)]
1111
extern crate libc;
12-
extern crate walker;
12+
extern crate walkdir;
1313

1414
#[macro_use]
1515
extern crate uucore;
@@ -20,7 +20,7 @@ use std::path::Path;
2020
use uucore::fs::display_permissions_unix;
2121
#[cfg(not(windows))]
2222
use uucore::mode;
23-
use walker::Walker;
23+
use walkdir::WalkDir;
2424

2525
const NAME: &str = "chmod";
2626
static SUMMARY: &str = "Change the mode of each FILE to MODE.
@@ -150,62 +150,46 @@ impl Chmoder {
150150
for filename in &files {
151151
let filename = &filename[..];
152152
let file = Path::new(filename);
153-
if file.exists() {
154-
if file.is_dir() {
155-
if !self.preserve_root || filename != "/" {
156-
if self.recursive {
157-
let walk_dir = match Walker::new(&file) {
158-
Ok(m) => m,
159-
Err(f) => {
160-
crash!(1, "{}", f.to_string());
161-
}
162-
};
163-
// XXX: here (and elsewhere) we see that this impl will have issues
164-
// with non-UTF-8 filenames. Using OsString won't fix this because
165-
// on Windows OsStrings cannot be built out of non-UTF-8 chars. One
166-
// possible fix is to use CStrings rather than Strings in the args
167-
// to chmod() and chmod_file().
168-
r = self
169-
.chmod(
170-
walk_dir
171-
.filter_map(|x| match x {
172-
Ok(o) => match o.path().into_os_string().to_str() {
173-
Some(s) => Some(s.to_owned()),
174-
None => None,
175-
},
176-
Err(_) => None,
177-
})
178-
.collect(),
179-
)
180-
.and(r);
181-
r = self.chmod_file(&file, filename).and(r);
182-
}
153+
if !file.exists() {
154+
show_error!("no such file or directory '{}'", filename);
155+
return Err(1);
156+
}
157+
if !self.recursive {
158+
r = self.chmod_file(&file).and(r);
159+
} else {
160+
for entry in WalkDir::new(&filename).into_iter().filter_map(|e| e.ok()) {
161+
let file = entry.path();
162+
if file.is_file() {
163+
r = self.chmod_file(&file).and(r);
183164
} else {
184-
show_error!("could not change permissions of directory '{}'", filename);
185-
r = Err(1);
165+
if file.is_dir() {
166+
if !self.preserve_root || filename != "/" {
167+
r = self.chmod_file(&file).and(r);
168+
} else {
169+
show_error!("could not change permissions of directory '{}'", filename);
170+
r = Err(1);
171+
}
172+
} else {
173+
r = self.chmod_file(&file).and(r);
174+
}
186175
}
187-
} else {
188-
r = self.chmod_file(&file, filename).and(r);
189-
}
190-
} else {
191-
show_error!("no such file or directory '{}'", filename);
192-
r = Err(1);
193176
}
194177
}
178+
}
195179

196180
r
197181
}
198182

199183
#[cfg(windows)]
200-
fn chmod_file(&self, file: &Path, name: &str) -> Result<(), i32> {
184+
fn chmod_file(&self, file: &Path) -> Result<(), i32> {
201185
// chmod is useless on Windows
202186
// it doesn't set any permissions at all
203187
// instead it just sets the readonly attribute on the file
204188
Err(0)
205189
}
206190
#[cfg(any(unix, target_os = "redox"))]
207-
fn chmod_file(&self, file: &Path, name: &str) -> Result<(), i32> {
208-
let mut fperm = match fs::metadata(name) {
191+
fn chmod_file(&self, file: &Path) -> Result<(), i32> {
192+
let mut fperm = match fs::metadata(file) {
209193
Ok(meta) => meta.mode() & 0o7777,
210194
Err(err) => {
211195
if !self.quiet {
@@ -215,7 +199,7 @@ impl Chmoder {
215199
}
216200
};
217201
match self.fmode {
218-
Some(mode) => self.change_file(fperm, mode, file, name)?,
202+
Some(mode) => self.change_file(fperm, mode, file)?,
219203
None => {
220204
let cmode_unwrapped = self.cmode.clone().unwrap();
221205
for mode in cmode_unwrapped.split(',') {
@@ -228,7 +212,7 @@ impl Chmoder {
228212
};
229213
match result {
230214
Ok(mode) => {
231-
self.change_file(fperm, mode, file, name)?;
215+
self.change_file(fperm, mode, file)?;
232216
fperm = mode;
233217
}
234218
Err(f) => {
@@ -246,7 +230,7 @@ impl Chmoder {
246230
}
247231

248232
#[cfg(unix)]
249-
fn change_file(&self, fperm: u32, mode: u32, file: &Path, path: &str) -> Result<(), i32> {
233+
fn change_file(&self, fperm: u32, mode: u32, file: &Path) -> Result<(), i32> {
250234
if fperm == mode {
251235
if self.verbose && !self.changes {
252236
show_info!(
@@ -258,7 +242,7 @@ impl Chmoder {
258242
}
259243
Ok(())
260244
} else if let Err(err) =
261-
fs::set_permissions(Path::new(path), fs::Permissions::from_mode(mode))
245+
fs::set_permissions(file, fs::Permissions::from_mode(mode))
262246
{
263247
if !self.quiet {
264248
show_error!("{}", err);

tests/by-util/test_chmod.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,3 +279,39 @@ fn test_chmod_reference_file() {
279279
mkfile(&at.plus_as_string(REFERENCE_FILE), REFERENCE_PERMS);
280280
run_single_test(&tests[0], at, ucmd);
281281
}
282+
283+
284+
#[test]
285+
fn test_chmod_recursive() {
286+
let _guard = UMASK_MUTEX.lock();
287+
288+
let original_umask = unsafe { umask(0) };
289+
let (at, mut ucmd) = at_and_ucmd!();
290+
at.mkdir("a");
291+
at.mkdir("a/b");
292+
at.mkdir("a/b/c");
293+
mkfile(&at.plus_as_string("a/a"), 0o100444);
294+
mkfile(&at.plus_as_string("a/b/b"), 0o100444);
295+
mkfile(&at.plus_as_string("a/b/c/c"), 0o100444);
296+
297+
let result = ucmd.arg("-R").arg("--verbose").arg("-r,a+w").arg("a").succeeds();
298+
assert_eq!(at.metadata("a/a").permissions().mode(), 0o100222);
299+
assert_eq!(at.metadata("a/b/b").permissions().mode(), 0o100222);
300+
assert_eq!(at.metadata("a/b/c/c").permissions().mode(), 0o100222);
301+
println!("mode {:o}", at.metadata("a").permissions().mode());
302+
assert_eq!(at.metadata("a").permissions().mode(), 0o40333);
303+
assert!(result.stderr.contains("to 333 (-wx-wx-wx)"));
304+
assert!(result.stderr.contains("to 222 (-w--w--w-)"));
305+
306+
unsafe {
307+
umask(original_umask);
308+
}
309+
}
310+
311+
#[test]
312+
fn test_chmod_non_existing_file() {
313+
let (_at, mut ucmd) = at_and_ucmd!();
314+
let result = ucmd.arg("-R").arg("--verbose").arg("-r,a+w").arg("dont-exist").fails();
315+
assert_eq!(result.stderr, "chmod: error: no such file or directory 'dont-exist'\n");
316+
317+
}

0 commit comments

Comments
 (0)