Bug 899757: Make nsServerSocket::InitWithAddress provide more detailed error results. r=mayhemer, r=ted

I looked through the NSPR socket creation functions that InitWithAddress
uses to see which errors they could return, and placed appropriate comments
in ErrorAccordingToNSPR.

The test coverage is not great; in particular, I wasn't able to find a way
to elicit "address in use" errors from Windows (although I could from
Linux); the web says that Windows is much more relaxed about binding
listening sockets than Unix derivatives. I'm interested in suggestions.
This commit is contained in:
Jim Blandy 2013-09-06 08:06:22 -07:00
parent 3e52bff456
commit 544b219f25
10 changed files with 185 additions and 4 deletions

View File

@ -167,6 +167,7 @@ XPC_MSG_DEF(NS_ERROR_UNKNOWN_PROXY_HOST , "The lookup of the proxy h
XPC_MSG_DEF(NS_ERROR_UNKNOWN_SOCKET_TYPE , "The specified socket type does not exist")
XPC_MSG_DEF(NS_ERROR_SOCKET_CREATE_FAILED , "The specified socket type could not be created")
XPC_MSG_DEF(NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED , "The specified socket address type is not supported")
XPC_MSG_DEF(NS_ERROR_SOCKET_ADDRESS_IN_USE , "Some other socket is already using the specified address.")
XPC_MSG_DEF(NS_ERROR_CACHE_KEY_NOT_FOUND , "Cache key could not be found")
XPC_MSG_DEF(NS_ERROR_CACHE_DATA_IS_STREAM , "Cache data is a stream")
XPC_MSG_DEF(NS_ERROR_CACHE_DATA_IS_NOT_STREAM , "Cache data is not a stream")

View File

@ -289,7 +289,7 @@ nsServerSocket::InitWithAddress(const PRNetAddr *aAddr, int32_t aBackLog)
if (!mFD)
{
NS_WARNING("unable to create server socket");
return NS_ERROR_FAILURE;
return ErrorAccordingToNSPR(PR_GetError());
}
PRSocketOptionData opt;
@ -330,8 +330,9 @@ nsServerSocket::InitWithAddress(const PRNetAddr *aAddr, int32_t aBackLog)
return NS_OK;
fail:
nsresult rv = ErrorAccordingToNSPR(PR_GetError());
Close();
return NS_ERROR_FAILURE;
return rv;
}
NS_IMETHODIMP

View File

@ -155,8 +155,13 @@ ErrorAccordingToNSPR(PRErrorCode errorCode)
rv = NS_ERROR_NET_INTERRUPT;
break;
case PR_CONNECT_REFUSED_ERROR:
case PR_NETWORK_UNREACHABLE_ERROR: // XXX need new nsresult for this!
case PR_HOST_UNREACHABLE_ERROR: // XXX and this!
// We lump the following NSPR codes in with PR_CONNECT_REFUSED_ERROR. We
// could get better diagnostics by adding distinct XPCOM error codes for
// each of these, but there are a lot of places in Gecko that check
// specifically for NS_ERROR_CONNECTION_REFUSED, all of which would need to
// be checked.
case PR_NETWORK_UNREACHABLE_ERROR:
case PR_HOST_UNREACHABLE_ERROR:
case PR_ADDRESS_NOT_AVAILABLE_ERROR:
// Treat EACCES as a soft error since (at least on Linux) connect() returns
// EACCES when an IPv6 connection is blocked by a firewall. See bug 270784.
@ -170,10 +175,68 @@ ErrorAccordingToNSPR(PRErrorCode errorCode)
case PR_CONNECT_TIMEOUT_ERROR:
rv = NS_ERROR_NET_TIMEOUT;
break;
case PR_OUT_OF_MEMORY_ERROR:
// These really indicate that the descriptor table filled up, or that the
// kernel ran out of network buffers - but nobody really cares which part of
// the system ran out of memory.
case PR_PROC_DESC_TABLE_FULL_ERROR:
case PR_SYS_DESC_TABLE_FULL_ERROR:
case PR_INSUFFICIENT_RESOURCES_ERROR:
rv = NS_ERROR_OUT_OF_MEMORY;
break;
case PR_ADDRESS_IN_USE_ERROR:
rv = NS_ERROR_SOCKET_ADDRESS_IN_USE;
break;
// These filename-related errors can arise when using Unix-domain sockets.
case PR_FILE_NOT_FOUND_ERROR:
rv = NS_ERROR_FILE_NOT_FOUND;
break;
case PR_IS_DIRECTORY_ERROR:
rv = NS_ERROR_FILE_IS_DIRECTORY;
break;
case PR_LOOP_ERROR:
rv = NS_ERROR_FILE_UNRESOLVABLE_SYMLINK;
break;
case PR_NAME_TOO_LONG_ERROR:
rv = NS_ERROR_FILE_NAME_TOO_LONG;
break;
case PR_NO_DEVICE_SPACE_ERROR:
rv = NS_ERROR_FILE_NO_DEVICE_SPACE;
break;
case PR_NOT_DIRECTORY_ERROR:
rv = NS_ERROR_FILE_NOT_DIRECTORY;
break;
case PR_READ_ONLY_FILESYSTEM_ERROR:
rv = NS_ERROR_FILE_READ_ONLY;
break;
default:
if (IsNSSErrorCode(errorCode))
rv = GetXPCOMFromNSSError(errorCode);
break;
// NSPR's socket code can return these, but they're not worth breaking out
// into their own error codes, distinct from NS_ERROR_FAILURE:
//
// PR_BAD_DESCRIPTOR_ERROR
// PR_INVALID_ARGUMENT_ERROR
// PR_NOT_SOCKET_ERROR
// PR_NOT_TCP_SOCKET_ERROR
// These would indicate a bug internal to the component.
//
// PR_PROTOCOL_NOT_SUPPORTED_ERROR
// This means that we can't use the given "protocol" (like
// IPPROTO_TCP or IPPROTO_UDP) with a socket of the given type. As
// above, this indicates an internal bug.
//
// PR_IS_CONNECTED_ERROR
// This indicates that we've applied a system call like 'bind' or
// 'connect' to a socket that is already connected. The socket
// components manage each file descriptor's state, and in some cases
// handle this error result internally. We shouldn't be returning
// this to our callers.
//
// PR_IO_ERROR
// This is so vague that NS_ERROR_FAILURE is just as good.
}
SOCKET_LOG(("ErrorAccordingToNSPR [in=%d out=%x]\n", errorCode, rv));
return rv;

View File

@ -0,0 +1,32 @@
// Opening a second listening socket on the same address as an extant
// socket should elicit NS_ERROR_SOCKET_ADDRESS_IN_USE on non-Windows
// machines.
const { classes: Cc, interfaces: Ci, results: Cr, Constructor: CC } = Components;
const ServerSocket = CC("@mozilla.org/network/server-socket;1",
"nsIServerSocket",
"init");
function testAddrInUse()
{
// Windows lets us have as many sockets listening on the same address as
// we like, evidently.
if ("@mozilla.org/windows-registry-key;1" in Cc) {
return;
}
// Create listening socket:
// any port (-1), loopback only (true), default backlog (-1)
let listener = ServerSocket(-1, true, -1);
do_check_true(listener instanceof Ci.nsIServerSocket);
// Try to create another listening socket on the same port, whatever that was.
do_check_throws_nsIException(() => ServerSocket(listener.port, true, -1),
"NS_ERROR_SOCKET_ADDRESS_IN_USE");
}
function run_test()
{
testAddrInUse();
}

View File

@ -2,6 +2,7 @@
head = head_channels.js head_cache.js
tail =
[test_addr_in_use_error.js]
[test_304_responses.js]
# Bug 675039: test hangs on Android-armv6
skip-if = os == "android"

View File

@ -0,0 +1,11 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
function run_test() {
let env = Components.classes["@mozilla.org/process/environment;1"]
.getService(Components.interfaces.nsIEnvironment);
do_check_throws_nsIException(function () {
env.QueryInterface(Components.interfaces.nsIFile);
}, "NS_NOINTERFACE");
}

View File

@ -0,0 +1,9 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
function run_test() {
do_check_throws_nsIException(function () {
throw Error("I find your relaxed dishabille unpalatable");
}, "NS_NOINTERFACE");
}

View File

@ -6,6 +6,10 @@
head =
tail =
[test_check_nsIException.js]
[test_check_nsIException_failing.js]
fail-if = true
[test_do_get_tempdir.js]
[test_execute_soon.js]
[test_get_file.js]

View File

@ -868,6 +868,63 @@ function format_pattern_match_failure(diagnosis, indent="") {
return indent + a;
}
// Check that |func| throws an nsIException that has
// |Components.results[resultName]| as the value of its 'result' property.
function do_check_throws_nsIException(func, resultName,
stack=Components.stack.caller, todo=false)
{
let expected = Components.results[resultName];
if (typeof expected !== 'number') {
do_throw("do_check_throws_nsIException requires a Components.results" +
" property name, not " + uneval(resultName), stack);
}
let msg = ("do_check_throws_nsIException: func should throw" +
" an nsIException whose 'result' is Components.results." +
resultName);
try {
func();
} catch (ex) {
if (!(ex instanceof Components.interfaces.nsIException) ||
ex.result !== expected) {
do_report_result(false, msg + ", threw " + legible_exception(ex) +
" instead", stack, todo);
}
do_report_result(true, msg, stack, todo);
return;
}
// Call this here, not in the 'try' clause, so do_report_result's own
// throw doesn't get caught by our 'catch' clause.
do_report_result(false, msg + ", but returned normally", stack, todo);
}
// Produce a human-readable form of |exception|. This looks up
// Components.results values, tries toString methods, and so on.
function legible_exception(exception)
{
switch (typeof exception) {
case 'object':
if (exception instanceof Components.interfaces.nsIException) {
return "nsIException instance: " + uneval(exception.toString());
}
return exception.toString();
case 'number':
for (let name in Components.results) {
if (exception === Components.results[name]) {
return "Components.results." + name;
}
}
// Fall through.
default:
return uneval(exception);
}
}
function do_test_pending(aName) {
++_tests_pending;

View File

@ -253,6 +253,8 @@
ERROR(NS_ERROR_SOCKET_CREATE_FAILED, FAILURE(52)),
/* The operating system doesn't support the given type of address. */
ERROR(NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED, FAILURE(53)),
/* The address to which we tried to bind the socket was busy. */
ERROR(NS_ERROR_SOCKET_ADDRESS_IN_USE, FAILURE(54)),
/* Cache specific error codes: */
ERROR(NS_ERROR_CACHE_KEY_NOT_FOUND, FAILURE(61)),