@fnndsc/salsa 4.4.3 → 4.5.0
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.
- package/dist/plugininstances/index.d.ts +13 -0
- package/dist/plugininstances/index.js +32 -0
- package/dist/plugininstances/index.js.map +1 -1
- package/dist/plugins/index.d.ts +7 -0
- package/dist/plugins/index.js +7 -0
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/plugin_add.d.ts +173 -0
- package/dist/plugins/plugin_add.js +325 -0
- package/dist/plugins/plugin_add.js.map +1 -0
- package/dist/plugins/plugin_docker.d.ts +90 -0
- package/dist/plugins/plugin_docker.js +158 -0
- package/dist/plugins/plugin_docker.js.map +1 -0
- package/dist/plugins/plugin_index.d.ts +157 -0
- package/dist/plugins/plugin_index.js +338 -0
- package/dist/plugins/plugin_index.js.map +1 -0
- package/dist/plugins/plugin_sync.d.ts +276 -0
- package/dist/plugins/plugin_sync.js +453 -0
- package/dist/plugins/plugin_sync.js.map +1 -0
- package/dist/plugins/plugin_validate.d.ts +224 -0
- package/dist/plugins/plugin_validate.js +395 -0
- package/dist/plugins/plugin_validate.js.map +1 -0
- package/dist/plugins/pool.d.ts +46 -0
- package/dist/plugins/pool.js +63 -0
- package/dist/plugins/pool.js.map +1 -0
- package/dist/plugins/tar_read.d.ts +73 -0
- package/dist/plugins/tar_read.js +190 -0
- package/dist/plugins/tar_read.js.map +1 -0
- package/dist/vfs/providers/proc.js +44 -14
- package/dist/vfs/providers/proc.js.map +1 -1
- package/package.json +6 -5
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Run a list of tasks a few at a time.
|
|
3
|
+
*
|
|
4
|
+
* Plugin uploads and registry lookups each run four at a time: enough to
|
|
5
|
+
* finish 175 in seconds, few enough not to hammer a CUBE or a registry. Each
|
|
6
|
+
* task's failure stays its own; the pool never stops early.
|
|
7
|
+
*
|
|
8
|
+
* Key components:
|
|
9
|
+
* - POOL_WIDTH: the number of tasks run at once.
|
|
10
|
+
* - items_mapPooled: map items through an async function, a few at a time, in order.
|
|
11
|
+
*
|
|
12
|
+
* Typical usage:
|
|
13
|
+
* ```ts
|
|
14
|
+
* const sizes: number[] = await items_mapPooled(urls, POOL_WIDTH, async (url: string): Promise<number> => size_get(url));
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* Dependencies: none.
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
/** Tasks run at once by the plugin verbs. */
|
|
22
|
+
export declare const POOL_WIDTH: number;
|
|
23
|
+
/**
|
|
24
|
+
* Maps items through an async function, at most `width` at a time.
|
|
25
|
+
*
|
|
26
|
+
* Results come back in the items' order, whatever order the tasks finish in.
|
|
27
|
+
* A task that throws is not caught here: map to a value that records the
|
|
28
|
+
* failure when each failure must stay its own.
|
|
29
|
+
*
|
|
30
|
+
* A stop (the signal aborting) lets the tasks already running finish and
|
|
31
|
+
* starts no more, so what was sent is all accounted for; the items never
|
|
32
|
+
* started have no result.
|
|
33
|
+
*
|
|
34
|
+
* @param items - The items.
|
|
35
|
+
* @param width - Tasks run at once; at least 1.
|
|
36
|
+
* @param task - The work for one item.
|
|
37
|
+
* @param signal - Stops the map; absent, it runs to the end.
|
|
38
|
+
* @returns The started items' results, in the items' order: every item's
|
|
39
|
+
* unless stopped.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* await items_mapPooled([1, 2, 3], 2, async (n: number): Promise<number> => n * 2); // [2, 4, 6]
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare function items_mapPooled<T, R>(items: readonly T[], width: number, task: (item: T, index: number) => Promise<R>, signal?: AbortSignal): Promise<R[]>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file Run a list of tasks a few at a time.
|
|
4
|
+
*
|
|
5
|
+
* Plugin uploads and registry lookups each run four at a time: enough to
|
|
6
|
+
* finish 175 in seconds, few enough not to hammer a CUBE or a registry. Each
|
|
7
|
+
* task's failure stays its own; the pool never stops early.
|
|
8
|
+
*
|
|
9
|
+
* Key components:
|
|
10
|
+
* - POOL_WIDTH: the number of tasks run at once.
|
|
11
|
+
* - items_mapPooled: map items through an async function, a few at a time, in order.
|
|
12
|
+
*
|
|
13
|
+
* Typical usage:
|
|
14
|
+
* ```ts
|
|
15
|
+
* const sizes: number[] = await items_mapPooled(urls, POOL_WIDTH, async (url: string): Promise<number> => size_get(url));
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* Dependencies: none.
|
|
19
|
+
*
|
|
20
|
+
* @module
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.POOL_WIDTH = void 0;
|
|
24
|
+
exports.items_mapPooled = items_mapPooled;
|
|
25
|
+
/** Tasks run at once by the plugin verbs. */
|
|
26
|
+
exports.POOL_WIDTH = 4;
|
|
27
|
+
/**
|
|
28
|
+
* Maps items through an async function, at most `width` at a time.
|
|
29
|
+
*
|
|
30
|
+
* Results come back in the items' order, whatever order the tasks finish in.
|
|
31
|
+
* A task that throws is not caught here: map to a value that records the
|
|
32
|
+
* failure when each failure must stay its own.
|
|
33
|
+
*
|
|
34
|
+
* A stop (the signal aborting) lets the tasks already running finish and
|
|
35
|
+
* starts no more, so what was sent is all accounted for; the items never
|
|
36
|
+
* started have no result.
|
|
37
|
+
*
|
|
38
|
+
* @param items - The items.
|
|
39
|
+
* @param width - Tasks run at once; at least 1.
|
|
40
|
+
* @param task - The work for one item.
|
|
41
|
+
* @param signal - Stops the map; absent, it runs to the end.
|
|
42
|
+
* @returns The started items' results, in the items' order: every item's
|
|
43
|
+
* unless stopped.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* await items_mapPooled([1, 2, 3], 2, async (n: number): Promise<number> => n * 2); // [2, 4, 6]
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
async function items_mapPooled(items, width, task, signal) {
|
|
51
|
+
const results = new Array(items.length);
|
|
52
|
+
let next = 0;
|
|
53
|
+
const worker = async () => {
|
|
54
|
+
while (next < items.length && signal?.aborted !== true) {
|
|
55
|
+
const index = next;
|
|
56
|
+
next += 1;
|
|
57
|
+
results[index] = await task(items[index], index);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
await Promise.all(Array.from({ length: Math.max(1, Math.min(width, items.length)) }, worker));
|
|
61
|
+
return results.slice(0, next);
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=pool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.js","sourceRoot":"","sources":["../../src/plugins/pool.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;;AA4BH,0CAiBC;AA3CD,6CAA6C;AAChC,QAAA,UAAU,GAAW,CAAC,CAAC;AAEpC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACI,KAAK,UAAU,eAAe,CACnC,KAAmB,EACnB,KAAa,EACb,IAA4C,EAC5C,MAAoB;IAEpB,MAAM,OAAO,GAAQ,IAAI,KAAK,CAAI,KAAK,CAAC,MAAM,CAAC,CAAC;IAChD,IAAI,IAAI,GAAW,CAAC,CAAC;IACrB,MAAM,MAAM,GAAG,KAAK,IAAmB,EAAE;QACvC,OAAO,IAAI,GAAG,KAAK,CAAC,MAAM,IAAI,MAAM,EAAE,OAAO,KAAK,IAAI,EAAE,CAAC;YACvD,MAAM,KAAK,GAAW,IAAI,CAAC;YAC3B,IAAI,IAAI,CAAC,CAAC;YACV,OAAO,CAAC,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAM,EAAE,KAAK,CAAC,CAAC;QACxD,CAAC;IACH,CAAC,CAAC;IACF,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9F,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AAChC,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file A small reader for ustar archives, gzipped or not.
|
|
3
|
+
*
|
|
4
|
+
* A plugin index arrives as GitHub's archive of a ref (a gzipped ustar file
|
|
5
|
+
* with a pax global header naming the commit) or as a `.tar.gz` an operator
|
|
6
|
+
* uploaded. Both need only regular files and pax headers read, so this is a
|
|
7
|
+
* reader for that much of the format and no more: no links, no devices, no
|
|
8
|
+
* sparse files. Entries are returned in archive order with their paths as
|
|
9
|
+
* the archive names them.
|
|
10
|
+
*
|
|
11
|
+
* A path that climbs out of the archive (`..`) or starts at the root is
|
|
12
|
+
* refused outright rather than skipped: an archive that carries one is not
|
|
13
|
+
* one this reader should be trusted to have read correctly.
|
|
14
|
+
*
|
|
15
|
+
* Key components:
|
|
16
|
+
* - TarFile: one regular file of the archive, its path and bytes.
|
|
17
|
+
* - TarArchive: the files and the pax global header's records.
|
|
18
|
+
* - tarArchive_read: read an archive's regular files and global header.
|
|
19
|
+
*
|
|
20
|
+
* Typical usage:
|
|
21
|
+
* ```ts
|
|
22
|
+
* const archive: TarArchive = tarArchive_read(bytes);
|
|
23
|
+
* const commit: string | undefined = archive.globals['comment'];
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* Dependencies: Node.js built-ins only (`node:zlib` for gzip).
|
|
27
|
+
*
|
|
28
|
+
* @module
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* One regular file read from an archive.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const file: TarFile = { path: 'repo-abc/plugins/pl-x.json', content: Buffer.from('{}') };
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export interface TarFile {
|
|
39
|
+
/** The file's path as the archive names it, prefix and pax `path` applied. */
|
|
40
|
+
readonly path: string;
|
|
41
|
+
/** The file's bytes. */
|
|
42
|
+
readonly content: Buffer;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* What an archive holds that a reader of plugin indexes needs.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* const archive: TarArchive = tarArchive_read(bytes);
|
|
50
|
+
* archive.files.length;
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export interface TarArchive {
|
|
54
|
+
/** Every regular file, in archive order. */
|
|
55
|
+
readonly files: TarFile[];
|
|
56
|
+
/** The pax global header's records (GitHub writes the commit under `comment`). */
|
|
57
|
+
readonly globals: Record<string, string>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Reads an archive's regular files and its pax global header.
|
|
61
|
+
*
|
|
62
|
+
* @param bytes - The archive, gzipped or plain ustar.
|
|
63
|
+
* @returns The regular files in archive order and the global records.
|
|
64
|
+
* @throws {Error} When the bytes are not a readable archive, or an entry's
|
|
65
|
+
* path is absolute or climbs out with `..`.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* const archive: TarArchive = tarArchive_read(readFileSync('index.tar.gz'));
|
|
70
|
+
* archive.files.map((file: TarFile): string => file.path);
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export declare function tarArchive_read(bytes: Buffer): TarArchive;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file A small reader for ustar archives, gzipped or not.
|
|
4
|
+
*
|
|
5
|
+
* A plugin index arrives as GitHub's archive of a ref (a gzipped ustar file
|
|
6
|
+
* with a pax global header naming the commit) or as a `.tar.gz` an operator
|
|
7
|
+
* uploaded. Both need only regular files and pax headers read, so this is a
|
|
8
|
+
* reader for that much of the format and no more: no links, no devices, no
|
|
9
|
+
* sparse files. Entries are returned in archive order with their paths as
|
|
10
|
+
* the archive names them.
|
|
11
|
+
*
|
|
12
|
+
* A path that climbs out of the archive (`..`) or starts at the root is
|
|
13
|
+
* refused outright rather than skipped: an archive that carries one is not
|
|
14
|
+
* one this reader should be trusted to have read correctly.
|
|
15
|
+
*
|
|
16
|
+
* Key components:
|
|
17
|
+
* - TarFile: one regular file of the archive, its path and bytes.
|
|
18
|
+
* - TarArchive: the files and the pax global header's records.
|
|
19
|
+
* - tarArchive_read: read an archive's regular files and global header.
|
|
20
|
+
*
|
|
21
|
+
* Typical usage:
|
|
22
|
+
* ```ts
|
|
23
|
+
* const archive: TarArchive = tarArchive_read(bytes);
|
|
24
|
+
* const commit: string | undefined = archive.globals['comment'];
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* Dependencies: Node.js built-ins only (`node:zlib` for gzip).
|
|
28
|
+
*
|
|
29
|
+
* @module
|
|
30
|
+
*/
|
|
31
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
+
exports.tarArchive_read = tarArchive_read;
|
|
33
|
+
const node_zlib_1 = require("node:zlib");
|
|
34
|
+
/** The tar block size: headers and padded data both come in these. */
|
|
35
|
+
const BLOCK_BYTES = 512;
|
|
36
|
+
/** The first two bytes of a gzip stream. */
|
|
37
|
+
const GZIP_MAGIC = [0x1f, 0x8b];
|
|
38
|
+
/** Header field offsets and widths, as the ustar format fixes them. */
|
|
39
|
+
const FIELD = {
|
|
40
|
+
name: [0, 100],
|
|
41
|
+
size: [124, 12],
|
|
42
|
+
typeflag: [156, 1],
|
|
43
|
+
magic: [257, 6],
|
|
44
|
+
prefix: [345, 155],
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Reads an archive's regular files and its pax global header.
|
|
48
|
+
*
|
|
49
|
+
* @param bytes - The archive, gzipped or plain ustar.
|
|
50
|
+
* @returns The regular files in archive order and the global records.
|
|
51
|
+
* @throws {Error} When the bytes are not a readable archive, or an entry's
|
|
52
|
+
* path is absolute or climbs out with `..`.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* const archive: TarArchive = tarArchive_read(readFileSync('index.tar.gz'));
|
|
57
|
+
* archive.files.map((file: TarFile): string => file.path);
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
function tarArchive_read(bytes) {
|
|
61
|
+
const tar = gzip_is(bytes) ? (0, node_zlib_1.gunzipSync)(bytes) : bytes;
|
|
62
|
+
const files = [];
|
|
63
|
+
const globals = {};
|
|
64
|
+
let pending = {};
|
|
65
|
+
let offset = 0;
|
|
66
|
+
while (offset + BLOCK_BYTES <= tar.length) {
|
|
67
|
+
const header = tar.subarray(offset, offset + BLOCK_BYTES);
|
|
68
|
+
if (block_isZero(header))
|
|
69
|
+
break;
|
|
70
|
+
if (!header.subarray(FIELD.magic[0], FIELD.magic[0] + 5).equals(Buffer.from('ustar'))) {
|
|
71
|
+
throw new Error(`not a ustar archive (no ustar magic at byte ${offset})`);
|
|
72
|
+
}
|
|
73
|
+
const size = headerSize_parse(header);
|
|
74
|
+
const type = field_read(header, FIELD.typeflag);
|
|
75
|
+
const body = tar.subarray(offset + BLOCK_BYTES, offset + BLOCK_BYTES + size);
|
|
76
|
+
if (body.length < size)
|
|
77
|
+
throw new Error('archive ends inside an entry');
|
|
78
|
+
offset += BLOCK_BYTES + Math.ceil(size / BLOCK_BYTES) * BLOCK_BYTES;
|
|
79
|
+
if (type === 'g') {
|
|
80
|
+
Object.assign(globals, paxRecords_parse(body));
|
|
81
|
+
}
|
|
82
|
+
else if (type === 'x') {
|
|
83
|
+
pending = paxRecords_parse(body);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const named = headerPath_read(header);
|
|
87
|
+
const path = pending['path'] ?? named;
|
|
88
|
+
pending = {};
|
|
89
|
+
path_check(path);
|
|
90
|
+
if (type === '0' || type === '' || type === '\0')
|
|
91
|
+
files.push({ path, content: Buffer.from(body) });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return { files, globals };
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Reports whether bytes start a gzip stream.
|
|
98
|
+
*
|
|
99
|
+
* @param bytes - The candidate bytes.
|
|
100
|
+
* @returns True on the gzip magic number.
|
|
101
|
+
*/
|
|
102
|
+
function gzip_is(bytes) {
|
|
103
|
+
return bytes[0] === GZIP_MAGIC[0] && bytes[1] === GZIP_MAGIC[1];
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Reports whether a block is all zero bytes (the archive's end marker).
|
|
107
|
+
*
|
|
108
|
+
* @param block - One 512-byte block.
|
|
109
|
+
* @returns True when every byte is zero.
|
|
110
|
+
*/
|
|
111
|
+
function block_isZero(block) {
|
|
112
|
+
return block.every((byte) => byte === 0);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Reads a NUL-terminated header field as text.
|
|
116
|
+
*
|
|
117
|
+
* @param header - The header block.
|
|
118
|
+
* @param field - The field's offset and width.
|
|
119
|
+
* @returns The field's text, up to its first NUL.
|
|
120
|
+
*/
|
|
121
|
+
function field_read(header, field) {
|
|
122
|
+
const raw = header.subarray(field[0], field[0] + field[1]).toString('utf8');
|
|
123
|
+
const nul = raw.indexOf('\0');
|
|
124
|
+
return nul === -1 ? raw : raw.slice(0, nul);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Reads an entry's size from its octal header field.
|
|
128
|
+
*
|
|
129
|
+
* @param header - The header block.
|
|
130
|
+
* @returns The size in bytes.
|
|
131
|
+
* @throws {Error} When the field is not octal (base-256 sizes are not read).
|
|
132
|
+
*/
|
|
133
|
+
function headerSize_parse(header) {
|
|
134
|
+
const text = field_read(header, FIELD.size).trim();
|
|
135
|
+
if (!/^[0-7]*$/.test(text))
|
|
136
|
+
throw new Error(`unreadable entry size '${text}'`);
|
|
137
|
+
return text === '' ? 0 : parseInt(text, 8);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Reads an entry's path from the header's prefix and name fields.
|
|
141
|
+
*
|
|
142
|
+
* @param header - The header block.
|
|
143
|
+
* @returns The joined path.
|
|
144
|
+
*/
|
|
145
|
+
function headerPath_read(header) {
|
|
146
|
+
const name = field_read(header, FIELD.name);
|
|
147
|
+
const prefix = field_read(header, FIELD.prefix);
|
|
148
|
+
return prefix === '' ? name : `${prefix}/${name}`;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Parses pax extended-header records (`<len> <key>=<value>\n`).
|
|
152
|
+
*
|
|
153
|
+
* @param body - The header's data.
|
|
154
|
+
* @returns The records by key.
|
|
155
|
+
* @throws {Error} When a record's length does not fit the data.
|
|
156
|
+
*/
|
|
157
|
+
function paxRecords_parse(body) {
|
|
158
|
+
const records = {};
|
|
159
|
+
let at = 0;
|
|
160
|
+
while (at < body.length) {
|
|
161
|
+
const space = body.indexOf(0x20, at);
|
|
162
|
+
if (space === -1)
|
|
163
|
+
break;
|
|
164
|
+
const length = parseInt(body.subarray(at, space).toString('utf8'), 10);
|
|
165
|
+
if (!Number.isInteger(length) || length <= 0 || at + length > body.length) {
|
|
166
|
+
throw new Error('unreadable pax header record');
|
|
167
|
+
}
|
|
168
|
+
const record = body.subarray(space + 1, at + length - 1).toString('utf8');
|
|
169
|
+
const equals = record.indexOf('=');
|
|
170
|
+
if (equals !== -1)
|
|
171
|
+
records[record.slice(0, equals)] = record.slice(equals + 1);
|
|
172
|
+
at += length;
|
|
173
|
+
}
|
|
174
|
+
return records;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Refuses a path that is absolute or climbs out of the archive.
|
|
178
|
+
*
|
|
179
|
+
* @param path - The entry's path.
|
|
180
|
+
* @throws {Error} When the path starts at the root or holds a `..` segment.
|
|
181
|
+
*/
|
|
182
|
+
function path_check(path) {
|
|
183
|
+
if (path.startsWith('/') || /^[A-Za-z]:[\\/]/.test(path)) {
|
|
184
|
+
throw new Error(`archive entry '${path}' is an absolute path`);
|
|
185
|
+
}
|
|
186
|
+
if (path.split(/[\\/]/).includes('..')) {
|
|
187
|
+
throw new Error(`archive entry '${path}' climbs out of the archive`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=tar_read.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tar_read.js","sourceRoot":"","sources":["../../src/plugins/tar_read.ts"],"names":[],"mappings":"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"}
|
|
@@ -219,6 +219,35 @@ async function cache_ensure() {
|
|
|
219
219
|
await procCache_build();
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Ensures what one path needs. `/proc/jobs` itself needs the whole index. A
|
|
224
|
+
* path naming one feed needs that feed alone: its row is read and kept while
|
|
225
|
+
* the index builds on behind, so a fresh session's `ls /proc/jobs/feed_N` (or
|
|
226
|
+
* a `cd` into its `data`) answers in that feed's time, not every feed's
|
|
227
|
+
* (#1364: minutes before the first line, with the index warming).
|
|
228
|
+
*
|
|
229
|
+
* @param pathStr - The path asked for.
|
|
230
|
+
*/
|
|
231
|
+
async function cacheFor_ensure(pathStr) {
|
|
232
|
+
const cache = (0, cumin_1.procCache_get)();
|
|
233
|
+
if (cache.built)
|
|
234
|
+
return;
|
|
235
|
+
const { feedID } = procPath_parse(pathStr.replace(/\/$/, ''));
|
|
236
|
+
if (feedID === null) {
|
|
237
|
+
await cache_ensure();
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
// The index lands behind the prompt; this read does not wait for it.
|
|
241
|
+
procCache_build().catch(() => { });
|
|
242
|
+
if (cache.feed_get(feedID) !== undefined)
|
|
243
|
+
return;
|
|
244
|
+
const client = await cumin_1.chrisConnection.client_get();
|
|
245
|
+
if (!client)
|
|
246
|
+
return;
|
|
247
|
+
const row = await feedRow_fetch(client, feedID);
|
|
248
|
+
if (row !== null)
|
|
249
|
+
cache.feed_add(procFeed_create(row.feed));
|
|
250
|
+
}
|
|
222
251
|
/**
|
|
223
252
|
* Loads instance topology for a single feed.
|
|
224
253
|
* Uses in-flight map to prevent duplicate API calls.
|
|
@@ -494,7 +523,10 @@ function outputPath_normalize(value) {
|
|
|
494
523
|
* @param remainder - The path below `data`, '' for the link itself.
|
|
495
524
|
* @returns The absolute CFS path, or Err when the job has no output space.
|
|
496
525
|
*/
|
|
497
|
-
async function dataTarget_resolve(instanceID, remainder) {
|
|
526
|
+
async function dataTarget_resolve(instanceID, remainder, feedID = null) {
|
|
527
|
+
// A fresh session knows no job until its feed's topology is read: read it, as a listing of the node would.
|
|
528
|
+
if (feedID !== null && (0, cumin_1.procCache_get)().instance_get(instanceID) === undefined)
|
|
529
|
+
await feedInstances_ensureLoaded(feedID);
|
|
498
530
|
const outputPath = await instanceOutputPath_ensure(instanceID);
|
|
499
531
|
if (!outputPath) {
|
|
500
532
|
cumin_1.errorStack.stack_push('error', `No CFS output path is available for job ${instanceID}`);
|
|
@@ -567,18 +599,16 @@ class ProcVfsProvider {
|
|
|
567
599
|
* @returns The authoritative CFS output path, or an error when absent.
|
|
568
600
|
*/
|
|
569
601
|
async linkTarget_resolve(pathStr) {
|
|
570
|
-
await
|
|
602
|
+
await cacheFor_ensure(pathStr);
|
|
571
603
|
const clean = pathStr.replace(/\/$/, '');
|
|
572
604
|
const { feedID, instanceID, virtualFile } = procPath_parse(clean);
|
|
573
605
|
if (feedID === null || instanceID === null || virtualFile !== 'data') {
|
|
574
606
|
cumin_1.errorStack.stack_push('error', `Not a /proc job data link: ${pathStr}`);
|
|
575
607
|
return (0, cumin_1.Err)();
|
|
576
608
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
cumin_1.errorStack.stack_push('error', `No CFS output path is available for job ${instanceID}`);
|
|
581
|
-
return (0, cumin_1.Err)();
|
|
609
|
+
// The link's target: the job's output, its feed read first when this session has not (#1364: `download` of
|
|
610
|
+
// the link on a fresh session found no job and said the file could not be opened).
|
|
611
|
+
return dataTarget_resolve(instanceID, '', feedID);
|
|
582
612
|
}
|
|
583
613
|
/**
|
|
584
614
|
* Everything beneath a `/proc/jobs` path from the cache, in the order its
|
|
@@ -673,7 +703,7 @@ class ProcVfsProvider {
|
|
|
673
703
|
return { entries, unanswered };
|
|
674
704
|
}
|
|
675
705
|
async list(pathStr, _options) {
|
|
676
|
-
await
|
|
706
|
+
await cacheFor_ensure(pathStr);
|
|
677
707
|
const cache = (0, cumin_1.procCache_get)();
|
|
678
708
|
const clean = pathStr.replace(/\/$/, '');
|
|
679
709
|
// /proc/jobs — list all feeds with aggregate status from counters
|
|
@@ -699,7 +729,7 @@ class ProcVfsProvider {
|
|
|
699
729
|
// target: resolve and delegate, so `ls .../data` (and deeper) shows the
|
|
700
730
|
// target's real contents instead of silently re-listing the instance.
|
|
701
731
|
if (instanceID !== null && virtualFile === 'data' && dataRemainder !== null) {
|
|
702
|
-
const target = await dataTarget_resolve(instanceID, dataRemainder);
|
|
732
|
+
const target = await dataTarget_resolve(instanceID, dataRemainder, feedID);
|
|
703
733
|
if (!target.ok)
|
|
704
734
|
return (0, cumin_1.Err)();
|
|
705
735
|
const { vfsDispatcher } = await import('../dispatcher.js');
|
|
@@ -796,11 +826,11 @@ class ProcVfsProvider {
|
|
|
796
826
|
* @returns The file's bytes.
|
|
797
827
|
*/
|
|
798
828
|
async bytes_read(pathStr) {
|
|
799
|
-
await
|
|
829
|
+
await cacheFor_ensure(pathStr);
|
|
800
830
|
const clean = pathStr.replace(/\/$/, '');
|
|
801
|
-
const { instanceID, virtualFile, dataRemainder } = procPath_parse(clean);
|
|
831
|
+
const { feedID, instanceID, virtualFile, dataRemainder } = procPath_parse(clean);
|
|
802
832
|
if (instanceID !== null && virtualFile === 'data' && dataRemainder !== null && dataRemainder !== '') {
|
|
803
|
-
const target = await dataTarget_resolve(instanceID, dataRemainder);
|
|
833
|
+
const target = await dataTarget_resolve(instanceID, dataRemainder, feedID);
|
|
804
834
|
if (!target.ok)
|
|
805
835
|
return (0, cumin_1.Err)();
|
|
806
836
|
// The target is a CFS file, and the dispatcher's default provider is
|
|
@@ -818,13 +848,13 @@ class ProcVfsProvider {
|
|
|
818
848
|
return (0, fond_1.vfsOutcome_convertFromResult)(await this.text_read(pathStr), 'ENOENT');
|
|
819
849
|
}
|
|
820
850
|
async text_read(pathStr) {
|
|
821
|
-
await
|
|
851
|
+
await cacheFor_ensure(pathStr);
|
|
822
852
|
const cache = (0, cumin_1.procCache_get)();
|
|
823
853
|
const clean = pathStr.replace(/\/$/, '');
|
|
824
854
|
const { feedID, instanceID, virtualFile, dataRemainder } = procPath_parse(clean);
|
|
825
855
|
// A file under the `data` link is the CFS target's file.
|
|
826
856
|
if (instanceID !== null && virtualFile === 'data' && dataRemainder !== null && dataRemainder !== '') {
|
|
827
|
-
const target = await dataTarget_resolve(instanceID, dataRemainder);
|
|
857
|
+
const target = await dataTarget_resolve(instanceID, dataRemainder, feedID);
|
|
828
858
|
if (!target.ok)
|
|
829
859
|
return (0, cumin_1.Err)();
|
|
830
860
|
const { fileContent_get } = await import('../../files/index.js');
|