Make improvements

- Introduce path module to redbean
- Fix glitch with linenoise printing extra line on eof
- Introduce closefrom() and close_range() system calls
- Make file descriptor closing more secure in pledge.com
This commit is contained in:
Justine Tunney 2022-07-20 15:13:39 -07:00
parent 439ad21b12
commit 1837dc2e85
31 changed files with 806 additions and 75 deletions

23
examples/tls.c Normal file
View file

@ -0,0 +1,23 @@
#if 0
/*─────────────────────────────────────────────────────────────────╗
To the extent possible under law, Justine Tunney has waived
all copyright and related or neighboring rights to this file,
as it is written in the following disclaimers:
http://unlicense.org/ │
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
/**
* @fileoverview thread local storage
*
* Cosmopolitan guarantees `_Thread_local` variables are always
* accessible, even if you're not using threads.
*/
_Thread_local int x;
_Thread_local int y = 42;
int main(int argc, char *argv[]) {
return x + y;
}

View file

@ -69,6 +69,8 @@ int chown(const char *, uint32_t, uint32_t);
int chroot(const char *);
int clone(void *, void *, size_t, int, void *, int *, void *, int *);
int close(int);
int close_range(unsigned, unsigned, unsigned);
int closefrom(int);
int creat(const char *, uint32_t);
int dup(int);
int dup2(int, int);

View file

@ -32,8 +32,8 @@
/**
* Closes file descriptor.
*
* This function may be used for file descriptors returned by socket,
* accept, epoll_create, and zipos file descriptors too.
* This function may be used for file descriptors returned by functions
* like open, socket, accept, epoll_create, and landlock_create_ruleset.
*
* This function should never be called twice for the same file
* descriptor, regardless of whether or not an error happened. However

65
libc/calls/close_range.c Normal file
View file

@ -0,0 +1,65 @@
/*-*- 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/strace.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/errno.h"
#include "libc/limits.h"
/**
* Closes inclusive range of file descriptors, e.g.
*
* // close all non-stdio file descriptors
* if (close_range(3, -1, 0) == -1) {
* for (int i = 3; i < 256; ++i) {
* close(i);
* }
* }
*
* This is supported on Linux 5.9+, FreeBSD, and OpenBSD. On FreeBSD,
* `flags` must be zero. On OpenBSD, we call closefrom(int) so `last`
* should be `-1` in order to get OpenBSD support, otherwise `ENOSYS`
* will be returned. We also polyfill closefrom on FreeBSD since it's
* available on older kernels.
*
* On Linux, the following flags are supported:
*
* - CLOSE_RANGE_UNSHARE
* - CLOSE_RANGE_CLOEXEC
*
* @return 0 on success, or -1 w/ errno
* @error ENOSYS if not Linux 5.9+ / FreeBSD / OpenBSD
* @error EBADF on OpenBSD if `first` is greater than highest fd
* @error EINVAL if flags are bad or first is greater than last
* @error EMFILE if a weird race condition happens on Linux
* @error EINTR possibly on OpenBSD
* @error ENOMEM on Linux maybe
*/
int close_range(unsigned int first, unsigned int last, unsigned int flags) {
int rc, err;
err = errno;
if ((rc = sys_close_range(first, last, flags)) == -1) {
if (errno == ENOSYS && first <= INT_MAX && last == UINT_MAX && !flags) {
errno = err;
rc = sys_closefrom(first);
}
}
STRACE("close_range(%d, %d, %#x) → %d% m", first, last, flags, rc);
return rc;
}

60
libc/calls/closefrom.c Normal file
View file

@ -0,0 +1,60 @@
/*-*- 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/strace.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/errno.h"
#include "libc/limits.h"
#include "libc/sysv/errfuns.h"
/**
* Closes extra file descriptors, e.g.
*
* // close all non-stdio file descriptors
* if (closefrom(3) == -1) {
* for (int i = 3; i < 256; ++i) {
* close(i);
* }
* }
*
* @return 0 on success, or -1 w/ errno
* @error ENOSYS if not Linux 5.9+ / FreeBSD / OpenBSD
* @error EBADF on OpenBSD if `first` is greater than highest fd
* @error EINVAL if flags are bad or first is greater than last
* @error EMFILE if a weird race condition happens on Linux
* @error EINTR possibly on OpenBSD
* @error ENOMEM on Linux maybe
* @note supported on Linux 5.9+, FreeBSD 8+, and OpenBSD
*/
int closefrom(int first) {
int rc, err;
if (first >= 0) {
err = errno;
if ((rc = sys_close_range(first, -1, 0)) == -1) {
if (errno == ENOSYS) {
errno = err;
rc = sys_closefrom(first);
}
}
} else {
rc = ebadf();
}
STRACE("closefrom(%d) → %d% m", first, rc);
return rc;
}

View file

@ -28,6 +28,8 @@ i32 sys_arch_prctl(i32, i64) hidden;
i32 sys_chdir(const char *) hidden;
i32 sys_chroot(const char *) hidden;
i32 sys_close(i32) hidden;
i32 sys_close_range(u32, u32, u32) hidden;
i32 sys_closefrom(i32) hidden;
i32 sys_dup(i32) hidden;
i32 sys_dup2(i32, i32) hidden;
i32 sys_dup3(i32, i32, i32) hidden;

View file

@ -25,6 +25,7 @@
*
* path dirname() basename()
*
* 0 . .
* . . .
* .. . ..
* / / /
@ -48,7 +49,7 @@ char *basename(char *path) {
for (; i && _isdirsep(path[i]); i--) {
path[i] = 0;
}
for (; i && !_isdirsep(path[i - 1]);) {
while (i && !_isdirsep(path[i - 1])) {
i--;
}
return path + i;

View file

@ -24,12 +24,18 @@
/**
* Joins paths, e.g.
*
* 0 + 0 0
* "" + "" ""
* "a" + 0 "a"
* "a" + "" "a/"
* 0 + "b" "b"
* "" + "b" "b"
* "." + "b" "./b"
* "b" + "." "b/."
* "a" + "b" "a/b"
* "a/" + "b" "a/b"
* "a" + "b/" "a/b/"
* "a" + "/b" "/b"
* "." + "b" "b"
* "" + "b" "b"
*
* @return joined path, which may be `buf`, `path`, or `other`, or null
* if (1) `buf` didn't have enough space, or (2) both `path` and
@ -39,14 +45,11 @@ char *_joinpaths(char *buf, size_t size, const char *path, const char *other) {
size_t pathlen, otherlen;
if (!other) return path;
if (!path) return other;
otherlen = strlen(other);
if (!otherlen) {
return (/*unconst*/ char *)path;
}
pathlen = strlen(path);
if (!pathlen || (READ16LE(path) == READ16LE(".")) || *other == '/') {
if (!pathlen || *other == '/') {
return (/*unconst*/ char *)other;
}
otherlen = strlen(other);
if (path[pathlen - 1] == '/') {
if (pathlen + otherlen + 1 <= size) {
memmove(buf, path, pathlen);

View file

@ -84,6 +84,7 @@ static const uint16_t kPledgeLinuxStdio[] = {
__NR_linux_clock_getres, //
__NR_linux_clock_gettime, //
__NR_linux_clock_nanosleep, //
__NR_linux_close_range, //
__NR_linux_close, //
__NR_linux_write, //
__NR_linux_writev, //

View file

@ -296,6 +296,9 @@ static int sys_unveil_linux(const char *path, const char *permissions) {
* possible to use opendir() and go fishing for paths which weren't
* previously known.
*
* 5. Always specify at least one path. OpenBSD has unclear semantics
* when `pledge(0,0)` is used without any previous calls.
*
* This system call is supported natively on OpenBSD and polyfilled on
* Linux using the Landlock LSM[1].
*
@ -321,6 +324,7 @@ static int sys_unveil_linux(const char *path, const char *permissions) {
* @raise EINVAL if one argument is set and the other is not
* @raise EINVAL if an invalid character in `permissions` was found
* @raise EPERM if unveil() is called after locking
* @note on Linux this function requires Linux Kernel 5.13+
* @see [1] https://docs.kernel.org/userspace-api/landlock.html
*/
int unveil(const char *path, const char *permissions) {

View file

@ -1,2 +0,0 @@
.include "o/libc/sysv/macros.internal.inc"
.scall close_range,0xfffffffffffff1b4,globl

View file

@ -1,2 +0,0 @@
.include "o/libc/sysv/macros.internal.inc"
.scall closefrom,0xfff11f1fdfffffff,globl

View file

@ -0,0 +1,2 @@
.include "o/libc/sysv/macros.internal.inc"
.scall sys_close_range,0xffffff23fffff1b4,globl,hidden

View file

@ -0,0 +1,2 @@
.include "o/libc/sysv/macros.internal.inc"
.scall sys_closefrom,0xfff11f1fdfffffff,globl,hidden

View file

@ -586,6 +586,12 @@ syscon ss MINSIGSTKSZ 2048 32768 2048 12288 8192 2048 # overlayed
syscon ss SS_ONSTACK 1 1 1 1 1 1 # unix consensus
syscon ss SS_DISABLE 2 4 4 4 4 2 # bsd consensus
# close_range() values
#
# group name GNU/Systemd XNU's Not UNIX! FreeBSD OpenBSD NetBSD The New Technology Commentary
syscon close CLOSE_RANGE_UNSHARE 2 -1 -1 -1 -1 -1 #
syscon close CLOSE_RANGE_CLOEXEC 4 -1 -1 -1 -1 -1 #
# clock_{gettime,settime} timers
#
# group name GNU/Systemd XNU's Not UNIX! FreeBSD OpenBSD NetBSD The New Technology Commentary

16
libc/sysv/consts/close.h Normal file
View file

@ -0,0 +1,16 @@
#ifndef COSMOPOLITAN_LIBC_SYSV_CONSTS_CLOSE_H_
#define COSMOPOLITAN_LIBC_SYSV_CONSTS_CLOSE_H_
#include "libc/runtime/symbolic.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
extern const long CLOSE_RANGE_UNSHARE;
extern const long CLOSE_RANGE_CLOEXEC;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#define CLOSE_RANGE_UNSHARE SYMBOLIC(CLOSE_RANGE_UNSHARE)
#define CLOSE_RANGE_CLOEXEC SYMBOLIC(CLOSE_RANGE_CLOEXEC)
#endif /* COSMOPOLITAN_LIBC_SYSV_CONSTS_CLOSE_H_ */

View file

@ -383,7 +383,7 @@ scall fsmount 0xfffffffffffff1b0 globl
scall fspick 0xfffffffffffff1b1 globl
scall pidfd_open 0xfffffffffffff1b2 globl
scall clone3 0xfffffffffffff1b3 globl
scall close_range 0xfffffffffffff1b4 globl
scall sys_close_range 0xffffff23fffff1b4 globl hidden # linux 5.9
scall sys_openat2 0xfffffffffffff1b5 globl hidden # Linux 5.6
scall pidfd_getfd 0xfffffffffffff1b6 globl
scall sys_faccessat2 0xfffffffffffff1b7 globl hidden
@ -463,7 +463,7 @@ scall chflagsat 0xfff06b21cfffffff globl
scall profil 0x02c02c02cfffffff globl
scall fhstatfs 0xfff04122efffffff globl
scall utrace 0x1320d114ffffffff globl
scall closefrom 0xfff11f1fdfffffff globl
scall sys_closefrom 0xfff11f1fdfffffff globl hidden
#───────────────────────────XNU──────────────────────────────
scall __pthread_markcancel 0xfffffffff214cfff globl
scall __pthread_kill 0xfffffffff2148fff globl

View file

@ -0,0 +1,61 @@
/*-*- 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/dce.h"
#include "libc/errno.h"
#include "libc/runtime/runtime.h"
#include "libc/sysv/consts/f.h"
#include "libc/testlib/testlib.h"
void SetUp(void) {
if (closefrom(3) == -1) {
if (IsOpenbsd()) {
ASSERT_EQ(EBADF, errno);
} else {
ASSERT_EQ(ENOSYS, errno);
exit(0);
}
}
}
TEST(closefrom, test) {
ASSERT_SYS(0, 3, dup(2));
ASSERT_SYS(0, 4, dup(2));
ASSERT_SYS(0, 5, dup(2));
ASSERT_SYS(0, 6, dup(2));
EXPECT_SYS(0, 0, closefrom(3));
ASSERT_SYS(0, 0, fcntl(2, F_GETFD));
ASSERT_SYS(EBADF, -1, fcntl(3, F_GETFD));
ASSERT_SYS(EBADF, -1, fcntl(4, F_GETFD));
ASSERT_SYS(EBADF, -1, fcntl(5, F_GETFD));
ASSERT_SYS(EBADF, -1, fcntl(6, F_GETFD));
}
TEST(close_range, test) {
ASSERT_SYS(0, 3, dup(2));
ASSERT_SYS(0, 4, dup(2));
ASSERT_SYS(0, 5, dup(2));
ASSERT_SYS(0, 6, dup(2));
EXPECT_SYS(0, 0, close_range(3, -1, 0));
ASSERT_SYS(0, 0, fcntl(2, F_GETFD));
ASSERT_SYS(EBADF, -1, fcntl(3, F_GETFD));
ASSERT_SYS(EBADF, -1, fcntl(4, F_GETFD));
ASSERT_SYS(EBADF, -1, fcntl(5, F_GETFD));
ASSERT_SYS(EBADF, -1, fcntl(6, F_GETFD));
}

View file

@ -16,23 +16,37 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/mem/mem.h"
#include "libc/str/path.h"
#include "libc/testlib/ezbench.h"
#include "libc/testlib/testlib.h"
#include "libc/x/x.h"
char b[PATH_MAX];
TEST(xjoinpaths, test) {
char b[PATH_MAX];
EXPECT_EQ(NULL, _joinpaths(b, sizeof(b), 0, 0));
EXPECT_STREQ("x", _joinpaths(b, sizeof(b), "x", 0));
EXPECT_STREQ("x", _joinpaths(b, sizeof(b), 0, "x"));
EXPECT_STREQ("", _joinpaths(b, sizeof(b), "", ""));
EXPECT_STREQ("", _joinpaths(b, sizeof(b), "", 0));
EXPECT_STREQ("", _joinpaths(b, sizeof(b), 0, ""));
EXPECT_STREQ("", _joinpaths(b, sizeof(b), "", ""));
EXPECT_STREQ("b", _joinpaths(b, sizeof(b), "", "b"));
EXPECT_STREQ("a/", _joinpaths(b, sizeof(b), "a", ""));
EXPECT_STREQ("a/b", _joinpaths(b, sizeof(b), "a", "b"));
EXPECT_STREQ("a/b", _joinpaths(b, sizeof(b), "a/", "b"));
EXPECT_STREQ("a/b/", _joinpaths(b, sizeof(b), "a", "b/"));
EXPECT_STREQ("/b", _joinpaths(b, sizeof(b), "a", "/b"));
EXPECT_STREQ("b", _joinpaths(b, sizeof(b), ".", "b"));
EXPECT_STREQ("./b", _joinpaths(b, sizeof(b), ".", "b"));
EXPECT_STREQ("b/.", _joinpaths(b, sizeof(b), "b", "."));
EXPECT_EQ(NULL, _joinpaths(b, 3, "a", "b/"));
EXPECT_EQ(NULL, _joinpaths(b, 4, "a", "b/"));
EXPECT_STREQ("a/b", _joinpaths(b, 4, "a/", "b"));
EXPECT_STREQ("a/b/", _joinpaths(b, 5, "a", "b/"));
}
BENCH(joinpaths, bench) {
EZBENCH2("_joinpaths", donothing, _joinpaths(b, sizeof(b), "care", "bear"));
EZBENCH2("xjoinpaths", donothing, free(xjoinpaths("care", "bear")));
}

View file

@ -182,6 +182,18 @@ TEST(unveil, dirfdHacking_doesntWork) {
EXITS(0);
}
TEST(unveil, mostRestrictivePolicy) {
if (IsOpenbsd()) return; // openbsd behaves oddly; see docs
SPAWN();
ASSERT_SYS(0, 0, mkdir("jail", 0755));
ASSERT_SYS(0, 0, mkdir("garden", 0755));
ASSERT_SYS(0, 0, touch("garden/secret.txt", 0644));
ASSERT_SYS(0, 0, unveil(0, 0));
ASSERT_SYS(EACCES_OR_ENOENT, -1, open("jail", O_RDONLY | O_DIRECTORY));
ASSERT_SYS(EACCES_OR_ENOENT, -1, open("garden/secret.txt", O_RDONLY));
EXITS(0);
}
TEST(unveil, overlappingDirectories_inconsistentBehavior) {
SPAWN();
ASSERT_SYS(0, 0, makedirs("f1/f2", 0755));

View file

@ -0,0 +1,28 @@
/*-*- 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/runtime/runtime.h"
#include "libc/testlib/testlib.h"
_Thread_local int x;
_Thread_local int y = 40;
int z = 2;
TEST(tls, test) {
EXPECT_EQ(42, x + y + z);
}

View file

@ -0,0 +1,77 @@
-- 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.
assert("/usr/lib" == path.dirname("/usr/lib/foo.bar"))
assert("/usr" == path.dirname("/usr/lib"))
assert("usr" == path.dirname("usr/lib"))
assert("/" == path.dirname("/usr//"))
assert("/" == path.dirname("/usr/"))
assert("." == path.dirname("usr"))
assert("/" == path.dirname("/"))
assert("." == path.dirname("."))
assert("." == path.dirname(".."))
assert("." == path.dirname(""))
assert("." == path.dirname(nil))
--------------------------------------------------------------------------------
assert("lib" == path.basename("/usr/lib"))
assert("lib" == path.basename("usr/lib"))
assert("usr" == path.basename("/usr/"))
assert("usr" == path.basename("/usr"))
assert("/" == path.basename("/"))
assert("." == path.basename("."))
assert(".." == path.basename(".."))
assert("." == path.basename(""))
assert("foo" == path.basename("foo/"))
assert("foo" == path.basename("foo//"))
assert("/" == path.basename("///"))
assert("0" == path.basename(0))
assert("." == path.basename(nil))
--------------------------------------------------------------------------------
assert("a" == path.join("a"))
assert("a/b/c" == path.join("a", "b", "c"))
assert("/c" == path.join("a", "b", "/c"))
assert("/b/c" == path.join("a", "/b", "c"))
assert("/c" == path.join("a", "/b", "/c"))
assert("./." == path.join(".", "."))
assert("./.." == path.join(".", ".."))
assert("../." == path.join("..", "."))
assert("./a" == path.join(".", "a"))
assert("c//c" == path.join("c//", "c"))
assert("a/." == path.join("a", "."))
assert("a/b" == path.join("a", "b"))
assert("a/b" == path.join("a/", "b"))
assert("a/b/" == path.join("a", "b/"))
assert("/b" == path.join("a", "/b"))
assert("./b" == path.join(".", "b"))
assert("a" * 3000 .."/123/".. "b" * 3000 .. "/123" == path.join("a" * 3000, 123, "b" * 3000, 123))
assert("1/2/3" == path.join(1, 2, 3))
assert(nil == path.join(nil))
assert(nil == path.join(nil, nil))
assert("a" == path.join("a", nil))
assert("a" == path.join(nil, "a"))
assert("a/a" == path.join("a", nil, "a"))
assert("a/a" == path.join("a/", nil, "a"))
assert("" == path.join(""))
assert("" == path.join("", ""))
assert("b" == path.join("", "b"))
assert("a" == path.join("", "a"))
assert("a/" == path.join("a", ""))
assert("a/b" == path.join("a", "", "b"))
assert("a/b" == path.join("a", "", "", "b"))
assert("a/b/" == path.join("a", "", "", "b", ""))

View file

@ -2428,7 +2428,9 @@ char *linenoiseRaw(const char *prompt, int infd, int outfd) {
rc = -1;
}
if (rc != -1) {
linenoiseWriteStr(outfd, "\n");
if (buf) {
linenoiseWriteStr(outfd, "\n");
}
return buf;
} else {
return 0;

View file

@ -35,6 +35,7 @@
#include "libc/calls/ucontext.h"
#include "libc/dns/dns.h"
#include "libc/errno.h"
#include "libc/fmt/conv.h"
#include "libc/fmt/fmt.h"
#include "libc/fmt/magnumstrs.internal.h"
#include "libc/log/log.h"
@ -49,6 +50,7 @@
#include "libc/sock/syslog.h"
#include "libc/stdio/append.internal.h"
#include "libc/stdio/stdio.h"
#include "libc/str/path.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/af.h"
#include "libc/sysv/consts/at.h"
@ -89,6 +91,7 @@
#include "third_party/lua/lgc.h"
#include "third_party/lua/lua.h"
#include "third_party/lua/luaconf.h"
#include "third_party/lua/lunix.h"
#include "tool/net/luacheck.h"
/**
@ -96,12 +99,6 @@
* @support Linux, Mac, Windows, FreeBSD, NetBSD, OpenBSD
*/
struct UnixErrno {
int errno_;
int winerr;
const char *call;
};
static lua_State *GL;
static void *LuaRealloc(lua_State *L, void *p, size_t n) {
@ -177,7 +174,7 @@ static dontinline int ReturnString(lua_State *L, const char *x) {
return 1;
}
static int SysretErrno(lua_State *L, const char *call, int olderr) {
int LuaUnixSysretErrno(lua_State *L, const char *call, int olderr) {
struct UnixErrno *ep;
int i, unixerr, winerr;
unixerr = errno;
@ -195,7 +192,7 @@ static int SysretErrno(lua_State *L, const char *call, int olderr) {
return 2;
}
int SysretBool(lua_State *L, const char *call, int olderr, int rc) {
static int SysretBool(lua_State *L, const char *call, int olderr, int rc) {
if (!IsTiny() && (rc != 0 && rc != -1)) {
WARNF("syscall supposed to return 0 / -1 but got %d", rc);
}
@ -203,7 +200,7 @@ int SysretBool(lua_State *L, const char *call, int olderr, int rc) {
lua_pushboolean(L, true);
return 1;
} else {
return SysretErrno(L, call, olderr);
return LuaUnixSysretErrno(L, call, olderr);
}
}
@ -216,7 +213,7 @@ static int SysretInteger(lua_State *L, const char *call, int olderr,
lua_pushinteger(L, rc);
return 1;
} else {
return SysretErrno(L, call, olderr);
return LuaUnixSysretErrno(L, call, olderr);
}
}
@ -502,7 +499,7 @@ static int LuaUnixReadlink(lua_State *L) {
}
}
free(buf);
return SysretErrno(L, "readlink", olderr);
return LuaUnixSysretErrno(L, "readlink", olderr);
}
// unix.getcwd()
@ -516,7 +513,7 @@ static int LuaUnixGetcwd(lua_State *L) {
free(path);
return 1;
} else {
return SysretErrno(L, "getcwd", olderr);
return LuaUnixSysretErrno(L, "getcwd", olderr);
}
}
@ -558,14 +555,14 @@ static int LuaUnixExecve(lua_State *L) {
freeme2 = envp;
} else {
FreeStringList(argv);
return SysretErrno(L, "execve", olderr);
return LuaUnixSysretErrno(L, "execve", olderr);
}
} else {
envp = environ;
freeme2 = 0;
}
} else {
return SysretErrno(L, "execve", olderr);
return LuaUnixSysretErrno(L, "execve", olderr);
}
} else {
ezargs[0] = prog;
@ -578,7 +575,7 @@ static int LuaUnixExecve(lua_State *L) {
execve(prog, argv, envp);
FreeStringList(freeme1);
FreeStringList(freeme2);
return SysretErrno(L, "execve", olderr);
return LuaUnixSysretErrno(L, "execve", olderr);
}
// unix.commandv(prog:str)
@ -597,7 +594,7 @@ static int LuaUnixCommandv(lua_State *L) {
return 1;
} else {
free(pathbuf);
return SysretErrno(L, "commandv", olderr);
return LuaUnixSysretErrno(L, "commandv", olderr);
}
}
@ -615,7 +612,7 @@ static int LuaUnixRealpath(lua_State *L) {
free(resolved);
return 1;
} else {
return SysretErrno(L, "realpath", olderr);
return LuaUnixSysretErrno(L, "realpath", olderr);
}
}
@ -656,7 +653,7 @@ static int LuaUnixGetrlimit(lua_State *L) {
lua_pushinteger(L, FixLimit(rlim.rlim_max));
return 2;
} else {
return SysretErrno(L, "getrlimit", olderr);
return LuaUnixSysretErrno(L, "getrlimit", olderr);
}
}
@ -670,7 +667,7 @@ static int LuaUnixGetrusage(lua_State *L) {
LuaPushRusage(L, &ru);
return 1;
} else {
return SysretErrno(L, "getrusage", olderr);
return LuaUnixSysretErrno(L, "getrusage", olderr);
}
}
@ -704,7 +701,7 @@ static int LuaUnixWait(lua_State *L) {
LuaPushRusage(L, &ru);
return 3;
} else {
return SysretErrno(L, "wait", olderr);
return LuaUnixSysretErrno(L, "wait", olderr);
}
}
@ -745,7 +742,7 @@ static int LuaUnixPipe(lua_State *L) {
lua_pushinteger(L, pipefd[1]);
return 2;
} else {
return SysretErrno(L, "pipe", olderr);
return LuaUnixSysretErrno(L, "pipe", olderr);
}
}
@ -898,7 +895,7 @@ static int LuaUnixGettime(lua_State *L) {
lua_pushinteger(L, ts.tv_nsec);
return 2;
} else {
return SysretErrno(L, "clock_gettime", olderr);
return LuaUnixSysretErrno(L, "clock_gettime", olderr);
}
}
@ -915,7 +912,7 @@ static int LuaUnixNanosleep(lua_State *L) {
lua_pushinteger(L, rem.tv_nsec);
return 2;
} else {
return SysretErrno(L, "nanosleep", olderr);
return LuaUnixSysretErrno(L, "nanosleep", olderr);
}
}
@ -1016,7 +1013,7 @@ static int LuaUnixRead(lua_State *L) {
return 1;
} else {
free(buf);
return SysretErrno(L, "read", olderr);
return LuaUnixSysretErrno(L, "read", olderr);
}
}
@ -1052,7 +1049,7 @@ static int LuaUnixStat(lua_State *L) {
LuaPushStat(L, &st);
return 1;
} else {
return SysretErrno(L, "stat", olderr);
return LuaUnixSysretErrno(L, "stat", olderr);
}
}
@ -1066,7 +1063,7 @@ static int LuaUnixFstat(lua_State *L) {
LuaPushStat(L, &st);
return 1;
} else {
return SysretErrno(L, "fstat", olderr);
return LuaUnixSysretErrno(L, "fstat", olderr);
}
}
@ -1140,7 +1137,7 @@ static int LuaUnixSetsockopt(lua_State *L) {
if (level == -1 || optname == 0) {
NoProtocolOption:
enoprotoopt();
return SysretErrno(L, "setsockopt", olderr);
return LuaUnixSysretErrno(L, "setsockopt", olderr);
}
if (IsSockoptBool(level, optname)) {
// unix.setsockopt(fd:int, level:int, optname:int, value:bool)
@ -1191,7 +1188,7 @@ static int LuaUnixGetsockopt(lua_State *L) {
if (level == -1 || optname == 0) {
NoProtocolOption:
enoprotoopt();
return SysretErrno(L, "setsockopt", olderr);
return LuaUnixSysretErrno(L, "setsockopt", olderr);
}
if (IsSockoptBool(level, optname) || IsSockoptInt(level, optname)) {
// unix.getsockopt(fd:int, level:int, optname:int)
@ -1240,7 +1237,7 @@ static int LuaUnixGetsockopt(lua_State *L) {
} else {
goto NoProtocolOption;
}
return SysretErrno(L, "getsockopt", olderr);
return LuaUnixSysretErrno(L, "getsockopt", olderr);
}
// unix.socket([family:int[, type:int[, protocol:int]]])
@ -1266,7 +1263,7 @@ static int LuaUnixSocketpair(lua_State *L) {
lua_pushinteger(L, sv[1]);
return 2;
} else {
return SysretErrno(L, "socketpair", olderr);
return LuaUnixSysretErrno(L, "socketpair", olderr);
}
}
@ -1315,7 +1312,7 @@ static int LuaUnixGetname(lua_State *L, const char *name,
if (!func(luaL_checkinteger(L, 1), &ss, &addrsize)) {
return PushSockaddr(L, &ss);
} else {
return SysretErrno(L, name, olderr);
return LuaUnixSysretErrno(L, name, olderr);
}
}
@ -1348,14 +1345,14 @@ static int LuaUnixSiocgifconf(lua_State *L) {
data = LuaAllocOrDie(L, (n = 4096));
if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP)) == -1) {
free(data);
return SysretErrno(L, "siocgifconf", olderr);
return LuaUnixSysretErrno(L, "siocgifconf", olderr);
}
conf.ifc_buf = data;
conf.ifc_len = n;
if (ioctl(fd, SIOCGIFCONF, &conf) == -1) {
close(fd);
free(data);
return SysretErrno(L, "siocgifconf", olderr);
return LuaUnixSysretErrno(L, "siocgifconf", olderr);
}
lua_newtable(L);
i = 0;
@ -1415,7 +1412,7 @@ static int LuaUnixGethostname(lua_State *L) {
enomem();
}
}
return SysretErrno(L, "gethostname", olderr);
return LuaUnixSysretErrno(L, "gethostname", olderr);
}
// unix.accept(serverfd:int[, flags:int])
@ -1435,7 +1432,7 @@ static int LuaUnixAccept(lua_State *L) {
lua_pushinteger(L, clientfd);
return 1 + PushSockaddr(L, &ss);
} else {
return SysretErrno(L, "accept", olderr);
return LuaUnixSysretErrno(L, "accept", olderr);
}
}
@ -1458,7 +1455,7 @@ static int LuaUnixPoll(lua_State *L) {
++nfds;
} else {
free(fds);
return SysretErrno(L, "poll", olderr);
return LuaUnixSysretErrno(L, "poll", olderr);
}
} else {
// ignore non-integer key
@ -1478,7 +1475,7 @@ static int LuaUnixPoll(lua_State *L) {
return 1;
} else {
free(fds);
return SysretErrno(L, "poll", olderr);
return LuaUnixSysretErrno(L, "poll", olderr);
}
}
@ -1508,7 +1505,7 @@ static int LuaUnixRecvfrom(lua_State *L) {
return pushed;
} else {
free(buf);
return SysretErrno(L, "recvfrom", olderr);
return LuaUnixSysretErrno(L, "recvfrom", olderr);
}
}
@ -1534,7 +1531,7 @@ static int LuaUnixRecv(lua_State *L) {
return 1;
} else {
free(buf);
return SysretErrno(L, "recv", olderr);
return LuaUnixSysretErrno(L, "recv", olderr);
}
}
@ -1591,7 +1588,7 @@ static int LuaUnixSigprocmask(lua_State *L) {
LuaPushSigset(L, oldmask);
return 1;
} else {
return SysretErrno(L, "sigprocmask", olderr);
return LuaUnixSysretErrno(L, "sigprocmask", olderr);
}
}
@ -1692,7 +1689,7 @@ static int LuaUnixSigaction(lua_State *L) {
LuaPushSigset(L, oldsa.sa_mask);
return 3;
} else {
return SysretErrno(L, "sigaction", olderr);
return LuaUnixSysretErrno(L, "sigaction", olderr);
}
}
@ -1701,7 +1698,7 @@ static int LuaUnixSigaction(lua_State *L) {
static int LuaUnixSigsuspend(lua_State *L) {
int olderr = errno;
sigsuspend(!lua_isnoneornil(L, 1) ? luaL_checkudata(L, 1, "unix.Sigset") : 0);
return SysretErrno(L, "sigsuspend", olderr);
return LuaUnixSysretErrno(L, "sigsuspend", olderr);
}
// unix.setitimer(which[, intervalsec, intns, valuesec, valuens])
@ -1727,7 +1724,7 @@ static int LuaUnixSetitimer(lua_State *L) {
lua_pushinteger(L, oldit.it_value.tv_usec * 1000);
return 4;
} else {
return SysretErrno(L, "setitimer", olderr);
return LuaUnixSysretErrno(L, "setitimer", olderr);
}
}
@ -1785,7 +1782,7 @@ static dontinline int LuaUnixTime(lua_State *L, const char *call,
lua_pushstring(L, tm.tm_zone);
return 11;
} else {
return SysretErrno(L, call, olderr);
return LuaUnixSysretErrno(L, call, olderr);
}
}
@ -1883,7 +1880,7 @@ static int LuaUnixTiocgwinsz(lua_State *L) {
lua_pushinteger(L, ws.ws_col);
return 2;
} else {
return SysretErrno(L, "tiocgwinsz", olderr);
return LuaUnixSysretErrno(L, "tiocgwinsz", olderr);
}
}
@ -2459,7 +2456,7 @@ static int LuaUnixDirFd(lua_State *L) {
lua_pushinteger(L, fd);
return 1;
} else {
return SysretErrno(L, "dirfd", olderr);
return LuaUnixSysretErrno(L, "dirfd", olderr);
}
}
@ -2494,7 +2491,7 @@ static int LuaUnixOpendir(lua_State *L) {
if ((dir = opendir(luaL_checkstring(L, 1)))) {
return ReturnDir(L, dir);
} else {
return SysretErrno(L, "opendir", olderr);
return LuaUnixSysretErrno(L, "opendir", olderr);
}
}
@ -2507,7 +2504,7 @@ static int LuaUnixFdopendir(lua_State *L) {
if ((dir = fdopendir(luaL_checkinteger(L, 1)))) {
return ReturnDir(L, dir);
} else {
return SysretErrno(L, "fdopendir", olderr);
return LuaUnixSysretErrno(L, "fdopendir", olderr);
}
}

View file

@ -4,7 +4,14 @@
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
struct UnixErrno {
int errno_;
int winerr;
const char *call;
};
int LuaUnix(lua_State *);
int LuaUnixSysretErrno(lua_State *, const char *, int);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -214,8 +214,11 @@ int GetPollMaxFds(void) {
}
void NormalizeFileDescriptors(void) {
int i, n, fd;
int e, i, n, fd;
n = GetPollMaxFds();
e = errno;
closefrom(3); // more secure if linux 5.9+
errno = e;
for (i = 0; i < n; ++i) {
pfds[i].fd = i;
pfds[i].events = POLLIN;

View file

@ -1959,6 +1959,96 @@ RE MODULE
and re.Regex:search.
────────────────────────────────────────────────────────────────────────────────
PATH MODULE
The path module may be used to manipulate unix paths.
Note that we use unix paths on Windows. For example, if you have a
path like C:\foo\bar then it should be /c/foo/bar with redbean. It
should also be noted the unix module is more permissive when using
Windows paths, where translation to win32 is very light.
path.dirname(str)
└─→ str
Strips final component of path, e.g.
path │ dirname
───────────────────
. │ .
.. │ .
/ │ /
usr │ .
/usr/ │ /
/usr/lib │ /usr
/usr/lib/ │ /usr
path.basename(path:str)
└─→ str
Returns final component of path, e.g.
path │ basename
─────────────────────
. │ .
.. │ ..
/ │ /
usr │ usr
/usr/ │ usr
/usr/lib │ lib
/usr/lib/ │ lib
path.join(str, ...)
└─→ str
Concatenates path components, e.g.
x │ y │ joined
─────────────────────────────────
/ │ / │ /
/usr │ lib │ /usr/lib
/usr/ │ lib │ /usr/lib
/usr/lib │ /lib │ /lib
You may specify 1+ arguments.
Specifying no arguments will raise an error. If nil arguments are
specified, then they're skipped over. If exclusively nil arguments
are passed, then nil is returned. Empty strings behave similarly to
nil, but unlike nil may coerce a trailing slash.
path.exists(path:str)
└─→ bool
Returns true if path exists.
This function is inclusive of regular files, directories, and
special files. Symbolic links are followed are resolved. On error,
false is returned.
path.isfile(path:str)
└─→ bool
Returns true if path exists and is regular file.
Symbolic links are not followed. On error, false is returned.
path.isdir(path:str)
└─→ bool
Returns true if path exists and is directory.
Symbolic links are not followed. On error, false is returned.
path.islink(path:str)
└─→ bool
Returns true if path exists and is symbolic link.
Symbolic links are not followed. On error, false is returned.
────────────────────────────────────────────────────────────────────────────────
MAXMIND MODULE
@ -2220,12 +2310,36 @@ UNIX MODULE
Closes file descriptor.
This function should never be called twice for the same file
descriptor, regardless of whether or not an error happened. The file
descriptor is always gone after close is called. So it technically
always succeeds, but that doesn't mean an error should be ignored.
For example, on NFS a close failure could indicate data loss.
Closing does not mean that scheduled i/o operations have been
completed. You'd need to use fsync() or fdatasync() beforehand to
ensure that. You shouldn't need to do that normally, because our
close implementation guarantees a consistent view, since on systems
where it isn't guaranteed (like Windows) close will implicitly sync.
File descriptors are automatically closed on exit().
Returns `EBADF` if `fd` wasn't valid.
Returns `EINTR` possibly maybe.
Returns `EIO` if an i/o error occurred.
unix.read(fd:int[, bufsiz:str[, offset:int]])
├─→ data:str
└─→ nil, unix.Errno
Reads from file descriptor.
This function returns empty string on end of file. The exception is
if `bufsiz` is zero, in which case an empty returned string means
the file descriptor works.
unix.write(fd:int, data:str[, offset:int])
├─→ wrotebytes:int
└─→ nil, unix.Errno
@ -3778,14 +3892,66 @@ UNIX MODULE
├─→ true
└─→ nil, unix.Errno
Unveil parts of a restricted filesystem view, e.g.
Restricts filesystem operations, e.g.
unix.unveil(".", "r")
unix.unveil(nil, nil)
unix.unveil(".", "r"); -- current dir + children visible
unix.unveil("/etc", "r"); -- make /etc readable too
unix.unveil(0, 0); -- commit and lock policy
This can be used for sandboxing file system access.
Unveiling restricts a thread's view of the filesystem to a set of
allowed paths with specific privileges.
Unveil support is a work in progress.
Once you start using unveil(), the entire file system is considered
hidden. You then specify, by repeatedly calling unveil(), which paths
should become unhidden. When you're finished, you call `unveil(0,0)`
which commits your policy, after which further use is forbidden, in
the current thread, as well as any threads or processes it spawns.
There are some differences between unveil() on Linux versus OpenBSD.
1. Build your policy and lock it in one go. On OpenBSD, policies take
effect immediately and may evolve as you continue to call unveil()
but only in a more restrictive direction. On Linux, nothing will
happen until you call `unveil(0,0)` which commits and locks.
2. Try not to overlap directory trees. On OpenBSD, if directory trees
overlap, then the most restrictive policy will be used for a given
file. On Linux overlapping may result in a less restrictive policy
and possibly even undefined behavior.
3. OpenBSD and Linux disagree on error codes. On OpenBSD, accessing
paths outside of the allowed set raises ENOENT, and accessing ones
with incorrect permissions raises EACCES. On Linux, both these
cases raise EACCES.
4. Unlike OpenBSD, Linux does nothing to conceal the existence of
paths. Even with an unveil() policy in place, it's still possible
to access the metadata of all files using functions like stat()
and open(O_PATH), provided you know the path. A sandboxed process
can always, for example, determine how many bytes of data are in
/etc/passwd, even if the file isn't readable. But it's still not
possible to use opendir() and go fishing for paths which weren't
previously known.
This system call is supported natively on OpenBSD and polyfilled on
Linux using the Landlock LSM[1].
`path` is the file or directory to unveil
`permissions` is a string consisting of zero or more of the
following characters:
- 'r' makes `path` available for read-only path operations,
corresponding to the pledge promise "rpath".
- `w` makes `path` available for write operations, corresponding
to the pledge promise "wpath".
- `x` makes `path` available for execute operations,
corresponding to the pledge promises "exec" and "execnative".
- `c` allows `path` to be created and removed, corresponding to
the pledge promise "cpath".
unix.gmtime(unixts:int)
├─→ year,mon,mday,hour,min,sec,gmtoffsec,wday,yday,dst:int,zone:str

166
tool/net/lpath.c Normal file
View file

@ -0,0 +1,166 @@
/*-*- 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/struct/stat.h"
#include "libc/errno.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/consts/s.h"
#include "third_party/lua/lauxlib.h"
/**
* @fileoverview redbean unix path manipulation module
*/
// path.basename(str)
// └─→ str
static int LuaPathBasename(lua_State *L) {
size_t i, n;
const char *p;
if ((p = luaL_optlstring(L, 1, 0, &n)) && n) {
while (n > 1 && p[n - 1] == '/') --n;
i = n - 1;
while (i && p[i - 1] != '/') --i;
lua_pushlstring(L, p + i, n - i);
} else {
lua_pushlstring(L, ".", 1);
}
return 1;
}
// path.dirname(str)
// └─→ str
static int LuaPathDirname(lua_State *L) {
size_t n;
const char *p;
if ((p = luaL_optlstring(L, 1, 0, &n)) && n--) {
for (; p[n] == '/'; n--) {
if (!n) goto ReturnSlash;
}
for (; p[n] != '/'; n--) {
if (!n) goto ReturnDot;
}
for (; p[n] == '/'; n--) {
if (!n) goto ReturnSlash;
}
lua_pushlstring(L, p, n + 1);
return 1;
}
ReturnDot:
lua_pushlstring(L, ".", 1);
return 1;
ReturnSlash:
lua_pushlstring(L, "/", 1);
return 1;
}
// path.join(str, ...)
// └─→ str
static int LuaPathJoin(lua_State *L) {
int i, n;
size_t z;
bool gotstr;
const char *c;
luaL_Buffer b;
bool needslash;
if ((n = lua_gettop(L))) {
luaL_buffinit(L, &b);
gotstr = false;
needslash = false;
for (i = 1; i <= n; ++i) {
if (lua_isnoneornil(L, i)) continue;
gotstr = true;
c = luaL_checklstring(L, i, &z);
if (z) {
if (c[0] == '/') {
luaL_buffsub(&b, luaL_bufflen(&b));
} else if (needslash) {
luaL_addchar(&b, '/');
}
luaL_addlstring(&b, c, z);
needslash = c[z - 1] != '/';
} else if (needslash) {
luaL_addchar(&b, '/');
needslash = false;
}
}
if (gotstr) {
luaL_pushresult(&b);
} else {
lua_pushnil(L);
}
return 1;
} else {
luaL_error(L, "missing argument");
unreachable;
}
}
static int CheckPath(lua_State *L, int type, int flags) {
int olderr;
struct stat st;
const char *path;
path = luaL_checkstring(L, 1);
olderr = errno;
if (fstatat(AT_FDCWD, path, &st, flags) != -1) {
lua_pushboolean(L, !type || (st.st_mode & S_IFMT) == type);
} else {
errno = olderr;
lua_pushboolean(L, false);
}
return 1;
}
// path.exists(str)
// └─→ bool
static int LuaPathExists(lua_State *L) {
return CheckPath(L, 0, 0);
}
// path.isfile(str)
// └─→ bool
static int LuaPathIsfile(lua_State *L) {
return CheckPath(L, S_IFREG, AT_SYMLINK_NOFOLLOW);
}
// path.islink(str)
// └─→ bool
static int LuaPathIslink(lua_State *L) {
return CheckPath(L, S_IFLNK, AT_SYMLINK_NOFOLLOW);
}
// path.isdir(str)
// └─→ bool
static int LuaPathIsdir(lua_State *L) {
return CheckPath(L, S_IFDIR, AT_SYMLINK_NOFOLLOW);
}
static const luaL_Reg kLuaPath[] = {
{"basename", LuaPathBasename}, //
{"dirname", LuaPathDirname}, //
{"exists", LuaPathExists}, //
{"isdir", LuaPathIsdir}, //
{"isfile", LuaPathIsfile}, //
{"islink", LuaPathIslink}, //
{"join", LuaPathJoin}, //
{0}, //
};
int LuaPath(lua_State *L) {
luaL_newlib(L, kLuaPath);
return 1;
}

11
tool/net/lpath.h Normal file
View file

@ -0,0 +1,11 @@
#ifndef COSMOPOLITAN_TOOL_NET_LPATH_H_
#define COSMOPOLITAN_TOOL_NET_LPATH_H_
#include "third_party/lua/lauxlib.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
int LuaPath(lua_State *);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_TOOL_NET_LPATH_H_ */

View file

@ -96,6 +96,7 @@ o/$(MODE)/tool/net/redbean.com.dbg: \
$(TOOL_NET_DEPS) \
o/$(MODE)/tool/net/redbean.o \
o/$(MODE)/tool/net/lfuncs.o \
o/$(MODE)/tool/net/lpath.o \
o/$(MODE)/tool/net/lfinger.o \
o/$(MODE)/tool/net/lre.o \
o/$(MODE)/tool/net/ljson.o \
@ -217,6 +218,7 @@ o/$(MODE)/tool/net/redbean-demo.com.dbg: \
$(TOOL_NET_DEPS) \
o/$(MODE)/tool/net/redbean.o \
o/$(MODE)/tool/net/lfuncs.o \
o/$(MODE)/tool/net/lpath.o \
o/$(MODE)/tool/net/lfinger.o \
o/$(MODE)/tool/net/lre.o \
o/$(MODE)/tool/net/ljson.o \
@ -336,6 +338,7 @@ o/$(MODE)/tool/net/redbean-unsecure.com.dbg: \
$(TOOL_NET_DEPS) \
o/$(MODE)/tool/net/redbean-unsecure.o \
o/$(MODE)/tool/net/lfuncs.o \
o/$(MODE)/tool/net/lpath.o \
o/$(MODE)/tool/net/lfinger.o \
o/$(MODE)/tool/net/lre.o \
o/$(MODE)/tool/net/ljson.o \

View file

@ -115,6 +115,7 @@
#include "tool/net/lfinger.h"
#include "tool/net/lfuncs.h"
#include "tool/net/ljson.h"
#include "tool/net/lpath.h"
#include "tool/net/luacheck.h"
#include "tool/net/sandbox.h"
@ -5272,6 +5273,7 @@ static const luaL_Reg kLuaLibs[] = {
{"lsqlite3", luaopen_lsqlite3}, //
{"maxmind", LuaMaxmind}, //
{"finger", LuaFinger}, //
{"path", LuaPath}, //
{"re", LuaRe}, //
{"unix", LuaUnix}, //
};
@ -6323,8 +6325,7 @@ static bool HandleMessageActual(void) {
if (hasonloglatency) LuaOnLogLatency(reqtime, contime);
if (loglatency || LOGGABLE(kLogDebug))
LOGF(kLogDebug, "(stat) %`'.*s latency r: %,ldµs c: %,ldµs",
msg.uri.b - msg.uri.a, inbuf.p + msg.uri.a,
reqtime, contime);
msg.uri.b - msg.uri.a, inbuf.p + msg.uri.a, reqtime, contime);
}
if (!generator) {
return TransmitResponse(p);