174068663SAxel Dörfler/*
2a77aa747SAxel Dörfler * Copyright 2015-2018, Axel D��rfler, axeld@pinc-software.de.
374068663SAxel Dörfler * Distributed under the terms of the MIT License.
474068663SAxel Dörfler */
574068663SAxel Dörfler
674068663SAxel Dörfler
774068663SAxel Dörfler/*!	The launch_daemon's companion command line tool. */
874068663SAxel Dörfler
974068663SAxel Dörfler
1074068663SAxel Dörfler#include <LaunchRoster.h>
1174068663SAxel Dörfler#include <StringList.h>
1274068663SAxel Dörfler
13a77aa747SAxel Dörfler#include <errno.h>
1474068663SAxel Dörfler#include <getopt.h>
1574068663SAxel Dörfler#include <stdio.h>
1674068663SAxel Dörfler#include <stdlib.h>
1774068663SAxel Dörfler#include <string.h>
18a77aa747SAxel Dörfler#include <time.h>
1974068663SAxel Dörfler
2074068663SAxel Dörfler
2174068663SAxel Dörflerstatic struct option const kLongOptions[] = {
2274068663SAxel Dörfler	{"verbose", no_argument, 0, 'v'},
2374068663SAxel Dörfler	{"help", no_argument, 0, 'h'},
2474068663SAxel Dörfler	{NULL}
2574068663SAxel Dörfler};
2674068663SAxel Dörfler
27a77aa747SAxel Dörflerstatic struct option const kLogLongOptions[] = {
28a77aa747SAxel Dörfler	{"help", no_argument, 0, 'h'},
29a77aa747SAxel Dörfler	{"raw", no_argument, 0, 'r'},
30a77aa747SAxel Dörfler	{"user", no_argument, 0, 'u'},
31a77aa747SAxel Dörfler	{"system", no_argument, 0, 's'},
32a77aa747SAxel Dörfler	{"event", required_argument, 0, 'e'},
33a77aa747SAxel Dörfler	{"limit", required_argument, 0, 'l'},
34a77aa747SAxel Dörfler	{NULL}
35a77aa747SAxel Dörfler};
36a77aa747SAxel Dörfler
3774068663SAxel Dörflerextern const char *__progname;
3874068663SAxel Dörflerstatic const char *kProgramName = __progname;
3974068663SAxel Dörfler
4074068663SAxel Dörfler
4174068663SAxel Dörflerstatic void
4274068663SAxel Dörflerlist_jobs(bool verbose)
4374068663SAxel Dörfler{
4474068663SAxel Dörfler	BLaunchRoster roster;
4574068663SAxel Dörfler	BStringList jobs;
4674068663SAxel Dörfler	status_t status = roster.GetJobs(NULL, jobs);
4774068663SAxel Dörfler	if (status != B_OK) {
4874068663SAxel Dörfler		fprintf(stderr, "%s: Could not get job listing: %s\n", kProgramName,
4974068663SAxel Dörfler			strerror(status));
5074068663SAxel Dörfler		exit(EXIT_FAILURE);
5174068663SAxel Dörfler	}
5274068663SAxel Dörfler
5374068663SAxel Dörfler	for (int32 i = 0; i < jobs.CountStrings(); i++)
5474068663SAxel Dörfler		puts(jobs.StringAt(i).String());
5574068663SAxel Dörfler}
5674068663SAxel Dörfler
5774068663SAxel Dörfler
5874068663SAxel Dörflerstatic void
5974068663SAxel Dörflerlist_targets(bool verbose)
6074068663SAxel Dörfler{
6174068663SAxel Dörfler	BLaunchRoster roster;
6274068663SAxel Dörfler	BStringList targets;
6374068663SAxel Dörfler	status_t status = roster.GetTargets(targets);
6474068663SAxel Dörfler	if (status != B_OK) {
6574068663SAxel Dörfler		fprintf(stderr, "%s: Could not get target listing: %s\n", kProgramName,
6674068663SAxel Dörfler			strerror(status));
6774068663SAxel Dörfler		exit(EXIT_FAILURE);
6874068663SAxel Dörfler	}
6974068663SAxel Dörfler
7074068663SAxel Dörfler	for (int32 i = 0; i < targets.CountStrings(); i++)
7174068663SAxel Dörfler		puts(targets.StringAt(i).String());
7274068663SAxel Dörfler}
7374068663SAxel Dörfler
7474068663SAxel Dörfler
75a77aa747SAxel Dörflerstatic void
76a77aa747SAxel Dörflerprint_log(const BMessage& log)
77a77aa747SAxel Dörfler{
78a77aa747SAxel Dörfler	time_t now = time(NULL);
79a77aa747SAxel Dörfler	bigtime_t runtime = system_time();
80a77aa747SAxel Dörfler
81a77aa747SAxel Dörfler	for (int32 index = 0;; index++) {
82a77aa747SAxel Dörfler		BMessage item;
83a77aa747SAxel Dörfler		if (log.FindMessage("item", index, &item) != B_OK)
84a77aa747SAxel Dörfler			break;
85a77aa747SAxel Dörfler
86a77aa747SAxel Dörfler		uint64 when;
87a77aa747SAxel Dörfler		const char* message;
88a77aa747SAxel Dörfler		if (item.FindUInt64("when", &when) != B_OK
89a77aa747SAxel Dörfler			|| item.FindString("message", &message) != B_OK)
90a77aa747SAxel Dörfler			break;
91a77aa747SAxel Dörfler
92a77aa747SAxel Dörfler		time_t at = now - (runtime - when) / 1000000l;
93a77aa747SAxel Dörfler		struct tm tm;
94a77aa747SAxel Dörfler		localtime_r(&at, &tm);
95a77aa747SAxel Dörfler		char label[256];
96a77aa747SAxel Dörfler		strftime(label, sizeof(label), "%F %X", &tm);
97a77aa747SAxel Dörfler		printf("%s %s\n", label, message);
98a77aa747SAxel Dörfler	}
99a77aa747SAxel Dörfler}
100a77aa747SAxel Dörfler
101a77aa747SAxel Dörfler
102a77aa747SAxel Dörflerstatic void
103a77aa747SAxel Dörflerlog_usage(int status)
104a77aa747SAxel Dörfler{
105a77aa747SAxel Dörfler	fprintf(stderr, "Usage: %s log [-rusel] [<job-name>]\n"
106a77aa747SAxel Dörfler		"Where the following options are allowed:\n"
107a77aa747SAxel Dörfler		"  -u --user       List only user log entries\n"
108a77aa747SAxel Dörfler		"  -s --system     List only system log entries\n"
109a77aa747SAxel Dörfler		"  -e --event      Filter by event name (partial names accepted)\n"
110a77aa747SAxel Dörfler		"  -l --limit <n>  Limit output to <n> events\n"
111a77aa747SAxel Dörfler		"<job-name>, if given, filters the jobs by name.\n",
112a77aa747SAxel Dörfler		kProgramName);
113a77aa747SAxel Dörfler
114a77aa747SAxel Dörfler	exit(status);
115a77aa747SAxel Dörfler}
116a77aa747SAxel Dörfler
117a77aa747SAxel Dörfler
118a77aa747SAxel Dörflerstatic void
119a77aa747SAxel Dörflerget_log(int argCount, char** args)
120a77aa747SAxel Dörfler{
121a77aa747SAxel Dörfler	bool raw = false;
122a77aa747SAxel Dörfler	bool userOnly = false;
123a77aa747SAxel Dörfler	bool systemOnly = false;
124a77aa747SAxel Dörfler	int32 limit = 0;
125a77aa747SAxel Dörfler	const char* event = NULL;
126a77aa747SAxel Dörfler	const char* job = NULL;
127a77aa747SAxel Dörfler
128a77aa747SAxel Dörfler	optind = 0;
129a77aa747SAxel Dörfler	int c;
130a77aa747SAxel Dörfler	while ((c = getopt_long(argCount, args, "hruse:l:", kLogLongOptions, NULL))
131a77aa747SAxel Dörfler			!= -1) {
132a77aa747SAxel Dörfler		switch (c) {
133a77aa747SAxel Dörfler			case 0:
134a77aa747SAxel Dörfler				break;
135a77aa747SAxel Dörfler			case 'h':
136a77aa747SAxel Dörfler				log_usage(0);
137a77aa747SAxel Dörfler				break;
138a77aa747SAxel Dörfler			case 'r':
139a77aa747SAxel Dörfler				raw = true;
140a77aa747SAxel Dörfler				break;
141a77aa747SAxel Dörfler			case 'u':
142a77aa747SAxel Dörfler				userOnly = true;
143a77aa747SAxel Dörfler				break;
144a77aa747SAxel Dörfler			case 's':
145a77aa747SAxel Dörfler				systemOnly = true;
146a77aa747SAxel Dörfler				break;
147a77aa747SAxel Dörfler			case 'e':
148a77aa747SAxel Dörfler				event = optarg;
149a77aa747SAxel Dörfler				break;
150a77aa747SAxel Dörfler			case 'l':
151a77aa747SAxel Dörfler				limit = strtol(optarg, NULL, 0);
152a77aa747SAxel Dörfler				break;
153a77aa747SAxel Dörfler		}
154a77aa747SAxel Dörfler	}
155a77aa747SAxel Dörfler
156a77aa747SAxel Dörfler	if (argCount - optind >= 1)
157a77aa747SAxel Dörfler		job = args[optind];
158a77aa747SAxel Dörfler
159a77aa747SAxel Dörfler	BLaunchRoster roster;
160a77aa747SAxel Dörfler	BMessage filter;
161a77aa747SAxel Dörfler	if (userOnly)
162a77aa747SAxel Dörfler		filter.AddBool("userOnly", true);
163a77aa747SAxel Dörfler	if (systemOnly)
164a77aa747SAxel Dörfler		filter.AddBool("systemOnly", true);
165a77aa747SAxel Dörfler	if (event != NULL)
166a77aa747SAxel Dörfler		filter.AddString("event", event);
167a77aa747SAxel Dörfler	if (job != NULL)
168a77aa747SAxel Dörfler		filter.AddString("job", job);
169a77aa747SAxel Dörfler	if (limit != 0)
170a77aa747SAxel Dörfler		filter.AddInt32("limit", limit);
171a77aa747SAxel Dörfler
172a77aa747SAxel Dörfler	BMessage info;
173a77aa747SAxel Dörfler	status_t status = roster.GetLog(filter, info);
174a77aa747SAxel Dörfler	if (status != B_OK) {
175a77aa747SAxel Dörfler		fprintf(stderr, "%s: Could not get log: %s\n", kProgramName,
176a77aa747SAxel Dörfler			strerror(status));
177a77aa747SAxel Dörfler		exit(EXIT_FAILURE);
178a77aa747SAxel Dörfler	}
179a77aa747SAxel Dörfler
180a77aa747SAxel Dörfler	if (raw) {
181a77aa747SAxel Dörfler		info.PrintToStream();
182a77aa747SAxel Dörfler		return;
183a77aa747SAxel Dörfler	}
184a77aa747SAxel Dörfler
185a77aa747SAxel Dörfler	print_log(info);
186a77aa747SAxel Dörfler
187a77aa747SAxel Dörfler	BMessage user;
188a77aa747SAxel Dörfler	if (info.FindMessage("user", &user) == B_OK) {
189a77aa747SAxel Dörfler		if (user.HasMessage("item"))
190a77aa747SAxel Dörfler			puts("User log:");
191a77aa747SAxel Dörfler		print_log(user);
192a77aa747SAxel Dörfler	}
193a77aa747SAxel Dörfler}
194a77aa747SAxel Dörfler
195a77aa747SAxel Dörfler
19674068663SAxel Dörflerstatic void
19774068663SAxel Dörflerget_info(const char* name)
19874068663SAxel Dörfler{
19974068663SAxel Dörfler	BLaunchRoster roster;
20074068663SAxel Dörfler	BMessage info;
20174068663SAxel Dörfler	status_t targetStatus = roster.GetTargetInfo(name, info);
20274068663SAxel Dörfler	if (targetStatus == B_OK) {
20374068663SAxel Dörfler		printf("Target: %s\n", name);
20474068663SAxel Dörfler		info.PrintToStream();
20574068663SAxel Dörfler	}
20674068663SAxel Dörfler
20774068663SAxel Dörfler	info.MakeEmpty();
20874068663SAxel Dörfler	status_t jobStatus = roster.GetJobInfo(name, info);
20974068663SAxel Dörfler	if (jobStatus == B_OK) {
21074068663SAxel Dörfler		printf("Job: %s\n", name);
21174068663SAxel Dörfler		info.PrintToStream();
21274068663SAxel Dörfler	}
21374068663SAxel Dörfler
21474068663SAxel Dörfler	if (jobStatus != B_OK && targetStatus != B_OK) {
21574068663SAxel Dörfler		fprintf(stderr, "%s: Could not get target or job info for \"%s\": "
21674068663SAxel Dörfler			"%s\n", kProgramName, name, strerror(jobStatus));
21774068663SAxel Dörfler		exit(EXIT_FAILURE);
21874068663SAxel Dörfler	}
21974068663SAxel Dörfler}
22074068663SAxel Dörfler
22174068663SAxel Dörfler
22274068663SAxel Dörflerstatic void
22374068663SAxel Dörflerstart_job(const char* name)
22474068663SAxel Dörfler{
22574068663SAxel Dörfler	BLaunchRoster roster;
22674068663SAxel Dörfler	status_t status = roster.Start(name);
227bf4c2f93SAxel Dörfler	if (status == B_NAME_NOT_FOUND)
228bf4c2f93SAxel Dörfler		status = roster.Target(name);
229bf4c2f93SAxel Dörfler
23074068663SAxel Dörfler	if (status != B_OK) {
23174068663SAxel Dörfler		fprintf(stderr, "%s: Starting job \"%s\" failed: %s\n", kProgramName,
23274068663SAxel Dörfler			name, strerror(status));
23374068663SAxel Dörfler		exit(EXIT_FAILURE);
23474068663SAxel Dörfler	}
23574068663SAxel Dörfler}
23674068663SAxel Dörfler
23774068663SAxel Dörfler
23874068663SAxel Dörflerstatic void
23974068663SAxel Dörflerstop_job(const char* name)
24074068663SAxel Dörfler{
24174068663SAxel Dörfler	BLaunchRoster roster;
24274068663SAxel Dörfler	status_t status = roster.Stop(name);
243bf4c2f93SAxel Dörfler	if (status == B_NAME_NOT_FOUND)
244bf4c2f93SAxel Dörfler		status = roster.StopTarget(name);
245bf4c2f93SAxel Dörfler
24674068663SAxel Dörfler	if (status != B_OK) {
24774068663SAxel Dörfler		fprintf(stderr, "%s: Stopping job \"%s\" failed: %s\n", kProgramName,
24874068663SAxel Dörfler			name, strerror(status));
24974068663SAxel Dörfler		exit(EXIT_FAILURE);
25074068663SAxel Dörfler	}
25174068663SAxel Dörfler}
25274068663SAxel Dörfler
25374068663SAxel Dörfler
254bf4c2f93SAxel Dörflerstatic void
255bf4c2f93SAxel Dörflerrestart_job(const char* name)
256bf4c2f93SAxel Dörfler{
257bf4c2f93SAxel Dörfler	stop_job(name);
258bf4c2f93SAxel Dörfler	start_job(name);
259bf4c2f93SAxel Dörfler}
260bf4c2f93SAxel Dörfler
261bf4c2f93SAxel Dörfler
26290fd6af0SAxel Dörflerstatic void
26390fd6af0SAxel Dörflerenable_job(const char* name, bool enable)
26490fd6af0SAxel Dörfler{
26590fd6af0SAxel Dörfler	BLaunchRoster roster;
26690fd6af0SAxel Dörfler	status_t status = roster.SetEnabled(name, enable);
26790fd6af0SAxel Dörfler	if (status != B_OK) {
26890fd6af0SAxel Dörfler		fprintf(stderr, "%s: %s job \"%s\" failed: %s\n", kProgramName,
26990fd6af0SAxel Dörfler			enable ? "Enabling" : "Disabling", name, strerror(status));
27090fd6af0SAxel Dörfler		exit(EXIT_FAILURE);
27190fd6af0SAxel Dörfler	}
27290fd6af0SAxel Dörfler}
27390fd6af0SAxel Dörfler
27490fd6af0SAxel Dörfler
27574068663SAxel Dörflerstatic void
27674068663SAxel Dörflerusage(int status)
27774068663SAxel Dörfler{
27874068663SAxel Dörfler	fprintf(stderr, "Usage: %s <command>\n"
27974068663SAxel Dörfler		"Where <command> is one of:\n"
28074068663SAxel Dörfler		"  list - Lists all jobs (the default command)\n"
28174068663SAxel Dörfler		"  list-targets - Lists all targets\n"
28274068663SAxel Dörfler		"The following <command>s have a <name> argument:\n"
283bf4c2f93SAxel Dörfler		"  start - Starts a job/target\n"
284bf4c2f93SAxel Dörfler		"  stop - Stops a running job/target\n"
285bf4c2f93SAxel Dörfler		"  restart - Restarts a running job/target\n"
28674068663SAxel Dörfler		"  info - Shows info for a job/target\n",
28774068663SAxel Dörfler		kProgramName);
28874068663SAxel Dörfler
28974068663SAxel Dörfler	exit(status);
29074068663SAxel Dörfler}
29174068663SAxel Dörfler
29274068663SAxel Dörfler
29374068663SAxel Dörflerint
29474068663SAxel Dörflermain(int argc, char** argv)
29574068663SAxel Dörfler{
29674068663SAxel Dörfler	const char* command = "list";
29774068663SAxel Dörfler	bool verbose = false;
29874068663SAxel Dörfler
29974068663SAxel Dörfler	int c;
300a77aa747SAxel Dörfler	while ((c = getopt_long(argc, argv, "+hv", kLongOptions, NULL)) != -1) {
30174068663SAxel Dörfler		switch (c) {
30274068663SAxel Dörfler			case 0:
30374068663SAxel Dörfler				break;
30474068663SAxel Dörfler			case 'h':
30574068663SAxel Dörfler				usage(0);
30674068663SAxel Dörfler				break;
30774068663SAxel Dörfler			case 'v':
30874068663SAxel Dörfler				verbose = true;
30974068663SAxel Dörfler				break;
31074068663SAxel Dörfler			default:
31174068663SAxel Dörfler				usage(1);
31274068663SAxel Dörfler				break;
31374068663SAxel Dörfler		}
31474068663SAxel Dörfler	}
31574068663SAxel Dörfler
31674068663SAxel Dörfler	if (argc - optind >= 1)
31774068663SAxel Dörfler		command = argv[optind];
31874068663SAxel Dörfler
31974068663SAxel Dörfler	if (strcmp(command, "list") == 0) {
32074068663SAxel Dörfler		list_jobs(verbose);
32174068663SAxel Dörfler	} else if (strcmp(command, "list-targets") == 0) {
32274068663SAxel Dörfler		list_targets(verbose);
323a77aa747SAxel Dörfler	} else if (strcmp(command, "log") == 0) {
324a77aa747SAxel Dörfler		get_log(argc - optind, &argv[optind]);
32590fd6af0SAxel Dörfler	} else if (argc == optind + 1) {
32690fd6af0SAxel Dörfler		// For convenience (the "info" command can be omitted)
32790fd6af0SAxel Dörfler		get_info(command);
32874068663SAxel Dörfler	} else {
32974068663SAxel Dörfler		// All commands that need a name following
33074068663SAxel Dörfler
33174068663SAxel Dörfler		const char* name = argv[argc - 1];
33274068663SAxel Dörfler
33374068663SAxel Dörfler		if (strcmp(command, "info") == 0) {
33474068663SAxel Dörfler			get_info(name);
33574068663SAxel Dörfler		} else if (strcmp(command, "start") == 0) {
33674068663SAxel Dörfler			start_job(name);
33774068663SAxel Dörfler		} else if (strcmp(command, "stop") == 0) {
33874068663SAxel Dörfler			stop_job(name);
339bf4c2f93SAxel Dörfler		} else if (strcmp(command, "restart") == 0) {
340bf4c2f93SAxel Dörfler			restart_job(name);
34190fd6af0SAxel Dörfler		} else if (strcmp(command, "enable") == 0) {
34290fd6af0SAxel Dörfler			enable_job(name, true);
34390fd6af0SAxel Dörfler		} else if (strcmp(command, "disable") == 0) {
34490fd6af0SAxel Dörfler			enable_job(name, false);
34574068663SAxel Dörfler		} else {
34674068663SAxel Dörfler			fprintf(stderr, "%s: Unknown command \"%s\".\n", kProgramName,
34774068663SAxel Dörfler				command);
34874068663SAxel Dörfler		}
34974068663SAxel Dörfler	}
35074068663SAxel Dörfler	return 0;
35174068663SAxel Dörfler}
352