Skip to content

Instantly share code, notes, and snippets.

@teidesu
Created January 2, 2025 14:21
Show Gist options
  • Select an option

  • Save teidesu/8bc54fb7e22fb2670937a5036335ba95 to your computer and use it in GitHub Desktop.

Select an option

Save teidesu/8bc54fb7e22fb2670937a5036335ba95 to your computer and use it in GitHub Desktop.
script to find and remove broken files from a gocryptfs-powered overlay filesystem
import * as fsp from 'fs/promises'
const BASE_PATH = '/mnt/s3-desu-priv-encrypted/music'
const BASE_PATH_UNDERLYING = '/mnt/s3-desu-priv'
async function* walk(path: string) {
const entries = await fsp.readdir(path, { withFileTypes: true })
for (const entry of entries) {
const fullPath = path + '/' + entry.name
if (entry.isDirectory()) {
yield* walk(fullPath)
} else {
yield fullPath
}
}
}
let _cachedWalk
async function findUnderlyingPath(inode: number) {
if (!_cachedWalk) {
_cachedWalk = []
for await (const path of walk(BASE_PATH_UNDERLYING)) {
_cachedWalk.push({
path,
inode: await fsp.stat(path).then(stat => stat.ino)
})
}
}
return _cachedWalk.find(it => it.inode === inode)?.path
}
const buffer = Buffer.alloc(100)
async function validateFile(path: string) {
// try reading the first few bytes of the file
const fd = await fsp.open(path, 'r')
try {
await fd.read(fd, buffer, 0, 100, 0)
await fd.close()
return
} catch (e) {
if (e.code !== 'EIO') {
throw e
}
}
const stat = await fd.stat()
const inode = stat.ino
await fd.close()
console.log(`found broken file at path ${path}, inode=${inode}. removing...`)
const underlyingPath = await findUnderlyingPath(inode)
if (!underlyingPath) {
throw new Error(`Could not find underlying path for file ${path} (inode ${inode})`)
}
await fsp.rm(path, { recursive: true })
}
for await (const path of walk(BASE_PATH)) {
await validateFile(path)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment