mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 11:37:35 +00:00
d230a01222
- Fix some minor issues in ar.com - Have execve() look for `ape` command - Rewrite NT paths using /c/ rather /??/c:/ - Replace broken GCC symlinks with .sym files - Rewrite $PATH environment variables on startup - Make $(APE_NO_MODIFY_SELF) the default bootloader - Add all build command dependencies to build/bootstrap - Get the repository mostly building from source on non-Linux
316 lines
9.4 KiB
C
316 lines
9.4 KiB
C
#if 0
|
|
/*─────────────────────────────────────────────────────────────────╗
|
|
│ To the extent possible under law, Justine Tunney has waived │
|
|
│ all copyright and related or neighboring rights to this file, │
|
|
│ as it is written in the following disclaimers: │
|
|
│ • http://unlicense.org/ │
|
|
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
|
╚─────────────────────────────────────────────────────────────────*/
|
|
#endif
|
|
#include "libc/calls/calls.h"
|
|
#include "libc/calls/sigbits.h"
|
|
#include "libc/calls/struct/sigaction.h"
|
|
#include "libc/calls/struct/sigset.h"
|
|
#include "libc/calls/struct/timespec.h"
|
|
#include "libc/fmt/fmt.h"
|
|
#include "libc/fmt/itoa.h"
|
|
#include "libc/intrin/kprintf.h"
|
|
#include "libc/log/internal.h"
|
|
#include "libc/log/log.h"
|
|
#include "libc/macros.internal.h"
|
|
#include "libc/runtime/internal.h"
|
|
#include "libc/runtime/runtime.h"
|
|
#include "libc/stdio/append.internal.h"
|
|
#include "libc/stdio/stdio.h"
|
|
#include "libc/str/str.h"
|
|
#include "libc/sysv/consts/clock.h"
|
|
#include "libc/sysv/consts/dt.h"
|
|
#include "libc/sysv/consts/o.h"
|
|
#include "libc/sysv/consts/sig.h"
|
|
#include "libc/time/time.h"
|
|
#include "libc/x/x.h"
|
|
#include "third_party/linenoise/linenoise.h"
|
|
|
|
/**
|
|
* @fileoverview Cosmopolitan Shell
|
|
*
|
|
* This doesn't have script language features like UNBOURNE.COM but it
|
|
* works on Windows and, unlike CMD.EXE, has CTRL-P, CTRL-R, and other
|
|
* GNU Emacs / Readline keyboard shortcuts.
|
|
*
|
|
* One day we'll have UNBOURNE.COM working on Windows but the code isn't
|
|
* very maintainable sadly.
|
|
*/
|
|
|
|
volatile int gotint;
|
|
|
|
static void OnInterrupt(int sig) {
|
|
gotint = sig;
|
|
}
|
|
|
|
static void AddUniqueCompletion(linenoiseCompletions *c, char *s) {
|
|
size_t i;
|
|
if (!s) return;
|
|
for (i = 0; i < c->len; ++i) {
|
|
if (!strcmp(s, c->cvec[i])) {
|
|
return;
|
|
}
|
|
}
|
|
c->cvec = realloc(c->cvec, ++c->len * sizeof(*c->cvec));
|
|
c->cvec[c->len - 1] = s;
|
|
}
|
|
|
|
static void CompleteFilename(const char *p, const char *q, const char *b,
|
|
linenoiseCompletions *c) {
|
|
DIR *d;
|
|
char *buf;
|
|
const char *g;
|
|
struct dirent *e;
|
|
if ((buf = malloc(512))) {
|
|
if ((g = memrchr(p, '/', q - p))) {
|
|
*(char *)mempcpy(buf, p, MIN(g - p, 511)) = 0;
|
|
p = ++g;
|
|
} else {
|
|
strcpy(buf, ".");
|
|
}
|
|
if ((d = opendir(buf))) {
|
|
while ((e = readdir(d))) {
|
|
if (!strcmp(e->d_name, ".")) continue;
|
|
if (!strcmp(e->d_name, "..")) continue;
|
|
if (!strncmp(e->d_name, p, q - p)) {
|
|
snprintf(buf, 512, "%.*s%s%s", p - b, b, e->d_name,
|
|
e->d_type == DT_DIR ? "/" : "");
|
|
AddUniqueCompletion(c, strdup(buf));
|
|
}
|
|
}
|
|
closedir(d);
|
|
}
|
|
free(buf);
|
|
}
|
|
}
|
|
|
|
static void ShellCompletion(const char *p, linenoiseCompletions *c) {
|
|
bool slashed;
|
|
const char *q, *b;
|
|
for (slashed = false, b = p, q = (p += strlen(p)); p > b; --p) {
|
|
if (p[-1] == '/' && p[-1] == '\\') slashed = true;
|
|
if (!isalnum(p[-1]) &&
|
|
(p[-1] != '.' && p[-1] != '_' && p[-1] != '-' && p[-1] != '+' &&
|
|
p[-1] != '[' && p[-1] != '/' && p[-1] != '\\')) {
|
|
break;
|
|
}
|
|
}
|
|
CompleteFilename(p, q, b, c);
|
|
}
|
|
|
|
static char *ShellHint(const char *p, const char **ansi1, const char **ansi2) {
|
|
char *h = 0;
|
|
linenoiseCompletions c = {0};
|
|
ShellCompletion(p, &c);
|
|
if (c.len == 1) {
|
|
h = strdup(c.cvec[0] + strlen(p));
|
|
}
|
|
linenoiseFreeCompletions(&c);
|
|
return h;
|
|
}
|
|
|
|
static char *MakePrompt(char *p) {
|
|
char *s, buf[256];
|
|
if (!gethostname(buf, sizeof(buf))) {
|
|
p = stpcpy(p, "\e[95m");
|
|
if ((s = getenv("USER"))) {
|
|
p = stpcpy(p, s);
|
|
*p++ = '@';
|
|
}
|
|
p = stpcpy(p, buf);
|
|
*p++ = ':';
|
|
}
|
|
p = stpcpy(p, "\e[96m");
|
|
if ((s = getcwd(buf, sizeof(buf)))) {
|
|
p = stpcpy(p, s);
|
|
}
|
|
return stpcpy(p, "\e[0m>: ");
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
bool timeit;
|
|
int64_t nanos;
|
|
struct rusage ru;
|
|
struct timespec ts1, ts2;
|
|
char *prog, path[PATH_MAX];
|
|
sigset_t chldmask, savemask;
|
|
int stdoutflags, stderrflags;
|
|
const char *stdoutpath, *stderrpath;
|
|
int n, rc, ws, pid, child, killcount;
|
|
struct sigaction sa, saveint, savequit;
|
|
char *p, *line, **args, *arg, *start, *state, prompt[1024];
|
|
linenoiseSetFreeHintsCallback(free);
|
|
linenoiseSetHintsCallback(ShellHint);
|
|
linenoiseSetCompletionCallback(ShellCompletion);
|
|
MakePrompt(prompt);
|
|
while ((line = linenoiseWithHistory(prompt, "cmd"))) {
|
|
n = 0;
|
|
start = line;
|
|
if (startswith(start, "time ")) {
|
|
timeit = true;
|
|
start += 5;
|
|
} else {
|
|
timeit = false;
|
|
}
|
|
stdoutpath = 0;
|
|
stderrpath = 0;
|
|
stdoutflags = 0;
|
|
stderrflags = 0;
|
|
args = xcalloc(1, sizeof(*args));
|
|
while ((arg = strtok_r(start, " \t\r\n", &state))) {
|
|
// cmd >>stdout.txt
|
|
if (arg[0] == '>' && arg[1] == '>') {
|
|
stdoutflags = O_WRONLY | O_APPEND | O_CREAT;
|
|
stdoutpath = arg + 2;
|
|
} else if (arg[0] == '>') {
|
|
// cmd >stdout.txt
|
|
stdoutflags = O_WRONLY | O_CREAT | O_TRUNC;
|
|
stdoutpath = arg + 1;
|
|
} else if (arg[0] == '2' && arg[1] == '>' && arg[2] == '>') {
|
|
// cmd 2>>stderr.txt
|
|
stderrflags = O_WRONLY | O_APPEND | O_CREAT;
|
|
stderrpath = arg + 3;
|
|
} else if (arg[0] == '2' && arg[1] == '>') {
|
|
// cmd 2>stderr.txt
|
|
stderrflags = O_WRONLY | O_CREAT | O_TRUNC;
|
|
stderrpath = arg + 2;
|
|
} else {
|
|
// arg
|
|
args = xrealloc(args, (++n + 1) * sizeof(*args));
|
|
args[n - 1] = arg;
|
|
args[n - 0] = 0;
|
|
}
|
|
start = 0;
|
|
}
|
|
if (n > 0) {
|
|
if ((prog = commandv(args[0], path, sizeof(path)))) {
|
|
|
|
// let keyboard interrupts kill child and not shell
|
|
gotint = 0;
|
|
killcount = 0;
|
|
sa.sa_flags = 0;
|
|
sa.sa_handler = SIG_IGN;
|
|
sigemptyset(&sa.sa_mask);
|
|
sigaction(SIGQUIT, &sa, &savequit);
|
|
sa.sa_handler = OnInterrupt;
|
|
sigaction(SIGINT, &sa, &saveint);
|
|
sigemptyset(&chldmask);
|
|
sigaddset(&chldmask, SIGCHLD);
|
|
sigprocmask(SIG_BLOCK, &chldmask, &savemask);
|
|
|
|
// record timestamp
|
|
if (timeit) {
|
|
clock_gettime(CLOCK_REALTIME, &ts1);
|
|
}
|
|
|
|
// launch process
|
|
if (!(child = vfork())) {
|
|
if (stdoutpath) {
|
|
close(1);
|
|
open(stdoutpath, stdoutflags, 0644);
|
|
}
|
|
if (stderrpath) {
|
|
close(2);
|
|
open(stderrpath, stderrflags, 0644);
|
|
}
|
|
sigaction(SIGINT, &saveint, 0);
|
|
sigaction(SIGQUIT, &savequit, 0);
|
|
sigprocmask(SIG_SETMASK, &savemask, 0);
|
|
execv(prog, args);
|
|
_Exit(127);
|
|
}
|
|
|
|
// wait for process
|
|
for (;;) {
|
|
if (gotint) {
|
|
switch (killcount) {
|
|
case 0:
|
|
// ctrl-c
|
|
// we do nothing
|
|
// terminals broadcast sigint to process group
|
|
rc = 0;
|
|
break;
|
|
case 1:
|
|
// ctrl-c ctrl-c
|
|
// we try sending sigterm
|
|
rc = kill(child, SIGTERM);
|
|
break;
|
|
default:
|
|
// ctrl-c ctrl-c ctrl-c ...
|
|
// we use kill -9 as our last resort
|
|
rc = kill(child, SIGKILL);
|
|
break;
|
|
}
|
|
if (rc == -1) {
|
|
fprintf(stderr, "kill failed: %m\n");
|
|
exit(1);
|
|
}
|
|
++killcount;
|
|
gotint = 0;
|
|
}
|
|
rc = wait4(0, &ws, 0, &ru);
|
|
if (rc != -1) {
|
|
break;
|
|
} else if (errno == EINTR) {
|
|
errno = 0;
|
|
} else {
|
|
fprintf(stderr, "wait failed: %m\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
// print resource consumption for `time` pseudocommand
|
|
if (timeit) {
|
|
clock_gettime(CLOCK_REALTIME, &ts2);
|
|
if (ts2.tv_sec == ts1.tv_sec) {
|
|
nanos = ts2.tv_nsec - ts1.tv_nsec;
|
|
} else {
|
|
nanos = (ts2.tv_sec - ts1.tv_sec) * 1000000000LL;
|
|
nanos += 1000000000LL - ts1.tv_nsec;
|
|
nanos += ts2.tv_nsec;
|
|
}
|
|
printf("took %,ldµs wall time\n", nanos / 1000);
|
|
p = 0;
|
|
AppendResourceReport(&p, &ru, "\n");
|
|
fputs(p, stdout);
|
|
free(p);
|
|
}
|
|
|
|
// update prompt to reflect exit status
|
|
p = prompt;
|
|
if (WIFEXITED(ws)) {
|
|
if (WEXITSTATUS(ws)) {
|
|
if (!__nocolor) p = stpcpy(p, "\e[1;31m");
|
|
p = stpcpy(p, "rc=");
|
|
p = FormatInt32(p, WEXITSTATUS(ws));
|
|
if (128 < WEXITSTATUS(ws) && WEXITSTATUS(ws) <= 128 + 32) {
|
|
*p++ = ' ';
|
|
p = stpcpy(p, strsignal(WEXITSTATUS(ws) - 128));
|
|
}
|
|
if (!__nocolor) p = stpcpy(p, "\e[0m");
|
|
*p++ = ' ';
|
|
}
|
|
} else {
|
|
if (!__nocolor) p = stpcpy(p, "\e[1;31m");
|
|
p = stpcpy(p, strsignal(WTERMSIG(ws)));
|
|
if (!__nocolor) p = stpcpy(p, "\e[0m");
|
|
*p++ = ' ';
|
|
}
|
|
MakePrompt(p);
|
|
|
|
sigaction(SIGINT, &saveint, 0);
|
|
sigaction(SIGQUIT, &savequit, 0);
|
|
sigprocmask(SIG_SETMASK, &savemask, 0);
|
|
} else {
|
|
fprintf(stderr, "%s: %s: command not found\n", argv[0], args[0]);
|
|
}
|
|
}
|
|
free(line);
|
|
free(args);
|
|
}
|
|
}
|