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 <File.h> 17 #include <ObjectList.h> 18 #include <Path.h> 19 #include <PathFinder.h> 20 #include <Server.h> 21 22 #include <AppMisc.h> 23 #include <DriverSettingsMessageAdapter.h> 24 #include <JobQueue.h> 25 #include <LaunchDaemonDefs.h> 26 #include <syscalls.h> 27 28 #include "InitRealTimeClockJob.h" 29 #include "InitSharedMemoryDirectoryJob.h" 30 #include "InitTemporaryDirectoryJob.h" 31 32 33 using namespace BPrivate; 34 using namespace BSupportKit; 35 using BSupportKit::BPrivate::JobQueue; 36 37 38 static const char* kLaunchDirectory = "launch"; 39 40 41 const static settings_template kPortTemplate[] = { 42 {B_STRING_TYPE, "name", NULL, true}, 43 {B_INT32_TYPE, "capacity", NULL}, 44 }; 45 46 const static settings_template kJobTemplate[] = { 47 {B_STRING_TYPE, "name", NULL, true}, 48 {B_BOOL_TYPE, "disabled", NULL}, 49 {B_STRING_TYPE, "launch", NULL}, 50 {B_BOOL_TYPE, "legacy", NULL}, 51 {B_MESSAGE_TYPE, "port", kPortTemplate}, 52 {B_BOOL_TYPE, "no_safemode", NULL}, 53 {0, NULL, NULL} 54 }; 55 56 const static settings_template kSettingsTemplate[] = { 57 {B_MESSAGE_TYPE, "job", kJobTemplate}, 58 {B_MESSAGE_TYPE, "service", kJobTemplate}, 59 {0, NULL, NULL} 60 }; 61 62 63 typedef std::map<BString, BMessage> PortMap; 64 65 66 class Job { 67 public: 68 Job(const char* name); 69 virtual ~Job(); 70 71 const char* Name() const; 72 73 bool IsEnabled() const; 74 void SetEnabled(bool enable); 75 76 bool IsService() const; 77 void SetService(bool service); 78 79 bool CreateDefaultPort() const; 80 void SetCreateDefaultPort(bool createPort); 81 82 void AddPort(BMessage& data); 83 84 bool LaunchInSafeMode() const; 85 void SetLaunchInSafeMode(bool launch); 86 87 const BStringList& Arguments() const; 88 BStringList& Arguments(); 89 void AddArgument(const char* argument); 90 91 status_t Init(); 92 status_t InitCheck() const; 93 94 team_id Team() const; 95 96 const PortMap& Ports() const; 97 port_id Port(const char* name = NULL) const; 98 99 status_t Launch(); 100 bool IsLaunched() const; 101 102 private: 103 BString fName; 104 BStringList fArguments; 105 bool fEnabled; 106 bool fService; 107 bool fCreateDefaultPort; 108 bool fLaunchInSafeMode; 109 PortMap fPortMap; 110 status_t fInitStatus; 111 team_id fTeam; 112 }; 113 114 115 typedef std::map<BString, Job*> JobMap; 116 117 118 class LaunchDaemon : public BServer { 119 public: 120 LaunchDaemon(status_t& error); 121 virtual ~LaunchDaemon(); 122 123 virtual void ReadyToRun(); 124 virtual void MessageReceived(BMessage* message); 125 126 private: 127 void _ReadPaths(const BStringList& paths); 128 void _ReadEntry(const char* context, BEntry& entry); 129 void _ReadDirectory(const char* context, 130 BEntry& directory); 131 status_t _ReadFile(const char* context, BEntry& entry); 132 133 void _AddJob(bool service, BMessage& message); 134 Job* _Job(const char* name); 135 void _InitJobs(); 136 void _LaunchJobs(); 137 138 void _RetrieveKernelOptions(); 139 void _SetupEnvironment(); 140 void _InitSystem(); 141 142 bool _IsSafeMode() const; 143 144 private: 145 JobMap fJobs; 146 JobQueue fJobQueue; 147 bool fSafeMode; 148 }; 149 150 151 static const char* 152 get_leaf(const char* signature) 153 { 154 const char* separator = strrchr(signature, '/'); 155 if (separator != NULL) 156 return separator + 1; 157 158 return signature; 159 } 160 161 162 // #pragma mark - 163 164 165 Job::Job(const char* name) 166 : 167 fName(name), 168 fEnabled(true), 169 fService(false), 170 fCreateDefaultPort(false), 171 fLaunchInSafeMode(true), 172 fInitStatus(B_NO_INIT), 173 fTeam(-1) 174 { 175 fName.ToLower(); 176 } 177 178 179 Job::~Job() 180 { 181 PortMap::const_iterator iterator = Ports().begin(); 182 for (; iterator != Ports().end(); iterator++) { 183 port_id port = iterator->second.GetInt32("port", -1); 184 if (port >= 0) 185 delete_port(port); 186 } 187 } 188 189 190 const char* 191 Job::Name() const 192 { 193 return fName.String(); 194 } 195 196 197 bool 198 Job::IsEnabled() const 199 { 200 return fEnabled; 201 } 202 203 204 void 205 Job::SetEnabled(bool enable) 206 { 207 fEnabled = enable; 208 } 209 210 211 bool 212 Job::IsService() const 213 { 214 return fService; 215 } 216 217 218 void 219 Job::SetService(bool service) 220 { 221 fService = service; 222 } 223 224 225 bool 226 Job::CreateDefaultPort() const 227 { 228 return fCreateDefaultPort; 229 } 230 231 232 void 233 Job::SetCreateDefaultPort(bool createPort) 234 { 235 fCreateDefaultPort = createPort; 236 } 237 238 239 void 240 Job::AddPort(BMessage& data) 241 { 242 const char* name = data.GetString("name"); 243 fPortMap.insert(std::pair<BString, BMessage>(BString(name), data)); 244 } 245 246 247 bool 248 Job::LaunchInSafeMode() const 249 { 250 return fLaunchInSafeMode; 251 } 252 253 254 void 255 Job::SetLaunchInSafeMode(bool launch) 256 { 257 fLaunchInSafeMode = launch; 258 } 259 260 261 const BStringList& 262 Job::Arguments() const 263 { 264 return fArguments; 265 } 266 267 268 BStringList& 269 Job::Arguments() 270 { 271 return fArguments; 272 } 273 274 275 void 276 Job::AddArgument(const char* argument) 277 { 278 fArguments.Add(argument); 279 } 280 281 282 status_t 283 Job::Init() 284 { 285 fInitStatus = B_OK; 286 287 // Create ports 288 // TODO: prefix system ports with "system:" 289 290 bool defaultPort = false; 291 292 for (PortMap::iterator iterator = fPortMap.begin(); 293 iterator != fPortMap.end(); iterator++) { 294 BString name(Name()); 295 const char* suffix = iterator->second.GetString("name"); 296 if (suffix != NULL) 297 name << ':' << suffix; 298 else 299 defaultPort = true; 300 301 const int32 capacity = iterator->second.GetInt32("capacity", 302 B_LOOPER_PORT_DEFAULT_CAPACITY); 303 304 port_id port = create_port(capacity, name.String()); 305 if (port < 0) { 306 fInitStatus = port; 307 break; 308 } 309 iterator->second.SetInt32("port", port); 310 } 311 312 if (fInitStatus == B_OK && fCreateDefaultPort && !defaultPort) { 313 BMessage data; 314 data.AddInt32("capacity", B_LOOPER_PORT_DEFAULT_CAPACITY); 315 316 port_id port = create_port(B_LOOPER_PORT_DEFAULT_CAPACITY, Name()); 317 if (port < 0) 318 fInitStatus = port; 319 else { 320 data.SetInt32("port", port); 321 AddPort(data); 322 } 323 } 324 325 return fInitStatus; 326 } 327 328 329 status_t 330 Job::InitCheck() const 331 { 332 return fInitStatus; 333 } 334 335 336 team_id 337 Job::Team() const 338 { 339 return fTeam; 340 } 341 342 343 const PortMap& 344 Job::Ports() const 345 { 346 return fPortMap; 347 } 348 349 350 port_id 351 Job::Port(const char* name) const 352 { 353 PortMap::const_iterator found = fPortMap.find(name); 354 if (found != fPortMap.end()) 355 return found->second.GetInt32("port", -1); 356 357 return B_NAME_NOT_FOUND; 358 } 359 360 361 status_t 362 Job::Launch() 363 { 364 if (fArguments.IsEmpty()) { 365 // TODO: Launch via signature 366 // We cannot use the BRoster here as it tries to pre-register 367 // the application. 368 BString signature("application/"); 369 signature << fName; 370 return B_NOT_SUPPORTED; 371 //return be_roster->Launch(signature.String(), (BMessage*)NULL, &fTeam); 372 } 373 374 entry_ref ref; 375 status_t status = get_ref_for_path(fArguments.StringAt(0).String(), &ref); 376 if (status != B_OK) 377 return status; 378 379 size_t count = fArguments.CountStrings(); 380 const char* args[count + 1]; 381 for (int32 i = 0; i < fArguments.CountStrings(); i++) { 382 args[i] = fArguments.StringAt(i); 383 } 384 args[count] = NULL; 385 386 thread_id thread = load_image(count, args, 387 const_cast<const char**>(environ)); 388 if (thread >= 0) 389 resume_thread(thread); 390 391 thread_info info; 392 if (get_thread_info(thread, &info) == B_OK) 393 fTeam = info.team; 394 return B_OK; 395 // return be_roster->Launch(&ref, count, args, &fTeam); 396 } 397 398 399 bool 400 Job::IsLaunched() const 401 { 402 return fTeam >= 0; 403 } 404 405 406 // #pragma mark - 407 408 409 LaunchDaemon::LaunchDaemon(status_t& error) 410 : 411 BServer(kLaunchDaemonSignature, NULL, 412 create_port(B_LOOPER_PORT_DEFAULT_CAPACITY, 413 B_LAUNCH_DAEMON_PORT_NAME), false, &error) 414 { 415 } 416 417 418 LaunchDaemon::~LaunchDaemon() 419 { 420 } 421 422 423 void 424 LaunchDaemon::ReadyToRun() 425 { 426 _RetrieveKernelOptions(); 427 _SetupEnvironment(); 428 _InitSystem(); 429 430 BStringList paths; 431 BPathFinder::FindPaths(B_FIND_PATH_DATA_DIRECTORY, kLaunchDirectory, 432 B_FIND_PATHS_SYSTEM_ONLY, paths); 433 _ReadPaths(paths); 434 435 BPathFinder::FindPaths(B_FIND_PATH_SETTINGS_DIRECTORY, kLaunchDirectory, 436 B_FIND_PATHS_SYSTEM_ONLY, paths); 437 _ReadPaths(paths); 438 439 _InitJobs(); 440 _LaunchJobs(); 441 } 442 443 444 void 445 LaunchDaemon::MessageReceived(BMessage* message) 446 { 447 switch (message->what) { 448 case B_GET_LAUNCH_DATA: 449 { 450 BMessage reply((uint32)B_OK); 451 Job* job = _Job(get_leaf(message->GetString("name"))); 452 if (job == NULL) { 453 reply.what = B_NAME_NOT_FOUND; 454 } else { 455 // If the job has not been launched yet, we'll pass on our 456 // team here. The rationale behind this is that this team 457 // will temporarily own the synchronous reply ports. 458 reply.AddInt32("team", job->Team() < 0 459 ? current_team() : job->Team()); 460 461 PortMap::const_iterator iterator = job->Ports().begin(); 462 for (; iterator != job->Ports().end(); iterator++) { 463 BString name; 464 if (iterator->second.HasString("name")) 465 name << iterator->second.GetString("name") << "_"; 466 name << "port"; 467 468 reply.AddInt32(name.String(), 469 iterator->second.GetInt32("port", -1)); 470 } 471 472 // Launch job now if it isn't running yet 473 if (!job->IsLaunched()) 474 job->Launch(); 475 } 476 message->SendReply(&reply); 477 break; 478 } 479 480 default: 481 BServer::MessageReceived(message); 482 break; 483 } 484 } 485 486 487 void 488 LaunchDaemon::_ReadPaths(const BStringList& paths) 489 { 490 for (int32 i = 0; i < paths.CountStrings(); i++) { 491 BEntry entry(paths.StringAt(i)); 492 if (entry.InitCheck() != B_OK || !entry.Exists()) 493 continue; 494 495 _ReadDirectory(NULL, entry); 496 } 497 } 498 499 500 void 501 LaunchDaemon::_ReadEntry(const char* context, BEntry& entry) 502 { 503 if (entry.IsDirectory()) 504 _ReadDirectory(context, entry); 505 else 506 _ReadFile(context, entry); 507 } 508 509 510 void 511 LaunchDaemon::_ReadDirectory(const char* context, BEntry& directoryEntry) 512 { 513 BDirectory directory(&directoryEntry); 514 515 BEntry entry; 516 while (directory.GetNextEntry(&entry) == B_OK) { 517 _ReadEntry(context, entry); 518 } 519 } 520 521 522 status_t 523 LaunchDaemon::_ReadFile(const char* context, BEntry& entry) 524 { 525 DriverSettingsMessageAdapter adapter; 526 527 BPath path; 528 status_t status = path.SetTo(&entry); 529 if (status != B_OK) 530 return status; 531 532 BMessage message; 533 status = adapter.ConvertFromDriverSettings(path.Path(), kSettingsTemplate, 534 message); 535 if (status == B_OK) { 536 message.PrintToStream(); 537 BMessage job; 538 for (int32 index = 0; message.FindMessage("service", index, 539 &job) == B_OK; index++) { 540 _AddJob(true, job); 541 } 542 543 for (int32 index = 0; message.FindMessage("job", index, &job) == B_OK; 544 index++) { 545 _AddJob(false, job); 546 } 547 } 548 549 return status; 550 } 551 552 553 void 554 LaunchDaemon::_AddJob(bool service, BMessage& message) 555 { 556 const char* name = message.GetString("name"); 557 if (name == NULL || name[0] == '\0') { 558 // Invalid job description 559 return; 560 } 561 562 Job* job = _Job(name); 563 if (job == NULL) 564 job = new Job(name); 565 566 job->SetEnabled(!message.GetBool("disabled", !job->IsEnabled())); 567 job->SetService(service); 568 job->SetCreateDefaultPort(!message.GetBool("legacy", !service)); 569 job->SetLaunchInSafeMode( 570 !message.GetBool("no_safemode", !job->LaunchInSafeMode())); 571 572 BMessage portMessage; 573 for (int32 index = 0; 574 message.FindMessage("port", index, &portMessage) == B_OK; index++) { 575 job->AddPort(portMessage); 576 } 577 578 const char* argument; 579 for (int32 index = 0; 580 message.FindString("launch", index, &argument) == B_OK; index++) { 581 job->AddArgument(argument); 582 } 583 584 fJobs.insert(std::pair<BString, Job*>(job->Name(), job)); 585 } 586 587 588 Job* 589 LaunchDaemon::_Job(const char* name) 590 { 591 if (name == NULL) 592 return NULL; 593 594 JobMap::const_iterator found = fJobs.find(BString(name).ToLower()); 595 if (found != fJobs.end()) 596 return found->second; 597 598 return NULL; 599 } 600 601 602 void 603 LaunchDaemon::_InitJobs() 604 { 605 for (JobMap::iterator iterator = fJobs.begin(); iterator != fJobs.end(); 606 iterator++) { 607 Job* job = iterator->second; 608 if (job->IsEnabled() && (!_IsSafeMode() || job->LaunchInSafeMode())) 609 job->Init(); 610 } 611 } 612 613 614 void 615 LaunchDaemon::_LaunchJobs() 616 { 617 for (JobMap::iterator iterator = fJobs.begin(); iterator != fJobs.end(); 618 iterator++) { 619 Job* job = iterator->second; 620 if (job->IsEnabled() && job->InitCheck() == B_OK) 621 job->Launch(); 622 } 623 } 624 625 626 void 627 LaunchDaemon::_RetrieveKernelOptions() 628 { 629 char buffer[32]; 630 size_t size = sizeof(buffer); 631 status_t status = _kern_get_safemode_option(B_SAFEMODE_SAFE_MODE, buffer, 632 &size); 633 if (status == B_OK) { 634 fSafeMode = !strncasecmp(buffer, "true", size) 635 || !strncasecmp(buffer, "yes", size) 636 || !strncasecmp(buffer, "on", size) 637 || !strncasecmp(buffer, "enabled", size); 638 } else 639 fSafeMode = false; 640 } 641 642 643 void 644 LaunchDaemon::_SetupEnvironment() 645 { 646 // Determine safemode kernel option 647 BString safemode = "SAFEMODE="; 648 safemode << _IsSafeMode() ? "yes" : "no"; 649 650 putenv(safemode.String()); 651 } 652 653 654 /*! Basic system initialization that must happen before any jobs are launched. 655 */ 656 void 657 LaunchDaemon::_InitSystem() 658 { 659 fJobQueue.AddJob(new InitRealTimeClockJob()); 660 fJobQueue.AddJob(new InitSharedMemoryDirectoryJob()); 661 fJobQueue.AddJob(new InitTemporaryDirectoryJob()); 662 663 // TODO: these should be done in parallel 664 while (BJob* job = fJobQueue.Pop()) 665 job->Run(); 666 } 667 668 669 bool 670 LaunchDaemon::_IsSafeMode() const 671 { 672 return fSafeMode; 673 } 674 675 676 // #pragma mark - 677 678 679 int 680 main() 681 { 682 // TODO: remove this again 683 close(STDOUT_FILENO); 684 int fd = open("/dev/dprintf", O_WRONLY); 685 if (fd != STDOUT_FILENO) 686 dup2(fd, STDOUT_FILENO); 687 puts("launch_daemon is alive and kicking."); 688 fflush(stdout); 689 690 status_t status; 691 LaunchDaemon* daemon = new LaunchDaemon(status); 692 if (status == B_OK) 693 daemon->Run(); 694 695 delete daemon; 696 return status == B_OK ? EXIT_SUCCESS : EXIT_FAILURE; 697 } 698