buninu-linux 0.7.3 → 0.7.5
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/NOTICE.md +46 -46
- package/README.md +23 -5
- package/build-uki.sh +21 -7
- package/check_build.js +217 -0
- package/fetch-alpine.sh +3 -0
- package/fetch-bun.sh +3 -0
- package/index.js +72 -8
- package/initramfs/bin/dd +126 -0
- package/initramfs/bin/gunzip +6 -0
- package/initramfs/bin/gzip +6 -0
- package/initramfs/bin/mcopy +6 -0
- package/initramfs/bin/mdir +6 -0
- package/initramfs/bin/mkfs.fat +73 -0
- package/initramfs/bin/mmd +6 -0
- package/initramfs/bin/parted +178 -0
- package/initramfs/bin/truncate +78 -0
- package/initramfs/bin/unzip +248 -0
- package/initramfs/bin/zcat +6 -0
- package/initramfs/lib/crc32.js +16 -0
- package/initramfs/lib/fat.js +534 -0
- package/initramfs/lib/gpt.js +144 -0
- package/initramfs/lib/gzip.js +115 -0
- package/initramfs/lib/mtools.js +158 -0
- package/package.json +1 -1
- package/scripts/arch.js +156 -0
- package/scripts/arch.sh +5 -5
- package/scripts/build.js +145 -0
- package/scripts/cpio.js +107 -0
- package/scripts/fetch.js +194 -0
- package/scripts/modules.js +158 -0
- package/scripts/pack-initramfs.sh +33 -0
- package/scripts/pack.js +184 -0
- package/scripts/uki.js +136 -0
- package/test/js-tools.test.js +213 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
#!/bin/bun
|
|
2
|
+
// unzip — list, test and extract zip archives (stored and deflated members,
|
|
3
|
+
// zip64 included) over node:zlib.
|
|
4
|
+
//
|
|
5
|
+
// Supported: -p (members to standard output), -l (list), -t (test),
|
|
6
|
+
// -d DIR, -o (overwrite), -n (never overwrite), -j (junk paths), -q/-qq;
|
|
7
|
+
// MEMBER operands may be wildcards. Unix modes and symlinks are restored.
|
|
8
|
+
// Runs under node as well as bun.
|
|
9
|
+
|
|
10
|
+
import { chmodSync, closeSync, fstatSync, lstatSync, mkdirSync, openSync, readSync, rmSync, symlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
11
|
+
import { basename, dirname, join, normalize, resolve } from "node:path";
|
|
12
|
+
import { inflateRawSync } from "node:zlib";
|
|
13
|
+
import { crc32 } from "../lib/crc32.js";
|
|
14
|
+
|
|
15
|
+
const fail = (message, status = 1) => {
|
|
16
|
+
console.error(`unzip: ${message}`);
|
|
17
|
+
process.exit(status);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const usage = () => {
|
|
21
|
+
console.error(`Usage:
|
|
22
|
+
unzip [-o|-n] [-j] [-q] ZIP [MEMBER...] [-d DIR]
|
|
23
|
+
unzip -p ZIP [MEMBER...]
|
|
24
|
+
unzip -l ZIP [MEMBER...]
|
|
25
|
+
unzip -t ZIP`);
|
|
26
|
+
process.exit(10);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
let mode = "extract";
|
|
30
|
+
let overwrite = null; // null: refuse, true: -o, false: -n
|
|
31
|
+
let junkPaths = false;
|
|
32
|
+
let quiet = 0;
|
|
33
|
+
let directory = ".";
|
|
34
|
+
const operands = [];
|
|
35
|
+
const argv = process.argv.slice(2);
|
|
36
|
+
for (let index = 0; index < argv.length; index++) {
|
|
37
|
+
const argument = argv[index];
|
|
38
|
+
if (argument === "-h" || argument === "--help") usage();
|
|
39
|
+
if (argument === "-d") {
|
|
40
|
+
if (index + 1 >= argv.length) fail("-d requires a directory", 10);
|
|
41
|
+
directory = argv[++index];
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (argument.startsWith("-d") && argument.length > 2) { directory = argument.slice(2); continue; }
|
|
45
|
+
if (argument.startsWith("-") && argument.length > 1) {
|
|
46
|
+
for (const letter of argument.slice(1)) {
|
|
47
|
+
if (letter === "p") mode = "pipe";
|
|
48
|
+
else if (letter === "l") mode = "list";
|
|
49
|
+
else if (letter === "t") mode = "test";
|
|
50
|
+
else if (letter === "o") overwrite = true;
|
|
51
|
+
else if (letter === "n") overwrite = false;
|
|
52
|
+
else if (letter === "j") junkPaths = true;
|
|
53
|
+
else if (letter === "q") quiet++;
|
|
54
|
+
else fail(`unknown option -${letter}`, 10);
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
operands.push(argument);
|
|
59
|
+
}
|
|
60
|
+
if (operands.length === 0) usage();
|
|
61
|
+
const [archive, ...patterns] = operands;
|
|
62
|
+
|
|
63
|
+
const fd = (() => {
|
|
64
|
+
try { return openSync(archive, "r"); } catch (error) { return fail(`cannot open ${archive}: ${error.message}`, 9); }
|
|
65
|
+
})();
|
|
66
|
+
const size = fstatSync(fd).size;
|
|
67
|
+
const read = (position, length) => {
|
|
68
|
+
const buffer = Buffer.alloc(length);
|
|
69
|
+
let done = 0;
|
|
70
|
+
while (done < length) {
|
|
71
|
+
const count = readSync(fd, buffer, done, length - done, position + done);
|
|
72
|
+
if (count === 0) fail(`${archive}: unexpected end of file`, 3);
|
|
73
|
+
done += count;
|
|
74
|
+
}
|
|
75
|
+
return buffer;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// End of central directory: in the last 64 KiB + 22 bytes, searched backwards.
|
|
79
|
+
const tailLength = Math.min(size, 65557);
|
|
80
|
+
const tail = read(size - tailLength, tailLength);
|
|
81
|
+
let eocd = -1;
|
|
82
|
+
for (let offset = tailLength - 22; offset >= 0; offset--) {
|
|
83
|
+
if (tail.readUInt32LE(offset) === 0x06054b50) { eocd = offset; break; }
|
|
84
|
+
}
|
|
85
|
+
if (eocd < 0) fail(`${archive}: not a zip archive (no end of central directory)`, 9);
|
|
86
|
+
let entryCount = tail.readUInt16LE(eocd + 10);
|
|
87
|
+
let directorySize = tail.readUInt32LE(eocd + 12);
|
|
88
|
+
let directoryOffset = tail.readUInt32LE(eocd + 16);
|
|
89
|
+
// zip64: the locator sits just before the end of central directory record.
|
|
90
|
+
if (eocd >= 20 && tail.readUInt32LE(eocd - 20) === 0x07064b50) {
|
|
91
|
+
const record = read(Number(tail.readBigUInt64LE(eocd - 20 + 8)), 56);
|
|
92
|
+
if (record.readUInt32LE(0) !== 0x06064b50) fail(`${archive}: bad zip64 end of central directory`, 3);
|
|
93
|
+
entryCount = Number(record.readBigUInt64LE(32));
|
|
94
|
+
directorySize = Number(record.readBigUInt64LE(40));
|
|
95
|
+
directoryOffset = Number(record.readBigUInt64LE(48));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const entries = [];
|
|
99
|
+
const central = read(directoryOffset, directorySize);
|
|
100
|
+
for (let offset = 0, index = 0; index < entryCount; index++) {
|
|
101
|
+
if (central.readUInt32LE(offset) !== 0x02014b50) fail(`${archive}: bad central directory entry ${index}`, 3);
|
|
102
|
+
const nameLength = central.readUInt16LE(offset + 28);
|
|
103
|
+
const extraLength = central.readUInt16LE(offset + 30);
|
|
104
|
+
const commentLength = central.readUInt16LE(offset + 32);
|
|
105
|
+
const flags = central.readUInt16LE(offset + 8);
|
|
106
|
+
const entry = {
|
|
107
|
+
madeBy: central.readUInt16LE(offset + 4) >> 8,
|
|
108
|
+
flags,
|
|
109
|
+
method: central.readUInt16LE(offset + 10),
|
|
110
|
+
time: central.readUInt16LE(offset + 12),
|
|
111
|
+
date: central.readUInt16LE(offset + 14),
|
|
112
|
+
crc: central.readUInt32LE(offset + 16),
|
|
113
|
+
compressedSize: central.readUInt32LE(offset + 20),
|
|
114
|
+
size: central.readUInt32LE(offset + 24),
|
|
115
|
+
external: central.readUInt32LE(offset + 38),
|
|
116
|
+
localOffset: central.readUInt32LE(offset + 42),
|
|
117
|
+
name: central.toString(flags & 0x800 ? "utf8" : "latin1", offset + 46, offset + 46 + nameLength),
|
|
118
|
+
};
|
|
119
|
+
// zip64 extra field: the 0xFFFFFFFF fields follow, in this order.
|
|
120
|
+
const extra = central.subarray(offset + 46 + nameLength, offset + 46 + nameLength + extraLength);
|
|
121
|
+
for (let position = 0; position + 4 <= extra.length;) {
|
|
122
|
+
const id = extra.readUInt16LE(position);
|
|
123
|
+
const length = extra.readUInt16LE(position + 2);
|
|
124
|
+
if (id === 0x0001) {
|
|
125
|
+
let field = position + 4;
|
|
126
|
+
for (const key of ["size", "compressedSize", "localOffset"]) {
|
|
127
|
+
if (entry[key] === 0xffffffff) { entry[key] = Number(extra.readBigUInt64LE(field)); field += 8; }
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
position += 4 + length;
|
|
131
|
+
}
|
|
132
|
+
entries.push(entry);
|
|
133
|
+
offset += 46 + nameLength + extraLength + commentLength;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Bun.Glob when running under Bun; elsewhere the wildcards become a RegExp:
|
|
137
|
+
// * and ? match across "/" as in Info-ZIP, [...] is a set.
|
|
138
|
+
const globToRegExp = (pattern) => new RegExp(`^${pattern.replace(/\[[^\]]*\]|[*?]|[.+^${}()|\\]/g, (token) =>
|
|
139
|
+
token === "*" ? ".*" : token === "?" ? "." : token.startsWith("[") ? token.replace(/^\[!/, "[^") : `\\${token}`)}$`, "s");
|
|
140
|
+
const toMatcher = (pattern) => {
|
|
141
|
+
if (typeof Bun !== "undefined") {
|
|
142
|
+
const glob = new Bun.Glob(pattern);
|
|
143
|
+
return { test: (name) => glob.match(name) };
|
|
144
|
+
}
|
|
145
|
+
return globToRegExp(pattern);
|
|
146
|
+
};
|
|
147
|
+
const globs = patterns.map(toMatcher);
|
|
148
|
+
const selected = globs.length ? entries.filter((entry) => globs.some((glob) => glob.test(entry.name))) : entries;
|
|
149
|
+
for (const [index, pattern] of patterns.entries()) {
|
|
150
|
+
if (!entries.some((entry) => globs[index].test(entry.name))) {
|
|
151
|
+
console.error(`caution: filename not matched: ${pattern}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const dosDate = (entry) => {
|
|
156
|
+
const year = (entry.date >> 9) + 1980;
|
|
157
|
+
const month = (entry.date >> 5) & 15;
|
|
158
|
+
const day = entry.date & 31;
|
|
159
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
160
|
+
return `${year}-${pad(month)}-${pad(day)} ${pad(entry.time >> 11)}:${pad((entry.time >> 5) & 63)}`;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const data = (entry) => {
|
|
164
|
+
if (entry.flags & 1) throw new Error("encrypted members are not supported");
|
|
165
|
+
const local = read(entry.localOffset, 30);
|
|
166
|
+
if (local.readUInt32LE(0) !== 0x04034b50) throw new Error("bad local header");
|
|
167
|
+
const start = entry.localOffset + 30 + local.readUInt16LE(26) + local.readUInt16LE(28);
|
|
168
|
+
const raw = read(start, entry.compressedSize);
|
|
169
|
+
let bytes;
|
|
170
|
+
if (entry.method === 0) bytes = raw;
|
|
171
|
+
else if (entry.method === 8) bytes = inflateRawSync(raw);
|
|
172
|
+
else throw new Error(`compression method ${entry.method} is not supported`);
|
|
173
|
+
if (bytes.length !== entry.size) throw new Error(`size mismatch (${bytes.length} != ${entry.size})`);
|
|
174
|
+
if (crc32(bytes) !== entry.crc) throw new Error("CRC error");
|
|
175
|
+
return bytes;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const writeAll = (target, bytes) => {
|
|
179
|
+
for (let offset = 0; offset < bytes.length;) offset += writeSync(target, bytes, offset, bytes.length - offset);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
let status = 0;
|
|
183
|
+
if (mode === "list") {
|
|
184
|
+
let total = 0;
|
|
185
|
+
console.log(" Length Date Time Name\n--------- ---------- ----- ----");
|
|
186
|
+
for (const entry of selected) {
|
|
187
|
+
total += entry.size;
|
|
188
|
+
console.log(`${String(entry.size).padStart(9)} ${dosDate(entry)} ${entry.name}`);
|
|
189
|
+
}
|
|
190
|
+
console.log(`--------- -------\n${String(total).padStart(9)} ${selected.length} file${selected.length === 1 ? "" : "s"}`);
|
|
191
|
+
} else if (mode === "pipe") {
|
|
192
|
+
for (const entry of selected) {
|
|
193
|
+
if (entry.name.endsWith("/")) continue;
|
|
194
|
+
try { writeAll(1, data(entry)); } catch (error) { console.error(`unzip: ${entry.name}: ${error.message}`); status = 2; }
|
|
195
|
+
}
|
|
196
|
+
} else if (mode === "test") {
|
|
197
|
+
for (const entry of selected) {
|
|
198
|
+
if (entry.name.endsWith("/")) continue;
|
|
199
|
+
try { data(entry); if (!quiet) console.log(` testing: ${entry.name.padEnd(24)} OK`); }
|
|
200
|
+
catch (error) { console.log(` testing: ${entry.name.padEnd(24)} ${error.message}`); status = 2; }
|
|
201
|
+
}
|
|
202
|
+
if (status === 0 && quiet < 2) console.log(`No errors detected in compressed data of ${archive}.`);
|
|
203
|
+
} else {
|
|
204
|
+
if (!quiet) console.log(`Archive: ${archive}`);
|
|
205
|
+
const root = resolve(directory);
|
|
206
|
+
mkdirSync(root, { recursive: true });
|
|
207
|
+
for (const entry of selected) {
|
|
208
|
+
const name = junkPaths ? basename(entry.name) : entry.name;
|
|
209
|
+
if (!name || (junkPaths && entry.name.endsWith("/"))) continue;
|
|
210
|
+
// No absolute paths or "..": a member never lands outside DIR.
|
|
211
|
+
const relative = normalize(name.replace(/^\/+/, ""));
|
|
212
|
+
if (relative.startsWith("..") || relative.split("/").includes("..")) {
|
|
213
|
+
console.error(`unzip: skipping ${entry.name}: path outside the destination`);
|
|
214
|
+
status = 1;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const target = join(root, relative);
|
|
218
|
+
const unixMode = entry.madeBy === 3 ? entry.external >>> 16 : 0;
|
|
219
|
+
try {
|
|
220
|
+
if (entry.name.endsWith("/")) {
|
|
221
|
+
mkdirSync(target, { recursive: true });
|
|
222
|
+
if (!quiet) console.log(` creating: ${join(directory, relative)}/`);
|
|
223
|
+
if (unixMode & 0o7777) chmodSync(target, unixMode & 0o7777);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const exists = (() => { try { lstatSync(target); return true; } catch { return false; } })();
|
|
227
|
+
if (exists) {
|
|
228
|
+
if (overwrite === false) continue;
|
|
229
|
+
if (overwrite === null) { console.error(`unzip: ${join(directory, relative)} exists; use -o to overwrite or -n to skip`); status = 1; continue; }
|
|
230
|
+
rmSync(target, { force: true });
|
|
231
|
+
}
|
|
232
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
233
|
+
const bytes = data(entry);
|
|
234
|
+
if ((unixMode & 0o170000) === 0o120000) {
|
|
235
|
+
symlinkSync(bytes.toString(), target);
|
|
236
|
+
} else {
|
|
237
|
+
writeFileSync(target, bytes);
|
|
238
|
+
if (unixMode & 0o7777) chmodSync(target, unixMode & 0o7777);
|
|
239
|
+
}
|
|
240
|
+
if (!quiet) console.log(` inflating: ${join(directory, relative)}`);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.error(`unzip: ${entry.name}: ${error.message}`);
|
|
243
|
+
status = 2;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
closeSync(fd);
|
|
248
|
+
process.exit(status);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// /lib/crc32.js — the CRC-32 of zip, gzip and GPT (IEEE 802.3, reflected,
|
|
2
|
+
// polynomial 0xEDB88320).
|
|
3
|
+
|
|
4
|
+
const table = new Uint32Array(256);
|
|
5
|
+
for (let index = 0; index < 256; index++) {
|
|
6
|
+
let value = index;
|
|
7
|
+
for (let bit = 0; bit < 8; bit++) value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
|
|
8
|
+
table[index] = value >>> 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** CRC-32 of `bytes`, continuing from `previous` (the CRC of what came before). */
|
|
12
|
+
export const crc32 = (bytes, previous = 0) => {
|
|
13
|
+
let crc = ~previous >>> 0;
|
|
14
|
+
for (let index = 0; index < bytes.length; index++) crc = table[(crc ^ bytes[index]) & 0xff] ^ (crc >>> 8);
|
|
15
|
+
return ~crc >>> 0;
|
|
16
|
+
};
|