mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 19:43:32 +00:00
f531acc8f9
- Invent openatemp() API - Invent O_UNLINK open flag - Introduce getenv_secure() API - Remove `git pull` from cosmocc - Fix utimes() when path is NULL - Fix mktemp() to never return NULL - Fix utimensat() UTIME_OMIT on XNU - Improve utimensat() code for RHEL5 - Turn `argv[0]` C:/ to /C/ on Windows - Introduce tmpnam() and tmpnam_r() APIs - Fix more const issues with internal APIs - Permit utimes() on WIN32 in O_RDONLY mode - Fix fdopendir() to check fd is a directory - Fix recent crash regression in landlock make - Fix futimens(AT_FDCWD, NULL) to return EBADF - Use workaround so `make -j` doesn't fork bomb - Rename dontdiscard to __wur (just like glibc) - Fix st_size for WIN32 symlinks containing UTF-8 - Introduce stdio ext APIs needed by GNU coreutils - Fix lstat() on WIN32 for symlinks to directories - Move some constants from normalize.inc to limits.h - Fix segv with memchr() and memcmp() overlapping page - Implement POSIX fflush() behavior for reader streams - Implement AT_SYMLINK_NOFOLLOW for utimensat() on WIN32 - Don't change read-only status of existing files on WIN32 - Correctly handle `0x[^[:xdigit:]]` case in strtol() functions
1063 lines
26 KiB
C
1063 lines
26 KiB
C
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
|
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
|
╞══════════════════════════════════════════════════════════════════════════════╡
|
|
│ Copyright 2022 Justine Alexandra Roberts Tunney │
|
|
│ │
|
|
│ Permission to use, copy, modify, and/or distribute this software for │
|
|
│ any purpose with or without fee is hereby granted, provided that the │
|
|
│ above copyright notice and this permission notice appear in all copies. │
|
|
│ │
|
|
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
|
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
|
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
|
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
|
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
|
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
|
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
|
│ PERFORMANCE OF THIS SOFTWARE. │
|
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
|
#include "libc/assert.h"
|
|
#include "libc/calls/calls.h"
|
|
#include "libc/calls/struct/stat.h"
|
|
#include "libc/calls/struct/timespec.h"
|
|
#include "libc/dce.h"
|
|
#include "libc/errno.h"
|
|
#include "libc/fmt/conv.h"
|
|
#include "libc/fmt/itoa.h"
|
|
#include "libc/fmt/magnumstrs.internal.h"
|
|
#include "libc/intrin/bits.h"
|
|
#include "libc/intrin/getenv.internal.h"
|
|
#include "libc/intrin/weaken.h"
|
|
#include "libc/limits.h"
|
|
#include "libc/macros.internal.h"
|
|
#include "libc/runtime/runtime.h"
|
|
#include "libc/stdio/stdio.h"
|
|
#include "libc/str/str.h"
|
|
#include "libc/sysv/consts/lock.h"
|
|
#include "libc/sysv/consts/o.h"
|
|
#include "libc/sysv/consts/ok.h"
|
|
#include "libc/sysv/consts/s.h"
|
|
#include "libc/sysv/consts/sig.h"
|
|
#include "libc/sysv/consts/timer.h"
|
|
#include "libc/temp.h"
|
|
#include "third_party/awk/cmd.h"
|
|
#include "third_party/getopt/getopt.internal.h"
|
|
#include "third_party/musl/glob.h"
|
|
#include "third_party/sed/cmd.h"
|
|
#include "third_party/tr/cmd.h"
|
|
#include "tool/curl/cmd.h"
|
|
|
|
/**
|
|
* @fileoverview Cosmopolitan Command Interpreter
|
|
*
|
|
* This is a lightweight command interpreter for GNU Make. It has just
|
|
* enough shell script language support to support our build config.
|
|
*/
|
|
|
|
#define STATE_CMD 0
|
|
#define STATE_VAR 1
|
|
#define STATE_SINGLE 2
|
|
#define STATE_QUOTED 3
|
|
#define STATE_QUOTED_VAR 4
|
|
#define STATE_WHITESPACE 5
|
|
|
|
#define TOMBSTONE ((char *)-1)
|
|
#define READ24(s) READ32LE(s "\0")
|
|
|
|
static char *p;
|
|
static char *q;
|
|
static char *r;
|
|
static int vari;
|
|
static size_t n;
|
|
static char *cmd;
|
|
static char *assign;
|
|
static char var[32];
|
|
static int lastchild;
|
|
static int exitstatus;
|
|
static char *envs[500];
|
|
static char *args[3000];
|
|
static const char *prog;
|
|
static char argbuf[ARG_MAX];
|
|
static bool unsupported[256];
|
|
|
|
static int ShellSpawn(void);
|
|
static int ShellExec(void);
|
|
|
|
static ssize_t Write(int fd, const char *s) {
|
|
return write(fd, s, strlen(s));
|
|
}
|
|
|
|
static wontreturn void UnsupportedSyntax(unsigned char c) {
|
|
char ibuf[13], cbuf[2] = {c};
|
|
FormatOctal32(ibuf, c, true);
|
|
tinyprint(2, prog, ": unsupported syntax '", cbuf, "' (", ibuf, "): ", cmd,
|
|
"\n", NULL);
|
|
_Exit(4);
|
|
}
|
|
|
|
static void Open(const char *path, int fd, int flags) {
|
|
close(fd);
|
|
if (open(path, flags, 0644) == -1) {
|
|
perror(path);
|
|
_Exit(1);
|
|
}
|
|
}
|
|
|
|
static int SystemExec(void) {
|
|
execvpe(args[0], args, envs);
|
|
perror(args[0]);
|
|
return (n = 0), 127;
|
|
}
|
|
|
|
static int GetSignalByName(const char *s) {
|
|
for (int i = 0; kSignalNames[i].x != MAGNUM_TERMINATOR; ++i) {
|
|
if (!strcmp(s, MAGNUM_STRING(kSignalNames, i) + 3)) {
|
|
return MAGNUM_NUMBER(kSignalNames, i);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void PutEnv(char **p, char *kv) {
|
|
struct Env e;
|
|
e = __getenv(p, kv);
|
|
p[e.i] = kv;
|
|
if (!e.s) p[e.i + 1] = 0;
|
|
}
|
|
|
|
static void UnsetEnv(char **p, const char *k) {
|
|
int i;
|
|
struct Env e;
|
|
if ((e = __getenv(p, k)).s) {
|
|
p[e.i] = 0;
|
|
for (i = e.i + 1; p[i]; ++i) {
|
|
p[i - 1] = p[i];
|
|
p[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void Append(int c) {
|
|
npassert(q + 1 < argbuf + sizeof(argbuf));
|
|
*q++ = c;
|
|
}
|
|
|
|
static char *Finish(void) {
|
|
char *s = r;
|
|
Append(0);
|
|
r = q;
|
|
if (!assign) {
|
|
return s;
|
|
} else {
|
|
PutEnv(envs, s);
|
|
assign = 0;
|
|
return TOMBSTONE;
|
|
}
|
|
}
|
|
|
|
static int True(void) {
|
|
return 0;
|
|
}
|
|
|
|
static int False(void) {
|
|
return 1;
|
|
}
|
|
|
|
static wontreturn void Exit(void) {
|
|
_Exit(n > 1 ? atoi(args[1]) : 0);
|
|
}
|
|
|
|
static int Waiter(int pid) {
|
|
int ws;
|
|
n = 0;
|
|
if (waitpid(pid, &ws, 0) == -1) {
|
|
perror("wait");
|
|
return 1;
|
|
}
|
|
if (WIFEXITED(ws)) {
|
|
return WEXITSTATUS(ws);
|
|
} else {
|
|
return 128 + WTERMSIG(ws);
|
|
}
|
|
}
|
|
|
|
static int Wait(void) {
|
|
int e, ws, pid;
|
|
if (n > 1) {
|
|
if ((pid = atoi(args[1])) <= 0) {
|
|
tinyprint(2, "wait: bad pid\n", NULL);
|
|
return 1;
|
|
}
|
|
return Waiter(pid);
|
|
} else {
|
|
for (n = 0, e = errno;;) {
|
|
if (waitpid(-1, &ws, 0) == -1) {
|
|
if (errno == ECHILD) {
|
|
errno = e;
|
|
break;
|
|
}
|
|
perror("wait");
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static const char *GetOptArg(int c, int *i, int j) {
|
|
if (args[*i][j] == c) {
|
|
if (args[*i][j + 1]) {
|
|
return args[*i] + j + 1;
|
|
} else if (*i + 1 < n) {
|
|
return args[++*i];
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int Echo(void) {
|
|
int i = 1;
|
|
bool once = false;
|
|
bool print_newline = true;
|
|
if (i < n && args[i][0] == '-' && args[i][1] == 'n' && !args[i][2]) {
|
|
++i, print_newline = false;
|
|
}
|
|
for (; i < n; ++i) {
|
|
if (once) {
|
|
Write(1, " ");
|
|
} else {
|
|
once = true;
|
|
}
|
|
Write(1, args[i]);
|
|
}
|
|
if (print_newline) {
|
|
Write(1, "\n");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int NeedArgument(const char *prog) {
|
|
tinyprint(2, prog, ": missing argument\n", NULL);
|
|
return 1;
|
|
}
|
|
|
|
static int Flock(void) {
|
|
int fd;
|
|
int i = 1;
|
|
char *endptr;
|
|
int mode = LOCK_EX;
|
|
if (i < n && args[i][0] == '-' && args[i][1] == 'x' && !args[i][2]) {
|
|
++i, mode = LOCK_EX;
|
|
}
|
|
if (i < n && args[i][0] == '-' && args[i][1] == 's' && !args[i][2]) {
|
|
++i, mode = LOCK_SH;
|
|
}
|
|
if (i == n) {
|
|
return NeedArgument("flock");
|
|
}
|
|
if (i + 1 != n) {
|
|
tinyprint(2, "flock: too many arguments\n", NULL);
|
|
return 1;
|
|
}
|
|
fd = strtol(args[i], &endptr, 10);
|
|
if (*endptr) {
|
|
tinyprint(2, "flock: need a number\n", NULL);
|
|
return 1;
|
|
}
|
|
if (flock(fd, mode)) {
|
|
perror("flock");
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int Chmod(void) {
|
|
int i, mode;
|
|
char *endptr;
|
|
if (n < 3) {
|
|
return NeedArgument("chmod");
|
|
}
|
|
mode = strtol(args[1], &endptr, 8);
|
|
if (*endptr) {
|
|
tinyprint(2, "chmod: bad octal mode\n", NULL);
|
|
return 1;
|
|
}
|
|
for (i = 2; i < n; ++i) {
|
|
if (chmod(args[i], mode)) {
|
|
perror(args[i]);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int Pwd(void) {
|
|
char path[PATH_MAX + 2];
|
|
if (getcwd(path, PATH_MAX)) {
|
|
strlcat(path, "\n", sizeof(path));
|
|
Write(1, path);
|
|
return 0;
|
|
} else {
|
|
perror("pwd");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static int CatDump(const char *path, int fd, bool dontclose) {
|
|
ssize_t rc;
|
|
char buf[512];
|
|
for (;;) {
|
|
rc = read(fd, buf, sizeof(buf));
|
|
if (rc == -1) {
|
|
perror(path);
|
|
if (!dontclose) {
|
|
close(fd);
|
|
}
|
|
return 1;
|
|
}
|
|
if (!rc) break;
|
|
rc = write(1, buf, rc);
|
|
if (rc == -1) {
|
|
perror("write");
|
|
if (!dontclose) {
|
|
close(fd);
|
|
}
|
|
return 1;
|
|
}
|
|
}
|
|
if (!dontclose && close(fd)) {
|
|
perror(path);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int Cat(void) {
|
|
int i, fd, rc;
|
|
if (n < 2) {
|
|
return CatDump("<stdin>", 0, true);
|
|
} else {
|
|
for (i = 1; i < n; ++i) {
|
|
bool dontclose = false;
|
|
if (args[i][0] == '-' && !args[i][1]) {
|
|
dontclose = true;
|
|
fd = 0;
|
|
} else if ((fd = open(args[i], O_RDONLY)) == -1) {
|
|
perror(args[i]);
|
|
return 1;
|
|
}
|
|
if ((rc = CatDump(args[i], fd, dontclose))) {
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int Mktemp(void) {
|
|
int fd;
|
|
char template[PATH_MAX + 1];
|
|
if (n == 2) {
|
|
strlcpy(template, args[1], sizeof(template));
|
|
} else {
|
|
strlcpy(template, kTmpPath, sizeof(template));
|
|
strlcat(template, "tmp.XXXXXX", sizeof(template));
|
|
}
|
|
if ((fd = mkstemp(template)) == -1) {
|
|
perror("mkstemp");
|
|
return 1;
|
|
}
|
|
strlcat(template, "\n", sizeof(template));
|
|
Write(1, template);
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
|
|
static int Read(void) {
|
|
unsigned char c;
|
|
int i, j, rc = 1;
|
|
for (i = 1; i < n; ++i) {
|
|
if (args[i][0] != '-') break;
|
|
if (args[i][1] == 'p' && !args[i][2] && i + 1 < n) {
|
|
Write(1, args[++i]);
|
|
}
|
|
}
|
|
if (i >= n) return 1;
|
|
for (j = 0; args[i][j]; ++j) {
|
|
Append(args[i][j]);
|
|
}
|
|
Append('=');
|
|
while (read(0, &c, 1) > 0) {
|
|
if (c == '\n') break;
|
|
Append(c);
|
|
rc = 0;
|
|
}
|
|
PutEnv(envs, Finish());
|
|
return rc;
|
|
}
|
|
|
|
static int Cd(void) {
|
|
const char *s;
|
|
if ((s = n > 1 ? args[1] : __getenv(envs, "HOME").s)) {
|
|
if (!chdir(s)) {
|
|
return 0;
|
|
} else {
|
|
perror(s);
|
|
return 1;
|
|
}
|
|
} else {
|
|
return NeedArgument("cd");
|
|
}
|
|
}
|
|
|
|
static int Mkdir(void) {
|
|
int i, j;
|
|
int mode = 0755;
|
|
const char *arg;
|
|
int (*mkdir_impl)(const char *, unsigned) = mkdir;
|
|
for (i = 1; i < n; ++i) {
|
|
if (args[i][0] == '-' && args[i][1]) {
|
|
if (args[i][1] == '-' && !args[i][2]) {
|
|
++i; // rm -- terminates option parsing
|
|
break;
|
|
}
|
|
for (j = 1; j < args[i][j]; ++j) {
|
|
if (args[i][j] == 'p') {
|
|
mkdir_impl = makedirs; // mkdir -p creates parents
|
|
continue;
|
|
}
|
|
if ((arg = GetOptArg('m', &i, j))) {
|
|
mode = strtol(arg, 0, 8); // mkdir -m OCTAL sets mode
|
|
break;
|
|
}
|
|
char option[2] = {args[i][j]};
|
|
tinyprint(2, "mkdir", ": illegal option -- ", option, "\n", NULL);
|
|
return 1;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (i == n) {
|
|
return NeedArgument("mkdir");
|
|
}
|
|
for (; i < n; ++i) {
|
|
if (mkdir_impl(args[i], mode)) {
|
|
perror(args[i]);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int Kill(void) {
|
|
int sig, rc = 0, i = 1;
|
|
if (i < n && args[i][0] == '-') {
|
|
sig = GetSignalByName(args[i++] + 1);
|
|
if (!sig) return -1; // fallback to system kill command
|
|
} else {
|
|
sig = SIGTERM;
|
|
}
|
|
for (; i < n; ++i) {
|
|
if (kill(atoi(args[i]), sig)) {
|
|
perror("kill");
|
|
rc = 1;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int Toupper(void) {
|
|
int i, n;
|
|
char b[512];
|
|
while ((n = read(0, b, 512)) > 0) {
|
|
for (i = 0; i < n; ++i) {
|
|
b[i] = toupper(b[i]);
|
|
}
|
|
write(1, b, n);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int Usleep(void) {
|
|
int f;
|
|
struct timespec t;
|
|
if (n > 1) {
|
|
f = 0;
|
|
t = timespec_frommicros(atoi(args[1]));
|
|
} else {
|
|
f = TIMER_ABSTIME;
|
|
t = timespec_max;
|
|
}
|
|
return clock_nanosleep(0, f, &t, 0);
|
|
}
|
|
|
|
static int Test(void) {
|
|
int w, m = n;
|
|
struct stat st;
|
|
if (m && READ16LE(args[m - 1]) == READ16LE("]")) --m;
|
|
if (m == 4) {
|
|
w = READ32LE(args[2]) & 0x00ffffff;
|
|
if ((w & 65535) == READ16LE("=")) return !!strcmp(args[1], args[3]);
|
|
if (w == READ24("==")) return !!strcmp(args[1], args[3]);
|
|
if (w == READ24("!=")) return !strcmp(args[1], args[3]);
|
|
} else if (m == 3) {
|
|
w = READ32LE(args[1]) & 0x00ffffff;
|
|
if (w == READ24("-n")) return !(strlen(args[2]) > 0);
|
|
if (w == READ24("-z")) return !(strlen(args[2]) == 0);
|
|
if (w == READ24("-e")) return !!stat(args[2], &st);
|
|
if (w == READ24("-f")) return !(!stat(args[2], &st) && S_ISREG(st.st_mode));
|
|
if (w == READ24("-d")) return !(!stat(args[2], &st) && S_ISDIR(st.st_mode));
|
|
if (w == READ24("-h")) return !(!stat(args[2], &st) && S_ISLNK(st.st_mode));
|
|
}
|
|
return -1; // fall back to system test command
|
|
}
|
|
|
|
static int Rm(void) {
|
|
int i, j;
|
|
bool force = false;
|
|
for (i = 1; i < n; ++i) {
|
|
if (args[i][0] == '-' && args[i][1]) {
|
|
if (args[i][1] == '-' && !args[i][2]) {
|
|
++i; // rm -- terminates option parsing
|
|
break;
|
|
}
|
|
for (j = 1; j < args[i][j]; ++j) {
|
|
if (args[i][j] == 'f') {
|
|
force = true; // rm -f forces removal
|
|
continue;
|
|
}
|
|
char option[2] = {args[i][j]};
|
|
tinyprint(2, "rm", ": illegal option -- ", option, "\n", NULL);
|
|
return 1;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (i == n) {
|
|
return NeedArgument("rm");
|
|
}
|
|
for (; i < n; ++i) {
|
|
struct stat st;
|
|
if ((!force && (lstat(args[i], &st) ||
|
|
(!S_ISLNK(st.st_mode) && access(args[i], W_OK)))) ||
|
|
unlink(args[i])) {
|
|
if (force && errno == ENOENT) continue;
|
|
perror(args[i]);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int Rmdir(void) {
|
|
int i;
|
|
for (i = 1; i < n; ++i) {
|
|
if (rmdir(args[i])) {
|
|
perror(args[i]);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int Touch(void) {
|
|
int i;
|
|
if (n > 1 && args[1][0] != '-') {
|
|
for (i = 1; i < n; ++i) {
|
|
if (touch(args[i], 0644)) {
|
|
perror(args[i]);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
} else {
|
|
return -1; // fall back to system rmdir command
|
|
}
|
|
}
|
|
|
|
static int Shift(int i) {
|
|
if (i <= n) {
|
|
memmove(args, args + i, (n - i + 1) * sizeof(*args));
|
|
n -= i;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int Fake(int main(int, char **)) {
|
|
int pid;
|
|
if ((pid = fork()) == -1) {
|
|
perror("fork");
|
|
return 127;
|
|
}
|
|
if (!pid) {
|
|
// TODO(jart): Maybe nuke stdio too?
|
|
if (_weaken(optind)) {
|
|
*_weaken(optind) = 1;
|
|
}
|
|
environ = envs;
|
|
exit(main(n, args));
|
|
}
|
|
return Waiter(pid);
|
|
}
|
|
|
|
static int Env(void) {
|
|
int i, j;
|
|
const char *arg;
|
|
char term = '\n';
|
|
for (i = 1; i < n; ++i) {
|
|
if (args[i][0] == '-') {
|
|
if (!args[i][1]) {
|
|
envs[0] = 0; // env - clears environment
|
|
continue;
|
|
}
|
|
for (j = 1; j < args[i][j]; ++j) {
|
|
if (args[i][j] == 'i') {
|
|
envs[0] = 0; // env -i clears environment
|
|
continue;
|
|
}
|
|
if (args[i][j] == '0') {
|
|
term = 0; // env -0 uses '\0' line separator
|
|
continue;
|
|
}
|
|
if ((arg = GetOptArg('u', &i, j))) {
|
|
UnsetEnv(envs, arg); // env -u VAR removes variable
|
|
break;
|
|
}
|
|
char option[2] = {args[i][j]};
|
|
tinyprint(2, "env", ": illegal option -- ", option, "\n", NULL);
|
|
return 1;
|
|
}
|
|
continue;
|
|
}
|
|
if (strchr(args[i], '=')) {
|
|
PutEnv(envs, args[i]);
|
|
} else {
|
|
Shift(i);
|
|
return ShellSpawn();
|
|
}
|
|
}
|
|
for (i = 0; envs[i]; ++i) {
|
|
Write(1, envs[i]);
|
|
write(1, &term, 1);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static wontreturn void Exec(void) {
|
|
Shift(1);
|
|
if (!ShellExec()) {
|
|
_Exit(0); // can happen for builtins
|
|
} else {
|
|
perror("exec");
|
|
_Exit(127); // sh exec never returns
|
|
}
|
|
}
|
|
|
|
static int TryBuiltin(void) {
|
|
if (!n) return exitstatus;
|
|
if (!strcmp(args[0], "exit")) Exit();
|
|
if (!strcmp(args[0], "exec")) Exec();
|
|
if (!strcmp(args[0], "cd")) return Cd();
|
|
if (!strcmp(args[0], "rm")) return Rm();
|
|
if (!strcmp(args[0], "[")) return Test();
|
|
if (!strcmp(args[0], "cat")) return Cat();
|
|
if (!strcmp(args[0], "env")) return Env();
|
|
if (!strcmp(args[0], "pwd")) return Pwd();
|
|
if (!strcmp(args[0], "wait")) return Wait();
|
|
if (!strcmp(args[0], "echo")) return Echo();
|
|
if (!strcmp(args[0], "read")) return Read();
|
|
if (!strcmp(args[0], "true")) return True();
|
|
if (!strcmp(args[0], "test")) return Test();
|
|
if (!strcmp(args[0], "kill")) return Kill();
|
|
if (!strcmp(args[0], "flock")) return Flock();
|
|
if (!strcmp(args[0], "chmod")) return Chmod();
|
|
if (!strcmp(args[0], "touch")) return Touch();
|
|
if (!strcmp(args[0], "rmdir")) return Rmdir();
|
|
if (!strcmp(args[0], "mkdir")) return Mkdir();
|
|
if (!strcmp(args[0], "false")) return False();
|
|
if (!strcmp(args[0], "mktemp")) return Mktemp();
|
|
if (!strcmp(args[0], "usleep")) return Usleep();
|
|
if (!strcmp(args[0], "toupper")) return Toupper();
|
|
if (_weaken(_tr) && !strcmp(args[0], "tr")) return Fake(_weaken(_tr));
|
|
if (_weaken(_sed) && !strcmp(args[0], "sed")) return Fake(_weaken(_sed));
|
|
if (_weaken(_awk) && !strcmp(args[0], "awk")) return Fake(_weaken(_awk));
|
|
if (_weaken(_curl) && !strcmp(args[0], "curl")) return Fake(_weaken(_curl));
|
|
return -1;
|
|
}
|
|
|
|
static int ShellExec(void) {
|
|
int rc;
|
|
if ((rc = TryBuiltin()) == -1) {
|
|
rc = SystemExec();
|
|
}
|
|
return (n = 0), rc;
|
|
}
|
|
|
|
static void Pipe(void) {
|
|
int pid, pfds[2];
|
|
if (pipe2(pfds, O_CLOEXEC)) {
|
|
perror("pipe");
|
|
_Exit(127);
|
|
}
|
|
if ((pid = fork()) == -1) {
|
|
perror("fork");
|
|
_Exit(127);
|
|
}
|
|
if (!pid) {
|
|
unassert(dup2(pfds[1], 1) == 1);
|
|
// we can't rely on cloexec because builtins
|
|
if (pfds[0] != 1) unassert(!close(pfds[0]));
|
|
if (pfds[1] != 1) unassert(!close(pfds[1]));
|
|
_Exit(ShellExec());
|
|
}
|
|
unassert(!dup2(pfds[0], 0));
|
|
if (pfds[1]) unassert(!close(pfds[1]));
|
|
n = 0;
|
|
}
|
|
|
|
static int ShellSpawn(void) {
|
|
int rc, pid;
|
|
if ((rc = TryBuiltin()) == -1) {
|
|
switch ((pid = fork())) {
|
|
case 0:
|
|
_Exit(SystemExec());
|
|
default:
|
|
rc = Waiter(pid);
|
|
break;
|
|
case -1:
|
|
perror("fork");
|
|
rc = 127;
|
|
break;
|
|
}
|
|
}
|
|
return (n = 0), rc;
|
|
}
|
|
|
|
static void ShellSpawnAsync(void) {
|
|
switch ((lastchild = fork())) {
|
|
case 0:
|
|
_Exit(ShellExec());
|
|
default:
|
|
break;
|
|
case -1:
|
|
perror("fork");
|
|
break;
|
|
}
|
|
n = 0;
|
|
}
|
|
|
|
static const char *IntToStr(int x) {
|
|
static char ibuf[12];
|
|
FormatInt32(ibuf, x);
|
|
return ibuf;
|
|
}
|
|
|
|
static const char *GetVar(const char *key) {
|
|
static char vbuf[PATH_MAX];
|
|
if (key[0] == '$' && !key[1]) {
|
|
return IntToStr(getpid());
|
|
} else if (key[0] == '!' && !key[1]) {
|
|
return IntToStr(lastchild);
|
|
} else if (key[0] == '?' && !key[1]) {
|
|
return IntToStr(exitstatus);
|
|
} else if (!strcmp(key, "PWD")) {
|
|
npassert(getcwd(vbuf, sizeof(vbuf)));
|
|
return vbuf;
|
|
} else if (!strcmp(key, "UID")) {
|
|
FormatInt32(vbuf, getuid());
|
|
return vbuf;
|
|
} else if (!strcmp(key, "GID")) {
|
|
FormatInt32(vbuf, getgid());
|
|
return vbuf;
|
|
} else if (!strcmp(key, "EUID")) {
|
|
FormatInt32(vbuf, geteuid());
|
|
return vbuf;
|
|
} else {
|
|
return __getenv(envs, key).s;
|
|
}
|
|
}
|
|
|
|
static bool IsVarName(int c) {
|
|
return isalnum(c) || c == '_' ||
|
|
(!vari && (c == '?' || c == '!' || c == '$'));
|
|
}
|
|
|
|
static bool CopyVar(void) {
|
|
size_t j;
|
|
const char *s;
|
|
if (IsVarName(*p)) {
|
|
npassert(vari + 1 < sizeof(var));
|
|
var[vari++] = *p;
|
|
var[vari] = 0;
|
|
return false;
|
|
}
|
|
if ((s = GetVar(var))) {
|
|
if ((j = strlen(s))) {
|
|
npassert(q + j < argbuf + sizeof(argbuf));
|
|
q = mempcpy(q, s, j);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static char *Tokenize(void) {
|
|
int c, t, rc;
|
|
for (t = STATE_WHITESPACE;; ++p) {
|
|
switch (t) {
|
|
|
|
case STATE_WHITESPACE:
|
|
if (!*p) return 0;
|
|
if (*p == ' ' || *p == '\t' || *p == '\n' ||
|
|
(p[0] == '\\' && p[1] == '\n')) {
|
|
continue;
|
|
}
|
|
t = STATE_CMD;
|
|
// fallthrough
|
|
|
|
case STATE_CMD:
|
|
if (unsupported[*p & 255]) {
|
|
UnsupportedSyntax(*p);
|
|
}
|
|
if (!*p || *p == ' ' || *p == '\t') {
|
|
return Finish();
|
|
} else if (*p == '"') {
|
|
t = STATE_QUOTED;
|
|
} else if (*p == '\'') {
|
|
t = STATE_SINGLE;
|
|
} else if (*p == '$') {
|
|
t = STATE_VAR;
|
|
var[(vari = 0)] = 0;
|
|
} else if (*p == '\\') {
|
|
if (!p[1]) UnsupportedSyntax(*p);
|
|
Append(*++p);
|
|
} else if (*p == '=') {
|
|
if (!n && q > r) assign = r;
|
|
Append(*p);
|
|
} else if (*p == '|') {
|
|
if (q > r) {
|
|
return Finish();
|
|
} else if (p[1] == '|') {
|
|
rc = ShellSpawn();
|
|
if (!rc) {
|
|
_Exit(0);
|
|
} else {
|
|
++p;
|
|
t = STATE_WHITESPACE;
|
|
}
|
|
} else {
|
|
Pipe();
|
|
t = STATE_WHITESPACE;
|
|
}
|
|
} else if (*p == ';' || *p == '\n') {
|
|
if (q > r) {
|
|
return Finish();
|
|
} else {
|
|
exitstatus = ShellSpawn();
|
|
t = STATE_WHITESPACE;
|
|
}
|
|
} else if (*p == '>') {
|
|
Append(*p);
|
|
if (p[1] == '&') {
|
|
Append(*++p);
|
|
}
|
|
} else if (*p == '&') {
|
|
if (q > r) {
|
|
return Finish();
|
|
} else if (p[1] == '&') {
|
|
rc = ShellSpawn();
|
|
if (!rc) {
|
|
++p;
|
|
t = STATE_WHITESPACE;
|
|
} else {
|
|
_Exit(rc);
|
|
}
|
|
} else {
|
|
ShellSpawnAsync();
|
|
t = STATE_WHITESPACE;
|
|
}
|
|
} else {
|
|
Append(*p);
|
|
}
|
|
break;
|
|
|
|
case STATE_VAR:
|
|
// XXX: we need to find a simple elegant way to break up
|
|
// unquoted variable expansions into multiple args.
|
|
if (CopyVar()) t = STATE_CMD, --p;
|
|
break;
|
|
|
|
case STATE_SINGLE:
|
|
if (!*p) goto UnterminatedString;
|
|
if (*p == '\'') {
|
|
t = STATE_CMD;
|
|
} else {
|
|
*q++ = *p;
|
|
}
|
|
break;
|
|
|
|
UnterminatedString:
|
|
tinyprint(2, "cmd: error: unterminated string\n", NULL);
|
|
_Exit(6);
|
|
|
|
case STATE_QUOTED_VAR:
|
|
if (!*p) goto UnterminatedString;
|
|
if (CopyVar()) t = STATE_QUOTED, --p;
|
|
break;
|
|
|
|
case STATE_QUOTED:
|
|
if (!*p) goto UnterminatedString;
|
|
if (*p == '"') {
|
|
t = STATE_CMD;
|
|
} else if (p[0] == '$') {
|
|
t = STATE_QUOTED_VAR;
|
|
var[(vari = 0)] = 0;
|
|
} else if (p[0] == '\\') {
|
|
switch ((c = *++p)) {
|
|
case 0:
|
|
UnsupportedSyntax('\\');
|
|
case '\n':
|
|
break;
|
|
case '$':
|
|
case '`':
|
|
case '"':
|
|
*q++ = c;
|
|
break;
|
|
default:
|
|
*q++ = '\\';
|
|
*q++ = c;
|
|
break;
|
|
}
|
|
} else {
|
|
*q++ = *p;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
__builtin_unreachable();
|
|
}
|
|
}
|
|
}
|
|
|
|
static const char *GetRedirectArg(const char *prog, const char *arg, int n) {
|
|
if (arg[n]) {
|
|
return arg + n;
|
|
} else if ((arg = Tokenize())) {
|
|
return arg;
|
|
} else {
|
|
tinyprint(2, prog, ": error: redirect missing path\n", NULL);
|
|
_Exit(14);
|
|
}
|
|
}
|
|
|
|
int _cocmd(int argc, char **argv, char **envp) {
|
|
size_t i;
|
|
char *arg;
|
|
size_t globCount = 0;
|
|
int globFlags = 0;
|
|
glob_t globTheBuilder;
|
|
prog = argc > 0 ? argv[0] : "cocmd.com";
|
|
|
|
for (i = 1; i < 32; ++i) {
|
|
unsupported[i] = true;
|
|
}
|
|
unsupported['\t'] = false;
|
|
unsupported['\n'] = false;
|
|
unsupported[0177] = true;
|
|
unsupported['~'] = true;
|
|
unsupported['`'] = true;
|
|
unsupported['#'] = true;
|
|
unsupported['('] = true;
|
|
unsupported[')'] = true;
|
|
unsupported['{'] = true;
|
|
unsupported['}'] = false; // Perl t/op/exec.t depends on unpaired } being
|
|
// passed from the shell to Perl
|
|
if (!_weaken(glob)) {
|
|
unsupported['*'] = true;
|
|
unsupported['?'] = true;
|
|
}
|
|
|
|
if (argc >= 3 && !strcmp(argv[1], "--")) {
|
|
for (i = 2; i < argc; ++i) {
|
|
args[n++] = argv[i];
|
|
}
|
|
_Exit(ShellExec());
|
|
}
|
|
|
|
if (argc != 3) {
|
|
tinyprint(2, prog, ": error: wrong number of args\n", NULL);
|
|
_Exit(10);
|
|
}
|
|
|
|
if (strcmp(argv[1], "-c")) {
|
|
tinyprint(2, prog, ": error: argv[1] should -c\n", NULL);
|
|
_Exit(11);
|
|
}
|
|
|
|
p = cmd = argv[2];
|
|
if (strlen(cmd) >= ARG_MAX) {
|
|
tinyprint(2, prog, ": error: cmd too long: ", cmd, "\n", NULL);
|
|
_Exit(12);
|
|
}
|
|
|
|
// copy environment variables
|
|
int envi = 0;
|
|
if (envp) {
|
|
for (; envp[envi]; ++envi) {
|
|
npassert(envi + 1 < ARRAYLEN(envs));
|
|
envs[envi] = envp[envi];
|
|
}
|
|
}
|
|
envs[envi] = 0;
|
|
|
|
// get command line arguments
|
|
n = 0;
|
|
r = q = argbuf;
|
|
while ((arg = Tokenize())) {
|
|
if (arg == TOMBSTONE) continue;
|
|
if (n + 1 < ARRAYLEN(args)) {
|
|
if (isdigit(arg[0]) && arg[1] == '>' && arg[2] == '&' &&
|
|
isdigit(arg[3])) {
|
|
dup2(arg[3] - '0', arg[0] - '0');
|
|
} else if (arg[0] == '>' && arg[1] == '&' && isdigit(arg[2])) {
|
|
dup2(arg[2] - '0', 1);
|
|
} else if (isdigit(arg[0]) && arg[1] == '>' && arg[2] == '>') {
|
|
Open(GetRedirectArg(prog, arg, 3), arg[0] - '0',
|
|
O_WRONLY | O_CREAT | O_APPEND);
|
|
} else if (arg[0] == '>' && arg[1] == '>') {
|
|
Open(GetRedirectArg(prog, arg, 2), 1, O_WRONLY | O_CREAT | O_APPEND);
|
|
} else if (isdigit(arg[0]) && arg[1] == '>') {
|
|
Open(GetRedirectArg(prog, arg, 2), arg[0] - '0',
|
|
O_WRONLY | O_CREAT | O_TRUNC);
|
|
} else if (arg[0] == '>') {
|
|
Open(GetRedirectArg(prog, arg, 1), 1, O_WRONLY | O_CREAT | O_TRUNC);
|
|
} else if (arg[0] == '<') {
|
|
Open(GetRedirectArg(prog, arg, 1), 0, O_RDONLY);
|
|
} else {
|
|
int globrc = GLOB_NOMATCH;
|
|
if (_weaken(glob)) {
|
|
globrc = _weaken(glob)(arg, globFlags, NULL, &globTheBuilder);
|
|
if (globrc == 0) {
|
|
for (; globCount < globTheBuilder.gl_pathc; globCount++) {
|
|
args[n++] = globTheBuilder.gl_pathv[globCount];
|
|
}
|
|
} else if (globrc != GLOB_NOMATCH) {
|
|
tinyprint(2, prog, ": error: with glob\n", NULL);
|
|
_Exit(16);
|
|
}
|
|
globFlags |= GLOB_APPEND;
|
|
}
|
|
if (globrc == GLOB_NOMATCH) {
|
|
args[n++] = arg;
|
|
}
|
|
args[n] = 0;
|
|
}
|
|
} else {
|
|
tinyprint(2, prog, ": error: too many args\n", NULL);
|
|
_Exit(13);
|
|
}
|
|
}
|
|
|
|
return ShellExec();
|
|
}
|