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