3732 lines
93 KiB
C
3732 lines
93 KiB
C
/* builtins.c -- the built in shell commands. */
|
|
|
|
/* Copyright (C) 1987,1989 Free Software Foundation, Inc.
|
|
|
|
This file is part of GNU Bash, the Bourne Again SHell.
|
|
|
|
Bash is free software; you can redistribute it and/or modify it under
|
|
the terms of the GNU General Public License as published by the Free
|
|
Software Foundation; either version 1, or (at your option) any later
|
|
version.
|
|
|
|
Bash is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along
|
|
with Bash; see the file COPYING. If not, write to the Free Software
|
|
Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
|
|
|
|
#include <stdio.h>
|
|
#include <sys/param.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/file.h>
|
|
#include <errno.h>
|
|
#include "shell.h"
|
|
|
|
#ifndef SYSV
|
|
#include <sys/time.h>
|
|
#include <sys/resource.h>
|
|
#endif
|
|
|
|
#include "builtins.h"
|
|
#include "trap.h"
|
|
#include "flags.h"
|
|
#include <readline/history.h>
|
|
|
|
#ifdef JOB_CONTROL
|
|
#include "jobs.h"
|
|
#endif
|
|
|
|
extern int errno; /* Not always in <errno.h>. Bogusness. */
|
|
|
|
#ifndef sigmask
|
|
#define sigmask(x) (1 << ((x)-1))
|
|
#endif
|
|
|
|
#ifdef SYSV
|
|
#include <fcntl.h>
|
|
#include <sys/times.h>
|
|
#endif
|
|
|
|
#if defined (HAVE_VPRINTF)
|
|
#include <varargs.h>
|
|
#endif
|
|
|
|
/* Yecch! Who cares about this gross concept in the first place? */
|
|
#ifndef MAXPATHLEN
|
|
#define MAXPATHLEN 1024
|
|
#endif
|
|
|
|
/* The command name of the currently running function. */
|
|
extern char *this_command_name;
|
|
|
|
/* Non-zero means running an interactive shell. */
|
|
extern int interactive;
|
|
|
|
/* The list of shell builtins. Each element is name, function, enabled-p,
|
|
short-doc, long-doc. The long-doc field should contain a set of indented
|
|
lines. The function takes a WORD_LIST *, where the first word in the list
|
|
is the first arg to the command. The list has already been word expanded.
|
|
|
|
Procedures which need to look at every simple command (like enable_builtin),
|
|
should tree-walk looking for (array[i].function == (Function *)NULL). The
|
|
list of executable builtins (in the shell sense) ends there. Then comes
|
|
the control structure commands, like `if' and `while'. */
|
|
|
|
struct builtin shell_builtins[] = {
|
|
|
|
{ ":", colon_builtin, 1, ":",
|
|
" No effect; the command does nothing. A zero exit code is returned" },
|
|
|
|
{ ".", period_builtin, 1, ". [filename]",
|
|
" Read and execute commands from FILENAME and return.\n\
|
|
Pathnames in $PATH are used to find the directory containing FILENAME" },
|
|
|
|
{ "alias", alias_builtin, 1, "alias [ name[=value] ... ]",
|
|
" Alias with no arguments prints the list of aliases in the form\n\
|
|
name=value on standard output. An alias is defined for each NAME\n\
|
|
whose VALUE is given. A trailing space in VALUE causes the next\n\
|
|
word to be checked for alias substitution. Alias returns true\n\
|
|
unless a NAME is given for which no alias has been defined" },
|
|
|
|
#ifdef JOB_CONTROL
|
|
{ "bg", bg_builtin, 1, "bg [job_spec]",
|
|
" Place JOB_SPEC in the background, as if it had been started with\n\
|
|
`&'. If JOB_SPEC is not present, the shell's notion of the current\n\
|
|
job is used" },
|
|
#endif
|
|
|
|
{ "break", break_builtin, 1, "break [n]",
|
|
" Exit from within a FOR, WHILE or UNTIL loop. If N is specified,\n\
|
|
break N levels" },
|
|
|
|
{ "builtin", builtin_builtin, 1, "builtin [shell-builtin [arg ...]]",
|
|
" Run a shell builtin. This is useful when you wish to rename a\n\
|
|
shell builtin to be a function, but need the functionality of the\n\
|
|
builtin within the function itself" },
|
|
|
|
{ "bye", exit_builtin, 1, "bye [n]",
|
|
" Synonym for exit" },
|
|
|
|
{ "cd", cd_builtin, 1, "cd [dir]",
|
|
" Change the current directory to DIR. The variable $HOME is the\n\
|
|
default DIR. The variable $CDPATH defines the search path for\n\
|
|
the directory containing DIR. Alternative directory names are\n\
|
|
separated by a colon (:). A null directory name is the same as\n\
|
|
the current directory, i.e. `.'. If DIR begins with a slash (/),\n\
|
|
then $CDPATH is not used" },
|
|
|
|
{ "command", command_builtin, 1, "command [command [arg ...]]",
|
|
" Runs COMMAND with ARGS ignoring shell functions. If you have a\n\
|
|
shell function called `ls', and you wish to call the command\n\
|
|
`ls', you can say \"command ls\"" },
|
|
|
|
{ "continue", continue_builtin, 1, "continue [n]",
|
|
" Resume the next iteration of the enclosing FOR, WHILE or UNTIL loop.\n\
|
|
If N is specified, resume at the N-th enclosing loop" },
|
|
|
|
{ "declare", declare_builtin, 1, "declare [-[frx]] name[=value]",
|
|
" Declare variables and/or give them attributes. If no NAMEs are\n\
|
|
are given, then display the values of variables instead. `-f'\n\
|
|
says to use function names only. `-r' says to make NAMEs readonly.\n\
|
|
`-x' says to make NAMEs export. Using `+' instead of `-' turns off\n\
|
|
the attribute instead. When used in a function, makes NAMEs local,\n\
|
|
as with the `local' command" },
|
|
|
|
#ifdef PUSHD_AND_POPD
|
|
{ "dirs", dirs_builtin, 1, "dirs",
|
|
" Display the list of currently remembered directories. Directories\n\
|
|
find their way onto the list with the `pushd' command; you can get\n\
|
|
back up through the list with the `popd' command" },
|
|
#endif
|
|
|
|
#ifndef V9_ECHO
|
|
{ "echo", echo_builtin, 1, "echo [-n] [arg]",
|
|
" Output the ARGs. If -n is specified, then suppress trailing\n\
|
|
newline" },
|
|
#else
|
|
{ "echo", echo_builtin, 1, "echo [-n] [-e] [arg ...]",
|
|
" Output the ARGs. if -n is specified, the trailing newline is\n\
|
|
suppressed. If the -e option is given, interpretation of the\n\
|
|
following backslash-escaped characters is turned on:\n\
|
|
\\b backspace\n\
|
|
\\c suppress trailing newline\n\
|
|
\\f form feed\n\
|
|
\\n new line\n\
|
|
\\r carriage return\n\
|
|
\\t horizontal tab\n\
|
|
\\v vertical tab\n\
|
|
\\num the character whose ASCII code is NUM (octal)"},
|
|
#endif
|
|
|
|
{ "enable", enable_builtin, 1, "enable [-n] [name ...]",
|
|
" Enable and disable builtin shell commands. This allows\n\
|
|
you to use a disk command which has the same name as a shell\n\
|
|
builtin. If -n is used, the NAMEs become disabled. Otherwise\n\
|
|
NAMEs are enabled. For example, to use the `test' found on your\n\
|
|
path instead of the shell builtin version, you type `enable -n test'" },
|
|
|
|
{ "eval", eval_builtin, 1, "eval [arg ...]",
|
|
" Read ARGs as input to the shell and execute the resulting command(s)" },
|
|
|
|
{ "exec", exec_builtin, 1, "exec [ [-] file [redirections]]",
|
|
" Exec FILE, replacing this shell with the specified program.\n\
|
|
If FILE is not specified, the redirections take effect in this\n\
|
|
shell. If the first argument is `-', then place a dash in the\n\
|
|
zeroith arg passed to FILE. This is what login does. If the file\n\
|
|
cannot be exec'ed for some reason, the shell exits, unless the\n\
|
|
shell variable \"no_exit_on_failed_exec\" exists" },
|
|
|
|
{ "exit", exit_builtin, 1, "exit [n]",
|
|
" Exit the shell with a status of N. If N is omitted, the exit status\n\
|
|
is that of the last command executed" },
|
|
|
|
{ "export", export_builtin, 1, "export [-n] [name] ...",
|
|
" NAMEs are marked for automatic export to the environment of\n\
|
|
subsequently executed commands. If no NAMEs are given, a list\n\
|
|
of all names that are exported in this shell is printed. Note\n\
|
|
that function names cannot be exported. An argument of `-n' says\n\
|
|
to remove the export property from subsequent NAMEs" },
|
|
|
|
#ifdef JOB_CONTROL
|
|
{ "fg", fg_builtin, 1, "fg [job_spec]",
|
|
" Place JOB_SPEC in the foreground, and make it the current job. If\n\
|
|
JOB_SPEC is not present, the shell's notion of the current job is\n\
|
|
used" },
|
|
#endif
|
|
|
|
{ "hash", hash_builtin, 1, "hash [-r] [name]",
|
|
" For each NAME, the full pathname of the command is determined\n\
|
|
and remembered. The -r option causes the shell to forget all\n\
|
|
remembered locations. If no arguments are given, information\n\
|
|
about remembered commands is presented" },
|
|
|
|
{ "help", help_builtin, 1, "help [pattern]",
|
|
" Display helpful information about builtin commands. If\n\
|
|
PATTERN is specified, gives detailed help on all commands\n\
|
|
matching PATTERN, otherwise a list of the builtins is\n\
|
|
printed" },
|
|
|
|
{ "history", history_builtin, 1, "history [n] [-s] [ [-w | -r] [filename]]",
|
|
" Display the history list with line numbers. Lines listed with\n\
|
|
with a `*' have been modified. Argument of N says to list only\n\
|
|
the last N lines. Argument `-w' means write out the current\n\
|
|
history file. `-r' means to read it instead. If FILENAME is\n\
|
|
given, then use that file, else if $HISTFILE has a value, use\n\
|
|
that, else use ~/.bash_history. Argument -s oerforms history\n\
|
|
substitution on the following args" },
|
|
|
|
#ifdef JOB_CONTROL
|
|
{ "jobs", jobs_builtin, 1, "jobs [-l]",
|
|
" Lists the active jobs; given the -l options lists process id's\n\
|
|
in addition to the normal information" },
|
|
|
|
{ "kill", kill_builtin, 1, "kill [-sigspec -l] [pid | job] ...",
|
|
" Send the processes named by PID (or JOB) the signal SIGSPEC.\n\
|
|
If SIGSPEC is not present, then SIGTERM is assumed. An argument\n\
|
|
of `-l' lists the signal names. Kill is a builtin for two reasons;\n\
|
|
it allows job id's to be used instead of pids, and if you run out of\n\
|
|
processes, you can still kill them" },
|
|
#endif
|
|
|
|
{ "local", local_builtin, 1, "local name[=value]",
|
|
" Create a local variable called NAME, and give it VALUE. LOCAL\n\
|
|
can only be used within a function; it makes the variable NAME\n\
|
|
have a visible scope restricted to that function and its children" },
|
|
|
|
{ "logout", logout_builtin, 1, "logout",
|
|
" Logout of a login shell" },
|
|
|
|
#ifdef PUSHD_AND_POPD
|
|
{ "popd", popd_builtin, 1, "popd [+n | -n]",
|
|
" Removes entries from the directory stack. With no arguments,\n\
|
|
removes the top directory from the stack, and cd's to the new\n\
|
|
top directory.\n\
|
|
\n\
|
|
+n removes the Nth entry counting from the left of the list\n\
|
|
shown by `dirs', starting with zero. For example: `popd +0'\n\
|
|
removes the first directory, `popd +1' the second.\n\
|
|
\n\
|
|
-n removes the Nth entry counting from the right of the list\n\
|
|
shown by `dirs', starting with zero. For example: `popd -0'\n\
|
|
removes the last directory, `popd -1' the next to last.\n\
|
|
\n\
|
|
You can see the directory stack with the `dirs' command.\n\
|
|
If the variable 'pushd_silent' is not set and the popd command\n\
|
|
was successful, a 'dirs' will be performed as well." },
|
|
|
|
{ "pushd", pushd_builtin, 1, "pushd [dir | +n | -n]",
|
|
" Adds a directory to the top of the directory stack, or rotates\n\
|
|
the stack, making the new top of the stack the current working\n\
|
|
directory. With no arguments, exchanges the top two directories.\n\
|
|
\n\
|
|
+n Rotates the stack so that the Nth directory (counting\n\
|
|
from the left of the list shown by `dirs') is at the top.\n\
|
|
\n\
|
|
-n Rotates the stack so that the Nth directory (counting\n\
|
|
from the right) is at the top.\n\
|
|
\n\
|
|
dir adds DIR to the directory stack at the top, making it the\n\
|
|
new current working directory.\n\
|
|
\n\
|
|
You can see the directory stack with the `dirs' command.\n\
|
|
If the variable 'pushd_silent' is not set and the pushd command\n\
|
|
was successful, a 'dirs' will be performed as well." },
|
|
#endif /* PUSHD_AND_POPD */
|
|
|
|
{ "pwd", pwd_builtin, 1, "pwd",
|
|
" Print the current working directory"},
|
|
|
|
{ "read", read_builtin, 1, "read [name ...]",
|
|
" One line is read from the standard input, and the first word\n\
|
|
is assigned to the first NAME, the second word to the second NAME,\n\
|
|
etc. with leftover words assigned to the last NAME. Only the\n\
|
|
characters in $IFS are recognized as word delimiters. The return\n\
|
|
code is zero, unless end-of-file is encountered" },
|
|
|
|
{ "readonly", readonly_builtin, 1, "readonly [name ...]",
|
|
" The given NAMEs are marked readonly and the values of these NAMEs\n\
|
|
may not be changed by subsequent assignment. If no arguments are\n\
|
|
given, a list of all readonly names is printed" },
|
|
|
|
{ "return", return_builtin, 1, "return [n]",
|
|
" Causes a function to exit with the return value specified by N.\n\
|
|
If N is omitted, the return status is that of the last command" },
|
|
|
|
{ "set", set_builtin, 1, "set [-aefhkntuvx] [arg ...]]",
|
|
" -a Mark variables which are modified or created for export\n\
|
|
-e Exit immediately if a command exits with a non-zero status\n\
|
|
-f Disable file name generation (globbing)\n\
|
|
-h Locate and remember function commands as functions are\n\
|
|
defined. Functions commands are normally looked up when\n\
|
|
the function is executed)\n\
|
|
-k All keyword arguments are placed in the environment for a\n\
|
|
comand, not just those that precede the command name\n\
|
|
-n Read commands but do not execute them\n\
|
|
-t Exit after reading and executing one command\n\
|
|
-u Treat unset variables as an error when substituting\n\
|
|
-v Print shell input lines as they are read\n\
|
|
-x Print commands and their arguments as they are executed\n\
|
|
-l Save and restore the binding of the NAME in a FOR command.\n\
|
|
-d Disable the hashing of commands that are looked up for execution.\n\
|
|
Normally, commands are remembered in a hash table, and once\n\
|
|
found, do not have to be looked up again\n\
|
|
-o Enable ! style history substitution. This flag is on by\n\
|
|
by default.\n\
|
|
\n\
|
|
Using + rather than - causes these flags to be turned off. The\n\
|
|
flags can also be used upon invocation of the shell. The current\n\
|
|
set of flags may be found in $-. The remaining ARGs are positional\n\
|
|
parameters and are assigned, in order, to $1, $2, .. $9. If no\n\
|
|
ARGs are given, all shell variables are printed" },
|
|
|
|
{ "shift", shift_builtin, 1, "shift [n]",
|
|
" The positional parameters from $N+1 ... are renamed to $1 .... If\n\
|
|
N is not given, it is assumed to be 1" },
|
|
|
|
{ "source", period_builtin, 1, "source <file>",
|
|
" An alias for the `.' builtin" },
|
|
|
|
#ifdef JOB_CONTROL
|
|
{ "suspend", suspend_builtin, 1, "suspend [-f]",
|
|
" Suspend the execution of this shell until it receives a SIGCONT\n\
|
|
signal. The `-f' if specified says not to complain about this\n\
|
|
being a login shell if it is; just suspend anyway" },
|
|
#endif
|
|
|
|
{ "[", test_builtin, 1, "[ args ]",
|
|
" Synonym for `test'" },
|
|
|
|
{ "test", test_builtin, 1, "test [expr]",
|
|
" Exits with a status of 0 (trueness) or 1 (falseness) depending on\n\
|
|
the evaluation of EXPR. Expressions may be unary or binary. Unary\n\
|
|
expressions are often used to examine the status of a file. There\n\
|
|
are string operators as well, and numeric comparison operators.\n\
|
|
\n\
|
|
File operators:\n\
|
|
\n\
|
|
-b FILE True if file is block special.\n\
|
|
-c FILE True if file is character special.\n\
|
|
-d FILE True if file is a directory.\n\
|
|
-ef FILE True if file is a hard link.\n\
|
|
-f FILE True if file is a plain file.\n\
|
|
-g FILE True if file is set-group-id.\n\
|
|
-L FILE True if file is a symbolic link.\n\
|
|
-k FILE True if file has its \"sticky\" bit set.\n\
|
|
-p FILE True if file is a named pipe.\n\
|
|
-r FILE True if file is readable by you.\n\
|
|
-s FILE True if file is not empty.\n\
|
|
-S FILE True if file is a socket.\n\
|
|
-t [FD] True if FD is opened on a terminal. If FD\n\
|
|
is omitted, it defaults to 1 (stdout).\n\
|
|
-u FILE True if the file is set-user-id.\n\
|
|
-w FILE True if the file is writable by you.\n\
|
|
-x FILE True if the file is executable by you.\n\
|
|
-O FILE True if the file is effectively owned by you.\n\
|
|
-G FILE True if the file is effectively owned by your group.\n\
|
|
\n\
|
|
FILE1 -nt FILE2 True if file1 is newer than (according to\n\
|
|
modification date) file2.\n\
|
|
\n\
|
|
FILE1 -ot FILE2 True if file1 is older than file2.\n\
|
|
\n\
|
|
String operators:\n\
|
|
\n\
|
|
-z STRING True if string is empty.\n\
|
|
-n STRING\n\
|
|
or True if string is not empty.\n\
|
|
STRING\n\
|
|
STRING1 = STRING2\n\
|
|
True if the strings are equal.\n\
|
|
STRING1 != STRING2\n\
|
|
True if the strings are not equal.\n\
|
|
\n\
|
|
Other operators:\n\
|
|
\n\
|
|
! EXPR True if expr is false.\n\
|
|
EXPR1 -a EXPR2 True if both expr1 AND expr2 are true.\n\
|
|
EXPR1 -o EXPR2 True if either expr1 OR expr2 is true.\n\
|
|
\n\
|
|
arg1 OP arg2\n\
|
|
OP is one of -eq, -ne, -lt, -le, -gt, ge.\n\
|
|
Arithmetic binary operators return true if ARG1\n\
|
|
is equal, not-equal, less-than, less-than-or-equal,\n\
|
|
greater-than, or greater-than-or-equal than arg2" },
|
|
|
|
{ "times", times_builtin, 1, "times",
|
|
" Print the accumulated user and system times for processes run from\n\
|
|
the shell" },
|
|
|
|
{ "trap", trap_builtin, 1, "trap [arg] [signal_spec]",
|
|
" The command ARG is to be read and executed when the shell receives\n\
|
|
signal(s) SIGNAL_SPEC. If ARG is absent all specified signals are\n\
|
|
are reset to their original values. If ARG is the null string this\n\
|
|
signal is ignored by the shell and by the commands it invokes. If\n\
|
|
SIGNAL_SPEC is ON_EXIT (0) the command ARG is executed on exit from\n\
|
|
the shell. The trap command with no arguments prints the list of\n\
|
|
commands associated with each signal number. SIGNAL_SPEC is either\n\
|
|
a signal name in <signal.h>, or a signal number. The syntax `trap -l'\n\
|
|
prints a list of signal names and their corresponding numbers.\n\
|
|
Note that a signal can be sent to the shell with \"kill -signal $$\"" },
|
|
|
|
{ "type", type_builtin, 1, "type [-all] [-type | -path] [name ...]",
|
|
" For each NAME, indicate how it would be interpreted if used as a\n\
|
|
command name.\n\
|
|
\n\
|
|
If the -type flag is used, returns a single word which is one of\n\
|
|
`alias', `function', `builtin', `file' or `', if NAME is an\n\
|
|
alias, shell function, shell builtin, disk file, or unfound,\n\
|
|
respectively.\n\
|
|
\n\
|
|
If the -path flag is used, either returns the name of the disk file\n\
|
|
that would be exec'ed, or nothing if -type wouldn't return `file'.\n\
|
|
\n\
|
|
If the -all flag is used, returns all of the places that contain\n\
|
|
an executable named `file'. This includes aliases and functions,\n\
|
|
if and only if the -path flag is not also used" },
|
|
|
|
{ "typeset", declare_builtin, 1, "typeset [-frx] [name[=word]]",
|
|
" Obsolete. See `declare'" },
|
|
|
|
{ "ulimit", ulimit_builtin, 1, "ulimit [-cdmstf [limit]]",
|
|
" Ulimit provides control over the resources available to processes\n\
|
|
started by the shell, on systems that allow such control. If an\n\
|
|
option is given, it is interpreted as follows:\n\
|
|
\n\
|
|
-c the maximum size of core files created\n\
|
|
-d the maximum size of a process's data segment\n\
|
|
-m the maximum resident set size\n\
|
|
-s the maximum stack size\n\
|
|
-t the maximum amount of cpu time in seconds\n\
|
|
-f the maximum size of files created by the shell\n\
|
|
\n\
|
|
If LIMIT is given, it is the new value of the specified resource.\n\
|
|
Otherwise, the current value of the specified resource is printed.\n\
|
|
If no option is given, then -f is assumed. Values are in 1k\n\
|
|
increments, except for -t, which is in seconds" },
|
|
|
|
{ "umask", umask_builtin, 1, "umask [nnn]",
|
|
" The user file-creation mask is set to NNN. If NNN is omitted, the\n\
|
|
current value of the mask is printed. NNN is read as an octal\n\
|
|
number" },
|
|
|
|
{ "unalias", unalias_builtin, 1, "unalias [name ...]",
|
|
" Remove NAMEs from the list of defined aliases" },
|
|
|
|
{ "unset", unset_builtin, 1, "unset [name ...]",
|
|
" For each NAME, remove the corresponding variable or function. Note\n\
|
|
that PATH and IFS cannot be unset" },
|
|
|
|
{ "wait", wait_builtin, 1, "wait [n]",
|
|
" Wait for the specified process and report its termination\n\
|
|
status. If N is not given, all currently active child processes\n\
|
|
are waited for, and the return code is zero" },
|
|
|
|
/* This marks the end of the functions which are builtins per-se. The
|
|
following are actually parser constructs. */
|
|
{ "for", (Function *)0x0, 1, "for NAME [in WORDS ...] ; do COMMANDS ; done",
|
|
" The `for' loop executes a sequence of commands for each member in a\n\
|
|
list of items. If \"in WORDS ...\" is not present, then \"in $*\" is\n\
|
|
assumed. For each element in WORDS, NAME is set to that element, and\n\
|
|
the COMMANDS are executed" },
|
|
|
|
{ "case", (Function *)0x0, 1, "case WORD in [PATTERN [| PATTERN]...) COMMANDS ;;]... esac",
|
|
" Selectively execute COMMANDS based upon WORD matching PATTERN. The\n\
|
|
`|' is used to separate multiple patterns" },
|
|
|
|
{ "if", (Function *)0x0, 1, "if COMMANDS then COMMANDS [else COMMANDS] fi",
|
|
" `if' executes the `then' COMMANDS only if the final command in the `if'\n\
|
|
COMMANDS has an exit status of zero" },
|
|
|
|
{ "while", (Function *)0x0, 1, "while COMMANDS do COMMANDS done",
|
|
" Expand and execute COMMANDS as long as the final command in the `while'\n\
|
|
COMMANDS has an exit status of zero" },
|
|
|
|
{ "until", (Function *)0x0, 1, "until COMMAND do COMMANDS done",
|
|
" Expand and execute COMMANDS as long as the final command in the `until'\n\
|
|
COMMANDS has an exit status which is not zero" },
|
|
|
|
{ "function", (Function *)0x0, 1, "function NAME { COMMANDS ; } or NAME () { COMMANDS ; }",
|
|
" Create a simple command invoked by NAME which runs COMMANDS. Arguments\n\
|
|
on the command line along with NAME are passed to the function as\n\
|
|
$0 .. $n" },
|
|
{ "{ ... }", (Function *)0x0, 1, "{ COMMANDS }",
|
|
" Run a set of commands in a group. This is one way to redirect an\n\
|
|
entire set of commands" },
|
|
|
|
#ifdef JOB_CONTROL
|
|
{ "%", (Function *)0x0, 1, "%[DIGITS | WORD] [&]",
|
|
" This is similar to the `fg' command. Resume a stopped or background\n\
|
|
job. If you specifiy DIGITS, then that job is used. If you specify\n\
|
|
WORD, then the job whose name begins with WORD is used. Following\n\
|
|
the job specification with a `&' places the job in the background" },
|
|
#endif
|
|
|
|
{ (char *)0x0, (Function *)0x0, 0, (char *)0x0, (char *)0x0 }
|
|
|
|
};
|
|
|
|
/* Enable the shell command NAME. If DISABLE_P is non-zero, then
|
|
disable NAME instead. */
|
|
enable_shell_command (name, disable_p)
|
|
char *name;
|
|
int disable_p;
|
|
{
|
|
register int i;
|
|
int found = 0;
|
|
|
|
for (i = 0; shell_builtins[i].function; i++)
|
|
if (strcmp (name, shell_builtins[i].name) == 0)
|
|
{
|
|
found++;
|
|
shell_builtins[i].enabled = !disable_p;
|
|
}
|
|
|
|
return (found);
|
|
}
|
|
|
|
/* Enable/disable shell commands present in LIST. If list is not specified,
|
|
then print out a list of shell commands showing which are enabled and
|
|
which are disabled. */
|
|
enable_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
int result = 0;
|
|
|
|
if (!list)
|
|
{
|
|
register int i;
|
|
|
|
for (i = 0; shell_builtins[i].function; i++)
|
|
printf ("enable %s%s\n", shell_builtins[i].enabled ? "" : "-n ",
|
|
shell_builtins[i].name);
|
|
}
|
|
else
|
|
{
|
|
int disable_p = (strcmp (list->word->word, "-n") == 0);
|
|
|
|
if (disable_p)
|
|
list = list->next;
|
|
|
|
while (list)
|
|
{
|
|
result = enable_shell_command (list->word->word, disable_p);
|
|
list = list->next;
|
|
}
|
|
}
|
|
return (result ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
|
|
}
|
|
|
|
/* Print out a list of the known functions in the shell, and what they do.
|
|
If LIST is supplied, print out the list which matches for each pattern
|
|
specified. */
|
|
help_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
if (!list)
|
|
{
|
|
register int j, i = 0;
|
|
char blurb[256];
|
|
|
|
show_shell_version ();
|
|
printf (
|
|
"Shell commands that are defined internally. Type `help' to see this list.\n\
|
|
Type `help name' to find out more about the function `name'.\n\
|
|
Use `info bash' to find out more about the shell in general.\n\
|
|
\n\
|
|
A star (*) next to a name means that the command is disabled.\n\
|
|
\n");
|
|
|
|
for (i = 0; shell_builtins[i].name; i++)
|
|
{
|
|
sprintf (blurb, "%c%s", shell_builtins[i].enabled ? ' ' : '*',
|
|
shell_builtins[i].short_doc);
|
|
|
|
blurb[35] = '\0';
|
|
printf ("%s", blurb);
|
|
|
|
if (i % 2)
|
|
printf ("\n");
|
|
else
|
|
for (j = strlen (blurb); j < 35; j++)
|
|
putc (' ', stdout);
|
|
|
|
}
|
|
if (i % 2)
|
|
printf ("\n");
|
|
}
|
|
else
|
|
{
|
|
int match_found = 0;
|
|
char *pattern = "";
|
|
|
|
if (glob_pattern_p (list->word->word))
|
|
{
|
|
printf ("Shell commands matching keyword%s `",
|
|
list->next ? "s" : "");
|
|
print_word_list (list, ", ");
|
|
printf ("'\n\n");
|
|
}
|
|
while (list)
|
|
{
|
|
register int i = 0, plen;
|
|
|
|
pattern = list->word->word;
|
|
plen = strlen (pattern);
|
|
|
|
while (shell_builtins[i].name)
|
|
{
|
|
if (glob_match (pattern, shell_builtins[i].name, 0) ||
|
|
strnicmp (pattern, shell_builtins[i].name, plen) == 0)
|
|
{
|
|
printf ("%s: %s\n%s.\n",
|
|
shell_builtins[i].name, shell_builtins[i].short_doc,
|
|
shell_builtins[i].long_doc);
|
|
match_found++;
|
|
}
|
|
i++;
|
|
}
|
|
list = list->next;
|
|
}
|
|
if (!match_found)
|
|
{
|
|
printf ("No help topics match `%s'. Try `help help'.\n", pattern);
|
|
fflush (stdout);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
}
|
|
fflush (stdout);
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
|
|
/* Do nothing. This command is a no-op. */
|
|
colon_builtin ()
|
|
{
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
|
|
/* Read and execute commands from the file passed as argument. Guess what.
|
|
This cannot be done in a subshell, since things like variable assignments
|
|
take place in there. So, I open the file, place it into a large string,
|
|
close the file, and then execute the string. */
|
|
period_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
int result = (EXECUTION_SUCCESS);
|
|
int push_dollar_vars (), pop_dollar_vars ();
|
|
|
|
if (list)
|
|
{
|
|
int fd; /* File descriptor. */
|
|
int tt; /* Temporary result. */
|
|
char *string; /* String to execute. */
|
|
char *filename, *tempfile;
|
|
extern char *find_path_file ();
|
|
struct stat finfo;
|
|
|
|
tempfile = find_path_file (list->word->word);
|
|
|
|
if (!tempfile)
|
|
tempfile = savestring (list->word->word);
|
|
|
|
filename = (char *)alloca (1 + strlen (tempfile));
|
|
strcpy (filename, tempfile);
|
|
free (tempfile);
|
|
|
|
if (stat (filename, &finfo) == -1 ||
|
|
(fd = open (filename, O_RDONLY)) == -1)
|
|
goto file_error_exit;
|
|
|
|
string = (char *)xmalloc (1 + finfo.st_size);
|
|
tt = read (fd, string, finfo.st_size);
|
|
|
|
/* Close the open file, preserving the state of errno. */
|
|
{ int temp = errno; close (fd); errno = temp; }
|
|
|
|
if (tt != finfo.st_size)
|
|
{
|
|
free (string);
|
|
file_error_exit:
|
|
file_error (filename);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
|
|
push_dollar_vars ();
|
|
remember_args (list->next, 1);
|
|
begin_unwind_frame ("file_sourceing");
|
|
add_unwind_protect (pop_dollar_vars, (char *)NULL);
|
|
string[finfo.st_size] = '\0';
|
|
result = parse_and_execute (string, filename);
|
|
run_unwind_frame ("file_sourceing");
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
/* Parse and execute the commands in STRING. Returns whatever
|
|
execute_command () returns. This frees STRING. */
|
|
int
|
|
parse_and_execute (string, from_file)
|
|
char *string;
|
|
char *from_file;
|
|
{
|
|
extern int remember_on_history;
|
|
extern int history_expansion_inhibited;
|
|
extern int indirection_level;
|
|
char *indirection_level_string ();
|
|
|
|
int old_interactive = interactive;
|
|
int old_remember_on_history = remember_on_history;
|
|
int old_history_expansion_inhibited = history_expansion_inhibited;
|
|
int last_result = EXECUTION_SUCCESS;
|
|
char *orig_string = string;
|
|
extern COMMAND *global_command;
|
|
|
|
push_stream ();
|
|
interactive = 0;
|
|
indirection_level++;
|
|
|
|
/* We don't remember text read by the shell this way on
|
|
the history list, and we don't use !$ in shell scripts. */
|
|
remember_on_history = 0;
|
|
history_expansion_inhibited = 1;
|
|
|
|
with_input_from_string (string, from_file);
|
|
{
|
|
extern char *yy_input_dev;
|
|
COMMAND *command;
|
|
|
|
while (*yy_input_dev)
|
|
{
|
|
if (interrupt_state)
|
|
{
|
|
last_result = EXECUTION_FAILURE;
|
|
break;
|
|
}
|
|
|
|
if (yyparse () == 0)
|
|
{
|
|
if ((command = global_command) != (COMMAND *)NULL)
|
|
{
|
|
global_command = (COMMAND *)NULL;
|
|
|
|
last_result = execute_command (command);
|
|
dispose_command (command);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
last_result = EXECUTION_FAILURE;
|
|
|
|
/* Since we are shell compatible, syntax errors in a script
|
|
abort the execution of the script. Right? */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
remember_on_history = old_remember_on_history;
|
|
history_expansion_inhibited = old_history_expansion_inhibited;
|
|
interactive = old_interactive;
|
|
pop_stream ();
|
|
indirection_level--;
|
|
if (orig_string)
|
|
free (orig_string);
|
|
|
|
return (last_result);
|
|
}
|
|
|
|
/* Set up to break x levels, where x defaults to 1, but can be specified
|
|
as the first argument. */
|
|
|
|
/* The depth of while's and until's. */
|
|
int loop_level = 0;
|
|
|
|
/* Non-zero when a "break" instruction is encountered. */
|
|
int breaking = 0;
|
|
|
|
/* Return non-zero if a break or continue command would be okay.
|
|
Print an error message if break or continue is meaningless here. */
|
|
check_loop_level ()
|
|
{
|
|
extern char *this_command_name;
|
|
|
|
#ifdef BREAK_COMPLAINS
|
|
if (!loop_level)
|
|
builtin_error ("Only meaningful in a `for', `while', or `until' loop");
|
|
#endif
|
|
return (loop_level);
|
|
}
|
|
|
|
break_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
|
|
if (!check_loop_level ())
|
|
return (EXECUTION_FAILURE);
|
|
|
|
breaking = get_numeric_arg (list);
|
|
|
|
if (breaking < 0)
|
|
breaking = 0;
|
|
|
|
if (breaking > loop_level)
|
|
breaking = loop_level;
|
|
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
|
|
/* Set up to continue x levels, where x defaults to 1, but can be specified
|
|
as the first argument. */
|
|
|
|
/* Non-zero when we have encountered a continue instruction. */
|
|
int continuing = 0;
|
|
|
|
continue_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
|
|
if (!check_loop_level ())
|
|
return (EXECUTION_FAILURE);
|
|
|
|
continuing = get_numeric_arg (list);
|
|
|
|
if (continuing < 0)
|
|
continuing = 0;
|
|
|
|
if (continuing > loop_level)
|
|
continuing = loop_level;
|
|
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
|
|
/* Read a numeric arg for this_command_name, the name of the shell builtin
|
|
that wants it. LIST is the word list that the arg is to come from. */
|
|
get_numeric_arg (list)
|
|
WORD_LIST *list;
|
|
{
|
|
int count = 1;
|
|
|
|
if (list)
|
|
{
|
|
if (sscanf (list->word->word, "%d", &count) != 1)
|
|
{
|
|
builtin_error ("bad non-numeric arg `%s'", list->word->word);
|
|
longjmp (top_level, DISCARD);
|
|
}
|
|
no_args (list->next);
|
|
}
|
|
return (count);
|
|
}
|
|
|
|
/* Change the current working directory to the first word in LIST, or
|
|
to $HOME if there is no LIST. Do nothing in the case that there is
|
|
no $HOME nor LIST. If the variable CDPATH exists, use that as the search
|
|
path for finding the directory. If all that fails, and the variable
|
|
`cdable_vars' exists, then try the word as a variable name. If that
|
|
variable has a value, then cd to the value of that variable. */
|
|
|
|
/* By default, follow the symbolic links as if they were real directories
|
|
while hacking the `cd' command. This means that `cd ..' moves up in
|
|
the string of symbolic links that make up the current directory, instead
|
|
of the absolute directory. The shell variable `nolinks' controls this
|
|
flag. */
|
|
int follow_symbolic_links = 1;
|
|
|
|
/* In order to keep track of the working directory, we have this static
|
|
variable hanging around. */
|
|
static char *the_current_working_directory = (char *)NULL;
|
|
|
|
cd_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
char *dirname;
|
|
|
|
#ifdef FACIST
|
|
{
|
|
extern int restricted;
|
|
if (restricted)
|
|
{
|
|
builtin_error ("Privileged command");
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (list)
|
|
{
|
|
char *extract_colon_unit ();
|
|
char *path_string = get_string_value ("CDPATH");
|
|
char *path;
|
|
int index = 0;
|
|
|
|
dirname = list->word->word;
|
|
|
|
if (path_string && !absolute_pathname (dirname))
|
|
{
|
|
while ((path = extract_colon_unit (path_string, &index)))
|
|
{
|
|
char *dir;
|
|
|
|
if (!disallow_filename_globbing && *path)
|
|
{
|
|
char *tilde_expand (), *te_string = tilde_expand (path);
|
|
|
|
free (path);
|
|
path = te_string;
|
|
}
|
|
|
|
dir = (char *)alloca (2 + strlen (dirname) + strlen (path));
|
|
|
|
if (!dir)
|
|
abort ();
|
|
|
|
if (!*path)
|
|
{
|
|
free (path);
|
|
path = savestring ("."); /* by definition. */
|
|
}
|
|
|
|
strcpy (dir, path);
|
|
if (path[strlen (path) - 1] != '/')
|
|
strcat (dir, "/");
|
|
strcat (dir, dirname);
|
|
free (path);
|
|
|
|
if (change_to_directory (dir))
|
|
{
|
|
if (strncmp (dir, "./", 2) != 0)
|
|
printf ("%s\n", dir);
|
|
dirname = dir;
|
|
|
|
goto bind_and_exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!change_to_directory (dirname))
|
|
{
|
|
/* Maybe this is `cd -', equivalent to `cd $OLDPWD' */
|
|
if (dirname[0] == '-' && dirname[1] == '\0')
|
|
{
|
|
char *t = get_string_value ("OLDPWD");
|
|
|
|
if (t && change_to_directory (t))
|
|
goto bind_and_exit;
|
|
}
|
|
|
|
/* If the user requests it, then perhaps this is the name of
|
|
a shell variable, whose value contains the directory to
|
|
change to. If that is the case, then change to that
|
|
directory. */
|
|
if (find_variable ("cdable_vars"))
|
|
{
|
|
char *t = get_string_value (dirname);
|
|
|
|
if (t && change_to_directory (t))
|
|
{
|
|
printf ("%s\n", t);
|
|
goto bind_and_exit;
|
|
}
|
|
}
|
|
|
|
file_error (dirname);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
goto bind_and_exit;
|
|
}
|
|
else
|
|
{
|
|
dirname = get_string_value ("HOME");
|
|
|
|
if (!dirname)
|
|
return (EXECUTION_FAILURE);
|
|
|
|
if (!change_to_directory (dirname))
|
|
{
|
|
file_error (dirname);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
|
|
bind_and_exit:
|
|
{
|
|
char *get_working_directory (), *get_string_value ();
|
|
char *directory = get_working_directory ("cd");
|
|
|
|
bind_variable ("OLDPWD", get_string_value ("PWD"));
|
|
bind_variable ("PWD", directory);
|
|
|
|
if (directory)
|
|
free (directory);
|
|
}
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
}
|
|
|
|
/* Do the work of changing to the directory NEWDIR. Handle symbolic
|
|
link following, etc. */
|
|
change_to_directory (newdir)
|
|
char *newdir;
|
|
{
|
|
char *get_working_directory (), *make_absolute ();
|
|
char *t;
|
|
|
|
if (follow_symbolic_links)
|
|
{
|
|
if (!the_current_working_directory)
|
|
{
|
|
t = get_working_directory ("cd_links");
|
|
if (t)
|
|
free (t);
|
|
}
|
|
|
|
if (the_current_working_directory)
|
|
t = make_absolute (newdir, the_current_working_directory);
|
|
else
|
|
t = savestring (newdir);
|
|
|
|
/* Get rid of trailing `/'. */
|
|
{
|
|
register int len_t = strlen (t);
|
|
if (len_t > 1)
|
|
{
|
|
--len_t;
|
|
if (t[len_t] == '/')
|
|
t[len_t] = '\0';
|
|
}
|
|
}
|
|
|
|
if (chdir (t) < 0)
|
|
{
|
|
free (t);
|
|
return (0);
|
|
}
|
|
|
|
if (the_current_working_directory)
|
|
strcpy (the_current_working_directory, t);
|
|
|
|
free (t);
|
|
return (1);
|
|
}
|
|
else
|
|
{
|
|
if (chdir (newdir) < 0)
|
|
return (0);
|
|
else
|
|
return (1);
|
|
}
|
|
}
|
|
|
|
/* Return a consed string which is the current working directory.
|
|
FOR_WHOM is the name of the caller for error printing. */
|
|
char *
|
|
get_working_directory (for_whom)
|
|
char *for_whom;
|
|
{
|
|
if (!follow_symbolic_links)
|
|
{
|
|
if (the_current_working_directory)
|
|
free (the_current_working_directory);
|
|
|
|
the_current_working_directory = (char *)NULL;
|
|
}
|
|
|
|
if (!the_current_working_directory)
|
|
{
|
|
char *directory, *getwd ();
|
|
|
|
the_current_working_directory = (char *)xmalloc (MAXPATHLEN);
|
|
directory = getwd (the_current_working_directory);
|
|
if (!directory)
|
|
{
|
|
fprintf (stderr, "%s: %s\n",
|
|
for_whom, the_current_working_directory);
|
|
free (the_current_working_directory);
|
|
the_current_working_directory = (char *)NULL;
|
|
return (char *)NULL;
|
|
}
|
|
}
|
|
|
|
return (savestring (the_current_working_directory));
|
|
}
|
|
|
|
|
|
/* Print the words in LIST to standard output. If the first word is
|
|
`-n', then don't print a trailing newline. We also support the
|
|
echo syntax from Version 9 unix systems. */
|
|
echo_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
int display_return = 1, do_v9 = 0;
|
|
|
|
/* System V machines already have a /bin/sh with a v9 behaviour. We
|
|
give Bash the identical behaviour for these machines so that the
|
|
existing system shells won't barf. */
|
|
#if defined (V9_ECHO) && defined (SYSV)
|
|
do_v9 = 1;
|
|
#endif
|
|
|
|
while (list && list->word->word[0] == '-')
|
|
{
|
|
char *temp = &(list->word->word[1]);
|
|
|
|
if (!*temp)
|
|
goto just_echo;
|
|
|
|
while (*temp)
|
|
{
|
|
if (*temp == 'n')
|
|
display_return = 0;
|
|
#ifdef V9_ECHO
|
|
else if (*temp == 'e')
|
|
do_v9 = 1;
|
|
#ifdef SYSV
|
|
else if (*temp == 'E')
|
|
do_v9 = 0;
|
|
#endif
|
|
#endif
|
|
else
|
|
goto just_echo;
|
|
|
|
temp++;
|
|
}
|
|
list = list->next;
|
|
}
|
|
|
|
just_echo:
|
|
|
|
if (list)
|
|
{
|
|
#ifdef V9_ECHO
|
|
if (do_v9)
|
|
{
|
|
while (list)
|
|
{
|
|
register char *s = list->word->word;
|
|
register int c;
|
|
|
|
while (c = *s++)
|
|
{
|
|
if (c == '\\' && *s)
|
|
{
|
|
switch (c = *s++)
|
|
{
|
|
case 'b': c = '\b'; break;
|
|
case 'c': display_return = 0; continue;
|
|
case 'f': c = '\f'; break;
|
|
case 'n': c = '\n'; break;
|
|
case 'r': c = '\r'; break;
|
|
case 't': c = '\t'; break;
|
|
case 'v': c = (int) 0x0B; break;
|
|
case '0': case '1': case '2': case '3':
|
|
case '4': case '5': case '6': case '7':
|
|
c -= '0';
|
|
if (*s >= '0' && *s <= '7')
|
|
c = c * 8 + (*s++ - '0');
|
|
if (*s >= '0' && *s <= '7')
|
|
c = c * 8 + (*s++ - '0');
|
|
break;
|
|
case '\\': break;
|
|
default: putchar ('\\'); break;
|
|
}
|
|
}
|
|
putchar(c);
|
|
}
|
|
list = list->next;
|
|
if (list)
|
|
putchar(' ');
|
|
}
|
|
}
|
|
else
|
|
#endif /* V9_ECHO */
|
|
print_word_list (list, " ");
|
|
}
|
|
if (display_return)
|
|
printf ("\n");
|
|
fflush (stdout);
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
|
|
/* Parse the string that these words make, and execute the command found. */
|
|
eval_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
char *string_list ();
|
|
int result;
|
|
|
|
/* Note that parse_and_execute () free ()'s what it is passed. */
|
|
if (list)
|
|
result = parse_and_execute (string_list (list), "eval");
|
|
else
|
|
result = EXECUTION_SUCCESS;
|
|
return (result);
|
|
}
|
|
|
|
/* Defined in execute_cmd.c */
|
|
extern REDIRECT *redirection_undo_list;
|
|
|
|
exec_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
extern char *find_user_command ();
|
|
|
|
maybe_make_export_env ();
|
|
|
|
/* First, let the redirections remain. */
|
|
dispose_redirects (redirection_undo_list);
|
|
redirection_undo_list = (REDIRECT *)NULL;
|
|
|
|
if (!list)
|
|
return (EXECUTION_SUCCESS);
|
|
else
|
|
{
|
|
/* Otherwise, execve the new command with args. */
|
|
char *command, **args;
|
|
int dash_name = 0;
|
|
|
|
if (strcmp (list->word->word, "-") == 0)
|
|
{
|
|
/* The user would like to exec this command as if it was a
|
|
login command. Do so. */
|
|
list = list->next;
|
|
dash_name++;
|
|
}
|
|
|
|
args = (char **)make_word_array (list);
|
|
command = find_user_command (args[0]);
|
|
if (!command)
|
|
{
|
|
builtin_error ("%s: not found", args[0]);
|
|
goto failed_exec;
|
|
}
|
|
|
|
command = (char *)full_pathname (command);
|
|
/* If the user wants this to look like a login shell, then
|
|
pass the full pathname in argv[0]. */
|
|
if (dash_name)
|
|
{
|
|
char *new_name = (char *)xmalloc (1 + strlen (command));
|
|
strcpy (new_name, "-");
|
|
strcat (new_name, command);
|
|
args[0] = new_name;
|
|
}
|
|
|
|
write_history (get_string_value ("HISTFILE"));
|
|
|
|
signal (SIGINT, SIG_DFL);
|
|
signal (SIGQUIT, SIG_DFL);
|
|
execve (command, args, export_env);
|
|
|
|
if (!executable_file (command))
|
|
builtin_error ("%s: cannot execute", command);
|
|
else
|
|
file_error (command);
|
|
|
|
failed_exec:
|
|
if (!interactive && !find_variable ("no_exit_on_failed_exec"))
|
|
exit (EXECUTION_FAILURE);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
}
|
|
|
|
exit_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
extern int login_shell;
|
|
|
|
if (interactive && login_shell)
|
|
{
|
|
fprintf (stderr, "logout\n");
|
|
fflush (stderr);
|
|
}
|
|
|
|
exit_or_logout (list);
|
|
}
|
|
|
|
/* How to logout. */
|
|
logout_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
if (!login_shell && interactive)
|
|
{
|
|
builtin_error ("Not login shell: use `exit' or `bye'");
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
else
|
|
exit_or_logout (list);
|
|
}
|
|
|
|
/* Clean up work for exiting or logging out. */
|
|
Function *last_shell_builtin = (Function *)NULL;
|
|
Function *this_shell_builtin = (Function *)NULL;
|
|
|
|
exit_or_logout (list)
|
|
WORD_LIST *list;
|
|
{
|
|
extern int last_command_exit_value;
|
|
|
|
#ifdef JOB_CONTROL
|
|
int exit_immediate_okay;
|
|
|
|
exit_immediate_okay = (!interactive ||
|
|
last_shell_builtin == exit_builtin ||
|
|
last_shell_builtin == logout_builtin ||
|
|
last_shell_builtin == jobs_builtin);
|
|
|
|
/* Check for stopped jobs if the user wants to. */
|
|
if (1 && !exit_immediate_okay)
|
|
{
|
|
register int i;
|
|
for (i = 0; i < job_slots; i++)
|
|
if (jobs[i] && (jobs[i]->state == JSTOPPED))
|
|
{
|
|
fprintf (stderr, "There are stopped jobs.\n");
|
|
|
|
/* This is NOT superfluous because EOF can get here without
|
|
going through the command parser. */
|
|
last_shell_builtin = exit_builtin;
|
|
|
|
longjmp (top_level, DISCARD);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Get return value if present. This means that you can type
|
|
`logout 5' to a shell, and it returns 5. */
|
|
|
|
if (list)
|
|
last_command_exit_value = get_numeric_arg (list);
|
|
|
|
/* Run our `~/.bash_logout' file if it exists, and this is a login shell. */
|
|
if (login_shell)
|
|
maybe_execute_file ("~/.bash_logout");
|
|
|
|
/* Exit the program. */
|
|
longjmp (top_level, EXITPROG);
|
|
}
|
|
|
|
/* For each variable name in LIST, make that variable appear in the
|
|
environment passed to simple commands. If there is no LIST, then
|
|
print all such variables. An argument of `-n' says to remove the
|
|
exported attribute from variables named in LIST. */
|
|
export_builtin (list)
|
|
register WORD_LIST *list;
|
|
{
|
|
return (set_or_show_attributes (list, att_exported));
|
|
}
|
|
|
|
/* For each variable name in LIST, make that variable have the specified
|
|
ATTRIBUTE. An arg of `-n' says to remove the attribute from the the
|
|
remaining names in LIST. */
|
|
set_or_show_attributes (list, attribute)
|
|
register WORD_LIST *list;
|
|
int attribute;
|
|
{
|
|
register SHELL_VAR *var;
|
|
int assign, undo = 0;
|
|
extern int array_needs_making;
|
|
|
|
if (list)
|
|
{
|
|
while (list)
|
|
{
|
|
register char *name = list->word->word;
|
|
|
|
if (strcmp (name, "-n") == 0)
|
|
{
|
|
undo++;
|
|
list = list->next;
|
|
continue;
|
|
}
|
|
|
|
var = find_variable (name);
|
|
|
|
if ((assign = assignment (name)) != 0)
|
|
{
|
|
do_assignment (name);
|
|
name[assign] = '\0';
|
|
}
|
|
|
|
if (undo)
|
|
{
|
|
var = find_variable (name);
|
|
if (var)
|
|
var->attributes &= ~attribute;
|
|
}
|
|
else
|
|
{
|
|
var = find_variable (name);
|
|
|
|
if (!var)
|
|
{
|
|
SHELL_VAR *find_tempenv_variable ();
|
|
|
|
if ((var = find_tempenv_variable (name)))
|
|
{
|
|
SHELL_VAR *disposer = var;
|
|
var = bind_variable (disposer->name, disposer->value);
|
|
dispose_variable (disposer);
|
|
}
|
|
else
|
|
{
|
|
var = bind_variable (name, (char *)NULL);
|
|
var->attributes |= att_invisible;
|
|
}
|
|
}
|
|
|
|
var->attributes |= attribute;
|
|
}
|
|
array_needs_making++;
|
|
|
|
list = list->next;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var = variable_list;
|
|
|
|
while (var)
|
|
{
|
|
if ((var->attributes & attribute) && !invisible_p (var))
|
|
{
|
|
char flags[5];
|
|
|
|
flags[0] = '\0';
|
|
|
|
if (var->attributes & att_exported)
|
|
strcat (flags, "x");
|
|
|
|
if (var->attributes & att_readonly)
|
|
strcat (flags, "r");
|
|
|
|
if (function_p (var))
|
|
strcat (flags, "f");
|
|
|
|
if (flags[0])
|
|
{
|
|
printf ("declare -%s ", flags);
|
|
|
|
if (!function_p (var))
|
|
printf ("%s=%s\n", var->name, value_cell (var));
|
|
else
|
|
{
|
|
char *named_function_string ();
|
|
|
|
printf ("%s\n",
|
|
named_function_string (var->name,
|
|
function_cell (var), 1));
|
|
}
|
|
}
|
|
}
|
|
var = var->next;
|
|
}
|
|
}
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
|
|
/* Hashing filenames in the shell. */
|
|
|
|
#include "hash.h"
|
|
|
|
#define FILENAME_HASH_BUCKETS 631
|
|
|
|
HASH_TABLE *hashed_filenames;
|
|
|
|
typedef struct {
|
|
/* The full pathname of the file. */
|
|
char *path;
|
|
|
|
/* Whether `.' appeared before this one in $PATHS. */
|
|
int check_dot;
|
|
} PATH_DATA;
|
|
|
|
#define pathdata(x) ((PATH_DATA *)(x)->data)
|
|
|
|
initialize_filename_hashing ()
|
|
{
|
|
hashed_filenames = make_hash_table (FILENAME_HASH_BUCKETS);
|
|
}
|
|
|
|
/* Place FILENAME (key) and FULL_PATHNAME (data->path) into the
|
|
hash table. CHECK_DOT if non-null is for future calls to
|
|
find_hashed_filename (). */
|
|
remember_filename (filename, full_pathname, check_dot)
|
|
char *filename, *full_pathname;
|
|
int check_dot;
|
|
{
|
|
register BUCKET_CONTENTS *item;
|
|
|
|
if (hashing_disabled)
|
|
return;
|
|
item = add_hash_item (filename, hashed_filenames);
|
|
if (item->data)
|
|
free (pathdata(item)->path);
|
|
else
|
|
item->data = (char *)xmalloc (sizeof (PATH_DATA));
|
|
|
|
item->key = savestring (filename);
|
|
pathdata(item)->path = savestring (full_pathname);
|
|
pathdata(item)->check_dot = check_dot;
|
|
item->times_found = 0;
|
|
}
|
|
|
|
/* Temporary static. */
|
|
char *dotted_filename = (char *)NULL;
|
|
|
|
/* Return the full pathname that FILENAME hashes to. If FILENAME
|
|
is hashed, but data->check_dot is non-zero, check ./FILENAME
|
|
and return that if it is executable. */
|
|
char *
|
|
find_hashed_filename (filename)
|
|
char *filename;
|
|
{
|
|
register BUCKET_CONTENTS *item;
|
|
|
|
if (hashing_disabled)
|
|
return ((char *)NULL);
|
|
|
|
item = find_hash_item (filename, hashed_filenames);
|
|
if (item) {
|
|
|
|
/* If this filename is hashed, but `.' comes before it in the path,
|
|
then see if `./filename' is an executable. */
|
|
if (pathdata(item)->check_dot) {
|
|
|
|
if (dotted_filename)
|
|
free (dotted_filename);
|
|
|
|
dotted_filename = (char *)xmalloc (3 + strlen (filename));
|
|
strcpy (dotted_filename, "./");
|
|
strcat (dotted_filename, filename);
|
|
|
|
if (executable_file (dotted_filename))
|
|
return (dotted_filename);
|
|
|
|
/* Watch out. If this file was hashed to "./filename", and
|
|
"./filename" is not executable, then return NULL. */
|
|
if (strcmp (pathdata(item)->path, dotted_filename) == 0)
|
|
return ((char *)NULL);
|
|
}
|
|
return (pathdata(item)->path);
|
|
} else {
|
|
return ((char *)NULL);
|
|
}
|
|
}
|
|
|
|
/* Print statistics on the current state of hashed commands. If LIST is
|
|
not empty, then rehash (or hash in the first place) the specified
|
|
commands. */
|
|
hash_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
extern Function *find_shell_builtin ();
|
|
extern char *find_user_command ();
|
|
int any_failed = 0;
|
|
|
|
if (hashing_disabled)
|
|
{
|
|
builtin_error ("Hashing is disabled");
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
|
|
if (!list)
|
|
{
|
|
/* Print information about current hashed info. */
|
|
int any_printed = 0;
|
|
int bucket = 0;
|
|
register BUCKET_CONTENTS *item_list;
|
|
|
|
while (bucket < hashed_filenames->nbuckets)
|
|
{
|
|
item_list = get_hash_bucket (bucket, hashed_filenames);
|
|
if (item_list)
|
|
{
|
|
if (!any_printed)
|
|
{
|
|
printf ("hits\tcommand\n");
|
|
any_printed++;
|
|
}
|
|
while (item_list)
|
|
{
|
|
printf ("%4d\t%s\n",
|
|
item_list->times_found, pathdata(item_list)->path);
|
|
item_list = item_list->next;
|
|
}
|
|
}
|
|
bucket++;
|
|
}
|
|
if (!any_printed)
|
|
{
|
|
printf ("No commands in hash table.\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Add or rehash the specified commands. */
|
|
char *word;
|
|
char *full_path;
|
|
SHELL_VAR *var;
|
|
int any_failed = 0;
|
|
|
|
if (strcmp (list->word->word, "-r") == 0)
|
|
{
|
|
int bucket = 0;
|
|
register BUCKET_CONTENTS *item_list, *prev;
|
|
|
|
while (bucket < hashed_filenames->nbuckets)
|
|
{
|
|
item_list = get_hash_bucket (bucket, hashed_filenames);
|
|
if (item_list)
|
|
{
|
|
while (item_list)
|
|
{
|
|
prev = item_list;
|
|
free (item_list->key);
|
|
free (pathdata(item_list)->path);
|
|
free (item_list->data);
|
|
item_list = item_list->next;
|
|
free (prev);
|
|
}
|
|
hashed_filenames->bucket_array[bucket] = (BUCKET_CONTENTS *)NULL;
|
|
}
|
|
bucket++;
|
|
}
|
|
list = list->next;
|
|
}
|
|
while (list)
|
|
{
|
|
word = list->word->word;
|
|
if (absolute_pathname (word))
|
|
goto next;
|
|
full_path = find_user_command (word);
|
|
var = find_variable (word);
|
|
|
|
if (!find_shell_builtin (word) &&
|
|
(!var || !function_p (var)))
|
|
{
|
|
if (full_path && executable_file (full_path))
|
|
{
|
|
extern int dot_found_in_search;
|
|
remember_filename (word, full_path, dot_found_in_search);
|
|
}
|
|
else
|
|
{
|
|
builtin_error ("%s: not found", word);
|
|
any_failed++;
|
|
}
|
|
}
|
|
next:
|
|
list = list->next;
|
|
}
|
|
}
|
|
return (any_failed ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
|
|
}
|
|
|
|
/* History. Arg of -w FILENAME means write file, arg of -r FILENAME
|
|
means read file. Arg of N means only display that many items. */
|
|
|
|
history_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
HIST_ENTRY **hlist = history_list ();
|
|
int limited = (list != (WORD_LIST *)NULL);
|
|
int limit;
|
|
register int i = 0;
|
|
|
|
if (hlist)
|
|
while (hlist[i]) i++;
|
|
|
|
if (list)
|
|
{
|
|
if ((strcmp (list->word->word, "-w") == 0) ||
|
|
(strcmp (list->word->word, "-r") == 0)) {
|
|
int writing = (strcmp (list->word->word, "-w") == 0);
|
|
char *file;
|
|
int result;
|
|
|
|
if (list->next)
|
|
file = list->next->word->word;
|
|
else
|
|
file = get_string_value ("HISTFILE");
|
|
|
|
if (writing)
|
|
result = write_history (file);
|
|
else
|
|
result = read_history (file);
|
|
|
|
return (result ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
|
|
}
|
|
}
|
|
|
|
if (strcmp (list->word->word, "-s") == 0)
|
|
{
|
|
extern int history_expand ();
|
|
char *expanded;
|
|
int rval;
|
|
|
|
list = list->next;
|
|
|
|
while (list)
|
|
{
|
|
rval = history_expand (list->word->word, &expanded);
|
|
printf ("%s", expanded);
|
|
fflush (stdout);
|
|
|
|
if (rval == -1)
|
|
return (EXECUTION_FAILURE);
|
|
|
|
free (expanded);
|
|
|
|
list = list->next;
|
|
}
|
|
}
|
|
|
|
limit = get_numeric_arg (list);
|
|
if (limit < 0)
|
|
limit = -limit;
|
|
|
|
if (!limited)
|
|
i = 0;
|
|
else
|
|
if ((i -= limit) < 0)
|
|
i = 0;
|
|
|
|
while (hlist[i])
|
|
{
|
|
fprintf (stdout,
|
|
"%5d%c %s\n", i + history_base, hlist[i]->data ? '*' : ' ',
|
|
hlist[i]->line);
|
|
i++;
|
|
}
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
|
|
/* Non-zero means that pwd always give verbatim directory, regardless of
|
|
symbolic link following. */
|
|
int verbatim_pwd = 1;
|
|
|
|
/* Print the name of the current working directory. */
|
|
pwd_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
char *get_working_directory (), *getwd (), *directory;
|
|
|
|
no_args (list);
|
|
|
|
if (verbatim_pwd)
|
|
{
|
|
char *buffer = (char *)xmalloc (MAXPATHLEN);
|
|
directory = getwd (buffer);
|
|
|
|
if (!directory)
|
|
{
|
|
builtin_error ("%s", buffer);
|
|
free (buffer);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
directory = get_working_directory ("pwd");
|
|
}
|
|
|
|
if (directory)
|
|
{
|
|
printf ("%s\n", directory);
|
|
fflush (stdout);
|
|
free (directory);
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
else
|
|
{
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
}
|
|
|
|
/* Read the value of the shell variables whose names follow.
|
|
The reading is done from the current input stream, whatever
|
|
that may be. Successive words of the input line are assigned
|
|
to the variables mentioned in LIST. The last variable in LIST
|
|
gets the remainder of the words on the line. If no variables
|
|
are mentioned in LIST, then the default variable is $REPLY.
|
|
|
|
S. R. Bourne's shell complains if you don't name a variable
|
|
to receive the stuff that is read. GNU's shell doesn't. This
|
|
allows you to let the user type random things. */
|
|
read_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
extern int interrupt_immediately, free ();
|
|
register char *varname;
|
|
int size, c, i = 0, fildes;
|
|
char *input_string, *ifs_chars;
|
|
WORD_LIST *words, *rwords, *list_string ();
|
|
FILE *input_stream;
|
|
|
|
ifs_chars = get_string_value ("IFS");
|
|
input_string = (char *)xmalloc (size = 128);
|
|
|
|
/* We need unbuffered input from stdin. So we make a new
|
|
unbuffered stream with the same file descriptor, then
|
|
unbuffer that one. */
|
|
fildes = dup (fileno (stdin));
|
|
|
|
if (fildes == -1)
|
|
return (EXECUTION_FAILURE);
|
|
|
|
input_stream = fdopen (fildes, "r");
|
|
|
|
if (!input_stream)
|
|
{
|
|
close (fildes);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
|
|
setbuf (input_stream, (char *)NULL);
|
|
|
|
{
|
|
int stream_close ();
|
|
begin_unwind_frame ("read_builtin");
|
|
add_unwind_protect (free, input_string);
|
|
add_unwind_protect (stream_close, input_stream);
|
|
interrupt_immediately++;
|
|
}
|
|
|
|
while ((c = getc (input_stream)) != EOF)
|
|
{
|
|
if (i + 1 >= size)
|
|
input_string = (char *)xrealloc (input_string, size += 128);
|
|
|
|
input_string[i++] = c;
|
|
|
|
if (c == '\n')
|
|
{
|
|
if ((i >= 2) && (input_string[i - 2] == '\\'))
|
|
{
|
|
i -= 2;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
input_string[i] = '\0';
|
|
|
|
interrupt_immediately--;
|
|
discard_unwind_frame ("read_builtin");
|
|
|
|
fclose (input_stream);
|
|
|
|
if (!i)
|
|
return (EXECUTION_FAILURE);
|
|
|
|
if (!list)
|
|
{
|
|
bind_variable ("REPLY", input_string);
|
|
free (input_string);
|
|
}
|
|
else
|
|
{
|
|
words = list_string (input_string, ifs_chars, 0);
|
|
rwords = words;
|
|
|
|
free (input_string);
|
|
|
|
while (list)
|
|
{
|
|
varname = list->word->word;
|
|
|
|
if (!list->next)
|
|
bind_variable (varname, words ? (char *)string_list (words) : "");
|
|
else
|
|
bind_variable (varname, words ? words->word->word : "");
|
|
stupidly_hack_special_variables (varname);
|
|
list = list->next;
|
|
if (words)
|
|
words = words->next;
|
|
}
|
|
if (rwords)
|
|
dispose_words (rwords);
|
|
}
|
|
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
|
|
/* This way I don't have to know whether fclose is a function or a macro. */
|
|
int
|
|
stream_close (file)
|
|
FILE *file;
|
|
{
|
|
return (fclose (file));
|
|
}
|
|
|
|
/* For each variable name in LIST, make that variable read_only. */
|
|
readonly_builtin (list)
|
|
register WORD_LIST *list;
|
|
{
|
|
return (set_or_show_attributes (list, att_readonly));
|
|
}
|
|
|
|
/* If we are executing a user-defined function then exit with the value
|
|
specified as an argument. if no argument is given, then the last
|
|
exit status is used. */
|
|
return_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
extern int last_command_exit_value;
|
|
extern int return_catch_flag, return_catch_value;
|
|
extern jmp_buf return_catch;
|
|
|
|
return_catch_value = get_numeric_arg (list);
|
|
|
|
if (!list)
|
|
return_catch_value = last_command_exit_value;
|
|
|
|
if (return_catch_flag)
|
|
longjmp (return_catch, 1);
|
|
else
|
|
{
|
|
builtin_error ("Can only `return' from a function");
|
|
longjmp (top_level, DISCARD);
|
|
}
|
|
}
|
|
|
|
/* Set some flags from the word values in the input list. If LIST is empty,
|
|
then print out the values of the variables instead. If LIST contains
|
|
non-flags, then set $1 - $9 to the successive words of LIST. */
|
|
set_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
int on_or_off, flag_name;
|
|
|
|
if (!list)
|
|
{
|
|
print_var_list (variable_list);
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
|
|
/* Do the set command. While the list consists of words starting with
|
|
'-' or '+' treat them as flags, otherwise, start assigning them to
|
|
$1 ... $n. */
|
|
while (list)
|
|
{
|
|
char *string = list->word->word;
|
|
#if defined (NEVER)
|
|
if (strcmp (string, "-") == 0)
|
|
{
|
|
WORD_LIST *t =
|
|
(WORD_LIST *)make_word_list (make_word ("-"), NULL);
|
|
remember_args (t, 1);
|
|
dispose_words (t);
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
#endif /* NEVER */
|
|
|
|
/* If the argument is `--' then signal the end of the list and
|
|
remember the remaining arguments. */
|
|
if (strcmp (string, "--") == 0)
|
|
{
|
|
list = list->next;
|
|
break;
|
|
}
|
|
|
|
if ((on_or_off = *string) &&
|
|
(on_or_off == '-' || on_or_off == '+'))
|
|
{
|
|
int i = 1;
|
|
while (flag_name = string[i++])
|
|
{
|
|
if (flag_name == '?')
|
|
{
|
|
/* Print all the possible flags. */
|
|
}
|
|
else
|
|
{
|
|
if (change_flag_char (flag_name, on_or_off) == FLAG_ERROR)
|
|
{
|
|
builtin_error ("%c%c: bad option", on_or_off, flag_name);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
list = list->next;
|
|
}
|
|
|
|
/* Assigning $1 ... $n */
|
|
if (list)
|
|
remember_args (list, 1);
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
|
|
|
|
/* **************************************************************** */
|
|
/* */
|
|
/* Pushing and Popping a Context */
|
|
/* */
|
|
/* **************************************************************** */
|
|
|
|
WORD_LIST **dollar_arg_stack = (WORD_LIST **)NULL;
|
|
int dollar_arg_stack_slots = 0;
|
|
int dollar_arg_stack_index = 0;
|
|
|
|
push_context ()
|
|
{
|
|
extern int variable_context;
|
|
|
|
push_dollar_vars ();
|
|
variable_context++;
|
|
}
|
|
|
|
pop_context ()
|
|
{
|
|
extern int variable_context;
|
|
|
|
pop_dollar_vars ();
|
|
kill_all_local_variables ();
|
|
variable_context--;
|
|
}
|
|
|
|
/* Save the existing arguments on a stack. */
|
|
push_dollar_vars ()
|
|
{
|
|
extern WORD_LIST *list_rest_of_args ();
|
|
|
|
if (dollar_arg_stack_index + 2 > dollar_arg_stack_slots)
|
|
{
|
|
if (!dollar_arg_stack)
|
|
{
|
|
dollar_arg_stack_slots = 10;
|
|
dollar_arg_stack =
|
|
(WORD_LIST **)xmalloc (dollar_arg_stack_slots * sizeof (WORD_LIST *));
|
|
}
|
|
else
|
|
{
|
|
dollar_arg_stack_slots += 10;
|
|
dollar_arg_stack =
|
|
(WORD_LIST **)xrealloc (dollar_arg_stack,
|
|
dollar_arg_stack_slots * sizeof (WORD_LIST **));
|
|
}
|
|
}
|
|
dollar_arg_stack[dollar_arg_stack_index] = list_rest_of_args ();
|
|
dollar_arg_stack[++dollar_arg_stack_index] = (WORD_LIST *)NULL;
|
|
}
|
|
|
|
pop_dollar_vars ()
|
|
{
|
|
if (!dollar_arg_stack || !dollar_arg_stack_index)
|
|
return;
|
|
|
|
remember_args (dollar_arg_stack[--dollar_arg_stack_index], 1);
|
|
dispose_words (dollar_arg_stack[dollar_arg_stack_index]);
|
|
dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL;
|
|
}
|
|
|
|
/* Remember LIST in $0 ... $9, and REST_OF_ARGS. If DESTRUCTIVE is
|
|
non-zero, then discard whatever the existing arguments are, else
|
|
only discard the ones that are to be replaced. */
|
|
remember_args (list, destructive)
|
|
WORD_LIST *list;
|
|
int destructive;
|
|
{
|
|
register int i;
|
|
|
|
for (i = 1; i < 10; i++)
|
|
{
|
|
if (destructive && dollar_vars[i])
|
|
{
|
|
free (dollar_vars[i]);
|
|
dollar_vars[i] = (char *)NULL;
|
|
}
|
|
|
|
if (list)
|
|
{
|
|
if (!destructive && dollar_vars[i])
|
|
free (dollar_vars[i]);
|
|
|
|
dollar_vars[i] = savestring (list->word->word);
|
|
list = list->next;
|
|
}
|
|
}
|
|
|
|
/* If arguments remain, assign them to REST_OF_ARGS. */
|
|
if (!list)
|
|
{
|
|
dispose_words (rest_of_args);
|
|
rest_of_args = NULL;
|
|
}
|
|
else
|
|
{
|
|
rest_of_args = (WORD_LIST *)copy_word_list (list);
|
|
}
|
|
}
|
|
|
|
/* Return if LIST is NULL else barf and jump to top_level. */
|
|
no_args (list)
|
|
WORD_LIST *list;
|
|
{
|
|
if (list)
|
|
{
|
|
builtin_error ("extra arguments");
|
|
longjmp (top_level, DISCARD);
|
|
}
|
|
}
|
|
|
|
/* Shift the arguments ``left''. Shift DOLLAR_VARS down then take one
|
|
off of REST_OF_ARGS and place it into DOLLAR_VARS[9]. If LIST has
|
|
anything in it, it is a number which says where to start the shifting. */
|
|
shift_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
int times = get_numeric_arg (list);
|
|
|
|
while (times-- > 0) {
|
|
register int count;
|
|
|
|
if (dollar_vars[1]) free (dollar_vars[1]);
|
|
|
|
for (count = 1; count < 9; count++)
|
|
dollar_vars[count] = dollar_vars[count + 1];
|
|
|
|
if (rest_of_args) {
|
|
WORD_LIST *temp = rest_of_args;
|
|
|
|
dollar_vars[9] = savestring (temp->word->word);
|
|
rest_of_args = rest_of_args->next;
|
|
dispose_word (temp->word);
|
|
} else {
|
|
dollar_vars[9] = (char *)NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* TEST/[ builtin. */
|
|
test_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
char **argv;
|
|
int argc, result;
|
|
WORD_LIST *t = list;
|
|
|
|
/* We let Matthew Bradburn and Kevin Braunsdorf's code do the
|
|
actual test command. So turn the list of args into an array
|
|
of strings, since that is what his code wants. */
|
|
|
|
if (!list)
|
|
{
|
|
if (strcmp (this_command_name, "[") == 0)
|
|
fprintf (stderr, "[: missing `]'\n");
|
|
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
|
|
/* Get the length of the argument list. */
|
|
for (argc = 0; t; t = t->next, argc++);
|
|
|
|
/* Account for argv[0] being a command name. This makes our life easier. */
|
|
argc++;
|
|
argv = (char **)xmalloc ((1 + argc) * sizeof (char *));
|
|
argv[argc] = (char *)NULL;
|
|
|
|
/* this_command_name is the name of the command that invoked this
|
|
function. So you can't call test_builtin () directly from
|
|
within this code, there are too many things to worry about. */
|
|
argv[0] = savestring (this_command_name);
|
|
|
|
for (t = list, argc = 1; t; t = t->next, argc++)
|
|
argv[argc] = savestring (t->word->word);
|
|
|
|
result = test_command (argc, argv);
|
|
free_array (argv);
|
|
return (result);
|
|
}
|
|
|
|
/* Print the totals for system and user time used. The
|
|
information comes from variables in jobs.c used to keep
|
|
track of this stuff. */
|
|
|
|
#if !defined (SYSV)
|
|
static long
|
|
scale60 (tvalp)
|
|
struct timeval *tvalp;
|
|
{
|
|
return (tvalp->tv_sec * 60 + tvalp->tv_usec / 16667);
|
|
}
|
|
#endif /* !SYSV */
|
|
|
|
times_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
int system_minutes_used, user_minutes_used;
|
|
long system_seconds_used, user_seconds_used;
|
|
#ifndef SYSV
|
|
struct rusage self, kids;
|
|
|
|
no_args (list);
|
|
|
|
getrusage (RUSAGE_SELF, &self);
|
|
getrusage (RUSAGE_CHILDREN, &kids); /* terminated child processes */
|
|
|
|
user_seconds_used = scale60(&self.ru_utime) + scale60(&kids.ru_utime);
|
|
system_seconds_used = scale60(&self.ru_stime) + scale60(&kids.ru_stime);
|
|
|
|
#else /* SYSV */
|
|
|
|
#ifndef HZ
|
|
#define HZ 100 /* From my Sys V.3.2 manual for times(2) */
|
|
#endif /* !HZ */
|
|
|
|
struct tms t;
|
|
int sys_seconds, user_seconds;
|
|
|
|
no_args (list);
|
|
|
|
times (&t);
|
|
|
|
/* As of System V.3, HP-UX 6.5, and other ATT-like systems, this stuff is
|
|
returned in terms of clock ticks (HZ from sys/param.h). C'mon, guys.
|
|
This kind of stupid clock-dependent stuff is exactly the reason 4.2BSD
|
|
introduced the `timeval' struct. */
|
|
|
|
system_seconds_used = ((t.tms_stime + HZ - 1) / HZ) +
|
|
((t.tms_cstime + HZ - 1) / HZ);
|
|
|
|
user_seconds_used = ((t.tms_utime + HZ - 1) / HZ) +
|
|
((t.tms_cutime + HZ - 1) / HZ);
|
|
|
|
#endif /* SYSV */
|
|
|
|
system_minutes_used = system_seconds_used / 60;
|
|
system_seconds_used %= 60;
|
|
|
|
user_minutes_used = user_seconds_used / 60;
|
|
user_seconds_used %= 60;
|
|
|
|
printf ("%0dm%0ds %0dm%0ds\n", system_minutes_used, system_seconds_used,
|
|
user_minutes_used, user_seconds_used);
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
|
|
/* The trap command:
|
|
|
|
trap <arg> <signal ...>
|
|
trap <signal ...>
|
|
trap -l
|
|
trap
|
|
|
|
Set things up so that ARG is executed when SIGNAL(s) N is recieved.
|
|
If ARG is the empty string, then ignore the SIGNAL(s). If there is
|
|
no ARG, then set the trap for SIGNAL(s) to its original value. Just
|
|
plain "trap" means to print out the list of commands associated with
|
|
each signal number. Single arg of "-l" means list the signal names. */
|
|
|
|
/* Possible operations to perform on the list of signals.*/
|
|
#define SET 0 /* Set this signal to first_arg. */
|
|
#define REVERT 1 /* Revert to this signals original value. */
|
|
#define IGNORE 2 /* Ignore this signal. */
|
|
|
|
trap_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
register int i;
|
|
|
|
if (!list)
|
|
{
|
|
for (i = 0; i < NSIG; i++)
|
|
if ((int)(trap_list[i]) > 0)
|
|
printf ("%d:%s: #(%s)\n", i, trap_list[i], signal_name (i));
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
else
|
|
{
|
|
char *first_arg = list->word->word;
|
|
int operation = SET, any_failed = 0;
|
|
|
|
if (strcmp ("-l", first_arg) == 0)
|
|
{
|
|
int column = 0;
|
|
|
|
for (i = 0; i < NSIG; i++)
|
|
{
|
|
printf ("%d) %s", i, signal_name (i));
|
|
if (++column < 4)
|
|
printf ("\t");
|
|
else
|
|
{
|
|
printf ("\n");
|
|
column = 0;
|
|
}
|
|
}
|
|
if (column != 0)
|
|
printf ("\n");
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
|
|
if (signal_object_p (first_arg))
|
|
operation = REVERT;
|
|
else
|
|
{
|
|
list = list->next;
|
|
if (*first_arg == '\0')
|
|
operation = IGNORE;
|
|
}
|
|
|
|
while (list)
|
|
{
|
|
int signal = decode_signal (list->word->word);
|
|
|
|
if (signal == NO_SIG)
|
|
{
|
|
builtin_error ("%s: not a signal specification", list->word->word);
|
|
any_failed++;
|
|
}
|
|
else
|
|
{
|
|
switch (operation)
|
|
{
|
|
case SET:
|
|
set_signal (signal, first_arg);
|
|
break;
|
|
|
|
case REVERT:
|
|
restore_default_signal (signal);
|
|
break;
|
|
|
|
case IGNORE:
|
|
ignore_signal (signal);
|
|
break;
|
|
}
|
|
}
|
|
list = list->next;
|
|
}
|
|
return ((!any_failed) ? EXECUTION_SUCCESS : EXECUTION_FAILURE);
|
|
}
|
|
}
|
|
|
|
/* For each word in LIST, find out what the shell is going to do with
|
|
it as a simple command. i.e., which file would this shell use to
|
|
execve, or if it is a builtin command, or an alias. Possible flag
|
|
arguments:
|
|
-type Returns the "type" of the object, one of
|
|
`alias', `function', `builtin', or `file'.
|
|
|
|
-path Returns the pathname of the file if -type is
|
|
a file.
|
|
|
|
-all Returns all occurrences of words, whether they
|
|
be a filename in the path, alias, function,
|
|
or builtin.
|
|
Order of evaluation:
|
|
alias
|
|
keyword
|
|
function
|
|
builtin
|
|
file
|
|
*/
|
|
type_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
extern Function *find_shell_builtin ();
|
|
extern char *user_command_matches (), *find_user_command ();
|
|
extern struct { char *word; int token; } token_word_alist[];
|
|
int path_only, type_only, all;
|
|
int found_something = 0;
|
|
int found_file = 0;
|
|
|
|
path_only = type_only = all = 0;
|
|
|
|
while (list && *(list->word->word) == '-')
|
|
{
|
|
char *flag = &(list->word->word[1]);
|
|
|
|
if (strcmp (flag, "type") == 0)
|
|
{
|
|
type_only = 1;
|
|
path_only = 0;
|
|
}
|
|
else if (strcmp (flag, "path") == 0 || strcmp (flag, "n") == 0)
|
|
{
|
|
path_only = 1;
|
|
type_only = 0;
|
|
}
|
|
else if (strcmp (flag, "all") == 0)
|
|
{
|
|
all = 1;
|
|
}
|
|
else
|
|
{
|
|
builtin_error ("Bad flag `%s'", flag);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
list = list->next;
|
|
}
|
|
|
|
while (list)
|
|
{
|
|
char *command = list->word->word;
|
|
SHELL_VAR *var = find_variable (command);
|
|
int hashed = 0;
|
|
char *full_path = (char *)NULL;
|
|
|
|
#ifdef ALIAS
|
|
/* Command is an alias? */
|
|
ASSOC *alias = find_alias (command);
|
|
if (alias)
|
|
{
|
|
if (type_only)
|
|
printf ("alias\n");
|
|
else if (!path_only)
|
|
printf ("%s is aliased to `%s'\n", command, alias->value);
|
|
found_something++;
|
|
if (!all)
|
|
goto next_item;
|
|
}
|
|
#endif
|
|
|
|
/* Command is a shell reserved word? */
|
|
{
|
|
register int i;
|
|
|
|
for (i = 0; token_word_alist[i].word; i++)
|
|
{
|
|
if (strcmp (token_word_alist[i].word, command) == 0)
|
|
{
|
|
if (type_only)
|
|
printf ("reserved word\n");
|
|
else if (!path_only)
|
|
printf ("%s is a shell reserved word\n", command);
|
|
|
|
found_something++;
|
|
|
|
if (!all)
|
|
goto next_item;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Command is a function? */
|
|
if (var && function_p (var))
|
|
{
|
|
if (type_only)
|
|
printf ("function\n");
|
|
else if (!path_only)
|
|
{
|
|
printf ("%s is a function\n", command);
|
|
printf ("%s () ", command);
|
|
print_command (function_cell (var));
|
|
printf ("\n");
|
|
}
|
|
found_something++;
|
|
if (!all)
|
|
goto next_item;
|
|
}
|
|
|
|
/* Command is a builtin? */
|
|
if (find_shell_builtin (command))
|
|
{
|
|
if (type_only)
|
|
printf ("builtin\n");
|
|
else if (!path_only)
|
|
printf ("%s is a shell builtin\n", command);
|
|
found_something++;
|
|
if (!all)
|
|
goto next_item;
|
|
}
|
|
|
|
full_path = (char *)NULL;
|
|
|
|
/* Command is a disk file? */
|
|
if (absolute_pathname (command))
|
|
{
|
|
full_path = savestring (command);
|
|
}
|
|
else
|
|
{
|
|
/* If the user isn't doing "-all", then we might care about
|
|
whether the file is present in our hash table. */
|
|
if (!all)
|
|
{
|
|
if ((full_path = find_hashed_filename (command)) != (char *)NULL)
|
|
{
|
|
hashed++;
|
|
full_path = savestring (full_path);
|
|
}
|
|
else
|
|
{
|
|
full_path = find_user_command (command);
|
|
}
|
|
}
|
|
}
|
|
if (all)
|
|
{
|
|
/* If full_path was set then it is an absolute path name. */
|
|
|
|
if (full_path)
|
|
{
|
|
found_something++;
|
|
if (type_only)
|
|
printf ("file\n");
|
|
else if (path_only)
|
|
printf ("%s\n", full_path);
|
|
else
|
|
printf ("%s is %s\n", command, full_path);
|
|
|
|
free (full_path);
|
|
}
|
|
else
|
|
while (full_path = user_command_matches (command, 1, found_file))
|
|
{
|
|
found_something++;
|
|
found_file++;
|
|
|
|
if (type_only)
|
|
printf ("file\n");
|
|
else if (path_only)
|
|
printf ("%s\n", full_path);
|
|
else
|
|
printf ("%s is %s\n", command, full_path);
|
|
|
|
free (full_path);
|
|
}
|
|
|
|
if (!found_something && !path_only)
|
|
printf ("%s not found\n", command);
|
|
|
|
goto next_item;
|
|
}
|
|
|
|
/* We can only get here if "-all" was not specified. */
|
|
if (!full_path)
|
|
{
|
|
if (!type_only && !path_only)
|
|
printf ("%s not found\n", command);
|
|
}
|
|
else
|
|
{
|
|
if (type_only)
|
|
printf ("file\n");
|
|
else if (path_only)
|
|
printf ("%s\n", full_path);
|
|
else
|
|
{
|
|
if (hashed)
|
|
printf ("%s is hashed (%s)\n", command, full_path);
|
|
else
|
|
printf ("%s is %s\n", command, full_path);
|
|
}
|
|
|
|
free (full_path);
|
|
}
|
|
next_item:
|
|
list = list->next;
|
|
}
|
|
fflush (stdout);
|
|
return (found_something ? EXECUTION_SUCCESS : EXECUTION_FAILURE);
|
|
}
|
|
|
|
/* Declare or change variable attributes. */
|
|
declare_builtin (list)
|
|
register WORD_LIST *list;
|
|
{
|
|
return (declare_internal (list, 0));
|
|
}
|
|
|
|
local_builtin (list)
|
|
register WORD_LIST *list;
|
|
{
|
|
extern int variable_context;
|
|
|
|
if (variable_context)
|
|
return (declare_internal (list, 1));
|
|
else
|
|
{
|
|
builtin_error ("Can only be used in a function");
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
}
|
|
|
|
declare_internal (list, no_modifiers)
|
|
register WORD_LIST *list;
|
|
int no_modifiers;
|
|
{
|
|
extern int variable_context, array_needs_making;
|
|
int flags_on = 0, flags_off = 0;
|
|
int any_failed = 0;
|
|
|
|
while (list)
|
|
{
|
|
register char *t = list->word->word;
|
|
int *flags;
|
|
|
|
if (*t != '+' && *t != '-')
|
|
break;
|
|
|
|
if (no_modifiers)
|
|
{
|
|
builtin_error ("Modifiers not allowed");
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
|
|
if (*t == '+')
|
|
flags = &flags_off;
|
|
else
|
|
flags = &flags_on;
|
|
|
|
t++;
|
|
|
|
while (*t)
|
|
{
|
|
if (*t == 'f')
|
|
*flags |= att_function, t++;
|
|
else if (*t == 'x')
|
|
*flags |= att_exported, t++, array_needs_making = 1;
|
|
else if (*t == 'r')
|
|
*flags |= att_readonly, t++;
|
|
else
|
|
{
|
|
builtin_error ("Bad flag `-%c'", *t);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
}
|
|
|
|
list = list->next;
|
|
}
|
|
|
|
/* If there are no more arguments left, then we just want to show
|
|
some variables. */
|
|
if (!list)
|
|
{
|
|
/* If we didn't allow modifiers, then this is the `local' command.
|
|
Perhaps the flag should be called `local_command' instead of
|
|
`no_modifiers'. At any rate, only show local variables defined
|
|
at this context level. */
|
|
if (no_modifiers)
|
|
{
|
|
extern SHELL_VAR *variable_list;
|
|
register SHELL_VAR *vlist = variable_list;
|
|
|
|
while (vlist)
|
|
{
|
|
if (!invisible_p (vlist) && vlist->context == variable_context)
|
|
print_assignment (vlist);
|
|
vlist = vlist->next;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!flags_on)
|
|
set_builtin ((WORD_LIST *)NULL);
|
|
else
|
|
set_or_show_attributes ((WORD_LIST *)NULL, flags_on);
|
|
}
|
|
|
|
fflush (stdout);
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
|
|
/* There are arguments left, so we are making variables. */
|
|
while (list)
|
|
{
|
|
char *value, *name = savestring (list->word->word);
|
|
int offset = assignment (name);
|
|
|
|
if (offset)
|
|
{
|
|
name[offset] = '\0';
|
|
value = name + offset + 1;
|
|
}
|
|
else
|
|
{
|
|
value = "";
|
|
}
|
|
|
|
/* If VARIABLE_CONTEXT has a non-zero value, then we are executing
|
|
inside of a function. This means we should make local variables,
|
|
not global ones. */
|
|
|
|
if (variable_context)
|
|
make_local_variable (name);
|
|
|
|
/* If we are declaring a function, then complain about it in some way.
|
|
We don't let people make functions by saying `typeset -f foo=bar'. */
|
|
|
|
if (flags_on & att_function)
|
|
{
|
|
builtin_error ("Can't use `-f' to make functions");
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
else
|
|
{
|
|
SHELL_VAR *bind_variable (), *find_variable (), *v;
|
|
|
|
if ((v = find_variable (name)) == (SHELL_VAR *)NULL)
|
|
v = bind_variable (name, "");
|
|
|
|
/* We are not allowed to rebind readonly variables that
|
|
already are readonly unless we are turning off the
|
|
readonly bit. */
|
|
if (flags_off & att_readonly)
|
|
flags_on &= ~att_readonly;
|
|
|
|
if (value && readonly_p (v) && (!(flags_off & att_readonly)))
|
|
{
|
|
builtin_error ("%s: readonly variable", name);
|
|
any_failed++;
|
|
goto hack_next_variable;
|
|
}
|
|
|
|
v->attributes |= flags_on;
|
|
v->attributes &= ~flags_off;
|
|
|
|
if (offset)
|
|
{
|
|
free (v->value);
|
|
v->value = savestring (value);
|
|
}
|
|
}
|
|
|
|
stupidly_hack_special_variables (name);
|
|
|
|
hack_next_variable:
|
|
free (name);
|
|
list = list->next;
|
|
}
|
|
return ((!any_failed) ? EXECUTION_SUCCESS : EXECUTION_FAILURE);
|
|
}
|
|
|
|
/* Set or display the mask used by the system when creating files. */
|
|
umask_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
if (list)
|
|
{
|
|
int new_umask;
|
|
new_umask = read_octal (list->word->word);
|
|
|
|
/* Note that other shells just let you set the umask to zero
|
|
by specifying a number out of range. This is a problem
|
|
with those shells. We don't change the umask if the input
|
|
is lousy. */
|
|
if (new_umask == -1)
|
|
{
|
|
builtin_error ("`%s' is not an octal number from 000 to 777",
|
|
list->word->word);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
umask (new_umask);
|
|
}
|
|
else
|
|
{
|
|
/* Display the UMASK for this user. */
|
|
int old_umask = umask (022);
|
|
umask (old_umask);
|
|
printf ("%03o\n", old_umask);
|
|
}
|
|
fflush (stdout);
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
|
|
/* Return the octal number parsed from STRING, or -1 to indicate
|
|
that the string contained a bad number. */
|
|
int
|
|
read_octal (string)
|
|
char *string;
|
|
{
|
|
int result = 0;
|
|
int digits = 0;
|
|
while (*string && *string >= '0' && *string < '8')
|
|
{
|
|
digits++;
|
|
result = (result * 8) + *string++ - '0';
|
|
}
|
|
|
|
if (!digits || result > 0777 || *string)
|
|
result = -1;
|
|
|
|
return (result);
|
|
}
|
|
|
|
/* Remove the variables specified in LIST from VARIABLE_LIST. */
|
|
unset_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
extern char **non_unsettable_vars;
|
|
int unset_function = 0;
|
|
int any_failed = 0;
|
|
|
|
while (list)
|
|
{
|
|
char *name = list->word->word;
|
|
|
|
if (strcmp (name, "-f") == 0)
|
|
{
|
|
list = list->next;
|
|
unset_function++;
|
|
continue;
|
|
}
|
|
|
|
if (find_name_in_list (name, non_unsettable_vars) > -1)
|
|
{
|
|
builtin_error ("%s: cannot unset", name);
|
|
any_failed++;
|
|
}
|
|
else
|
|
{
|
|
int tem;
|
|
|
|
if (unset_function)
|
|
tem = unbind_function (list->word->word);
|
|
else
|
|
tem = unbind_variable (list->word->word);
|
|
|
|
if (tem == -1)
|
|
any_failed++;
|
|
else
|
|
stupidly_hack_special_variables (list->word->word);
|
|
}
|
|
list = list->next;
|
|
}
|
|
return (any_failed ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
|
|
}
|
|
|
|
/* Run the command mentioned in list directly, without going through the
|
|
normal alias/function/builtin/filename lookup process. */
|
|
builtin_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
extern Function *find_shell_builtin ();
|
|
Function *function;
|
|
register char *command;
|
|
|
|
if (!list)
|
|
return (EXECUTION_SUCCESS);
|
|
|
|
command = (list->word->word);
|
|
function = find_shell_builtin (command);
|
|
|
|
if (!function)
|
|
{
|
|
builtin_error ("%s: Not a shell builtin!", command);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
else
|
|
{
|
|
this_command_name = command;
|
|
list = list->next;
|
|
return ((*function) (list));
|
|
}
|
|
}
|
|
|
|
decrement_variable (var)
|
|
int *var;
|
|
{
|
|
*var = *var - 1;
|
|
}
|
|
|
|
/* Run the commands mentioned in LIST without paying attention to shell
|
|
functions. */
|
|
int ignore_function_references = 0;
|
|
|
|
command_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
char *string, *string_list ();
|
|
int result;
|
|
|
|
begin_unwind_frame ("command_builtin");
|
|
add_unwind_protect (decrement_variable, &ignore_function_references);
|
|
ignore_function_references++;
|
|
|
|
string = string_list (list);
|
|
|
|
if (!string)
|
|
result = EXECUTION_SUCCESS;
|
|
else
|
|
result = parse_and_execute (string, "command");
|
|
|
|
run_unwind_frame ("command_builtin");
|
|
|
|
return (result);
|
|
}
|
|
|
|
char tdir[MAXPATHLEN];
|
|
/* Return a pretty pathname. If the first part of the pathname is
|
|
the same as $HOME, then replace that with `~'. */
|
|
char *
|
|
polite_directory_format (name)
|
|
char *name;
|
|
{
|
|
char *home = get_string_value ("HOME");
|
|
int l = home ? strlen (home) : 0;
|
|
|
|
if (l > 1 && strncmp (home, name, l) == 0 && (!name[l] || name[l] == '/'))
|
|
{
|
|
strcpy (tdir + 1, name + l);
|
|
tdir[0] = '~';
|
|
return (tdir);
|
|
}
|
|
else
|
|
return (name);
|
|
}
|
|
|
|
#ifdef PUSHD_AND_POPD
|
|
|
|
/* Some useful commands whose behaviour has been observed in csh. */
|
|
|
|
/* The list of remembered directories. */
|
|
char **pushd_directory_list = (char **)NULL;
|
|
|
|
/* Number of existing slots in this list. */
|
|
int directory_list_size = 0;
|
|
|
|
/* Offset to the end of the list. */
|
|
int directory_list_offset = 0;
|
|
|
|
pushd_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
char *temp, *current_directory, *get_working_directory ();
|
|
int j = directory_list_offset - 1;
|
|
char direction = '+';
|
|
|
|
/* If there is no argument list then switch current and
|
|
top of list. */
|
|
if (!list)
|
|
{
|
|
if (!directory_list_offset)
|
|
{
|
|
builtin_error ("No other directory");
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
|
|
current_directory = get_working_directory ("pushd");
|
|
if (!current_directory)
|
|
return (EXECUTION_FAILURE);
|
|
|
|
temp = pushd_directory_list[j];
|
|
pushd_directory_list[j] = current_directory;
|
|
goto change_to_temp;
|
|
}
|
|
else
|
|
{
|
|
direction = *(list->word->word);
|
|
if (direction == '+' || direction == '-')
|
|
{
|
|
int num;
|
|
if (1 == sscanf (&(list->word->word)[1], "%d", &num))
|
|
{
|
|
if (direction == '-')
|
|
num = directory_list_offset - num;
|
|
|
|
if (num > directory_list_offset)
|
|
{
|
|
if (!directory_list_offset)
|
|
builtin_error ("Directory stack empty");
|
|
else
|
|
builtin_error ("Stack contains only %d directories",
|
|
directory_list_offset + 1);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
else
|
|
{
|
|
/* Rotate the stack num times. Remember, the
|
|
current directory acts like it is part of the
|
|
stack. */
|
|
temp = get_working_directory ("pushd");
|
|
|
|
if (!num)
|
|
goto change_to_temp;
|
|
|
|
do
|
|
{
|
|
char *top =
|
|
pushd_directory_list[directory_list_offset - 1];
|
|
|
|
for (j = directory_list_offset - 2; j > -1; j--)
|
|
pushd_directory_list[j + 1] = pushd_directory_list[j];
|
|
|
|
pushd_directory_list[j + 1] = temp;
|
|
|
|
temp = top;
|
|
num--;
|
|
}
|
|
while (num);
|
|
|
|
temp = savestring (temp);
|
|
change_to_temp:
|
|
{
|
|
int tt = EXECUTION_FAILURE;
|
|
|
|
if (temp)
|
|
{
|
|
tt = cd_to_string (temp);
|
|
free (temp);
|
|
}
|
|
|
|
if ((tt == EXECUTION_SUCCESS) &&
|
|
(!find_variable ("pushd_silent")))
|
|
dirs_builtin ((WORD_LIST *)NULL);
|
|
|
|
return (tt);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Change to the directory in list->word->word. Save the current
|
|
directory on the top of the stack. */
|
|
current_directory = get_working_directory ("pushd");
|
|
if (!current_directory)
|
|
return (EXECUTION_FAILURE);
|
|
|
|
if (cd_builtin (list) == EXECUTION_SUCCESS)
|
|
{
|
|
if (directory_list_offset == directory_list_size)
|
|
{
|
|
if (!pushd_directory_list)
|
|
pushd_directory_list =
|
|
(char **)xmalloc ((directory_list_size = 10) * sizeof (char *));
|
|
else
|
|
pushd_directory_list =
|
|
(char **)xrealloc (pushd_directory_list,
|
|
(directory_list_size += 10) * sizeof (char *));
|
|
}
|
|
pushd_directory_list[directory_list_offset++] = current_directory;
|
|
|
|
if (!find_variable ("pushd_silent"))
|
|
dirs_builtin ((WORD_LIST *)NULL);
|
|
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
else
|
|
{
|
|
free (current_directory);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Print the current list of directories on the directory stack. */
|
|
dirs_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
register int i, format = 0;
|
|
char *temp, *polite_directory_format (), *get_working_directory ();
|
|
|
|
/* Maybe do long form? */
|
|
if (list)
|
|
{
|
|
if (strcmp (list->word->word, "-l") == 0)
|
|
format++;
|
|
|
|
if (!format || list->next)
|
|
{
|
|
builtin_error ("usage: dirs [ -l ]");
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
}
|
|
|
|
/* The first directory printed is always the current working directory. */
|
|
temp = get_working_directory ("dirs");
|
|
if (!temp)
|
|
temp = savestring ("<no directory>");
|
|
printf ("%s ", format ? temp : polite_directory_format (temp));
|
|
free (temp);
|
|
|
|
/* Now print any directories in the array. */
|
|
for (i = (directory_list_offset - 1); i > -1; i--)
|
|
printf ("%s ", format ? pushd_directory_list[i] :
|
|
polite_directory_format (pushd_directory_list[i]));
|
|
|
|
printf ("\n");
|
|
fflush (stdout);
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
|
|
/* Switch to the directory in NAME. This uses the cd_builtin to do the work,
|
|
so if the result is EXECUTION_FAILURE then an error message has already
|
|
been printed. */
|
|
cd_to_string (name)
|
|
char *name;
|
|
{
|
|
WORD_LIST *tlist = make_word_list (make_word (name), NULL);
|
|
int result = (cd_builtin (tlist));
|
|
dispose_words (tlist);
|
|
return (result);
|
|
}
|
|
|
|
/* Pop the directory stack, and then change to the new top of the stack.
|
|
If LIST is non-null it should consist of a word +N or -N, which says
|
|
what element to delete from the stack. The default is the top one. */
|
|
popd_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
register int i;
|
|
int which = 0;
|
|
char direction = '+';
|
|
|
|
if (list)
|
|
{
|
|
direction = *(list->word->word);
|
|
|
|
if ((direction != '+' && direction != '-') ||
|
|
(1 != sscanf (&((list->word->word)[1]), "%d", &which)))
|
|
{
|
|
builtin_error ("Bad arg `%s'", list->word->word);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
}
|
|
|
|
if (which > directory_list_offset || (!directory_list_offset && !which))
|
|
{
|
|
if (!directory_list_offset)
|
|
builtin_error ("Directory stack empty");
|
|
else
|
|
builtin_error ("Stack contains only %d directories",
|
|
directory_list_offset + 1);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
|
|
/* Handle case of no specification, or top of stack specification. */
|
|
if ((direction == '+' && which == 0) ||
|
|
(direction == '-' && which == directory_list_offset))
|
|
{
|
|
i = cd_to_string (pushd_directory_list[directory_list_offset - 1]);
|
|
if (i != EXECUTION_SUCCESS)
|
|
return (i);
|
|
free (pushd_directory_list[--directory_list_offset]);
|
|
}
|
|
else
|
|
{
|
|
/* Since an offset other than the top directory was specified,
|
|
remove that directory from the list and shift the remainder
|
|
of the list into place. */
|
|
|
|
if (direction == '+')
|
|
i = directory_list_offset - which;
|
|
else
|
|
i = which;
|
|
|
|
free (pushd_directory_list[i]);
|
|
directory_list_offset--;
|
|
|
|
/* Shift the remainder of the list into place. */
|
|
for (; i < directory_list_offset; i++)
|
|
pushd_directory_list[i] = pushd_directory_list[i + 1];
|
|
}
|
|
|
|
if (!find_variable ("pushd_silent"))
|
|
dirs_builtin ((WORD_LIST *)NULL);
|
|
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
|
|
#endif /* PUSHD_AND_POPD */
|
|
|
|
#ifdef ALIAS
|
|
|
|
/* Hack the alias command in a Korn shell way. */
|
|
alias_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
int any_failed = 0;
|
|
|
|
if (!list)
|
|
{
|
|
register int i;
|
|
|
|
if (!aliases)
|
|
return (EXECUTION_FAILURE);
|
|
|
|
for (i = 0; aliases[i]; i++)
|
|
print_alias (aliases[i]);
|
|
}
|
|
else
|
|
{
|
|
while (list)
|
|
{
|
|
register char *value, *name = list->word->word;
|
|
register int offset;
|
|
|
|
for (offset = 0; name[offset] && name[offset] != '='; offset++);
|
|
|
|
if (offset && name[offset] == '=')
|
|
{
|
|
name[offset] = '\0';
|
|
value = name + offset + 1;
|
|
|
|
add_alias (name, value);
|
|
}
|
|
else
|
|
{
|
|
ASSOC *t = find_alias (name);
|
|
if (t)
|
|
print_alias (t);
|
|
else
|
|
{
|
|
if (interactive)
|
|
builtin_error ("`%s' not found", name);
|
|
any_failed++;
|
|
}
|
|
}
|
|
list = list->next;
|
|
}
|
|
}
|
|
return (any_failed ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
|
|
}
|
|
|
|
/* Remove aliases named in LIST from the aliases database. */
|
|
unalias_builtin (list)
|
|
register WORD_LIST *list;
|
|
{
|
|
register ASSOC *alias;
|
|
int any_failed = 0;
|
|
|
|
while (list)
|
|
{
|
|
alias = find_alias (list->word->word);
|
|
|
|
if (alias)
|
|
remove_alias (alias->name);
|
|
else
|
|
{
|
|
if (interactive)
|
|
builtin_error ("`%s' not an alias", list->word->word);
|
|
|
|
any_failed++;
|
|
}
|
|
|
|
list = list->next;
|
|
}
|
|
return (any_failed ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
|
|
}
|
|
|
|
/* Output ALIAS in such a way as to allow it to be read back in. */
|
|
print_alias (alias)
|
|
ASSOC *alias;
|
|
{
|
|
register int i;
|
|
|
|
for (i = 0; alias->value[i] && !whitespace (alias->value[i]); i++);
|
|
|
|
if (alias->value[i])
|
|
printf ("alias %s=\"%s\"\n", alias->name, alias->value);
|
|
else
|
|
printf ("alias %s=%s\n", alias->name, alias->value);
|
|
|
|
fflush (stdout);
|
|
}
|
|
|
|
#endif /* ALIAS */
|
|
|
|
/* Wait for the pid in LIST to stop or die. If no arguments are given, then
|
|
wait for all of the active background processes of the shell and return
|
|
0. If a list of pids or job specs are given, return the exit status of
|
|
the last one waited for. */
|
|
wait_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
extern wait_for_background_pids ();
|
|
extern wait_for_single_pid ();
|
|
extern int job_control;
|
|
int status = EXECUTION_SUCCESS;
|
|
|
|
/* We support jobs or pids.
|
|
wait <pid-or-job> [pid-or-job ...] */
|
|
|
|
/* But wait without any arguments means to wait for all of the shell's
|
|
currently active background processes. */
|
|
if (!list)
|
|
{
|
|
wait_for_background_pids ();
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
|
|
while (list)
|
|
{
|
|
int pid;
|
|
if (digit (*(list->word->word)))
|
|
{
|
|
if (sscanf (list->word->word, "%d", &pid) == 1)
|
|
status = wait_for_single_pid (pid);
|
|
else
|
|
{
|
|
builtin_error ("`%s' is not a pid or job spec", list->word->word);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
}
|
|
#ifdef JOB_CONTROL
|
|
else if (job_control)
|
|
/* Must be a job spec. Check it out. */
|
|
{
|
|
int oldmask = sigblock (sigmask (SIGCHLD));
|
|
int job = get_job_spec (list);
|
|
|
|
if (job < 0 || job >= job_slots || !jobs[job])
|
|
{
|
|
if (job != DUP_JOB)
|
|
builtin_error ("No such job %s", list->word->word);
|
|
sigsetmask (oldmask);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
|
|
/* Job spec used. Wait for the last pid in the pipeline. */
|
|
|
|
sigsetmask (oldmask);
|
|
|
|
status = wait_for_job (job);
|
|
}
|
|
#endif /* JOB_CONTROL */
|
|
else
|
|
{
|
|
builtin_error ("%s is not a pid or legal job spec", list->word->word);
|
|
status = EXECUTION_FAILURE;
|
|
}
|
|
list = list->next;
|
|
}
|
|
return (status);
|
|
}
|
|
|
|
/* This is a lot like report_error (), but it is for shell builtins instead
|
|
of shell control structures, and it won't ever exit the shell. */
|
|
#if defined (HAVE_VPRINTF)
|
|
/* VARARGS */
|
|
builtin_error (va_alist)
|
|
va_dcl
|
|
{
|
|
extern char *this_command_name;
|
|
char *format;
|
|
va_list args;
|
|
|
|
fprintf (stderr, "%s: ", this_command_name);
|
|
va_start (args);
|
|
format = va_arg (args, char *);
|
|
vfprintf (stderr, format, args);
|
|
va_end (args);
|
|
fprintf (stderr, "\n");
|
|
}
|
|
|
|
#else
|
|
|
|
builtin_error (format, arg1, arg2)
|
|
char *format, *arg1, *arg2;
|
|
{
|
|
extern char *this_command_name;
|
|
|
|
fprintf (stderr, "%s: ", this_command_name);
|
|
fprintf (stderr, format, arg1, arg2);
|
|
fprintf (stderr, "\n");
|
|
fflush (stderr);
|
|
}
|
|
#endif /* HAVE_VPRINTF */
|
|
|
|
#ifdef JOB_CONTROL
|
|
/* **************************************************************** */
|
|
/* */
|
|
/* Job Control! */
|
|
/* */
|
|
/* **************************************************************** */
|
|
|
|
extern int job_control;
|
|
|
|
/* The `jobs' command. Prints outs a list of active jobs. If the
|
|
first argument is `-l', then the process id's are printed also. */
|
|
jobs_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
int long_form = 0;
|
|
|
|
if (!job_control)
|
|
return (EXECUTION_SUCCESS);
|
|
|
|
if (list && (strcmp (list->word->word, "-l") == 0))
|
|
long_form = 1;
|
|
|
|
list_jobs (long_form);
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
|
|
/* Suspend the current shell. Not hard to do. */
|
|
|
|
static SigHandler *old_cont, *old_tstp;
|
|
|
|
/* Continue handler. */
|
|
sighandler
|
|
suspend_continue ()
|
|
{
|
|
signal (SIGCONT, old_cont);
|
|
signal (SIGTSTP, old_tstp);
|
|
}
|
|
|
|
/* Suspending the shell. If -f is the arg, then do the suspend
|
|
no matter what. Otherwise, complain if a login shell. */
|
|
suspend_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
extern int shell_pgrp;
|
|
|
|
if (!job_control)
|
|
{
|
|
builtin_error ("Cannot suspend a shell without job control");
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
|
|
if (list)
|
|
if (strcmp (list->word->word, "-f") == 0)
|
|
goto do_suspend;
|
|
|
|
no_args (list);
|
|
|
|
if (login_shell)
|
|
{
|
|
builtin_error ("Can't suspend a login shell");
|
|
longjmp (top_level, DISCARD);
|
|
}
|
|
|
|
do_suspend:
|
|
old_cont = (SigHandler *)signal (SIGCONT, suspend_continue);
|
|
old_tstp = (SigHandler *)signal (SIGTSTP, SIG_DFL);
|
|
killpg (shell_pgrp, SIGTSTP);
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
|
|
/* How to bring a job into the foreground. */
|
|
fg_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
int fg_bit = 1;
|
|
register WORD_LIST *t = list;
|
|
|
|
if (!job_control)
|
|
return (EXECUTION_SUCCESS);
|
|
|
|
/* If the last arg on the line is '&', then start this job in the
|
|
background. Else, fg the job. */
|
|
|
|
while (t && t->next)
|
|
t = t->next;
|
|
|
|
if (t && strcmp (t->word->word, "&") == 0)
|
|
fg_bit = 0;
|
|
|
|
return (fg_bg (list, fg_bit));
|
|
}
|
|
|
|
/* How to put a job into the background. */
|
|
bg_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
if (!job_control)
|
|
return (EXECUTION_SUCCESS);
|
|
|
|
return (fg_bg (list, 0));
|
|
}
|
|
|
|
/* How to put a job into the foreground/background. */
|
|
fg_bg (list, foreground)
|
|
WORD_LIST *list;
|
|
int foreground;
|
|
{
|
|
int job = get_job_spec (list);
|
|
extern char *this_command_name;
|
|
|
|
if (job < 0 || job >= job_slots || !jobs[job])
|
|
{
|
|
if (job != DUP_JOB)
|
|
builtin_error ("No such job %s", list ? list->word->word : "");
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
|
|
/* Or if jobs[job]->pgrp == shell_pgrp. */
|
|
if (jobs[job]->job_control == 0)
|
|
{
|
|
builtin_error ("job %%%d started without job control", job + 1);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
|
|
if (start_job (job, foreground))
|
|
return (EXECUTION_SUCCESS);
|
|
else
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
|
|
/* Return the job spec found in LIST. */
|
|
get_job_spec (list)
|
|
WORD_LIST *list;
|
|
{
|
|
register char *word;
|
|
int job = NO_JOB;
|
|
int substring = 0;
|
|
|
|
if (!list)
|
|
return (current_job);
|
|
|
|
word = list->word->word;
|
|
|
|
if (!*word)
|
|
return (current_job);
|
|
|
|
if (*word == '%')
|
|
word++;
|
|
|
|
if (digit (*word) && (sscanf (word, "%d", &job) == 1))
|
|
return (job - 1);
|
|
|
|
switch (*word) {
|
|
|
|
case 0:
|
|
case '%':
|
|
case '+':
|
|
return (current_job);
|
|
|
|
case '-':
|
|
return (previous_job);
|
|
|
|
case '?': /* Substring search requested. */
|
|
substring++;
|
|
word++;
|
|
goto find_string;
|
|
|
|
default:
|
|
find_string:
|
|
{
|
|
register int i, wl = strlen (word);
|
|
for (i = 0; i < job_slots; i++)
|
|
{
|
|
if (jobs[i])
|
|
{
|
|
register PROCESS *p = jobs[i]->pipe;
|
|
extern char *strindex ();
|
|
do
|
|
{
|
|
if ((substring && strindex (p->command, word)) ||
|
|
(strncmp (p->command, word, wl) == 0))
|
|
if (job != NO_JOB)
|
|
{
|
|
builtin_error ("ambigious job spec: %s", word);
|
|
return (DUP_JOB);
|
|
}
|
|
else
|
|
job = i;
|
|
|
|
p = p->next;
|
|
}
|
|
while (p != jobs[i]->pipe);
|
|
}
|
|
}
|
|
return (job);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef CONTINUE_AFTER_KILL_ERROR
|
|
#define CONTINUE_OR_FAIL return (EXECUTION_FAILURE)
|
|
#else
|
|
#define CONTINUE_OR_FAIL goto continue_killing
|
|
#endif
|
|
|
|
/* Here is the kill builtin. We only have it so that people can type
|
|
kill -KILL %1? No, if you fill up the process table this way you
|
|
can kill some. */
|
|
kill_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
int signal = SIGTERM;
|
|
int pid;
|
|
|
|
if (!list)
|
|
return (EXECUTION_SUCCESS);
|
|
|
|
if (strcmp (list->word->word, "-l") == 0)
|
|
{
|
|
register int i, column = 0;
|
|
for (i = 1; i < NSIG; i++)
|
|
{
|
|
printf ("%d) %s", i, signal_name (i));
|
|
if (++column < 4)
|
|
printf ("\t");
|
|
else {
|
|
printf ("\n");
|
|
column = 0;
|
|
}
|
|
}
|
|
if (column != 0)
|
|
printf ("\n");
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
|
|
/* If the user specified a signal, use that. */
|
|
if (*(list->word->word) == '-')
|
|
{
|
|
signal = decode_signal (&(list->word->word)[1]);
|
|
if (signal == NO_SIG)
|
|
{
|
|
builtin_error ("bad signal spec `%s'", &(list->word->word)[1]);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
else
|
|
{
|
|
list = list->next;
|
|
}
|
|
}
|
|
|
|
while (list)
|
|
{
|
|
if (digit (*(list->word->word)))
|
|
{
|
|
if (sscanf (list->word->word, "%d", &pid) == 1)
|
|
{
|
|
if (kill_pid (pid, signal, 0) < 0)
|
|
goto signal_error;
|
|
}
|
|
else
|
|
{
|
|
builtin_error ("No such pid %d", pid);
|
|
CONTINUE_OR_FAIL;
|
|
}
|
|
}
|
|
else if (job_control) /* can't kill jobs if not using job control */
|
|
{ /* Must be a job spec. Check it out. */
|
|
int oldmask = sigblock (sigmask (SIGCHLD));
|
|
int job = get_job_spec (list);
|
|
|
|
if (job < 0 || job >= job_slots || !jobs[job])
|
|
{
|
|
if (job != DUP_JOB)
|
|
builtin_error ("No such job %s", list->word->word);
|
|
sigsetmask (oldmask);
|
|
CONTINUE_OR_FAIL;
|
|
}
|
|
|
|
/* Job spec used. Kill the process group. If the job was started
|
|
without job control, then its pgrp == shell_pgrp, so we have
|
|
to be careful. We take the pid of the first job in the pipeline
|
|
in that case. */
|
|
if (jobs[job]->job_control)
|
|
pid = jobs[job]->pgrp;
|
|
else
|
|
pid = jobs[job]->pipe->pid;
|
|
|
|
sigsetmask (oldmask);
|
|
|
|
if (kill_pid (pid, signal, 1) < 0)
|
|
{
|
|
signal_error:
|
|
if (errno == EPERM)
|
|
builtin_error ("(%d) - Not owner", pid);
|
|
else if (errno == ESRCH)
|
|
builtin_error ("(%d) - No such pid", pid);
|
|
else
|
|
builtin_error ("Invalid signal %d", signal);
|
|
CONTINUE_OR_FAIL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
builtin_error ("bad process specification `%s'", list->word->word);
|
|
CONTINUE_OR_FAIL;
|
|
}
|
|
continue_killing:
|
|
list = list->next;
|
|
}
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
|
|
#define DETACH
|
|
#ifdef DETACH
|
|
detach_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
int job = NO_JOB;
|
|
JOB_STATE job_state;
|
|
int oldmask;
|
|
PROCESS *process, *pipeline;
|
|
|
|
if (!job_control)
|
|
return (EXECUTION_SUCCESS);
|
|
|
|
job = get_job_spec (list);
|
|
|
|
oldmask = sigblock (sigmask (SIGCHLD));
|
|
job_state = JOBSTATE(job);
|
|
|
|
killpg (jobs[job]->pgrp, SIGSTOP);
|
|
while (jobs[job] && JOBSTATE (job) == JRUNNING)
|
|
sigpause (0);
|
|
|
|
/* Make the process group of the pipeline the same as init's. */
|
|
process = pipeline = jobs[job]->pipe;
|
|
do
|
|
{
|
|
setpgrp (process->pid, 1);
|
|
process = process -> next;
|
|
}
|
|
while (process != pipeline);
|
|
|
|
/* Return the pipeline to the state it was in before we stopped it. */
|
|
if (job_state == JRUNNING)
|
|
{
|
|
process = pipeline;
|
|
do
|
|
{
|
|
kill (process->pid, SIGCONT);
|
|
process = process -> next;
|
|
}
|
|
while (process != pipeline);
|
|
}
|
|
|
|
/* Remove the job from the job list. */
|
|
delete_job (job);
|
|
|
|
sigsetmask (oldmask);
|
|
}
|
|
#endif /* DETACH */
|
|
#endif /* JOB_CONTROL */
|