xref: /haiku/src/bin/launch_roster.cpp (revision fc7456e9b1ec38c941134ed6d01c438cf289381e)
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 #include <TextTable.h>
13 
14 #include <errno.h>
15 #include <getopt.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <time.h>
20 
21 
22 #define COLOR_RED	"\033[31;1m"
23 #define COLOR_GREEN "\033[32;1m"
24 #define COLOR_BLUE  "\033[34;1m"
25 #define COLOR_BOLD	"\033[;1m"
26 #define COLOR_RESET "\033[0m"
27 
28 
29 static struct option const kLongOptions[] = {
30 	{"verbose", no_argument, 0, 'v'},
31 	{"help", no_argument, 0, 'h'},
32 	{NULL}
33 };
34 
35 static struct option const kLogLongOptions[] = {
36 	{"help", no_argument, 0, 'h'},
37 	{"raw", no_argument, 0, 'r'},
38 	{"user", no_argument, 0, 'u'},
39 	{"system", no_argument, 0, 's'},
40 	{"event", required_argument, 0, 'e'},
41 	{"limit", required_argument, 0, 'l'},
42 	{NULL}
43 };
44 
45 extern const char *__progname;
46 static const char *kProgramName = __progname;
47 
48 
49 static void
50 print_log(const BMessage& log)
51 {
52 	time_t now = time(NULL);
53 	bigtime_t runtime = system_time();
54 
55 	for (int32 index = 0;; index++) {
56 		BMessage item;
57 		if (log.FindMessage("item", index, &item) != B_OK)
58 			break;
59 
60 		uint64 when;
61 		const char* message;
62 		if (item.FindUInt64("when", &when) != B_OK
63 			|| item.FindString("message", &message) != B_OK)
64 			break;
65 
66 		time_t at = now - (runtime - when) / 1000000l;
67 		struct tm tm;
68 		localtime_r(&at, &tm);
69 		char label[256];
70 		strftime(label, sizeof(label), "%F %X", &tm);
71 		printf("%s %s\n", label, message);
72 	}
73 }
74 
75 
76 static void
77 log_usage(int status)
78 {
79 	fprintf(stderr, "Usage: %s log [-rusel] [<job-name>]\n"
80 		"Where the following options are allowed:\n"
81 		"  -u --user       List only user log entries\n"
82 		"  -s --system     List only system log entries\n"
83 		"  -e --event      Filter by event name (partial names accepted)\n"
84 		"  -l --limit <n>  Limit output to <n> events\n"
85 		"<job-name>, if given, filters the jobs by name.\n",
86 		kProgramName);
87 
88 	exit(status);
89 }
90 
91 
92 static void
93 get_log(int argCount, char** args)
94 {
95 	bool raw = false;
96 	bool userOnly = false;
97 	bool systemOnly = false;
98 	int32 limit = 0;
99 	const char* event = NULL;
100 	const char* job = NULL;
101 
102 	optind = 0;
103 	int c;
104 	while ((c = getopt_long(argCount, args, "hruse:l:", kLogLongOptions, NULL))
105 			!= -1) {
106 		switch (c) {
107 			case 0:
108 				break;
109 			case 'h':
110 				log_usage(0);
111 				break;
112 			case 'r':
113 				raw = true;
114 				break;
115 			case 'u':
116 				userOnly = true;
117 				break;
118 			case 's':
119 				systemOnly = true;
120 				break;
121 			case 'e':
122 				event = optarg;
123 				break;
124 			case 'l':
125 				limit = strtol(optarg, NULL, 0);
126 				break;
127 		}
128 	}
129 
130 	if (argCount - optind >= 1)
131 		job = args[optind];
132 
133 	BLaunchRoster roster;
134 	BMessage filter;
135 	if (userOnly)
136 		filter.AddBool("userOnly", true);
137 	if (systemOnly)
138 		filter.AddBool("systemOnly", true);
139 	if (event != NULL)
140 		filter.AddString("event", event);
141 	if (job != NULL)
142 		filter.AddString("job", job);
143 	if (limit != 0)
144 		filter.AddInt32("limit", limit);
145 
146 	BMessage info;
147 	status_t status = roster.GetLog(filter, info);
148 	if (status != B_OK) {
149 		fprintf(stderr, "%s: Could not get log: %s\n", kProgramName,
150 			strerror(status));
151 		exit(EXIT_FAILURE);
152 	}
153 
154 	if (raw) {
155 		info.PrintToStream();
156 		return;
157 	}
158 
159 	print_log(info);
160 
161 	BMessage user;
162 	if (info.FindMessage("user", &user) == B_OK) {
163 		if (user.HasMessage("item"))
164 			puts("User log:");
165 		print_log(user);
166 	}
167 }
168 
169 
170 static void
171 print_summary(TextTable& table, BMessage info, bool target)
172 {
173 	status_t result = B_OK;
174 	const char* name;
175 
176 	result = info.FindString("name", &name);
177 	if (result != B_OK) {
178 		fprintf(stderr, "%s: Could not find target or job name.\n", kProgramName);
179 		exit(EXIT_FAILURE);
180 	}
181 
182 	const int row = table.CountRows();
183 	table.SetTextAt(row, 0, BString(COLOR_BOLD).Append(name).Append(COLOR_RESET));
184 
185 	if (target)
186 		return;
187 
188 	bool service = false;
189 	result = info.FindBool("service", &service);
190 	if (result == B_OK)
191 		table.SetTextAt(row, 1, service ? "service" : "job");
192 
193 	bool running = false, launched = false;
194 	result = info.FindBool("running", &running);
195 	if (result == B_OK) {
196 		result = info.FindBool("launched", &launched);
197 		table.SetTextAt(row, 2,
198 			BString(running ? COLOR_GREEN : launched ? COLOR_BLUE : COLOR_RED)
199 				.Append(running ? "running" : launched ? "idle" : "stopped")
200 				.Append(COLOR_RESET));
201 	}
202 
203 	bool enabled = false;
204 	result = info.FindBool("enabled", &enabled);
205 	if (result == B_OK)
206 		table.SetTextAt(row, 3, enabled ? "yes" : "no");
207 
208 	return;
209 }
210 
211 
212 static status_t
213 get_info(TextTable& table, const char* name, bool verbose)
214 {
215 	BLaunchRoster roster;
216 	BMessage info;
217 
218 	// Is it a target?
219 	status_t status = roster.GetTargetInfo(name, info);
220 	if (status == B_OK) {
221 		print_summary(table, info, true);
222 
223 		if (table.CountColumns() < 1)
224 			table.AddColumn("Name", B_ALIGN_RIGHT);
225 	} else {
226 		// No. Is it a Job or Service?
227 		info.MakeEmpty();
228 		status = roster.GetJobInfo(name, info);
229 		if (status == B_OK)
230 			print_summary(table, info, false);
231 
232 		if (table.CountColumns() < 4) {
233 			if (table.CountColumns() < 1)
234 				table.AddColumn("Name", B_ALIGN_RIGHT);
235 			table.AddColumn("Type");
236 			table.AddColumn("State");
237 			table.AddColumn("Enabled");
238 		}
239 	}
240 
241 	if (status != B_OK) {
242 		fprintf(stderr, "%s: Could not get target or job info for \"%s\": "
243 			"%s\n", kProgramName, name, strerror(status));
244 		return status;
245 	}
246 
247 	if (verbose) {
248 		// verbose is singular, so print the table now.
249 		table.Print(INT32_MAX);
250 
251 		printf("\nDetails: ");
252 		info.PrintToStream();
253 	}
254 
255 	return B_OK;
256 }
257 
258 
259 static void
260 list_targets(bool verbose)
261 {
262 	BLaunchRoster roster;
263 	BStringList targets;
264 	status_t status = roster.GetTargets(targets);
265 	if (status != B_OK) {
266 		fprintf(stderr, "%s: Could not get target listing: %s\n", kProgramName,
267 			strerror(status));
268 		exit(EXIT_FAILURE);
269 	}
270 
271 	TextTable table;
272 	for (int32 i = 0; i < targets.CountStrings(); i++)
273 		get_info(table, targets.StringAt(i).String(), verbose);
274 
275 	table.Print(INT32_MAX);
276 }
277 
278 
279 static void
280 list_jobs(bool verbose)
281 {
282 	BLaunchRoster roster;
283 	BStringList jobs;
284 	status_t status = roster.GetJobs(NULL, jobs);
285 	if (status != B_OK) {
286 		fprintf(stderr, "%s: Could not get job listing: %s\n", kProgramName,
287 			strerror(status));
288 		exit(EXIT_FAILURE);
289 	}
290 
291 	TextTable table;
292 	for (int32 i = 0; i < jobs.CountStrings(); i++)
293 		get_info(table, jobs.StringAt(i).String(), verbose);
294 
295 	table.Print(INT32_MAX);
296 }
297 
298 
299 static void
300 start_job(const char* name)
301 {
302 	BLaunchRoster roster;
303 	status_t status = roster.Start(name);
304 	if (status == B_NAME_NOT_FOUND)
305 		status = roster.Target(name);
306 
307 	if (status != B_OK) {
308 		fprintf(stderr, "%s: Starting job \"%s\" failed: %s\n", kProgramName,
309 			name, strerror(status));
310 		exit(EXIT_FAILURE);
311 	}
312 }
313 
314 
315 static void
316 stop_job(const char* name)
317 {
318 	BLaunchRoster roster;
319 	status_t status = roster.Stop(name);
320 	if (status == B_NAME_NOT_FOUND)
321 		status = roster.StopTarget(name);
322 
323 	if (status != B_OK) {
324 		fprintf(stderr, "%s: Stopping job \"%s\" failed: %s\n", kProgramName,
325 			name, strerror(status));
326 		exit(EXIT_FAILURE);
327 	}
328 }
329 
330 
331 static void
332 restart_job(const char* name)
333 {
334 	stop_job(name);
335 	start_job(name);
336 }
337 
338 
339 static void
340 enable_job(const char* name, bool enable)
341 {
342 	BLaunchRoster roster;
343 	status_t status = roster.SetEnabled(name, enable);
344 	if (status != B_OK) {
345 		fprintf(stderr, "%s: %s job \"%s\" failed: %s\n", kProgramName,
346 			enable ? "Enabling" : "Disabling", name, strerror(status));
347 		exit(EXIT_FAILURE);
348 	}
349 }
350 
351 
352 static void
353 usage(int status)
354 {
355 	fprintf(stderr, "Usage: %s <command>\n"
356 		"Where <command> is one of:\n"
357 		"  list - Lists all jobs (the default command)\n"
358 		"  list-targets - Lists all targets\n"
359 		"  log - Displays the event log\n"
360 		"The following <command>s have a <name> argument:\n"
361 		"  start - Starts a job/target\n"
362 		"  stop - Stops a running job/target\n"
363 		"  restart - Restarts a running job/target\n"
364 		"  info - Shows info for a job/target\n",
365 		kProgramName);
366 
367 	exit(status);
368 }
369 
370 
371 int
372 main(int argc, char** argv)
373 {
374 	const char* command = "list";
375 	bool verbose = false;
376 
377 	int c;
378 	while ((c = getopt_long(argc, argv, "+hv", kLongOptions, NULL)) != -1) {
379 		switch (c) {
380 			case 0:
381 				break;
382 			case 'h':
383 				usage(0);
384 				break;
385 			case 'v':
386 				verbose = true;
387 				break;
388 			default:
389 				usage(1);
390 				break;
391 		}
392 	}
393 
394 	if (argc - optind >= 1)
395 		command = argv[optind];
396 
397 	if (strcmp(command, "list") == 0) {
398 		list_jobs(verbose);
399 	} else if (strcmp(command, "list-targets") == 0) {
400 		list_targets(verbose);
401 	} else if (strcmp(command, "log") == 0) {
402 		get_log(argc - optind, &argv[optind]);
403 	} else if (argc == optind + 1) {
404 		// For convenience (the "info" command can be omitted)
405 		TextTable table;
406 		get_info(table, command, true);
407 	} else {
408 		// All commands that need a name following
409 
410 		const char* name = argv[argc - 1];
411 
412 		if (strcmp(command, "info") == 0) {
413 			TextTable table;
414 			get_info(table, name, true);
415 		} else if (strcmp(command, "start") == 0) {
416 			start_job(name);
417 		} else if (strcmp(command, "stop") == 0) {
418 			stop_job(name);
419 		} else if (strcmp(command, "restart") == 0) {
420 			restart_job(name);
421 		} else if (strcmp(command, "enable") == 0) {
422 			enable_job(name, true);
423 		} else if (strcmp(command, "disable") == 0) {
424 			enable_job(name, false);
425 		} else {
426 			fprintf(stderr, "%s: Unknown command \"%s\".\n", kProgramName,
427 				command);
428 		}
429 	}
430 	return 0;
431 }
432