root/usr/src/cmd/ast/ksh/builtins/alias.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright 2021 OmniOS Community Edition (OmniOSce) Association.
 */

/*
 * alias.c is a C version of the alias.sh wrapper (which links ksh
 * builtins to commands in /usr/bin/, e.g. calling this wrapper as
 * /usr/bin/alias will call the ksh "alias" builtin, running it as
 * /usr/bin/cut will call the ksh "cut" builtin etc.
 */

#include <defs.h>
#include <shell.h>
#include <nval.h>
#include <cmdext.h>
#include <stdio.h>
#include <setjmp.h>

#undef calloc
#undef free

typedef struct {
        const char *name;
        int (* func)(int, char **, Shbltin_t *);
} bfastpathrec;

/*
 * We've disabled the "fastpath" codepath for some commands below
 * because it causes a paradoxon for large input files (as used by
 * ON PerfPIT for testing). For /usr/bin/rev (where the issue was
 * first discovered) it looks like this:
 * - for small files like /etc/profile the fastpath is faster in a loop
 *   with 1000 iterations (8 seconds with fastpath, 14 seconds without
 *   fastpath)
 * - for large files (/usr/pub/UTF-8 replicated until the test file
 *   reaches 24884706 bytes) the benchmark reverses: The fastpath now
 *   needs 40 seconds and without fastpath it needs 30 seconds (for 100
 *   iterations).
 */
#if 0
#define ENABLE_PERFORMANCE_PARADOXON 1
#endif

/*
 * List of libcmd builtins which do not require a |Shell_t| context.
 * This list was automatically generated from <ast/cmdext.h>
 */
static const
bfastpathrec fastpath_builtins[] =
{
        /* This list must be alphabetically sorted for |strcmp()| usage */
        { "basename",   b_basename      },
        { "cat",        b_cat           },
        { "chgrp",      b_chgrp         },
        { "chmod",      b_chmod         },
        { "chown",      b_chown         },
#ifdef ENABLE_PERFORMANCE_PARADOXON
        { "cksum",      b_cksum         },
#endif /* ENABLE_PERFORMANCE_PARADOXON */
        { "cmp",        b_cmp           },
        { "comm",       b_comm          },
        { "cp",         b_cp            },
        { "cut",        b_cut           },
        { "date",       b_date          },
        { "dirname",    b_dirname       },
        { "expr",       b_expr          },
        { "fds",        b_fds           },
        { "fmt",        b_fmt           },
        { "fold",       b_fold          },
        { "getconf",    b_getconf       },
        { "head",       b_head          },
        { "id",         b_id            },
        { "join",       b_join          },
        { "ln",         b_ln            },
        { "md5sum",     b_md5sum        },
        { "mkdir",      b_mkdir         },
        { "mkfifo",     b_mkfifo        },
        { "mktemp",     b_mktemp        },
        { "mv",         b_mv            },
        { "paste",      b_paste         },
        { "pathchk",    b_pathchk       },
        { "pids",       b_pids          },
#ifdef ENABLE_PERFORMANCE_PARADOXON
        { "rev",        b_rev           },
#endif /* ENABLE_PERFORMANCE_PARADOXON */
        { "rm",         b_rm            },
        { "rmdir",      b_rmdir         },
        { "stty",       b_stty          },
#ifdef ENABLE_PERFORMANCE_PARADOXON
        { "sum",        b_sum           },
#endif /* ENABLE_PERFORMANCE_PARADOXON */
        { "sync",       b_sync          },
        { "tail",       b_tail          },
        { "tee",        b_tee           },
        { "tty",        b_tty           },
        { "uname",      b_uname         },
        { "uniq",       b_uniq          },
        { "vmstate",    b_vmstate       },
        { "wc",         b_wc            },
        { NULL,         NULL            }
};

static inline const bfastpathrec *
find_bfastpathrec(const char *name)
{
        unsigned int i;
        signed int cmpres;
        for (i = 0; fastpath_builtins[i].name != NULL; i++) {
                cmpres = strcmp(fastpath_builtins[i].name, name);
                if (cmpres == 0)
                        return (&fastpath_builtins[i]);
                else if (cmpres > 0)
                        return (NULL);
        }
        return (NULL);
}

static inline int
fastpath_builtin_main(const bfastpathrec *brec, int argc, char *argv[])
{
        setlocale(LC_ALL, ""); /* calls |_ast_setlocale()| */

        return ((*brec->func)(argc, argv, NULL));
}


/* Builtin script, originally derived from alias.sh */
static const char *script = "\n"
/* Get name of builtin */
"typeset cmd=\"${0##*/}\"\n"
/*
 * If the requested command is not an alias, load it explicitly
 * to make sure it is not bound to a path (those built-ins which
 * are mapped via shell aliases point to commands which are
 * "special shell built-ins" which cannot be bound to a specific
 * PATH element) - otherwise we may execute the wrong command
 * if an executable with the same name sits in a PATH element
 * before /usr/bin (e.g. /usr/xpg4/bin/ls would be executed
 * before /usr/bin/ls if the path was something like
 * PATH=/usr/xpg4/bin:/usr/bin).
 */
"if [[ \"${cmd}\" != ~(Elr)(alias|unalias|command) ]] && "
        "! alias \"${cmd}\" >/dev/null 2>&1 ; then\n"
        "PATH='' builtin \"${cmd}\"\n"
"fi\n"
/* command is a keyword and needs to be handled separately */
"if [[ \"${cmd}\" == \"command\" ]] ; then\n"
        "command \"$@\"\n"
"else\n"
        "\"${cmd}\" \"$@\"\n"
"fi\n"
"exitval=$?";

static int
script_builtin_main(int argc, char **argv)
{
        int i;
        Shell_t *shp;
        Namval_t *np;
        int exitval;
        char **xargv;

        /*
         * Create copy of |argv| array shifted by one position to
         * emulate $ /usr/bin/sh <scriptname> <args1> <arg2> ... #.
         * First position is set to "/usr/bin/sh" since other
         * values may trigger special shell modes (e.g. *rsh* will
         * trigger "restricted" shell mode etc.).
         */
        xargv = calloc(argc + 2, sizeof (char *));
        if (xargv == NULL)
                return (1);
        xargv[0] = "/usr/bin/sh";
        for (i = 0; i < argc; i++)
                xargv[i + 1] = argv[i];
        xargv[i + 1] = NULL;

        shp = sh_init(argc + 1, xargv, 0);
        if (!shp)
                error(ERROR_exit(1), "shell initialisation failed.");
        if (setjmp(*shp->jmplist) == 0)
                (void) sh_trap(script, 0);

        np = nv_open("exitval", shp->var_tree, 0);
        if (!np)
                error(ERROR_exit(1), "variable %s not found.", "exitval");
        exitval = (int)nv_getnum(np);
        nv_close(np);

        free(xargv);

        return (exitval);
}

int
main(int argc, char **argv)
{
        const char *progname;
        const bfastpathrec *brec;
        char execnamebuff[PATH_MAX + 1];

        /* Get program name */
        if (pathprog(argv[0], execnamebuff, sizeof (execnamebuff)) <= 0)
                error(ERROR_exit(1), "could not determinate exec name.");

        progname = (const char *)strrchr(execnamebuff, '/');

        if (progname != NULL)
                progname++;
        else
                progname = execnamebuff;

        /* Execute command... */
        if (brec = find_bfastpathrec(progname)) {
                /* ... either via a fast path (calling the code directly) ... */
                return (fastpath_builtin_main(brec, argc, argv));
        } else {
                /* ... or from within a full shell. */
                return (script_builtin_main(argc, argv));
        }
}