Do some work on redbean

- Rewrite Slurp() API to be like string.sub()
- Introduce a new Barf() API for creating files
- Update Redbean `-S` sandbox flag to do unveiling
This commit is contained in:
Justine Tunney 2022-07-22 20:44:24 -07:00
parent 742251dd92
commit 48ce3ad7cc
9 changed files with 296 additions and 33 deletions

View file

@ -33,7 +33,9 @@
* @param fd is something open()'d earlier, noting pipes might not work
* @param buf is copied from, cf. copy_file_range(), sendfile(), etc.
* @param size in range [1..0x7ffff000] is reasonable
* @param offset is bytes from start of file at which write begins
* @param offset is bytes from start of file at which write begins,
* which can exceed or overlap the end of file, in which case your
* file will be extended
* @return [1..size] bytes on success, or -1 w/ errno; noting zero is
* impossible unless size was passed as zero to do an error check
* @see pread(), write()

View file

@ -29,13 +29,13 @@ __attribute__((__constructor__)) static void init(void) {
errno = 0;
}
TEST(dog, testReadPastEof_returnsZero) {
TEST(pread, testReadPastEof_returnsZero) {
EXPECT_NE(-1, (fd = open("a", O_RDWR | O_CREAT | O_TRUNC, 0644)));
EXPECT_EQ(0, pread(fd, buf, 8, 0));
EXPECT_EQ(0, close(fd));
}
TEST(dog, testReadOverlapsEof_returnsShortNumber) {
TEST(pread, testReadOverlapsEof_returnsShortNumber) {
EXPECT_NE(-1, (fd = open("b", O_RDWR | O_CREAT | O_TRUNC, 0644)));
EXPECT_EQ(4, pwrite(fd, buf, 4, 0));
EXPECT_EQ(4, pread(fd, buf, 8, 0));

View file

@ -0,0 +1,39 @@
/*-*- 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/calls/calls.h"
#include "libc/calls/struct/stat.h"
#include "libc/sysv/consts/o.h"
#include "libc/testlib/testlib.h"
char buf[8];
struct stat st;
char testlib_enable_tmp_setup_teardown;
__attribute__((__constructor__)) static void init(void) {
pledge("stdio rpath wpath cpath fattr", 0);
errno = 0;
}
TEST(pwrite, testWritePastEof_extendsFile) {
EXPECT_SYS(0, 3, creat("foo", 0644));
EXPECT_SYS(0, 8, pwrite(3, buf, 8, 100));
EXPECT_SYS(0, 0, fstat(3, &st));
EXPECT_EQ(108, st.st_size);
EXPECT_SYS(0, 0, close(3));
}

View file

@ -0,0 +1,50 @@
-- 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.
tmpdir = "o/tmp/lunix_test.%d" % {unix.getpid()}
local function Path(name)
return tmpdir .. '/' .. name
end
local function SlurpTest()
data = 'abc123' * 5000
assert(Barf(Path('foo'), data))
assert(assert(Slurp(Path('foo'))) == data)
assert(assert(Slurp(Path('foo'), 2, 3)) == 'bc')
assert(assert(Slurp(Path('foo'), 2)) == data:sub(2))
assert(assert(Slurp(Path('foo'), 2, 1)) == data:sub(2, 1))
assert(assert(Slurp(Path('foo'), 2, 2)) == data:sub(2, 2))
assert(assert(Slurp(Path('foo'), 1, 2)) == data:sub(1, 2))
assert(assert(Slurp(Path('foo'), -3, -1)) == data:sub(-3, -1))
assert(Barf(Path('foo'), 'XX', 0, 0, 3))
assert(assert(Slurp(Path('foo'), 1, 6)) == 'abXX23')
end
local function main()
assert(unix.makedirs(tmpdir))
ok, err = pcall(SlurpTest)
if ok then
assert(unix.rmrf(tmpdir))
else
print(err)
error('SlurpTest failed (%s)' % {tmpdir})
end
end
main()

View file

@ -39,24 +39,32 @@ local function main()
WriteForm("https://www.cloudflare.com/robots.txt")
elseif GetMethod() == 'POST' then
status, headers, payload = Fetch(GetParam('url'))
WriteForm(GetParam('url'))
Write('<dl>\r\n')
Write('<dt>Status\r\n')
Write('<dd><p>%d %s\r\n' % {status, GetHttpReason(status)})
Write('<dt>Headers\r\n')
Write('<dd>\r\n')
for k,v in pairs(headers) do
Write('<div class="hdr"><strong>')
Write(EscapeHtml(k))
Write('</strong>: ')
Write(EscapeHtml(v))
Write('</div>\r\n')
if status then
WriteForm(GetParam('url'))
Write('<dl>\r\n')
Write('<dt>Status\r\n')
Write('<dd><p>%d %s\r\n' % {status, GetHttpReason(status)})
Write('<dt>Headers\r\n')
Write('<dd>\r\n')
for k,v in pairs(headers) do
Write('<div class="hdr"><strong>')
Write(EscapeHtml(k))
Write('</strong>: ')
Write(EscapeHtml(v))
Write('</div>\r\n')
end
Write('<dt>Payload\r\n')
Write('<dd><pre>')
Write(EscapeHtml(VisualizeControlCodes(payload)))
Write('</pre>\r\n')
Write('</dl>\r\n')
else
err = headers
WriteForm(GetParam('url'))
Write('<h3>Error</h3>\n')
Write('<p>')
Write(EscapeHtml(VisualizeControlCodes(err)))
end
Write('<dt>Payload\r\n')
Write('<dd><pre>')
Write(EscapeHtml(VisualizeControlCodes(payload)))
Write('</pre>\r\n')
Write('</dl>\r\n')
else
ServeError(405)
SetHeader('Allow', 'GET, HEAD, POST')

View file

@ -1499,8 +1499,46 @@ FUNCTIONS
Same as the -u flag if called from .init.lua. Can be used to
configure the uniprocess mode. The current value is returned.
Slurp(filename:str) → str
Reads file data from local file system.
Slurp(filename:str[, i:int[, j:int]])
├─→ data:str
└─→ nil, unix.Errno
Writes all data from file the easy way.
This function reads file data from local file system. Zip file
assets can be accessed using the `/zip/...` prefix.
`i` and `j` may be used to slice a substring in `filename`.
These parameters are 1-indexed and behave consistently with
Lua's string.sub() API. For example:
assert(Barf('x.txt', 'abc123'))
assert(assert(Slurp('x.txt', 2, 3)) == 'bc')
This function is uninterruptible so `unix.EINTR` errors will
be ignored. This should only be a concern if you've installed
signal handlers. Use the UNIX API if you need to react to it.
Barf(filename:str, data:str[, mode:int[, flags:int[, offset:int]]])
├─→ true
└─→ nil, unix.Errno
Writes all data to file the easy way.
This function writes to the local file system.
`mode` defaults to `0644`. This parameter is ignored when
`flags` doesn't have `unix.O_CREAT`.
`flags` defaults to `unix.O_TRUNC | unix.O_CREAT`.
`offset` is 1-indexed and may be used to overwrite arbitrary
slices within a file when used in conjunction with `flags=0`.
For example:
assert(Barf('x.txt', 'abc123'))
assert(Barf('x.txt', 'XX', 0, 0, 3))
assert(assert(Slurp('x.txt', 1, 6)) == 'abXX23')
Sleep(seconds:number)
Sleeps the specified number of seconds (can be fractional). The
@ -3069,7 +3107,6 @@ UNIX MODULE
Sets user id for file system ops.
unix.setgid(gid:int)
├─→ true
└─→ nil, unix.Errno

View file

@ -21,7 +21,9 @@
#include "libc/bits/popcnt.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/rusage.h"
#include "libc/calls/struct/stat.h"
#include "libc/dns/dns.h"
#include "libc/errno.h"
#include "libc/fmt/itoa.h"
#include "libc/fmt/leb128.h"
#include "libc/intrin/kprintf.h"
@ -43,6 +45,7 @@
#include "libc/sock/sock.h"
#include "libc/sysv/consts/af.h"
#include "libc/sysv/consts/ipproto.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/rusage.h"
#include "libc/sysv/consts/sock.h"
#include "libc/time/time.h"
@ -54,6 +57,8 @@
#include "third_party/lua/cosmo.h"
#include "third_party/lua/lauxlib.h"
#include "third_party/lua/lua.h"
#include "third_party/lua/luaconf.h"
#include "third_party/lua/lunix.h"
#include "third_party/mbedtls/md.h"
#include "third_party/mbedtls/md5.h"
#include "third_party/mbedtls/platform.h"
@ -368,19 +373,128 @@ int LuaGetMonospaceWidth(lua_State *L) {
return 1;
}
// Slurp(path:str[, i:int[, j:int]])
// ├─→ data:str
// └─→ nil, unix.Errno
int LuaSlurp(lua_State *L) {
char *p, *f;
size_t n;
f = luaL_checkstring(L, 1);
if ((p = xslurp(f, &n))) {
lua_pushlstring(L, p, n);
free(p);
return 1;
ssize_t rc;
char tb[2048];
luaL_Buffer b;
struct stat st;
int fd, olderr;
bool shouldpread;
lua_Integer i, j, got;
olderr = errno;
if (lua_isnoneornil(L, 2)) {
i = 1;
} else {
lua_pushnil(L);
lua_pushstring(L, gc(xasprintf("Can't slurp file %`'s: %m", f)));
return 2;
i = luaL_checkinteger(L, 2);
}
if (lua_isnoneornil(L, 3)) {
j = LUA_MAXINTEGER;
} else {
j = luaL_checkinteger(L, 3);
}
luaL_buffinit(L, &b);
if ((fd = open(luaL_checkstring(L, 1), O_RDONLY | O_SEQUENTIAL)) == -1) {
return LuaUnixSysretErrno(L, "open", olderr);
}
if (i < 0 || j < 0) {
if (fstat(fd, &st) == -1) {
close(fd);
return LuaUnixSysretErrno(L, "fstat", olderr);
}
if (i < 0) {
i = st.st_size + (i + 1);
}
if (j < 0) {
j = st.st_size + (j + 1);
}
}
if (i < 1) {
i = 1;
}
shouldpread = i > 1;
for (; i <= j; i += got) {
if (shouldpread) {
rc = pread(fd, tb, MIN(j - i + 1, sizeof(tb)), i - 1);
} else {
rc = read(fd, tb, MIN(j - i + 1, sizeof(tb)));
}
if (rc != -1) {
got = rc;
if (!got) break;
luaL_addlstring(&b, tb, got);
} else if (errno == EINTR) {
errno = olderr;
got = 0;
} else {
close(fd);
return LuaUnixSysretErrno(L, "read", olderr);
}
}
if (close(fd) == -1) {
return LuaUnixSysretErrno(L, "close", olderr);
}
luaL_pushresult(&b);
return 1;
}
// Barf(path:str, data:str[, mode:int[, flags:int[, offset:int]]])
// ├─→ true
// └─→ nil, unix.Errno
int LuaBarf(lua_State *L) {
char *data;
ssize_t rc;
lua_Number offset;
size_t i, n, wrote;
int fd, mode, flags, olderr;
olderr = errno;
data = luaL_checklstring(L, 2, &n);
if (lua_isnoneornil(L, 5)) {
offset = 0;
} else {
offset = luaL_checkinteger(L, 5);
if (offset < 1) {
luaL_error(L, "offset must be >= 1");
unreachable;
}
--offset;
}
mode = luaL_optinteger(L, 3, 0644);
flags = O_WRONLY | O_SEQUENTIAL | luaL_optinteger(L, 4, O_TRUNC | O_CREAT);
if (flags & O_NONBLOCK) {
luaL_error(L, "O_NONBLOCK not allowed");
unreachable;
}
if ((flags & O_APPEND) && offset) {
luaL_error(L, "O_APPEND with offset not possible");
unreachable;
}
if ((fd = open(luaL_checkstring(L, 1), flags, mode)) == -1) {
return LuaUnixSysretErrno(L, "open", olderr);
}
for (i = 0; i < n; i += wrote) {
if (offset) {
rc = pwrite(fd, data + i, n - i, offset + i);
} else {
rc = write(fd, data + i, n - i);
}
if (rc != -1) {
wrote = rc;
} else if (errno == EINTR) {
errno = olderr;
wrote = 0;
} else {
close(fd);
return LuaUnixSysretErrno(L, "write", olderr);
}
}
if (close(fd) == -1) {
return LuaUnixSysretErrno(L, "close", olderr);
}
lua_pushboolean(L, true);
return 1;
}
int LuaResolveIp(lua_State *L) {

View file

@ -10,6 +10,7 @@ int LuaRe(lua_State *);
int luaopen_argon2(lua_State *);
int luaopen_lsqlite3(lua_State *);
int LuaBarf(lua_State *);
int LuaBenchmark(lua_State *);
int LuaBin(lua_State *);
int LuaBsf(lua_State *);

View file

@ -5126,6 +5126,7 @@ static const char *const kDontAutoComplete[] = {
// </SORTED>
static const luaL_Reg kLuaFuncs[] = {
{"Barf", LuaBarf}, //
{"Benchmark", LuaBenchmark}, //
{"Bsf", LuaBsf}, //
{"Bsr", LuaBsr}, //
@ -6543,18 +6544,29 @@ static int ExitWorker(void) {
_Exit(0);
}
static void UnveilRedbean(void) {
size_t i;
for (i = 0; i < stagedirs.n; ++i) {
unveil(stagedirs.p[i].s, "r");
}
unveil(0, 0);
}
static int EnableSandbox(void) {
switch (sandboxed) {
case 0:
return 0;
case 1: // -S
DEBUGF("(stat) applying '%s' sandbox policy", "online");
UnveilRedbean();
return pledge("stdio rpath inet dns", 0);
case 2: // -SS
DEBUGF("(stat) applying '%s' sandbox policy", "offline");
UnveilRedbean();
return pledge("stdio rpath", 0);
default: // -SSS
DEBUGF("(stat) applying '%s' sandbox policy", "contained");
UnveilRedbean();
return pledge("stdio", 0);
}
}