Index: tools/android/forwarder2/daemon.cc |
diff --git a/tools/android/forwarder2/daemon.cc b/tools/android/forwarder2/daemon.cc |
index 2928f8f627f7c778c08857a09def4e6442bf76f3..5bdf936aa9cd2085f62edf16b5fa6e0280297e9f 100644 |
--- a/tools/android/forwarder2/daemon.cc |
+++ b/tools/android/forwarder2/daemon.cc |
@@ -7,29 +7,32 @@ |
#include <errno.h> |
#include <fcntl.h> |
#include <signal.h> |
-#include <stdio.h> |
#include <sys/file.h> |
#include <sys/stat.h> |
#include <sys/types.h> |
#include <sys/wait.h> |
#include <unistd.h> |
+#include <cstdlib> |
+#include <cstring> |
#include <string> |
#include "base/basictypes.h" |
#include "base/file_path.h" |
#include "base/file_util.h" |
#include "base/logging.h" |
+#include "base/memory/scoped_ptr.h" |
#include "base/posix/eintr_wrapper.h" |
#include "base/safe_strerror_posix.h" |
#include "base/string_number_conversions.h" |
#include "base/stringprintf.h" |
#include "tools/android/forwarder2/common.h" |
+#include "tools/android/forwarder2/socket.h" |
namespace forwarder2 { |
namespace { |
-const char kLogFilePath[] = "/tmp/host_forwarder_log"; |
+const int kBufferSize = 256; |
class FileDescriptorAutoCloser { |
public: |
@@ -54,71 +57,44 @@ class FileDescriptorAutoCloser { |
DISALLOW_COPY_AND_ASSIGN(FileDescriptorAutoCloser); |
}; |
-// Handles creation and destruction of the PID file. |
-class PIDFile { |
- public: |
- static scoped_ptr<PIDFile> Create(const std::string& path) { |
- scoped_ptr<PIDFile> pid_file; |
- const int pid_file_fd = HANDLE_EINTR( |
- open(path.c_str(), O_CREAT | O_WRONLY, 0600)); |
- if (pid_file_fd < 0) { |
- PError("open()"); |
- return pid_file.Pass(); |
- } |
- FileDescriptorAutoCloser fd_closer(pid_file_fd); |
- struct flock lock_info = {}; |
- lock_info.l_type = F_WRLCK; |
- lock_info.l_whence = SEEK_CUR; |
- if (HANDLE_EINTR(fcntl(pid_file_fd, F_SETLK, &lock_info)) < 0) { |
- if (errno == EAGAIN || errno == EACCES) { |
- LOG(ERROR) << "Daemon already running (PID file already locked)"; |
- return pid_file.Pass(); |
- } |
- PError("lockf()"); |
- return pid_file.Pass(); |
- } |
- const std::string pid_string = base::StringPrintf("%d\n", getpid()); |
- CHECK(HANDLE_EINTR(write(pid_file_fd, pid_string.c_str(), |
- pid_string.length()))); |
- pid_file.reset(new PIDFile(fd_closer.Release(), path)); |
- return pid_file.Pass(); |
- } |
- |
- ~PIDFile() { |
- CloseFD(fd_); // This also releases the lock. |
- if (remove(path_.c_str()) < 0) |
- PError("remove"); |
- } |
- |
- private: |
- PIDFile(int fd, const std::string& path) : fd_(fd), path_(path) { |
- DCHECK(fd_ >= 0); |
- } |
- |
- const int fd_; |
- const std::string path_; |
- |
- DISALLOW_COPY_AND_ASSIGN(PIDFile); |
-}; |
- |
-// Takes ownership of |data|. |
-void ReleaseDaemonResourcesAtExit(void* data) { |
- DCHECK(data); |
- delete reinterpret_cast<PIDFile*>(data); |
-} |
- |
-void InitLogging(const char* log_file) { |
+void InitLoggingForDaemon(const std::string& log_file) { |
CHECK( |
logging::InitLogging( |
- log_file, |
- logging::LOG_ONLY_TO_FILE, |
- logging::DONT_LOCK_LOG_FILE, |
- logging::APPEND_TO_OLD_LOG_FILE, |
+ log_file.c_str(), |
+ log_file.empty() ? |
+ logging::LOG_ONLY_TO_SYSTEM_DEBUG_LOG : logging::LOG_ONLY_TO_FILE, |
+ logging::DONT_LOCK_LOG_FILE, logging::APPEND_TO_OLD_LOG_FILE, |
logging::ENABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS)); |
} |
+bool RunServerAcceptLoop(const std::string& welcome_message, |
+ Socket* server_socket, |
+ Daemon::ServerDelegate* server_delegate) { |
+ bool failed = false; |
+ for (;;) { |
+ scoped_ptr<Socket> client_socket(new Socket()); |
+ if (!server_socket->Accept(client_socket.get())) { |
+ if (server_socket->exited()) |
+ break; |
+ PError("Accept()"); |
+ failed = true; |
+ break; |
+ } |
+ if (!client_socket->Write(welcome_message.c_str(), |
+ welcome_message.length() + 1)) { |
+ PError("Write()"); |
+ failed = true; |
+ continue; |
+ } |
+ server_delegate->OnClientConnected(client_socket.Pass()); |
+ } |
+ server_delegate->OnServerExited(); |
+ return !failed; |
+} |
+ |
void SigChildHandler(int signal_number) { |
DCHECK_EQ(signal_number, SIGCHLD); |
+ SIGNAL_SAFE_LOG(ERROR, "Caught unexpected SIGCHLD"); |
// The daemon should not terminate while its parent is still running. |
int status; |
pid_t child_pid = waitpid(-1 /* any child */, &status, WNOHANG); |
@@ -166,39 +142,181 @@ bool GetFileLockOwnerPid(int fd, pid_t* lock_owner_pid) { |
return true; |
} |
+scoped_ptr<Socket> ConnectToUnixDomainSocket( |
+ const std::string& socket_name, |
+ int tries_count, |
+ int idle_time_msec, |
+ const std::string& expected_welcome_message) { |
+ for (int i = 0; i < tries_count; ++i) { |
+ scoped_ptr<Socket> socket(new Socket()); |
+ if (!socket->ConnectUnix(socket_name, true)) { |
+ if (idle_time_msec) |
+ usleep(idle_time_msec * 1000); |
+ continue; |
+ } |
+ char buf[kBufferSize]; |
+ DCHECK(expected_welcome_message.length() + 1 <= sizeof(buf)); |
+ memset(buf, 0, sizeof(buf)); |
+ if (socket->Read(buf, sizeof(buf)) < 0) { |
+ perror("read"); |
+ continue; |
+ } |
+ if (expected_welcome_message != buf) { |
+ LOG(ERROR) << "Unexpected message read from daemon: " << buf; |
+ break; |
+ } |
+ return socket.Pass(); |
+ } |
+ return scoped_ptr<Socket>(NULL); |
+} |
+ |
} // namespace |
-Daemon::Daemon(const std::string& pid_file_path) |
- : pid_file_path_(pid_file_path) { |
+// Handles creation and destruction of the PID file. |
+class Daemon::PIDFile { |
+ public: |
+ static scoped_ptr<PIDFile> Create(const std::string& path) { |
+ scoped_ptr<PIDFile> pid_file; |
+ const int pid_file_fd = HANDLE_EINTR( |
+ open(path.c_str(), O_CREAT | O_WRONLY, 0600)); |
+ if (pid_file_fd < 0) { |
+ PError("open()"); |
+ return pid_file.Pass(); |
+ } |
+ FileDescriptorAutoCloser fd_closer(pid_file_fd); |
+ struct flock lock_info = {}; |
+ lock_info.l_type = F_WRLCK; |
+ lock_info.l_whence = SEEK_CUR; |
+ if (HANDLE_EINTR(fcntl(pid_file_fd, F_SETLK, &lock_info)) < 0) { |
+ if (errno == EAGAIN || errno == EACCES) { |
+ LOG(ERROR) << "Daemon already running (PID file already locked)"; |
+ return pid_file.Pass(); |
+ } |
+ PError("lockf()"); |
+ return pid_file.Pass(); |
+ } |
+ const std::string pid_string = base::StringPrintf("%d\n", getpid()); |
+ CHECK(HANDLE_EINTR(write(pid_file_fd, pid_string.c_str(), |
+ pid_string.length()))); |
+ pid_file.reset(new PIDFile(fd_closer.Release(), path)); |
+ return pid_file.Pass(); |
+ } |
+ |
+ ~PIDFile() { |
+ CloseFD(fd_); // This also releases the lock. |
+ if (remove(path_.c_str()) < 0) |
+ PError("remove"); |
+ } |
+ |
+ private: |
+ PIDFile(int fd, const std::string& path) : fd_(fd), path_(path) { |
+ DCHECK(fd_ >= 0); |
+ } |
+ |
+ const int fd_; |
+ const std::string path_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(PIDFile); |
+}; |
+ |
+Daemon::Daemon(const std::string& log_file_path, |
+ const std::string& pid_file_path, |
+ const std::string& identifier, |
+ ClientDelegate* client_delegate, |
+ ServerDelegate* server_delegate, |
+ GetExitNotifierFDCallback get_exit_fd_callback) |
+ : log_file_path_(log_file_path), |
+ pid_file_path_(pid_file_path), |
+ identifier_(identifier), |
+ client_delegate_(client_delegate), |
+ server_delegate_(server_delegate), |
+ get_exit_fd_callback_(get_exit_fd_callback) { |
+ DCHECK(client_delegate_); |
+ DCHECK(server_delegate_); |
+ DCHECK(get_exit_fd_callback_); |
} |
-bool Daemon::Spawn(bool* is_daemon) { |
- switch (fork()) { |
- case -1: |
- *is_daemon = false; |
- PError("fork()"); |
- return false; |
- case 0: { // Child. |
- *is_daemon = true; |
- scoped_ptr<PIDFile> pid_file = PIDFile::Create(pid_file_path_); |
- if (!pid_file) |
- return false; |
- base::AtExitManager::RegisterCallback( |
- &ReleaseDaemonResourcesAtExit, pid_file.release()); |
- if (setsid() < 0) { // Detach the child process from its parent. |
- PError("setsid"); |
+Daemon::~Daemon() {} |
+ |
+bool Daemon::SpawnIfNeeded() { |
+ const int kSingleTry = 1; |
+ const int kNoIdleTime = 0; |
+ scoped_ptr<Socket> client_socket = ConnectToUnixDomainSocket( |
+ identifier_, kSingleTry, kNoIdleTime, identifier_); |
+ if (!client_socket) { |
+ switch (fork()) { |
+ case -1: |
+ PError("fork()"); |
return false; |
+ // Child. |
+ case 0: { |
+ DCHECK(!pid_file_); |
+ pid_file_ = PIDFile::Create(pid_file_path_); |
+ if (!pid_file_) |
+ exit(1); |
+ if (setsid() < 0) { // Detach the child process from its parent. |
+ PError("setsid()"); |
+ exit(1); |
+ } |
+ InitLoggingForDaemon(log_file_path_); |
+ CloseFD(STDIN_FILENO); |
+ CloseFD(STDOUT_FILENO); |
+ CloseFD(STDERR_FILENO); |
+ const int null_fd = open("/dev/null", O_RDWR); |
+ CHECK_EQ(null_fd, STDIN_FILENO); |
+ CHECK_EQ(dup(null_fd), STDOUT_FILENO); |
+ CHECK_EQ(dup(null_fd), STDERR_FILENO); |
+ Socket command_socket; |
+ if (!command_socket.BindUnix(identifier_, true)) { |
+ PError("bind()"); |
+ exit(1); |
+ } |
+ server_delegate_->Init(); |
+ command_socket.set_exit_notifier_fd(get_exit_fd_callback_()); |
+ exit(!RunServerAcceptLoop(identifier_, &command_socket, |
+ server_delegate_)); |
} |
- CloseFD(STDOUT_FILENO); |
- CloseFD(STDERR_FILENO); |
- InitLogging(kLogFilePath); |
- break; |
+ default: |
+ break; |
} |
- default: // Parent. |
- *is_daemon = false; |
- signal(SIGCHLD, SigChildHandler); |
} |
- return true; |
+ // Parent. |
+ // Install the custom SIGCHLD handler. |
+ sigset_t blocked_signals_set; |
+ if (sigprocmask(0 /* first arg ignored */, NULL, &blocked_signals_set) < 0) { |
+ PError("sigprocmask()"); |
+ return false; |
+ } |
+ struct sigaction old_action; |
+ struct sigaction new_action; |
+ memset(&new_action, 0, sizeof(new_action)); |
+ new_action.sa_handler = SigChildHandler; |
+ new_action.sa_flags = SA_NOCLDSTOP; |
+ sigemptyset(&new_action.sa_mask); |
+ if (sigaction(SIGCHLD, &new_action, &old_action) < 0) { |
+ PError("sigaction()"); |
+ return false; |
+ } |
+ // Connect to the daemon's Unix Domain Socket. |
+ bool failed = false; |
+ if (!client_socket) { |
+ const int kConnectTries = 20; |
+ const int kConnectIdleTimeMSec = 10; |
+ client_socket = ConnectToUnixDomainSocket( |
+ identifier_, kConnectTries, kConnectIdleTimeMSec, identifier_); |
+ if (!client_socket) { |
+ LOG(ERROR) << "Could not connect to daemon's Unix Daemon socket"; |
+ failed = true; |
+ } |
+ } |
+ if (!failed) |
+ client_delegate_->OnDaemonReady(client_socket.get()); |
+ // Restore the previous signal action for SIGCHLD. |
+ if (sigaction(SIGCHLD, &old_action, NULL) < 0) { |
+ PError("sigaction"); |
+ failed = true; |
+ } |
+ return !failed; |
} |
bool Daemon::Kill() { |