1 // Copyright 2013 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.
5 #include "chrome/browser/apps/app_shim/app_shim_host_manager_mac.h"
9 #include "base/base64.h"
10 #include "base/bind.h"
11 #include "base/command_line.h"
12 #include "base/files/file_path.h"
13 #include "base/files/file_util.h"
14 #include "base/logging.h"
15 #include "base/path_service.h"
16 #include "base/sha1.h"
17 #include "base/strings/string_util.h"
18 #include "chrome/browser/apps/app_shim/app_shim_handler_mac.h"
19 #include "chrome/browser/apps/app_shim/app_shim_host_mac.h"
20 #include "chrome/browser/apps/app_shim/extension_app_shim_handler_mac.h"
21 #include "chrome/browser/browser_process.h"
22 #include "chrome/common/chrome_paths.h"
23 #include "chrome/common/chrome_switches.h"
24 #include "chrome/common/mac/app_mode_common.h"
25 #include "components/version_info/version_info.h"
27 using content::BrowserThread;
31 void CreateAppShimHost(const IPC::ChannelHandle& handle) {
32 // AppShimHost takes ownership of itself.
33 (new AppShimHost)->ServeChannel(handle);
36 base::FilePath GetDirectoryInTmpTemplate(const base::FilePath& user_data_dir) {
37 base::FilePath temp_dir;
38 CHECK(PathService::Get(base::DIR_TEMP, &temp_dir));
39 // Check that it's shorter than the IPC socket length (104) minus the
40 // intermediate folder ("/chrome-XXXXXX/") and kAppShimSocketShortName.
41 DCHECK_GT(83u, temp_dir.value().length());
42 return temp_dir.Append("chrome-XXXXXX");
45 void DeleteSocketFiles(const base::FilePath& directory_in_tmp,
46 const base::FilePath& symlink_path,
47 const base::FilePath& version_path) {
48 // Delete in reverse order of creation.
49 if (!version_path.empty())
50 base::DeleteFile(version_path, false);
51 if (!symlink_path.empty())
52 base::DeleteFile(symlink_path, false);
53 if (!directory_in_tmp.empty())
54 base::DeleteFile(directory_in_tmp, true);
59 AppShimHostManager::AppShimHostManager() {
62 void AppShimHostManager::Init() {
63 DCHECK_CURRENTLY_ON(BrowserThread::UI);
64 DCHECK(!extension_app_shim_handler_);
65 extension_app_shim_handler_.reset(new apps::ExtensionAppShimHandler());
66 apps::AppShimHandler::SetDefaultHandler(extension_app_shim_handler_.get());
67 BrowserThread::PostTask(
68 BrowserThread::FILE, FROM_HERE,
69 base::Bind(&AppShimHostManager::InitOnFileThread, this));
72 AppShimHostManager::~AppShimHostManager() {
75 // The AppShimHostManager is only initialized if the Chrome process
76 // successfully took the singleton lock. If it was not initialized, do not
77 // delete existing app shim socket files as they belong to another process.
78 if (!extension_app_shim_handler_)
81 apps::AppShimHandler::SetDefaultHandler(NULL);
82 base::FilePath user_data_dir;
83 base::FilePath symlink_path;
84 base::FilePath version_path;
85 if (PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) {
86 symlink_path = user_data_dir.Append(app_mode::kAppShimSocketSymlinkName);
88 user_data_dir.Append(app_mode::kRunningChromeVersionSymlinkName);
90 BrowserThread::PostTask(
94 &DeleteSocketFiles, directory_in_tmp_, symlink_path, version_path));
97 void AppShimHostManager::InitOnFileThread() {
98 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
99 base::FilePath user_data_dir;
100 if (!PathService::Get(chrome::DIR_USER_DATA, &user_data_dir))
103 // The socket path must be shorter than 104 chars (IPC::kMaxSocketNameLength).
104 // To accommodate this, we use a short path in /tmp/ that is generated from a
105 // hash of the user data dir.
106 std::string directory_string =
107 GetDirectoryInTmpTemplate(user_data_dir).value();
109 // mkdtemp() replaces trailing X's randomly and creates the directory.
110 if (!mkdtemp(&directory_string[0])) {
111 PLOG(ERROR) << directory_string;
115 directory_in_tmp_ = base::FilePath(directory_string);
116 // Check that the directory was created with the correct permissions.
118 if (!base::GetPosixFilePermissions(directory_in_tmp_, &dir_mode) ||
119 dir_mode != base::FILE_PERMISSION_USER_MASK) {
124 // UnixDomainSocketAcceptor creates the socket immediately.
125 base::FilePath socket_path =
126 directory_in_tmp_.Append(app_mode::kAppShimSocketShortName);
127 acceptor_.reset(new apps::UnixDomainSocketAcceptor(socket_path, this));
129 // Create a symlink to the socket in the user data dir. This lets the shim
130 // process started from Finder find the actual socket path by following the
131 // symlink with ::readlink().
132 base::FilePath symlink_path =
133 user_data_dir.Append(app_mode::kAppShimSocketSymlinkName);
134 base::DeleteFile(symlink_path, false);
135 base::CreateSymbolicLink(socket_path, symlink_path);
137 // Create a symlink containing the current version string. This allows the
138 // shim to load the same framework version as the currently running Chrome
140 base::FilePath version_path =
141 user_data_dir.Append(app_mode::kRunningChromeVersionSymlinkName);
142 base::DeleteFile(version_path, false);
143 base::CreateSymbolicLink(base::FilePath(version_info::GetVersionNumber()),
146 BrowserThread::PostTask(
147 BrowserThread::IO, FROM_HERE,
148 base::Bind(&AppShimHostManager::ListenOnIOThread, this));
151 void AppShimHostManager::ListenOnIOThread() {
152 DCHECK_CURRENTLY_ON(BrowserThread::IO);
153 if (!acceptor_->Listen()) {
154 BrowserThread::PostTask(
155 BrowserThread::UI, FROM_HERE,
156 base::Bind(&AppShimHostManager::OnListenError, this));
160 void AppShimHostManager::OnClientConnected(
161 const IPC::ChannelHandle& handle) {
162 DCHECK_CURRENTLY_ON(BrowserThread::IO);
163 BrowserThread::PostTask(
164 BrowserThread::UI, FROM_HERE,
165 base::Bind(&CreateAppShimHost, handle));
168 void AppShimHostManager::OnListenError() {
169 // TODO(tapted): Set a timeout and attempt to reconstruct the channel. Until
170 // cases where the error could occur are better known, just reset the acceptor
171 // to allow failure to be communicated via the test API.