From 668b400ff2fa5634f575e54f40ab1f0b78fcdf16 Mon Sep 17 00:00:00 2001 From: Feng Yu Date: Mon, 11 Oct 2021 21:21:18 +0800 Subject: [PATCH] feat(runtime): improve error messages of runtime fs (#11984) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit annotates errors returned from FS Deno APIs to include paths that were passed to the API calls. Co-authored-by: Bartek IwaƄczuk --- cli/tests/unit/chmod_test.ts | 24 ++- cli/tests/unit/chown_test.ts | 20 +- cli/tests/unit/copy_file_test.ts | 20 +- cli/tests/unit/dir_test.ts | 10 +- cli/tests/unit/files_test.ts | 34 +++- cli/tests/unit/link_test.ts | 83 ++++++++- cli/tests/unit/mkdir_test.ts | 20 +- cli/tests/unit/read_dir_test.ts | 30 ++- cli/tests/unit/read_link_test.ts | 20 +- cli/tests/unit/remove_test.ts | 32 +++- cli/tests/unit/rename_test.ts | 14 ++ cli/tests/unit/stat_test.ts | 34 +++- cli/tests/unit/symlink_test.ts | 31 ++++ cli/tests/unit/truncate_test.ts | 28 +++ cli/tests/unit/utime_test.ts | 20 +- runtime/errors.rs | 2 +- runtime/ops/fs.rs | 302 ++++++++++++++++++++++++------- 17 files changed, 589 insertions(+), 135 deletions(-) diff --git a/cli/tests/unit/chmod_test.ts b/cli/tests/unit/chmod_test.ts index 8d73a3ba43..2ab4c5b0cc 100644 --- a/cli/tests/unit/chmod_test.ts +++ b/cli/tests/unit/chmod_test.ts @@ -82,10 +82,14 @@ unitTest( ); unitTest({ permissions: { write: true } }, function chmodSyncFailure() { - assertThrows(() => { - const filename = "/badfile.txt"; - Deno.chmodSync(filename, 0o777); - }, Deno.errors.NotFound); + const filename = "/badfile.txt"; + assertThrows( + () => { + Deno.chmodSync(filename, 0o777); + }, + Deno.errors.NotFound, + `chmod '${filename}'`, + ); }); unitTest({ permissions: { write: false } }, function chmodSyncPerm() { @@ -170,10 +174,14 @@ unitTest( ); unitTest({ permissions: { write: true } }, async function chmodFailure() { - await assertRejects(async () => { - const filename = "/badfile.txt"; - await Deno.chmod(filename, 0o777); - }, Deno.errors.NotFound); + const filename = "/badfile.txt"; + await assertRejects( + async () => { + await Deno.chmod(filename, 0o777); + }, + Deno.errors.NotFound, + `chmod '${filename}'`, + ); }); unitTest({ permissions: { write: false } }, async function chmodPerm() { diff --git a/cli/tests/unit/chown_test.ts b/cli/tests/unit/chown_test.ts index 60cb45959c..82335a7aff 100644 --- a/cli/tests/unit/chown_test.ts +++ b/cli/tests/unit/chown_test.ts @@ -48,9 +48,13 @@ unitTest( const { uid, gid } = await getUidAndGid(); const filePath = Deno.makeTempDirSync() + "/chown_test_file.txt"; - assertThrows(() => { - Deno.chownSync(filePath, uid, gid); - }, Deno.errors.NotFound); + assertThrows( + () => { + Deno.chownSync(filePath, uid, gid); + }, + Deno.errors.NotFound, + `chown '${filePath}'`, + ); }, ); @@ -63,9 +67,13 @@ unitTest( const { uid, gid } = await getUidAndGid(); const filePath = (await Deno.makeTempDir()) + "/chown_test_file.txt"; - await assertRejects(async () => { - await Deno.chown(filePath, uid, gid); - }, Deno.errors.NotFound); + await assertRejects( + async () => { + await Deno.chown(filePath, uid, gid); + }, + Deno.errors.NotFound, + `chown '${filePath}'`, + ); }, ); diff --git a/cli/tests/unit/copy_file_test.ts b/cli/tests/unit/copy_file_test.ts index cc2699bd37..f232efba85 100644 --- a/cli/tests/unit/copy_file_test.ts +++ b/cli/tests/unit/copy_file_test.ts @@ -72,9 +72,13 @@ unitTest( const fromFilename = tempDir + "/from.txt"; const toFilename = tempDir + "/to.txt"; // We skip initial writing here, from.txt does not exist - assertThrows(() => { - Deno.copyFileSync(fromFilename, toFilename); - }, Deno.errors.NotFound); + assertThrows( + () => { + Deno.copyFileSync(fromFilename, toFilename); + }, + Deno.errors.NotFound, + `copy '${fromFilename}' -> '${toFilename}'`, + ); Deno.removeSync(tempDir, { recursive: true }); }, @@ -162,9 +166,13 @@ unitTest( const fromFilename = tempDir + "/from.txt"; const toFilename = tempDir + "/to.txt"; // We skip initial writing here, from.txt does not exist - await assertRejects(async () => { - await Deno.copyFile(fromFilename, toFilename); - }, Deno.errors.NotFound); + await assertRejects( + async () => { + await Deno.copyFile(fromFilename, toFilename); + }, + Deno.errors.NotFound, + `copy '${fromFilename}' -> '${toFilename}'`, + ); Deno.removeSync(tempDir, { recursive: true }); }, diff --git a/cli/tests/unit/dir_test.ts b/cli/tests/unit/dir_test.ts index fdcab5ddfe..bcddb744a8 100644 --- a/cli/tests/unit/dir_test.ts +++ b/cli/tests/unit/dir_test.ts @@ -52,8 +52,12 @@ unitTest( { permissions: { read: true, write: true } }, function dirChdirError() { const path = Deno.makeTempDirSync() + "test"; - assertThrows(() => { - Deno.chdir(path); - }, Deno.errors.NotFound); + assertThrows( + () => { + Deno.chdir(path); + }, + Deno.errors.NotFound, + `chdir '${path}'`, + ); }, ); diff --git a/cli/tests/unit/files_test.ts b/cli/tests/unit/files_test.ts index 410c89de9a..9f5812d325 100644 --- a/cli/tests/unit/files_test.ts +++ b/cli/tests/unit/files_test.ts @@ -2,7 +2,13 @@ // deno-lint-ignore-file no-deprecated-deno-api -import { assert, assertEquals, assertRejects, unitTest } from "./test_util.ts"; +import { + assert, + assertEquals, + assertRejects, + assertThrows, + unitTest, +} from "./test_util.ts"; import { copy } from "../../../test_util/std/io/util.ts"; unitTest(function filesStdioFileDescriptors() { @@ -361,6 +367,32 @@ unitTest( }, ); +unitTest( + { permissions: { write: true, read: true } }, + async function openNotFound() { + await assertRejects( + async () => { + await Deno.open("bad_file_name"); + }, + Deno.errors.NotFound, + `open 'bad_file_name'`, + ); + }, +); + +unitTest( + { permissions: { write: true, read: true } }, + function openSyncNotFound() { + assertThrows( + () => { + Deno.openSync("bad_file_name"); + }, + Deno.errors.NotFound, + `open 'bad_file_name'`, + ); + }, +); + unitTest( { permissions: { read: true, write: true } }, async function createFile() { diff --git a/cli/tests/unit/link_test.ts b/cli/tests/unit/link_test.ts index 1688b53f1f..fdd7d2fe82 100644 --- a/cli/tests/unit/link_test.ts +++ b/cli/tests/unit/link_test.ts @@ -1,5 +1,11 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -import { assert, assertEquals, assertThrows, unitTest } from "./test_util.ts"; +import { + assert, + assertEquals, + assertRejects, + assertThrows, + unitTest, +} from "./test_util.ts"; unitTest( { permissions: { read: true, write: true } }, @@ -50,9 +56,13 @@ unitTest( // newname is already created. Deno.writeFileSync(newName, new TextEncoder().encode("newName")); - assertThrows(() => { - Deno.linkSync(oldName, newName); - }, Deno.errors.AlreadyExists); + assertThrows( + () => { + Deno.linkSync(oldName, newName); + }, + Deno.errors.AlreadyExists, + `link '${oldName}' -> '${newName}'`, + ); }, ); @@ -63,9 +73,13 @@ unitTest( const oldName = testDir + "/oldname"; const newName = testDir + "/newname"; - assertThrows(() => { - Deno.linkSync(oldName, newName); - }, Deno.errors.NotFound); + assertThrows( + () => { + Deno.linkSync(oldName, newName); + }, + Deno.errors.NotFound, + `link '${oldName}' -> '${newName}'`, + ); }, ); @@ -125,3 +139,58 @@ unitTest( ); }, ); + +unitTest( + { permissions: { read: true, write: true } }, + async function linkExists() { + const testDir = Deno.makeTempDirSync(); + const oldName = testDir + "/oldname"; + const newName = testDir + "/newname"; + Deno.writeFileSync(oldName, new TextEncoder().encode("oldName")); + // newname is already created. + Deno.writeFileSync(newName, new TextEncoder().encode("newName")); + + await assertRejects( + async () => { + await Deno.link(oldName, newName); + }, + Deno.errors.AlreadyExists, + `link '${oldName}' -> '${newName}'`, + ); + }, +); + +unitTest( + { permissions: { read: true, write: true } }, + async function linkNotFound() { + const testDir = Deno.makeTempDirSync(); + const oldName = testDir + "/oldname"; + const newName = testDir + "/newname"; + + await assertRejects( + async () => { + await Deno.link(oldName, newName); + }, + Deno.errors.NotFound, + `link '${oldName}' -> '${newName}'`, + ); + }, +); + +unitTest( + { permissions: { read: false, write: true } }, + async function linkReadPerm() { + await assertRejects(async () => { + await Deno.link("oldbaddir", "newbaddir"); + }, Deno.errors.PermissionDenied); + }, +); + +unitTest( + { permissions: { read: true, write: false } }, + async function linkWritePerm() { + await assertRejects(async () => { + await Deno.link("oldbaddir", "newbaddir"); + }, Deno.errors.PermissionDenied); + }, +); diff --git a/cli/tests/unit/mkdir_test.ts b/cli/tests/unit/mkdir_test.ts index 2e84ed06f6..b06a1cd4cf 100644 --- a/cli/tests/unit/mkdir_test.ts +++ b/cli/tests/unit/mkdir_test.ts @@ -59,15 +59,23 @@ unitTest( ); unitTest({ permissions: { write: true } }, function mkdirErrSyncIfExists() { - assertThrows(() => { - Deno.mkdirSync("."); - }, Deno.errors.AlreadyExists); + assertThrows( + () => { + Deno.mkdirSync("."); + }, + Deno.errors.AlreadyExists, + `mkdir '.'`, + ); }); unitTest({ permissions: { write: true } }, async function mkdirErrIfExists() { - await assertRejects(async () => { - await Deno.mkdir("."); - }, Deno.errors.AlreadyExists); + await assertRejects( + async () => { + await Deno.mkdir("."); + }, + Deno.errors.AlreadyExists, + `mkdir '.'`, + ); }); unitTest( diff --git a/cli/tests/unit/read_dir_test.ts b/cli/tests/unit/read_dir_test.ts index ca900153a3..686c38af34 100644 --- a/cli/tests/unit/read_dir_test.ts +++ b/cli/tests/unit/read_dir_test.ts @@ -40,15 +40,23 @@ unitTest({ permissions: { read: false } }, function readDirSyncPerm() { }); unitTest({ permissions: { read: true } }, function readDirSyncNotDir() { - assertThrows(() => { - Deno.readDirSync("cli/tests/testdata/fixture.json"); - }, Error); + assertThrows( + () => { + Deno.readDirSync("cli/tests/testdata/fixture.json"); + }, + Error, + `readdir 'cli/tests/testdata/fixture.json'`, + ); }); unitTest({ permissions: { read: true } }, function readDirSyncNotFound() { - assertThrows(() => { - Deno.readDirSync("bad_dir_name"); - }, Deno.errors.NotFound); + assertThrows( + () => { + Deno.readDirSync("bad_dir_name"); + }, + Deno.errors.NotFound, + `readdir 'bad_dir_name'`, + ); }); unitTest({ permissions: { read: true } }, async function readDirSuccess() { @@ -94,3 +102,13 @@ unitTest( } }, ); + +unitTest({ permissions: { read: true } }, async function readDirNotFound() { + await assertRejects( + async () => { + await Deno.readDir("bad_dir_name")[Symbol.asyncIterator]().next(); + }, + Deno.errors.NotFound, + `readdir 'bad_dir_name'`, + ); +}); diff --git a/cli/tests/unit/read_link_test.ts b/cli/tests/unit/read_link_test.ts index f468e6f52e..d59dae2b82 100644 --- a/cli/tests/unit/read_link_test.ts +++ b/cli/tests/unit/read_link_test.ts @@ -44,9 +44,13 @@ unitTest({ permissions: { read: false } }, function readLinkSyncPerm() { }); unitTest({ permissions: { read: true } }, function readLinkSyncNotFound() { - assertThrows(() => { - Deno.readLinkSync("bad_filename"); - }, Deno.errors.NotFound); + assertThrows( + () => { + Deno.readLinkSync("bad_filename"); + }, + Deno.errors.NotFound, + `readlink 'bad_filename'`, + ); }); unitTest( @@ -84,3 +88,13 @@ unitTest({ permissions: { read: false } }, async function readLinkPerm() { await Deno.readLink("/symlink"); }, Deno.errors.PermissionDenied); }); + +unitTest({ permissions: { read: true } }, async function readLinkNotFound() { + await assertRejects( + async () => { + await Deno.readLink("bad_filename"); + }, + Deno.errors.NotFound, + `readlink 'bad_filename'`, + ); +}); diff --git a/cli/tests/unit/remove_test.ts b/cli/tests/unit/remove_test.ts index 192ac676ec..5ea265ea8c 100644 --- a/cli/tests/unit/remove_test.ts +++ b/cli/tests/unit/remove_test.ts @@ -80,15 +80,23 @@ unitTest( const subPathInfo = Deno.statSync(subPath); assert(subPathInfo.isDirectory); // check exist first - await assertRejects(async () => { - await Deno[method](path); - }, Error); + await assertRejects( + async () => { + await Deno[method](path); + }, + Error, + `remove '${path}'`, + ); // TODO(ry) Is Other really the error we should get here? What would Go do? // NON-EXISTENT DIRECTORY/FILE - await assertRejects(async () => { - await Deno[method]("/baddir"); - }, Deno.errors.NotFound); + await assertRejects( + async () => { + await Deno[method]("/baddir"); + }, + Deno.errors.NotFound, + `remove '/baddir'`, + ); } }, ); @@ -210,10 +218,14 @@ unitTest( unitTest({ permissions: { write: true } }, async function removeAllFail() { for (const method of REMOVE_METHODS) { // NON-EXISTENT DIRECTORY/FILE - await assertRejects(async () => { - // Non-existent - await Deno[method]("/baddir", { recursive: true }); - }, Deno.errors.NotFound); + await assertRejects( + async () => { + // Non-existent + await Deno[method]("/baddir", { recursive: true }); + }, + Deno.errors.NotFound, + `remove '/baddir'`, + ); } }); diff --git a/cli/tests/unit/rename_test.ts b/cli/tests/unit/rename_test.ts index 387c0a51dc..a2291dd1c5 100644 --- a/cli/tests/unit/rename_test.ts +++ b/cli/tests/unit/rename_test.ts @@ -164,6 +164,13 @@ unitTest( Error, "Not a directory", ); + assertThrows( + () => { + Deno.renameSync(olddir, file); + }, + undefined, + `rename '${olddir}' -> '${file}'`, + ); const fileLink = testDir + "/fileLink"; const dirLink = testDir + "/dirLink"; @@ -242,6 +249,13 @@ unitTest( Deno.errors.PermissionDenied, "Access is denied", ); + assertThrows( + () => { + Deno.renameSync(olddir, emptydir); + }, + undefined, + `rename '${olddir}' -> '${emptydir}'`, + ); // should succeed on Windows Deno.renameSync(olddir, file); diff --git a/cli/tests/unit/stat_test.ts b/cli/tests/unit/stat_test.ts index 3628991280..eefbab2c5c 100644 --- a/cli/tests/unit/stat_test.ts +++ b/cli/tests/unit/stat_test.ts @@ -108,9 +108,13 @@ unitTest({ permissions: { read: false } }, function statSyncPerm() { }); unitTest({ permissions: { read: true } }, function statSyncNotFound() { - assertThrows(() => { - Deno.statSync("bad_file_name"); - }, Deno.errors.NotFound); + assertThrows( + () => { + Deno.statSync("bad_file_name"); + }, + Deno.errors.NotFound, + `stat 'bad_file_name'`, + ); }); unitTest({ permissions: { read: true } }, function lstatSyncSuccess() { @@ -148,9 +152,13 @@ unitTest({ permissions: { read: false } }, function lstatSyncPerm() { }); unitTest({ permissions: { read: true } }, function lstatSyncNotFound() { - assertThrows(() => { - Deno.lstatSync("bad_file_name"); - }, Deno.errors.NotFound); + assertThrows( + () => { + Deno.lstatSync("bad_file_name"); + }, + Deno.errors.NotFound, + `stat 'bad_file_name'`, + ); }); unitTest( @@ -228,8 +236,10 @@ unitTest({ permissions: { read: false } }, async function statPerm() { unitTest({ permissions: { read: true } }, async function statNotFound() { await assertRejects( async () => { - await Deno.stat("bad_file_name"), Deno.errors.NotFound; + await Deno.stat("bad_file_name"); }, + Deno.errors.NotFound, + `stat 'bad_file_name'`, ); }); @@ -268,9 +278,13 @@ unitTest({ permissions: { read: false } }, async function lstatPerm() { }); unitTest({ permissions: { read: true } }, async function lstatNotFound() { - await assertRejects(async () => { - await Deno.lstat("bad_file_name"); - }, Deno.errors.NotFound); + await assertRejects( + async () => { + await Deno.lstat("bad_file_name"); + }, + Deno.errors.NotFound, + `stat 'bad_file_name'`, + ); }); unitTest( diff --git a/cli/tests/unit/symlink_test.ts b/cli/tests/unit/symlink_test.ts index b5700b4f3c..f0db2d615d 100644 --- a/cli/tests/unit/symlink_test.ts +++ b/cli/tests/unit/symlink_test.ts @@ -1,6 +1,7 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. import { assert, + assertRejects, assertThrows, pathToAbsoluteFileUrl, unitTest, @@ -45,6 +46,21 @@ unitTest(function symlinkSyncPerm() { }, Deno.errors.PermissionDenied); }); +unitTest( + { permissions: { read: true, write: true } }, + function symlinkSyncAlreadyExist() { + const existingFile = Deno.makeTempFileSync(); + const existingFile2 = Deno.makeTempFileSync(); + assertThrows( + () => { + Deno.symlinkSync(existingFile, existingFile2); + }, + Deno.errors.AlreadyExists, + `symlink '${existingFile}' -> '${existingFile2}'`, + ); + }, +); + unitTest( { permissions: { read: true, write: true } }, async function symlinkSuccess() { @@ -77,3 +93,18 @@ unitTest( assert(newNameInfoStat.isDirectory, "NOT DIRECTORY"); }, ); + +unitTest( + { permissions: { read: true, write: true } }, + async function symlinkAlreadyExist() { + const existingFile = Deno.makeTempFileSync(); + const existingFile2 = Deno.makeTempFileSync(); + await assertRejects( + async () => { + await Deno.symlink(existingFile, existingFile2); + }, + Deno.errors.AlreadyExists, + `symlink '${existingFile}' -> '${existingFile2}'`, + ); + }, +); diff --git a/cli/tests/unit/truncate_test.ts b/cli/tests/unit/truncate_test.ts index 19a44dba94..33a1cd4b17 100644 --- a/cli/tests/unit/truncate_test.ts +++ b/cli/tests/unit/truncate_test.ts @@ -91,3 +91,31 @@ unitTest({ permissions: { write: false } }, async function truncatePerm() { await Deno.truncate("/test_truncatePermission.txt"); }, Deno.errors.PermissionDenied); }); + +unitTest( + { permissions: { read: true, write: true } }, + function truncateSyncNotFound() { + const filename = "/badfile.txt"; + assertThrows( + () => { + Deno.truncateSync(filename); + }, + Deno.errors.NotFound, + `truncate '${filename}'`, + ); + }, +); + +unitTest( + { permissions: { read: true, write: true } }, + async function truncateSyncNotFound() { + const filename = "/badfile.txt"; + await assertRejects( + async () => { + await Deno.truncate(filename); + }, + Deno.errors.NotFound, + `truncate '${filename}'`, + ); + }, +); diff --git a/cli/tests/unit/utime_test.ts b/cli/tests/unit/utime_test.ts index 3e73722aae..a46bd7a0ca 100644 --- a/cli/tests/unit/utime_test.ts +++ b/cli/tests/unit/utime_test.ts @@ -160,9 +160,13 @@ unitTest( const atime = 1000; const mtime = 50000; - assertThrows(() => { - Deno.utimeSync("/baddir", atime, mtime); - }, Deno.errors.NotFound); + assertThrows( + () => { + Deno.utimeSync("/baddir", atime, mtime); + }, + Deno.errors.NotFound, + "utime '/baddir'", + ); }, ); @@ -271,9 +275,13 @@ unitTest( const atime = 1000; const mtime = 50000; - await assertRejects(async () => { - await Deno.utime("/baddir", atime, mtime); - }, Deno.errors.NotFound); + await assertRejects( + async () => { + await Deno.utime("/baddir", atime, mtime); + }, + Deno.errors.NotFound, + "utime '/baddir'", + ); }, ); diff --git a/runtime/errors.rs b/runtime/errors.rs index 79f3c5e7a4..fe6e711931 100644 --- a/runtime/errors.rs +++ b/runtime/errors.rs @@ -137,7 +137,7 @@ fn get_hyper_error_class(_error: &hyper::Error) -> &'static str { } #[cfg(unix)] -fn get_nix_error_class(error: &nix::Error) -> &'static str { +pub fn get_nix_error_class(error: &nix::Error) -> &'static str { match error { nix::Error::ECHILD => "NotFound", nix::Error::EINVAL => "TypeError", diff --git a/runtime/ops/fs.rs b/runtime/ops/fs.rs index 3ae3bf0f0f..c3e9215a28 100644 --- a/runtime/ops/fs.rs +++ b/runtime/ops/fs.rs @@ -23,7 +23,7 @@ use std::cell::RefCell; use std::convert::From; use std::env::{current_dir, set_current_dir, temp_dir}; use std::io; -use std::io::{Seek, SeekFrom}; +use std::io::{Error, Seek, SeekFrom}; use std::path::{Path, PathBuf}; use std::rc::Rc; use std::time::SystemTime; @@ -163,7 +163,9 @@ fn op_open_sync( _: (), ) -> Result { let (path, open_options) = open_helper(state, args)?; - let std_file = open_options.open(path)?; + let std_file = open_options.open(&path).map_err(|err| { + Error::new(err.kind(), format!("{}, open '{}'", err, path.display())) + })?; let tokio_file = tokio::fs::File::from_std(std_file); let resource = StdFileResource::fs_file(tokio_file); let rid = state.resource_table.add(resource); @@ -177,8 +179,11 @@ async fn op_open_async( ) -> Result { let (path, open_options) = open_helper(&mut state.borrow_mut(), args)?; let tokio_file = tokio::fs::OpenOptions::from(open_options) - .open(path) - .await?; + .open(&path) + .await + .map_err(|err| { + Error::new(err.kind(), format!("{}, open '{}'", err, path.display())) + })?; let resource = StdFileResource::fs_file(tokio_file); let rid = state.borrow_mut().resource_table.add(resource); Ok(rid) @@ -503,7 +508,9 @@ fn op_chdir( ) -> Result<(), AnyError> { let d = PathBuf::from(&directory); state.borrow_mut::().read.check(&d)?; - set_current_dir(&d)?; + set_current_dir(&d).map_err(|err| { + Error::new(err.kind(), format!("{}, chdir '{}'", err, directory)) + })?; Ok(()) } @@ -531,7 +538,9 @@ fn op_mkdir_sync( use std::os::unix::fs::DirBuilderExt; builder.mode(mode); } - builder.create(path)?; + builder.create(&path).map_err(|err| { + Error::new(err.kind(), format!("{}, mkdir '{}'", err, path.display())) + })?; Ok(()) } @@ -557,7 +566,9 @@ async fn op_mkdir_async( use std::os::unix::fs::DirBuilderExt; builder.mode(mode); } - builder.create(path)?; + builder.create(&path).map_err(|err| { + Error::new(err.kind(), format!("{}, mkdir '{}'", err, path.display())) + })?; Ok(()) }) .await @@ -578,6 +589,9 @@ fn op_chmod_sync( ) -> Result<(), AnyError> { let path = Path::new(&args.path).to_path_buf(); let mode = args.mode & 0o777; + let err_mapper = |err: Error| { + Error::new(err.kind(), format!("{}, chmod '{}'", err, path.display())) + }; state.borrow_mut::().write.check(&path)?; debug!("op_chmod_sync {} {:o}", path.display(), mode); @@ -585,14 +599,14 @@ fn op_chmod_sync( { use std::os::unix::fs::PermissionsExt; let permissions = PermissionsExt::from_mode(mode); - std::fs::set_permissions(&path, permissions)?; + std::fs::set_permissions(&path, permissions).map_err(err_mapper)?; Ok(()) } // TODO Implement chmod for Windows (#4357) #[cfg(not(unix))] { // Still check file/dir exists on Windows - let _metadata = std::fs::metadata(&path)?; + let _metadata = std::fs::metadata(&path).map_err(err_mapper)?; Err(generic_error("Not implemented")) } } @@ -612,18 +626,21 @@ async fn op_chmod_async( tokio::task::spawn_blocking(move || { debug!("op_chmod_async {} {:o}", path.display(), mode); + let err_mapper = |err: Error| { + Error::new(err.kind(), format!("{}, chmod '{}'", err, path.display())) + }; #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; let permissions = PermissionsExt::from_mode(mode); - std::fs::set_permissions(&path, permissions)?; + std::fs::set_permissions(&path, permissions).map_err(err_mapper)?; Ok(()) } // TODO Implement chmod for Windows (#4357) #[cfg(not(unix))] { // Still check file/dir exists on Windows - let _metadata = std::fs::metadata(&path)?; + let _metadata = std::fs::metadata(&path).map_err(err_mapper)?; Err(not_supported()) } }) @@ -654,10 +671,16 @@ fn op_chown_sync( ); #[cfg(unix)] { + use crate::errors::get_nix_error_class; use nix::unistd::{chown, Gid, Uid}; let nix_uid = args.uid.map(Uid::from_raw); let nix_gid = args.gid.map(Gid::from_raw); - chown(&path, nix_uid, nix_gid)?; + chown(&path, nix_uid, nix_gid).map_err(|err| { + custom_error( + get_nix_error_class(&err), + format!("{}, chown '{}'", err.desc(), path.display()), + ) + })?; Ok(()) } // TODO Implement chown for Windows @@ -688,10 +711,16 @@ async fn op_chown_async( ); #[cfg(unix)] { + use crate::errors::get_nix_error_class; use nix::unistd::{chown, Gid, Uid}; let nix_uid = args.uid.map(Uid::from_raw); let nix_gid = args.gid.map(Gid::from_raw); - chown(&path, nix_uid, nix_gid)?; + chown(&path, nix_uid, nix_gid).map_err(|err| { + custom_error( + get_nix_error_class(&err), + format!("{}, chown '{}'", err.desc(), path.display()), + ) + })?; Ok(()) } // TODO Implement chown for Windows @@ -722,31 +751,34 @@ fn op_remove_sync( #[cfg(not(unix))] use std::os::windows::prelude::MetadataExt; - let metadata = std::fs::symlink_metadata(&path)?; + let err_mapper = |err: Error| { + Error::new(err.kind(), format!("{}, remove '{}'", err, path.display())) + }; + let metadata = std::fs::symlink_metadata(&path).map_err(err_mapper)?; debug!("op_remove_sync {} {}", path.display(), recursive); let file_type = metadata.file_type(); if file_type.is_file() { - std::fs::remove_file(&path)?; + std::fs::remove_file(&path).map_err(err_mapper)?; } else if recursive { - std::fs::remove_dir_all(&path)?; + std::fs::remove_dir_all(&path).map_err(err_mapper)?; } else if file_type.is_symlink() { #[cfg(unix)] - std::fs::remove_file(&path)?; + std::fs::remove_file(&path).map_err(err_mapper)?; #[cfg(not(unix))] { use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY; if metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 { - std::fs::remove_dir(&path)?; + std::fs::remove_dir(&path).map_err(err_mapper)?; } else { - std::fs::remove_file(&path)?; + std::fs::remove_file(&path).map_err(err_mapper)?; } } } else if file_type.is_dir() { - std::fs::remove_dir(&path)?; + std::fs::remove_dir(&path).map_err(err_mapper)?; } else { // pipes, sockets, etc... - std::fs::remove_file(&path)?; + std::fs::remove_file(&path).map_err(err_mapper)?; } Ok(()) } @@ -767,32 +799,34 @@ async fn op_remove_async( tokio::task::spawn_blocking(move || { #[cfg(not(unix))] use std::os::windows::prelude::MetadataExt; - - let metadata = std::fs::symlink_metadata(&path)?; + let err_mapper = |err: Error| { + Error::new(err.kind(), format!("{}, remove '{}'", err, path.display())) + }; + let metadata = std::fs::symlink_metadata(&path).map_err(err_mapper)?; debug!("op_remove_async {} {}", path.display(), recursive); let file_type = metadata.file_type(); if file_type.is_file() { - std::fs::remove_file(&path)?; + std::fs::remove_file(&path).map_err(err_mapper)?; } else if recursive { - std::fs::remove_dir_all(&path)?; + std::fs::remove_dir_all(&path).map_err(err_mapper)?; } else if file_type.is_symlink() { #[cfg(unix)] - std::fs::remove_file(&path)?; + std::fs::remove_file(&path).map_err(err_mapper)?; #[cfg(not(unix))] { use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY; if metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 { - std::fs::remove_dir(&path)?; + std::fs::remove_dir(&path).map_err(err_mapper)?; } else { - std::fs::remove_file(&path)?; + std::fs::remove_file(&path).map_err(err_mapper)?; } } } else if file_type.is_dir() { - std::fs::remove_dir(&path)?; + std::fs::remove_dir(&path).map_err(err_mapper)?; } else { // pipes, sockets, etc... - std::fs::remove_file(&path)?; + std::fs::remove_file(&path).map_err(err_mapper)?; } Ok(()) }) @@ -824,11 +858,24 @@ fn op_copy_file_sync( // See https://github.com/rust-lang/rust/issues/54800 // Once the issue is resolved, we should remove this workaround. if cfg!(unix) && !from.is_file() { - return Err(custom_error("NotFound", "File not found")); + return Err(custom_error( + "NotFound", + format!( + "File not found, copy '{}' -> '{}'", + from.display(), + to.display() + ), + )); } + let err_mapper = |err: Error| { + Error::new( + err.kind(), + format!("{}, copy '{}' -> '{}'", err, from.display(), to.display()), + ) + }; // returns size of from as u64 (we ignore) - std::fs::copy(&from, &to)?; + std::fs::copy(&from, &to).map_err(err_mapper)?; Ok(()) } @@ -853,11 +900,24 @@ async fn op_copy_file_async( // See https://github.com/rust-lang/rust/issues/54800 // Once the issue is resolved, we should remove this workaround. if cfg!(unix) && !from.is_file() { - return Err(custom_error("NotFound", "File not found")); + return Err(custom_error( + "NotFound", + format!( + "File not found, copy '{}' -> '{}'", + from.display(), + to.display() + ), + )); } + let err_mapper = |err: Error| { + Error::new( + err.kind(), + format!("{}, copy '{}' -> '{}'", err, from.display(), to.display()), + ) + }; // returns size of from as u64 (we ignore) - std::fs::copy(&from, &to)?; + std::fs::copy(&from, &to).map_err(err_mapper)?; Ok(()) }) .await @@ -956,10 +1016,13 @@ fn op_stat_sync( let lstat = args.lstat; state.borrow_mut::().read.check(&path)?; debug!("op_stat_sync {} {}", path.display(), lstat); + let err_mapper = |err: Error| { + Error::new(err.kind(), format!("{}, stat '{}'", err, path.display())) + }; let metadata = if lstat { - std::fs::symlink_metadata(&path)? + std::fs::symlink_metadata(&path).map_err(err_mapper)? } else { - std::fs::metadata(&path)? + std::fs::metadata(&path).map_err(err_mapper)? }; Ok(get_stat(metadata)) } @@ -979,10 +1042,13 @@ async fn op_stat_async( tokio::task::spawn_blocking(move || { debug!("op_stat_async {} {}", path.display(), lstat); + let err_mapper = |err: Error| { + Error::new(err.kind(), format!("{}, stat '{}'", err, path.display())) + }; let metadata = if lstat { - std::fs::symlink_metadata(&path)? + std::fs::symlink_metadata(&path).map_err(err_mapper)? } else { - std::fs::metadata(&path)? + std::fs::metadata(&path).map_err(err_mapper)? }; Ok(get_stat(metadata)) }) @@ -1058,7 +1124,11 @@ fn op_read_dir_sync( state.borrow_mut::().read.check(&path)?; debug!("op_read_dir_sync {}", path.display()); - let entries: Vec<_> = std::fs::read_dir(path)? + let err_mapper = |err: Error| { + Error::new(err.kind(), format!("{}, readdir '{}'", err, path.display())) + }; + let entries: Vec<_> = std::fs::read_dir(&path) + .map_err(err_mapper)? .filter_map(|entry| { let entry = entry.unwrap(); // Not all filenames can be encoded as UTF-8. Skip those for now. @@ -1096,7 +1166,11 @@ async fn op_read_dir_async( } tokio::task::spawn_blocking(move || { debug!("op_read_dir_async {}", path.display()); - let entries: Vec<_> = std::fs::read_dir(path)? + let err_mapper = |err: Error| { + Error::new(err.kind(), format!("{}, readdir '{}'", err, path.display())) + }; + let entries: Vec<_> = std::fs::read_dir(&path) + .map_err(err_mapper)? .filter_map(|entry| { let entry = entry.unwrap(); // Not all filenames can be encoded as UTF-8. Skip those for now. @@ -1145,7 +1219,18 @@ fn op_rename_sync( permissions.write.check(&oldpath)?; permissions.write.check(&newpath)?; debug!("op_rename_sync {} {}", oldpath.display(), newpath.display()); - std::fs::rename(&oldpath, &newpath)?; + let err_mapper = |err: Error| { + Error::new( + err.kind(), + format!( + "{}, rename '{}' -> '{}'", + err, + oldpath.display(), + newpath.display() + ), + ) + }; + std::fs::rename(&oldpath, &newpath).map_err(err_mapper)?; Ok(()) } @@ -1169,7 +1254,18 @@ async fn op_rename_async( oldpath.display(), newpath.display() ); - std::fs::rename(&oldpath, &newpath)?; + let err_mapper = |err: Error| { + Error::new( + err.kind(), + format!( + "{}, rename '{}' -> '{}'", + err, + oldpath.display(), + newpath.display() + ), + ) + }; + std::fs::rename(&oldpath, &newpath).map_err(err_mapper)?; Ok(()) }) .await @@ -1198,7 +1294,18 @@ fn op_link_sync( permissions.write.check(&newpath)?; debug!("op_link_sync {} {}", oldpath.display(), newpath.display()); - std::fs::hard_link(&oldpath, &newpath)?; + let err_mapper = |err: Error| { + Error::new( + err.kind(), + format!( + "{}, link '{}' -> '{}'", + err, + oldpath.display(), + newpath.display() + ), + ) + }; + std::fs::hard_link(&oldpath, &newpath).map_err(err_mapper)?; Ok(()) } @@ -1221,7 +1328,18 @@ async fn op_link_async( tokio::task::spawn_blocking(move || { debug!("op_link_async {} {}", oldpath.display(), newpath.display()); - std::fs::hard_link(&oldpath, &newpath)?; + let err_mapper = |err: Error| { + Error::new( + err.kind(), + format!( + "{}, link '{}' -> '{}'", + err, + oldpath.display(), + newpath.display() + ), + ) + }; + std::fs::hard_link(&oldpath, &newpath).map_err(err_mapper)?; Ok(()) }) .await @@ -1259,10 +1377,21 @@ fn op_symlink_sync( oldpath.display(), newpath.display() ); + let err_mapper = |err: Error| { + Error::new( + err.kind(), + format!( + "{}, symlink '{}' -> '{}'", + err, + oldpath.display(), + newpath.display() + ), + ) + }; #[cfg(unix)] { use std::os::unix::fs::symlink; - symlink(&oldpath, &newpath)?; + symlink(&oldpath, &newpath).map_err(err_mapper)?; Ok(()) } #[cfg(not(unix))] @@ -1271,8 +1400,8 @@ fn op_symlink_sync( match args.options { Some(options) => match options._type.as_ref() { - "file" => symlink_file(&oldpath, &newpath)?, - "dir" => symlink_dir(&oldpath, &newpath)?, + "file" => symlink_file(&oldpath, &newpath).map_err(err_mapper)?, + "dir" => symlink_dir(&oldpath, &newpath).map_err(err_mapper)?, _ => return Err(type_error("unsupported type")), }, None => { @@ -1280,9 +1409,9 @@ fn op_symlink_sync( match old_meta { Ok(metadata) => { if metadata.is_file() { - symlink_file(&oldpath, &newpath)? + symlink_file(&oldpath, &newpath).map_err(err_mapper)? } else if metadata.is_dir() { - symlink_dir(&oldpath, &newpath)? + symlink_dir(&oldpath, &newpath).map_err(err_mapper)? } } Err(_) => return Err(type_error("you must pass a `options` argument for non-existent target path in windows".to_string())), @@ -1308,10 +1437,21 @@ async fn op_symlink_async( tokio::task::spawn_blocking(move || { debug!("op_symlink_async {} {}", oldpath.display(), newpath.display()); + let err_mapper = |err: Error| { + Error::new( + err.kind(), + format!( + "{}, symlink '{}' -> '{}'", + err, + oldpath.display(), + newpath.display() + ), + ) + }; #[cfg(unix)] { use std::os::unix::fs::symlink; - symlink(&oldpath, &newpath)?; + symlink(&oldpath, &newpath).map_err(err_mapper)?; Ok(()) } #[cfg(not(unix))] @@ -1320,8 +1460,8 @@ async fn op_symlink_async( match args.options { Some(options) => match options._type.as_ref() { - "file" => symlink_file(&oldpath, &newpath)?, - "dir" => symlink_dir(&oldpath, &newpath)?, + "file" => symlink_file(&oldpath, &newpath).map_err(err_mapper)?, + "dir" => symlink_dir(&oldpath, &newpath).map_err(err_mapper)?, _ => return Err(type_error("unsupported type")), }, None => { @@ -1329,9 +1469,9 @@ async fn op_symlink_async( match old_meta { Ok(metadata) => { if metadata.is_file() { - symlink_file(&oldpath, &newpath)? + symlink_file(&oldpath, &newpath).map_err(err_mapper)? } else if metadata.is_dir() { - symlink_dir(&oldpath, &newpath)? + symlink_dir(&oldpath, &newpath).map_err(err_mapper)? } } Err(_) => return Err(type_error("you must pass a `options` argument for non-existent target path in windows".to_string())), @@ -1355,7 +1495,15 @@ fn op_read_link_sync( state.borrow_mut::().read.check(&path)?; debug!("op_read_link_value {}", path.display()); - let target = std::fs::read_link(&path)?.into_os_string(); + let err_mapper = |err: Error| { + Error::new( + err.kind(), + format!("{}, readlink '{}'", err, path.display()), + ) + }; + let target = std::fs::read_link(&path) + .map_err(err_mapper)? + .into_os_string(); let targetstr = into_string(target)?; Ok(targetstr) } @@ -1372,7 +1520,15 @@ async fn op_read_link_async( } tokio::task::spawn_blocking(move || { debug!("op_read_link_async {}", path.display()); - let target = std::fs::read_link(&path)?.into_os_string(); + let err_mapper = |err: Error| { + Error::new( + err.kind(), + format!("{}, readlink '{}'", err, path.display()), + ) + }; + let target = std::fs::read_link(&path) + .map_err(err_mapper)? + .into_os_string(); let targetstr = into_string(target)?; Ok(targetstr) }) @@ -1444,8 +1600,17 @@ fn op_truncate_sync( state.borrow_mut::().write.check(&path)?; debug!("op_truncate_sync {} {}", path.display(), len); - let f = std::fs::OpenOptions::new().write(true).open(&path)?; - f.set_len(len)?; + let err_mapper = |err: Error| { + Error::new( + err.kind(), + format!("{}, truncate '{}'", err, path.display()), + ) + }; + let f = std::fs::OpenOptions::new() + .write(true) + .open(&path) + .map_err(err_mapper)?; + f.set_len(len).map_err(err_mapper)?; Ok(()) } @@ -1462,8 +1627,17 @@ async fn op_truncate_async( } tokio::task::spawn_blocking(move || { debug!("op_truncate_async {} {}", path.display(), len); - let f = std::fs::OpenOptions::new().write(true).open(&path)?; - f.set_len(len)?; + let err_mapper = |err: Error| { + Error::new( + err.kind(), + format!("{}, truncate '{}'", err, path.display()), + ) + }; + let f = std::fs::OpenOptions::new() + .write(true) + .open(&path) + .map_err(err_mapper)?; + f.set_len(len).map_err(err_mapper)?; Ok(()) }) .await @@ -1740,7 +1914,9 @@ fn op_utime_sync( let mtime = filetime::FileTime::from_unix_time(args.mtime.0, args.mtime.1); state.borrow_mut::().write.check(&path)?; - filetime::set_file_times(path, atime, mtime)?; + filetime::set_file_times(&path, atime, mtime).map_err(|err| { + Error::new(err.kind(), format!("{}, utime '{}'", err, path.display())) + })?; Ok(()) } @@ -1762,7 +1938,9 @@ async fn op_utime_async( .check(&path)?; tokio::task::spawn_blocking(move || { - filetime::set_file_times(path, atime, mtime)?; + filetime::set_file_times(&path, atime, mtime).map_err(|err| { + Error::new(err.kind(), format!("{}, utime '{}'", err, path.display())) + })?; Ok(()) }) .await