|  | // Copyright 2014 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. | 
|  |  | 
|  | // On Linux, when the user tries to launch a second copy of chrome, we check | 
|  | // for a socket in the user's profile directory.  If the socket file is open we | 
|  | // send a message to the first chrome browser process with the current | 
|  | // directory and second process command line flags.  The second process then | 
|  | // exits. | 
|  | // | 
|  | // Because many networked filesystem implementations do not support unix domain | 
|  | // sockets, we create the socket in a temporary directory and create a symlink | 
|  | // in the profile. This temporary directory is no longer bound to the profile, | 
|  | // and may disappear across a reboot or login to a separate session. To bind | 
|  | // them, we store a unique cookie in the profile directory, which must also be | 
|  | // present in the remote directory to connect. The cookie is checked both before | 
|  | // and after the connection. /tmp is sticky, and different Chrome sessions use | 
|  | // different cookies. Thus, a matching cookie before and after means the | 
|  | // connection was to a directory with a valid cookie. | 
|  | // | 
|  | // We also have a lock file, which is a symlink to a non-existent destination. | 
|  | // The destination is a string containing the hostname and process id of | 
|  | // chrome's browser process, eg. "SingletonLock -> example.com-9156".  When the | 
|  | // first copy of chrome exits it will delete the lock file on shutdown, so that | 
|  | // a different instance on a different host may then use the profile directory. | 
|  | // | 
|  | // If writing to the socket fails, the hostname in the lock is checked to see if | 
|  | // another instance is running a different host using a shared filesystem (nfs, | 
|  | // etc.) If the hostname differs an error is displayed and the second process | 
|  | // exits.  Otherwise the first process (if any) is killed and the second process | 
|  | // starts as normal. | 
|  | // | 
|  | // When the second process sends the current directory and command line flags to | 
|  | // the first process, it waits for an ACK message back from the first process | 
|  | // for a certain time. If there is no ACK message back in time, then the first | 
|  | // process will be considered as hung for some reason. The second process then | 
|  | // retrieves the process id from the symbol link and kills it by sending | 
|  | // SIGKILL. Then the second process starts as normal. | 
|  |  | 
|  | #include "chrome/browser/process_singleton.h" | 
|  |  | 
|  | #include <errno.h> | 
|  | #include <fcntl.h> | 
|  | #include <signal.h> | 
|  | #include <sys/socket.h> | 
|  | #include <sys/stat.h> | 
|  | #include <sys/types.h> | 
|  | #include <sys/un.h> | 
|  | #include <unistd.h> | 
|  |  | 
|  | #include <cstring> | 
|  | #include <memory> | 
|  | #include <set> | 
|  | #include <string> | 
|  |  | 
|  | #include <stddef.h> | 
|  |  | 
|  | #include "base/base_paths.h" | 
|  | #include "base/bind.h" | 
|  | #include "base/command_line.h" | 
|  | #include "base/containers/unique_ptr_adapters.h" | 
|  | #include "base/files/file_descriptor_watcher_posix.h" | 
|  | #include "base/files/file_path.h" | 
|  | #include "base/files/file_util.h" | 
|  | #include "base/location.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/memory/ref_counted.h" | 
|  | #include "base/metrics/histogram_functions.h" | 
|  | #include "base/metrics/histogram_macros.h" | 
|  | #include "base/path_service.h" | 
|  | #include "base/posix/eintr_wrapper.h" | 
|  | #include "base/posix/safe_strerror.h" | 
|  | #include "base/rand_util.h" | 
|  | #include "base/sequenced_task_runner_helpers.h" | 
|  | #include "base/single_thread_task_runner.h" | 
|  | #include "base/stl_util.h" | 
|  | #include "base/strings/string_number_conversions.h" | 
|  | #include "base/strings/string_split.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "base/strings/sys_string_conversions.h" | 
|  | #include "base/strings/utf_string_conversions.h" | 
|  | #include "base/threading/platform_thread.h" | 
|  | #include "base/threading/thread_task_runner_handle.h" | 
|  | #include "base/time/time.h" | 
|  | #include "base/timer/timer.h" | 
|  | #include "build/build_config.h" | 
|  | #include "build/chromeos_buildflags.h" | 
|  | #include "chrome/common/chrome_constants.h" | 
|  | #include "chrome/common/process_singleton_lock_posix.h" | 
|  | #include "chrome/grit/chromium_strings.h" | 
|  | #include "chrome/grit/generated_resources.h" | 
|  | #include "content/public/browser/browser_task_traits.h" | 
|  | #include "content/public/browser/browser_thread.h" | 
|  | #include "net/base/network_interfaces.h" | 
|  | #include "ui/base/l10n/l10n_util.h" | 
|  |  | 
|  | #if defined(OS_LINUX) || defined(OS_CHROMEOS) | 
|  | #include "chrome/browser/ui/process_singleton_dialog_linux.h" | 
|  | #endif | 
|  |  | 
|  | #if defined(TOOLKIT_VIEWS) && \ | 
|  | (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)) | 
|  | #include "ui/views/linux_ui/linux_ui.h" | 
|  | #endif | 
|  |  | 
|  | using content::BrowserThread; | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Timeout for the current browser process to respond. 20 seconds should be | 
|  | // enough. | 
|  | const int kTimeoutInSeconds = 20; | 
|  | // Number of retries to notify the browser. 20 retries over 20 seconds = 1 try | 
|  | // per second. | 
|  | const int kRetryAttempts = 20; | 
|  | const char kStartToken[] = "START"; | 
|  | const char kACKToken[] = "ACK"; | 
|  | const char kShutdownToken[] = "SHUTDOWN"; | 
|  | const char kTokenDelimiter = '\0'; | 
|  | const int kMaxMessageLength = 32 * 1024; | 
|  | const int kMaxACKMessageLength = base::size(kShutdownToken) - 1; | 
|  |  | 
|  | bool g_disable_prompt = false; | 
|  | bool g_skip_is_chrome_process_check = false; | 
|  | bool g_user_opted_unlock_in_use_profile = false; | 
|  |  | 
|  | // Set the close-on-exec bit on a file descriptor. | 
|  | // Returns 0 on success, -1 on failure. | 
|  | int SetCloseOnExec(int fd) { | 
|  | int flags = fcntl(fd, F_GETFD, 0); | 
|  | if (-1 == flags) | 
|  | return flags; | 
|  | if (flags & FD_CLOEXEC) | 
|  | return 0; | 
|  | return fcntl(fd, F_SETFD, flags | FD_CLOEXEC); | 
|  | } | 
|  |  | 
|  | // Close a socket and check return value. | 
|  | void CloseSocket(int fd) { | 
|  | int rv = IGNORE_EINTR(close(fd)); | 
|  | DCHECK_EQ(0, rv) << "Error closing socket: " << base::safe_strerror(errno); | 
|  | } | 
|  |  | 
|  | // Write a message to a socket fd. | 
|  | bool WriteToSocket(int fd, const char *message, size_t length) { | 
|  | DCHECK(message); | 
|  | DCHECK(length); | 
|  | size_t bytes_written = 0; | 
|  | do { | 
|  | ssize_t rv = HANDLE_EINTR( | 
|  | write(fd, message + bytes_written, length - bytes_written)); | 
|  | if (rv < 0) { | 
|  | if (errno == EAGAIN || errno == EWOULDBLOCK) { | 
|  | // The socket shouldn't block, we're sending so little data.  Just give | 
|  | // up here, since NotifyOtherProcess() doesn't have an asynchronous api. | 
|  | LOG(ERROR) << "ProcessSingleton would block on write(), so it gave up."; | 
|  | return false; | 
|  | } | 
|  | PLOG(ERROR) << "write() failed"; | 
|  | return false; | 
|  | } | 
|  | bytes_written += rv; | 
|  | } while (bytes_written < length); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | struct timeval TimeDeltaToTimeVal(const base::TimeDelta& delta) { | 
|  | struct timeval result; | 
|  | result.tv_sec = delta.InSeconds(); | 
|  | result.tv_usec = delta.InMicroseconds() % base::Time::kMicrosecondsPerSecond; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | // Wait a socket for read for a certain timeout. | 
|  | // Returns -1 if error occurred, 0 if timeout reached, > 0 if the socket is | 
|  | // ready for read. | 
|  | int WaitSocketForRead(int fd, const base::TimeDelta& timeout) { | 
|  | fd_set read_fds; | 
|  | struct timeval tv = TimeDeltaToTimeVal(timeout); | 
|  |  | 
|  | FD_ZERO(&read_fds); | 
|  | FD_SET(fd, &read_fds); | 
|  |  | 
|  | return HANDLE_EINTR(select(fd + 1, &read_fds, NULL, NULL, &tv)); | 
|  | } | 
|  |  | 
|  | // Read a message from a socket fd, with an optional timeout. | 
|  | // If |timeout| <= 0 then read immediately. | 
|  | // Return number of bytes actually read, or -1 on error. | 
|  | ssize_t ReadFromSocket(int fd, | 
|  | char* buf, | 
|  | size_t bufsize, | 
|  | const base::TimeDelta& timeout) { | 
|  | if (timeout > base::TimeDelta()) { | 
|  | int rv = WaitSocketForRead(fd, timeout); | 
|  | if (rv <= 0) | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | size_t bytes_read = 0; | 
|  | do { | 
|  | ssize_t rv = HANDLE_EINTR(read(fd, buf + bytes_read, bufsize - bytes_read)); | 
|  | if (rv < 0) { | 
|  | if (errno != EAGAIN && errno != EWOULDBLOCK) { | 
|  | PLOG(ERROR) << "read() failed"; | 
|  | return rv; | 
|  | } else { | 
|  | // It would block, so we just return what has been read. | 
|  | return bytes_read; | 
|  | } | 
|  | } else if (!rv) { | 
|  | // No more data to read. | 
|  | return bytes_read; | 
|  | } else { | 
|  | bytes_read += rv; | 
|  | } | 
|  | } while (bytes_read < bufsize); | 
|  |  | 
|  | return bytes_read; | 
|  | } | 
|  |  | 
|  | // Set up a sockaddr appropriate for messaging. | 
|  | bool SetupSockAddr(const std::string& path, struct sockaddr_un* addr) { | 
|  | addr->sun_family = AF_UNIX; | 
|  | if (path.length() >= base::size(addr->sun_path)) | 
|  | return false; | 
|  | base::strlcpy(addr->sun_path, path.c_str(), base::size(addr->sun_path)); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Set up a socket appropriate for messaging. | 
|  | int SetupSocketOnly() { | 
|  | int sock = socket(PF_UNIX, SOCK_STREAM, 0); | 
|  | PCHECK(sock >= 0) << "socket() failed"; | 
|  |  | 
|  | DCHECK(base::SetNonBlocking(sock)) << "Failed to make non-blocking socket."; | 
|  | int rv = SetCloseOnExec(sock); | 
|  | DCHECK_EQ(0, rv) << "Failed to set CLOEXEC on socket."; | 
|  |  | 
|  | return sock; | 
|  | } | 
|  |  | 
|  | // Set up a socket and sockaddr appropriate for messaging. | 
|  | void SetupSocket(const std::string& path, int* sock, struct sockaddr_un* addr) { | 
|  | *sock = SetupSocketOnly(); | 
|  | CHECK(SetupSockAddr(path, addr)) << "Socket path too long: " << path; | 
|  | } | 
|  |  | 
|  | // Read a symbolic link, return empty string if given path is not a symbol link. | 
|  | base::FilePath ReadLink(const base::FilePath& path) { | 
|  | base::FilePath target; | 
|  | if (!base::ReadSymbolicLink(path, &target)) { | 
|  | // The only errno that should occur is ENOENT. | 
|  | if (errno != 0 && errno != ENOENT) | 
|  | PLOG(ERROR) << "readlink(" << path.value() << ") failed"; | 
|  | } | 
|  | return target; | 
|  | } | 
|  |  | 
|  | // Unlink a path. Return true on success. | 
|  | bool UnlinkPath(const base::FilePath& path) { | 
|  | int rv = unlink(path.value().c_str()); | 
|  | if (rv < 0 && errno != ENOENT) | 
|  | PLOG(ERROR) << "Failed to unlink " << path.value(); | 
|  |  | 
|  | return rv == 0; | 
|  | } | 
|  |  | 
|  | // Create a symlink. Returns true on success. | 
|  | bool SymlinkPath(const base::FilePath& target, const base::FilePath& path) { | 
|  | if (!base::CreateSymbolicLink(target, path)) { | 
|  | // Double check the value in case symlink suceeded but we got an incorrect | 
|  | // failure due to NFS packet loss & retry. | 
|  | int saved_errno = errno; | 
|  | if (ReadLink(path) != target) { | 
|  | // If we failed to create the lock, most likely another instance won the | 
|  | // startup race. | 
|  | errno = saved_errno; | 
|  | PLOG(ERROR) << "Failed to create " << path.value(); | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Returns true if the user opted to unlock the profile. | 
|  | bool DisplayProfileInUseError(const base::FilePath& lock_path, | 
|  | const std::string& hostname, | 
|  | int pid) { | 
|  | std::u16string error = l10n_util::GetStringFUTF16( | 
|  | IDS_PROFILE_IN_USE_POSIX, base::NumberToString16(pid), | 
|  | base::ASCIIToUTF16(hostname)); | 
|  | LOG(ERROR) << error; | 
|  |  | 
|  | if (g_disable_prompt) | 
|  | return g_user_opted_unlock_in_use_profile; | 
|  |  | 
|  | #if defined(OS_LINUX) || defined(OS_CHROMEOS) | 
|  | std::u16string relaunch_button_text = | 
|  | l10n_util::GetStringUTF16(IDS_PROFILE_IN_USE_LINUX_RELAUNCH); | 
|  | return ShowProcessSingletonDialog(error, relaunch_button_text); | 
|  | #elif defined(OS_MAC) | 
|  | // On Mac, always usurp the lock. | 
|  | return true; | 
|  | #endif | 
|  |  | 
|  | NOTREACHED(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool IsChromeProcess(pid_t pid) { | 
|  | if (g_skip_is_chrome_process_check) | 
|  | return true; | 
|  |  | 
|  | base::FilePath other_chrome_path(base::GetProcessExecutablePath(pid)); | 
|  | return (!other_chrome_path.empty() && | 
|  | other_chrome_path.BaseName() == | 
|  | base::FilePath(chrome::kBrowserProcessExecutableName)); | 
|  | } | 
|  |  | 
|  | // A helper class to hold onto a socket. | 
|  | class ScopedSocket { | 
|  | public: | 
|  | ScopedSocket() : fd_(-1) { Reset(); } | 
|  | ~ScopedSocket() { Close(); } | 
|  | int fd() { return fd_; } | 
|  | void Reset() { | 
|  | Close(); | 
|  | fd_ = SetupSocketOnly(); | 
|  | } | 
|  | void Close() { | 
|  | if (fd_ >= 0) | 
|  | CloseSocket(fd_); | 
|  | fd_ = -1; | 
|  | } | 
|  | private: | 
|  | int fd_; | 
|  | }; | 
|  |  | 
|  | // Returns a random string for uniquifying profile connections. | 
|  | std::string GenerateCookie() { | 
|  | return base::NumberToString(base::RandUint64()); | 
|  | } | 
|  |  | 
|  | bool CheckCookie(const base::FilePath& path, const base::FilePath& cookie) { | 
|  | return (cookie == ReadLink(path)); | 
|  | } | 
|  |  | 
|  | bool ConnectSocket(ScopedSocket* socket, | 
|  | const base::FilePath& socket_path, | 
|  | const base::FilePath& cookie_path) { | 
|  | base::FilePath socket_target; | 
|  | if (base::ReadSymbolicLink(socket_path, &socket_target)) { | 
|  | // It's a symlink. Read the cookie. | 
|  | base::FilePath cookie = ReadLink(cookie_path); | 
|  | if (cookie.empty()) | 
|  | return false; | 
|  | base::FilePath remote_cookie = socket_target.DirName(). | 
|  | Append(chrome::kSingletonCookieFilename); | 
|  | // Verify the cookie before connecting. | 
|  | if (!CheckCookie(remote_cookie, cookie)) | 
|  | return false; | 
|  | // Now we know the directory was (at that point) created by the profile | 
|  | // owner. Try to connect. | 
|  | sockaddr_un addr; | 
|  | if (!SetupSockAddr(socket_target.value(), &addr)) { | 
|  | // If a sockaddr couldn't be initialized due to too long of a socket | 
|  | // path, we can be sure there isn't already a Chrome running with this | 
|  | // socket path, since it would have hit the CHECK() on the path length. | 
|  | return false; | 
|  | } | 
|  | int ret = HANDLE_EINTR(connect(socket->fd(), | 
|  | reinterpret_cast<sockaddr*>(&addr), | 
|  | sizeof(addr))); | 
|  | if (ret != 0) | 
|  | return false; | 
|  | // Check the cookie again. We only link in /tmp, which is sticky, so, if the | 
|  | // directory is still correct, it must have been correct in-between when we | 
|  | // connected. POSIX, sadly, lacks a connectat(). | 
|  | if (!CheckCookie(remote_cookie, cookie)) { | 
|  | socket->Reset(); | 
|  | return false; | 
|  | } | 
|  | // Success! | 
|  | return true; | 
|  | } else if (errno == EINVAL) { | 
|  | // It exists, but is not a symlink (or some other error we detect | 
|  | // later). Just connect to it directly; this is an older version of Chrome. | 
|  | sockaddr_un addr; | 
|  | if (!SetupSockAddr(socket_path.value(), &addr)) { | 
|  | // If a sockaddr couldn't be initialized due to too long of a socket | 
|  | // path, we can be sure there isn't already a Chrome running with this | 
|  | // socket path, since it would have hit the CHECK() on the path length. | 
|  | return false; | 
|  | } | 
|  | int ret = HANDLE_EINTR(connect(socket->fd(), | 
|  | reinterpret_cast<sockaddr*>(&addr), | 
|  | sizeof(addr))); | 
|  | return (ret == 0); | 
|  | } else { | 
|  | // File is missing, or other error. | 
|  | if (errno != ENOENT) | 
|  | PLOG(ERROR) << "readlink failed"; | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | #if defined(OS_MAC) | 
|  | bool ReplaceOldSingletonLock(const base::FilePath& symlink_content, | 
|  | const base::FilePath& lock_path) { | 
|  | // Try taking an flock(2) on the file. Failure means the lock is taken so we | 
|  | // should quit. | 
|  | base::ScopedFD lock_fd(HANDLE_EINTR( | 
|  | open(lock_path.value().c_str(), O_RDWR | O_CREAT | O_SYMLINK, 0644))); | 
|  | if (!lock_fd.is_valid()) { | 
|  | PLOG(ERROR) << "Could not open singleton lock"; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | int rc = HANDLE_EINTR(flock(lock_fd.get(), LOCK_EX | LOCK_NB)); | 
|  | if (rc == -1) { | 
|  | if (errno == EWOULDBLOCK) { | 
|  | LOG(ERROR) << "Singleton lock held by old process."; | 
|  | } else { | 
|  | PLOG(ERROR) << "Error locking singleton lock"; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Successfully taking the lock means we can replace it with the a new symlink | 
|  | // lock. We never flock() the lock file from now on. I.e. we assume that an | 
|  | // old version of Chrome will not run with the same user data dir after this | 
|  | // version has run. | 
|  | if (!base::DeleteFile(lock_path)) { | 
|  | PLOG(ERROR) << "Could not delete old singleton lock."; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return SymlinkPath(symlink_content, lock_path); | 
|  | } | 
|  | #endif  // defined(OS_MAC) | 
|  |  | 
|  | void SendRemoteProcessInteractionResultHistogram( | 
|  | ProcessSingleton::RemoteProcessInteractionResult result) { | 
|  | UMA_HISTOGRAM_ENUMERATION( | 
|  | "Chrome.ProcessSingleton.RemoteProcessInteractionResult", result, | 
|  | ProcessSingleton::REMOTE_PROCESS_INTERACTION_RESULT_COUNT); | 
|  | } | 
|  |  | 
|  | void SendRemoteHungProcessTerminateReasonHistogram( | 
|  | ProcessSingleton::RemoteHungProcessTerminateReason reason) { | 
|  | UMA_HISTOGRAM_ENUMERATION( | 
|  | "Chrome.ProcessSingleton.RemoteHungProcessTerminateReason", reason, | 
|  | ProcessSingleton::REMOTE_HUNG_PROCESS_TERMINATE_REASON_COUNT); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | /////////////////////////////////////////////////////////////////////////////// | 
|  | // ProcessSingleton::LinuxWatcher | 
|  | // A helper class for a Linux specific implementation of the process singleton. | 
|  | // This class sets up a listener on the singleton socket and handles parsing | 
|  | // messages that come in on the singleton socket. | 
|  | class ProcessSingleton::LinuxWatcher | 
|  | : public base::RefCountedThreadSafe<ProcessSingleton::LinuxWatcher, | 
|  | BrowserThread::DeleteOnIOThread> { | 
|  | public: | 
|  | // A helper class to read message from an established socket. | 
|  | class SocketReader { | 
|  | public: | 
|  | SocketReader(ProcessSingleton::LinuxWatcher* parent, | 
|  | scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner, | 
|  | int fd) | 
|  | : parent_(parent), | 
|  | ui_task_runner_(ui_task_runner), | 
|  | fd_(fd), | 
|  | bytes_read_(0) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::IO); | 
|  | // Wait for reads. | 
|  | fd_watch_controller_ = base::FileDescriptorWatcher::WatchReadable( | 
|  | fd, base::BindRepeating(&SocketReader::OnSocketCanReadWithoutBlocking, | 
|  | base::Unretained(this))); | 
|  | // If we haven't completed in a reasonable amount of time, give up. | 
|  | timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(kTimeoutInSeconds), | 
|  | this, &SocketReader::CleanupAndDeleteSelf); | 
|  | } | 
|  |  | 
|  | ~SocketReader() { CloseSocket(fd_); } | 
|  |  | 
|  | // Finish handling the incoming message by optionally sending back an ACK | 
|  | // message and removing this SocketReader. | 
|  | void FinishWithACK(const char *message, size_t length); | 
|  |  | 
|  | private: | 
|  | void OnSocketCanReadWithoutBlocking(); | 
|  |  | 
|  | void CleanupAndDeleteSelf() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::IO); | 
|  |  | 
|  | parent_->RemoveSocketReader(this); | 
|  | // We're deleted beyond this point. | 
|  | } | 
|  |  | 
|  | // Controls watching |fd_|. | 
|  | std::unique_ptr<base::FileDescriptorWatcher::Controller> | 
|  | fd_watch_controller_; | 
|  |  | 
|  | // The ProcessSingleton::LinuxWatcher that owns us. | 
|  | ProcessSingleton::LinuxWatcher* const parent_; | 
|  |  | 
|  | // A reference to the UI task runner. | 
|  | scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_; | 
|  |  | 
|  | // The file descriptor we're reading. | 
|  | const int fd_; | 
|  |  | 
|  | // Store the message in this buffer. | 
|  | char buf_[kMaxMessageLength]; | 
|  |  | 
|  | // Tracks the number of bytes we've read in case we're getting partial | 
|  | // reads. | 
|  | size_t bytes_read_; | 
|  |  | 
|  | base::OneShotTimer timer_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(SocketReader); | 
|  | }; | 
|  |  | 
|  | // We expect to only be constructed on the UI thread. | 
|  | explicit LinuxWatcher(ProcessSingleton* parent) | 
|  | : ui_task_runner_(base::ThreadTaskRunnerHandle::Get()), parent_(parent) {} | 
|  |  | 
|  | // Start listening for connections on the socket.  This method should be | 
|  | // called from the IO thread. | 
|  | void StartListening(int socket); | 
|  |  | 
|  | // This method determines if we should use the same process and if we should, | 
|  | // opens a new browser tab.  This runs on the UI thread. | 
|  | // |reader| is for sending back ACK message. | 
|  | void HandleMessage(const std::string& current_dir, | 
|  | const std::vector<std::string>& argv, | 
|  | SocketReader* reader); | 
|  |  | 
|  | private: | 
|  | friend struct BrowserThread::DeleteOnThread<BrowserThread::IO>; | 
|  | friend class base::DeleteHelper<ProcessSingleton::LinuxWatcher>; | 
|  |  | 
|  | ~LinuxWatcher() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::IO); | 
|  | } | 
|  |  | 
|  | void OnSocketCanReadWithoutBlocking(int socket); | 
|  |  | 
|  | // Removes and deletes the SocketReader. | 
|  | void RemoveSocketReader(SocketReader* reader); | 
|  |  | 
|  | std::unique_ptr<base::FileDescriptorWatcher::Controller> socket_watcher_; | 
|  |  | 
|  | // A reference to the UI message loop (i.e., the message loop we were | 
|  | // constructed on). | 
|  | scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_; | 
|  |  | 
|  | // The ProcessSingleton that owns us. | 
|  | ProcessSingleton* const parent_; | 
|  |  | 
|  | std::set<std::unique_ptr<SocketReader>, base::UniquePtrComparator> readers_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(LinuxWatcher); | 
|  | }; | 
|  |  | 
|  | void ProcessSingleton::LinuxWatcher::OnSocketCanReadWithoutBlocking( | 
|  | int socket) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::IO); | 
|  | // Accepting incoming client. | 
|  | sockaddr_un from; | 
|  | socklen_t from_len = sizeof(from); | 
|  | int connection_socket = HANDLE_EINTR( | 
|  | accept(socket, reinterpret_cast<sockaddr*>(&from), &from_len)); | 
|  | if (-1 == connection_socket) { | 
|  | PLOG(ERROR) << "accept() failed"; | 
|  | return; | 
|  | } | 
|  | DCHECK(base::SetNonBlocking(connection_socket)) | 
|  | << "Failed to make non-blocking socket."; | 
|  | readers_.insert( | 
|  | std::make_unique<SocketReader>(this, ui_task_runner_, connection_socket)); | 
|  | } | 
|  |  | 
|  | void ProcessSingleton::LinuxWatcher::StartListening(int socket) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::IO); | 
|  | // Watch for client connections on this socket. | 
|  | socket_watcher_ = base::FileDescriptorWatcher::WatchReadable( | 
|  | socket, base::BindRepeating(&LinuxWatcher::OnSocketCanReadWithoutBlocking, | 
|  | base::Unretained(this), socket)); | 
|  | } | 
|  |  | 
|  | void ProcessSingleton::LinuxWatcher::HandleMessage( | 
|  | const std::string& current_dir, const std::vector<std::string>& argv, | 
|  | SocketReader* reader) { | 
|  | DCHECK(ui_task_runner_->BelongsToCurrentThread()); | 
|  | DCHECK(reader); | 
|  |  | 
|  | if (parent_->notification_callback_.Run(base::CommandLine(argv), | 
|  | base::FilePath(current_dir))) { | 
|  | // Send back "ACK" message to prevent the client process from starting up. | 
|  | reader->FinishWithACK(kACKToken, base::size(kACKToken) - 1); | 
|  | } else { | 
|  | LOG(WARNING) << "Not handling interprocess notification as browser" | 
|  | " is shutting down"; | 
|  | // Send back "SHUTDOWN" message, so that the client process can start up | 
|  | // without killing this process. | 
|  | reader->FinishWithACK(kShutdownToken, base::size(kShutdownToken) - 1); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | void ProcessSingleton::LinuxWatcher::RemoveSocketReader(SocketReader* reader) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::IO); | 
|  | DCHECK(reader); | 
|  | auto it = readers_.find(reader); | 
|  | readers_.erase(it); | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////////////////////// | 
|  | // ProcessSingleton::LinuxWatcher::SocketReader | 
|  | // | 
|  |  | 
|  | void ProcessSingleton::LinuxWatcher::SocketReader:: | 
|  | OnSocketCanReadWithoutBlocking() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::IO); | 
|  | while (bytes_read_ < sizeof(buf_)) { | 
|  | ssize_t rv = | 
|  | HANDLE_EINTR(read(fd_, buf_ + bytes_read_, sizeof(buf_) - bytes_read_)); | 
|  | if (rv < 0) { | 
|  | if (errno != EAGAIN && errno != EWOULDBLOCK) { | 
|  | PLOG(ERROR) << "read() failed"; | 
|  | CloseSocket(fd_); | 
|  | return; | 
|  | } else { | 
|  | // It would block, so we just return and continue to watch for the next | 
|  | // opportunity to read. | 
|  | return; | 
|  | } | 
|  | } else if (!rv) { | 
|  | // No more data to read.  It's time to process the message. | 
|  | break; | 
|  | } else { | 
|  | bytes_read_ += rv; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Validate the message.  The shortest message is kStartToken\0x\0x | 
|  | const size_t kMinMessageLength = base::size(kStartToken) + 4; | 
|  | if (bytes_read_ < kMinMessageLength) { | 
|  | buf_[bytes_read_] = 0; | 
|  | LOG(ERROR) << "Invalid socket message (wrong length):" << buf_; | 
|  | CleanupAndDeleteSelf(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::string str(buf_, bytes_read_); | 
|  | std::vector<std::string> tokens = base::SplitString( | 
|  | str, std::string(1, kTokenDelimiter), | 
|  | base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); | 
|  |  | 
|  | if (tokens.size() < 3 || tokens[0] != kStartToken) { | 
|  | LOG(ERROR) << "Wrong message format: " << str; | 
|  | CleanupAndDeleteSelf(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Stop the expiration timer to prevent this SocketReader object from being | 
|  | // terminated unexpectly. | 
|  | timer_.Stop(); | 
|  |  | 
|  | std::string current_dir = tokens[1]; | 
|  | // Remove the first two tokens.  The remaining tokens should be the command | 
|  | // line argv array. | 
|  | tokens.erase(tokens.begin()); | 
|  | tokens.erase(tokens.begin()); | 
|  |  | 
|  | // Return to the UI thread to handle opening a new browser tab. | 
|  | ui_task_runner_->PostTask( | 
|  | FROM_HERE, base::BindOnce(&ProcessSingleton::LinuxWatcher::HandleMessage, | 
|  | parent_, current_dir, tokens, this)); | 
|  | fd_watch_controller_.reset(); | 
|  |  | 
|  | // LinuxWatcher::HandleMessage() is in charge of destroying this SocketReader | 
|  | // object by invoking SocketReader::FinishWithACK(). | 
|  | } | 
|  |  | 
|  | void ProcessSingleton::LinuxWatcher::SocketReader::FinishWithACK( | 
|  | const char *message, size_t length) { | 
|  | if (message && length) { | 
|  | // Not necessary to care about the return value. | 
|  | WriteToSocket(fd_, message, length); | 
|  | } | 
|  |  | 
|  | if (shutdown(fd_, SHUT_WR) < 0) | 
|  | PLOG(ERROR) << "shutdown() failed"; | 
|  |  | 
|  | content::GetIOThreadTaskRunner({})->PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce(&ProcessSingleton::LinuxWatcher::RemoveSocketReader, | 
|  | parent_, this)); | 
|  | // We will be deleted once the posted RemoveSocketReader task runs. | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////////////////////// | 
|  | // ProcessSingleton | 
|  | // | 
|  | ProcessSingleton::ProcessSingleton( | 
|  | const base::FilePath& user_data_dir, | 
|  | const NotificationCallback& notification_callback) | 
|  | : notification_callback_(notification_callback), | 
|  | current_pid_(base::GetCurrentProcId()), | 
|  | watcher_(new LinuxWatcher(this)) { | 
|  | socket_path_ = user_data_dir.Append(chrome::kSingletonSocketFilename); | 
|  | lock_path_ = user_data_dir.Append(chrome::kSingletonLockFilename); | 
|  | cookie_path_ = user_data_dir.Append(chrome::kSingletonCookieFilename); | 
|  |  | 
|  | kill_callback_ = base::BindRepeating(&ProcessSingleton::KillProcess, | 
|  | base::Unretained(this)); | 
|  | } | 
|  |  | 
|  | ProcessSingleton::~ProcessSingleton() { | 
|  | DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
|  | } | 
|  |  | 
|  | ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() { | 
|  | return NotifyOtherProcessWithTimeout( | 
|  | *base::CommandLine::ForCurrentProcess(), kRetryAttempts, | 
|  | base::TimeDelta::FromSeconds(kTimeoutInSeconds), true); | 
|  | } | 
|  |  | 
|  | ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout( | 
|  | const base::CommandLine& cmd_line, | 
|  | int retry_attempts, | 
|  | const base::TimeDelta& timeout, | 
|  | bool kill_unresponsive) { | 
|  | DCHECK_GE(retry_attempts, 0); | 
|  | DCHECK_GE(timeout.InMicroseconds(), 0); | 
|  |  | 
|  | base::TimeDelta sleep_interval = timeout / retry_attempts; | 
|  |  | 
|  | ScopedSocket socket; | 
|  | int pid = 0; | 
|  | for (int retries = 0; retries <= retry_attempts; ++retries) { | 
|  | // Try to connect to the socket. | 
|  | if (ConnectSocket(&socket, socket_path_, cookie_path_)) { | 
|  | #if defined(OS_MAC) | 
|  | // On Mac, we want the open process' pid in case there are | 
|  | // Apple Events to forward. See crbug.com/777863. | 
|  | std::string hostname; | 
|  | ParseProcessSingletonLock(lock_path_, &hostname, &pid); | 
|  | #endif | 
|  | break; | 
|  | } | 
|  |  | 
|  | // If we're in a race with another process, they may be in Create() and have | 
|  | // created the lock but not attached to the socket.  So we check if the | 
|  | // process with the pid from the lockfile is currently running and is a | 
|  | // chrome browser.  If so, we loop and try again for |timeout|. | 
|  |  | 
|  | std::string hostname; | 
|  | if (!ParseProcessSingletonLock(lock_path_, &hostname, &pid)) { | 
|  | // No lockfile exists. | 
|  | return PROCESS_NONE; | 
|  | } | 
|  |  | 
|  | if (hostname.empty()) { | 
|  | // Invalid lockfile. | 
|  | UnlinkPath(lock_path_); | 
|  | SendRemoteProcessInteractionResultHistogram(INVALID_LOCK_FILE); | 
|  | return PROCESS_NONE; | 
|  | } | 
|  |  | 
|  | if (hostname != net::GetHostName() && !IsChromeProcess(pid)) { | 
|  | // Locked by process on another host. If the user selected to unlock | 
|  | // the profile, try to continue; otherwise quit. | 
|  | if (DisplayProfileInUseError(lock_path_, hostname, pid)) { | 
|  | UnlinkPath(lock_path_); | 
|  | SendRemoteProcessInteractionResultHistogram(PROFILE_UNLOCKED); | 
|  | return PROCESS_NONE; | 
|  | } | 
|  | return PROFILE_IN_USE; | 
|  | } | 
|  |  | 
|  | if (!IsChromeProcess(pid)) { | 
|  | // Orphaned lockfile (no process with pid, or non-chrome process.) | 
|  | UnlinkPath(lock_path_); | 
|  | SendRemoteProcessInteractionResultHistogram(ORPHANED_LOCK_FILE); | 
|  | return PROCESS_NONE; | 
|  | } | 
|  |  | 
|  | if (IsSameChromeInstance(pid)) { | 
|  | // Orphaned lockfile (pid is part of same chrome instance we are, even | 
|  | // though we haven't tried to create a lockfile yet). | 
|  | UnlinkPath(lock_path_); | 
|  | SendRemoteProcessInteractionResultHistogram(SAME_BROWSER_INSTANCE); | 
|  | return PROCESS_NONE; | 
|  | } | 
|  |  | 
|  | if (retries == retry_attempts) { | 
|  | // Retries failed.  Kill the unresponsive chrome process and continue. | 
|  | if (!kill_unresponsive || !KillProcessByLockPath(false)) | 
|  | return PROFILE_IN_USE; | 
|  | SendRemoteHungProcessTerminateReasonHistogram(NOTIFY_ATTEMPTS_EXCEEDED); | 
|  | return PROCESS_NONE; | 
|  | } | 
|  |  | 
|  | base::PlatformThread::Sleep(sleep_interval); | 
|  | } | 
|  |  | 
|  | #if defined(OS_MAC) | 
|  | if (pid > 0 && WaitForAndForwardOpenURLEvent(pid)) { | 
|  | return PROCESS_NOTIFIED; | 
|  | } | 
|  | #endif | 
|  | timeval socket_timeout = TimeDeltaToTimeVal(timeout); | 
|  | setsockopt(socket.fd(), | 
|  | SOL_SOCKET, | 
|  | SO_SNDTIMEO, | 
|  | &socket_timeout, | 
|  | sizeof(socket_timeout)); | 
|  |  | 
|  | // Found another process, prepare our command line | 
|  | // format is "START\0<current dir>\0<argv[0]>\0...\0<argv[n]>". | 
|  | std::string to_send(kStartToken); | 
|  | to_send.push_back(kTokenDelimiter); | 
|  |  | 
|  | base::FilePath current_dir; | 
|  | if (!base::PathService::Get(base::DIR_CURRENT, ¤t_dir)) | 
|  | return PROCESS_NONE; | 
|  | to_send.append(current_dir.value()); | 
|  |  | 
|  | const std::vector<std::string>& argv = cmd_line.argv(); | 
|  | for (auto it = argv.begin(); it != argv.end(); ++it) { | 
|  | to_send.push_back(kTokenDelimiter); | 
|  | to_send.append(*it); | 
|  | } | 
|  |  | 
|  | // Send the message | 
|  | if (!WriteToSocket(socket.fd(), to_send.data(), to_send.length())) { | 
|  | // Try to kill the other process, because it might have been dead. | 
|  | if (!kill_unresponsive || !KillProcessByLockPath(true)) | 
|  | return PROFILE_IN_USE; | 
|  | SendRemoteHungProcessTerminateReasonHistogram(SOCKET_WRITE_FAILED); | 
|  | return PROCESS_NONE; | 
|  | } | 
|  |  | 
|  | if (shutdown(socket.fd(), SHUT_WR) < 0) | 
|  | PLOG(ERROR) << "shutdown() failed"; | 
|  |  | 
|  | // Read ACK message from the other process. It might be blocked for a certain | 
|  | // timeout, to make sure the other process has enough time to return ACK. | 
|  | char buf[kMaxACKMessageLength + 1]; | 
|  | ssize_t len = ReadFromSocket(socket.fd(), buf, kMaxACKMessageLength, timeout); | 
|  |  | 
|  | // Failed to read ACK, the other process might have been frozen. | 
|  | if (len <= 0) { | 
|  | if (!kill_unresponsive || !KillProcessByLockPath(true)) | 
|  | return PROFILE_IN_USE; | 
|  | SendRemoteHungProcessTerminateReasonHistogram(SOCKET_READ_FAILED); | 
|  | return PROCESS_NONE; | 
|  | } | 
|  |  | 
|  | buf[len] = '\0'; | 
|  | if (strncmp(buf, kShutdownToken, base::size(kShutdownToken) - 1) == 0) { | 
|  | // The other process is shutting down, it's safe to start a new process. | 
|  | SendRemoteProcessInteractionResultHistogram(REMOTE_PROCESS_SHUTTING_DOWN); | 
|  | return PROCESS_NONE; | 
|  | } else if (strncmp(buf, kACKToken, base::size(kACKToken) - 1) == 0) { | 
|  | #if defined(TOOLKIT_VIEWS) && \ | 
|  | (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)) | 
|  | // Likely NULL in unit tests. | 
|  | views::LinuxUI* linux_ui = views::LinuxUI::instance(); | 
|  | if (linux_ui) | 
|  | linux_ui->NotifyWindowManagerStartupComplete(); | 
|  | #endif | 
|  |  | 
|  | // Assume the other process is handling the request. | 
|  | return PROCESS_NOTIFIED; | 
|  | } | 
|  |  | 
|  | NOTREACHED() << "The other process returned unknown message: " << buf; | 
|  | return PROCESS_NOTIFIED; | 
|  | } | 
|  |  | 
|  | ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessOrCreate() { | 
|  | return NotifyOtherProcessWithTimeoutOrCreate( | 
|  | *base::CommandLine::ForCurrentProcess(), kRetryAttempts, | 
|  | base::TimeDelta::FromSeconds(kTimeoutInSeconds)); | 
|  | } | 
|  |  | 
|  | ProcessSingleton::NotifyResult | 
|  | ProcessSingleton::NotifyOtherProcessWithTimeoutOrCreate( | 
|  | const base::CommandLine& command_line, | 
|  | int retry_attempts, | 
|  | const base::TimeDelta& timeout) { | 
|  | const base::TimeTicks begin_ticks = base::TimeTicks::Now(); | 
|  | NotifyResult result = NotifyOtherProcessWithTimeout( | 
|  | command_line, retry_attempts, timeout, true); | 
|  | if (result != PROCESS_NONE) { | 
|  | if (result == PROCESS_NOTIFIED) { | 
|  | UMA_HISTOGRAM_MEDIUM_TIMES("Chrome.ProcessSingleton.TimeToNotify", | 
|  | base::TimeTicks::Now() - begin_ticks); | 
|  | } else { | 
|  | UMA_HISTOGRAM_MEDIUM_TIMES("Chrome.ProcessSingleton.TimeToFailure", | 
|  | base::TimeTicks::Now() - begin_ticks); | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | if (Create()) { | 
|  | UMA_HISTOGRAM_MEDIUM_TIMES("Chrome.ProcessSingleton.TimeToCreate", | 
|  | base::TimeTicks::Now() - begin_ticks); | 
|  | return PROCESS_NONE; | 
|  | } | 
|  |  | 
|  | // If the Create() failed, try again to notify. (It could be that another | 
|  | // instance was starting at the same time and managed to grab the lock before | 
|  | // we did.) | 
|  | // This time, we don't want to kill anything if we aren't successful, since we | 
|  | // aren't going to try to take over the lock ourselves. | 
|  | result = NotifyOtherProcessWithTimeout( | 
|  | command_line, retry_attempts, timeout, false); | 
|  |  | 
|  | if (result == PROCESS_NOTIFIED) { | 
|  | UMA_HISTOGRAM_MEDIUM_TIMES("Chrome.ProcessSingleton.TimeToNotify", | 
|  | base::TimeTicks::Now() - begin_ticks); | 
|  | } else { | 
|  | UMA_HISTOGRAM_MEDIUM_TIMES("Chrome.ProcessSingleton.TimeToFailure", | 
|  | base::TimeTicks::Now() - begin_ticks); | 
|  | } | 
|  |  | 
|  | if (result != PROCESS_NONE) | 
|  | return result; | 
|  |  | 
|  | return LOCK_ERROR; | 
|  | } | 
|  |  | 
|  | void ProcessSingleton::OverrideCurrentPidForTesting(base::ProcessId pid) { | 
|  | current_pid_ = pid; | 
|  | } | 
|  |  | 
|  | void ProcessSingleton::OverrideKillCallbackForTesting( | 
|  | const base::RepeatingCallback<void(int)>& callback) { | 
|  | kill_callback_ = callback; | 
|  | } | 
|  |  | 
|  | // static | 
|  | void ProcessSingleton::DisablePromptForTesting() { | 
|  | g_disable_prompt = true; | 
|  | } | 
|  |  | 
|  | // static | 
|  | void ProcessSingleton::SkipIsChromeProcessCheckForTesting(bool skip) { | 
|  | g_skip_is_chrome_process_check = skip; | 
|  | } | 
|  |  | 
|  | // static | 
|  | void ProcessSingleton::SetUserOptedUnlockInUseProfileForTesting( | 
|  | bool set_unlock) { | 
|  | g_user_opted_unlock_in_use_profile = set_unlock; | 
|  | } | 
|  |  | 
|  | bool ProcessSingleton::Create() { | 
|  | int sock; | 
|  | sockaddr_un addr; | 
|  |  | 
|  | // The symlink lock is pointed to the hostname and process id, so other | 
|  | // processes can find it out. | 
|  | base::FilePath symlink_content( | 
|  | base::StringPrintf("%s%c%u", net::GetHostName().c_str(), | 
|  | kProcessSingletonLockDelimiter, current_pid_)); | 
|  |  | 
|  | // Create symbol link before binding the socket, to ensure only one instance | 
|  | // can have the socket open. | 
|  | if (!SymlinkPath(symlink_content, lock_path_)) { | 
|  | // TODO(jackhou): Remove this case once this code is stable on Mac. | 
|  | // http://crbug.com/367612 | 
|  | #if defined(OS_MAC) | 
|  | // On Mac, an existing non-symlink lock file means the lock could be held by | 
|  | // the old process singleton code. If we can successfully replace the lock, | 
|  | // continue as normal. | 
|  | if (base::IsLink(lock_path_) || | 
|  | !ReplaceOldSingletonLock(symlink_content, lock_path_)) { | 
|  | return false; | 
|  | } | 
|  | #else | 
|  | // If we failed to create the lock, most likely another instance won the | 
|  | // startup race. | 
|  | return false; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | // Create the socket file somewhere in /tmp which is usually mounted as a | 
|  | // normal filesystem. Some network filesystems (notably AFS) are screwy and | 
|  | // do not support Unix domain sockets. | 
|  | if (!socket_dir_.CreateUniqueTempDir()) { | 
|  | LOG(ERROR) << "Failed to create socket directory."; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Check that the directory was created with the correct permissions. | 
|  | int dir_mode = 0; | 
|  | CHECK(base::GetPosixFilePermissions(socket_dir_.GetPath(), &dir_mode) && | 
|  | dir_mode == base::FILE_PERMISSION_USER_MASK) | 
|  | << "Temp directory mode is not 700: " << std::oct << dir_mode; | 
|  |  | 
|  | // Try to create the socket before creating the symlink, as SetupSocket may | 
|  | // fail on a CHECK if the |socket_target_path| is too long, and this avoids | 
|  | // leaving a dangling symlink. | 
|  | base::FilePath socket_target_path = | 
|  | socket_dir_.GetPath().Append(chrome::kSingletonSocketFilename); | 
|  | SetupSocket(socket_target_path.value(), &sock, &addr); | 
|  |  | 
|  | // Setup the socket symlink and the two cookies. | 
|  | base::FilePath cookie(GenerateCookie()); | 
|  | base::FilePath remote_cookie_path = | 
|  | socket_dir_.GetPath().Append(chrome::kSingletonCookieFilename); | 
|  | UnlinkPath(socket_path_); | 
|  | UnlinkPath(cookie_path_); | 
|  | if (!SymlinkPath(socket_target_path, socket_path_) || | 
|  | !SymlinkPath(cookie, cookie_path_) || | 
|  | !SymlinkPath(cookie, remote_cookie_path)) { | 
|  | // We've already locked things, so we can't have lost the startup race, | 
|  | // but something doesn't like us. | 
|  | LOG(ERROR) << "Failed to create symlinks."; | 
|  | if (!socket_dir_.Delete()) | 
|  | LOG(ERROR) << "Encountered a problem when deleting socket directory."; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (bind(sock, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) { | 
|  | PLOG(ERROR) << "Failed to bind() " << socket_target_path.value(); | 
|  | CloseSocket(sock); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (listen(sock, 5) < 0) | 
|  | NOTREACHED() << "listen failed: " << base::safe_strerror(errno); | 
|  |  | 
|  | DCHECK(BrowserThread::IsThreadInitialized(BrowserThread::IO)); | 
|  | content::GetIOThreadTaskRunner({})->PostTask( | 
|  | FROM_HERE, base::BindOnce(&ProcessSingleton::LinuxWatcher::StartListening, | 
|  | watcher_, sock)); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void ProcessSingleton::Cleanup() { | 
|  | UnlinkPath(socket_path_); | 
|  | UnlinkPath(cookie_path_); | 
|  | UnlinkPath(lock_path_); | 
|  | } | 
|  |  | 
|  | bool ProcessSingleton::IsSameChromeInstance(pid_t pid) { | 
|  | pid_t cur_pid = current_pid_; | 
|  | while (pid != cur_pid) { | 
|  | pid = base::GetParentProcessId(pid); | 
|  | if (pid <= 0) | 
|  | return false; | 
|  | if (!IsChromeProcess(pid)) | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool ProcessSingleton::KillProcessByLockPath(bool is_connected_to_socket) { | 
|  | std::string hostname; | 
|  | int pid; | 
|  | ParseProcessSingletonLock(lock_path_, &hostname, &pid); | 
|  |  | 
|  | if (!hostname.empty() && hostname != net::GetHostName() && | 
|  | !is_connected_to_socket) { | 
|  | bool res = DisplayProfileInUseError(lock_path_, hostname, pid); | 
|  | if (res) { | 
|  | UnlinkPath(lock_path_); | 
|  | SendRemoteProcessInteractionResultHistogram(PROFILE_UNLOCKED_BEFORE_KILL); | 
|  | } | 
|  | return res; | 
|  | } | 
|  | UnlinkPath(lock_path_); | 
|  |  | 
|  | if (IsSameChromeInstance(pid)) { | 
|  | SendRemoteProcessInteractionResultHistogram( | 
|  | SAME_BROWSER_INSTANCE_BEFORE_KILL); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | if (pid > 0) { | 
|  | kill_callback_.Run(pid); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | SendRemoteProcessInteractionResultHistogram(FAILED_TO_EXTRACT_PID); | 
|  |  | 
|  | LOG(ERROR) << "Failed to extract pid from path: " << lock_path_.value(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void ProcessSingleton::KillProcess(int pid) { | 
|  | // TODO(james.su@gmail.com): Is SIGKILL ok? | 
|  | int rv = kill(static_cast<base::ProcessHandle>(pid), SIGKILL); | 
|  | // ESRCH = No Such Process (can happen if the other process is already in | 
|  | // progress of shutting down and finishes before we try to kill it). | 
|  | DCHECK(rv == 0 || errno == ESRCH) << "Error killing process: " | 
|  | << base::safe_strerror(errno); | 
|  |  | 
|  | int error_code = (rv == 0) ? 0 : errno; | 
|  | base::UmaHistogramSparse( | 
|  | "Chrome.ProcessSingleton.TerminateProcessErrorCode.Posix", error_code); | 
|  |  | 
|  | RemoteProcessInteractionResult action = TERMINATE_SUCCEEDED; | 
|  | if (rv != 0) { | 
|  | switch (error_code) { | 
|  | case ESRCH: | 
|  | action = REMOTE_PROCESS_NOT_FOUND; | 
|  | break; | 
|  | case EPERM: | 
|  | action = TERMINATE_NOT_ENOUGH_PERMISSIONS; | 
|  | break; | 
|  | default: | 
|  | action = TERMINATE_FAILED; | 
|  | break; | 
|  | } | 
|  | } | 
|  | SendRemoteProcessInteractionResultHistogram(action); | 
|  | } |