Twelve login terminals, root only, console fixes, a faster build, a Niveus splash, and X back on evdev

- tty1-tty12 all run login; the image has only root. /dev/tty7..12 could not
  be written (cdev.c knew minors 10-16 only), which sent their jobs into a
  restart storm that took the supervisor down; fixed, and stat reports them as
  Linux's tty7..12.
- Erasing text at a prompt did nothing: erase_one sent a bare backspace, which
  only moves; it and the echo path now send back-blank-back through vt_feed.
- A white cursor block was left at the end of every line of a verbose boot:
  a line feed at the bottom scrolls the pixels, cursor included, and the
  cursor was erased after the scroll. It comes off first now. A blank cell is
  also marked dirty, which it was not.
- ldlsd died with a general protection fault on real hardware in its variadic
  log function: Linux module code calls with an 8-byte-aligned stack and an
  unset %al, and the prologue's movaps faults. LDLS guest code is built with
  -mstackrealign.
- make iso took over a minute for any change, because staging r8169 rewrote
  the same files and every mtime moved; it now replaces files only if their
  contents differ (about 10 s).
- The loader's splash is redrawn in the boot manager's style, and the boot
  manager shows its cursor whenever a pointer device exists.
- xf86-input-libinput is out of the image again: with it Xorg took both
  keyboard and pointer from libinput and, on the user's laptop, neither worked.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
This commit is contained in:
Nexoniarz 2026-10-26 17:43:44 +01:00
commit 2aef583aad
23 changed files with 290 additions and 155 deletions

View file

@ -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

View file

@ -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;
}
}

View file

@ -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();

View file

@ -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.

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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');
}
}
}

View file

@ -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

View file

@ -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

View file

@ -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);
}

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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