Commit 3114e51a authored by garykac@chromium.org's avatar garykac@chromium.org

[Chromoting] Update uninstaller to facilitate testing automation.

With this change, if launched from shell as root, the uninstaller will:
* Not show any confirmation UX
* Not prompt for admin elevation

Also in this cl, cleanup some of the shared Mac constants by pushing
more into into constants_mac and fixing up the naming style. This
required minor updates to the prefpane and daemon controller.

BUG=None
TEST=None
Review URL: https://chromiumcodereview.appspot.com/10807061

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@148442 0039d316-1c4b-4281-b951-d872f2087c98
parent 0812b714
......@@ -6,6 +6,34 @@
namespace remoting {
const char kHostHelperTool[] = kHostConfigDir kServiceName ".me2me.sh";
#define SERVICE_NAME "org.chromium.chromoting"
#define APPLICATIONS_DIR "/Applications/"
#define HELPER_TOOLS_DIR "/Library/PrivilegedHelperTools/"
#define LAUNCH_AGENTS_DIR "/Library/LaunchAgents/"
#define PREFERENCE_PANES_DIR "/Library/PreferencePanes/"
const char kServiceName[] = SERVICE_NAME;
const char kUpdateSucceededNotificationName[] =
SERVICE_NAME ".update_succeeded";
const char kUpdateFailedNotificationName[] = SERVICE_NAME ".update_failed";
const char kPrefPaneFileName[] = SERVICE_NAME ".prefPane";
const char kPrefPaneFilePath[] = PREFERENCE_PANES_DIR SERVICE_NAME ".prefPane";
const char kHostConfigFileName[] = SERVICE_NAME ".json";
const char kHostConfigFilePath[] = HELPER_TOOLS_DIR SERVICE_NAME ".json";
const char kHostHelperScriptPath[] = HELPER_TOOLS_DIR SERVICE_NAME ".me2me.sh";
const char kHostBinaryPath[] = HELPER_TOOLS_DIR SERVICE_NAME ".me2me_host.app";
const char kHostEnabledPath[] = HELPER_TOOLS_DIR SERVICE_NAME ".me2me_enabled";
const char kServicePlistPath[] = LAUNCH_AGENTS_DIR SERVICE_NAME ".plist";
const char kBrandedUninstallerPath[] = APPLICATIONS_DIR
"Chrome Remote Desktop Host Uninstaller.app";
const char kUnbrandedUninstallerPath[] = APPLICATIONS_DIR
"Chromoting Host Uninstaller.app";
} // namespace remoting
......@@ -8,21 +8,49 @@
namespace remoting {
// The name of the Remoting Host service that is registered with launchd.
#define kServiceName "org.chromium.chromoting"
extern const char kServiceName[];
// Use separate named notifications for success and failure because sandboxed
// components can't include a dictionary when sending distributed notifications.
// The preferences panel is not yet sandboxed, but err on the side of caution.
#define kUpdateSucceededNotificationName kServiceName ".update_succeeded"
#define kUpdateFailedNotificationName kServiceName ".update_failed"
// These are #defines because they are used with CFSTR macro, which requires
// string literals.
#define UPDATE_SUCCEEDED_NOTIFICATION_NAME \
"org.chromium.chromoting.update_succeeded"
#define UPDATE_FAILED_NOTIFICATION_NAME "org.chromium.chromoting.update_failed"
#define kHostConfigDir "/Library/PrivilegedHelperTools/"
// This helper tool is executed as root to enable/disable/configure the host
// Chromoting's preference pane file.
extern const char kPrefPaneFileName[];
extern const char kPrefPaneFilePath[];
// Use a single configuration file, instead of separate "auth" and "host" files.
// This is because the SetConfigAndStart() API only provides a single
// dictionary, and splitting this into two dictionaries would require
// knowledge of which keys belong in which files.
extern const char kHostConfigFileName[];
extern const char kHostConfigFilePath[];
// This helper script is executed as root to enable/disable/configure the host
// service.
// It is also used (as non-root) to provide version information for the
// installed host components.
extern const char kHostHelperTool[];
extern const char kHostHelperScriptPath[];
// Path to the service binary (.app).
extern const char kHostBinaryPath[];
// If this file exists, it means that the host is enabled for sharing.
extern const char kHostEnabledPath[];
// The .plist file for the Chromoting service.
extern const char kServicePlistPath[];
// The branded and unbranded names for the uninstaller.
// This is the only file that changes names based on branding. We define both
// because we want local dev builds to be able to clean up both files.
extern const char kBrandedUninstallerPath[];
extern const char kUnbrandedUninstallerPath[];
} // namespace remoting
......
......@@ -4,11 +4,9 @@
#import <Cocoa/Cocoa.h>
@interface RemotingUninstallerAppDelegate : NSObject {
@interface RemotingUninstaller : NSObject {
}
- (IBAction)uninstall:(id)sender;
- (IBAction)cancel:(id)sender;
- (OSStatus)remotingUninstall;
- (IBAction)handleMenuClose:(NSMenuItem*)sender;
@end
// Copyright (c) 2012 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.
#import <Cocoa/Cocoa.h>
@interface RemotingUninstallerAppDelegate : NSObject {
}
- (IBAction)uninstall:(id)sender;
- (IBAction)cancel:(id)sender;
- (IBAction)handleMenuClose:(NSMenuItem*)sender;
@end
// Copyright (c) 2012 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.
#include "remoting/host/installer/mac/uninstaller/remoting_uninstaller_app.h"
#import <Cocoa/Cocoa.h>
#include "base/mac/scoped_cftyperef.h"
#include "remoting/host/installer/mac/uninstaller/remoting_uninstaller.h"
@implementation RemotingUninstallerAppDelegate
- (void)dealloc {
[super dealloc];
}
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification {
}
- (void)showSuccess:(bool)success withMessage:(NSString*) message {
NSString* summary = success ? @"Uninstall succeeded" : @"Uninstall failed";
NSAlert* alert = [NSAlert alertWithMessageText:summary
defaultButton:@"OK"
alternateButton:nil
otherButton:nil
informativeTextWithFormat:message];
[alert runModal];
}
- (IBAction)uninstall:(NSButton*)sender {
@try {
NSLog(@"Chrome Remote Desktop uninstall starting.");
RemotingUninstaller* uninstaller =
[[[RemotingUninstaller alloc] init] autorelease];
OSStatus status = [uninstaller remotingUninstall];
NSLog(@"Chrome Remote Desktop Host uninstall complete.");
bool success = false;
NSString* message = NULL;
if (status == errAuthorizationSuccess) {
success = true;
message = @"Chrome Remote Desktop Host successfully uninstalled.";
} else if (status == errAuthorizationCanceled) {
message = @"Chrome Remote Desktop Host uninstall canceled.";
} else if (status == errAuthorizationDenied) {
message = @"Chrome Remote Desktop Host uninstall authorization denied.";
} else {
[NSException raise:@"AuthorizationCopyRights Failure"
format:@"Error during AuthorizationCopyRights status=%ld", status];
}
if (message != NULL) {
NSLog(@"Uninstall %s: %@", success ? "succeeded" : "failed", message);
[self showSuccess:success withMessage:message];
}
}
@catch (NSException* exception) {
NSLog(@"Exception %@ %@", [exception name], [exception reason]);
NSString* message =
@"Error! Unable to uninstall Chrome Remote Desktop Host.";
[self showSuccess:false withMessage:message];
}
[NSApp terminate:self];
}
- (IBAction)cancel:(id)sender {
[NSApp terminate:self];
}
- (IBAction)handleMenuClose:(NSMenuItem*)sender {
[NSApp terminate:self];
}
@end
int main(int argc, char* argv[])
{
// The no-ui option skips the UI confirmation dialogs. This is provided as
// a convenience for our automated testing.
// There will still be an elevation prompt unless the command is run as root.
if (argc == 2 && !strcmp(argv[1], "--no-ui")) {
@autoreleasepool {
NSLog(@"Chrome Remote Desktop uninstall starting.");
NSLog(@"--no-ui : Suppressing UI");
RemotingUninstaller* uninstaller =
[[[RemotingUninstaller alloc] init] autorelease];
OSStatus status = [uninstaller remotingUninstall];
NSLog(@"Chrome Remote Desktop Host uninstall complete.");
NSLog(@"Status = %ld", status);
return status != errAuthorizationSuccess;
}
} else {
return NSApplicationMain(argc, (const char**)argv);
}
}
......@@ -33,8 +33,8 @@ bool GetTemporaryConfigFilePath(std::string* path) {
if (filename == nil)
return false;
filename = [filename stringByAppendingString:@"/" kServiceName ".json"];
*path = [filename UTF8String];
*path = [[NSString stringWithFormat:@"%@/%s",
filename, remoting::kHostConfigFileName] UTF8String];
return true;
}
......@@ -226,6 +226,7 @@ OSStatus ExecuteWithPrivilegesAndGetPID(AuthorizationRef authorization,
} // namespace base
namespace remoting {
JsonHostConfig::JsonHostConfig(const std::string& filename)
: filename_(filename) {
}
......@@ -282,7 +283,7 @@ std::string JsonHostConfig::GetSerializedData() const {
[NSDistributedNotificationCenter defaultCenter];
[center addObserver:self
selector:@selector(onNewConfigFile:)
name:@kServiceName
name:[NSString stringWithUTF8String:remoting::kServiceName]
object:nil];
service_status_timer_ =
......@@ -314,7 +315,7 @@ std::string JsonHostConfig::GetSerializedData() const {
[service_status_timer_ release];
service_status_timer_ = nil;
[self notifyPlugin:kUpdateFailedNotificationName];
[self notifyPlugin:UPDATE_FAILED_NOTIFICATION_NAME];
}
- (void)applyConfiguration:(id)sender
......@@ -358,7 +359,7 @@ std::string JsonHostConfig::GetSerializedData() const {
inputData:""]) {
NSLog(@"Failed to run the helper tool");
[self showError];
[self notifyPlugin: kUpdateFailedNotificationName];
[self notifyPlugin:UPDATE_FAILED_NOTIFICATION_NAME];
return;
}
......@@ -381,7 +382,7 @@ std::string JsonHostConfig::GetSerializedData() const {
[self updateServiceStatus];
if (awaiting_service_stop_ && !is_service_running_) {
awaiting_service_stop_ = NO;
[self notifyPlugin:kUpdateSucceededNotificationName];
[self notifyPlugin:UPDATE_SUCCEEDED_NOTIFICATION_NAME];
}
if (was_running != is_service_running_)
......@@ -399,7 +400,7 @@ std::string JsonHostConfig::GetSerializedData() const {
}
- (void)updateServiceStatus {
pid_t job_pid = base::mac::PIDForJob(kServiceName);
pid_t job_pid = base::mac::PIDForJob(remoting::kServiceName);
is_service_running_ = (job_pid > 0);
}
......@@ -525,11 +526,11 @@ std::string JsonHostConfig::GetSerializedData() const {
// If the service is running, send a signal to cause it to reload its
// configuration, otherwise start the service.
if (is_service_running_) {
pid_t job_pid = base::mac::PIDForJob(kServiceName);
pid_t job_pid = base::mac::PIDForJob(remoting::kServiceName);
if (job_pid > 0) {
kill(job_pid, SIGHUP);
} else {
NSLog(@"Failed to obtain PID of service " kServiceName);
NSLog(@"Failed to obtain PID of service %s", remoting::kServiceName);
[self showError];
}
} else {
......@@ -538,7 +539,7 @@ std::string JsonHostConfig::GetSerializedData() const {
// Broadcast a distributed notification to inform the plugin that the
// configuration has been applied.
[self notifyPlugin: kUpdateSucceededNotificationName];
[self notifyPlugin:UPDATE_SUCCEEDED_NOTIFICATION_NAME];
}
- (BOOL)runHelperAsRootWithCommand:(const char*)command
......@@ -558,7 +559,7 @@ std::string JsonHostConfig::GetSerializedData() const {
pid_t pid;
OSStatus status = base::mac::ExecuteWithPrivilegesAndGetPID(
authorization,
remoting::kHostHelperTool,
remoting::kHostHelperScriptPath,
kAuthorizationFlagDefaults,
arguments,
&pipe,
......@@ -618,7 +619,7 @@ std::string JsonHostConfig::GetSerializedData() const {
if (WIFEXITED(exit_status) && WEXITSTATUS(exit_status) == 0) {
return YES;
} else {
NSLog(@"%s failed with exit status %d", remoting::kHostHelperTool,
NSLog(@"%s failed with exit status %d", remoting::kHostHelperScriptPath,
exit_status);
return NO;
}
......@@ -626,7 +627,7 @@ std::string JsonHostConfig::GetSerializedData() const {
- (BOOL)sendJobControlMessage:(const char*)launch_key {
base::mac::ScopedLaunchData response(
base::mac::MessageForJob(kServiceName, launch_key));
base::mac::MessageForJob(remoting::kServiceName, launch_key));
if (!response) {
NSLog(@"Failed to send message to launchd");
[self showError];
......@@ -742,14 +743,14 @@ std::string JsonHostConfig::GetSerializedData() const {
}
remove(file.c_str());
[self notifyPlugin:kUpdateFailedNotificationName];
[self notifyPlugin:UPDATE_FAILED_NOTIFICATION_NAME];
}
}
- (void)restartSystemPreferences {
NSTask* task = [[NSTask alloc] init];
NSString* command =
[NSString stringWithUTF8String:remoting::kHostHelperTool];
[NSString stringWithUTF8String:remoting::kHostHelperScriptPath];
NSArray* arguments = [NSArray arrayWithObjects:@"--relaunch-prefpane", nil];
[task setLaunchPath:command];
[task setArguments:arguments];
......
......@@ -37,12 +37,6 @@ namespace {
// Therefore, we define the needed constants here.
const int NSLibraryDirectory = 5;
// Use a single configuration file, instead of separate "auth" and "host" files.
// This is because the SetConfigAndStart() API only provides a single
// dictionary, and splitting this into two dictionaries would require
// knowledge of which keys belong in which files.
const char kHostConfigFile[] = kHostConfigDir kServiceName ".json";
class DaemonControllerMac : public remoting::DaemonController {
public:
DaemonControllerMac();
......@@ -104,12 +98,12 @@ void DaemonControllerMac::DeregisterForPreferencePaneNotifications() {
CFNotificationCenterRemoveObserver(
CFNotificationCenterGetDistributedCenter(),
this,
CFSTR(kUpdateSucceededNotificationName),
CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME),
NULL);
CFNotificationCenterRemoveObserver(
CFNotificationCenterGetDistributedCenter(),
this,
CFSTR(kUpdateFailedNotificationName),
CFSTR(UPDATE_FAILED_NOTIFICATION_NAME),
NULL);
}
......@@ -180,7 +174,7 @@ void DaemonControllerMac::GetUsageStatsConsent(
}
void DaemonControllerMac::DoGetConfig(const GetConfigCallback& callback) {
FilePath config_path(kHostConfigFile);
FilePath config_path(kHostConfigFilePath);
JsonHostConfig host_config(config_path);
scoped_ptr<base::DictionaryValue> config;
......@@ -198,7 +192,7 @@ void DaemonControllerMac::DoGetConfig(const GetConfigCallback& callback) {
void DaemonControllerMac::DoGetVersion(const GetVersionCallback& callback) {
std::string version = "";
std::string command_line = remoting::kHostHelperTool;
std::string command_line = remoting::kHostHelperScriptPath;
command_line += " --host-version";
FILE* script_output = popen(command_line.c_str(), "r");
if (script_output) {
......@@ -231,7 +225,7 @@ void DaemonControllerMac::DoSetConfigAndStart(
void DaemonControllerMac::DoUpdateConfig(
scoped_ptr<base::DictionaryValue> config,
const CompletionCallback& done_callback) {
FilePath config_file_path(kHostConfigFile);
FilePath config_file_path(kHostConfigFilePath);
JsonHostConfig config_file(config_file_path);
if (!config_file.Read()) {
done_callback.Run(RESULT_FAILED);
......@@ -268,7 +262,7 @@ bool DaemonControllerMac::DoShowPreferencePane(const std::string& config_data) {
LOG(ERROR) << "Failed to get filename for saving configuration data.";
return false;
}
config_path = config_path.Append(kServiceName ".json");
config_path = config_path.Append(kHostConfigFileName);
int written = file_util::WriteFile(config_path, config_data.data(),
config_data.size());
......@@ -286,8 +280,7 @@ bool DaemonControllerMac::DoShowPreferencePane(const std::string& config_data) {
LOG(ERROR) << "Failed to get directory for local preference panes.";
return false;
}
pane_path = pane_path.Append("PreferencePanes")
.Append(kServiceName ".prefPane");
pane_path = pane_path.Append("PreferencePanes").Append(kPrefPaneFileName);
FSRef pane_path_ref;
if (!base::mac::FSRefFromPath(pane_path.value(), &pane_path_ref)) {
......@@ -303,7 +296,10 @@ bool DaemonControllerMac::DoShowPreferencePane(const std::string& config_data) {
CFNotificationCenterRef center =
CFNotificationCenterGetDistributedCenter();
CFNotificationCenterPostNotification(center, CFSTR(kServiceName), NULL, NULL,
base::mac::ScopedCFTypeRef<CFStringRef> service_name(
CFStringCreateWithCString(kCFAllocatorDefault, remoting::kServiceName,
kCFStringEncodingUTF8));
CFNotificationCenterPostNotification(center, service_name, NULL, NULL,
TRUE);
return true;
}
......@@ -328,24 +324,24 @@ void DaemonControllerMac::RegisterForPreferencePaneNotifications(
CFNotificationCenterGetDistributedCenter(),
this,
&DaemonControllerMac::PreferencePaneCallback,
CFSTR(kUpdateSucceededNotificationName),
CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME),
NULL,
CFNotificationSuspensionBehaviorDeliverImmediately);
CFNotificationCenterAddObserver(
CFNotificationCenterGetDistributedCenter(),
this,
&DaemonControllerMac::PreferencePaneCallback,
CFSTR(kUpdateFailedNotificationName),
CFSTR(UPDATE_FAILED_NOTIFICATION_NAME),
NULL,
CFNotificationSuspensionBehaviorDeliverImmediately);
}
void DaemonControllerMac::PreferencePaneCallbackDelegate(CFStringRef name) {
AsyncResult result = RESULT_FAILED;
if (CFStringCompare(name, CFSTR(kUpdateSucceededNotificationName), 0) ==
if (CFStringCompare(name, CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME), 0) ==
kCFCompareEqualTo) {
result = RESULT_OK;
} else if (CFStringCompare(name, CFSTR(kUpdateFailedNotificationName), 0) ==
} else if (CFStringCompare(name, CFSTR(UPDATE_FAILED_NOTIFICATION_NAME), 0) ==
kCFCompareEqualTo) {
result = RESULT_FAILED;
} else {
......
......@@ -272,8 +272,12 @@
'<(DEPTH)/base/base.gyp:base',
],
'sources': [
'host/constants_mac.cc',
'host/constants_mac.h',
'host/installer/mac/uninstaller/remoting_uninstaller.h',
'host/installer/mac/uninstaller/remoting_uninstaller.mm',
'host/installer/mac/uninstaller/remoting_uninstaller_app.h',
'host/installer/mac/uninstaller/remoting_uninstaller_app.mm',
],
'xcode_settings': {
'INFOPLIST_FILE': 'host/installer/mac/uninstaller/remoting_uninstaller-Info.plist',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment