mirror of
https://github.com/blacktop/ipsw.git
synced 2026-06-07 12:27:36 +00:00
Refactor diff jobs into task-owned renderers with persistent cache support, root-confined walkers, and quieter Mach-O/Firmware output.
165 lines
6.8 KiB
Go
165 lines
6.8 KiB
Go
package diff
|
|
|
|
import (
|
|
"context"
|
|
"html/template"
|
|
|
|
"github.com/blacktop/ipsw/internal/diff/storage"
|
|
"github.com/blacktop/ipsw/internal/search"
|
|
)
|
|
|
|
// Side identifies which IPSW (old or new) a Mach-O handler is being invoked
|
|
// against during the shared per-volume walk.
|
|
type Side string
|
|
|
|
const (
|
|
SideOld Side = "old"
|
|
SideNew Side = "new"
|
|
)
|
|
|
|
// HTMLFragment is the per-Task contribution to the final HTML report. Body
|
|
// uses html/template so renderers may compose pre-escaped fragments without
|
|
// re-escaping them at the outer template level.
|
|
type HTMLFragment struct {
|
|
Heading string
|
|
Body template.HTML
|
|
}
|
|
|
|
// MachoScanHandler is invoked once per Mach-O encountered during a shared
|
|
// per-volume walk. The same (path, *macho.File) is fed to every registered
|
|
// handler before the next binary is opened.
|
|
//
|
|
// The type is defined in internal/search so that ForEachMachoInMountMulti
|
|
// can fan handlers out without a cyclic dependency on internal/diff. Diff
|
|
// re-exports the alias here so task implementations read naturally.
|
|
type MachoScanHandler = search.MachoScanHandler
|
|
|
|
// Task is the minimal contract every diff dimension implements: a stable
|
|
// identifier used for logs, cache scoping, and ordering. Specialized
|
|
// behaviors (rendering, caching, per-volume work) are layered via the
|
|
// optional interfaces below.
|
|
//
|
|
// Report rendering is intentionally not modeled as an interface here. Each
|
|
// section is rendered by a small per-section renderer (jobs_*.go) or
|
|
// TopLevelTask (tasks_*.go) that exposes Markdown/HTML/JSON/JSONKey/Empty
|
|
// methods; buildReport duck-types them through the narrow reportContributor
|
|
// interface in report.go, and md.go / format.go call the concrete renderers
|
|
// directly. The renderers and the TopLevelTasks deliberately keep distinct
|
|
// shapes (the renderers are not Tasks), so a single shared RenderTask
|
|
// interface would not unify them.
|
|
type Task interface {
|
|
// Name returns the stable cache and log identifier for this task.
|
|
Name() string
|
|
}
|
|
|
|
// CacheableTask is implemented by tasks that participate in idempotent
|
|
// reruns. The orchestrator consults Version/OptionsHash/InputHash to build a
|
|
// storage.Scope, queries the store's completion sentinel, and on a hit calls
|
|
// Hydrate to rebuild render/report state before any rendering occurs. On a
|
|
// miss it calls persistTo after a successful fresh walk and only then
|
|
// MarkComplete(scope).
|
|
//
|
|
// Hydrate (load) and persistTo (store) are inverses and both are required:
|
|
// a task that could be marked complete without writing its rows would leave
|
|
// an empty completed scope that hydrates to bogus empty output on the next
|
|
// run. Keeping both on one interface makes that state unrepresentable.
|
|
type CacheableTask interface {
|
|
Task
|
|
// Version is bumped when the cache payload layout or output semantics
|
|
// change in a way that invalidates prior runs.
|
|
Version() int
|
|
// OptionsHash digests every output-affecting option (allow/block
|
|
// lists, verbosity, task-specific flags).
|
|
OptionsHash() string
|
|
// InputHash digests the task-scope old/new input set (e.g. relevant
|
|
// DMG or kernelcache pair digest).
|
|
InputHash() string
|
|
// Hydrate rebuilds render/report state from a cache hit so the task
|
|
// can render without re-running ProcessVolume or Parse.
|
|
Hydrate(scope storage.Scope, store storage.Store) error
|
|
// persistTo writes the task's final result rows under scope. It runs
|
|
// only after a successful fresh walk + Finalize and before MarkComplete.
|
|
persistTo(scope storage.Scope, store storage.Store) error
|
|
}
|
|
|
|
// MountTask is the subset of Task that needs per-volume mount roots.
|
|
type MountTask interface {
|
|
Task
|
|
// Needs reports whether this task wants the given volume type
|
|
// ("fs", "sys", "app", "exc").
|
|
Needs(typ string) bool
|
|
// ProcessVolume runs once per applicable volume after both sides are
|
|
// mounted. Implementations scan both roots, store per-volume diff
|
|
// results internally, and release raw scan data before returning.
|
|
ProcessVolume(typ, oldRoot, newRoot string) error
|
|
// Finalize is called once after every applicable volume has been
|
|
// processed (or skipped). Implementations aggregate per-volume
|
|
// results into the final shape consumers expect.
|
|
Finalize() error
|
|
}
|
|
|
|
// MachoWalkTask is a mount-scoped Task that participates in the shared
|
|
// per-volume Mach-O walk. The orchestrator collects handlers from every
|
|
// active task and feeds each Mach-O to all of them before moving on.
|
|
type MachoWalkTask interface {
|
|
Task
|
|
// Needs reports whether this task wants the given volume type.
|
|
Needs(typ string) bool
|
|
// BeginVolume initializes per-volume state or cache buckets.
|
|
BeginVolume(typ string) error
|
|
// MachoHandler returns the handler that consumes Mach-Os for the
|
|
// given volume and side. Returning nil opts out of that side for
|
|
// this volume.
|
|
MachoHandler(typ string, side Side) MachoScanHandler
|
|
// EndVolume runs after both sides have been walked; tasks perform
|
|
// fold/removal checks here.
|
|
EndVolume(typ string) error
|
|
// Finalize is called once after every volume has been processed.
|
|
Finalize() error
|
|
}
|
|
|
|
// MachoVolumeAborter is optionally implemented by [MachoWalkTask]s that keep
|
|
// per-volume state which must be discarded after a handler failure. The
|
|
// orchestrator calls it before suppressing the task's EndVolume hook.
|
|
type MachoVolumeAborter interface {
|
|
AbortVolume(typ string)
|
|
}
|
|
|
|
// SessionFallbackTask is optionally implemented by [MountTask]s whose absent
|
|
// side should still receive a root resolved by the mount session itself.
|
|
// mount.Session.Root("sys") falls back to the filesystem DMG on pre-cryptex
|
|
// IPSWs — exactly where the DSC lives on those builds — while the strict
|
|
// per-volume resolver (volumeResolves) deliberately has no such fallback
|
|
// because the walk-based jobs must not re-scan filesystem content under the
|
|
// SystemOS label. The orchestrator hands the session-resolved fallback root
|
|
// ONLY to tasks implementing this interface; every other active job still
|
|
// sees the absent side as an empty root.
|
|
type SessionFallbackTask interface {
|
|
// WantsSessionFallback reports whether the task wants the session-
|
|
// resolved root for a side whose typ is absent per the strict resolver.
|
|
WantsSessionFallback(typ string) bool
|
|
}
|
|
|
|
// TopLevelTask runs after all mounts have closed and needs no per-volume
|
|
// state. Kexts, KDKs, firmwares, iBoot, and sandbox parse here.
|
|
type TopLevelTask interface {
|
|
Task
|
|
// Parse runs the task against the fully populated Diff. ctx allows
|
|
// cancellation of long-running parse work.
|
|
Parse(ctx context.Context, d *Diff) error
|
|
}
|
|
|
|
// TaskSetup is the optional storage-aware setup hook.
|
|
type TaskSetup interface {
|
|
// Setup runs before the volume loop starts and receives the chosen
|
|
// store backend.
|
|
Setup(store storage.Store) error
|
|
}
|
|
|
|
// TaskCleanup is implemented by tasks that need to release resources after
|
|
// the full diff completes (regardless of success or error).
|
|
type TaskCleanup interface {
|
|
// Cleanup releases task-owned resources.
|
|
Cleanup() error
|
|
}
|