<commit_before>// Copyright 2003-2022 <NAME> <<EMAIL> at gmail dot com>
// SPDX-License-Identifier: MIT
#include <sys/types.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <pwd.h>
#include "nk/exec.h"
#include "nk/io.h"

/*
 * Note that neither nk_generate_env or nk_execute are async signal safe, so
 * these functions should only be called after fork() in a non-multithreaded
 * process.
 *
 * I don't consider this to be a problem in general, since in a multithreaded process
 * it would be far better to fork off a subprocess early on before threads are
 * created and use a socketpair() to request subprocess creation from the
 * single-threaded subprocess from the multithreaded main program.
 */

#define DEFAULT_ROOT_PATH "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin"
#define DEFAULT_PATH "/bin:/usr/bin:/usr/local/bin"
#define MAX_ARGS 256
#define MAX_ARGBUF 4096

#define NK_GEN_ENV(GEN_STR, ...) do { \
        if (env_offset >= envlen) return -3; \
        ssize_t snlen = snprintf(envbuf, envbuflen, GEN_STR, __VA_ARGS__); \
        if (snlen < 0 || (size_t)snlen >= envbuflen) return -2; \
        env[env_offset++] = envbuf; envbuf += snlen; envbuflen -= (size_t)snlen; \
    } while (0)

/*
 * uid: userid of the user account that the environment will constructed for
 * chroot_path: path where the environment will be chrooted or NULL if no chroot
 * path_var: value of the PATH variable in the environment or defaults if NULL
 * env: array of character pointers that will be filled in with the new environment
 * envlen: number of character pointers available in env; a terminal '0' ptr must be available
 * envbuf: character buffer that will be used for storing state associated with env
 * envbuflen: number of available characters in envbuf for use
 *
 * returns:
 * 0 on success
 * -1 if an account for uid does not exist
 * -2 if there is not enough space in envbuf for the generated environment
 * -3 if there is not enough space in env for the generated environment
 * -4 if chdir to homedir or rootdir failed
 * -5 if oom or i/o failed
 */
int nk_generate_env(uid_t uid, const char *chroot_path, const char *path_var,
                    char *env[], size_t envlen, char *envbuf, size_t envbuflen)
{
    char pw_strs[4096];
    struct passwd pw_s;
    struct passwd *pw;
    char *pw_buf = NULL;
    int ret = 0, pwr;

getpwagain0:
    pwr = getpwuid_r(uid, &pw_s, pw_strs, sizeof pw_strs, &pw);
    if (pw == NULL) {
        if (pwr == 0) { ret = -1; goto out; }
        if (pwr == EINTR) goto getpwagain0;
        if (pwr == ERANGE) {
            size_t pwlen = (sizeof pw_strs >> 1) * 3;
            for (;;) {
                if (pw_buf) free(pw_buf);
                pw_buf = malloc(pwlen);
                if (!pw_buf) { ret = -5; goto out; }
getpwagain:
                pwr = getpwuid_r(uid, &pw_s, pw_buf, pwlen, &pw);
                if (pw == NULL) {
                    if (pwr == 0) { ret = -1; goto out; }
                    if (pwr == EINTR) goto getpwagain;
                    if (pwr == ERANGE) {
                        size_t oldpwlen = pwlen;
                        pwlen = (pwlen >> 1) * 3;
                        if (pwlen > oldpwlen) continue;
                        else { // overflowed
                            ret = -5; goto out;
                        }
                    }
                    ret = -5; goto out;
                }
                break; // the pwr != 0 check below applies here
            }
        }
        ret = -5; goto out;
    }
    if (pwr != 0) { ret = -5; goto out; }

    size_t env_offset = 0;
    if (envlen-- < 1) { // So we don't have to account for the terminal NULL
        ret = -3;
        goto out;
    }

    NK_GEN_ENV("UID=%i", uid);
    NK_GEN_ENV("USER=%s", pw->pw_name);
    NK_GEN_ENV("USERNAME=%s", pw->pw_name);
    NK_GEN_ENV("LOGNAME=%s", pw->pw_name);
    NK_GEN_ENV("HOME=%s", pw->pw_dir);
    NK_GEN_ENV("SHELL=%s", pw->pw_shell);
    NK_GEN_ENV("PATH=%s", path_var ? path_var : (uid > 0 ? DEFAULT_PATH : DEFAULT_ROOT_PATH));
    NK_GEN_ENV("PWD=%s", !chroot_path ? pw->pw_dir : "/");
    if (chroot_path && chroot(chroot_path)) { ret = -4; goto out; }
    if (chdir(chroot_path ? chroot_path : "/")) { ret = -4; goto out; }

    env[env_offset] = 0;
out:
    free(pw_buf);
    return ret;
}

#define ERRSTR0 "exec: failed to generate environment - (?) unknown error\n"
#define ERRSTR1 "exec: failed to generate environment - (-1) account for uid does not exist\n"
#define ERRSTR2 "exec: failed to generate environment - (-2) not enough space in envbuf\n"
#define ERRSTR3 "exec: failed to generate environment - (-3) not enough space in env\n"
#define ERRSTR4 "exec: failed to generate environment - (-4) chdir to homedir or rootdir failed\n"
#define ERRSTR5 "exec: failed to generate environment - (-5) oom or i/o error\n"
void nk_generate_env_print_error(int err)
{
    switch (err) {
    default: safe_write(STDERR_FILENO, ERRSTR0, sizeof ERRSTR0); break;
    case -1: safe_write(STDERR_FILENO, ERRSTR1, sizeof ERRSTR1); break;
    case -2: safe_write(STDERR_FILENO, ERRSTR2, sizeof ERRSTR2); break;
    case -3: safe_write(STDERR_FILENO, ERRSTR3, sizeof ERRSTR3); break;
    case -4: safe_write(STDERR_FILENO, ERRSTR4, sizeof ERRSTR4); break;
    case -5: safe_write(STDERR_FILENO, ERRSTR5, sizeof ERRSTR5); break;
    }
}
#undef ERRSTR0
#undef ERRSTR1
#undef ERRSTR2
#undef ERRSTR3
#undef ERRSTR4
#undef ERRSTR5

#define NK_GEN_ARG(GEN_STR, ...) do { \
        ssize_t snlen = snprintf(argbuf, argbuflen, GEN_STR, __VA_ARGS__); \
        if (snlen < 0 || (size_t)snlen >= argbuflen) { \
            static const char errstr[] = "nk_execute: constructing argument list failed\n"; \
            safe_write(STDERR_FILENO, errstr, sizeof errstr); \
            _Exit(EXIT_FAILURE); \
        } \
        argv[curv] = argbuf; argv[++curv] = NULL; \
        argbuf += snlen; argbuflen -= (size_t)snlen; \
    } while (0)

void __attribute__((noreturn))
nk_execute(const char *command, const char *args, char * const envp[])
{
    char *argv[MAX_ARGS];
    char argbuf_s[MAX_ARGBUF];
    char *argbuf = argbuf_s;
    size_t curv = 0;
    size_t argbuflen = sizeof argbuf_s;

    if (!command)
        _Exit(EXIT_SUCCESS);

    // strip the path from the command name and set argv[0]
    const char *p = strrchr(command, '/');
    NK_GEN_ARG("%s", p ? p + 1 : command);<commit_msg>nk/exec: Eliminate malloc() and simplify nk_generate_env().<commit_after>// Copyright 2003-2022 <NAME> <<EMAIL>>
// SPDX-License-Identifier: MIT
#include <sys/types.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <pwd.h>
#include "nk/exec.h"
#include "nk/io.h"

/*
 * Note that neither nk_generate_env or nk_execute are async signal safe, so
 * these functions should only be called after fork() in a non-multithreaded
 * process.
 *
 * I don't consider this to be a problem in general, since in a multithreaded process
 * it would be far better to fork off a subprocess early on before threads are
 * created and use a socketpair() to request subprocess creation from the
 * single-threaded subprocess from the multithreaded main program.
 */

#define DEFAULT_ROOT_PATH "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin"
#define DEFAULT_PATH "/bin:/usr/bin:/usr/local/bin"
#define MAX_ARGS 256
#define MAX_ARGBUF 4096
#define MAX_PWBUF 16384

#define NK_GEN_ENV(GEN_STR, ...) do { \
        if (env_offset >= envlen) return -3; \
        ssize_t snlen = snprintf(envbuf, envbuflen, GEN_STR, __VA_ARGS__); \
        if (snlen < 0 || (size_t)snlen >= envbuflen) return -2; \
        env[env_offset++] = envbuf; envbuf += snlen; envbuflen -= (size_t)snlen; \
    } while (0)

/*
 * uid: userid of the user account that the environment will constructed for
 * chroot_path: path where the environment will be chrooted or NULL if no chroot
 * path_var: value of the PATH variable in the environment or defaults if NULL
 * env: array of character pointers that will be filled in with the new environment
 * envlen: number of character pointers available in env; a terminal '0' ptr must be available
 * envbuf: character buffer that will be used for storing state associated with env
 * envbuflen: number of available characters in envbuf for use
 *
 * returns:
 * 0 on success
 * -1 if an account for uid does not exist
 * -2 if there is not enough space in envbuf for the generated environment
 * -3 if there is not enough space in env for the generated environment
 * -4 if chdir to homedir or rootdir failed
 * -5 if oom or i/o failed
 * -6 if MAX_PWBUF is too small
 */
int nk_generate_env(uid_t uid, const char *chroot_path, const char *path_var,
                    char *env[], size_t envlen, char *envbuf, size_t envbuflen)
{
    char pwbuf[MAX_PWBUF];
    struct passwd pw_s, *pw;

    for (;;) {
        int r = getpwuid_r(uid, &pw_s, pwbuf, sizeof pwbuf, &pw);
        if (!r) {
            if (pw == NULL) return -1;
            break;
        } else {
            if (r == EINTR) continue;
            if (r == ERANGE) return -6;
            return -5;
        }
    }

    size_t env_offset = 0;
    if (envlen-- < 1) return -3; // So we don't have to account for the terminal NULL

    NK_GEN_ENV("UID=%i", uid);
    NK_GEN_ENV("USER=%s", pw->pw_name);
    NK_GEN_ENV("USERNAME=%s", pw->pw_name);
    NK_GEN_ENV("LOGNAME=%s", pw->pw_name);
    NK_GEN_ENV("HOME=%s", pw->pw_dir);
    NK_GEN_ENV("SHELL=%s", pw->pw_shell);
    NK_GEN_ENV("PATH=%s", path_var ? path_var : (uid > 0 ? DEFAULT_PATH : DEFAULT_ROOT_PATH));
    NK_GEN_ENV("PWD=%s", !chroot_path ? pw->pw_dir : "/");
    if (chroot_path && chroot(chroot_path)) return -4;
    if (chdir(chroot_path ? chroot_path : "/")) return -4;

    env[env_offset] = 0;
    return 0;
}

#define ERRSTR0 "exec: failed to generate environment - (?) unknown error\n"
#define ERRSTR1 "exec: failed to generate environment - (-1) account for uid does not exist\n"
#define ERRSTR2 "exec: failed to generate environment - (-2) not enough space in envbuf\n"
#define ERRSTR3 "exec: failed to generate environment - (-3) not enough space in env\n"
#define ERRSTR4 "exec: failed to generate environment - (-4) chdir to homedir or rootdir failed\n"
#define ERRSTR5 "exec: failed to generate environment - (-5) oom or i/o error\n"
#define ERRSTR6 "exec: failed to generate environment - (-6) MAX_PWBUF is too small\n"
void nk_generate_env_print_error(int err)
{
    switch (err) {
    default: safe_write(STDERR_FILENO, ERRSTR0, sizeof ERRSTR0); break;
    case -1: safe_write(STDERR_FILENO, ERRSTR1, sizeof ERRSTR1); break;
    case -2: safe_write(STDERR_FILENO, ERRSTR2, sizeof ERRSTR2); break;
    case -3: safe_write(STDERR_FILENO, ERRSTR3, sizeof ERRSTR3); break;
    case -4: safe_write(STDERR_FILENO, ERRSTR4, sizeof ERRSTR4); break;
    case -5: safe_write(STDERR_FILENO, ERRSTR5, sizeof ERRSTR5); break;
    case -6: safe_write(STDERR_FILENO, ERRSTR6, sizeof ERRSTR6); break;
    }
}
#undef ERRSTR0
#undef ERRSTR1
#undef ERRSTR2
#undef ERRSTR3
#undef ERRSTR4
#undef ERRSTR5
#undef ERRSTR6

#define NK_GEN_ARG(GEN_STR, ...) do { \
        ssize_t snlen = snprintf(argbuf, argbuflen, GEN_STR, __VA_ARGS__); \
        if (snlen < 0 || (size_t)snlen >= argbuflen) { \
            static const char errstr[] = "nk_execute: constructing argument list failed\n"; \
            safe_write(STDERR_FILENO, errstr, sizeof errstr); \
            _Exit(EXIT_FAILURE); \
        } \
        argv[curv] = argbuf; argv[++curv] = NULL; \
        argbuf += snlen; argbuflen -= (size_t)snlen; \
    } while (0)

void __attribute__((noreturn))
nk_execute(const char *command, const char *args, char * const envp[])
{
    char *argv[MAX_ARGS];
    char argbuf_s[MAX_ARGBUF];
    char *argbuf = argbuf_s;
    size_t curv = 0;
    size_t argbuflen = sizeof argbuf_s;

    if (!command)
        _Exit(EXIT_SUCCESS);

    // strip the path from the command name and set argv[0]
    const char *p = strrchr(command, '/');
    NK_GEN_ARG("%s", p ? p + 1 : command);
{'fraction_non_alphanumeric': 0.11853307138179436, 'fraction_numerical': 0.01489849377865095, 'mean_word_length': 2.918216805644644, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 52, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1907096', 'n_tokens_mistral': 4328, 'n_tokens_neox': 4165, 'n_words': 1349}