├── syscall ├── errno_msgs.c ├── clone.h ├── memory.c ├── clone3.h ├── clone3.c ├── clone.c ├── signal.c ├── make_syscall.h ├── syscall.c ├── errno_msgs.h └── syscall.h ├── aspawn_all.h ├── create_pipe ├── create_pipe.c └── create_pipe.h ├── common.h ├── test ├── run_tests.sh ├── Makefile ├── utility.h ├── test_aspawn.c ├── utility.c └── test_syscall.c ├── benchmark ├── run_benchmark.sh ├── Makefile └── bench_aspawn_responsiveness.cc ├── clone_internal ├── stack_growth.h ├── clone_internal.h └── clone_internal.c ├── signal ├── signal.c └── signal.h ├── .gitignore ├── .github └── workflows │ └── c-cpp.yml ├── cached_stack ├── cached_stack.h └── cached_stack.c ├── Makefile ├── aspawn.h ├── aspawn.c ├── example └── naive_example.c ├── README.md └── LICENSE /syscall/errno_msgs.c: -------------------------------------------------------------------------------- 1 | #include "errno_msgs.h" 2 | 3 | const cstr* get_errno_msgs_cstrs() 4 | { 5 | return errno_msgs; 6 | } 7 | -------------------------------------------------------------------------------- /aspawn_all.h: -------------------------------------------------------------------------------- 1 | #ifndef __aspawn_aspawn_all_H__ 2 | # define __aspawn_aspawn_all_H__ 3 | 4 | # include "aspawn.h" 5 | # include "syscall/syscall.h" 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /create_pipe/create_pipe.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include "create_pipe.h" 4 | 5 | #include 6 | #include "../syscall/syscall.h" 7 | 8 | int create_cloexec_pipe(int pipefd[2]) 9 | { 10 | return psys_pipe2(pipefd, O_CLOEXEC); 11 | } 12 | -------------------------------------------------------------------------------- /common.h: -------------------------------------------------------------------------------- 1 | # if __GNUC__ >= 4 2 | # define PUBLIC __attribute__ ((visibility ("default"))) 3 | # define LOCAL __attribute__ ((visibility ("hidden"))) 4 | # define ALWAYS_INLINE __attribute__ ((always_inline)) 5 | # else 6 | # define PUBLIC 7 | # define LOCAL 8 | # endif 9 | -------------------------------------------------------------------------------- /test/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo Env variable PATH = ${PATH} 4 | 5 | exit_code=0 6 | 7 | for each in $1; do 8 | echo -e '\nRunning' $each ... 9 | ./$each 10 | 11 | [ $? -ne 0 ] && exit_code=1 12 | 13 | echo 14 | done 15 | 16 | exit $exit_code 17 | -------------------------------------------------------------------------------- /benchmark/run_benchmark.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo cpupower frequency-set --governor performance 4 | echo 5 | 6 | for each in $1; do 7 | echo -e '\nRunning' $each ... 8 | LD_LIBRARY_PATH=../ ./$each 9 | echo 10 | done 11 | 12 | sudo cpupower frequency-set --governor powersave 13 | -------------------------------------------------------------------------------- /create_pipe/create_pipe.h: -------------------------------------------------------------------------------- 1 | #ifndef __aspawn_create_pipe_create_pipe_H__ 2 | # define __aspawn_create_pipe_create_pipe_H__ 3 | 4 | # include "../common.h" 5 | 6 | /** 7 | * @return 0 on success, (-errno) on failure. 8 | */ 9 | ALWAYS_INLINE int create_cloexec_pipe(int pipefd[2]); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /clone_internal/stack_growth.h: -------------------------------------------------------------------------------- 1 | #ifndef STACK_GROWS_DOWN 2 | 3 | # if defined(__hppa__) || defined(__ia64__) 4 | /** 5 | * STACK_GROWS_DOWN is a boolean, indicates whether stack grows down on this platform. 6 | */ 7 | # define STACK_GROWS_DOWN 0 8 | # else 9 | # define STACK_GROWS_DOWN 1 10 | # endif 11 | #endif 12 | -------------------------------------------------------------------------------- /syscall/clone.h: -------------------------------------------------------------------------------- 1 | #ifndef __aspawn_syscall_clone_H__ 2 | # define __aspawn_syscall_clone_H__ 3 | 4 | # include "../common.h" 5 | # include 6 | 7 | ALWAYS_INLINE int psys_clone(int (*fn)(void *arg), void *stack, int flags, void *arg, 8 | /* optional args */ pid_t *parent_tid, void *tls, pid_t *child_tid); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /signal/signal.c: -------------------------------------------------------------------------------- 1 | # define _POSIX_C_SOURCE 201107L 2 | # include 3 | 4 | #include "signal.h" 5 | #include "../syscall/syscall.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | int sig_blockall(void *oldset) 12 | { 13 | sigset_t set; 14 | pure_sigfillset(&set); 15 | return psys_sigprocmask(SIG_BLOCK, &set, oldset); 16 | } 17 | 18 | int sig_setmask(const void *set) 19 | { 20 | return psys_sigprocmask(SIG_SETMASK, set, NULL); 21 | } 22 | -------------------------------------------------------------------------------- /signal/signal.h: -------------------------------------------------------------------------------- 1 | #ifndef __aspawn_signal_signal_H__ 2 | # define __aspawn_signal_signal_H__ 3 | 4 | # include "../common.h" 5 | 6 | ALWAYS_INLINE int psys_sig_clear_handler(int signum); 7 | ALWAYS_INLINE void psys_sig_clearall_handler(); 8 | 9 | /** 10 | * @return 0 on success, (-errno) on failure. 11 | */ 12 | ALWAYS_INLINE int sig_blockall(void *oldset); 13 | 14 | /** 15 | * @return 0 on success, (-errno) on failure. 16 | */ 17 | ALWAYS_INLINE int sig_setmask(const void *set); 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /syscall/memory.c: -------------------------------------------------------------------------------- 1 | #include "syscall.h" 2 | 3 | size_t pstrlen(const char *s) 4 | { 5 | size_t len = 0; 6 | for (; s[len] != '\0'; ++len); 7 | return len; 8 | } 9 | void pstrcpy(char *dest, const char *src, size_t n) 10 | { 11 | pmemcpy(dest, src, n); 12 | dest[n] = '\0'; 13 | } 14 | 15 | void pmemset(void *s, int c, size_t n) 16 | { 17 | char *p = s; 18 | for (size_t i = 0; i != n; ++i) 19 | p[i] = c; 20 | } 21 | 22 | void pmemcpy(void *dest, const void *src, size_t n) 23 | { 24 | char *destp = dest; 25 | const char *srcp = src; 26 | 27 | for (size_t i = 0; i != n; ++i) 28 | destp[i] = srcp[i]; 29 | } 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | CC = clang 2 | 3 | CFLAGS := -fPIC -std=c11 -Wall -g -fsanitize=address 4 | LDFLAGS := -fPIC -fuse-ld=lld -Wl,--as-needed 5 | 6 | ## Objects to build 7 | SRCS := $(shell find -name '*.c' ! -name 'utility.c') 8 | OUTS := $(SRCS:.c=.out) 9 | 10 | ## Build rules 11 | test: $(OUTS) 12 | ./run_tests.sh "$(OUTS)" 13 | 14 | utility.o: utility.c utility.h 15 | $(CC) $(CFLAGS) $< -c -o $@ 16 | 17 | ../libaspawn.a: 18 | $(MAKE) -C ../ 19 | 20 | %.out: %.c ../aspawn.h ../syscall/syscall.h ../libaspawn.a Makefile utility.o 21 | $(CC) $(CFLAGS) $(LDFLAGS) $< utility.o ../libaspawn.a -o $@ 22 | 23 | clean: 24 | rm -f $(OUTS) utility.o 25 | 26 | .PHONY: test clean ../libaspawn.a 27 | 28 | ## Dependencies 29 | test_syscall.out: ../syscall/clone3.h 30 | -------------------------------------------------------------------------------- /.github/workflows/c-cpp.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-20.04 13 | # Below is adapted from: https://github.community/t/create-matrix-with-multiple-os-and-env-for-each-one/16895 14 | strategy: 15 | matrix: 16 | include: 17 | - os: ubuntu-18.04 # Use linux kernel 5.4, which doesn't have clone3 18 | - os: ubuntu-20.04 # Use linux kernel that have clone3 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: install dependencies 23 | run: sudo apt-get update && sudo apt-get install -y llvm clang lld 24 | - name: Echo environment 25 | run: uname -a && clang --version && which echo 26 | - name: Testing 27 | run: DEBUG=true USE_SANITIZER=true make test -j $(nproc) 28 | -------------------------------------------------------------------------------- /cached_stack/cached_stack.h: -------------------------------------------------------------------------------- 1 | #ifndef __aspawn_cached_stack_cached_stack_H__ 2 | # define __aspawn_cached_stack_cached_stack_H__ 3 | 4 | # include "../common.h" 5 | # include 6 | 7 | ALWAYS_INLINE void init_cached_stack_internal(struct Stack_t *cached_stack); 8 | 9 | /** 10 | * @return 0 on success, (-errno) on failure. 11 | */ 12 | ALWAYS_INLINE int cleanup_cached_stack_internal(const struct Stack_t *cached_stack); 13 | 14 | /** 15 | * First, align size to meet mmap and clone's requirement. 16 | * Then, (re)allocate the stack to `size` if the current one isn't large enough.. 17 | * 18 | * @return 0 on success, (-errno) on failure. 19 | */ 20 | ALWAYS_INLINE int allocate_stack(struct Stack_t *cached_stack, size_t size, size_t obj_to_place_on_stack_len); 21 | 22 | /** 23 | * @pre len <= stack->size 24 | */ 25 | ALWAYS_INLINE void* allocate_obj_on_stack(struct Stack_t *stack, size_t len); 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /benchmark/Makefile: -------------------------------------------------------------------------------- 1 | CC = clang 2 | CXX = clang++ 3 | 4 | CFLAGS := -Ofast -Wall -flto 5 | CFLAGS += -fno-asynchronous-unwind-tables -fno-unwind-tables -fmerge-all-constants 6 | 7 | CXXFLAGS := -fno-exceptions -fno-rtti 8 | 9 | LDFLAGS := -L /usr/local/lib/ -s -Wl,-icf=all,--gc-sections -flto -Wl,--plugin-opt=O3 -fuse-ld=lld 10 | LIBS := -lbenchmark -lpthread 11 | 12 | ## Objects to build 13 | C_SRCS := $(wildcard *.c) 14 | CXX_SRCS := $(wildcard *.cc) 15 | OUTS := $(C_SRCS:.c=.out) $(CXX_SRCS:.cc=.out) 16 | 17 | benchmark: pre-build $(OUTS) 18 | ./run_benchmark.sh "$(OUTS)" 19 | 20 | pre-build: 21 | $(MAKE) -C ../ 22 | 23 | ../libaspawn.so: 24 | $(MAKE) -C ../ 25 | 26 | %.out: %.c ../aspawn.h ../syscall/syscall.h ../libaspawn.so Makefile 27 | $(CC) -std=c11 $(CFLAGS) $(LDFLAGS) $(LIBS) $< ../libaspawn.so -o $@ 28 | 29 | %.out: %.cc ../aspawn.h ../syscall/syscall.h ../libaspawn.so Makefile 30 | $(CXX) -std=c++17 $(CFLAGS) $(CXXFLAGS) $(LDFLAGS) $(LIBS) $< ../libaspawn.so -o $@ 31 | 32 | clean: 33 | rm -f $(OUTS) 34 | 35 | .PHONY: benchmark clean 36 | -------------------------------------------------------------------------------- /test/utility.h: -------------------------------------------------------------------------------- 1 | #ifndef __aspawn_test_utility_H__ 2 | # define __aspawn_test_utility_H__ 3 | 4 | # include 5 | # include 6 | 7 | # define ASSERT_SYSCALL(expr) \ 8 | ({ \ 9 | int result = (expr); \ 10 | if (result < 0) \ 11 | err(1, "%s on line %zu failed", # expr, (size_t) __LINE__); \ 12 | result; \ 13 | }) 14 | 15 | void assert_aspawnf_internal(int result, const char *msg); 16 | 17 | # define ASSERT_ASPAWNF(expr) \ 18 | ({ \ 19 | int ret = (expr); \ 20 | assert_aspawnf_internal( \ 21 | ret, \ 22 | # expr "failed" \ 23 | ); \ 24 | ret; \ 25 | }) 26 | 27 | int test_aspawn_fn(void *arg, int write_end_fd, void *old_sigset); 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /clone_internal/clone_internal.h: -------------------------------------------------------------------------------- 1 | #ifndef __aspawn_clone_internal_clone_internal_H__ 2 | # define __aspawn_clone_internal_clone_internal_H__ 3 | 4 | # include "../common.h" 5 | # include 6 | 7 | /** 8 | * @return 0 on success, (-errno) on failure. 9 | */ 10 | ALWAYS_INLINE int clone_internal(int (*fn)(void *arg), void *arg, const struct Stack_t *stack); 11 | 12 | /** 13 | * HAS_CLONE_CLEAR_SIGHAND_INTERNAL is a compile time constant. 14 | * 15 | * You still need to test for -ENOSYS which can be returned from clone_clear_sighand_internal. 16 | */ 17 | # ifdef SYS_clone3 18 | # define HAS_CLONE_CLEAR_SIGHAND_INTERNAL 1 19 | # else 20 | # define HAS_CLONE_CLEAR_SIGHAND_INTERNAL 0 21 | # endif 22 | 23 | /** 24 | * @return -ENOSYS if syscall clone3 is not supported clone3. 25 | * -EINVAL might mean that CLONE_CLEAR_SIGHAND is not supported, or stack is not aligned, 26 | * according to https://elixir.bootlin.com/linux/latest/source/kernel/fork.c. 27 | */ 28 | ALWAYS_INLINE int clone_clear_sighand_internal(int (*fn)(void *arg), void *arg, const struct Stack_t *stack); 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /syscall/clone3.h: -------------------------------------------------------------------------------- 1 | #ifndef __aspawn_syscall_clone3_H__ 2 | # define __aspawn_syscall_clone3_H__ 3 | 4 | # include "../common.h" 5 | # include 6 | # include 7 | 8 | struct psys_clone_args { 9 | __aligned_u64 flags; /* Flags bit mask */ 10 | __aligned_u64 pidfd; /* Where to store PID file descriptor (pid_t *) */ 11 | __aligned_u64 child_tid; /* Where to store child TID, in child's memory (pid_t *) */ 12 | __aligned_u64 parent_tid; /* Where to store child TID, in parent's memory (int *) */ 13 | __aligned_u64 exit_signal; /* Signal to deliver to parent on child termination */ 14 | __aligned_u64 stack; /* Pointer to lowest byte of stack */ 15 | __aligned_u64 stack_size; /* Size of stack */ 16 | __aligned_u64 tls; /* Location of new TLS */ 17 | __aligned_u64 set_tid; /* Pointer to a pid_t array */ 18 | __aligned_u64 set_tid_size; /* Number of elements in set_tid */ 19 | }; 20 | 21 | # define CLONE_CLEAR_SIGHAND 0x100000000ULL /* Clear any signal handler and reset to SIG_DFL. */ 22 | 23 | ALWAYS_INLINE long psys_clone3(struct psys_clone_args *cl_args, size_t size, int (*fn)(void *arg), void *arg); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /syscall/clone3.c: -------------------------------------------------------------------------------- 1 | #include "clone3.h" 2 | #include "make_syscall.h" 3 | #include "syscall.h" 4 | 5 | #include 6 | #include 7 | 8 | long psys_clone3(struct psys_clone_args *cl_args, size_t size, int (*fn)(void *arg), void *arg) 9 | { 10 | #ifdef SYS_clone3 11 | typedef int (*fn_t)(void *arg); 12 | 13 | // TODO: Remove manual register allocation as compiler knows exactly 14 | // which register needs to spoil for the syscall 15 | register fn_t fn_reg __asm__ ("r13"); 16 | register void *arg_reg __asm__ ("r12"); 17 | register int result __asm__ ("rax"); 18 | 19 | fn_reg = fn; 20 | arg_reg = arg; 21 | 22 | // invoke_syscall is always inline, thus no need of worring about the affect of switching to a new stack, 23 | // since no function return (req + pop) will be performed, and the return value is in the register. 24 | // 25 | // The compiler probably won't spoil the return value from the register to the stack. 26 | result = INTERNAL_SYSCALL(SYS_clone3, 2, cl_args, size); 27 | 28 | // If this is the child 29 | if (result == 0) { 30 | RESET_EBP(); 31 | 32 | int exit_status = fn_reg(arg_reg); 33 | psys_exit(exit_status); 34 | __builtin_unreachable(); 35 | } 36 | 37 | return result; 38 | #else 39 | return -ENOSYS; 40 | #endif 41 | } 42 | -------------------------------------------------------------------------------- /clone_internal/clone_internal.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include "../aspawn.h" 4 | 5 | #include "clone_internal.h" 6 | #include "stack_growth.h" 7 | 8 | #include "../syscall/clone3.h" 9 | #include "../syscall/clone.h" 10 | #include 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #define STACK(addr, len) ((addr) + (len) * STACK_GROWS_DOWN) 20 | 21 | int clone_internal(int (*fn)(void *arg), void *arg, const struct Stack_t *stack) 22 | { 23 | return psys_clone(fn, STACK((char*) stack->addr, stack->size), CLONE_VM | SIGCHLD, arg, 24 | /* optional args */ NULL, NULL, NULL); 25 | } 26 | 27 | int clone_clear_sighand_internal(int (*fn)(void *arg), void *arg, const struct Stack_t *stack) 28 | { 29 | #ifdef SYS_clone3 30 | struct psys_clone_args cl_args = { 31 | .flags = CLONE_VM | CLONE_CLEAR_SIGHAND, 32 | 33 | .pidfd = (uint64_t) NULL, 34 | .child_tid = (uint64_t) NULL, 35 | .parent_tid = (uint64_t) NULL, 36 | 37 | .exit_signal = SIGCHLD, 38 | 39 | .stack = (uint64_t) stack->addr, 40 | .stack_size = stack->size, 41 | 42 | .tls = (uint64_t) NULL, 43 | .set_tid = (uint64_t) NULL, 44 | .set_tid_size = 0, 45 | }; 46 | 47 | return psys_clone3(&cl_args, sizeof(cl_args), fn, arg); 48 | #else 49 | return -ENOSYS; 50 | #endif 51 | } 52 | -------------------------------------------------------------------------------- /test/test_aspawn.c: -------------------------------------------------------------------------------- 1 | #include "../aspawn.h" 2 | #include "../syscall/syscall.h" 3 | 4 | #include "utility.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | 21 | int main(int argc, char* argv[]) 22 | { 23 | /* Get path */ 24 | const char *path = getenv("PATH"); 25 | assert(path != NULL); 26 | 27 | const size_t path_sz = strlen(path) + 1; 28 | 29 | assert(path[0] != '\0'); 30 | 31 | /* Setup stack */ 32 | struct Stack_t stack; 33 | 34 | init_cached_stack(&stack); 35 | 36 | pid_t pids[3]; 37 | for (size_t i = 0; i != 3; ++i) { 38 | reserve_stack(&stack, PATH_MAX + 1, path_sz); 39 | 40 | void *new_path = allocate_obj_on_stack(&stack, path_sz); 41 | memcpy(new_path, path, path_sz); 42 | 43 | int result = ASSERT_ASPAWNF( 44 | aspawn(&pids[i], &stack, test_aspawn_fn, new_path) 45 | ); 46 | 47 | struct pollfd pfd = { 48 | .fd = result, 49 | .events = POLLHUP, 50 | }; 51 | if (poll(&pfd, 1, -1) < 0) 52 | err(1, "poll failed"); 53 | 54 | close(result); 55 | 56 | int wstatus; 57 | ASSERT_SYSCALL((wait(&wstatus))); 58 | assert(!WIFSIGNALED(wstatus)); 59 | assert(WEXITSTATUS(wstatus) == 0); 60 | } 61 | 62 | ASSERT_ASPAWNF(cleanup_stack(&stack)); 63 | 64 | return 0; 65 | } 66 | -------------------------------------------------------------------------------- /test/utility.c: -------------------------------------------------------------------------------- 1 | #include "utility.h" 2 | 3 | #include "../aspawn.h" 4 | #include "../syscall/syscall.h" 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | void psys_put_impl(int fd, const char *s, size_t len) 12 | { 13 | size_t i = 0; 14 | do { 15 | ssize_t result = psys_write(fd, s + i, len - i); 16 | if (result < 0) 17 | return; 18 | i += result; 19 | } while (i != len); 20 | } 21 | #define psys_put(fd, s) psys_put_impl((fd), (s), sizeof(s)) 22 | #define psys_err(exit_status, s) \ 23 | do { \ 24 | psys_put(2, s "\n"); \ 25 | return (exit_status); \ 26 | } while (0) 27 | 28 | int psys_execvep(const char *file, size_t file_len, const char * const argv[], const char *path) 29 | { 30 | static const char * const envp[] = {NULL}; 31 | 32 | char constructed_path[PATH_MAX + 1]; 33 | 34 | for (int got_eaccess = 0; ; ) { 35 | int result; 36 | switch (find_exe(file, file_len, constructed_path, &path, PATH_MAX)) { 37 | case 1: 38 | result = handle_execve_err(psys_execve(constructed_path, argv, envp), &got_eaccess); 39 | if (result < 0) 40 | psys_err(1, "Executable is found, but failed to execute it"); 41 | continue; 42 | 43 | case -1: 44 | psys_err(1, "One of the environment path is longer than PATH_MAX"); 45 | 46 | case 0: 47 | break; 48 | } 49 | if (got_eaccess) 50 | psys_err(1, "Executable is found but execve failed"); 51 | psys_err(1, "Executable not found"); 52 | } 53 | } 54 | int test_aspawn_fn(void *arg, int write_end_fd, void *old_sigset) 55 | { 56 | static const char * const argv[] = {"echo", "-e", "\nHello,", "world!", NULL}; 57 | 58 | return psys_execvep(argv[0], 4, argv, arg); 59 | } 60 | 61 | void assert_aspawnf_internal(int result, const char *msg) 62 | { 63 | if (result < 0) { 64 | errno = -result; 65 | err(1, "%s", msg); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /syscall/clone.c: -------------------------------------------------------------------------------- 1 | #include "clone.h" 2 | #include "make_syscall.h" 3 | #include "syscall.h" 4 | 5 | #include 6 | #include 7 | 8 | /** 9 | * 10 | * __clone(func, stack, flags, arg, ptid, tls, ctid) 11 | * # %rdi, %rsi, %rdx, %rcx, %r8, and %r9 12 | * 13 | * xor %eax,%eax 14 | * 15 | mov $56,%al # syscall_number 16 | 17 | mov 3rd arg,%rdi # syscall arg1 18 | 19 | # rsi is the 2st arg 20 | and $-16,%rsi # syscall arg2 21 | sub $8,%rsi 22 | mov 4th arg,(%rsi) 23 | 24 | mov 5th arg,%rdx # syscall arg3 25 | mov 7th arg,%r10 # syscall arg4 26 | mov 6th arg,%r8 # syscall arg5 27 | 28 | mov 1st arg, syscall arg6 # I believe this isn't part of syscall argument 29 | 30 | syscall 31 | 32 | if (eax == 0) { 33 | xor %ebp,%ebp 34 | 35 | mov %rcx (arg), %rdi 36 | call fp 37 | 38 | mov %eax,%edi 39 | xor %eax,%eax 40 | mov $60,%al 41 | syscall 42 | hlt 43 | } 44 | ret 45 | 46 | */ 47 | 48 | int psys_clone(int (*fn)(void *arg), void *stack, int flags, void *arg, 49 | pid_t *parent_tid, void *tls, pid_t *child_tid) 50 | { 51 | typedef int (*fn_t)(void *arg); 52 | 53 | // TODO: Remove manual register allocation as compiler knows exactly 54 | // which register needs to spoil for the syscall 55 | register fn_t fn_reg __asm__ ("r13"); 56 | register void *arg_reg __asm__ ("r12"); 57 | register int result __asm__ ("rax"); 58 | 59 | fn_reg = fn; 60 | arg_reg = arg; 61 | 62 | //stack = (void*) (((uintptr_t) stack) & ((uintptr_t) -16)); 63 | 64 | // invoke_syscall is always inline, thus no need of worring about the affect of switching to a new stack, 65 | // since no function return (req + pop) will be performed, and the return value is in the register. 66 | result = INTERNAL_SYSCALL(SYS_clone, 5, flags, stack, parent_tid, child_tid, tls); 67 | 68 | // If this is the child 69 | if (result == 0) { 70 | RESET_EBP(); 71 | 72 | int exit_status = fn_reg(arg_reg); 73 | psys_exit(exit_status); 74 | __builtin_unreachable(); 75 | } 76 | 77 | return result; 78 | } 79 | -------------------------------------------------------------------------------- /syscall/signal.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 201107L 2 | 3 | #include "syscall.h" 4 | #include "make_syscall.h" 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | void pure_sigemptyset(void *set) 11 | { 12 | pmemset(set, 0, sizeof(sigset_t)); 13 | } 14 | void pure_sigfillset(void *set) 15 | { 16 | pmemset(set, -1, sizeof(sigset_t)); 17 | } 18 | 19 | /* This is the sigaction structure from the Linux 3.2 kernel. */ 20 | struct psys_kernel_sigaction { 21 | void (*psys_sa_handler)(int); 22 | unsigned long psys_sa_flags; 23 | void (*psys_sa_restorer) (void); 24 | /* glibc sigset is larger than kernel expected one, however sigaction 25 | passes the kernel expected size on rt_sigaction syscall. */ 26 | sigset_t sa_mask; 27 | }; 28 | 29 | /** 30 | * The code below on PSIGRTMAX is adapted from glibc. 31 | */ 32 | #ifdef __linux__ 33 | # ifdef __mips__ 34 | /** 35 | * On mips, maximum rt signal is 127 in linux. 36 | */ 37 | # define PSIGRTMAX 127 38 | # else 39 | /** 40 | * On most platform, linux has maximum rt signal as 64. 41 | */ 42 | # define PSIGRTMAX 64 43 | # endif 44 | #else 45 | /** 46 | * On other platform, there seems to be no real time signal support. 47 | */ 48 | # define PSIGRTMAX 32 49 | #endif 50 | 51 | #define NSIG (PSIGRTMAX + 1) 52 | 53 | #define UCHAR_WIDTH 8 54 | #define ULONG_WIDTH (sizeof(unsigned long) * UCHAR_WIDTH) 55 | 56 | #define ALIGN_DOWN(base, size) ((base) & -((__typeof__ (base)) (size))) 57 | #define ALIGN_UP(base, size) ALIGN_DOWN ((base) + (size) - 1, (size)) 58 | 59 | #define NSIG_WORDS (ALIGN_UP((NSIG - 1), ULONG_WIDTH) / ULONG_WIDTH) 60 | #define NSIG_BYTES (NSIG_WORDS * (ULONG_WIDTH / UCHAR_WIDTH)) 61 | 62 | int psys_sig_clear_handler(int signum) 63 | { 64 | struct psys_kernel_sigaction act; 65 | 66 | pmemset(&act, 0, sizeof(act)); 67 | 68 | if (SIG_DFL != 0) 69 | act.psys_sa_handler = SIG_DFL; 70 | 71 | return INTERNAL_SYSCALL(SYS_rt_sigaction, 4, signum, &act, NULL, NSIG_BYTES); 72 | } 73 | void psys_sig_clearall_handler() 74 | { 75 | for (int sig = 1; sig < NSIG; ++sig) 76 | psys_sig_clear_handler(sig); 77 | } 78 | 79 | int psys_sigprocmask(int how, const void *set, void *oldset) 80 | { 81 | return INTERNAL_SYSCALL(SYS_rt_sigprocmask, 4, how, set, oldset, NSIG_BYTES); 82 | } 83 | -------------------------------------------------------------------------------- /cached_stack/cached_stack.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include "../aspawn.h" 4 | #include "cached_stack.h" 5 | #include "../clone_internal/stack_growth.h" 6 | #include "../syscall/syscall.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | void init_cached_stack_internal(struct Stack_t *cached_stack) 13 | { 14 | cached_stack->addr = NULL; 15 | cached_stack->size = 0; 16 | } 17 | 18 | int cleanup_cached_stack_internal(const struct Stack_t *cached_stack) 19 | { 20 | if (cached_stack->addr != NULL && cached_stack->size != 0) 21 | return psys_munmap(cached_stack->addr, cached_stack->size); 22 | 23 | return 0; 24 | } 25 | 26 | size_t align(size_t sz, size_t alignment) 27 | { 28 | size_t remnant = sz % alignment; 29 | return sz + ((remnant != 0) ? (alignment - remnant) : 0); 30 | } 31 | size_t align_to_page(size_t sz) 32 | { 33 | return align(sz, psys_get_pagesz()); 34 | } 35 | size_t align_stack_sz(size_t sz) 36 | { 37 | sz = align_to_page(sz); 38 | return sz; 39 | } 40 | 41 | int allocate_stack(struct Stack_t *cached_stack, size_t size, size_t obj_to_place_on_stack_len) 42 | { 43 | const int prot = PROT_READ | PROT_WRITE; 44 | const int flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK; 45 | 46 | size_t stack_size = align_to_page(align_stack_sz(size) + obj_to_place_on_stack_len); 47 | 48 | void *stack; 49 | 50 | /** 51 | * TODO: Allocate a 'null' page that is inaccessible at the end of the stack to help preventing 52 | * stack overflow error from overwriting other memory region. 53 | * 54 | * Use mmap + mprotect 55 | */ 56 | 57 | int errno_v; 58 | 59 | if (cached_stack->addr != NULL) { 60 | if (cached_stack->size < stack_size) 61 | stack = psys_mremap(&errno_v, cached_stack->addr, cached_stack->size, stack_size, 62 | MREMAP_MAYMOVE, NULL); 63 | else 64 | return 0; 65 | } else 66 | stack = psys_mmap(&errno_v, NULL, stack_size, prot, flags, -1, 0); 67 | 68 | if (stack == MAP_FAILED) 69 | return -errno_v; 70 | cached_stack->addr = stack; 71 | cached_stack->size = stack_size; 72 | 73 | return 0; 74 | } 75 | 76 | void* allocate_obj_on_stack(struct Stack_t *stack, size_t len) 77 | { 78 | void *ret; 79 | 80 | stack->size -= len; 81 | if (STACK_GROWS_DOWN) { 82 | ret = ((char*) stack->addr) + stack->size; 83 | } else { 84 | ret = stack->addr; 85 | stack->addr = ((char*) stack->addr) + len; 86 | } 87 | 88 | return ret; 89 | } 90 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = clang 2 | CXX = clang++ 3 | 4 | # Path 5 | BUILD_DIR ?= . 6 | ## Install prefix 7 | PREFIX ?= /usr/local/ 8 | 9 | # Prepare $(BUILD_DIR) 10 | dirs=cached_stack create_pipe syscall signal clone_internal 11 | $(shell mkdir -p $(addprefix $(BUILD_DIR)/, $(dirs))) 12 | 13 | ifeq ($(DEBUG), true) 14 | CFLAGS := -Og -g -Wall 15 | 16 | ifeq ($(USE_SANITIZER), true) 17 | CFLAGS += -fsanitize=address 18 | endif 19 | 20 | else 21 | CFLAGS := -O2 -fvisibility=hidden -Wall -flto 22 | CFLAGS += -fno-asynchronous-unwind-tables -fno-unwind-tables -fmerge-all-constants 23 | endif 24 | 25 | CXXFLAGS := -fno-exceptions -fno-rtti 26 | 27 | LDFLAGS = -s -shared -Wl,-soname,$@ -Wl,-icf=all,--gc-sections -flto -Wl,--plugin-opt=O3 -fuse-ld=lld 28 | 29 | ## Objects to build 30 | SRCS := $(shell find . -name '*.c' -a ! -wholename './test/*' -a ! -wholename './benchmark/*' -a ! -wholename './example/*') 31 | OBJS := $(addprefix $(BUILD_DIR)/, $(SRCS:.c=.o)) 32 | 33 | TARGETS := $(BUILD_DIR)/libaspawn.so $(BUILD_DIR)/libaspawn.a 34 | 35 | .DEFAULT_GOAL := all 36 | 37 | ## Automatic dependency building 38 | DEPFLAGS = -MT $@ -MMD -MP -MF $(BUILD_DIR)/$*.Td 39 | DEPFILES := $(OBJS:%.o=%.d) 40 | 41 | ## Dummy target for $(DEPFILES) when they are not present. 42 | $(DEPFILES): 43 | ## Use wildcard so that nonexisitent dep files are ignored. 44 | include $(wildcard $(DEPFILES)) 45 | 46 | ## Build rules 47 | .SECONDEXPANSION: 48 | 49 | all: $(TARGETS) 50 | 51 | $(BUILD_DIR)/libaspawn.so: $(OBJS) 52 | $(CC) -std=c11 -fPIC $(LDFLAGS) -o $@ $^ 53 | 54 | $(BUILD_DIR)/libaspawn.a: $(OBJS) 55 | llvm-ar rcsuT $@ $^ 56 | 57 | $(BUILD_DIR)/%.o: %.c $$(wildcard %.h) Makefile 58 | $(CC) -std=c11 -fPIC -c $(CFLAGS) $(DEPFLAGS) -o $@ $< 59 | mv -f $(BUILD_DIR)/$*.Td $(BUILD_DIR)/$*.d && touch $@ 60 | 61 | $(BUILD_DIR)/%.o: %.cc $$(wildcard %.h) $$(wildcard %.hpp) Makefile 62 | $(CXX) -std=c++17 -fPIC -c $(CXXFLAGS) $(CFLAGS) $(DEPFLAGS) -o $@ $< 63 | mv -f $(BUILD_DIR)/$*.Td $(BUILD_DIR)/$*.d && touch $@ 64 | 65 | ### Specialize rule 66 | $(BUILD_DIR)/syscall/memory.o: syscall/memory.c syscall/syscall.h Makefile 67 | $(CC) -std=c11 -fPIC -c $(CFLAGS) $(DEPFLAGS) -Ofast -fno-builtin -o $@ $< 68 | mv -f $(BUILD_DIR)/$*.Td $(BUILD_DIR)/$*.d && touch $@ 69 | 70 | clean: 71 | rm -f $(OBJS) $(TARGETS) $(DEPFILES) 72 | 73 | test: $(BUILD_DIR)/libaspawn.a 74 | $(MAKE) -C test 75 | 76 | install: $(TARGETS) aspawn.h common.h syscall/syscall.h 77 | cp $(TARGETS) $(PREFIX)/lib/ 78 | mkdir -p $(PREFIX)/include/aspawn/syscall 79 | cp aspawn.h common.h $(PREFIX)/include/aspawn/ 80 | cp syscall/syscall.h $(PREFIX)/include/aspawn/syscall/ 81 | .PHONY: clean test install all 82 | -------------------------------------------------------------------------------- /aspawn.h: -------------------------------------------------------------------------------- 1 | #ifndef __aspawn_aspawn_H__ 2 | # define __aspawn_aspawn_H__ 3 | 4 | /** 5 | * Make sure that function symbols in this project are immediately resolved if dynamically linked, 6 | * or make sure this library is statically linked. 7 | */ 8 | 9 | # include 10 | # include 11 | # include 12 | # include 13 | # include "common.h" 14 | 15 | # ifdef __cplusplus 16 | extern "C" { 17 | # endif 18 | 19 | struct Stack_t { 20 | void *addr; 21 | size_t size; 22 | }; 23 | 24 | PUBLIC void init_cached_stack(struct Stack_t *cached_stack); 25 | 26 | /** 27 | * @param reserved_stack_sz should be the maximum size of variables that will be 28 | * defined in the stack of the child. 29 | * @return 0 on success, (-errno) on failure. 30 | * 31 | * First, align size to meet mmap and clone's requirement 32 | * Then, (re)allocate the stack to `size` if the current one isn't large enough. 33 | */ 34 | PUBLIC int reserve_stack( 35 | struct Stack_t *cached_stack, 36 | size_t reserved_stack_sz, size_t obj_to_place_on_stack_len 37 | ); 38 | 39 | /** 40 | * @pre You must have called reserved_stack(stack, ..., len) 41 | * 42 | * The way allocate_obj_on_stack allocates object is similar to how stack is used 43 | * during normal function execution. 44 | * 45 | * This is done to improve locality and avoid allocating an empty page just 46 | * for these objects. 47 | */ 48 | PUBLIC void* allocate_obj_on_stack(struct Stack_t *stack, size_t len); 49 | 50 | /** 51 | * @param old_sigset of type sigset_t*. The original value of sigmask. 52 | * It can be modified to any value user desired. 53 | * 54 | * The value of sigmask in aspawn_fn is unspecified. 55 | */ 56 | typedef int (*aspawn_fn)(void *arg, int wirte_end_fd, void *old_sigset); 57 | 58 | /** 59 | * @param pid the pid of the child will be stored into it on success. 60 | * @param cached_stack must call reserve_stack before using aspawn 61 | * @param fn If fn returns, then the child will exit with the return value of fn as the exit code. 62 | * @return fd of read end of CLOEXEC pipe if success, eitherwise (-errno). 63 | * 64 | * aspawn would disable thread cancellation, then it would revert it before return. 65 | * 66 | * aspawn would also mask all signals in parent and reset the signal handler in the child process. 67 | * Before aspawn returns in parent, it would revert the signal mask. 68 | * 69 | * In the function fn, you can only use syscall declared in syscall/syscall.h 70 | * Use of any glibc function or any function that modifies global/thread-local variable is undefined behavior. 71 | */ 72 | PUBLIC int aspawn(pid_t *pid, const struct Stack_t *cached_stack, aspawn_fn fn, void *arg); 73 | 74 | /** 75 | * recursive version of aspawn that can be called inside aspawn_fn. 76 | * 77 | * @param old_sigset should be old_sigset from aspawn_fn. 78 | */ 79 | PUBLIC int aspawn_rec(pid_t *pid, const struct Stack_t *cached_stack, 80 | aspawn_fn fn, void *arg, const void *old_sigset); 81 | 82 | /** 83 | * @param cached_stack must be 84 | * @return 0 on success, (-errno) on failure. 85 | * 86 | * To reuse the destroyed stack, call init_cached_stack again. 87 | */ 88 | PUBLIC int cleanup_stack(const struct Stack_t *cached_stack); 89 | 90 | # ifdef __cplusplus 91 | } 92 | # endif 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /aspawn.c: -------------------------------------------------------------------------------- 1 | # define _POSIX_C_SOURCE 201107L 2 | 3 | #include "signal/signal.h" 4 | 5 | #include "aspawn.h" 6 | #include "cached_stack/cached_stack.h" 7 | #include "clone_internal/clone_internal.h" 8 | #include "create_pipe/create_pipe.h" 9 | #include "syscall/syscall.h" 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | struct aspawn_child_args { 23 | /* Improve locality of data */ 24 | 25 | int pipefd[2]; 26 | 27 | aspawn_fn fn; 28 | void *arg; 29 | 30 | /* These fields aren't used in aspawn_child, only taken address of */ 31 | sigset_t old_sigset; 32 | }; 33 | 34 | void init_cached_stack(struct Stack_t *cached_stack) 35 | { 36 | init_cached_stack_internal(cached_stack); 37 | } 38 | 39 | int cleanup_stack(const struct Stack_t *cached_stack) 40 | { 41 | return cleanup_cached_stack_internal(cached_stack); 42 | } 43 | 44 | int reserve_stack( 45 | struct Stack_t *cached_stack, 46 | size_t reserved_stack_sz, size_t obj_to_place_on_stack_len 47 | ) 48 | { 49 | reserved_stack_sz += (32 * 1024); 50 | obj_to_place_on_stack_len += sizeof(struct aspawn_child_args); 51 | return allocate_stack(cached_stack, reserved_stack_sz, obj_to_place_on_stack_len); 52 | } 53 | 54 | int aspawn_child(void *arg); 55 | int aspawn_child_clear_sighand(void *arg) 56 | { 57 | /* 58 | * Clear all signal handler so that even user decided to restore masks, 59 | * the sig handler of the parent won't be accidentally called. 60 | */ 61 | psys_sig_clearall_handler(); 62 | 63 | return aspawn_child(arg); 64 | } 65 | int aspawn_child(void *arg) 66 | { 67 | struct aspawn_child_args *args = arg; 68 | 69 | /* 70 | * This should always succeeds, unless there is bug in this library or the user of 71 | * this library. 72 | */ 73 | int ret = psys_close(args->pipefd[0]); 74 | # ifndef NDEBUG 75 | if (ret < 0) 76 | perr(1, -ret, "psys_close in aspawn_child failed"); 77 | # endif 78 | 79 | return args->fn(args->arg, args->pipefd[1], &args->old_sigset); 80 | } 81 | int aspawn_impl(pid_t *pid, const struct Stack_t *cached_stack, aspawn_fn fn, void *arg, 82 | const void *old_sigset) 83 | { 84 | int pipefd[2]; 85 | int result = create_cloexec_pipe(pipefd); 86 | if (result < 0) 87 | return result; 88 | 89 | struct Stack_t stack = *cached_stack; 90 | const size_t size = sizeof(struct aspawn_child_args); 91 | struct aspawn_child_args *args = allocate_obj_on_stack(&stack, size); 92 | 93 | args->pipefd[0] = pipefd[0]; 94 | args->pipefd[1] = pipefd[1]; 95 | 96 | args->fn = fn; 97 | args->arg = arg; 98 | pmemcpy(&args->old_sigset, old_sigset, sizeof(sigset_t)); 99 | 100 | int new_pid; 101 | 102 | if (!HAS_CLONE_CLEAR_SIGHAND_INTERNAL) { 103 | new_pid = clone_internal(aspawn_child_clear_sighand, args, &stack); 104 | } else { 105 | /* Initialized to 0(false) */ 106 | static atomic_bool does_not_have_clone_clear_sighand_internal; 107 | switch ((int) atomic_load(&does_not_have_clone_clear_sighand_internal)) { 108 | case 0: 109 | new_pid = clone_clear_sighand_internal(aspawn_child, args, &stack); 110 | if (new_pid != -ENOSYS && new_pid != -EINVAL) 111 | /* The clone3 syscall exists but fail */ 112 | break; 113 | /* The clone3 syscall doesn't exist, now fallback to clone */ 114 | atomic_store(&does_not_have_clone_clear_sighand_internal, 1); 115 | 116 | default: 117 | case 1: 118 | new_pid = clone_internal(aspawn_child_clear_sighand, args, &stack); 119 | } 120 | } 121 | 122 | if (new_pid >= 0) { 123 | *pid = new_pid; 124 | result = pipefd[0]; 125 | } else { 126 | result = new_pid; 127 | psys_close(pipefd[0]); 128 | } 129 | 130 | psys_close(pipefd[1]); 131 | 132 | return result; 133 | } 134 | int aspawn(pid_t *pid, const struct Stack_t *cached_stack, aspawn_fn fn, void *arg) 135 | { 136 | int oldstate; 137 | pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); 138 | 139 | sigset_t oldset; 140 | int result = sig_blockall(&oldset); 141 | 142 | if (result == 0) { 143 | result = aspawn_impl(pid, cached_stack, fn, arg, &oldset); 144 | sig_setmask(&oldset); 145 | } 146 | 147 | pthread_setcancelstate(oldstate, NULL); 148 | 149 | return result; 150 | } 151 | int aspawn_rec(pid_t *pid, const struct Stack_t *cached_stack, aspawn_fn fn, void *arg, 152 | const void *old_sigset) 153 | { 154 | return aspawn_impl(pid, cached_stack, fn, arg, old_sigset); 155 | } 156 | -------------------------------------------------------------------------------- /test/test_syscall.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | 6 | #include "../syscall/clone3.h" 7 | #include "../syscall/clone.h" 8 | #include "../syscall/syscall.h" 9 | 10 | #include "utility.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | int test_psys_clone3_fn(void *arg) 25 | { 26 | assert(arg == NULL); 27 | ASSERT_SYSCALL(raise(SIGURG)); 28 | 29 | return 0; 30 | } 31 | void test_psys_clone3_sa_handler(int signum) 32 | { 33 | errx(1, "received signal SIGURG in child."); 34 | } 35 | void test_psys_clone3() 36 | { 37 | #ifdef SYS_clone3 38 | struct sigaction act; 39 | struct sigaction oldact; 40 | 41 | memset(&act, 0, sizeof(act)); 42 | act.sa_handler = test_psys_clone3_sa_handler; 43 | 44 | ASSERT_SYSCALL((sigaction(SIGURG, &act, &oldact))); 45 | 46 | struct psys_clone_args cl_args = { 47 | .flags = CLONE_CLEAR_SIGHAND, 48 | 49 | .pidfd = (uint64_t) NULL, 50 | .child_tid = (uint64_t) NULL, 51 | .parent_tid = (uint64_t) NULL, 52 | 53 | .exit_signal = SIGCHLD, 54 | 55 | .stack = (uint64_t) NULL, 56 | .stack_size = 0, 57 | 58 | .tls = (uint64_t) NULL, 59 | .set_tid = (uint64_t) NULL, 60 | .set_tid_size = 0, 61 | }; 62 | 63 | long result = psys_clone3(&cl_args, sizeof(cl_args), test_psys_clone3_fn, NULL); 64 | if (result < 0) { 65 | if (-result == EINVAL) { 66 | warnx("In test_clone: You linux kernel does not support CLONE_CLEAR_SIGHAND"); 67 | return; 68 | } 69 | errno = -result; 70 | err(1, "%s on line %zu failed", "psys_clone3", (size_t) __LINE__); 71 | } 72 | 73 | int wstatus; 74 | pid_t pid; 75 | ASSERT_SYSCALL((pid = wait(&wstatus))); 76 | 77 | assert(pid == result); 78 | assert(!WIFSIGNALED(wstatus)); 79 | assert(WEXITSTATUS(wstatus) == 0); 80 | 81 | ASSERT_SYSCALL((sigaction(SIGURG, &oldact, NULL))); 82 | 83 | #endif 84 | } 85 | 86 | int test_psys_clone_fn(void *arg) 87 | { 88 | assert(arg == NULL); 89 | return 0; 90 | } 91 | void test_psys_clone() 92 | { 93 | int result = psys_clone(test_psys_clone_fn, NULL, SIGCHLD, NULL, /* optional args */ NULL, NULL, NULL); 94 | if (result < 0) { 95 | errno = -result; 96 | err(1, "%s on line %zu failed", "psys_clone", (size_t) __LINE__); 97 | } 98 | 99 | int wstatus; 100 | pid_t pid; 101 | ASSERT_SYSCALL((pid = wait(&wstatus))); 102 | 103 | assert(pid == result); 104 | assert(!WIFSIGNALED(wstatus)); 105 | assert(WEXITSTATUS(wstatus) == 0); 106 | } 107 | 108 | void test_psys_pipe2() 109 | { 110 | for (int i = 0; i != 10; ++i) { 111 | int pipefd[2]; 112 | int ret = psys_pipe2(pipefd, O_CLOEXEC); 113 | if (ret < 0) { 114 | errno = -ret; 115 | err(1, "psys_pipe2 failed"); 116 | } 117 | 118 | if (write(pipefd[1], "hello", sizeof("hello")) < 0) 119 | err(1, "write(%d) failed", pipefd[1]); 120 | char buffer[sizeof("hello")]; 121 | if (read(pipefd[0], buffer, sizeof(buffer)) < 0) 122 | err(1, "read(%d) failed", pipefd[0]); 123 | assert(strcmp(buffer, "hello") == 0); 124 | } 125 | } 126 | 127 | /** 128 | * test psys_m*ap. 129 | */ 130 | void test_psys_maps() 131 | { 132 | int errno_v; 133 | 134 | for (int i = 0; i != 10; ++i) { 135 | char *addr = psys_mmap(&errno_v, NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 136 | if (addr == MAP_FAILED) { 137 | errno = errno_v; 138 | err(1, "psys_mmap failed"); 139 | } 140 | for (size_t j = 0; j != 4096; ++j) { 141 | assert(addr[j] == 0); 142 | addr[j] = -1; 143 | assert(addr[j] == -1); 144 | } 145 | addr = psys_mremap(&errno_v, addr, 4096, 4096 * 2, MREMAP_MAYMOVE, NULL); 146 | if (addr == MAP_FAILED) { 147 | errno = errno_v; 148 | err(1, "psys_mremap failed"); 149 | } 150 | for (size_t j = 0; j != 4096; ++j) 151 | assert(addr[j] == -1); 152 | for (size_t j = 4096; j != 4096 * 2; ++j) { 153 | assert(addr[j] == 0); 154 | addr[j] = -1; 155 | assert(addr[j] == -1); 156 | } 157 | errno_v = psys_munmap(addr, 4096 * 2); 158 | if (errno_v < 0) { 159 | errno = errno_v; 160 | err(1, "psys_munmap failed"); 161 | } 162 | } 163 | } 164 | 165 | int main(int argc, char* argv[]) 166 | { 167 | test_psys_clone3(); 168 | test_psys_clone(); 169 | test_psys_pipe2(); 170 | test_psys_maps(); 171 | } 172 | -------------------------------------------------------------------------------- /example/naive_example.c: -------------------------------------------------------------------------------- 1 | /* cd /path/to/aspawn/repo 2 | * make -j $(nproc) 3 | * sudo make install 4 | * cd example/ 5 | * clang -std=c11 -L /usr/local/lib/ -laspawn example1.c 6 | * ./a.out 7 | * 8 | * If you prefer static link, then be sure to use `-fuse-ld=lld` 9 | */ 10 | 11 | #define _POSIX_C_SOURCE 200809L 12 | #define _GNU_SOURCE 13 | 14 | #include "../aspawn.h" 15 | #include "../syscall/syscall.h" 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | static const char * const argv1[] = {"echo", "-e", "\nHello,", "world!", NULL}; 31 | static const char * const argv2[] = {"ls", NULL}; 32 | static const char * const argv3[] = {"uname", "-a", NULL}; 33 | 34 | static const char * const * argvs[] = {argv1, argv2, argv3}; 35 | 36 | #define argv_cnt (sizeof(argvs) / sizeof(argvs[0])) 37 | 38 | void psys_put_impl(int fd, const char *s, size_t len) 39 | { 40 | size_t i = 0; 41 | do { 42 | ssize_t result = psys_write(fd, s + i, len - i); 43 | if (result < 0) 44 | return; 45 | i += result; 46 | } while (i != len); 47 | } 48 | #define psys_put(fd, s) psys_put_impl((fd), (s), sizeof(s)) 49 | #define psys_err(exit_status, fd, s) \ 50 | do { \ 51 | psys_put(fd, s "\n"); \ 52 | return (exit_status); \ 53 | } while (0) 54 | 55 | struct Args { 56 | const char * const * argv; 57 | char path[]; 58 | }; 59 | 60 | static int fn(void *arg, int write_end_fd, void *old_sigset) 61 | { 62 | static const char * const envp[] = {NULL}; 63 | 64 | struct Args *args = arg; 65 | 66 | const char * const * argv = args->argv; 67 | const char *path_env = args->path; 68 | size_t file_len = pstrlen(argv[0]); 69 | 70 | psys_sigprocmask(SIG_SETMASK, old_sigset, NULL); 71 | 72 | char constructed_path[PATH_MAX + 1]; 73 | 74 | for (int got_eaccess = 0; ; ) { 75 | int result; 76 | switch (find_exe(argv[0], file_len, constructed_path, &path_env, PATH_MAX)) { 77 | case 1: 78 | result = handle_execve_err(psys_execve(constructed_path, argv, envp), &got_eaccess); 79 | if (result < 0) 80 | psys_err(1, write_end_fd, "Executable is found, but failed to execute it"); 81 | continue; 82 | 83 | case -1: 84 | psys_err(1, write_end_fd, "One of the environment path is longer than PATH_MAX"); 85 | 86 | case 0: 87 | break; 88 | } 89 | if (got_eaccess) 90 | psys_err(1, write_end_fd, "Executable is found but execve failed"); 91 | psys_err(1, write_end_fd, "Executable not found"); 92 | } 93 | } 94 | int main(int argc, char* argv[]) 95 | { 96 | /* Get path */ 97 | char *path = getenv("PATH"); 98 | if (path == NULL) 99 | errx(1, "PATH not found"); 100 | if (path[0] == '\0') 101 | errx(1, "PATH is empty"); 102 | const size_t path_sz = strlen(path) + 1; 103 | 104 | struct Stack_t stack; 105 | init_cached_stack(&stack); 106 | 107 | pid_t pids[argv_cnt]; 108 | int fds[argv_cnt]; 109 | for (size_t i = 0; i != argv_cnt; ++i) { 110 | reserve_stack(&stack, PATH_MAX + 1, path_sz); 111 | 112 | struct Args *args = allocate_obj_on_stack(&stack, sizeof(struct Args) + path_sz); 113 | memcpy(args->path, path, path_sz); 114 | 115 | args->argv = argvs[i]; 116 | 117 | fds[i] = aspawn(&pids[i], &stack, fn, args); 118 | if (fds[i] < 0) 119 | errx(1, "aspawn failed: %s", strerror(-fds[i])); 120 | 121 | // Wait for the child to execve or exit 122 | struct pollfd pfd = { 123 | .fd = fds[i], 124 | .events = POLLHUP, 125 | }; 126 | if (poll(&pfd, 1, -1) < 0) 127 | err(1, "poll failed"); 128 | // Now the stack is no longer used by the child process, we can reuse it 129 | } 130 | 131 | char buffer[4095]; 132 | 133 | for (size_t i = 0; i != argv_cnt; ++i) { 134 | int wstatus; 135 | if (waitpid(pids[i], &wstatus, 0) < 0) 136 | err(1, "waitpid failed"); 137 | 138 | if (WIFSIGNALED(wstatus)) 139 | errx(1, "%ld is killed by signal!", (long) pids[i]); 140 | int exit_status = WEXITSTATUS(wstatus); 141 | if (exit_status != 0) 142 | errx(1, "%ld exited with %d", (long) pids[i], exit_status); 143 | 144 | // Check fds[i] for error message 145 | for (ssize_t result; (result = read(fds[i], buffer, sizeof(buffer))) != 0;) { 146 | if (result < 0) 147 | err(1, "read failed"); 148 | do { 149 | int cnt = write(2, buffer, result); 150 | if (cnt < 0) 151 | err(1, "write failed"); 152 | result -= cnt; 153 | } while (result); 154 | } 155 | } 156 | 157 | int result = cleanup_stack(&stack); 158 | if (result < 0) 159 | errx(1, "cleanup_stack failed: %s", strerror(-result)); 160 | 161 | return 0; 162 | } 163 | -------------------------------------------------------------------------------- /benchmark/bench_aspawn_responsiveness.cc: -------------------------------------------------------------------------------- 1 | /** 2 | * bench_aspawn_throughput.cc benchmarks how fast can aspawn creates a new 3 | * process with and without reusing stack when there is no additional stack usage. 4 | */ 5 | 6 | #include "../aspawn.h" 7 | #include "../syscall/syscall.h" 8 | 9 | #include "../test/utility.h" 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | #include 30 | 31 | static void Cleanup_stacks(const struct Stack_t *stack) 32 | { 33 | int result = cleanup_stack(stack); 34 | if (result < 0) { 35 | errno = -result; 36 | err(1, "cleanup_stacks failed"); 37 | } 38 | } 39 | 40 | static const char * const argv[] = {"/bin/true", NULL}; 41 | static const char * const envp[] = {NULL}; 42 | 43 | static void waitfor_stack_reuse(int fd) 44 | { 45 | struct pollfd pfd = { 46 | .fd = fd, 47 | .events = POLLHUP, 48 | }; 49 | if (poll(&pfd, 1, -1) < 0) 50 | err(1, "poll failed"); 51 | } 52 | static int bench_aspawn_fn(void *arg, int write_end_fd, void *old_sigset) 53 | { 54 | psys_sigprocmask(SIG_SETMASK, old_sigset, NULL); 55 | psys_execve(argv[0], argv, envp); 56 | return 1; 57 | } 58 | static void BM_aspawn_no_reuse(benchmark::State &state) 59 | { 60 | for ([[maybe_unused]] auto _: state) { 61 | struct Stack_t stack; 62 | 63 | init_cached_stack(&stack); 64 | reserve_stack(&stack, 0, 0); 65 | 66 | pid_t pid; 67 | int result = aspawn(&pid, &stack, bench_aspawn_fn, NULL); 68 | if (result < 0) { 69 | errno = -result; 70 | err(1, "aspawn failed"); 71 | } 72 | 73 | state.PauseTiming(); 74 | 75 | waitfor_stack_reuse(result); 76 | close(result); 77 | Cleanup_stacks(&stack); 78 | 79 | state.ResumeTiming(); 80 | } 81 | } 82 | BENCHMARK(BM_aspawn_no_reuse); 83 | 84 | static void BM_aspawn(benchmark::State &state) 85 | { 86 | struct Stack_t stack; 87 | init_cached_stack(&stack); 88 | reserve_stack(&stack, 0, 0); 89 | 90 | for ([[maybe_unused]] auto _: state) { 91 | pid_t pid; 92 | 93 | int result = aspawn(&pid, &stack, bench_aspawn_fn, NULL); 94 | if (result < 0) { 95 | errno = -result; 96 | err(1, "aspawn failed"); 97 | } 98 | 99 | state.PauseTiming(); 100 | waitfor_stack_reuse(result); 101 | close(result); 102 | state.ResumeTiming(); 103 | } 104 | cleanup_stack(&stack); 105 | } 106 | BENCHMARK(BM_aspawn)->Threads(1); 107 | 108 | static void sig_blockall(sigset_t *oldset) 109 | { 110 | sigset_t set; 111 | sigfillset(&set); 112 | sigprocmask(SIG_SETMASK, &set, oldset); 113 | } 114 | static void BM_vfork_with_shared_stack(benchmark::State &state) 115 | { 116 | int oldstate; 117 | for ([[maybe_unused]] auto _: state) { 118 | pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); 119 | 120 | sigset_t oldset; 121 | sig_blockall(&oldset); 122 | 123 | pid_t pid = vfork(); 124 | if (pid < 0) 125 | err(1, "vfork failed"); 126 | if (pid == 0) { 127 | psys_sigprocmask(SIG_SETMASK, &oldset, NULL); 128 | psys_execve(argv[0], argv, envp); 129 | _exit(1); 130 | } 131 | 132 | sigprocmask(SIG_SETMASK, &oldset, NULL); 133 | pthread_setcancelstate(oldstate, NULL); 134 | } 135 | } 136 | BENCHMARK(BM_vfork_with_shared_stack); 137 | 138 | static void BM_fork(benchmark::State &state) 139 | { 140 | for ([[maybe_unused]] auto _: state) { 141 | pid_t pid = fork(); 142 | if (pid < 0) 143 | err(1, "vfork failed"); 144 | if (pid == 0) { 145 | psys_execve(argv[0], argv, envp); 146 | _exit(1); 147 | } 148 | } 149 | } 150 | BENCHMARK(BM_fork); 151 | 152 | static void BM_posix_spawn(benchmark::State &state) 153 | { 154 | pid_t pid; 155 | 156 | posix_spawnattr_t attr; 157 | if (posix_spawnattr_init(&attr) != 0) 158 | err(1, "posix_spawnattr_init failed"); 159 | if (posix_spawnattr_setflags(&attr, POSIX_SPAWN_USEVFORK) != 0) 160 | err(1, "posix_spawnattr_setflags failed"); 161 | 162 | for ([[maybe_unused]] auto _: state) { 163 | int result = posix_spawnp(&pid, argv[0], NULL, &attr, (char * const *) argv, (char * const *) envp); 164 | if (result != 0) { 165 | errno = result; 166 | err(1, "posix_spawnp failed"); 167 | } 168 | } 169 | 170 | posix_spawnattr_destroy(&attr); 171 | } 172 | BENCHMARK(BM_posix_spawn); 173 | 174 | static void* wait_thread(void*) 175 | { 176 | int wstatus; 177 | pid_t pid; 178 | int exit_status; 179 | for (; ;) { 180 | pid = wait(&wstatus); 181 | if (pid == -1 && errno == ECHILD) 182 | continue; 183 | if (WIFSIGNALED(wstatus)) 184 | errx(1, "pid %ld is terminated by signal", (long) pid); 185 | exit_status = WEXITSTATUS(wstatus); 186 | if (exit_status != 0) 187 | errx(1, "pid %ld exited with %d", (long) pid, exit_status); 188 | } 189 | 190 | return NULL; 191 | } 192 | 193 | int main(int argc, char** argv) { 194 | ::benchmark::Initialize(&argc, argv); 195 | if (::benchmark::ReportUnrecognizedArguments(argc, argv)) 196 | return 1; 197 | 198 | pthread_t thread_id; 199 | pthread_create(&thread_id, NULL, wait_thread, NULL); 200 | 201 | ::benchmark::RunSpecifiedBenchmarks(); 202 | return 0; 203 | } 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aspawn 2 | 3 | ![C/C++ CI](https://github.com/NobodyXu/aspawn/workflows/C/C++%20CI/badge.svg) 4 | 5 | Asynchronous and more versatile replacement of posix_spawn 6 | 7 | ## Intro 8 | 9 | `aspawn` implements the idea from [`avfork`][4], enabling user to do `posix_spawn` in an asynchronous way by 10 | making syscall directly via `pure_syscall` implemented in my library that does not use 11 | any global/thread local variable at all. 12 | 13 | My `aspawn` has signature: 14 | 15 | ```c 16 | struct Stack_t { 17 | void *addr; 18 | size_t size; 19 | }; 20 | 21 | /** 22 | * @param old_sigset of type sigset_t*. The original value of sigmask. 23 | * It can be modified to any value user desired. 24 | * 25 | * The value of sigmask in aspawn_fn is unspecified. 26 | */ 27 | typedef int (*aspawn_fn)(void *arg, int wirte_end_fd, void *old_sigset); 28 | 29 | /** 30 | * @param pid the pid of the child will be stored into it on success. 31 | * @param cached_stack must call reserve_stack before using aspawn 32 | * @param fn If fn returns, then the child will exit with the return value of fn as the exit code. 33 | * @return fd of read end of CLOEXEC pipe if success, eitherwise (-errno). 34 | * 35 | * aspawn would disable thread cancellation, then it would revert it before return. 36 | * 37 | * aspawn would also mask all signals in parent and reset the signal handler in the child process. 38 | * Before aspawn returns in parent, it would revert the signal mask. 39 | * 40 | * In the function fn, you can only use syscall declared in syscall/syscall.h 41 | * Use of any glibc function or any function that modifies global/thread-local variable is undefined behavior. 42 | */ 43 | PUBLIC int aspawn(pid_t *pid, struct Stack_t *cached_stack, aspawn_fn fn, void *arg); 44 | ``` 45 | 46 | By returning the write end of the `CLOEXEC` pipefd, user of this library is able to receive error message/check whether 47 | the child process has done using `cached_stack` so that `aspawn` can reuse `cached_stack`. 48 | 49 | It also allows user to pass arbitary data in the stack via `allocate_obj_on_stack`, 50 | thus user does not have to allocate them separately on heap. 51 | 52 | To use a syscall, you need to include [`syscall/syscall.h`][2], which defines the syscall routine used by the child process including 53 | `find_exe`, `psys_execve` and `psys_execveat`. 54 | 55 | User will be able to reuse stack by `poll`ing the fd returned by `aspawn` and wait for it to hup. 56 | 57 | Compare to [`posix_spawn`][3], `aspawn` has 3 advantages: 58 | - `aspawn` allows user to **do anything** in the child process before `exec`. 59 | - `aspawn` can reuse stack, `posix_spawn` can't; 60 | - `aspawn` doesn't block the parent thread; 61 | 62 | ### Example code 63 | 64 | Examples can be seen [here][10] 65 | 66 | ### Platform support 67 | 68 | Currently, it only supports x86-64 modern linux (>= 4.0), but ports can be easily made by modifing [`syscall/`][7], 69 | [`create_pipe/create_pipe.c`][8] and [`clone_internal/`][9] 70 | 71 | ## Benchmark 72 | 73 | ### Responsive benchmark 74 | 75 | Responsive comparison between `posix_spawn` and `aspawn`, [source code][5] (benchmarking is done via [google/benchmark][6]: 76 | 77 | ```console 78 | $ ll -h bench_aspawn_responsiveness.out 79 | -rwxrwxr-x 1 nobodyxu nobodyxu 254K Oct 2 15:02 bench_aspawn_responsiveness.out* 80 | 81 | $ uname -a 82 | Linux pop-os 5.4.0-7642-generic #46~1598628707~20.04~040157c-Ubuntu SMP Fri Aug 28 18:02:16 UTC x86_64 x86_64 x86_64 GNU/Linux 83 | 84 | $ ./a.out 85 | 2020-10-02T15:02:45+10:00 86 | Running ./bench_aspawn_responsiveness.out 87 | Run on (12 X 4100 MHz CPU s) 88 | CPU Caches: 89 | L1 Data 32 KiB (x6) 90 | L1 Instruction 32 KiB (x6) 91 | L2 Unified 256 KiB (x6) 92 | L3 Unified 9216 KiB (x1) 93 | Load Average: 0.31, 0.36, 0.32 94 | --------------------------------------------------------------------- 95 | Benchmark Time CPU Iterations 96 | --------------------------------------------------------------------- 97 | BM_aspawn_no_reuse 18009 ns 17942 ns 38943 98 | BM_aspawn/threads:1 14500 ns 14446 ns 48339 99 | BM_vfork_with_shared_stack 46545 ns 16554 ns 44027 100 | BM_fork 54583 ns 54527 ns 12810 101 | BM_posix_spawn 125061 ns 29091 ns 24483 102 | ``` 103 | 104 | The column "Time" is measured in terms of system clock, while "CPU" is measured in terms of per-process CPU time. 105 | 106 | ### Throughput benchmark 107 | 108 | Since `aspawn` allows user to **do anything** in the vforked child via `aspawn_fn`, it makes no sense 109 | to benchmark how many processes can `aspawn` created as it depends on user provided argument `fn`. 110 | 111 | ## Build and Install 112 | 113 | Make sure that you have installed `make`, `clang` `lld` and `llvm-ar`. 114 | 115 | Then run `make -j $(nproc)` to build the project, `sudo make install` to install project to `/usr/local/`. 116 | 117 | ## Testing 118 | 119 | Make sure you have installed all depedencies listed above for building this project, 120 | then run `make test -j $(nproc)` 121 | 122 | ## Contributing to this project 123 | 124 | Any commits on this project will be welcome! 125 | 126 | It would be even better if you can help me improve test coverages by adding more unit tests or port this project to other platform (e.g. `arm`, `mips`). 127 | 128 | ## Contributors 129 | 130 | Thank you for people who contributed to this project: 131 | - @HappyFacade 132 | 133 | [1]: https://github.com/NobodyXu/aspawn 134 | [2]: https://github.com/NobodyXu/aspawn/blob/master/syscall/syscall.h 135 | [3]: https://man7.org/linux/man-pages/man3/posix_spawn.3.html 136 | [4]: https://gist.github.com/nicowilliams/a8a07b0fc75df05f684c23c18d7db234 137 | [5]: https://github.com/NobodyXu/aspawn/blob/master/benchmark/bench_aspawn_responsiveness.cc 138 | [6]: https://github.com/google/benchmark 139 | [7]: https://github.com/NobodyXu/aspawn/tree/master/syscall 140 | [8]: https://github.com/NobodyXu/aspawn/blob/master/create_pipe/create_pipe.c 141 | [9]: https://github.com/NobodyXu/aspawn/tree/master/clone_internal 142 | [10]: https://github.com/NobodyXu/aspawn/tree/master/example 143 | -------------------------------------------------------------------------------- /syscall/make_syscall.h: -------------------------------------------------------------------------------- 1 | #ifndef make_syscall 2 | # ifdef __x86_64__ 3 | 4 | /** 5 | * Clear the frame pointer %rbp 6 | */ 7 | # define RESET_EBP() \ 8 | __asm__ __volatile__ ( \ 9 | "xorl %%ebp, %%ebp" \ 10 | : \ 11 | : \ 12 | : "rbp" \ 13 | ) 14 | 15 | /** 16 | * The code below is copied from glibc 17 | */ 18 | 19 | /* Registers clobbered by syscall. */ 20 | # define REGISTERS_CLOBBERED_BY_SYSCALL "cc", "r11", "cx" 21 | 22 | /* NB: This also works when X is an array. For an array X, type of 23 | (X) - (X) is ptrdiff_t, which is signed, since size of ptrdiff_t 24 | == size of pointer, cast is a NOP. */ 25 | # define TYPEFY1(X) __typeof__ ((X) - (X)) 26 | /* Explicit cast the argument. */ 27 | # define ARGIFY(X) ((TYPEFY1 (X)) (X)) 28 | /* Create a variable 'name' based on type of variable 'X' to avoid 29 | explicit types. */ 30 | # define TYPEFY(X, name) __typeof__ (ARGIFY (X)) name 31 | 32 | # undef INTERNAL_SYSCALL 33 | # define INTERNAL_SYSCALL(number, nr, args...) \ 34 | internal_syscall##nr (number, args) 35 | 36 | # undef internal_syscall0 37 | # define internal_syscall0(number, dummy...) \ 38 | ({ \ 39 | unsigned long int resultvar; \ 40 | __asm__ __volatile__ ( \ 41 | "syscall\n\t" \ 42 | : "=a" (resultvar) \ 43 | : "0" (number) \ 44 | : "memory", REGISTERS_CLOBBERED_BY_SYSCALL); \ 45 | (long int) resultvar; \ 46 | }) 47 | 48 | # undef internal_syscall1 49 | # define internal_syscall1(number, arg1) \ 50 | ({ \ 51 | unsigned long int resultvar; \ 52 | TYPEFY (arg1, __arg1) = ARGIFY (arg1); \ 53 | register TYPEFY (arg1, _a1) __asm__ ("rdi") = __arg1; \ 54 | __asm__ __volatile__ ( \ 55 | "syscall\n\t" \ 56 | : "=a" (resultvar) \ 57 | : "0" (number), "r" (_a1) \ 58 | : "memory", REGISTERS_CLOBBERED_BY_SYSCALL); \ 59 | (long int) resultvar; \ 60 | }) 61 | 62 | # undef internal_syscall2 63 | # define internal_syscall2(number, arg1, arg2) \ 64 | ({ \ 65 | unsigned long int resultvar; \ 66 | TYPEFY (arg2, __arg2) = ARGIFY (arg2); \ 67 | TYPEFY (arg1, __arg1) = ARGIFY (arg1); \ 68 | register TYPEFY (arg2, _a2) __asm__ ("rsi") = __arg2; \ 69 | register TYPEFY (arg1, _a1) __asm__ ("rdi") = __arg1; \ 70 | __asm__ __volatile__ ( \ 71 | "syscall\n\t" \ 72 | : "=a" (resultvar) \ 73 | : "0" (number), "r" (_a1), "r" (_a2) \ 74 | : "memory", REGISTERS_CLOBBERED_BY_SYSCALL); \ 75 | (long int) resultvar; \ 76 | }) 77 | 78 | # undef internal_syscall3 79 | # define internal_syscall3(number, arg1, arg2, arg3) \ 80 | ({ \ 81 | unsigned long int resultvar; \ 82 | TYPEFY (arg3, __arg3) = ARGIFY (arg3); \ 83 | TYPEFY (arg2, __arg2) = ARGIFY (arg2); \ 84 | TYPEFY (arg1, __arg1) = ARGIFY (arg1); \ 85 | register TYPEFY (arg3, _a3) __asm__ ("rdx") = __arg3; \ 86 | register TYPEFY (arg2, _a2) __asm__ ("rsi") = __arg2; \ 87 | register TYPEFY (arg1, _a1) __asm__ ("rdi") = __arg1; \ 88 | __asm__ __volatile__ ( \ 89 | "syscall\n\t" \ 90 | : "=a" (resultvar) \ 91 | : "0" (number), "r" (_a1), "r" (_a2), "r" (_a3) \ 92 | : "memory", REGISTERS_CLOBBERED_BY_SYSCALL); \ 93 | (long int) resultvar; \ 94 | }) 95 | 96 | # undef internal_syscall4 97 | # define internal_syscall4(number, arg1, arg2, arg3, arg4) \ 98 | ({ \ 99 | unsigned long int resultvar; \ 100 | TYPEFY (arg4, __arg4) = ARGIFY (arg4); \ 101 | TYPEFY (arg3, __arg3) = ARGIFY (arg3); \ 102 | TYPEFY (arg2, __arg2) = ARGIFY (arg2); \ 103 | TYPEFY (arg1, __arg1) = ARGIFY (arg1); \ 104 | register TYPEFY (arg4, _a4) __asm__ ("r10") = __arg4; \ 105 | register TYPEFY (arg3, _a3) __asm__ ("rdx") = __arg3; \ 106 | register TYPEFY (arg2, _a2) __asm__ ("rsi") = __arg2; \ 107 | register TYPEFY (arg1, _a1) __asm__ ("rdi") = __arg1; \ 108 | __asm__ __volatile__ ( \ 109 | "syscall\n\t" \ 110 | : "=a" (resultvar) \ 111 | : "0" (number), "r" (_a1), "r" (_a2), "r" (_a3), "r" (_a4) \ 112 | : "memory", REGISTERS_CLOBBERED_BY_SYSCALL); \ 113 | (long int) resultvar; \ 114 | }) 115 | 116 | # undef internal_syscall5 117 | # define internal_syscall5(number, arg1, arg2, arg3, arg4, arg5) \ 118 | ({ \ 119 | unsigned long int resultvar; \ 120 | TYPEFY (arg5, __arg5) = ARGIFY (arg5); \ 121 | TYPEFY (arg4, __arg4) = ARGIFY (arg4); \ 122 | TYPEFY (arg3, __arg3) = ARGIFY (arg3); \ 123 | TYPEFY (arg2, __arg2) = ARGIFY (arg2); \ 124 | TYPEFY (arg1, __arg1) = ARGIFY (arg1); \ 125 | register TYPEFY (arg5, _a5) __asm__ ("r8") = __arg5; \ 126 | register TYPEFY (arg4, _a4) __asm__ ("r10") = __arg4; \ 127 | register TYPEFY (arg3, _a3) __asm__ ("rdx") = __arg3; \ 128 | register TYPEFY (arg2, _a2) __asm__ ("rsi") = __arg2; \ 129 | register TYPEFY (arg1, _a1) __asm__ ("rdi") = __arg1; \ 130 | __asm__ __volatile__ ( \ 131 | "syscall\n\t" \ 132 | : "=a" (resultvar) \ 133 | : "0" (number), "r" (_a1), "r" (_a2), "r" (_a3), "r" (_a4), \ 134 | "r" (_a5) \ 135 | : "memory", REGISTERS_CLOBBERED_BY_SYSCALL); \ 136 | (long int) resultvar; \ 137 | }) 138 | 139 | # undef internal_syscall6 140 | # define internal_syscall6(number, arg1, arg2, arg3, arg4, arg5, arg6) \ 141 | ({ \ 142 | unsigned long int resultvar; \ 143 | TYPEFY (arg6, __arg6) = ARGIFY (arg6); \ 144 | TYPEFY (arg5, __arg5) = ARGIFY (arg5); \ 145 | TYPEFY (arg4, __arg4) = ARGIFY (arg4); \ 146 | TYPEFY (arg3, __arg3) = ARGIFY (arg3); \ 147 | TYPEFY (arg2, __arg2) = ARGIFY (arg2); \ 148 | TYPEFY (arg1, __arg1) = ARGIFY (arg1); \ 149 | register TYPEFY (arg6, _a6) __asm__ ("r9") = __arg6; \ 150 | register TYPEFY (arg5, _a5) __asm__ ("r8") = __arg5; \ 151 | register TYPEFY (arg4, _a4) __asm__ ("r10") = __arg4; \ 152 | register TYPEFY (arg3, _a3) __asm__ ("rdx") = __arg3; \ 153 | register TYPEFY (arg2, _a2) __asm__ ("rsi") = __arg2; \ 154 | register TYPEFY (arg1, _a1) __asm__ ("rdi") = __arg1; \ 155 | __asm__ __volatile__ ( \ 156 | "syscall\n\t" \ 157 | : "=a" (resultvar) \ 158 | : "0" (number), "r" (_a1), "r" (_a2), "r" (_a3), "r" (_a4), \ 159 | "r" (_a5), "r" (_a6) \ 160 | : "memory", REGISTERS_CLOBBERED_BY_SYSCALL); \ 161 | (long int) resultvar; \ 162 | }) 163 | 164 | # endif 165 | #endif 166 | -------------------------------------------------------------------------------- /syscall/syscall.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE // for program_invocation_name 2 | #include "syscall.h" 3 | #include "make_syscall.h" 4 | #include "errno_msgs.h" 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | const char* pstrerror(int errno_v) 12 | { 13 | if (errno_v <= 0 || errno_v > errno_msgs_sz) 14 | return "Invalid errno"; 15 | else 16 | return errno_msgs[errno_v - 1]; 17 | } 18 | 19 | noreturn void perr(int exit_status, int errno_v, const char *msg) 20 | { 21 | const char *err_msg = pstrerror(errno_v); 22 | 23 | // format: "{program_invocation_short_name}: {msg}: {errno err msg}\n" 24 | 25 | psys_write(2, program_invocation_short_name, pstrlen(program_invocation_short_name)); 26 | psys_write(2, ": ", 2); 27 | 28 | psys_write(2, msg, pstrlen(msg)); 29 | psys_write(2, ": ", 2); 30 | 31 | psys_write(2, err_msg, pstrlen(err_msg)); 32 | psys_write(2, "\n", 1); 33 | 34 | psys_exit(1); 35 | } 36 | 37 | long pure_syscall(long syscall_number, long arg1, long arg2, long arg3, long arg4, long arg5, long arg6) 38 | { 39 | return INTERNAL_SYSCALL(syscall_number, 6, arg1, arg2, arg3, arg4, arg5, arg6); 40 | } 41 | 42 | int psys_openat(int dirfd, const char *pathname, int flags, mode_t mode) 43 | { 44 | return INTERNAL_SYSCALL(SYS_openat, 4, dirfd, pathname, flags, mode); 45 | } 46 | int psys_close(int fd) 47 | { 48 | return INTERNAL_SYSCALL(SYS_close, 1, fd); 49 | } 50 | 51 | int psys_dup(int oldfd) 52 | { 53 | return INTERNAL_SYSCALL(SYS_dup, 1, oldfd); 54 | } 55 | int psys_dup3(int oldfd, int newfd, int flags) 56 | { 57 | return INTERNAL_SYSCALL(SYS_dup3, 3, oldfd, newfd, flags); 58 | } 59 | 60 | int psys_pipe2(int pipefd[2], int flag) 61 | { 62 | return INTERNAL_SYSCALL(SYS_pipe2, 2, pipefd, flag); 63 | } 64 | 65 | int psys_epoll_create1(int flags) 66 | { 67 | return INTERNAL_SYSCALL(SYS_epoll_create1, 1, flags); 68 | } 69 | int psys_epoll_ctl(int epfd, int op, int fd, void *event) 70 | { 71 | return INTERNAL_SYSCALL(SYS_epoll_ctl, 4, epfd, op, fd, event); 72 | } 73 | int psys_epoll_pwait(int epfd, void *events, int maxevents, int timeout, const void *sigmask) 74 | { 75 | return INTERNAL_SYSCALL(SYS_epoll_pwait, 5, epfd, events, maxevents, timeout, sigmask); 76 | } 77 | 78 | int psys_chdir(const char *path) 79 | { 80 | return INTERNAL_SYSCALL(SYS_chdir, 1, path); 81 | } 82 | int psys_fchdir(int fd) 83 | { 84 | return INTERNAL_SYSCALL(SYS_fchdir, 1, fd); 85 | } 86 | 87 | ssize_t psys_write(int fd, const void *buf, size_t count) 88 | { 89 | return INTERNAL_SYSCALL(SYS_write, 3, fd, buf, count); 90 | } 91 | 92 | ssize_t psys_read(int fd, void *buf, size_t count) 93 | { 94 | return INTERNAL_SYSCALL(SYS_read, 3, fd, buf, count); 95 | } 96 | 97 | size_t psys_get_pagesz() 98 | { 99 | return 4096; 100 | } 101 | 102 | #define MMAP_OFF_LOW_MASK (psys_get_pagesz() - 1) 103 | 104 | void *check_map_error(long result, int *errno_v) 105 | { 106 | if (result > -4096UL) { 107 | *errno_v = -result; 108 | return (void*) -1; 109 | } 110 | return (void*) result; 111 | } 112 | void* psys_mmap(int *errno_v, void *addr, size_t len, int prot, int flags, int fd, off_t off) 113 | { 114 | if (off & MMAP_OFF_LOW_MASK) { 115 | *errno_v = EINVAL; 116 | return (void*) -1; 117 | } 118 | 119 | #ifdef SYS_mmap2 120 | return check_map_error(INTERNAL_SYSCALL(SYS_mmap2, 6, addr, len, prot, flags, fd, off / psys_get_pagesz()), 121 | errno_v); 122 | #else 123 | return check_map_error(INTERNAL_SYSCALL(SYS_mmap, 6, addr, len, prot, flags, fd, off), errno_v); 124 | #endif 125 | } 126 | 127 | int psys_munmap(void *addr, size_t len) 128 | { 129 | return INTERNAL_SYSCALL(SYS_munmap, 2, addr, len); 130 | } 131 | 132 | void* psys_mremap(int *errno_v, void *old_addr, size_t old_len, size_t new_len, int flags, void *new_addr) 133 | { 134 | return check_map_error(INTERNAL_SYSCALL(SYS_mremap, 5, old_addr, old_len, new_len, flags, new_addr), 135 | errno_v); 136 | } 137 | 138 | int psys_setresuid(uid_t ruid, uid_t euid, uid_t suid) 139 | { 140 | return INTERNAL_SYSCALL(SYS_setresuid, 3, ruid, euid, suid); 141 | } 142 | int psys_setresgid(gid_t rgid, gid_t egid, gid_t sgid) 143 | { 144 | return INTERNAL_SYSCALL(SYS_setresgid, 3, rgid, egid, sgid); 145 | } 146 | int psys_setgroups(size_t size, const gid_t *list) 147 | { 148 | return INTERNAL_SYSCALL(SYS_setgroups, 2, size, list); 149 | } 150 | 151 | pid_t psys_getpid() 152 | { 153 | return INTERNAL_SYSCALL(SYS_getpid, 0); 154 | } 155 | 156 | int psys_sched_setparam(pid_t pid, const void *param) 157 | { 158 | return INTERNAL_SYSCALL(SYS_sched_setparam, 2, pid, param); 159 | } 160 | int psys_sched_getparam(pid_t pid, void *param) 161 | { 162 | return INTERNAL_SYSCALL(SYS_sched_getparam, 2, pid, param); 163 | } 164 | int psys_sched_setscheduler(pid_t pid, int policy, const void *param) 165 | { 166 | return INTERNAL_SYSCALL(SYS_sched_setscheduler, 3, pid, policy, param); 167 | } 168 | int psys_sched_getscheduler(pid_t pid) 169 | { 170 | return INTERNAL_SYSCALL(SYS_sched_getscheduler, 1, pid); 171 | } 172 | int psys_getpriority(int which, long who) 173 | { 174 | return INTERNAL_SYSCALL(SYS_getpriority, 2, which, who); 175 | } 176 | int psys_setpriority(int which, long who, int unice) 177 | { 178 | return INTERNAL_SYSCALL(SYS_setpriority, 3, which, who, unice); 179 | } 180 | 181 | int psys_prlimit( 182 | int resource, 183 | const struct rlimit64 *new_limit, 184 | struct rlimit64 *old_limit 185 | ) 186 | { 187 | return INTERNAL_SYSCALL(SYS_prlimit64, 4, 0, resource, new_limit, old_limit); 188 | } 189 | 190 | noreturn void psys_exit(int status) 191 | { 192 | INTERNAL_SYSCALL(SYS_exit, 1, status); 193 | 194 | __builtin_unreachable(); 195 | } 196 | 197 | int psys_execve(const char *pathname, const char * const argv[], const char * const envp[]) 198 | { 199 | return INTERNAL_SYSCALL(SYS_execve, 3, pathname, argv, envp); 200 | } 201 | int psys_execveat( 202 | int dirfd, 203 | const char *pathname, 204 | const char * const argv[], 205 | const char * const envp[], 206 | int flags 207 | ) 208 | { 209 | return INTERNAL_SYSCALL(SYS_execveat, 5, dirfd, pathname, argv, envp, flags); 210 | } 211 | 212 | int find_exe(const char *file, size_t file_len, char *constructed_path, const char **PATH, size_t path_max_len) 213 | { 214 | // Ignore empty path in *PATH 215 | for (; (*PATH)[0] == ':'; ++(*PATH)); 216 | 217 | if ((*PATH)[0] == '\0') 218 | return 0; 219 | 220 | size_t i = 0; 221 | for (; (*PATH)[i] != '\0' && (*PATH)[i] != ':'; ++i) { 222 | if (i + 1 > path_max_len) 223 | return -1; 224 | constructed_path[i] = (*PATH)[i]; 225 | } 226 | 227 | size_t path_prefix_len = i; 228 | 229 | if (constructed_path[i - 1] != '/') 230 | constructed_path[i++] = '/'; 231 | 232 | if (i + file_len > path_max_len || /* Check for overflow */ i == 0 || i + file_len < i) 233 | return -1; 234 | 235 | pstrcpy(constructed_path + i, file, file_len); 236 | 237 | *PATH += path_prefix_len + ((*PATH)[path_prefix_len] == ':'); 238 | 239 | return 1; 240 | } 241 | 242 | int handle_execve_err(int result, int *got_eaccess) 243 | { 244 | switch (-result) { 245 | case EACCES: 246 | // Record that we got a 'Permission denied' error. If we end 247 | // up finding no executable we can use, we want to diagnose 248 | // that we did find one but were denied access. 249 | *got_eaccess = 1; 250 | case ENOENT: 251 | case ESTALE: 252 | case ENOTDIR: 253 | // Those errors indicate the file is missing or not executable 254 | // by us, in which case we want to just try the next path 255 | // directory. 256 | case ENODEV: 257 | case ETIMEDOUT: 258 | // Some strange filesystems like AFS return even 259 | // stranger error numbers. They cannot reasonably mean 260 | // anything else so ignore those, too. 261 | return 0; 262 | 263 | default: 264 | // Some other error means we found an executable file, but 265 | // something went wrong executing it. 266 | return result; 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /syscall/errno_msgs.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../common.h" 4 | 5 | typedef const char* cstr; 6 | static const cstr errno_msgs[] = 7 | { 8 | "Operation not permitted", // 1 EPERM 9 | "No such file or directory", // 2 ENOENT 10 | "No such process", // 3 ESRCH 11 | "Interrupted system call", // 4 EINTR 12 | "Input/output error", // 5 EIO 13 | "No such device or address", // 6 ENXIO 14 | "Argument list too long", // 7 E2BIG 15 | "Exec format error", // 8 ENOEXEC 16 | "Bad file descriptor", // 9 EBADF 17 | "No child processes", // 10 ECHILD 18 | "Resource temporarily unavailable", // 11 EAGAIN/EWOULDBLOCK 19 | "Cannot allocate memory", // 12 ENOMEM 20 | "Permission denied", // 13 EACCES 21 | "Bad address", // 14 EFAULT 22 | "Block device required", // 15 ENOTBLK 23 | "Device or resource busy", // 16 EBUSY 24 | "File exists", // 17 EEXIST 25 | "Invalid cross-device link", // 18 EXDEV 26 | "No such device", // 19 ENODEV 27 | "Not a directory", // 20 ENOTDIR 28 | "Is a directory", // 21 EISDIR 29 | "Invalid argument", // 22 EINVAL 30 | "Too many open files in system", // 23 ENFILE 31 | "Too many open files", // 24 EMFILE 32 | "Inappropriate ioctl for device", // 25 ENOTTY 33 | "Text file busy", // 26 ETXTBSY 34 | "File too large", // 27 EFBIG 35 | "No space left on device", // 28 ENOSPC 36 | "Illegal seek", // 29 ESPIPE 37 | "Read-only file system", // 30 EROFS 38 | "Too many links", // 31 EMLINK 39 | "Broken pipe", // 32 EPIPE 40 | "Numerical argument out of domain", // 33 EDOM 41 | "Numerical result out of range", // 34 ERANGE 42 | "Resource deadlock avoided", // 35 EDEADLK/EDEADLOCK 43 | "File name too long", // 36 ENAMETOOLONG 44 | "No locks available", // 37 ENOLCK 45 | "Function not implemented", // 38 ENOSYS 46 | "Directory not empty", // 39 ENOTEMPTY 47 | "Too many levels of symbolic links",// 40 ELOOP 48 | "Unknown errno", 49 | "No message of desired type", // 42 ENOMSG 50 | "Identifier removed", // 43 EIDRM 51 | "Channel number out of range", // 44 ECHRNG 52 | "Level 2 not synchronized", // 45 EL2NSYNC 53 | "Level 3 halted", // 46 EL3HLT 54 | "Level 3 reset", // 47 EL3RST 55 | "Link number out of range", // 48 ELNRNG 56 | "Protocol driver not attached", // 49 EUNATCH 57 | "No CSI structure available", // 50 ENOCSI 58 | "Level 2 halted", // 51 EL2HLT 59 | "Invalid exchange", // 52 EBADE 60 | "Invalid request descriptor", // 53 EBADR 61 | "Exchange full", // 54 EXFULL 62 | "No anode", // 55 ENOANO 63 | "Invalid request code", // 56 EBADRQC 64 | "Invalid slot", // 57 EBADSLT 65 | "Unknown errno", 66 | "Bad font file format", // 59 EBFONT 67 | "Device not a stream", // 60 ENOSTR 68 | "No data available", // 61 ENODATA 69 | "Timer expired", // 62 ETIME 70 | "Out of streams resources", // 63 ENOSR 71 | "Machine is not on the network", // 64 ENONET 72 | "Package not installed", // 65 ENOPKG 73 | "Object is remote", // 66 EREMOTE 74 | "Link has been severed", // 67 ENOLINK 75 | "Advertise error", // 68 EADV 76 | "Srmount error", // 69 ESRMNT 77 | "Communication error on send", // 70 ECOMM 78 | "Protocol error", // 71 EPROTO 79 | "Multihop attempted", // 72 EMULTIHOP 80 | "RFS specific error", // 73 EDOTDOT 81 | "Bad message", // 74 EBADMSG 82 | "Value too large for defined data type", // 75 EOVERFLOW 83 | "Name not unique on network", // 76 ENOTUNIQ 84 | "File descriptor in bad state", // 77 EBADFD 85 | "Remote address changed", // 78 EREMCHG 86 | "Can not access a needed shared library", // 79 ELIBACC 87 | "Accessing a corrupted shared library", // 80 ELIBBAD 88 | ".lib section in a.out corrupted", // 81 ELIBSCN 89 | "Attempting to link in too many shared libraries", // 82 ELIBMAX 90 | "Cannot exec a shared library directly", // 83 ELIBEXEC 91 | "Invalid or incomplete multibyte or wide character", // 84 EILSEQ 92 | "Interrupted system call should be restarted", // 85 ERESTART 93 | "Streams pipe error", // 86 ESTRPIPE 94 | "Too many users", // 87 EUSERS 95 | "Socket operation on non-socket", // 88 ENOTSOCK 96 | "Destination address required", // 89 EDESTADDRREQ 97 | "Message too long", // 90 EMSGSIZE 98 | "Protocol wrong type for socket", // 91 EPROTOTYPE 99 | "Protocol not available", // 92 ENOPROTOOPT 100 | "Protocol not supported", // 93 EPROTONOSUPPORT 101 | "Socket type not supported", // 94 ESOCKTNOSUPPORT 102 | "Operation not supported", // 95 ENOTSUP/EOPNOTSUPP 103 | "Protocol family not supported", // 96 EPFNOSUPPORT 104 | "Address family not supported by protocol", // 97 EAFNOSUPPORT 105 | "Address already in use", // 98 EADDRINUSE 106 | "Cannot assign requested address", // 99 EADDRNOTAVAIL 107 | "Network is down", // 100 ENETDOWN 108 | "Network is unreachable", // 101 ENETUNREACH 109 | "Network dropped connection on reset", // 102 ENETRESET 110 | "Software caused connection abort", // 103 ECONNABORTED 111 | "Connection reset by peer", // 104 ECONNRESET 112 | "No buffer space available", // 105 ENOBUFS 113 | "Transport endpoint is already connected", // 106 EISCONN 114 | "Transport endpoint is not connected", // 107 ENOTCONN 115 | "Cannot send after transport endpoint shutdown", // 108 ESHUTDOWN 116 | "Too many references: cannot splice", // 109 ETOOMANYREFS 117 | "Connection timed out", // 110 ETIMEDOUT 118 | "Connection refused", // 111 ECONNREFUSED 119 | "Host is down", // 112 EHOSTDOWN 120 | "No route to host", // 113 EHOSTUNREACH 121 | "Operation already in progress", // 114 EALREADY 122 | "Operation now in progress", // 115 EINPROGRESS 123 | "Stale file handle", // 116 ESTALE 124 | "Structure needs cleaning", // 117 EUCLEAN 125 | "Not a XENIX named type file", // 118 ENOTNAM 126 | "No XENIX semaphores available", // 119 ENAVAIL 127 | "Is a named type file", // 120 EISNAM 128 | "Remote I/O error", // 121 EREMOTEIO 129 | "Disk quota exceeded", // 122 EDQUOT 130 | "No medium found", // 123 ENOMEDIUM 131 | "Wrong medium type", // 124 EMEDIUMTYPE 132 | "Operation canceled", // 125 ECANCELED 133 | "Required key not available", // 126 ENOKEY 134 | "Key has expired", // 127 EKEYEXPIRED 135 | "Key has been revoked", // 128 EKEYREVOKED 136 | "Key was rejected by service", // 129 EKEYREJECTED 137 | "Owner died", // 130 EOWNERDEAD 138 | "State not recoverable", // 131 ENOTRECOVERABLE 139 | "Operation not possible due to RF-kill", // 132 ERFKILL 140 | "Memory page has hardware error", // 133 EHWPOISON 141 | }; 142 | static const size_t errno_msgs_sz = sizeof(errno_msgs) / sizeof(cstr); // 133 143 | 144 | PUBLIC const cstr* get_errno_msgs_cstrs(); 145 | -------------------------------------------------------------------------------- /syscall/syscall.h: -------------------------------------------------------------------------------- 1 | #ifndef __aspawn_syscall_syscall_H__ 2 | # define __aspawn_syscall_syscall_H__ 3 | 4 | /** 5 | * This function contains almost all functions that will be used in the aspawn-ed child. 6 | */ 7 | 8 | # include "../common.h" 9 | # include 10 | # include 11 | # include 12 | # include 13 | # include 14 | 15 | # define GET_ARG_N(_null, _0, _1, _2, _3, _4, _5, _6, _7, ...) _7 16 | # define GET_NARGS_(...) GET_ARG_N(__VA_ARGS__) 17 | /** 18 | * GET_NARGS can at most detect nargs up util 7. 19 | */ 20 | # define GET_NARGS(...) GET_NARGS_(99, ## __VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0) 21 | 22 | # ifdef __cplusplus 23 | extern "C" { 24 | # endif 25 | 26 | PUBLIC size_t pstrlen(const char *s); 27 | /** 28 | * Copy exacting n bytes(excluding null byte) from src to dest. 29 | * Will append the null byte to dest. 30 | */ 31 | PUBLIC void pstrcpy(char *dest, const char *src, size_t n); 32 | 33 | PUBLIC void pmemset(void *s, int c, size_t n); 34 | PUBLIC void pmemcpy(void *dest, const void *src, size_t n); 35 | 36 | /** 37 | * @param errno_v Returns error message for the errno (positive); 38 | * If it is an invalid errno number, returns "Invalid errno"; 39 | * If there is no error message for this errno number, returns "Unknown errno". 40 | */ 41 | PUBLIC const char* pstrerror(int errno_v); 42 | 43 | /** 44 | * @param msg a zero-terminated string (not format string). 45 | * @param errno_v errno. Must be positive. 46 | * 47 | * It will output "{program_invocation_short_name}: {msg}: {errno err msg}\n" 48 | */ 49 | PUBLIC noreturn void perr(int exit_status, int errno_v, const char *msg); 50 | 51 | /** 52 | * All psys_* here returns negative error code on failure and does not modify errno. 53 | * 54 | * The negative error code is equaivlent to (-errno). 55 | */ 56 | 57 | /** 58 | * Rationale on why syscall takes long: 59 | * - https://stackoverflow.com/questions/35628927/what-is-the-type-of-system-call-arguments-on-linux 60 | */ 61 | PUBLIC long pure_syscall(long syscall_number, long arg1, long arg2, long arg3, long arg4, long arg5, long arg6); 62 | 63 | # define GET_syscall_args(_syscall_num, _1, _2, _3, _4, _5, _6, ...) _syscall_num, _1, _2, _3, _4, _5, _6 64 | /** 65 | * pure_syscall2 takes at most 6 arguments. 66 | */ 67 | # define pure_syscall2(syscall_number, ...) \ 68 | pure_syscall(GET_syscall_args(syscall_number, ## __VA_ARGS__, 0, 0, 0, 0, 0, 0)) 69 | 70 | /** 71 | * @param mode please set it to 0 when O_CREAT isn't specified in flags. 72 | */ 73 | PUBLIC int psys_openat(int dirfd, const char *pathname, int flags, mode_t mode); 74 | 75 | PUBLIC int psys_close(int fd); 76 | 77 | PUBLIC int psys_dup(int oldfd); 78 | PUBLIC int psys_dup3(int oldfd, int newfd, int flags); 79 | 80 | PUBLIC int psys_pipe2(int pipefd[2], int flag); 81 | 82 | PUBLIC int psys_epoll_create1(int flags); 83 | PUBLIC int psys_epoll_ctl(int epfd, int op, int fd, void *event); 84 | PUBLIC int psys_epoll_pwait(int epfd, void *events, int maxevents, int timeout, const void *sigmask); 85 | 86 | PUBLIC int psys_chdir(const char *path); 87 | PUBLIC int psys_fchdir(int fd); 88 | 89 | PUBLIC ssize_t psys_write(int fd, const void *buf, size_t count); 90 | PUBLIC ssize_t psys_read(int fd, void *buf, size_t count); 91 | 92 | PUBLIC size_t psys_get_pagesz(); 93 | 94 | /** 95 | * @return MMAP_FAILED on error. 96 | */ 97 | PUBLIC void* psys_mmap(int *errno_v, void *addr, size_t len, int prot, int flags, int fd, off_t off); 98 | PUBLIC int psys_munmap(void *addr, size_t len); 99 | 100 | /** 101 | * @param new_addr please set it to NULL when MREMAP_FIXED isn't specified in flags. 102 | */ 103 | PUBLIC void* psys_mremap(int *errno_v, void *old_addr, size_t old_len, size_t new_len, int flags, 104 | void *new_addr); 105 | 106 | PUBLIC int psys_setresuid(uid_t ruid, uid_t euid, uid_t suid); 107 | PUBLIC int psys_setresgid(gid_t rgid, gid_t egid, gid_t sgid); 108 | PUBLIC int psys_setgroups(size_t size, const gid_t *list); 109 | 110 | PUBLIC pid_t psys_getpid(); 111 | 112 | PUBLIC int psys_sched_setparam(pid_t pid, const void *param); 113 | PUBLIC int psys_sched_getparam(pid_t pid, void *param); 114 | PUBLIC int psys_sched_setscheduler(pid_t pid, int policy, const void *param); 115 | PUBLIC int psys_sched_getscheduler(pid_t pid); 116 | 117 | /** 118 | * param pid is removed from this function to improve portability to os other than linux. 119 | * 120 | * @param new_limit should be of type struct rlimit64 121 | * @param old_limit should be of type struct rlimit64 122 | */ 123 | PUBLIC int psys_prlimit( 124 | int resource, 125 | const struct rlimit64 *new_limit, 126 | struct rlimit64 *old_limit 127 | ); 128 | 129 | /** 130 | * @return nice value in the range [40, 1], corresponding to commonly used [-20, 19]. 131 | * Translate between them using unice = 20 - knice. 132 | */ 133 | PUBLIC int psys_getpriority(int which, long who); 134 | /** 135 | * @param knice in the range [40, 1], corresponding to commonly used [-20, 19]. 136 | * Translate between them using unice = 20 - knice. 137 | */ 138 | PUBLIC int psys_setpriority(int which, long who, int knice); 139 | 140 | ALWAYS_INLINE PUBLIC void pure_sigemptyset(void *set); 141 | ALWAYS_INLINE PUBLIC void pure_sigfillset(void *set); 142 | 143 | /** 144 | * Check man sigprocmask for its API doc. 145 | */ 146 | PUBLIC int psys_sigprocmask(int how, const void *set, void *oldset); 147 | 148 | PUBLIC noreturn void psys_exit(int status); 149 | 150 | PUBLIC int psys_execve(const char *pathname, const char * const argv[], const char * const envp[]); 151 | /** 152 | * This syscall is native to linux, but is emulated on any other target 153 | * Checks `man 2 execveat` for more info. 154 | */ 155 | PUBLIC int psys_execveat( 156 | int dirfd, 157 | const char *pathname, 158 | const char * const argv[], 159 | const char * const envp[], 160 | int flags 161 | ); 162 | 163 | /** 164 | * @param file must not be NULL. 165 | * @param constructed_path constructed_path will only be changed on success. 166 | * Must be of path_max_len + 1. 167 | * @param PATH a pointer to the ':' separated string, containing directories to be looked up for the binary. 168 | * Will be modified during call. 169 | * On the first call, *PATH must point to the environment variable PATH. 170 | * @param path_max_len the max len of path (including the filename) on the system, 171 | * excluding the trailing null byte. 172 | * 173 | * @return 1 when next candidate path to exe is ready; 174 | * -1 when the constructed path will be longer than path_max_len; 175 | * 0 when all possible path is tried. 176 | * 177 | * This is a copy of glibc's execvep's implementation, except that it doesn't use 178 | * optimized strchrnull or memcpy from glibc (due to ). 179 | * It simply concat each path with file and let the user tries it with execve :D 180 | * A smarter application can utilize a cache to speed this up for repeated execves of binaries. 181 | * 182 | * Usage: 183 | * char constructed_path[PATH_MAX + 1]; 184 | * const char *path = // Get envir var path from parent; 185 | * if (path == NULL || path[0] == '\0') { 186 | * // *Handle that situation yourself* 187 | * } 188 | * if (argv[0] contains slash and is a path) { 189 | * // In case where argv[0] is already a path, there is no need to call find_exe. 190 | * } 191 | * for (int got_eaccess = 0; ; ) { 192 | * int result; 193 | * switch (find_exe(file, file_len, constructed_path, &path, PATH_MAX)) { 194 | * case 1: 195 | * result = handle_execve_err(psys_execve(constructed_path, argv, envp), &got_eaccess); 196 | * if (result < 0) { 197 | * int errno_v = -result; 198 | * // executable is found, but it failed to execute 199 | * // *Handle the errors here* 200 | * } 201 | * continue; 202 | * 203 | * case -1: 204 | * // One of the path is longer than path_max_len. 205 | * // Can chose the continue or break. 206 | * 207 | * case 0: 208 | * break; 209 | * } 210 | * break; 211 | * } 212 | * // If got_eaccess, the the executable is probably found but not executable. 213 | * // I.E., not a regular file, exe permission is denied, the filesystem is found is mounted noexec. 214 | * // 215 | * // But it could also be that the user do not have search permission on the prefix of constructed_path. 216 | */ 217 | PUBLIC int find_exe(const char *file, size_t file_len, char *constructed_path, 218 | const char **PATH, size_t path_max_len); 219 | 220 | /** 221 | * @param result return value of execve or fexecve. 222 | * @return 0 to try next path in PATH, negative number for failure. 223 | * In case negative number is returned, it is like psys_*: the negative number is equaivlent to 224 | * (-errno). 225 | * 226 | * Check find_exe for doc. 227 | */ 228 | PUBLIC int handle_execve_err(int result, int *got_eaccess); 229 | 230 | # ifdef __cplusplus 231 | } 232 | # endif 233 | 234 | #endif 235 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | --------------------------------------------------------------------------------