Repository: SerenityOS/serenity
File: Kernel/Syscalls/posix_spawn.cpp (C++)

Code before:
            auto string = TRY(try_copy_kstring_from_user(strings[i]));
            TRY(output.try_append(move(string)));
        }
        return {};
    };

    Vector<NonnullOwnPtr<KString>> arguments;
    TRY(copy_user_strings(params.arguments, arguments));

    Vector<NonnullOwnPtr<KString>> environment;
    TRY(copy_user_strings(params.environment, environment));

    auto credentials = this->credentials();
    Process::Name name {};
    Process::current().name().with([&name](auto& process_name) {
        name.store_characters(process_name.representable_view());
    });
    auto [child, child_first_thread] = TRY(Process::create(name.representable_view(), credentials->uid(), credentials->gid(), pid(), m_is_kernel_process, vfs_root_context(), hostname_context(), current_directory(), nullptr, tty(), nullptr));

    ArmedScopeGuard thread_finalizer_guard = [&child_first_thread]() {
        SpinlockLocker lock(g_scheduler_lock);
        child_first_thread->detach();
        child_first_thread->set_state(Thread::State::Dying);
    };

    // Since we aren't copying parent's file descriptor table, set it up like create init process does
    TRY(child->m_fds.with_exclusive([&](auto& fds) -> ErrorOr<void> {
        TRY(fds.try_resize(Process::OpenFileDescriptions::max_open()));

        // NOTE: If Device::base_devices() is returning nullptr, it means the null device is not attached which is a bug.
        VERIFY(Device::base_devices() != nullptr);
        auto& device_to_use_as_tty = tty() ? static_cast<CharacterDevice&>(*tty()) : Device::base_devices()->null_device;
        auto description = TRY(device_to_use_as_tty.open(O_RDWR));
        auto setup_description = [&](int fd) {
            fds.m_fds_metadatas[fd].allocate();
            fds[fd].set(*description);
        };
        setup_description(0);
        setup_description(1);
        setup_description(2);

        return {};
    }));

    // Copy protected data
    with_protected_data([&](auto& my_protected_data) {
        child->with_mutable_protected_data([&](auto& child_protected_data) {
            child_protected_data.umask = my_protected_data.umask;
            child_protected_data.process_group = my_protected_data.process_group;
        });
    });

Review comment (bug):
This seems wrong, the `posix_spawn` documentation says that file descriptors should be kept except for ones with `FD_CLOEXEC` and `FD_CLOFORK` set (plus other restrictions that I don't think apply to us yet).

At the very least file descriptors 0, 1, 2 should be copied over, otherwise it's going to be confusing when interacting with spawned processes over stdio.

Code after:
        TRY(strings.try_resize(list.length));
        TRY(copy_from_user(strings.data(), list.strings, size.value()));
        for (size_t i = 0; i < list.length; ++i) {
            auto string = TRY(try_copy_kstring_from_user(strings[i]));
            TRY(output.try_append(move(string)));
        }
        return {};
    };

    Vector<NonnullOwnPtr<KString>> arguments;
    TRY(copy_user_strings(params.arguments, arguments));

    Vector<NonnullOwnPtr<KString>> environment;
    TRY(copy_user_strings(params.environment, environment));

    auto credentials = this->credentials();
    auto [child, child_first_thread] = TRY(Process::create({}, credentials->uid(), credentials->gid(), pid(), m_is_kernel_process, vfs_root_context(), hostname_context(), current_directory(), nullptr, tty(), nullptr));

    ArmedScopeGuard thread_finalizer_guard = [&child_first_thread]() {
        SpinlockLocker lock(g_scheduler_lock);
        child_first_thread->detach();
        child_first_thread->set_state(Thread::State::Dying);
    };

    // "It is implementation-defined whether the fork handlers are run when posix_spawn() or posix_spawnp() is called."
    // We don't run them, as they are currently implemented in LibC.

    TRY(child->m_fds.with_exclusive([&](auto& child_fds) {
        return m_fds.with_exclusive([&](auto const& parent_fds) {
            return child_fds.try_clone(parent_fds);
        });

        // FD_CLOEXEC is handled by do_exec().
        // FIXME: Support FD_CLOFORK.
    }));

    // FIXME: "If file descriptor 0, 1, or 2 would otherwise be closed in the new process image created by posix_spawn() or posix_spawnp(),
    //         implementations may open an unspecified file for the file descriptor in the new process image."

    // Copy protected data which isn't set by do_exec().
    child->with_mutable_protected_data([&](auto& child_protected_data) {
        with_protected_data([&](auto const& parent_protected_data) {
            child_protected_data.umask = parent_protected_data.umask;
            child_protected_data.process_group = parent_protected_data.process_group;
            child_protected_data.credentials = parent_protected_data.credentials;
        });
    });

    dbgln_if(FORK_DEBUG, "posix_spawn: child={}", child);

    // A child created via posix_spawn inherits a copy of its parent's signal mask