Skip to content

Commit

Permalink
Initial code for sh command
Browse files Browse the repository at this point in the history
  • Loading branch information
Your Name committed Jun 11, 2024
1 parent 88abb53 commit 2ddad5b
Show file tree
Hide file tree
Showing 4 changed files with 271 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/cmdlets/dump.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export class DumpCmdLet extends CmdLet {
output.writeByteArray(bytes.buffer as ArrayBuffer);
Output.write(' '.repeat(2 + Process.pointerSize * 2));
for (let i = 0; i < 16; i++) {
if (i % width != 0) continue;
if (i % width !== 0) continue;
const hdr = i.toString(16).toUpperCase();
const padLen = width * 2 + 1 - hdr.length;
Output.write(` ${hdr.padStart(padLen, ' ')}`);
Expand Down
264 changes: 264 additions & 0 deletions src/cmdlets/sh.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
import { CmdLet } from '../commands/cmdlet.js';
import { Output } from '../io/output.js';
import { Token } from '../io/token.js';
import { Format } from '../misc/format.js';
import { Var } from '../vars/var.js';

const INT_SIZE: number = 4;
const PIPE_READ_OFFSET: number = 0;
const PIPE_WRITE_OFFSET: number = 4;
const STDIN_FILENO: number = 0;
const STDOUT_FILENO: number = 1;
const STDERR_FILENO: number = 2;
const READ_SIZE: number = 4096;

const USAGE: string = `Usage: fd
sh - run a shell
`;

export class ShCmdLet extends CmdLet {
name = 'sh';
category = 'misc';
help = 'run a shell';

private pGetEnv: NativePointer | null = null;
private pPipe: NativePointer | null = null;
private pFork: NativePointer | null = null;
private pClose: NativePointer | null = null;
private pDup2: NativePointer | null = null;
private pExecV: NativePointer | null = null;
private pWaitPid: NativePointer | null = null;

public run(tokens: Token[]): Var {
const retWithoutParams = this.runWithoutParams(tokens);
if (retWithoutParams !== null) return retWithoutParams;

return this.usage();
}

private runWithoutParams(tokens: Token[]): Var | null {
if (tokens.length !== 0) return null;

this.runSh();

return Var.ZERO;
}

private async runSh(): Promise<void> {
if (
this.pPipe === null ||
this.pFork === null ||
this.pClose === null ||
this.pDup2 === null ||
this.pGetEnv == null ||
this.pExecV == null ||
this.pWaitPid == null
)
throw new Error('failed to find necessary native functions');

// char *getenv(const char *name);
const fnGetEnv = new SystemFunction(this.pGetEnv, 'pointer', ['pointer']);
// int pipe(int pipefd[2]);
const fnPipe = new SystemFunction(this.pPipe, 'int', ['pointer']);
// pid_t fork(void);
const fnFork = new SystemFunction(this.pFork, 'int', []);
// int close(int fd);
const fnClose = new SystemFunction(this.pClose, 'int', ['int']);
// int dup2(int oldfd, int newfd);
const fnDup2 = new SystemFunction(this.pDup2, 'int', ['int', 'int']);
// int execv(const char *path, char *const argv[]);
const fnExecV = new SystemFunction(this.pExecV, 'int', [
'pointer',
'pointer',
]);
// pid_t waitpid(pid_t pid, int *status, int options);
const fnWaitPid = new SystemFunction(this.pExecV, 'int', [
'int',
'pointer',
'int',
]);

const shell = Memory.allocUtf8String('SHELL');
const { value: shellVar, errno: getenvErrno } = fnGetEnv(
shell,
) as UnixSystemFunctionResult<NativePointer>;
if (shellVar.equals(ptr(0)))
throw new Error(`failed to getenv("SHELL"), errno: ${getenvErrno}`);

Output.writeln(`SHELL: ${shellVar.readUtf8String()}`, true);

const toChildPipes = Memory.alloc(INT_SIZE * 2);
const { value: pipeChildRet, errno: pipeChildErrno } = fnPipe(
toChildPipes,
) as UnixSystemFunctionResult<number>;
if (pipeChildRet !== 0)
throw new Error(`failed to pipe(child), errno: ${pipeChildErrno}`);

const toParentPipes = Memory.alloc(INT_SIZE * 2);
const { value: pipeParentRet, errno: pipeParentErrno } = fnPipe(
toParentPipes,
) as UnixSystemFunctionResult<number>;
if (pipeParentRet !== 0)
throw new Error(`failed to pipe, errno: ${pipeParentErrno}`);

const { value: forkRet, errno: forkErrno } =
fnFork() as UnixSystemFunctionResult<number>;
if (forkRet < 0) throw new Error(`failed to fork, errno: ${forkErrno}`);

if (forkRet === 0) {
// child
const { value: closeChildRet, errno: closeChildErrno } = fnClose(
toChildPipes.add(PIPE_WRITE_OFFSET).readInt(),
) as UnixSystemFunctionResult<number>;
if (closeChildRet !== 0)
throw new Error(`failed to close(child), errno: ${closeChildErrno}`);

const { value: closeParentRet, errno: closeParentErrno } = fnClose(
toParentPipes.add(PIPE_READ_OFFSET).readInt(),
) as UnixSystemFunctionResult<number>;
if (closeParentRet !== 0)
throw new Error(`failed to close(parent), errno: ${closeParentErrno}`);

const { value: dup2InRet, errno: dup2InErrno } = fnDup2(
toChildPipes.add(PIPE_READ_OFFSET).readInt(),
STDIN_FILENO,
) as UnixSystemFunctionResult<number>;
if (dup2InRet !== 0)
throw new Error(`failed to dup2(stdin), errno: ${dup2InErrno}`);

const { value: dup2OutRet, errno: dup2OutErrno } = fnDup2(
toParentPipes.add(PIPE_WRITE_OFFSET).readInt(),
STDOUT_FILENO,
) as UnixSystemFunctionResult<number>;
if (dup2OutRet !== 0)
throw new Error(`failed to dup2(stdout), errno: ${dup2OutErrno}`);

const { value: dup2ErrRet, errno: dup2ErrErrno } = fnDup2(
toParentPipes.add(PIPE_WRITE_OFFSET).readInt(),
STDERR_FILENO,
) as UnixSystemFunctionResult<number>;
if (dup2ErrRet !== 0)
throw new Error(`failed to dup2(stderr), errno: ${dup2ErrErrno}`);

const argv = Memory.alloc(Process.pointerSize * 2);
const cmd = Memory.allocUtf8String('/bin/ls');
argv.writePointer(cmd);
argv.add(Process.pointerSize).writePointer(ptr(0));

const { value: execvRet, errno: execvErrno } = fnExecV(
cmd,
argv,
) as UnixSystemFunctionResult<number>;
if (execvRet !== 0)
throw new Error(`failed to execv, errno: ${execvErrno}`);

// unreachable
} else {
// parent
const { value: closeChildRet, errno: closeChildErrno } = fnClose(
toChildPipes.add(PIPE_READ_OFFSET).readInt(),
) as UnixSystemFunctionResult<number>;
if (closeChildRet !== 0)
throw new Error(`failed to close(child), errno: ${closeChildErrno}`);

const { value: closeParentRet, errno: closeParentErrno } = fnClose(
toParentPipes.add(PIPE_WRITE_OFFSET).readInt(),
) as UnixSystemFunctionResult<number>;
if (closeParentRet !== 0)
throw new Error(`failed to close(parent), errno: ${closeParentErrno}`);

const input = new UnixInputStream(
toParentPipes.add(PIPE_READ_OFFSET).readInt(),
{ autoClose: true },
);
// const output = new UnixOutputStream(
// toChildPipes.add(PIPE_WRITE_OFFSET).readInt(),
// { autoClose: true },
// );

const pStatus = Memory.alloc(INT_SIZE);
const { value: waitRet, errno: waitErrno } = fnWaitPid(
forkRet,
pStatus,
0,
) as UnixSystemFunctionResult<number>;
if (waitRet < 0)
throw new Error(`failed to waitpid, errno: ${waitErrno}`);

if (waitRet !== forkRet)
throw new Error(`waitpid exited abnormally, status: ${waitRet}`);

const status = pStatus.readInt();
if (this.wifExited(status)) {
const exitStatus = this.wExitStatus(status);
Output.writeln(`exit status: ${exitStatus}`);
} else if (this.wifSignalled(status)) {
const termSig = this.wTermSig(status);
Output.writeln(`terminated by signal: ${termSig}`);
}

for (let buf = await input.read(READ_SIZE); buf.byteLength !== 0; buf = await input.read(READ_SIZE)) {
const str: string = Format.toTextString(buf);
Output.write(str);
}
}

throw new Error('Unimplemented');
}

private wifExited(status: number): boolean {
return (status & 0xff00) >> 8 === 0;
}

private wExitStatus(status: number): number {
return (status & 0xff00) >> 8;
}

private wifSignalled(status: number): boolean {
return (status & 0xff00) >> 8 === 0x7f;
}

private wTermSig(status: number): number {
return status & 0x7f;
}

public usage(): Var {
Output.write(USAGE);
return Var.ZERO;
}

public override isSupported(): boolean {
switch (Process.platform) {
case 'darwin':
case 'freebsd':
case 'linux':
case 'qnx':
this.pGetEnv = Module.findExportByName(null, 'getenv');
this.pPipe = Module.findExportByName(null, 'pipe');
this.pFork = Module.findExportByName(null, 'fork');
this.pClose = Module.findExportByName(null, 'close');
this.pDup2 = Module.findExportByName(null, 'dup2');

this.pExecV = Module.findExportByName(null, 'execv');
this.pWaitPid = Module.findExportByName(null, 'waitpid');

if (
this.pGetEnv == null ||
this.pPipe === null ||
this.pFork === null ||
this.pClose === null ||
this.pDup2 === null ||
this.pExecV == null ||
this.pWaitPid == null
)
return false;
break;
case 'windows':
case 'barebone':
default:
return false;
}

return true;
}
}
2 changes: 2 additions & 0 deletions src/commands/cmdlets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { LdCmdLet } from '../cmdlets/ld.js';
import { FdCmdLet } from '../cmdlets/fd.js';
import { SrcCmdLet } from '../cmdlets/src.js';
import { PrintCmdLet } from '../cmdlets/print.js';
import { ShCmdLet } from '../cmdlets/sh.js';

export class CmdLets {
private static byName: Map<string, CmdLet> = new Map<string, CmdLet>();
Expand Down Expand Up @@ -68,6 +69,7 @@ export class CmdLets {
this.registerCmdletType(PrintCmdLet);
this.registerCmdletType(ReadBpCmdLet);
this.registerCmdletType(RegCmdLet);
this.registerCmdletType(ShCmdLet);
this.registerCmdletType(ShlCmdLet);
this.registerCmdletType(ShrCmdLet);
this.registerCmdletType(SrcCmdLet);
Expand Down
4 changes: 4 additions & 0 deletions src/misc/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,8 @@ export class Format {
return `${val.toString().padStart(4, ' ')} B`;
}
}

public static toTextString(bytes: ArrayBuffer): string {
return String.fromCharCode(...new Uint8Array(bytes));
}
}

0 comments on commit 2ddad5b

Please sign in to comment.