this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Spawn shells via launchd daemon

+936 -171
+1
src/CMakeLists.txt
··· 200 200 add_subdirectory(external/python/2.7/Python-2.7.10) 201 201 add_subdirectory(external/ruby) 202 202 add_subdirectory(external/expat) 203 + add_subdirectory(shellspawn) 203 204 #add_subdirectory(external/libauto) 204 205 add_subdirectory(external/libarchive/libarchive) 205 206 add_subdirectory(external/apr)
+11
src/shellspawn/CMakeLists.txt
··· 1 + project(shellspawn) 2 + 3 + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -nostdinc") 4 + 5 + include(darling_exe) 6 + 7 + add_darling_executable(shellspawn shellspawn.c duct_signals.c) 8 + 9 + install(TARGETS shellspawn DESTINATION libexec/darling/usr/libexec) 10 + install(FILES org.darlinghq.shellspawn.plist DESTINATION libexec/darling/System/Library/LaunchDaemons) 11 +
+71
src/shellspawn/duct_signals.c
··· 1 + #include <sys/signal.h> 2 + #include "duct_signals.h" 3 + 4 + int signum_linux_to_bsd(int signum) 5 + { 6 + switch (signum) 7 + { 8 + case LINUX_SIGHUP: 9 + return SIGHUP; 10 + case LINUX_SIGINT: 11 + return SIGINT; 12 + case LINUX_SIGQUIT: 13 + return SIGQUIT; 14 + case LINUX_SIGILL: 15 + return SIGILL; 16 + case LINUX_SIGTRAP: 17 + return SIGTRAP; 18 + case LINUX_SIGABRT: 19 + return SIGABRT; 20 + case LINUX_SIGFPE: 21 + return SIGFPE; 22 + case LINUX_SIGKILL: 23 + return SIGKILL; 24 + case LINUX_SIGBUS: 25 + return SIGBUS; 26 + case LINUX_SIGSEGV: 27 + return SIGSEGV; 28 + case LINUX_SIGSYS: 29 + return SIGSYS; 30 + case LINUX_SIGPIPE: 31 + return SIGPIPE; 32 + case LINUX_SIGALRM: 33 + return SIGALRM; 34 + case LINUX_SIGTERM: 35 + return SIGTERM; 36 + case LINUX_SIGURG: 37 + return SIGURG; 38 + case LINUX_SIGSTOP: 39 + return SIGSTOP; 40 + case LINUX_SIGTSTP: 41 + return SIGTSTP; 42 + case LINUX_SIGCONT: 43 + return SIGCONT; 44 + case LINUX_SIGCHLD: 45 + return SIGCHLD; 46 + case LINUX_SIGTTIN: 47 + return SIGTTIN; 48 + case LINUX_SIGTTOU: 49 + return SIGTTOU; 50 + case LINUX_SIGIO: 51 + return SIGIO; 52 + case LINUX_SIGXCPU: 53 + return SIGXCPU; 54 + case LINUX_SIGXFSZ: 55 + return SIGXFSZ; 56 + case LINUX_SIGVTALRM: 57 + return SIGVTALRM; 58 + case LINUX_SIGPROF: 59 + return SIGPROF; 60 + case LINUX_SIGWINCH: 61 + return SIGWINCH; 62 + case LINUX_SIGUSR1: 63 + return SIGUSR1; 64 + case LINUX_SIGUSR2: 65 + return SIGUSR2; 66 + default: 67 + return 0; 68 + } 69 + } 70 + 71 +
+54
src/shellspawn/duct_signals.h
··· 1 + #ifndef LINUX_DUCT_SIGNALS_H 2 + #define LINUX_DUCT_SIGNALS_H 3 + 4 + #define LINUX_NSIG 32 5 + #define LINUX_SIGHUP 1 6 + #define LINUX_SIGINT 2 7 + #define LINUX_SIGQUIT 3 8 + #define LINUX_SIGILL 4 9 + #define LINUX_SIGTRAP 5 10 + #define LINUX_SIGABRT 6 11 + #define LINUX_SIGIOT 6 12 + #define LINUX_SIGBUS 7 13 + #define LINUX_SIGFPE 8 14 + #define LINUX_SIGKILL 9 15 + #define LINUX_SIGUSR1 10 16 + #define LINUX_SIGSEGV 11 17 + #define LINUX_SIGUSR2 12 18 + #define LINUX_SIGPIPE 13 19 + #define LINUX_SIGALRM 14 20 + #define LINUX_SIGTERM 15 21 + #define LINUX_SIGSTKFLT 16 22 + #define LINUX_SIGCHLD 17 23 + #define LINUX_SIGCONT 18 24 + #define LINUX_SIGSTOP 19 25 + #define LINUX_SIGTSTP 20 26 + #define LINUX_SIGTTIN 21 27 + #define LINUX_SIGTTOU 22 28 + #define LINUX_SIGURG 23 29 + #define LINUX_SIGXCPU 24 30 + #define LINUX_SIGXFSZ 25 31 + #define LINUX_SIGVTALRM 26 32 + #define LINUX_SIGPROF 27 33 + #define LINUX_SIGWINCH 28 34 + #define LINUX_SIGIO 29 35 + #define LINUX_SIGLOST 29 36 + #define LINUX_SIGPWR 30 37 + #define LINUX_SIGSYS 31 38 + #define LINUX_SIGUNUSED 31 39 + #define LINUX_SIGRTMIN 32 40 + #define LINUX_SA_NOCLDSTOP 0x00000001u 41 + #define LINUX_SA_NOCLDWAIT 0x00000002u 42 + #define LINUX_SA_SIGINFO 0x00000004u 43 + #define LINUX_SA_ONSTACK 0x08000000u 44 + #define LINUX_SA_RESTART 0x10000000u 45 + #define LINUX_SA_NODEFER 0x40000000u 46 + #define LINUX_SA_RESETHAND 0x80000000u 47 + #define LINUX_SA_RESTORER 0x04000000 48 + #define LINUX_MINSIGSTKSZ 2048 49 + #define LINUX_SIGSTKSZ 8192 50 + 51 + int signum_linux_to_bsd(int signum); 52 + 53 + #endif 54 +
+17
src/shellspawn/org.darlinghq.shellspawn.plist
··· 1 + <?xml version="1.0" encoding="UTF-8"?> 2 + <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" 3 + "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 4 + <plist version="1.0"> 5 + <dict> 6 + <key>Label</key> 7 + <string>org.darlinghq.shellspawn</string> 8 + <key>ProgramArguments</key> 9 + <array> 10 + <string>/usr/libexec/shellspawn</string> 11 + </array> 12 + <key>RunAtLoad</key> 13 + <true/> 14 + <key>KeepAlive</key> 15 + <true/> 16 + </dict> 17 + </plist>
src/shellspawn/shellspawn

This is a binary file and will not be displayed.

+383
src/shellspawn/shellspawn.c
··· 1 + /* 2 + This file is part of Darling. 3 + 4 + Copyright (C) 2017 Lubos Dolezel 5 + 6 + Darling is free software: you can redistribute it and/or modify 7 + it under the terms of the GNU General Public License as published by 8 + the Free Software Foundation, either version 3 of the License, or 9 + (at your option) any later version. 10 + 11 + Darling is distributed in the hope that it will be useful, 12 + but WITHOUT ANY WARRANTY; without even the implied warranty of 13 + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 + GNU General Public License for more details. 15 + 16 + You should have received a copy of the GNU General Public License 17 + along with Darling. If not, see <http://www.gnu.org/licenses/>. 18 + */ 19 + 20 + #include <sys/socket.h> 21 + #include <sys/un.h> 22 + #include <sys/stat.h> 23 + #include <stdlib.h> 24 + #include <unistd.h> 25 + #include <fcntl.h> 26 + #include <stdbool.h> 27 + #include <string.h> 28 + #include <stdio.h> 29 + #include <errno.h> 30 + #include <sys/poll.h> 31 + #include <sys/types.h> 32 + #include <sys/wait.h> 33 + #include <sys/event.h> 34 + #include <sys/ioctl.h> 35 + #include <signal.h> 36 + #include "shellspawn.h" 37 + #include "duct_signals.h" 38 + 39 + #define DBG 0 40 + 41 + int g_serverSocket = -1; 42 + 43 + void setupSocket(void); 44 + void listenForConnections(void); 45 + void spawnShell(int fd); 46 + 47 + int main(int argc, const char** argv) 48 + { 49 + setupSocket(); 50 + listenForConnections(); 51 + 52 + if (g_serverSocket != -1) 53 + close(g_serverSocket); 54 + return 0; 55 + } 56 + 57 + void setupSocket(void) 58 + { 59 + struct sockaddr_un addr = { 60 + .sun_family = AF_UNIX, 61 + .sun_path = SHELLSPAWN_SOCKPATH 62 + }; 63 + 64 + g_serverSocket = socket(AF_UNIX, SOCK_STREAM, 0); 65 + if (g_serverSocket == -1) 66 + { 67 + perror("Creating unix socket"); 68 + exit(EXIT_FAILURE); 69 + } 70 + 71 + fcntl(g_serverSocket, F_SETFD, FD_CLOEXEC); 72 + unlink(SHELLSPAWN_SOCKPATH); 73 + 74 + if (bind(g_serverSocket, (struct sockaddr*) &addr, sizeof(addr)) == -1) 75 + { 76 + perror("Binding the unix socket"); 77 + exit(EXIT_FAILURE); 78 + } 79 + 80 + chmod(addr.sun_path, 0600); 81 + 82 + if (listen(g_serverSocket, 1) == -1) 83 + { 84 + perror("Listening on unix socket"); 85 + exit(EXIT_FAILURE); 86 + } 87 + } 88 + 89 + void listenForConnections(void) 90 + { 91 + int sock; 92 + struct sockaddr_un addr; 93 + socklen_t len = sizeof(addr); 94 + 95 + while (true) 96 + { 97 + sock = accept(g_serverSocket, (struct sockaddr*) &addr, &len); 98 + if (sock == -1) 99 + break; 100 + 101 + if (fork() == 0) 102 + { 103 + fcntl(sock, F_SETFD, FD_CLOEXEC); 104 + spawnShell(sock); 105 + exit(EXIT_SUCCESS); 106 + } 107 + else 108 + { 109 + close(sock); 110 + } 111 + } 112 + } 113 + 114 + void spawnShell(int fd) 115 + { 116 + pid_t shell_pid = -1; 117 + int shellfd[3] = { -1, -1, -1 }; 118 + int pipefd[2]; 119 + int rv; 120 + struct pollfd pfd[2]; 121 + char** argv = NULL; 122 + int argc = 1; 123 + struct msghdr msg; 124 + struct iovec iov; 125 + char cmsgbuf[CMSG_SPACE(sizeof(int)) * 3]; 126 + int kq; 127 + 128 + bool read_cmds = true; 129 + 130 + argv = (char**) malloc(sizeof(char*) * 2); 131 + argv[0] = "/bin/bash"; 132 + 133 + // Read commands from client 134 + while (read_cmds) 135 + { 136 + struct shellspawn_cmd cmd; 137 + char* param = NULL; 138 + 139 + memset(&msg, 0, sizeof(msg)); 140 + msg.msg_control = cmsgbuf; 141 + msg.msg_controllen = sizeof(cmsgbuf); 142 + 143 + iov.iov_base = &cmd; 144 + iov.iov_len = sizeof(cmd); 145 + msg.msg_iov = &iov; 146 + msg.msg_iovlen = 1; 147 + 148 + if (recvmsg(fd, &msg, 0) != sizeof(cmd)) 149 + { 150 + if (DBG) puts("bad recvmsg"); 151 + goto err; 152 + } 153 + 154 + if (cmd.data_length != 0) 155 + { 156 + param = (char*) malloc(cmd.data_length + 1); 157 + if (read(fd, param, cmd.data_length) != cmd.data_length) 158 + goto err; 159 + param[cmd.data_length] = '\0'; 160 + } 161 + 162 + switch (cmd.cmd) 163 + { 164 + case SHELLSPAWN_ADDARG: 165 + { 166 + if (param != NULL) 167 + { 168 + argv = (char**) realloc(argv, sizeof(char*) * (argc + 1)); 169 + argv[argc] = param; 170 + if (DBG) printf("add arg: %s\n", param); 171 + argc++; 172 + } 173 + break; 174 + } 175 + case SHELLSPAWN_SETENV: 176 + { 177 + if (param != NULL) 178 + { 179 + if (DBG) printf("set env: %s\n", param); 180 + putenv(param); 181 + free(param); 182 + } 183 + break; 184 + } 185 + case SHELLSPAWN_CHDIR: 186 + { 187 + if (param != NULL) 188 + { 189 + if (DBG) printf("chdir: %s\n", param); 190 + chdir(param); 191 + free(param); 192 + } 193 + break; 194 + } 195 + case SHELLSPAWN_GO: 196 + { 197 + struct cmsghdr *cmptr = CMSG_FIRSTHDR(&msg); 198 + 199 + if (cmptr == NULL) 200 + { 201 + if (DBG) puts("bad cmptr"); 202 + goto err; 203 + } 204 + if (cmptr->cmsg_level != SOL_SOCKET 205 + || cmptr->cmsg_type != SCM_RIGHTS) 206 + { 207 + if (DBG) puts("bad cmsg level/type"); 208 + goto err; 209 + } 210 + if (cmptr->cmsg_len != CMSG_LEN(sizeof(int) * 3)) 211 + { 212 + if (DBG) printf("bad cmsg_len: %d\n", cmptr->cmsg_len); 213 + goto err; 214 + } 215 + 216 + memcpy(shellfd, CMSG_DATA(cmptr), sizeof(int) * 3); 217 + 218 + if (DBG) printf("go, fds={ %d, %d, %d }\n", shellfd[0], shellfd[1], shellfd[2]); 219 + free(param); 220 + read_cmds = false; 221 + break; 222 + } 223 + } 224 + } 225 + 226 + // Add terminating NULL 227 + argv = (char**) realloc(argv, sizeof(char*) * (argc + 1)); 228 + argv[argc] = NULL; 229 + 230 + if (pipe(pipefd) == -1) 231 + goto err; 232 + 233 + setsid(); 234 + setpgrp(); 235 + 236 + close(STDIN_FILENO); 237 + close(STDOUT_FILENO); 238 + close(STDERR_FILENO); 239 + 240 + dup2(shellfd[0], STDIN_FILENO); 241 + dup2(shellfd[1], STDOUT_FILENO); 242 + dup2(shellfd[2], STDERR_FILENO); 243 + 244 + ioctl(STDIN_FILENO, TIOCSCTTY, STDIN_FILENO); 245 + 246 + shell_pid = fork(); 247 + if (shell_pid == 0) 248 + { 249 + close(fd); 250 + 251 + fcntl(pipefd[1], F_SETFD, FD_CLOEXEC); 252 + 253 + // In future, we may support spawning something else than Bash 254 + // and check the provided shell against /etc/shells 255 + execv("/bin/bash", argv); 256 + 257 + rv = errno; 258 + write(pipefd[1], &rv, sizeof(rv)); 259 + close(pipefd[1]); 260 + 261 + exit(EXIT_FAILURE); 262 + } 263 + 264 + // Check that exec succeeded 265 + close(pipefd[1]); // close the write end 266 + if (read(pipefd[0], &rv, sizeof(rv)) == sizeof(rv)) 267 + { 268 + errno = rv; 269 + goto err; 270 + } 271 + close(pipefd[0]); 272 + 273 + // Now we start passing signals 274 + // and check for child process exit 275 + 276 + kq = kqueue(); 277 + 278 + { 279 + struct kevent changes[2]; 280 + EV_SET(&changes[0], fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, NULL); 281 + EV_SET(&changes[1], shell_pid, EVFILT_PROC, EV_ADD | EV_ENABLE, NOTE_EXIT, 0, NULL); 282 + 283 + if (kevent(kq, changes, 2, NULL, 0, NULL) == -1) 284 + goto err; 285 + } 286 + 287 + while (true) 288 + { 289 + struct kevent ev; 290 + 291 + if (kevent(kq, NULL, 0, &ev, 1, NULL) <= 0) 292 + { 293 + if (DBG) puts("kevent fail"); 294 + goto err; 295 + } 296 + 297 + if (ev.filter == EVFILT_PROC && (ev.fflags & NOTE_EXIT)) 298 + { 299 + if (DBG) puts("subprocess exit"); 300 + break; 301 + } 302 + else if (ev.filter == EVFILT_READ) 303 + { 304 + struct shellspawn_cmd cmd; 305 + 306 + if (read(fd, &cmd, sizeof(cmd)) != sizeof(cmd)) 307 + { 308 + if (DBG) puts("Cannot read cmd"); 309 + break; 310 + } 311 + 312 + switch (cmd.cmd) 313 + { 314 + case SHELLSPAWN_SIGNAL: 315 + { 316 + int linux_signal, darwin_signal; 317 + 318 + if (cmd.data_length != sizeof(int)) 319 + goto err; 320 + 321 + if (read(fd, &linux_signal, sizeof(int)) != sizeof(int)) 322 + goto err; 323 + 324 + // Convert Linux signal number to Darwin signal number 325 + darwin_signal = signum_linux_to_bsd(linux_signal); 326 + if (DBG) printf("rcvd signal %d -> %d\n", linux_signal, darwin_signal); 327 + 328 + if (darwin_signal != 0) 329 + { 330 + int fg_pid = tcgetpgrp(shellfd[0]); 331 + if (fg_pid != -1) 332 + { 333 + if (DBG) printf("fg_pid = %d\n", fg_pid); 334 + kill(fg_pid, darwin_signal); 335 + } 336 + else 337 + kill(-shell_pid, darwin_signal); 338 + } 339 + 340 + break; 341 + } 342 + default: 343 + goto err; 344 + } 345 + } 346 + } 347 + 348 + // Kill the child process in case it's still running 349 + kill(shell_pid, SIGKILL); 350 + 351 + // Close shell fds 352 + for (int i = 0; i < 3; i++) 353 + { 354 + if (shellfd[i] != -1) 355 + close(shellfd[0]); 356 + } 357 + 358 + // Reap the child 359 + int wstatus; 360 + waitpid(shell_pid, &wstatus, WEXITED); 361 + wstatus = WEXITSTATUS(wstatus); 362 + 363 + // Report exit code back to the client 364 + write(fd, &wstatus, sizeof(int)); 365 + 366 + if (DBG) printf("Shell terminated with exit code %d\n", wstatus); 367 + close(fd); 368 + return; 369 + err: 370 + if (DBG) fprintf(stderr, "Error spawning shell: %s\n", strerror(errno)); 371 + 372 + for (int i = 0; i < 3; i++) 373 + { 374 + if (shellfd[i] != -1) 375 + close(shellfd[0]); 376 + } 377 + 378 + if (shell_pid != -1) 379 + kill(shell_pid, SIGKILL); 380 + 381 + close(fd); 382 + } 383 +
+47
src/shellspawn/shellspawn.h
··· 1 + /* 2 + This file is part of Darling. 3 + 4 + Copyright (C) 2017 Lubos Dolezel 5 + 6 + Darling is free software: you can redistribute it and/or modify 7 + it under the terms of the GNU General Public License as published by 8 + the Free Software Foundation, either version 3 of the License, or 9 + (at your option) any later version. 10 + 11 + Darling is distributed in the hope that it will be useful, 12 + but WITHOUT ANY WARRANTY; without even the implied warranty of 13 + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 + GNU General Public License for more details. 15 + 16 + You should have received a copy of the GNU General Public License 17 + along with Darling. If not, see <http://www.gnu.org/licenses/>. 18 + */ 19 + 20 + #ifndef _SHELLSPAWN_H 21 + #define _SHELLSPAWN_H 22 + 23 + #ifdef TESTING 24 + #define SHELLSPAWN_SOCKPATH "/tmp/shellspawn.sock" 25 + #else 26 + #define SHELLSPAWN_SOCKPATH "/var/run/shellspawn.sock" 27 + #endif 28 + 29 + typedef unsigned short shellspawn_cmd_type_t; 30 + 31 + enum { 32 + SHELLSPAWN_ADDARG = 1, // add shell argument 33 + SHELLSPAWN_SETENV, // add env variable string 34 + SHELLSPAWN_CHDIR, 35 + SHELLSPAWN_GO, // execute the shell now, must also contain file descriptors 36 + SHELLSPAWN_SIGNAL, // pass a signal from client 37 + }; 38 + 39 + struct __attribute__((packed)) shellspawn_cmd 40 + { 41 + shellspawn_cmd_type_t cmd; 42 + unsigned short data_length; 43 + char data[]; 44 + }; 45 + 46 + #endif 47 +
+3 -1
src/startup/CMakeLists.txt
··· 9 9 10 10 enable_language(C ASM) 11 11 12 - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11") 12 + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11 -ggdb -O0") 13 13 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 14 14 #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Ttext-segment,0x400000 -Wl,-Tbss,0x410000 -Wl,-Tdata,0x420000") 15 15 add_definitions(-DINSTALL_PREFIX="${CMAKE_INSTALL_PREFIX}" -D_GNU_SOURCE -DMLDR_BUILD) 16 16 17 17 add_executable(darling darling.c) 18 + 19 + target_link_libraries(darling -lutil) 18 20 19 21 include_directories(${CMAKE_CURRENT_SOURCE_DIR}) 20 22
+346 -166
src/startup/darling.c
··· 36 36 #include <sys/utsname.h> 37 37 #include <sys/stat.h> 38 38 #include <sys/syscall.h> 39 + #include <sys/inotify.h> 40 + #include <sys/poll.h> 41 + #include <sys/socket.h> 42 + #include <sys/un.h> 39 43 #include <getopt.h> 44 + #include <termios.h> 45 + #include <pty.h> 46 + #include "../shellspawn/shellspawn.h" 40 47 #include "darling.h" 41 48 #include "darling-config.h" 42 49 ··· 50 57 51 58 int main(int argc, char ** argv, char ** envp) 52 59 { 53 - pid_t pidInit, pidChild; 60 + pid_t pidInit; 54 61 int wstatus; 55 62 56 63 if (argc <= 1) ··· 156 163 // If prefix's init is not running, start it up 157 164 if (pidInit == 0) 158 165 { 166 + char socketPath[4096]; 167 + 168 + snprintf(socketPath, sizeof(socketPath), "%s" SHELLSPAWN_SOCKPATH, prefix); 169 + 170 + unlink(socketPath); 171 + 159 172 setupWorkdir(); 160 173 pidInit = spawnInitProcess(); 161 174 putInitPid(pidInit); 162 - } 163 - 164 - if (strcmp(argv[1], "shell") != 0) 165 - { 166 - char *path = realpath(argv[1], NULL); 167 - char *fullPath; 168 - 169 - if (path == NULL) 175 + 176 + // Wait until shellspawn starts 177 + for (int i = 0; i < 15; i++) 170 178 { 171 - fprintf(stderr, "Cannot resolve path: %s\n", strerror(errno)); 172 - exit(1); 179 + if (access(socketPath, F_OK) == 0) 180 + break; 181 + sleep(1); 173 182 } 183 + } 174 184 175 - const char *argv_child[argc + 1]; 185 + seteuid(g_originalUid); 176 186 177 - argv_child[0] = MLDR_PATH; 178 - 179 - fullPath = malloc(strlen(SYSTEM_ROOT) + strlen(path) + 1); 180 - strcpy(fullPath, SYSTEM_ROOT); 181 - strcat(fullPath, path); 182 - argv_child[1] = fullPath; 183 - 184 - for (int i = 2; i < argc; i++) 185 - argv_child[i] = argv[i]; 186 - argv_child[argc] = NULL; 187 - 188 - pidChild = spawnChild(pidInit, MLDR_PATH, argv_child); 189 - free(path); 190 - free(fullPath); 191 - } 192 - else 187 + if (strcmp(argv[1], "shell") == 0) 193 188 { 194 189 // Spawn the shell 195 190 if (argc > 2) 196 - { 197 - size_t total_len = 0; 198 - for (int i = 2; i < argc; i++) 199 - total_len += strlen(argv[i]); 200 - 201 - char buffer[total_len + argc]; 202 - 203 - char *to = buffer; 204 - for (int i = 2; i < argc; i++) 205 - to = stpcpy(stpcpy(to, argv[i]), " "); 206 - // Overwrite the last whitespace 207 - *(to - 1) = '\0'; 208 - 209 - pidChild = spawnChild(pidInit, MLDR_PATH, 210 - (const char *[5]) {MLDR_PATH, "/bin/bash", "-c", buffer, NULL}); 211 - } 191 + spawnShell((const char**) &argv[2]); 212 192 else 213 - pidChild = spawnChild(pidInit, MLDR_PATH, 214 - (const char *[3]) {MLDR_PATH, "/bin/bash", NULL}); 193 + spawnShell(NULL); 215 194 } 216 - if (pidChild == -ENOMEM) 195 + else 217 196 { 218 - pidInit = 0; 219 - goto start_init; 220 - } 197 + char *fullPath; 198 + char** child_argv; 199 + char *path = realpath(argv[1], NULL); 221 200 222 - // Drop the privileges so that we can be killed, etc by the user 223 - seteuid(g_originalUid); 201 + fullPath = malloc(strlen(SYSTEM_ROOT) + strlen(path) + 1); 202 + strcpy(fullPath, SYSTEM_ROOT); 203 + strcat(fullPath, path); 224 204 225 - waitpid(pidChild, &wstatus, 0); 205 + argv[1] = fullPath; 206 + spawnShell((const char**) &argv[1]); 207 + } 226 208 227 - if (WIFEXITED(wstatus)) 228 - return WEXITSTATUS(wstatus); 229 - if (WIFSIGNALED(wstatus)) 230 - return WTERMSIG(wstatus); 231 209 return 0; 232 210 } 233 211 234 - static void joinNamespace(pid_t pid, int type, const char* typeName) 212 + static void pushShellspawnCommandData(int sockfd, shellspawn_cmd_type_t type, const void* data, size_t data_length) 235 213 { 236 - int fdNS; 237 - char pathNS[4096]; 238 - 239 - snprintf(pathNS, sizeof(pathNS), "/proc/%d/ns/%s", pid, typeName); 214 + struct shellspawn_cmd* cmd; 215 + size_t length; 240 216 241 - fdNS = open(pathNS, O_RDONLY); 217 + length = sizeof(*cmd) + data_length; 242 218 243 - if (fdNS < 0) 219 + cmd = (struct shellspawn_cmd*) malloc(length); 220 + cmd->cmd = type; 221 + cmd->data_length = data_length; 222 + 223 + if (data != NULL) 224 + memcpy(cmd->data, data, data_length); 225 + 226 + if (write(sockfd, cmd, length) != length) 244 227 { 245 - fprintf(stderr, "Cannot open %s namespace file: %s\n", typeName, strerror(errno)); 246 - exit(1); 228 + fprintf(stderr, "Error sending command to shellspawn: %s\n", strerror(errno)); 229 + exit(EXIT_FAILURE); 247 230 } 231 + } 248 232 249 - // Calling setns() with a PID namespace doesn't move our process into it, 250 - // but our child process will be spawned inside the namespace 251 - if (setns(fdNS, type) != 0) 233 + static void pushShellspawnCommand(int sockfd, shellspawn_cmd_type_t type, const char* value) 234 + { 235 + if (!value) 236 + pushShellspawnCommandData(sockfd, type, NULL, 0); 237 + else 238 + pushShellspawnCommandData(sockfd, type, value, strlen(value) + 1); 239 + } 240 + 241 + static void pushShellspawnCommandFDs(int sockfd, shellspawn_cmd_type_t type, const int fds[3]) 242 + { 243 + struct shellspawn_cmd cmd; 244 + char cmsgbuf[CMSG_SPACE(sizeof(int) * 3)]; 245 + struct msghdr msg; 246 + struct iovec iov; 247 + struct cmsghdr *cmptr; 248 + 249 + cmd.cmd = type; 250 + cmd.data_length = 0; 251 + 252 + iov.iov_base = &cmd; 253 + 254 + memset(&msg, 0, sizeof(msg)); 255 + msg.msg_control = cmsgbuf; 256 + msg.msg_controllen = sizeof(cmsgbuf); 257 + 258 + iov.iov_base = &cmd; 259 + iov.iov_len = sizeof(cmd); 260 + msg.msg_iov = &iov; 261 + msg.msg_iovlen = 1; 262 + 263 + cmptr = CMSG_FIRSTHDR(&msg); 264 + 265 + cmptr->cmsg_len = CMSG_LEN(sizeof(int) * 3); 266 + cmptr->cmsg_level = SOL_SOCKET; 267 + cmptr->cmsg_type = SCM_RIGHTS; 268 + memcpy(CMSG_DATA(cmptr), fds, sizeof(fds[0])*3); 269 + 270 + if (sendmsg(sockfd, &msg, 0) < 0) 252 271 { 253 - fprintf(stderr, "Cannot join %s namespace: %s\n", typeName, strerror(errno)); 254 - exit(1); 272 + fprintf(stderr, "Error sending command to shellspawn: %s\n", strerror(errno)); 273 + exit(EXIT_FAILURE); 255 274 } 256 - close(fdNS); 257 275 } 258 276 259 - pid_t spawnChild(int pidInit, const char *path, const char *const argv[]) 277 + static int _shSockfd = -1; 278 + static struct termios orig_termios; 279 + static int pty_master; 280 + static void signalHandler(int signo) 260 281 { 261 - pid_t pidChild; 262 - char curPath[4096]; 282 + // printf("Received signal %d\n", signo); 263 283 264 - if (getcwd(curPath, sizeof(curPath)) == NULL) 284 + // Forward window size changes 285 + if (signo == SIGWINCH && pty_master != -1) 265 286 { 266 - fprintf(stderr, "Cannot get current directory: %s\n", strerror(errno)); 267 - exit(1); 287 + struct winsize win; 288 + 289 + ioctl(0, TIOCGWINSZ, &win); 290 + ioctl(pty_master, TIOCSWINSZ, &win); 268 291 } 292 + 293 + // Foreground process loopkup in shellspawn doesn't work 294 + // if we're not running in TTY mode, so shellspawn falls back 295 + // to forwarding signals to the Bash subprocess. 296 + // 297 + // Hence we translate SIGINT to SIGTERM for user convenience, 298 + // because Bash will not terminate on SIGINT. 299 + if (pty_master == -1 && signo == SIGINT) 300 + signo = SIGTERM; 269 301 270 - joinNamespace(pidInit, CLONE_NEWPID, "pid"); 271 - joinNamespace(pidInit, CLONE_NEWUTS, "uts"); 302 + pushShellspawnCommandData(_shSockfd, SHELLSPAWN_SIGNAL, &signo, sizeof(signo)); 303 + } 272 304 273 - pidChild = fork(); 274 - if (pidChild < 0) 305 + static void shellLoop(int sockfd, int master) 306 + { 307 + struct sigaction sa; 308 + struct pollfd pfds[3]; 309 + const int fdcount = (master != -1) ? 3 : 1; // do we do pty proxying? 310 + 311 + // Vars for signal handler 312 + _shSockfd = sockfd; 313 + pty_master = master; 314 + 315 + memset(&sa, 0, sizeof(sa)); 316 + sa.sa_handler = signalHandler; 317 + sigfillset(&sa.sa_mask); 318 + 319 + for (int i = 1; i < 32; i++) 320 + sigaction(i, &sa, NULL); 321 + 322 + pfds[2].fd = master; 323 + pfds[2].events = POLLOUT; 324 + pfds[2].revents = 0; 325 + pfds[1].fd = STDIN_FILENO; 326 + pfds[1].events = POLLIN; 327 + pfds[1].revents = 0; 328 + pfds[0].fd = sockfd; 329 + pfds[0].events = POLLIN; 330 + pfds[0].revents = 0; 331 + 332 + if (master != -1) 333 + fcntl(master, F_SETFL, O_NONBLOCK); 334 + fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); 335 + fcntl(sockfd, F_SETFL, O_NONBLOCK); 336 + 337 + while (1) 275 338 { 276 - if (errno == ENOMEM) 339 + char buf[4096]; 340 + 341 + if (poll(pfds, fdcount, -1) < 0) 277 342 { 278 - // This condition happens specifically when the init process is a zombie. 279 - // We should simply start a new init process. 280 - return -ENOMEM; 343 + if (errno != EINTR) 344 + { 345 + perror("poll"); 346 + break; 347 + } 281 348 } 282 - fprintf(stderr, "Cannot spawn a child process: %s\n", strerror(errno)); 283 - exit(1); 284 - } 285 349 286 - if (pidChild == 0) 287 - { 288 - // This is the child process 289 - 290 - // We still have the outside PIDs in /proc 291 - joinNamespace(pidInit, CLONE_NEWNS, "mnt"); 350 + if (pfds[2].revents & POLLOUT) 351 + { 352 + int rd; 353 + do 354 + { 355 + rd = read(master, buf, sizeof(buf)); 356 + if (rd > 0) 357 + write(STDOUT_FILENO, buf, rd); 358 + } 359 + while (rd == sizeof(buf)); 360 + } 292 361 293 - /* 294 - snprintf(pathNS, sizeof(pathNS), SYSTEM_ROOT "/proc/%d/ns/user", pidInit); 295 - fdNS = open(pathNS, O_RDONLY); 296 - if (fdNS < 0) 362 + if (pfds[1].revents & POLLIN) 297 363 { 298 - fprintf(stderr, "Cannot open user namespace file: %s\n", strerror(errno)); 299 - exit(1); 364 + int rd; 365 + do 366 + { 367 + rd = read(STDIN_FILENO, buf, sizeof(buf)); 368 + if (rd > 0) 369 + write(master, buf, rd); 370 + } 371 + while (rd == sizeof(buf)); 300 372 } 301 - */ 302 - 303 - // Drop the privileges. It's important to drop GID first, because 304 - // non-root users can't change their GID. 305 - setresgid(g_originalGid, g_originalGid, g_originalGid); 306 - setresuid(g_originalUid, g_originalUid, g_originalUid); 307 373 308 - /* 309 - if (setns(fdNS, CLONE_NEWUSER) != 0) 374 + if (pfds[0].revents & (POLLHUP | POLLIN)) 310 375 { 311 - fprintf(stderr, "Cannot join user namespace: %s\n", strerror(errno)); 312 - exit(1); 376 + int exitStatus; 377 + 378 + if (read(sockfd, &exitStatus, sizeof(int)) == sizeof(int)) 379 + exit(exitStatus); 380 + else 381 + exit(1); 313 382 } 314 - close(fdNS); 315 - */ 383 + } 384 + } 316 385 317 - setupChild(curPath); 386 + static void restoreTermios(void) 387 + { 388 + tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios); 389 + } 318 390 319 - execv(path, (char * const *) argv); 391 + static void setupPtys(int fds[3], int* master) 392 + { 393 + struct winsize win; 394 + struct termios termios; 395 + bool tty = true; 320 396 321 - fprintf(stderr, "Cannot exec the target program: %s\n", strerror(errno)); 397 + if (tcgetattr(STDIN_FILENO, &termios) < 0) 398 + tty = false; 399 + 400 + if (openpty(master, &fds[0], NULL, &termios, &win) < 0) 401 + { 402 + perror("opentty"); 322 403 exit(1); 323 404 } 405 + fds[2] = fds[1] = fds[0]; 324 406 325 - return pidChild; 407 + if (tty) 408 + { 409 + orig_termios = termios; 410 + 411 + ioctl(0, TIOCGWINSZ, &win); 412 + 413 + termios.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO); 414 + termios.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | 415 + INPCK | ISTRIP | IXON | PARMRK); 416 + termios.c_oflag &= ~OPOST; 417 + termios.c_cc[VMIN] = 1; 418 + termios.c_cc[VTIME] = 0; 419 + 420 + if (tcsetattr(STDIN_FILENO, TCSANOW, &termios) < 0) 421 + { 422 + perror("tcsetattr"); 423 + exit(1); 424 + } 425 + ioctl(*master, TIOCSWINSZ, &win); 426 + 427 + atexit(restoreTermios); 428 + } 326 429 } 327 430 328 - void setupChild(const char *curPath) 431 + void spawnShell(const char** argv) 329 432 { 433 + size_t total_len = 0; 434 + int count; 330 435 char buffer1[4096]; 331 436 char buffer2[4096]; 437 + int sockfd; 438 + struct sockaddr_un addr; 439 + char* buffer; 332 440 441 + if (argv != NULL) 442 + { 443 + for (count = 0; argv[count] != NULL; count++) 444 + total_len += strlen(argv[count]); 333 445 334 - unsetenv("LESSOPEN"); 335 - unsetenv("LESSCLOSE"); 336 - unsetenv("LESSECHO"); 446 + buffer = malloc(total_len + count*3); 337 447 338 - setenv("PATH", 339 - "/usr/bin:" 448 + char *to = buffer; 449 + for (int i = 0; argv[i] != NULL; i++) 450 + { 451 + if (to != buffer) 452 + to = stpcpy(to, " "); 453 + to = stpcpy(to, "'"); 454 + to = stpcpy(to, argv[i]); 455 + to = stpcpy(to, "'"); 456 + } 457 + } 458 + else 459 + buffer = NULL; 460 + 461 + // Connect to the shellspawn daemon in the container 462 + addr.sun_family = AF_UNIX; 463 + snprintf(addr.sun_path, sizeof(addr.sun_path), "%s" SHELLSPAWN_SOCKPATH, prefix); 464 + 465 + sockfd = socket(AF_UNIX, SOCK_STREAM, 0); 466 + if (sockfd == -1) 467 + { 468 + fprintf(stderr, "Error creating a unix domain socket: %s\n", strerror(errno)); 469 + exit(1); 470 + } 471 + 472 + if (connect(sockfd, (struct sockaddr*) &addr, sizeof(addr)) == -1) 473 + { 474 + fprintf(stderr, "Error connecting to shellspawn in the container: %s\n", strerror(errno)); 475 + exit(1); 476 + } 477 + 478 + // Push environment variables 479 + pushShellspawnCommand(sockfd, SHELLSPAWN_SETENV, 480 + "PATH=/usr/bin:" 340 481 "/bin:" 341 482 "/usr/sbin:" 342 483 "/sbin:" 343 - "/usr/local/bin", 344 - 1); 484 + "/usr/local/bin"); 345 485 346 - sscanf(getenv("HOME"), "/home/%4096s", buffer1); 347 - snprintf(buffer2, sizeof(buffer2), "/Users/%s", buffer1); 348 - setenv("HOME", buffer2, 1); 486 + if (sscanf(getenv("HOME"), "/home/%4096s", buffer1) == 1) 487 + { 488 + snprintf(buffer2, sizeof(buffer2), "HOME=/Users/%s", buffer1); 489 + pushShellspawnCommand(sockfd, SHELLSPAWN_SETENV, buffer2); 490 + } 349 491 350 - if (sscanf(curPath, "/home/%4096s", buffer1) == 1) 492 + // Push shell arguments 493 + if (buffer != NULL) 351 494 { 352 - // We're currently inside our home directory 353 - snprintf(buffer2, sizeof(buffer2), "/Users/%s", buffer1); 354 - setenv("PWD", buffer2, 1); 355 - chdir(buffer2); 495 + pushShellspawnCommand(sockfd, SHELLSPAWN_ADDARG, "-c"); 496 + pushShellspawnCommand(sockfd, SHELLSPAWN_ADDARG, buffer); 497 + 498 + free(buffer); 356 499 } 357 - else 500 + 501 + if (getcwd(buffer1, sizeof(buffer1)) != NULL) 358 502 { 359 - snprintf(buffer2, sizeof(buffer2), SYSTEM_ROOT "%s", curPath); 360 - setenv("PWD", buffer2, 1); 361 - chdir(buffer2); 503 + snprintf(buffer2, sizeof(buffer2), SYSTEM_ROOT "%s", buffer1); 504 + pushShellspawnCommand(sockfd, SHELLSPAWN_CHDIR, buffer2); 362 505 } 506 + 507 + int fds[3], master = -1; 508 + 509 + if (isatty(STDIN_FILENO)) 510 + setupPtys(fds, &master); 511 + else 512 + fds[0] = dup(STDIN_FILENO); // dup() because we close() a few lines below 513 + 514 + if (master == -1 || !isatty(STDOUT_FILENO)) 515 + fds[1] = STDOUT_FILENO; 516 + if (master == -1 || !isatty(STDERR_FILENO)) 517 + fds[2] = STDERR_FILENO; 518 + 519 + pushShellspawnCommandFDs(sockfd, SHELLSPAWN_GO, fds); 520 + close(fds[0]); 521 + 522 + shellLoop(sockfd, master); 523 + 524 + if (master != -1) 525 + close(master); 526 + close(sockfd); 363 527 } 364 528 365 529 void showHelp(const char* argv0) ··· 432 596 { 433 597 pid_t pid; 434 598 int pipefd[2]; 435 - // char idmap[100]; 599 + char path[1024]; 436 600 char buffer[1]; 437 601 FILE *file; 438 602 ··· 540 704 // if we enable user namespaces 541 705 542 706 darlingPreInit(); 707 + spawnLaunchd(); 708 + 543 709 // Never returns 544 710 } 545 711 ··· 575 741 fprintf(stderr, "Cannot set gid_map for the init process: %s\n", strerror(errno)); 576 742 } 577 743 */ 578 - 579 - // This is for development only! 580 - if (getenv("TRY_LAUNCHD") != NULL) 581 - { 582 - int status = 0; 583 - waitpid(pid, &status, 0); 584 - } 585 744 586 745 // Here's where we resume the child 587 746 // if we enable user namespaces ··· 616 775 fclose(fp); 617 776 } 618 777 619 - void darlingPreInit(void) 778 + void spawnLaunchd(void) 620 779 { 621 - // TODO: Run /usr/libexec/makewhatis 780 + puts("Bootstrapping the container with launchd..."); 622 781 623 - // This is for development only! 624 - if (getenv("TRY_LAUNCHD") != NULL) 625 - { 626 - // putenv("KQUEUE_DEBUG=1"); 627 - execl("/bin/mldr", "mldr!/sbin/launchd", "launchd", NULL); 628 - 629 - fprintf(stderr, "Failed to exec launchd: %s\n", strerror(errno)); 630 - abort(); 631 - } 632 - 633 - // TODO: this is where we will exec() launchd in future. 634 - // Instead, we just reap zombies. 635 - while (1) 782 + execl(MLDR_PATH, "mldr!/sbin/launchd", "launchd", NULL); 783 + 784 + fprintf(stderr, "Failed to exec launchd: %s\n", strerror(errno)); 785 + abort(); 786 + } 787 + 788 + static void wipeDir(const char* dirpath) 789 + { 790 + char path[4096]; 791 + struct dirent* ent; 792 + DIR* dir = opendir(dirpath); 793 + 794 + if (!dir) 795 + return; 796 + 797 + while ((ent = readdir(dir)) != NULL) 636 798 { 637 - int status, sig; 638 - sigset_t chld; 799 + if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) 800 + continue; 639 801 640 - sigemptyset(&chld); 641 - sigaddset(&chld, SIGCHLD); 642 - sigwait(&chld, &sig); 802 + snprintf(path, sizeof(path), "%s/%s", dirpath, ent->d_name); 643 803 644 - while (waitpid(-1, &status, 0) != -1); 804 + if (ent->d_type == DT_DIR) 805 + { 806 + wipeDir(path); 807 + rmdir(path); 808 + } 809 + else 810 + unlink(path); 645 811 } 812 + 813 + closedir(dir); 814 + } 815 + 816 + void darlingPreInit(void) 817 + { 818 + // TODO: Run /usr/libexec/makewhatis 819 + const char* dirs[] = { 820 + "/var/tmp", 821 + "/var/run" 822 + }; 823 + 824 + for (size_t i = 0; i < sizeof(dirs)/sizeof(dirs[0]); i++) 825 + wipeDir(dirs[i]); 646 826 } 647 827 648 828 char* defaultPrefixPath(void)
+3 -4
src/startup/darling.h
··· 40 40 // Creates the given directory, exit()ing if not possible 41 41 void createDir(const char* path); 42 42 43 - // Spawn the specified proceess inside the namespaces that PID 1 is in 44 - // Returns the PID of the child 45 - // exit()s on error 46 - pid_t spawnChild(int pidInit, const char *path, const char *const argv[]); 43 + void spawnShell(const char** argv); 47 44 48 45 // Set up some environment variables 49 46 // As well as the working directory ··· 57 54 pid_t spawnInitProcess(void); 58 55 59 56 void putInitPid(pid_t pidInit); 57 + void mapUids(pid_t pid); 60 58 59 + void spawnLaunchd(void); 61 60 void darlingPreInit(void); 62 61 63 62 void checkPrefixOwner(void);