Implement profile(5) and shrc(5) in sh(1).

This commit is contained in:
Jonas 'Sortie' Termansen 2022-11-06 00:57:49 +01:00
parent c6af3bc074
commit 159415c3b1
8 changed files with 240 additions and 18 deletions

View File

@ -214,13 +214,16 @@ bool login(const char* username)
int pipe_fds[2];
if ( pipe2(pipe_fds, O_CLOEXEC) < 0 )
return false;
char* login_shell;
if ( asprintf(&login_shell, "-%s", pwd->pw_shell) < 0 )
return close(pipe_fds[0]), close(pipe_fds[1]), false;
sigset_t oldset, sigttou;
sigemptyset(&sigttou);
sigaddset(&sigttou, SIGTTOU);
sigprocmask(SIG_BLOCK, &sigttou, &oldset);
pid_t child_pid = fork();
if ( child_pid < 0 )
return close(pipe_fds[0]), close(pipe_fds[1]), false;
return free(login_shell), close(pipe_fds[0]), close(pipe_fds[1]), false;
if ( child_pid == 0 )
{
sigdelset(&oldset, SIGINT);
@ -253,10 +256,11 @@ bool login(const char* username)
errno != ENOENT && errno != EACCES) ||
(execlp("/etc/session", "/etc/session", (const char*) NULL) < 0 &&
errno != ENOENT && errno != EACCES) ||
execlp(pwd->pw_shell, pwd->pw_shell, (const char*) NULL));
execlp(pwd->pw_shell, login_shell, (const char*) NULL));
write(pipe_fds[1], &errno, sizeof(errno));
_exit(127);
}
free(login_shell);
close(pipe_fds[1]);
int errnum;
if ( readall(pipe_fds[0], &errnum, sizeof(errnum)) < (ssize_t) sizeof(errnum) )

View File

@ -29,7 +29,9 @@ install: all
cp sh.1 $(DESTDIR)$(MANDIR)/man1/sh.1
ln -sf sh.1 $(DESTDIR)$(MANDIR)/man1/sortix-sh.1
mkdir -p $(DESTDIR)$(MANDIR)/man5
cp profile.5 $(DESTDIR)$(MANDIR)/man5/profile.5
cp proper-sh.5 $(DESTDIR)$(MANDIR)/man5/proper-sh.5
cp shrc.5 $(DESTDIR)$(MANDIR)/man5/shrc.5
sortix-sh: $(SORTIX_SH_SRCS) *.h
$(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) $(SORTIX_SH_SRCS) -o $@

59
sh/profile.5 Normal file
View File

@ -0,0 +1,59 @@
.Dd November 9, 2022
.Dt PROFILE 5
.Os
.Sh NAME
.Nm profile
.Nd login shell startup
.Sh SYNOPSIS
.Nm ~/.profile
.Nm /etc/profile
.Nm /etc/default/profile
.Sh DESCRIPTION
Interactive login shell sessions in
.Xr sh 1
execute the commands in the
.Nm
script upon startup, searching for the user's script at
.Pa ~/.profile ,
any system administrator provided script at
.Pa /etc/profile ,
or any operating system provided script at
.Pa /etc/default/profile ,
whichever exists first.
.Pp
The
.Xr shrc 5
script is run instead in interactive non-login shell sessions.
.Sh FILES
.Bl -tag -width "/etc/default/profile" -compact
.It Pa ~/.profile
The user's
.Nm
script.
.It Pa /etc/profile
The system administor provided
.Nm
script.
.It Pa /etc/default/profile
The operating system provided
.Nm
script.
.El
.Sh EXAMPLES
To portably run the
.Xr shrc 5
script upon startup of non-login interactive shells in all shells:
.Bd -literal -offset indent
export ENV="$HOME/.shrc"
.Ed
.Sh SEE ALSO
.Xr dash 1 ,
.Xr sh 1 ,
.Xr shrc 5
.Sh BUGS
.Xr sh 1
is currently primitive and cannot execute most scripts.
Beware of sharing the
.Nm
script between it and other shells such as
.Xr dash 1 .

View File

@ -42,6 +42,7 @@ static bool is_supported(int argc, char* argv[])
{
case 'e': break;
case 'i': break;
case 'l': break;
case 's': break;
default: return false;
}

30
sh/sh.1
View File

@ -6,10 +6,10 @@
.Nd shell command interpreter
.Sh SYNOPSIS
.Nm sh
.Op Fl ceis
.Op Fl ceils
.Op Ar script Oo argument ... Oc
.Nm sortix-sh
.Op Fl ceis
.Op Fl ceils
.Op Ar script Oo argument ... Oc
.Sh DESCRIPTION
.Nm
@ -50,6 +50,15 @@ argument contains the script's text instead of a path to the script file.
Exit if any command exit non-zero.
.It Fl i
Interactively read and execute commands.
.It Fl l
The shell is a login shell.
Interactive shells run the
.Xr profile 5
script on startup instead of the
.Xr shrc
script.
This option is set if the shell is invoked by a name starting with a dash
.Sq - .
.It Fl s
Read commands from the standard input (the default).
This option can be combined with the
@ -62,6 +71,12 @@ argument before reading normally from the standard input
.Nm
uses environment these variables:
.Bl -tag -width "HISTFILE"
.It Ev ENV
File to execute on non-login interactive startup instead of
.Pa ~/.shrc
per
.Xr shrc 5 .
This variable is subject to path expansion.
.It Ev HISTFILE
Save the shell history in this file.
The default is
@ -93,6 +108,9 @@ This variable takes precedence over
.El
.Sh FILES
.Bl -tag -width "/etc/proper-sh" -compact
.It Pa ~/.profile , /etc/profile , /etc/default/profile
.Xr profile 5
script whose commands are run on non-login interactive shell startup.
.It Pa /etc/proper-sh
Name of a better shell to use for non-interactive use per
.Xr proper-sh 5 .
@ -106,6 +124,12 @@ The saved shell history.
This location is controlled by the
.Ev HISTFILE
environment variable.
.It Pa ~/.shrc , /etc/shrc , /etc/default/shrc
.Xr shrc 5
script whose commands are run on login interactive shell startup.
The
.Ev ENV
environment variable overrides the search for the script if set.
.El
.Sh EXIT STATUS
.Nm
@ -113,6 +137,8 @@ exits with the same exit status as the last run command, or 0 if no command has
been run.
.Sh SEE ALSO
.Xr dash 1 ,
.Xr profile 5 ,
.Xr proper-sh 5 ,
.Xr session 5 ,
.Xr shrc 5 ,
.Xr login 8

75
sh/sh.c
View File

@ -2079,36 +2079,81 @@ static int run(FILE* fp,
return status;
}
static char* find_rc(bool login)
{
const char* env = getenv("ENV");
if ( !login && env )
{
// TODO: Path expansion.
char* result = strdup(env);
if ( !result )
err(1, "malloc");
return result;
}
const char* home = getenv("HOME");
const char* rcname = login ? "profile" : "shrc";
const char* dirs[] = { home, "/etc", "/etc/default" };
bool found = false;
for ( size_t i = 0; !found && i < sizeof(dirs) / sizeof(dirs[0]); i++ )
{
char* rc;
if ( asprintf(&rc, "%s%s%s", dirs[i], i ? "/" : "/.", rcname) < 0 )
err(1, "malloc");
if ( (found = !access(rc, F_OK)) )
return rc;
}
return NULL;
}
static int top(FILE* fp,
const char* fp_name,
bool interactive,
bool exit_on_error,
bool login,
bool* script_exited,
int status)
{
if ( interactive )
{
const char* home;
const char* home = getenv("HOME");
const char* histfile = getenv("HISTFILE");
if ( !histfile && (home = getenv("HOME")) )
if ( !histfile && home )
{
char* path;
if ( asprintf(&path, "%s/.sh_history", home) < 0 ||
setenv("HISTFILE", path, 1) < 0 )
err(1, "malloc");
free(path);
histfile = getenv("HISTFILE");
}
edit_line_history_load(&edit_state, histfile);
char* rc = find_rc(login);
if ( rc )
{
FILE* rcfp = fopen(rc, "r");
if ( !rcfp )
warn("%s", rc);
else
{
status = run(rcfp, rc, false, exit_on_error, script_exited,
status);
fclose(rcfp);
}
free(rc);
}
if ( *script_exited || (status != 0 && exit_on_error) )
return status;
edit_line_history_load(&edit_state, getenv("HISTFILE"));
}
int r = run(fp, fp_name, interactive, exit_on_error, script_exited, status);
status = run(fp, fp_name, interactive, exit_on_error, script_exited,
status);
if ( interactive )
edit_line_history_save(&edit_state, getenv("HISTFILE"));
return r;
return status;
}
static void compact_arguments(int* argc, char*** argv)
@ -2186,8 +2231,10 @@ int main(int argc, char* argv[])
bool flag_c_first_operand_is_command = false;
bool flag_e_exit_on_error = false;
bool flag_i_interactive = false;
bool flag_l_login = argv[0][0] == '-';
bool flag_s_stdin = false;
// The well implemented options are recognized in proper-sh.c.
const char* argv0 = argv[0];
for ( int i = 1; i < argc; i++ )
{
@ -2205,6 +2252,7 @@ int main(int argc, char* argv[])
case 'c': flag_c_first_operand_is_command = false; break;
case 'e': flag_e_exit_on_error = false; break;
case 'i': flag_i_interactive = false; break;
case 'l': flag_l_login = false; break;
case 's': flag_s_stdin = false; break;
default:
fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c);
@ -2220,6 +2268,7 @@ int main(int argc, char* argv[])
case 'c': flag_c_first_operand_is_command = true; break;
case 'e': flag_e_exit_on_error = true; break;
case 'i': flag_i_interactive = true; break;
case 'l': flag_l_login = true; break;
case 's': flag_s_stdin = true; break;
default:
fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c);
@ -2261,7 +2310,7 @@ int main(int argc, char* argv[])
char ppidstr[3 * sizeof(pid_t)];
snprintf(pidstr, sizeof(pidstr), "%ji", (intmax_t) getpid());
snprintf(ppidstr, sizeof(ppidstr), "%ji", (intmax_t) getppid());
setenv("SHELL", argv[0], 1);
setenv("SHELL", argv[0] + (argv[0][0] == '-'), 1);
setenv("$", pidstr, 1);
setenv("PPID", ppidstr, 1);
setenv("?", "0", 1);
@ -2291,7 +2340,7 @@ int main(int argc, char* argv[])
error(2, errno, "fmemopen");
status = top(fp, "<command-line>", false, flag_e_exit_on_error,
&script_exited, status);
flag_l_login, &script_exited, status);
fclose(fp);
@ -2302,7 +2351,7 @@ int main(int argc, char* argv[])
{
bool is_interactive = flag_i_interactive || isatty(fileno(stdin));
status = top(stdin, "<stdin>", is_interactive, flag_e_exit_on_error,
&script_exited, status);
flag_l_login, &script_exited, status);
if ( script_exited || (status != 0 && flag_e_exit_on_error) )
exit(status);
}
@ -2318,7 +2367,7 @@ int main(int argc, char* argv[])
bool is_interactive = flag_i_interactive || isatty(fileno(stdin));
status = top(stdin, "<stdin>", is_interactive, flag_e_exit_on_error,
&script_exited, status);
flag_l_login, &script_exited, status);
if ( script_exited || (status != 0 && flag_e_exit_on_error) )
exit(status);
}
@ -2335,8 +2384,8 @@ int main(int argc, char* argv[])
FILE* fp = fopen(path, "r");
if ( !fp )
error(127, errno, "%s", path);
status = top(fp, path, false, flag_e_exit_on_error, &script_exited,
status);
status = top(fp, path, false, flag_e_exit_on_error, flag_l_login,
&script_exited, status);
fclose(fp);
if ( script_exited || (status != 0 && flag_e_exit_on_error) )
exit(status);
@ -2345,7 +2394,7 @@ int main(int argc, char* argv[])
{
bool is_interactive = flag_i_interactive || isatty(fileno(stdin));
status = top(stdin, "<stdin>", is_interactive, flag_e_exit_on_error,
&script_exited, status);
flag_l_login, &script_exited, status);
if ( script_exited || (status != 0 && flag_e_exit_on_error) )
exit(status);
}

81
sh/shrc.5 Normal file
View File

@ -0,0 +1,81 @@
.Dd November 9, 2022
.Dt SHRC 5
.Os
.Sh NAME
.Nm shrc
.Nd login shell startup
.Sh SYNOPSIS
.Nm $ENV
.Nm ~/.shrc
.Nm /etc/shrc
.Nm /etc/default/shrc
.Sh DESCRIPTION
Interactive non-login shell sessions in
.Xr sh 1
execute the commands in the
.Nm
script upon startup, using the
.Pa ENV
environment variable with path expansion if set, and otherwise searching for the
user's script at
.Pa ~/.shrc ,
any system administrator provided script at
.Pa /etc/shrc ,
or any operating system provided script at
.Pa /etc/default/shrc ,
whichever exists first.
.Pp
The
.Xr profile 5
script is run instead in interactive login shell sessions.
.Sh ENVIRONMENT
.Bl -tag -width "ENV"
.It Ev ENV
File to execute on non-login interactive startup instead of searching the
standard paths for the
.Nm
script.
This variable is subject to path expansion.
.El
.Sh FILES
.Bl -tag -width "/etc/default/shrc" -compact
.It Pa ~/.shrc
The user's
.Nm
script.
.It Pa /etc/shrc
The system administor provided
.Nm
script.
.It Pa /etc/default/shrc
The operating system provided
.Nm
script.
.El
.Sh SEE ALSO
.Xr dash 1 ,
.Xr sh 1 ,
.Xr shrc 5
.Sh CAVEATS
.Xr dash
does not use the
.Nm
script, but instead only uses the
.Ev ENV
environment variable.
To invoke the
.Nm
script portably across all standard shells upon startup of non-interactive login
sells, set the
.Ev ENV
variable to the user's
.Nm
script per the example in
.Xr profile 5 .
.Sh BUGS
.Xr sh 1
is currently primitive and cannot execute most scripts.
Beware of sharing the
.Nm
script between it and other shells such as
.Xr dash 1 .

View File

@ -6,4 +6,4 @@ need tty
cd "$HOME"
exit-code-meaning poweroff-reboot
exec "$SHELL"
exec "$SHELL" -l