diff --git a/Makefile b/Makefile index 5ae514e..07a4669 100644 --- a/Makefile +++ b/Makefile @@ -1120,22 +1120,30 @@ $(BUILD)/lib/%.o: lib/%.S # own -- a specific rule beats the pattern one below. ldls/ko.c, btf.c and # stub.c are freestanding and build unchanged for either side, which is the # whole point of having written them that way. +# A Linux module is compiled with an 8-byte-aligned stack and, being kernel +# code, never sets %al for a variadic call -- so it arrives in this code with +# the stack off by eight and %al garbage. Any function here that takes `...` +# then opens with `movaps %xmm0, -0xb0(%rbp)`, which faults on a misaligned +# address: that was the general-protection fault in ldlsd on real hardware, in +# the log function, the moment the first driver message was printed. +# -mstackrealign makes every function re-align its own stack on entry. +LDLS_ALIGN = -mstackrealign LDLS_GUEST = $(addprefix $(BUILD)/ldls/guest/, ko.o btf.o stub.o) \ $(addprefix $(BUILD)/ldls/guest/kpi/, kpi.o pci.o io.o net.o skb.o string.o work.o thunk.o base.o) $(BUILD)/ldls/guest/%.o: ldls/%.c ldls/ko.h ldls/btf.h ldls/stub.h @mkdir -p $(BUILD)/ldls/guest - $(CC) $(CFLAGS) -c $< -o $@ + $(CC) $(CFLAGS) $(LDLS_ALIGN) -c $< -o $@ # The KPI -- the Linux symbols, implemented. Guest-only: it reaches /dev/pci # and /dev/netdev, which exist on SchOS and nowhere else. $(BUILD)/ldls/guest/kpi/%.o: ldls/kpi/%.c ldls/kpi/kpi.h ldls/kpi/link_modes.inc @mkdir -p $(BUILD)/ldls/guest/kpi - $(CC) $(CFLAGS) -I./ldls -c $< -o $@ + $(CC) $(CFLAGS) $(LDLS_ALIGN) -I./ldls -c $< -o $@ $(BUILD)/cmd/ldlsd: cmd/ldlsd/main.c $(ULIB) $(LDLS_GUEST) @mkdir -p $(BUILD)/cmd - $(CC) $(CFLAGS) -I./ldls -c $< -o $(BUILD)/cmd/ldlsd.o + $(CC) $(CFLAGS) $(LDLS_ALIGN) -I./ldls -c $< -o $(BUILD)/cmd/ldlsd.o $(LD) $(LDFLAGS) $(LD_LOWBASE) -e _start -Ttext 0x4000 -o $@ \ $(BUILD)/cmd/ldlsd.o $(LDLS_GUEST) $(ULIB) $(OBJCOPY) -S $@ @@ -1427,21 +1435,33 @@ LDLS_FWDIR ?= /run/current-system/firmware LDLS_R8169 = drivers/net/phy/mdio-bus drivers/net/phy/libphy \ drivers/net/phy/mdio_devres drivers/net/phy/realtek/realtek \ drivers/net/ethernet/realtek/r8169 +# Every file is replaced only if its contents differ. sysroot.stamp records the +# mtime of each staged file, `make iso` runs this every time, and rewriting the +# same bytes moved every mtime -- which rebuilt the root image and compressed it +# at zstd -19 (a minute) after a one-line kernel change. + ldls-r8169: @mkdir -p $(SYSROOT)/lib/modules $(SYSROOT)/lib/firmware/rtl_nic @set -e; list=; for m in $(LDLS_R8169); do \ f=$$(ls $(LDLS_KDIR)/$$m.ko* | head -1); n=$$(basename $$m).ko; \ - case $$f in *.xz) xz -dc $$f;; *.zst) zstd -qdc $$f;; *) cat $$f;; esac \ - > $(SYSROOT)/lib/modules/$$n; \ + t=$(SYSROOT)/lib/modules/$$n; \ + case $$f in *.xz) xz -dc $$f;; *.zst) zstd -qdc $$f;; *) cat $$f;; esac > $$t.new; \ + if cmp -s $$t.new $$t; then rm -f $$t.new; else mv -f $$t.new $$t; fi; \ list=$$list$${list:+,}/lib/modules/$$n; done; \ - cp /sys/kernel/btf/vmlinux $(SYSROOT)/lib/modules/btf; \ + if ! cmp -s /sys/kernel/btf/vmlinux $(SYSROOT)/lib/modules/btf; then \ + cp /sys/kernel/btf/vmlinux $(SYSROOT)/lib/modules/btf; fi; \ printf '#!/bin/sh\nmkdir -p /var/log\nexec /bin/ldlsd %s /lib/modules/btf > /var/log/ldls-r8169.log 2>&1\n' \ - "$$list" > $(SYSROOT)/lib/modules/ldls-r8169.sh + "$$list" > $(SYSROOT)/lib/modules/ldls-r8169.sh.new; \ + if cmp -s $(SYSROOT)/lib/modules/ldls-r8169.sh.new $(SYSROOT)/lib/modules/ldls-r8169.sh; then \ + rm -f $(SYSROOT)/lib/modules/ldls-r8169.sh.new; \ + else mv -f $(SYSROOT)/lib/modules/ldls-r8169.sh.new $(SYSROOT)/lib/modules/ldls-r8169.sh; fi @for f in $(LDLS_FWDIR)/rtl_nic/rtl8168*.fw*; do \ n=$$(basename $$f); case $$n in \ - *.zst) zstd -qdc $$f > $(SYSROOT)/lib/firmware/rtl_nic/$${n%.zst};; \ - *.xz) xz -dc $$f > $(SYSROOT)/lib/firmware/rtl_nic/$${n%.xz};; \ - *) cp $$f $(SYSROOT)/lib/firmware/rtl_nic/;; esac; done + *.zst) out=$${n%.zst}; zstd -qdc $$f > $(SYSROOT)/lib/firmware/rtl_nic/$$out.new;; \ + *.xz) out=$${n%.xz}; xz -dc $$f > $(SYSROOT)/lib/firmware/rtl_nic/$$out.new;; \ + *) out=$$n; cp $$f $(SYSROOT)/lib/firmware/rtl_nic/$$out.new;; esac; \ + d=$(SYSROOT)/lib/firmware/rtl_nic/$$out; \ + if cmp -s $$d.new $$d; then rm -f $$d.new; else mv -f $$d.new $$d; fi; done @echo "staged Linux r8169 ($(shell uname -r)) into $(SYSROOT)" # `make iso` runs this first: the ISO ships Linux's r8169 set (NOTICE), so stage it whenever diff --git a/boot/common/ui.c b/boot/common/ui.c index 4f168b2..694441a 100644 --- a/boot/common/ui.c +++ b/boot/common/ui.c @@ -265,40 +265,57 @@ ui_logo(int y, int w) /* Where the bar goes. Derived, not stored, so nothing can set it and then have * the screen change underneath it. * - * The bar is exactly the mark's width and sits directly beneath it: the two are - * one object, and a bar wider than the mark it belongs to reads as a separate - * thing that happens to be nearby. + * The track is wider than the mark and sits beneath it: a mark with a recessed + * track and one line of type, centred, on the same graphite the boot manager + * uses. Laid out from the middle outwards so it reads the same at 800x600 and + * at 4K. S() is defined further down with the rest of the Niveus code, so the + * scale is applied through SC directly. */ static void bar_geometry(void) { - PROG_W = ui_mark_width(); - PROG_H = BAR_H; + PROG_W = 220 * SC; + PROG_H = 6 * SC; PROG_X = ((int)FB.width - PROG_W) / 2; - int mark_h = (LOGO_H * PROG_W) / LOGO_W; - int group_h = mark_h + MARK_GAP + PROG_H; + int mw = ui_mark_width() * SC; + if (mw > LOGO_W) mw = LOGO_W; + int mark_h = (LOGO_H * mw) / LOGO_W; + int gap = 34 * SC; + int group_h = mark_h + gap + PROG_H; int top = ((int)FB.height - group_h) / 2; - PROG_Y = top + mark_h + MARK_GAP; + PROG_Y = top + mark_h + gap; } +static ui_u32 bg_at(int y); +static void nv_backdrop(void); +static int nv_width(const struct nvfont *f, const char *s); +static void nv_text(const struct nvfont *f, int x, int base, const char *s, + ui_u32 fg, ui_u32 bgtop, ui_u32 bgbot, int y0, int h); +static void rrect(int x, int y, int w, int h, int r, ui_u32 top, ui_u32 bot, + ui_u32 bgtop, ui_u32 bgbot, int bgy, int bgh); + void ui_splash(const char *subtitle) { if (!ACTIVE) return; - ui_clear(); + nv_backdrop(); bar_geometry(); - int w = PROG_W; + int w = ui_mark_width() * SC; + if (w > LOGO_W) w = LOGO_W; int mark_h = (LOGO_H * w) / LOGO_W; - int top = PROG_Y - MARK_GAP - mark_h; + int top = PROG_Y - 34 * SC - mark_h; + ui_u32 saved = LOGO_BG; + LOGO_BG = bg_at(top + mark_h / 2); ui_logo(top, w); + LOGO_BG = saved; if (subtitle) - ui_text_centered(PROG_Y + PROG_H + SUB_GAP, subtitle, SCH_DIM); + ui_progress_label(subtitle); } /* --- Niveus ----------------------------------------------------------------- @@ -912,6 +929,25 @@ ui_notice(const char *title, const char *const *lines, int n, box(title, lines, 0, n, button, hint); } +/* The track is a recess, the fill is the accent, lit from above -- the same two + * materials as a well and a selected key in the boot manager. The fill is + * redrawn from the left edge on every tick rather than extended, because its + * rounded end moves; the bar is small enough that this costs nothing. */ +static void +progress_paint(int px) +{ + int r = PROG_H / 2; + ui_u32 bg = bg_at(PROG_Y + PROG_H / 2); + + rrect(PROG_X, PROG_Y, PROG_W, PROG_H, r, 0x0A0A0Bu, 0x151517u, bg, bg, 0, 1); + if (px >= PROG_H) { + ui_u32 top = mix(SCH_ACCENT, 0xFFFFFFu, 28, 100); + ui_u32 bot = mix(SCH_ACCENT, 0x000000u, 24, 100); + rrect(PROG_X + 1, PROG_Y + 1, px - 2, PROG_H - 2, r - 1, top, bot, + 0x0A0A0Bu, 0x151517u, PROG_Y, PROG_H); + } +} + void ui_progress_begin(const char *label, ui_u32 total) { @@ -924,10 +960,7 @@ ui_progress_begin(const char *label, ui_u32 total) PROG_LAST_PX = 0; PROG_ACTIVE = 1; - /* The unfilled track: a recess, not a colour. Drawn once -- repainting it - * on every tick is what makes a progress bar flicker. */ - ui_fill_rect(PROG_X, PROG_Y, PROG_W, PROG_H, 0x1B1F28u); - ui_fill_rect(PROG_X, PROG_Y, PROG_W, 1, 0x101319u); + progress_paint(0); if (label) ui_progress_label(label); } @@ -938,11 +971,16 @@ ui_progress_label(const char *label) if (!ACTIVE || !label) return; - /* Clear the line first. A shorter label after a longer one would otherwise - * leave the tail of the old one behind. */ - int y = PROG_Y + PROG_H + SUB_GAP; - ui_fill_rect(0, y, (int)FB.width, GLYPH_H, SCH_BG); - ui_text_centered(y, label, SCH_DIM); + /* Repaint the backdrop under the line first. A shorter label after a longer + * one would otherwise leave the tail of the old one behind. */ + int h = nv_height(F_BODY) + 4 * SC; + int y = PROG_Y + PROG_H + 18 * SC; + for (int r = 0; r < h; r++) + ui_fill_rect(0, y + r, (int)FB.width, 1, bg_at(y + r)); + + int tw = nv_width(F_BODY, label); + nv_text(F_BODY, ((int)FB.width - tw) / 2, y + F_BODY->ascent, label, NV_TXT3, + bg_at(y), bg_at(y + h), y, h); } void @@ -956,16 +994,8 @@ ui_progress_tick(ui_u32 done) PROG_DONE = done; int px = (int)(((ui_u64)PROG_DONE * (ui_u64)PROG_W) / PROG_TOTAL); - - /* Only the newly filled part. Filling from zero each time is O(width) per - * tick and visibly slower than the thing it is reporting on. */ if (px > PROG_LAST_PX) { - /* The fill is metal too, shaded across the bar's height, so it looks - * like the same material as the mark rather than a coloured rectangle - * that happens to be underneath it. */ - for (int r = 0; r < PROG_H; r++) - ui_fill_rect(PROG_X + PROG_LAST_PX, PROG_Y + r, - px - PROG_LAST_PX, 1, metal(r, PROG_H)); + progress_paint(px); PROG_LAST_PX = px; } } diff --git a/boot/uefi/main.c b/boot/uefi/main.c index 86ecb63..f8a6e97 100644 --- a/boot/uefi/main.c +++ b/boot/uefi/main.c @@ -1063,15 +1063,15 @@ pointer_poll(void) } /* Wait for a key, a click on a key, or the pointer moving onto one. + * + * The cursor is shown whenever a pointer device is present: hiding it until + * something moved left a machine whose pointer sends no events to the + * loader with nothing on screen to say a pointer was even looked for. * * `budget` is a countdown in milliseconds that survives across calls: a * caller that returns here after a hover continues the same ten seconds * instead of being given a fresh ten. NULL waits for ever. - * - * The cursor stays hidden until the pointer has actually done something. The - * firmware's console layer installs a pointer protocol whether or not a mouse - * is attached, so "a protocol exists" proves nothing -- and a cursor parked in - * the middle of the menu of a laptop with no mouse is worse than none. */ + */ static int wait_event(EFI_INPUT_KEY *key, int *item, UINTN *budget) { @@ -1082,6 +1082,8 @@ wait_event(EFI_INPUT_KEY *key, int *item, UINTN *budget) PTR_X = (int)ui_width() / 2; PTR_Y = (int)ui_height() / 2; } + if (have) + PTR_ACTIVE = 1; ui_cursor(PTR_X, PTR_Y, PTR_ACTIVE); for (;;) { if (ST->ConIn && !EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, key))) @@ -1094,6 +1096,10 @@ wait_event(EFI_INPUT_KEY *key, int *item, UINTN *budget) PTR_RESCANS++; pointers_find(); have = PTR_NREL + PTR_NABS; + if (have && !PTR_ACTIVE) { + PTR_ACTIVE = 1; + ui_cursor(PTR_X, PTR_Y, 1); + } } if (have) { int e = pointer_poll(); diff --git a/docs/architecture/desktop.md b/docs/architecture/desktop.md index 7108238..bba2848 100644 --- a/docs/architecture/desktop.md +++ b/docs/architecture/desktop.md @@ -75,5 +75,5 @@ through ACPI, and reach X as a mouse: pointer, click and tap. - **Touchpads are mice**: no two-finger scroll, no gestures, no palm rejection. - **No resolution change from inside X.** The mode is the console's; `schmode` changes the `vgpud` head only, and the loader's menu is where a real panel's mode is chosen. -- **Runs as an ordinary user.** `startx` as `schos` works by group permission alone - ([users.md](users.md)); X itself is still a single seat with no logind or seatd. +- **Runs as an ordinary user** where there is one: `startx` works by group permission alone + ([users.md](users.md)); the image has only root; X itself is still a single seat with no logind or seatd. diff --git a/docs/architecture/users.md b/docs/architecture/users.md index 252ab4c..c4c1c47 100644 --- a/docs/architecture/users.md +++ b/docs/architecture/users.md @@ -6,15 +6,15 @@ the part that makes it a multi-user system. ## What you meet -- **`login` on tty1–tty5.** `etc/schos-getty` takes the terminal back from - whoever had it and runs `login`, which asks who is there and starts their - shell; when the shell exits `schinit` starts the prompt again. -- **tty6 is a root shell with no login**, on purpose: the way in when login - itself is what is broken. -- **Two accounts in the image.** `root`, and `schos` (uid 1000, home - `/home/schos`, in `wheel tty video input audio cdrom`). Both have empty - passwords, so `login` lets them in without asking — it is a live image. - `passwd` sets one. +- **`login` on all twelve terminals, tty1–tty12.** `etc/schos-getty` takes the + terminal back from whoever had it and runs `login`, which asks who is there + and starts their shell; when the shell exits `schinit` starts the prompt + again. Alt+F1…F12 (or Ctrl+Alt+F1…F12) switches between them. (There is no + longer a login-free root shell on tty6.) +- **One account in the image: `root`**, with an empty password, so `login` lets + it in without asking — it is a live image. `passwd` sets one. More users are + an `adduser` away; `wheel` is empty, so `su` and `doas` have no one to serve + until then. - **`su` and `passwd`** (`cmd/su`, `cmd/passwd`) are setuid root and small programs of their own, not toybox applets: toybox is one binary under every name, and a setuid copy of it can be asked for any applet by naming it, as @@ -61,8 +61,8 @@ kernel's devices with the answers the kernel gave. Everything in the image is owned by root: the build host's user means nothing inside it. `mkschfs --owners FILE` takes the exceptions, one `path uid gid -[mode]` per line (`tools/posix/mkaroot.sh` writes it: `/home/schos 1000 1000 -0700`, and the modes of `/tmp`, `/var/tmp` and `/root`). The setuid bits are set +[mode]` per line (`tools/posix/mkaroot.sh` writes it: the modes of `/tmp`, +`/var/tmp` and `/root`). The setuid bits are set on `su`, `passwd` and `doas` in the root tree directly, because `apk` run as an ordinary user may have dropped them. diff --git a/etc/schinit/tty10-shell.job b/etc/schinit/tty10-shell.job new file mode 100644 index 0000000..c1e88b3 --- /dev/null +++ b/etc/schinit/tty10-shell.job @@ -0,0 +1,20 @@ +# A login on /dev/tty10: every one of the twelve virtual terminals asks who is there. +# Alt+F10 (or Ctrl+Alt+F10) shows it. Twelve manifests rather than one with a +# range, because schinit has no notion of a template. + +Label = os.schos.tty10-shell +Program = /bin/sh +Args = sh /etc/schos-getty +RunAtLoad = yes +KeepAlive = yes + +Environment = TERM=linux +Environment = PATH=/bin:/usr/bin + +ControllingTerminal = yes + +StandardIn = /dev/tty10 +StandardOut = /dev/tty10 +StandardError = /dev/tty10 + +ThrottleInterval = 1 diff --git a/etc/schinit/tty11-shell.job b/etc/schinit/tty11-shell.job new file mode 100644 index 0000000..5d25203 --- /dev/null +++ b/etc/schinit/tty11-shell.job @@ -0,0 +1,20 @@ +# A login on /dev/tty11: every one of the twelve virtual terminals asks who is there. +# Alt+F11 (or Ctrl+Alt+F11) shows it. Twelve manifests rather than one with a +# range, because schinit has no notion of a template. + +Label = os.schos.tty11-shell +Program = /bin/sh +Args = sh /etc/schos-getty +RunAtLoad = yes +KeepAlive = yes + +Environment = TERM=linux +Environment = PATH=/bin:/usr/bin + +ControllingTerminal = yes + +StandardIn = /dev/tty11 +StandardOut = /dev/tty11 +StandardError = /dev/tty11 + +ThrottleInterval = 1 diff --git a/etc/schinit/tty12-shell.job b/etc/schinit/tty12-shell.job new file mode 100644 index 0000000..212ac19 --- /dev/null +++ b/etc/schinit/tty12-shell.job @@ -0,0 +1,20 @@ +# A login on /dev/tty12: every one of the twelve virtual terminals asks who is there. +# Alt+F12 (or Ctrl+Alt+F12) shows it. Twelve manifests rather than one with a +# range, because schinit has no notion of a template. + +Label = os.schos.tty12-shell +Program = /bin/sh +Args = sh /etc/schos-getty +RunAtLoad = yes +KeepAlive = yes + +Environment = TERM=linux +Environment = PATH=/bin:/usr/bin + +ControllingTerminal = yes + +StandardIn = /dev/tty12 +StandardOut = /dev/tty12 +StandardError = /dev/tty12 + +ThrottleInterval = 1 diff --git a/etc/schinit/tty3-shell.job b/etc/schinit/tty3-shell.job index c34c027..6b6e8c5 100644 --- a/etc/schinit/tty3-shell.job +++ b/etc/schinit/tty3-shell.job @@ -1,13 +1,6 @@ -# An interactive shell on /dev/tty3. -# -# NVT is 6 (include/dev/vt.h) and the kernel creates all six terminals, but only -# two ever had anything running on them -- so Alt+F3 switched to a terminal -# that was working perfectly and had nobody to talk to, which looks exactly -# like a broken one. -# -# Identical to tty2-shell apart from the number. Six manifests rather than one -# with a range, because schinit has no notion of a template and inventing one -# for six lines each is the wrong trade. +# A login on /dev/tty3: every one of the twelve virtual terminals asks who is there. +# Alt+F3 (or Ctrl+Alt+F3) shows it. Twelve manifests rather than one with a +# range, because schinit has no notion of a template. Label = os.schos.tty3-shell Program = /bin/sh diff --git a/etc/schinit/tty4-shell.job b/etc/schinit/tty4-shell.job index 020e94a..b9d021a 100644 --- a/etc/schinit/tty4-shell.job +++ b/etc/schinit/tty4-shell.job @@ -1,13 +1,6 @@ -# An interactive shell on /dev/tty4. -# -# NVT is 6 (include/dev/vt.h) and the kernel creates all six terminals, but only -# two ever had anything running on them -- so Alt+F4 switched to a terminal -# that was working perfectly and had nobody to talk to, which looks exactly -# like a broken one. -# -# Identical to tty2-shell apart from the number. Six manifests rather than one -# with a range, because schinit has no notion of a template and inventing one -# for six lines each is the wrong trade. +# A login on /dev/tty4: every one of the twelve virtual terminals asks who is there. +# Alt+F4 (or Ctrl+Alt+F4) shows it. Twelve manifests rather than one with a +# range, because schinit has no notion of a template. Label = os.schos.tty4-shell Program = /bin/sh diff --git a/etc/schinit/tty5-shell.job b/etc/schinit/tty5-shell.job index fb09d19..76ee275 100644 --- a/etc/schinit/tty5-shell.job +++ b/etc/schinit/tty5-shell.job @@ -1,13 +1,6 @@ -# An interactive shell on /dev/tty5. -# -# NVT is 6 (include/dev/vt.h) and the kernel creates all six terminals, but only -# two ever had anything running on them -- so Alt+F5 switched to a terminal -# that was working perfectly and had nobody to talk to, which looks exactly -# like a broken one. -# -# Identical to tty2-shell apart from the number. Six manifests rather than one -# with a range, because schinit has no notion of a template and inventing one -# for six lines each is the wrong trade. +# A login on /dev/tty5: every one of the twelve virtual terminals asks who is there. +# Alt+F5 (or Ctrl+Alt+F5) shows it. Twelve manifests rather than one with a +# range, because schinit has no notion of a template. Label = os.schos.tty5-shell Program = /bin/sh diff --git a/etc/schinit/tty6-shell.job b/etc/schinit/tty6-shell.job index 90471e6..ff62eda 100644 --- a/etc/schinit/tty6-shell.job +++ b/etc/schinit/tty6-shell.job @@ -1,27 +1,15 @@ -# A root shell on /dev/tty6, with no login: the way in when login itself is -# what has broken. Every other terminal asks who is there. -# -# An interactive shell on /dev/tty6. -# -# NVT is 6 (include/dev/vt.h) and the kernel creates all six terminals, but only -# two ever had anything running on them -- so Alt+F6 switched to a terminal -# that was working perfectly and had nobody to talk to, which looks exactly -# like a broken one. -# -# Identical to tty2-shell apart from the number. Six manifests rather than one -# with a range, because schinit has no notion of a template and inventing one -# for six lines each is the wrong trade. +# A login on /dev/tty6: every one of the twelve virtual terminals asks who is there. +# Alt+F6 (or Ctrl+Alt+F6) shows it. Twelve manifests rather than one with a +# range, because schinit has no notion of a template. Label = os.schos.tty6-shell -Program = /bin/zsh -Args = -zsh +Program = /bin/sh +Args = sh /etc/schos-getty RunAtLoad = yes KeepAlive = yes Environment = TERM=linux Environment = PATH=/bin:/usr/bin -Environment = HOME=/root -Environment = SHELL=/bin/zsh ControllingTerminal = yes diff --git a/etc/schinit/tty7-shell.job b/etc/schinit/tty7-shell.job new file mode 100644 index 0000000..2f1a9da --- /dev/null +++ b/etc/schinit/tty7-shell.job @@ -0,0 +1,20 @@ +# A login on /dev/tty7: every one of the twelve virtual terminals asks who is there. +# Alt+F7 (or Ctrl+Alt+F7) shows it. Twelve manifests rather than one with a +# range, because schinit has no notion of a template. + +Label = os.schos.tty7-shell +Program = /bin/sh +Args = sh /etc/schos-getty +RunAtLoad = yes +KeepAlive = yes + +Environment = TERM=linux +Environment = PATH=/bin:/usr/bin + +ControllingTerminal = yes + +StandardIn = /dev/tty7 +StandardOut = /dev/tty7 +StandardError = /dev/tty7 + +ThrottleInterval = 1 diff --git a/etc/schinit/tty8-shell.job b/etc/schinit/tty8-shell.job new file mode 100644 index 0000000..5ce6fd6 --- /dev/null +++ b/etc/schinit/tty8-shell.job @@ -0,0 +1,20 @@ +# A login on /dev/tty8: every one of the twelve virtual terminals asks who is there. +# Alt+F8 (or Ctrl+Alt+F8) shows it. Twelve manifests rather than one with a +# range, because schinit has no notion of a template. + +Label = os.schos.tty8-shell +Program = /bin/sh +Args = sh /etc/schos-getty +RunAtLoad = yes +KeepAlive = yes + +Environment = TERM=linux +Environment = PATH=/bin:/usr/bin + +ControllingTerminal = yes + +StandardIn = /dev/tty8 +StandardOut = /dev/tty8 +StandardError = /dev/tty8 + +ThrottleInterval = 1 diff --git a/etc/schinit/tty9-shell.job b/etc/schinit/tty9-shell.job new file mode 100644 index 0000000..511dd1a --- /dev/null +++ b/etc/schinit/tty9-shell.job @@ -0,0 +1,20 @@ +# A login on /dev/tty9: every one of the twelve virtual terminals asks who is there. +# Alt+F9 (or Ctrl+Alt+F9) shows it. Twelve manifests rather than one with a +# range, because schinit has no notion of a template. + +Label = os.schos.tty9-shell +Program = /bin/sh +Args = sh /etc/schos-getty +RunAtLoad = yes +KeepAlive = yes + +Environment = TERM=linux +Environment = PATH=/bin:/usr/bin + +ControllingTerminal = yes + +StandardIn = /dev/tty9 +StandardOut = /dev/tty9 +StandardError = /dev/tty9 + +ThrottleInterval = 1 diff --git a/kernel/core/console.c b/kernel/core/console.c index 60474f4..6b39994 100644 --- a/kernel/core/console.c +++ b/kernel/core/console.c @@ -302,7 +302,7 @@ consputc(int c) uartputc('\b'); uartputc(' '); uartputc('\b'); if (!cons_serial_only) { struct vt *v = vt_current(); - vt_putc(v, '\b'); vt_putc(v, ' '); vt_putc(v, '\b'); + vt_feed(v, '\b'); vt_feed(v, ' '); vt_feed(v, '\b'); } return; } @@ -452,8 +452,12 @@ erase_one(struct vt *v) v->in.e--; if (v->tio.c_lflag & ECHOE) { if (v == vt_current()) { uartputc('\b'); uartputc(' '); uartputc('\b'); } - if (!cons_serial_only) - vt_putc(v, '\b'); + // Back, blank, back -- and through vt_feed, which keeps the text cursor + // in step. A bare \b only moves (see vt.c), so this used to leave the + // rubbed-out character on screen and the cursor block behind it. + if (!cons_serial_only) { + vt_feed(v, '\b'); vt_feed(v, ' '); vt_feed(v, '\b'); + } } } diff --git a/kernel/dev/cdev.c b/kernel/dev/cdev.c index 3d52758..9dbe67e 100644 --- a/kernel/dev/cdev.c +++ b/kernel/dev/cdev.c @@ -149,9 +149,10 @@ memread(struct inode *ip, uint64 off, char *dst, int n) case 1: return consoleread(ip, off, dst, n); case 2: return nullread(ip, off, dst, n); case 3: return consoleread(ip, off, dst, n); - /* 10 = /dev/tty, 11..16 = /dev/tty1..tty6. consoleread resolves the + /* 10 = /dev/tty, 11..22 = /dev/tty1..tty12. consoleread resolves the minor to a terminal; see vt_for_minor(). */ case 10: case 11: case 12: case 13: case 14: case 15: case 16: + case 17: case 18: case 19: case 20: case 21: case 22: return consoleread(ip, off, dst, n); case 5: return zeroread(ip, off, dst, n); case 7: return zeroread(ip, off, dst, n); // /dev/full reads as zeros @@ -169,6 +170,7 @@ memwrite(struct inode *ip, uint64 off, char *dst, int n) switch(ip->minor) { case 1: return consolewrite(ip, off, dst, n); case 10: case 11: case 12: case 13: case 14: case 15: case 16: + case 17: case 18: case 19: case 20: case 21: case 22: return consolewrite(ip, off, dst, n); case 7: return fullwrite(ip, off, dst, n); // /dev/full: always ENOSPC case 3: // /dev/keyboard: discarded diff --git a/kernel/dev/gfx.c b/kernel/dev/gfx.c index 0e887ba..c64fa08 100644 --- a/kernel/dev/gfx.c +++ b/kernel/dev/gfx.c @@ -406,6 +406,11 @@ draw_glyph_px(int x, int y, uint32 cp, uint32 fg, uint32 bg) for (int c = 0; c < cell_w; c++) gfx_draw_pixel(x + c, y + r, bg); + // Dirty before the early return: a blank cell is a background fill and + // nothing else, and that fill is what rubs the text cursor out. Marked + // only after the glyph, the blank was drawn to the back buffer and never + // copied to the screen -- a block left at the end of every line. + dirty(x, y, x + cell_w - 1, y + cell_h - 1); if (cp == ' ') return; // nothing to draw over the background diff --git a/kernel/dev/vt.c b/kernel/dev/vt.c index e892624..bfb8412 100644 --- a/kernel/dev/vt.c +++ b/kernel/dev/vt.c @@ -1241,20 +1241,26 @@ vt_cycle(int dir) void vt_feed(struct vt *v, int c) { - // The grid is the truth, so the old cursor cell can be repainted from it - // *after* the byte is consumed -- which lets two cases cost nothing: - // - a byte inside an escape sequence changes nothing on screen; - // - a plain glyph is drawn over the cell the cursor sat on, which repaints - // it anyway (unless a wrap is pending, when it lands on the next line). + // A plain glyph is drawn over the cell the cursor sits on, which repaints it + // anyway (unless a wrap is pending, when it lands on the next line), so the + // cursor needs no separate erase there. int mid = v->esc_state != VE_NORMAL; int over = !mid && !v->wrap_pending && c >= 0x20 && c < 0x7f && v->u8_need == 0; - vt_feed_1(v, c); - if (mid && v->esc_state != VE_NORMAL) - return; - if (over) + + if (over) { + vt_feed_1(v, c); v->curs_drawn = 0; - else - cursor_erase(v); + cursor_draw(v); + return; + } + + // Anything else may move the cursor, and a line feed at the bottom scrolls + // the *pixels*, block and all. So the cursor comes off first. Erased after + // the scroll, it was repainted where the cell had been and the old block + // went up a row with the text: a white block at the end of every line of a + // verbose boot. + cursor_erase(v); + vt_feed_1(v, c); cursor_draw(v); } diff --git a/kernel/sys/sys_fs.c b/kernel/sys/sys_fs.c index 60292ac..da2c1b1 100644 --- a/kernel/sys/sys_fs.c +++ b/kernel/sys/sys_fs.c @@ -82,7 +82,7 @@ static uint64 linux_tty_rdev(uint32 major, uint32 minor) { if (major == 1 && minor == 0) return linux_makedev(4, 0); - if (major == 1 && minor >= 11 && minor <= 16) return linux_makedev(4, minor - 10); + if (major == 1 && minor >= 11 && minor <= 22) return linux_makedev(4, minor - 10); if (major == 1 && minor == 10) return linux_makedev(5, 0); if (major == 1 && minor == 1) return linux_makedev(5, 1); // Pseudo-terminals, as Linux numbers them: ptmx (5, 2), slaves 136. diff --git a/ports/alpine/world b/ports/alpine/world index 4fdf5c3..214f187 100644 --- a/ports/alpine/world +++ b/ports/alpine/world @@ -76,6 +76,5 @@ libinput-udev libseat seatd seatd-launch -xf86-input-libinput # lsusb that reads /sys/bus/usb/devices, which busybox's does not. usbutils diff --git a/ports/alpine/world.lock b/ports/alpine/world.lock index 9bdfaa3..29652d3 100644 --- a/ports/alpine/world.lock +++ b/ports/alpine/world.lock @@ -181,7 +181,6 @@ xcalc-1.1.3-r0.apk 2f0d9ab5674a5f82f4adfb6dd5f0a9c944421feea2d6aaf18998b0326fe4c xclock-1.1.1-r0.apk ae2a3cb515664cf34dd9cd0f81945cad75d0b6bbf44696830f29f1ab4713b475 community xeyes-1.3.1-r0.apk 4b31dabe8d7f5000d82a24445972859bf960c1b07bca3d4f87e80b3c710902c6 community xf86-input-evdev-2.11.0-r0.apk 1f3fcd3e837b43ad5e32005e03095b547ddbfc25973f0511e9ff74bf342d2fe0 community -xf86-input-libinput-1.5.0-r0.apk 2bd4b9257f755bc68c7460068821e954ee6ca484e6b05637e673d5b9abace0e8 community xf86-video-fbdev-0.5.0-r6.apk 6a8a1b16f82d8810896f00c00b356c8b8d923e9aa0ce64737ae8274ca82f507a community xinit-1.4.4-r0.apk efb5dc1e7929824f5add7d5d51d0ecb34ecfd0a773dfca4f061adc8a353b234f community xkbcomp-1.5.0-r0.apk 8bfbd3c8ce7180e8cf599d5a0b791993b61fa98f3a19c47aaf5fcbe30c12d0ba main diff --git a/tools/posix/mkaroot.sh b/tools/posix/mkaroot.sh index bb64d96..ec2b820 100644 --- a/tools/posix/mkaroot.sh +++ b/tools/posix/mkaroot.sh @@ -358,46 +358,24 @@ chmod 1777 "$ROOT/tmp" "$ROOT/var/tmp" # --- accounts ------------------------------------------------------------------- # # Alpine's baselayout has root and the system accounts, with root's password -# locked. A live image needs a way in, so: root with no password, and an -# ordinary user, `schos`, who can become root with doas and su (they are in the -# wheel group) and uses the desktop. Both have empty passwords -- login lets -# them in without asking -- and `passwd` sets one. -# -# The numbers are Alpine's: wheel 10, tty 5, video 27, input 23, audio 18. -add_member() { # add_member GROUP USER - if grep -q "^$1:[^:]*:[^:]*:$" "$ROOT/etc/group"; then - sed -i "s/^\($1:[^:]*:[^:]*:\)$/\1$2/" "$ROOT/etc/group" - else - sed -i "s/^\($1:[^:]*:[^:]*:.*\)$/\1,$2/" "$ROOT/etc/group" - fi -} +# locked. A live image needs a way in, so: root, with no password -- login lets +# root in without asking, and `passwd` sets one. That is the only account; an +# ordinary user is one `adduser` away, and the permission checks, su and doas +# are all still there for when there is one. sed -i 's/^root:[^:]*:/root::/' "$ROOT/etc/shadow" -grep -q '^schos:' "$ROOT/etc/passwd" || - echo 'schos:x:1000:1000:SchOS user:/home/schos:/bin/zsh' >> "$ROOT/etc/passwd" -grep -q '^schos:' "$ROOT/etc/group" || - echo 'schos:x:1000:' >> "$ROOT/etc/group" -grep -q '^schos:' "$ROOT/etc/shadow" || - echo 'schos::0:0:::::' >> "$ROOT/etc/shadow" -for g in wheel tty video input audio cdrom; do - grep -q "^$g:.*[:,]schos\(,\|\$\)" "$ROOT/etc/group" || add_member $g schos -done chmod 640 "$ROOT/etc/shadow" -# Who may become root with doas: the wheel group, without being asked again. -# The file has to be root's and not writable by anyone else, or doas refuses it. +# doas: members of wheel run anything, without being asked again. Nobody is in +# wheel yet. The file has to be root's and not writable by anyone else. cat > "$ROOT/etc/doas.conf" <<'DEOF' # doas: see doas.conf(5). Members of wheel run anything as anyone. permit nopass :wheel DEOF chmod 644 "$ROOT/etc/doas.conf" -# The user's home, with what startx wants there. root's are in /root. -mkdir -p "$ROOT/home/schos" -cp "$ROOT/root/.xinitrc" "$ROOT/root/.xserverrc" "$ROOT/home/schos/" -chmod 755 "$ROOT/home/schos/.xserverrc" # zsh offers to write one on first login; /etc/zsh is where the settings are. -: > "$ROOT/home/schos/.zshrc" -chmod 700 "$ROOT/home/schos" "$ROOT/root" +: > "$ROOT/root/.zshrc" +chmod 700 "$ROOT/root" # The setuid programs. Run as an ordinary user, apk may have dropped the bit, # and nothing here should depend on whether it did. @@ -406,9 +384,8 @@ for f in bin/su usr/bin/passwd usr/bin/doas; do done # Who owns what inside the image: everything is root's -- the build host's -# user means nothing here -- except the user's home. +# user means nothing here. cat > "$OUT/owners" <<'OEOF' -/home/schos 1000 1000 0700 /root 0 0 0700 /tmp 0 0 1777 /var/tmp 0 0 1777