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
print_log(const BMessage & log)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
log_usage(int status)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
get_log(int argCount,char ** args)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
print_summary(TextTable & table,BMessage info,bool target)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
get_info(TextTable & table,const char * name,bool verbose)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
list_targets(bool verbose)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
list_jobs(bool verbose)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
start_job(const char * name)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
stop_job(const char * name)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
restart_job(const char * name)332 restart_job(const char* name)
333 {
334 stop_job(name);
335 start_job(name);
336 }
337
338
339 static void
enable_job(const char * name,bool enable)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
usage(int status)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
main(int argc,char ** argv)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