Skip to content
Permalink
Browse files

Switch b_alias() from optget() to getopt_long()

Another step in switching from the DocOpt base option processing done by
the AST `optget()` function to the borg standard `getopt_long()`.

It also introduces a system-wide ksh config script to enable auto-loading
functions included with ksh by default. This allows the use of the
auto-loaded `_ksh_print_help` function. As well as the dirs/popd/pushd
set of functions that have been the sole functions included with ksh
forever but not automatically enabled.

Related #507
Fixes #835
  • Loading branch information...
krader1961 committed Sep 1, 2019
1 parent 24e968c commit fa9673c64d5e0dd043d4a2f5c19c43e8189dfd44
@@ -6,6 +6,9 @@

## Notable fixes and improvements

- A ${.sh.install_prefix}/config.ksh file will be sourced if it exists. It is
the first config file loaded by every ksh instance. It is loaded regardless
of whether the shell is interactive, login, or neither.
- A .sh.install_prefix var has been introduced to reflect the `meson -prefix=`
value. This will be used to establish the location of supporting files such
as man pages and autoloaded functions.
@@ -19,6 +19,8 @@
***********************************************************************/
#include "config_ast.h" // IWYU pragma: keep

#include <getopt.h>
#include <stdlib.h>
#include <string.h>

#include "argnod.h"
@@ -27,31 +29,38 @@
#include "defs.h"
#include "error.h"
#include "name.h"
#include "option.h"
#include "shcmd.h"
#include "variables.h"

static const char *short_options = ":ptx";
static const struct option long_options[] = {{"help", 0, NULL, 1}, // all builtins supports --help
{NULL, 0, NULL, 0}};

int b_alias(int argc, char *argv[], Shbltin_t *context) {
UNUSED(argc);
nvflag_t nvflags = NV_NOARRAY | NV_NOSCOPE | NV_ASSIGN;
int opt;
Dt_t *troot;
int n;
struct tdata tdata;
Shell_t *shp = context->shp;
char *cmd = argv[0];
nvflag_t nvflags = NV_NOARRAY | NV_NOSCOPE | NV_ASSIGN;

memset(&tdata, 0, sizeof(tdata));
tdata.sh = context->shp;
tdata.sh = shp;
troot = tdata.sh->alias_tree;
if (*argv[0] == 'h') nvflags = NV_TAGGED;
if (sh_isoption(tdata.sh, SH_BASH)) tdata.prefix = argv[0];
if (sh_isoption(tdata.sh, SH_BASH)) tdata.prefix = cmd;
if (!argv[1]) return setall(argv, nvflags, troot, &tdata);

opt_info.offset = 0;
opt_info.index = 1;
*opt_info.option = 0;
tdata.argnum = 0;
tdata.aflag = *argv[1];
while ((n = optget(argv, sh_optalias))) {
switch (n) { //!OCLINT(MissingDefaultStatement)

optind = 0;
while ((opt = getopt_long(argc, argv, short_options, long_options, NULL)) != -1) {
switch (opt) {
case 1: {
builtin_print_help(shp, cmd);
return 0;
}
case 'p': {
tdata.prefix = argv[0];
break;
@@ -65,21 +74,18 @@ int b_alias(int argc, char *argv[], Shbltin_t *context) {
break;
}
case ':': {
errormsg(SH_DICT, 2, "%s", opt_info.arg);
break;
builtin_missing_argument(shp, cmd, argv[optind - 1]);
return 2;
}
case '?': {
errormsg(SH_DICT, ERROR_usage(0), "%s", opt_info.arg);
builtin_unknown_option(shp, cmd, argv[optind - 1]);
return 2;
}
default: { abort(); }
}
}
if (error_info.errors) {
errormsg(SH_DICT, ERROR_usage(2), "%s", optusage(NULL));
__builtin_unreachable();
}

argv += (opt_info.index - 1);
argv += (optind - 1);
if (!nv_isflag(nvflags, NV_TAGGED)) return setall(argv, nvflags, troot, &tdata);

// Hacks to handle hash -r | --.
@@ -99,6 +105,7 @@ int b_alias(int argc, char *argv[], Shbltin_t *context) {
}
}
}

troot = tdata.sh->track_tree;
return setall(argv, nvflags, troot, &tdata);
}
@@ -29,6 +29,8 @@

#include "builtins.h"
#include "jobs.h"
#include "sfio.h"

#define bltin(x) (b_##x)

// In the last beta release that came out from AT&T, all the builtins for standard commands
@@ -228,3 +230,28 @@ const char e_nofork[] = "cannot fork";
const char e_nosignal[] = "%s: unknown signal name";
const char e_condition[] = "condition(s) required";
const char e_cneedsarg[] = "-c requires argument";

// Error message for missing flag argument.
#define BUILTIN_ERR_MISSING "\n%s: Expected argument for flag '%s'\n"
// Error message for unrecognized flag.
#define BUILTIN_ERR_UNKNOWN "\n%s: Unknown flag '%s'\n"

// Invoke a helper function or command to print a subset of the man page for the command. Such as
// from doing "cmd --help".
void builtin_print_help(Shell_t *shp, const char *cmd) {
const char *argv[3] = {"_ksh_print_help", cmd, NULL};
sh_eval(shp, sh_sfeval(argv), 0);
return;
}

// Error reporting for encounter with unknown option when parsing command arguments.
void builtin_unknown_option(Shell_t *shp, const char *cmd, const char *opt) {
builtin_print_help(shp, cmd);
sfprintf(sfstderr, BUILTIN_ERR_UNKNOWN, cmd, opt);
}

// Error reporting for encounter with missing argument when parsing command arguments.
void builtin_missing_argument(Shell_t *shp, const char *cmd, const char *opt) {
builtin_print_help(shp, cmd);
sfprintf(sfstderr, BUILTIN_ERR_MISSING, cmd, opt);
}
@@ -0,0 +1,24 @@
#
# This script is sourced first when the shell starts. It's purpose is to perform basic setup of the
# shell state. For example, where to find autoloaded functions that ship with the shell and arrange
# for them to be loaded on demand. It should not do anything not suitable for every single new ksh
# process. In particular it should be as fast as possible.
#
# Arrange for standard autoloaded functions to be available. The test for whether or not FPATH is
# already set isn't technically necessary since empty path components are guaranteed not to be
# equivalent to `.` (the CWD). But I prefer to be paranoid since doing so is cheap.
#
__fpath="${.sh.install_prefix}/share/ksh/functions"
if [[ -z ${FPATH:-''} ]]
then
FPATH="$__fpath"
else
FPATH="$__fpath:$FPATH"
fi

# Arrange for these function names to be autoloaded by declaring them to be undefined.
for f in "$__fpath"/*
do
typeset -fu $(basename $f)
done
unset __fpath

This file was deleted.

@@ -0,0 +1,43 @@
#
# Given a ksh man page emit the text of the SYNOPSIS and FLAGS sections and nothing else. This is
# primarily meant to be used by builtins (either special or normal commands) to emit a subset of its
# man page when invoked with `cmd --help` or the argument parsing for the command detects an error.
# Such as via the `builtin_print_help()` function in the ksh source.
#
function _ksh_print_help {
local cmd=$1
local fname="${.sh.install_prefix}/share/ksh/man/man1/${cmd}.1"

[[ -f $fname ]] || return 1 # can't find the man page; give up

# Find the `ul` command to translate x\cHx sequences, etc., into bold chars and such. Pagers
# like `less` do this but better to not rely on them. If `ul` is not available just pipe the
# nroff output unmodified.
local ul=$(whence ul)
[[ -z $ul ]] && ul=cat

# Try to determine an optimal line length for the output.
local cols=80
if [[ -t 0 && -t 1 ]]
then
stty size | read _ cols
fi
cols=$(( cols - 4 )) # leave a little space on the right side of the screen

# Convert the nroff source for the command into something that looks good on the terminal.
# Specifically, by elimating all the text outside of the SYNOPSIS and FLAGS sections while
# attempting to preserve some of the highlighting understood by the terminal.
nroff -c -man -rLL=${cols}n "$fname" |
sed -n \
-e '/^S.*Y.*N.*O.*P.*S.*I.*S$/,/^[^ ]/ {
/^S/p
/^[^ ]/b
p
}' \
-e '/^F.*L.*A.*G.*S$/,/^[^ ]/ {
/^F/p
/^[^ ]/b
p
}' |
uniq | "$ul"
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -68,6 +68,9 @@ struct tdata {

extern int setall(char **, nvflag_t, Dt_t *, struct tdata *);
extern void print_scan(Sfio_t *file, nvflag_t flag, Dt_t *root, bool omit_attrs, struct tdata *tp);
extern void builtin_print_help(Shell_t *shp, const char *cmd);
extern void builtin_unknown_option(Shell_t *shp, const char *cmd, const char *opt);
extern void builtin_missing_argument(Shell_t *shp, const char *cmd, const char *opt);

// Entry points for shell special builtins.
extern int b_alias(int, char *[], Shbltin_t *);
@@ -160,7 +163,6 @@ extern const char e_defined[];

// For option parsing.
extern const char sh_set[];
extern const char sh_optalias[];
extern const char sh_optbreak[];
extern const char sh_optbuiltin[];
extern const char sh_optcd[];
@@ -39,6 +39,9 @@ shcomp_exe = executable('shcomp', ['sh/shcomp.c'], c_args: shared_c_args,
dependencies: [libm_dep, libexecinfo_dep, libdl_dep, libsocket_dep, libnsl_dep],
install: true)

install_subdir('functions', install_dir: 'share/ksh')
install_data(['data/config.ksh'], install_dir: 'share/ksh')

test_dir = join_paths(meson.current_source_dir(), 'tests')
test_driver = join_paths(test_dir, 'util', 'run_test.sh')

@@ -162,6 +162,7 @@ int sh_main(int ac, char *av[], Shinit_f userinit) {
sh_onoption(shp, SH_MONITOR);
}
job_init(shp, sh_isoption(shp, SH_LOGIN_SHELL));
sh_source(shp, iop, INSTALL_PREFIX "/share/ksh/config.ksh");
if (sh_isoption(shp, SH_LOGIN_SHELL)) {
// System profile.
sh_source(shp, iop, e_sysprofile);
@@ -29,28 +29,13 @@ for name in $(builtin -l |

actual=$($name --this-option-does-not-exist 2>&1)
expect="Usage: $name"
[[ "$actual" =~ "$expect" ]] ||
[[ "$actual" =~ "$expect" ||
"$actual" =~ "SYNOPSIS".*"$name: Unknown flag '--this-option-does-not-exist'" ]] ||
log_error "$name should show usage info on unrecognized options" "$expect" "$actual"
done

# ==========
# Verify --man works for the builtins.
for name in $(builtin -l |
grep -Ev '(local|declare|echo|test|true|false|login|newgrp|type|source|\[|:)')
do
# Extract builtin name from /opt path
if [[ "$name" =~ "/opt" ]];
then
name="${name##*/}"
fi

actual=$($name --man 2>&1 | head -5 | sed $'s,\x1B\[[0-9;]*[a-zA-Z],,g' | tr -s '\n ' ' ')
expect="NAME $name - "
[[ "$actual" =~ "^$expect" ]] ||
log_error "$name --man should show documentation" "$expect" "$actual"
done

# test shell builtin commands
# Test shell builtin commands.
: ${foo=bar} || log_error ": failed"
[[ $foo == bar ]] || log_error ": side effects failed"

@@ -178,7 +178,6 @@ echo "echo '$t'" > $HOME/.profile
cp $SHELL ./-ksh
if [[ -o privileged ]]
then
log_warning WTF privileged
actual=$($SHELL -l </dev/null 2>&1)
[[ "$actual" == *$t* ]] &&
log_error 'privileged -l reads .profile' "*$t*" "$actual"
@@ -201,8 +200,6 @@ then
[[ "$actual" == *$t* ]] &&
log_error 'privileged ./-ksh -p reads .profile' "*$t*" "$actual"
else
log_warning WTF HOME $HOME
log_warning WTF PWD $PWD
actual=$($SHELL -l </dev/null 2>&1)
[[ "$actual" == *$t* ]] ||
log_error '-l ignores .profile' "*$t*" "$actual"
@@ -572,7 +569,8 @@ done
# process source files from profiles as profile files
print '. ./dotfile' > envfile
print $'alias print=:\nprint foobar' > dotfile
[[ $(ENV=$PWD/envfile $SHELL -i -c : 2>/dev/null) == foobar ]] && log_error 'files source from profile does not process aliases correctly'
[[ $(ENV=$PWD/envfile $SHELL -i -c : 2>/dev/null) == foobar ]] &&
log_error 'files source from profile does not process aliases correctly'

# tests the set -m puts background jobs in separate process group
Command=$Command LINENO=$LINENO $SHELL -m <<- \EOF
@@ -593,6 +591,7 @@ Command=$Command LINENO=$LINENO $SHELL -m <<- \EOF
EOF
((error_count+=$?))

# ==========
$SHELL 2> /dev/null <<- \EOF && log_error 'unset variable with set -u on does not terminate script'
set -e -u -o pipefail
ls | while read file
@@ -602,4 +601,7 @@ $SHELL 2> /dev/null <<- \EOF && log_error 'unset variable with set -u on does no
exit
EOF

[[ $($SHELL -vc : 2>&1) == : ]] || log_error 'incorrect output with ksh -v'
# ==========
actual="$($SHELL -vc 'print yes' 2>&1)"
expect="print yes"
[[ "$actual" =~ .*"$expect".* ]] || log_error 'incorrect output from ksh -v' "$expect" "$actual"
@@ -383,11 +383,18 @@ print 'FPATH=../fun' > bin/.paths
cat <<- \EOF > fun/myfun
function myfun
{
print myfun
print myfun ran
}
EOF
actual=$(FPATH= PATH=$PWD/bin:$PATH $SHELL -c ': $(whence less);myfun') 2> /dev/null
expect=myfun
# TODO: Use the first statement rather than the second once https://github.com/att/ast/issues/1400
# is resolved. The problem is that if an external command is run in the context of executing the
# `typeset -fu` command in ${.sh.install_prefix}/config.ksh script it causes the .paths mechanism to
# fail. The second variant works around that bug by forcing the shell to re-evaluate paths that have
# .paths files (or something of that nature).
#
# actual=$(FPATH= PATH=$PWD/bin:$PATH $SHELL -c 'myfun')
actual=$(FPATH= $SHELL -c 'PATH=$PWD/bin:$PATH; myfun')
expect='myfun ran'
[[ $actual == $expect ]] || log_error 'function myfun not found' "$expect" "$actual"

cp $bin_echo user_to_group_relationship.hdr.query
@@ -71,6 +71,11 @@ then
export USER=$(id -un)
fi

# The use of the "dumb" terminal type is to minimize, and hopefully eliminate completely,
# terminal control/escape sequences that affect the terminal's behavior. This makes writing
# robust unit tests, especially Expect based tests, easier.
export TERM=dumb

# TODO: Enable the `io` test on Travis macOS once we understand why it dies from an abort().
# I'm not seeing that failure happen on either of my macOS 10.12 or 10.13 systems.
if [[ $test_name == io && $OS_NAME == darwin && $CI == true ]]
@@ -236,10 +241,6 @@ function run_interactive {
cp $TEST_ROOT/data/sh_history $HISTFILE
fi

# The use of the "dumb" terminal type is to minimize, and hopefully eliminate completely,
# terminal control/escape sequences that affect the terminal's behavior. This makes writing
# robust Expect scripts easier.
export TERM=dumb
expect -n -c "source $TEST_ROOT/util/interactive.expect.rc" -f $test_path \
>$test_name.out 2>$test_name.err
exit_status=$?

2 comments on commit fa9673c

@siteshwar

This comment has been minimized.

Copy link
Collaborator

replied Sep 11, 2019

Reference to issue 835 is incorrect in this commit.

@krader1961

This comment has been minimized.

Copy link
Collaborator Author

replied Sep 11, 2019

Reference to issue 835 is incorrect in this commit.

Actually, it isn't. This change installs and enables dirs, pushd and popd as part of making man an autoloaded function.

Please sign in to comment.
You can’t perform that action at this time.