AU/unittest: test code spawns local HTTP server with unique port
With this change, unit testing code spawns a local (test) HTTP server
that listens on a unique TCP port. It is up to the server to allocate an
available port number (we use auto-allocation via bind) and report it
back (by default, via its stdout), which the unit test process parses.
Also part of this CL:
- Made the port a property of the server object, rather than a global
value. This makes more sense in general and may lend itself better to
future testing scenarios, such as running multiple servers in
parallel.
- Removed a redundant field (validate_quit) from PythonHttpServer and
simplified/robustified its shutdown procedure: if the server is known
to be responsive, a graceful signal is sent (via wget); otherwise, or
if the former failed, a more brutral signal(SIGKILL) is used.
- http_fetcher_unittest code now properly kills test_http_server if the
latter is unresponsive.
BUG=chromium:236465
TEST=Test server spawned with unique port
Change-Id: I699cd5019e4bd860f38205d84e5403cfb9b39f81
Reviewed-on: https://gerrit.chromium.org/gerrit/60637
Commit-Queue: Gilad Arnold <garnold@chromium.org>
Reviewed-by: Gilad Arnold <garnold@chromium.org>
Tested-by: Gilad Arnold <garnold@chromium.org>
diff --git a/http_fetcher_unittest.cc b/http_fetcher_unittest.cc
index b638411..7386bb3 100644
--- a/http_fetcher_unittest.cc
+++ b/http_fetcher_unittest.cc
@@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
#include <unistd.h>
#include <string>
@@ -45,16 +48,17 @@
const int kFlakySleepEvery = 3;
const int kFlakySleepSecs = 10;
-const int kServerPort = 8088;
-
} // namespace
namespace chromeos_update_engine {
static const char *kUnusedUrl = "unused://unused";
-static inline string LocalServerUrlForPath(const string& path) {
- return base::StringPrintf("http://127.0.0.1:%d%s", kServerPort, path.c_str());
+static inline string LocalServerUrlForPath(in_port_t port,
+ const string& path) {
+ string port_str = (port ? StringPrintf(":%hu", port) : "");
+ return base::StringPrintf("http://127.0.0.1%s%s", port_str.c_str(),
+ path.c_str());
}
//
@@ -66,6 +70,10 @@
// This makes it an abstract class (dirty but works).
virtual ~HttpServer() = 0;
+ virtual in_port_t GetPort() const {
+ return 0;
+ }
+
bool started_;
};
@@ -82,87 +90,104 @@
class PythonHttpServer : public HttpServer {
public:
- PythonHttpServer() {
- char *port_str = NULL;
- char *argv[] = {
- strdup("./test_http_server"),
- asprintf(&port_str, "%d", kServerPort) >= 0 ? port_str : NULL,
- NULL};
- GError *err;
+ PythonHttpServer() : pid_(-1), port_(0) {
started_ = false;
- validate_quit_ = true;
- if (!g_spawn_async(NULL,
- argv,
- NULL,
- G_SPAWN_DO_NOT_REAP_CHILD,
- NULL,
- NULL,
- &pid_,
- &err)) {
- LOG(INFO) << "unable to spawn http server process";
+
+ // Spawn the server process.
+ gchar *argv[] = {
+ const_cast<gchar*>("./test_http_server"),
+ NULL };
+ GError *err;
+ gint server_stdout = -1;
+ if (!g_spawn_async_with_pipes(NULL, argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
+ NULL, NULL, &pid_, NULL, &server_stdout, NULL,
+ &err)) {
+ LOG(ERROR) << "failed to spawn http server process";
return;
}
+ CHECK_GT(pid_, 0);
+ CHECK_GE(server_stdout, 0);
LOG(INFO) << "started http server with pid " << pid_;
- int rc = 1;
- const TimeDelta kMaxSleep = TimeDelta::FromMinutes(60);
- TimeDelta timeout = TimeDelta::FromMilliseconds(15);
+
+ // Wait for server to begin accepting connections, obtain its port.
+ char line[80];
+ const size_t listening_msg_prefix_len = strlen(kServerListeningMsgPrefix);
+ CHECK_GT(sizeof(line), listening_msg_prefix_len);
+ int line_len = read(server_stdout, line, sizeof(line) - 1);
+ if (line_len <= static_cast<int>(listening_msg_prefix_len)) {
+ if (line_len < 0) {
+ LOG(ERROR) << "error reading http server stdout: "
+ << strerror(errno);
+ } else {
+ LOG(ERROR) << "server output too short";
+ }
+ Terminate(true);
+ return;
+ }
+
+ line[line_len] = '\0';
+ CHECK_EQ(strstr(line, kServerListeningMsgPrefix), line);
+ const char* listening_port_str = line + listening_msg_prefix_len;
+ char* end_ptr;
+ long raw_port = strtol(listening_port_str, &end_ptr, 10);
+ CHECK(!*end_ptr || *end_ptr == '\n');
+ port_ = static_cast<in_port_t>(raw_port);
+ CHECK_GT(port_, 0);
started_ = true;
- while (rc && timeout < kMaxSleep) {
- // Wait before the first attempt also as it takes a while for the
- // test_http_server to be ready.
- LOG(INFO) << "waiting for " << utils::FormatTimeDelta(timeout);
- g_usleep(timeout.InMicroseconds());
- timeout *= 2;
-
- LOG(INFO) << "running wget to start";
- // rc should be 0 if we're able to successfully talk to the server.
- rc = system((string("wget --output-document=/dev/null ") +
- LocalServerUrlForPath("/test")).c_str());
- LOG(INFO) << "done running wget to start, rc = " << rc;
- }
-
- if (rc) {
- LOG(ERROR) << "Http server is not responding to wget.";
- // TODO(jaysri): Currently we're overloading two things in
- // started_ flag. One is that the process is running and other
- // is that the process is responsive. We should separate these
- // two so that we can do cleanup appropriately in each case.
- started_ = false;
- }
-
- for (unsigned i = 0; i < arraysize(argv); i++)
- free(argv[i]);
+ LOG(INFO) << "server running, listening on port " << port_;
LOG(INFO) << "gdb attach now!";
}
~PythonHttpServer() {
- if (!started_) {
- LOG(INFO) << "not waiting for http server with pid " << pid_
- << " to terminate, as it's not responding.";
- // TODO(jaysri): Kill the process if it's really running but
- // wgets or failing for some reason. Or if it's not running,
- // add code to get rid of the defunct process.
+ // If there's no process, do nothing.
+ if (pid_ == -1)
return;
+
+ // If server is responsive, request that it gracefully terminate.
+ bool do_kill = false;
+ if (started_) {
+ LOG(INFO) << "running wget to exit";
+ if (system((string("wget -t 1 --output-document=/dev/null ") +
+ LocalServerUrlForPath(port_, "/quitquitquit")).c_str())) {
+ LOG(WARNING) << "wget failed, resorting to brute force";
+ do_kill = true;
+ }
}
- // request that the server exit itself
- LOG(INFO) << "running wget to exit";
- int rc = system((string("wget -t 1 --output-document=/dev/null ") +
- LocalServerUrlForPath("/quitquitquit")).c_str());
- LOG(INFO) << "done running wget to exit";
- if (validate_quit_)
- EXPECT_EQ(0, rc);
- LOG(INFO) << "waiting for http server with pid " << pid_ << " to terminate";
- int status;
- waitpid(pid_, &status, 0);
- LOG(INFO) << "http server with pid " << pid_
- << " terminated with status " << status;
+ // Server not responding or wget failed, kill the process.
+ Terminate(do_kill);
}
+ virtual in_port_t GetPort() const {
+ return port_;
+ }
+
+ private:
+ void Terminate(bool do_kill) {
+ ASSERT_GT(pid_, 0);
+
+ if (do_kill) {
+ LOG(INFO) << "terminating (SIGKILL) server process with pid " << pid_;
+ kill(pid_, SIGKILL);
+ }
+
+ LOG(INFO) << "waiting for http server with pid " << pid_ << " to terminate";
+ int status;
+ pid_t killed_pid = waitpid(pid_, &status, 0);
+ ASSERT_EQ(killed_pid, pid_);
+ LOG(INFO) << "http server with pid " << pid_
+ << " terminated with status " << status;
+ pid_ = -1;
+ }
+
+ static const char* kServerListeningMsgPrefix;
+
GPid pid_;
- bool validate_quit_;
+ in_port_t port_;
};
+const char* PythonHttpServer::kServerListeningMsgPrefix = "listening on port ";
+
//
// Class hierarchy for HTTP fetcher test wrappers.
//
@@ -184,9 +209,9 @@
return NewSmallFetcher(1);
}
- virtual string BigUrl() const { return kUnusedUrl; }
- virtual string SmallUrl() const { return kUnusedUrl; }
- virtual string ErrorUrl() const { return kUnusedUrl; }
+ virtual string BigUrl(in_port_t port) const { return kUnusedUrl; }
+ virtual string SmallUrl(in_port_t port) const { return kUnusedUrl; }
+ virtual string ErrorUrl(in_port_t port) const { return kUnusedUrl; }
virtual bool IsMock() const = 0;
virtual bool IsMulti() const = 0;
@@ -257,23 +282,23 @@
return NewLargeFetcher(num_proxies);
}
- virtual string BigUrl() const {
- return LocalServerUrlForPath(base::StringPrintf("/download/%d",
+ virtual string BigUrl(in_port_t port) const {
+ return LocalServerUrlForPath(port,
+ base::StringPrintf("/download/%d",
kBigLength));
}
- virtual string SmallUrl() const {
- return LocalServerUrlForPath("/foo");
+ virtual string SmallUrl(in_port_t port) const {
+ return LocalServerUrlForPath(port, "/foo");
}
- virtual string ErrorUrl() const {
- return LocalServerUrlForPath("/error");
+ virtual string ErrorUrl(in_port_t port) const {
+ return LocalServerUrlForPath(port, "/error");
}
virtual bool IsMock() const { return false; }
virtual bool IsMulti() const { return false; }
virtual void IgnoreServerAborting(HttpServer* server) const {
- PythonHttpServer *pyserver = reinterpret_cast<PythonHttpServer*>(server);
- pyserver->validate_quit_ = false;
+ // Nothing to do.
}
virtual HttpServer *CreateServer() {
@@ -416,7 +441,8 @@
scoped_ptr<HttpServer> server(this->test_.CreateServer());
ASSERT_TRUE(server->started_);
- StartTransferArgs start_xfer_args = {fetcher.get(), this->test_.SmallUrl()};
+ StartTransferArgs start_xfer_args = {
+ fetcher.get(), this->test_.SmallUrl(server->GetPort())};
g_timeout_add(0, StartTransfer, &start_xfer_args);
g_main_loop_run(loop);
@@ -444,7 +470,8 @@
scoped_ptr<HttpServer> server(this->test_.CreateServer());
ASSERT_TRUE(server->started_);
- StartTransferArgs start_xfer_args = {fetcher.get(), this->test_.BigUrl()};
+ StartTransferArgs start_xfer_args = {
+ fetcher.get(), this->test_.BigUrl(server->GetPort())};
g_timeout_add(0, StartTransfer, &start_xfer_args);
g_main_loop_run(loop);
@@ -482,7 +509,7 @@
StartTransferArgs start_xfer_args = {
fetcher.get(),
- this->test_.ErrorUrl()
+ this->test_.ErrorUrl(server->GetPort())
};
g_timeout_add(0, StartTransfer, &start_xfer_args);
@@ -561,7 +588,7 @@
guint callback_id = g_timeout_add(kHttpResponseInternalServerError,
UnpausingTimeoutCallback, &delegate);
- fetcher->BeginTransfer(this->test_.BigUrl());
+ fetcher->BeginTransfer(this->test_.BigUrl(server->GetPort()));
g_main_loop_run(loop);
g_source_remove(callback_id);
@@ -641,7 +668,7 @@
g_source_set_callback(timeout_source_, AbortingTimeoutCallback, &delegate,
NULL);
g_source_attach(timeout_source_, NULL);
- delegate.fetcher_->BeginTransfer(this->test_.BigUrl());
+ delegate.fetcher_->BeginTransfer(this->test_.BigUrl(server->GetPort()));
g_main_loop_run(loop);
CHECK(!delegate.once_);
@@ -696,7 +723,8 @@
StartTransferArgs start_xfer_args = {
fetcher.get(),
- LocalServerUrlForPath(StringPrintf("/flaky/%d/%d/%d/%d", kBigLength,
+ LocalServerUrlForPath(server->GetPort(),
+ StringPrintf("/flaky/%d/%d/%d/%d", kBigLength,
kFlakyTruncateLength,
kFlakySleepEvery,
kFlakySleepSecs))
@@ -775,7 +803,7 @@
StartTransferArgs start_xfer_args = {
fetcher.get(),
- LocalServerUrlForPath(this->test_.SmallUrl())
+ LocalServerUrlForPath(0, this->test_.SmallUrl(0))
};
g_timeout_add(0, StartTransfer, &start_xfer_args);
@@ -798,7 +826,8 @@
StartTransferArgs start_xfer_args = {
fetcher.get(),
- LocalServerUrlForPath(StringPrintf("/flaky/%d/%d/%d/%d", kBigLength,
+ LocalServerUrlForPath(0,
+ StringPrintf("/flaky/%d/%d/%d/%d", kBigLength,
kFlakyTruncateLength,
kFlakySleepEvery,
kFlakySleepSecs))
@@ -845,7 +874,8 @@
};
// RedirectTest takes ownership of |http_fetcher|.
-void RedirectTest(bool expected_successful,
+void RedirectTest(const HttpServer* server,
+ bool expected_successful,
const string& url,
HttpFetcher* http_fetcher) {
GMainLoop* loop = g_main_loop_new(g_main_context_default(), FALSE);
@@ -865,7 +895,7 @@
.WillRepeatedly(Return(flimflam::kTypeEthernet));
StartTransferArgs start_xfer_args =
- { fetcher.get(), LocalServerUrlForPath(url) };
+ { fetcher.get(), LocalServerUrlForPath(server->GetPort(), url) };
g_timeout_add(0, StartTransfer, &start_xfer_args);
g_main_loop_run(loop);
@@ -893,7 +923,7 @@
const string url = base::StringPrintf("/redirect/%d/download/%d",
kRedirectCodes[c],
kMediumLength);
- RedirectTest(true, url, this->test_.NewLargeFetcher());
+ RedirectTest(server.get(), true, url, this->test_.NewLargeFetcher());
}
}
@@ -910,7 +940,7 @@
kRedirectCodes[r % arraysize(kRedirectCodes)]);
}
url += base::StringPrintf("/download/%d", kMediumLength);
- RedirectTest(true, url, this->test_.NewLargeFetcher());
+ RedirectTest(server.get(), true, url, this->test_.NewLargeFetcher());
}
TYPED_TEST(HttpFetcherTest, BeyondMaxRedirectTest) {
@@ -926,7 +956,7 @@
kRedirectCodes[r % arraysize(kRedirectCodes)]);
}
url += base::StringPrintf("/download/%d", kMediumLength);
- RedirectTest(false, url, this->test_.NewLargeFetcher());
+ RedirectTest(server.get(), false, url, this->test_.NewLargeFetcher());
}
namespace {
@@ -1025,7 +1055,7 @@
ranges.push_back(make_pair(0, 25));
ranges.push_back(make_pair(99, 0));
MultiTest(this->test_.NewLargeFetcher(),
- this->test_.BigUrl(),
+ this->test_.BigUrl(server->GetPort()),
ranges,
"abcdefghijabcdefghijabcdejabcdefghijabcdef",
kBigLength - (99 - 25),
@@ -1042,7 +1072,7 @@
vector<pair<off_t, off_t> > ranges;
ranges.push_back(make_pair(0, 24));
MultiTest(this->test_.NewLargeFetcher(),
- this->test_.BigUrl(),
+ this->test_.BigUrl(server->GetPort()),
ranges,
"abcdefghijabcdefghijabcd",
24,
@@ -1060,7 +1090,7 @@
ranges.push_back(make_pair(kBigLength - 2, 0));
ranges.push_back(make_pair(kBigLength - 3, 0));
MultiTest(this->test_.NewLargeFetcher(),
- this->test_.BigUrl(),
+ this->test_.BigUrl(server->GetPort()),
ranges,
"ijhij",
5,
@@ -1079,7 +1109,7 @@
for (int i = 0; i < 2; ++i) {
LOG(INFO) << "i = " << i;
MultiTest(this->test_.NewLargeFetcher(),
- this->test_.BigUrl(),
+ this->test_.BigUrl(server->GetPort()),
ranges,
"ij",
2,
@@ -1104,7 +1134,8 @@
ranges.push_back(make_pair(0, 25));
ranges.push_back(make_pair(99, 0));
MultiTest(this->test_.NewLargeFetcher(3),
- LocalServerUrlForPath(base::StringPrintf("/error-if-offset/%d/2",
+ LocalServerUrlForPath(server->GetPort(),
+ base::StringPrintf("/error-if-offset/%d/2",
kBigLength)),
ranges,
"abcdefghijabcdefghijabcdejabcdefghijabcdef",
@@ -1125,7 +1156,8 @@
ranges.push_back(make_pair(0, 25));
ranges.push_back(make_pair(99, 0));
MultiTest(this->test_.NewLargeFetcher(2),
- LocalServerUrlForPath(base::StringPrintf("/error-if-offset/%d/3",
+ LocalServerUrlForPath(server->GetPort(),
+ base::StringPrintf("/error-if-offset/%d/3",
kBigLength)),
ranges,
"abcdefghijabcdefghijabcde", // only received the first chunk
@@ -1185,7 +1217,9 @@
fetcher->set_delegate(&delegate);
StartTransferArgs start_xfer_args =
- { fetcher.get(), LocalServerUrlForPath(this->test_.SmallUrl()) };
+ {fetcher.get(),
+ LocalServerUrlForPath(server->GetPort(),
+ this->test_.SmallUrl(server->GetPort()))};
g_timeout_add(0, StartTransfer, &start_xfer_args);
g_main_loop_run(loop);
diff --git a/subprocess_unittest.cc b/subprocess_unittest.cc
index 427f7e3..cb295e1 100644
--- a/subprocess_unittest.cc
+++ b/subprocess_unittest.cc
@@ -2,6 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <poll.h>
+#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
@@ -31,7 +35,7 @@
};
namespace {
-const int kLocalHttpPort = 8088;
+int local_server_port = 0;
void Callback(int return_code, const string& output, void *p) {
EXPECT_EQ(1, return_code);
@@ -110,33 +114,62 @@
GMainLoop *loop;
};
+// TODO(garnold) this test method uses test_http_server as a representative for
+// interactive processes that can be spawned/terminated at will. This causes us
+// to go through hoops when spawning this process (e.g. obtaining the port
+// number it uses so we can control it with wget). It would have been much
+// preferred to use something else and thus simplify both test_http_server
+// (doesn't have to be able to communicate through a temp file) and the test
+// code below; for example, it sounds like a brain dead sleep loop with proper
+// signal handlers could be used instead.
gboolean StartAndCancelInRunLoop(gpointer data) {
CancelTestData* cancel_test_data = reinterpret_cast<CancelTestData*>(data);
+
+ // Create a temp file for test_http_server to communicate its port number.
+ char temp_file_name[] = "/tmp/subprocess_unittest-test_http_server-XXXXXX";
+ int temp_fd = mkstemp(temp_file_name);
+ CHECK_GE(temp_fd, 0);
+ int temp_flags = fcntl(temp_fd, F_GETFL, 0) | O_NONBLOCK;
+ CHECK_EQ(fcntl(temp_fd, F_SETFL, temp_flags), 0);
+
vector<string> cmd;
cmd.push_back("./test_http_server");
- cmd.push_back(StringPrintf("%d", kLocalHttpPort));
+ cmd.push_back(temp_file_name);
uint32_t tag = Subprocess::Get().Exec(cmd, CallbackBad, NULL);
EXPECT_NE(0, tag);
cancel_test_data->spawned = true;
- printf("spawned\n");
+ printf("test http server spawned\n");
// Wait for server to be up and running
TimeDelta total_wait_time;
const TimeDelta kSleepTime = TimeDelta::FromMilliseconds(100);
const TimeDelta kMaxWaitTime = TimeDelta::FromSeconds(3);
- for (;;) {
- int status =
- System(StringPrintf("wget -O /dev/null http://127.0.0.1:%d/foo",
- kLocalHttpPort));
- EXPECT_NE(-1, status) << "system() failed";
- EXPECT_TRUE(WIFEXITED(status))
- << "command failed to run or died abnormally";
- if (0 == WEXITSTATUS(status))
+ local_server_port = 0;
+ static const char* kServerListeningMsgPrefix = "listening on port ";
+ while (total_wait_time.InMicroseconds() < kMaxWaitTime.InMicroseconds()) {
+ char line[80];
+ int line_len = read(temp_fd, line, sizeof(line) - 1);
+ if (line_len > 0) {
+ line[line_len] = '\0';
+ CHECK_EQ(strstr(line, kServerListeningMsgPrefix), line);
+ const char* listening_port_str =
+ line + strlen(kServerListeningMsgPrefix);
+ char* end_ptr;
+ long raw_port = strtol(listening_port_str, &end_ptr, 10);
+ CHECK(!*end_ptr || *end_ptr == '\n');
+ local_server_port = static_cast<in_port_t>(raw_port);
break;
-
+ } else if (line_len < 0 && errno != EAGAIN) {
+ LOG(INFO) << "error reading from " << temp_file_name << ": "
+ << strerror(errno);
+ break;
+ }
g_usleep(kSleepTime.InMicroseconds());
total_wait_time += kSleepTime;
- CHECK_LT(total_wait_time.InMicroseconds(), kMaxWaitTime.InMicroseconds());
}
+ close(temp_fd);
+ remove(temp_file_name);
+ CHECK_GT(local_server_port, 0);
+ LOG(INFO) << "server listening on port " << local_server_port;
Subprocess::Get().CancelExec(tag);
return FALSE;
}
@@ -149,7 +182,7 @@
printf("tear down time\n");
int status = System(
StringPrintf("wget -O /dev/null http://127.0.0.1:%d/quitquitquit",
- kLocalHttpPort));
+ local_server_port));
EXPECT_NE(-1, status) << "system() failed";
EXPECT_TRUE(WIFEXITED(status))
<< "command failed to run or died abnormally";
@@ -168,7 +201,6 @@
g_timeout_add(10, &ExitWhenDone, &cancel_test_data);
g_main_loop_run(loop);
g_main_loop_unref(loop);
- printf("here\n");
}
} // namespace chromeos_update_engine
diff --git a/test_http_server.cc b/test_http_server.cc
index 2272052..92904ad 100644
--- a/test_http_server.cc
+++ b/test_http_server.cc
@@ -12,6 +12,7 @@
#include <err.h>
#include <errno.h>
+#include <fcntl.h>
#include <inttypes.h>
#include <netinet/in.h>
#include <signal.h>
@@ -19,6 +20,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
+#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
@@ -44,10 +46,7 @@
namespace chromeos_update_engine {
-// Allowed port range and default value.
-const long kPortMin = static_cast<long>(1) << 10;
-const long kPortMax = (static_cast<long>(1) << 16) - 1;
-const in_port_t kPortDefault = 8080;
+static const char* kListeningMsgPrefix = "listening on port ";
enum {
RC_OK = 0,
@@ -55,6 +54,9 @@
RC_ERR_READ,
RC_ERR_SETSOCKOPT,
RC_ERR_BIND,
+ RC_ERR_LISTEN,
+ RC_ERR_GETSOCKNAME,
+ RC_ERR_REPORT,
};
struct HttpRequest {
@@ -514,12 +516,15 @@
using namespace chromeos_update_engine;
+
void usage(const char *prog_arg) {
- static const char usage_str[] =
- "Usage: %s [ PORT ]\n"
- "where PORT is an integer between %ld and %ld (default is %d).\n";
- fprintf(stderr, usage_str, basename(prog_arg), kPortMin, kPortMax,
- kPortDefault);
+ fprintf(
+ stderr,
+ "Usage: %s [ FILE ]\n"
+ "Once accepting connections, the following is written to FILE (or "
+ "stdout):\n"
+ "\"%sN\" (where N is an integer port number)\n",
+ basename(prog_arg), kListeningMsgPrefix);
}
int main(int argc, char** argv) {
@@ -527,35 +532,28 @@
if (argc > 2)
errx(RC_BAD_ARGS, "unexpected number of arguments (use -h for usage)");
- // Parse inbound port number argument (in host byte-order, as of yet).
- in_port_t port = kPortDefault;
+ // Parse (optional) argument.
+ int report_fd = STDOUT_FILENO;
if (argc == 2) {
if (!strcmp(argv[1], "-h")) {
usage(argv[0]);
exit(RC_OK);
}
- char *end_ptr;
- long raw_port = strtol(argv[1], &end_ptr, 10);
- if (*end_ptr || raw_port < kPortMin || raw_port > kPortMax)
- errx(RC_BAD_ARGS, "invalid port: %s", argv[1]);
- port = static_cast<int>(raw_port);
+ report_fd = open(argv[1], O_WRONLY | O_CREAT, 00644);
}
// Ignore SIGPIPE on write() to sockets.
signal(SIGPIPE, SIG_IGN);
- socklen_t clilen;
- struct sockaddr_in server_addr = sockaddr_in();
- struct sockaddr_in client_addr = sockaddr_in();
-
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0)
LOG(FATAL) << "socket() failed";
+ struct sockaddr_in server_addr = sockaddr_in();
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
- server_addr.sin_port = htons(port); // byte-order conversion is necessary!
+ server_addr.sin_port = 0;
{
// Get rid of "Address in use" error
@@ -567,19 +565,45 @@
}
}
+ // Bind the socket and set for listening.
if (bind(listen_fd, reinterpret_cast<struct sockaddr *>(&server_addr),
sizeof(server_addr)) < 0) {
perror("bind");
exit(RC_ERR_BIND);
}
- CHECK_EQ(listen(listen_fd,5), 0);
+ if (listen(listen_fd, 5) < 0) {
+ perror("listen");
+ exit(RC_ERR_LISTEN);
+ }
+
+ // Check the actual port.
+ struct sockaddr_in bound_addr = sockaddr_in();
+ socklen_t bound_addr_len = sizeof(bound_addr);
+ if (getsockname(listen_fd, reinterpret_cast<struct sockaddr*>(&bound_addr),
+ &bound_addr_len) < 0) {
+ perror("getsockname");
+ exit(RC_ERR_GETSOCKNAME);
+ }
+ in_port_t port = ntohs(bound_addr.sin_port);
+
+ // Output the listening port, indicating that the server is processing
+ // requests. IMPORTANT! (a) the format of this message is as expected by some
+ // unit tests, avoid unilateral changes; (b) it is necessary to flush/sync the
+ // file to prevent the spawning process from waiting indefinitely for this
+ // message.
+ string listening_msg = StringPrintf("%s%hu", kListeningMsgPrefix, port);
+ LOG(INFO) << listening_msg;
+ CHECK_EQ(write(report_fd, listening_msg.c_str(), listening_msg.length()),
+ static_cast<int>(listening_msg.length()));
+ CHECK_EQ(write(report_fd, "\n", 1), 1);
+ if (report_fd == STDOUT_FILENO)
+ fsync(report_fd);
+ else
+ close(report_fd);
+
while (1) {
- LOG(INFO) << "pid(" << getpid()
- << "): waiting to accept new connection on port " << port;
- clilen = sizeof(client_addr);
- int client_fd = accept(listen_fd,
- (struct sockaddr *) &client_addr,
- &clilen);
+ LOG(INFO) << "pid(" << getpid() << "): waiting to accept new connection";
+ int client_fd = accept(listen_fd, NULL, NULL);
LOG(INFO) << "got past accept";
if (client_fd < 0)
LOG(FATAL) << "ERROR on accept";