Further improve cocmd interpreter

This commit is contained in:
Justine Tunney 2022-10-12 10:44:54 -07:00
parent 0cee831da3
commit 0f89140882
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
14 changed files with 278 additions and 181 deletions

View file

@ -129,8 +129,9 @@ int linkat(int, const char *, int, const char *, int);
int madvise(void *, uint64_t, int);
int memfd_create(const char *, unsigned int);
int mincore(void *, size_t, unsigned char *);
int mkdir(const char *, uint32_t);
int mkdirat(int, const char *, uint32_t);
int mkdir(const char *, unsigned);
int mkdirat(int, const char *, unsigned);
int makedirs(const char *, unsigned);
int mkfifo(const char *, uint32_t);
int mkfifoat(int, const char *, uint32_t);
int mknod(const char *, uint32_t, uint64_t);

View file

@ -75,10 +75,10 @@
* @return 0 on success, or errno on error
* @raise EINTR when a signal got delivered while we were waiting
* @raise ENOTSUP if `clock` is known but we can't use it here
* @raise EFAULT if `req` or null or bad memory was passed
* @raise EINVAL if `clock` is unknown to current platform
* @raise EINVAL if `flags` has an unrecognized value
* @raise EINVAL if `req->tv_nsec [0,1000000000)`
* @raise EFAULT if bad memory was passed
* @raise ENOSYS on bare metal
* @returnserrno
* @norestart

View file

@ -17,58 +17,75 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/calls/struct/stat.h"
#include "libc/errno.h"
#include "libc/fmt/conv.h"
#include "libc/mem/mem.h"
#include "libc/str/path.h"
#include "libc/str/str.h"
static char *DirName(const char *path) {
char *dirp;
if (!(path = strdup(path))) return 0;
dirp = strdup(dirname(path));
free(path);
return dirp;
}
static int MakeDirs(const char *path, unsigned mode, int e) {
int rc;
char *dir;
if (!mkdir(path, mode) || errno == EEXIST) {
errno = e;
return 0;
}
if (errno != ENOENT) {
return -1;
}
if (!(dir = DirName(path))) {
return -1;
}
if (strcmp(dir, path)) {
rc = MakeDirs(dir, mode, e);
} else {
rc = -1;
}
free(dir);
if (rc == -1) return -1;
errno = e;
if (!mkdir(path, mode) || errno == EEXIST) {
errno = e;
return 0;
} else {
return -1;
}
}
#include "libc/sysv/consts/s.h"
#include "libc/sysv/errfuns.h"
/**
* Recursively creates directory a.k.a. folder.
* Creates directory and parent components.
*
* This function won't fail if the directory already exists.
* This function is similar to mkdir() except it iteratively creates
* parent directories and it won't fail if the directory already exists.
*
* @param path is a UTF-8 string, preferably relative w/ forward slashes
* @param mode can be, for example, 0755
* @return 0 on success or -1 w/ errno
* @raise EEXIST if named file already exists as non-directory
* @raise ENOTDIR if directory component in `path` existed as non-directory
* @raise ENAMETOOLONG if symlink-resolved `path` length exceeds `PATH_MAX`
* @raise ENAMETOOLONG if component in `path` exists longer than `NAME_MAX`
* @raise EROFS if parent directory is on read-only filesystem
* @raise ENOSPC if file system or parent directory is full
* @raise EACCES if write permission was denied on parent directory
* @raise EACCES if search permission was denied on component in `path`
* @raise ENOENT if `path` is an empty string
* @raise ELOOP if loop was detected resolving components of `path`
* @asyncsignalsafe
* @threadsafe
*/
int makedirs(const char *path, unsigned mode) {
return MakeDirs(path, mode, errno);
int c, e, i, n;
struct stat st;
char buf[PATH_MAX];
e = errno;
n = strlen(path);
if (n >= PATH_MAX) return enametoolong();
memcpy(buf, path, n + 1);
i = n;
// descend
while (i) {
if (!mkdir(buf, mode)) break;
if (errno == EEXIST) {
if (i == n) goto CheckTop;
break;
}
if (errno != ENOENT) return -1;
while (i && _isdirsep(buf[i - 1])) buf[--i] = 0;
while (i && !_isdirsep(buf[i - 1])) buf[--i] = 0;
}
// ascend
for (;;) {
if (mkdir(buf, mode)) {
if (errno != EEXIST) return -1;
if (i == n) goto CheckTop;
}
if (i == n) break;
while (i < n && !_isdirsep((c = path[i]))) buf[i++] = c;
while (i < n && _isdirsep((c = path[i]))) buf[i++] = c;
}
Finish:
errno = e;
return 0;
CheckTop:
if (stat(path, &st)) return -1;
if (S_ISDIR(st.st_mode)) goto Finish;
return eexist();
}

View file

@ -17,12 +17,7 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/dce.h"
#include "libc/nt/files.h"
#include "libc/nt/runtime.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/errfuns.h"
/**
* Creates directory a.k.a. folder.
@ -36,9 +31,21 @@
* @param path is a UTF-8 string, preferably relative w/ forward slashes
* @param mode can be, for example, 0755
* @return 0 on success or -1 w/ errno
* @error ENAMETOOLONG if >246 characters on NT
* @raise EEXIST if named file already exists
* @raise ENOTDIR if directory component in `path` existed as non-directory
* @raise ENAMETOOLONG if symlink-resolved `path` length exceeds `PATH_MAX`
* @raise ENAMETOOLONG if component in `path` exists longer than `NAME_MAX`
* @raise EROFS if parent directory is on read-only filesystem
* @raise ENOSPC if file system or parent directory is full
* @raise EACCES if write permission was denied on parent directory
* @raise EACCES if search permission was denied on component in `path`
* @raise ENOENT if a component within `path` didn't exist
* @raise ENOENT if `path` is an empty string
* @raise ELOOP if loop was detected resolving components of `path`
* @see makedirs() which is higher-level
* @see mkdirat() for modern call
* @asyncsignalsafe
* @see makedirs()
* @threadsafe
*/
int mkdir(const char *path, unsigned mode) {
return mkdirat(AT_FDCWD, path, mode);

View file

@ -31,12 +31,23 @@
/**
* Creates directory a.k.a. folder.
*
* @param dirfd is normally AT_FDCWD but if it's an open directory and
* @param dirfd is normally `AT_FDCWD` but if it's an open directory and
* path is relative, then path becomes relative to dirfd
* @param path is a UTF-8 string, preferably relative w/ forward slashes
* @param mode can be, for example, 0755
* @return 0 on success or -1 w/ errno
* @error EEXIST, ENOTDIR, ENAMETOOLONG, EACCES, ENOENT
* @param mode is permissions bits, which is usually 0755
* @return 0 on success, or -1 w/ errno
* @raise EEXIST if named file already exists
* @raise EBADF if `path` is relative and `dirfd` isn't `AT_FDCWD` or valid
* @raise ENOTDIR if directory component in `path` existed as non-directory
* @raise ENAMETOOLONG if symlink-resolved `path` length exceeds `PATH_MAX`
* @raise ENAMETOOLONG if component in `path` exists longer than `NAME_MAX`
* @raise EROFS if parent directory is on read-only filesystem
* @raise ENOSPC if file system or parent directory is full
* @raise EACCES if write permission was denied on parent directory
* @raise EACCES if search permission was denied on component in `path`
* @raise ENOENT if a component within `path` didn't exist
* @raise ENOENT if `path` is an empty string
* @raise ELOOP if loop was detected resolving components of `path`
* @asyncsignalsafe
* @see makedirs()
*/

View file

@ -20,22 +20,19 @@
#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/safemacros.internal.h"
#include "libc/intrin/bits.h"
#include "libc/macros.internal.h"
#include "libc/mem/mem.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/append.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/s.h"
#include "libc/sysv/consts/sig.h"
#include "libc/x/x.h"
#include "third_party/double-conversion/wrapper.h"
#include "libc/sysv/consts/timer.h"
/**
* @fileoverview Cosmopolitan Command Interpreter
@ -51,15 +48,25 @@
#define STATE_QUOTED_VAR 4
#define STATE_WHITESPACE 5
#define READ24LE(s) READ32LE(s "\0")
struct Env {
char *s;
int i;
};
static char *p;
static char *q;
static char *r;
static int envi;
static int vari;
static size_t n;
static char *cmd;
static char var[32];
static int lastchild;
static int exitstatus;
static char *args[8192];
static char *envs[3000];
static char *args[3000];
static const char *prog;
static char errbuf[512];
static char argbuf[ARG_MAX];
@ -94,13 +101,10 @@ static wontreturn void Wexit(int rc, const char *s, ...) {
}
static wontreturn void UnsupportedSyntax(unsigned char c) {
char cbuf[2];
char ibuf[13];
cbuf[0] = c;
cbuf[1] = 0;
char ibuf[13], cbuf[2] = {c};
FormatOctal32(ibuf, c, true);
Wexit(4, prog, ": unsupported shell syntax '", cbuf, "' (", ibuf, "): ", cmd,
"\n", 0);
Wexit(4, prog, ": unsupported syntax '", cbuf, "' (", ibuf, "): ", cmd, "\n",
0);
}
static wontreturn void SysExit(int rc, const char *call, const char *thing) {
@ -123,9 +127,9 @@ static void Open(const char *path, int fd, int flags) {
}
static wontreturn void Exec(void) {
_npassert(args[0][0]);
_unassert(args[0][0]);
if (!n) Wexit(5, prog, ": error: too few args\n", 0);
execvp(args[0], args);
execvpe(args[0], args, envs);
SysExit(127, "execve", args[0]);
}
@ -138,6 +142,42 @@ static int GetSignalByName(const char *s) {
return 0;
}
static struct Env GetEnv(char **p, const char *k) {
int i, j;
for (i = 0; p[i]; ++i) {
for (j = 0;; ++j) {
if (!k[j] || k[j] == '=') {
if (p[i][j] == '=') {
return (struct Env){p[i] + j + 1, i};
}
break;
}
if (toupper(k[j] & 255) != toupper(p[i][j] & 255)) {
break;
}
}
}
return (struct Env){0, i};
}
static void PutEnv(char **p, const char *kv) {
struct Env e;
e = GetEnv(p, kv);
p[e.i] = kv;
if (!e.s) p[e.i + 1] = 0;
}
static void Append(int c) {
_npassert(q + 1 < argbuf + sizeof(argbuf));
*q++ = c;
}
static char *Finish(void) {
char *s = r;
Append(0);
return r = q, s;
}
static int True(void) {
return 0;
}
@ -194,31 +234,31 @@ static int Echo(void) {
}
static int Read(void) {
char *b = 0;
unsigned char c;
int a = 1, rc = 1;
if (n >= 3 && !strcmp(args[1], "-p")) {
Write(1, args[2]);
a = 3;
}
appendr(&b, 0);
while (read(0, &c, 1) > 0) {
if (c == '\n') {
rc = 0;
break;
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]);
}
appendw(&b, c);
}
if (a < n) {
setenv(args[1], b, true);
if (i >= n) return 1;
for (j = 0; args[i][j]; ++j) {
Append(args[i][j]);
}
free(b);
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 = n > 1 ? args[1] : getenv("HOME");
if (s) {
const char *s;
if ((s = n > 1 ? args[1] : GetEnv(envs, "HOME").s)) {
if (!chdir(s)) {
return 0;
} else {
@ -274,36 +314,37 @@ static int Toupper(void) {
}
static int Usleep(void) {
struct timespec t, *p = 0;
int f;
struct timespec t;
if (n > 1) {
f = 0;
t = _timespec_frommicros(atoi(args[1]));
p = &t;
} else {
f = TIMER_ABSTIME;
t = _timespec_max;
}
return clock_nanosleep(0, 0, p, 0);
return clock_nanosleep(0, f, &t, 0);
}
static int Test(void) {
int w;
struct stat st;
if (n && !strcmp(args[n - 1], "]")) --n;
if (n == 4 && !strcmp(args[2], "=")) {
return !!strcmp(args[1], args[3]);
} else if (n == 4 && !strcmp(args[2], "!=")) {
return !strcmp(args[1], args[3]);
} else if (n == 3 && !strcmp(args[1], "-n")) {
return !(strlen(args[2]) > 0);
} else if (n == 3 && !strcmp(args[1], "-z")) {
return !(strlen(args[2]) == 0);
} else if (n == 3 && !strcmp(args[1], "-e")) {
return !!stat(args[2], &st);
} else if (n == 3 && !strcmp(args[1], "-f")) {
return !stat(args[2], &st) && S_ISREG(st.st_mode);
} else if (n == 3 && !strcmp(args[1], "-d")) {
return !stat(args[2], &st) && S_ISDIR(st.st_mode);
} else if (n == 3 && !strcmp(args[1], "-h")) {
return !stat(args[2], &st) && S_ISLNK(st.st_mode);
} else {
return 1;
if (n && READ16LE(args[n - 1]) == READ16LE("]")) --n;
if (n == 4) {
w = READ32LE(args[2]) & 0x00ffffff;
if ((w & 65535) == READ16LE("=")) return !!strcmp(args[1], args[3]);
if (w == READ24LE("==")) return !!strcmp(args[1], args[3]);
if (w == READ24LE("!=")) return !strcmp(args[1], args[3]);
} else if (n == 3) {
w = READ32LE(args[1]) & 0x00ffffff;
if (w == READ24LE("-n")) return !(strlen(args[2]) > 0);
if (w == READ24LE("-z")) return !(strlen(args[2]) == 0);
if (w == READ24LE("-e")) return !!stat(args[2], &st);
if (w == READ24LE("-f")) return !stat(args[2], &st) && S_ISREG(st.st_mode);
if (w == READ24LE("-d")) return !stat(args[2], &st) && S_ISDIR(st.st_mode);
if (w == READ24LE("-h")) return !stat(args[2], &st) && S_ISLNK(st.st_mode);
}
return 1;
}
static int TryBuiltin(void) {
@ -370,7 +411,7 @@ static const char *IntToStr(int x) {
return ibuf;
}
static const char *GetEnv(const char *key) {
static const char *GetVar(const char *key) {
if (key[0] == '$' && !key[1]) {
return IntToStr(getpid());
} else if (key[0] == '!' && !key[1]) {
@ -378,7 +419,7 @@ static const char *GetEnv(const char *key) {
} else if (key[0] == '?' && !key[1]) {
return IntToStr(exitstatus);
} else {
return getenv(key);
return GetEnv(envs, key).s;
}
}
@ -387,16 +428,28 @@ static bool IsVarName(int c) {
(!vari && (c == '?' || c == '!' || c == '$'));
}
static inline void Append(int c) {
_unassert(q + 1 < argbuf + sizeof(argbuf));
*q++ = 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) {
char *r;
const char *s;
int c, t, j, k, rc;
for (r = q, t = STATE_WHITESPACE;; ++p) {
for (t = STATE_WHITESPACE;; ++p) {
switch (t) {
case STATE_WHITESPACE:
@ -413,8 +466,7 @@ static char *Tokenize(void) {
UnsupportedSyntax(*p);
}
if (!*p || *p == ' ' || *p == '\t') {
Append(0);
return r;
return Finish();
} else if (*p == '"') {
t = STATE_QUOTED;
} else if (*p == '\'') {
@ -427,8 +479,7 @@ static char *Tokenize(void) {
Append(*++p);
} else if (*p == '|') {
if (q > r) {
Append(0);
return r;
return Finish();
} else if (p[1] == '|') {
rc = Run();
if (!rc) {
@ -443,16 +494,14 @@ static char *Tokenize(void) {
}
} else if (*p == ';') {
if (q > r) {
Append(0);
return r;
return Finish();
} else {
Run();
t = STATE_WHITESPACE;
}
} else if (*p == '&') {
if (q > r) {
Append(0);
return r;
return Finish();
} else if (p[1] == '&') {
rc = Run();
if (!rc) {
@ -471,22 +520,9 @@ static char *Tokenize(void) {
break;
case STATE_VAR:
if (IsVarName(*p)) {
_unassert(vari + 1 < sizeof(var));
var[vari++] = *p;
var[vari] = 0;
} else {
// XXX: we need to find a simple elegant way to break up
// unquoted variable expansions into multiple args.
if ((s = GetEnv(var))) {
if ((j = strlen(s))) {
_unassert(q + j < argbuf + sizeof(argbuf));
q = mempcpy(q, s, j);
}
}
--p;
t = STATE_CMD;
}
// 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:
@ -501,6 +537,11 @@ static char *Tokenize(void) {
UnterminatedString:
Wexit(6, "cmd: error: unterminated string\n", 0);
case STATE_QUOTED_VAR:
if (!*p) goto UnterminatedString;
if (CopyVar()) t = STATE_QUOTED, --p;
break;
case STATE_QUOTED:
if (!*p) goto UnterminatedString;
if (*p == '"') {
@ -529,24 +570,6 @@ static char *Tokenize(void) {
}
break;
case STATE_QUOTED_VAR:
if (!*p) goto UnterminatedString;
if (IsVarName(*p)) {
_unassert(vari + 1 < sizeof(var));
var[vari++] = *p;
var[vari] = 0;
} else {
if ((s = GetEnv(var))) {
if ((j = strlen(s))) {
_unassert(q + j < argbuf + sizeof(argbuf));
q = mempcpy(q, s, j);
}
}
--p;
t = STATE_QUOTED;
}
break;
default:
unreachable;
}
@ -563,7 +586,7 @@ static const char *GetRedirectArg(const char *prog, const char *arg, int n) {
}
}
int cocmd(int argc, char *argv[]) {
int _cocmd(int argc, char **argv, char **envp) {
char *arg;
size_t i, j;
prog = argc > 0 ? argv[0] : "cocmd.com";
@ -596,8 +619,19 @@ int cocmd(int argc, char *argv[]) {
Wexit(12, prog, ": error: cmd too long: ", cmd, "\n", 0);
}
// copy environment variables
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;
q = argbuf;
r = q = argbuf;
while ((arg = Tokenize())) {
if (n + 1 < ARRAYLEN(args)) {
if (isdigit(arg[0]) && arg[1] == '>' && arg[2] == '&' &&

View file

@ -53,6 +53,7 @@ void mcount(void);
int daemon(int, int);
int _freestack(void *);
void _bt(const char *, ...);
int _cocmd(int, char **, char **);
unsigned long getauxval(unsigned long);
void *_mapanon(size_t) attributeallocsize((1)) mallocesque;
void *_mapshared(size_t) attributeallocsize((1)) mallocesque;

View file

@ -1,10 +0,0 @@
#ifndef COSMOPOLITAN_LIBC_STDIO_COCMD_INTERNAL_H_
#define COSMOPOLITAN_LIBC_STDIO_COCMD_INTERNAL_H_
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
int cocmd(int, char **);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_STDIO_COCMD_INTERNAL_H_ */

View file

@ -21,7 +21,6 @@
#include "libc/errno.h"
#include "libc/paths.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/cocmd.internal.h"
#include "libc/stdio/internal.h"
#include "libc/stdio/stdio.h"
#include "libc/sysv/consts/f.h"
@ -57,7 +56,7 @@ FILE *popen(const char *cmdline, const char *mode) {
// we can't rely on cloexec because cocmd builtins don't execve
if (pipefds[0] != !dir) _unassert(!close(pipefds[0]));
if (pipefds[1] != !dir) _unassert(!close(pipefds[1]));
_Exit(cocmd(3, (char *[]){"popen", "-c", cmdline, 0}));
_Exit(_cocmd(3, (char *[]){"popen", "-c", cmdline, 0}, environ));
default:
f->pid = pid;
_unassert(!close(pipefds[!dir]));

View file

@ -86,7 +86,6 @@ int setvbuf(FILE *, char *, int, size_t);
int pclose(FILE *);
char *ctermid(char *);
void perror(const char *) relegated;
int makedirs(const char *, unsigned);
typedef uint64_t fpos_t;
char *gets(char *) paramsnonnull();

View file

@ -24,8 +24,6 @@
#include "libc/log/log.h"
#include "libc/paths.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/cocmd.internal.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/ok.h"
#include "libc/sysv/consts/sig.h"
@ -57,7 +55,7 @@ int system(const char *cmdline) {
sigaction(SIGINT, &saveint, 0);
sigaction(SIGQUIT, &savequit, 0);
sigprocmask(SIG_SETMASK, &savemask, 0);
_Exit(cocmd(3, (char *[]){"system", "-c", cmdline, 0}));
_Exit(_cocmd(3, (char *[]){"system", "-c", cmdline, 0}, environ));
} else if (pid != -1) {
while (wait4(pid, &wstatus, 0, 0) == -1) {
if (errno != EINTR) {

View file

@ -16,12 +16,41 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/errno.h"
#include "libc/mem/gc.internal.h"
#include "libc/mem/mem.h"
#include "libc/stdio/stdio.h"
#include "libc/testlib/testlib.h"
#include "libc/thread/spawn.h"
#include "libc/thread/thread.h"
#include "libc/x/x.h"
TEST(makedirs, empty) {
ASSERT_SYS(ENOENT, -1, makedirs("", 0755));
}
TEST(makedirs, isfile) {
ASSERT_SYS(0, 0, xbarf("f", "hi", -1));
ASSERT_SYS(EEXIST, -1, makedirs("f", 0755));
}
TEST(makedirs, isdir) {
ASSERT_SYS(0, 0, mkdir("d", 0755));
ASSERT_SYS(0, 0, makedirs("d", 0755));
}
TEST(makedirs, enotdir) {
ASSERT_SYS(0, 0, xbarf("f", "hi", -1));
ASSERT_SYS(ENOTDIR, -1, makedirs("f/d", 0755));
}
TEST(makedirs, basic) {
ASSERT_SYS(0, 0, makedirs("a/b/c", 0755));
ASSERT_TRUE(isdirectory("a/b/c"));
ASSERT_SYS(0, 0, makedirs("a/b/c/d/e/", 0755));
ASSERT_SYS(0, 0, makedirs("a/b/c/d/e/", 0755));
ASSERT_TRUE(isdirectory("a/b/c/d/e"));
}
#define DIR \
"a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/A/B/C/D/E/F/G/H/I/J/K/" \

View file

@ -17,12 +17,14 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/dce.h"
#include "libc/mem/copyfd.internal.h"
#include "libc/mem/gc.h"
#include "libc/paths.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/sig.h"
#include "libc/testlib/ezbench.h"
#include "libc/testlib/testlib.h"
#include "libc/x/x.h"
@ -105,3 +107,12 @@ TEST(system, notequals) {
ASSERT_EQ(1, WEXITSTATUS(system("test a != a")));
ASSERT_EQ(0, WEXITSTATUS(system("test a != b")));
}
TEST(system, usleep) {
ASSERT_EQ(0, WEXITSTATUS(system("usleep & kill $!")));
}
TEST(system, kill) {
int ws = system("kill -TERM $$; usleep");
if (!IsWindows()) ASSERT_EQ(SIGTERM, WTERMSIG(ws));
}

View file

@ -16,8 +16,8 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/stdio/cocmd.internal.h"
#include "libc/runtime/runtime.h"
int main(int argc, char *argv[]) {
return cocmd(argc, argv);
int main(int argc, char **argv, char **envp) {
return _cocmd(argc, argv, envp);
}