Serenity Operating System
at master 421 lines 14 kB view raw
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}