1 /* 2 * Copyright 2015, Axel Dörfler, axeld@pinc-software.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include <map> 8 9 #include <stdio.h> 10 #include <stdlib.h> 11 #include <unistd.h> 12 13 #include <Directory.h> 14 #include <driver_settings.h> 15 #include <Entry.h> 16 #include <ObjectList.h> 17 #include <Path.h> 18 #include <PathFinder.h> 19 #include <Server.h> 20 21 #include <AppMisc.h> 22 #include <DriverSettingsMessageAdapter.h> 23 #include <LaunchDaemonDefs.h> 24 #include <syscalls.h> 25 26 27 using namespace BPrivate; 28 29 30 static const char* kLaunchDirectory = "launch"; 31 32 33 const static settings_template kJobTemplate[] = { 34 {B_STRING_TYPE, "name", NULL, true}, 35 {B_BOOL_TYPE, "disabled", NULL}, 36 {B_STRING_TYPE, "launch", NULL}, 37 {B_BOOL_TYPE, "create_port", NULL}, 38 {B_BOOL_TYPE, "no_safemode", NULL}, 39 {0, NULL, NULL} 40 }; 41 42 const static settings_template kSettingsTemplate[] = { 43 {B_MESSAGE_TYPE, "job", kJobTemplate}, 44 {B_MESSAGE_TYPE, "service", kJobTemplate}, 45 {0, NULL, NULL} 46 }; 47 48 49 class Job { 50 public: 51 Job(const char* name); 52 virtual ~Job(); 53 54 const char* Name() const; 55 56 bool IsEnabled() const; 57 void SetEnabled(bool enable); 58 59 bool IsService() const; 60 void SetService(bool service); 61 62 bool CreatePort() const; 63 void SetCreatePort(bool createPort); 64 65 bool LaunchInSafeMode() const; 66 void SetLaunchInSafeMode(bool launch); 67 68 const BStringList& Arguments() const; 69 BStringList& Arguments(); 70 void AddArgument(const char* argument); 71 72 status_t Init(); 73 status_t InitCheck() const; 74 75 team_id Team() const; 76 port_id Port() const; 77 78 status_t Launch(); 79 bool IsLaunched() const; 80 81 private: 82 BString fName; 83 BStringList fArguments; 84 bool fEnabled; 85 bool fService; 86 bool fCreatePort; 87 bool fLaunchInSafeMode; 88 port_id fPort; 89 status_t fInitStatus; 90 team_id fTeam; 91 }; 92 93 94 typedef std::map<BString, Job*> JobMap; 95 96 97 class LaunchDaemon : public BServer { 98 public: 99 LaunchDaemon(status_t& error); 100 virtual ~LaunchDaemon(); 101 102 virtual void ReadyToRun(); 103 virtual void MessageReceived(BMessage* message); 104 105 private: 106 void _ReadPaths(const BStringList& paths); 107 void _ReadEntry(const char* context, BEntry& entry); 108 void _ReadDirectory(const char* context, 109 BEntry& directory); 110 status_t _ReadFile(const char* context, BEntry& entry); 111 112 void _AddJob(bool service, BMessage& message); 113 Job* _Job(const char* name); 114 void _InitJobs(); 115 void _LaunchJobs(); 116 117 void _SetupEnvironment(); 118 bool _IsSafeMode() const; 119 120 private: 121 JobMap fJobs; 122 }; 123 124 125 static const char* 126 get_leaf(const char* signature) 127 { 128 const char* separator = strrchr(signature, '/'); 129 if (separator != NULL) 130 return separator + 1; 131 132 return signature; 133 } 134 135 136 // #pragma mark - 137 138 139 Job::Job(const char* name) 140 : 141 fName(name), 142 fEnabled(true), 143 fService(false), 144 fCreatePort(false), 145 fLaunchInSafeMode(true), 146 fPort(-1), 147 fInitStatus(B_NO_INIT), 148 fTeam(-1) 149 { 150 fName.ToLower(); 151 } 152 153 154 Job::~Job() 155 { 156 if (fPort >= 0) 157 delete_port(fPort); 158 } 159 160 161 const char* 162 Job::Name() const 163 { 164 return fName.String(); 165 } 166 167 168 bool 169 Job::IsEnabled() const 170 { 171 return fEnabled; 172 } 173 174 175 void 176 Job::SetEnabled(bool enable) 177 { 178 fEnabled = enable; 179 } 180 181 182 bool 183 Job::IsService() const 184 { 185 return fService; 186 } 187 188 189 void 190 Job::SetService(bool service) 191 { 192 fService = service; 193 } 194 195 196 bool 197 Job::CreatePort() const 198 { 199 return fCreatePort; 200 } 201 202 203 void 204 Job::SetCreatePort(bool createPort) 205 { 206 fCreatePort = createPort; 207 } 208 209 210 bool 211 Job::LaunchInSafeMode() const 212 { 213 return fLaunchInSafeMode; 214 } 215 216 217 void 218 Job::SetLaunchInSafeMode(bool launch) 219 { 220 fLaunchInSafeMode = launch; 221 } 222 223 224 const BStringList& 225 Job::Arguments() const 226 { 227 return fArguments; 228 } 229 230 231 BStringList& 232 Job::Arguments() 233 { 234 return fArguments; 235 } 236 237 238 void 239 Job::AddArgument(const char* argument) 240 { 241 fArguments.Add(argument); 242 } 243 244 245 status_t 246 Job::Init() 247 { 248 fInitStatus = B_OK; 249 250 if (fCreatePort) { 251 // TODO: prefix system ports with "system:" 252 fPort = create_port(B_LOOPER_PORT_DEFAULT_CAPACITY, Name()); 253 if (fPort < 0) 254 fInitStatus = fPort; 255 } 256 257 return fInitStatus; 258 } 259 260 261 status_t 262 Job::InitCheck() const 263 { 264 return fInitStatus; 265 } 266 267 268 team_id 269 Job::Team() const 270 { 271 return fTeam; 272 } 273 274 275 port_id 276 Job::Port() const 277 { 278 return fPort; 279 } 280 281 282 status_t 283 Job::Launch() 284 { 285 if (fArguments.IsEmpty()) { 286 // TODO: Launch via signature 287 // We cannot use the BRoster here as it tries to pre-register 288 // the application. 289 BString signature("application/"); 290 signature << fName; 291 return B_NOT_SUPPORTED; 292 //return be_roster->Launch(signature.String(), (BMessage*)NULL, &fTeam); 293 } 294 295 entry_ref ref; 296 status_t status = get_ref_for_path(fArguments.StringAt(0).String(), &ref); 297 if (status != B_OK) 298 return status; 299 300 size_t count = fArguments.CountStrings(); 301 const char* args[count + 1]; 302 for (int32 i = 0; i < fArguments.CountStrings(); i++) { 303 args[i] = fArguments.StringAt(i); 304 } 305 args[count] = NULL; 306 307 thread_id thread = load_image(count, args, 308 const_cast<const char**>(environ)); 309 if (thread >= 0) 310 resume_thread(thread); 311 312 thread_info info; 313 if (get_thread_info(thread, &info) == B_OK) 314 fTeam = info.team; 315 return B_OK; 316 // return be_roster->Launch(&ref, count, args, &fTeam); 317 } 318 319 320 bool 321 Job::IsLaunched() const 322 { 323 return fTeam >= 0; 324 } 325 326 327 // #pragma mark - 328 329 330 LaunchDaemon::LaunchDaemon(status_t& error) 331 : 332 BServer(kLaunchDaemonSignature, NULL, 333 create_port(B_LOOPER_PORT_DEFAULT_CAPACITY, 334 B_LAUNCH_DAEMON_PORT_NAME), false, &error) 335 { 336 } 337 338 339 LaunchDaemon::~LaunchDaemon() 340 { 341 } 342 343 344 void 345 LaunchDaemon::ReadyToRun() 346 { 347 _SetupEnvironment(); 348 349 BStringList paths; 350 BPathFinder::FindPaths(B_FIND_PATH_DATA_DIRECTORY, kLaunchDirectory, 351 B_FIND_PATHS_SYSTEM_ONLY, paths); 352 _ReadPaths(paths); 353 354 BPathFinder::FindPaths(B_FIND_PATH_SETTINGS_DIRECTORY, kLaunchDirectory, 355 B_FIND_PATHS_SYSTEM_ONLY, paths); 356 _ReadPaths(paths); 357 358 _InitJobs(); 359 _LaunchJobs(); 360 } 361 362 363 void 364 LaunchDaemon::MessageReceived(BMessage* message) 365 { 366 switch (message->what) { 367 case B_GET_LAUNCH_DATA: 368 { 369 BMessage reply; 370 Job* job = _Job(get_leaf(message->GetString("name"))); 371 if (job == NULL) { 372 reply.AddInt32("error", B_NAME_NOT_FOUND); 373 } else { 374 // If the job has not been launched yet, we'll pass on our 375 // team here. The rationale behind this is that this team 376 // will temporarily own the synchronous reply ports. 377 reply.AddInt32("team", job->Team() < 0 378 ? current_team() : job->Team()); 379 if (job->CreatePort()) 380 reply.AddInt32("port", job->Port()); 381 382 // Launch job now if it isn't running yet 383 if (!job->IsLaunched()) 384 job->Launch(); 385 } 386 message->SendReply(&reply); 387 break; 388 } 389 390 default: 391 BServer::MessageReceived(message); 392 break; 393 } 394 } 395 396 397 void 398 LaunchDaemon::_ReadPaths(const BStringList& paths) 399 { 400 for (int32 i = 0; i < paths.CountStrings(); i++) { 401 BEntry entry(paths.StringAt(i)); 402 if (entry.InitCheck() != B_OK || !entry.Exists()) 403 continue; 404 405 _ReadDirectory(NULL, entry); 406 } 407 } 408 409 410 void 411 LaunchDaemon::_ReadEntry(const char* context, BEntry& entry) 412 { 413 if (entry.IsDirectory()) 414 _ReadDirectory(context, entry); 415 else 416 _ReadFile(context, entry); 417 } 418 419 420 void 421 LaunchDaemon::_ReadDirectory(const char* context, BEntry& directoryEntry) 422 { 423 BDirectory directory(&directoryEntry); 424 425 BEntry entry; 426 while (directory.GetNextEntry(&entry) == B_OK) { 427 _ReadEntry(context, entry); 428 } 429 } 430 431 432 status_t 433 LaunchDaemon::_ReadFile(const char* context, BEntry& entry) 434 { 435 DriverSettingsMessageAdapter adapter; 436 437 BPath path; 438 status_t status = path.SetTo(&entry); 439 if (status != B_OK) 440 return status; 441 442 BMessage message; 443 status = adapter.ConvertFromDriverSettings(path.Path(), kSettingsTemplate, 444 message); 445 if (status == B_OK) { 446 message.PrintToStream(); 447 BMessage job; 448 for (int32 index = 0; message.FindMessage("service", index, 449 &job) == B_OK; index++) { 450 _AddJob(false, job); 451 } 452 453 for (int32 index = 0; message.FindMessage("job", index, &job) == B_OK; 454 index++) { 455 _AddJob(false, job); 456 } 457 } 458 459 return status; 460 } 461 462 463 void 464 LaunchDaemon::_AddJob(bool service, BMessage& message) 465 { 466 const char* name = message.GetString("name"); 467 if (name == NULL || name[0] == '\0') { 468 // Invalid job description 469 return; 470 } 471 472 Job* job = _Job(name); 473 if (job == NULL) 474 job = new Job(name); 475 476 job->SetEnabled(!message.GetBool("disabled", !job->IsEnabled())); 477 job->SetService(service); 478 job->SetCreatePort(message.GetBool("create_port", job->CreatePort())); 479 job->SetLaunchInSafeMode( 480 !message.GetBool("no_safemode", !job->LaunchInSafeMode())); 481 482 const char* argument; 483 for (int32 index = 0; 484 message.FindString("launch", index, &argument) == B_OK; index++) { 485 job->AddArgument(argument); 486 } 487 488 fJobs.insert(std::pair<BString, Job*>(job->Name(), job)); 489 } 490 491 492 Job* 493 LaunchDaemon::_Job(const char* name) 494 { 495 if (name == NULL) 496 return NULL; 497 498 JobMap::const_iterator found = fJobs.find(BString(name).ToLower()); 499 if (found != fJobs.end()) 500 return found->second; 501 502 return NULL; 503 } 504 505 506 void 507 LaunchDaemon::_InitJobs() 508 { 509 for (JobMap::iterator iterator = fJobs.begin(); iterator != fJobs.end(); 510 iterator++) { 511 Job* job = iterator->second; 512 if (job->IsEnabled() && (!_IsSafeMode() || job->LaunchInSafeMode())) 513 job->Init(); 514 } 515 } 516 517 518 void 519 LaunchDaemon::_LaunchJobs() 520 { 521 for (JobMap::iterator iterator = fJobs.begin(); iterator != fJobs.end(); 522 iterator++) { 523 Job* job = iterator->second; 524 if (job->IsEnabled() && job->InitCheck() == B_OK) 525 job->Launch(); 526 } 527 } 528 529 530 void 531 LaunchDaemon::_SetupEnvironment() 532 { 533 // Determine safemode kernel option 534 BString safemode = "SAFEMODE="; 535 safemode << _IsSafeMode() ? "yes" : "no"; 536 } 537 538 539 bool 540 LaunchDaemon::_IsSafeMode() const 541 { 542 char buffer[32]; 543 size_t size = sizeof(buffer); 544 status_t status = _kern_get_safemode_option(B_SAFEMODE_SAFE_MODE, buffer, 545 &size); 546 if (status == B_OK) { 547 return !strncasecmp(buffer, "true", size) 548 || !strncasecmp(buffer, "yes", size) 549 || !strncasecmp(buffer, "on", size) 550 || !strncasecmp(buffer, "enabled", size); 551 } 552 553 return false; 554 } 555 556 557 // #pragma mark - 558 559 560 int 561 main() 562 { 563 // TODO: remove this again 564 close(STDOUT_FILENO); 565 int fd = open("/dev/dprintf", O_WRONLY); 566 if (fd != STDOUT_FILENO) 567 dup2(fd, STDOUT_FILENO); 568 puts("launch_daemon is alive and kicking."); 569 570 status_t status; 571 LaunchDaemon* daemon = new LaunchDaemon(status); 572 if (status == B_OK) 573 daemon->Run(); 574 575 delete daemon; 576 return status == B_OK ? EXIT_SUCCESS : EXIT_FAILURE; 577 } 578