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]; int pipe_fds[2];
if ( pipe2(pipe_fds, O_CLOEXEC) < 0 ) if ( pipe2(pipe_fds, O_CLOEXEC) < 0 )
return false; 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; sigset_t oldset, sigttou;
sigemptyset(&sigttou); sigemptyset(&sigttou);
sigaddset(&sigttou, SIGTTOU); sigaddset(&sigttou, SIGTTOU);
sigprocmask(SIG_BLOCK, &sigttou, &oldset); sigprocmask(SIG_BLOCK, &sigttou, &oldset);
pid_t child_pid = fork(); pid_t child_pid = fork();
if ( child_pid < 0 ) 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 ) if ( child_pid == 0 )
{ {
sigdelset(&oldset, SIGINT); sigdelset(&oldset, SIGINT);
@ -253,10 +256,11 @@ bool login(const char* username)
errno != ENOENT && errno != EACCES) || errno != ENOENT && errno != EACCES) ||
(execlp("/etc/session", "/etc/session", (const char*) NULL) < 0 && (execlp("/etc/session", "/etc/session", (const char*) NULL) < 0 &&
errno != ENOENT && errno != EACCES) || 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)); write(pipe_fds[1], &errno, sizeof(errno));
_exit(127); _exit(127);
} }
free(login_shell);
close(pipe_fds[1]); close(pipe_fds[1]);
int errnum; int errnum;
if ( readall(pipe_fds[0], &errnum, sizeof(errnum)) < (ssize_t) sizeof(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 cp sh.1 $(DESTDIR)$(MANDIR)/man1/sh.1
ln -sf sh.1 $(DESTDIR)$(MANDIR)/man1/sortix-sh.1 ln -sf sh.1 $(DESTDIR)$(MANDIR)/man1/sortix-sh.1
mkdir -p $(DESTDIR)$(MANDIR)/man5 mkdir -p $(DESTDIR)$(MANDIR)/man5
cp profile.5 $(DESTDIR)$(MANDIR)/man5/profile.5
cp proper-sh.5 $(DESTDIR)$(MANDIR)/man5/proper-sh.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 sortix-sh: $(SORTIX_SH_SRCS) *.h
$(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) $(SORTIX_SH_SRCS) -o $@ $(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 'e': break;
case 'i': break; case 'i': break;
case 'l': break;
case 's': break; case 's': break;
default: return false; default: return false;
} }

30
sh/sh.1
View File

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

75
sh/sh.c
View File

@ -2079,36 +2079,81 @@ static int run(FILE* fp,
return status; 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, static int top(FILE* fp,
const char* fp_name, const char* fp_name,
bool interactive, bool interactive,
bool exit_on_error, bool exit_on_error,
bool login,
bool* script_exited, bool* script_exited,
int status) int status)
{ {
if ( interactive ) if ( interactive )
{ {
const char* home; const char* home = getenv("HOME");
const char* histfile = getenv("HISTFILE"); const char* histfile = getenv("HISTFILE");
if ( !histfile && (home = getenv("HOME")) ) if ( !histfile && home )
{ {
char* path; char* path;
if ( asprintf(&path, "%s/.sh_history", home) < 0 || if ( asprintf(&path, "%s/.sh_history", home) < 0 ||
setenv("HISTFILE", path, 1) < 0 ) setenv("HISTFILE", path, 1) < 0 )
err(1, "malloc"); err(1, "malloc");
free(path); 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 ) if ( interactive )
edit_line_history_save(&edit_state, getenv("HISTFILE")); edit_line_history_save(&edit_state, getenv("HISTFILE"));
return r; return status;
} }
static void compact_arguments(int* argc, char*** argv) 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_c_first_operand_is_command = false;
bool flag_e_exit_on_error = false; bool flag_e_exit_on_error = false;
bool flag_i_interactive = false; bool flag_i_interactive = false;
bool flag_l_login = argv[0][0] == '-';
bool flag_s_stdin = false; bool flag_s_stdin = false;
// The well implemented options are recognized in proper-sh.c.
const char* argv0 = argv[0]; const char* argv0 = argv[0];
for ( int i = 1; i < argc; i++ ) 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 'c': flag_c_first_operand_is_command = false; break;
case 'e': flag_e_exit_on_error = false; break; case 'e': flag_e_exit_on_error = false; break;
case 'i': flag_i_interactive = false; break; case 'i': flag_i_interactive = false; break;
case 'l': flag_l_login = false; break;
case 's': flag_s_stdin = false; break; case 's': flag_s_stdin = false; break;
default: default:
fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c); 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 'c': flag_c_first_operand_is_command = true; break;
case 'e': flag_e_exit_on_error = true; break; case 'e': flag_e_exit_on_error = true; break;
case 'i': flag_i_interactive = true; break; case 'i': flag_i_interactive = true; break;
case 'l': flag_l_login = true; break;
case 's': flag_s_stdin = true; break; case 's': flag_s_stdin = true; break;
default: default:
fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c); 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)]; char ppidstr[3 * sizeof(pid_t)];
snprintf(pidstr, sizeof(pidstr), "%ji", (intmax_t) getpid()); snprintf(pidstr, sizeof(pidstr), "%ji", (intmax_t) getpid());
snprintf(ppidstr, sizeof(ppidstr), "%ji", (intmax_t) getppid()); 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("$", pidstr, 1);
setenv("PPID", ppidstr, 1); setenv("PPID", ppidstr, 1);
setenv("?", "0", 1); setenv("?", "0", 1);
@ -2291,7 +2340,7 @@ int main(int argc, char* argv[])
error(2, errno, "fmemopen"); error(2, errno, "fmemopen");
status = top(fp, "<command-line>", false, flag_e_exit_on_error, status = top(fp, "<command-line>", false, flag_e_exit_on_error,
&script_exited, status); flag_l_login, &script_exited, status);
fclose(fp); fclose(fp);
@ -2302,7 +2351,7 @@ int main(int argc, char* argv[])
{ {
bool is_interactive = flag_i_interactive || isatty(fileno(stdin)); bool is_interactive = flag_i_interactive || isatty(fileno(stdin));
status = top(stdin, "<stdin>", is_interactive, flag_e_exit_on_error, 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) ) if ( script_exited || (status != 0 && flag_e_exit_on_error) )
exit(status); exit(status);
} }
@ -2318,7 +2367,7 @@ int main(int argc, char* argv[])
bool is_interactive = flag_i_interactive || isatty(fileno(stdin)); bool is_interactive = flag_i_interactive || isatty(fileno(stdin));
status = top(stdin, "<stdin>", is_interactive, flag_e_exit_on_error, 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) ) if ( script_exited || (status != 0 && flag_e_exit_on_error) )
exit(status); exit(status);
} }
@ -2335,8 +2384,8 @@ int main(int argc, char* argv[])
FILE* fp = fopen(path, "r"); FILE* fp = fopen(path, "r");
if ( !fp ) if ( !fp )
error(127, errno, "%s", path); error(127, errno, "%s", path);
status = top(fp, path, false, flag_e_exit_on_error, &script_exited, status = top(fp, path, false, flag_e_exit_on_error, flag_l_login,
status); &script_exited, status);
fclose(fp); fclose(fp);
if ( script_exited || (status != 0 && flag_e_exit_on_error) ) if ( script_exited || (status != 0 && flag_e_exit_on_error) )
exit(status); exit(status);
@ -2345,7 +2394,7 @@ int main(int argc, char* argv[])
{ {
bool is_interactive = flag_i_interactive || isatty(fileno(stdin)); bool is_interactive = flag_i_interactive || isatty(fileno(stdin));
status = top(stdin, "<stdin>", is_interactive, flag_e_exit_on_error, 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) ) if ( script_exited || (status != 0 && flag_e_exit_on_error) )
exit(status); 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" cd "$HOME"
exit-code-meaning poweroff-reboot exit-code-meaning poweroff-reboot
exec "$SHELL" exec "$SHELL" -l