/* * Copyright 2017-2019, Jérôme Duval, jerome.duval@gmail.com * Distributed under the terms of the MIT License. */ #include #include #include #include #include #include #include #include #include #include enum action_type { file_action_open, file_action_close, file_action_dup2, file_action_chdir, file_action_fchdir }; struct _file_action { enum action_type type; int fd; union { struct { char* path; int oflag; mode_t mode; } open_action; struct { int srcfd; } dup2_action; struct { char* path; } chdir_action; } action; }; struct _posix_spawnattr { short flags; pid_t pgroup; sigset_t sigdefault; sigset_t sigmask; }; struct _posix_spawn_file_actions { int size; int count; _file_action *actions; }; static int posix_spawn_file_actions_extend(struct _posix_spawn_file_actions *actions) { int newsize = actions->size + 4; void *newactions = realloc(actions->actions, newsize * sizeof(struct _file_action)); if (newactions == NULL) return ENOMEM; actions->actions = (struct _file_action*)newactions; actions->size = newsize; return 0; } int posix_spawn_file_actions_init(posix_spawn_file_actions_t *_file_actions) { posix_spawn_file_actions_t actions = (posix_spawn_file_actions_t)malloc( sizeof(struct _posix_spawn_file_actions)); if (actions == NULL) return ENOMEM; memset(actions, 0, sizeof(*actions)); *_file_actions = actions; return 0; } int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t *_actions) { struct _posix_spawn_file_actions* actions = _actions != NULL ? *_actions : NULL; if (actions == NULL) return EINVAL; for (int i = 0; i < actions->count; i++) { struct _file_action *action = &actions->actions[i]; if (action->type == file_action_open) free(action->action.open_action.path); else if (action->type == file_action_chdir) free(action->action.chdir_action.path); } free(actions); return 0; } int posix_spawn_file_actions_addopen(posix_spawn_file_actions_t *_actions, int fildes, const char *path, int oflag, mode_t mode) { struct _posix_spawn_file_actions* actions = _actions != NULL ? *_actions : NULL; if (actions == NULL) return EINVAL; if (fildes < 0 || fildes >= sysconf(_SC_OPEN_MAX)) return EBADF; char* npath = strdup(path); if (npath == NULL) return ENOMEM; if (actions->count == actions->size && posix_spawn_file_actions_extend(actions) != 0) { free(npath); return ENOMEM; } struct _file_action *action = &actions->actions[actions->count]; action->type = file_action_open; action->fd = fildes; action->action.open_action.path = npath; action->action.open_action.oflag = oflag; action->action.open_action.mode = mode; actions->count++; return 0; } int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t *_actions, int fildes) { struct _posix_spawn_file_actions* actions = _actions != NULL ? *_actions : NULL; if (actions == NULL) return EINVAL; if (fildes < 0 || fildes >= sysconf(_SC_OPEN_MAX)) return EBADF; if (actions->count == actions->size && posix_spawn_file_actions_extend(actions) != 0) { return ENOMEM; } struct _file_action *action = &actions->actions[actions->count]; action->type = file_action_close; action->fd = fildes; actions->count++; return 0; } int posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t *_actions, int fildes, int newfildes) { struct _posix_spawn_file_actions* actions = _actions != NULL ? *_actions : NULL; if (actions == NULL) return EINVAL; if (fildes < 0 || fildes >= sysconf(_SC_OPEN_MAX)) return EBADF; if (actions->count == actions->size && posix_spawn_file_actions_extend(actions) != 0) { return ENOMEM; } struct _file_action *action = &actions->actions[actions->count]; action->type = file_action_dup2; action->fd = newfildes; action->action.dup2_action.srcfd = fildes; actions->count++; return 0; } extern "C" int posix_spawn_file_actions_addchdir_np(posix_spawn_file_actions_t *_actions, const char *path) { struct _posix_spawn_file_actions* actions = _actions != NULL ? *_actions : NULL; if (actions == NULL) return EINVAL; char* npath = strdup(path); if (npath == NULL) return ENOMEM; if (actions->count == actions->size && posix_spawn_file_actions_extend(actions) != 0) { free(npath); return ENOMEM; } struct _file_action *action = &actions->actions[actions->count]; action->type = file_action_chdir; action->fd = -1; action->action.chdir_action.path = npath; actions->count++; return 0; } extern "C" int posix_spawn_file_actions_addfchdir_np(posix_spawn_file_actions_t *_actions, int fildes) { struct _posix_spawn_file_actions* actions = _actions != NULL ? *_actions : NULL; if (actions == NULL) return EINVAL; if (fildes < 0 || fildes >= sysconf(_SC_OPEN_MAX)) return EBADF; if (actions->count == actions->size && posix_spawn_file_actions_extend(actions) != 0) { return ENOMEM; } struct _file_action *action = &actions->actions[actions->count]; action->type = file_action_fchdir; action->fd = fildes; actions->count++; return 0; } int posix_spawnattr_init(posix_spawnattr_t *_attr) { posix_spawnattr_t attr = (posix_spawnattr_t)malloc( sizeof(struct _posix_spawnattr)); if (attr == NULL) return ENOMEM; memset(attr, 0, sizeof(*attr)); *_attr = attr; return 0; } int posix_spawnattr_destroy(posix_spawnattr_t *_attr) { struct _posix_spawnattr* attr = _attr != NULL ? *_attr : NULL; if (attr == NULL) return EINVAL; free(attr); return 0; } int posix_spawnattr_getflags(const posix_spawnattr_t *_attr, short *flags) { struct _posix_spawnattr *attr; if (_attr == NULL || (attr = *_attr) == NULL || flags == NULL) return EINVAL; *flags = attr->flags; return 0; } int posix_spawnattr_setflags(posix_spawnattr_t *_attr, short flags) { struct _posix_spawnattr *attr; if (_attr == NULL || (attr = *_attr) == NULL) return EINVAL; if ((flags & ~(POSIX_SPAWN_RESETIDS | POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSID)) != 0) { return EINVAL; } attr->flags = flags; return 0; } int posix_spawnattr_getpgroup(const posix_spawnattr_t *_attr, pid_t *pgroup) { struct _posix_spawnattr *attr; if (_attr == NULL || (attr = *_attr) == NULL || pgroup == NULL) return EINVAL; *pgroup = attr->pgroup; return 0; } int posix_spawnattr_setpgroup(posix_spawnattr_t *_attr, pid_t pgroup) { struct _posix_spawnattr *attr; if (_attr == NULL || (attr = *_attr) == NULL) return EINVAL; attr->pgroup = pgroup; return 0; } int posix_spawnattr_getsigdefault(const posix_spawnattr_t *_attr, sigset_t *sigdefault) { struct _posix_spawnattr *attr; if (_attr == NULL || (attr = *_attr) == NULL || sigdefault == NULL) return EINVAL; memcpy(sigdefault, &attr->sigdefault, sizeof(sigset_t)); return 0; } int posix_spawnattr_setsigdefault(posix_spawnattr_t *_attr, const sigset_t *sigdefault) { struct _posix_spawnattr *attr; if (_attr == NULL || (attr = *_attr) == NULL || sigdefault == NULL) return EINVAL; memcpy(&attr->sigdefault, sigdefault, sizeof(sigset_t)); return 0; } int posix_spawnattr_getsigmask(const posix_spawnattr_t *_attr, sigset_t *sigmask) { struct _posix_spawnattr *attr; if (_attr == NULL || (attr = *_attr) == NULL || sigmask == NULL) return EINVAL; memcpy(sigmask, &attr->sigmask, sizeof(sigset_t)); return 0; } int posix_spawnattr_setsigmask(posix_spawnattr_t *_attr, const sigset_t *sigmask) { struct _posix_spawnattr *attr; if (_attr == NULL || (attr = *_attr) == NULL || sigmask == NULL) return EINVAL; memcpy(&attr->sigmask, sigmask, sizeof(sigset_t)); return 0; } static int process_spawnattr(const posix_spawnattr_t *_attr) { if (_attr == NULL) return 0; struct _posix_spawnattr *attr = *_attr; if (attr == NULL) return EINVAL; if ((attr->flags & POSIX_SPAWN_SETSIGMASK) != 0) sigprocmask(SIG_SETMASK, &attr->sigmask, NULL); if ((attr->flags & POSIX_SPAWN_SETSIGDEF) != 0) { struct sigaction action; action.sa_handler = SIG_DFL; action.sa_flags = 0; action.sa_userdata = NULL; sigemptyset(&action.sa_mask); for (int i = 1; i <= MAX_SIGNAL_NUMBER; i++) { if (sigismember(&attr->sigdefault, i) == 1 && sigaction(i, &action, NULL) != 0) { return errno; } } } if ((attr->flags & POSIX_SPAWN_RESETIDS) != 0) { if (setegid(getgid()) != 0) return errno; if (seteuid(getuid()) != 0) return errno; } if ((attr->flags & POSIX_SPAWN_SETSID) != 0) { if (setsid() != 0) return errno; } if ((attr->flags & POSIX_SPAWN_SETPGROUP) != 0) { if (setpgid(0, attr->pgroup) != 0) return errno; } return 0; } static int process_file_actions(const posix_spawn_file_actions_t *_actions, int *errfd) { if (_actions == NULL) return 0; struct _posix_spawn_file_actions* actions = *_actions; if (actions == NULL) return EINVAL; for (int i = 0; i < actions->count; i++) { struct _file_action *action = &actions->actions[i]; if (action->fd == *errfd) { int newfd = dup(action->fd); if (newfd == -1) return errno; close(action->fd); fcntl(newfd, F_SETFD, FD_CLOEXEC); *errfd = newfd; } if (action->type == file_action_close) { if (close(action->fd) != 0) return errno; } else if (action->type == file_action_open) { int fd = open(action->action.open_action.path, action->action.open_action.oflag, action->action.open_action.mode); if (fd == -1) return errno; if (fd != action->fd) { if (dup2(fd, action->fd) == -1) return errno; if (close(fd) != 0) return errno; } } else if (action->type == file_action_dup2) { if (dup2(action->action.dup2_action.srcfd, action->fd) == -1) return errno; } else if (action->type == file_action_chdir) { if (chdir(action->action.chdir_action.path) == -1) return errno; } else if (action->type == file_action_fchdir) { if (fchdir(action->fd) == -1) return errno; } } return 0; } static int spawn_using_fork(pid_t *_pid, const char *path, const posix_spawn_file_actions_t *actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[], bool envpath) { int err = 0; int fds[2]; pid_t pid; if (pipe(fds) != 0) return errno; if (fcntl(fds[0], F_SETFD, FD_CLOEXEC) != 0 || fcntl(fds[1], F_SETFD, FD_CLOEXEC) != 0) { goto fail; } pid = vfork(); if (pid == -1) goto fail; if (pid != 0) { // parent if (_pid != NULL) *_pid = pid; close(fds[1]); read(fds[0], &err, sizeof(err)); if (err != 0) waitpid(pid, NULL, WNOHANG); close(fds[0]); return err; } // child close(fds[0]); err = process_spawnattr(attrp); if (err == 0) err = process_file_actions(actions, &fds[1]); if (err != 0) goto fail_child; if (envpath) execvpe(path, argv, envp != NULL ? envp : environ); else execve(path, argv, envp != NULL ? envp : environ); err = errno; fail_child: write(fds[1], &err, sizeof(err)); close(fds[1]); _exit(127); fail: err = errno; close(fds[0]); close(fds[1]); return err; } static int spawn_using_load_image(pid_t *_pid, const char *_path, char *const argv[], char *const envp[], bool envpath) { const char* path; // if envpath is specified but the path contains '/', don't search PATH if (!envpath || strchr(_path, '/') != NULL) { path = _path; } else { char* buffer = (char*)alloca(B_PATH_NAME_LENGTH); status_t status = __look_up_in_path(_path, buffer); if (status != B_OK) return status; path = buffer; } // count arguments int32 argCount = 0; while (argv[argCount] != NULL) argCount++; thread_id thread = __load_image_at_path(path, argCount, (const char**)argv, (const char**)(envp != NULL ? envp : environ)); if (thread < 0) return thread; *_pid = thread; return resume_thread(thread); } static int do_posix_spawn(pid_t *_pid, const char *path, const posix_spawn_file_actions_t *actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[], bool envpath) { if ((actions == NULL || (*actions)->count == 0) && (attrp == NULL || (*attrp)->flags == 0)) { return spawn_using_load_image(_pid, path, argv, envp, envpath); } else { return spawn_using_fork(_pid, path, actions, attrp, argv, envp, envpath); } } int posix_spawn(pid_t *pid, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[]) { return do_posix_spawn(pid, path, file_actions, attrp, argv, envp, false); } int posix_spawnp(pid_t *pid, const char *file, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[]) { return do_posix_spawn(pid, file, file_actions, attrp, argv, envp, true); } B_DEFINE_WEAK_ALIAS(posix_spawn_file_actions_addchdir_np, posix_spawn_file_actions_addchdir); B_DEFINE_WEAK_ALIAS(posix_spawn_file_actions_addfchdir_np, posix_spawn_file_actions_addfchdir);