在 macOS 中以编程方式启用、禁用和启动服务
Enable, disable and start services programmatically in macOS
我正在编写一个带有服务的程序。到目前为止,我所做的是创建一个辅助工具,它可以 运行 为我的流程提升任务并可以通过 XPC 进行通信。
我的程序捆绑了一个服务,我想使用帮助工具来安装和start/stop这个服务,这样我的程序可以在设置中有一个复选框"start service with system"。
我可以成功复制该服务的 plist,但我找不到任何方法以编程方式启用、禁用、启动或停止该服务。我认为调用 system("launchctl load /path/to/service.plist");
的解决方案非常难看。 objective C 中是否有任何机制来完成此任务并获得成功或失败的结果?
Apple 已弃用 C API,用于在 launch.h
中启动、停止和启用已启动的服务。 API 的源代码在他们的开源站点上:https://opensource.apple.com/source/launchd/launchd-442.26.2/liblaunch/
下面是一些要求 launchd 启动 LittleSnitchUIAgent 服务的示例代码:
#include <launch.h>
int main(int argc, const char * argv[]) {
const char *job = "at.obdev.LittleSnitchUIAgent";
launch_data_t resp, msg;
msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
launch_data_dict_insert(
msg, launch_data_new_string(job), LAUNCH_KEY_STARTJOB);
resp = launch_msg(msg);
launch_data_free(msg);
return 0;
}
LittleSnitchUIAgent 并不重要——我是从本地服务列表中随机选择的。我没有检查示例中的错误,以保持直截了当。
如果您还没有,我建议您仔细研究 launchd man pages and the Daemons and Services Programming Guide。 Launchd 可以启动您的进程以响应几乎任何事情:计时器、套接字连接、添加到系统的设备等等。您实际上很少需要管理自己的服务。我无法证实这一点,但我怀疑这就是他们弃用 API.
的原因
看起来有一个 ServiceManagement API/Framework used to load LaunchAgents. Looks SMJobBless 是唯一可用的方法
在 ObjC 中,您始终可以使用 NSTask 来使用 launchctl
command-line.
操作守护进程和代理
示例:
- (BOOL)isDaemonLoaded:(NSString *)daemonLabel {
NSTask *task = [NSTask new];
[task setLaunchPath:@"/bin/launchctl"];
[task setArguments:@[@"list", daemonLabel]];
[task launch];
[task waitUntilExit];
// return code 0 is for loaded daemon, and much info is in stdout, and 113 or other nonzero value when daemon is not loaded
return ([task terminationStatus] == 0);
}
或者,starting/stopping 已启动的全局守护程序:
typedef NS_ENUM(BOOL, DaemonState)
{
DaemonStateOff = NO,
DaemonStateOn = YES
};
- (BOOL)switchGlobalDaemon:(NSString *)daemonLabel
state:(DaemonState)newState {
NSString *command = (newState == OITStateOn) ? @"load" : @"unload";
NSString *daemonPath = [[@"/Library/LaunchDaemons" stringByAppendingPathComponent: daemonLabel] stringByAppendingPathExtension:@"plist"];
if (NO == [[NSFileManager defaultManager] fileExistsAtPath:daemonPath])
return NO;
NSTask *task = [NSTask new];
[task setLaunchPath:@"/bin/launchctl"];
[task setArguments:@[command, daemonPath]];
[task launch];
[task waitUntilExit];
return ([task terminationStatus] == 0);
}
当然任何launchctl命令都可以这样包装。这样做看起来很愚蠢,但由于 Apple 删除了编程 API 并留给我们 launchctl...
无论如何,有很多方法可以“直接”管理守护进程和代理 - 通过在 .plist 中设置规则,然后让 launchd 应用规则。
只是有时会考虑 MacOS 域之外的业务逻辑(比如 - 来自远程服务器的通知,通过 push-notifications,告诉您关闭某些服务,因为客户停止付款...)或更改 运行 服务的配置 - 但在您重新启动它之前不会生效。所以知道这个技巧可能还是有用的。
我正在编写一个带有服务的程序。到目前为止,我所做的是创建一个辅助工具,它可以 运行 为我的流程提升任务并可以通过 XPC 进行通信。
我的程序捆绑了一个服务,我想使用帮助工具来安装和start/stop这个服务,这样我的程序可以在设置中有一个复选框"start service with system"。
我可以成功复制该服务的 plist,但我找不到任何方法以编程方式启用、禁用、启动或停止该服务。我认为调用 system("launchctl load /path/to/service.plist");
的解决方案非常难看。 objective C 中是否有任何机制来完成此任务并获得成功或失败的结果?
Apple 已弃用 C API,用于在 launch.h
中启动、停止和启用已启动的服务。 API 的源代码在他们的开源站点上:https://opensource.apple.com/source/launchd/launchd-442.26.2/liblaunch/
下面是一些要求 launchd 启动 LittleSnitchUIAgent 服务的示例代码:
#include <launch.h>
int main(int argc, const char * argv[]) {
const char *job = "at.obdev.LittleSnitchUIAgent";
launch_data_t resp, msg;
msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
launch_data_dict_insert(
msg, launch_data_new_string(job), LAUNCH_KEY_STARTJOB);
resp = launch_msg(msg);
launch_data_free(msg);
return 0;
}
LittleSnitchUIAgent 并不重要——我是从本地服务列表中随机选择的。我没有检查示例中的错误,以保持直截了当。
如果您还没有,我建议您仔细研究 launchd man pages and the Daemons and Services Programming Guide。 Launchd 可以启动您的进程以响应几乎任何事情:计时器、套接字连接、添加到系统的设备等等。您实际上很少需要管理自己的服务。我无法证实这一点,但我怀疑这就是他们弃用 API.
的原因看起来有一个 ServiceManagement API/Framework used to load LaunchAgents. Looks SMJobBless 是唯一可用的方法
在 ObjC 中,您始终可以使用 NSTask 来使用 launchctl
command-line.
示例:
- (BOOL)isDaemonLoaded:(NSString *)daemonLabel {
NSTask *task = [NSTask new];
[task setLaunchPath:@"/bin/launchctl"];
[task setArguments:@[@"list", daemonLabel]];
[task launch];
[task waitUntilExit];
// return code 0 is for loaded daemon, and much info is in stdout, and 113 or other nonzero value when daemon is not loaded
return ([task terminationStatus] == 0);
}
或者,starting/stopping 已启动的全局守护程序:
typedef NS_ENUM(BOOL, DaemonState)
{
DaemonStateOff = NO,
DaemonStateOn = YES
};
- (BOOL)switchGlobalDaemon:(NSString *)daemonLabel
state:(DaemonState)newState {
NSString *command = (newState == OITStateOn) ? @"load" : @"unload";
NSString *daemonPath = [[@"/Library/LaunchDaemons" stringByAppendingPathComponent: daemonLabel] stringByAppendingPathExtension:@"plist"];
if (NO == [[NSFileManager defaultManager] fileExistsAtPath:daemonPath])
return NO;
NSTask *task = [NSTask new];
[task setLaunchPath:@"/bin/launchctl"];
[task setArguments:@[command, daemonPath]];
[task launch];
[task waitUntilExit];
return ([task terminationStatus] == 0);
}
当然任何launchctl命令都可以这样包装。这样做看起来很愚蠢,但由于 Apple 删除了编程 API 并留给我们 launchctl...
无论如何,有很多方法可以“直接”管理守护进程和代理 - 通过在 .plist 中设置规则,然后让 launchd 应用规则。
只是有时会考虑 MacOS 域之外的业务逻辑(比如 - 来自远程服务器的通知,通过 push-notifications,告诉您关闭某些服务,因为客户停止付款...)或更改 运行 服务的配置 - 但在您重新启动它之前不会生效。所以知道这个技巧可能还是有用的。