Skip to content

Commit

Permalink
Merge pull request #107 from depot/feat/unmount-children
Browse files Browse the repository at this point in the history
feat: try to unmount any spurious overlay mounts
  • Loading branch information
goller authored May 15, 2024
2 parents d4d02c2 + 9196367 commit 23cd912
Showing 1 changed file with 48 additions and 2 deletions.
50 changes: 48 additions & 2 deletions src/utils/mounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,17 @@ async function mountDevice(
}

// Unmounts device at path. If the device is not mounted, this is a no-op.
export async function unmountDevice(path: string) {
export async function unmountDevice(path: string, seenPaths = new Set<string>()) {
if (seenPaths.has(path)) return
seenPaths.add(path)

// Sometimes overlays are left over and need to be unmounted first.
const overlays = await findOverlayPaths(path, seenPaths)
for (const overlay of overlays) {
console.log(`Unmounting overlay ${overlay}`)
await unmountDevice(overlay)
}

// Retry unmounting a few times (5 seconds).
for (let i = 0; i < 10; i++) {
const {exitCode, stderr} = await execa('umount', [path], {reject: false})
Expand All @@ -128,7 +138,7 @@ export async function unmountDevice(path: string) {

// exitCode 32 means that the device is not mounted or busy.
if (exitCode !== 32) {
throw new Error(`Failed to unmount ${path}: ${stderr}`)
throw new Error(`Failed to unmount ${path} with exit code ${exitCode} : ${stderr}`)
}

if (stderr.includes('not mounted')) {
Expand Down Expand Up @@ -225,3 +235,39 @@ export async function fstrim(path: string) {
isTrimInProgress = false
}
}

interface FileSystem {
target: string
source: string
fstype: string
options: string
children?: FileSystem[]
}

interface FileSystems {
filesystems: FileSystem[]
}

// Returns the paths of all overlayfs mounts that are children of the given path.
// This is useful to unmount all child mounts before unmounting the parent.
export async function findOverlayPaths(path: string): Promise<string[]> {
try {
const {stdout} = await execa('findmnt', ['-R', '-J', path])
const fs: FileSystems = JSON.parse(stdout)
if (fs.filesystems.length === 0) {
return []
}

const childTargets = new Set<string>()
for (const f of fs.filesystems) {
for (const c of f.children ?? []) {
childTargets.add(c.target)
}
}

return Array.from(childTargets)
} catch (err) {
console.error('Failed to run findmnt', err)
return []
}
}

0 comments on commit 23cd912

Please sign in to comment.