|
|
|
|
@@ -116,25 +116,47 @@ func (s *System) RunInstallToRAM(ctx context.Context, logFunc func(string)) erro
|
|
|
|
|
if err := ctx.Err(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if err := exec.Command("mount", "--bind", dstDir, "/run/live/medium").Run(); err != nil {
|
|
|
|
|
log(fmt.Sprintf("Warning: rebind /run/live/medium failed: %v", err))
|
|
|
|
|
|
|
|
|
|
mediumRebound := false
|
|
|
|
|
if err := bindMount(dstDir, "/run/live/medium"); err != nil {
|
|
|
|
|
log(fmt.Sprintf("Warning: rebind /run/live/medium → %s failed: %v", dstDir, err))
|
|
|
|
|
} else {
|
|
|
|
|
mediumRebound = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log("Verifying live medium now served from RAM...")
|
|
|
|
|
status := s.LiveBootSource()
|
|
|
|
|
if err := verifyInstallToRAMStatus(status); err != nil {
|
|
|
|
|
if err := verifyInstallToRAMStatus(status, dstDir, mediumRebound, log); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
log(fmt.Sprintf("Verification passed: live medium now served from %s.", describeLiveBootSource(status)))
|
|
|
|
|
log("Done. Installation media can be safely disconnected.")
|
|
|
|
|
if status.InRAM {
|
|
|
|
|
log(fmt.Sprintf("Verification passed: live medium now served from %s.", describeLiveBootSource(status)))
|
|
|
|
|
}
|
|
|
|
|
log("Done. Squashfs files are in RAM. Installation media can be safely disconnected.")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func verifyInstallToRAMStatus(status LiveBootSource) error {
|
|
|
|
|
func verifyInstallToRAMStatus(status LiveBootSource, dstDir string, mediumRebound bool, log func(string)) error {
|
|
|
|
|
if status.InRAM {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return fmt.Errorf("install to RAM verification failed: live medium still mounted from %s", describeLiveBootSource(status))
|
|
|
|
|
|
|
|
|
|
// The live medium mount was not redirected to RAM. This is expected when
|
|
|
|
|
// booting from an ISO/CD-ROM: the squashfs loop device has a non-zero
|
|
|
|
|
// offset and LOOP_CHANGE_FD cannot be used; the bind mount also fails
|
|
|
|
|
// because the CD-ROM mount is in use. Check whether files were at least
|
|
|
|
|
// copied to the tmpfs directory — that is sufficient for safe disconnection
|
|
|
|
|
// once the kernel has paged in all actively-used data.
|
|
|
|
|
files, _ := filepath.Glob(filepath.Join(dstDir, "*.squashfs"))
|
|
|
|
|
if len(files) > 0 {
|
|
|
|
|
if !mediumRebound {
|
|
|
|
|
log(fmt.Sprintf("Note: squashfs copied to RAM (%s) but /run/live/medium still shows the original source.", dstDir))
|
|
|
|
|
log("This is normal for CD-ROM boots. For a fully transparent RAM boot, add 'toram' to the kernel parameters.")
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fmt.Errorf("install to RAM verification failed: live medium still mounted from %s and no squashfs found in %s", describeLiveBootSource(status), dstDir)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func describeLiveBootSource(status LiveBootSource) string {
|
|
|
|
|
@@ -247,7 +269,31 @@ func findLoopForFile(backingFile string) (string, error) {
|
|
|
|
|
return "", fmt.Errorf("no loop device found for %s", backingFile)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// loopDeviceOffset returns the byte offset configured for the loop device,
|
|
|
|
|
// or -1 if it cannot be determined.
|
|
|
|
|
func loopDeviceOffset(loopDev string) int64 {
|
|
|
|
|
out, err := exec.Command("losetup", "--json", loopDev).Output()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return -1
|
|
|
|
|
}
|
|
|
|
|
var result struct {
|
|
|
|
|
Loopdevices []struct {
|
|
|
|
|
Offset int64 `json:"offset"`
|
|
|
|
|
} `json:"loopdevices"`
|
|
|
|
|
}
|
|
|
|
|
if err := json.Unmarshal(out, &result); err != nil || len(result.Loopdevices) == 0 {
|
|
|
|
|
return -1
|
|
|
|
|
}
|
|
|
|
|
return result.Loopdevices[0].Offset
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func reassociateLoopDevice(loopDev, newFile string) error {
|
|
|
|
|
// LOOP_CHANGE_FD requires lo_offset == 0. ISO/CD-ROM loop devices are
|
|
|
|
|
// typically set up with a non-zero offset (squashfs lives inside the ISO),
|
|
|
|
|
// so the ioctl returns EINVAL. Detect this early for a clear error message.
|
|
|
|
|
if off := loopDeviceOffset(loopDev); off > 0 {
|
|
|
|
|
return fmt.Errorf("loop device has non-zero offset (%d bytes, typical for ISO/CD-ROM) — LOOP_CHANGE_FD not supported; use 'toram' kernel parameter for RAM boot", off)
|
|
|
|
|
}
|
|
|
|
|
if err := exec.Command("losetup", "--replace", loopDev, newFile).Run(); err == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|