Files
phantomptrandClaude Opus 5.5 064392d6a8 console files: permissions, new file and text editing
Info gets a Permissions editor (owner/group/other x read/write/run, octal,
"apply to everything inside" for folders; exFAT/FAT drives say permissions
are not kept). /api/fs/chmod takes an optional mode (<= 0777) for the item
alone (FS_CHMOD) or recursively (CHMOD_R job); without it, Fix permissions
is unchanged.

New file (toolbar + sheet) makes an empty file and opens it in the new text
editor; Edit / Edit as text opens any file up to 1 MiB that is valid UTF-8
without NUL. Saves go through POST /api/fs/save-text: the web server can now
hand a request to a streamed route after the token check (body read by the
route with an idle timeout, not the 5 s header deadline); the save is
refused over 1 MiB or with a NUL, checks the mtime/size (and mtime_ns, now in
/api/fs/stat) the editor read before writing and again before the rename,
and writes a same-folder temp file that keeps the old mode and is renamed
onto the file after a same-device check (webui_files.c, host selftest;
cross_device.h audit updated). CRLF files stay CRLF; a changed file asks
Save anyway / Discard; ↑/↓ stay inside the text box until its first/last
line. Phone width: tools wrap, rows tighten, dialogs fit (no sideways
scroll). Strings in all 21 locales.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RxCGb8Asp1A1AQYiv1kbgS
2026-10-18 08:28:00 -07:00

91 lines
4.9 KiB
C

/* Bounded HTTP server for the console web UI, running inside the helper.
*
* One accept thread and WEBUI_WORKERS workers. Accepted sockets go through a
* bounded queue (depth 32; a connection that does not fit is closed at once).
* Every connection carries a deadline of accept time + 5 s: reads and writes
* stop there, and a worker that dequeues an already-expired connection closes
* it without reading. A slow, silent or hostile client therefore holds one
* worker for at most 5 s and can never stall the accept thread. */
#ifndef PS5UPLOAD_WEBUI_SERVER_H
#define PS5UPLOAD_WEBUI_SERVER_H
#include <netinet/in.h>
#include <stddef.h>
#include <stdint.h>
#include "webui_http.h"
/* Writes a complete response to fd (use webui_send / webui_send_json).
* Runs on a worker after the Host/Origin/token checks passed. Unknown paths
* answer 404 and wrong methods 405. The return value is ignored. */
typedef int (*webui_route_fn)(const webui_req_t *r, const uint8_t *body, size_t body_len, int fd);
/* A streamed request (an upload, a text save over 64 KiB): its body may be up to
* WEBUI_STREAM_BODY_MAX and is NOT read by the server. After the Host/Origin/token checks passed,
* the handler gets the body bytes that arrived with the headers (`pre`, `pre_len`) and reads the
* remaining content_length - pre_len itself, with its own idle timeout (the 5 s connection deadline
* covers the request line and headers only). It must write a complete response. */
typedef int (*webui_stream_fn)(const webui_req_t *r, const uint8_t *pre, size_t pre_len, int fd);
#define WEBUI_STREAM_BODY_MAX (1ull << 42) /* 4 TiB: larger than any file a console drive holds */
typedef struct {
uint16_t port;
int lan; /* 0: bind 127.0.0.1; 1: bind 0.0.0.0 */
webui_route_fn route;
/* Optional (NULL: nothing is streamed). 1 = this request (by method + path) is a streamed one. */
int (*stream_for)(const webui_req_t *r);
webui_stream_fn stream;
} webui_cfg_t;
/* New token, bind, start the threads. Returns 0, or -1 when already running or
* the threads cannot start. A bind failure is NOT an error: it is logged and
* retried every 5 s by the accept thread (a helper takeover means the old
* instance may still hold the port for a moment). */
int webui_start(const webui_cfg_t *cfg);
/* Close the listener and the queued connections, wake the workers and join
* them within 2 s. A worker still inside route() after that is detached and
* finishes on its own; it keeps the server state it needs alive. */
void webui_stop(void);
/* Rebind the listener for a LAN setting change. Safe to call from a route
* handler (it never waits on the workers). The token is kept. Returns 0 when
* the new listener is bound, -1 when it is not (yet): the accept thread keeps
* retrying every 5 s. */
int webui_restart(int lan);
/* The listener heals itself (the accept thread, no other thread): every 5 s it checks for a resume
* from rest mode (a gap of 10 s or more on either clock) and, with LAN access on, for a change in
* the console's IPv4 addresses (DHCP, another network: the Host allowlist is refreshed); either one
* rebinds the listener (the token is kept). A listener whose poll or accept keeps failing for 5 s
* is rebound too. */
/* The console's LAN address a phone should open (a private one first, never loopback), from the
* running server's list; 1 and `out` set when LAN access is on and there is one, else 0 and "". */
int webui_lan_addr(char out[INET_ADDRSTRLEN]);
/* The watch's decisions, pure (tests): */
/* Did the console sleep between two checks? (monotonic and wall-clock ms at each) */
int webui_watch_resumed(uint64_t mono_prev, uint64_t mono_now, int64_t real_prev, int64_t real_now);
/* Has a listener failing since `err_since` (0 = not failing) failed long enough to rebind? */
int webui_watch_broken(uint64_t err_since, uint64_t now);
/* Do two address lists differ (as sets)? */
int webui_addrs_differ(const char (*a)[INET_ADDRSTRLEN], size_t na, const char (*b)[INET_ADDRSTRLEN], size_t nb);
/* The address to show for LAN access: private ranges first, then others, link-local last; never
* 127.x or 0.x. 1 and `out` set, else 0 and "". */
int webui_pick_lan_addr(const char (*addrs)[INET_ADDRSTRLEN], size_t n, char out[INET_ADDRSTRLEN]);
/* The token for this start ("" when the server is not running). */
const char *webui_token(void);
/* The token copied under the server's lock (webui_token() reads it unsynchronized): use this one
* from any thread that may race a start, stop or restart. */
void webui_token_copy(char out[65]);
/* Write a complete response (head + body) within the connection deadline. */
void webui_send(int fd, int status, const char *ctype, const void *body, size_t len);
void webui_send_json(int fd, int status, const char *json);
/* webui_send with extra header lines (each a complete "Name: value\r\n"), e.g. a CSP. */
void webui_send_ex(int fd, int status, const char *ctype, const void *body, size_t len, const char *extra_headers);
#endif