Compare commits

...

11 Commits

Author SHA1 Message Date
Jonas 'Sortie' Termansen 2ace33176c Add sh(1) prompt variable expansion. 2022-11-20 22:15:51 +01:00
Jonas 'Sortie' Termansen 159415c3b1 Implement profile(5) and shrc(5) in sh(1). 2022-11-19 18:01:30 +01:00
Jonas 'Sortie' Termansen c6af3bc074 Add sh(1) history builtin. 2022-11-19 18:01:30 +01:00
Jonas 'Sortie' Termansen 3c69791078 Save sh(1) history in ~/.sh_history. 2022-11-19 18:01:30 +01:00
Jonas 'Sortie' Termansen f4152b3863 Document sh(1). 2022-11-16 21:10:46 +01:00
Jonas 'Sortie' Termansen 6bab3819e2 Replace /etc/proper-shells with /etc/proper-sh defaulting to dash. 2022-11-16 21:10:46 +01:00
Juhani Krekelä 212539c9de Update to dash-0.5.11.5. 2022-11-16 20:29:22 +01:00
Juhani Krekelä cc9c031e5e Update to libressl-3.6.1. 2022-11-16 20:23:51 +01:00
Jonas 'Sortie' Termansen a8c05711aa Switch bga(4) to the new PCI API. 2022-11-16 20:22:29 +01:00
Jonas 'Sortie' Termansen aefec2f7cd Don't warn on read-only /var/log filesystem. 2022-11-16 20:22:29 +01:00
Jonas 'Sortie' Termansen 07dd21146b Fix sh(1) changing foreground group when non-interactive. 2022-11-16 20:16:34 +01:00
17 changed files with 977 additions and 240 deletions

View File

@ -440,7 +440,8 @@ static bool log_open(struct log* log)
log->fd = open(log->path, logflags, log->file_mode);
if ( log->fd < 0 )
{
log_error(log, "", NULL);
if ( errno != EROFS )
log_error(log, "", NULL);
// Don't block daemon startup on read-only filesystems.
return errno == EROFS;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2014, 2016, 2017 Jonas 'Sortie' Termansen.
* Copyright (c) 2012, 2014, 2016, 2017, 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -238,6 +238,7 @@ bool BGADevice::Initialize()
Video::ConfigureDevice(this);
#if defined(__i386__) || defined(__x86_64__)
if ( guest_additions )
guest_additions->ReadyVideoDevice(device_index);
#endif
@ -609,24 +610,24 @@ static void TryInitializeDevice(uint32_t devaddr)
}
}
static bool OnDevice(uint32_t devaddr, const pciid_t*, const pcitype_t*, void*,
void*)
{
TryInitializeDevice(devaddr);
return true;
}
void Init()
{
pcifind_t bga_pcifind;
memset(&bga_pcifind, 255, sizeof(bga_pcifind));
bga_pcifind.vendorid = 0x1234;
bga_pcifind.deviceid = 0x1111;
pcifind_t patterns[2];
memset(&patterns[0], 255, sizeof(patterns[0]));
patterns[0].vendorid = 0x1234;
patterns[0].deviceid = 0x1111;
memset(&patterns[1], 255, sizeof(patterns[1]));
patterns[1].vendorid = 0x80EE;
patterns[1].deviceid = 0xBEEF;
uint32_t devaddr = 0;
while ( (devaddr = PCI::SearchForDevices(bga_pcifind, devaddr)) )
TryInitializeDevice(devaddr);
memset(&bga_pcifind, 255, sizeof(bga_pcifind));
bga_pcifind.vendorid = 0x80EE;
bga_pcifind.deviceid = 0xBEEF;
devaddr = 0;
while ( (devaddr = PCI::SearchForDevices(bga_pcifind, devaddr)) )
TryInitializeDevice(devaddr);
PCI::Search(OnDevice, NULL, patterns, 2);
}
} // namespace BGA

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

@ -1,7 +1,7 @@
diff -Paur --no-dereference -- dash.upstream/src/cd.c dash/src/cd.c
--- dash.upstream/src/cd.c
+++ dash/src/cd.c
@@ -252,7 +252,7 @@
@@ -268,7 +268,7 @@
STATIC char *
getpwd()
{
@ -30,7 +30,24 @@ diff -Paur --no-dereference -- dash.upstream/src/exec.c dash/src/exec.c
+#endif
}
static const char *legal_pathopt(const char *opt, const char *term, int magic)
diff -Paur --no-dereference -- dash.upstream/src/expand.c dash/src/expand.c
--- dash.upstream/src/expand.c
+++ dash/src/expand.c
@@ -44,6 +44,13 @@
#include <stdio.h>
#include <inttypes.h>
#include <limits.h>
+/* PATCH: Sortix does not currently have maximum filename or path length */
+#ifndef PATH_MAX
+#define PATH_MAX 4096
+#endif
+#ifndef NAME_MAX
+#define NAME_MAX 255
+#endif
#include <string.h>
#ifdef HAVE_FNMATCH
#include <fnmatch.h>
diff -Paur --no-dereference -- dash.upstream/src/histedit.c dash/src/histedit.c
--- dash.upstream/src/histedit.c
+++ dash/src/histedit.c
@ -53,7 +70,7 @@ diff -Paur --no-dereference -- dash.upstream/src/jobs.c dash/src/jobs.c
#ifdef BSD
#include <sys/wait.h>
#include <sys/time.h>
@@ -207,7 +206,7 @@
@@ -212,7 +211,7 @@
mflag = on = 0;
goto close;
}
@ -62,7 +79,7 @@ diff -Paur --no-dereference -- dash.upstream/src/jobs.c dash/src/jobs.c
break;
killpg(0, SIGTTIN);
} while (1);
@@ -457,7 +456,7 @@
@@ -461,7 +460,7 @@
if (mode & SHOW_PGID) {
/* just output process (group) id of pipeline */
@ -71,7 +88,7 @@ diff -Paur --no-dereference -- dash.upstream/src/jobs.c dash/src/jobs.c
return;
}
@@ -470,7 +469,7 @@
@@ -474,7 +473,7 @@
s[col - 2] = '-';
if (mode & SHOW_PID)
@ -80,7 +97,7 @@ diff -Paur --no-dereference -- dash.upstream/src/jobs.c dash/src/jobs.c
psend = ps + jp->nprocs;
@@ -490,7 +489,7 @@
@@ -494,7 +493,7 @@
do {
/* for each process */
@ -89,43 +106,57 @@ diff -Paur --no-dereference -- dash.upstream/src/jobs.c dash/src/jobs.c
start:
outfmt(
@@ -1136,7 +1135,7 @@
@@ -971,7 +970,7 @@
sigblockall(NULL);
vforked++;
- pid = vfork();
+ pid = fork();
if (!pid) {
forkchild(jp, n, FORK_FG);
@@ -1179,7 +1178,7 @@
do {
gotsigchld = 0;
- err = wait3(status, flags, NULL);
+ err = waitpid(-1, status, flags);
if (err || !block)
break;
do
- err = wait3(status, flags, NULL);
+ err = waitpid(-1, status, flags);
while (err < 0 && errno == EINTR);
if (err || (err = -!block))
diff -Paur --no-dereference -- dash.upstream/src/Makefile.in dash/src/Makefile.in
--- dash.upstream/src/Makefile.in
+++ dash/src/Makefile.in
@@ -170,9 +170,9 @@
AM_CFLAGS = $(COMMON_CFLAGS)
AM_CPPFLAGS = $(COMMON_CPPFLAGS)
AM_CFLAGS_FOR_BUILD = -g -O2 $(COMMON_CFLAGS)
-AM_CPPFLAGS_FOR_BUILD = $(COMMON_CPPFLAGS)
+AM_CPPFLAGS_FOR_BUILD = -DBSD=1 -DSHELL -DIFS_BROKEN
COMPILE_FOR_BUILD = \
- $(CC_FOR_BUILD) $(DEFAULT_INCLUDES) $(AM_CPPFLAGS_FOR_BUILD) \
+ unset HOST_SYSTEM_ROOT && $(CC_FOR_BUILD) $(DEFAULT_INCLUDES) $(AM_CPPFLAGS_FOR_BUILD) \
$(CPPFLAGS_FOR_BUILD) \
$(AM_CFLAGS_FOR_BUILD) $(CFLAGS_FOR_BUILD)
@@ -542,7 +542,11 @@
info-am:
-install-data-am: install-man
+install-data-am: install-man install-proper-shells
+
+install-proper-shells:
+ mkdir -p "$(DESTDIR)$(sysconfdir)/proper-shells"
+ echo dash > "$(DESTDIR)$(sysconfdir)/proper-shells/dash"
install-dvi: install-dvi-am
@@ -669,12 +669,14 @@
-rm -f ./$(DEPDIR)/alias.Po
-rm -f ./$(DEPDIR)/arith_yacc.Po
-rm -f ./$(DEPDIR)/arith_yylex.Po
+ -rm -f ./$(DEPDIR)/builtins.Po
-rm -f ./$(DEPDIR)/cd.Po
-rm -f ./$(DEPDIR)/error.Po
-rm -f ./$(DEPDIR)/eval.Po
-rm -f ./$(DEPDIR)/exec.Po
-rm -f ./$(DEPDIR)/expand.Po
-rm -f ./$(DEPDIR)/histedit.Po
+ -rm -f ./$(DEPDIR)/init.Po
-rm -f ./$(DEPDIR)/input.Po
-rm -f ./$(DEPDIR)/jobs.Po
-rm -f ./$(DEPDIR)/mail.Po
@@ -682,11 +684,14 @@
-rm -f ./$(DEPDIR)/memalloc.Po
-rm -f ./$(DEPDIR)/miscbltin.Po
-rm -f ./$(DEPDIR)/mystring.Po
+ -rm -f ./$(DEPDIR)/nodes.Po
-rm -f ./$(DEPDIR)/options.Po
-rm -f ./$(DEPDIR)/output.Po
-rm -f ./$(DEPDIR)/parser.Po
-rm -f ./$(DEPDIR)/redir.Po
-rm -f ./$(DEPDIR)/show.Po
+ -rm -f ./$(DEPDIR)/signames.Po
+ -rm -f ./$(DEPDIR)/syntax.Po
-rm -f ./$(DEPDIR)/system.Po
-rm -f ./$(DEPDIR)/trap.Po
-rm -f ./$(DEPDIR)/var.Po
diff -Paur --no-dereference -- dash.upstream/src/miscbltin.c dash/src/miscbltin.c
--- dash.upstream/src/miscbltin.c
+++ dash/src/miscbltin.c
@ -151,18 +182,18 @@ diff -Paur --no-dereference -- dash.upstream/src/output.c dash/src/output.c
diff -Paur --no-dereference -- dash.upstream/src/parser.c dash/src/parser.c
--- dash.upstream/src/parser.c
+++ dash/src/parser.c
@@ -32,10 +32,6 @@
@@ -32,10 +32,7 @@
* SUCH DAMAGE.
*/
-#if HAVE_ALLOCA_H
-#include <alloca.h>
#include <alloca.h>
-#endif
-
#include <stdlib.h>
#include "shell.h"
@@ -1090,10 +1086,12 @@
@@ -1137,10 +1134,12 @@
if (len) {
char *str;
@ -176,7 +207,7 @@ diff -Paur --no-dereference -- dash.upstream/src/parser.c dash/src/parser.c
}
}
}
@@ -1300,7 +1298,7 @@
@@ -1373,7 +1372,7 @@
str = NULL;
savelen = out - (char *)stackblock();
if (savelen > 0) {
@ -185,7 +216,7 @@ diff -Paur --no-dereference -- dash.upstream/src/parser.c dash/src/parser.c
memcpy(str, stackblock(), savelen);
}
if (oldstyle) {
@@ -1400,6 +1398,7 @@
@@ -1463,6 +1462,7 @@
if (str) {
memcpy(out, str, savelen);
STADJUST(savelen, out);

View File

@ -1,10 +1,12 @@
NAME=dash
BUILD_LIBRARIES=
VERSION=0.5.7
VERSION=0.5.11.5
DISTNAME=$NAME-$VERSION
COMPRESSION=tar.gz
ARCHIVE=$DISTNAME.$COMPRESSION
SHA256SUM=ae89fa9f1145b7748cf0740e1df04cd52fdf8a285da4911dd0f04983efba4e39
SHA256SUM=db778110891f7937985f29bf23410fe1c5d669502760f584e54e0e7b29e123bd
UPSTREAM_SITE='http://gondor.apana.org.au/~herbert/dash/files'
UPSTREAM_ARCHIVE=$ARCHIVE
LICENSE=BSD-3-Clause
BUILD_SYSTEM=configure
MAKE_VARS='V=1'

View File

@ -26,7 +26,7 @@ diff -Paur --no-dereference -- libssl.upstream/apps/nc/netcat.c libssl/apps/nc/n
unix_dg_tmp_socket = unix_dg_tmp_socket_buf;
}
}
@@ -1405,7 +1412,8 @@
@@ -1403,7 +1410,8 @@
memset(&cmsgbuf, 0, sizeof(cmsgbuf));
memset(&iov, 0, sizeof(iov));
@ -36,7 +36,7 @@ diff -Paur --no-dereference -- libssl.upstream/apps/nc/netcat.c libssl/apps/nc/n
mh.msg_controllen = sizeof(cmsgbuf.buf);
cmsg = CMSG_FIRSTHDR(&mh);
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
@@ -1442,6 +1450,7 @@
@@ -1440,6 +1448,7 @@
void
atelnet(int nfd, unsigned char *buf, unsigned int size)
{
@ -44,7 +44,7 @@ diff -Paur --no-dereference -- libssl.upstream/apps/nc/netcat.c libssl/apps/nc/n
unsigned char *p, *end;
unsigned char obuf[4];
@@ -1467,6 +1476,9 @@
@@ -1465,6 +1474,9 @@
if (atomicio(vwrite, nfd, obuf, 3) != 3)
warn("Write Error!");
}
@ -53,8 +53,8 @@ diff -Paur --no-dereference -- libssl.upstream/apps/nc/netcat.c libssl/apps/nc/n
+#endif
}
@@ -1581,16 +1593,20 @@
int
@@ -1578,16 +1590,20 @@
err(1, NULL);
}
if (Tflag != -1) {
@ -77,7 +77,7 @@ diff -Paur --no-dereference -- libssl.upstream/apps/nc/netcat.c libssl/apps/nc/n
errno = ENOPROTOOPT;
err(1, "set IPv6 traffic class not supported");
}
@@ -1608,13 +1624,16 @@
@@ -1605,13 +1621,16 @@
}
if (ttl != -1) {
@ -96,7 +96,7 @@ diff -Paur --no-dereference -- libssl.upstream/apps/nc/netcat.c libssl/apps/nc/n
}
if (minttl != -1) {
@@ -1652,7 +1671,9 @@
@@ -1649,7 +1668,9 @@
{ "af41", IPTOS_DSCP_AF41 },
{ "af42", IPTOS_DSCP_AF42 },
{ "af43", IPTOS_DSCP_AF43 },
@ -106,7 +106,7 @@ diff -Paur --no-dereference -- libssl.upstream/apps/nc/netcat.c libssl/apps/nc/n
{ "cs0", IPTOS_DSCP_CS0 },
{ "cs1", IPTOS_DSCP_CS1 },
{ "cs2", IPTOS_DSCP_CS2 },
@@ -1662,11 +1683,21 @@
@@ -1659,11 +1680,21 @@
{ "cs6", IPTOS_DSCP_CS6 },
{ "cs7", IPTOS_DSCP_CS7 },
{ "ef", IPTOS_DSCP_EF },

View File

@ -1,10 +1,10 @@
NAME=libssl
BUILD_LIBRARIES=
VERSION=3.5.3
VERSION=3.6.1
DISTNAME=libressl-$VERSION
COMPRESSION=tar.gz
ARCHIVE=$DISTNAME.$COMPRESSION
SHA256SUM=3ab5e5eaef69ce20c6b170ee64d785b42235f48f2e62b095fca5d7b6672b8b28
SHA256SUM=acfac61316e93b919c28d62d53037ca734de85c46b4d703f19fd8395cf006774
UPSTREAM_SITE=https://ftp.openbsd.org/pub/OpenBSD/LibreSSL
UPSTREAM_ARCHIVE=$ARCHIVE
LICENSE=OpenSSL

View File

@ -25,6 +25,13 @@ all: $(BINARIES)
install: all
mkdir -p $(DESTDIR)$(BINDIR)
install $(BINARIES) $(DESTDIR)$(BINDIR)
mkdir -p $(DESTDIR)$(MANDIR)/man1
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 $@

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016 Jonas 'Sortie' Termansen.
* Copyright (c) 2011-2016, 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -18,6 +18,9 @@
*/
#include <assert.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
@ -495,6 +498,161 @@ void edit_line_type_complete(struct edit_line* edit_state)
free(partial);
}
static size_t get_histsize(void)
{
const char* histfile = getenv("HISTSIZE");
if ( histfile && isdigit(*histfile) )
{
errno = 0;
char* end;
size_t value = strtoul(histfile, &end, 10);
// Enforce a reasonable upper limit to avoid OOM on misconfigurations,
// when users try to have unlimited history size, but the below saving
// will allocate an array of this size.
if ( 1048576 <= value )
value = 1048576;
if ( !errno && !*end )
return value;
}
return 500;
}
bool edit_line_history_load(struct edit_line* edit_state, const char* path)
{
if ( !path )
return true;
FILE* fp = fopen(path, "r");
if ( !fp )
{
if ( errno == ENOENT )
return true;
warn("%s", path);
return false;
}
char* line = NULL;
size_t line_size;
ssize_t line_length;
while ( 0 < (line_length = getline(&line, &line_size, fp)) )
{
if ( line[line_length - 1] == '\n' )
line[--line_length] = '\0';
edit_line_append_history(edit_state, line);
}
edit_state->history_begun = edit_state->history_used;
if ( ferror(fp) )
warn("read: %s", path);
free(line);
fclose(fp);
return true;
}
bool edit_line_history_save(struct edit_line* edit_state, const char* path)
{
size_t histsize = get_histsize();
if ( !path || !histsize )
return true;
// Avoid replacing the null device if used to disable the history.
if ( !strcmp(path, "/dev/null") )
return true;
// TODO: File locking is a better alternative to replacing the actual file.
// A temporary file rename is used to atomically replace the contents
// but may lose the race with other processes.
char* tmp;
if ( asprintf(&tmp, "%s.XXXXXXXXX", path) < 0 )
{
warn("malloc");
return false;
}
int fd = mkstemp(tmp);
if ( fd < 0 )
{
if ( errno == EROFS )
return true;
warn("%s", path);
return false;
}
FILE* fpout = fdopen(fd, "w");
if ( !fpout )
{
warn("%s", path);
close(fd);
unlink(tmp);
free(tmp);
return false;
}
// Merge with any updated history.
bool success = true;
char** history = calloc(sizeof(char*), histsize);
if ( !history )
warn("malloc"), success = false;
size_t first = 0;
size_t used = 0;
FILE* fpin;
if ( success && (fpin = fopen(path, "r")) )
{
char* line = NULL;
size_t line_size;
ssize_t line_length;
while ( 0 < (line_length = getline(&line, &line_size, fpin)) )
{
if ( line[line_length - 1] == '\n' )
line[--line_length] = '\0';
size_t n = (first + used) % histsize;
if ( history[n] )
free(history[n]);
history[n] = line;
if ( used == histsize )
first = (first + 1) % histsize;
else
used++;
line = NULL;
}
if ( ferror(fpin) )
warn("read: %s", path), success = false;
fclose(fpin);
}
else if ( errno != ENOENT )
warn("%s", path);
for ( size_t i = edit_state->history_begun;
success && i < edit_state->history_used;
i++ )
{
char* line = strdup(edit_state->history[i]);
if ( !line )
{
warn("malloc");
success = false;
break;
}
size_t n = (first + used) % histsize;
if ( history[n] )
free(history[n]);
history[n] = line;
if ( used == histsize )
first = (first + 1) % histsize;
else
used++;
line = NULL;
}
for ( size_t i = 0; i < used; i++ )
{
size_t n = (first + i) % histsize;
char* line = history[n];
if ( success && fprintf(fpout, "%s\n", line) < 0 )
warn("%s", tmp), success = false;
free(line);
}
int ret = fclose(fpout);
if ( success && ret == EOF )
warn("%s", path), success = false;
if ( success && rename(tmp, path) < 0 )
warn("rename: %s -> %s", tmp, path), success = false;
if ( !success )
unlink(tmp);
free(tmp);
return success;
}
#define SORTIX_LFLAGS (ISORTIX_KBKEY | ISORTIX_CHARS_DISABLE | ISORTIX_32BIT | \
ISORTIX_NONBLOCK | ISORTIX_TERMMODE)

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016 Jonas 'Sortie' Termansen.
* Copyright (c) 2011-2016, 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -38,6 +38,7 @@ struct edit_line
size_t history_used;
size_t history_length;
size_t history_target;
size_t history_begun;
void* check_input_incomplete_context;
bool (*check_input_incomplete)(void*, const char*);
void* trap_eof_opportunity_context;
@ -81,6 +82,8 @@ void edit_line_type_clear(struct edit_line* edit_state);
void edit_line_type_delete_word_before(struct edit_line* edit_state);
int edit_line_completion_sort(const void* a_ptr, const void* b_ptr);
void edit_line_type_complete(struct edit_line* edit_state);
bool edit_line_history_load(struct edit_line* edit_state, const char* path);
bool edit_line_history_save(struct edit_line* edit_state, const char* path);
void edit_line(struct edit_line* edit_state);
#endif

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 .

49
sh/proper-sh.5 Normal file
View File

@ -0,0 +1,49 @@
.Dd November 16, 2022
.Dt PROPER-SH 5
.Os
.Sh NAME
.Nm proper-sh
.Nd name of a better non-interactive shell
.Sh SYNOPSIS
.Nm /etc/proper-sh
.Sh DESCRIPTION
The
.Xr sh 1
program is a thin wrapper that invokes
.Xr sortix-sh 1
on interactive use, but this shell is primitive and cannot execute most scripts,
so non-interactive uses of
.Xr sh 1
instead searches for the name of a better shell to execute in this order:
.Pp
.Bl -bullet -compact
.It
The shell named in the
.Ev SORTIX_SH_BACKEND
environment variable.
.It
The shell named in the
.Pa /etc/proper-sh
file.
.It
.Xr dash 1 ,
if installed.
.It
.Sy sortix-sh ,
if no better shell was found.
.El
.Sh ENVIRONMENT
.Bl -tag -width "SORTIX_SH_BACKEND"
.It Ev SORTIX_SH_BACKEND
The name of a better non-interactive shell, taking precedence if set over the
.Pa /etc/proper-sh
file.
.El
.Sh FILES
.Bl -tag -width "/etc/proper-sh" -compact
.It Pa /etc/proper-sh
File containing the name of a better non-interactive shell.
.El
.Sh SEE ALSO
.Xr dash 1 ,
.Xr sh 1

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014 Jonas 'Sortie' Termansen.
* Copyright (c) 2014, 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -17,114 +17,85 @@
* Forward execution to the best shell.
*/
#include <sys/wait.h>
#include <dirent.h>
#include <fcntl.h>
#include <err.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
const char* getenv_safe(const char* name, const char* def)
static bool is_supported(int argc, char* argv[])
{
const char* ret = getenv(name);
return ret ? ret : def;
}
bool is_existing_shell(const char* candidate)
{
pid_t child_pid = fork();
if ( child_pid < 0 )
return false;
if ( !child_pid )
int i;
for ( i = 1; i < argc; i++ )
{
close(0);
close(1);
close(2);
open("/dev/null", O_RDONLY);
open("/dev/null", O_WRONLY);
open("/dev/null", O_WRONLY);
execlp("which", "which", "--", candidate, (const char*) NULL);
exit(127);
}
int status;
waitpid(child_pid, &status, 0);
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
}
char* search_for_proper_shell(void)
{
if ( getenv("SORTIX_SH_BACKEND") )
{
if ( !getenv("SORTIX_SH_BACKEND")[0] )
return NULL;
if ( is_existing_shell(getenv("SORTIX_SH_BACKEND")) )
return strdup(getenv("SORTIX_SH_BACKEND"));
}
const char* backends_dir_path =
getenv_safe("SORTIX_SH_BACKENDS_DIR", "/etc/proper-shells");
struct dirent** shell_entries;
int num_shell_entries = scandir(backends_dir_path, &shell_entries, NULL, alphasort);
if ( 0 <= num_shell_entries )
{
char* result = NULL;
for ( int i = 0; i < num_shell_entries; i++ )
{
struct dirent* entry = shell_entries[i];
const char* name = entry->d_name;
if ( !strcmp(name, ".") || !strcmp(name, "..") )
continue;
size_t path_length = strlen(backends_dir_path) + 1 + strlen(name);
char* path = (char*) malloc(path_length + 1);
if ( !path )
continue;
stpcpy(stpcpy(stpcpy(path, backends_dir_path), "/"), name);
FILE* fp = fopen(path, "r");
free(path);
if ( !fp )
continue;
size_t result_size = 0;
ssize_t result_length = getline(&result, &result_size, fp);
fclose(fp);
if ( result_length < 0 )
{
free(result);
continue;
}
if ( result[result_length-1] == '\n' )
result[--result_length] = '\0';
if ( !is_existing_shell(result) )
{
free(result);
result = NULL;
continue;
}
const char* arg = argv[i];
if ( (arg[0] != '-' && arg[0] != '+') || !arg[1] )
break; // Intentionally not continue and note '+' support.
if ( !strcmp(arg, "--") )
break;
if ( arg[0] == '+' || arg[1] != '-' )
{
char c;
while ( (c = *++arg) ) switch ( c )
{
case 'e': break;
case 'i': break;
case 'l': break;
case 's': break;
default: return false;
}
}
for ( int i = 0; i < num_shell_entries; i++ )
free(shell_entries[i]);
free(shell_entries);
if ( result )
return result;
else if ( !strcmp(arg, "--help") )
;
else if ( !strcmp(arg, "--version") )
;
else
return false;
}
return NULL;
return i == argc;
}
int main(int argc, char* argv[])
{
if ( argc == 1 && isatty(0) && isatty(1) )
execvp("sortix-sh", argv);
if ( !isatty(0) || !isatty(1) || !is_supported(argc, argv) )
{
const char* env = getenv("SORTIX_SH_BACKEND");
if ( env && env[0] )
{
execvp(env, argv);
if ( errno != ENOENT )
err(127, "%s", env);
}
char* proper_shell = search_for_proper_shell();
if ( proper_shell )
execvp(proper_shell, argv);
free(proper_shell);
FILE* fp = fopen("/etc/proper-sh", "r");
if ( !fp && errno != ENOENT )
err(127, "/etc/proper-sh");
if ( fp )
{
char* proper_sh = NULL;
size_t proper_sh_size = 0;
ssize_t proper_sh_length = getline(&proper_sh, &proper_sh_size, fp);
if ( proper_sh_length < 0 )
err(127, "/etc/proper-sh");
if ( proper_sh_length && proper_sh[proper_sh_length - 1] == '\n' )
proper_sh[--proper_sh_length] = '\0';
if ( proper_sh[0] )
{
execvp(proper_sh, argv);
if ( errno != ENOENT )
err(127, "%s", proper_sh);
}
free(proper_sh);
fclose(fp);
}
execvp("dash", argv);
if ( errno != ENOENT )
err(127, "dash");
}
execvp("sortix-sh", argv);
return 127;
err(127, "sortix-sh");
}

148
sh/sh.1 Normal file
View File

@ -0,0 +1,148 @@
.Dd November 9, 2022
.Dt SH 1
.Os
.Sh NAME
.Nm sh
.Nd shell command interpreter
.Sh SYNOPSIS
.Nm sh
.Op Fl ceils
.Op Ar script Oo argument ... Oc
.Nm sortix-sh
.Op Fl ceils
.Op Ar script Oo argument ... Oc
.Sh DESCRIPTION
.Nm
is the command line interpreter for the shell language.
It reads and executes commands from the standard input or the
.Ar script
file if specified.
.Nm
is interactive if the standard input is a terminal and no
.Ar script
file was specified.
.Pp
The standard shell
.Nm sortix-sh
is currently primitive and cannot execute most scripts.
.Nm sh
is currently a thin wrapper that detects non-interactive use and invokes a
better shell instead, named in the
.Ev SORTIX_SH_BACKEND
environment variable if set, or named in
.Xr proper-sh 5
if it exists, and otherwise
.Xr dash 1
is invoked.
.Pp
The options can be unset by prefixing them with a plus
.Sq +
instead of a dash
.Sq - .
.Pp
The options are as follows:
.Bl -tag -width "12345678"
.It Fl c
The
.Ar script
argument contains the script's text instead of a path to the script file.
.It Fl e
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
.Fl c
option to execute the script text in the
.Ar script
argument before reading normally from the standard input
.El
.Sh ENVIRONMENT
.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
.Pa ~/.sh_history .
.It Ev HISTSIZE
Maximum number of commands in the saved shell history.
The default is 500.
.It Ev HOME
The user's home directory
.Sq ( ~ ) .
.It Ev PATH
The colon-separated list of directory paths to search for programs.
.It Ev PS1
Interactive shell prompt when expecting a new command.
.It Ev PS2
Interactive shell prompt when the current command continues onto another line.
.It Ev PWD
Set to the current working directory.
.It Ev SHELL
Set to
.Nm .
.It Ev SHLVL
Depth of recursive shell sessions.
The outermost interactive shell (depth 1) will currently refuse to exit on an
end-of-file condition (^D) when on the
.Pa /dev/tty1
terminal to avoid accidentally powering off the machine.
.It Ev SORTIX_SH_BACKEND
Name of a better shell to use for non-interactive use per
.Xr proper-sh 5 .
This variable takes precedence over
.Pa /etc/proper-sh .
.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 .
The
.Ev SORTIX_SH_BACKEND
environment variable takes precedence over this file if set.
.Xr dash 1
is used by default if it is installed.
.It Pa ~/.sh_history
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
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

366
sh/sh.c
View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016 Jonas 'Sortie' Termansen.
* Copyright (c) 2011-2016, 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -23,6 +23,7 @@
#include <assert.h>
#include <ctype.h>
#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <error.h>
#include <fcntl.h>
@ -38,6 +39,7 @@
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
#include <wchar.h>
#include <wctype.h>
@ -51,17 +53,19 @@
#include "showline.h"
#include "util.h"
const char* builtin_commands[] =
static const char* builtin_commands[] =
{
"cd",
"exit",
"unset",
"clearenv",
"history",
(const char*) NULL,
};
int status = 0;
bool foreground_shell;
static bool foreground_shell;
static int status = 0;
static struct edit_line edit_state;
static bool is_proper_absolute_path(const char* path)
{
@ -1295,7 +1299,7 @@ struct execute_result execute(char** tokens,
// foreground process group, but bar dies prior to foo's tcsetpgrp
// call, because then the shell would run tcsetpgrp to take back
// control, and only then would foo do its tcsetpgrp call.
while ( foreground_shell && pgid == -1 && tcgetpgrp(0) != childpid )
while ( interactive && pgid == -1 && tcgetpgrp(0) != childpid )
sched_yield();
struct execute_result result;
@ -1306,7 +1310,7 @@ struct execute_result execute(char** tokens,
}
setpgid(0, pgid != -1 ? pgid : 0);
if ( foreground_shell && pgid == -1 )
if ( interactive && pgid == -1 )
{
sigset_t oldset, sigttou;
sigemptyset(&sigttou);
@ -1339,6 +1343,19 @@ struct execute_result execute(char** tokens,
}
}
if ( strcmp(argv[0], "history") == 0 )
{
for ( size_t i = 0; i < edit_state.history_used; i++ )
{
const char* line = edit_state.history[i];
if ( fprintf(stdout, "%5zu %s\n", i + 1, line) < 0 )
err(1, "stdout");
}
if ( fflush(stdout) == EOF )
err(1, "stdout");
exit(0);
}
execvp(argv[0], argv);
if ( interactive && errno == ENOENT )
@ -1491,12 +1508,15 @@ readcmd:
// as that may have side effects if a process checks for this
// behavior and then unexpectedly the shell takes back support
// without the usual ^Z mechanism.
sigset_t oldset, sigttou;
sigemptyset(&sigttou);
sigaddset(&sigttou, SIGTTOU);
sigprocmask(SIG_BLOCK, &sigttou, &oldset);
tcsetpgrp(0, getpgid(0));
sigprocmask(SIG_SETMASK, &oldset, NULL);
if ( interactive )
{
sigset_t oldset, sigttou;
sigemptyset(&sigttou);
sigaddset(&sigttou, SIGTTOU);
sigprocmask(SIG_BLOCK, &sigttou, &oldset);
tcsetpgrp(0, getpgid(0));
sigprocmask(SIG_SETMASK, &oldset, NULL);
}
pgid = -1;
status = 0;
goto readcmd;
@ -1520,12 +1540,15 @@ readcmd:
status = 1;
return status;
}
sigset_t oldset, sigttou;
sigemptyset(&sigttou);
sigaddset(&sigttou, SIGTTOU);
sigprocmask(SIG_BLOCK, &sigttou, &oldset);
tcsetpgrp(0, getpgid(0));
sigprocmask(SIG_SETMASK, &oldset, NULL);
if ( interactive )
{
sigset_t oldset, sigttou;
sigemptyset(&sigttou);
sigaddset(&sigttou, SIGTTOU);
sigprocmask(SIG_BLOCK, &sigttou, &oldset);
tcsetpgrp(0, getpgid(0));
sigprocmask(SIG_SETMASK, &oldset, NULL);
}
if ( WIFSIGNALED(exitstatus) && WTERMSIG(exitstatus) == SIGINT )
printf("^C\n");
else if ( WIFSIGNALED(exitstatus) && WTERMSIG(exitstatus) != SIGPIPE )
@ -1834,6 +1857,152 @@ size_t do_complete(char*** completions_ptr,
return *completions_ptr = completions, completions_count;
}
static void eval_ps_append_c(struct stringbuf* buf, char c)
{
if ( c == '\\' || c == '\'' || c == '"' || c == '$' || c == '`' )
stringbuf_append_c(buf, '\\');
stringbuf_append_c(buf, c);
}
static void eval_ps_append(struct stringbuf* buf, const char* str)
{
for ( size_t i = 0; str[i]; i++ )
eval_ps_append_c(buf, str[i]);
}
static char* eval_ps(const char* ps)
{
struct stringbuf buf;
stringbuf_begin(&buf);
bool escaped = false;
while ( *ps )
{
char c = *ps++;
if ( !escaped && c == '\\' )
{
escaped = true;
continue;
}
else if ( escaped && '0' <= c && c <= '7' )
{
unsigned char byte = c - '0';
if ( '0' <= *ps && *ps <= '7' )
{
byte = byte * 8 + *ps++ - '0';
if ( byte <= 037 && '0' <= *ps && *ps <= '7' )
byte = byte * 8 + *ps++ - '0';
}
eval_ps_append_c(&buf, byte);
}
else if ( escaped && c == 'a' )
eval_ps_append_c(&buf, '\a');
else if ( escaped && c == 'e' )
eval_ps_append_c(&buf, '\e');
else if ( escaped && (c == 'h' || c == 'H') )
{
char hostname[HOST_NAME_MAX + 1] = "?";
gethostname(hostname, sizeof(hostname));
if ( c == 'h' )
hostname[strcspn(hostname, ".")] = '\0';
eval_ps_append(&buf, hostname);
}
else if ( escaped && c == 'l' )
{
char* tty = ttyname(0);
if ( tty )
eval_ps_append(&buf, basename(tty));
else
eval_ps_append_c(&buf, '?');
}
else if ( escaped && c == 'n' )
eval_ps_append_c(&buf, '\n');
else if ( escaped && c == 'r' )
eval_ps_append_c(&buf, '\r');
else if ( escaped && c == 's' )
{
const char* argv0 = getenv("0");
if ( !argv0 )
argv0 = program_invocation_short_name;
char* base = strdup(argv0);
if ( !base )
eval_ps_append_c(&buf, '?');
else
{
eval_ps_append(&buf, basename(base));
free(base);
}
}
else if ( escaped && (c == 't' || c == 'T' || c == '@' || c == 'A') )
{
const char* format = "";
switch ( c )
{
case 't': format = "%H:%M:%S"; break;
case 'T': format = "%I:%M:%S"; break;
case '@': format = "%I:%M %p"; break;
case 'A': format = "%H:%M"; break;
}
time_t now = time(NULL);
struct tm tm;
localtime_r(&now, &tm);
char buffer[16] = "";
strftime(buffer, sizeof(buffer), format, &tm);
eval_ps_append(&buf, buffer);
}
else if ( escaped && c == 'u' )
{
char* user = getlogin();
eval_ps_append(&buf, user ? user : "?");
}
else if ( escaped && (c == 'w' || c == 'W') )
{
char* dir = get_current_dir_name();
const char* home = getenv("HOME");
if ( !dir )
eval_ps_append_c(&buf, '?');
else if ( c == 'w' )
{
size_t home_len = home ? strlen(home) : 0;
if ( home_len && !strncmp(dir, home, home_len) )
{
eval_ps_append_c(&buf, '~');
eval_ps_append(&buf, dir + home_len);
}
else
eval_ps_append(&buf, dir);
}
else if ( home && !strcmp(dir, home) )
eval_ps_append_c(&buf, '~');
else
eval_ps_append(&buf, basename(dir));
free(dir);
}
else if ( escaped && c == '$' )
eval_ps_append_c(&buf, getuid() == 0 ? '#' : '$');
else if ( escaped && (c == '[' || c == ']') )
{
// TODO: Ignoring this sequence when predicting cursor position.
}
else
{
if ( escaped || c == '\'' || c == '"' )
stringbuf_append_c(&buf, '\\');
stringbuf_append_c(&buf, c);
}
escaped = false;
}
char* string = stringbuf_finish(&buf);
if ( !string )
return NULL;
char* expanded = token_expand_variables(string);
free(string);
if ( !expanded )
return NULL;
char* finalized = token_finalize(expanded);
free(expanded);
return finalized;
}
struct sh_read_command
{
char* command;
@ -1846,7 +2015,6 @@ void read_command_interactive(struct sh_read_command* sh_read_command)
{
update_env();
static struct edit_line edit_state; // static to preserve command history.
edit_state.in_fd = 0;
edit_state.out_fd = 1;
edit_state.check_input_incomplete_context = NULL;
@ -1856,45 +2024,15 @@ void read_command_interactive(struct sh_read_command* sh_read_command)
edit_state.complete_context = NULL;
edit_state.complete = do_complete;
char* current_dir = get_current_dir_name();
const char* print_username = getlogin();
if ( !print_username )
print_username = getuid() == 0 ? "root" : "?";
char hostname[HOST_NAME_MAX + 1];
if ( gethostname(hostname, sizeof(hostname)) < 0 )
strlcpy(hostname, "(none)", sizeof(hostname));
const char* print_hostname = hostname;
const char* print_dir = current_dir ? current_dir : "?";
const char* home_dir = getenv_safe("HOME");
const char* print_dir_1 = print_dir;
const char* print_dir_2 = "";
char prompt_char = getuid() == 0 ? '#' : '$';
size_t home_dir_len = strlen(home_dir);
if ( home_dir_len && strncmp(print_dir, home_dir, home_dir_len) == 0 )
{
print_dir_1 = "~";
print_dir_2 = print_dir + home_dir_len;
}
char* ps1;
asprintf(&ps1, "\e[;1;32m%s@%s \e[1;34m%s%s %c\e[m ",
print_username,
print_hostname,
print_dir_1,
print_dir_2,
prompt_char);
free(current_dir);
edit_state.ps1 = ps1;
edit_state.ps2 = "> ";
const char* def_ps1 = "\\033[;1;32m\\u@\\H \\033[1;34m\\w \\$\\033[m ";
const char* def_ps2 = "> ";
edit_state.ps1 = eval_ps(getenv_safe_def("PS1", def_ps1));
edit_state.ps2 = eval_ps(getenv_safe_def("PS2", def_ps2));
edit_line(&edit_state);
free(ps1);
free((char*) edit_state.ps1);
free((char*) edit_state.ps2);
if ( edit_state.abort_editing )
{
@ -2004,12 +2142,12 @@ void read_command_non_interactive(struct sh_read_command* sh_read_command,
sh_read_command->command = command;
}
int run(FILE* fp,
const char* fp_name,
bool interactive,
bool exit_on_error,
bool* script_exited,
int status)
static int run(FILE* fp,
const char* fp_name,
bool interactive,
bool exit_on_error,
bool* script_exited,
int status)
{
// TODO: The interactive read code should cope when the input is not a
// terminal; it should print the prompt and then read normally without
@ -2058,6 +2196,83 @@ 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 = getenv("HOME");
const char* histfile = getenv("HISTFILE");
if ( !histfile && home )
{
char* path;
if ( asprintf(&path, "%s/.sh_history", home) < 0 ||
setenv("HISTFILE", path, 1) < 0 )
err(1, "malloc");
free(path);
}
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"));
}
status = run(fp, fp_name, interactive, exit_on_error, script_exited,
status);
if ( interactive )
edit_line_history_save(&edit_state, getenv("HISTFILE"));
return status;
}
static void compact_arguments(int* argc, char*** argv)
{
for ( int i = 0; i < *argc; i++ )
@ -2133,8 +2348,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++ )
{
@ -2149,7 +2366,11 @@ int main(int argc, char* argv[])
char c;
while ( (c = *++arg) ) switch ( c )
{
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);
help(stderr, argv0);
@ -2164,6 +2385,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);
@ -2205,7 +2427,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);
@ -2234,8 +2456,8 @@ int main(int argc, char* argv[])
if ( !fp )
error(2, errno, "fmemopen");
status = run(fp, "<command-line>", false, flag_e_exit_on_error,
&script_exited, status);
status = top(fp, "<command-line>", false, flag_e_exit_on_error,
flag_l_login, &script_exited, status);
fclose(fp);
@ -2245,8 +2467,8 @@ int main(int argc, char* argv[])
if ( flag_s_stdin )
{
bool is_interactive = flag_i_interactive || isatty(fileno(stdin));
status = run(stdin, "<stdin>", is_interactive, flag_e_exit_on_error,
&script_exited, status);
status = top(stdin, "<stdin>", is_interactive, flag_e_exit_on_error,
flag_l_login, &script_exited, status);
if ( script_exited || (status != 0 && flag_e_exit_on_error) )
exit(status);
}
@ -2261,8 +2483,8 @@ int main(int argc, char* argv[])
}
bool is_interactive = flag_i_interactive || isatty(fileno(stdin));
status = run(stdin, "<stdin>", is_interactive, flag_e_exit_on_error,
&script_exited, status);
status = top(stdin, "<stdin>", is_interactive, flag_e_exit_on_error,
flag_l_login, &script_exited, status);
if ( script_exited || (status != 0 && flag_e_exit_on_error) )
exit(status);
}
@ -2279,8 +2501,8 @@ int main(int argc, char* argv[])
FILE* fp = fopen(path, "r");
if ( !fp )
error(127, errno, "%s", path);
status = run(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);
@ -2288,8 +2510,8 @@ int main(int argc, char* argv[])
else
{
bool is_interactive = flag_i_interactive || isatty(fileno(stdin));
status = run(stdin, "<stdin>", is_interactive, flag_e_exit_on_error,
&script_exited, status);
status = top(stdin, "<stdin>", is_interactive, flag_e_exit_on_error,
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