Seed RootHide loader links for bootstrap tools

This commit is contained in:
Lakr
2026-09-25 04:16:05 +09:00
parent 59873ece15
commit 3995e2c385
3 changed files with 56 additions and 3 deletions
+7 -1
View File
@@ -49,7 +49,13 @@ script runs. For this minimal vphone bootstrap, vphoned writes a real installed
`Library/dpkg/status`; Irisin's installed list, resolver, and helper then read
the same record. If the status already contains firmware from another
bootstrap, vphoned preserves it. A vphoned-owned record is updated after an
iOS version change when vphoned starts. `POST /v1/bootstrap/firmware` (RPC `bootstrap.firmware`)
iOS version change when vphoned starts. For RootHide, vphoned also creates
the `.jbroot` loader links in the bootstrap root and standard executable and
library directories. On startup it repairs missing links for an existing
completed installation without replacing links that point elsewhere. These
links let `@loader_path/.jbroot/usr/lib/...` dependencies resolve when a
package manager later installs tools such as `dash`.
`POST /v1/bootstrap/firmware` (RPC `bootstrap.firmware`)
repairs the record for a bootstrap already identified by the completion marker
without running another install. The reply includes the tag,
bootstrap path, registration record, and launchd status. A successful bootstrap
+48 -1
View File
@@ -238,6 +238,7 @@ enum GuestIrisinInstaller {
for (source, target) in components {
replaced.append(try replace(source, at: target))
}
if layout == "roothide" { try ensureRootHideLinks(root: root) }
let registration = try registerApp(installedApp.path)
let loaded = try loadServices([installedPlist.path], load: true, override: false)
var started: [String: Any]?
@@ -312,8 +313,15 @@ enum GuestIrisinInstaller {
"firmware_version": firmware.version, "dpkg_database_updated": firmware.updated]
}
static func refreshFirmwareOnStartup() {
static func refreshBootstrapOnStartup() {
guard itemExists(completionMarker) else { return }
do {
if let installation = try completedBootstrap(), installation.layout == "roothide" {
try ensureRootHideLinks(root: installation.root)
}
} catch {
NSLog("vphoned: could not repair RootHide loader links: %@", String(describing: error))
}
do {
_ = try repairFirmwareRecord()
} catch {
@@ -321,6 +329,45 @@ enum GuestIrisinInstaller {
}
}
/// RootHide's @loader_path references resolve through a .jbroot link in
/// each directory containing bootstrap Mach-O files. Seed the standard
/// directories before a package manager installs its first shell.
private static func ensureRootHideLinks(root: String) throws {
guard try directoryExistsWithoutSymlink(root) else {
throw GuestAPIError.operationFailed("RootHide bootstrap root is missing: \(root)")
}
let files = FileManager.default
func link(_ path: String, target: String) throws {
var info = stat()
if lstat(path, &info) == 0 {
guard info.st_mode & mode_t(S_IFMT) == mode_t(S_IFLNK),
try files.destinationOfSymbolicLink(atPath: path) == target else {
throw GuestAPIError.operationFailed("RootHide loader link has an unexpected target: \(path)")
}
return
}
guard errno == ENOENT else {
throw GuestAPIError.operationFailed("Could not inspect RootHide loader link: \(path)")
}
try files.createSymbolicLink(atPath: path, withDestinationPath: target)
}
try link(root + "/.jbroot", target: ".")
for relative in ["bin", "sbin", "usr/bin", "usr/sbin", "usr/lib", "usr/libexec"] {
var directory = root
for component in relative.split(separator: "/") {
directory += "/" + component
if try !directoryExistsWithoutSymlink(directory) {
try files.createDirectory(atPath: directory, withIntermediateDirectories: false)
}
}
let depth = relative.split(separator: "/").count
let target = String(repeating: "../", count: depth) + ".jbroot"
try link(directory + "/.jbroot", target: target)
}
}
/// This vphone bootstrap has no firmware maintainer script. Write the
/// virtual package into the same dpkg status file Irisin and its helper read.
private static func ensureFirmwareRecord(root: String) throws -> (version: String, updated: Bool) {
+1 -1
View File
@@ -7,7 +7,7 @@ import VphonedNative
vp_native_bootstrap_cached_binary()
vp_vcam_start()
GuestIrisinInstaller.refreshFirmwareOnStartup()
GuestIrisinInstaller.refreshBootstrapOnStartup()
let group = MultiThreadedEventLoopGroup(numberOfThreads: 2)
let filePool = NIOThreadPool(numberOfThreads: 2)