Serenity Operating System
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}