Fix bugs and add security features to redbean

- Fix a regression with the previous change that broke redbean
- Add chroot(), resource limit, seccomp, and other stuff to redbean
- Write lots and lots of documentation
- Iron out more system call issues
This commit is contained in:
Justine Tunney 2022-04-18 00:01:26 -07:00
parent f1dfa4bdfa
commit 7166679620
182 changed files with 1855 additions and 918 deletions

View file

@ -3,7 +3,7 @@
#define RUNITD_PORT 31337
#define RUNITD_MAGIC 0xFEEDABEEu
#define RUNITD_TIMEOUT_MS (1000 * 30)
#define RUNITD_TIMEOUT_MS (1000 * 60 * 60)
enum RunitCommand {
kRunitExecute,

View file

@ -1,6 +1,6 @@
SYNOPSIS
redbean.com [-hvduzmbagf] [-p PORT] [-D DIR] [-- SCRIPTARGS...]
redbean.com [-?BVabdfghjkmsuvz] [-p PORT] [-D DIR] [-- SCRIPTARGS...]
DESCRIPTION
@ -26,7 +26,7 @@ OVERVIEW
FLAGS
-h help
-h or -? help
-d daemonize
-u uniprocess
-z print port
@ -39,6 +39,7 @@ FLAGS
-E show crash reports to public ips
-j enable ssl client verify
-k disable ssl fetch verify
-Z log worker system calls
-f log worker function calls
-B only use stronger cryptography
-s increase silence [repeatable]
@ -1184,7 +1185,7 @@ RE MODULE
re.NEWLINE
Use this flag to change the handling of NEWLINE (\x0a) characters.
When this flag is set, (1) a NEWLINE shall not be matched by a "."
or any form of a non-matching list, (2) a "^" shall match the
or any form of a non-matching list, () a "^" shall match the
zero-length string immediately after a NEWLINE (regardless of
re.NOTBOL), and (3) a "$" shall match the zero-length string
immediately before a NEWLINE (regardless of re.NOTEOL).
@ -1226,53 +1227,57 @@ MAXMIND MODULE
UNIX MODULE
This module exports the best raw system calls from Cosmopolitan Libc.
These UNIX APIs are supported across all supported operating systems,
and that includes Windows.
fork exit stat open close seek read write access fcntl chdir chown
chmod getcwd kill raise wait pipe dup mkdir rmdir opendir rename
link unlink symlink sync fsync fdatasync truncate umask getppid
getpgrp getpgid setpgid getsid setsid getpid getuid getgid gettime
nanosleep socket socketpair bind listen accept connect recvfrom
sendto shutdown getpeername getsockname sigaction sigprocmask
strerror
This module also provides the following magic numbers:
O_RDONLY O_WRONLY O_RDWR O_ACCMODE O_CREAT O_EXCL O_TRUNC
O_CLOEXEC O_APPEND O_TMPFILE O_NOFOLLOW O_SYNC O_ASYNC O_NOCTTY
O_NOATIME O_EXEC O_SEARCH O_DSYNC O_RSYNC O_PATH O_VERIFY O_SHLOCK
O_EXLOCK O_RANDOM O_SEQUENTIAL O_COMPRESSED O_INDEXED SEEK_SET
SEEK_CUR SEEK_END F_GETFD F_SETFD F_GETFL F_SETFL F_UNLCK F_RDLCK
F_WRLCK F_SETLK F_SETLKW FD_CLOEXEC R_OK W_OK X_OK F_OK WNOHANG
CLOCK_REALTIME CLOCK_MONOTONIC CLOCK_MONOTONIC_RAW
CLOCK_REALTIME_COARSE CLOCK_MONOTONIC_COARSE
CLOCK_PROCESS_CPUTIME_ID CLOCK_TAI CLOCK_PROF CLOCK_BOOTTIME
CLOCK_REALTIME_ALARM CLOCK_BOOTTIME_ALARM AF_UNSPEC AF_UNIX
AF_INET SOCK_STREAM SOCK_DGRAM SOCK_CLOEXEC IPPROTO_TCP
IPPROTO_UDP SHUT_RD SHUT_WR SHUT_RDWR MSG_WAITALL MSG_DONTROUTE
MSG_PEEK MSG_OOB MSG_NOSIGNAL DT_UNKNOWN DT_REG DT_DIR DT_BLK
DT_LNK DT_CHR DT_FIFO DT_SOCK AT_FDCWD AT_SYMLINK_NOFOLLOW
SIG_BLOCK SIG_UNBLOCK SIG_SETMASK SIG_DFL SIG_IGN
Please see the Cosmopolitan Libc documentation for further details.
There's also a /unix.lua file in redbean-demo.com that provides a
glimpse of how these powerful APIs can be used. Here's a synopsis:
unix.open(path, flags[, mode]) → fd, errno
Opens file.
unix.read(fd[, bufsiz, offset]) → data, errno
unix.read(fd:int[, bufsiz:int, offset:int]) → data:str, errno:int
Reads from file descriptor.
unix.write(fd, data[, offset]) → rc, errno
unix.write(fd:int, data[, offset]) → rc:int, errno:int
Writes to file descriptor.
unix.close(fd) → rc, errno
unix.open(path:str, flags:int[, mode:int]) → fd:int[, errno:int]
Opens file.
`flags` should have one of `O_RDONLY`, `O_WRONLY`, or `O_RDWR`.
The following values may also be OR'd into `flags`:
- `O_CREAT`: Create file if it doesn't exist.
- `O_TRUNC` Automatic truncate(fd,0) if exists.
- `O_CLOEXEC`: Automatic close() upon execve().
- `O_EXCL`: Exclusive access. See below.
- `O_APPEND`: Open file for append only.
- `O_DIRECT` (not supported on Apple and OpenBSD)
- `O_DIRECTORY` (hint on UNIX but required on NT)
- `O_TMPFILE` (for Linux and Windows only)
- `O_NOFOLLOW` (zero on Windows)
- `O_DSYNC` (zero on non-Linux/Apple)
- `O_RSYNC` (zero on non-Linux/Apple)
- `O_PATH` (zero on non-Linux)
- `O_VERIFY` (zero on non-FreeBSD)
- `O_SHLOCK` (zero on non-BSD)
- `O_EXLOCK` (zero on non-BSD)
- `O_RANDOM` (zero on non-Windows)
- `O_SEQUENTIAL` (zero on non-Windows)
- `O_COMPRESSED` (zero on non-Windows)
- `O_INDEXED` (zero on non-Windows)
There are three regular combinations for the above flags:
- `O_RDONLY`: Opens existing file for reading. If it doesn't
exist then nil is returned and errno will be `ENOENT` (or in
some other cases `ENOTDIR`).
- `O_WRONLY|O_CREAT|O_TRUNC`: Creates file. If it already
exists, then the existing copy is destroyed and the opened
file will start off with a length of zero. This is the
behavior of the traditional creat() system call.
- `O_WRONLY|O_CREAT|O_EXCL`: Create file only if doesn't exist
already. If it does exist then `nil` is returned along with
`errno` set to `EEXIST`.
unix.close(fd:int) → rc:int, errno:int
Closes file descriptor.
@ -1280,14 +1285,15 @@ UNIX MODULE
Invokes `_Exit(exitcode)` on the process. This will immediately
halt the current process. Memory will be freed. File descriptors
will be closed. Any open connections it owns will be reset.
will be closed. Any open connections it owns will be reset. This
function never returns.
unix.fork() → childpid|0, errno
unix.fork() → childpid|0, errno:int
Creates a new process mitosis style. This returns twice. The
parent process gets the nonzero pid. The child gets zero.
unix.commandv(prog) → path, errno
unix.commandv(prog) → path, errno:int
Performs `$PATH` lookup of executable. We automatically suffix
`.com` and `.exe` automatically for all platforms when path
@ -1309,120 +1315,131 @@ UNIX MODULE
The first element in `argv` should be `prog`. This function is
normally called after forking.
unix.access(path, mode) → rc, errno
unix.access(path:str, how) → rc:int, errno:int
Checks if effective user of current process has permission to
access file. `mode` can be `R_OK`, `W_OK`, `X_OK`, or `F_OK` to
access file. `how` can be `R_OK`, `W_OK`, `X_OK`, or `F_OK` to
check for read, write, execute, and existence respectively.
unix.mkdir(path, mode) → rc, errno
unix.mkdir(path:str, mode) → rc:int, errno:int
Makes directory. `mode` should be octal, e.g. `0755`.
unix.chdir(path) → rc, errno
unix.chdir(path:str) → rc:int, errno:int
Changes current directory to `path`.
unix.unlink(path) → rc, errno
unix.unlink(path:str) → rc:int, errno:int
Removes file at `path`.
unix.rmdir(path) → rc, errno
unix.rmdir(path:str) → rc:int, errno:int
Removes empty directory at `path`.
unix.rename(oldpath, newpath) → rc, errno
unix.link(existingpath, newpath) → rc, errno
unix.symlink(target, linkpath) → rc, errno
unix.chown(path, uid, gid) → rc, errno
unix.chmod(path, mode) → rc, errno
unix.getcwd(path, mode) → rc, errno
unix.chroot(path:str) → rc:int, errno:int
Changes root directory. Raises `ENOSYS` on Windows.
unix.dup(oldfd[, newfd[, flags]]) → newfd:int, errno:int
Duplicates file descriptor. `flags` can have `O_CLOEXEC`.
unix.pipe([flags]) → reader, writer, errno:int
Creates fifo which enables communication between processes.
Returns two file descriptors: one for reading and one for
writing. `flags` can have `O_CLOEXEC`. On error, `reader` and
`writer` will be `nil` and `errno` will be set to non-nil.
unix.rename(oldpath, newpath) → rc:int, errno:int
unix.link(existingpath, newpath) → rc:int, errno:int
unix.symlink(target, linkpath) → rc:int, errno:int
unix.chown(path:str, uid, gid) → rc:int, errno:int
unix.chmod(path:str, mode) → rc:int, errno:int
unix.getcwd(path:str, mode) → rc:int, errno:int
unix.getpid() → pid
unix.getppid() → pid
unix.kill(pid, sig) → rc, errno
unix.raise(sig) → rc, errno
unix.wait(pid[, options]) → pid, wstatus, nil, errno
unix.fcntl(fd, cmd[, arg]) → rc, errno
unix.dup(oldfd[, newfd[, flags]]) → newfd, errno
flags can have O_CLOEXEC
unix.pipe([flags]) → reader, writer, errno
flags can have O_CLOEXEC
unix.getsid(pid) → sid, errno
unix.getpgrp() → pgid, errno
unix.getpgid(pid) → pgid, errno
unix.setpgid(pid, pgid) → pgid, errno
unix.setsid() → sid, errno
unix.getuid() → uid, errno
unix.getgid() → gid, errno
unix.umask(mask) → rc, errno
unix.gettime([clock]) → seconds, nanos, errno
unix.nanosleep(seconds, nanos) → remseconds, remnanos, errno
unix.sync(fd)
unix.fsync(fd) → rc, errno
unix.fdatasync(fd) → rc, errno
unix.kill(pid, sig) → rc:int, errno:int
unix.raise(sig) → rc:int, errno:int
unix.wait(pid[, options]) → pid, wstatus, nil, errno:int
unix.fcntl(fd:int, cmd[, arg]) → rc:int, errno:int
unix.getsid(pid) → sid, errno:int
unix.getpgrp() → pgid, errno:int
unix.getpgid(pid) → pgid, errno:int
unix.setpgid(pid, pgid) → pgid, errno:int
unix.setsid() → sid, errno:int
unix.getuid() → uid, errno:int
unix.getgid() → gid, errno:int
unix.umask(mask) → rc:int, errno:int
unix.gettime([clock]) → seconds, nanos, errno:int
unix.nanosleep(seconds, nanos) → remseconds, remnanos, errno:int
unix.sync(fd:int)
unix.fsync(fd:int) → rc:int, errno:int
unix.fdatasync(fd:int) → rc:int, errno:int
unix.seek(fd, offset, whence) → newpos, errno
unix.seek(fd:int, offset, whence) → newpos, errno:int
where whence ∈ {SEEK_SET, SEEK_CUR, SEEK_END}
whence defaults to SEEK_SET
unix.truncate(path, length) → rc, errno
unix.truncate(fd, length) → rc, errno
unix.truncate(path:str, length) → rc:int, errno:int
unix.truncate(fd:int, length) → rc:int, errno:int
unix.socket([family[, type[, protocol]]]) → fd, errno
unix.socket([family[, type[, protocol]]]) → fd:int[, errno:int]
`SOCK_CLOEXEC` may be or'd into type
`family` defaults to `AF_INET`
`type` defaults to `SOCK_STREAM`
`protocol` defaults to `IPPROTO_TCP`
unix.socketpair([family[, type[, protocol]]]) → fd1, fd2, errno
unix.socketpair([family[, type[, protocol]]]) → fd1, fd2, errno:int
`SOCK_CLOEXEC` may be or'd into type
`family` defaults to `AF_INET`
`type` defaults to `SOCK_STREAM`
`protocol` defaults to `IPPROTO_TCP`
unix.bind(fd, ip, port) → rc, errno
unix.bind(fd:int, ip, port) → rc:int, errno:int
unix.connect(fd, ip, port) → rc, errno
unix.connect(fd:int, ip, port) → rc:int, errno:int
unix.listen(fd[, backlog]) → rc, errno
unix.listen(fd:int[, backlog]) → rc:int, errno:int
unix.getsockname(fd) → ip, port, errno
unix.getsockname(fd:int) → ip, port, errno:int
unix.getpeername(fd) → ip, port, errno
unix.getpeername(fd:int) → ip, port, errno:int
unix.accept(serverfd) → clientfd, ip, port, errno
unix.accept(serverfd) → clientfd, ip, port, errno:int
unix.recv(fd[, bufsiz[, flags]]) → data, errno
unix.recv(fd:int[, bufsiz[, flags]]) → data, errno:int
`flags` can have MSG_{WAITALL,DONTROUTE,PEEK,OOB}, etc.
unix.recvfrom(fd[, bufsiz[, flags]]) → data, ip, port, errno
unix.recvfrom(fd:int[, bufsiz[, flags]]) → data, ip, port, errno:int
`flags` can have MSG_{WAITALL,DONTROUTE,PEEK,OOB}, etc.
unix.send(fd, data[, flags]) → sent, errno
unix.send(fd:int, data[, flags]) → sent, errno:int
This is the same as `write` except it has a `flags` argument
that's intended for sockets. `flags` can have `MSG_OOB`,
`MSG_DONTROUTE`, or `MSG_NOSIGNAL`.
unix.sendto(fd, data, ip, port[, flags]) → sent, errno
unix.sendto(fd:int, data, ip, port[, flags]) → sent, errno:int
This is useful for sending messages over UDP sockets to specific
addresses. The `flags` parameter can have `MSG_OOB`,
`MSG_DONTROUTE`, or `MSG_NOSIGNAL`.
unix.shutdown(fd, how) → rc, errno
unix.shutdown(fd:int, how:int) → rc:int, errno:int
Partially closes socket. `how` can be `SHUT_RD`, `SHUT_WR`, or
`SHUT_RDWR`.
unix.sigprocmask(how[, mask]) → oldmask, errno
unix.sigprocmask(how[, mask]) → oldmask, errno:int
`how` can be `SIG_BLOCK`, `SIG_UNBLOCK`, `SIG_SETMASK`
unix.sigaction(sig[, handler[, flags[, mask]]]) → handler, flags, mask, errno
unix.sigaction(sig[, handler[, flags[, mask]]]) → handler, flags, mask, errno:int
`handler` can be `SIG_IGN`, `SIG_DFL`, `intptr_t`, or a Lua
function. `sig` can be `SIGINT`, `SIGQUIT`, `SIGTERM`, etc.
@ -1443,7 +1460,7 @@ UNIX MODULE
Waits for signal to be delivered.
unix.setitimer(which[, intsec, intmicros, valsec, valmicros])
→ intsec, intns, valsec, valns, errno
→ intsec, intns, valsec, valns, errno:int
Causes `SIGALRM` signals to be generated at some point(s) in the
future. The `which` parameter should be `ITIMER_REAL`.
@ -1474,38 +1491,387 @@ UNIX MODULE
Turns platform-specific `sig` code into its name, e.g.
`strsignal(9)` always returns `"SIGKILL"`.
Here's your UnixStat* object.
unix.stat(x) → UnixStat*, errno:int
Gets information about file or directory. `x` may be a file or
directory path string, or it may be a file descriptor int that
was made by open().
unix.opendir(path:str) → UnixDir*, errno:int
Opens directory for listing its contents.
unix.opendir(fd:int) → UnixDir*, errno:int
Opens directory for listing its contents, using a file
descriptor from `open(path, O_RDONLY|O_DIRECTORY)`.
UnixDir* Object
UnixDir:close() → rc:int[, errno:int]
may be called multiple times
called by the garbage collector too
UnixDir:read() → name:str, kind:int, ino:int, off:int[, errno:int]
Returns `nil` if there are no more entries. Or error, `nil` will
be returned and `errno` will be non-nil.
`kind` can be `DT_UNKNOWN`, `DT_REG`, `DT_DIR`, `DT_BLK`,
`DT_LNK`, `DT_CHR`, `DT_FIFO`, or `DT_SOCK`.
UnixDir:fd() → fd:int[, errno:int]
EOPNOTSUPP if using /zip/
EOPNOTSUPP if IsWindows()
UnixDir:tell() → offset:int
Returns current arbitrary offset into stream.
UnixDir:rewind()
Resets stream back to beginning.
UnixStat* object.
unix.stat(path) → UnixStat*, errno
unix.stat(fd) → UnixStat*, errno
UnixStat:size() → bytes:int
Size of file in bytes.
UnixStat:mode() → mode:int
Contains file type and permissions.
For example, `0010644` is what you might see for a file and
`0040755` is what you might see for a directory.
To determine the file type:
- `(st:mode() & 0170000) == 0010000` means fifo or pipe
- `(st:mode() & 0170000) == 0020000` means character device
- `(st:mode() & 0170000) == 0040000` means directory
- `(st:mode() & 0170000) == 0060000` means block device
- `(st:mode() & 0170000) == 0100000` means regular file
- `(st:mode() & 0170000) == 0120000` means symbolic link
- `(st:mode() & 0170000) == 0140000` means socket
UnixStat:atim() → secs:int, nanos:int
Size of file in bytes.
UnixStat:uid() → int
User ID of file owner.
UnixStat:gid() → int
Group ID of file owner.
UnixStat:mtim() → secs:int, nanos:int
Last modified time.
UnixStat:birthtim() → secs:int, nanos:int
Creation time. Note that on Linux this is the mimimum of
atom/mtim/ctim.
UnixStat:ctim() → secs:int, nanos:int
Complicated time. Means time file status was last changed on
UNIX. Means creation time on Windows.
UnixStat:blocks() → int
Number of blocks used by storage medium.
UnixStat:blksize() → int
Block size is usually 4096 for file system files.
UnixStat:dev() → int
UnixStat:ino() → int
UnixStat:rdev() → int
UnixStat:uid() → int
UnixStat:gid() → int
UnixStat:atim() → secs:int, nanos:int
UnixStat:mtim() → secs:int, nanos:int
UnixStat:ctim() → secs:int, nanos:int
UnixStat:blocks() → int
UnixStat:blksize() → int
Here's your UnixDir* object.
Here are your error numbers:
- `EINVAL`: Invalid argument. Raised by [pretty much everything].
- `ENOSYS`: System call not available on this platform. On Windows
this is raised by chroot(), setuid(), setgid().
- `EPERM`: Operation not permitted. Raised by accept(), adjtimex(),
arch_prctl(), bdflush(), capget(), chmod(), chown(), chroot(),
clock_getres(), copy_file_range(), execve(), fcntl(),
get_robust_list(), getdomainname(), getgroups(), gethostname(),
getpriority(), getrlimit(), getsid(), gettimeofday(), kill(),
link(), mbind(), membarrier(), migrate_pages(), mkdir(), mknod(),
mlock(), mmap(), msgctl(), nice(), open(), prctl(), ptrace(),
reboot(), rename(), rmdir(), sched_setaffinity(),
sched_setattr(), sched_setparam(), sched_setscheduler(),
seteuid(), setfsgid(), setfsuid(), setgid(), setpgid(),
setresuid(), setreuid(), setsid(), setuid(), setup(), shmget(),
sigaltstack(), stime(), swapon(), symlink(), syslog(),
timer_create(), timerfd_create(), tkill(), truncate(), u
unlink(), utime(), utimensat(), vhangup(), vm86(), write().
- `ENOENT`: no such file or directory. Raised by access(),
alloc_hugepages(), bind(), chdir(), chmod(), chown(), chroot(),
clock_getres(), execve(), opendir(), inotify_add_watch(), kcmp(),
link(), mkdir(), mknod(), msgget(), open(), readlink(), rename(),
rmdir(), semget(), shmget(), stat(), swapon(), symlink(),
truncate(), unlink(), utime(), utimensat().
- `ESRCH`: No such process. Raised by getpriority(), getrlimit(),
getsid(), ioprio_set(), kill(), setpgid(), tkill(), utimensat(),
- `EINTR`: The greatest of all errnos; crucial for building real
time reliable software. Raised by accept(), clock_nanosleep(),
close(), connect(), dup(), fcntl(), flock(), getrandom(),
nanosleep(), open(), pause(), poll(), ptrace(), read(), recv(),
select(), send(), sigsuspend(), sigwaitinfo(), truncate(),
wait(), write()
- `EIO`: Raised by access() acct() chdir() chmod() chown() chroot()
close() copy_file_range() execve() fallocate() fsync() ioperm()
link() madvise() mbind() pciconfig_read() ptrace() read()
readlink() sendfile() statfs() symlink() sync_file_range()
truncate() unlink() write()
- `ENXIO`: No such device or address. Raised by lseek(), open(),
prctl()
- `E2BIG`: Argument list too long. Raised by execve(), msgop(),
sched_setattr(), semop()
- `ENOEXEC`: exec format error. Raised by execve(), kexec_load(),
uselib()
- `EBADF`: bad file descriptor; cf. EBADFD. Raised by accept(),
access(), bind(), chdir(), chmod(), chown(), close(), connect(),
copy_file_range(), dup(), fcntl(), flock(), fsync(), futimesat(),
opendir(), getpeername(), getsockname(), getsockopt(),
inotify_add_watch(), inotify_rm_watch(), ioctl(), kcmp(),
kexec_load(), link(), listen(), llseek(), lseek(), mkdir(),
mknod(), mmap(), open(), prctl(), read(), readahead(),
readlink(), recv(), rename(), select(), send(), shutdown(),
splice(), stat(), symlink(), sync(), sync_file_range(),
timerfd_create(), truncate(), unlink(), utimensat(), write(),
- `ECHILD`: no child process. Raised by wait(), waitpid(),
waitid(), wait3(), wait4()
- `EAGAIN`: resource temporarily unavailable (e.g. SO_RCVTIMEO
expired, too many processes, too much memory locked, read or
write with O_NONBLOCK needs polling, etc.). Raised by accept(),
connect(), eventfd(), fcntl(), fork(), getrandom(), mincore(),
mlock(), mmap(), mremap(), msgop(), poll(), read(), select(),
send(), setresuid(), setreuid(), setuid(), sigwaitinfo(),
splice(), tee(), timer_create(), timerfd_create(), tkill(),
write(),
- `ENOMEM`: We require more vespene gas. Raised by access(),
bind(), chdir(), chmod(), chown(), chroot(), clone(),
copy_file_range(), create_module(), eventfd(), execve(),
fanotify_init(), fork(), getgroups(), getrlimit(),
inotify_add_watch(), inotify_init(), ioperm(), kexec_load(),
link(), mbind(), memfd_create(), mincore(), mkdir(), mknod(),
mlock(), mmap(), mprotect(), mremap(), msgget(), msgop(),
msync(), open(), poll(), readlink(), recv(), rename(), rmdir(),
select(), semget(), send(), shmget(), sigaltstack(), splice(),
stat(), subpage_prot(), swapon(), symlink(), sync_file_range(),
tee(), timer_create(), timerfd_create(), unlink().
- `EACCES`: Permission denied. Raised by access(), bind(), bpf(),
chdir(), chmod(), chown(), chroot(), clock_getres(), connect(),
execve(), fcntl(), getpriority(), inotify_add_watch(), link(),
mkdir(), mknod(), mmap(), mprotect(), msgctl(), msgget(),
msgop(), open(), prctl(), ptrace(), readlink(), rename(),
rmdir(), semget(), send(), setpgid(), shmget(), socket(), stat(),
symlink(), truncate(), unlink(), uselib(), utime(), utimensat(),
- `ENOTBLK`: Block device required. Raised by umount().
- `EBUSY`: Device or resource busy. Raised by bdflush(), dup(),
fcntl(), msync(), prctl(), ptrace(), rename(),
rmdir().
- `EEXIST`: File exists. Raised by bpf(), create_module(),
inotify_add_watch(), link(), mkdir(), mknod(), mmap(), msgget(),
open(), rename(), rmdir(), semget(), shmget(), symlink()
- `EXDEV`: Improper link. Raised by copy_file_range(), link(),
rename()
- `ENODEV`: No such device. Raised by arch_prctl(), eventfd(),
mmap(), open(), prctl(), timerfd_create()
- `ENOTDIR`: Not a directory. This means that a directory component
in a supplied path *existed* but wasn't a directory. For example,
if you try to `open("foo/bar")` and `foo` is a regular file, then
`ENOTDIR` will be returned. Raised by open(), access(), chdir(),
chmod(), chown(), chroot(), execve(), fcntl(), futimesat(),
inotify_add_watch(), link(), mkdir(), mknod(), opendir(),
readlink(), rename(), rmdir(), stat(), symlink(), sysctl(),
truncate(), unlink(), utimensat(), bind().
- `EISDIR`: Is a a directory. Raised by copy_file_range(),
execve(), open(), read(), rename(), truncate(), unlink().
- `ENFILE`: Too many open files in system. Raised by accept(),
eventfd(), execve(), inotify_init(), memfd_create(), mmap(),
open(), pipe(), shmget(), socket(), socketpair(), swapon(),
timerfd_create(), uselib(), userfaultfd().
- `EMFILE`: Too many open files. Raised by accept(), dup(),
eventfd(), execve(), fanotify_init(), fcntl(), inotify_init(),
memfd_create(), open(), pipe(), socket(), socketpair(),
timerfd_create().
- `ENOTTY`: Inappropriate i/o control operation. Raised by ioctl().
- `ETXTBSY`: Won't open executable that's executing in write mode.
Raised by access(), copy_file_range(), execve(), mmap(), open(),
truncate().
- `EFBIG`: File too large. Raised by copy_file_range(), open(),
truncate(), write().
- `ENOSPC`: No space left on device. Raised by copy_file_range(),
fsync(), inotify_add_watch(), link(), mkdir(), mknod(), msgget(),
open(), rename(), semget(), shmget(), symlink(),
sync_file_range(), write().
- `EDQUOT`: Disk quota exceeded. Raised by link(), mkdir(),
mknod(), open(), rename(), symlink(), write()
- `ESPIPE`: Invalid seek. Raised by lseek(), splice(),
sync_file_range().
- `EROFS`: Read-only filesystem. Raised by access(), bind(),
chmod(), chown(), link(), mkdir(), mknod(), open(), rename(),
rmdir(), symlink(), truncate(), unlink(), utime(), utimensat()
- `EMLINK`: Too many links; raised by link(), mkdir(), rename()
- `EPIPE`: Broken pipe. Raised by send(), write().
- `ERANGE`: Result too large. Raised by prctl(), semop().
- `EDEADLK`: Resource deadlock avoided. Raised by fcntl().
- `ENAMETOOLONG`: Filename too long. Raised by access(), bind(),
chdir(), chmod(), chown(), chroot(), execve(), gethostname(),
inotify_add_watch(), link(), mkdir(), mknod(), open(),
readlink(), rename(), rmdir(), stat(), symlink(),
truncate(), u unlink(), utimensat()
- `ENOLCK`: No locks available. Raised by fcntl(), flock().
- `ENOTEMPTY`: Directory not empty. Raised by rmdir().
- `ELOOP`: Too many levels of symbolic links. Raised by access(),
bind(), chdir(), chmod(), chown(), chroot(), execve(), link(),
mkdir(), mknod(), open(), readlink(), rename(), rmdir(), stat(),
symlink(), truncate(), unlink(), utimensat().
- `ENOMSG`: Raised by msgop().
- `EIDRM`: Identifier removed. Raised by msgctl(), msgget(),
msgop(), shmget().
- `ETIME`: Timer expired; timer expired. Raised by connect().
- `EPROTO`: Raised by accept(), connect(), socket(), socketpair().
- `EOVERFLOW`: Raised by copy_file_range(), fanotify_init(),
lseek(), mmap(), open(), stat(), statfs()
- `ENOTSOCK`: Not a socket. Raised by accept(), bind(),
connect(), getpeername(), getsockname(), getsockopt(),
listen(), recv(), send(), shutdown().
- `EDESTADDRREQ`: Destination address required. Raised by send(),
write().
- `EMSGSIZE`: Message too long. Raised by send().
- `EPROTOTYPE`: Protocol wrong type for socket. Raised by
connect().
- `ENOPROTOOPT`: Protocol not available. Raised by getsockopt(),
accept().
- `EPROTONOSUPPORT`: Protocol not supported. Raised by socket(),
socketpair().
- `ESOCKTNOSUPPORT`: Socket type not supported.
- `ENOTSUP`: Operation not supported. Raised by chmod(),
clock_getres(), clock_nanosleep(), timer_create()
- `EOPNOTSUPP`: Socket operation not supported. Raised by accept(),
listen(), mmap(), prctl(), readv(), send(), socketpair(),
- `EPFNOSUPPORT`: protocol family not supported
- `EAFNOSUPPORT`: address family not supported. Raised by
connect(), socket(), socketpair()
- `EADDRINUSE`: address already in use. Raised by bind(),
connect(), listen()
- `EADDRNOTAVAIL`: address not available. Raised by bind(),
connect().
- `ENETDOWN`: network is down; ; WSAENETDOWN. Raised
by accept()
- `ENETUNREACH`: host is unreachable; ;
WSAENETUNREACH. Raised by accept(), connect()
- `ENETRESET`: connection reset by network
- `ECONNABORTED`: connection reset before accept. Raised by
accept()
- `ECONNRESET`: connection reset by client. Raised by send(),
- `ENOBUFS`: no buffer space available;
raised by getpeername(), getsockname(), send(),
- `EISCONN`: socket is connected. Raised by
connect(), send().
- `ENOTCONN`: socket is not connected. Raised by getpeername(),
recv(), send(), shutdown(),
- `ESHUTDOWN`: cannot send after transport endpoint shutdown; note
that shutdown write is an `EPIPE`
- `ETOOMANYREFS`: too many references: cannot splice. Raised by
sendmsg(),
- `ETIMEDOUT`: connection timed out; ; WSAETIMEDOUT; raised by
connect(),
- `ECONNREFUSED`: system-imposed limit on the number of threads was
encountered.; WSAECONNREFUSED. Raised by connect(), listen(),
recv()
- `EHOSTDOWN`: Host is down. Raised by accept()
- `EHOSTUNREACH`: Host is unreachable. Raised by accept()
- `EALREADY`: Connection already in progress. Raised by connect(),
send()
- `ENODATA`: No message is available in xsi stream or named pipe is
being closed; no data available; barely in posix; returned by
ioctl; very close in spirit to EPIPE?
unix.opendir(path) → UnixDir*, errno
unix.opendir(fd) → UnixDir*, errno
UnixDir:close()
may be called multiple times
called by the garbage collector too
UnixDir:read() → name, kind, ino, off
returns nil if no more entries
kind can be DT_UNKNOWN/REG/DIR/BLK/LNK/CHR/FIFO/SOCK
UnixDir:fd() → fd, errno
EOPNOTSUPP if using /zip/
EOPNOTSUPP if IsWindows()
UnixDir:tell() → off
UnixDir:rewind()
CONSTANTS

View file

@ -47,8 +47,10 @@
#include "libc/sysv/consts/ipproto.h"
#include "libc/sysv/consts/itimer.h"
#include "libc/sysv/consts/msg.h"
#include "libc/sysv/consts/nr.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/ok.h"
#include "libc/sysv/consts/rlimit.h"
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/shut.h"
#include "libc/sysv/consts/sig.h"
@ -132,12 +134,11 @@ static void FreeStringList(char **p) {
// System Calls
// unix.exit([exitcode]) → ⊥
static int LuaUnixExit(lua_State *L) {
static wontreturn int LuaUnixExit(lua_State *L) {
_Exit(luaL_optinteger(L, 1, 0));
unreachable;
}
// unix.access(path, mode) → rc, errno
// unix.access(path, mode) → rc[, errno]
// mode can be: R_OK, W_OK, X_OK, F_OK
static int LuaUnixAccess(lua_State *L) {
const char *file;
@ -149,7 +150,7 @@ static int LuaUnixAccess(lua_State *L) {
return ReturnRc(L, rc, olderr);
}
// unix.mkdir(path, mode) → rc, errno
// unix.mkdir(path, mode) → rc[, errno]
// mode should be octal
static int LuaUnixMkdir(lua_State *L) {
const char *file;
@ -161,7 +162,7 @@ static int LuaUnixMkdir(lua_State *L) {
return ReturnRc(L, rc, olderr);
}
// unix.chdir(path) → rc, errno
// unix.chdir(path) → rc[, errno]
static int LuaUnixChdir(lua_State *L) {
int rc, olderr;
const char *file;
@ -171,7 +172,7 @@ static int LuaUnixChdir(lua_State *L) {
return ReturnRc(L, rc, olderr);
}
// unix.unlink(path) → rc, errno
// unix.unlink(path) → rc[, errno]
static int LuaUnixUnlink(lua_State *L) {
int rc, olderr;
const char *file;
@ -181,7 +182,7 @@ static int LuaUnixUnlink(lua_State *L) {
return ReturnRc(L, rc, olderr);
}
// unix.rmdir(path) → rc, errno
// unix.rmdir(path) → rc[, errno]
static int LuaUnixRmdir(lua_State *L) {
const char *file;
int rc, olderr;
@ -191,7 +192,7 @@ static int LuaUnixRmdir(lua_State *L) {
return ReturnRc(L, rc, olderr);
}
// unix.rename(oldpath, newpath) → rc, errno
// unix.rename(oldpath, newpath) → rc[, errno]
static int LuaUnixRename(lua_State *L) {
const char *oldpath, *newpath;
int rc, olderr;
@ -202,7 +203,7 @@ static int LuaUnixRename(lua_State *L) {
return ReturnRc(L, rc, olderr);
}
// unix.link(existingpath, newpath) → rc, errno
// unix.link(existingpath, newpath) → rc[, errno]
static int LuaUnixLink(lua_State *L) {
const char *existingpath, *newpath;
int rc, olderr;
@ -213,7 +214,7 @@ static int LuaUnixLink(lua_State *L) {
return ReturnRc(L, rc, olderr);
}
// unix.symlink(target, linkpath) → rc, errno
// unix.symlink(target, linkpath) → rc[, errno]
static int LuaUnixSymlink(lua_State *L) {
const char *target, *linkpath;
int rc, olderr;
@ -224,7 +225,7 @@ static int LuaUnixSymlink(lua_State *L) {
return ReturnRc(L, rc, olderr);
}
// unix.chown(path, uid, gid) → rc, errno
// unix.chown(path, uid, gid) → rc[, errno]
static int LuaUnixChown(lua_State *L) {
const char *file;
int rc, uid, gid, olderr;
@ -236,7 +237,7 @@ static int LuaUnixChown(lua_State *L) {
return ReturnRc(L, rc, olderr);
}
// unix.chmod(path, mode) → rc, errno
// unix.chmod(path, mode) → rc[, errno]
static int LuaUnixChmod(lua_State *L) {
const char *file;
int rc, mode, olderr;
@ -247,7 +248,7 @@ static int LuaUnixChmod(lua_State *L) {
return ReturnRc(L, rc, olderr);
}
// unix.getcwd(path, mode) → rc, errno
// unix.getcwd(path, mode) → rc[, errno]
static int LuaUnixGetcwd(lua_State *L) {
char *path;
path = getcwd(0, 0);
@ -297,24 +298,65 @@ static int LuaUnixExecve(lua_State *L) {
return 1;
}
// unix.commandv(prog) → path, errno
// unix.commandv(prog) → path[, errno]
static int LuaUnixCommandv(lua_State *L) {
int olderr;
const char *prog;
int rc, olderr, pushed;
char *pathbuf, *resolved;
olderr = errno;
pathbuf = xmalloc(PATH_MAX);
prog = luaL_checkstring(L, 1);
if ((resolved = commandv(prog, pathbuf))) {
lua_pushstring(L, resolved);
lua_pushnil(L);
pushed = 1;
} else {
lua_pushnil(L);
lua_pushinteger(L, errno);
errno = olderr;
pushed = 2;
}
free(pathbuf);
return 2;
return pushed;
}
// unix.chroot(path) → rc[, errno]
static int LuaUnixChroot(lua_State *L) {
int rc, olderr;
const char *path;
olderr = errno;
path = luaL_checkstring(L, 1);
rc = chroot(path);
return ReturnRc(L, rc, olderr);
}
// unix.setrlimit(resource:int, soft:int[, hard:int]) → rc:int[, errno:int]
static int LuaUnixSetrlimit(lua_State *L) {
struct rlimit rlim;
int rc, olderr, resource;
olderr = errno;
resource = luaL_checkinteger(L, 1);
rlim.rlim_cur = luaL_checkinteger(L, 2);
rlim.rlim_max = luaL_optinteger(L, 3, rlim.rlim_cur);
return ReturnRc(L, setrlimit(resource, &rlim), olderr);
}
// unix.getrlimit(resource:int) → soft:int, hard:int[, errno:int]
static int LuaUnixGetrlimit(lua_State *L) {
struct rlimit rlim;
int rc, olderr, resource;
olderr = errno;
resource = luaL_checkinteger(L, 1);
if (!getrlimit(resource, &rlim)) {
lua_pushinteger(L, rlim.rlim_cur);
lua_pushinteger(L, rlim.rlim_max);
return 2;
} else {
lua_pushnil(L);
lua_pushnil(L);
lua_pushinteger(L, errno);
errno = olderr;
return 3;
}
}
// unix.getpid() → pid
@ -329,7 +371,7 @@ static int LuaUnixGetppid(lua_State *L) {
return 1;
}
// unix.kill(pid, sig) → rc, errno
// unix.kill(pid, sig) → rc[, errno]
static int LuaUnixKill(lua_State *L) {
int rc, pid, sig, olderr;
olderr = errno;
@ -339,7 +381,7 @@ static int LuaUnixKill(lua_State *L) {
return ReturnRc(L, rc, olderr);
}
// unix.raise(sig) → rc, errno
// unix.raise(sig) → rc[, errno]
static int LuaUnixRaise(lua_State *L) {
int rc, sig, olderr;
olderr = errno;
@ -369,7 +411,7 @@ static int LuaUnixWait(lua_State *L) {
}
}
// unix.fcntl(fd, cmd[, arg]) → rc, errno
// unix.fcntl(fd, cmd[, arg]) → rc[, errno]
static int LuaUnixFcntl(lua_State *L) {
intptr_t arg;
int rc, fd, cmd, olderr;
@ -442,7 +484,7 @@ static int LuaUnixGetpgid(lua_State *L) {
return ReturnRc(L, rc, olderr);
}
// unix.umask(mask) → rc, errno
// unix.umask(mask) → rc[, errno]
static int LuaUnixUmask(lua_State *L) {
int rc, mask, olderr;
olderr = errno;
@ -532,7 +574,7 @@ static int LuaUnixSync(lua_State *L) {
return 0;
}
// unix.fsync(fd) → rc, errno
// unix.fsync(fd) → rc[, errno]
static int LuaUnixFsync(lua_State *L) {
int rc, fd, olderr;
olderr = errno;
@ -541,7 +583,7 @@ static int LuaUnixFsync(lua_State *L) {
return ReturnRc(L, rc, olderr);
}
// unix.fdatasync(fd) → rc, errno
// unix.fdatasync(fd) → rc[, errno]
static int LuaUnixFdatasync(lua_State *L) {
int rc, fd, olderr;
olderr = errno;
@ -562,7 +604,7 @@ static int LuaUnixOpen(lua_State *L) {
return ReturnRc(L, rc, olderr);
}
// unix.close(fd) → rc, errno
// unix.close(fd) → rc[, errno]
static int LuaUnixClose(lua_State *L) {
int rc, fd, olderr;
olderr = errno;
@ -585,8 +627,8 @@ static int LuaUnixSeek(lua_State *L) {
return ReturnRc(L, newpos, olderr);
}
// unix.truncate(path, length) → rc, errno
// unix.truncate(fd, length) → rc, errno
// unix.truncate(path, length) → rc[, errno]
// unix.truncate(fd, length) → rc[, errno]
static int LuaUnixTruncate(lua_State *L) {
int64_t length;
const char *path;
@ -611,7 +653,7 @@ static int LuaUnixTruncate(lua_State *L) {
static int LuaUnixRead(lua_State *L) {
char *buf;
size_t got;
int fd, olderr;
int fd, olderr, pushed;
int64_t rc, bufsiz, offset;
olderr = errno;
fd = luaL_checkinteger(L, 1);
@ -627,17 +669,18 @@ static int LuaUnixRead(lua_State *L) {
if (rc != -1) {
got = rc;
lua_pushlstring(L, buf, got);
lua_pushnil(L);
pushed = 1;
} else {
lua_pushnil(L);
lua_pushinteger(L, errno);
errno = olderr;
pushed = 2;
}
free(buf);
return 2;
return pushed;
}
// unix.write(fd, data[, offset]) → rc, errno
// unix.write(fd, data[, offset]) → rc[, errno]
static int LuaUnixWrite(lua_State *L) {
size_t size;
int fd, olderr;
@ -656,8 +699,8 @@ static int LuaUnixWrite(lua_State *L) {
return ReturnRc(L, rc, olderr);
}
// unix.stat(path) → UnixStat*, errno
// unix.stat(fd) → UnixStat*, errno
// unix.stat(path) → UnixStat*[, errno]
// unix.stat(fd) → UnixStat*[, errno]
static int LuaUnixStat(lua_State *L) {
const char *path;
int rc, fd, olderr;
@ -688,8 +731,8 @@ static int LuaUnixStat(lua_State *L) {
return 1;
}
// unix.opendir(path) → UnixDir*, errno
// unix.opendir(fd) → UnixDir*, errno
// unix.opendir(path) → UnixDir*[, errno]
// unix.opendir(fd) → UnixDir*[, errno]
static int LuaUnixOpendir(lua_State *L) {
DIR *rc;
int fd, olderr;
@ -738,7 +781,7 @@ static int LuaUnixSocket(lua_State *L) {
return ReturnRc(L, rc, olderr);
}
// unix.socketpair([family[, type[, protocol]]]) → fd1, fd2, errno
// unix.socketpair([family[, type[, protocol]]]) → fd1, fd2[, errno]
// SOCK_CLOEXEC may be or'd into type
// family defaults to AF_INET
// type defaults to SOCK_STREAM
@ -762,7 +805,7 @@ static int LuaUnixSocketpair(lua_State *L) {
}
}
// unix.bind(fd, ip, port) → rc, errno
// unix.bind(fd, ip, port) → rc[, errno]
// SOCK_CLOEXEC may be or'd into type
// family defaults to AF_INET
// type defaults to SOCK_STREAM
@ -779,7 +822,7 @@ static int LuaUnixBind(lua_State *L) {
return ReturnRc(L, rc, olderr);
}
// unix.connect(fd, ip, port) → rc, errno
// unix.connect(fd, ip, port) → rc[, errno]
// SOCK_CLOEXEC may be or'd into type
// family defaults to AF_INET
// type defaults to SOCK_STREAM
@ -796,7 +839,7 @@ static int LuaUnixConnect(lua_State *L) {
return ReturnRc(L, rc, olderr);
}
// unix.listen(fd[, backlog]) → rc, errno
// unix.listen(fd[, backlog]) → rc[, errno]
static int LuaUnixListen(lua_State *L) {
int rc, fd, olderr, backlog;
olderr = errno;
@ -875,7 +918,7 @@ static int LuaUnixAccept(lua_State *L) {
}
}
// unix.recvfrom(fd[, bufsiz[, flags]]) → data, ip, port, errno
// unix.recvfrom(fd[, bufsiz[, flags]]) → data, ip, port[, errno]
// flags can have MSG_{WAITALL,DONTROUTE,PEEK,OOB}, etc.
static int LuaUnixRecvfrom(lua_State *L) {
char *buf;
@ -883,7 +926,7 @@ static int LuaUnixRecvfrom(lua_State *L) {
ssize_t rc;
uint32_t addrsize;
struct sockaddr_in sa;
int fd, flags, bufsiz, olderr;
int fd, flags, bufsiz, olderr, pushed;
olderr = errno;
bzero(&sa, sizeof(sa));
addrsize = sizeof(sa);
@ -898,24 +941,25 @@ static int LuaUnixRecvfrom(lua_State *L) {
lua_pushlstring(L, buf, got);
lua_pushinteger(L, ntohl(sa.sin_addr.s_addr));
lua_pushinteger(L, ntohs(sa.sin_port));
lua_pushnil(L);
pushed = 3;
} else {
lua_pushnil(L);
lua_pushnil(L);
lua_pushnil(L);
lua_pushinteger(L, errno);
errno = olderr;
pushed = 4;
}
free(buf);
return 4;
return pushed;
}
// unix.recv(fd[, bufsiz[, flags]]) → data, errno
// unix.recv(fd[, bufsiz[, flags]]) → data[, errno]
static int LuaUnixRecv(lua_State *L) {
char *buf;
size_t got;
ssize_t rc;
int fd, flags, bufsiz, olderr;
int fd, flags, bufsiz, olderr, pushed;
olderr = errno;
fd = luaL_checkinteger(L, 1);
bufsiz = luaL_optinteger(L, 2, 1500);
@ -926,14 +970,15 @@ static int LuaUnixRecv(lua_State *L) {
if (rc != -1) {
got = rc;
lua_pushlstring(L, buf, got);
lua_pushnil(L);
pushed = 1;
} else {
lua_pushnil(L);
lua_pushinteger(L, errno);
errno = olderr;
pushed = 2;
}
free(buf);
return 4;
return pushed;
}
// unix.send(fd, data[, flags]) → sent, errno
@ -971,7 +1016,7 @@ static int LuaUnixSendto(lua_State *L) {
return ReturnRc(L, rc, olderr);
}
// unix.shutdown(fd, how) → rc, errno
// unix.shutdown(fd, how) → rc[, errno]
// how can be SHUT_RD, SHUT_WR, or SHUT_RDWR
static int LuaUnixShutdown(lua_State *L) {
int rc, fd, how, olderr;
@ -982,7 +1027,7 @@ static int LuaUnixShutdown(lua_State *L) {
return ReturnRc(L, rc, olderr);
}
// unix.sigprocmask(how[, mask]) → oldmask, errno
// unix.sigprocmask(how[, mask]) → oldmask[, errno]
// how can be SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK
static int LuaUnixSigprocmask(lua_State *L) {
uint64_t imask;
@ -1025,7 +1070,7 @@ static void LuaUnixOnSignal(int sig, siginfo_t *si, ucontext_t *ctx) {
}
}
// unix.sigaction(sig[, handler[, flags[, mask]]]) → handler, flags, mask, errno
// unix.sigaction(sig[,handler[,flags[,mask]]]) → handler,flags,mask[,errno]
//
// unix = require "unix"
// unix.sigaction(unix.SIGUSR1, function(sig)
@ -1109,7 +1154,7 @@ static int LuaUnixSigaction(lua_State *L) {
lua_pushnil(L);
lua_pushinteger(L, errno);
errno = olderr;
return 2;
return 4;
}
}
@ -1127,7 +1172,7 @@ static int LuaUnixSigsuspend(lua_State *L) {
}
// unix.setitimer(which[, intsec, intmicros, valsec, valmicros])
// → intsec, intns, valsec, valns, errno
// → intsec, intns, valsec, valns[, errno]
//
// ticks = 0
// unix.sigaction(unix.SIGALRM, function(sig)
@ -1244,6 +1289,10 @@ static int LuaUnixStatCtim(lua_State *L) {
return ReturnTimespec(L, &GetUnixStat(L)->st_ctim);
}
static int LuaUnixStatBirthtim(lua_State *L) {
return ReturnTimespec(L, &GetUnixStat(L)->st_birthtim);
}
static void FreeUnixStat(struct UnixStat *stat) {
if (!--stat->refs) {
free(stat);
@ -1261,20 +1310,21 @@ static int LuaUnixStatGc(lua_State *L) {
}
static const luaL_Reg kLuaUnixStatMeth[] = {
{"size", LuaUnixStatSize}, //
{"mode", LuaUnixStatMode}, //
{"dev", LuaUnixStatDev}, //
{"ino", LuaUnixStatIno}, //
{"nlink", LuaUnixStatNlink}, //
{"rdev", LuaUnixStatRdev}, //
{"uid", LuaUnixStatUid}, //
{"gid", LuaUnixStatGid}, //
{"atim", LuaUnixStatAtim}, //
{"mtim", LuaUnixStatMtim}, //
{"ctim", LuaUnixStatCtim}, //
{"blocks", LuaUnixStatBlocks}, //
{"blksize", LuaUnixStatBlksize}, //
{0}, //
{"size", LuaUnixStatSize}, //
{"mode", LuaUnixStatMode}, //
{"dev", LuaUnixStatDev}, //
{"ino", LuaUnixStatIno}, //
{"nlink", LuaUnixStatNlink}, //
{"rdev", LuaUnixStatRdev}, //
{"uid", LuaUnixStatUid}, //
{"gid", LuaUnixStatGid}, //
{"atim", LuaUnixStatAtim}, //
{"mtim", LuaUnixStatMtim}, //
{"ctim", LuaUnixStatCtim}, //
{"birthtim", LuaUnixStatBirthtim}, //
{"blocks", LuaUnixStatBlocks}, //
{"blksize", LuaUnixStatBlksize}, //
{0}, //
};
static const luaL_Reg kLuaUnixStatMeta[] = {
@ -1307,38 +1357,49 @@ static DIR *GetDirOrDie(lua_State *L) {
unreachable;
}
static void FreeUnixDir(struct UnixDir *dir) {
if (!--dir->refs) {
closedir(dir->dir);
}
static int FreeUnixDir(struct UnixDir *dir) {
if (--dir->refs) return 0;
return closedir(dir->dir);
}
// UnixDir:close()
// UnixDir:close() → rc[, errno]
// may be called multiple times
// called by the garbage collector too
static int LuaUnixDirClose(lua_State *L) {
int rc, olderr;
struct UnixDir **udir;
udir = GetUnixDirSelf(L);
if (*udir) {
FreeUnixDir(*udir);
*udir = 0;
}
return 0;
if (!*udir) return 0;
olderr = 0;
rc = FreeUnixDir(*udir);
*udir = 0;
return ReturnRc(L, rc, olderr);
}
// UnixDir:read() → name, kind, ino, off
// UnixDir:read() → name, kind, ino, off[, errno]
// returns nil if no more entries
// kind can be DT_UNKNOWN/REG/DIR/BLK/LNK/CHR/FIFO/SOCK
static int LuaUnixDirRead(lua_State *L) {
int olderr;
struct dirent *ent;
olderr = errno;
errno = 0;
if ((ent = readdir(GetDirOrDie(L)))) {
lua_pushlstring(L, ent->d_name, strnlen(ent->d_name, sizeof(ent->d_name)));
lua_pushinteger(L, ent->d_type);
lua_pushinteger(L, ent->d_ino);
lua_pushinteger(L, ent->d_off);
return 4;
} else if (!ent && !errno) {
return 0; // end of listing
} else {
return 0;
lua_pushnil(L);
lua_pushnil(L);
lua_pushnil(L);
lua_pushnil(L);
lua_pushinteger(L, errno);
errno = olderr;
return 5;
}
}
@ -1433,7 +1494,10 @@ static const luaL_Reg kLuaUnix[] = {
{"fsync", LuaUnixFsync}, // flush open file
{"fdatasync", LuaUnixFdatasync}, // flush open file w/o metadata
{"truncate", LuaUnixTruncate}, // shrink or extend file medium
{"umask", LuaUnixUmask}, // set file mode creation mask
{"umask", LuaUnixChroot}, // change root directory
{"chroot", LuaUnixGetppid}, // get parent process id
{"setrlimit", LuaUnixSetrlimit}, // prevent cpu memory bombs
{"getrlimit", LuaUnixGetrlimit}, // query resource limits
{"getppid", LuaUnixGetppid}, // get parent process id
{"getpgrp", LuaUnixGetpgrp}, // get process group id
{"getpgid", LuaUnixGetpgid}, // get process group id of pid
@ -1544,6 +1608,13 @@ int LuaUnix(lua_State *L) {
LuaSetIntField(L, "X_OK", X_OK);
LuaSetIntField(L, "F_OK", F_OK);
// rlimit() resources
LuaSetIntField(L, "RLIMIT_AS", RLIMIT_AS);
LuaSetIntField(L, "RLIMIT_CPU", RLIMIT_CPU);
LuaSetIntField(L, "RLIMIT_FSIZE", RLIMIT_FSIZE);
LuaSetIntField(L, "RLIMIT_NPROC", RLIMIT_NPROC);
LuaSetIntField(L, "RLIMIT_NOFILE", RLIMIT_NOFILE);
// wait() options
LuaSetIntField(L, "WNOHANG", WNOHANG);

View file

@ -22,6 +22,7 @@
#include "libc/bits/popcnt.h"
#include "libc/bits/safemacros.internal.h"
#include "libc/calls/calls.h"
#include "libc/calls/issandboxed.h"
#include "libc/calls/math.h"
#include "libc/calls/sigbits.h"
#include "libc/calls/strace.internal.h"
@ -52,6 +53,7 @@
#include "libc/nexgen32e/rdtsc.h"
#include "libc/nexgen32e/rdtscp.h"
#include "libc/nt/enum/fileflagandattributes.h"
#include "libc/nt/runtime.h"
#include "libc/rand/rand.h"
#include "libc/runtime/clktck.h"
#include "libc/runtime/directmap.internal.h"
@ -81,11 +83,14 @@
#include "libc/sysv/consts/madv.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/msync.h"
#include "libc/sysv/consts/nr.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/poll.h"
#include "libc/sysv/consts/pr.h"
#include "libc/sysv/consts/prot.h"
#include "libc/sysv/consts/rusage.h"
#include "libc/sysv/consts/s.h"
#include "libc/sysv/consts/seccomp.h"
#include "libc/sysv/consts/shut.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/consts/so.h"
@ -180,6 +185,11 @@ STATIC_YOINK("zip_uri_support");
#define HeaderEqualCase(H, S) \
SlicesEqualCase(S, strlen(S), HeaderData(H), HeaderLength(H))
// letters not used: EIJNOQWXYinoqwxy
// digits not used: 0123456789
// puncts not used: !"#$%&'()*+,-./;<=>@[\]^_`{|}~
#define GETOPTS "BSVZabdfghjkmsuvzA:C:D:F:G:H:K:L:M:P:R:T:U:c:e:l:p:r:t:"
static const uint8_t kGzipHeader[] = {
0x1F, // MAGNUM
0x8B, // MAGNUM
@ -345,6 +355,7 @@ static bool zombied;
static bool gzipped;
static bool branded;
static bool funtrace;
static bool systrace;
static bool meltdown;
static bool printport;
static bool daemonize;
@ -356,6 +367,7 @@ static bool terminated;
static bool uniprocess;
static bool invalidated;
static bool logmessages;
static bool issandboxed;
static bool isinitialized;
static bool checkedmethod;
static bool sslinitialized;
@ -5817,7 +5829,7 @@ static void HandleHeartbeat(void) {
LuaRunAsset("/.heartbeat.lua", false);
CollectGarbage();
#endif
for (i = 0; i < servers.n; ++i) {
for (i = 1; i < servers.n; ++i) {
if (polls[i].fd < 0) {
polls[i].fd = -polls[i].fd;
}
@ -6569,12 +6581,11 @@ static void CloseServerFds(void) {
}
static int ExitWorker(void) {
if (!IsModeDbg()) {
_Exit(0);
} else {
if (IsModeDbg() && !issandboxed) {
isexitingworker = true;
return eintr();
}
_Exit(0);
}
// returns 0 otherwise -1 if worker needs to unwind stack and exit
@ -6597,8 +6608,9 @@ static int HandleConnection(size_t i) {
case 0:
meltdown = false;
connectionclose = false;
if (funtrace && !IsTiny()) {
ftrace_install();
if (!IsTiny()) {
if (systrace) __strace = 1;
if (funtrace) ftrace_install();
}
if (hasonworkerstart) {
CallSimpleHook("OnWorkerStart");
@ -6990,19 +7002,19 @@ static void MemDestroy(void) {
static void GetOpts(int argc, char *argv[]) {
int opt;
bool storeasset = false;
while ((opt = getopt(argc, argv,
"jkazhdugvVsmbfB"
"e:A:l:p:r:R:H:c:L:P:U:G:D:t:M:C:K:F:T:")) != -1) {
while ((opt = getopt(argc, argv, GETOPTS)) != -1) {
switch (opt) {
CASE('v', ++__log_level);
CASE('s', --__log_level);
CASE('f', funtrace = true);
CASE('Z', systrace = true);
CASE('b', logbodies = true);
CASE('z', printport = true);
CASE('d', daemonize = true);
CASE('a', logrusage = true);
CASE('u', uniprocess = true);
CASE('g', loglatency = true);
CASE('S', issandboxed = true);
CASE('m', logmessages = true);
CASE('l', ProgramAddr(optarg));
CASE('H', ProgramHeader(optarg));

188
tool/viz/getopts.c Normal file
View file

@ -0,0 +1,188 @@
/*-*- 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/stdio/stdio.h"
#include "libc/str/str.h"
/**
* @fileoverview Tool for printing which getopt() letters are used, e.g.
*
* o//tool/viz/getopts.com jkazhdugvVsmbBfe:A:l:p:r:R:H
*/
int letters_used[256];
bool letters_with_args[256];
bool IsLegal(int c) {
return isgraph(c) && c != ':' && c != '?';
}
int main(int argc, char *argv[]) {
int i, j;
bool hasargless;
for (i = 1; i < argc; ++i) {
for (j = 0; argv[i][j]; ++j) {
++letters_used[argv[i][j] & 255];
if (argv[i][j + 1] == ':') {
letters_with_args[argv[i][j] & 255] = true;
}
}
}
// usage report
fprintf(stderr, "options used: ");
for (j = i = 0; i < 128; ++i) {
if (!IsLegal(i)) continue;
if (letters_used[i]) {
if (j++) fprintf(stderr, "");
fprintf(stderr, "%c", i);
}
}
if (!j) fprintf(stderr, "none");
fprintf(stderr, "\n");
fprintf(stderr, "letters not used: ");
for (j = i = 0; i < 128; ++i) {
if (!isalpha(i)) continue;
if (!letters_used[i]) {
if (j++) fprintf(stderr, "");
fprintf(stderr, "%c", i);
}
}
if (!j) fprintf(stderr, "none");
fprintf(stderr, "\n");
fprintf(stderr, "digits not used: ");
for (j = i = 0; i < 128; ++i) {
if (!isdigit(i)) continue;
if (!letters_used[i]) {
if (j++) fprintf(stderr, "");
fprintf(stderr, "%c", i);
}
}
if (!j) fprintf(stderr, "none");
fprintf(stderr, "\n");
fprintf(stderr, "puncts not used: ");
for (j = i = 0; i < 128; ++i) {
if (!IsLegal(i)) continue;
if (isalnum(i)) continue;
if (!letters_used[i]) {
if (j++) fprintf(stderr, "");
fprintf(stderr, "%c", i);
}
}
if (!j) fprintf(stderr, "none");
fprintf(stderr, "\n");
fprintf(stderr, "letters duplicated: ");
for (j = i = 0; i < 128; ++i) {
if (!IsLegal(i)) continue;
if (letters_used[i] > 1) {
if (j++) fprintf(stderr, "");
fprintf(stderr, "%c", i);
}
}
if (!j) fprintf(stderr, "none");
fprintf(stderr, "\n");
// generated code
hasargless = false;
printf("\n#define GETOPTS \"");
for (j = i = 0; i < 128; ++i) {
if (!IsLegal(i)) continue;
if (letters_used[i] && !letters_with_args[i]) {
printf("%c", i);
hasargless = true;
}
}
for (j = i = 0; i < 128; ++i) {
if (!IsLegal(i)) continue;
if (letters_used[i] && letters_with_args[i]) {
printf("%c:", i);
}
}
printf("\"\n");
printf("\n#define USAGE \"\\\n");
printf("Usage: program.com");
if (hasargless) {
printf(" [-");
for (j = i = 0; i < 128; ++i) {
if (!IsLegal(i)) continue;
if (letters_used[i] && !letters_with_args[i]) {
printf("%c", i);
hasargless = true;
}
}
printf("]");
}
printf(" ARGS...\\n\\\n");
for (j = i = 0; i < 128; ++i) {
if (!IsLegal(i)) continue;
if (letters_used[i]) {
printf(" -%c the %c option\\n\\\n", i, i);
}
}
for (j = i = 0; i < 128; ++i) {
if (!IsLegal(i)) continue;
if (letters_used[i]) {
printf(" -%c VAL the %c option\\n\\\n", i, i);
}
}
printf("\"\n\n");
for (i = 0; i < 128; ++i) {
if (!IsLegal(i)) continue;
if (letters_used[i]) {
if (isalpha(i) || i == '_') {
printf("int %cflag;\n", i);
}
}
}
printf("\n\
static void GetOpts(int argc, char *argv[]) {\n\
while ((opt = getopt(argc, argv, GETOPTS)) != -1) {\n\
switch (opt) {\n");
for (i = 0; i < 128; ++i) {
if (!IsLegal(i)) continue;
if (letters_used[i]) {
printf(" case '%c':\n", i);
if (isalpha(i) || i == '_') {
printf(" %cflag", i);
} else {
printf(" XXXflag", i);
}
if (letters_with_args[i]) {
printf(" = optarg;\n");
} else {
printf("++;\n");
}
printf(" break;\n");
}
}
printf(" case '?':\n");
printf(" write(1, USAGE, strlen(USAGE));\n");
printf(" exit(0);\n");
printf(" default:\n");
printf(" write(2, USAGE, strlen(USAGE));\n");
printf(" exit(64);\n");
printf(" }\n");
printf(" }\n");
printf("}\n");
return 0;
}