Serenity Operating System
at master 557 lines 20 kB view raw
1/* 2 * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2021, Peter Elliott <pelliott@serenityos.org> 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include "Service.h" 9#include <AK/Assertions.h> 10#include <AK/ByteBuffer.h> 11#include <AK/Debug.h> 12#include <AK/String.h> 13#include <Kernel/API/DeviceEvent.h> 14#include <LibCore/ArgsParser.h> 15#include <LibCore/ConfigFile.h> 16#include <LibCore/DeprecatedFile.h> 17#include <LibCore/DirIterator.h> 18#include <LibCore/Event.h> 19#include <LibCore/EventLoop.h> 20#include <LibCore/System.h> 21#include <LibMain/Main.h> 22#include <errno.h> 23#include <fcntl.h> 24#include <grp.h> 25#include <signal.h> 26#include <stdio.h> 27#include <sys/stat.h> 28#include <sys/sysmacros.h> 29#include <sys/types.h> 30#include <sys/wait.h> 31#include <unistd.h> 32 33DeprecatedString g_system_mode = "graphical"; 34Vector<NonnullRefPtr<Service>> g_services; 35 36// NOTE: This handler ensures that the destructor of g_services is called. 37static void sigterm_handler(int) 38{ 39 exit(0); 40} 41 42static void sigchld_handler(int) 43{ 44 for (;;) { 45 int status = 0; 46 pid_t pid = waitpid(-1, &status, WNOHANG); 47 if (pid < 0) { 48 perror("waitpid"); 49 break; 50 } 51 if (pid == 0) 52 break; 53 54 dbgln_if(SYSTEMSERVER_DEBUG, "Reaped child with pid {}, exit status {}", pid, status); 55 56 Service* service = Service::find_by_pid(pid); 57 if (service == nullptr) { 58 // This can happen for multi-instance services. 59 continue; 60 } 61 62 if (auto result = service->did_exit(status); result.is_error()) 63 dbgln("{}: {}", service->name(), result.release_error()); 64 } 65} 66 67static ErrorOr<void> determine_system_mode() 68{ 69 ArmedScopeGuard declare_text_mode_on_failure([&] { 70 // Note: Only if the mode is not set to self-test, degrade it to text mode. 71 if (g_system_mode != "self-test") 72 g_system_mode = "text"; 73 }); 74 75 auto f = Core::DeprecatedFile::construct("/sys/kernel/constants/system_mode"); 76 if (!f->open(Core::OpenMode::ReadOnly)) { 77 dbgln("Failed to read system_mode: {}", f->error_string()); 78 // Continue and assume "text" mode. 79 return {}; 80 } 81 const DeprecatedString system_mode = DeprecatedString::copy(f->read_all(), Chomp); 82 if (f->error()) { 83 dbgln("Failed to read system_mode: {}", f->error_string()); 84 // Continue and assume "text" mode. 85 return {}; 86 } 87 88 g_system_mode = system_mode; 89 declare_text_mode_on_failure.disarm(); 90 91 dbgln("Read system_mode: {}", g_system_mode); 92 93 struct stat file_state; 94 int rc = lstat("/dev/gpu/connector0", &file_state); 95 if (rc != 0 && g_system_mode == "graphical") { 96 dbgln("WARNING: No device nodes at /dev/gpu/ directory. This is probably a sign of disabled graphics functionality."); 97 dbgln("To cope with this, I'll turn off graphical mode."); 98 g_system_mode = "text"; 99 } 100 return {}; 101} 102 103static ErrorOr<void> chown_all_matching_device_nodes_under_specific_directory(StringView directory, group const& group) 104{ 105 struct stat cur_file_stat; 106 107 Core::DirIterator di(directory, Core::DirIterator::SkipParentAndBaseDir); 108 if (di.has_error()) 109 VERIFY_NOT_REACHED(); 110 while (di.has_next()) { 111 auto entry_name = di.next_full_path(); 112 auto rc = stat(entry_name.characters(), &cur_file_stat); 113 if (rc < 0) 114 continue; 115 TRY(Core::System::chown(entry_name, 0, group.gr_gid)); 116 } 117 return {}; 118} 119 120static ErrorOr<void> chown_all_matching_device_nodes(group const& group, unsigned major_number) 121{ 122 struct stat cur_file_stat; 123 124 Core::DirIterator di("/dev/", Core::DirIterator::SkipParentAndBaseDir); 125 if (di.has_error()) 126 VERIFY_NOT_REACHED(); 127 while (di.has_next()) { 128 auto entry_name = di.next_full_path(); 129 auto rc = stat(entry_name.characters(), &cur_file_stat); 130 if (rc < 0) 131 continue; 132 if (major(cur_file_stat.st_rdev) != major_number) 133 continue; 134 TRY(Core::System::chown(entry_name, 0, group.gr_gid)); 135 } 136 return {}; 137} 138 139inline char offset_character_with_number(char base_char, u8 offset) 140{ 141 char offsetted_char = base_char; 142 VERIFY(static_cast<size_t>(offsetted_char) + static_cast<size_t>(offset) < 256); 143 offsetted_char += offset; 144 return offsetted_char; 145} 146 147static ErrorOr<void> create_devtmpfs_block_device(StringView name, mode_t mode, unsigned major, unsigned minor) 148{ 149 return Core::System::mknod(name, mode | S_IFBLK, makedev(major, minor)); 150} 151 152static ErrorOr<void> create_devtmpfs_char_device(StringView name, mode_t mode, unsigned major, unsigned minor) 153{ 154 return Core::System::mknod(name, mode | S_IFCHR, makedev(major, minor)); 155} 156 157static ErrorOr<void> populate_devtmpfs_char_devices_based_on_sysfs() 158{ 159 Core::DirIterator di("/sys/dev/char/", Core::DirIterator::SkipParentAndBaseDir); 160 if (di.has_error()) { 161 auto error = di.error(); 162 warnln("Failed to open /sys/dev/char - {}", error); 163 return error; 164 } 165 while (di.has_next()) { 166 auto entry_name = di.next_path().split(':'); 167 VERIFY(entry_name.size() == 2); 168 auto major_number = entry_name[0].to_uint<unsigned>().value(); 169 auto minor_number = entry_name[1].to_uint<unsigned>().value(); 170 switch (major_number) { 171 case 2: { 172 switch (minor_number) { 173 case 10: { 174 TRY(create_devtmpfs_char_device("/dev/devctl"sv, 0660, 2, 10)); 175 break; 176 } 177 default: 178 warnln("Unknown character device {}:{}", major_number, minor_number); 179 } 180 break; 181 } 182 183 default: 184 break; 185 } 186 } 187 return {}; 188} 189 190static ErrorOr<void> populate_devtmpfs_devices_based_on_devctl() 191{ 192 auto f = Core::DeprecatedFile::construct("/dev/devctl"); 193 if (!f->open(Core::OpenMode::ReadOnly)) { 194 warnln("Failed to open /dev/devctl - {}", f->error_string()); 195 VERIFY_NOT_REACHED(); 196 } 197 198 DeviceEvent event; 199 while (f->read((u8*)&event, sizeof(DeviceEvent)) > 0) { 200 if (event.state != DeviceEvent::Inserted) 201 continue; 202 auto major_number = event.major_number; 203 auto minor_number = event.minor_number; 204 bool is_block_device = (event.is_block_device == 1); 205 switch (major_number) { 206 case 116: { 207 if (!is_block_device) { 208 auto name = TRY(String::formatted("/dev/audio/{}", minor_number)); 209 TRY(create_devtmpfs_char_device(name.bytes_as_string_view(), 0220, 116, minor_number)); 210 break; 211 } 212 break; 213 } 214 case 28: { 215 auto name = TRY(String::formatted("/dev/gpu/render{}", minor_number)); 216 TRY(create_devtmpfs_block_device(name.bytes_as_string_view(), 0666, 28, minor_number)); 217 break; 218 } 219 case 226: { 220 auto name = TRY(String::formatted("/dev/gpu/connector{}", minor_number)); 221 TRY(create_devtmpfs_char_device(name.bytes_as_string_view(), 0666, 226, minor_number)); 222 break; 223 } 224 case 229: { 225 if (!is_block_device) { 226 auto name = TRY(String::formatted("/dev/hvc0p{}", minor_number)); 227 TRY(create_devtmpfs_char_device(name.bytes_as_string_view(), 0666, 229, minor_number)); 228 } 229 break; 230 } 231 case 10: { 232 if (!is_block_device) { 233 switch (minor_number) { 234 case 0: { 235 TRY(create_devtmpfs_char_device("/dev/input/mouse/0"sv, 0666, 10, 0)); 236 break; 237 } 238 case 183: { 239 TRY(create_devtmpfs_char_device("/dev/hwrng"sv, 0666, 10, 183)); 240 break; 241 } 242 default: 243 warnln("Unknown character device {}:{}", major_number, minor_number); 244 } 245 } 246 break; 247 } 248 case 85: { 249 if (!is_block_device) { 250 switch (minor_number) { 251 case 0: { 252 TRY(create_devtmpfs_char_device("/dev/input/keyboard/0"sv, 0666, 85, 0)); 253 break; 254 } 255 default: 256 warnln("Unknown character device {}:{}", major_number, minor_number); 257 } 258 } 259 break; 260 } 261 case 1: { 262 if (!is_block_device) { 263 switch (minor_number) { 264 case 5: { 265 TRY(create_devtmpfs_char_device("/dev/zero"sv, 0666, 1, 5)); 266 break; 267 } 268 case 1: { 269 TRY(create_devtmpfs_char_device("/dev/mem"sv, 0666, 1, 1)); 270 break; 271 } 272 case 3: { 273 TRY(create_devtmpfs_char_device("/dev/null"sv, 0666, 1, 3)); 274 break; 275 } 276 case 7: { 277 TRY(create_devtmpfs_char_device("/dev/full"sv, 0666, 1, 7)); 278 break; 279 } 280 case 8: { 281 TRY(create_devtmpfs_char_device("/dev/random"sv, 0666, 1, 8)); 282 break; 283 } 284 default: 285 warnln("Unknown character device {}:{}", major_number, minor_number); 286 break; 287 } 288 } 289 break; 290 } 291 case 30: { 292 if (!is_block_device) { 293 auto name = TRY(String::formatted("/dev/kcov{}", minor_number)); 294 TRY(create_devtmpfs_char_device(name.bytes_as_string_view(), 0666, 30, minor_number)); 295 } 296 break; 297 } 298 case 3: { 299 if (is_block_device) { 300 auto name = TRY(String::formatted("/dev/hd{}", offset_character_with_number('a', minor_number))); 301 TRY(create_devtmpfs_block_device(name.bytes_as_string_view(), 0600, 3, minor_number)); 302 } 303 break; 304 } 305 case 5: { 306 if (!is_block_device) { 307 switch (minor_number) { 308 case 1: { 309 TRY(create_devtmpfs_char_device("/dev/console"sv, 0666, 5, 1)); 310 break; 311 } 312 case 2: { 313 TRY(create_devtmpfs_char_device("/dev/ptmx"sv, 0666, 5, 2)); 314 break; 315 } 316 case 0: { 317 TRY(create_devtmpfs_char_device("/dev/tty"sv, 0666, 5, 0)); 318 break; 319 } 320 default: 321 warnln("Unknown character device {}:{}", major_number, minor_number); 322 } 323 } 324 break; 325 } 326 case 4: { 327 if (!is_block_device) { 328 switch (minor_number) { 329 case 0: { 330 TRY(create_devtmpfs_char_device("/dev/tty0"sv, 0620, 4, 0)); 331 break; 332 } 333 case 1: { 334 TRY(create_devtmpfs_char_device("/dev/tty1"sv, 0620, 4, 1)); 335 break; 336 } 337 case 2: { 338 TRY(create_devtmpfs_char_device("/dev/tty2"sv, 0620, 4, 2)); 339 break; 340 } 341 case 3: { 342 TRY(create_devtmpfs_char_device("/dev/tty3"sv, 0620, 4, 3)); 343 break; 344 } 345 case 64: { 346 TRY(create_devtmpfs_char_device("/dev/ttyS0"sv, 0620, 4, 64)); 347 break; 348 } 349 case 65: { 350 TRY(create_devtmpfs_char_device("/dev/ttyS1"sv, 0620, 4, 65)); 351 break; 352 } 353 case 66: { 354 TRY(create_devtmpfs_char_device("/dev/ttyS2"sv, 0620, 4, 66)); 355 break; 356 } 357 case 67: { 358 TRY(create_devtmpfs_char_device("/dev/ttyS3"sv, 0666, 4, 67)); 359 break; 360 } 361 default: 362 warnln("Unknown character device {}:{}", major_number, minor_number); 363 } 364 } 365 break; 366 } 367 default: 368 if (!is_block_device) 369 warnln("Unknown character device {}:{}", major_number, minor_number); 370 else 371 warnln("Unknown block device {}:{}", major_number, minor_number); 372 break; 373 } 374 } 375 return {}; 376} 377 378static ErrorOr<void> populate_devtmpfs() 379{ 380 mode_t old_mask = umask(0); 381 printf("Changing umask %#o\n", old_mask); 382 TRY(populate_devtmpfs_char_devices_based_on_sysfs()); 383 TRY(populate_devtmpfs_devices_based_on_devctl()); 384 umask(old_mask); 385 return {}; 386} 387 388static ErrorOr<void> prepare_synthetic_filesystems() 389{ 390 // FIXME: Don't hardcode the fs type as the ext2 filesystem and once there's 391 // more than this filesystem implementation (which is suitable for usage on 392 // physical storage), find a way to detect it. 393 TRY(Core::System::mount(-1, "/"sv, "ext2"sv, MS_REMOUNT | MS_NODEV | MS_NOSUID | MS_RDONLY)); 394 // FIXME: Find a better way to all of this stuff, without hardcoding all of this! 395 TRY(Core::System::mount(-1, "/proc"sv, "proc"sv, MS_NOSUID)); 396 TRY(Core::System::mount(-1, "/sys"sv, "sys"sv, 0)); 397 TRY(Core::System::mount(-1, "/dev"sv, "ram"sv, MS_NOSUID | MS_NOEXEC | MS_NOREGULAR)); 398 399 TRY(Core::System::mount(-1, "/tmp"sv, "ram"sv, MS_NOSUID | MS_NODEV)); 400 // NOTE: Set /tmp to have a sticky bit with 0777 permissions. 401 TRY(Core::System::chmod("/tmp"sv, 01777)); 402 403 TRY(Core::System::mkdir("/dev/audio"sv, 0755)); 404 TRY(Core::System::mkdir("/dev/input"sv, 0755)); 405 TRY(Core::System::mkdir("/dev/input/keyboard"sv, 0755)); 406 TRY(Core::System::mkdir("/dev/input/mouse"sv, 0755)); 407 408 TRY(Core::System::symlink("/proc/self/fd/0"sv, "/dev/stdin"sv)); 409 TRY(Core::System::symlink("/proc/self/fd/1"sv, "/dev/stdout"sv)); 410 TRY(Core::System::symlink("/proc/self/fd/2"sv, "/dev/stderr"sv)); 411 412 TRY(Core::System::mkdir("/dev/gpu"sv, 0755)); 413 414 TRY(populate_devtmpfs()); 415 416 TRY(Core::System::mkdir("/dev/pts"sv, 0755)); 417 418 TRY(Core::System::mount(-1, "/dev/pts"sv, "devpts"sv, 0)); 419 420 TRY(Core::System::symlink("/dev/random"sv, "/dev/urandom"sv)); 421 422 TRY(Core::System::chmod("/dev/urandom"sv, 0666)); 423 424 auto phys_group = TRY(Core::System::getgrnam("phys"sv)); 425 VERIFY(phys_group.has_value()); 426 // FIXME: Try to find a way to not hardcode the major number of display connector device nodes. 427 TRY(chown_all_matching_device_nodes(phys_group.value(), 29)); 428 429 auto const filter_chown_ENOENT = [](ErrorOr<void> result) -> ErrorOr<void> { 430 auto const chown_enoent = Error::from_syscall("chown"sv, -ENOENT); 431 if (result.is_error() && result.error() == chown_enoent) { 432 dbgln("{}", result.release_error()); 433 return {}; 434 } 435 return result; 436 }; 437 438 TRY(filter_chown_ENOENT(Core::System::chown("/dev/input/keyboard/0"sv, 0, phys_group.value().gr_gid))); 439 TRY(filter_chown_ENOENT(Core::System::chown("/dev/input/mouse/0"sv, 0, phys_group.value().gr_gid))); 440 441 auto tty_group = TRY(Core::System::getgrnam("tty"sv)); 442 VERIFY(tty_group.has_value()); 443 // FIXME: Try to find a way to not hardcode the major number of tty nodes. 444 TRY(chown_all_matching_device_nodes(tty_group.release_value(), 4)); 445 446 auto audio_group = TRY(Core::System::getgrnam("audio"sv)); 447 VERIFY(audio_group.has_value()); 448 TRY(Core::System::chown("/dev/audio"sv, 0, audio_group->gr_gid)); 449 TRY(chown_all_matching_device_nodes_under_specific_directory("/dev/audio"sv, audio_group.release_value())); 450 451 // Note: We open the /dev/null device and set file descriptors 0, 1, 2 to it 452 // because otherwise these file descriptors won't have a custody, making 453 // the ProcFS file descriptor links (at /proc/PID/fd/{0,1,2}) to have an 454 // absolute path of "device:1,3" instead of something like "/dev/null". 455 // This affects also every other process that inherits the file descriptors 456 // from SystemServer, so it is important for other things (also for ProcFS 457 // tests that are running in CI mode). 458 int stdin_new_fd = TRY(Core::System::open("/dev/null"sv, O_NONBLOCK)); 459 460 TRY(Core::System::dup2(stdin_new_fd, 0)); 461 TRY(Core::System::dup2(stdin_new_fd, 1)); 462 TRY(Core::System::dup2(stdin_new_fd, 2)); 463 464 TRY(Core::System::endgrent()); 465 return {}; 466} 467 468static ErrorOr<void> mount_all_filesystems() 469{ 470 dbgln("Spawning mount -a to mount all filesystems."); 471 pid_t pid = TRY(Core::System::fork()); 472 473 if (pid == 0) 474 TRY(Core::System::exec("/bin/mount"sv, Vector { "mount"sv, "-a"sv }, Core::System::SearchInPath::No)); 475 476 wait(nullptr); 477 return {}; 478} 479 480static ErrorOr<void> create_tmp_coredump_directory() 481{ 482 dbgln("Creating /tmp/coredump directory"); 483 auto old_umask = umask(0); 484 // FIXME: the coredump directory should be made read-only once CrashDaemon is no longer responsible for compressing coredumps 485 TRY(Core::System::mkdir("/tmp/coredump"sv, 0777)); 486 umask(old_umask); 487 return {}; 488} 489 490static ErrorOr<void> set_default_coredump_directory() 491{ 492 dbgln("Setting /tmp/coredump as the coredump directory"); 493 auto sysfs_coredump_directory_variable_fd = TRY(Core::System::open("/sys/kernel/variables/coredump_directory"sv, O_RDWR)); 494 ScopeGuard close_on_exit([&] { 495 close(sysfs_coredump_directory_variable_fd); 496 }); 497 auto tmp_coredump_directory_path = "/tmp/coredump"sv; 498 auto nwritten = TRY(Core::System::write(sysfs_coredump_directory_variable_fd, tmp_coredump_directory_path.bytes())); 499 VERIFY(static_cast<size_t>(nwritten) == tmp_coredump_directory_path.length()); 500 return {}; 501} 502 503static ErrorOr<void> create_tmp_semaphore_directory() 504{ 505 dbgln("Creating /tmp/semaphore directory"); 506 auto old_umask = umask(0); 507 TRY(Core::System::mkdir("/tmp/semaphore"sv, 0777)); 508 umask(old_umask); 509 return {}; 510} 511 512ErrorOr<int> serenity_main(Main::Arguments arguments) 513{ 514 bool user = false; 515 Core::ArgsParser args_parser; 516 args_parser.add_option(user, "Run in user-mode", "user", 'u'); 517 args_parser.parse(arguments); 518 519 if (!user) { 520 TRY(mount_all_filesystems()); 521 TRY(prepare_synthetic_filesystems()); 522 } 523 524 TRY(Core::System::pledge("stdio proc exec tty accept unix rpath wpath cpath chown fattr id sigaction")); 525 526 if (!user) { 527 TRY(create_tmp_coredump_directory()); 528 TRY(set_default_coredump_directory()); 529 TRY(create_tmp_semaphore_directory()); 530 TRY(determine_system_mode()); 531 } 532 533 Core::EventLoop event_loop; 534 535 event_loop.register_signal(SIGCHLD, sigchld_handler); 536 event_loop.register_signal(SIGTERM, sigterm_handler); 537 538 // Read our config and instantiate services. 539 // This takes care of setting up sockets. 540 auto config = (user) 541 ? TRY(Core::ConfigFile::open_for_app("SystemServer")) 542 : TRY(Core::ConfigFile::open_for_system("SystemServer")); 543 for (auto const& name : config->groups()) { 544 auto service = TRY(Service::try_create(*config, name)); 545 if (service->is_enabled()) 546 g_services.append(move(service)); 547 } 548 549 // After we've set them all up, activate them! 550 dbgln("Activating {} services...", g_services.size()); 551 for (auto& service : g_services) { 552 if (auto result = service->activate(); result.is_error()) 553 dbgln("{}: {}", service->name(), result.release_error()); 554 } 555 556 return event_loop.exec(); 557}