Commit fa29446a authored by Mila Green's avatar Mila Green Committed by Commit Bot

Updater: Support system installs, uninstalls and updates.

Bug: 1069114

Change-Id: I1d52c030ad4efc12992133f869089dcc83655988
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2140925Reviewed-by: default avatarSorin Jianu <sorin@chromium.org>
Reviewed-by: default avatarRobert Sesek <rsesek@chromium.org>
Commit-Queue: Mila Green <milagreen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#759309}
parent 643ed47b
......@@ -19,6 +19,7 @@ const char kCrashHandlerSwitch[] = "crash-handler";
const char kInstallSwitch[] = "install";
const char kUninstallSwitch[] = "uninstall";
const char kUpdateAppsSwitch[] = "ua";
const char kSystemSwitch[] = "system";
const char kTestSwitch[] = "test";
const char kInitDoneNotifierSwitch[] = "init-done-notifier";
const char kNoRateLimitSwitch[] = "no-rate-limit";
......
......@@ -70,6 +70,9 @@ extern const char kUninstallSwitch[];
// Updates all apps registered with the updater.
extern const char kUpdateAppsSwitch[];
// The updater needs to operate in the system context.
extern const char kSystemSwitch[];
// Runs in test mode. Currently, it exits right away.
extern const char kTestSwitch[];
......
......@@ -15,6 +15,7 @@
#include "base/mac/foundation_util.h"
#include "base/mac/scoped_nsobject.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/strings/sys_string_conversions.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/threading/scoped_blocking_call.h"
......@@ -31,60 +32,108 @@
namespace updater {
namespace {
#pragma mark Helpers
const base::FilePath GetUpdateFolderName() {
return base::FilePath(COMPANY_SHORTNAME_STRING)
.AppendASCII(PRODUCT_FULLNAME_STRING);
}
const base::FilePath GetUpdaterAppName() {
return base::FilePath(PRODUCT_FULLNAME_STRING ".app");
}
const base::FilePath GetUpdaterAppExecutablePath() {
return base::FilePath("Contents/MacOS").AppendASCII(PRODUCT_FULLNAME_STRING);
}
bool CopyBundle() {
// Copy bundle to ~/Library/COMPANY_SHORTNAME_STRING/PRODUCT_FULLNAME_STRING.
bool IsSystemInstall() {
return geteuid() == 0;
}
const base::FilePath GetLocalLibraryDirectory() {
base::FilePath local_library_path;
if (!base::mac::GetLocalDirectory(NSLibraryDirectory, &local_library_path)) {
DLOG(WARNING) << "Could not get local library path";
}
return local_library_path;
}
const base::FilePath GetLibraryFolderPath() {
// For user installations: the "~/Library" for the logged in user.
// For system installations: "/Library".
return IsSystemInstall() ? GetLocalLibraryDirectory()
: base::mac::GetUserLibraryPath();
}
const base::FilePath GetUpdaterFolderPath() {
// For user installations:
// ~/Library/COMPANY_SHORTNAME_STRING/PRODUCT_FULLNAME_STRING.
// e.g. ~/Library/Google/GoogleUpdater
const base::FilePath dest_path =
base::mac::GetUserLibraryPath().Append(GetUpdateFolderName());
// For system installations:
// /Library/COMPANY_SHORTNAME_STRING/PRODUCT_FULLNAME_STRING.
// e.g. /Library/Google/GoogleUpdater
return GetLibraryFolderPath().Append(GetUpdateFolderName());
}
const base::FilePath GetUpdaterExecutablePath() {
return GetLibraryFolderPath()
.Append(GetUpdateFolderName())
.Append(GetUpdaterAppName())
.Append(GetUpdaterAppExecutablePath());
}
Launchd::Domain LaunchdDomain() {
return IsSystemInstall() ? Launchd::Domain::Local : Launchd::Domain::User;
}
Launchd::Type ServiceLaunchdType() {
return IsSystemInstall() ? Launchd::Type::Daemon : Launchd::Type::Agent;
}
Launchd::Type UpdateCheckLaunchdType() {
return Launchd::Type::Agent;
}
#pragma mark Setup
bool CopyBundle() {
const base::FilePath dest_path = GetUpdaterFolderPath();
if (!base::PathExists(dest_path)) {
if (!base::CreateDirectory(dest_path)) {
LOG(ERROR) << "Failed to create directory at "
<< dest_path.value().c_str();
base::File::Error error;
if (!base::CreateDirectoryAndGetError(dest_path, &error)) {
LOG(ERROR) << "Failed to create '" << dest_path.value().c_str()
<< "' directory: " << base::File::ErrorToString(error);
return false;
}
}
const base::FilePath src_path = base::mac::OuterBundlePath();
if (!base::CopyDirectory(src_path, dest_path, true)) {
LOG(ERROR) << "Copying app to ~/Library failed";
LOG(ERROR) << "Copying app to '" << dest_path.value().c_str() << "' failed";
return false;
}
return true;
}
bool DeleteInstallFolder() {
// Delete install folder
// ~/Library/COMPANY_SHORTNAME_STRING/PRODUCT_FULLNAME_STRING.
// e.g. ~/Library/Google/GoogleUpdater
const base::FilePath dest_path =
base::mac::GetUserLibraryPath().Append(GetUpdateFolderName());
if (!base::DeleteFileRecursively(dest_path)) {
LOG(ERROR) << "Deleting " << dest_path << " failed";
return false;
}
return true;
NSString* MakeProgramArgument(const char* argument) {
return base::SysUTF8ToNSString(base::StrCat({"--", argument}));
}
base::ScopedCFTypeRef<CFDictionaryRef> CreateGoogleUpdateCheckLaunchdPlist(
const base::FilePath* updater_path) {
// See the man page for launchd.plist.
NSMutableArray* programArguments = [NSMutableArray array];
[programArguments addObjectsFromArray:@[
base::SysUTF8ToNSString(updater_path->value()),
MakeProgramArgument(kUpdateAppsSwitch)
]];
if (IsSystemInstall())
[programArguments addObject:MakeProgramArgument(kSystemSwitch)];
NSDictionary* launchd_plist = @{
@LAUNCH_JOBKEY_LABEL : GetGoogleUpdateCheckLaunchDLabel(),
@LAUNCH_JOBKEY_PROGRAMARGUMENTS :
@[ base::SysUTF8ToNSString(updater_path->value()), @"--ua" ],
@LAUNCH_JOBKEY_PROGRAMARGUMENTS : programArguments,
@LAUNCH_JOBKEY_STARTINTERVAL : @18000,
@LAUNCH_JOBKEY_ABANDONPROCESSGROUP : @NO,
@LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE : @"Aqua"
......@@ -100,8 +149,10 @@ base::ScopedCFTypeRef<CFDictionaryRef> CreateGoogleUpdateServiceLaunchdPlist(
// See the man page for launchd.plist.
NSDictionary* launchd_plist = @{
@LAUNCH_JOBKEY_LABEL : GetGoogleUpdateServiceLaunchDLabel(),
@LAUNCH_JOBKEY_PROGRAMARGUMENTS :
@[ base::SysUTF8ToNSString(updater_path->value()), @"--server" ],
@LAUNCH_JOBKEY_PROGRAMARGUMENTS : @[
base::SysUTF8ToNSString(updater_path->value()),
MakeProgramArgument(kServerSwitch)
],
@LAUNCH_JOBKEY_MACHSERVICES : @{GetGoogleUpdateServiceMachName() : @YES},
@LAUNCH_JOBKEY_ABANDONPROCESSGROUP : @NO,
@LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE : @"Aqua"
......@@ -118,16 +169,12 @@ bool CreateLaunchdCheckItem() {
base::BlockingType::MAY_BLOCK);
base::ScopedCFTypeRef<CFStringRef> name(CopyGoogleUpdateCheckLaunchDName());
const base::FilePath updater_path =
base::mac::GetUserLibraryPath()
.Append(GetUpdateFolderName())
.Append(GetUpdaterAppName())
.Append(GetUpdaterAppExecutablePath());
const base::FilePath updater_path = GetUpdaterExecutablePath();
base::ScopedCFTypeRef<CFDictionaryRef> plist(
CreateGoogleUpdateCheckLaunchdPlist(&updater_path));
return Launchd::GetInstance()->WritePlistToFile(Launchd::User, Launchd::Agent,
name, plist);
return Launchd::GetInstance()->WritePlistToFile(
LaunchdDomain(), UpdateCheckLaunchdType(), name, plist);
}
bool CreateLaunchdServiceItem() {
......@@ -136,37 +183,24 @@ bool CreateLaunchdServiceItem() {
base::BlockingType::MAY_BLOCK);
base::ScopedCFTypeRef<CFStringRef> name(CopyGoogleUpdateServiceLaunchDName());
const base::FilePath updater_path =
base::mac::GetUserLibraryPath()
.Append(GetUpdateFolderName())
.Append(GetUpdaterAppName())
.Append(GetUpdaterAppExecutablePath());
const base::FilePath updater_path = GetUpdaterExecutablePath();
base::ScopedCFTypeRef<CFDictionaryRef> plist(
CreateGoogleUpdateServiceLaunchdPlist(&updater_path));
return Launchd::GetInstance()->WritePlistToFile(Launchd::User, Launchd::Agent,
name, plist);
}
bool RemoveFromLaunchd() {
// This may block while deleting the launchd plist file.
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
base::ScopedCFTypeRef<CFStringRef> name(CopyGoogleUpdateCheckLaunchDName());
return Launchd::GetInstance()->DeletePlist(Launchd::User, Launchd::Agent,
name);
return Launchd::GetInstance()->WritePlistToFile(
LaunchdDomain(), ServiceLaunchdType(), name, plist);
}
bool StartLaunchdUpdateCheckTask() {
base::ScopedCFTypeRef<CFStringRef> name(CopyGoogleUpdateCheckLaunchDName());
return Launchd::GetInstance()->RestartJob(Launchd::User, Launchd::Agent, name,
CFSTR("Aqua"));
return Launchd::GetInstance()->RestartJob(
LaunchdDomain(), UpdateCheckLaunchdType(), name, CFSTR("Aqua"));
}
bool StartLaunchdServiceTask() {
base::ScopedCFTypeRef<CFStringRef> name(CopyGoogleUpdateServiceLaunchDName());
return Launchd::GetInstance()->RestartJob(Launchd::User, Launchd::Agent, name,
CFSTR("Aqua"));
return Launchd::GetInstance()->RestartJob(
LaunchdDomain(), ServiceLaunchdType(), name, CFSTR("Aqua"));
}
} // namespace
......@@ -190,14 +224,46 @@ int SetupUpdater() {
return 0;
}
#pragma mark Uninstall
bool RemoveUpdateCheckFromLaunchd() {
// This may block while deleting the launchd plist file.
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
base::ScopedCFTypeRef<CFStringRef> name(CopyGoogleUpdateCheckLaunchDName());
return Launchd::GetInstance()->DeletePlist(LaunchdDomain(),
UpdateCheckLaunchdType(), name);
}
bool RemoveServiceFromLaunchd() {
// This may block while deleting the launchd plist file.
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
base::ScopedCFTypeRef<CFStringRef> name(CopyGoogleUpdateServiceLaunchDName());
return Launchd::GetInstance()->DeletePlist(LaunchdDomain(),
ServiceLaunchdType(), name);
}
bool DeleteInstallFolder() {
const base::FilePath dest_path = GetUpdaterFolderPath();
if (!base::DeleteFileRecursively(dest_path)) {
LOG(ERROR) << "Deleting " << dest_path << " failed";
return false;
}
return true;
}
int Uninstall(bool is_machine) {
ALLOW_UNUSED_LOCAL(is_machine);
if (!RemoveFromLaunchd())
if (!RemoveUpdateCheckFromLaunchd())
return -1;
if (!DeleteInstallFolder())
if (!RemoveServiceFromLaunchd())
return -2;
if (!DeleteInstallFolder())
return -3;
return 0;
}
......
......@@ -31,7 +31,7 @@ namespace updater {
// All functions and callbacks must be called on the same sequence.
class UpdateServiceOutOfProcess : public UpdateService {
public:
UpdateServiceOutOfProcess();
explicit UpdateServiceOutOfProcess(UpdateService::Scope scope);
// Overrides for UpdateService.
void RegisterApp(
......
......@@ -17,6 +17,7 @@
#import "chrome/updater/mac/xpc_service_names.h"
#import "chrome/updater/server/mac/service_protocol.h"
#import "chrome/updater/server/mac/update_service_wrappers.h"
#include "chrome/updater/update_service.h"
#include "chrome/updater/updater_version.h"
#include "components/update_client/update_client_errors.h"
......@@ -25,6 +26,8 @@ using base::SysUTF8ToNSString;
// Interface to communicate with the XPC Updater Service.
@interface CRUUpdateServiceOutOfProcessImpl : NSObject <CRUUpdateChecking>
- (instancetype)initPrivileged;
@end
@implementation CRUUpdateServiceOutOfProcessImpl {
......@@ -32,10 +35,18 @@ using base::SysUTF8ToNSString;
}
- (instancetype)init {
return [self initWithConnectionOptions:0];
}
- (instancetype)initPrivileged {
return [self initWithConnectionOptions:NSXPCConnectionPrivileged];
}
- (instancetype)initWithConnectionOptions:(NSXPCConnectionOptions)options {
if ((self = [super init])) {
_xpcConnection.reset([[NSXPCConnection alloc]
initWithMachServiceName:updater::GetGoogleUpdateServiceMachName().get()
options:0]);
options:options]);
_xpcConnection.get().remoteObjectInterface = updater::GetXpcInterface();
......@@ -110,8 +121,18 @@ using base::SysUTF8ToNSString;
namespace updater {
UpdateServiceOutOfProcess::UpdateServiceOutOfProcess() {
client_.reset([[CRUUpdateServiceOutOfProcessImpl alloc] init]);
UpdateServiceOutOfProcess::UpdateServiceOutOfProcess(
UpdateService::Scope scope) {
switch (scope) {
case UpdateService::Scope::kSystem:
client_.reset([[CRUUpdateServiceOutOfProcessImpl alloc] initPrivileged]);
break;
case UpdateService::Scope::kUser:
client_.reset([[CRUUpdateServiceOutOfProcessImpl alloc] init]);
break;
default:
CHECK(false) << "Unexpected value for UpdateService::Scope";
}
callback_runner_ = base::SequencedTaskRunnerHandle::Get();
}
......
......@@ -15,10 +15,15 @@ namespace updater {
scoped_refptr<UpdateService> CreateUpdateService(
scoped_refptr<update_client::Configurator> config) {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSingleProcessSwitch))
base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
if (cmdline->HasSwitch(kSingleProcessSwitch))
return base::MakeRefCounted<UpdateServiceInProcess>(config);
else
return base::MakeRefCounted<UpdateServiceOutOfProcess>();
return cmdline->HasSwitch(kSystemSwitch)
? base::MakeRefCounted<UpdateServiceOutOfProcess>(
UpdateService::Scope::kSystem)
: base::MakeRefCounted<UpdateServiceOutOfProcess>(
UpdateService::Scope::kUser);
}
} // namespace updater
......@@ -69,6 +69,15 @@ class UpdateService : public base::RefCountedThreadSafe<UpdateService> {
kForeground = 2,
};
// Scope of the update service invocation.
enum class Scope {
// The updater is running in the logged in user's scope.
kUser = 1,
// The updater is running in the system's scope.
kSystem = 2,
};
using StateChangeCallback = base::RepeatingCallback<void(UpdateState)>;
using Callback = base::OnceCallback<void(Result)>;
......
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