Skip to content

Commit 1ad25a4

Browse files
Begin reorganizing the kernel with kernel-space typescript fs
1 parent 09d501e commit 1ad25a4

File tree

9 files changed

+388
-0
lines changed

9 files changed

+388
-0
lines changed

example/index.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<title>Getting Started</title>
5+
</head>
6+
<body>
7+
<script src="./build/main.js"></script>
8+
</body>
9+
</html>

example/package.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "webabi-example",
3+
"version": "0.0.1",
4+
"description": "",
5+
"scripts": {
6+
"build": "tsc",
7+
"install": "npm run build",
8+
"test": "npm run build && webpack && node build/main.js"
9+
},
10+
"author": "Will Fancher",
11+
"dependencies": {
12+
"typescript": "^2.9.2",
13+
"webabi-kernel": "file:../kernel"
14+
},
15+
"devDependencies": {
16+
"webpack": "^4.16.3",
17+
"webpack-cli": "^3.1.0"
18+
}
19+
}

example/src/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Device, configureFileSystem, BFSCallback, Stats, File, FileFlag } from "webabi-kernel";
2+
3+
class JSaddleDevice implements Device {
4+
open(flag: FileFlag, cb: BFSCallback<File>): void {
5+
}
6+
openSync(flag: FileFlag, mode: number): File {
7+
throw "NYI";
8+
}
9+
stat(isLstat: boolean | null, cb: BFSCallback<Stats>): void {
10+
}
11+
statSync(isLstat: boolean | null): Stats {
12+
throw "NYI";
13+
}
14+
}
15+
16+
configureFileSystem({ "/jsaddle": new JSaddleDevice() }, (err, fs) => {
17+
console.log(err);
18+
let buf = Buffer.from("hi\n");
19+
fs.write(1, buf, 0, buf.length, null, () => {});
20+
});

example/tsconfig.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es6",
4+
"outDir": "dist",
5+
"lib": ["dom", "es2015", "es2016", "es2017"],
6+
"module": "commonjs",
7+
"declaration": true
8+
},
9+
"include": [
10+
"src/**/*.ts"
11+
],
12+
"exclude": [
13+
"node_modules"
14+
]
15+
}

example/webpack.config.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const path = require('path');
2+
3+
console.log(require.resolve("webabi-kernel"));
4+
5+
module.exports = {
6+
entry: './dist/index.js',
7+
mode: "production",
8+
output: {
9+
filename: 'main.js',
10+
path: path.resolve(__dirname, 'build')
11+
},
12+
resolve: {
13+
// Using file:../kernel in package.json requires this
14+
symlinks: false
15+
}
16+
};

kernel/package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "webabi-kernel",
3+
"version": "0.0.1",
4+
"description": "",
5+
"scripts": {
6+
"build": "tsc",
7+
"main": "npm run tsc && node dist/foo.js",
8+
"install": "npm run build"
9+
},
10+
"main": "dist/index.js",
11+
"author": "Will Fancher",
12+
"dependencies": {
13+
"@types/archiver": "^2.0.0",
14+
"@types/async": "^2.0.49",
15+
"@types/body-parser": "^1.16.4",
16+
"@types/dropboxjs": "0.0.29",
17+
"@types/express": "^4.0.36",
18+
"@types/filesystem": "0.0.28",
19+
"@types/isomorphic-fetch": "^0.0.34",
20+
"@types/mocha": "^5.2.5",
21+
"@types/node": "^7.0",
22+
"@types/rimraf": "^2.0.2",
23+
"browserfs": "^1.4.3",
24+
"typescript": "^2.9.2"
25+
}
26+
}

kernel/src/index.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import MountableFileSystem from "browserfs/dist/node/backend/MountableFileSystem";
2+
import * as handles from "./stdio_handles";
3+
import { BaseFileSystem, FileSystemConstructor, BFSCallback,
4+
BFSOneArgCallback, BFSThreeArgCallback, FileSystem,
5+
FileSystemOptions } from "browserfs/dist/node/core/file_system";
6+
import { FileType } from 'browserfs/dist/node/core/node_fs_stats';
7+
import Stats from 'browserfs/dist/node/core/node_fs_stats';
8+
import { File } from "browserfs/dist/node/core/file";
9+
import { FileFlag } from "browserfs/dist/node/core/file_flag";
10+
import { ApiError, ErrorCode } from 'browserfs/dist/node/core/api_error';
11+
import FS from "browserfs/dist/node/core/FS";
12+
13+
export interface Device {
14+
open(flag: FileFlag, cb: BFSCallback<File>): void;
15+
stat(isLstat: boolean | null, cb: BFSCallback<Stats>): void;
16+
}
17+
18+
export interface DeviceFileSystemOptions {
19+
devices: {[name: string]: Device};
20+
}
21+
22+
export class DeviceFileSystem extends BaseFileSystem implements FileSystem {
23+
public static readonly Name = "DeviceFileSystem";
24+
public static readonly Options: FileSystemOptions = {};
25+
26+
public static Create(opts: DeviceFileSystemOptions, cb: BFSCallback<DeviceFileSystem>): void {
27+
return cb(null, new DeviceFileSystem(opts));
28+
}
29+
30+
public static isAvailable(): boolean {
31+
return true;
32+
}
33+
34+
options: DeviceFileSystemOptions;
35+
36+
constructor(options: DeviceFileSystemOptions) {
37+
super();
38+
this.options = options;
39+
}
40+
41+
public getName() {
42+
return "DeviceFileSystem";
43+
}
44+
public isReadOnly() {
45+
return false;
46+
}
47+
public supportsProps() {
48+
return false;
49+
}
50+
public supportsSynch() {
51+
return false;
52+
}
53+
54+
public openFile(p: string, flag: FileFlag, cb: BFSCallback<File>): void {
55+
if (this.options.devices.hasOwnProperty(p)) {
56+
return this.options.devices[p].open(flag, cb);
57+
} else {
58+
return cb(ApiError.ENOENT(p));
59+
}
60+
}
61+
public stat(p: string, isLstat: boolean | null, cb: BFSCallback<Stats>): void {
62+
if (this.options.devices.hasOwnProperty(p)) {
63+
return this.options.devices[p].stat(isLstat, cb);
64+
} else {
65+
return cb(ApiError.ENOENT(p));
66+
}
67+
}
68+
}
69+
70+
export function configureFileSystem(devices: { [name: string]: Device }, cb: BFSCallback<FS>): void {
71+
DeviceFileSystem.Create({ devices: devices }, (e, dfs) => {
72+
if (e) {
73+
cb(e);
74+
return;
75+
}
76+
MountableFileSystem.Create({
77+
"/dev": dfs
78+
}, (e, mfs) => {
79+
if (e) {
80+
cb(e);
81+
return
82+
}
83+
84+
const fs = new FS();
85+
fs.initialize(mfs);
86+
87+
const fdMap: {[id: number]: File} = (fs as any).fdMap;
88+
fdMap[0] = handles.stdin;
89+
fdMap[1] = handles.stdout;
90+
fdMap[2] = handles.stderr;
91+
92+
cb(undefined, fs);
93+
});
94+
});
95+
}
96+
97+
// Re-export for device implementors
98+
export { BFSCallback, Stats, File, FileFlag };

kernel/src/stdio_handles.ts

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import { File, BaseFile } from "browserfs/dist/node/core/file";
2+
import { BaseFileSystem, FileSystemConstructor, BFSCallback, BFSOneArgCallback, BFSThreeArgCallback, FileSystem, FileSystemOptions } from "browserfs/dist/node/core/file_system";
3+
import { FileType } from 'browserfs/dist/node/core/node_fs_stats';
4+
import Stats from 'browserfs/dist/node/core/node_fs_stats';
5+
import { ApiError, ErrorCode } from 'browserfs/dist/node/core/api_error';
6+
7+
export let stdin: File;
8+
export let stdout: File;
9+
export let stderr: File;
10+
11+
class UselessFile extends BaseFile implements File {
12+
getPos(): number | undefined {
13+
return undefined;
14+
}
15+
stat(cb: BFSCallback<Stats>): void {
16+
return cb(undefined, new Stats(FileType.FILE, 0));
17+
}
18+
statSync(): Stats {
19+
return new Stats(FileType.FILE, 0)
20+
}
21+
close(cb: BFSOneArgCallback): void {
22+
cb(new ApiError(ErrorCode.ENOTSUP));
23+
}
24+
closeSync(): void {
25+
throw new ApiError(ErrorCode.ENOTSUP);
26+
}
27+
truncate(len: number, cb: BFSOneArgCallback): void {
28+
cb(new ApiError(ErrorCode.ENOTSUP));
29+
}
30+
truncateSync(len: number): void {
31+
throw new ApiError(ErrorCode.ENOTSUP);
32+
}
33+
write(buffer: Buffer, offset: number, length: number, position: number | null, cb: BFSThreeArgCallback<number, Buffer>): void {
34+
cb(new ApiError(ErrorCode.ENOTSUP));
35+
}
36+
writeSync(buffer: Buffer, offset: number, length: number, position: number | null): number {
37+
throw new ApiError(ErrorCode.ENOTSUP);
38+
}
39+
read(buffer: Buffer, offset: number, length: number, position: number | null, cb: BFSThreeArgCallback<number, Buffer>): void {
40+
cb(new ApiError(ErrorCode.ENOTSUP));
41+
}
42+
readSync(buffer: Buffer, offset: number, length: number, position: number): number {
43+
throw new ApiError(ErrorCode.ENOTSUP);
44+
}
45+
}
46+
47+
if (process && !(process as any).browser) {
48+
interface Request {
49+
buffer: Buffer;
50+
offset: number;
51+
length: number;
52+
cb: BFSThreeArgCallback<number, Buffer>;
53+
}
54+
class ReadWriteStreamFile extends UselessFile implements File {
55+
stream: NodeJS.ReadWriteStream;
56+
requests: [Request] = <[Request]> [];
57+
leftover?: Buffer = null;
58+
59+
constructor(stream: NodeJS.ReadWriteStream) {
60+
super();
61+
this.stream = stream;
62+
this.stream.pause();
63+
this.stream.on("error", (err) => {
64+
const reqs = this.requests;
65+
this.requests = <[Request]> [];
66+
for (const req of reqs) {
67+
req.cb(err, undefined, undefined);
68+
}
69+
});
70+
this.stream.on("data", (buf) => {
71+
this.stream.pause();
72+
if (this.leftover) {
73+
buf = Buffer.concat([this.leftover, buf]);
74+
this.leftover = null;
75+
}
76+
this.onData(buf);
77+
});
78+
}
79+
80+
onData(buf: Buffer): void {
81+
const reqs = this.requests;
82+
this.requests = <[Request]> [];
83+
let nextBuf: Buffer | null = null;
84+
for (const req of reqs) {
85+
if (buf.length > req.length) {
86+
nextBuf = buf.slice(req.length);
87+
buf = buf.slice(0, req.length);
88+
} else {
89+
nextBuf = null;
90+
}
91+
92+
const copied = buf.copy(req.buffer, req.offset);
93+
req.cb(undefined, copied, req.buffer);
94+
95+
buf = nextBuf;
96+
}
97+
98+
if (nextBuf) {
99+
// nextBuf may still have the old leftover underlying it.
100+
// Use Buffer.from to avoid retaining the entire history.
101+
this.leftover = Buffer.from(nextBuf);
102+
}
103+
};
104+
close(cb: BFSOneArgCallback): void {
105+
this.stream.end(cb);
106+
}
107+
write(buffer: Buffer, offset: number, length: number, position: number | null, cb: BFSThreeArgCallback<number, Buffer>): void {
108+
this.stream.write(buffer.slice(offset, offset + length), (err) => {
109+
if (err) {
110+
cb(err);
111+
} else {
112+
cb(undefined, length, buffer);
113+
}
114+
});
115+
}
116+
read(buffer: Buffer, offset: number, length: number, position: number | null, cb: BFSThreeArgCallback<number, Buffer>): void {
117+
this.stream.resume();
118+
this.requests.push({
119+
buffer: buffer,
120+
offset: offset,
121+
length: length,
122+
cb: cb
123+
});
124+
}
125+
}
126+
127+
stdin = new ReadWriteStreamFile(process.stdin);
128+
stdout = new ReadWriteStreamFile(process.stdout);
129+
stderr = new ReadWriteStreamFile(process.stderr);
130+
} else {
131+
class ConsoleFile extends UselessFile implements File {
132+
log: (msg: string) => void;
133+
buffer?: Buffer = null;
134+
135+
constructor(log: (msg: string) => void) {
136+
super();
137+
this.log = log;
138+
}
139+
140+
write(buffer: Buffer, offset: number, length: number, position: number | null, cb: BFSThreeArgCallback<number, Buffer>): void {
141+
let slicedBuffer = buffer.slice(offset, offset + length);
142+
let n = slicedBuffer.lastIndexOf("\n");
143+
if (n < 0) {
144+
if (this.buffer) {
145+
this.buffer = Buffer.concat([this.buffer, slicedBuffer]);
146+
} else {
147+
this.buffer = slicedBuffer;
148+
}
149+
} else {
150+
let logBuffer = slicedBuffer.slice(0, n);
151+
if (this.buffer) {
152+
logBuffer = Buffer.concat([this.buffer, logBuffer]);
153+
}
154+
this.log(logBuffer.toString());
155+
156+
// + 1 to skip the \n
157+
if (n + 1 < slicedBuffer.length) {
158+
this.buffer = slicedBuffer.slice(n + 1);
159+
} else {
160+
this.buffer = null;
161+
}
162+
}
163+
cb(undefined, length, buffer);
164+
}
165+
}
166+
167+
stdin = new UselessFile();
168+
stdout = new ConsoleFile((msg) => console.log(msg));
169+
stderr = new ConsoleFile((msg) => console.error(msg));
170+
}

kernel/tsconfig.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es6",
4+
"outDir": "dist",
5+
"lib": ["dom", "es2015", "es2016", "es2017"],
6+
"module": "commonjs",
7+
"declaration": true
8+
},
9+
"include": [
10+
"src/**/*.ts"
11+
],
12+
"exclude": [
13+
"node_modules"
14+
]
15+
}

0 commit comments

Comments
 (0)