Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / common / service_process_util_mac.mm
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 "chrome/common/service_process_util_posix.h"
6
7 #import <Foundation/Foundation.h>
8 #include <launch.h>
9
10 #include <vector>
11
12 #include "base/bind.h"
13 #include "base/command_line.h"
14 #include "base/files/file_path.h"
15 #include "base/mac/bundle_locations.h"
16 #include "base/mac/foundation_util.h"
17 #include "base/mac/mac_util.h"
18 #include "base/mac/scoped_nsautorelease_pool.h"
19 #include "base/mac/scoped_nsobject.h"
20 #include "base/path_service.h"
21 #include "base/strings/string_util.h"
22 #include "base/strings/stringprintf.h"
23 #include "base/strings/sys_string_conversions.h"
24 #include "base/threading/thread_restrictions.h"
25 #include "base/version.h"
26 #include "chrome/common/chrome_paths.h"
27 #include "chrome/common/chrome_switches.h"
28 #include "chrome/common/chrome_version_info.h"
29 #include "chrome/common/mac/launchd.h"
30
31 using ::base::FilePathWatcher;
32
33 namespace {
34
35 #define kServiceProcessSessionType "Aqua"
36
37 CFStringRef CopyServiceProcessLaunchDName() {
38   base::mac::ScopedNSAutoreleasePool pool;
39   NSBundle* bundle = base::mac::FrameworkBundle();
40   return CFStringCreateCopy(kCFAllocatorDefault,
41                             base::mac::NSToCFCast([bundle bundleIdentifier]));
42 }
43
44 NSString* GetServiceProcessLaunchDLabel() {
45   base::scoped_nsobject<NSString> name(
46       base::mac::CFToNSCast(CopyServiceProcessLaunchDName()));
47   NSString *label = [name stringByAppendingString:@".service_process"];
48   base::FilePath user_data_dir;
49   PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
50   std::string user_data_dir_path = user_data_dir.value();
51   NSString *ns_path = base::SysUTF8ToNSString(user_data_dir_path);
52   ns_path = [ns_path stringByReplacingOccurrencesOfString:@" "
53                                                withString:@"_"];
54   label = [label stringByAppendingString:ns_path];
55   return label;
56 }
57
58 NSString* GetServiceProcessLaunchDSocketKey() {
59   return @"ServiceProcessSocket";
60 }
61
62 bool GetParentFSRef(const FSRef& child, FSRef* parent) {
63   return FSGetCatalogInfo(&child, 0, NULL, NULL, NULL, parent) == noErr;
64 }
65
66 bool RemoveFromLaunchd() {
67   // We're killing a file.
68   base::ThreadRestrictions::AssertIOAllowed();
69   base::ScopedCFTypeRef<CFStringRef> name(CopyServiceProcessLaunchDName());
70   return Launchd::GetInstance()->DeletePlist(Launchd::User,
71                                              Launchd::Agent,
72                                              name);
73 }
74
75 class ExecFilePathWatcherCallback {
76  public:
77   ExecFilePathWatcherCallback() {}
78   ~ExecFilePathWatcherCallback() {}
79
80   bool Init(const base::FilePath& path);
81   void NotifyPathChanged(const base::FilePath& path, bool error);
82
83  private:
84   FSRef executable_fsref_;
85 };
86
87 }  // namespace
88
89 NSString* GetServiceProcessLaunchDSocketEnvVar() {
90   NSString *label = GetServiceProcessLaunchDLabel();
91   NSString *env_var = [label stringByReplacingOccurrencesOfString:@"."
92                                                        withString:@"_"];
93   env_var = [env_var stringByAppendingString:@"_SOCKET"];
94   env_var = [env_var uppercaseString];
95   return env_var;
96 }
97
98 // Gets the name of the service process IPC channel.
99 IPC::ChannelHandle GetServiceProcessChannel() {
100   base::mac::ScopedNSAutoreleasePool pool;
101   std::string socket_path;
102   base::scoped_nsobject<NSDictionary> dictionary(
103       base::mac::CFToNSCast(Launchd::GetInstance()->CopyExports()));
104   NSString *ns_socket_path =
105       [dictionary objectForKey:GetServiceProcessLaunchDSocketEnvVar()];
106   if (ns_socket_path) {
107     socket_path = base::SysNSStringToUTF8(ns_socket_path);
108   }
109   return IPC::ChannelHandle(socket_path);
110 }
111
112 bool ForceServiceProcessShutdown(const std::string& /* version */,
113                                  base::ProcessId /* process_id */) {
114   base::mac::ScopedNSAutoreleasePool pool;
115   CFStringRef label = base::mac::NSToCFCast(GetServiceProcessLaunchDLabel());
116   CFErrorRef err = NULL;
117   bool ret = Launchd::GetInstance()->RemoveJob(label, &err);
118   if (!ret) {
119     DLOG(ERROR) << "ForceServiceProcessShutdown: " << err << " "
120                 << base::SysCFStringRefToUTF8(label);
121     CFRelease(err);
122   }
123   return ret;
124 }
125
126 bool GetServiceProcessData(std::string* version, base::ProcessId* pid) {
127   base::mac::ScopedNSAutoreleasePool pool;
128   CFStringRef label = base::mac::NSToCFCast(GetServiceProcessLaunchDLabel());
129   base::scoped_nsobject<NSDictionary> launchd_conf(
130       base::mac::CFToNSCast(Launchd::GetInstance()->CopyJobDictionary(label)));
131   if (!launchd_conf.get()) {
132     return false;
133   }
134   // Anything past here will return true in that there does appear
135   // to be a service process of some sort registered with launchd.
136   if (version) {
137     *version = "0";
138     NSString *exe_path = [launchd_conf objectForKey:@ LAUNCH_JOBKEY_PROGRAM];
139     if (exe_path) {
140       NSString *bundle_path = [[[exe_path stringByDeletingLastPathComponent]
141                                 stringByDeletingLastPathComponent]
142                                stringByDeletingLastPathComponent];
143       NSBundle *bundle = [NSBundle bundleWithPath:bundle_path];
144       if (bundle) {
145         NSString *ns_version =
146             [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
147         if (ns_version) {
148           *version = base::SysNSStringToUTF8(ns_version);
149         } else {
150           DLOG(ERROR) << "Unable to get version at: "
151                       << reinterpret_cast<CFStringRef>(bundle_path);
152         }
153       } else {
154         // The bundle has been deleted out from underneath the registered
155         // job.
156         DLOG(ERROR) << "Unable to get bundle at: "
157                     << reinterpret_cast<CFStringRef>(bundle_path);
158       }
159     } else {
160       DLOG(ERROR) << "Unable to get executable path for service process";
161     }
162   }
163   if (pid) {
164     *pid = -1;
165     NSNumber* ns_pid = [launchd_conf objectForKey:@ LAUNCH_JOBKEY_PID];
166     if (ns_pid) {
167      *pid = [ns_pid intValue];
168     }
169   }
170   return true;
171 }
172
173 bool ServiceProcessState::Initialize() {
174   CFErrorRef err = NULL;
175   CFDictionaryRef dict =
176       Launchd::GetInstance()->CopyDictionaryByCheckingIn(&err);
177   if (!dict) {
178     DLOG(ERROR) << "ServiceProcess must be launched by launchd. "
179                 << "CopyLaunchdDictionaryByCheckingIn: " << err;
180     CFRelease(err);
181     return false;
182   }
183   state_->launchd_conf_.reset(dict);
184   return true;
185 }
186
187 IPC::ChannelHandle ServiceProcessState::GetServiceProcessChannel() {
188   DCHECK(state_);
189   NSDictionary *ns_launchd_conf = base::mac::CFToNSCast(state_->launchd_conf_);
190   NSDictionary* socket_dict =
191       [ns_launchd_conf objectForKey:@ LAUNCH_JOBKEY_SOCKETS];
192   NSArray* sockets =
193       [socket_dict objectForKey:GetServiceProcessLaunchDSocketKey()];
194   DCHECK_EQ([sockets count], 1U);
195   int socket = [[sockets objectAtIndex:0] intValue];
196   base::FileDescriptor fd(socket, false);
197   return IPC::ChannelHandle(std::string(), fd);
198 }
199
200 bool CheckServiceProcessReady() {
201   std::string version;
202   pid_t pid;
203   if (!GetServiceProcessData(&version, &pid)) {
204     return false;
205   }
206   Version service_version(version);
207   bool ready = true;
208   if (!service_version.IsValid()) {
209     ready = false;
210   } else {
211     chrome::VersionInfo version_info;
212     Version running_version(version_info.Version());
213     if (!running_version.IsValid()) {
214       // Our own version is invalid. This is an error case. Pretend that we
215       // are out of date.
216       NOTREACHED();
217       ready = true;
218     } else if (running_version.CompareTo(service_version) > 0) {
219       ready = false;
220     } else {
221       ready = true;
222     }
223   }
224   if (!ready) {
225     ForceServiceProcessShutdown(version, pid);
226   }
227   return ready;
228 }
229
230 CFDictionaryRef CreateServiceProcessLaunchdPlist(CommandLine* cmd_line,
231                                                  bool for_auto_launch) {
232   base::mac::ScopedNSAutoreleasePool pool;
233
234   NSString *program =
235       base::SysUTF8ToNSString(cmd_line->GetProgram().value());
236
237   std::vector<std::string> args = cmd_line->argv();
238   NSMutableArray *ns_args = [NSMutableArray arrayWithCapacity:args.size()];
239
240   for (std::vector<std::string>::iterator iter = args.begin();
241        iter < args.end();
242        ++iter) {
243     [ns_args addObject:base::SysUTF8ToNSString(*iter)];
244   }
245
246   NSDictionary *socket =
247       [NSDictionary dictionaryWithObject:GetServiceProcessLaunchDSocketEnvVar()
248                                   forKey:@ LAUNCH_JOBSOCKETKEY_SECUREWITHKEY];
249   NSDictionary *sockets =
250       [NSDictionary dictionaryWithObject:socket
251                                   forKey:GetServiceProcessLaunchDSocketKey()];
252
253   // See the man page for launchd.plist.
254   NSMutableDictionary *launchd_plist =
255       [[NSMutableDictionary alloc] initWithObjectsAndKeys:
256         GetServiceProcessLaunchDLabel(), @ LAUNCH_JOBKEY_LABEL,
257         program, @ LAUNCH_JOBKEY_PROGRAM,
258         ns_args, @ LAUNCH_JOBKEY_PROGRAMARGUMENTS,
259         sockets, @ LAUNCH_JOBKEY_SOCKETS,
260         nil];
261
262   if (for_auto_launch) {
263     // We want the service process to be able to exit if there are no services
264     // enabled. With a value of NO in the SuccessfulExit key, launchd will
265     // relaunch the service automatically in any other case than exiting
266     // cleanly with a 0 return code.
267     NSDictionary *keep_alive =
268       [NSDictionary
269         dictionaryWithObject:[NSNumber numberWithBool:NO]
270                       forKey:@ LAUNCH_JOBKEY_KEEPALIVE_SUCCESSFULEXIT];
271     NSDictionary *auto_launchd_plist =
272       [[NSDictionary alloc] initWithObjectsAndKeys:
273         [NSNumber numberWithBool:YES], @ LAUNCH_JOBKEY_RUNATLOAD,
274         keep_alive, @ LAUNCH_JOBKEY_KEEPALIVE,
275         @ kServiceProcessSessionType, @ LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE,
276         nil];
277     [launchd_plist addEntriesFromDictionary:auto_launchd_plist];
278   }
279   return reinterpret_cast<CFDictionaryRef>(launchd_plist);
280 }
281
282 // Writes the launchd property list into the user's LaunchAgents directory,
283 // creating that directory if needed. This will cause the service process to be
284 // auto launched on the next user login.
285 bool ServiceProcessState::AddToAutoRun() {
286   // We're creating directories and writing a file.
287   base::ThreadRestrictions::AssertIOAllowed();
288   DCHECK(autorun_command_line_.get());
289   base::ScopedCFTypeRef<CFStringRef> name(CopyServiceProcessLaunchDName());
290   base::ScopedCFTypeRef<CFDictionaryRef> plist(
291       CreateServiceProcessLaunchdPlist(autorun_command_line_.get(), true));
292   return Launchd::GetInstance()->WritePlistToFile(Launchd::User,
293                                                   Launchd::Agent,
294                                                   name,
295                                                   plist);
296 }
297
298 bool ServiceProcessState::RemoveFromAutoRun() {
299   return RemoveFromLaunchd();
300 }
301
302 bool ServiceProcessState::StateData::WatchExecutable() {
303   base::mac::ScopedNSAutoreleasePool pool;
304   NSDictionary* ns_launchd_conf = base::mac::CFToNSCast(launchd_conf_);
305   NSString* exe_path = [ns_launchd_conf objectForKey:@ LAUNCH_JOBKEY_PROGRAM];
306   if (!exe_path) {
307     DLOG(ERROR) << "No " LAUNCH_JOBKEY_PROGRAM;
308     return false;
309   }
310
311   base::FilePath executable_path =
312       base::FilePath([exe_path fileSystemRepresentation]);
313   scoped_ptr<ExecFilePathWatcherCallback> callback(
314       new ExecFilePathWatcherCallback);
315   if (!callback->Init(executable_path)) {
316     DLOG(ERROR) << "executable_watcher_.Init " << executable_path.value();
317     return false;
318   }
319   if (!executable_watcher_.Watch(
320           executable_path,
321           false,
322           base::Bind(&ExecFilePathWatcherCallback::NotifyPathChanged,
323                      base::Owned(callback.release())))) {
324     DLOG(ERROR) << "executable_watcher_.watch " << executable_path.value();
325     return false;
326   }
327   return true;
328 }
329
330 bool ExecFilePathWatcherCallback::Init(const base::FilePath& path) {
331   return base::mac::FSRefFromPath(path.value(), &executable_fsref_);
332 }
333
334 void ExecFilePathWatcherCallback::NotifyPathChanged(const base::FilePath& path,
335                                                     bool error) {
336   if (error) {
337     NOTREACHED();  // TODO(darin): Do something smarter?
338     return;
339   }
340
341   base::mac::ScopedNSAutoreleasePool pool;
342   bool needs_shutdown = false;
343   bool needs_restart = false;
344   bool good_bundle = false;
345
346   FSRef macos_fsref;
347   if (GetParentFSRef(executable_fsref_, &macos_fsref)) {
348     FSRef contents_fsref;
349     if (GetParentFSRef(macos_fsref, &contents_fsref)) {
350       FSRef bundle_fsref;
351       if (GetParentFSRef(contents_fsref, &bundle_fsref)) {
352         base::ScopedCFTypeRef<CFURLRef> bundle_url(
353             CFURLCreateFromFSRef(kCFAllocatorDefault, &bundle_fsref));
354         if (bundle_url.get()) {
355           base::ScopedCFTypeRef<CFBundleRef> bundle(
356               CFBundleCreate(kCFAllocatorDefault, bundle_url));
357           // Check to see if the bundle still has a minimal structure.
358           good_bundle = CFBundleGetIdentifier(bundle) != NULL;
359         }
360       }
361     }
362   }
363   if (!good_bundle) {
364     needs_shutdown = true;
365   } else {
366     Boolean in_trash;
367     OSErr err = FSDetermineIfRefIsEnclosedByFolder(kOnAppropriateDisk,
368                                                    kTrashFolderType,
369                                                    &executable_fsref_,
370                                                    &in_trash);
371     if (err == noErr && in_trash) {
372       needs_shutdown = true;
373     } else {
374       bool was_moved = true;
375       FSRef path_ref;
376       if (base::mac::FSRefFromPath(path.value(), &path_ref)) {
377         if (FSCompareFSRefs(&path_ref, &executable_fsref_) == noErr) {
378           was_moved = false;
379         }
380       }
381       if (was_moved) {
382         needs_restart = true;
383       }
384     }
385   }
386   if (needs_shutdown || needs_restart) {
387     // First deal with the plist.
388     base::ScopedCFTypeRef<CFStringRef> name(CopyServiceProcessLaunchDName());
389     if (needs_restart) {
390       base::ScopedCFTypeRef<CFMutableDictionaryRef> plist(
391           Launchd::GetInstance()->CreatePlistFromFile(
392               Launchd::User, Launchd::Agent, name));
393       if (plist.get()) {
394         NSMutableDictionary* ns_plist = base::mac::CFToNSCast(plist);
395         std::string new_path = base::mac::PathFromFSRef(executable_fsref_);
396         NSString* ns_new_path = base::SysUTF8ToNSString(new_path);
397         [ns_plist setObject:ns_new_path forKey:@ LAUNCH_JOBKEY_PROGRAM];
398         base::scoped_nsobject<NSMutableArray> args([[ns_plist
399             objectForKey:@LAUNCH_JOBKEY_PROGRAMARGUMENTS] mutableCopy]);
400         [args replaceObjectAtIndex:0 withObject:ns_new_path];
401         [ns_plist setObject:args forKey:@ LAUNCH_JOBKEY_PROGRAMARGUMENTS];
402         if (!Launchd::GetInstance()->WritePlistToFile(Launchd::User,
403                                                       Launchd::Agent,
404                                                       name,
405                                                       plist)) {
406           DLOG(ERROR) << "Unable to rewrite plist.";
407           needs_shutdown = true;
408         }
409       } else {
410         DLOG(ERROR) << "Unable to read plist.";
411         needs_shutdown = true;
412       }
413     }
414     if (needs_shutdown) {
415       if (!RemoveFromLaunchd()) {
416         DLOG(ERROR) << "Unable to RemoveFromLaunchd.";
417       }
418     }
419
420     // Then deal with the process.
421     CFStringRef session_type = CFSTR(kServiceProcessSessionType);
422     if (needs_restart) {
423       if (!Launchd::GetInstance()->RestartJob(Launchd::User,
424                                               Launchd::Agent,
425                                               name,
426                                               session_type)) {
427         DLOG(ERROR) << "RestartLaunchdJob";
428         needs_shutdown = true;
429       }
430     }
431     if (needs_shutdown) {
432       CFStringRef label =
433           base::mac::NSToCFCast(GetServiceProcessLaunchDLabel());
434       CFErrorRef err = NULL;
435       if (!Launchd::GetInstance()->RemoveJob(label, &err)) {
436         base::ScopedCFTypeRef<CFErrorRef> scoped_err(err);
437         DLOG(ERROR) << "RemoveJob " << err;
438         // Exiting with zero, so launchd doesn't restart the process.
439         exit(0);
440       }
441     }
442   }
443 }