MCPcopy Create free account
hub / github.com/cloudflare/computer / exec

Method exec

packages/computerd/src/exec/runner.ts:104–269  ·  view source on GitHub ↗
(command: string, options: ExecOptions = {})

Source from the content-addressed store, hash-verified

102 }
103
104 exec(command: string, options: ExecOptions = {}): ExecHandle {
105 if (this.disposed) throw new Error("runner disposed");
106 const id = options.id ?? randomUUID();
107 const existing = this.records.get(id);
108 if (existing?.live) {
109 throw new ExecError("EEXEC_BUSY", `exec id ${id} is already running`);
110 }
111 if (existing !== undefined) this.disposeRecord(existing);
112
113 const cwd = options.cwd ?? this.opts.cwd;
114 const env = { ...process.env, ...this.opts.env, ...options.env };
115 // Pre-flight the cwd via dofs's stat which walks vfs_nodes /
116 // vfs_dirents in SQLite directly — no node:fs.statSync, no
117 // FUSE callback. Preserves the historical ENOENT-cwd error
118 // shape callers see when the path doesn't exist, without
119 // taking a route that could deadlock against computerd's own FUSE
120 // server.
121 if (cwd !== undefined) {
122 try {
123 stat(this.db, cwd);
124 } catch (err) {
125 return this.spawnFailed(id, err);
126 }
127 }
128 // Don't pass cwd to spawn. libuv's uv_spawn does fork +
129 // chdir + execve in the child while the parent blocks on a
130 // status pipe. If cwd lives inside computerd's own FUSE mount, the
131 // child's chdir issues a FUSE LOOKUP that computerd can't service
132 // (its event loop is stuck in uv_spawn), and the whole
133 // process deadlocks. Have /bin/sh do the chdir instead: by
134 // the time the shell runs its `cd`, computerd's event loop is back
135 // and can answer the FUSE callback normally.
136 //
137 // No `exec` prefix on the user command: the user's argument
138 // may be a compound expression (`echo a && exit 3`), and
139 // `exec` only binds to the next simple command, which would
140 // cut off everything after the first `&&`. The shell stays
141 // as the spawned process either way — the original code
142 // (`spawn("/bin/sh", ["-c", command])`) was already a shell-
143 // owned exec, so this is no change in process shape.
144 const wrapped = cwd !== undefined ? `cd ${shellQuote(cwd)} && ${command}` : command;
145 const child = spawn("/bin/sh", ["-c", wrapped], {
146 env,
147 stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"],
148 });
149 if (options.stdin !== undefined && child.stdin) {
150 // Feed the caller's bytes then close so the child sees EOF.
151 // Ignore write/EPIPE errors: a command that never reads stdin
152 // (or exits first) must not fail the run.
153 child.stdin.on("error", () => {});
154 child.stdin.end(Buffer.from(options.stdin));
155 }
156 const log = createLog(this.db, id, {
157 maxBytes: this.opts.logMaxBytes,
158 now: this.opts.now,
159 });
160 const record: ExecRecord = {
161 id,

Callers

nothing calls this directly

Calls 12

disposeRecordMethod · 0.95
spawnFailedMethod · 0.95
scheduleSweepMethod · 0.95
makeLiveStreamMethod · 0.95
statFunction · 0.90
createLogFunction · 0.85
shellQuoteFunction · 0.70
getMethod · 0.65
endMethod · 0.65
setMethod · 0.65
appendMethod · 0.65
killMethod · 0.65

Tested by

no test coverage detected