Skip to content
Snippets Groups Projects
execl-envfile.c 10.73 KiB
/*
 * execl-envfile.c
 *
 * Copyright (c) 2018-2023 Eric Vidal <eric@obarun.org>
 *
 * All rights reserved.
 *
 * This file is part of Obarun. It is subject to the license terms in
 * the LICENSE file found in the top-level directory of this
 * distribution.
 * This file may not be copied, modified, propagated, or distributed
 * except according to the terms contained in the LICENSE file./
 */
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>

#include <oblibs/string.h>
#include <oblibs/log.h>
#include <oblibs/types.h>
#include <oblibs/directory.h>
#include <oblibs/files.h>
#include <oblibs/environ.h>
#include <oblibs/sastr.h>

#include <skalibs/bytestr.h>
#include <skalibs/stralloc.h>
#include <skalibs/env.h>
#include <skalibs/djbunix.h>
#include <skalibs/env.h>
#include <skalibs/sgetopt.h>
#include <skalibs/exec.h>

#include <execline/execline.h>

#define USAGE "execl-envfile [ -h ] [ -v verbosity ] [ -l ] src prog"

static inline void info_help (void)
{
  static char const *help =
"\n"
"options :\n"
"   -h: print this help\n"
"   -v: increase/decrease verbosity\n"
"   -l: loose\n"
;

    log_info(USAGE,"\n",help) ;
}

static void parse_env_var(stralloc *result, char const *line) ;

static void die_or_exec(char const *path, uint8_t insist, char const *const *argv, char const *const *envp)
{
    if (insist)
        log_dieu(LOG_EXIT_SYS, "get environment from: ", path) ;
    else
        log_warnu("get environment from: ", path) ;

    xexec_ae(argv[0], argv, envp) ;
}

static void substitute_env_var(stralloc *result, char const *mkey, char const *key, char const *regex)
{
    stralloc tmp = STRALLOC_ZERO ;

    if (!stralloc_copy(&tmp, result) ||
        !stralloc_0(&tmp))
            log_die_nomem("stralloc") ;

    if (!sastr_rebuild_in_nline(&tmp))
        log_dieu(LOG_EXIT_SYS,"rebuild line") ;

    char *by = getenv(key) ;

    if (!by) {

            /** check if key and mkey are the same
             * variable to avoid a variable calling itself.
             * we accept e.g ${PATH}=${PATH} only if PATH
             * is set on the environnement */
             if (!strcmp(mkey,key))
                log_die(LOG_EXIT_USER,"recursive call of variable: ",regex) ;

            /** if the key doesn't exist we let
             * the variable as it A.K.A. ${VAR}  */
            if (!environ_get_val_of_key(&tmp, key))
                return ;

            if (!stralloc_0(&tmp))
                log_die_nomem("stralloc") ;

            by = tmp.s ;
    }

    /** recursive call at variable definition.
     * parse first the recursive variable*/
    if (str_contain(by,"${") >= 0) {

        char tmp[strlen(key) + 1 + strlen(by) + 1] ;
        auto_strings(tmp,key,"=",by) ;

        parse_env_var(result, tmp) ;
    }

    if (!sastr_replace_all(result, regex, by))
        log_dieu(LOG_EXIT_SYS,"replace: ", regex, " by: ", by) ;

    stralloc_free(&tmp) ;
}

void parse_env_var(stralloc *result, char const *line)
{
    size_t spos = 0, pos = 0 ;
    stralloc subs = STRALLOC_ZERO ;

    /** be sure to deal with key=value */
    spos = get_sep_before(line,'=','\n') ;
    if (spos <= 0)
        log_dieu(LOG_EXIT_SYS,"get value from line: ", line) ;

    spos++ ; //remove the = character

    /** master key -- see comment at substitute_env_var */
    char mkey[spos + 1] ;
    memcpy(mkey,line,spos - 1) ;
    mkey[spos - 1] = 0 ;

    if (!line[spos]) {

        stralloc_free(&subs) ;
        return ;
    }

    if (!auto_stra(&subs, line + spos))
        log_die_nomem("stralloc") ;

    if (!sastr_split_element_in_nline(&subs))
        log_dieu(LOG_EXIT_SYS,"split line") ;

    {
        FOREACH_SASTR(&subs, pos) {

            char *line = subs.s + pos ;
            size_t len = strlen(line) ;
            char key[len + 1] ;

            ssize_t rstart = 0 ;

            /** deal with multiple ${} on the
             * same line */
            while (rstart < len) {

                ssize_t r = get_len_until(line + rstart, '$') ;

                if (r == -1)
                    break ;

                rstart += r ;

                ssize_t start = rstart + 1 ;

                if (line[start] == '{') {

                    ssize_t rend = get_len_until(line + start , '}') ;

                    if (rend == -1) {
                        log_warn("unmatched { at line: ", line) ;
                        return ;
                    }

                    ssize_t end = rend - 1 ;
                    start++ ;

                    memcpy(key, line + start, end) ;
                    key[end] = 0 ;

                    char regex[rend + 3] ;
                    memcpy(regex,line + rstart, rend + 2) ;
                    regex[rend + 2] = 0 ;

                    substitute_env_var(result, mkey, key, regex) ;

                    rstart += rstart + rend + 2 ;

                } else {

                    log_warn("ignoring variable at line: ", line," -- missing {}" ) ;
                    rstart += rstart + 1 ;
                    return ;
                }
            }
        }
    }
    stralloc_free(&subs) ;
}

int main (int argc, char const *const *argv, char const *const *envp)
{

    int r = 0 ;
    uint8_t unexport = 0, insist = 1 ;
    size_t pos = 0, nvar = 0 ;
    char const *path = 0 ;
    char tpath[MAXENV + 1], tfile[MAXENV+1] ;
    struct stat st ;

    stralloc modifs = STRALLOC_ZERO ;
    stralloc sa = STRALLOC_ZERO ;

    exlsn_t info = EXLSN_ZERO ;

    PROG = "execl-envfile" ;
    {
        subgetopt l = SUBGETOPT_ZERO ;

        for (;;) {

            int opt = subgetopt_r(argc, argv, "hv:l:", &l) ;
            if (opt == -1)
                break ;

            switch (opt) {

                case 'h' :

                    info_help();
                    return 0 ;

                case 'v' :

                    if (!uint0_scan(l.arg, &VERBOSITY))
                        log_usage(USAGE) ;

                    break ;

                case 'l' :

                    insist = 0 ;
                    break ;

                default :

                    log_usage(USAGE) ;
            }
        }
        argc -= l.ind ; argv += l.ind ;
    }

    if (argc < 2)
        log_usage(USAGE) ;

    path = *argv ;
    argv++;
    argc--;

    if (path[0] == '.') {

        if (!dir_beabsolute(tpath, path))
            die_or_exec(path, insist, argv, envp) ;

        path = tpath ;
    }

    r = stat(path, &st) ;
    if (r < 0)
        die_or_exec(path, insist, argv, envp) ;

    if (S_ISREG(st.st_mode)) {

        if (!ob_basename(tfile, path))
            log_dieu(LOG_EXIT_SYS, "get basename of: ", path) ;

        if (!ob_dirname(tpath, path))
            log_dieu(LOG_EXIT_SYS, "get dirname of: ", path) ;

        if (!file_readputsa(&modifs, tpath, tfile))
            die_or_exec(path, insist, argv, envp) ;

        if (!environ_get_clean_env(&modifs))
            log_dieu(LOG_EXIT_SYS, "clean environment of: ", path) ;

        if (!auto_stra(&sa, modifs.s))
            log_die_nomem("stralloc") ;

        if (!environ_remove_unexport(&modifs, modifs.s, modifs.len))
            log_dieu(LOG_EXIT_SYS, "remove exclamation mark from: ", path) ;

        if (!sastr_split_string_in_nline(&modifs))
            log_dieu(LOG_EXIT_SYS, "build environment line of: ", path) ;

        nvar = sastr_nelement(&modifs) ;
        if (nvar > MAXVAR)
            log_dieusys(LOG_EXIT_SYS, "to many variables in file: ", path) ;

        if (modifs.len > MAXENV)
            log_die(LOG_EXIT_SYS, "environment string too long") ;

    } else if (S_ISDIR(st.st_mode)) {

        if (!environ_clean_envfile(&modifs, path))
            die_or_exec(path, insist, argv, envp) ;

        if (!stralloc_copy(&sa, &modifs))
            log_die_nomem("stralloc") ;

        if (!environ_remove_unexport(&modifs, modifs.s, modifs.len))
            log_dieu(LOG_EXIT_SYS, "remove exclamation mark from environment variables") ;

        if (!sastr_split_string_in_nline(&modifs))
            log_dieu(LOG_EXIT_SYS, "build environment line") ;

    } else {

        errno = EINVAL ;
        log_diesys(LOG_EXIT_USER, "invalid format for path: ", path) ;
    }

    {
        pos = 0 ;
        size_t len = modifs.len ;
        char t[len + 1] ;

        sastr_to_char(t, &modifs) ;

        for (pos = 0 ; pos < len ; pos += strlen(t + pos) + 1)
            parse_env_var(&modifs, t + pos) ;

        if (!sastr_split_string_in_nline(&modifs))
            log_dieu(LOG_EXIT_SYS,"split string") ;
    }

    /** be able to freed the stralloc before existing */
    char tmp[modifs.len + 1] ;
    sastr_to_char(tmp, &modifs) ;

    size_t tmplen = modifs.len, n = env_len(envp) + 1 + byte_count(modifs.s, modifs.len, '\0') ;
    char const *newenv[n + 1] ;

    if (!env_merge(newenv, n , envp, env_len(envp), tmp, tmplen))
        log_dieusys(LOG_EXIT_SYS, "build environment") ;

    {
        /** keep variable in environment if ! do not exist*/
        size_t tmplen = sa.len ;
        pos = 0 ;

        char tmp[sa.len + 1] ;

        sastr_to_char(tmp, &sa) ;

        while (pos < tmplen) {

            unexport = 0 ;
            sa.len = 0 ;

            if (!stralloc_copyb(&sa, tmp, tmplen))
                log_die_nomem("stralloc") ;

            if (!environ_get_key_nclean(&sa, &pos))
                log_dieu(LOG_EXIT_SYS, "get key from line: ", sa.s + pos) ;

            if (!sa.len)
                continue ;

            char tkey[strlen(sa.s) + 1] ;
            auto_strings(tkey, sa.s) ;

            sa.len = 0 ;

            if (!stralloc_copyb(&sa, tmp, tmplen))
                log_die_nomem("stralloc") ;

            pos-- ;// retrieve the '=' character
            if (!environ_get_val(&sa, &pos))
                log_dieu(LOG_EXIT_SYS, "get value from line: ", sa.s + pos) ;

            char *uval = sa.s ;

            if (sa.s[0] == VAR_UNEXPORT) {

                uval = sa.s + 1 ;
                unexport = 1 ;
            }

            if(sastr_cmp(&info.vars, tkey) == -1)
                if (!environ_substitute(tkey, uval, &info, newenv, unexport))
                    log_dieu(LOG_EXIT_SYS, "substitute value of: ", tkey, " by: ", uval) ;
        }
    }

    sa.len = 0 ;
    modifs.len = 0 ;

    if (!env_string(&modifs, argv, (unsigned int)argc))
        log_dieu(LOG_EXIT_SYS, "make environment string") ;

    r = el_substitute(&sa, modifs.s, modifs.len, info.vars.s, info.values.s,
        genalloc_s(elsubst_t const, &info.data), genalloc_len(elsubst_t const, &info.data)) ;

    stralloc_free(&modifs) ;

    if (r < 0)
        log_dieusys(LOG_EXIT_SYS, "el_substitute") ;
    else if (!r) {
        stralloc_free(&sa) ;
        _exit(0) ;
    }

    char const *v[r + 1] ;
    if (!env_make (v, r , sa.s, sa.len))
        log_dieusys(LOG_EXIT_SYS, "make environment") ;

    v[r] = 0 ;

    mexec_fm(v, newenv, env_len(newenv), info.modifs.s, info.modifs.len) ;
}