From 159415c3b1fb9c355abe08e217b99fd17726fd80 Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Sun, 6 Nov 2022 00:57:49 +0100 Subject: [PATCH] Implement profile(5) and shrc(5) in sh(1). --- login/login.c | 8 +++-- sh/Makefile | 2 ++ sh/profile.5 | 59 ++++++++++++++++++++++++++++++ sh/proper-sh.c | 1 + sh/sh.1 | 30 ++++++++++++++-- sh/sh.c | 75 +++++++++++++++++++++++++++++++------- sh/shrc.5 | 81 ++++++++++++++++++++++++++++++++++++++++++ share/init/single-user | 2 +- 8 files changed, 240 insertions(+), 18 deletions(-) create mode 100644 sh/profile.5 create mode 100644 sh/shrc.5 diff --git a/login/login.c b/login/login.c index c7f294b5..3c7c75de 100644 --- a/login/login.c +++ b/login/login.c @@ -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) ) diff --git a/sh/Makefile b/sh/Makefile index 5821c429..21d8e226 100644 --- a/sh/Makefile +++ b/sh/Makefile @@ -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 $@ diff --git a/sh/profile.5 b/sh/profile.5 new file mode 100644 index 00000000..6b6d9049 --- /dev/null +++ b/sh/profile.5 @@ -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 . diff --git a/sh/proper-sh.c b/sh/proper-sh.c index 72a425b6..48461550 100644 --- a/sh/proper-sh.c +++ b/sh/proper-sh.c @@ -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; } diff --git a/sh/sh.1 b/sh/sh.1 index 282efa51..268f0afb 100644 --- a/sh/sh.1 +++ b/sh/sh.1 @@ -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 diff --git a/sh/sh.c b/sh/sh.c index 09a397b6..7c7fb4b1 100644 --- a/sh/sh.c +++ b/sh/sh.c @@ -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, "", 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, "", 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, "", 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, "", 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); } diff --git a/sh/shrc.5 b/sh/shrc.5 new file mode 100644 index 00000000..77e83270 --- /dev/null +++ b/sh/shrc.5 @@ -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 . diff --git a/share/init/single-user b/share/init/single-user index 1c420b48..e877457a 100644 --- a/share/init/single-user +++ b/share/init/single-user @@ -6,4 +6,4 @@ need tty cd "$HOME" exit-code-meaning poweroff-reboot -exec "$SHELL" +exec "$SHELL" -l