summaryrefslogtreecommitdiffstats
path: root/tools/android/forwarder2/daemon.cc
diff options
context:
space:
mode:
Diffstat (limited to 'tools/android/forwarder2/daemon.cc')
-rw-r--r--tools/android/forwarder2/daemon.cc256
1 files changed, 256 insertions, 0 deletions
diff --git a/tools/android/forwarder2/daemon.cc b/tools/android/forwarder2/daemon.cc
new file mode 100644
index 0000000..c056460
--- /dev/null
+++ b/tools/android/forwarder2/daemon.cc
@@ -0,0 +1,256 @@
+// Copyright (c) 2012 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 "tools/android/forwarder2/daemon.h"
+
+#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 <string>
+
+#include "base/basictypes.h"
+#include "base/eintr_wrapper.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/safe_strerror_posix.h"
+#include "base/string_number_conversions.h"
+#include "base/stringprintf.h"
+#include "tools/android/forwarder2/common.h"
+
+namespace forwarder2 {
+namespace {
+
+const char kLogFilePath[] = "/tmp/host_forwarder_log";
+
+class FileDescriptorAutoCloser {
+ public:
+ explicit FileDescriptorAutoCloser(int fd) : fd_(fd) {
+ DCHECK(fd_ >= 0);
+ }
+
+ ~FileDescriptorAutoCloser() {
+ if (fd_ > -1)
+ CloseFD(fd_);
+ }
+
+ int Release() {
+ const int fd = fd_;
+ fd_ = -1;
+ return fd;
+ }
+
+ private:
+ int fd_;
+
+ 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) {
+ CHECK(
+ logging::InitLogging(
+ log_file,
+ logging::LOG_ONLY_TO_FILE,
+ logging::DONT_LOCK_LOG_FILE,
+ logging::APPEND_TO_OLD_LOG_FILE,
+ logging::ENABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS));
+}
+
+void SigChildHandler(int signal_number) {
+ DCHECK_EQ(signal_number, SIGCHLD);
+ // The daemon should not terminate while its parent is still running.
+ int status;
+ pid_t child_pid = waitpid(-1 /* any child */, &status, WNOHANG);
+ if (child_pid < 0) {
+ PError("waitpid");
+ return;
+ }
+ if (child_pid == 0)
+ return;
+ // Avoid using StringAppendF() since it's unsafe in a signal handler due to
+ // its use of LOG().
+ FixedSizeStringBuilder<256> string_builder;
+ string_builder.Append("Daemon (pid=%d) died unexpectedly with ", child_pid);
+ if (WIFEXITED(status))
+ string_builder.Append("status %d.", WEXITSTATUS(status));
+ else if (WIFSIGNALED(status))
+ string_builder.Append("signal %d.", WTERMSIG(status));
+ else
+ string_builder.Append("unknown reason.");
+ SIGNAL_SAFE_LOG(ERROR, string_builder.buffer());
+}
+
+// Note that 0 is written to |lock_owner_pid| in case the file is not locked.
+bool GetFileLockOwnerPid(int fd, pid_t* lock_owner_pid) {
+ struct flock lock_info = {};
+ lock_info.l_type = F_WRLCK;
+ lock_info.l_whence = SEEK_CUR;
+ const int ret = HANDLE_EINTR(fcntl(fd, F_GETLK, &lock_info));
+ if (ret < 0) {
+ if (errno == EBADF) {
+ // Assume that the provided file descriptor corresponding to the PID file
+ // was valid until the daemon removed this file.
+ *lock_owner_pid = 0;
+ return true;
+ }
+ PError("fcntl");
+ return false;
+ }
+ if (lock_info.l_type == F_UNLCK) {
+ *lock_owner_pid = 0;
+ return true;
+ }
+ CHECK_EQ(F_WRLCK /* exclusive lock */, lock_info.l_type);
+ *lock_owner_pid = lock_info.l_pid;
+ return true;
+}
+
+} // namespace
+
+Daemon::Daemon(const std::string& pid_file_path)
+ : pid_file_path_(pid_file_path) {
+}
+
+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");
+ return false;
+ }
+ CloseFD(STDOUT_FILENO);
+ CloseFD(STDERR_FILENO);
+ InitLogging(kLogFilePath);
+ break;
+ }
+ default: // Parent.
+ *is_daemon = false;
+ signal(SIGCHLD, SigChildHandler);
+ }
+ return true;
+}
+
+bool Daemon::Kill() {
+ int pid_file_fd = HANDLE_EINTR(open(pid_file_path_.c_str(), O_WRONLY));
+ if (pid_file_fd < 0) {
+ if (errno == ENOENT)
+ return true;
+ LOG(ERROR) << "Could not open " << pid_file_path_ << " in write mode: "
+ << safe_strerror(errno);
+ return false;
+ }
+ const FileDescriptorAutoCloser fd_closer(pid_file_fd);
+ pid_t lock_owner_pid;
+ if (!GetFileLockOwnerPid(pid_file_fd, &lock_owner_pid))
+ return false;
+ if (lock_owner_pid == 0)
+ // No daemon running.
+ return true;
+ if (kill(lock_owner_pid, SIGTERM) < 0) {
+ if (errno == ESRCH /* invalid PID */)
+ // The daemon exited for some reason (e.g. kill by a process other than
+ // us) right before the call to kill() above.
+ return true;
+ PError("kill");
+ return false;
+ }
+ // Wait until the daemon exits. Rely on the fact that the daemon releases the
+ // lock on the PID file when it exits.
+ // TODO(pliard): Consider using a mutex + condition in shared memory to avoid
+ // polling.
+ const int kTries = 20;
+ const int kIdleTimeMS = 50;
+ for (int i = 0; i < kTries; ++i) {
+ pid_t current_lock_owner_pid;
+ if (!GetFileLockOwnerPid(pid_file_fd, &current_lock_owner_pid))
+ return false;
+ if (current_lock_owner_pid == 0)
+ // The daemon released the PID file's lock.
+ return true;
+ // Since we are polling we might not see the 'daemon exited' event if
+ // another daemon was spawned during our idle period.
+ if (current_lock_owner_pid != lock_owner_pid) {
+ LOG(WARNING) << "Daemon (pid=" << lock_owner_pid
+ << ") was successfully killed but a new daemon (pid="
+ << current_lock_owner_pid << ") seems to be running now.";
+ return true;
+ }
+ usleep(kIdleTimeMS * 1000);
+ }
+ LOG(ERROR) << "Timed out while killing daemon. "
+ "It might still be tearing down.";
+ return false;
+}
+
+} // namespace forwarder2