Убедитесь, что вспомогательный инструмент установлен - PullRequest
0 голосов
/ 19 января 2019

Я пишу приложение для MacOS на Swift, которому нужен привилегированный вспомогательный инструмент - хотелось бы, чтобы повышение не было необходимости, но похоже, что оно .

Я нашел это отличный пример приложения, специально созданный для этого сценария. Мне удалось перенести его код в мое собственное приложение, но я застрял в том месте, где мне нужно проверить, установлен ли вспомогательный инструмент, а если нет, использовать SMJobBless() и друзей для его установки.

При запуске примера приложения, если вспомогательный инструмент не установлен, приложение застревает на следующем экране:

enter image description here

Чтобы было ясно, после прочтения кода я подумал, что в какой-то момент я должен был обновить ярлык до "Установлен помощник: нет", но, похоже, этого не происходит.

Если я нажму «Установить помощник», это будет результат.

enter image description here

С этого момента, если я не удалю вспомогательный инструмент вручную, при повторном запуске приложения на этом экране будет отображаться «Установлено вспомогательное средство: Да».

Это может быть нормально в этом примере ситуации, когда пользователь должен вручную нажать кнопку «Установить помощник». Однако в моем приложении мне бы хотелось, чтобы оно автоматически запрашивало установку вспомогательного инструмента, если он еще не установлен. Если он уже установлен, я не хочу тратить время пользователя на повторный запрос пароля.

Я подумал, что это будет достаточно просто: если вспомогательный инструмент недоступен, где-то в процессе подключения к нему произойдет ошибка, которая послужит мне триггером для запроса установки инструмента. Если ошибки не возникают, предполагается, что инструмент уже установлен.

Вот взломанный код, который я написал для подключения к вспомогательному инструменту через XPC:

var helperConnection: NSXPCConnection?
var xpcErrorHandler: ((Error) -> Void)?
var helper: MyServiceProtocol?

// ...

helperConnection = NSXPCConnection(machServiceName: MyServiceName, options: .privileged)
helperConnection?.remoteObjectInterface = NSXPCInterface(with: MyServiceProtocol.self)
helperConnection?.resume()

helperConnection?.interruptionHandler = {
    // Handle interruption
    NSLog("interruptionHandler()")
}

helperConnection?.invalidationHandler = {
    // Handle invalidation
    NSLog("invalidationHandler()")
}

xpcErrorHandler = { error in
   NSLog("xpcErrorHandler: \(error.localizedDescription)")
}

guard
    let errorHandler = xpcErrorHandler,
    let helperService = helperConnection?.remoteObjectProxyWithErrorHandler(errorHandler) as? MyServiceProtocol
    else {
        return
}

helper = helperService

Если вспомогательный инструмент не установлен, запуск этого кода не приводит к ошибкам или выводу NSLog(). Если после этого я вызываю функцию через XPC (используя helper?.someFunction(...)), ничего не происходит - я мог бы также говорить с /dev/null.

Теперь я почесал голову в поисках техники, чтобы определить, установлен ли инструмент. Решение проблем в примерах приложений заключается в добавлении метода getVersion(); если он что-то возвращает, «Install Helper» отображается серым цветом, а метка меняется на «Helper Installed: Yes».

Я подумал о том, чтобы немного расширить эту идею, написав простую функцию в моем инструменте, которая мгновенно возвращает результат, и использовать тайм-аут в основном приложении - если я не получу результат до истечения времени ожидания кода, вспомогательный инструмент скорее всего не установлен. Я считаю это хакерским решением - что, если, например, вспомогательный инструмент (запускаемый по требованию) запускается слишком долго, скажем, из-за того, что компьютер устарел, а пользователь использует что-то интенсивное использование процессора?

Я вижу другие альтернативы, такие как просмотр файловой системы в ожидаемых местах (/Library/PrivilegedHelperTools и /Library/LaunchDaemons), но опять-таки это решение кажется мне неудовлетворительным.

Мой вопрос: Есть ли способ однозначно определить, прослушивает ли привилегированный вспомогательный инструмент XPC на другом конце?

Моя среда: macOS Mojave 10.14.2, Xcode 10.1, Swift 4.2.

Ответы [ 2 ]

0 голосов
/ 21 января 2019

Поскольку вы создаете вспомогательный инструмент, просто добавьте обработчик сообщений XPC, чтобы сообщить о состоянии вашего инструмента.Когда вы запустите, подключитесь и отправьте это сообщение.Если что-то из этого не получается, ваш инструмент установлен неправильно (или не отвечает).

В моем коде все мои службы XPC (включая моего привилегированного помощника) используют базовый протокол, используемый для тестирования иманипулировать установками:

@protocol DDComponentInstalling /*<NSObject>*/

@required
- (void)queryBuildNumberWithReply:(void(^_Nonnull)(UInt32))reply;

@optional
- (void)didInstallComponent;
- (void)willUninstallComponent;

queryBuildNumberWithReply: возвращает целое число, описывающее номер версии компонента:

- (void)queryBuildNumberWithReply:(void(^)(UInt32))reply
{
    reply(FULL_BUILD_VERSION);
}

Если сообщение успешно, я сравниваю возвращенное значение со сборкойЧисловая константа в моем приложении.Если они не совпадают, служба является более старой / более новой версией и должна быть заменена.Эта константа увеличивается с каждым публичным выпуском моего продукта.

Код, который я использую, выглядит примерно так:

- (BOOL)verifyServiceVersion
{
    DDConnection* connection = self.serviceConnection;
    id<DDComponentInstalling> proxy = connection.serviceProxy;  // get the proxy (will connect, as needed)
    if (proxy==nil)
        // an XPC connection could not be established or the proxy object could not be obtained
        return NO;  // assume service is not installed

    // Ask for the version number and wait for a response
    NSConditionLock* barrierLock = [[NSConditionLock alloc] initWithCondition:NO];
    __block UInt32 serviceVersion = UNKNOWN_BUILD_VERSION;
    [proxy queryBuildNumberWithReply:^(UInt32 version) {
        // Executes when service returns the build version
        [barrierLock lock];
        serviceVersion = version;
        [barrierLock unlockWithCondition:YES];  // signal to foreground thead that query is finished
        }];
    // wait for the message to reply
    [barrierLock lockWhenCondition:YES beforeDate:[NSDate dateWithTimeIntervalSinceNow:30.0];
    BOOL answer = (serviceVersion==FULL_BUILD_VERSION); // YES means helper is installed, alive, and correct version
    [barrierLock unlock];

    return answer;
}

Обратите внимание, что DDConnection - это служебная оболочка для соединений XPC, итрюк barrierLock фактически инкапсулирован в общем методе - так что я не заканчиваю писать это снова и снова - но здесь развернут для целей демонстрации.

У меня также есть пре / пост-Проблемы с установкой / обновлением, которые необходимо устранить, поэтому во всех моих компонентах реализованы дополнительные методы didInstallComponent и willUninstallComponent, которые я отправляю сразу после установки нового помощника или непосредственно перед тем, как планирую удалить или заменить установленный помощник.

0 голосов
/ 19 января 2019

Я бы проверил файловую систему, если бинарный файл существует (в / Library / PrivilegedHelperTools и если plist существует в / Library / LaunchDaemons).Затем вы можете связаться со службой XPC и вызвать функцию ping, которая отвечает, если служба запущена и работает.

только мои 2 cts,

Robert

...