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_STRING_TYPE, "requires", NULL}, 51 {B_BOOL_TYPE, "legacy", NULL}, 52 {B_MESSAGE_TYPE, "port", kPortTemplate}, 53 {B_BOOL_TYPE, "no_safemode", NULL}, 54 {0, NULL, NULL} 55 }; 56 57 const static settings_template kSettingsTemplate[] = { 58 {B_MESSAGE_TYPE, "job", kJobTemplate}, 59 {B_MESSAGE_TYPE, "service", kJobTemplate}, 60 {0, NULL, NULL} 61 }; 62 63 64 typedef std::map<BString, BMessage> PortMap; 65 66 67 class Job { 68 public: 69 Job(const char* name); 70 virtual ~Job(); 71 72 const char* Name() const; 73 74 bool IsEnabled() const; 75 void SetEnabled(bool enable); 76 77 bool IsService() const; 78 void SetService(bool service); 79 80 bool CreateDefaultPort() const; 81 void SetCreateDefaultPort(bool createPort); 82 83 void AddPort(BMessage& data); 84 85 bool LaunchInSafeMode() const; 86 void SetLaunchInSafeMode(bool launch); 87 88 const BStringList& Arguments() const; 89 BStringList& Arguments(); 90 void AddArgument(const char* argument); 91 92 const BStringList& Requirements() const; 93 BStringList& Requirements(); 94 void AddRequirement(const char* requirement); 95 96 status_t Init(); 97 status_t InitCheck() const; 98 99 team_id Team() const; 100 101 const PortMap& Ports() const; 102 port_id Port(const char* name = NULL) const; 103 104 status_t Launch(); 105 bool IsLaunched() const; 106 107 private: 108 BString fName; 109 BStringList fArguments; 110 BStringList fRequirements; 111 bool fEnabled; 112 bool fService; 113 bool fCreateDefaultPort; 114 bool fLaunchInSafeMode; 115 PortMap fPortMap; 116 status_t fInitStatus; 117 team_id fTeam; 118 }; 119 120 121 class LaunchJob : public BJob { 122 public: 123 LaunchJob(Job* job); 124 125 protected: 126 virtual status_t Execute(); 127 128 private: 129 Job* fJob; 130 }; 131 132 133 class Target : public BJob { 134 public: 135 Target(const char* name); 136 137 protected: 138 virtual status_t Execute(); 139 }; 140 141 142 class Worker { 143 public: 144 Worker(JobQueue& queue); 145 virtual ~Worker(); 146 147 protected: 148 virtual status_t Process(); 149 virtual bigtime_t Timeout() const; 150 virtual status_t Run(BJob* job); 151 152 private: 153 static status_t _Process(void* self); 154 155 protected: 156 thread_id fThread; 157 JobQueue& fJobQueue; 158 }; 159 160 161 class MainWorker : public Worker { 162 public: 163 MainWorker(JobQueue& queue); 164 165 protected: 166 virtual bigtime_t Timeout() const; 167 virtual status_t Run(BJob* job); 168 169 private: 170 int32 fCPUCount; 171 }; 172 173 174 typedef std::map<BString, Job*> JobMap; 175 176 177 class LaunchDaemon : public BServer { 178 public: 179 LaunchDaemon(status_t& error); 180 virtual ~LaunchDaemon(); 181 182 virtual void ReadyToRun(); 183 virtual void MessageReceived(BMessage* message); 184 185 private: 186 void _ReadPaths(const BStringList& paths); 187 void _ReadEntry(const char* context, BEntry& entry); 188 void _ReadDirectory(const char* context, 189 BEntry& directory); 190 status_t _ReadFile(const char* context, BEntry& entry); 191 192 void _AddJob(bool service, BMessage& message); 193 Job* _Job(const char* name); 194 void _InitJobs(); 195 void _LaunchJobs(); 196 LaunchJob* _AddLaunchJob(Job* job); 197 198 void _RetrieveKernelOptions(); 199 void _SetupEnvironment(); 200 void _InitSystem(); 201 void _AddInitJob(BJob* job); 202 203 bool _IsSafeMode() const; 204 205 private: 206 JobMap fJobs; 207 JobQueue fJobQueue; 208 MainWorker* fMainWorker; 209 Target* fInitTarget; 210 bool fSafeMode; 211 }; 212 213 214 static const bigtime_t kWorkerTimeout = 1000000; 215 // One second until a worker thread quits without a job 216 217 static int32 sWorkerCount; 218 219 220 static const char* 221 get_leaf(const char* signature) 222 { 223 const char* separator = strrchr(signature, '/'); 224 if (separator != NULL) 225 return separator + 1; 226 227 return signature; 228 } 229 230 231 // #pragma mark - 232 233 234 Job::Job(const char* name) 235 : 236 fName(name), 237 fEnabled(true), 238 fService(false), 239 fCreateDefaultPort(false), 240 fLaunchInSafeMode(true), 241 fInitStatus(B_NO_INIT), 242 fTeam(-1) 243 { 244 fName.ToLower(); 245 } 246 247 248 Job::~Job() 249 { 250 PortMap::const_iterator iterator = Ports().begin(); 251 for (; iterator != Ports().end(); iterator++) { 252 port_id port = iterator->second.GetInt32("port", -1); 253 if (port >= 0) 254 delete_port(port); 255 } 256 } 257 258 259 const char* 260 Job::Name() const 261 { 262 return fName.String(); 263 } 264 265 266 bool 267 Job::IsEnabled() const 268 { 269 return fEnabled; 270 } 271 272 273 void 274 Job::SetEnabled(bool enable) 275 { 276 fEnabled = enable; 277 } 278 279 280 bool 281 Job::IsService() const 282 { 283 return fService; 284 } 285 286 287 void 288 Job::SetService(bool service) 289 { 290 fService = service; 291 } 292 293 294 bool 295 Job::CreateDefaultPort() const 296 { 297 return fCreateDefaultPort; 298 } 299 300 301 void 302 Job::SetCreateDefaultPort(bool createPort) 303 { 304 fCreateDefaultPort = createPort; 305 } 306 307 308 void 309 Job::AddPort(BMessage& data) 310 { 311 const char* name = data.GetString("name"); 312 fPortMap.insert(std::pair<BString, BMessage>(BString(name), data)); 313 } 314 315 316 bool 317 Job::LaunchInSafeMode() const 318 { 319 return fLaunchInSafeMode; 320 } 321 322 323 void 324 Job::SetLaunchInSafeMode(bool launch) 325 { 326 fLaunchInSafeMode = launch; 327 } 328 329 330 const BStringList& 331 Job::Arguments() const 332 { 333 return fArguments; 334 } 335 336 337 BStringList& 338 Job::Arguments() 339 { 340 return fArguments; 341 } 342 343 344 void 345 Job::AddArgument(const char* argument) 346 { 347 fArguments.Add(argument); 348 } 349 350 351 const BStringList& 352 Job::Requirements() const 353 { 354 return fRequirements; 355 } 356 357 358 BStringList& 359 Job::Requirements() 360 { 361 return fRequirements; 362 } 363 364 365 void 366 Job::AddRequirement(const char* requirement) 367 { 368 fRequirements.Add(requirement); 369 } 370 371 372 status_t 373 Job::Init() 374 { 375 fInitStatus = B_OK; 376 377 // Create ports 378 // TODO: prefix system ports with "system:" 379 380 bool defaultPort = false; 381 382 for (PortMap::iterator iterator = fPortMap.begin(); 383 iterator != fPortMap.end(); iterator++) { 384 BString name(Name()); 385 const char* suffix = iterator->second.GetString("name"); 386 if (suffix != NULL) 387 name << ':' << suffix; 388 else 389 defaultPort = true; 390 391 const int32 capacity = iterator->second.GetInt32("capacity", 392 B_LOOPER_PORT_DEFAULT_CAPACITY); 393 394 port_id port = create_port(capacity, name.String()); 395 if (port < 0) { 396 fInitStatus = port; 397 break; 398 } 399 iterator->second.SetInt32("port", port); 400 } 401 402 if (fInitStatus == B_OK && fCreateDefaultPort && !defaultPort) { 403 BMessage data; 404 data.AddInt32("capacity", B_LOOPER_PORT_DEFAULT_CAPACITY); 405 406 port_id port = create_port(B_LOOPER_PORT_DEFAULT_CAPACITY, Name()); 407 if (port < 0) 408 fInitStatus = port; 409 else { 410 data.SetInt32("port", port); 411 AddPort(data); 412 } 413 } 414 415 return fInitStatus; 416 } 417 418 419 status_t 420 Job::InitCheck() const 421 { 422 return fInitStatus; 423 } 424 425 426 team_id 427 Job::Team() const 428 { 429 return fTeam; 430 } 431 432 433 const PortMap& 434 Job::Ports() const 435 { 436 return fPortMap; 437 } 438 439 440 port_id 441 Job::Port(const char* name) const 442 { 443 PortMap::const_iterator found = fPortMap.find(name); 444 if (found != fPortMap.end()) 445 return found->second.GetInt32("port", -1); 446 447 return B_NAME_NOT_FOUND; 448 } 449 450 451 status_t 452 Job::Launch() 453 { 454 if (fArguments.IsEmpty()) { 455 // TODO: Launch via signature 456 // We cannot use the BRoster here as it tries to pre-register 457 // the application. 458 BString signature("application/"); 459 signature << fName; 460 return B_NOT_SUPPORTED; 461 //return be_roster->Launch(signature.String(), (BMessage*)NULL, &fTeam); 462 } 463 464 entry_ref ref; 465 status_t status = get_ref_for_path(fArguments.StringAt(0).String(), &ref); 466 if (status != B_OK) 467 return status; 468 469 size_t count = fArguments.CountStrings(); 470 const char* args[count + 1]; 471 for (int32 i = 0; i < fArguments.CountStrings(); i++) { 472 args[i] = fArguments.StringAt(i); 473 } 474 args[count] = NULL; 475 476 thread_id thread = load_image(count, args, 477 const_cast<const char**>(environ)); 478 if (thread >= 0) 479 resume_thread(thread); 480 481 thread_info info; 482 if (get_thread_info(thread, &info) == B_OK) 483 fTeam = info.team; 484 return B_OK; 485 // return be_roster->Launch(&ref, count, args, &fTeam); 486 } 487 488 489 bool 490 Job::IsLaunched() const 491 { 492 return fTeam >= 0; 493 } 494 495 496 // #pragma mark - 497 498 499 LaunchJob::LaunchJob(Job* job) 500 : 501 BJob(job->Name()), 502 fJob(job) 503 { 504 } 505 506 507 status_t 508 LaunchJob::Execute() 509 { 510 if (!fJob->IsLaunched()) 511 return fJob->Launch(); 512 513 return B_OK; 514 } 515 516 517 // #pragma mark - 518 519 520 Target::Target(const char* name) 521 : 522 BJob(name) 523 { 524 } 525 526 527 status_t 528 Target::Execute() 529 { 530 return B_OK; 531 } 532 533 534 // #pragma mark - 535 536 537 Worker::Worker(JobQueue& queue) 538 : 539 fJobQueue(queue) 540 { 541 fThread = spawn_thread(&Worker::_Process, "worker", B_NORMAL_PRIORITY, 542 this); 543 if (fThread >= 0 && resume_thread(fThread) == B_OK) 544 atomic_add(&sWorkerCount, 1); 545 } 546 547 548 Worker::~Worker() 549 { 550 } 551 552 553 status_t 554 Worker::Process() 555 { 556 while (true) { 557 BJob* job; 558 status_t status = fJobQueue.Pop(Timeout(), false, &job); 559 if (status != B_OK) 560 return status; 561 562 Run(job); 563 // TODO: proper error reporting on failed job! 564 } 565 } 566 567 568 bigtime_t 569 Worker::Timeout() const 570 { 571 return kWorkerTimeout; 572 } 573 574 575 status_t 576 Worker::Run(BJob* job) 577 { 578 return job->Run(); 579 } 580 581 582 /*static*/ status_t 583 Worker::_Process(void* _self) 584 { 585 Worker* self = (Worker*)_self; 586 status_t status = self->Process(); 587 delete self; 588 589 return status; 590 } 591 592 593 // #pragma mark - 594 595 596 MainWorker::MainWorker(JobQueue& queue) 597 : 598 Worker(queue) 599 { 600 // TODO: keep track of workers, and quit them on destruction 601 system_info info; 602 if (get_system_info(&info) == B_OK) 603 fCPUCount = info.cpu_count; 604 } 605 606 607 bigtime_t 608 MainWorker::Timeout() const 609 { 610 return B_INFINITE_TIMEOUT; 611 } 612 613 614 status_t 615 MainWorker::Run(BJob* job) 616 { 617 int32 count = atomic_get(&sWorkerCount); 618 619 size_t jobCount = fJobQueue.CountJobs(); 620 if (jobCount > INT_MAX) 621 jobCount = INT_MAX; 622 623 if ((int32)jobCount > count && count < fCPUCount) 624 new Worker(fJobQueue); 625 626 return Worker::Run(job); 627 } 628 629 630 // #pragma mark - 631 632 633 LaunchDaemon::LaunchDaemon(status_t& error) 634 : 635 BServer(kLaunchDaemonSignature, NULL, 636 create_port(B_LOOPER_PORT_DEFAULT_CAPACITY, 637 B_LAUNCH_DAEMON_PORT_NAME), false, &error), 638 fInitTarget(new Target("init")) 639 { 640 fMainWorker = new MainWorker(fJobQueue); 641 } 642 643 644 LaunchDaemon::~LaunchDaemon() 645 { 646 } 647 648 649 void 650 LaunchDaemon::ReadyToRun() 651 { 652 _RetrieveKernelOptions(); 653 _SetupEnvironment(); 654 _InitSystem(); 655 656 BStringList paths; 657 BPathFinder::FindPaths(B_FIND_PATH_DATA_DIRECTORY, kLaunchDirectory, 658 B_FIND_PATHS_SYSTEM_ONLY, paths); 659 _ReadPaths(paths); 660 661 BPathFinder::FindPaths(B_FIND_PATH_SETTINGS_DIRECTORY, kLaunchDirectory, 662 B_FIND_PATHS_SYSTEM_ONLY, paths); 663 _ReadPaths(paths); 664 665 _InitJobs(); 666 _LaunchJobs(); 667 } 668 669 670 void 671 LaunchDaemon::MessageReceived(BMessage* message) 672 { 673 switch (message->what) { 674 case B_GET_LAUNCH_DATA: 675 { 676 BMessage reply((uint32)B_OK); 677 Job* job = _Job(get_leaf(message->GetString("name"))); 678 if (job == NULL) { 679 reply.what = B_NAME_NOT_FOUND; 680 } else { 681 // If the job has not been launched yet, we'll pass on our 682 // team here. The rationale behind this is that this team 683 // will temporarily own the synchronous reply ports. 684 reply.AddInt32("team", job->Team() < 0 685 ? current_team() : job->Team()); 686 687 PortMap::const_iterator iterator = job->Ports().begin(); 688 for (; iterator != job->Ports().end(); iterator++) { 689 BString name; 690 if (iterator->second.HasString("name")) 691 name << iterator->second.GetString("name") << "_"; 692 name << "port"; 693 694 reply.AddInt32(name.String(), 695 iterator->second.GetInt32("port", -1)); 696 } 697 698 _AddLaunchJob(job); 699 } 700 message->SendReply(&reply); 701 break; 702 } 703 704 default: 705 BServer::MessageReceived(message); 706 break; 707 } 708 } 709 710 711 void 712 LaunchDaemon::_ReadPaths(const BStringList& paths) 713 { 714 for (int32 i = 0; i < paths.CountStrings(); i++) { 715 BEntry entry(paths.StringAt(i)); 716 if (entry.InitCheck() != B_OK || !entry.Exists()) 717 continue; 718 719 _ReadDirectory(NULL, entry); 720 } 721 } 722 723 724 void 725 LaunchDaemon::_ReadEntry(const char* context, BEntry& entry) 726 { 727 if (entry.IsDirectory()) 728 _ReadDirectory(context, entry); 729 else 730 _ReadFile(context, entry); 731 } 732 733 734 void 735 LaunchDaemon::_ReadDirectory(const char* context, BEntry& directoryEntry) 736 { 737 BDirectory directory(&directoryEntry); 738 739 BEntry entry; 740 while (directory.GetNextEntry(&entry) == B_OK) { 741 _ReadEntry(context, entry); 742 } 743 } 744 745 746 status_t 747 LaunchDaemon::_ReadFile(const char* context, BEntry& entry) 748 { 749 DriverSettingsMessageAdapter adapter; 750 751 BPath path; 752 status_t status = path.SetTo(&entry); 753 if (status != B_OK) 754 return status; 755 756 BMessage message; 757 status = adapter.ConvertFromDriverSettings(path.Path(), kSettingsTemplate, 758 message); 759 if (status == B_OK) { 760 message.PrintToStream(); 761 BMessage job; 762 for (int32 index = 0; message.FindMessage("service", index, 763 &job) == B_OK; index++) { 764 _AddJob(true, job); 765 } 766 767 for (int32 index = 0; message.FindMessage("job", index, &job) == B_OK; 768 index++) { 769 _AddJob(false, job); 770 } 771 } 772 773 return status; 774 } 775 776 777 void 778 LaunchDaemon::_AddJob(bool service, BMessage& message) 779 { 780 const char* name = message.GetString("name"); 781 if (name == NULL || name[0] == '\0') { 782 // Invalid job description 783 return; 784 } 785 786 Job* job = _Job(name); 787 if (job == NULL) 788 job = new Job(name); 789 790 job->SetEnabled(!message.GetBool("disabled", !job->IsEnabled())); 791 job->SetService(service); 792 job->SetCreateDefaultPort(!message.GetBool("legacy", !service)); 793 job->SetLaunchInSafeMode( 794 !message.GetBool("no_safemode", !job->LaunchInSafeMode())); 795 796 BMessage portMessage; 797 for (int32 index = 0; 798 message.FindMessage("port", index, &portMessage) == B_OK; index++) { 799 job->AddPort(portMessage); 800 } 801 802 const char* argument; 803 for (int32 index = 0; 804 message.FindString("launch", index, &argument) == B_OK; index++) { 805 job->AddArgument(argument); 806 } 807 808 const char* requirement; 809 for (int32 index = 0; 810 message.FindString("requires", index, &requirement) == B_OK; 811 index++) { 812 job->AddRequirement(requirement); 813 } 814 815 fJobs.insert(std::pair<BString, Job*>(job->Name(), job)); 816 } 817 818 819 Job* 820 LaunchDaemon::_Job(const char* name) 821 { 822 if (name == NULL) 823 return NULL; 824 825 JobMap::const_iterator found = fJobs.find(BString(name).ToLower()); 826 if (found != fJobs.end()) 827 return found->second; 828 829 return NULL; 830 } 831 832 833 void 834 LaunchDaemon::_InitJobs() 835 { 836 for (JobMap::iterator iterator = fJobs.begin(); iterator != fJobs.end(); 837 iterator++) { 838 Job* job = iterator->second; 839 if (job->IsEnabled() && (!_IsSafeMode() || job->LaunchInSafeMode())) 840 job->Init(); 841 } 842 } 843 844 845 void 846 LaunchDaemon::_LaunchJobs() 847 { 848 for (JobMap::iterator iterator = fJobs.begin(); iterator != fJobs.end(); 849 iterator++) { 850 Job* job = iterator->second; 851 if (job->IsEnabled() && job->InitCheck() == B_OK) 852 _AddLaunchJob(job); 853 } 854 } 855 856 857 LaunchJob* 858 LaunchDaemon::_AddLaunchJob(Job* job) 859 { 860 if (job->IsLaunched()) 861 return NULL; 862 863 LaunchJob* launchJob = new LaunchJob(job); 864 865 // All jobs depend on the init target 866 if (fInitTarget->State() < B_JOB_STATE_SUCCEEDED) 867 launchJob->AddDependency(fInitTarget); 868 869 for (int32 index = 0; index < job->Requirements().CountStrings(); index++) { 870 Job* dependency = _Job(job->Requirements().StringAt(index)); 871 if (dependency != NULL) { 872 // Create launch job 873 // TODO: detect circular dependencies! 874 LaunchJob* dependentLaunchJob = _AddLaunchJob(dependency); 875 if (dependentLaunchJob != NULL) 876 launchJob->AddDependency(dependentLaunchJob); 877 } 878 } 879 880 fJobQueue.AddJob(launchJob); 881 return launchJob; 882 } 883 884 885 void 886 LaunchDaemon::_RetrieveKernelOptions() 887 { 888 char buffer[32]; 889 size_t size = sizeof(buffer); 890 status_t status = _kern_get_safemode_option(B_SAFEMODE_SAFE_MODE, buffer, 891 &size); 892 if (status == B_OK) { 893 fSafeMode = !strncasecmp(buffer, "true", size) 894 || !strncasecmp(buffer, "yes", size) 895 || !strncasecmp(buffer, "on", size) 896 || !strncasecmp(buffer, "enabled", size); 897 } else 898 fSafeMode = false; 899 } 900 901 902 void 903 LaunchDaemon::_SetupEnvironment() 904 { 905 // Determine safemode kernel option 906 BString safemode = "SAFEMODE="; 907 safemode << _IsSafeMode() ? "yes" : "no"; 908 909 putenv(safemode.String()); 910 } 911 912 913 /*! Basic system initialization that must happen before any jobs are launched. 914 */ 915 void 916 LaunchDaemon::_InitSystem() 917 { 918 _AddInitJob(new InitRealTimeClockJob()); 919 _AddInitJob(new InitSharedMemoryDirectoryJob()); 920 _AddInitJob(new InitTemporaryDirectoryJob()); 921 922 fJobQueue.AddJob(fInitTarget); 923 } 924 925 926 void 927 LaunchDaemon::_AddInitJob(BJob* job) 928 { 929 fInitTarget->AddDependency(job); 930 fJobQueue.AddJob(job); 931 } 932 933 934 bool 935 LaunchDaemon::_IsSafeMode() const 936 { 937 return fSafeMode; 938 } 939 940 941 // #pragma mark - 942 943 944 int 945 main() 946 { 947 // TODO: remove this again 948 close(STDOUT_FILENO); 949 int fd = open("/dev/dprintf", O_WRONLY); 950 if (fd != STDOUT_FILENO) 951 dup2(fd, STDOUT_FILENO); 952 puts("launch_daemon is alive and kicking."); 953 fflush(stdout); 954 955 status_t status; 956 LaunchDaemon* daemon = new LaunchDaemon(status); 957 if (status == B_OK) 958 daemon->Run(); 959 960 delete daemon; 961 return status == B_OK ? EXIT_SUCCESS : EXIT_FAILURE; 962 } 963