1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <errno.h>
6#include <fcntl.h>
7#include <sys/file.h>
8
9#include "chrome/browser/process_singleton.h"
10
11#include "base/metrics/histogram.h"
12#include "base/posix/eintr_wrapper.h"
13#include "chrome/common/chrome_constants.h"
14
15namespace {
16
17// From "man 2 intro", the largest errno is |EOPNOTSUPP|, which is
18// |102|.  Since the histogram memory usage is proportional to this
19// number, using the |102| directly rather than the macro.
20const int kMaxErrno = 102;
21
22}  // namespace
23
24// This class is used to funnel messages to a single instance of the browser
25// process. This is needed for several reasons on other platforms.
26//
27// On Windows, when the user re-opens the application from the shell (e.g. an
28// explicit double-click, a shortcut that opens a webpage, etc.) we need to send
29// the message to the currently-existing copy of the browser.
30//
31// On Linux, opening a URL is done by creating an instance of the web browser
32// process and passing it the URL to go to on its commandline.
33//
34// Neither of those cases apply on the Mac. Launch Services ensures that there
35// is only one instance of the process, and we get URLs to open via AppleEvents
36// and, once again, the Launch Services system. We have no need to manage this
37// ourselves.  An exclusive lock is used to flush out anyone making incorrect
38// assumptions.
39
40ProcessSingleton::ProcessSingleton(
41    const base::FilePath& user_data_dir,
42    const NotificationCallback& /* notification_callback */)
43    : lock_path_(user_data_dir.Append(chrome::kSingletonLockFilename)),
44      lock_fd_(-1) {
45}
46
47ProcessSingleton::~ProcessSingleton() {
48  // Make sure the lock is released.  Process death will also release
49  // it, even if this is not called.
50  Cleanup();
51}
52
53ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() {
54  // This space intentionally left blank.
55  return PROCESS_NONE;
56}
57
58ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessOrCreate() {
59  // Windows tries NotifyOtherProcess() first.
60  return Create() ? PROCESS_NONE : PROFILE_IN_USE;
61}
62
63// Attempt to acquire an exclusive lock on an empty file in the
64// profile directory.  Returns |true| if it gets the lock.  Returns
65// |false| if the lock is held, or if there is an error.
66// |notification_callback| is not actually used. See the comments at the top of
67// this file for details.
68// TODO(shess): Rather than logging failures, popup an alert.  Punting
69// that for now because it would require confidence that this code is
70// never called in a situation where an alert wouldn't work.
71// http://crbug.com/59061
72bool ProcessSingleton::Create() {
73  DCHECK_EQ(-1, lock_fd_) << "lock_path_ is already open.";
74
75  lock_fd_ = HANDLE_EINTR(open(lock_path_.value().c_str(),
76                               O_RDONLY | O_CREAT, 0644));
77  if (lock_fd_ == -1) {
78    const int capture_errno = errno;
79    DPCHECK(lock_fd_ != -1) << "Unexpected failure opening profile lockfile";
80    UMA_HISTOGRAM_ENUMERATION("ProcessSingleton.OpenError",
81                              capture_errno, kMaxErrno);
82    return false;
83  }
84
85  // Acquire an exclusive lock in non-blocking fashion.  If the lock
86  // is already held, this will return |EWOULDBLOCK|.
87  int rc = HANDLE_EINTR(flock(lock_fd_, LOCK_EX|LOCK_NB));
88  if (rc == -1) {
89    const int capture_errno = errno;
90    DPCHECK(errno == EWOULDBLOCK)
91        << "Unexpected failure locking profile lockfile";
92
93    Cleanup();
94
95    // Other errors indicate something crazy is happening.
96    if (capture_errno != EWOULDBLOCK) {
97      UMA_HISTOGRAM_ENUMERATION("ProcessSingleton.LockError",
98                                capture_errno, kMaxErrno);
99      return false;
100    }
101
102    // The file is open by another process and locked.
103    LOG(ERROR) << "Unable to obtain profile lock.";
104    return false;
105  }
106
107  return true;
108}
109
110void ProcessSingleton::Cleanup() {
111  // Closing the file also releases the lock.
112  if (lock_fd_ != -1) {
113    int rc = IGNORE_EINTR(close(lock_fd_));
114    DPCHECK(!rc) << "Closing lock_fd_:";
115  }
116  lock_fd_ = -1;
117}
118