buninu-linux 0.7.4 → 0.7.55
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/fetch-alpine.sh +3 -0
- package/fetch-bun.sh +3 -0
- package/index.js +48 -5
- 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 +0 -0
- package/scripts/fetch.js +194 -0
- package/scripts/modules.js +158 -0
- package/scripts/pack-initramfs.sh +17 -0
- package/scripts/pack.js +184 -0
- package/scripts/uki.js +0 -0
- package/test/js-tools.test.js +213 -0
package/NOTICE.md
CHANGED
|
@@ -103,83 +103,83 @@ the built image redistributes them.
|
|
|
103
103
|
- License: GPL-2.0-only WITH Linux-syscall-note
|
|
104
104
|
- See: `LICENSES/GPL-2.0.txt` and `LICENSES/Linux-syscall-note.txt`
|
|
105
105
|
|
|
106
|
-
Linux 6.18.
|
|
107
|
-
The default build uses package `linux-virt`, version 6.18.
|
|
108
|
-
`--linux-lts` and `--real` use package `linux-lts`, version 6.18.
|
|
106
|
+
Linux 6.18.55 as packaged by Alpine Linux 3.24 from the `linux-lts` aport.
|
|
107
|
+
The default build uses package `linux-virt`, version 6.18.55-r0.
|
|
108
|
+
`--linux-lts` and `--real` use package `linux-lts`, version 6.18.55-r0.
|
|
109
109
|
Each comes from the x86_64 or aarch64 repository to match `--arch`. All
|
|
110
110
|
listed modules are the selected package's `.ko.gz` files decompressed without
|
|
111
111
|
modification.
|
|
112
112
|
|
|
113
|
-
- Upstream source: https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.18.
|
|
113
|
+
- Upstream source: https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.18.55.tar.xz
|
|
114
114
|
- Alpine build recipe, configuration and patches (Corresponding Source),
|
|
115
|
-
pinned to the commit shipping 6.18.
|
|
116
|
-
https://gitlab.alpinelinux.org/alpine/aports/-/blob/
|
|
115
|
+
pinned to the commit shipping 6.18.55-r0 (the same for both architectures):
|
|
116
|
+
https://gitlab.alpinelinux.org/alpine/aports/-/blob/52fae6d7d56f3958f32e5bf9121a7536524b9ef4/main/linux-lts/APKBUILD
|
|
117
117
|
- Packages:
|
|
118
|
-
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/x86_64/linux-virt-6.18.
|
|
119
|
-
SHA-256 `
|
|
120
|
-
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/x86_64/linux-lts-6.18.
|
|
121
|
-
SHA-256 `
|
|
122
|
-
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-virt-6.18.
|
|
123
|
-
SHA-256 `
|
|
124
|
-
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-lts-6.18.
|
|
125
|
-
SHA-256 `
|
|
118
|
+
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/x86_64/linux-virt-6.18.55-r0.apk
|
|
119
|
+
SHA-256 `a941c15fc5db26b6692fd0140fa0970da76cb12aadf3dc8306c419f21bd39c93`
|
|
120
|
+
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/x86_64/linux-lts-6.18.55-r0.apk
|
|
121
|
+
SHA-256 `02ba9491d4cc110707de64ed03dd7e2fa5c93fdd851979c244ceb018785a9eea`
|
|
122
|
+
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-virt-6.18.55-r0.apk
|
|
123
|
+
SHA-256 `c7fb892408d7fe163a18671e5c7816752976d1c67fd17794dfba794aa0d6c1ac`
|
|
124
|
+
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-lts-6.18.55-r0.apk
|
|
125
|
+
SHA-256 `31aeae56fa527b2fb4fdd4aefbd693cbec210a8cbfd4ea7597a1773b832e9f83`
|
|
126
126
|
|
|
127
127
|
SHA-256 of files in the default x86_64 `linux-virt` image:
|
|
128
128
|
|
|
129
129
|
```text
|
|
130
|
-
kernel/x86_64/vmlinuz-virt
|
|
131
|
-
failover.ko
|
|
132
|
-
net_failover.ko
|
|
133
|
-
virtio_net.ko
|
|
130
|
+
kernel/x86_64/vmlinuz-virt fcee60bd42bdcc5f165278e092de0bb00d969843be326f7bd9a9602c2f52de9e
|
|
131
|
+
failover.ko 75d408861dd0ee89bf94753c9f2fff0f3831a6f76eaab8870d186910e6e2924b
|
|
132
|
+
net_failover.ko 4db140c0f8e32a31e95f56f35af62e5279864104f57d30af0684c75e3505bdd5
|
|
133
|
+
virtio_net.ko f2867d641f808b7f5d7d4eb972c72398902589a83b48f6f7a174b844717da117
|
|
134
134
|
```
|
|
135
135
|
|
|
136
136
|
SHA-256 of files in an x86_64 `--linux-lts` image:
|
|
137
137
|
|
|
138
138
|
```text
|
|
139
|
-
kernel/x86_64/vmlinuz-lts
|
|
140
|
-
failover.ko
|
|
141
|
-
net_failover.ko
|
|
142
|
-
virtio_net.ko
|
|
143
|
-
virtio_ring.ko
|
|
144
|
-
virtio.ko
|
|
145
|
-
virtio_pci_legacy_dev.ko
|
|
146
|
-
virtio_pci_modern_dev.ko
|
|
147
|
-
virtio_pci.ko
|
|
139
|
+
kernel/x86_64/vmlinuz-lts 6c85e6375814c51257a3d1b21c3c4c02803231d42e42c1a3d50236bf543cbeba
|
|
140
|
+
failover.ko b563c6734384c5c41ed1b227a221a3e7b511bca743e583dd33d6f85405a8df07
|
|
141
|
+
net_failover.ko 4ee36e32ed5db502125b57d237b5db31509c7b9c9a49ca9221b704fcfcd80b1c
|
|
142
|
+
virtio_net.ko 4aa2b98c0fed35be473ff22f4d3f89cb184ca13e6605dd9e8a68b5854eba7b6a
|
|
143
|
+
virtio_ring.ko ed1c704eddca74e4f68164efc62b65f8e8a8276b7ebb2eeb0185326dbd093603
|
|
144
|
+
virtio.ko 986cab65ecfce9c6291cc54598301ede8e8e2eca521a82dfbaf6dcf76ec6f8c6
|
|
145
|
+
virtio_pci_legacy_dev.ko dedd092324e5d23ac218dfa2aad3fa8bc2f0b49e54c1763423627c75a8c4e907
|
|
146
|
+
virtio_pci_modern_dev.ko 855221097ff07f0981d4e9fbb4200a0628a154826f37299d1434811f21902388
|
|
147
|
+
virtio_pci.ko aee0a18cce24968d1e06c026a056e570b3039884b900385f019410a751ea8a44
|
|
148
148
|
```
|
|
149
149
|
|
|
150
150
|
`--real` additionally includes the following modules from that same
|
|
151
151
|
x86_64 `linux-lts` package for physical xHCI controllers and USB HID keyboards:
|
|
152
152
|
|
|
153
153
|
```text
|
|
154
|
-
usb-common.ko
|
|
155
|
-
usbcore.ko
|
|
156
|
-
xhci-hcd.ko
|
|
157
|
-
xhci-pci.ko
|
|
158
|
-
xhci-pci-renesas.ko
|
|
159
|
-
hid.ko
|
|
160
|
-
hid-generic.ko
|
|
161
|
-
usbhid.ko
|
|
154
|
+
usb-common.ko 38a461cdee1e8dd82985fedbd050d3a496825c5b59c66dc3305c5508c93fb0e4
|
|
155
|
+
usbcore.ko 18949ea4cab0f98846f4fea192c9ebe9892c5504f6750529a4db8c1a718fdd3b
|
|
156
|
+
xhci-hcd.ko aed7f3e8d2f1e2d722fb625fcd68adce5212a30ce1a25a934832177594670543
|
|
157
|
+
xhci-pci.ko 8959b6c1790ac1b656308278313f78cf4fde8b811456a42579b467a8196515b8
|
|
158
|
+
xhci-pci-renesas.ko 3aa013b9ca71ee5bd16b30efb1f68aa2c29ac84cf15dd92caf413f7577fc36cf
|
|
159
|
+
hid.ko e3fecc9b96b44d1c572337a937927291c3219d43a00a357049a1285ab57f5bbe
|
|
160
|
+
hid-generic.ko a3d76a35904f26d8bb8d5bff115de12a1cb966c3e428e529767dde28c24e4421
|
|
161
|
+
usbhid.ko 6b50c4a6037fa740b3f576d5bf362bdad7aa86ebc8da5e1777e69c03ed70f703
|
|
162
162
|
```
|
|
163
163
|
|
|
164
164
|
SHA-256 of files in the default aarch64 `linux-virt` image:
|
|
165
165
|
|
|
166
166
|
```text
|
|
167
|
-
kernel/aarch64/vmlinuz-virt
|
|
168
|
-
failover.ko
|
|
169
|
-
net_failover.ko
|
|
170
|
-
virtio_net.ko
|
|
167
|
+
kernel/aarch64/vmlinuz-virt af8ad21e222dbff8c89909e1ef7b8b7607dd58188b2f3067128aac4f646d212d
|
|
168
|
+
failover.ko aff1648d08436515b509c3c848049e44540ff0d26afc113a496db961f6871df6
|
|
169
|
+
net_failover.ko 4461014c041c8cbdfc881c0d820361014a491547b694d36425d084ab9008a33f
|
|
170
|
+
virtio_net.ko 7b6e3c89d666419d336990688711c4d6b0cab91e5934a7bd99e6d8dcc5caa40b
|
|
171
171
|
```
|
|
172
172
|
|
|
173
173
|
SHA-256 of files in an aarch64 `--linux-lts` image:
|
|
174
174
|
|
|
175
175
|
```text
|
|
176
|
-
kernel/aarch64/vmlinuz-lts
|
|
177
|
-
failover.ko
|
|
178
|
-
net_failover.ko
|
|
179
|
-
virtio_net.ko
|
|
180
|
-
virtio_pci_legacy_dev.ko
|
|
181
|
-
virtio_pci_modern_dev.ko
|
|
182
|
-
virtio_pci.ko
|
|
176
|
+
kernel/aarch64/vmlinuz-lts 6c6625bc5ba59136101414d09efbeaaed000517e654a75157f42565cec15abad
|
|
177
|
+
failover.ko 1f10c1ac74c6d35f745227899372b1b6656a3197436d9c72e0bb39a19779c3a6
|
|
178
|
+
net_failover.ko 5815342b859f820adfd90ffdddde689c73f0e6fcc34efbc9ada8beabb2fd37b2
|
|
179
|
+
virtio_net.ko 2ef9114dea6b40bd9d2e1460c6be94859b5527af5479928cfd90a66eff843bab
|
|
180
|
+
virtio_pci_legacy_dev.ko be6654a06cfb30f2598b184f7a2b2372314c5a5325a984b73a7ef9a3c39e7eb3
|
|
181
|
+
virtio_pci_modern_dev.ko d88219ed14678c0aba06a882d6cf43b0ad9462eeae09638b6382ba5601b5a8c4
|
|
182
|
+
virtio_pci.ko 6bdafb0472d289e2ca6dfc63bf2cb2e54e1258e9c4774317ca1c7df059dfb31e
|
|
183
183
|
```
|
|
184
184
|
|
|
185
185
|
### linuxx64.efi.stub and linuxaa64.efi.stub
|
package/README.md
CHANGED
|
@@ -64,16 +64,23 @@
|
|
|
64
64
|
- This guide builds a bootable `buninu-linux-<version>.img`
|
|
65
65
|
- The documented build environment is Debian 13 under Termux PRoot
|
|
66
66
|
- A regular Debian installation works as well.
|
|
67
|
+
* Experimental Alpine support now!
|
|
67
68
|
- For a source checkout, clone into the Termux home so native Termux and PRoot
|
|
68
69
|
can share it; `~` differs between them, but the absolute path is the same.
|
|
69
70
|
|
|
70
71
|
### Steps
|
|
71
72
|
|
|
72
|
-
- Before anything, install Bun in Debian first:
|
|
73
|
+
- Before anything, install Bun in Debian/Alpine first:
|
|
73
74
|
|
|
74
75
|
```sh
|
|
76
|
+
# Debian/Ubuntu
|
|
75
77
|
apt update
|
|
76
78
|
apt install curl unzip
|
|
79
|
+
|
|
80
|
+
# Alpine
|
|
81
|
+
apk update
|
|
82
|
+
apk add curl unzip bash libgcc libstdc++
|
|
83
|
+
|
|
77
84
|
curl -fsSL https://bun.sh/install | bash
|
|
78
85
|
```
|
|
79
86
|
|
|
@@ -85,8 +92,12 @@ Install the build tools, enter a directory where
|
|
|
85
92
|
you want to keep the finished image, then run the published package:
|
|
86
93
|
|
|
87
94
|
```sh
|
|
95
|
+
# Debian/Ubuntu
|
|
88
96
|
apt install binutils-mingw-w64-x86-64 cpio curl dosfstools fakeroot mtools parted unzip
|
|
89
97
|
|
|
98
|
+
# Alpine
|
|
99
|
+
apk add bash coreutils tar curl unzip parted dosfstools mtools libgcc libstdc++
|
|
100
|
+
|
|
90
101
|
mkdir -p /data/data/com.termux/files/home/buninu-build
|
|
91
102
|
cd /data/data/com.termux/files/home/buninu-build
|
|
92
103
|
|
|
@@ -94,7 +105,14 @@ export PATH=$HOME/.bun/bin:$PATH
|
|
|
94
105
|
|
|
95
106
|
bun x buninu-linux --version
|
|
96
107
|
|
|
108
|
+
# Debian/Ubuntu
|
|
97
109
|
bun x buninu-linux -fb --real --export
|
|
110
|
+
|
|
111
|
+
# Alpine
|
|
112
|
+
export BUNINU_JS_CPIO=1
|
|
113
|
+
export BUNINU_JS_UKI=1
|
|
114
|
+
bun x buninu-linux -fb --real --export
|
|
115
|
+
|
|
98
116
|
```
|
|
99
117
|
|
|
100
118
|
`--export` copies the completed `vda.img` out of bunx's package directory and
|
|
@@ -890,7 +908,7 @@ initramfs/ init.js, commands, userspace: the same for every archi
|
|
|
890
908
|
native/<arch>/ musl and the GCC runtime (committed); bun and modules (fetched)
|
|
891
909
|
```
|
|
892
910
|
|
|
893
|
-
The `--real` inputs are Alpine v3.24 `linux-lts-6.18.
|
|
911
|
+
The `--real` inputs are Alpine v3.24 `linux-lts-6.18.55-r0`, musl `1.2.6-r2`,
|
|
894
912
|
`systemd-efistub-260.2-r0`, `libstdc++`/`libgcc` `15.2.0-r5`, and Bun 1.4.2
|
|
895
913
|
`linux-x64-musl-baseline`. The aarch64 guest uses the same versions from
|
|
896
914
|
Alpine's aarch64 repository and Bun's `linux-aarch64-musl`. There is no BusyBox
|
|
@@ -987,7 +1005,7 @@ The physical boot chain is firmware → the UKI stub → its embedded
|
|
|
987
1005
|
and, with the currently pinned kernel, embeds this complete command line:
|
|
988
1006
|
|
|
989
1007
|
```text
|
|
990
|
-
console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.
|
|
1008
|
+
console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.55-0-lts rdinit=/bin/bun -- -e import('/init.js')
|
|
991
1009
|
```
|
|
992
1010
|
|
|
993
1011
|
Serial kernel logging is retained, while the final console makes
|
|
@@ -1011,7 +1029,7 @@ the same way). Build without `--real`; this selects Alpine `linux-virt`, omits t
|
|
|
1011
1029
|
module set, and puts `ttyS0` last so serial stdio owns `/dev/console`:
|
|
1012
1030
|
|
|
1013
1031
|
```text
|
|
1014
|
-
console=tty0 console=ttyS0,115200 panic=0 PATH=/bin KERNEL_RELEASE=6.18.
|
|
1032
|
+
console=tty0 console=ttyS0,115200 panic=0 PATH=/bin KERNEL_RELEASE=6.18.55-0-virt rdinit=/bin/bun -- -e import('/init.js')
|
|
1015
1033
|
```
|
|
1016
1034
|
|
|
1017
1035
|
Build and boot it with:
|
|
@@ -1128,7 +1146,7 @@ The default `--real` image embeds this kernel command line (shown for the
|
|
|
1128
1146
|
currently pinned LTS release):
|
|
1129
1147
|
|
|
1130
1148
|
```text
|
|
1131
|
-
console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.
|
|
1149
|
+
console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.55-0-lts rdinit=/bin/bun -- -e import('/init.js')
|
|
1132
1150
|
```
|
|
1133
1151
|
|
|
1134
1152
|
The physical display (`tty0`) is the final console and therefore owns PID 1's
|
package/fetch-alpine.sh
CHANGED
package/fetch-bun.sh
CHANGED
package/index.js
CHANGED
|
@@ -120,6 +120,10 @@ initramfs/init.js needs, -r just boots what is already there. Anything after
|
|
|
120
120
|
-- goes to qemu-system-x86_64 or qemu-system-aarch64 verbatim (for example:
|
|
121
121
|
-r -- -smp 4).
|
|
122
122
|
|
|
123
|
+
With BUNINU_JS_TOOLS=1 in the environment, fetch and build are done in
|
|
124
|
+
JavaScript (scripts/fetch.js, scripts/build.js) and need only node or bun:
|
|
125
|
+
no Debian toolchain and no Docker.
|
|
126
|
+
|
|
123
127
|
Fetch and build need the Debian toolchain from README "Build environment"
|
|
124
128
|
(PRoot, a Debian or Ubuntu host, or --docker); run needs QEMU (Termux
|
|
125
129
|
pkg, apt, or brew install qemu). Tools missing for the stages you picked are
|
|
@@ -250,13 +254,18 @@ function hasTool(name) {
|
|
|
250
254
|
return false;
|
|
251
255
|
}
|
|
252
256
|
|
|
253
|
-
|
|
257
|
+
// BUNINU_JS_TOOLS=1: fetch and build run scripts/fetch.js and scripts/build.js
|
|
258
|
+
// in this process instead of the shell scripts, so they need no toolchain
|
|
259
|
+
// and no Docker; run is unchanged.
|
|
260
|
+
const jsTools = process.env.BUNINU_JS_TOOLS === "1";
|
|
261
|
+
const usesJs = (stage) => jsTools && stage.flag !== "run";
|
|
262
|
+
const inDocker = (stage) => useDocker && stage.flag !== "run" && !jsTools;
|
|
254
263
|
|
|
255
264
|
function checkTools(selectedStages) {
|
|
256
265
|
const missing = [];
|
|
257
266
|
for (const stage of selectedStages) {
|
|
258
267
|
const arch = stage.flag === "run" ? runArch : buildArch;
|
|
259
|
-
for (const tool of inDocker(stage) ? ["docker"] : stage.tools(arch)) {
|
|
268
|
+
for (const tool of inDocker(stage) ? ["docker"] : usesJs(stage) ? [] : stage.tools(arch)) {
|
|
260
269
|
if (!hasTool(tool)) missing.push(`${tool} (for --${stage.flag})`);
|
|
261
270
|
}
|
|
262
271
|
}
|
|
@@ -348,12 +357,46 @@ const stageEnv = {
|
|
|
348
357
|
...(real && { REAL_MACHINE: "1" }),
|
|
349
358
|
};
|
|
350
359
|
|
|
360
|
+
// The arch.js config for the JS stages, from the same settings as stageEnv.
|
|
361
|
+
const jsConfig = async (env) => (await import("./scripts/arch.js")).config({
|
|
362
|
+
arch: env.BUNINU_ARCH,
|
|
363
|
+
flavor: env.LINUX_FLAVOR,
|
|
364
|
+
real: env.REAL_MACHINE === "1",
|
|
365
|
+
mirror: env.ALPINE_MIRROR,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
async function runJs(stage, env) {
|
|
369
|
+
const module = stage.flag === "fetch" ? "scripts/fetch.js" : "scripts/build.js";
|
|
370
|
+
console.log(`\n==> [${stage.flag} js] ${module}`);
|
|
371
|
+
try {
|
|
372
|
+
const cfg = await jsConfig(env);
|
|
373
|
+
if (stage.flag === "fetch") await (await import("./scripts/fetch.js")).fetchAll(cfg);
|
|
374
|
+
else await (await import("./scripts/build.js")).buildAll(cfg);
|
|
375
|
+
} catch (error) {
|
|
376
|
+
fail(`${module}: ${error.message}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
351
380
|
for (const stage of ordered) {
|
|
352
381
|
// run-qemu.sh builds a missing vda.img itself, which needs the build
|
|
353
|
-
// toolchain; with Docker that has to happen in the container first
|
|
354
|
-
|
|
382
|
+
// toolchain; with Docker that has to happen in the container first, and
|
|
383
|
+
// with BUNINU_JS_TOOLS=1 scripts/build.js writes it.
|
|
384
|
+
if (stage.flag === "run" && (useDocker || jsTools) && !selected.has(stages[1]) &&
|
|
355
385
|
!existsSync(resolve(rootDir, "vda.img"))) {
|
|
356
|
-
|
|
386
|
+
if (jsTools) {
|
|
387
|
+
console.log("\n==> [build js] scripts/build.js image");
|
|
388
|
+
try {
|
|
389
|
+
(await import("./scripts/build.js")).image(await jsConfig({ ...stageEnv, BUNINU_ARCH: runArch }));
|
|
390
|
+
} catch (error) {
|
|
391
|
+
fail(`scripts/build.js: ${error.message}`);
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
runScript(stages[1], "build-image.sh", [], { ...stageEnv, BUNINU_ARCH: runArch });
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (usesJs(stage)) {
|
|
398
|
+
await runJs(stage, stageEnv);
|
|
399
|
+
continue;
|
|
357
400
|
}
|
|
358
401
|
for (const script of stage.scripts) {
|
|
359
402
|
runScript(stage, script, stage.flag === "run" ? passthrough : [], stageEnv);
|
package/initramfs/bin/dd
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/bin/bun
|
|
2
|
+
// dd — copy blocks between files.
|
|
3
|
+
//
|
|
4
|
+
// dd [if=FILE] [of=FILE] [bs=N] [ibs=N] [obs=N] [count=N] [skip=N] [seek=N]
|
|
5
|
+
// [conv=notrunc,sparse,sync,fsync,fdatasync,noerror] [status=none|noxfer|progress]
|
|
6
|
+
//
|
|
7
|
+
// N takes c (1), w (2), b (512), K/KiB, M/MiB, G/GiB (powers of 1024) or
|
|
8
|
+
// kB, MB, GB (powers of 1000), and the AxB product form, as in coreutils.
|
|
9
|
+
// conv=sparse seeks over output blocks that are all zero instead of writing them.
|
|
10
|
+
|
|
11
|
+
import { closeSync, fstatSync, fsyncSync, ftruncateSync, openSync, readSync, writeSync } from "node:fs";
|
|
12
|
+
|
|
13
|
+
const fail = (message) => {
|
|
14
|
+
console.error(`dd: ${message}`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const multipliers = { c: 1, w: 2, b: 512, k: 1024, K: 1024, KiB: 1024, kB: 1e3, KB: 1e3, M: 1024 ** 2, MiB: 1024 ** 2, MB: 1e6, G: 1024 ** 3, GiB: 1024 ** 3, GB: 1e9 };
|
|
19
|
+
const number = (name, text) => text.split("x").reduce((product, part) => {
|
|
20
|
+
const match = /^(\d+)([a-zA-Z]*)$/.exec(part);
|
|
21
|
+
if (!match || !(match[2] in { "": 1, ...multipliers })) fail(`invalid number: '${text}' for ${name}`);
|
|
22
|
+
return product * Number(match[1]) * (multipliers[match[2]] ?? 1);
|
|
23
|
+
}, 1);
|
|
24
|
+
|
|
25
|
+
const options = { if: null, of: null, ibs: 512, obs: 512, count: Infinity, skip: 0, seek: 0, conv: new Set(), status: "default" };
|
|
26
|
+
for (const argument of process.argv.slice(2)) {
|
|
27
|
+
if (argument === "-h" || argument === "--help") {
|
|
28
|
+
console.error("Usage: dd [if=FILE] [of=FILE] [bs=N] [count=N] [skip=N] [seek=N] [conv=notrunc,sparse,...] [status=none]");
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
const separator = argument.indexOf("=");
|
|
32
|
+
if (separator < 0) fail(`unrecognized operand '${argument}'`);
|
|
33
|
+
const key = argument.slice(0, separator);
|
|
34
|
+
const value = argument.slice(separator + 1);
|
|
35
|
+
if (key === "if" || key === "of") options[key] = value;
|
|
36
|
+
else if (key === "bs") options.ibs = options.obs = number(key, value);
|
|
37
|
+
else if (key === "ibs" || key === "obs" || key === "count" || key === "skip" || key === "seek") options[key] = number(key, value);
|
|
38
|
+
else if (key === "conv") for (const flag of value.split(",")) {
|
|
39
|
+
if (!["notrunc", "sparse", "sync", "fsync", "fdatasync", "noerror"].includes(flag)) fail(`invalid conversion: '${flag}'`);
|
|
40
|
+
options.conv.add(flag);
|
|
41
|
+
}
|
|
42
|
+
else if (key === "status") {
|
|
43
|
+
if (!["none", "noxfer", "progress"].includes(value)) fail(`invalid status level: '${value}'`);
|
|
44
|
+
options.status = value;
|
|
45
|
+
}
|
|
46
|
+
else if (key === "iflag" || key === "oflag") {}
|
|
47
|
+
else fail(`unrecognized operand '${argument}'`);
|
|
48
|
+
}
|
|
49
|
+
if (options.ibs < 1 || options.obs < 1) fail("block sizes must be positive");
|
|
50
|
+
|
|
51
|
+
const input = options.if === null ? 0 : (() => {
|
|
52
|
+
try { return openSync(options.if, "r"); } catch (error) { return fail(`failed to open '${options.if}': ${error.message}`); }
|
|
53
|
+
})();
|
|
54
|
+
const output = options.of === null ? 1 : (() => {
|
|
55
|
+
try { return openSync(options.of, options.conv.has("notrunc") ? (() => { try { openSync(options.of, "r"); return "r+"; } catch { return "w"; } })() : "a+"); }
|
|
56
|
+
catch (error) { return fail(`failed to open '${options.of}': ${error.message}`); }
|
|
57
|
+
})();
|
|
58
|
+
// "a+" would append; reopen read-write so writes land at explicit positions.
|
|
59
|
+
const outputFd = options.of !== null && !options.conv.has("notrunc") ? (closeSync(output), openSync(options.of, "r+")) : output;
|
|
60
|
+
const inputSeekable = fstatSync(input).isFile() || fstatSync(input).isBlockDevice();
|
|
61
|
+
const outputSeekable = fstatSync(outputFd).isFile() || fstatSync(outputFd).isBlockDevice();
|
|
62
|
+
|
|
63
|
+
let inputPosition = inputSeekable ? options.skip * options.ibs : null;
|
|
64
|
+
if (!inputSeekable && options.skip) {
|
|
65
|
+
// Pipes: read and drop skip blocks.
|
|
66
|
+
const discard = Buffer.alloc(options.ibs);
|
|
67
|
+
for (let block = 0; block < options.skip; block++) if (readSync(input, discard, 0, options.ibs, null) === 0) break;
|
|
68
|
+
}
|
|
69
|
+
let outputPosition = options.seek * options.obs;
|
|
70
|
+
// Without notrunc the output is cut at the seek point, as dd does.
|
|
71
|
+
if (options.of !== null && !options.conv.has("notrunc") && fstatSync(outputFd).isFile()) ftruncateSync(outputFd, outputPosition);
|
|
72
|
+
|
|
73
|
+
const stats = { fullIn: 0, partialIn: 0, fullOut: 0, partialOut: 0, bytes: 0 };
|
|
74
|
+
const started = performance.now();
|
|
75
|
+
const buffer = Buffer.alloc(options.ibs);
|
|
76
|
+
let pending = Buffer.alloc(0);
|
|
77
|
+
let skippedTail = false;
|
|
78
|
+
|
|
79
|
+
const writeBlock = (block) => {
|
|
80
|
+
if (options.conv.has("sparse") && outputSeekable && block.every((byte) => byte === 0)) {
|
|
81
|
+
outputPosition += block.length;
|
|
82
|
+
skippedTail = true;
|
|
83
|
+
} else {
|
|
84
|
+
for (let offset = 0; offset < block.length;) {
|
|
85
|
+
offset += writeSync(outputFd, block, offset, block.length - offset, outputSeekable ? outputPosition + offset : null);
|
|
86
|
+
}
|
|
87
|
+
outputPosition += block.length;
|
|
88
|
+
skippedTail = false;
|
|
89
|
+
}
|
|
90
|
+
if (block.length === options.obs) stats.fullOut++; else stats.partialOut++;
|
|
91
|
+
stats.bytes += block.length;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
for (let blocks = 0; blocks < options.count; blocks++) {
|
|
95
|
+
let count;
|
|
96
|
+
try {
|
|
97
|
+
count = readSync(input, buffer, 0, options.ibs, inputPosition);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (!options.conv.has("noerror")) fail(`error reading '${options.if ?? "standard input"}': ${error.message}`);
|
|
100
|
+
count = 0;
|
|
101
|
+
}
|
|
102
|
+
if (count === 0) break;
|
|
103
|
+
if (inputPosition !== null) inputPosition += count;
|
|
104
|
+
if (count === options.ibs) stats.fullIn++; else stats.partialIn++;
|
|
105
|
+
let block = buffer.subarray(0, count);
|
|
106
|
+
if (options.conv.has("sync") && count < options.ibs) block = Buffer.concat([block, Buffer.alloc(options.ibs - count)]);
|
|
107
|
+
pending = Buffer.concat([pending, block]);
|
|
108
|
+
while (pending.length >= options.obs) {
|
|
109
|
+
writeBlock(pending.subarray(0, options.obs));
|
|
110
|
+
pending = pending.subarray(options.obs);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (pending.length) writeBlock(Buffer.from(pending));
|
|
114
|
+
// A sparse run at the end still has to reach the file's length.
|
|
115
|
+
if (skippedTail && fstatSync(outputFd).size < outputPosition) ftruncateSync(outputFd, outputPosition);
|
|
116
|
+
if (options.conv.has("fsync") || options.conv.has("fdatasync")) fsyncSync(outputFd);
|
|
117
|
+
|
|
118
|
+
if (options.status !== "none") {
|
|
119
|
+
console.error(`${stats.fullIn}+${stats.partialIn} records in\n${stats.fullOut}+${stats.partialOut} records out`);
|
|
120
|
+
if (options.status !== "noxfer") {
|
|
121
|
+
const seconds = (performance.now() - started) / 1000;
|
|
122
|
+
console.error(`${stats.bytes} bytes copied, ${seconds.toFixed(3)} s, ${(stats.bytes / Math.max(seconds, 1e-9) / 1e6).toFixed(1)} MB/s`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (input !== 0) closeSync(input);
|
|
126
|
+
if (outputFd !== 1) closeSync(outputFd);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/bin/bun
|
|
2
|
+
// mkfs.fat — make a FAT32 filesystem in an image file, laid out as
|
|
3
|
+
// mkfs.fat 4.2 does (see /lib/fat.js).
|
|
4
|
+
//
|
|
5
|
+
// mkfs.fat [-F 32] [-n LABEL] [-h HIDDEN] [-i VOLID] [-s SECTORS_PER_CLUSTER]
|
|
6
|
+
// [--invariant] [-C] FILE [BLOCKS]
|
|
7
|
+
//
|
|
8
|
+
// BLOCKS (1 KiB each) sizes the filesystem; -C creates FILE with that size.
|
|
9
|
+
// SOURCE_DATE_EPOCH, when set, is the volume ID and the label's time, as
|
|
10
|
+
// with mkfs.fat; --invariant fixes both to mkfs.fat's constants.
|
|
11
|
+
|
|
12
|
+
import { closeSync, fstatSync, ftruncateSync, openSync } from "node:fs";
|
|
13
|
+
import { FatError, format } from "../lib/fat.js";
|
|
14
|
+
|
|
15
|
+
const fail = (message) => {
|
|
16
|
+
console.error(`mkfs.fat: ${message}`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const usage = () => {
|
|
21
|
+
console.error(`Usage: mkfs.fat [-F 32] [-n LABEL] [-h HIDDEN] [-i VOLID] [-s SECTORS_PER_CLUSTER] [--invariant] [-C] FILE [BLOCKS]`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const options = { label: "NO NAME", hidden: 0, volumeId: null, invariant: false, sectorsPerCluster: undefined, create: false, quiet: false };
|
|
26
|
+
const operands = [];
|
|
27
|
+
const argv = process.argv.slice(2);
|
|
28
|
+
const value = (index, option) => {
|
|
29
|
+
if (index + 1 >= argv.length) fail(`option ${option} requires an argument`);
|
|
30
|
+
return argv[index + 1];
|
|
31
|
+
};
|
|
32
|
+
for (let index = 0; index < argv.length; index++) {
|
|
33
|
+
const argument = argv[index];
|
|
34
|
+
if (argument === "--help") usage();
|
|
35
|
+
else if (argument === "--invariant") options.invariant = true;
|
|
36
|
+
else if (argument === "-F") { if (value(index++, "-F") !== "32") fail("only -F 32 is supported"); }
|
|
37
|
+
else if (argument === "-n") options.label = value(index++, "-n");
|
|
38
|
+
else if (argument === "-h") options.hidden = Number(value(index++, "-h"));
|
|
39
|
+
else if (argument === "-i") options.volumeId = Number.parseInt(value(index++, "-i"), 16) >>> 0;
|
|
40
|
+
else if (argument === "-s") options.sectorsPerCluster = Number(value(index++, "-s"));
|
|
41
|
+
else if (argument === "-C") options.create = true;
|
|
42
|
+
else if (argument === "-v" || argument === "-I" || argument === "-a") {}
|
|
43
|
+
else if (argument === "-q") options.quiet = true;
|
|
44
|
+
else if (argument.startsWith("-") && argument.length > 1) fail(`unknown option ${argument}`);
|
|
45
|
+
else operands.push(argument);
|
|
46
|
+
}
|
|
47
|
+
if (operands.length < 1 || operands.length > 2) usage();
|
|
48
|
+
const [file, blocks] = operands;
|
|
49
|
+
if (options.label.length > 11) fail("label can be no longer than 11 characters");
|
|
50
|
+
if (!Number.isInteger(options.hidden) || options.hidden < 0) fail(`bad number of hidden sectors: ${options.hidden}`);
|
|
51
|
+
if (options.sectorsPerCluster !== undefined && ![1, 2, 4, 8, 16, 32, 64, 128].includes(options.sectorsPerCluster)) fail("bad number of sectors per cluster");
|
|
52
|
+
|
|
53
|
+
let fd;
|
|
54
|
+
try {
|
|
55
|
+
fd = openSync(file, options.create ? "wx" : "r+");
|
|
56
|
+
} catch (error) {
|
|
57
|
+
fail(`unable to open ${file}: ${error.message}`);
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
if (options.create) {
|
|
61
|
+
if (!blocks) fail("-C needs BLOCKS");
|
|
62
|
+
ftruncateSync(fd, Number(blocks) * 1024);
|
|
63
|
+
}
|
|
64
|
+
const bytes = blocks ? Number(blocks) * 1024 : fstatSync(fd).size;
|
|
65
|
+
if (!Number.isFinite(bytes) || bytes <= 0) fail(`bad size for ${file}`);
|
|
66
|
+
console.log("mkfs.fat (buninu-linux /bin/mkfs.fat)");
|
|
67
|
+
format(fd, { sectors: Math.floor(bytes / 512), label: options.label, volumeId: options.volumeId, hidden: options.hidden, invariant: options.invariant, sectorsPerCluster: options.sectorsPerCluster });
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (error instanceof FatError) fail(error.message);
|
|
70
|
+
throw error;
|
|
71
|
+
} finally {
|
|
72
|
+
closeSync(fd);
|
|
73
|
+
}
|