Serenity Operating System
1/*
2 * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include "Service.h"
8#include <AK/Debug.h>
9#include <AK/HashMap.h>
10#include <AK/JsonArray.h>
11#include <AK/JsonObject.h>
12#include <AK/String.h>
13#include <AK/StringBuilder.h>
14#include <LibCore/ConfigFile.h>
15#include <LibCore/DeprecatedFile.h>
16#include <LibCore/Directory.h>
17#include <LibCore/SessionManagement.h>
18#include <LibCore/SocketAddress.h>
19#include <LibCore/System.h>
20#include <fcntl.h>
21#include <sched.h>
22#include <stdio.h>
23#include <sys/ioctl.h>
24#include <unistd.h>
25
26static HashMap<pid_t, Service*> s_service_map;
27
28Service* Service::find_by_pid(pid_t pid)
29{
30 auto it = s_service_map.find(pid);
31 if (it == s_service_map.end())
32 return nullptr;
33 return (*it).value;
34}
35
36ErrorOr<void> Service::setup_socket(SocketDescriptor& socket)
37{
38 VERIFY(socket.fd == -1);
39
40 // Note: The purpose of this syscall is to remove potential left-over of previous portal.
41 // The return value is discarded as sockets are not always there, and unlinking a non-existent path is considered as a failure.
42 (void)Core::System::unlink(socket.path);
43
44 TRY(Core::Directory::create(LexicalPath(socket.path).parent(), Core::Directory::CreateDirectories::Yes));
45
46 // Note: we use SOCK_CLOEXEC here to make sure we don't leak every socket to
47 // all the clients. We'll make the one we do need to pass down !CLOEXEC later
48 // after forking off the process.
49 int const socket_fd = TRY(Core::System::socket(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
50 socket.fd = socket_fd;
51
52 if (m_account.has_value()) {
53 auto& account = m_account.value();
54 TRY(Core::System::fchown(socket_fd, account.uid(), account.gid()));
55 }
56
57 TRY(Core::System::fchmod(socket_fd, socket.permissions));
58
59 auto socket_address = Core::SocketAddress::local(socket.path);
60 auto un_optional = socket_address.to_sockaddr_un();
61 if (!un_optional.has_value()) {
62 dbgln("Socket name {} is too long. BUG! This should have failed earlier!", socket.path);
63 VERIFY_NOT_REACHED();
64 }
65 auto un = un_optional.value();
66
67 TRY(Core::System::bind(socket_fd, (sockaddr const*)&un, sizeof(un)));
68 TRY(Core::System::listen(socket_fd, 16));
69 return {};
70}
71
72ErrorOr<void> Service::setup_sockets()
73{
74 for (SocketDescriptor& socket : m_sockets)
75 TRY(setup_socket(socket));
76 return {};
77}
78
79void Service::setup_notifier()
80{
81 VERIFY(m_lazy);
82 VERIFY(m_sockets.size() == 1);
83 VERIFY(!m_socket_notifier);
84
85 m_socket_notifier = Core::Notifier::construct(m_sockets[0].fd, Core::Notifier::Event::Read, this);
86 m_socket_notifier->on_ready_to_read = [this] {
87 if (auto result = handle_socket_connection(); result.is_error())
88 dbgln("{}", result.release_error());
89 };
90}
91
92ErrorOr<void> Service::handle_socket_connection()
93{
94 VERIFY(m_sockets.size() == 1);
95 dbgln_if(SERVICE_DEBUG, "Ready to read on behalf of {}", name());
96
97 int socket_fd = m_sockets[0].fd;
98
99 if (m_accept_socket_connections) {
100 auto const accepted_fd = TRY(Core::System::accept(socket_fd, nullptr, nullptr));
101
102 TRY(determine_account(accepted_fd));
103 TRY(spawn(accepted_fd));
104 TRY(Core::System::close(accepted_fd));
105 } else {
106 remove_child(*m_socket_notifier);
107 m_socket_notifier = nullptr;
108 TRY(spawn(socket_fd));
109 }
110 return {};
111}
112
113ErrorOr<void> Service::activate()
114{
115 VERIFY(m_pid < 0);
116
117 if (m_lazy)
118 setup_notifier();
119 else
120 TRY(spawn());
121 return {};
122}
123
124ErrorOr<void> Service::spawn(int socket_fd)
125{
126 if (!Core::DeprecatedFile::exists(m_executable_path)) {
127 dbgln("{}: binary \"{}\" does not exist, skipping service.", name(), m_executable_path);
128 return Error::from_errno(ENOENT);
129 }
130
131 dbgln_if(SERVICE_DEBUG, "Spawning {}", name());
132
133 m_run_timer.start();
134 pid_t pid = TRY(Core::System::fork());
135
136 if (pid == 0) {
137 // We are the child.
138 if (!m_working_directory.is_null())
139 TRY(Core::System::chdir(m_working_directory));
140
141 struct sched_param p;
142 p.sched_priority = m_priority;
143 int rc = sched_setparam(0, &p);
144 if (rc < 0) {
145 perror("sched_setparam");
146 VERIFY_NOT_REACHED();
147 }
148
149 if (!m_stdio_file_path.is_null()) {
150 close(STDIN_FILENO);
151 auto const fd = TRY(Core::System::open(m_stdio_file_path, O_RDWR, 0));
152 VERIFY(fd == 0);
153
154 dup2(STDIN_FILENO, STDOUT_FILENO);
155 dup2(STDIN_FILENO, STDERR_FILENO);
156
157 if (isatty(STDIN_FILENO)) {
158 ioctl(STDIN_FILENO, TIOCSCTTY);
159 }
160 } else {
161 if (isatty(STDIN_FILENO)) {
162 ioctl(STDIN_FILENO, TIOCNOTTY);
163 }
164 close(STDIN_FILENO);
165 close(STDOUT_FILENO);
166 close(STDERR_FILENO);
167
168 auto const fd = TRY(Core::System::open("/dev/null"sv, O_RDWR));
169 VERIFY(fd == STDIN_FILENO);
170 dup2(STDIN_FILENO, STDOUT_FILENO);
171 dup2(STDIN_FILENO, STDERR_FILENO);
172 }
173
174 StringBuilder socket_takeover_builder;
175
176 if (socket_fd >= 0) {
177 // We were spawned by socket activation. We currently only support
178 // single sockets for socket activation, so make sure that's the case.
179 VERIFY(m_sockets.size() == 1);
180
181 int fd = dup(socket_fd);
182 TRY(socket_takeover_builder.try_appendff("{}:{}", m_sockets[0].path, fd));
183 } else {
184 // We were spawned as a regular process, so dup every socket for this
185 // service and let the service know via SOCKET_TAKEOVER.
186 for (unsigned i = 0; i < m_sockets.size(); i++) {
187 SocketDescriptor& socket = m_sockets.at(i);
188
189 int new_fd = dup(socket.fd);
190 if (i != 0)
191 TRY(socket_takeover_builder.try_append(';'));
192 TRY(socket_takeover_builder.try_appendff("{}:{}", socket.path, new_fd));
193 }
194 }
195
196 if (!m_sockets.is_empty()) {
197 // The new descriptor is !CLOEXEC here.
198 TRY(Core::System::setenv("SOCKET_TAKEOVER"sv, socket_takeover_builder.string_view(), true));
199 }
200
201 if (m_account.has_value() && m_account.value().uid() != getuid()) {
202 auto& account = m_account.value();
203 if (account.login().is_error()) {
204 dbgln("Failed to drop privileges (GID={}, UID={})\n", account.gid(), account.uid());
205 exit(1);
206 }
207 TRY(Core::System::setenv("HOME"sv, account.home_directory(), true));
208 }
209
210 TRY(m_environment.view().for_each_split_view(' ', SplitBehavior::Nothing, [&](auto env) {
211 return Core::System::putenv(env);
212 }));
213
214 Vector<StringView, 10> arguments;
215 TRY(arguments.try_append(m_executable_path));
216 TRY(m_extra_arguments.view().for_each_split_view(' ', SplitBehavior::Nothing, [&](auto arg) {
217 return arguments.try_append(arg);
218 }));
219
220 TRY(Core::System::exec(m_executable_path, arguments, Core::System::SearchInPath::No));
221 } else if (!m_multi_instance) {
222 // We are the parent.
223 m_pid = pid;
224 s_service_map.set(pid, this);
225 }
226
227 return {};
228}
229
230ErrorOr<void> Service::did_exit(int exit_code)
231{
232 using namespace AK::TimeLiterals;
233
234 VERIFY(m_pid > 0);
235 VERIFY(!m_multi_instance);
236
237 dbgln("Service {} has exited with exit code {}", name(), exit_code);
238
239 s_service_map.remove(m_pid);
240 m_pid = -1;
241
242 if (!m_keep_alive)
243 return {};
244
245 auto run_time = m_run_timer.elapsed_time();
246 bool exited_successfully = exit_code == 0;
247
248 if (!exited_successfully && run_time < 1_sec) {
249 switch (m_restart_attempts) {
250 case 0:
251 dbgln("Trying again");
252 break;
253 case 1:
254 dbgln("Third time's the charm?");
255 break;
256 default:
257 dbgln("Giving up on {}. Good luck!", name());
258 return {};
259 }
260 m_restart_attempts++;
261 }
262
263 TRY(activate());
264 return {};
265}
266
267Service::Service(Core::ConfigFile const& config, StringView name)
268 : Core::Object(nullptr)
269{
270 VERIFY(config.has_group(name));
271
272 set_name(name);
273 m_executable_path = config.read_entry(name, "Executable", DeprecatedString::formatted("/bin/{}", this->name()));
274 m_extra_arguments = config.read_entry(name, "Arguments", "");
275 m_stdio_file_path = config.read_entry(name, "StdIO");
276
277 DeprecatedString prio = config.read_entry(name, "Priority");
278 if (prio == "low")
279 m_priority = 10;
280 else if (prio == "normal" || prio.is_null())
281 m_priority = 30;
282 else if (prio == "high")
283 m_priority = 50;
284 else
285 VERIFY_NOT_REACHED();
286
287 m_keep_alive = config.read_bool_entry(name, "KeepAlive");
288 m_lazy = config.read_bool_entry(name, "Lazy");
289
290 m_user = config.read_entry(name, "User");
291 if (!m_user.is_null()) {
292 auto result = Core::Account::from_name(m_user, Core::Account::Read::PasswdOnly);
293 if (result.is_error())
294 warnln("Failed to resolve user {}: {}", m_user, result.error());
295 else
296 m_account = result.value();
297 }
298
299 m_working_directory = config.read_entry(name, "WorkingDirectory");
300 m_environment = config.read_entry(name, "Environment");
301 m_system_modes = config.read_entry(name, "SystemModes", "graphical").split(',');
302 m_multi_instance = config.read_bool_entry(name, "MultiInstance");
303 m_accept_socket_connections = config.read_bool_entry(name, "AcceptSocketConnections");
304
305 DeprecatedString socket_entry = config.read_entry(name, "Socket");
306 DeprecatedString socket_permissions_entry = config.read_entry(name, "SocketPermissions", "0600");
307
308 if (!socket_entry.is_null()) {
309 Vector<DeprecatedString> socket_paths = socket_entry.split(',');
310 Vector<DeprecatedString> socket_perms = socket_permissions_entry.split(',');
311 m_sockets.ensure_capacity(socket_paths.size());
312
313 // Need i here to iterate along with all other vectors.
314 for (unsigned i = 0; i < socket_paths.size(); i++) {
315 auto const path = Core::SessionManagement::parse_path_with_sid(socket_paths.at(i));
316 if (path.is_error()) {
317 // FIXME: better error handling for this case.
318 TODO();
319 }
320
321 // Socket path (plus NUL) must fit into the structs sent to the Kernel.
322 VERIFY(path.value().length() < UNIX_PATH_MAX);
323
324 // This is done so that the last permission repeats for every other
325 // socket. So you can define a single permission, and have it
326 // be applied for every socket.
327 mode_t permissions = strtol(socket_perms.at(min(socket_perms.size() - 1, (long unsigned)i)).characters(), nullptr, 8) & 0777;
328
329 m_sockets.empend(path.value(), -1, permissions);
330 }
331 }
332
333 // Lazy requires Socket, but only one.
334 VERIFY(!m_lazy || m_sockets.size() == 1);
335 // AcceptSocketConnections always requires Socket (single), Lazy, and MultiInstance.
336 VERIFY(!m_accept_socket_connections || (m_sockets.size() == 1 && m_lazy && m_multi_instance));
337 // MultiInstance doesn't work with KeepAlive.
338 VERIFY(!m_multi_instance || !m_keep_alive);
339}
340
341ErrorOr<NonnullRefPtr<Service>> Service::try_create(Core::ConfigFile const& config, StringView name)
342{
343 auto service = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) Service(config, name)));
344 if (service->is_enabled())
345 TRY(service->setup_sockets());
346 return service;
347}
348
349void Service::save_to(JsonObject& json)
350{
351 Core::Object::save_to(json);
352
353 json.set("executable_path", m_executable_path);
354
355 // FIXME: This crashes Inspector.
356 /*
357 JsonArray extra_args;
358 for (String& arg : m_extra_arguments)
359 extra_args.append(arg);
360 json.set("extra_arguments", move(extra_args));
361
362 JsonArray system_modes;
363 for (String& mode : m_system_modes)
364 system_modes.append(mode);
365 json.set("system_modes", system_modes);
366
367 JsonArray environment;
368 for (String& env : m_environment)
369 system_modes.append(env);
370 json.set("environment", environment);
371
372 JsonArray sockets;
373 for (SocketDescriptor &socket : m_sockets) {
374 JsonObject socket_obj;
375 socket_obj.set("path", socket.path);
376 socket_obj.set("permissions", socket.permissions);
377 sockets.append(socket);
378 }
379 json.set("sockets", sockets);
380 */
381
382 json.set("stdio_file_path", m_stdio_file_path);
383 json.set("priority", m_priority);
384 json.set("keep_alive", m_keep_alive);
385 json.set("lazy", m_lazy);
386 json.set("user", m_user);
387 json.set("multi_instance", m_multi_instance);
388 json.set("accept_socket_connections", m_accept_socket_connections);
389
390 if (m_pid > 0)
391 json.set("pid", m_pid);
392 else
393 json.set("pid", nullptr);
394
395 json.set("restart_attempts", m_restart_attempts);
396 json.set("working_directory", m_working_directory);
397}
398
399bool Service::is_enabled() const
400{
401 extern DeprecatedString g_system_mode;
402 return m_system_modes.contains_slow(g_system_mode);
403}
404
405ErrorOr<void> Service::determine_account(int fd)
406{
407 struct ucred creds = {};
408 socklen_t creds_size = sizeof(creds);
409 TRY(Core::System::getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &creds, &creds_size));
410
411 m_account = TRY(Core::Account::from_uid(creds.uid, Core::Account::Read::PasswdOnly));
412 return {};
413}
414
415Service::~Service()
416{
417 for (auto& socket : m_sockets) {
418 if (auto rc = remove(socket.path.characters()); rc != 0)
419 dbgln("{}", Error::from_errno(errno));
420 }
421}