файл launchd.plist работает каждые 10 секунд вместо одного раза


Я настраиваю launchd.plist XML, который запускается каждый раз, когда монтируется конкретное USB-устройство. Я следовал инструкциям на man-страницеxpc_events(3) , и она запускает приложение всякий раз, когда устройство монтируется.

Проблема, с которой я сталкиваюсь, заключается в том, что приложение запускается снова и снова каждые 10 секунд, пока устройство все еще подключено. Как настроить его так, чтобы он работал только один раз, когда устройство вставлено в USB-порт?

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.myapp.agent</string>
    <key>Program</key>
    <string>/Applications/MyApp.app/Contents/MacOS/MyAgent</string>
    <key>LaunchEvents</key>
    <dict>
        <key>com.apple.iokit.matching</key>
        <dict>
            <key>com.apple.device-attach</key>
            <dict>
                <key>idVendor</key>
                <integer>2316</integer>
                <key>idProduct</key>
                <integer>4096</integer>
                <key>IOProviderClass</key>
                <string>IOUSBDevice</string>
                <key>IOMatchLaunchStream</key>
                <true/>
            </dict>
        </dict>
        <key>com.apple.notifyd.matching</key>
        <dict>
            <key>com.apple.interesting-notification</key>
            <dict>
                <key>Notification</key>
                <string>com.apple.interesting-notification</string>
            </dict>
        </dict>
    </dict>
</dict>
</plist>
4 4

4 ответа:

AIUI ваше приложение должно вызвать xpc_set_event_stream_handler, чтобы удалить событие из очереди. Возможно, Вам также придется добавить <key>KeepAlive</key><false/>.плист, но я в этом не уверен.

Я пытаюсь использовать что-то вроде этого:

#include <xpc/xpc.h>
#include <unistd.h>
#include <asl.h>

int main(int argc, char *argv[]) {
    if (argc < 2) {
        return 1;
    }

    asl_log(NULL, NULL, ASL_LEVEL_DEBUG, "event_stream_handler: starting");

    xpc_set_event_stream_handler("com.apple.iokit.matching", dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(xpc_object_t event) {
        const char *name = xpc_dictionary_get_string(event, XPC_EVENT_KEY_NAME);
        uint64_t id = xpc_dictionary_get_uint64(event, "IOMatchLaunchServiceID");
        asl_log(NULL, NULL, ASL_LEVEL_DEBUG, "event_stream_handler: received event: %s: %llu", name, id);

        execv(argv[1], argv + 1);
    });

    dispatch_main();

    return 0;
}

Таким образом, сценарий, который потребляет событие и запускает сценарий, переданный в качестве аргумента.

Я написал учебник по этому с подробными инструкциями и файлами примеров для запуска произвольного исполняемого файла или скрипта оболочки путем подключения внешнего устройства (usb/thunderbolt) к компьютеру Mac, без проблемы респауна.

Как и подход авторов, он опирается на библиотеку Apple IOKit для обнаружения устройств и демона для запуска желаемого исполняемого файла. Чтобы демон не запускался повторно после подключения устройства, специальный поток обработчик (xpc_set_event_stream_handler) используется для "потребления" события com.apple.iokit.matching, как описано в посте @ford и в его РЕПО github.

В частности, учебник описывает, как скомпилировать обработчик потока xpc и как ссылаться на него вместе с исполняемым файлом в файле daemon plist и где разместить все соответствующие файлы с правильными разрешениями.

Для файлов, пожалуйста, перейдите сюда . Для полноты картины я также вставил их содержание ниже.

Беги сценарий оболочки или исполняемый файл, вызванный обнаружением устройства на mac

Здесь я использую пример подмены MAC-адреса адаптера ethernet, когда он подключен к Mac. Это можно обобщить на произвольные исполняемые файлы и устройства.

Поместите свой сценарий оболочки или исполняемый файл на место

Адаптировать сценарий оболочки spoof-mac.sh

#!/bin/bash
ifconfig en12 ether 12:34:56:78:9A:BC

К вашим потребностям и сделайте его исполняемым:

sudo chmod 755 spoof-mac.sh

Затем переместите его в /usr/local/bin или в другой каталог:

Cp spoof-mac.sh /usr / local / bin /

Построение обработчика потока

Обработчик потока xpc_set_event_stream_handler.m

//  Created by Ford Parsons on 10/23/17.
//  Copyright © 2017 Ford Parsons. All rights reserved.
//

#import <Foundation/Foundation.h>
#include <xpc/xpc.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        xpc_set_event_stream_handler("com.apple.iokit.matching", NULL, ^(xpc_object_t _Nonnull object) {
            const char *event = xpc_dictionary_get_string(object, XPC_EVENT_KEY_NAME);
            NSLog(@"%s", event);
            dispatch_semaphore_signal(semaphore);
        });
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        if(argc >= 2) {
            execv(argv[1], (char **)argv+1);
        }
    }
}

Является универсальным (нет необходимости адаптироваться) и может быть построен на командной строке mac (с установленным xcode):

gcc -framework Foundation -o xpc_set_event_stream_handler xpc_set_event_stream_handler.m

Давайте поместим его в /usr/local/bin, как основной исполняемый файл для демона.

cp xpc_set_event_stream_handler /usr/local/bin/

Настройка демона

Файл plist com.spoofmac.plist содержит свойства демона, который будет запускать исполняемый файл на устройстве connect спусковой крючок.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>UserName</key>
    <string>root</string>
    <key>StandardErrorPath</key>
    <string>/tmp/spoofmac.stderr</string>
    <key>StandardOutPath</key>
    <string>/tmp/spoofmac.stdout</string>
    <key>Label</key>
    <string>com.spoofmac.program</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/local/bin/xpc_set_event_stream_handler</string>
        <string>/usr/local/bin/spoofmac.sh</string>
    </array>
    <key>LaunchEvents</key>
    <dict>
        <key>com.apple.iokit.matching</key>
        <dict>
            <key>com.apple.device-attach</key>
            <dict>
                <key>idVendor</key>
                <integer>32902</integer>
                <key>idProduct</key>
                <integer>5427</integer>
                <key>IOProviderClass</key>
                <string>IOPCIDevice</string>
                <key>IOMatchLaunchStream</key>
                <true/>
                <key>IOMatchStream</key>
                <true/>
            </dict>
        </dict>
    </dict>
</dict>
</plist>

Он содержит информацию для идентификации устройства, на котором вы хотите основать свой триггер, например idVendor, idProduct, IOProviderClass. Они могут быть вычислены в приложении System Information на вашем mac.

Скриншот Системной Информации

Преобразуйте шестнадцатеричные идентификаторы в целые числа перед вставкой в файл plist (например, используя int(0x8086) в python).

IOProviderClass должно быть либо IOPCIDevice (Thunderbolt), либо IOUSBDevice (USB).

Другой соответствующей записью в файле plist является расположение xpc_set_event_stream_handler и исполняемого файла.

Другие записи включают расположение стандартных выходных файлов (log) и исполняющего пользователя.

Поскольку подмена MAC требует привилегий root, мы помещаем com.spoofmac.plist в /Library/LaunchDaemons:

cp com.spoofmac.plist /Library/LaunchDaemons/

Не в папку LaunchAgents. Агенты запуска игнорируют аргумент UserName.

Убедитесь, что владельцем файла является root:

sudo chown root:wheel /Library/LaunchDaemons/com.spoofmac.plist

Запуск демона

Активируйте демона:

launchctl load /Library/LaunchDaemons/com.spoofmac.plist

И ты хорошо относишься к идти.

Разгрузка производится с помощью launchctl unload.

Это работает для меня:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        xpc_set_event_stream_handler("com.apple.iokit.matching", NULL, ^(xpc_object_t _Nonnull object) {
            const char *event = xpc_dictionary_get_string(object, XPC_EVENT_KEY_NAME);
            dispatch_semaphore_signal(semaphore);
        });
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        if(argc >= 2) {
            execv(argv[1], (char **)argv+1);
        }
    }
}

Полный исходный код здесь