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 <algorithm>
6
7#include "base/command_line.h"
8#include "base/logging.h"
9#include "base/memory/singleton.h"
10#include "base/path_service.h"
11#include "base/sha1.h"
12#include "base/strings/string16.h"
13#include "base/strings/string_number_conversions.h"
14#include "base/strings/string_util.h"
15#include "base/strings/utf_string_conversions.h"
16#include "base/version.h"
17#include "chrome/common/chrome_constants.h"
18#include "chrome/common/chrome_paths.h"
19#include "chrome/common/chrome_switches.h"
20#include "chrome/common/chrome_version_info.h"
21#include "chrome/common/service_process_util.h"
22#include "content/public/common/content_paths.h"
23
24#if !defined(OS_MACOSX)
25
26namespace {
27
28// This should be more than enough to hold a version string assuming each part
29// of the version string is an int64.
30const uint32 kMaxVersionStringLength = 256;
31
32// The structure that gets written to shared memory.
33struct ServiceProcessSharedData {
34  char service_process_version[kMaxVersionStringLength];
35  base::ProcessId service_process_pid;
36};
37
38// Gets the name of the shared memory used by the service process to write its
39// version. The name is not versioned.
40std::string GetServiceProcessSharedMemName() {
41  return GetServiceProcessScopedName("_service_shmem");
42}
43
44enum ServiceProcessRunningState {
45  SERVICE_NOT_RUNNING,
46  SERVICE_OLDER_VERSION_RUNNING,
47  SERVICE_SAME_VERSION_RUNNING,
48  SERVICE_NEWER_VERSION_RUNNING,
49};
50
51ServiceProcessRunningState GetServiceProcessRunningState(
52    std::string* service_version_out, base::ProcessId* pid_out) {
53  std::string version;
54  if (!GetServiceProcessData(&version, pid_out))
55    return SERVICE_NOT_RUNNING;
56
57#if defined(OS_POSIX)
58  // We only need to check for service running on POSIX because Windows cleans
59  // up shared memory files when an app crashes, so there isn't a chance of
60  // us reading bogus data from shared memory for an app that has died.
61  if (!CheckServiceProcessReady()) {
62    return SERVICE_NOT_RUNNING;
63  }
64#endif  // defined(OS_POSIX)
65
66  // At this time we have a version string. Set the out param if it exists.
67  if (service_version_out)
68    *service_version_out = version;
69
70  Version service_version(version);
71  // If the version string is invalid, treat it like an older version.
72  if (!service_version.IsValid())
73    return SERVICE_OLDER_VERSION_RUNNING;
74
75  // Get the version of the currently *running* instance of Chrome.
76  chrome::VersionInfo version_info;
77  if (!version_info.is_valid()) {
78    NOTREACHED() << "Failed to get current file version";
79    // Our own version is invalid. This is an error case. Pretend that we
80    // are out of date.
81    return SERVICE_NEWER_VERSION_RUNNING;
82  }
83  Version running_version(version_info.Version());
84  if (!running_version.IsValid()) {
85    NOTREACHED() << "Failed to parse version info";
86    // Our own version is invalid. This is an error case. Pretend that we
87    // are out of date.
88    return SERVICE_NEWER_VERSION_RUNNING;
89  }
90
91  if (running_version.CompareTo(service_version) > 0) {
92    return SERVICE_OLDER_VERSION_RUNNING;
93  } else if (service_version.CompareTo(running_version) > 0) {
94    return SERVICE_NEWER_VERSION_RUNNING;
95  }
96  return SERVICE_SAME_VERSION_RUNNING;
97}
98
99}  // namespace
100
101// Return a name that is scoped to this instance of the service process. We
102// use the hash of the user-data-dir as a scoping prefix. We can't use
103// the user-data-dir itself as we have limits on the size of the lock names.
104std::string GetServiceProcessScopedName(const std::string& append_str) {
105  base::FilePath user_data_dir;
106  PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
107#if defined(OS_WIN)
108  std::string user_data_dir_path = WideToUTF8(user_data_dir.value());
109#elif defined(OS_POSIX)
110  std::string user_data_dir_path = user_data_dir.value();
111#endif  // defined(OS_WIN)
112  std::string hash = base::SHA1HashString(user_data_dir_path);
113  std::string hex_hash = base::HexEncode(hash.c_str(), hash.length());
114  return hex_hash + "." + append_str;
115}
116
117// Return a name that is scoped to this instance of the service process. We
118// use the user-data-dir and the version as a scoping prefix.
119std::string GetServiceProcessScopedVersionedName(
120    const std::string& append_str) {
121  std::string versioned_str;
122  chrome::VersionInfo version_info;
123  DCHECK(version_info.is_valid());
124  versioned_str.append(version_info.Version());
125  versioned_str.append(append_str);
126  return GetServiceProcessScopedName(versioned_str);
127}
128
129// Reads the named shared memory to get the shared data. Returns false if no
130// matching shared memory was found.
131bool GetServiceProcessData(std::string* version, base::ProcessId* pid) {
132  scoped_ptr<base::SharedMemory> shared_mem_service_data;
133  shared_mem_service_data.reset(new base::SharedMemory());
134  ServiceProcessSharedData* service_data = NULL;
135  if (shared_mem_service_data.get() &&
136      shared_mem_service_data->Open(GetServiceProcessSharedMemName(), true) &&
137      shared_mem_service_data->Map(sizeof(ServiceProcessSharedData))) {
138    service_data = reinterpret_cast<ServiceProcessSharedData*>(
139        shared_mem_service_data->memory());
140    // Make sure the version in shared memory is null-terminated. If it is not,
141    // treat it as invalid.
142    if (version && memchr(service_data->service_process_version, '\0',
143                          sizeof(service_data->service_process_version)))
144      *version = service_data->service_process_version;
145    if (pid)
146      *pid = service_data->service_process_pid;
147    return true;
148  }
149  return false;
150}
151
152#endif  // !OS_MACOSX
153
154ServiceProcessState::ServiceProcessState() : state_(NULL) {
155  CreateAutoRunCommandLine();
156  CreateState();
157}
158
159ServiceProcessState::~ServiceProcessState() {
160#if !defined(OS_MACOSX)
161  if (shared_mem_service_data_.get()) {
162    shared_mem_service_data_->Delete(GetServiceProcessSharedMemName());
163  }
164#endif  // !OS_MACOSX
165  TearDownState();
166}
167
168void ServiceProcessState::SignalStopped() {
169  TearDownState();
170  shared_mem_service_data_.reset();
171}
172
173#if !defined(OS_MACOSX)
174bool ServiceProcessState::Initialize() {
175  if (!TakeSingletonLock()) {
176    return false;
177  }
178  // Now that we have the singleton, take care of killing an older version, if
179  // it exists.
180  if (!HandleOtherVersion())
181    return false;
182
183  // Write the version we are using to shared memory. This can be used by a
184  // newer service to signal us to exit.
185  return CreateSharedData();
186}
187
188bool ServiceProcessState::HandleOtherVersion() {
189  std::string running_version;
190  base::ProcessId process_id = 0;
191  ServiceProcessRunningState state =
192      GetServiceProcessRunningState(&running_version, &process_id);
193  switch (state) {
194    case SERVICE_SAME_VERSION_RUNNING:
195    case SERVICE_NEWER_VERSION_RUNNING:
196      return false;
197    case SERVICE_OLDER_VERSION_RUNNING:
198      // If an older version is running, kill it.
199      ForceServiceProcessShutdown(running_version, process_id);
200      break;
201    case SERVICE_NOT_RUNNING:
202      break;
203  }
204  return true;
205}
206
207bool ServiceProcessState::CreateSharedData() {
208  chrome::VersionInfo version_info;
209  if (!version_info.is_valid()) {
210    NOTREACHED() << "Failed to get current file version";
211    return false;
212  }
213  if (version_info.Version().length() >= kMaxVersionStringLength) {
214    NOTREACHED() << "Version string length is << " <<
215        version_info.Version().length() << "which is longer than" <<
216        kMaxVersionStringLength;
217    return false;
218  }
219
220  scoped_ptr<base::SharedMemory> shared_mem_service_data(
221      new base::SharedMemory());
222  if (!shared_mem_service_data.get())
223    return false;
224
225  uint32 alloc_size = sizeof(ServiceProcessSharedData);
226  if (!shared_mem_service_data->CreateNamed(GetServiceProcessSharedMemName(),
227                                            true, alloc_size))
228    return false;
229
230  if (!shared_mem_service_data->Map(alloc_size))
231    return false;
232
233  memset(shared_mem_service_data->memory(), 0, alloc_size);
234  ServiceProcessSharedData* shared_data =
235      reinterpret_cast<ServiceProcessSharedData*>(
236          shared_mem_service_data->memory());
237  memcpy(shared_data->service_process_version, version_info.Version().c_str(),
238         version_info.Version().length());
239  shared_data->service_process_pid = base::GetCurrentProcId();
240  shared_mem_service_data_.reset(shared_mem_service_data.release());
241  return true;
242}
243
244IPC::ChannelHandle ServiceProcessState::GetServiceProcessChannel() {
245  return ::GetServiceProcessChannel();
246}
247
248#endif  // !OS_MACOSX
249
250void ServiceProcessState::CreateAutoRunCommandLine() {
251  base::FilePath exe_path;
252  PathService::Get(content::CHILD_PROCESS_EXE, &exe_path);
253  DCHECK(!exe_path.empty()) << "Unable to get service process binary name.";
254  autorun_command_line_.reset(new CommandLine(exe_path));
255  autorun_command_line_->AppendSwitchASCII(switches::kProcessType,
256                                           switches::kServiceProcess);
257
258  // The user data directory is the only other flag we currently want to
259  // possibly store.
260  const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
261  base::FilePath user_data_dir =
262    browser_command_line.GetSwitchValuePath(switches::kUserDataDir);
263  if (!user_data_dir.empty())
264    autorun_command_line_->AppendSwitchPath(switches::kUserDataDir,
265                                            user_data_dir);
266}
267