mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-03-03 07:29:23 +00:00
Add more documentation to redbean
This change also improves the unix module, adding a reboot() system call for fun and profit, fixing the execve() api, and a printimage release.
This commit is contained in:
parent
87396f43bc
commit
9bfa6ec06e
16 changed files with 498 additions and 133 deletions
|
@ -121,7 +121,7 @@ ssize_t pwritev(int fd, const struct iovec *iov, int iovlen, int64_t off) {
|
|||
if (rc == -1 && errno == EFAULT) {
|
||||
STRACE("pwritev(%d, %p, %d, %'ld) → %'zd% m", fd, iov, iovlen, off, rc);
|
||||
} else {
|
||||
kprintf(STRACE_PROLOGUE "readv(%d, ", fd);
|
||||
kprintf(STRACE_PROLOGUE "pwritev(%d, ", fd);
|
||||
__strace_iov(iov, iovlen, rc != -1 ? rc : 0);
|
||||
kprintf(", %d, %'ld) → %'ld% m%n", iovlen, off, rc);
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ ssize_t writev(int fd, const struct iovec *iov, int iovlen) {
|
|||
if (rc == -1 && errno == EFAULT) {
|
||||
STRACE("writev(%d, %p, %d) → %'zd% m", fd, iov, iovlen, rc);
|
||||
} else {
|
||||
kprintf(STRACE_PROLOGUE "readv(%d, ", fd);
|
||||
kprintf(STRACE_PROLOGUE "writev(%d, ", fd);
|
||||
__strace_iov(iov, iovlen, rc != -1 ? rc : 0);
|
||||
kprintf(", %d) → %'ld% m%n", iovlen, rc);
|
||||
}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#include "libc/sysv/consts/syscon.internal.h"
|
||||
.syscon reboot,RB_DISABLE_CAD,0,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff
|
||||
.syscon reboot,RB_DISABLE_CAD,0,-1,-1,-1,-1,-1
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#include "libc/sysv/consts/syscon.internal.h"
|
||||
.syscon reboot,RB_ENABLE_CAD,0x89abcdef,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff
|
||||
.syscon reboot,RB_ENABLE_CAD,0x89abcdef,-1,-1,-1,-1,-1
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#include "libc/sysv/consts/syscon.internal.h"
|
||||
.syscon reboot,RB_KEXEC,0x45584543,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff
|
||||
.syscon reboot,RB_KEXEC,0x45584543,-1,-1,-1,-1,-1
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#include "libc/sysv/consts/syscon.internal.h"
|
||||
.syscon reboot,RB_POWERDOWN,0x4321fedc,0xffffffff,0x4000,0x1000,0x808,8
|
||||
.syscon reboot,RB_POWERDOWN,0x4321fedc,-1,0x4000,0x1000,0x808,8
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#include "libc/sysv/consts/syscon.internal.h"
|
||||
.syscon reboot,RB_POWEROFF,0x4321fedc,0xffffffff,0x4000,0x1000,0x808,8
|
||||
.syscon reboot,RB_POWEROFF,0x4321fedc,-1,0x4000,0x1000,0x808,8
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#include "libc/sysv/consts/syscon.internal.h"
|
||||
.syscon reboot,RB_POWER_OFF,0x4321fedc,0xffffffff,0x4000,0x1000,0x808,8
|
||||
.syscon reboot,RB_POWER_OFF,0x4321fedc,-1,0x4000,0x1000,0x808,8
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#include "libc/sysv/consts/syscon.internal.h"
|
||||
.syscon reboot,RB_SW_SUSPEND,0xd000fce2,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xd000fce2
|
||||
.syscon reboot,RB_SW_SUSPEND,0xd000fce2,-1,-1,-1,-1,0xd000fce2
|
||||
|
|
|
@ -4,17 +4,17 @@
|
|||
#if !(__ASSEMBLER__ + __LINKER__ + 0)
|
||||
COSMOPOLITAN_C_START_
|
||||
|
||||
extern const uint32_t RB_AUTOBOOT;
|
||||
extern const uint32_t RB_POWER_OFF;
|
||||
extern const uint32_t RB_POWERDOWN;
|
||||
extern const uint32_t RB_POWEROFF;
|
||||
extern const uint32_t RB_HALT_SYSTEM;
|
||||
extern const uint32_t RB_HALT;
|
||||
extern const uint32_t RB_SW_SUSPEND;
|
||||
extern const uint32_t RB_KEXEC;
|
||||
extern const uint32_t RB_ENABLE_CAD;
|
||||
extern const uint32_t RB_DISABLE_CAD;
|
||||
extern const uint32_t RB_NOSYNC;
|
||||
extern const int RB_AUTOBOOT;
|
||||
extern const int RB_POWER_OFF;
|
||||
extern const int RB_POWERDOWN;
|
||||
extern const int RB_POWEROFF;
|
||||
extern const int RB_HALT_SYSTEM;
|
||||
extern const int RB_HALT;
|
||||
extern const int RB_SW_SUSPEND;
|
||||
extern const int RB_KEXEC;
|
||||
extern const int RB_ENABLE_CAD;
|
||||
extern const int RB_DISABLE_CAD;
|
||||
extern const int RB_NOSYNC;
|
||||
|
||||
COSMOPOLITAN_C_END_
|
||||
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
||||
|
|
26
tool/net/demo/unix2.lua
Normal file
26
tool/net/demo/unix2.lua
Normal file
|
@ -0,0 +1,26 @@
|
|||
-- example of how to run the ls command
|
||||
-- and pipe its output to the http user
|
||||
local unix = require "unix"
|
||||
ls = unix.commandv("ls")
|
||||
reader, writer = unix.pipe()
|
||||
if unix.fork() == 0 then
|
||||
unix.close(1)
|
||||
unix.dup(writer)
|
||||
unix.close(writer)
|
||||
unix.close(reader)
|
||||
unix.execve(ls, {ls, "-Shal"})
|
||||
unix.exit(127)
|
||||
else
|
||||
unix.close(writer)
|
||||
SetHeader('Content-Type', 'text/plain')
|
||||
while true do
|
||||
data = unix.read(reader)
|
||||
if data ~= "" then
|
||||
Write(data)
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
unix.close(reader)
|
||||
unix.wait(-1)
|
||||
end
|
|
@ -24,6 +24,17 @@ OVERVIEW
|
|||
itself is written as a single .c file. It embeds the Lua programming
|
||||
language and SQLite which let you write dynamic pages.
|
||||
|
||||
FEATURES
|
||||
|
||||
- Lua v5.4
|
||||
- SQLite 3.35.5
|
||||
- TLS v1.2 / v1.1 / v1.0
|
||||
- HTTP v1.1 / v1.0 / v0.9
|
||||
- Chromium-Zlib Compression
|
||||
- Statusz Monitoring Statistics
|
||||
- Self-Modifying PKZIP Object Store
|
||||
- Linux + Windows + Mac + FreeBSD + OpenBSD + NetBSD
|
||||
|
||||
FLAGS
|
||||
|
||||
-h or -? help
|
||||
|
@ -65,16 +76,53 @@ FLAGS
|
|||
--strace enables system call tracing
|
||||
--ftrace enables function call tracing
|
||||
|
||||
FEATURES
|
||||
KEYBOARD
|
||||
|
||||
- Lua v5.4
|
||||
- SQLite 3.35.5
|
||||
- TLS v1.2 / v1.1 / v1.0
|
||||
- HTTP v1.1 / v1.0 / v0.9
|
||||
- Chromium-Zlib Compression
|
||||
- Statusz Monitoring Statistics
|
||||
- Self-Modifying PKZIP Object Store
|
||||
- Linux + Windows + Mac + FreeBSD + OpenBSD + NetBSD
|
||||
CTRL-D EXIT
|
||||
CTRL-C CTRL-C EXIT
|
||||
CTRL-E END
|
||||
CTRL-A START
|
||||
CTRL-B BACK
|
||||
CTRL-F FORWARD
|
||||
CTRL-L CLEAR
|
||||
CTRL-H BACKSPACE
|
||||
CTRL-D DELETE
|
||||
CTRL-N NEXT HISTORY
|
||||
CTRL-P PREVIOUS HISTORY
|
||||
CTRL-R SEARCH HISTORY
|
||||
CTRL-G CANCEL SEARCH
|
||||
ALT-< BEGINNING OF HISTORY
|
||||
ALT-> END OF HISTORY
|
||||
ALT-F FORWARD WORD
|
||||
ALT-B BACKWARD WORD
|
||||
CTRL-K KILL LINE FORWARDS
|
||||
CTRL-U KILL LINE BACKWARDS
|
||||
ALT-H KILL WORD BACKWARDS
|
||||
CTRL-W KILL WORD BACKWARDS
|
||||
CTRL-ALT-H KILL WORD BACKWARDS
|
||||
ALT-D KILL WORD FORWARDS
|
||||
CTRL-Y YANK
|
||||
ALT-Y ROTATE KILL RING AND YANK AGAIN
|
||||
CTRL-T TRANSPOSE
|
||||
ALT-T TRANSPOSE WORD
|
||||
ALT-U UPPERCASE WORD
|
||||
ALT-L LOWERCASE WORD
|
||||
ALT-C CAPITALIZE WORD
|
||||
CTRL-\ QUIT PROCESS
|
||||
CTRL-S PAUSE OUTPUT
|
||||
CTRL-Q UNPAUSE OUTPUT (IF PAUSED)
|
||||
CTRL-Q ESCAPED INSERT
|
||||
CTRL-ALT-F FORWARD EXPR
|
||||
CTRL-ALT-B BACKWARD EXPR
|
||||
ALT-RIGHT FORWARD EXPR
|
||||
ALT-LEFT BACKWARD EXPR
|
||||
ALT-SHIFT-B BARF EXPR
|
||||
ALT-SHIFT-S SLURP EXPR
|
||||
CTRL-SPACE SET MARK
|
||||
CTRL-X CTRL-X GOTO MARK
|
||||
CTRL-Z SUSPEND PROCESS
|
||||
ALT-\ SQUEEZE ADJACENT WHITESPACE
|
||||
PROTIP REMAP CAPS LOCK TO CTRL
|
||||
|
||||
USAGE
|
||||
|
||||
|
@ -1303,23 +1351,139 @@ UNIX MODULE
|
|||
`prog` contains slashes then it's not path searched either and
|
||||
will be returned if it exists.
|
||||
|
||||
unix.realpath(filename:str) → abspath:str[, errno:int]
|
||||
|
||||
Returns absolute path of filename, with `.` and `..` components
|
||||
removed, and symlinks will be resolved.
|
||||
|
||||
unix.execve(prog, argv[, envp]) → errno
|
||||
unix.execve(prog:str[, args:List<*>, env:Map<str,*>]) → errno:int
|
||||
|
||||
Exits current process, replacing it with a new instance of the
|
||||
specified program. `prog` needs to be an absolute path, see
|
||||
commandv(). `envp` defaults to to the current `environ`. Both
|
||||
`prog` and `envp` are arrays of strings.
|
||||
commandv(). `env` defaults to to the current `environ`. Here's
|
||||
a basic usage example:
|
||||
|
||||
unix.execve("/bin/ls", {"/bin/ls", "-hal"})
|
||||
unix.execve("/bin/ls", {"/bin/ls", "-hal"}, {PATH="/bin"})
|
||||
unix.exit(127)
|
||||
|
||||
The first element in `argv` should be `prog`. This function is
|
||||
normally called after forking.
|
||||
`prog` needs to be the resolved pathname of your executable. You
|
||||
can use commandv() to search your `PATH`.
|
||||
|
||||
`args` is a string list table. The first element in `args`
|
||||
should be `prog`. Values are coerced to strings. This parameter
|
||||
defaults to `{prog}`.
|
||||
|
||||
`env` is a key/value table. Keys that aren't strings are
|
||||
ignored. Values are coerced to strings. This parameter defaults
|
||||
to environ() in a way that avoids copying.
|
||||
|
||||
execve() function is normally called after fork() returns 0. If
|
||||
that isn't the case, then your redbean worker will be destroyed.
|
||||
|
||||
This function never returns on success.
|
||||
|
||||
`EAGAIN` is returned if you've enforced a max number of
|
||||
processes using `setrlimit(RLIMIT_NPROC)`.
|
||||
|
||||
unix.dup(oldfd:int[, newfd:int[, flags:int]]) → newfd:int, errno:int
|
||||
|
||||
Duplicates file descriptor.
|
||||
|
||||
`newfd` defaults to the lowest number available file descriptor.
|
||||
If the new number is specified and it's already open, then it'll
|
||||
be silently closed before the duplication happens.
|
||||
|
||||
`flags` can have `O_CLOEXEC` which means the returned file
|
||||
descriptors will be automatically closed upon execve().
|
||||
|
||||
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.
|
||||
|
||||
Here's an example of how pipe(), fork(), dup(), etc. may be used
|
||||
to serve an HTTP response containing the output of a subprocess.
|
||||
|
||||
local unix = require "unix"
|
||||
ls = unix.commandv("ls")
|
||||
reader, writer = unix.pipe()
|
||||
if unix.fork() == 0 then
|
||||
unix.close(1)
|
||||
unix.dup(writer)
|
||||
unix.close(writer)
|
||||
unix.close(reader)
|
||||
unix.execve(ls, {ls, "-Shal"})
|
||||
unix.exit(127)
|
||||
else
|
||||
unix.close(writer)
|
||||
SetHeader('Content-Type', 'text/plain')
|
||||
while true do
|
||||
data = unix.read(reader)
|
||||
if data ~= "" then
|
||||
Write(data)
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
unix.close(reader)
|
||||
unix.wait(-1)
|
||||
end
|
||||
|
||||
unix.wait([pid:int, options:int])
|
||||
→ pid:int, wstatus:int, nil, errno:int
|
||||
|
||||
Waits for subprocess to terminate.
|
||||
|
||||
`pid` defaults to `-1` which means any child process. Setting
|
||||
`pid` to `0` is equivalent to `-getpid()`. If `pid < -1` then
|
||||
that means wait for any pid in the process group `-pid`. Then
|
||||
lastly if `pid > 0` then this waits for a specific process id
|
||||
|
||||
Options may have `WNOHANG` which means don't block, check for
|
||||
the existence of processes that are already dead (technically
|
||||
speaking zombies) and if so harvest them immediately.
|
||||
|
||||
Returns the process id of the child that terminated. In other
|
||||
cases, the returned `pid` is nil and `errno` is non-nil.
|
||||
|
||||
The returned `wstatus` contains information about the process
|
||||
exit status. It's a complicated integer and there's functions
|
||||
that can help interpret it. For example:
|
||||
|
||||
-- wait for zombies
|
||||
-- traditional technique for SIGCHLD handlers
|
||||
while true do
|
||||
pid, wstatus, errno = unix.wait(-1, unix.WNOHANG)
|
||||
if pid then
|
||||
if unix.WIFEXITED(wstatus) then
|
||||
print('child', pid, 'exited with',
|
||||
unix.WEXITSTATUS(wstatus))
|
||||
elseif unix.WIFSIGNALED(wstatus) then
|
||||
print('child', pid, 'crashed with',
|
||||
unix.strsignal(unix.WTERMSIG(wstatus)))
|
||||
end
|
||||
elseif errno == unix.ECHILD then
|
||||
print('no more zombies')
|
||||
break
|
||||
elseif errno == unix.ECHILD then
|
||||
print('wait failed', unix.strerror(errno))
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
unix.getpid() → pid
|
||||
|
||||
Returns process id of current process.
|
||||
|
||||
unix.getppid() → pid
|
||||
|
||||
Returns process id of parent process.
|
||||
|
||||
unix.kill(pid, sig) → rc:int[, errno:int]
|
||||
|
||||
Returns process id of current process.
|
||||
|
||||
unix.raise(sig) → rc:int[, errno:int]
|
||||
|
||||
Triggers signal in current process.
|
||||
This is pretty much the same as `kill(getpid(), sig)`.
|
||||
|
||||
unix.access(path:str, how) → rc:int[, errno:int]
|
||||
|
||||
|
@ -1347,17 +1511,6 @@ UNIX MODULE
|
|||
|
||||
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:str, newpath:str) → rc:int[, errno:int]
|
||||
|
||||
Renames file.
|
||||
|
@ -1370,28 +1523,79 @@ UNIX MODULE
|
|||
|
||||
Creates soft link, or a symbolic link.
|
||||
|
||||
unix.realpath(filename:str) → abspath:str[, errno:int]
|
||||
|
||||
Returns absolute path of filename, with `.` and `..` components
|
||||
removed, and symlinks will be resolved.
|
||||
|
||||
unix.chown(path:str, uid, gid) → rc:int[, errno:int]
|
||||
|
||||
Changes user and gorup on file.
|
||||
|
||||
unix.chmod(path:str, mode) → rc:int[, errno:int]
|
||||
|
||||
Changes mode bits on file.
|
||||
|
||||
unix.getcwd(path:str, mode) → rc:int[, errno:int]
|
||||
unix.getpid() → pid
|
||||
unix.getppid() → pid
|
||||
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]
|
||||
|
||||
Returns current working directory.
|
||||
|
||||
unix.fcntl(fd:int, cmd:int[, arg:int]) → rc:int[, errno:int]
|
||||
|
||||
Manipulates file descriptor.
|
||||
|
||||
Setting `cmd` to `F_GETFD`, `F_SETFD`, `F_GETFL` or `F_SETFL`
|
||||
lets you query and/or change the status of file descriptors. For
|
||||
example, it's possible using this to change `FD_CLOEXEC`.
|
||||
|
||||
POSIX advisory locks can be controlled by setting `cmd` to
|
||||
`F_UNLCK`, `F_RDLCK`, `F_WRLCK`, `F_SETLK`, or `F_SETLKW`.
|
||||
|
||||
unix.getsid(pid) → sid, errno:int
|
||||
|
||||
Gets session id.
|
||||
|
||||
unix.getpgrp() → pgid, errno:int
|
||||
unix.getpgid(pid) → pgid, errno:int
|
||||
|
||||
Gets process group id.
|
||||
|
||||
unix.setpgrp() → pgid, errno:int
|
||||
|
||||
Sets process group id. This is the same as `setpgid(0,0)`.
|
||||
|
||||
unix.setpgid(pid, pgid) → pgid, errno:int
|
||||
|
||||
Sets process group id the modern way.
|
||||
|
||||
unix.getpgid(pid) → pgid, errno:int
|
||||
|
||||
Gets process group id the modern wayp.
|
||||
|
||||
unix.setsid() → sid, errno:int
|
||||
unix.getuid() → uid, errno:int
|
||||
|
||||
Sets session id.
|
||||
|
||||
This function can be used to create daemons.
|
||||
|
||||
unix.getuid() → uid:int
|
||||
|
||||
Gets user id.
|
||||
|
||||
unix.getgid() → gid:int
|
||||
|
||||
Sets group id.
|
||||
|
||||
unix.setuid(uid:int) → rc:int[, errno:int]
|
||||
unix.getgid() → gid, errno:int
|
||||
|
||||
Sets user id.
|
||||
|
||||
unix.setgid(gid:int) → rc:int[, errno:int]
|
||||
unix.umask(mask) → rc:int[, errno:int]
|
||||
|
||||
Sets group id.
|
||||
|
||||
unix.umask(mask) → oldmask:int
|
||||
|
||||
Sets file permission mask and returns the old one.
|
||||
|
||||
unix.syslog(priority:int, msg:str)
|
||||
|
||||
|
@ -1455,17 +1659,42 @@ UNIX MODULE
|
|||
`type` defaults to `SOCK_STREAM`
|
||||
`protocol` defaults to `IPPROTO_TCP`
|
||||
|
||||
unix.bind(fd:int, ip, port) → rc:int[, errno:int]
|
||||
unix.bind(fd:int[, ip:uint32, port:uint16]) → rc:int[, errno:int]
|
||||
|
||||
unix.connect(fd:int, ip, port) → rc:int[, errno:int]
|
||||
Binds socket.
|
||||
|
||||
`ip` and `port` are in host endian order. For example, if you
|
||||
wanted to listen on `10.0.0.1:31337` you could do:
|
||||
|
||||
unix.bind(sock, 10 << 24 | 0 << 16 | 0 << 8 | 1, 31337)
|
||||
|
||||
By default, `ip` and `port` are set to zero, which means to
|
||||
listen on all interfaces with a kernel-assigned port number that
|
||||
can be retrieved and used as follows:
|
||||
|
||||
local sock = unix.socket()
|
||||
unix.bind(sock)
|
||||
ip, port = unix.getsockname(sock)
|
||||
print("listening on ip", FormatIp(ip), "port", port)
|
||||
unix.listen(sock)
|
||||
unix.accept(sock)
|
||||
while true do
|
||||
client, clientip, clientport = unix.accept(sock)
|
||||
print("got client ip", FormatIp(clientip), "port", clientport)
|
||||
unix.close(client)
|
||||
end
|
||||
|
||||
unix.listen(fd:int[, backlog]) → rc:int[, errno:int]
|
||||
|
||||
unix.getsockname(fd:int) → ip, port, errno:int
|
||||
Listens for incoming connections on bound socket.
|
||||
|
||||
unix.getpeername(fd:int) → ip, port, errno:int
|
||||
unix.accept(serverfd) → clientfd:int, ip:uint32, port:uint16[, errno:int]
|
||||
|
||||
unix.accept(serverfd) → clientfd, ip, port, errno:int
|
||||
unix.connect(fd:int, ip:uint32, port:uint16) → rc:int[, errno:int]
|
||||
|
||||
unix.getsockname(fd:int) → ip:uint32, port:uint16[, errno:int]
|
||||
|
||||
unix.getpeername(fd:int) → ip:uint32, port:uint16[, errno:int]
|
||||
|
||||
unix.recv(fd:int[, bufsiz[, flags]]) → data, errno:int
|
||||
|
||||
|
@ -1496,7 +1725,8 @@ UNIX MODULE
|
|||
|
||||
`how` can be `SIG_BLOCK`, `SIG_UNBLOCK`, `SIG_SETMASK`
|
||||
|
||||
unix.sigaction(sig[, handler[, flags[, mask]]]) → handler, flags, mask, errno:int
|
||||
unix.sigaction(sig:int[, handler:func|int[, flags:int[, mask:int]]])
|
||||
→ oldhandler:func|int, flags:int, mask:int, errno:int
|
||||
|
||||
`handler` can be `SIG_IGN`, `SIG_DFL`, `intptr_t`, or a Lua
|
||||
function. `sig` can be `SIGINT`, `SIGQUIT`, `SIGTERM`, etc.
|
||||
|
@ -1539,6 +1769,20 @@ UNIX MODULE
|
|||
unix.sigaction(unix.SIGALRM, MyOnSigAlrm, unix.SA_RESETHAND)
|
||||
unix.setitimer(unix.ITIMER_REAL, 0, 0, 1, 0)
|
||||
|
||||
unix.reboot(how:int) → str
|
||||
|
||||
Changes power status of system.
|
||||
|
||||
`how` may be `RB_AUTOBOOT` to reboot, `RB_POWER_OFF` to power
|
||||
down, `RB_HALT_SYSTEM` to literally just stop the processor, or
|
||||
`RB_SW_SUSPEND` to put the machine into a suspend state. These
|
||||
magnums will be set to -1 if the method isn't supported on the
|
||||
host platform.
|
||||
|
||||
By default, an implicit sync() is performed. That's to help
|
||||
prevent you from losing data. If you don't want to shutdown
|
||||
gracefully, then you can bitwise or `RB_NOSYNC` into `how`.
|
||||
|
||||
unix.strerrno(errno:int) → str
|
||||
|
||||
Turns `errno` code into its symbolic name, e.g. `"EINTR"`.
|
||||
|
|
171
tool/net/lunix.c
171
tool/net/lunix.c
|
@ -52,6 +52,7 @@
|
|||
#include "libc/sysv/consts/nr.h"
|
||||
#include "libc/sysv/consts/o.h"
|
||||
#include "libc/sysv/consts/ok.h"
|
||||
#include "libc/sysv/consts/reboot.h"
|
||||
#include "libc/sysv/consts/rlimit.h"
|
||||
#include "libc/sysv/consts/sa.h"
|
||||
#include "libc/sysv/consts/shut.h"
|
||||
|
@ -92,6 +93,11 @@ static dontinline int ReturnInteger(lua_State *L, lua_Integer x) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
static dontinline int ReturnString(lua_State *L, const char *x) {
|
||||
lua_pushstring(L, x);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static dontinline int ReturnTimespec(lua_State *L, struct timespec *ts) {
|
||||
lua_pushinteger(L, ts->tv_sec);
|
||||
lua_pushinteger(L, ts->tv_nsec);
|
||||
|
@ -132,6 +138,23 @@ static char **ConvertLuaArrayToStringList(lua_State *L, int i) {
|
|||
return p;
|
||||
}
|
||||
|
||||
static char **ConvertLuaTableToEnvList(lua_State *L, int i) {
|
||||
int j, n;
|
||||
char **p, *s;
|
||||
luaL_checktype(L, i, LUA_TTABLE);
|
||||
p = xcalloc((n = 0) + 1, sizeof(char *));
|
||||
lua_pushnil(L);
|
||||
for (n = 0; lua_next(L, i);) {
|
||||
if (lua_type(L, -2) == LUA_TSTRING) {
|
||||
p = xrealloc(p, (++n + 1) * sizeof(char *));
|
||||
p[n - 1] = xasprintf("%s=%s", lua_tostring(L, -2), lua_tostring(L, -1));
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
p[n] = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
static void FreeStringList(char **p) {
|
||||
int i;
|
||||
if (p) {
|
||||
|
@ -145,6 +168,31 @@ static void FreeStringList(char **p) {
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
// System Calls
|
||||
|
||||
// unix.getpid() → pid:int
|
||||
static int LuaUnixGetpid(lua_State *L) {
|
||||
return ReturnInteger(L, getpid());
|
||||
}
|
||||
|
||||
// unix.getppid() → pid:int
|
||||
static int LuaUnixGetppid(lua_State *L) {
|
||||
return ReturnInteger(L, getppid());
|
||||
}
|
||||
|
||||
// unix.getuid() → uid:int
|
||||
static int LuaUnixGetuid(lua_State *L) {
|
||||
return ReturnInteger(L, getuid());
|
||||
}
|
||||
|
||||
// unix.getgid() → gid:int
|
||||
static int LuaUnixGetgid(lua_State *L) {
|
||||
return ReturnInteger(L, getgid());
|
||||
}
|
||||
|
||||
// unix.umask(mask:int) → oldmask:int
|
||||
static int LuaUnixUmask(lua_State *L) {
|
||||
return ReturnInteger(L, umask(luaL_checkinteger(L, 1)));
|
||||
}
|
||||
|
||||
// unix.exit([exitcode:int]) → ⊥
|
||||
static wontreturn int LuaUnixExit(lua_State *L) {
|
||||
_Exit(luaL_optinteger(L, 1, 0));
|
||||
|
@ -290,12 +338,11 @@ static int LuaUnixFork(lua_State *L) {
|
|||
return ReturnRc(L, rc, olderr);
|
||||
}
|
||||
|
||||
// unix.execve(prog, argv[, envp]) → errno
|
||||
// unix.exit(127)
|
||||
// unix.execve(prog:str[, args:List<*>, env:Map<str,*>]) → errno:int
|
||||
//
|
||||
// unix = require "unix"
|
||||
// prog = unix.commandv("ls")
|
||||
// unix.execve(prog, {prog, "-hal", "."})
|
||||
// unix.execve(prog, {prog, "-hal", "."}, {PATH="/bin"})
|
||||
// unix.exit(127)
|
||||
//
|
||||
// prog needs to be absolute, see commandv()
|
||||
|
@ -303,20 +350,30 @@ static int LuaUnixFork(lua_State *L) {
|
|||
static int LuaUnixExecve(lua_State *L) {
|
||||
int olderr;
|
||||
const char *prog;
|
||||
char **argv, **envp, **freeme;
|
||||
char **argv, **envp, **freeme1, **freeme2, *ezargs[2];
|
||||
olderr = errno;
|
||||
prog = luaL_checkstring(L, 1);
|
||||
if (!lua_isnoneornil(L, 2)) {
|
||||
argv = ConvertLuaArrayToStringList(L, 2);
|
||||
freeme1 = argv;
|
||||
if (!lua_isnoneornil(L, 3)) {
|
||||
envp = ConvertLuaArrayToStringList(L, 3);
|
||||
freeme = envp;
|
||||
envp = ConvertLuaTableToEnvList(L, 3);
|
||||
freeme2 = envp;
|
||||
} else {
|
||||
envp = environ;
|
||||
freeme = 0;
|
||||
freeme2 = 0;
|
||||
}
|
||||
} else {
|
||||
ezargs[0] = prog;
|
||||
ezargs[1] = 0;
|
||||
argv = ezargs;
|
||||
envp = environ;
|
||||
freeme1 = 0;
|
||||
freeme2 = 0;
|
||||
}
|
||||
execve(prog, argv, envp);
|
||||
FreeStringList(freeme);
|
||||
FreeStringList(argv);
|
||||
FreeStringList(freeme1);
|
||||
FreeStringList(freeme2);
|
||||
lua_pushinteger(L, errno);
|
||||
errno = olderr;
|
||||
return 1;
|
||||
|
@ -401,18 +458,6 @@ static int LuaUnixGetrlimit(lua_State *L) {
|
|||
}
|
||||
}
|
||||
|
||||
// unix.getpid() → pid:int
|
||||
static int LuaUnixGetpid(lua_State *L) {
|
||||
lua_pushinteger(L, getpid());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// unix.getppid() → pid:int
|
||||
static int LuaUnixGetppid(lua_State *L) {
|
||||
lua_pushinteger(L, getppid());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// unix.kill(pid, sig) → rc:int[, errno:int]
|
||||
static int LuaUnixKill(lua_State *L) {
|
||||
int rc, pid, sig, olderr;
|
||||
|
@ -432,11 +477,11 @@ static int LuaUnixRaise(lua_State *L) {
|
|||
return ReturnRc(L, rc, olderr);
|
||||
}
|
||||
|
||||
// unix.wait(pid[, options]) → pid, wstatus, nil, errno
|
||||
// unix.wait([pid, options]) → pid, wstatus, nil, errno
|
||||
static int LuaUnixWait(lua_State *L) {
|
||||
int rc, pid, olderr, options, wstatus;
|
||||
olderr = errno;
|
||||
pid = luaL_checkinteger(L, 1);
|
||||
pid = luaL_optinteger(L, 1, -1);
|
||||
options = luaL_optinteger(L, 2, 0);
|
||||
rc = wait4(pid, &wstatus, options, 0);
|
||||
if (rc != -1) {
|
||||
|
@ -517,15 +562,6 @@ static int LuaUnixGetpgid(lua_State *L) {
|
|||
return ReturnRc(L, rc, olderr);
|
||||
}
|
||||
|
||||
// unix.umask(mask:int) → rc:int[, errno:int]
|
||||
static int LuaUnixUmask(lua_State *L) {
|
||||
int rc, mask, olderr;
|
||||
olderr = errno;
|
||||
mask = luaL_checkinteger(L, 1);
|
||||
rc = umask(mask);
|
||||
return ReturnRc(L, rc, olderr);
|
||||
}
|
||||
|
||||
// unix.setpgid(pid:int, pgid:int) → pgid:int[, errno:int]
|
||||
static int LuaUnixSetpgid(lua_State *L) {
|
||||
int rc, pid, pgid, olderr;
|
||||
|
@ -554,28 +590,12 @@ static int LuaUnixSetsid(lua_State *L) {
|
|||
return ReturnRc(L, rc, olderr);
|
||||
}
|
||||
|
||||
// unix.getuid() → uid[, errno]
|
||||
static int LuaUnixGetuid(lua_State *L) {
|
||||
int rc, olderr;
|
||||
olderr = errno;
|
||||
rc = getuid();
|
||||
return ReturnRc(L, rc, olderr);
|
||||
}
|
||||
|
||||
// unix.setuid(uid:int) → rc:int[, errno:int]
|
||||
static int LuaUnixSetuid(lua_State *L) {
|
||||
int olderr = errno;
|
||||
return ReturnRc(L, setuid(luaL_checkinteger(L, 1)), olderr);
|
||||
}
|
||||
|
||||
// unix.getgid() → gid:int[, errno:int]
|
||||
static int LuaUnixGetgid(lua_State *L) {
|
||||
int rc, olderr;
|
||||
olderr = errno;
|
||||
rc = getgid();
|
||||
return ReturnRc(L, rc, olderr);
|
||||
}
|
||||
|
||||
// unix.setgid(gid:int) → rc:int[, errno:int]
|
||||
static int LuaUnixSetgid(lua_State *L) {
|
||||
int olderr = errno;
|
||||
|
@ -848,7 +868,7 @@ static int LuaUnixSocketpair(lua_State *L) {
|
|||
}
|
||||
}
|
||||
|
||||
// unix.bind(fd, ip, port) → rc:int[, errno:int]
|
||||
// unix.bind(fd[, ip, port]) → rc:int[, errno:int]
|
||||
// SOCK_CLOEXEC may be or'd into type
|
||||
// family defaults to AF_INET
|
||||
// type defaults to SOCK_STREAM
|
||||
|
@ -859,8 +879,8 @@ static int LuaUnixBind(lua_State *L) {
|
|||
bzero(&sa, sizeof(sa));
|
||||
olderr = errno;
|
||||
fd = luaL_checkinteger(L, 1);
|
||||
sa.sin_addr.s_addr = htonl(luaL_checkinteger(L, 2));
|
||||
sa.sin_port = htons(luaL_checkinteger(L, 3));
|
||||
sa.sin_addr.s_addr = htonl(luaL_optinteger(L, 2, 0));
|
||||
sa.sin_port = htons(luaL_optinteger(L, 3, 0));
|
||||
rc = bind(fd, &sa, sizeof(sa));
|
||||
return ReturnRc(L, rc, olderr);
|
||||
}
|
||||
|
@ -928,7 +948,7 @@ static int LuaUnixGetpeername(lua_State *L) {
|
|||
}
|
||||
}
|
||||
|
||||
// unix.accept(serverfd) → clientfd, ip, port, errno
|
||||
// unix.accept(serverfd:int) → clientfd:int, ip:uint32, port:uint16, errno
|
||||
static int LuaUnixAccept(lua_State *L) {
|
||||
uint32_t addrsize;
|
||||
struct sockaddr_in sa;
|
||||
|
@ -1237,20 +1257,42 @@ static int LuaUnixSetitimer(lua_State *L) {
|
|||
|
||||
// unix.strerror(errno) → str
|
||||
static int LuaUnixStrerror(lua_State *L) {
|
||||
lua_pushstring(L, strerror(luaL_checkinteger(L, 1)));
|
||||
return 1;
|
||||
return ReturnString(L, strerror(luaL_checkinteger(L, 1)));
|
||||
}
|
||||
|
||||
// unix.strerrno(errno) → str
|
||||
static int LuaUnixStrerrno(lua_State *L) {
|
||||
lua_pushstring(L, strerror_short(luaL_checkinteger(L, 1)));
|
||||
return 1;
|
||||
return ReturnString(L, strerror_short(luaL_checkinteger(L, 1)));
|
||||
}
|
||||
|
||||
// unix.strsignal(sig) → str
|
||||
static int LuaUnixStrsignal(lua_State *L) {
|
||||
lua_pushstring(L, strsignal(luaL_checkinteger(L, 1)));
|
||||
return 1;
|
||||
return ReturnString(L, strsignal(luaL_checkinteger(L, 1)));
|
||||
}
|
||||
|
||||
// unix.WIFEXITED(wstatus) → int
|
||||
static int LuaUnixWifexited(lua_State *L) {
|
||||
return ReturnInteger(L, WIFEXITED(luaL_checkinteger(L, 1)));
|
||||
}
|
||||
|
||||
// unix.WEXITSTATUS(wstatus) → int
|
||||
static int LuaUnixWexitstatus(lua_State *L) {
|
||||
return ReturnInteger(L, WEXITSTATUS(luaL_checkinteger(L, 1)));
|
||||
}
|
||||
|
||||
// unix.WIFSIGNALED(wstatus) → int
|
||||
static int LuaUnixWifsignaled(lua_State *L) {
|
||||
return ReturnInteger(L, WIFSIGNALED(luaL_checkinteger(L, 1)));
|
||||
}
|
||||
|
||||
// unix.WTERMSIG(wstatus) → int
|
||||
static int LuaUnixWtermsig(lua_State *L) {
|
||||
return ReturnInteger(L, WTERMSIG(luaL_checkinteger(L, 1)));
|
||||
}
|
||||
|
||||
// unix.reboot(how:int) → rc:int[, errno:int]
|
||||
static int LuaUnixReboot(lua_State *L) {
|
||||
return ReturnInteger(L, reboot(luaL_checkinteger(L, 1)));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1549,9 +1591,14 @@ static const luaL_Reg kLuaUnix[] = {
|
|||
{"sigprocmask", LuaUnixSigprocmask}, // change signal mask
|
||||
{"sigsuspend", LuaUnixSigsuspend}, // wait for signal
|
||||
{"setitimer", LuaUnixSetitimer}, // set alarm clock
|
||||
{"reboot", LuaUnixReboot}, // reboots system
|
||||
{"strerror", LuaUnixStrerror}, // turn errno into string
|
||||
{"strerrno", LuaUnixStrerrno}, // turn errno into string
|
||||
{"strsignal", LuaUnixStrsignal}, // turn signal into string
|
||||
{"WIFEXITED", LuaUnixWifexited}, // gets exit code from wait status
|
||||
{"WEXITSTATUS", LuaUnixWexitstatus}, // gets exit status from wait status
|
||||
{"WIFSIGNALED", LuaUnixWifsignaled}, // determines if died due to signal
|
||||
{"WTERMSIG", LuaUnixWtermsig}, // gets the signal code
|
||||
{0}, //
|
||||
};
|
||||
|
||||
|
@ -1643,6 +1690,7 @@ int LuaUnix(lua_State *L) {
|
|||
|
||||
// wait() options
|
||||
LuaSetIntField(L, "WNOHANG", WNOHANG);
|
||||
LuaSetIntField(L, "WNOHANG", WNOHANG);
|
||||
|
||||
// gettime() clocks
|
||||
LuaSetIntField(L, "CLOCK_REALTIME", CLOCK_REALTIME); // portable
|
||||
|
@ -1721,5 +1769,12 @@ int LuaUnix(lua_State *L) {
|
|||
LuaSetIntField(L, "LOG_INFO", LOG_INFO);
|
||||
LuaSetIntField(L, "LOG_DEBUG", LOG_DEBUG);
|
||||
|
||||
// reboot() howto
|
||||
LuaSetIntField(L, "RB_AUTOBOOT", RB_AUTOBOOT);
|
||||
LuaSetIntField(L, "RB_POWER_OFF", RB_POWER_OFF);
|
||||
LuaSetIntField(L, "RB_HALT_SYSTEM", RB_HALT_SYSTEM);
|
||||
LuaSetIntField(L, "RB_SW_SUSPEND", RB_SW_SUSPEND);
|
||||
LuaSetIntField(L, "RB_NOSYNC", RB_NOSYNC);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -212,6 +212,7 @@ o/$(MODE)/tool/net/redbean-demo.com.dbg: \
|
|||
o/$(MODE)/tool/net/net.pkg \
|
||||
o/$(MODE)/tool/net/demo/sql.lua.zip.o \
|
||||
o/$(MODE)/tool/net/demo/unix.lua.zip.o \
|
||||
o/$(MODE)/tool/net/demo/unix2.lua.zip.o \
|
||||
o/$(MODE)/tool/net/demo/fetch.lua.zip.o \
|
||||
o/$(MODE)/tool/net/demo/hello.lua.zip.o \
|
||||
o/$(MODE)/tool/net/demo/redbean.lua.zip.o \
|
||||
|
|
|
@ -53,12 +53,15 @@
|
|||
#include "third_party/stb/stb_image.h"
|
||||
#include "tool/viz/lib/graphic.h"
|
||||
|
||||
STATIC_YOINK("__zipos_get");
|
||||
|
||||
static struct Flags {
|
||||
const char *out;
|
||||
bool subpixel;
|
||||
bool unsharp;
|
||||
bool dither;
|
||||
bool ruler;
|
||||
bool magikarp;
|
||||
bool trailingnewline;
|
||||
long half;
|
||||
bool full;
|
||||
|
@ -85,6 +88,7 @@ FLAGS\n\
|
|||
-f display full size\n\
|
||||
-s unsharp sharpening\n\
|
||||
-x xterm256 color mode\n\
|
||||
-m use magikarp scaling\n\
|
||||
-d hilbert curve dithering\n\
|
||||
-r display pixel ruler on sides\n\
|
||||
-p convert to subpixel layout\n\
|
||||
|
@ -94,7 +98,6 @@ FLAGS\n\
|
|||
EXAMPLES\n\
|
||||
\n\
|
||||
printimage.com -sxd lemurs.jpg # 256-color dither unsharp\n\
|
||||
\n\
|
||||
\n");
|
||||
exit(rc);
|
||||
}
|
||||
|
@ -118,7 +121,7 @@ static void GetOpts(int *argc, char *argv[]) {
|
|||
(strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-help") == 0)) {
|
||||
PrintUsage(EXIT_SUCCESS, stdout);
|
||||
}
|
||||
while ((opt = getopt(*argc, argv, "?vpfrtxads234o:w:h:")) != -1) {
|
||||
while ((opt = getopt(*argc, argv, "?vpmfrtxads234o:w:h:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'o':
|
||||
g_flags.out = optarg;
|
||||
|
@ -146,6 +149,9 @@ static void GetOpts(int *argc, char *argv[]) {
|
|||
case 'r':
|
||||
g_flags.ruler = true;
|
||||
break;
|
||||
case 'm':
|
||||
g_flags.magikarp = true;
|
||||
break;
|
||||
case 'p':
|
||||
g_flags.subpixel = true;
|
||||
break;
|
||||
|
@ -173,10 +179,6 @@ static void GetOpts(int *argc, char *argv[]) {
|
|||
PrintUsage(EX_USAGE, stderr);
|
||||
}
|
||||
}
|
||||
if (optind == *argc) {
|
||||
if (!g_flags.out) g_flags.out = "-";
|
||||
argv[(*argc)++] = "-";
|
||||
}
|
||||
if (!g_flags.full && (!g_flags.width || !g_flags.width)) {
|
||||
ws.ws_col = 80;
|
||||
ws.ws_row = 24;
|
||||
|
@ -398,6 +400,22 @@ void WithImageFile(const char *path,
|
|||
sxn = xn;
|
||||
dyn = g_flags.height;
|
||||
dxn = g_flags.width;
|
||||
if (g_flags.magikarp) {
|
||||
while (HALF(syn) > dyn || HALF(sxn) > dxn) {
|
||||
if (HALF(sxn) > dxn) {
|
||||
Magikarp2xX(yn, xn, data, syn, sxn);
|
||||
Magikarp2xX(yn, xn, (char *)data + yn * xn, syn, sxn);
|
||||
Magikarp2xX(yn, xn, (char *)data + yn * xn * 2, syn, sxn);
|
||||
sxn = HALF(sxn);
|
||||
}
|
||||
if (HALF(syn) > dyn) {
|
||||
Magikarp2xY(yn, xn, data, syn, sxn);
|
||||
Magikarp2xY(yn, xn, (char *)data + yn * xn, syn, sxn);
|
||||
Magikarp2xY(yn, xn, (char *)data + yn * xn * 2, syn, sxn);
|
||||
syn = HALF(syn);
|
||||
}
|
||||
}
|
||||
}
|
||||
data = EzGyarados(3, dyn, dxn, gc(memalign(32, dyn * dxn * 3)), cn, yn, xn,
|
||||
data, 0, cn, dyn, dxn, syn, sxn, 0, 0, 0, 0);
|
||||
yn = dyn;
|
||||
|
@ -410,6 +428,7 @@ int main(int argc, char *argv[]) {
|
|||
int i;
|
||||
ShowCrashReports();
|
||||
GetOpts(&argc, argv);
|
||||
if (optind == argc) PrintUsage(0, stdout);
|
||||
stbi_set_unpremultiply_on_load(true);
|
||||
for (i = optind; i < argc; ++i) {
|
||||
WithImageFile(argv[i], ProcessImage);
|
||||
|
|
|
@ -45,6 +45,7 @@ TOOL_VIZ_DIRECTDEPS = \
|
|||
LIBC_TINYMATH \
|
||||
LIBC_UNICODE \
|
||||
LIBC_X \
|
||||
LIBC_ZIPOS \
|
||||
NET_HTTP \
|
||||
THIRD_PARTY_DLMALLOC \
|
||||
THIRD_PARTY_GDTOA \
|
||||
|
@ -71,6 +72,25 @@ o/$(MODE)/tool/viz/%.com.dbg: \
|
|||
$(APE)
|
||||
@$(APELINK)
|
||||
|
||||
o/$(MODE)/tool/viz/printimage.com.dbg: \
|
||||
$(TOOL_VIZ_DEPS) \
|
||||
o/$(MODE)/tool/viz/printimage.o \
|
||||
o/$(MODE)/tool/viz/viz.pkg \
|
||||
o/$(MODE)/LICENSE.zip.o \
|
||||
$(CRT) \
|
||||
$(APE_NO_MODIFY_SELF)
|
||||
@$(APELINK)
|
||||
|
||||
o/$(MODE)/tool/viz/printimage.com: \
|
||||
o/$(MODE)/tool/viz/printimage.com.dbg \
|
||||
o/$(MODE)/third_party/zip/zip.com \
|
||||
o/$(MODE)/tool/build/symtab.com
|
||||
@$(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@
|
||||
@$(COMPILE) -ASYMTAB o/$(MODE)/tool/build/symtab.com \
|
||||
-o o/$(MODE)/tool/viz/.printimage/.symtab $<
|
||||
@$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -9qj $@ \
|
||||
o/$(MODE)/tool/viz/.printimage/.symtab
|
||||
|
||||
o/$(MODE)/tool/viz/printvideo.com: \
|
||||
o/$(MODE)/tool/viz/printvideo.com.dbg \
|
||||
o/$(MODE)/third_party/zip/zip.com \
|
||||
|
|
Loading…
Add table
Reference in a new issue