File
Blob: src/pyodide/internal/metadatafs.ts
| 1 | // Copyright (c) 2026 Cloudflare, Inc. |
| 2 | // Licensed under the Apache 2.0 license found in the LICENSE file or at: |
| 3 | // https://opensource.org/licenses/Apache-2.0 |
| 4 | |
| 5 | import { default as MetadataReader } from 'pyodide-internal:runtime-generated/metadata'; |
| 6 | import { createReadonlyFS } from 'pyodide-internal:readOnlyFS'; |
| 7 | import { PythonWorkersInternalError } from 'pyodide-internal:util'; |
| 8 | |
| 9 | function createTree(paths: string[]): MetadataDirInfo { |
| 10 | const tree: MetadataFSInfo = new Map(); |
| 11 | paths.forEach((elt: string, idx: number) => { |
| 12 | let subTree: MetadataFSInfo = tree; |
| 13 | const parts = elt.split('/'); |
| 14 | const name = parts.pop()!; |
| 15 | for (const part of parts) { |
| 16 | if (typeof subTree === 'number') { |
| 17 | throw new PythonWorkersInternalError( |
| 18 | 'expected subtree to not be a number' |
| 19 | ); |
| 20 | } |
| 21 | let next: MetadataFSInfo | undefined = subTree.get(part); |
| 22 | if (!next) { |
| 23 | next = new Map(); |
| 24 | subTree.set(part, next); |
| 25 | } |
| 26 | subTree = next; |
| 27 | } |
| 28 | if (typeof subTree === 'number') { |
| 29 | throw new PythonWorkersInternalError( |
| 30 | 'expected subtree to not be a number' |
| 31 | ); |
| 32 | } |
| 33 | subTree.set(name, idx); |
| 34 | }); |
| 35 | return tree; |
| 36 | } |
| 37 | |
| 38 | export function createMetadataFS(Module: Module): object { |
| 39 | // TODO: Make this type more specific |
| 40 | const TIMESTAMP = Date.now(); |
| 41 | const names = MetadataReader.getNames(); |
| 42 | const sizes = MetadataReader.getSizes(); |
| 43 | const rootInfo = createTree(names); |
| 44 | const FSOps: FSOps<MetadataFSInfo> = { |
| 45 | getNodeMode(_parent, _name, info) { |
| 46 | return { |
| 47 | permissions: 0o555, // read and execute but not write |
| 48 | isDir: typeof info !== 'number', |
| 49 | }; |
| 50 | }, |
| 51 | setNodeAttributes(node, info, isDir) { |
| 52 | node.modtime = TIMESTAMP; |
| 53 | node.usedBytes = 0; |
| 54 | if (info === undefined) { |
| 55 | info = rootInfo; |
| 56 | } |
| 57 | if (isDir) { |
| 58 | node.tree = info as MetadataDirInfo; |
| 59 | } else { |
| 60 | node.index = info as number; |
| 61 | node.usedBytes = sizes[info as number]!; |
| 62 | } |
| 63 | }, |
| 64 | readdir(node) { |
| 65 | if (node.tree == undefined) { |
| 66 | throw new PythonWorkersInternalError( |
| 67 | 'cannot read directory, tree is undefined' |
| 68 | ); |
| 69 | } |
| 70 | return Array.from(node.tree.keys()); |
| 71 | }, |
| 72 | lookup(parent, name) { |
| 73 | // Parent is not a directory so we always raise ENOENT (44) |
| 74 | if (parent.tree == undefined) { |
| 75 | throw new Module.FS.ErrnoError(44); |
| 76 | } |
| 77 | const res = parent.tree.get(name); |
| 78 | if (res === undefined) { |
| 79 | throw new Module.FS.ErrnoError(44); |
| 80 | } |
| 81 | return res; |
| 82 | }, |
| 83 | read(stream, position, buffer) { |
| 84 | return MetadataReader.read(stream.node.index!, position, buffer); |
| 85 | }, |
| 86 | }; |
| 87 | |
| 88 | return createReadonlyFS(FSOps, Module); |
| 89 | } |