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>
This commit is contained in:
parent
0767c2feb1
commit
bc2c85ceef
@ -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>
|
||||||
|
@ -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);
|
||||||
|
113
src/node.cc
113
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,65 +751,79 @@ 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,
|
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) {
|
||||||
|
return UVException(isolate, errorno, syscall, msg, path, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Local<Value> UVException(Isolate* isolate,
|
||||||
|
int errorno,
|
||||||
|
const char* syscall,
|
||||||
|
const char* msg,
|
||||||
|
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> js_msg = js_code;
|
||||||
|
js_msg = String::Concat(js_msg, FIXED_ONE_BYTE_STRING(isolate, ": "));
|
||||||
Local<String> path_str;
|
js_msg = String::Concat(js_msg, OneByteString(isolate, msg));
|
||||||
|
js_msg = String::Concat(js_msg, FIXED_ONE_BYTE_STRING(isolate, ", "));
|
||||||
if (path) {
|
js_msg = String::Concat(js_msg, js_syscall);
|
||||||
#ifdef _WIN32
|
|
||||||
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 =
|
|
||||||
String::Concat(cons2, FIXED_ONE_BYTE_STRING(env->isolate(), " '"));
|
|
||||||
Local<String> cons4 =
|
|
||||||
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());
|
|
||||||
// TODO(piscisaureus) errno should probably go
|
|
||||||
obj->Set(env->errno_string(), Integer::New(env->isolate(), errorno));
|
|
||||||
obj->Set(env->code_string(), estring);
|
|
||||||
|
|
||||||
if (path != nullptr) {
|
if (path != nullptr) {
|
||||||
obj->Set(env->path_string(), path_str);
|
js_path = StringFromPath(isolate, path);
|
||||||
|
|
||||||
|
js_msg = String::Concat(js_msg, FIXED_ONE_BYTE_STRING(isolate, " '"));
|
||||||
|
js_msg = String::Concat(js_msg, js_path);
|
||||||
|
js_msg = String::Concat(js_msg, FIXED_ONE_BYTE_STRING(isolate, "'"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (syscall != nullptr) {
|
if (dest != nullptr) {
|
||||||
obj->Set(env->syscall_string(), OneByteString(env->isolate(), syscall));
|
js_dest = StringFromPath(isolate, dest);
|
||||||
|
|
||||||
|
js_msg = String::Concat(js_msg, FIXED_ONE_BYTE_STRING(isolate, " -> '"));
|
||||||
|
js_msg = String::Concat(js_msg, js_dest);
|
||||||
|
js_msg = String::Concat(js_msg, FIXED_ONE_BYTE_STRING(isolate, "'"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Local<Object> e = Exception::Error(js_msg)->ToObject(isolate);
|
||||||
|
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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, ...) \
|
||||||
|
@ -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) {
|
||||||
|
@ -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');
|
||||||
|
@ -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…
x
Reference in New Issue
Block a user