1/*
2 * Copyright 2015-2018, Axel D��rfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7/*!	The launch_daemon's companion command line tool. */
8
9
10#include <LaunchRoster.h>
11#include <StringList.h>
12
13#include <errno.h>
14#include <getopt.h>
15#include <stdio.h>
16#include <stdlib.h>
17#include <string.h>
18#include <time.h>
19
20
21static struct option const kLongOptions[] = {
22	{"verbose", no_argument, 0, 'v'},
23	{"help", no_argument, 0, 'h'},
24	{NULL}
25};
26
27static struct option const kLogLongOptions[] = {
28	{"help", no_argument, 0, 'h'},
29	{"raw", no_argument, 0, 'r'},
30	{"user", no_argument, 0, 'u'},
31	{"system", no_argument, 0, 's'},
32	{"event", required_argument, 0, 'e'},
33	{"limit", required_argument, 0, 'l'},
34	{NULL}
35};
36
37extern const char *__progname;
38static const char *kProgramName = __progname;
39
40
41static void
42list_jobs(bool verbose)
43{
44	BLaunchRoster roster;
45	BStringList jobs;
46	status_t status = roster.GetJobs(NULL, jobs);
47	if (status != B_OK) {
48		fprintf(stderr, "%s: Could not get job listing: %s\n", kProgramName,
49			strerror(status));
50		exit(EXIT_FAILURE);
51	}
52
53	for (int32 i = 0; i < jobs.CountStrings(); i++)
54		puts(jobs.StringAt(i).String());
55}
56
57
58static void
59list_targets(bool verbose)
60{
61	BLaunchRoster roster;
62	BStringList targets;
63	status_t status = roster.GetTargets(targets);
64	if (status != B_OK) {
65		fprintf(stderr, "%s: Could not get target listing: %s\n", kProgramName,
66			strerror(status));
67		exit(EXIT_FAILURE);
68	}
69
70	for (int32 i = 0; i < targets.CountStrings(); i++)
71		puts(targets.StringAt(i).String());
72}
73
74
75static void
76print_log(const BMessage& log)
77{
78	time_t now = time(NULL);
79	bigtime_t runtime = system_time();
80
81	for (int32 index = 0;; index++) {
82		BMessage item;
83		if (log.FindMessage("item", index, &item) != B_OK)
84			break;
85
86		uint64 when;
87		const char* message;
88		if (item.FindUInt64("when", &when) != B_OK
89			|| item.FindString("message", &message) != B_OK)
90			break;
91
92		time_t at = now - (runtime - when) / 1000000l;
93		struct tm tm;
94		localtime_r(&at, &tm);
95		char label[256];
96		strftime(label, sizeof(label), "%F %X", &tm);
97		printf("%s %s\n", label, message);
98	}
99}
100
101
102static void
103log_usage(int status)
104{
105	fprintf(stderr, "Usage: %s log [-rusel] [<job-name>]\n"
106		"Where the following options are allowed:\n"
107		"  -u --user       List only user log entries\n"
108		"  -s --system     List only system log entries\n"
109		"  -e --event      Filter by event name (partial names accepted)\n"
110		"  -l --limit <n>  Limit output to <n> events\n"
111		"<job-name>, if given, filters the jobs by name.\n",
112		kProgramName);
113
114	exit(status);
115}
116
117
118static void
119get_log(int argCount, char** args)
120{
121	bool raw = false;
122	bool userOnly = false;
123	bool systemOnly = false;
124	int32 limit = 0;
125	const char* event = NULL;
126	const char* job = NULL;
127
128	optind = 0;
129	int c;
130	while ((c = getopt_long(argCount, args, "hruse:l:", kLogLongOptions, NULL))
131			!= -1) {
132		switch (c) {
133			case 0:
134				break;
135			case 'h':
136				log_usage(0);
137				break;
138			case 'r':
139				raw = true;
140				break;
141			case 'u':
142				userOnly = true;
143				break;
144			case 's':
145				systemOnly = true;
146				break;
147			case 'e':
148				event = optarg;
149				break;
150			case 'l':
151				limit = strtol(optarg, NULL, 0);
152				break;
153		}
154	}
155
156	if (argCount - optind >= 1)
157		job = args[optind];
158
159	BLaunchRoster roster;
160	BMessage filter;
161	if (userOnly)
162		filter.AddBool("userOnly", true);
163	if (systemOnly)
164		filter.AddBool("systemOnly", true);
165	if (event != NULL)
166		filter.AddString("event", event);
167	if (job != NULL)
168		filter.AddString("job", job);
169	if (limit != 0)
170		filter.AddInt32("limit", limit);
171
172	BMessage info;
173	status_t status = roster.GetLog(filter, info);
174	if (status != B_OK) {
175		fprintf(stderr, "%s: Could not get log: %s\n", kProgramName,
176			strerror(status));
177		exit(EXIT_FAILURE);
178	}
179
180	if (raw) {
181		info.PrintToStream();
182		return;
183	}
184
185	print_log(info);
186
187	BMessage user;
188	if (info.FindMessage("user", &user) == B_OK) {
189		if (user.HasMessage("item"))
190			puts("User log:");
191		print_log(user);
192	}
193}
194
195
196static void
197get_info(const char* name)
198{
199	BLaunchRoster roster;
200	BMessage info;
201	status_t targetStatus = roster.GetTargetInfo(name, info);
202	if (targetStatus == B_OK) {
203		printf("Target: %s\n", name);
204		info.PrintToStream();
205	}
206
207	info.MakeEmpty();
208	status_t jobStatus = roster.GetJobInfo(name, info);
209	if (jobStatus == B_OK) {
210		printf("Job: %s\n", name);
211		info.PrintToStream();
212	}
213
214	if (jobStatus != B_OK && targetStatus != B_OK) {
215		fprintf(stderr, "%s: Could not get target or job info for \"%s\": "
216			"%s\n", kProgramName, name, strerror(jobStatus));
217		exit(EXIT_FAILURE);
218	}
219}
220
221
222static void
223start_job(const char* name)
224{
225	BLaunchRoster roster;
226	status_t status = roster.Start(name);
227	if (status == B_NAME_NOT_FOUND)
228		status = roster.Target(name);
229
230	if (status != B_OK) {
231		fprintf(stderr, "%s: Starting job \"%s\" failed: %s\n", kProgramName,
232			name, strerror(status));
233		exit(EXIT_FAILURE);
234	}
235}
236
237
238static void
239stop_job(const char* name)
240{
241	BLaunchRoster roster;
242	status_t status = roster.Stop(name);
243	if (status == B_NAME_NOT_FOUND)
244		status = roster.StopTarget(name);
245
246	if (status != B_OK) {
247		fprintf(stderr, "%s: Stopping job \"%s\" failed: %s\n", kProgramName,
248			name, strerror(status));
249		exit(EXIT_FAILURE);
250	}
251}
252
253
254static void
255restart_job(const char* name)
256{
257	stop_job(name);
258	start_job(name);
259}
260
261
262static void
263enable_job(const char* name, bool enable)
264{
265	BLaunchRoster roster;
266	status_t status = roster.SetEnabled(name, enable);
267	if (status != B_OK) {
268		fprintf(stderr, "%s: %s job \"%s\" failed: %s\n", kProgramName,
269			enable ? "Enabling" : "Disabling", name, strerror(status));
270		exit(EXIT_FAILURE);
271	}
272}
273
274
275static void
276usage(int status)
277{
278	fprintf(stderr, "Usage: %s <command>\n"
279		"Where <command> is one of:\n"
280		"  list - Lists all jobs (the default command)\n"
281		"  list-targets - Lists all targets\n"
282		"The following <command>s have a <name> argument:\n"
283		"  start - Starts a job/target\n"
284		"  stop - Stops a running job/target\n"
285		"  restart - Restarts a running job/target\n"
286		"  info - Shows info for a job/target\n",
287		kProgramName);
288
289	exit(status);
290}
291
292
293int
294main(int argc, char** argv)
295{
296	const char* command = "list";
297	bool verbose = false;
298
299	int c;
300	while ((c = getopt_long(argc, argv, "+hv", kLongOptions, NULL)) != -1) {
301		switch (c) {
302			case 0:
303				break;
304			case 'h':
305				usage(0);
306				break;
307			case 'v':
308				verbose = true;
309				break;
310			default:
311				usage(1);
312				break;
313		}
314	}
315
316	if (argc - optind >= 1)
317		command = argv[optind];
318
319	if (strcmp(command, "list") == 0) {
320		list_jobs(verbose);
321	} else if (strcmp(command, "list-targets") == 0) {
322		list_targets(verbose);
323	} else if (strcmp(command, "log") == 0) {
324		get_log(argc - optind, &argv[optind]);
325	} else if (argc == optind + 1) {
326		// For convenience (the "info" command can be omitted)
327		get_info(command);
328	} else {
329		// All commands that need a name following
330
331		const char* name = argv[argc - 1];
332
333		if (strcmp(command, "info") == 0) {
334			get_info(name);
335		} else if (strcmp(command, "start") == 0) {
336			start_job(name);
337		} else if (strcmp(command, "stop") == 0) {
338			stop_job(name);
339		} else if (strcmp(command, "restart") == 0) {
340			restart_job(name);
341		} else if (strcmp(command, "enable") == 0) {
342			enable_job(name, true);
343		} else if (strcmp(command, "disable") == 0) {
344			enable_job(name, false);
345		} else {
346			fprintf(stderr, "%s: Unknown command \"%s\".\n", kProgramName,
347				command);
348		}
349	}
350	return 0;
351}
352