2026-07-28 14:35:14 +08:00
|
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
|
|
/*
|
|
|
|
|
|
* utsm_exec_daemon — Linux guest side exec daemon for Deshab Linux compat layer.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Runs as the first userspace process (PID 2, after init) in the Linux guest.
|
|
|
|
|
|
* Communicates with UTSM via /dev/utsm ioctl interface:
|
|
|
|
|
|
*
|
|
|
|
|
|
* 1. Send EXEC_READY, then ioctl(PARK) → HLT → guest parks
|
|
|
|
|
|
* (this is the first HLT; linux_launch() returns to UTSM)
|
|
|
|
|
|
* 2. On vmresume: ioctl(RECV_MSG) → EXEC_REQUEST
|
|
|
|
|
|
* 3. fork + exec the requested program, capture stdout/stderr via pipe
|
|
|
|
|
|
* 4. Stream output back via ioctl(SEND_MSG, EXEC_STDOUT/STDERR)
|
|
|
|
|
|
* 5. Send EXEC_EXIT with waitpid exit code
|
|
|
|
|
|
* 6. Loop: ioctl(PARK) → wait for next request
|
|
|
|
|
|
*
|
|
|
|
|
|
* Build: static musl/glibc binary, stripped, copied into initramfs /bin/.
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
|
#include <errno.h>
|
2026-07-31 13:53:49 +08:00
|
|
|
|
#include <dirent.h>
|
2026-07-28 14:35:14 +08:00
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
|
|
#include <sys/wait.h>
|
|
|
|
|
|
#include <sys/types.h>
|
2026-07-31 13:53:49 +08:00
|
|
|
|
#include <sys/stat.h>
|
2026-08-09 22:14:01 +08:00
|
|
|
|
#include <signal.h>
|
2026-10-01 23:13:42 +08:00
|
|
|
|
#include <sched.h>
|
|
|
|
|
|
#include <poll.h>
|
2026-07-28 14:35:14 +08:00
|
|
|
|
|
|
|
|
|
|
/* Shared IPC protocol — must match UTSM side */
|
|
|
|
|
|
#include "../../../utsm-ipc/ipc_proto.h"
|
|
|
|
|
|
|
|
|
|
|
|
/* Ioctl definitions — must match utsm_hcall.c driver */
|
|
|
|
|
|
#define UTSM_IOCTL_RECV_MSG _IOWR('U', 1, struct utsm_ioctl_msg)
|
|
|
|
|
|
#define UTSM_IOCTL_SEND_MSG _IOWR('U', 2, struct utsm_ioctl_msg)
|
|
|
|
|
|
#define UTSM_IOCTL_PARK _IO('U', 3)
|
|
|
|
|
|
#define UTSM_IOCTL_GET_READY _IOR('U', 4, int)
|
2026-07-31 13:53:49 +08:00
|
|
|
|
#define UTSM_IOCTL_WRITE_POOL _IOW('U', 5, struct utsm_ioctl_pool)
|
|
|
|
|
|
#define UTSM_IOCTL_READ_POOL _IOWR('U', 6, struct utsm_ioctl_pool)
|
2026-07-28 14:35:14 +08:00
|
|
|
|
|
|
|
|
|
|
struct utsm_ioctl_msg {
|
|
|
|
|
|
unsigned int type;
|
|
|
|
|
|
unsigned int data_len;
|
|
|
|
|
|
unsigned int reserved;
|
|
|
|
|
|
unsigned int buf_size;
|
|
|
|
|
|
unsigned char data[UTSM_IPC_MSG_DATA_SIZE];
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-31 13:53:49 +08:00
|
|
|
|
/* Pool transfer descriptor — must match utsm_hcall.c driver */
|
|
|
|
|
|
struct utsm_ioctl_pool {
|
|
|
|
|
|
unsigned int offset;
|
|
|
|
|
|
unsigned int len;
|
|
|
|
|
|
unsigned long long user_buf;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-28 14:35:14 +08:00
|
|
|
|
static int g_utsm_fd = -1;
|
2026-10-01 23:13:42 +08:00
|
|
|
|
static volatile sig_atomic_t g_async_live;
|
2026-07-28 14:35:14 +08:00
|
|
|
|
|
2026-08-09 22:14:01 +08:00
|
|
|
|
/* pool_write 定义在文件传输段(下文),exec 同步路径复用同一通道 */
|
|
|
|
|
|
static int pool_write(unsigned int offset, const void *buf, unsigned int len);
|
|
|
|
|
|
static int send_msg(unsigned int type, const void *data, unsigned int len);
|
|
|
|
|
|
static void err_write(const char *s, unsigned int len);
|
|
|
|
|
|
|
|
|
|
|
|
/* ===== exec 超时看门狗(P3 补完) =====
|
|
|
|
|
|
* park-and-resume 模型下 UTSM 在 guest 运行期间完全不执行,宿主侧无法
|
|
|
|
|
|
* 看门狗。子进程超时必须 guest 内兜底:alarm + SIGALRM → SIGKILL,
|
|
|
|
|
|
* 保证 daemon 在 UTSM_EXEC_TIMEOUT_SEC 内一定 park,整系统不冻结。 */
|
|
|
|
|
|
static volatile sig_atomic_t g_exec_timed_out = 0;
|
|
|
|
|
|
static volatile sig_atomic_t g_exec_child = -1;
|
|
|
|
|
|
|
|
|
|
|
|
static void on_exec_alarm(int sig)
|
|
|
|
|
|
{
|
|
|
|
|
|
(void)sig;
|
|
|
|
|
|
g_exec_timed_out = 1;
|
|
|
|
|
|
if (g_exec_child > 0)
|
|
|
|
|
|
kill((pid_t)g_exec_child, SIGKILL);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* EXEC_EXIT 必须送达:ring 满(-EAGAIN)时短暂重试。新协议下 stdout
|
|
|
|
|
|
* 走 payload_pool,ring 内通常只有这一条消息,满的概率极低。 */
|
|
|
|
|
|
static void send_exit_msg(const struct ipc_exec_exit *ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int t = 0; t < 200; t++) {
|
|
|
|
|
|
if (send_msg(UTSM_MSG_EXEC_EXIT, ex, sizeof(*ex)) == 0)
|
|
|
|
|
|
return;
|
|
|
|
|
|
usleep(1000);
|
|
|
|
|
|
}
|
|
|
|
|
|
err_write("utsm_exec_daemon: EXEC_EXIT send failed (ring full)\n", 52);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 14:35:14 +08:00
|
|
|
|
/* Send a typed message to UTSM via the linux_to_utsm ring */
|
|
|
|
|
|
static int send_msg(unsigned int type, const void *data, unsigned int len)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct utsm_ioctl_msg umsg;
|
|
|
|
|
|
memset(&umsg, 0, sizeof(umsg));
|
|
|
|
|
|
umsg.type = type;
|
|
|
|
|
|
umsg.data_len = len;
|
|
|
|
|
|
umsg.buf_size = UTSM_IPC_MSG_DATA_SIZE;
|
|
|
|
|
|
if (data && len > 0) {
|
|
|
|
|
|
if (len > UTSM_IPC_MSG_DATA_SIZE)
|
|
|
|
|
|
len = UTSM_IPC_MSG_DATA_SIZE;
|
|
|
|
|
|
memcpy(umsg.data, data, len);
|
|
|
|
|
|
umsg.data_len = len;
|
|
|
|
|
|
}
|
|
|
|
|
|
return ioctl(g_utsm_fd, UTSM_IOCTL_SEND_MSG, &umsg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Receive a message from UTSM (non-blocking). Returns 0 on success, -EAGAIN if empty. */
|
|
|
|
|
|
static int recv_msg(struct utsm_ioctl_msg *umsg)
|
|
|
|
|
|
{
|
|
|
|
|
|
memset(umsg, 0, sizeof(*umsg));
|
|
|
|
|
|
umsg->buf_size = UTSM_IPC_MSG_DATA_SIZE;
|
|
|
|
|
|
return ioctl(g_utsm_fd, UTSM_IOCTL_RECV_MSG, umsg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Park: HLT → VM-Exit → UTSM parks guest. Returns on vmresume. */
|
|
|
|
|
|
static int park(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
return ioctl(g_utsm_fd, UTSM_IOCTL_PARK);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-31 13:53:49 +08:00
|
|
|
|
/* Best-effort write to the child's stderr (exec failure path);
|
|
|
|
|
|
* the result is intentionally ignored. */
|
|
|
|
|
|
static void err_write(const char *s, unsigned int len)
|
|
|
|
|
|
{
|
|
|
|
|
|
ssize_t n = write(STDERR_FILENO, s, len);
|
|
|
|
|
|
(void)n;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-01 23:13:42 +08:00
|
|
|
|
static void kmsg_write(const char *s)
|
|
|
|
|
|
{
|
|
|
|
|
|
int fd;
|
|
|
|
|
|
if (!s) return;
|
|
|
|
|
|
fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
|
|
|
|
|
|
if (fd < 0) return;
|
|
|
|
|
|
(void)write(fd, s, strlen(s));
|
|
|
|
|
|
close(fd);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int path_needs_deshab_libs(const char *path)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!path || !path[0])
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
if (strstr(path, "deshab-kde-session"))
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
if (strstr(path, "/bin/sh") || strstr(path, "/bin/bash") ||
|
|
|
|
|
|
strstr(path, "/usr/bin/bash") || strstr(path, "/usr/bin/env"))
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Nested TSC is often marked unstable, so usleep() returns immediately
|
|
|
|
|
|
* and async children never run on the only vCPU. A yield loop also
|
|
|
|
|
|
* starves D-state I/O: PID 1 stays runnable and idle never runs.
|
|
|
|
|
|
* poll(POLLIN, 10ms) on /dev/utsm sleeps interruptibly so Plasma/session
|
|
|
|
|
|
* and virtio completions can run. Driver poll does not wait-queue, so
|
|
|
|
|
|
* a new EXEC_REQUEST is seen at the next timeout. */
|
|
|
|
|
|
static void donate_cpu(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct pollfd pfd;
|
|
|
|
|
|
|
|
|
|
|
|
memset(&pfd, 0, sizeof(pfd));
|
|
|
|
|
|
pfd.fd = g_utsm_fd;
|
|
|
|
|
|
pfd.events = POLLIN;
|
|
|
|
|
|
if (poll(&pfd, 1, 10) < 0) {
|
|
|
|
|
|
unsigned i;
|
|
|
|
|
|
for (i = 0; i < 64u; i++)
|
|
|
|
|
|
sched_yield();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 14:35:14 +08:00
|
|
|
|
/* ===== Exec request handler =====
|
|
|
|
|
|
*
|
|
|
|
|
|
* Fork a child to exec the requested program. Parent reads stdout/stderr
|
|
|
|
|
|
* from a pipe and streams chunks back to UTSM via EXEC_STDOUT/EXEC_STDERR
|
|
|
|
|
|
* messages. Finally sends EXEC_EXIT with the child's exit status.
|
|
|
|
|
|
*
|
|
|
|
|
|
* argv parsing: argv_blob contains NUL-terminated argv[0..argc-1]
|
|
|
|
|
|
* concatenated. We split it into a char* array for execv. */
|
|
|
|
|
|
static void handle_exec_request(const struct ipc_exec_request *req)
|
|
|
|
|
|
{
|
|
|
|
|
|
/* Parse argv_blob into argv[] array */
|
|
|
|
|
|
char *argv[UTSM_EXEC_MAX_ARGS + 1];
|
|
|
|
|
|
int argc = (int)req->argc;
|
|
|
|
|
|
if (argc > UTSM_EXEC_MAX_ARGS) argc = UTSM_EXEC_MAX_ARGS;
|
|
|
|
|
|
if (argc < 1) {
|
2026-08-09 22:14:01 +08:00
|
|
|
|
struct ipc_exec_exit ex = { .exit_code = 1, .flags = 0,
|
|
|
|
|
|
.stdout_len = 0, .stdout_total = 0 };
|
|
|
|
|
|
send_exit_msg(&ex);
|
2026-07-28 14:35:14 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const char *blob = req->argv_blob;
|
|
|
|
|
|
int blob_consumed = 0;
|
|
|
|
|
|
for (int i = 0; i < argc; i++) {
|
|
|
|
|
|
if (blob_consumed >= UTSM_EXEC_ARGV_MAX) {
|
|
|
|
|
|
argv[i] = (char *)"";
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
argv[i] = (char *)(blob + blob_consumed);
|
|
|
|
|
|
int slen = strnlen(blob + blob_consumed, UTSM_EXEC_ARGV_MAX - blob_consumed);
|
|
|
|
|
|
blob_consumed += slen + 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
argv[argc] = NULL;
|
|
|
|
|
|
|
2026-08-09 22:14:01 +08:00
|
|
|
|
/* VSCode Phase 5: ASYNC launch path.
|
|
|
|
|
|
* The caller (desktop IDE attach / app icon) wants to start a long-running
|
|
|
|
|
|
* GUI program (X server, VSCode) without blocking the UTSM side for its
|
|
|
|
|
|
* whole lifetime. We fork, detach the child into its own session with
|
|
|
|
|
|
* stdio routed to /dev/null, reply EXEC_EXIT(0) immediately (0 = spawned
|
|
|
|
|
|
* ok), and never waitpid — SIGCHLD is SIG_IGN so the kernel reaps it. */
|
|
|
|
|
|
if (req->flags & UTSM_EXEC_FLAG_ASYNC) {
|
|
|
|
|
|
pid_t pid = fork();
|
|
|
|
|
|
if (pid < 0) {
|
|
|
|
|
|
struct ipc_exec_exit ex = { .exit_code = 127, .flags = 0,
|
|
|
|
|
|
.stdout_len = 0, .stdout_total = 0 };
|
|
|
|
|
|
send_exit_msg(&ex);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (pid == 0) {
|
2026-10-01 23:13:42 +08:00
|
|
|
|
char line[192];
|
2026-08-09 22:14:01 +08:00
|
|
|
|
/* Child: new session, no controlling tty, stdio → /dev/null */
|
|
|
|
|
|
setsid();
|
|
|
|
|
|
int nullfd = open("/dev/null", O_RDWR);
|
|
|
|
|
|
if (nullfd >= 0) {
|
|
|
|
|
|
dup2(nullfd, STDIN_FILENO);
|
|
|
|
|
|
dup2(nullfd, STDOUT_FILENO);
|
|
|
|
|
|
dup2(nullfd, STDERR_FILENO);
|
|
|
|
|
|
if (nullfd > STDERR_FILENO) close(nullfd);
|
|
|
|
|
|
}
|
2026-10-01 23:13:42 +08:00
|
|
|
|
/* DISPLAY 指向 virtio-gpu X server。不要把 LD_LIBRARY_PATH
|
|
|
|
|
|
* 套到 bash/session:/usr/lib/deshab 会劫持 glibc。 */
|
|
|
|
|
|
if (path_needs_deshab_libs(req->path))
|
|
|
|
|
|
setenv("LD_LIBRARY_PATH", "/usr/lib/deshab", 1);
|
2026-08-09 22:14:01 +08:00
|
|
|
|
setenv("DISPLAY", ":0", 1);
|
2026-10-01 23:13:42 +08:00
|
|
|
|
snprintf(line, sizeof(line),
|
|
|
|
|
|
"[deshab-kde] async exec %s\n", req->path);
|
|
|
|
|
|
kmsg_write(line);
|
2026-08-09 22:14:01 +08:00
|
|
|
|
execv(req->path, argv);
|
2026-10-01 23:13:42 +08:00
|
|
|
|
snprintf(line, sizeof(line),
|
|
|
|
|
|
"[deshab-kde] execv failed errno=%d path=%s\n",
|
|
|
|
|
|
errno, req->path);
|
|
|
|
|
|
kmsg_write(line);
|
2026-08-09 22:14:01 +08:00
|
|
|
|
_exit(127); /* execv 仅失败时返回 */
|
|
|
|
|
|
}
|
|
|
|
|
|
/* Parent: report successful spawn immediately */
|
2026-10-01 23:13:42 +08:00
|
|
|
|
g_async_live = 1;
|
|
|
|
|
|
{
|
|
|
|
|
|
char line[128];
|
|
|
|
|
|
snprintf(line, sizeof(line),
|
|
|
|
|
|
"[deshab-kde] async spawn pid=%d\n", (int)pid);
|
|
|
|
|
|
kmsg_write(line);
|
|
|
|
|
|
}
|
2026-08-09 22:14:01 +08:00
|
|
|
|
struct ipc_exec_exit ex = { .exit_code = 0, .flags = 0,
|
|
|
|
|
|
.stdout_len = 0, .stdout_total = 0 };
|
|
|
|
|
|
send_exit_msg(&ex);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 14:35:14 +08:00
|
|
|
|
/* Create a pipe for child stdout+stderr */
|
|
|
|
|
|
int pipefd[2];
|
|
|
|
|
|
if (pipe(pipefd) != 0) {
|
2026-08-09 22:14:01 +08:00
|
|
|
|
struct ipc_exec_exit ex = { .exit_code = 126, .flags = 0,
|
|
|
|
|
|
.stdout_len = 0, .stdout_total = 0 };
|
|
|
|
|
|
send_exit_msg(&ex);
|
2026-07-28 14:35:14 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pid_t pid = fork();
|
|
|
|
|
|
if (pid < 0) {
|
|
|
|
|
|
/* fork failed */
|
|
|
|
|
|
close(pipefd[0]);
|
|
|
|
|
|
close(pipefd[1]);
|
2026-08-09 22:14:01 +08:00
|
|
|
|
struct ipc_exec_exit ex = { .exit_code = 127, .flags = 0,
|
|
|
|
|
|
.stdout_len = 0, .stdout_total = 0 };
|
|
|
|
|
|
send_exit_msg(&ex);
|
2026-07-28 14:35:14 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (pid == 0) {
|
|
|
|
|
|
/* Child: redirect stdout + stderr to pipe write end, then exec */
|
|
|
|
|
|
close(pipefd[0]);
|
|
|
|
|
|
dup2(pipefd[1], STDOUT_FILENO);
|
|
|
|
|
|
dup2(pipefd[1], STDERR_FILENO);
|
|
|
|
|
|
close(pipefd[1]);
|
|
|
|
|
|
|
2026-10-01 23:13:42 +08:00
|
|
|
|
if (path_needs_deshab_libs(req->path))
|
|
|
|
|
|
setenv("LD_LIBRARY_PATH", "/usr/lib/deshab", 1);
|
2026-08-09 22:14:01 +08:00
|
|
|
|
|
2026-07-28 14:35:14 +08:00
|
|
|
|
execv(req->path, argv);
|
|
|
|
|
|
/* execv only returns on failure */
|
|
|
|
|
|
const char *err1 = "utsm_exec_daemon: exec failed: ";
|
2026-07-31 13:53:49 +08:00
|
|
|
|
err_write(err1, (unsigned int)strlen(err1));
|
|
|
|
|
|
err_write(req->path, (unsigned int)strnlen(req->path, UTSM_EXEC_PATH_MAX));
|
2026-07-28 14:35:14 +08:00
|
|
|
|
const char *err2 = ": ";
|
2026-07-31 13:53:49 +08:00
|
|
|
|
err_write(err2, (unsigned int)strlen(err2));
|
2026-07-28 14:35:14 +08:00
|
|
|
|
const char *emsg = strerror(errno);
|
2026-07-31 13:53:49 +08:00
|
|
|
|
err_write(emsg, (unsigned int)strlen(emsg));
|
|
|
|
|
|
err_write("\n", 1);
|
2026-07-28 14:35:14 +08:00
|
|
|
|
_exit(127);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-09 22:14:01 +08:00
|
|
|
|
/* Parent: read child output from pipe, stage into payload_pool.
|
|
|
|
|
|
* P3 大输出改造:旧路径逐条 EXEC_STDOUT 入 ring(64 槽 × 240B ≈ 15KB,
|
|
|
|
|
|
* 满则 -EAGAIN 静默丢弃);现直写 payload_pool(~1MB),EXEC_EXIT
|
|
|
|
|
|
* 携带 stdout_len/stdout_total,UTSM 侧按新协议从 pool 拷出。 */
|
2026-07-28 14:35:14 +08:00
|
|
|
|
close(pipefd[1]);
|
|
|
|
|
|
|
2026-08-09 22:14:01 +08:00
|
|
|
|
/* 安装超时看门狗:UTSM_EXEC_TIMEOUT_SEC 内子进程不退 → SIGKILL。
|
|
|
|
|
|
* 不设 SA_RESTART:SIGALRM 中断 read(EINTR),随后子进程死亡 pipe EOF。 */
|
|
|
|
|
|
g_exec_timed_out = 0;
|
|
|
|
|
|
g_exec_child = pid;
|
|
|
|
|
|
{
|
|
|
|
|
|
struct sigaction sa;
|
|
|
|
|
|
memset(&sa, 0, sizeof(sa));
|
|
|
|
|
|
sa.sa_handler = on_exec_alarm;
|
|
|
|
|
|
sigemptyset(&sa.sa_mask);
|
|
|
|
|
|
sigaction(SIGALRM, &sa, NULL);
|
|
|
|
|
|
alarm(UTSM_EXEC_TIMEOUT_SEC);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
char buf[4096];
|
|
|
|
|
|
unsigned int pool_off = 0;
|
|
|
|
|
|
unsigned int total = 0;
|
|
|
|
|
|
int truncated = 0;
|
2026-07-28 14:35:14 +08:00
|
|
|
|
ssize_t n;
|
2026-08-09 22:14:01 +08:00
|
|
|
|
for (;;) {
|
|
|
|
|
|
n = read(pipefd[0], buf, sizeof(buf));
|
|
|
|
|
|
if (n > 0) {
|
|
|
|
|
|
total += (unsigned int)n;
|
|
|
|
|
|
if (!truncated) {
|
|
|
|
|
|
unsigned int room = (pool_off < UTSM_IPC_PAYLOAD_SIZE)
|
|
|
|
|
|
? (UTSM_IPC_PAYLOAD_SIZE - pool_off) : 0;
|
|
|
|
|
|
unsigned int chunk = ((unsigned int)n < room) ? (unsigned int)n : room;
|
|
|
|
|
|
if (chunk > 0) {
|
|
|
|
|
|
if (pool_write(pool_off, buf, chunk) != 0) {
|
|
|
|
|
|
/* pool 写入失败:后续输出全部丢弃,记截断 */
|
|
|
|
|
|
truncated = 1;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
pool_off += chunk;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (chunk < (unsigned int)n)
|
|
|
|
|
|
truncated = 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (n < 0 && errno == EINTR)
|
|
|
|
|
|
continue; /* SIGALRM 打断;子进程已被 SIGKILL,下轮 EOF */
|
|
|
|
|
|
break; /* EOF 或真实错误 */
|
2026-07-28 14:35:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
close(pipefd[0]);
|
|
|
|
|
|
|
|
|
|
|
|
/* Wait for child and send exit code */
|
|
|
|
|
|
int status = 0;
|
|
|
|
|
|
waitpid(pid, &status, 0);
|
2026-08-09 22:14:01 +08:00
|
|
|
|
alarm(0);
|
|
|
|
|
|
g_exec_child = -1;
|
2026-07-28 14:35:14 +08:00
|
|
|
|
|
|
|
|
|
|
struct ipc_exec_exit ex;
|
2026-08-09 22:14:01 +08:00
|
|
|
|
ex.flags = UTSM_EXEC_EXIT_F_POOL;
|
|
|
|
|
|
ex.stdout_len = pool_off;
|
|
|
|
|
|
ex.stdout_total = total;
|
|
|
|
|
|
if (truncated)
|
|
|
|
|
|
ex.flags |= UTSM_EXEC_EXIT_F_TRUNC;
|
|
|
|
|
|
if (g_exec_timed_out) {
|
|
|
|
|
|
ex.flags |= UTSM_EXEC_EXIT_F_TIMEOUT;
|
|
|
|
|
|
ex.exit_code = 124; /* GNU timeout 惯例 */
|
|
|
|
|
|
} else if (WIFEXITED(status)) {
|
2026-07-28 14:35:14 +08:00
|
|
|
|
ex.exit_code = (unsigned int)WEXITSTATUS(status);
|
|
|
|
|
|
} else if (WIFSIGNALED(status)) {
|
|
|
|
|
|
ex.exit_code = 128u + (unsigned int)WTERMSIG(status);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
ex.exit_code = 1;
|
|
|
|
|
|
}
|
2026-08-09 22:14:01 +08:00
|
|
|
|
send_exit_msg(&ex);
|
2026-07-28 14:35:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-31 13:53:49 +08:00
|
|
|
|
/* ===== File transfer handlers (Phase 3) =====
|
|
|
|
|
|
*
|
|
|
|
|
|
* UTSM sends FILE_LIST_REQUEST / FILE_READ_REQUEST with an ipc_file_request
|
|
|
|
|
|
* payload. Bulk result data is staged into the shared payload_pool via
|
|
|
|
|
|
* UTSM_IOCTL_WRITE_POOL, then a FILE_RESPONSE message (ipc_file_response)
|
|
|
|
|
|
* carries status + data_len + total_size back to UTSM.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Directory listing format: one entry per line, directories suffixed '/'. */
|
|
|
|
|
|
|
|
|
|
|
|
/* Staging buffer for file reads / listings (capped by pool capacity ~1MB) */
|
|
|
|
|
|
#define FT_STAGE_SIZE (768 * 1024)
|
|
|
|
|
|
static unsigned char g_ft_stage[FT_STAGE_SIZE];
|
|
|
|
|
|
|
|
|
|
|
|
static int pool_write(unsigned int offset, const void *buf, unsigned int len)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct utsm_ioctl_pool p;
|
|
|
|
|
|
p.offset = offset;
|
|
|
|
|
|
p.len = len;
|
|
|
|
|
|
p.user_buf = (unsigned long long)(uintptr_t)buf;
|
|
|
|
|
|
return ioctl(g_utsm_fd, UTSM_IOCTL_WRITE_POOL, &p);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int pool_read(unsigned int offset, void *buf, unsigned int len)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct utsm_ioctl_pool p;
|
|
|
|
|
|
p.offset = offset;
|
|
|
|
|
|
p.len = len;
|
|
|
|
|
|
p.user_buf = (unsigned long long)(uintptr_t)buf;
|
|
|
|
|
|
return ioctl(g_utsm_fd, UTSM_IOCTL_READ_POOL, &p);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static unsigned int errno_to_file_status(int e)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (e) {
|
|
|
|
|
|
case ENOENT: return UTSM_FILE_ERR_NOENT;
|
|
|
|
|
|
case ENOTDIR: return UTSM_FILE_ERR_NOTDIR;
|
|
|
|
|
|
case EISDIR: return UTSM_FILE_ERR_ISDIR;
|
|
|
|
|
|
case EACCES: return UTSM_FILE_ERR_PERM;
|
|
|
|
|
|
case ENOSPC:
|
|
|
|
|
|
case EFBIG: return UTSM_FILE_ERR_NOSPC;
|
|
|
|
|
|
default: return UTSM_FILE_ERR_IO;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void send_file_response(unsigned int status, unsigned int data_len,
|
|
|
|
|
|
unsigned long long total_size)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct ipc_file_response resp;
|
|
|
|
|
|
memset(&resp, 0, sizeof(resp));
|
|
|
|
|
|
resp.status = status;
|
|
|
|
|
|
resp.data_len = data_len;
|
|
|
|
|
|
resp.total_size = total_size;
|
|
|
|
|
|
send_msg(UTSM_MSG_FILE_RESPONSE, &resp, sizeof(resp));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* FILE_LIST_REQUEST: list directory entries as "name\n" ("name/\n" for dirs) */
|
|
|
|
|
|
static void handle_file_list(const struct ipc_file_request *req)
|
|
|
|
|
|
{
|
|
|
|
|
|
char path[UTSM_FILE_PATH_MAX + 1];
|
|
|
|
|
|
memcpy(path, req->path, UTSM_FILE_PATH_MAX);
|
|
|
|
|
|
path[UTSM_FILE_PATH_MAX] = '\0';
|
|
|
|
|
|
|
|
|
|
|
|
if (req->pool_capacity == 0 || req->pool_offset >= UTSM_IPC_PAYLOAD_SIZE) {
|
|
|
|
|
|
send_file_response(UTSM_FILE_ERR_INVAL, 0, 0);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DIR *d = opendir(path);
|
|
|
|
|
|
if (!d) {
|
|
|
|
|
|
send_file_response(errno_to_file_status(errno), 0, 0);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
unsigned int cap = req->pool_capacity;
|
|
|
|
|
|
if (cap > FT_STAGE_SIZE) cap = FT_STAGE_SIZE;
|
|
|
|
|
|
|
|
|
|
|
|
unsigned int used = 0;
|
|
|
|
|
|
struct dirent *de;
|
|
|
|
|
|
while ((de = readdir(d)) != NULL) {
|
|
|
|
|
|
if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
/* Determine directory flag: d_type if available, else stat */
|
|
|
|
|
|
int is_dir = 0;
|
|
|
|
|
|
if (de->d_type == DT_DIR) {
|
|
|
|
|
|
is_dir = 1;
|
|
|
|
|
|
} else if (de->d_type == DT_UNKNOWN || de->d_type == DT_LNK) {
|
|
|
|
|
|
struct stat st;
|
|
|
|
|
|
char full[512];
|
|
|
|
|
|
snprintf(full, sizeof(full), "%s/%s", path, de->d_name);
|
|
|
|
|
|
if (stat(full, &st) == 0 && S_ISDIR(st.st_mode))
|
|
|
|
|
|
is_dir = 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
int nl = snprintf((char *)g_ft_stage + used, cap - used,
|
|
|
|
|
|
"%s%s\n", de->d_name, is_dir ? "/" : "");
|
|
|
|
|
|
if (nl < 0) break;
|
|
|
|
|
|
if ((unsigned int)nl >= cap - used) {
|
|
|
|
|
|
/* 缓冲已满:本条未完整写入,截断结束 */
|
|
|
|
|
|
used = cap;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
used += (unsigned int)nl;
|
|
|
|
|
|
}
|
|
|
|
|
|
closedir(d);
|
|
|
|
|
|
|
|
|
|
|
|
if (used > 0 && pool_write(req->pool_offset, g_ft_stage, used) != 0) {
|
|
|
|
|
|
send_file_response(UTSM_FILE_ERR_IO, 0, 0);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
send_file_response(UTSM_FILE_OK, used, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* FILE_READ_REQUEST: read up to pool_capacity bytes at file_offset */
|
|
|
|
|
|
static void handle_file_read(const struct ipc_file_request *req)
|
|
|
|
|
|
{
|
|
|
|
|
|
char path[UTSM_FILE_PATH_MAX + 1];
|
|
|
|
|
|
memcpy(path, req->path, UTSM_FILE_PATH_MAX);
|
|
|
|
|
|
path[UTSM_FILE_PATH_MAX] = '\0';
|
|
|
|
|
|
|
|
|
|
|
|
if (req->pool_capacity == 0 || req->pool_offset >= UTSM_IPC_PAYLOAD_SIZE) {
|
|
|
|
|
|
send_file_response(UTSM_FILE_ERR_INVAL, 0, 0);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct stat st;
|
|
|
|
|
|
if (stat(path, &st) != 0) {
|
|
|
|
|
|
send_file_response(errno_to_file_status(errno), 0, 0);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (S_ISDIR(st.st_mode)) {
|
|
|
|
|
|
send_file_response(UTSM_FILE_ERR_ISDIR, 0, (unsigned long long)st.st_size);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int fd = open(path, O_RDONLY);
|
|
|
|
|
|
if (fd < 0) {
|
|
|
|
|
|
send_file_response(errno_to_file_status(errno), 0,
|
|
|
|
|
|
(unsigned long long)st.st_size);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
unsigned int cap = req->pool_capacity;
|
|
|
|
|
|
if (cap > FT_STAGE_SIZE) cap = FT_STAGE_SIZE;
|
|
|
|
|
|
|
|
|
|
|
|
off_t off = (off_t)req->file_offset;
|
|
|
|
|
|
ssize_t n = pread(fd, g_ft_stage, cap, off);
|
|
|
|
|
|
int saved = errno;
|
|
|
|
|
|
close(fd);
|
|
|
|
|
|
|
|
|
|
|
|
if (n < 0) {
|
|
|
|
|
|
send_file_response(errno_to_file_status(saved), 0,
|
|
|
|
|
|
(unsigned long long)st.st_size);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (n > 0 && pool_write(req->pool_offset, g_ft_stage, (unsigned int)n) != 0) {
|
|
|
|
|
|
send_file_response(UTSM_FILE_ERR_IO, 0, (unsigned long long)st.st_size);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
send_file_response(UTSM_FILE_OK, (unsigned int)n,
|
|
|
|
|
|
(unsigned long long)st.st_size);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* FILE_WRITE_REQUEST: write payload_pool data to file at file_offset.
|
|
|
|
|
|
* UTSM pre-stages req->pool_capacity bytes at req->pool_offset in the pool.
|
|
|
|
|
|
* Convention: file_offset == 0 truncates the file first (push = replace);
|
|
|
|
|
|
* the file is created with mode 0644 if missing. Data is moved in
|
|
|
|
|
|
* FT_STAGE_SIZE pieces so a full pool (~1MB) can be consumed per request. */
|
|
|
|
|
|
static void handle_file_write(const struct ipc_file_request *req)
|
|
|
|
|
|
{
|
|
|
|
|
|
char path[UTSM_FILE_PATH_MAX + 1];
|
|
|
|
|
|
memcpy(path, req->path, UTSM_FILE_PATH_MAX);
|
|
|
|
|
|
path[UTSM_FILE_PATH_MAX] = '\0';
|
|
|
|
|
|
|
|
|
|
|
|
if (req->pool_offset >= UTSM_IPC_PAYLOAD_SIZE ||
|
|
|
|
|
|
req->pool_capacity > UTSM_IPC_PAYLOAD_SIZE - req->pool_offset) {
|
|
|
|
|
|
send_file_response(UTSM_FILE_ERR_INVAL, 0, 0);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct stat st;
|
|
|
|
|
|
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
|
|
|
|
|
|
send_file_response(UTSM_FILE_ERR_ISDIR, 0, 0);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int flags = O_WRONLY | O_CREAT;
|
|
|
|
|
|
if (req->file_offset == 0)
|
|
|
|
|
|
flags |= O_TRUNC;
|
|
|
|
|
|
int fd = open(path, flags, 0644);
|
|
|
|
|
|
if (fd < 0) {
|
|
|
|
|
|
send_file_response(errno_to_file_status(errno), 0, 0);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
unsigned int remaining = req->pool_capacity;
|
|
|
|
|
|
unsigned int done = 0;
|
|
|
|
|
|
while (remaining > 0) {
|
|
|
|
|
|
unsigned int chunk = remaining;
|
|
|
|
|
|
if (chunk > FT_STAGE_SIZE) chunk = FT_STAGE_SIZE;
|
|
|
|
|
|
|
|
|
|
|
|
if (pool_read(req->pool_offset + done, g_ft_stage, chunk) != 0) {
|
|
|
|
|
|
close(fd);
|
|
|
|
|
|
send_file_response(UTSM_FILE_ERR_IO, 0, 0);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
ssize_t n = pwrite(fd, g_ft_stage, chunk,
|
|
|
|
|
|
(off_t)(req->file_offset + done));
|
|
|
|
|
|
if (n < 0) {
|
|
|
|
|
|
int saved = errno;
|
|
|
|
|
|
close(fd);
|
|
|
|
|
|
send_file_response(errno_to_file_status(saved), done, 0);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
done += (unsigned int)n;
|
|
|
|
|
|
remaining -= (unsigned int)n;
|
|
|
|
|
|
if ((unsigned int)n < chunk)
|
|
|
|
|
|
break; /* short write (e.g. ENOSPC on next call would fail) */
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
unsigned long long total = 0;
|
|
|
|
|
|
if (fstat(fd, &st) == 0)
|
|
|
|
|
|
total = (unsigned long long)st.st_size;
|
|
|
|
|
|
close(fd);
|
|
|
|
|
|
send_file_response(UTSM_FILE_OK, done, total);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 14:35:14 +08:00
|
|
|
|
/* ===== Main loop ===== */
|
|
|
|
|
|
|
|
|
|
|
|
int main(void)
|
|
|
|
|
|
{
|
2026-08-09 22:14:01 +08:00
|
|
|
|
/* ASYNC children (VSCode Phase 5: X server / VSCode) are never waitpid'd;
|
|
|
|
|
|
* SIG_IGN lets the kernel reap them automatically so they don't zombie. */
|
|
|
|
|
|
signal(SIGCHLD, SIG_IGN);
|
|
|
|
|
|
|
2026-07-28 14:35:14 +08:00
|
|
|
|
g_utsm_fd = open("/dev/utsm", O_RDWR);
|
|
|
|
|
|
if (g_utsm_fd < 0) {
|
|
|
|
|
|
/* Cannot communicate with UTSM — exit */
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Announce daemon ready. UTSM's linux_launch() will see the subsequent
|
|
|
|
|
|
* HLT (via PARK ioctl) and return, marking the compat layer ready. */
|
|
|
|
|
|
send_msg(UTSM_MSG_EXEC_READY, NULL, 0);
|
|
|
|
|
|
|
|
|
|
|
|
/* First park: this is the HLT that causes linux_launch() to return. */
|
|
|
|
|
|
park();
|
|
|
|
|
|
|
|
|
|
|
|
/* Main loop: process exec requests */
|
|
|
|
|
|
for (;;) {
|
|
|
|
|
|
struct utsm_ioctl_msg umsg;
|
|
|
|
|
|
|
|
|
|
|
|
/* Drain any pending messages (there may be multiple if UTSM
|
|
|
|
|
|
* queued several before vmresume). */
|
|
|
|
|
|
while (recv_msg(&umsg) == 0) {
|
|
|
|
|
|
if (umsg.type == UTSM_MSG_EXEC_REQUEST &&
|
|
|
|
|
|
umsg.data_len >= sizeof(struct ipc_exec_request)) {
|
|
|
|
|
|
handle_exec_request((const struct ipc_exec_request *)umsg.data);
|
2026-07-31 13:53:49 +08:00
|
|
|
|
} else if (umsg.type == UTSM_MSG_FILE_LIST_REQUEST &&
|
|
|
|
|
|
umsg.data_len >= sizeof(struct ipc_file_request)) {
|
|
|
|
|
|
handle_file_list((const struct ipc_file_request *)umsg.data);
|
|
|
|
|
|
} else if (umsg.type == UTSM_MSG_FILE_READ_REQUEST &&
|
|
|
|
|
|
umsg.data_len >= sizeof(struct ipc_file_request)) {
|
|
|
|
|
|
handle_file_read((const struct ipc_file_request *)umsg.data);
|
|
|
|
|
|
} else if (umsg.type == UTSM_MSG_FILE_WRITE_REQUEST &&
|
|
|
|
|
|
umsg.data_len >= sizeof(struct ipc_file_request)) {
|
|
|
|
|
|
handle_file_write((const struct ipc_file_request *)umsg.data);
|
2026-07-28 14:35:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
/* Ignore other message types (HELLO, PING, etc.) */
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-01 23:13:42 +08:00
|
|
|
|
/* Single vCPU: PARK/HLT freezes the whole VM, including async
|
|
|
|
|
|
* children and in-flight virtio-blk. Yields return immediately
|
|
|
|
|
|
* when the child is in D-state, so a yield-then-HLT loop parks
|
|
|
|
|
|
* before the virtio IRQ can be injected. Stay runnable; the
|
|
|
|
|
|
* host returns from linux_resume() on a preemption timeslice. */
|
|
|
|
|
|
if (g_async_live) {
|
|
|
|
|
|
donate_cpu();
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-31 13:53:49 +08:00
|
|
|
|
/* Responses (if any) are already queued. Park to let UTSM read
|
|
|
|
|
|
* them and return to the shell; a spurious wakeup with no
|
|
|
|
|
|
* request simply parks again. */
|
2026-07-28 14:35:14 +08:00
|
|
|
|
park();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
close(g_utsm_fd);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|