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 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.53 as packaged by Alpine Linux 3.24 from the `linux-lts` aport.
107
- The default build uses package `linux-virt`, version 6.18.53-r0.
108
- `--linux-lts` and `--real` use package `linux-lts`, version 6.18.53-r0.
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.53.tar.xz
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.53-r0 (the same for both architectures):
116
- https://gitlab.alpinelinux.org/alpine/aports/-/blob/00abeb21803f818099833e761976a578dd1c0380/main/linux-lts/APKBUILD
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.53-r0.apk
119
- SHA-256 `cad859cc46342e18002621fdde166bf2cd520dfec5e781d241de7d13e53970d6`
120
- https://dl-cdn.alpinelinux.org/alpine/v3.24/main/x86_64/linux-lts-6.18.53-r0.apk
121
- SHA-256 `8e3cfdd1d98e0e70c2e70a8c299ed3cd1e0939d60ad7bbd1da1cf241d794490b`
122
- https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-virt-6.18.53-r0.apk
123
- SHA-256 `fea61b7fd5e72e626d771f9798df56c83db658d0e221044f06fc28aacf5a118b`
124
- https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-lts-6.18.53-r0.apk
125
- SHA-256 `46885041022ea11afa1e07c40199c74146bf72d362368a844338af0c162e365c`
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 3b6e001d41938fdf4fab7d87826fc40dfab733973a5ef96a14c892dbdec87502
131
- failover.ko f52f8dedf68be2a228e84d796170471c7103d88910a14e2307484fe94c4d1a11
132
- net_failover.ko 8fe715d813a1e795cf0b46d54fd67f6c625dfabf1a4eebfeec96ae34f8aab0b1
133
- virtio_net.ko cdf49d6216ba92313d3e40f065c326d86eae654bcbb811ac53e7c2613e686f1e
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 1b2ba2cad7973637f0f5589f845f7b773e73920e69d26785d2ff21c6c8995dc7
140
- failover.ko 0fcd683e5023a3910bac2d2eb02eff349ce00d81b572f4a9093a0c70fc1c77f3
141
- net_failover.ko 613809bd8d4c283b7a2b9dba5d8466cc36f478772a86e8c89de5e792bce69207
142
- virtio_net.ko 1fb4ce7a0b546933b1e40d67b3570aec808307dcafead342d3f4bcd5a0523b7c
143
- virtio_ring.ko eba2c109827a5d3b0e869505268a68d72cd6f2f9cc68778b66145c0503d9561f
144
- virtio.ko 00d107d78dc1d47a4eea335c6d6b28026ae2c783a9b7629f2cc080bdbc8d2dc3
145
- virtio_pci_legacy_dev.ko 370b8ad3521cbd3934c78eacc5965b20eff29c55c7fcded023c75c5d21424841
146
- virtio_pci_modern_dev.ko 572d719ccad98342ebce909b8dac65ef64379c3a6c97e2d1fb38ff66950d9322
147
- virtio_pci.ko e64039e30729e535b3ce9394249c79147bab27dfd20b7cf9914cd635dda95fc3
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 3c7f2f1de34cafbf64fa3528862539cbe8cd3830cd97110e8401b510835c8761
155
- usbcore.ko 0fce52fd1836304d48d25ef322558f27304ea9247c5b234f8ac2ecb9d8f00597
156
- xhci-hcd.ko 22e687fe561127ea4cd1aba21058cbf253d0541ccf315555004f5a94ed7ab727
157
- xhci-pci.ko 82eb0ea3869da82d1762130a738f2ff3ae6d77b16670f69f7f09cff4e8f3e360
158
- xhci-pci-renesas.ko 07df52a6ee408723938bc8a32cb4a2bfe4cbcf014ff48d23fd7f7ab41c201f6f
159
- hid.ko fbe4561a8dd65cd26160cbe9b7c5af23c7920044771e2e08a8aee6aa71141053
160
- hid-generic.ko acfda4f50e19c055e5d719ebc2b9b8f8c6c076552592dfaabccbe128a9862694
161
- usbhid.ko 12d3648467db87c2904a11a076c17768d8d51c28fe8ec41046e7d961592a81f1
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 9884ee00ecfff6a0dc9821cc9981b3aca74f741950704776b4335b1e076d96e6
168
- failover.ko 61b052e5ed3ef01bc6f71d84acb79bfe8cf2e5168f625fce76630dec14bf8b99
169
- net_failover.ko a0b150f46c7e9aa48156a8b94916ab65a4d8947143b381af56da677436584259
170
- virtio_net.ko e9c007778d2b0a7d4986cd04b724965a01ffee05ada92f75ce13bf59bef2491c
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 00426cce3a4b2b4972043eeb68a3a2af5645eb7a50ca853c375e41944ac66d27
177
- failover.ko b9bea5bb0c956b58faf0f803a205dafd889bfda75e7c59d234f60e65145fbec8
178
- net_failover.ko d8eb3087b75aba891e93327c6c43f37362cfe49421b7cabfd12f57a34d5c215a
179
- virtio_net.ko 10e73397bbb424b4bdb86781297f6fc30b13091e9a77fc7059e4861c09ef609d
180
- virtio_pci_legacy_dev.ko 476c6762743b9a5789200a31ee339173610f975b42e588914aabf41d8ade2958
181
- virtio_pci_modern_dev.ko 595618d12b67c67a0f29c406f6578d10d010024ac83014855672fd3fd695d6ed
182
- virtio_pci.ko 545a599cb7136a509a33cd3b52e80a8cd2bfa10668b0e766dc52872ce07d510b
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.53-r0`, musl `1.2.6-r2`,
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.53-0-lts rdinit=/bin/bun -- -e import('/init.js')
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.53-0-virt rdinit=/bin/bun -- -e import('/init.js')
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.53-0-lts rdinit=/bin/bun -- -e import('/init.js')
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
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
+ # What this script creates is packed with its modes: the same umask as
4
+ # PRoot and most hosts, not native Termux's 0077.
5
+ umask 022
3
6
 
4
7
  cd "$(dirname "$0")"
5
8
 
package/fetch-bun.sh CHANGED
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
+ # What this script creates is packed with its modes: the same umask as
4
+ # PRoot and most hosts, not native Termux's 0077.
5
+ umask 022
3
6
 
4
7
  cd "$(dirname "$0")"
5
8
 
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
- const inDocker = (stage) => useDocker && stage.flag !== "run";
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
- if (stage.flag === "run" && useDocker && !selected.has(stages[1]) &&
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
- runScript(stages[1], "build-image.sh", [], { ...stageEnv, BUNINU_ARCH: runArch });
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);
@@ -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,6 @@
1
+ #!/bin/bun
2
+ // gunzip — see /lib/gzip.js.
3
+
4
+ import { run } from "../lib/gzip.js";
5
+
6
+ process.exit(run("gunzip", process.argv.slice(2), { decompress: true }));
@@ -0,0 +1,6 @@
1
+ #!/bin/bun
2
+ // gzip — see /lib/gzip.js.
3
+
4
+ import { run } from "../lib/gzip.js";
5
+
6
+ process.exit(run("gzip", process.argv.slice(2), {}));
@@ -0,0 +1,6 @@
1
+ #!/bin/bun
2
+ // mcopy — see /lib/mtools.js.
3
+
4
+ import { mcopy } from "../lib/mtools.js";
5
+
6
+ process.exit(mcopy(process.argv.slice(2)));
@@ -0,0 +1,6 @@
1
+ #!/bin/bun
2
+ // mdir — see /lib/mtools.js.
3
+
4
+ import { mdir } from "../lib/mtools.js";
5
+
6
+ process.exit(mdir(process.argv.slice(2)));
@@ -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
+ }
@@ -0,0 +1,6 @@
1
+ #!/bin/bun
2
+ // mmd — see /lib/mtools.js.
3
+
4
+ import { mmd } from "../lib/mtools.js";
5
+
6
+ process.exit(mmd(process.argv.slice(2)));