Skip to content

Commit 5102de2

Browse files
committed
Improve error messages on mkdir failure
This commit ensures that `fs::create_dir*` isn't called throughout Cargo and is instead routed through our own wrapper `paths::create_dir_all` which brings with it a few benefits: * Gracefully handles when the directory already exists (which is the behavior we always want anyway) * Includes the path name in the error message of what failed * Handles races of creating a directory by default Closes #7304
1 parent 732cc52 commit 5102de2

File tree

14 files changed

+73
-83
lines changed

14 files changed

+73
-83
lines changed

src/cargo/core/compiler/custom_build.rs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use std::collections::hash_map::{Entry, HashMap};
22
use std::collections::{BTreeSet, HashSet};
3-
use std::fs;
43
use std::path::{Path, PathBuf};
54
use std::str;
65
use std::sync::Arc;
@@ -262,8 +261,8 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes
262261
let extra_verbose = bcx.config.extra_verbose();
263262
let (prev_output, prev_script_out_dir) = prev_build_output(cx, unit);
264263

265-
fs::create_dir_all(&script_dir)?;
266-
fs::create_dir_all(&script_out_dir)?;
264+
paths::create_dir_all(&script_dir)?;
265+
paths::create_dir_all(&script_out_dir)?;
267266

268267
// Prepare the unit of "dirty work" which will actually run the custom build
269268
// command.
@@ -275,14 +274,12 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes
275274
//
276275
// If we have an old build directory, then just move it into place,
277276
// otherwise create it!
278-
if fs::metadata(&script_out_dir).is_err() {
279-
fs::create_dir(&script_out_dir).chain_err(|| {
280-
internal(
281-
"failed to create script output directory for \
282-
build command",
283-
)
284-
})?;
285-
}
277+
paths::create_dir_all(&script_out_dir).chain_err(|| {
278+
internal(
279+
"failed to create script output directory for \
280+
build command",
281+
)
282+
})?;
286283

287284
// For all our native lib dependencies, pick up their metadata to pass
288285
// along to this custom build command. We're also careful to augment our
@@ -588,7 +585,7 @@ fn prepare_metabuild<'a, 'cfg>(
588585
output.push("}\n".to_string());
589586
let output = output.join("");
590587
let path = unit.pkg.manifest().metabuild_path(cx.bcx.ws.target_dir());
591-
fs::create_dir_all(path.parent().unwrap())?;
588+
paths::create_dir_all(path.parent().unwrap())?;
592589
paths::write_if_changed(path, &output)?;
593590
Ok(())
594591
}

src/cargo/core/compiler/fingerprint.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,6 @@
189189
190190
use std::collections::hash_map::{Entry, HashMap};
191191
use std::env;
192-
use std::fs;
193192
use std::hash::{self, Hasher};
194193
use std::path::{Path, PathBuf};
195194
use std::sync::{Arc, Mutex};
@@ -1339,7 +1338,7 @@ pub fn prepare_init<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> Ca
13391338

13401339
// Doc tests have no output, thus no fingerprint.
13411340
if !new1.exists() && !unit.mode.is_doc_test() {
1342-
fs::create_dir(&new1)?;
1341+
paths::create_dir_all(&new1)?;
13431342
}
13441343

13451344
Ok(())

src/cargo/core/compiler/layout.rs

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,10 @@
9090
//! When cross-compiling, the layout is the same, except it appears in
9191
//! `target/$TRIPLE`.
9292
93-
use std::fs;
94-
use std::io;
95-
use std::path::{Path, PathBuf};
96-
9793
use crate::core::Workspace;
94+
use crate::util::paths;
9895
use crate::util::{CargoResult, FileLock};
96+
use std::path::{Path, PathBuf};
9997

10098
/// Contains the paths of all target output locations.
10199
///
@@ -183,21 +181,14 @@ impl Layout {
183181
}
184182

185183
/// Makes sure all directories stored in the Layout exist on the filesystem.
186-
pub fn prepare(&mut self) -> io::Result<()> {
187-
mkdir(&self.deps)?;
188-
mkdir(&self.incremental)?;
189-
mkdir(&self.fingerprint)?;
190-
mkdir(&self.examples)?;
191-
mkdir(&self.build)?;
184+
pub fn prepare(&mut self) -> CargoResult<()> {
185+
paths::create_dir_all(&self.deps)?;
186+
paths::create_dir_all(&self.incremental)?;
187+
paths::create_dir_all(&self.fingerprint)?;
188+
paths::create_dir_all(&self.examples)?;
189+
paths::create_dir_all(&self.build)?;
192190

193191
return Ok(());
194-
195-
fn mkdir(dir: &Path) -> io::Result<()> {
196-
if fs::metadata(&dir).is_err() {
197-
fs::create_dir(dir)?;
198-
}
199-
Ok(())
200-
}
201192
}
202193

203194
/// Fetch the destination path for final artifacts (`/…/target/debug`).

src/cargo/core/compiler/mod.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -447,9 +447,7 @@ fn link_targets<'a, 'cfg>(
447447
hardlink_or_copy(src, dst)?;
448448
if let Some(ref path) = output.export_path {
449449
let export_dir = export_dir.as_ref().unwrap();
450-
if !export_dir.exists() {
451-
fs::create_dir_all(export_dir)?;
452-
}
450+
paths::create_dir_all(export_dir)?;
453451

454452
hardlink_or_copy(src, path)?;
455453
}
@@ -628,7 +626,7 @@ fn rustdoc<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoResult
628626
// Create the documentation directory ahead of time as rustdoc currently has
629627
// a bug where concurrent invocations will race to create this directory if
630628
// it doesn't already exist.
631-
fs::create_dir_all(&doc_dir)?;
629+
paths::create_dir_all(&doc_dir)?;
632630

633631
rustdoc.arg("-o").arg(doc_dir);
634632

src/cargo/ops/cargo_install.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ fn install_one(
347347
(Some(tracker), duplicates)
348348
};
349349

350-
fs::create_dir_all(&dst)?;
350+
paths::create_dir_all(&dst)?;
351351

352352
// Copy all binaries to a temporary directory under `dst` first, catching
353353
// some failure modes (e.g., out of space) before touching the existing

src/cargo/ops/cargo_new.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ fn init_vcs(path: &Path, vcs: VersionControl, config: &Config) -> CargoResult<()
513513
// Temporary fix to work around bug in libgit2 when creating a
514514
// directory in the root of a posix filesystem.
515515
// See: https://github.com/libgit2/libgit2/issues/5130
516-
fs::create_dir_all(path)?;
516+
paths::create_dir_all(path)?;
517517
GitRepo::init(path, config.cwd())?;
518518
}
519519
}
@@ -533,7 +533,7 @@ fn init_vcs(path: &Path, vcs: VersionControl, config: &Config) -> CargoResult<()
533533
}
534534
}
535535
VersionControl::NoVcs => {
536-
fs::create_dir_all(path)?;
536+
paths::create_dir_all(path)?;
537537
}
538538
};
539539

@@ -650,7 +650,7 @@ edition = {}
650650
let path_of_source_file = path.join(i.relative_path.clone());
651651

652652
if let Some(src_dir) = path_of_source_file.parent() {
653-
fs::create_dir_all(src_dir)?;
653+
paths::create_dir_all(src_dir)?;
654654
}
655655

656656
let default_file_content: &[u8] = if i.bin {

src/cargo/ops/vendor.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ fn sync(
7575
.map(|p| &**p)
7676
.unwrap_or(opts.destination);
7777

78-
fs::create_dir_all(&canonical_destination)?;
78+
paths::create_dir_all(&canonical_destination)?;
7979
let mut to_remove = HashSet::new();
8080
if !opts.no_delete {
8181
for entry in canonical_destination.read_dir()? {
@@ -322,7 +322,7 @@ fn cp_sources(
322322
.iter()
323323
.fold(dst.to_owned(), |acc, component| acc.join(&component));
324324

325-
fs::create_dir_all(dst.parent().unwrap())?;
325+
paths::create_dir_all(dst.parent().unwrap())?;
326326

327327
fs::copy(&p, &dst)
328328
.chain_err(|| format!("failed to copy `{}` to `{}`", p.display(), dst.display()))?;

src/cargo/sources/git/utils.rs

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
1-
use std::env;
2-
use std::fmt;
3-
use std::fs::{self, File};
4-
use std::mem;
5-
use std::path::{Path, PathBuf};
6-
use std::process::Command;
7-
1+
use crate::core::GitReference;
2+
use crate::util::errors::{CargoResult, CargoResultExt};
3+
use crate::util::paths;
4+
use crate::util::process_builder::process;
5+
use crate::util::{internal, network, Config, IntoUrl, Progress};
86
use curl::easy::{Easy, List};
97
use git2::{self, ObjectType};
108
use log::{debug, info};
119
use serde::ser;
1210
use serde::Serialize;
11+
use std::env;
12+
use std::fmt;
13+
use std::fs::File;
14+
use std::mem;
15+
use std::path::{Path, PathBuf};
16+
use std::process::Command;
1317
use url::Url;
1418

15-
use crate::core::GitReference;
16-
use crate::util::errors::{CargoResult, CargoResultExt};
17-
use crate::util::paths;
18-
use crate::util::process_builder::process;
19-
use crate::util::{internal, network, Config, IntoUrl, Progress};
20-
2119
#[derive(PartialEq, Clone, Debug)]
2220
pub struct GitRevision(git2::Oid);
2321

@@ -145,10 +143,10 @@ impl GitRemote {
145143
}
146144

147145
fn clone_into(&self, dst: &Path, cargo_config: &Config) -> CargoResult<git2::Repository> {
148-
if fs::metadata(&dst).is_ok() {
146+
if dst.exists() {
149147
paths::remove_dir_all(dst)?;
150148
}
151-
fs::create_dir_all(dst)?;
149+
paths::create_dir_all(dst)?;
152150
let mut repo = init(dst, true)?;
153151
fetch(
154152
&mut repo,
@@ -257,8 +255,7 @@ impl<'a> GitCheckout<'a> {
257255
config: &Config,
258256
) -> CargoResult<GitCheckout<'a>> {
259257
let dirname = into.parent().unwrap();
260-
fs::create_dir_all(&dirname)
261-
.chain_err(|| format!("Couldn't mkdir {}", dirname.display()))?;
258+
paths::create_dir_all(&dirname)?;
262259
if into.exists() {
263260
paths::remove_dir_all(into)?;
264261
}

src/cargo/sources/registry/index.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,17 @@
6666
//! details like invalidating caches and whatnot which are handled below, but
6767
//! hopefully those are more obvious inline in the code itself.
6868
69-
use std::collections::{HashMap, HashSet};
70-
use std::fs;
71-
use std::path::Path;
72-
use std::str;
73-
74-
use log::info;
75-
use semver::{Version, VersionReq};
76-
7769
use crate::core::dependency::Dependency;
7870
use crate::core::{InternedString, PackageId, SourceId, Summary};
7971
use crate::sources::registry::{RegistryData, RegistryPackage};
72+
use crate::util::paths;
8073
use crate::util::{internal, CargoResult, Config, Filesystem, ToSemver};
74+
use log::info;
75+
use semver::{Version, VersionReq};
76+
use std::collections::{HashMap, HashSet};
77+
use std::fs;
78+
use std::path::Path;
79+
use std::str;
8180

8281
/// Crates.io treats hyphen and underscores as interchangeable, but the index and old Cargo do not.
8382
/// Therefore, the index must store uncanonicalized version of the name so old Cargo's can find it.
@@ -559,7 +558,7 @@ impl Summaries {
559558
// This is opportunistic so we ignore failure here but are sure to log
560559
// something in case of error.
561560
if let Some(cache_bytes) = cache_bytes {
562-
if fs::create_dir_all(cache_path.parent().unwrap()).is_ok() {
561+
if paths::create_dir_all(cache_path.parent().unwrap()).is_ok() {
563562
let path = Filesystem::new(cache_path.clone());
564563
config.assert_package_cache_locked(&path);
565564
if let Err(e) = fs::write(cache_path, cache_bytes) {

src/cargo/sources/registry/remote.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ use crate::sources::git;
33
use crate::sources::registry::MaybeLock;
44
use crate::sources::registry::{RegistryConfig, RegistryData, CRATE_TEMPLATE, VERSION_TEMPLATE};
55
use crate::util::errors::{CargoResult, CargoResultExt};
6+
use crate::util::paths;
67
use crate::util::{Config, Filesystem, Sha256};
78
use lazycell::LazyCell;
89
use log::{debug, trace};
910
use std::cell::{Cell, Ref, RefCell};
1011
use std::fmt::Write as FmtWrite;
11-
use std::fs::{self, File, OpenOptions};
12+
use std::fs::{File, OpenOptions};
1213
use std::io::prelude::*;
1314
use std::io::SeekFrom;
1415
use std::mem;
@@ -55,8 +56,8 @@ impl<'cfg> RemoteRegistry<'cfg> {
5556
match git2::Repository::open(&path) {
5657
Ok(repo) => Ok(repo),
5758
Err(_) => {
58-
drop(fs::remove_dir_all(&path));
59-
fs::create_dir_all(&path)?;
59+
drop(paths::remove_dir_all(&path));
60+
paths::create_dir_all(&path)?;
6061

6162
// Note that we'd actually prefer to use a bare repository
6263
// here as we're not actually going to check anything out.

src/cargo/util/flock.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::fs::{self, File, OpenOptions};
1+
use std::fs::{File, OpenOptions};
22
use std::io;
33
use std::io::{Read, Seek, SeekFrom, Write};
44
use std::path::{Display, Path, PathBuf};
@@ -149,8 +149,9 @@ impl Filesystem {
149149
///
150150
/// Handles errors where other Cargo processes are also attempting to
151151
/// concurrently create this directory.
152-
pub fn create_dir(&self) -> io::Result<()> {
153-
fs::create_dir_all(&self.root)
152+
pub fn create_dir(&self) -> CargoResult<()> {
153+
paths::create_dir_all(&self.root)?;
154+
Ok(())
154155
}
155156

156157
/// Returns an adaptor that can be used to print the path of this
@@ -221,10 +222,10 @@ impl Filesystem {
221222
.open(&path)
222223
.or_else(|e| {
223224
if e.kind() == io::ErrorKind::NotFound && state == State::Exclusive {
224-
fs::create_dir_all(path.parent().unwrap())?;
225-
opts.open(&path)
225+
paths::create_dir_all(path.parent().unwrap())?;
226+
Ok(opts.open(&path)?)
226227
} else {
227-
Err(e)
228+
Err(failure::Error::from(e))
228229
}
229230
})
230231
.chain_err(|| format!("failed to open: {}", path.display()))?;

src/cargo/util/paths.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,15 @@ impl<'a> Iterator for PathAncestors<'a> {
269269
}
270270
}
271271

272+
pub fn create_dir_all(p: impl AsRef<Path>) -> CargoResult<()> {
273+
_create_dir_all(p.as_ref())
274+
}
275+
276+
fn _create_dir_all(p: &Path) -> CargoResult<()> {
277+
fs::create_dir_all(p).chain_err(|| format!("failed to create directory `{}`", p.display()))?;
278+
Ok(())
279+
}
280+
272281
pub fn remove_dir_all<P: AsRef<Path>>(p: P) -> CargoResult<()> {
273282
_remove_dir_all(p.as_ref())
274283
}

src/cargo/util/vcs.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
use std::fs::create_dir;
2-
use std::path::Path;
3-
4-
use git2;
5-
1+
use crate::util::paths;
62
use crate::util::{process, CargoResult};
3+
use git2;
4+
use std::path::Path;
75

86
// Check if we are in an existing repo. We define that to be true if either:
97
//
@@ -68,7 +66,7 @@ impl PijulRepo {
6866
impl FossilRepo {
6967
pub fn init(path: &Path, cwd: &Path) -> CargoResult<FossilRepo> {
7068
// fossil doesn't create the directory so we'll do that first
71-
create_dir(path)?;
69+
paths::create_dir_all(path)?;
7270

7371
// set up the paths we'll use
7472
let db_fname = ".fossil";

tests/testsuite/out_dir.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ fn out_dir_is_a_file() {
179179
p.cargo("build -Z unstable-options --out-dir out")
180180
.masquerade_as_nightly_cargo()
181181
.with_status(101)
182-
.with_stderr_contains("[ERROR] failed to link or copy [..]")
182+
.with_stderr_contains("[ERROR] failed to create directory [..]")
183183
.run();
184184
}
185185

0 commit comments

Comments
 (0)