buninu-linux 0.7.4 → 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 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.54 as packaged by Alpine Linux 3.24 from the `linux-lts` aport.
107
+ The default build uses package `linux-virt`, version 6.18.54-r0.
108
+ `--linux-lts` and `--real` use package `linux-lts`, version 6.18.54-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.54.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.54-r0 (the same for both architectures):
116
+ https://gitlab.alpinelinux.org/alpine/aports/-/blob/f8254a6d11c37b153dae6a0fd98ba378dd0d093d/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.54-r0.apk
119
+ SHA-256 `57a522d2b6e9d1b6c9de6b9f7fc42aa4b8d33ebf9f2b42b4251dbee6ff2d9839`
120
+ https://dl-cdn.alpinelinux.org/alpine/v3.24/main/x86_64/linux-lts-6.18.54-r0.apk
121
+ SHA-256 `1d02a1b74a8a09c476a3c01782c0b8613ae2935988e65d4d31e6bef44dfb8b64`
122
+ https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-virt-6.18.54-r0.apk
123
+ SHA-256 `9a4a6fa042b60b70f453dded99762810516b35793c64d776d75986b0b110b6e0`
124
+ https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-lts-6.18.54-r0.apk
125
+ SHA-256 `f945ddcc306546c7c22cf10249f637fc6e806ecc40286fb50950558dd573e7e9`
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 c7ce829b618d4a9d2df79c58ea0fb2a392e606f6c47bb94696ea75610a568166
131
+ failover.ko c02fc21655bd3a8598ddf29f1028a1029bcb19b1da8611d3cf3c8685d0412f84
132
+ net_failover.ko 46e0c3d3f4cdfafabffbd1a57ef59aac0bcc0f1a4e22b114d992cd46ac264145
133
+ virtio_net.ko 1163e07fd5391849820ede09277b0147ae7e48ef0f08f9e96da08bb2ff131c2a
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 d3cb46d949f3d05a7e6753ba0b9a054323e6a7b6eeb231b31a1659d8f9f51a28
140
+ failover.ko 3c0e33c374419512c410559561e8bc5d2fac573cb449da404bbeaf94ec7641fe
141
+ net_failover.ko 2a5db89834e5f35ecdf114da8dbe4d4db4061371d37768d9f92eb09d479d21ab
142
+ virtio_net.ko 1fc0c4992e4a9ad83985fbad5a5674bacceb4c8ab5329b4509656ba3350478eb
143
+ virtio_ring.ko 3c14098101a9f30f034410efe7e91e369057d1709152a0419a3a9fc4b1367915
144
+ virtio.ko f47dac1696dcab539c5d313f13bd5e06945cf921bf406e0f709e1866de72803d
145
+ virtio_pci_legacy_dev.ko 662c0f09bcb88c7c8a79fb2159d4aa0b62044d1859a46de03fc9e57fc3501931
146
+ virtio_pci_modern_dev.ko 006e525fda5e369b6a1669ec0e8e174c784f1ecdb7dcfb6257ffc67fd5b93525
147
+ virtio_pci.ko 94d66ebd7f2a325ce83c9f42175432a7797969b3e3c7fcc07d2b913012f1c77f
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 471bed173d18c373c80e9cce646f75b71b034b3b398e6bd04c51fa065ac92e6b
155
+ usbcore.ko 99b20afa2057720f2aa36224909c37740cbe1068e23ba52872ed09e6c7966c29
156
+ xhci-hcd.ko b3ffac428a78858532a5114ad4f2f66de253aba32932f508d62eceb29af39313
157
+ xhci-pci.ko 2a924cfba63360acf3a9a688a5fb69f87c2c82a02f904e972a9f5c9cb303588f
158
+ xhci-pci-renesas.ko 2c8fa8dac908da5a13c8f87e520cc1d4a026aeb9ee58d1ac0380d696c8ef6a72
159
+ hid.ko 3aa16634ee8621291abfa1f9efca287299dea052678c8ead5af86003a7e493f3
160
+ hid-generic.ko 2879395175b39a47f09e81bb00cb1a4b565103ba4583ce99169b9ef7fd943d62
161
+ usbhid.ko e80b60c6dd99a87c95bd97434679aa3f069c9bce2f67a7311f179b3066cdea59
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 29de2da2ea7aa1d95da37c947278caca65ef3ad211dbb92ac75a6b962f96d0d2
168
+ failover.ko 62bfac505026e368b291e8d7e167dc90bff826c44d0db1ff4340de2201022674
169
+ net_failover.ko 3761f107b61ae4d11a7bed402cdab1862f3d61cacc612b2e6d9bc61ad1e20d6d
170
+ virtio_net.ko 22a95296a008f009de59e933cc1b9615396612ef5eb3922fac2b79ac672a1305
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 52521d144fe344f23ffd03297fd0e0da42578e54d3da18685cbb7f613244c001
177
+ failover.ko 73571d1192683fa4f6346d7082920b4ac1a3b2edfb2da9e08ec13269dcb71f5e
178
+ net_failover.ko ba1700cf056585843488a5a8e75884d16cee45b4d40d8ace2f977e697cb5557c
179
+ virtio_net.ko fea67b801481f1445be419541e7a352a07019d52acbf644fe01f21c51d8d1475
180
+ virtio_pci_legacy_dev.ko 0cbfc93f3ef238c2565187eb8be94d1d0127bf2fe521f28b75245db12f4331ff
181
+ virtio_pci_modern_dev.ko 3fba75f38825df5ab616492bf3fb3329c4aed1a8b7f9a98370706e70a14fc2a3
182
+ virtio_pci.ko 92652ff84904b2b2dda1549d362ec97377f954da5463575f7e35a4ef5847c3ad
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.54-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.54-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.54-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.54-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)));