ports/toybox already installed its applets as hard links to one multicall binary, with a cp fallback. The links had never happened: the install is stamped, the stamp predated the ln, and 236 separate 784 KB copies had been sitting in sysroot/bin ever since. Forcing the install: 194 MB -> 13 MB. Which immediately broke everything, in a way worth writing down. **/bin/ping exists twice.** toybox has a ping applet and cmd/ping is a first-party program that talks to lwIP directly. The install rule for cmd/ programs was `cp`, and cp onto an existing hard link writes *through* it -- so copying cmd/ping over the applet of the same name replaced the contents of the shared inode and all 236 links became ping. `cat /proc/uptime` answered "ping: not a dotted-quad address". Invisible until the links existed: while the applets were separate copies, overwriting one affected exactly one. Fixed from both directions, because it can be hit either way -- the cmd/ rule removes the target before copying, and toybox skips names SchOS provides itself. **The USB test image stopped fitting.** mkfs writes each hard link as its own copy, so /bin expanded to over 60 MB on the way in. That image exists to prove the USB driver can read one block, and bootcheck asserts on its 16 MB size, so it excludes /bin rather than growing. The ISO is the point of all this: 196 MB before, 62 MB now, and it boots to a shell with RAMROOT=1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
95 lines
4 KiB
Makefile
95 lines
4 KiB
Makefile
# SPDX-License-Identifier: Apache-2.0
|
|
# toybox — 0BSD-licensed: https://landley.net/toybox/
|
|
# Provides SchOS's core utilities (ls, cat, cp, mv, rm, mkdir, ...) as a
|
|
# single multicall binary, installed under sysroot/bin/ once per applet name.
|
|
#
|
|
# mkfs (tools/mkfs.c) doesn't understand symlinks or hardlink dedup — each
|
|
# sysroot/bin/<name> below is a real, independent copy of the same toybox
|
|
# binary. That costs disk space per applet instead of one shared inode, but
|
|
# needs no changes to mkfs and keeps this Makefile self-contained.
|
|
|
|
include ../mk/port.mk
|
|
|
|
# Applets SchOS provides itself, which toybox must not link over.
|
|
#
|
|
# The two collide by name, and the collision is worse than a duplicate: the
|
|
# applets are hard links to one multicall binary, so whichever install runs
|
|
# second wins *for all 236 of them*. Installing first-party ping over the
|
|
# applet replaced the shared inode and every applet became ping; installing
|
|
# the applet over first-party ping put it back and lost the real one.
|
|
#
|
|
# SchOS's ping talks to its own lwIP stack and knows there is no resolver yet.
|
|
# toybox's would be the wrong answer even if it worked.
|
|
TOYBOX_SKIP = ping
|
|
|
|
TOYBOX_VERSION := 0.8.12
|
|
TOYBOX_URL := http://landley.net/toybox/downloads/toybox-$(TOYBOX_VERSION).tar.gz
|
|
TOYBOX_SHA256 := ad88a921133ae2231d9f2df875ec0bd42af4429145caea7d7db9e02208a6fd2e
|
|
|
|
TARBALL := $(DISTDIR)/toybox-$(TOYBOX_VERSION).tar.gz
|
|
|
|
CFLAGS := -static -D__SCHOS__ -I$(MUSL)/include -fno-stack-protector
|
|
LDFLAGS := -static -L$(MUSL)/lib -lc
|
|
|
|
# The applet list is not hard-coded any more: `toybox` with no arguments prints
|
|
# exactly the applets the binary was built with, so the install below asks the
|
|
# binary itself. A hand-maintained list installed 57 of the ~250 available and
|
|
# silently drifted every time the toybox config changed.
|
|
|
|
$(TARBALL): | $(DISTDIR)
|
|
curl -fsSL -o $@ $(TOYBOX_URL)
|
|
sh $(SHA256CHECK) "$(TOYBOX_SHA256)" "$@"
|
|
|
|
download: $(TARBALL)
|
|
|
|
$(SRCDIR): $(TARBALL)
|
|
mkdir -p $(PORTDIR)
|
|
tar -xzf $< -C $(PORTDIR)
|
|
mv $(PORTDIR)/toybox-$(TOYBOX_VERSION) $@
|
|
|
|
# toybox has no out-of-tree build mode, so its source is copied into $(BUILDDIR)
|
|
# and built in place. $(SRCDIR) is an order-only prerequisite (|): the extracted
|
|
# tarball never changes, and depending on it normally made a directory mtime
|
|
# trigger a rebuild. The rebuild then failed anyway, because `cp -r` over a
|
|
# populated $(BUILDDIR) hits "File exists" on kconfig's symlinks — so wipe it
|
|
# first and always build from a clean copy.
|
|
$(BUILDDIR)/toybox: | $(SRCDIR)
|
|
rm -rf $(BUILDDIR)
|
|
mkdir -p $(BUILDDIR)
|
|
cp -r $(SRCDIR)/. $(BUILDDIR)/
|
|
$(MAKE) -C $(BUILDDIR) defconfig
|
|
$(MAKE) -C $(BUILDDIR) CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)"
|
|
|
|
# Install every applet the binary provides, one name per applet.
|
|
#
|
|
# mkfs deduplicates by content, so all of these collapse to a single inode with
|
|
# many hard links in the image -- the cost is one copy, not one per name. On the
|
|
# host we try a hard link first for the same reason and fall back to a copy if
|
|
# the filesystem will not take one (NTFS via WSL usually will).
|
|
#
|
|
# The work hangs off a stamp file rather than off the phony `install` target.
|
|
# port.mk declares install .PHONY for every port, so this recipe used to run on
|
|
# every single build -- re-creating 236 links whether or not toybox had been
|
|
# rebuilt. That is slow on its own, and worse, it bumped the mtime of 236 files
|
|
# in sysroot/ every time, which made the top-level build think the staged tree
|
|
# had changed and rebuild the whole filesystem image from scratch.
|
|
install: $(BUILDDIR)/toybox.installed
|
|
|
|
$(BUILDDIR)/toybox.installed: $(BUILDDIR)/toybox | $(SYSROOT)/bin
|
|
cp $< $(SYSROOT)/bin/toybox
|
|
chmod 755 $(SYSROOT)/bin/toybox
|
|
@for a in $$($(BUILDDIR)/toybox); do \
|
|
case " $(TOYBOX_SKIP) " in *" $$a "*) continue ;; esac; \
|
|
rm -f $(SYSROOT)/bin/"$$a"; \
|
|
ln $(SYSROOT)/bin/toybox $(SYSROOT)/bin/"$$a" 2>/dev/null \
|
|
|| cp $< $(SYSROOT)/bin/"$$a"; \
|
|
done
|
|
@echo "toybox: installed $$($(BUILDDIR)/toybox | wc -w) applets"
|
|
@touch $@
|
|
|
|
|
|
clean:
|
|
rm -rf $(PORTDIR)
|
|
|
|
distclean: clean
|
|
rm -f $(TARBALL)
|