Skip to content

feat(factory-reset): drop mode 4 from the accepted reset modes - #128

Closed
JanZachmann wants to merge 1 commit into
omnect:mainfrom
JanZachmann:feat/drop-factory-reset-mode4
Closed

JanZachmann wants to merge 1 commit into
omnect:mainfrom
JanZachmann:feat/drop-factory-reset-mode4

Conversation

@JanZachmann

@JanZachmann JanZachmann commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Removes mode 4 from the factory-reset modes the web API accepts. A request carrying it is rejected when the body is deserialized; modes 1 to 3 are unchanged.

Reason

Mode 4 ran a customer-supplied wipe hook inside the initramfs. The hook is gone with the initramfs rewrite and the new init does not accept the mode: a request carrying it comes back as Invalid, a reset that never runs. Rejecting the value here keeps the request from being sent at all.

This has to reach a release together with the initramfs change and the matching change in omnect-device-service, so no component still offers a mode the device refuses.

Mode 4 ran a customer-supplied wipe hook in the initramfs. The hook is gone
and the initramfs no longer accepts the mode: a trigger carrying it fails to
parse, the device boots normally and no result is reported at all, so a reset
requested with it would never be answered.

The web API rejects the value now, instead of passing it on.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
JanZachmann added a commit to omnect/omnect-os-init that referenced this pull request Sep 24, 2026
## Summary

Adds the factory-reset wipe modes 2 and 3. Mode 2 overwrites `etc` and
`data` with data from `/dev/urandom`, mode 3 discards every block of
both with the `BLKDISCARD` ioctl. The wipe runs between the backup and
the reformat and never aborts the reset: a failure on one partition does
not skip the other, reformat and restore still run, and the outcome is
reported as `Error` with the reason in `error`. Mode 4 is no longer an
accepted mode.

It also closes what the on-device tests found: a trigger the init cannot
parse is answered instead of ignored, and a preserve configuration that
says nothing is rejected instead of skipped. A blank trigger is not one
of those — it is no reset request at all.

Design:
`docs/superpowers/specs/2026-08-11-factory-reset-modes-2-3-4-design.md`,
with a correction of what it claimed about rejected triggers.

## Reason

The legacy shell init implemented the wipe with external tools. Both are
replaced by native code, so the behaviour differs in ways worth stating.

**Mode 2** covers the whole partition from byte 0. The legacy `dd`
started at offset 2048 so an interrupted wipe kept the volume label
readable; the init finds partitions by number, never by label, and
re-creates the filesystem anyway. Privacy is the same or better. The
legacy `dd` also used `oflag=direct`; the write loop goes through the
page cache instead and flushes every gigabyte and again at the end, so
after a power loss at most one gigabyte of already overwritten blocks
can still hold the old content.

**Mode 3** issues one `BLKDISCARD` over the whole partition. The legacy
path mounted it, removed the files and ran `fstrim`, which trims only
the blocks the remove had freed and leaves journal remnants — and
counted a failed mount as a successful wipe. Without discard support the
ioctl now fails cleanly and the reset reports it. Discard stays a hint
on some disks, so the "no total privacy guarantee" note in the layer
README remains true.

Both modes open the device with `O_EXCL`, so a partition that is still
mounted fails with `EBUSY` rather than being overwritten under a live
filesystem.

**Mode 4 is dropped, not ported.** It handed the wipe — the one step the
caller asked for — to a customer-supplied hook that is not built or
tested here, and modes 2 and 3 cover rotating disks and flash. The
removal has to land in the same release wherever the mode is known: the
mode table in the meta layer README, and the mode type in
omnect/omnect-device-service#207 and omnect/omnect-ui#128.

A failed wipe reports `Error`, not `Warning`: the device is usable, but
the operation the caller asked for did not happen. For modes 2 and 3 the
destructive boundary moves to the wipe, so every outcome from there on
reports `data_wiped` — a partly written overwrite destroys the data even
when the reformat never runs.

**A trigger the init cannot use is now answered.** It used to produce a
kmsg warning only: the boot continued as Normal, no `factory_reset`
object was written, and the trigger kept its value, so the warning
repeated on every boot while the caller waited for a result. Such a
trigger is now cleared like any other and reported with the status the
shell implementation used — anything about `mode` is `Invalid`, anything
about the preserve configuration is `ConfigError`. That also makes
`preserve` mandatory again, and a file in `factory-reset.d` without a
usable `paths` array an error rather than something to skip, since
skipping it wipes the paths the caller asked to keep and still reports
success.

**A blank trigger is not a reset request.** GRUB reports a key set to an
empty value as present where U-Boot reports it as unset, and the shell
init cleared the key by writing an empty string. A device coming from it
would otherwise be answered with one "reset failed" nobody asked for.

The statuses a configuration problem reports are now the same wherever
it occurs: a `factory-reset.json` that does not exist reports
`ConfigError` like a key missing inside it, which is what the shell
implementation did too. Only an absent file counts — a path that exists
but cannot be read stays an I/O error and reports `Error`, because the
request was usable and the storage was not, which is worth retrying. The
`error` field itself has one shape everywhere now: it sits inside the
factory-reset result, so no writer repeats the outer `Factory reset
error:` prefix.

**Known gap.** Nothing pins that the wipe runs after the backup and
after the unmount — moving `wipe_and_rebuild` above `backup_all` passes
the whole suite. The `wipe_and_rebuild` seam starts after both steps, so
covering the order against them needs a mount/backup seam as well.
Tracked separately, together with the `data_dev` / `etc_dev` argument
swap, which has the same shape.

The result schema is unchanged and the initramfs image needs no new
packages: the wipe uses one ioctl and `/dev/urandom` instead of
`fstrim`, `blkdiscard` or `dd`.

---------

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
@JanZachmann JanZachmann closed this Oct 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant