@threadedstack/wmd-web 0.0.0-stage → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +2601 -2
  2. package/RELEASE.json +123 -0
  3. package/export-page/coi-sw.js +20 -0
  4. package/export-page/index.html +17 -0
  5. package/export-page/run.css +10 -0
  6. package/export-page/run.js +236 -0
  7. package/guest/arm64/Image +4 -0
  8. package/guest/arm64/Image-4k +4 -0
  9. package/guest/arm64/initramfs.cpio.gz +0 -0
  10. package/guest/arm64/vm.wasm +0 -0
  11. package/package.json +36 -4
  12. package/page/sw.js +381 -0
  13. package/relay/control.ts +139 -0
  14. package/relay/engine.ts +787 -0
  15. package/relay/hosted.ts +660 -0
  16. package/relay/keys.ts +53 -0
  17. package/relay/local-tls.ts +121 -0
  18. package/relay/lock.ts +81 -0
  19. package/relay/logfile.ts +138 -0
  20. package/relay/main.ts +642 -0
  21. package/relay/policy.ts +241 -0
  22. package/relay/relay.ts +266 -0
  23. package/relay/token.ts +116 -0
  24. package/relay/v1.ts +107 -0
  25. package/relay/wisp.ts +615 -0
  26. package/src/base.js +325 -0
  27. package/src/boot.js +2037 -0
  28. package/src/calib-worker.js +40 -0
  29. package/src/fs/9p/codec.js +303 -0
  30. package/src/fs/9p/server.js +521 -0
  31. package/src/fs/errno.js +43 -0
  32. package/src/fs/graph.js +380 -0
  33. package/src/fs/overlay.js +1018 -0
  34. package/src/fs/pack-write.js +165 -0
  35. package/src/fs/pack.js +427 -0
  36. package/src/fs/seed.js +22 -0
  37. package/src/fs/svc-snapshot.js +65 -0
  38. package/src/fs/upper-pack.js +112 -0
  39. package/src/host/base-cache.js +195 -0
  40. package/src/host/base-file.js +328 -0
  41. package/src/host/base-io.js +93 -0
  42. package/src/host/boot-events.js +209 -0
  43. package/src/host/chan-service.js +300 -0
  44. package/src/host/chan-services.js +689 -0
  45. package/src/host/chan.js +655 -0
  46. package/src/host/ckpt-file.js +395 -0
  47. package/src/host/ckpt-io.js +207 -0
  48. package/src/host/ckpt-point.js +160 -0
  49. package/src/host/ckpt-save.js +64 -0
  50. package/src/host/ckpt.js +434 -0
  51. package/src/host/clock.js +111 -0
  52. package/src/host/ctl.js +778 -0
  53. package/src/host/errno.js +18 -0
  54. package/src/host/fs.js +167 -0
  55. package/src/host/gcnudge.js +83 -0
  56. package/src/host/i64shim.js +81 -0
  57. package/src/host/jit-cache.js +341 -0
  58. package/src/host/jit-hub-core.js +208 -0
  59. package/src/host/jit-hub.js +93 -0
  60. package/src/host/jit-snap.js +226 -0
  61. package/src/host/jit.js +1903 -0
  62. package/src/host/layout.js +100 -0
  63. package/src/host/monitor.js +110 -0
  64. package/src/host/p9mount.js +27 -0
  65. package/src/host/ram-mem.js +125 -0
  66. package/src/host/stdio.js +143 -0
  67. package/src/host/threads.js +109 -0
  68. package/src/host/wasi.js +335 -0
  69. package/src/host/wmd-imports.js +71 -0
  70. package/src/host/yamux.js +387 -0
  71. package/src/image-files.js +154 -0
  72. package/src/net/dhcp-dns.js +126 -0
  73. package/src/net/net-worker.js +172 -0
  74. package/src/net/packet.js +227 -0
  75. package/src/net/peer.js +230 -0
  76. package/src/net/proto.js +189 -0
  77. package/src/net/relay-client.js +226 -0
  78. package/src/net/stack.js +867 -0
  79. package/src/net/switch.js +160 -0
  80. package/src/net/wisp-client.js +570 -0
  81. package/src/net/wisp-transport.js +196 -0
  82. package/src/net/wisp.js +511 -0
  83. package/src/registry/client.js +176 -0
  84. package/src/registry/converter.js +68 -0
  85. package/src/registry/hub.js +221 -0
  86. package/src/registry/oci.js +76 -0
  87. package/src/registry/pack-worker.js +70 -0
  88. package/src/registry/pull.js +258 -0
  89. package/src/registry/ref.js +110 -0
  90. package/src/registry/wasi-mem.js +355 -0
  91. package/src/worker.js +315 -0
  92. package/wmdpack.wasm +0 -0
@@ -0,0 +1,165 @@
1
+ // W62: a writer for wmd's pack format (internal/pack/pack.go Writer), byte-compatible with
2
+ // the Go writer for the entries it takes: no sparse runs (the JS reader has none either), so
3
+ // a file's contents are all at its entry's off.
4
+ //
5
+ // writePack(entries) -> Uint8Array. An entry is {path (byte string, '' the root), whiteout?,
6
+ // opaque?, mode, uid, gid, rdev, atime, mtime, ctime (BigInt ns), size, data? (Uint8Array, or
7
+ // a function (off, len) -> Uint8Array for a regular file's contents), target, link, xattrs
8
+ // (Map name -> Uint8Array)}. Entries with the same non-zero link share the first one's
9
+ // contents (pass data on the first only). The index is sorted by path as Go sorts it (byte
10
+ // order), and a path added twice is an error, as Go's Close says.
11
+
12
+ import { MAGIC, S_IFMT, S_IFREG, bytesOf } from './pack.js'
13
+
14
+ const FLAG_WHITEOUT = 1
15
+ const FLAG_OPAQUE = 2
16
+ const READ_CHUNK = 1 << 20
17
+
18
+ class Bytes {
19
+ constructor(n = 1 << 16) {
20
+ this.b = new Uint8Array(n)
21
+ this.n = 0
22
+ }
23
+
24
+ room(k) {
25
+ if (this.n + k <= this.b.length) return
26
+ let len = this.b.length * 2
27
+ while (len < this.n + k) len *= 2
28
+ const g = new Uint8Array(len)
29
+ g.set(this.b.subarray(0, this.n))
30
+ this.b = g
31
+ }
32
+
33
+ byte(x) {
34
+ this.room(1)
35
+ this.b[this.n++] = x
36
+ }
37
+
38
+ raw(u8) {
39
+ this.room(u8.length)
40
+ this.b.set(u8, this.n)
41
+ this.n += u8.length
42
+ }
43
+
44
+ // Go's binary.AppendUvarint (x a Number up to 2^53 or a BigInt up to 2^64)
45
+ uvarint(x) {
46
+ if (typeof x === 'number' && x < 2 ** 31) {
47
+ if (x < 0) throw new RangeError(`uvarint ${x}`)
48
+ while (x >= 0x80) {
49
+ this.byte((x & 0x7f) | 0x80)
50
+ x >>>= 7
51
+ }
52
+ this.byte(x)
53
+ return
54
+ }
55
+ let v = BigInt(x)
56
+ if (v < 0n || v >= 1n << 64n) throw new RangeError(`uvarint ${x}`)
57
+ while (v >= 0x80n) {
58
+ this.byte(Number(v & 0x7fn) | 0x80)
59
+ v >>= 7n
60
+ }
61
+ this.byte(Number(v))
62
+ }
63
+
64
+ // Go's binary.AppendVarint: zig-zag
65
+ varint(x) {
66
+ const v = BigInt(x)
67
+ if (v < -(1n << 63n) || v >= 1n << 63n) throw new RangeError(`varint ${x}`)
68
+ this.uvarint(v < 0n ? (-v << 1n) - 1n : v << 1n)
69
+ }
70
+
71
+ str(s) {
72
+ const u8 = typeof s === 'string' ? bytesOf(s) : s
73
+ this.uvarint(u8.length)
74
+ this.raw(u8)
75
+ }
76
+
77
+ done() {
78
+ return this.b.subarray(0, this.n)
79
+ }
80
+ }
81
+
82
+ // a byte string's order is code unit order (pack.js), the order Go sorts its index in
83
+ const byPath = (a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)
84
+
85
+ /** The pack holding entries, as bytes. */
86
+ export function writePack(entries) {
87
+ const ents = entries.map(e => ({ ...e, path: cleanPath(e.path) }))
88
+ const data = new Bytes(1 << 20)
89
+ data.raw(bytesOf(MAGIC))
90
+ const linked = new Map() // link -> {off, size}
91
+ for (const e of ents) {
92
+ e.off = 0
93
+ if (e.whiteout || ((e.mode >>> 0) & S_IFMT) !== S_IFREG) continue
94
+ const had = e.link ? linked.get(e.link) : undefined
95
+ if (had) {
96
+ e.off = had.off
97
+ e.size = had.size
98
+ continue
99
+ }
100
+ const size = e.size || 0
101
+ e.off = data.n
102
+ if (size > 0) {
103
+ if (typeof e.data === 'function') {
104
+ for (let o = 0; o < size; o += READ_CHUNK) {
105
+ const want = Math.min(READ_CHUNK, size - o)
106
+ const got = e.data(o, want)
107
+ if (got.length !== want) throw new Error(`pack ${JSON.stringify(e.path)}: read ${got.length} of ${want} bytes at ${o}`)
108
+ data.raw(got)
109
+ }
110
+ } else {
111
+ if (!e.data || e.data.length !== size) throw new Error(`pack ${JSON.stringify(e.path)}: ${e.data ? e.data.length : 0} bytes of data for size ${size}`)
112
+ data.raw(e.data)
113
+ }
114
+ }
115
+ if (e.link) linked.set(e.link, { off: e.off, size })
116
+ }
117
+ ents.sort(byPath)
118
+ for (let i = 1; i < ents.length; i++) {
119
+ if (ents[i].path === ents[i - 1].path) throw new Error(`pack: ${JSON.stringify(ents[i].path)} added twice`)
120
+ }
121
+ const start = data.n
122
+ data.uvarint(ents.length)
123
+ for (const e of ents) entry(data, e)
124
+ const len = data.n - start
125
+ const foot = new Uint8Array(16)
126
+ const dv = new DataView(foot.buffer)
127
+ dv.setBigUint64(0, BigInt(start), true)
128
+ dv.setBigUint64(8, BigInt(len), true)
129
+ data.raw(foot)
130
+ data.raw(bytesOf(MAGIC))
131
+ return data.done().slice()
132
+ }
133
+
134
+ function cleanPath(p) {
135
+ const parts = []
136
+ for (const s of p.split('/')) {
137
+ if (s === '' || s === '.') continue
138
+ if (s === '..') parts.pop()
139
+ else parts.push(s)
140
+ }
141
+ return parts.join('/')
142
+ }
143
+
144
+ function entry(w, e) {
145
+ w.str(e.path)
146
+ w.byte((e.whiteout ? FLAG_WHITEOUT : 0) | (e.opaque ? FLAG_OPAQUE : 0))
147
+ if (e.whiteout) return
148
+ w.uvarint(e.mode >>> 0)
149
+ w.uvarint((e.uid || 0) >>> 0)
150
+ w.uvarint((e.gid || 0) >>> 0)
151
+ w.uvarint(e.rdev || 0)
152
+ w.varint(e.atime || 0n)
153
+ w.varint(e.mtime || 0n)
154
+ w.varint(e.ctime || 0n)
155
+ w.varint(e.size || 0)
156
+ w.varint(e.off || 0)
157
+ w.str(e.target || '')
158
+ w.uvarint(e.link || 0)
159
+ const keys = e.xattrs ? [...e.xattrs.keys()].sort() : []
160
+ w.uvarint(keys.length)
161
+ for (const k of keys) {
162
+ w.str(k)
163
+ w.str(e.xattrs.get(k))
164
+ }
165
+ }
package/src/fs/pack.js ADDED
@@ -0,0 +1,427 @@
1
+ // Reader for wmd's pack format (internal/pack/pack.go), byte-compatible with
2
+ // the Go reader.
3
+ //
4
+ // A pack is one image layer in one file:
5
+ //
6
+ // magic "WMDPACK1"
7
+ // data (each regular file's contents at its entry's off)
8
+ // index (uvarint count, then the entries, sorted by path)
9
+ // footer (index offset u64 LE, index length u64 LE, magic)
10
+ //
11
+ // An entry is: path (uvarint length + bytes), flags (1 byte: 1 whiteout,
12
+ // 2 opaque) and, unless it is a whiteout, mode, uid, gid, rdev (uvarints),
13
+ // atime, mtime, ctime, size, off (zig-zag varints), target (string), link
14
+ // (uvarint) and the xattrs (uvarint count, then name and value strings).
15
+ //
16
+ // Names are kept as "byte strings": one UTF-16 code unit per byte (Latin-1),
17
+ // so any byte sequence a Linux name can hold round-trips exactly and string
18
+ // order is byte order, the order Go sorts the index in. bytesOf/strOf convert.
19
+
20
+ export const MAGIC = 'WMDPACK1'
21
+
22
+ // Linux file types (st_mode & S_IFMT).
23
+ export const S_IFMT = 0o170000
24
+ export const S_IFREG = 0o100000
25
+ export const S_IFDIR = 0o040000
26
+ export const S_IFLNK = 0o120000
27
+ export const S_IFIFO = 0o010000
28
+ export const S_IFSOCK = 0o140000
29
+ export const S_IFCHR = 0o020000
30
+ export const S_IFBLK = 0o060000
31
+
32
+ const FLAG_WHITEOUT = 1
33
+ const FLAG_OPAQUE = 2
34
+
35
+ export class PackFormatError extends Error {}
36
+
37
+ /** A byte string (one char per byte) from bytes. */
38
+ export function strOf(u8) {
39
+ let s = ''
40
+ for (let i = 0; i < u8.length; i += 8192) {
41
+ s += String.fromCharCode.apply(null, u8.subarray(i, i + 8192))
42
+ }
43
+ return s
44
+ }
45
+
46
+ /** The bytes of a byte string. Chars above 0xff are not bytes: an error. */
47
+ export function bytesOf(s) {
48
+ const out = new Uint8Array(s.length)
49
+ for (let i = 0; i < s.length; i++) {
50
+ const c = s.charCodeAt(i)
51
+ if (c > 0xff) throw new RangeError('not a byte string')
52
+ out[i] = c
53
+ }
54
+ return out
55
+ }
56
+
57
+ /** A UTF-8 JavaScript string as a byte string. */
58
+ export function byteStr(utf8) {
59
+ return strOf(new TextEncoder().encode(utf8))
60
+ }
61
+
62
+ /** A random access source over bytes held in memory. */
63
+ export function bytesSource(u8) {
64
+ return {
65
+ size: u8.length,
66
+ read(off, len) {
67
+ return u8.subarray(off, Math.min(off + len, u8.length))
68
+ },
69
+ }
70
+ }
71
+
72
+ /** A source over an OPFS FileSystemSyncAccessHandle (Worker only). */
73
+ export function syncHandleSource(handle) {
74
+ return {
75
+ size: handle.getSize(),
76
+ read(off, len) {
77
+ const b = new Uint8Array(len)
78
+ const n = handle.read(b, { at: off })
79
+ return n === len ? b : b.subarray(0, n)
80
+ },
81
+ }
82
+ }
83
+
84
+ /** Linux's device number for major and minor (new_encode_dev). */
85
+ export function makedev(major, minor) {
86
+ return (major & 0xfff) * 256 + Math.floor(major / 4096) * 2 ** 44 + (minor & 0xff) + Math.floor(minor / 256) * 2 ** 20
87
+ }
88
+
89
+ /** A Linux device number's major. */
90
+ export function major(dev) {
91
+ dev = BigInt(dev)
92
+ return Number(((dev >> 8n) & 0xfffn) | ((dev >> 32n) & ~0xfffn))
93
+ }
94
+
95
+ /** A Linux device number's minor. */
96
+ export function minor(dev) {
97
+ dev = BigInt(dev)
98
+ return Number((dev & 0xffn) | ((dev >> 12n) & 0xffffff00n))
99
+ }
100
+
101
+ const MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER)
102
+
103
+ class Decoder {
104
+ constructor(b) {
105
+ this.b = b
106
+ this.p = 0
107
+ }
108
+
109
+ bad() {
110
+ throw new PackFormatError('not a pack')
111
+ }
112
+
113
+ // uvarint is a Number when it fits in 53 bits, else a BigInt.
114
+ uvarint() {
115
+ const b = this.b
116
+ let p = this.p
117
+ let x = 0
118
+ let mul = 1
119
+ for (let i = 0; i < 7; i++) {
120
+ if (p >= b.length) this.bad()
121
+ const c = b[p++]
122
+ x += (c & 0x7f) * mul
123
+ if (c < 0x80) {
124
+ this.p = p
125
+ return x
126
+ }
127
+ mul *= 128
128
+ }
129
+ // Beyond 49 bits: finish in BigInt.
130
+ let big = BigInt(x)
131
+ let shift = 49n
132
+ for (let i = 7; i < 10; i++) {
133
+ if (p >= b.length) this.bad()
134
+ const c = b[p++]
135
+ if (i === 9 && c > 1) this.bad() // overflows 64 bits, as Go's Uvarint says
136
+ big |= BigInt(c & 0x7f) << shift
137
+ if (c < 0x80) {
138
+ this.p = p
139
+ return big <= MAX_SAFE ? Number(big) : big
140
+ }
141
+ shift += 7n
142
+ }
143
+ this.bad()
144
+ }
145
+
146
+ // varint is zig-zag: a Number when safe, else a BigInt.
147
+ varint() {
148
+ const u = this.uvarint()
149
+ if (typeof u === 'number') {
150
+ const h = Math.floor(u / 2)
151
+ return u % 2 ? -h - 1 : h
152
+ }
153
+ let x = u >> 1n
154
+ if (u & 1n) x = ~x
155
+ return x >= -MAX_SAFE && x <= MAX_SAFE ? Number(x) : x
156
+ }
157
+
158
+ bytes() {
159
+ const n = this.uvarint()
160
+ if (typeof n !== 'number' || n > this.b.length - this.p) this.bad()
161
+ const s = this.b.subarray(this.p, this.p + n)
162
+ this.p += n
163
+ return s
164
+ }
165
+
166
+ str() {
167
+ return strOf(this.bytes())
168
+ }
169
+ }
170
+
171
+ const ROOT = -1 // the parent of a top-level entry
172
+ const NONE = -1 // no child, no sibling
173
+ // Distinct times kept as one BigInt each (images share few mtimes).
174
+ const MAX_INTERNED_TIMES = 1 << 16
175
+ // Names kept as one string each: the first this many distinct, this short.
176
+ const MAX_INTERNED_NAMES = 1 << 16
177
+ const MAX_INTERNED_NAME = 32
178
+
179
+ /**
180
+ * An open pack. Its index stays in memory as the pack's bytes (one copy),
181
+ * with three 32-bit numbers per entry (where it starts, its first child, its
182
+ * next sibling); an entry is decoded when asked for (entry(i), lookup()),
183
+ * never kept. Opening still decodes and checks every entry once, so a bad
184
+ * pack is refused at open, as Go's reader does.
185
+ *
186
+ * A decoded entry is {path, name, dir, whiteout, opaque, mode, uid, gid,
187
+ * rdev, atime, mtime, ctime (BigInt ns), size, off, target, link, xattrs
188
+ * (Map|null)}, a new object each time.
189
+ */
190
+ export class Pack {
191
+ constructor(source, name = 'pack') {
192
+ this.source = source
193
+ this.name = name
194
+ const size = source.size
195
+ const foot = 16 + MAGIC.length
196
+ if (size < 2 * MAGIC.length + 16) throw new PackFormatError(`${name}: not a pack`)
197
+ const f = source.read(size - foot, foot)
198
+ if (strOf(f.subarray(16)) !== MAGIC) throw new PackFormatError(`${name}: not a pack`)
199
+ const dv = new DataView(f.buffer, f.byteOffset, f.byteLength)
200
+ const start = Number(dv.getBigUint64(0, true))
201
+ const n = Number(dv.getBigUint64(8, true))
202
+ if (start < MAGIC.length || n < 0 || start + n !== size - foot) {
203
+ throw new PackFormatError(`${name}: not a pack`)
204
+ }
205
+ this.dataEnd = start
206
+ this.times = new Map()
207
+ this.lastTimeAt = 0
208
+ this.lastTimeLen = 0
209
+ this.lastTime = 0n
210
+ this.names = new Map()
211
+ // A copy: the source's bytes may be a view the caller reuses.
212
+ const d = new Decoder(source.read(start, n).slice())
213
+ this.idx = d.b
214
+ const count = d.uvarint()
215
+ if (typeof count !== 'number' || count > n || count >= 2 ** 31) throw new PackFormatError(`${name}: not a pack`)
216
+ this.count = count
217
+ this.pos = n < 2 ** 32 ? new Uint32Array(count) : new Float64Array(count)
218
+ this.firstChild = new Int32Array(count).fill(NONE)
219
+ this.nextSibling = new Int32Array(count).fill(NONE)
220
+ this.rootFirst = NONE
221
+ this.links = new Map() // hard-link group -> {n, first}
222
+ const lastChild = new Int32Array(count)
223
+ let rootLast = NONE
224
+ const dirs = new Map() // directory path -> index, while opening only
225
+ let prev = null
226
+ for (let i = 0; i < count; i++) {
227
+ this.pos[i] = d.p
228
+ const e = this.#entry(d)
229
+ if (prev !== null && prev >= e.path) {
230
+ throw new PackFormatError(`${name}: index not sorted at ${JSON.stringify(e.path)}`)
231
+ }
232
+ prev = e.path
233
+ if (!e.whiteout && (e.mode & S_IFMT) === S_IFREG && (e.off < 0 || e.size < 0 || e.off + e.size > start)) {
234
+ throw new PackFormatError(`${name}: ${e.path}: contents outside the data`)
235
+ }
236
+ if (e.link) {
237
+ const g = this.links.get(e.link)
238
+ if (g) g.n++
239
+ else this.links.set(e.link, { n: 1, first: i })
240
+ }
241
+ if (e.path === '') continue
242
+ if (!e.whiteout && (e.mode & S_IFMT) === S_IFDIR) dirs.set(e.path, i)
243
+ // Children are linked in index order, which is name order. An entry
244
+ // whose directory is not a directory entry of this pack is reachable
245
+ // by no lookup (as before: only directory entries are listed).
246
+ const parent = e.dir === '' ? ROOT : dirs.get(e.dir)
247
+ if (parent === undefined) continue
248
+ if (parent === ROOT) {
249
+ if (rootLast === NONE) this.rootFirst = i
250
+ else this.nextSibling[rootLast] = i
251
+ rootLast = i
252
+ } else {
253
+ if (this.firstChild[parent] === NONE) this.firstChild[parent] = i
254
+ else this.nextSibling[lastChild[parent]] = i
255
+ lastChild[parent] = i
256
+ }
257
+ }
258
+ }
259
+
260
+ // One string per distinct short name (index.js, package.json, ...).
261
+ #name(b) {
262
+ const s = strOf(b)
263
+ if (b.length > MAX_INTERNED_NAME) return s
264
+ const had = this.names.get(s)
265
+ if (had !== undefined) return had
266
+ if (this.names.size < MAX_INTERNED_NAMES) this.names.set(s, s)
267
+ return s
268
+ }
269
+
270
+ // A time (zig-zag varint ns) as a BigInt, one per distinct time. The
271
+ // same bytes as the last time decoded (the common case: an image's files
272
+ // share an mtime) are that time, without decoding.
273
+ #time(d) {
274
+ const b = d.b
275
+ const s = d.p
276
+ const ls = this.lastTimeAt
277
+ const ln = this.lastTimeLen
278
+ if (ln > 0 && s + ln <= b.length) {
279
+ let k = 0
280
+ while (k < ln && b[s + k] === b[ls + k]) k++
281
+ if (k === ln) {
282
+ d.p = s + ln
283
+ return this.lastTime
284
+ }
285
+ }
286
+ const v = d.varint()
287
+ let t = typeof v === 'bigint' ? v : BigInt(v)
288
+ const had = this.times.get(t)
289
+ if (had !== undefined) t = had
290
+ else if (this.times.size < MAX_INTERNED_TIMES) this.times.set(t, t)
291
+ if (b === this.idx) {
292
+ this.lastTimeAt = s
293
+ this.lastTimeLen = d.p - s
294
+ this.lastTime = t
295
+ }
296
+ return t
297
+ }
298
+
299
+ #entry(d) {
300
+ const pb = d.bytes()
301
+ const path = strOf(pb)
302
+ if (d.p >= d.b.length) d.bad()
303
+ const fl = d.b[d.p++]
304
+ const slash = pb.lastIndexOf(0x2f)
305
+ const e = {
306
+ path,
307
+ dir: slash < 0 ? '' : path.slice(0, slash),
308
+ // Its own string (not a slice that would keep path), shared by equal names.
309
+ name: this.#name(slash < 0 ? pb : pb.subarray(slash + 1)),
310
+ whiteout: (fl & FLAG_WHITEOUT) !== 0,
311
+ opaque: (fl & FLAG_OPAQUE) !== 0,
312
+ mode: 0, uid: 0, gid: 0, rdev: 0,
313
+ atime: 0n, mtime: 0n, ctime: 0n,
314
+ size: 0, off: 0, target: '', link: 0, xattrs: null,
315
+ }
316
+ if (e.whiteout) return e
317
+ e.mode = Number(d.uvarint()) >>> 0
318
+ e.uid = Number(d.uvarint()) >>> 0
319
+ e.gid = Number(d.uvarint()) >>> 0
320
+ e.rdev = d.uvarint()
321
+ e.atime = this.#time(d)
322
+ e.mtime = this.#time(d)
323
+ e.ctime = this.#time(d)
324
+ e.size = d.varint()
325
+ e.off = d.varint()
326
+ if (typeof e.size !== 'number' || typeof e.off !== 'number') d.bad()
327
+ e.target = d.str()
328
+ e.link = d.uvarint()
329
+ let nx = d.uvarint()
330
+ if (typeof nx !== 'number' || nx > d.b.length - d.p) d.bad()
331
+ for (; nx > 0; nx--) {
332
+ const k = d.str()
333
+ const v = d.bytes().slice()
334
+ if (!e.xattrs) e.xattrs = new Map()
335
+ e.xattrs.set(k, v)
336
+ }
337
+ return e
338
+ }
339
+
340
+ get length() {
341
+ return this.count
342
+ }
343
+
344
+ /** Entry i, decoded. */
345
+ entry(i) {
346
+ const d = new Decoder(this.idx)
347
+ d.p = this.pos[i]
348
+ return this.#entry(d)
349
+ }
350
+
351
+ /** Every entry, decoded (for tests and tools: it holds them all). */
352
+ get entries() {
353
+ if (!this.all) {
354
+ this.all = new Array(this.count)
355
+ for (let i = 0; i < this.count; i++) this.all[i] = this.entry(i)
356
+ }
357
+ return this.all
358
+ }
359
+
360
+ // Compares entry i's path with byte string s: <0, 0, >0.
361
+ #cmp(i, s) {
362
+ const b = this.idx
363
+ let p = this.pos[i]
364
+ // The path's uvarint length (checked at open).
365
+ let len = 0
366
+ let mul = 1
367
+ for (;;) {
368
+ const c = b[p++]
369
+ len += (c & 0x7f) * mul
370
+ if (c < 0x80) break
371
+ mul *= 128
372
+ }
373
+ const m = Math.min(len, s.length)
374
+ for (let k = 0; k < m; k++) {
375
+ const x = b[p + k] - s.charCodeAt(k)
376
+ if (x !== 0) return x
377
+ }
378
+ return len - s.length
379
+ }
380
+
381
+ /** The index of the entry at path (whiteouts included), or -1. */
382
+ index(path) {
383
+ let lo = 0
384
+ let hi = this.count - 1
385
+ while (lo <= hi) {
386
+ const mid = (lo + hi) >>> 1
387
+ const c = this.#cmp(mid, path)
388
+ if (c === 0) return mid
389
+ if (c < 0) lo = mid + 1
390
+ else hi = mid - 1
391
+ }
392
+ return -1
393
+ }
394
+
395
+ /** The entry at path (whiteouts included), or undefined. */
396
+ lookup(path) {
397
+ const i = this.index(path)
398
+ return i < 0 ? undefined : this.entry(i)
399
+ }
400
+
401
+ /** Directory path's entries' indexes (whiteouts included), in name order. */
402
+ children(path) {
403
+ let i
404
+ if (path === '') {
405
+ i = this.rootFirst
406
+ } else {
407
+ const d = this.index(path)
408
+ if (d < 0) return []
409
+ i = this.firstChild[d]
410
+ }
411
+ const out = []
412
+ for (; i !== NONE; i = this.nextSibling[i]) out.push(i)
413
+ return out
414
+ }
415
+
416
+ /** Hard-link group link's size and its first entry's index (Go's LinkGroup). */
417
+ linkGroup(link) {
418
+ return this.links.get(link) || { n: 0, first: 0 }
419
+ }
420
+
421
+ /** Up to len bytes of regular file entry e's contents at off. */
422
+ read(e, off, len) {
423
+ if (off >= e.size || len <= 0) return new Uint8Array(0)
424
+ const n = Math.min(len, e.size - off)
425
+ return this.source.read(e.off + off, n)
426
+ }
427
+ }
package/src/fs/seed.js ADDED
@@ -0,0 +1,22 @@
1
+ // What a 9P service's `files` option puts in its tree at boot (bootWmd channels.services[].files).
2
+
3
+ /**
4
+ * One entry of a 9P service's files: its bytes or text, or {data, mode?, uid?, gid?} (P-B11: the agent
5
+ * binary, 0755), or {dir: true, mode?, uid?, gid?} (W65: a directory, such as the image's WORKDIR).
6
+ * Missing parents are made root's, 0755. An entry that names a path the layers have takes its contents
7
+ * (a file) and the mode and owner it gives; the rest of the layer's attributes stay.
8
+ */
9
+ export function seedEntry(fs, path, f) {
10
+ const o = f && typeof f === 'object' && !ArrayBuffer.isView(f) && !(f instanceof ArrayBuffer) ? f : { data: f }
11
+ const attrs = { mode: o.mode, uid: o.uid, gid: o.gid }
12
+ let n
13
+ const slash = path.replace(/\/+$/, '').lastIndexOf('/')
14
+ if (slash > 0) fs.mkdirp(path.slice(0, slash))
15
+ if (o.dir) {
16
+ n = fs.mkdirp(path, o.mode ?? 0o755)
17
+ } else {
18
+ const bytes = typeof o.data === 'string' ? new TextEncoder().encode(o.data) : new Uint8Array(o.data)
19
+ n = fs.writeFile(path, bytes, { mode: o.mode ?? 0o644, uid: o.uid ?? 0, gid: o.gid ?? 0 })
20
+ }
21
+ if (attrs.mode !== undefined || attrs.uid !== undefined || attrs.gid !== undefined) fs.setattr(n, attrs)
22
+ }
@@ -0,0 +1,65 @@
1
+ // P-B9: the file server's state as bytes, and back. What a checkpoint needs of the 9P
2
+ // service: the Overlay's tree (its upper, and every lower inode that has been read, with
3
+ // their identity: inode numbers, link counts, who is linked to whom) and the live
4
+ // connection's fid table (the guest kernel's v9fs holds those fids across a restore; a
5
+ // new server with none would answer every request on an open file with EBADF).
6
+ //
7
+ // The lower layers are not written: they are the image's packs, which the restoring page
8
+ // gets the way the first boot did. Inodes and Contents that read from a pack are written
9
+ // with a token for the pack (its index in the overlay's layer list).
10
+ //
11
+ // The tree goes through graph.js as one graph, so an inode shared by a name and a fid stays
12
+ // one inode.
13
+
14
+ import { Content, DirInode, Inode, Overlay } from './overlay.js'
15
+ import { Fid } from './9p/server.js'
16
+ import { Pack } from './pack.js'
17
+ import { decodeGraph, encodeGraph } from './graph.js'
18
+
19
+ /** Overlay fields the constructor of the restoring process sets (live objects, config). */
20
+ const OVERLAY_SKIP = ['layers', 'now', 'onFull', 'maxUpperBytes']
21
+
22
+ function classesFor(target) {
23
+ return {
24
+ Overlay: { ctor: Overlay, make: () => target, skip: OVERLAY_SKIP },
25
+ Inode: { ctor: Inode, make: () => new Inode(0n, 0) },
26
+ DirInode: { ctor: DirInode, make: () => new DirInode(0n, 0) },
27
+ // Content has private methods: it must come from its constructor.
28
+ Content: { ctor: Content, make: () => new Content(null, null, 0) },
29
+ Fid: { ctor: Fid, make: () => new Fid(null, null, '', 0) },
30
+ }
31
+ }
32
+
33
+ /**
34
+ * The state of overlay (and of the 9P server over it, or null) as graph segments.
35
+ * @returns {{segments: Uint8Array[], bytes: number, inodes: number, fids: number}}
36
+ */
37
+ export function dumpService(overlay, server = null) {
38
+ const special = { pack: v => (v instanceof Pack ? overlay.layers.indexOf(v) : undefined) }
39
+ const root = { overlay, msize: server ? server.msize : 0, fids: server ? server.fids : new Map() }
40
+ const out = encodeGraph(root, { classes: classesFor(overlay), special })
41
+ return { ...out, fids: root.fids.size }
42
+ }
43
+
44
+ /**
45
+ * Replaces the state of `overlay` (built from the same layers by the restoring process) with
46
+ * the dumped one. Returns {msize, fids}: the server state to put on the connection's server.
47
+ */
48
+ export function loadService(bytes, overlay) {
49
+ const special = {
50
+ revive: i => {
51
+ const p = overlay.layers[i]
52
+ if (!p) throw new Error(`service snapshot: it refers to layer ${i}, and this boot has ${overlay.layers.length}`)
53
+ return p
54
+ },
55
+ }
56
+ const root = decodeGraph(bytes, { classes: classesFor(overlay), special })
57
+ if (root.overlay !== overlay) throw new Error('service snapshot: the overlay is not the first object')
58
+ return { msize: root.msize, fids: root.fids }
59
+ }
60
+
61
+ /** Puts a loaded fid table on a NinePServer. */
62
+ export function adoptServer(server, state) {
63
+ server.msize = state.msize
64
+ server.fids = state.fids
65
+ }