Repository: SerenityOS/serenity
File: Userland/Libraries/LibC/spawn.cpp (C++)

Code before:
        return errno;

    if (child_pid != 0) {
        *out_pid = child_pid;
        return 0;
    }

    posix_spawn_child(path, file_actions, attr, argv, envp, execve);
}

// https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnp.html
int posix_spawnp(pid_t* out_pid, char const* file, posix_spawn_file_actions_t const* file_actions, posix_spawnattr_t const* attr, char* const argv[], char* const envp[])
{
    if (strchr(file, '/') != nullptr)
        return posix_spawn(out_pid, file, file_actions, attr, argv, envp);

    ByteString path = getenv("PATH");
    if (path.is_empty())
        path = DEFAULT_PATH;

    int rc = ENOENT;

    path.view().for_each_split_view(":"sv, SplitBehavior::Nothing, [out_pid, file, file_actions, attr, argv, envp, &rc](auto const directory) -> IterationDecision {
        auto absolute_path = ByteString::formatted("{}/{}", directory, file);

        if (access(absolute_path.characters(), X_OK) == 0) {
            rc = posix_spawn(out_pid, absolute_path.characters(), file_actions, attr, argv, envp);
            return IterationDecision::Break;
        }

        return IterationDecision::Continue;
    });

    return rc;
}

// https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawn_file_actions_addchdir.html
int posix_spawn_file_actions_addchdir(posix_spawn_file_actions_t* actions, char const* path)
{
    actions->state->actions.append([path]() { return chdir(path); });
    return 0;
}

int posix_spawn_file_actions_addfchdir(posix_spawn_file_actions_t* actions, int fd)
{
    actions->state->actions.append([fd]() { return fchdir(fd); });
    return 0;
}

// https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawn_file_actions_addclose.html
int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t* actions, int fd)

Review comment (suggestion):
execvpe() in unistd.cpp works by calling execve on each candidate and retrying on ENOENT. Could we call posix_spawn() on each candidate and continue on ENOENT? That might resolve the TOCTOU issue.

Code after:
        return errno;

    if (child_pid != 0) {
        *out_pid = child_pid;
        return 0;
    }

    posix_spawn_child(path, file_actions, attr, argv, envp, execve);
}

// https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnp.html
int posix_spawnp(pid_t* out_pid, char const* file, posix_spawn_file_actions_t const* file_actions, posix_spawnattr_t const* attr, char* const argv[], char* const envp[])
{
    if (strchr(file, '/') != nullptr)
        return posix_spawn(out_pid, file, file_actions, attr, argv, envp);

    if ((!file_actions || file_actions->state->actions.is_empty()) && !attr) {
        // FIXME: This is currently not OOM-safe because ByteString does not handle OOMs!

        ByteString path = getenv("PATH");
        if (path.is_empty())
            path = DEFAULT_PATH;

        int rc = ENOENT;

        path.view().for_each_split_view(":"sv, SplitBehavior::Nothing, [out_pid, file, file_actions, attr, argv, envp, &rc](auto const directory) -> IterationDecision {
            auto absolute_path = ByteString::formatted("{}/{}", directory, file);

            rc = posix_spawn(out_pid, absolute_path.characters(), file_actions, attr, argv, envp);
            if (rc == ENOENT)
                return IterationDecision::Continue;
            return IterationDecision::Break;
        });

        return rc;
    }

    pid_t child_pid = fork();
    if (child_pid < 0)
        return errno;

    if (child_pid != 0) {
        *out_pid = child_pid;
        return 0;
    }

    posix_spawn_child(file, file_actions, attr, argv, envp, execvpe);
}

// https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawn_file_actions_addchdir.html
int posix_spawn_file_actions_addchdir(posix_spawn_file_actions_t* actions, char const* path)