【问题标题】:launchd.plist runs every 10 seconds instead of just oncelaunchd.plist 每 10 秒运行一次,而不是只运行一次
【发布时间】:2012-12-08 21:18:50
【问题描述】:

我一直在设置一个launchd.plist XML,它会在每次挂载特定 USB 设备时运行。我按照xpc_events(3) man page 上的说明进行操作,只要安装设备,它就会运行应用程序。

我遇到的问题是,只要设备仍处于安装状态,应用程序就会每 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>

【问题讨论】:

    标签: macos usb launchd


    【解决方案1】:

    我写了一个tutorial on this,其中包含详细说明和示例文件,用于通过将外部设备(usb/thunderbolt)连接到 Mac 计算机来触发任意可执行文件或 shell 脚本,而不会出现重生问题。

    与作者的方法一样,它依赖 Apple 的 IOKit 库进行设备检测和运行所需可执行文件的守护进程。为了在连接设备后不会重复触发守护进程,使用了一个特殊的流处理程序 (xpc_set_event_stream_handler) 来“消耗”com.apple.iokit.matching 事件,正如@ford 的帖子和他的github repo 中所解释的那样。

    本教程特别介绍了如何编译 xpc 流处理程序以及如何将其与守护程序 plist 文件中的可执行文件一起引用,以及将所有相关文件放置在正确权限的位置。

    有关文件,请转至here。为了完整起见,我还在下面粘贴了他们的内容。

    在 Mac 上运行由设备检测触发的 shell 脚本或可执行文件

    这里我使用以太网适配器连接到 Mac 时欺骗 MAC 地址的示例。这可以推广到任意可执行文件和设备。

    将您的 shell 脚本或可执行文件放置到位

    改编shell脚本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 包含将在设备连接触发器上运行可执行文件的守护程序的属性。

    <?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>
    

    它包含用于识别您想要触发的设备的信息,例如idVendoridProductIOProviderClass。这些可以在你的 mac 上的System Information App 中找到。

    在插入 plist 文件之前将十六进制标识符转换为整数(例如在 python 中使用int(0x8086))。

    IOProviderClass 应该是 IOPCIDevice (Thunderbolt) 或 IOUSBDevice (USB)。

    plist 文件中的另一个相关条目是xpc_set_event_stream_handler 的位置和可执行文件。

    其他条目包括标准输出(日志)文件的位置和执行用户。

    由于 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完成卸载。

    【讨论】:

    • 虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会失效。 - From Review
    • 我更改了帖子以包含所有外部链接内容。
    【解决方案2】:

    AIUI 您的应用程序必须调用 xpc_set_event_stream_handler 才能从队列中删除事件。您可能还需要将 &lt;key&gt;KeepAlive&lt;/key&gt;&lt;false/&gt; 添加到 .plist,但我不确定。

    【讨论】:

    • 顺便说一句,如果您未能将 IOMatchLaunchStream 设置为 true,那么 xpc_set_event_stream_handler 不会产生任何影响,并且您的应用程序会在您退出它后一遍又一遍地重新启动。只是一个有用的提示。
    • 根据launchd.plist 手册页,KeepAlive 的默认值为false
    • 发现了这个神奇的实用程序,它将调用 xpc_set_event_stream_handler 从队列中删除事件,然后调用您想要的应用程序。允许我在不编译任何代码的情况下提出解决方案。查看github.com/snosrap/xpc_set_event_stream_handler
    【解决方案3】:

    我正在尝试使用这样的东西:

    #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;
    }
    

    所以一个脚本消耗事件并运行作为参数传递的脚本。

    【讨论】:

    • 我认为在 xpc_set_event_stream_handler 处理程序中调用 execv 可以防止事件被消耗。在处理程序之外调用execv 效果很好。我发布了我的代码below
    【解决方案4】:

    这对我有用:

    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);
            }
        }
    }
    

    full source code here

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-06-09
      • 2022-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多