configurable trans-membrance hostcall blocklist

This commit is contained in:
root
2026-06-16 01:13:06 +00:00
parent 52cf266a5e
commit f2a3503ac3
60 changed files with 1675 additions and 338 deletions
+6
View File
@@ -8,3 +8,9 @@ gen_noise64
gen_float
gen_int
gen_sha1
sha256
sha256_hex
hmac_sha256
hmac_sha256_hex
random_bytes
crypto_equal
+2
View File
@@ -1,5 +1,7 @@
Socket Functions
http_request
http_request_async
socket_close
socket_connect
socket_read
+24 -1
View File
@@ -4,7 +4,6 @@ basename
dirname
expand_path
file_append
file_append_contents
file_exists
file_get_contents
file_mtime
@@ -16,6 +15,11 @@ path_join
cwd_set
shell_escape
shell_exec
shell_spawn
job_status
job_result
job_await
job_cancel
file_unlink
zip_create
zip_list
@@ -25,3 +29,22 @@ gz_compress
gz_uncompress
server_start_http
server_stop
file_open
file_read
file_pread
file_write
file_pwrite
file_seek
file_tell
file_close
file_stat
dir_list
file_rename
file_copy
file_truncate
dir_remove
file_temp
file_chmod
file_symlink
file_readlink
file_fsync
+60
View File
@@ -0,0 +1,60 @@
:title
Blocked functions (hostcall blocklist)
:content
UCE units reach the operating system only through a fixed set of `uce_host_*`
membrane hostcalls (see the runtime architecture). A server operator can
**disable individual hostcalls** so a deployment exposes only the capabilities it
wants — for example turning off `shell_exec` or `http_request` on a hardened
host. A unit that calls a disabled function fails at request time with the
configurable error page, stating exactly which function was blocked and why.
## Configuration
Set `UCE_HOSTCALL_BLOCKLIST` in `/etc/uce/settings.cfg` to a comma-separated list
of hostcall names. Names may be given bare (`shell_exec`) or fully qualified
(`uce_host_shell_exec`); whitespace is ignored. Empty (the default) blocks
nothing.
```
UCE_HOSTCALL_BLOCKLIST=shell_exec, shell_spawn, http_request, http_request_async, mysql
```
Changes take effect on **restart** (`systemctl restart uce`). There is no hot
reload — the list is parsed once per worker process into a fast lookup, so an
empty list has zero runtime cost and a non-empty list costs only a single check
per hostcall at workspace birth (never per call).
## Behaviour when a blocked function is called
The blocked hostcall resolves to a trap stub instead of its real implementation.
When a unit invokes it, the request fails into the runtime error page with:
- error type `policy_blocked` (so a custom error page template can special-case it),
- a title `function disabled by server policy`,
- a message naming the exact function, e.g. *"this unit called uce_host_shell_exec,
which is disabled on this server by configuration (UCE_HOSTCALL_BLOCKLIST)"*.
The worker is unharmed (it is a clean guest trap, like any other), and only the
offending request fails. A unit cannot catch this — blocking is enforcement, not
a soft signal.
## What can and cannot be blocked
Any `uce_host_*` capability hostcall can be listed — file I/O, `shell_exec` /
`shell_spawn`, `http_request`, `mysql`, sockets, memcache, crypto, the job
registry, etc. (the full set is the membrane list in the runtime architecture
doc and `src/wasm/core_hostcalls.syms`).
A small core set the runtime itself needs is **exempt** and ignored even if
listed, so a deployment cannot be bricked by an over-broad blocklist:
`component_resolve` (used for `component()` / unit rendering).
## Notes
- No recompilation is required (neither the wasm core nor the native binary) —
this is pure runtime configuration; blocked hostcalls still exist as imports,
they simply resolve to a trap.
- Pure-compute library functions that are NOT hostcalls (string ops, `DValue`
methods, hashing helpers like `gen_noise`, etc.) are not OS capabilities and
cannot be blocked this way — only `uce_host_*` membrane calls are gateable.
+7
View File
@@ -0,0 +1,7 @@
# crypto_equal
```cpp
bool crypto_equal(String a, String b)
```
Constant-time byte comparison for secrets such as MACs and tokens.
+3
View File
@@ -0,0 +1,3 @@
DValue dir_list(String path)
Returns a list of `{ name, type, size, mtime }` entries for a policy-gated directory. Names are sorted and exclude `.`/`..`.
+3
View File
@@ -0,0 +1,3 @@
dir_remove
Structural filesystem operation across the wasm host membrane. All path arguments are policy-gated through the same guest file/write roots as other file APIs and return false on denial or OS error.
+1
View File
@@ -11,6 +11,7 @@ file_name : file name of file that should be written to
:content
Opens or creates a file and appends data to it.
The append transparently takes an exclusive file lock for the duration of the append operation. Concurrent writers are serialized, and `file_get_contents()` waits for in-progress writes to finish; callers do not manage locks manually.
## Related Concepts
- PHP: `file_put_contents($file, $data, FILE_APPEND)`
-26
View File
@@ -1,26 +0,0 @@
:sig
bool file_append_contents(String file_name, String content)
:params
file_name : path to open or create
content : bytes to append
return value : `true` when the append succeeds
:see
>sys
file_append
file_put_contents
file_get_contents
:content
Appends one string to a file.
`file_append()` is the variadic convenience wrapper for ordinary page code. Use `file_append_contents()` when you already have one string buffer.
Example:
```uce
bool ok = file_append_contents("/tmp/uce-log.txt", "line\n");
```
The file is created if it does not exist.
+3
View File
@@ -0,0 +1,3 @@
file_chmod
Filesystem nicety added to the wasm host membrane. Path arguments are policy-gated; operations return an empty value or false on denial/error. file_fsync() takes an open file handle and flushes it with fsync().
+5
View File
@@ -0,0 +1,5 @@
file_close
Streaming/handle-based file I/O across the wasm host membrane. Handles are opaque u64 values returned by file_open(); 0 means open failed (including path policy denial or bounded lock timeout). Locks are automatic and lifetime-scoped: read opens take a shared lock, write/append/read-write opens take an exclusive lock, and file_close() releases it. Lock wait is bounded by UCE_FILE_LOCK_TIMEOUT_MS (default 2000ms).
See also: file_get_contents, file_put_contents, file_append.
-12
View File
@@ -1,12 +0,0 @@
:sig
void file_close_locked(int fd)
:params
fd : handle returned by `file_open_locked()`
:see
>sys
>file_open_locked
:content
Releases the flock and closes a locked file handle. Wasm handles are opaque and request-local.
+3
View File
@@ -0,0 +1,3 @@
file_copy
Structural filesystem operation across the wasm host membrane. All path arguments are policy-gated through the same guest file/write roots as other file APIs and return false on denial or OS error.
+3
View File
@@ -0,0 +1,3 @@
file_fsync
Filesystem nicety added to the wasm host membrane. Path arguments are policy-gated; operations return an empty value or false on denial/error. file_fsync() takes an open file handle and flushes it with fsync().
+2 -1
View File
@@ -11,8 +11,9 @@ return value : String containing the file's contents
:content
Reads the file identified by `file_name` and returns it as a `String`.
If the file cannot be read, this function returns an empty string.
The read transparently takes a shared file lock for the duration of the call. If another worker is writing the same file with `file_put_contents()` or `file_append()`, this read waits for that exclusive write lock to finish, so callers do not manage locks manually.
If the file cannot be read, this function returns an empty string.
## Related Concepts
- PHP: `file_get_contents()`
@@ -1,14 +0,0 @@
:sig
String file_get_contents_locked_fd(int fd)
:params
fd : handle returned by `file_open_locked()`
return value : complete file contents, or an empty string on error/empty file
:see
>sys
>file_open_locked
>file_put_contents_locked_fd
:content
Reads the full contents of a locked file handle from the start of the file.
+5
View File
@@ -0,0 +1,5 @@
file_open
Streaming/handle-based file I/O across the wasm host membrane. Handles are opaque u64 values returned by file_open(); 0 means open failed (including path policy denial or bounded lock timeout). Locks are automatic and lifetime-scoped: read opens take a shared lock, write/append/read-write opens take an exclusive lock, and file_close() releases it. Lock wait is bounded by UCE_FILE_LOCK_TIMEOUT_MS (default 2000ms).
See also: file_get_contents, file_put_contents, file_append.
-18
View File
@@ -1,18 +0,0 @@
:sig
int file_open_locked(String file_name, int open_flags, int lock_type = LOCK_SH, int create_mode = 0644, f64 wait_timeout_seconds = 3.0, String purpose = "")
:params
file_name : path to open
open_flags : host open(2) flags
lock_type : `LOCK_SH` or `LOCK_EX`
create_mode : mode used when creating
return value : opaque locked file handle, or -1
:see
>sys
>file_close_locked
>file_get_contents_locked_fd
>file_put_contents_locked_fd
:content
Opens and locks a file on the host. In wasm units the returned integer is an opaque worker-owned handle that is valid only for the current request.
+5
View File
@@ -0,0 +1,5 @@
file_pread
Streaming/handle-based file I/O across the wasm host membrane. Handles are opaque u64 values returned by file_open(); 0 means open failed (including path policy denial or bounded lock timeout). Locks are automatic and lifetime-scoped: read opens take a shared lock, write/append/read-write opens take an exclusive lock, and file_close() releases it. Lock wait is bounded by UCE_FILE_LOCK_TIMEOUT_MS (default 2000ms).
See also: file_get_contents, file_put_contents, file_append.
+1
View File
@@ -12,6 +12,7 @@ return value : true if write was successful
:content
Writes `content` into the file identified by `file_name`, overwriting any pre-existing content.
The write transparently takes an exclusive file lock for the duration of the truncate-and-write operation. Concurrent writers are serialized, and `file_get_contents()` waits for in-progress writes to finish; callers do not manage locks manually.
## Related Concepts
- PHP: `file_put_contents()`
@@ -1,15 +0,0 @@
:sig
bool file_put_contents_locked_fd(int fd, String content)
:params
fd : handle returned by `file_open_locked()`
content : bytes to write
return value : true on complete write
:see
>sys
>file_open_locked
>file_get_contents_locked_fd
:content
Truncates and rewrites the file behind a locked file handle.
+5
View File
@@ -0,0 +1,5 @@
file_pwrite
Streaming/handle-based file I/O across the wasm host membrane. Handles are opaque u64 values returned by file_open(); 0 means open failed (including path policy denial or bounded lock timeout). Locks are automatic and lifetime-scoped: read opens take a shared lock, write/append/read-write opens take an exclusive lock, and file_close() releases it. Lock wait is bounded by UCE_FILE_LOCK_TIMEOUT_MS (default 2000ms).
See also: file_get_contents, file_put_contents, file_append.
+5
View File
@@ -0,0 +1,5 @@
file_read
Streaming/handle-based file I/O across the wasm host membrane. Handles are opaque u64 values returned by file_open(); 0 means open failed (including path policy denial or bounded lock timeout). Locks are automatic and lifetime-scoped: read opens take a shared lock, write/append/read-write opens take an exclusive lock, and file_close() releases it. Lock wait is bounded by UCE_FILE_LOCK_TIMEOUT_MS (default 2000ms).
See also: file_get_contents, file_put_contents, file_append.
+3
View File
@@ -0,0 +1,3 @@
file_readlink
Filesystem nicety added to the wasm host membrane. Path arguments are policy-gated; operations return an empty value or false on denial/error. file_fsync() takes an open file handle and flushes it with fsync().
@@ -1,12 +0,0 @@
:sig
void file_release_process_locks(String reason = "")
:params
reason : diagnostic reason for releasing locks
:see
>sys
>file_open_locked
:content
Releases locked file handles owned by the current process/workspace. Wasm uses this to close all request-local locked file handles.
+3
View File
@@ -0,0 +1,3 @@
file_rename
Structural filesystem operation across the wasm host membrane. All path arguments are policy-gated through the same guest file/write roots as other file APIs and return false on denial or OS error.
+5
View File
@@ -0,0 +1,5 @@
file_seek
Streaming/handle-based file I/O across the wasm host membrane. Handles are opaque u64 values returned by file_open(); 0 means open failed (including path policy denial or bounded lock timeout). Locks are automatic and lifetime-scoped: read opens take a shared lock, write/append/read-write opens take an exclusive lock, and file_close() releases it. Lock wait is bounded by UCE_FILE_LOCK_TIMEOUT_MS (default 2000ms).
See also: file_get_contents, file_put_contents, file_append.
+3
View File
@@ -0,0 +1,3 @@
DValue file_stat(String path)
Returns `{ exists, size, mtime, ctime, mode, is_dir, is_file, is_symlink }` for a policy-gated path. Missing or denied paths return `exists=false`.
+3
View File
@@ -0,0 +1,3 @@
file_symlink
Filesystem nicety added to the wasm host membrane. Path arguments are policy-gated; operations return an empty value or false on denial/error. file_fsync() takes an open file handle and flushes it with fsync().
+5
View File
@@ -0,0 +1,5 @@
file_tell
Streaming/handle-based file I/O across the wasm host membrane. Handles are opaque u64 values returned by file_open(); 0 means open failed (including path policy denial or bounded lock timeout). Locks are automatic and lifetime-scoped: read opens take a shared lock, write/append/read-write opens take an exclusive lock, and file_close() releases it. Lock wait is bounded by UCE_FILE_LOCK_TIMEOUT_MS (default 2000ms).
See also: file_get_contents, file_put_contents, file_append.
+3
View File
@@ -0,0 +1,3 @@
file_temp
Filesystem nicety added to the wasm host membrane. Path arguments are policy-gated; operations return an empty value or false on denial/error. file_fsync() takes an open file handle and flushes it with fsync().
+3
View File
@@ -0,0 +1,3 @@
file_truncate
Structural filesystem operation across the wasm host membrane. All path arguments are policy-gated through the same guest file/write roots as other file APIs and return false on denial or OS error.
+5
View File
@@ -0,0 +1,5 @@
file_write
Streaming/handle-based file I/O across the wasm host membrane. Handles are opaque u64 values returned by file_open(); 0 means open failed (including path policy denial or bounded lock timeout). Locks are automatic and lifetime-scoped: read opens take a shared lock, write/append/read-write opens take an exclusive lock, and file_close() releases it. Lock wait is bounded by UCE_FILE_LOCK_TIMEOUT_MS (default 2000ms).
See also: file_get_contents, file_put_contents, file_append.
+7
View File
@@ -0,0 +1,7 @@
# hmac_sha256
```cpp
String hmac_sha256(String key, String data)
```
Returns the raw 32-byte HMAC-SHA-256 digest for `data` keyed by `key`.
+7
View File
@@ -0,0 +1,7 @@
# hmac_sha256_hex
```cpp
String hmac_sha256_hex(String key, String data)
```
Returns the lowercase hexadecimal HMAC-SHA-256 digest.
+9
View File
@@ -0,0 +1,9 @@
# http_request
```cpp
DValue http_request(DValue req)
```
Performs a bounded outbound HTTP(S) request using the runtime `curl` binary. Request fields: `method`, `url`, `headers`, `body`, `timeout_ms`, `follow_redirects`.
Returns `{ status, headers, body, error }`. `headers` is a name/value map. A missing `curl` binary returns a clear `error` string.
+7
View File
@@ -0,0 +1,7 @@
# http_request_async
```cpp
u64 http_request_async(DValue req)
```
Starts the same bounded curl-backed request as `http_request()` in the file-backed async job registry and returns a job id. Use `job_await()` or `job_result()` to retrieve the HTTP result.
+7
View File
@@ -0,0 +1,7 @@
# job_await
```cpp
DValue job_await(u64 job_id, u64 timeout_ms)
```
Waits up to `timeout_ms` for a job to finish, then returns status/result data. The wait is always bounded and returns with `state=running` if the job is still active.
+7
View File
@@ -0,0 +1,7 @@
# job_cancel
```cpp
bool job_cancel(u64 job_id)
```
Attempts to terminate the background job process group and marks the registry entry as `cancelled`.
+7
View File
@@ -0,0 +1,7 @@
# job_result
```cpp
DValue job_result(u64 job_id)
```
Checks a job result with a small bounded wait. The returned value includes the current status fields and, when complete, a structured `result` value.
+7
View File
@@ -0,0 +1,7 @@
# job_status
```cpp
DValue job_status(u64 job_id)
```
Returns the file-backed async job state, e.g. `{ state, done, kind, pid, job_id }`. States include `pending`, `running`, `done`, `failed`, `cancelled`, and `missing`.
+7
View File
@@ -0,0 +1,7 @@
# random_bytes
```cpp
String random_bytes(u64 n)
```
Returns up to `n` bytes from the host CSPRNG. Requests are capped to a bounded size.
+7
View File
@@ -0,0 +1,7 @@
# sha256
```cpp
String sha256(String data)
```
Returns the raw 32-byte SHA-256 digest for `data`. Use `sha256_hex()` for printable lowercase hex.
+7
View File
@@ -0,0 +1,7 @@
# sha256_hex
```cpp
String sha256_hex(String data)
```
Returns the lowercase hexadecimal SHA-256 digest for `data`.
+9
View File
@@ -0,0 +1,9 @@
# shell_spawn
```cpp
u64 shell_spawn(DValue spec)
```
Starts a bounded background shell job and returns a job id. `spec` fields: `cmd`, optional `stdin`, optional `env` map, optional `timeout_ms`.
Use `job_status()`, `job_await()`, `job_result()`, or `job_cancel()` with the returned id.
+88 -12
View File
@@ -31,9 +31,9 @@ RENDER(Request& context)
check("path_join()", file_name == "/tmp/uce-site-tests-io.txt", file_name);
check("basename() / dirname() / expand_path()", basename(file_name) == "uce-site-tests-io.txt" && dirname(file_name) == "/tmp" && expand_path("child", "/tmp/base") == "/tmp/base/child" && contains(expand_path("../sibling", "/tmp/base"), "sibling"), basename(file_name) + " / " + dirname(file_name) + " / " + expand_path("../sibling", "/tmp/base"));
check("file_put_contents()", write_ok, file_name);
check("file_append() / file_append_contents()", file_append_contents(file_name, "|gamma") && contains(file_get_contents(file_name), "|gamma"), file_get_contents(file_name));
check("file_append() automatic exclusive lock", file_append(file_name, "|gamma") && contains(file_get_contents(file_name), "|gamma"), file_get_contents(file_name));
file_put_contents(file_name, "alpha|beta");
check("file_get_contents()", file_text == "alpha|beta", file_text);
check("file_get_contents() automatic shared lock", file_text == "alpha|beta", file_text);
check("file_mtime()", file_mtime(file_name) > 0, std::to_string((u64)file_mtime(file_name)));
String real_tmp = path_real("/tmp");
@@ -42,17 +42,88 @@ RENDER(Request& context)
String shell_escaped = shell_escape("a'b; echo injected");
String shell_out = shell_exec("printf %s " + shell_escaped);
check("shell_escape() / shell_exec()", shell_out == "a'b; echo injected" && contains(shell_escaped, "'\\''"), shell_escaped + " => " + shell_out);
DValue shell_spec;
shell_spec["cmd"] = "printf out; printf err >&2";
shell_spec["timeout_ms"] = (f64)500;
DValue shell_dv = shell_exec(shell_spec);
check("shell_escape() / shell_exec()", shell_out == "a'b; echo injected" && shell_dv["stdout"].to_string() == "out" && shell_dv["stderr"].to_string() == "err" && shell_dv["exit_code"].to_u64() == 0 && contains(shell_escaped, "'\\''"), shell_escaped + " => " + shell_out + " / " + json_encode(shell_dv));
String lock_file = "/tmp/uce-site-tests-lock.txt";
int lock_fd = file_open_locked(lock_file, 66, LOCK_EX, 0644, 3.0, "site tests");
bool lock_write = file_put_contents_locked_fd(lock_fd, "locked-content");
String lock_read = file_get_contents_locked_fd(lock_fd);
file_close_locked(lock_fd);
check("file_open_locked() / locked fd read-write / close", lock_fd > 0 && lock_write && lock_read == "locked-content" && file_get_contents(lock_file) == "locked-content", "fd=" + std::to_string(lock_fd) + " read=" + lock_read);
int release_fd = file_open_locked(lock_file, 66, LOCK_EX, 0644, 3.0, "release test");
file_release_process_locks("site tests release");
check("file_release_process_locks()", release_fd > 0, "fd=" + std::to_string(release_fd));
DValue spawn_spec;
spawn_spec["cmd"] = "printf spawned";
spawn_spec["timeout_ms"] = (f64)500;
u64 job_id = shell_spawn(spawn_spec);
DValue job_waited = job_await(job_id, 3000);
DValue job_checked = job_status(job_id);
DValue job_res = job_result(job_id);
DValue cancel_spec;
cancel_spec["cmd"] = "sleep 2";
cancel_spec["timeout_ms"] = (f64)5000;
u64 cancel_job = shell_spawn(cancel_spec);
bool cancel_ok = job_cancel(cancel_job);
check("shell_spawn() / async job registry", job_id > 0 && job_waited["done"].to_bool() && job_checked["state"].to_string() == "done" && job_res["result"]["stdout"].to_string() == "spawned" && cancel_ok, "job=" + std::to_string(job_id) + " wait=" + json_encode(job_waited) + " cancel=" + std::to_string(cancel_job));
DValue http_req;
http_req["method"] = "GET";
http_req["url"] = "http://127.0.0.1/tests/security_headers.uce";
http_req["headers"]["Host"] = "uce.openfu.com";
http_req["timeout_ms"] = (f64)500;
DValue http_res = http_request(http_req);
u64 http_job = http_request_async(http_req);
DValue http_async = job_await(http_job, 1500);
check("http_request() / http_request_async()", http_res["status"].to_u64() == 500 && contains(http_res["body"].to_string(), "security header sanitizer test") && http_job > 0 && http_async["done"].to_bool() && http_async["result"]["status"].to_u64() == 500, json_encode(http_res) + " async=" + json_encode(http_async));
String auto_lock_file = "/tmp/uce-site-tests-auto-lock.txt";
bool auto_lock_ok = file_put_contents(auto_lock_file, "one") && file_append(auto_lock_file, "|two") && file_get_contents(auto_lock_file) == "one|two";
check("automatic file locking API shape", auto_lock_ok, file_get_contents(auto_lock_file));
String stream_file = "/tmp/uce-site-tests-stream.txt";
u64 wh = file_open(stream_file, "w");
u64 stream_write_ok = wh ? file_write(wh, "abcdef") : 0;
s64 stream_pos = file_tell(wh);
file_close(wh);
u64 rh = file_open(stream_file, "r");
String stream_read = file_read(rh, 3);
String stream_pread = file_pread(rh, 2, 3);
s64 stream_seek = file_seek(rh, 1, 0);
String stream_after_seek = file_read(rh, 2);
file_close(rh);
u64 rwh = file_open(stream_file, "r+");
u64 stream_pwrite_ok = file_pwrite(rwh, 3, "XYZ");
file_close(rwh);
u64 ah = file_open(stream_file, "a");
u64 stream_append_ok = file_write(ah, "!");
file_close(ah);
check("streaming file handles", wh > 0 && rh > 0 && rwh > 0 && ah > 0 && stream_write_ok == 6 && stream_pos == 6 && stream_read == "abc" && stream_pread == "cde" && stream_seek == 1 && stream_after_seek == "bc" && stream_pwrite_ok == 3 && stream_append_ok == 1 && file_get_contents(stream_file) == "abcXYZ!", stream_read + " / " + stream_pread + " / " + file_get_contents(stream_file));
DValue st = file_stat(stream_file);
DValue dl = dir_list("/tmp");
bool found_stream = false;
dl.each([&](const DValue& item, String key) { if(item.key("name") && item.key("name")->to_string() == "uce-site-tests-stream.txt") found_stream = true; });
check("file_stat() / dir_list()", st["exists"].to_bool() && st["is_file"].to_bool() && st["size"].to_u64() == 7 && found_stream, json_encode(st));
String ops_src = "/tmp/uce-site-tests-ops-src.txt";
String ops_copy = "/tmp/uce-site-tests-ops-copy.txt";
String ops_renamed = "/tmp/uce-site-tests-ops-renamed.txt";
file_put_contents(ops_src, "abcdef");
bool copy_ok = file_copy(ops_src, ops_copy);
bool trunc_ok = file_truncate(ops_copy, 3);
bool rename_ok = file_rename(ops_copy, ops_renamed);
String dir_ops = "/tmp/uce-site-tests-rmdir";
mkdir(dir_ops);
file_put_contents(path_join(dir_ops, "child.txt"), "child");
bool dir_remove_ok = dir_remove(dir_ops, true);
check("file_rename() / file_copy() / file_truncate() / dir_remove()", copy_ok && trunc_ok && rename_ok && file_get_contents(ops_renamed) == "abc" && dir_remove_ok && !file_exists(dir_ops), file_get_contents(ops_renamed));
String tmp_created = file_temp("/tmp/uce-site-tests-temp-");
bool chmod_ok = tmp_created != "" && file_chmod(tmp_created, 0600);
u64 fsync_h = file_open(tmp_created, "a");
bool fsync_ok = fsync_h > 0 && file_write(fsync_h, "durable") == 7 && file_fsync(fsync_h);
file_close(fsync_h);
String link_path = "/tmp/uce-site-tests-link";
file_unlink(link_path);
bool symlink_ok = file_symlink(tmp_created, link_path);
String readlink_value = file_readlink(link_path);
check("file_temp() / file_chmod() / file_symlink() / file_readlink() / file_fsync()", tmp_created != "" && file_exists(tmp_created) && chmod_ok && fsync_ok && symlink_ok && readlink_value == path_real(tmp_created), tmp_created + " -> " + readlink_value);
String start_dir = process_start_directory();
String old_cwd = cwd_get();
@@ -79,6 +150,11 @@ RENDER(Request& context)
String relative = time_format_relative(time() - 120, "recent %deltaS", 1, "medium %deltaM", 3600, "old %deltaH");
check("time_format_local() / time_format_relative() / time_parse()", parsed_epoch == 5 && utc_year == "1970" && local_year != "" && contains(relative, "medium"), "parsed=" + std::to_string(parsed_epoch) + " utc=" + utc_year + " local=" + local_year + " rel=" + relative);
String crypto_random = random_bytes(16);
String crypto_b64 = base64_encode("hello");
String crypto_b64_dec = base64_decode(crypto_b64);
check("sha256() / hmac_sha256() / base64 / random_bytes() / crypto_equal()", sha256_hex("abc") == "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" && hmac_sha256_hex("key", "The quick brown fox jumps over the lazy dog") == "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8" && crypto_b64 == "aGVsbG8=" && crypto_b64_dec == "hello" && crypto_random.length() == 16 && crypto_equal("same", "same") && !crypto_equal("same", "diff"), crypto_b64 + " / " + sha256_hex("abc"));
StringList escaped_keys = memcache_escape_keys({"a b", "line\nkey"});
check("memcache_escape_key() / memcache_escape_keys()", memcache_escape_key("a b") == "a_b" && escaped_keys.size() == 2 && !contains(escaped_keys[1], "\n"), join(escaped_keys, ","));
check("signal_name() / runtime_safe_key() / backtrace helpers", signal_name(SIGSEGV) == "SIGSEGV" && runtime_safe_key(" task one ") == gen_sha1("task one") && runtime_safe_key(" ") == "" && capture_backtrace_string() == "" && backtrace_frames_string(0, 0) == "", signal_name(SIGSEGV) + " / " + runtime_safe_key(" task one "));