Serenity Operating System
1/*
2 * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "Service.h"
28#include <AK/HashMap.h>
29#include <AK/JsonArray.h>
30#include <AK/JsonObject.h>
31#include <LibCore/ConfigFile.h>
32#include <LibCore/LocalSocket.h>
33#include <fcntl.h>
34#include <grp.h>
35#include <libgen.h>
36#include <pwd.h>
37#include <sched.h>
38#include <stdio.h>
39#include <sys/ioctl.h>
40#include <sys/stat.h>
41#include <unistd.h>
42
43struct UidAndGids {
44 uid_t uid;
45 gid_t gid;
46 Vector<gid_t> extra_gids;
47};
48
49static HashMap<String, UidAndGids>* s_user_map;
50static HashMap<pid_t, Service*> s_service_map;
51
52void Service::resolve_user()
53{
54 if (s_user_map == nullptr) {
55 s_user_map = new HashMap<String, UidAndGids>;
56 for (struct passwd* passwd = getpwent(); passwd; passwd = getpwent()) {
57 Vector<gid_t> extra_gids;
58 for (struct group* group = getgrent(); group; group = getgrent()) {
59 for (size_t m = 0; group->gr_mem[m]; ++m) {
60 if (!strcmp(group->gr_mem[m], passwd->pw_name))
61 extra_gids.append(group->gr_gid);
62 }
63 }
64 endgrent();
65 s_user_map->set(passwd->pw_name, { passwd->pw_uid, passwd->pw_gid, move(extra_gids) });
66 }
67 endpwent();
68 }
69
70 auto user = s_user_map->get(m_user);
71 if (!user.has_value()) {
72 dbg() << "Failed to resolve user name " << m_user;
73 ASSERT_NOT_REACHED();
74 }
75 m_uid = user.value().uid;
76 m_gid = user.value().gid;
77 m_extra_gids = user.value().extra_gids;
78}
79
80Service* Service::find_by_pid(pid_t pid)
81{
82 auto it = s_service_map.find(pid);
83 if (it == s_service_map.end())
84 return nullptr;
85 return (*it).value;
86}
87
88static int ensure_parent_directories(const char* path)
89{
90 ASSERT(path[0] == '/');
91
92 char* parent_buffer = strdup(path);
93 const char* parent = dirname(parent_buffer);
94
95 int rc = 0;
96 while (true) {
97 int rc = mkdir(parent, 0755);
98
99 if (rc == 0)
100 break;
101
102 if (errno != ENOENT)
103 break;
104
105 ensure_parent_directories(parent);
106 };
107
108 free(parent_buffer);
109 return rc;
110}
111
112void Service::setup_socket()
113{
114 ASSERT(!m_socket_path.is_null());
115 ASSERT(m_socket_fd == -1);
116
117 ensure_parent_directories(m_socket_path.characters());
118
119 // Note: we use SOCK_CLOEXEC here to make sure we don't leak every socket to
120 // all the clients. We'll make the one we do need to pass down !CLOEXEC later
121 // after forking off the process.
122 m_socket_fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
123 if (m_socket_fd < 0) {
124 perror("socket");
125 ASSERT_NOT_REACHED();
126 }
127
128 if (fchown(m_socket_fd, m_uid, m_gid) < 0) {
129 perror("fchown");
130 ASSERT_NOT_REACHED();
131 }
132
133 if (fchmod(m_socket_fd, m_socket_permissions) < 0) {
134 perror("fchmod");
135 ASSERT_NOT_REACHED();
136 }
137
138 auto socket_address = Core::SocketAddress::local(m_socket_path);
139 auto un = socket_address.to_sockaddr_un();
140 int rc = bind(m_socket_fd, (const sockaddr*)&un, sizeof(un));
141 if (rc < 0) {
142 perror("bind");
143 ASSERT_NOT_REACHED();
144 }
145
146 rc = listen(m_socket_fd, 5);
147 if (rc < 0) {
148 perror("listen");
149 ASSERT_NOT_REACHED();
150 }
151}
152
153void Service::setup_notifier()
154{
155 ASSERT(m_lazy);
156 ASSERT(m_socket_fd >= 0);
157 ASSERT(!m_socket_notifier);
158
159 m_socket_notifier = Core::Notifier::construct(m_socket_fd, Core::Notifier::Event::Read, this);
160 m_socket_notifier->on_ready_to_read = [this] {
161 dbg() << "Ready to read on behalf of " << name();
162 remove_child(*m_socket_notifier);
163 m_socket_notifier = nullptr;
164 spawn();
165 };
166}
167
168void Service::activate()
169{
170 ASSERT(m_pid < 0);
171
172 if (m_lazy)
173 setup_notifier();
174 else
175 spawn();
176}
177
178void Service::spawn()
179{
180 dbg() << "Spawning " << name();
181
182 m_run_timer.start();
183 m_pid = fork();
184
185 if (m_pid < 0) {
186 perror("fork");
187 ASSERT_NOT_REACHED();
188 } else if (m_pid == 0) {
189 // We are the child.
190
191 struct sched_param p;
192 p.sched_priority = m_priority;
193 int rc = sched_setparam(0, &p);
194 if (rc < 0) {
195 perror("sched_setparam");
196 ASSERT_NOT_REACHED();
197 }
198
199 if (!m_stdio_file_path.is_null()) {
200 close(STDIN_FILENO);
201 int fd = open_with_path_length(m_stdio_file_path.characters(), m_stdio_file_path.length(), O_RDWR, 0);
202 ASSERT(fd <= 0);
203 if (fd < 0) {
204 perror("open");
205 ASSERT_NOT_REACHED();
206 }
207 dup2(STDIN_FILENO, STDOUT_FILENO);
208 dup2(STDIN_FILENO, STDERR_FILENO);
209
210 if (isatty(STDIN_FILENO)) {
211 ioctl(STDIN_FILENO, TIOCSCTTY);
212 }
213 } else {
214 if (isatty(STDIN_FILENO)) {
215 ioctl(STDIN_FILENO, TIOCNOTTY);
216 }
217 close(STDIN_FILENO);
218 close(STDOUT_FILENO);
219 close(STDERR_FILENO);
220
221 int fd = open("/dev/null", O_RDWR);
222 ASSERT(fd == STDIN_FILENO);
223 dup2(STDIN_FILENO, STDOUT_FILENO);
224 dup2(STDIN_FILENO, STDERR_FILENO);
225 }
226
227 if (!m_socket_path.is_null()) {
228 ASSERT(m_socket_fd > 2);
229 dup2(m_socket_fd, 3);
230 // The new descriptor is !CLOEXEC here.
231 // This is true even if m_socket_fd == 3.
232 setenv("SOCKET_TAKEOVER", "1", true);
233 }
234
235 if (!m_user.is_null()) {
236 if (setgid(m_gid) < 0 || setgroups(m_extra_gids.size(), m_extra_gids.data()) < 0 || setuid(m_uid) < 0) {
237 dbgprintf("Failed to drop privileges (GID=%u, UID=%u)\n", m_gid, m_uid);
238 exit(1);
239 }
240 }
241
242 char* argv[m_extra_arguments.size() + 2];
243 argv[0] = const_cast<char*>(m_executable_path.characters());
244 for (size_t i = 0; i < m_extra_arguments.size(); i++)
245 argv[i + 1] = const_cast<char*>(m_extra_arguments[i].characters());
246 argv[m_extra_arguments.size() + 1] = nullptr;
247
248 rc = execv(argv[0], argv);
249 perror("exec");
250 ASSERT_NOT_REACHED();
251 } else {
252 // We are the parent.
253 s_service_map.set(m_pid, this);
254 }
255}
256
257void Service::did_exit(int exit_code)
258{
259 ASSERT(m_pid > 0);
260
261 dbg() << "Service " << name() << " has exited with exit code " << exit_code;
262
263 s_service_map.remove(m_pid);
264 m_pid = -1;
265
266 if (!m_keep_alive)
267 return;
268
269 int run_time_in_msec = m_run_timer.elapsed();
270 bool exited_successfully = exit_code == 0;
271
272 if (!exited_successfully && run_time_in_msec < 1000) {
273 switch (m_restart_attempts) {
274 case 0:
275 dbg() << "Trying again";
276 break;
277 case 1:
278 dbg() << "Third time's a charm?";
279 break;
280 default:
281 dbg() << "Giving up on " << name() << ". Good luck!";
282 return;
283 }
284 m_restart_attempts++;
285 }
286
287 activate();
288}
289
290Service::Service(const Core::ConfigFile& config, const StringView& name)
291 : Core::Object(nullptr)
292{
293 ASSERT(config.has_group(name));
294
295 set_name(name);
296 m_executable_path = config.read_entry(name, "Executable", String::format("/bin/%s", this->name().characters()));
297 m_extra_arguments = config.read_entry(name, "Arguments", "").split(' ');
298 m_stdio_file_path = config.read_entry(name, "StdIO");
299
300 String prio = config.read_entry(name, "Priority");
301 if (prio == "low")
302 m_priority = 10;
303 else if (prio == "normal" || prio.is_null())
304 m_priority = 30;
305 else if (prio == "high")
306 m_priority = 50;
307 else
308 ASSERT_NOT_REACHED();
309
310 m_keep_alive = config.read_bool_entry(name, "KeepAlive");
311 m_lazy = config.read_bool_entry(name, "Lazy");
312
313 m_user = config.read_entry(name, "User");
314 if (!m_user.is_null())
315 resolve_user();
316
317 m_socket_path = config.read_entry(name, "Socket");
318 if (!m_socket_path.is_null()) {
319 auto socket_permissions_string = config.read_entry(name, "SocketPermissions", "0600");
320 m_socket_permissions = strtol(socket_permissions_string.characters(), nullptr, 8) & 04777;
321 setup_socket();
322 }
323}
324
325void Service::save_to(JsonObject& json)
326{
327 Core::Object::save_to(json);
328
329 json.set("executable_path", m_executable_path);
330
331 // FIXME: This crashes Inspector.
332 /*
333 JsonArray extra_args;
334 for (String& arg : m_extra_arguments)
335 extra_args.append(arg);
336 json.set("extra_arguments", move(extra_args));
337 */
338
339 json.set("stdio_file_path", m_stdio_file_path);
340 json.set("priority", m_priority);
341 json.set("keep_alive", m_keep_alive);
342 json.set("socket_path", m_socket_path);
343 json.set("socket_permissions", m_socket_permissions);
344 json.set("lazy", m_lazy);
345 json.set("user", m_user);
346 json.set("uid", m_uid);
347 json.set("gid", m_gid);
348
349 if (m_pid > 0)
350 json.set("pid", m_pid);
351 else
352 json.set("pid", nullptr);
353
354 json.set("restart_attempts", m_restart_attempts);
355}