agent v0.36.3: DetachDrive loop-umounts stacked binds (full detach)

Detach now removes ALL stacked binds at a stable path, not just one layer, so an
eject fully detaches even with >1 bind (keeping fail-close intact).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 20:16:47 +02:00
parent 038f0ecd5d
commit cff9284453
3 changed files with 17 additions and 6 deletions
+10 -5
View File
@@ -293,13 +293,18 @@ func (b *GuestBinder) DetachDrive(ctx context.Context, where string) error {
if stable == "" {
return fmt.Errorf("guest-detach: %q is not a /mnt/<name> mount", where)
}
if !isHostMountpoint(stable) {
return nil // already detached
// Loop-umount: a stable path can carry MORE THAN ONE stacked bind (e.g. an operator-applied bind on
// top of the agent's, or a rare attach race). Detach must remove ALL layers, else eject leaves a
// lower bind exposing data → fail-close broken. Bounded to avoid an infinite loop.
for i := 0; i < 16 && isHostMountpoint(stable); i++ {
if err := b.run(ctx, "umount", stable); err != nil {
return fmt.Errorf("guest-detach: umount %s (layer %d): %w", stable, i, err)
}
}
if err := b.run(ctx, "umount", stable); err != nil {
return fmt.Errorf("guest-detach: umount %s: %w", stable, err)
if isHostMountpoint(stable) {
return fmt.Errorf("guest-detach: %s still a mountpoint after 16 umounts", stable)
}
b.logger.Info("guest-detach: drive unmounted from shared parent (live, fail-closed)", "where", where, "stable", stable)
b.logger.Info("guest-detach: drive fully unmounted from shared parent (live, fail-closed)", "where", where, "stable", stable)
return nil
}