blob: 6b7648a35db5478f4977af3da4b50980265c472b [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/test/chromedriver/net/port_server.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/logging.h"
#include "base/process/process_handle.h"
#include "base/strings/string_number_conversions.h"
#include "base/sync_socket.h"
#include "chrome/test/chromedriver/chrome/status.h"
#include "net/base/sys_addrinfo.h"
#if defined(OS_LINUX)
#include <sys/socket.h>
#include <sys/un.h>
#endif
PortReservation::PortReservation(const base::Closure& on_free_func, int port)
: on_free_func_(on_free_func), port_(port) {}
PortReservation::~PortReservation() {
if (!on_free_func_.is_null())
on_free_func_.Run();
}
void PortReservation::Leak() {
LOG(ERROR) << "Port leaked: " << port_;
on_free_func_.Reset();
}
PortServer::PortServer(const std::string& path) : path_(path) {
CHECK(path_.size() && path_[0] == 0)
<< "path must be for Linux abstract namespace";
}
PortServer::~PortServer() {}
Status PortServer::ReservePort(int* port,
scoped_ptr<PortReservation>* reservation) {
int port_to_use = 0;
{
base::AutoLock lock(free_lock_);
if (free_.size()) {
port_to_use = free_.front();
free_.pop_front();
}
}
if (!port_to_use) {
Status status = RequestPort(&port_to_use);
if (status.IsError())
return status;
}
*port = port_to_use;
reservation->reset(new PortReservation(
base::Bind(&PortServer::ReleasePort, base::Unretained(this), port_to_use),
port_to_use));
return Status(kOk);
}
Status PortServer::RequestPort(int* port) {
// The client sends its PID + \n, and the server responds with a port + \n,
// which is valid for the lifetime of the referred process.
#if defined(OS_LINUX)
int sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock_fd < 0)
return Status(kUnknownError, "unable to create socket");
base::SyncSocket sock(sock_fd);
struct timeval tv;
tv.tv_sec = 10;
tv.tv_usec = 0;
if (setsockopt(sock_fd,
SOL_SOCKET,
SO_RCVTIMEO,
reinterpret_cast<char*>(&tv),
sizeof(tv)) < 0 ||
setsockopt(sock_fd,
SOL_SOCKET,
SO_SNDTIMEO,
reinterpret_cast<char*>(&tv),
sizeof(tv)) < 0) {
return Status(kUnknownError, "unable to set socket timeout");
}
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
memcpy(addr.sun_path, &path_[0], path_.length());
if (connect(sock.handle(),
reinterpret_cast<struct sockaddr*>(&addr),
sizeof(sa_family_t) + path_.length())) {
return Status(kUnknownError, "unable to connect");
}
int proc_id = static_cast<int>(base::GetCurrentProcId());
std::string request = base::IntToString(proc_id);
request += "\n";
VLOG(0) << "PORTSERVER REQUEST " << request;
if (sock.Send(request.c_str(), request.length()) != request.length())
return Status(kUnknownError, "failed to send portserver request");
std::string response;
do {
char c = 0;
size_t rv = sock.Receive(&c, 1);
if (!rv)
break;
response.push_back(c);
} while (sock.Peek());
if (response.empty())
return Status(kUnknownError, "failed to receive portserver response");
VLOG(0) << "PORTSERVER RESPONSE " << response;
int new_port = 0;
if (*response.rbegin() != '\n' ||
!base::StringToInt(response.substr(0, response.length() - 1), &new_port))
return Status(kUnknownError, "failed to parse portserver response");
*port = new_port;
return Status(kOk);
#else
return Status(kUnknownError, "not implemented for this platform");
#endif
}
void PortServer::ReleasePort(int port) {
base::AutoLock lock(free_lock_);
free_.push_back(port);
}