|
|
|
@@ -155,41 +155,33 @@ func (s *System) RunInstallToRAM(ctx context.Context, logFunc func(string)) (ret
|
|
|
|
|
log("Already running from RAM — installation media can be safely disconnected.")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
originalStatus := state.LiveBootSource
|
|
|
|
|
|
|
|
|
|
squashfsFiles, sourceAvailable := ensureLiveMediumAvailable(log)
|
|
|
|
|
|
|
|
|
|
dstDir := installToRAMDir
|
|
|
|
|
loopsSwitched := false
|
|
|
|
|
defer func() {
|
|
|
|
|
if retErr == nil || loopsSwitched {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
_ = os.RemoveAll(dstDir)
|
|
|
|
|
log("Removed incomplete RAM copy.")
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
// If the source medium is unavailable, check whether a previous run already
|
|
|
|
|
// produced a complete copy in RAM. If so, skip the copy phase and proceed
|
|
|
|
|
// directly to the loop-rebind / bind-mount steps.
|
|
|
|
|
// A worker or virtual-CD disconnect may have interrupted the task after a
|
|
|
|
|
// complete squashfs copy was fsynced. That copy is useful, but it is not
|
|
|
|
|
// success by itself: the active loop device still has to accept it as its
|
|
|
|
|
// backing file and the live-medium mount must move to tmpfs below.
|
|
|
|
|
if !sourceAvailable {
|
|
|
|
|
copiedFiles, _ := filepath.Glob(filepath.Join(dstDir, "*.squashfs"))
|
|
|
|
|
if len(copiedFiles) > 0 {
|
|
|
|
|
log("Source medium not available, but a previous RAM copy was found — resuming from existing copy.")
|
|
|
|
|
// Proceed to rebind with the already-copied files.
|
|
|
|
|
for _, dst := range copiedFiles {
|
|
|
|
|
base := filepath.Base(dst)
|
|
|
|
|
// Re-associate the loop device that was originally backed by the
|
|
|
|
|
// source file (now gone); find it by the old source path pattern.
|
|
|
|
|
srcGuess := "/run/live/medium/live/" + base
|
|
|
|
|
loopDev, lerr := findLoopForFile(srcGuess)
|
|
|
|
|
if lerr != nil {
|
|
|
|
|
log(fmt.Sprintf("Loop device for %s not found (%v) — skipping re-association.", base, lerr))
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if rerr := reassociateLoopDevice(loopDev, dst); rerr != nil {
|
|
|
|
|
log(fmt.Sprintf("Warning: could not re-associate %s → %s: %v", loopDev, dst, rerr))
|
|
|
|
|
} else {
|
|
|
|
|
log(fmt.Sprintf("Loop device %s now backed by RAM copy.", loopDev))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
goto bindMedium
|
|
|
|
|
if len(copiedFiles) == 0 {
|
|
|
|
|
return fmt.Errorf("no squashfs files found in /run/live/medium/live/ and no prior RAM copy in %s — reconnect the installation medium and retry (or run bee-remount-medium as root)", dstDir)
|
|
|
|
|
}
|
|
|
|
|
return fmt.Errorf("no squashfs files found in /run/live/medium/live/ and no prior RAM copy in %s — reconnect the installation medium and retry (or run bee-remount-medium as root)", dstDir)
|
|
|
|
|
log("Source medium not available, but a previous RAM copy was found — verifying and resuming it.")
|
|
|
|
|
squashfsFiles = copiedFiles
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
if sourceAvailable {
|
|
|
|
|
free := freeMemBytes()
|
|
|
|
|
var needed int64
|
|
|
|
|
for _, sf := range squashfsFiles {
|
|
|
|
@@ -204,72 +196,78 @@ func (s *System) RunInstallToRAM(ctx context.Context, logFunc func(string)) (ret
|
|
|
|
|
return fmt.Errorf("insufficient RAM: need %s, available %s",
|
|
|
|
|
humanBytes(needed+headroom), humanBytes(free))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if state.CopyPresent {
|
|
|
|
|
log("Removing stale partial RAM copy before retry...")
|
|
|
|
|
}
|
|
|
|
|
_ = os.RemoveAll(dstDir)
|
|
|
|
|
if err := os.MkdirAll(dstDir, 0755); err != nil {
|
|
|
|
|
return fmt.Errorf("create tmpfs dir: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer func() {
|
|
|
|
|
if retErr == nil {
|
|
|
|
|
return
|
|
|
|
|
if state.CopyPresent {
|
|
|
|
|
log("Removing stale partial RAM copy before retry...")
|
|
|
|
|
}
|
|
|
|
|
_ = os.RemoveAll(dstDir)
|
|
|
|
|
log("Removed incomplete RAM copy.")
|
|
|
|
|
}()
|
|
|
|
|
if err := os.MkdirAll(dstDir, 0755); err != nil {
|
|
|
|
|
return fmt.Errorf("create tmpfs dir: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, sf := range squashfsFiles {
|
|
|
|
|
for _, sf := range squashfsFiles {
|
|
|
|
|
if err := ctx.Err(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
base := filepath.Base(sf)
|
|
|
|
|
dst := filepath.Join(dstDir, base)
|
|
|
|
|
log(fmt.Sprintf("Copying %s to RAM...", base))
|
|
|
|
|
if err := copyFileLarge(ctx, sf, dst, log); err != nil {
|
|
|
|
|
return fmt.Errorf("copy %s: %v", base, err)
|
|
|
|
|
}
|
|
|
|
|
log(fmt.Sprintf("Copied %s.", base))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log("Copying remaining medium files...")
|
|
|
|
|
if err := cpDir(ctx, "/run/live/medium", dstDir, log); err != nil {
|
|
|
|
|
log(fmt.Sprintf("Warning: partial metadata copy: %v", err))
|
|
|
|
|
}
|
|
|
|
|
if err := ctx.Err(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switchedCount := 0
|
|
|
|
|
for _, sf := range squashfsFiles {
|
|
|
|
|
base := filepath.Base(sf)
|
|
|
|
|
dst := filepath.Join(dstDir, base)
|
|
|
|
|
log(fmt.Sprintf("Copying %s to RAM...", base))
|
|
|
|
|
if err := copyFileLarge(ctx, sf, dst, log); err != nil {
|
|
|
|
|
return fmt.Errorf("copy %s: %v", base, err)
|
|
|
|
|
oldBacking := sf
|
|
|
|
|
if !sourceAvailable {
|
|
|
|
|
oldBacking = filepath.Join("/run/live/medium/live", base)
|
|
|
|
|
}
|
|
|
|
|
log(fmt.Sprintf("Copied %s.", base))
|
|
|
|
|
|
|
|
|
|
loopDev, err := findLoopForFile(sf)
|
|
|
|
|
loopDev, err := findLoopForFile(oldBacking)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log(fmt.Sprintf("Loop device for %s not found (%v) — skipping re-association.", base, err))
|
|
|
|
|
continue
|
|
|
|
|
return fmt.Errorf("find active loop device for %s: %v", base, err)
|
|
|
|
|
}
|
|
|
|
|
if err := reassociateLoopDevice(loopDev, dst); err != nil {
|
|
|
|
|
log(fmt.Sprintf("Warning: could not re-associate %s → %s: %v", loopDev, dst, err))
|
|
|
|
|
} else {
|
|
|
|
|
log(fmt.Sprintf("Loop device %s now backed by RAM copy.", loopDev))
|
|
|
|
|
return fmt.Errorf("switch %s to RAM copy %s: %v", loopDev, dst, err)
|
|
|
|
|
}
|
|
|
|
|
loopsSwitched = true
|
|
|
|
|
newLoop, err := findLoopForFile(dst)
|
|
|
|
|
if err != nil || newLoop != loopDev {
|
|
|
|
|
return fmt.Errorf("verify %s RAM backing: active backing file did not change to %s", loopDev, dst)
|
|
|
|
|
}
|
|
|
|
|
switchedCount++
|
|
|
|
|
log(fmt.Sprintf("Loop device %s now backed by RAM copy.", loopDev))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if strings.TrimSpace(mountSource("/run/live/medium")) != "" {
|
|
|
|
|
log("Unmounting original live medium...")
|
|
|
|
|
if err := umountLiveMedium(); err != nil {
|
|
|
|
|
return fmt.Errorf("unmount original /run/live/medium: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bindMedium:
|
|
|
|
|
log("Copying remaining medium files...")
|
|
|
|
|
if err := cpDir(ctx, "/run/live/medium", dstDir, log); err != nil {
|
|
|
|
|
log(fmt.Sprintf("Warning: partial copy: %v", err))
|
|
|
|
|
}
|
|
|
|
|
if err := ctx.Err(); err != nil {
|
|
|
|
|
return 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
|
|
|
|
|
return fmt.Errorf("bind RAM copy on /run/live/medium: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log("Verifying live medium now served from RAM...")
|
|
|
|
|
status := s.LiveBootSource()
|
|
|
|
|
if err := verifyInstallToRAMStatus(status, dstDir, mediumRebound, log); err != nil {
|
|
|
|
|
if err := verifyInstallToRAMStatus(status, switchedCount); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if status.InRAM {
|
|
|
|
|
log(fmt.Sprintf("Verification passed: live medium now served from %s.", describeLiveBootSource(status)))
|
|
|
|
|
}
|
|
|
|
|
detachInstallMedium(status, log)
|
|
|
|
|
log(fmt.Sprintf("Verification passed: %d loop device(s) and live medium now served from RAM.", switchedCount))
|
|
|
|
|
detachInstallMedium(originalStatus, log)
|
|
|
|
|
log("Done. Squashfs files are in RAM. Installation media has been detached when possible.")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
@@ -324,12 +322,6 @@ func detachInstallMedium(status LiveBootSource, log func(string)) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log("Detaching original installation medium...")
|
|
|
|
|
if err := umountLiveMedium(); err != nil {
|
|
|
|
|
log(fmt.Sprintf("Warning: could not unmount /run/live/medium: %v", err))
|
|
|
|
|
} else {
|
|
|
|
|
log("Unmounted /run/live/medium.")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
device := strings.TrimSpace(status.Device)
|
|
|
|
|
if device == "" {
|
|
|
|
|
device = strings.TrimSpace(status.Source)
|
|
|
|
@@ -346,27 +338,14 @@ func detachInstallMedium(status LiveBootSource, log func(string)) {
|
|
|
|
|
log(fmt.Sprintf("Ejected %s.", device))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func verifyInstallToRAMStatus(status LiveBootSource, dstDir string, mediumRebound bool, log func(string)) error {
|
|
|
|
|
if status.InRAM {
|
|
|
|
|
return nil
|
|
|
|
|
func verifyInstallToRAMStatus(status LiveBootSource, switchedLoops int) error {
|
|
|
|
|
if switchedLoops <= 0 {
|
|
|
|
|
return fmt.Errorf("install to RAM verification failed: no active squashfs loop device was switched to RAM")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
if !status.InRAM {
|
|
|
|
|
return fmt.Errorf("install to RAM verification failed: live medium still mounted from %s", describeLiveBootSource(status))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fmt.Errorf("install to RAM verification failed: live medium still mounted from %s and no squashfs found in %s", describeLiveBootSource(status), dstDir)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func describeLiveBootSource(status LiveBootSource) string {
|
|
|
|
@@ -519,8 +498,5 @@ func reassociateLoopDevice(loopDev, newFile string) error {
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
return loopChangeFD(loopDev, newFile)
|
|
|
|
|
}
|
|
|
|
|