Browse Source

fs: improve error messages

* Include a description for the error message
* For rename, link, and symlink, include both the source and destination
  path in the error message.
* Expose the destination path as the `dest` property on the error object.
* Fix a bug where `ThrowUVException()` would incorrectly delegate to
  `Environment::TrowErrnoException()`.

API impact:
* Adds an extra overload for node::UVException() which takes 6
  arguments.

PR: https://github.com/iojs/io.js/pull/675
Fixes: https://github.com/iojs/io.js/issues/207
Closes: https://github.com/iojs/io.js/pull/293
Reviewed-by: Ben Noordhuis <info@bnoordhuis.nl>
v1.8.0-commit
Bert Belder 10 years ago
parent
commit
bc2c85ceef
  1. 5
      src/env-inl.h
  2. 4
      src/env.h
  3. 109
      src/node.cc
  4. 6
      src/node.h
  5. 34
      src/node_file.cc
  6. 3
      src/node_internals.h
  7. 4
      test/parallel/test-domain.js
  8. 6
      test/parallel/test-fs-error-messages.js

5
src/env-inl.h

@ -377,9 +377,10 @@ inline void Environment::ThrowErrnoException(int errorno,
inline void Environment::ThrowUVException(int errorno, inline void Environment::ThrowUVException(int errorno,
const char* syscall, const char* syscall,
const char* message, const char* message,
const char* path) { const char* path,
const char* dest) {
isolate()->ThrowException( isolate()->ThrowException(
UVException(isolate(), errorno, syscall, message, path)); UVException(isolate(), errorno, syscall, message, path, dest));
} }
inline v8::Local<v8::FunctionTemplate> inline v8::Local<v8::FunctionTemplate>

4
src/env.h

@ -61,6 +61,7 @@ namespace node {
V(cwd_string, "cwd") \ V(cwd_string, "cwd") \
V(debug_port_string, "debugPort") \ V(debug_port_string, "debugPort") \
V(debug_string, "debug") \ V(debug_string, "debug") \
V(dest_string, "dest") \
V(detached_string, "detached") \ V(detached_string, "detached") \
V(dev_string, "dev") \ V(dev_string, "dev") \
V(disposed_string, "_disposed") \ V(disposed_string, "_disposed") \
@ -407,7 +408,8 @@ class Environment {
inline void ThrowUVException(int errorno, inline void ThrowUVException(int errorno,
const char* syscall = nullptr, const char* syscall = nullptr,
const char* message = nullptr, const char* message = nullptr,
const char* path = nullptr); const char* path = nullptr,
const char* dest = nullptr);
// Convenience methods for contextify // Convenience methods for contextify
inline static void ThrowError(v8::Isolate* isolate, const char* errmsg); inline static void ThrowError(v8::Isolate* isolate, const char* errmsg);

109
src/node.cc

@ -698,11 +698,10 @@ void ThrowUVException(v8::Isolate* isolate,
int errorno, int errorno,
const char* syscall, const char* syscall,
const char* message, const char* message,
const char* path) { const char* path,
Environment::GetCurrent(isolate)->ThrowErrnoException(errorno, const char* dest) {
syscall, Environment::GetCurrent(isolate)
message, ->ThrowUVException(errorno, syscall, message, path, dest);
path);
} }
@ -752,64 +751,78 @@ Local<Value> ErrnoException(Isolate* isolate,
} }
// hack alert! copy of ErrnoException, tuned for uv errors static Local<String> StringFromPath(Isolate* isolate, const char* path) {
#ifdef _WIN32
if (strncmp(path, "\\\\?\\UNC\\", 8) == 0) {
return String::Concat(FIXED_ONE_BYTE_STRING(isolate, "\\\\"),
String::NewFromUtf8(isolate, path + 8));
} else if (strncmp(path, "\\\\?\\", 4) == 0) {
return String::NewFromUtf8(isolate, path + 4);
}
#endif
return String::NewFromUtf8(isolate, path);
}
Local<Value> UVException(Isolate* isolate,
int errorno,
const char* syscall,
const char* msg,
const char* path) {
return UVException(isolate, errorno, syscall, msg, path, nullptr);
}
Local<Value> UVException(Isolate* isolate, Local<Value> UVException(Isolate* isolate,
int errorno, int errorno,
const char *syscall, const char* syscall,
const char *msg, const char* msg,
const char *path) { const char* path,
const char* dest) {
Environment* env = Environment::GetCurrent(isolate); Environment* env = Environment::GetCurrent(isolate);
if (!msg || !msg[0]) if (!msg || !msg[0])
msg = uv_strerror(errorno); msg = uv_strerror(errorno);
Local<String> estring = OneByteString(env->isolate(), uv_err_name(errorno)); Local<String> js_code = OneByteString(isolate, uv_err_name(errorno));
Local<String> message = OneByteString(env->isolate(), msg); Local<String> js_syscall = OneByteString(isolate, syscall);
Local<String> cons1 = Local<String> js_path;
String::Concat(estring, FIXED_ONE_BYTE_STRING(env->isolate(), ", ")); Local<String> js_dest;
Local<String> cons2 = String::Concat(cons1, message);
Local<Value> e;
Local<String> path_str; Local<String> js_msg = js_code;
js_msg = String::Concat(js_msg, FIXED_ONE_BYTE_STRING(isolate, ": "));
js_msg = String::Concat(js_msg, OneByteString(isolate, msg));
js_msg = String::Concat(js_msg, FIXED_ONE_BYTE_STRING(isolate, ", "));
js_msg = String::Concat(js_msg, js_syscall);
if (path) { if (path != nullptr) {
#ifdef _WIN32 js_path = StringFromPath(isolate, path);
if (strncmp(path, "\\\\?\\UNC\\", 8) == 0) {
path_str = String::Concat(FIXED_ONE_BYTE_STRING(env->isolate(), "\\\\"),
String::NewFromUtf8(env->isolate(), path + 8));
} else if (strncmp(path, "\\\\?\\", 4) == 0) {
path_str = String::NewFromUtf8(env->isolate(), path + 4);
} else {
path_str = String::NewFromUtf8(env->isolate(), path);
}
#else
path_str = String::NewFromUtf8(env->isolate(), path);
#endif
Local<String> cons3 = js_msg = String::Concat(js_msg, FIXED_ONE_BYTE_STRING(isolate, " '"));
String::Concat(cons2, FIXED_ONE_BYTE_STRING(env->isolate(), " '")); js_msg = String::Concat(js_msg, js_path);
Local<String> cons4 = js_msg = String::Concat(js_msg, FIXED_ONE_BYTE_STRING(isolate, "'"));
String::Concat(cons3, path_str);
Local<String> cons5 =
String::Concat(cons4, FIXED_ONE_BYTE_STRING(env->isolate(), "'"));
e = Exception::Error(cons5);
} else {
e = Exception::Error(cons2);
} }
Local<Object> obj = e->ToObject(env->isolate()); if (dest != nullptr) {
// TODO(piscisaureus) errno should probably go js_dest = StringFromPath(isolate, dest);
obj->Set(env->errno_string(), Integer::New(env->isolate(), errorno));
obj->Set(env->code_string(), estring);
if (path != nullptr) { js_msg = String::Concat(js_msg, FIXED_ONE_BYTE_STRING(isolate, " -> '"));
obj->Set(env->path_string(), path_str); js_msg = String::Concat(js_msg, js_dest);
js_msg = String::Concat(js_msg, FIXED_ONE_BYTE_STRING(isolate, "'"));
} }
if (syscall != nullptr) { Local<Object> e = Exception::Error(js_msg)->ToObject(isolate);
obj->Set(env->syscall_string(), OneByteString(env->isolate(), syscall));
} // TODO(piscisaureus) errno should probably go; the user has no way of
// knowing which uv errno value maps to which error.
e->Set(env->errno_string(), Integer::New(isolate, errorno));
e->Set(env->code_string(), js_code);
e->Set(env->syscall_string(), js_syscall);
if (!js_path.IsEmpty())
e->Set(env->path_string(), js_path);
if (!js_dest.IsEmpty())
e->Set(env->dest_string(), js_dest);
return e; return e;
} }

6
src/node.h

@ -59,6 +59,12 @@ NODE_EXTERN v8::Local<v8::Value> UVException(v8::Isolate* isolate,
const char* syscall = NULL, const char* syscall = NULL,
const char* message = NULL, const char* message = NULL,
const char* path = NULL); const char* path = NULL);
NODE_EXTERN v8::Local<v8::Value> UVException(v8::Isolate* isolate,
int errorno,
const char* syscall,
const char* message,
const char* path,
const char* dest);
NODE_DEPRECATED("Use UVException(isolate, ...)", NODE_DEPRECATED("Use UVException(isolate, ...)",
inline v8::Local<v8::Value> ErrnoException( inline v8::Local<v8::Value> ErrnoException(

34
src/node_file.cc

@ -109,23 +109,14 @@ static void After(uv_fs_t *req) {
Local<Value> argv[2]; Local<Value> argv[2];
if (req->result < 0) { if (req->result < 0) {
// If the request doesn't have a path parameter set. // An error happened.
if (req->path == nullptr) { const char* dest = req_wrap->dest_len() > 0 ? req_wrap->dest() : nullptr;
argv[0] = UVException(req->result, nullptr, req_wrap->syscall()); argv[0] = UVException(env->isolate(),
} else if ((req->result == UV_EEXIST || req->result,
req->result == UV_ENOTEMPTY || req_wrap->syscall(),
req->result == UV_EPERM) && nullptr,
req_wrap->dest_len() > 0) { req->path,
argv[0] = UVException(req->result, dest);
nullptr,
req_wrap->syscall(),
req_wrap->dest());
} else {
argv[0] = UVException(req->result,
nullptr,
req_wrap->syscall(),
static_cast<const char*>(req->path));
}
} else { } else {
// error value is empty or null for non-error. // error value is empty or null for non-error.
argv[0] = Null(env->isolate()); argv[0] = Null(env->isolate());
@ -270,14 +261,7 @@ struct fs_req_wrap {
__VA_ARGS__, \ __VA_ARGS__, \
nullptr); \ nullptr); \
if (err < 0) { \ if (err < 0) { \
if (dest != nullptr && \ return env->ThrowUVException(err, #func, nullptr, path, dest); \
(err == UV_EEXIST || \
err == UV_ENOTEMPTY || \
err == UV_EPERM)) { \
return env->ThrowUVException(err, #func, "", dest); \
} else { \
return env->ThrowUVException(err, #func, "", path); \
} \
} \ } \
#define SYNC_CALL(func, path, ...) \ #define SYNC_CALL(func, path, ...) \

3
src/node_internals.h

@ -160,7 +160,8 @@ void ThrowUVException(v8::Isolate* isolate,
int errorno, int errorno,
const char* syscall = nullptr, const char* syscall = nullptr,
const char* message = nullptr, const char* message = nullptr,
const char* path = nullptr); const char* path = nullptr,
const char* dest = nullptr);
NODE_DEPRECATED("Use ThrowError(isolate)", NODE_DEPRECATED("Use ThrowError(isolate)",
inline void ThrowError(const char* errmsg) { inline void ThrowError(const char* errmsg) {

4
test/parallel/test-domain.js

@ -48,7 +48,7 @@ d.on('error', function(er) {
assert.equal(er.domainThrown, true); assert.equal(er.domainThrown, true);
break; break;
case "ENOENT, open 'this file does not exist'": case "ENOENT: no such file or directory, open 'this file does not exist'":
assert.equal(er.domain, d); assert.equal(er.domain, d);
assert.equal(er.domainThrown, false); assert.equal(er.domainThrown, false);
assert.equal(typeof er.domainBound, 'function'); assert.equal(typeof er.domainBound, 'function');
@ -58,7 +58,7 @@ d.on('error', function(er) {
assert.equal(typeof er.errno, 'number'); assert.equal(typeof er.errno, 'number');
break; break;
case "ENOENT, open 'stream for nonexistent file'": case "ENOENT: no such file or directory, open 'stream for nonexistent file'":
assert.equal(typeof er.errno, 'number'); assert.equal(typeof er.errno, 'number');
assert.equal(er.code, 'ENOENT'); assert.equal(er.code, 'ENOENT');
assert.equal(er_path, 'stream for nonexistent file'); assert.equal(er_path, 'stream for nonexistent file');

6
test/parallel/test-fs-error-messages.js

@ -29,10 +29,12 @@ fs.link(fn, 'foo', function(err) {
}); });
fs.link(existingFile, existingFile2, function(err) { fs.link(existingFile, existingFile2, function(err) {
assert.ok(0 <= err.message.indexOf(existingFile));
assert.ok(0 <= err.message.indexOf(existingFile2)); assert.ok(0 <= err.message.indexOf(existingFile2));
}); });
fs.symlink(existingFile, existingFile2, function(err) { fs.symlink(existingFile, existingFile2, function(err) {
assert.ok(0 <= err.message.indexOf(existingFile));
assert.ok(0 <= err.message.indexOf(existingFile2)); assert.ok(0 <= err.message.indexOf(existingFile2));
}); });
@ -45,6 +47,7 @@ fs.rename(fn, 'foo', function(err) {
}); });
fs.rename(existingDir, existingDir2, function(err) { fs.rename(existingDir, existingDir2, function(err) {
assert.ok(0 <= err.message.indexOf(existingDir));
assert.ok(0 <= err.message.indexOf(existingDir2)); assert.ok(0 <= err.message.indexOf(existingDir2));
}); });
@ -130,6 +133,7 @@ try {
fs.linkSync(existingFile, existingFile2); fs.linkSync(existingFile, existingFile2);
} catch (err) { } catch (err) {
errors.push('link'); errors.push('link');
assert.ok(0 <= err.message.indexOf(existingFile));
assert.ok(0 <= err.message.indexOf(existingFile2)); assert.ok(0 <= err.message.indexOf(existingFile2));
} }
@ -138,6 +142,7 @@ try {
fs.symlinkSync(existingFile, existingFile2); fs.symlinkSync(existingFile, existingFile2);
} catch (err) { } catch (err) {
errors.push('symlink'); errors.push('symlink');
assert.ok(0 <= err.message.indexOf(existingFile));
assert.ok(0 <= err.message.indexOf(existingFile2)); assert.ok(0 <= err.message.indexOf(existingFile2));
} }
@ -186,6 +191,7 @@ try {
fs.renameSync(existingDir, existingDir2); fs.renameSync(existingDir, existingDir2);
} catch (err) { } catch (err) {
errors.push('rename'); errors.push('rename');
assert.ok(0 <= err.message.indexOf(existingDir));
assert.ok(0 <= err.message.indexOf(existingDir2)); assert.ok(0 <= err.message.indexOf(existingDir2));
} }

Loading…
Cancel
Save