Skip to content

Commit

Permalink
fix: fix up usb sensing named volumes
Browse files Browse the repository at this point in the history
This regex wasn't working for usb mass storage devices with volume
labels. Now it does. It also wasn't working for mass storage devices
that were flat partitioned (i.e. just mounted from /dev/sda rather than
/dev/sdaN). Now it does.
  • Loading branch information
sfoster1 committed Oct 16, 2024
1 parent 93898da commit d209c91
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 26 deletions.
11 changes: 9 additions & 2 deletions app-shell-odd/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,15 @@ function startUp(): void {
log.info('First dispatch, showing')
systemd.sendStatus('started')
systemd.ready()
const stopWatching = watchForMassStorage(dispatch)
ipcMain.once('quit', stopWatching)
try {
const stopWatching = watchForMassStorage(dispatch)
ipcMain.once('quit', stopWatching)
} catch (err) {
console.log(
`Failed to watch for mass storage: ${err.name}: ${err.message}`,

Check failure on line 145 in app-shell-odd/src/main.ts

View workflow job for this annotation

GitHub Actions / js checks

'err' is of type 'unknown'.

Check failure on line 145 in app-shell-odd/src/main.ts

View workflow job for this annotation

GitHub Actions / js checks

'err' is of type 'unknown'.

Check failure on line 145 in app-shell-odd/src/main.ts

View workflow job for this annotation

GitHub Actions / js checks

'err' is of type 'unknown'.

Check failure on line 145 in app-shell-odd/src/main.ts

View workflow job for this annotation

GitHub Actions / js checks

'err' is of type 'unknown'.
err
)
}
// TODO: This is where we render the main window for the first time. See ui.ts
// in the createUI function for more.
if (!!!mainWindow) {
Expand Down
94 changes: 70 additions & 24 deletions app-shell-odd/src/usb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as fs from 'fs'
import * as fsPromises from 'fs/promises'
import { join } from 'path'
import { flatten } from 'lodash'
import { createLogger } from './log'
import {
robotMassStorageDeviceAdded,
robotMassStorageDeviceEnumerated,
Expand All @@ -16,7 +17,12 @@ import type { Dispatch, Action } from './types'

const FLEX_USB_MOUNT_DIR = '/media/'
const FLEX_USB_DEVICE_DIR = '/dev/'
const FLEX_USB_MOUNT_FILTER = /sd[a-z]+[0-9]+$/
// filter matches sda0, sdc9, sdb
const FLEX_USB_DEVICE_FILTER = /sd[a-z]+[0-9]*$/
// filter matches sda0, sdc9, sdb, VOLUME-sdc10
const FLEX_USB_MOUNT_FILTER = /([^/]+-)?(sd[a-z]+[0-9]*)$/

const log = createLogger('mass-storage')

// These are for backoff algorithm
// apply the delay from 1 sec 64 sec
Expand Down Expand Up @@ -69,22 +75,27 @@ const enumerateMassStorage = (path: string): Promise<string[]> => {
)
)
)
.catch(error => {
console.error(`Error enumerating mass storage: ${error}`)
.catch((error: Error) => {
log.error(
`Error enumerating mass storage: ${error.name}: ${error.message}`
)
return []
})
.then(flatten)
.then(result => {
return result
})
.then(result => result)
}
export function watchForMassStorage(dispatch: Dispatch): () => void {
console.log('watching for mass storage')
log.info('watching for mass storage')
let prevDirs: string[] = []
const handleNewlyPresent = (path: string): Promise<string> => {
dispatch(robotMassStorageDeviceAdded(path))
return enumerateMassStorage(path)
.then(contents => {
log.debug(
`mass storage device at ${path} enumerated: ${JSON.stringify(
contents
)}`
)
dispatch(robotMassStorageDeviceEnumerated(path, contents))
})
.then(() => path)
Expand Down Expand Up @@ -133,32 +144,46 @@ export function watchForMassStorage(dispatch: Dispatch): () => void {
return
}
if (!fileName.match(FLEX_USB_MOUNT_FILTER)) {
log.debug(
`mediaWatcher: filename ${fileName} does not match ${FLEX_USB_MOUNT_FILTER}`
)
return
}
const fullPath = join(FLEX_USB_MOUNT_DIR, fileName)
fsPromises
.stat(fullPath)
.then(info => {
if (!info.isDirectory) {
log.debug(`mediaWatcher: ${fullPath} is not a directory`)
return
}
if (prevDirs.includes(fullPath)) {
log.debug(`mediaWatcher: ${fullPath} is known`)
return
}
console.log(`New mass storage device ${fileName} detected`)
log.info(`New mass storage device ${fileName} detected`)
prevDirs.push(fullPath)
return handleNewlyPresent(fullPath)
})
.catch(() => {
.catch(err => {
if (prevDirs.includes(fullPath)) {
console.log(`Mass storage device at ${fileName} removed`)
log.info(
`Mass storage device at ${fileName} removed because its mount point disappeared`,
err
)
prevDirs = prevDirs.filter(entry => entry !== fullPath)
dispatch(robotMassStorageDeviceRemoved(fullPath))
} else {
log.debug(
`Mass storage device candidate mountpoint at ${fileName} disappeared`,
err
)
}
})
}
)
} catch {
log.error(`Failed to start watcher for ${FLEX_USB_MOUNT_DIR}`)
return null
}
}
Expand All @@ -170,21 +195,42 @@ export function watchForMassStorage(dispatch: Dispatch): () => void {
{ persistent: true },
(event, fileName) => {
if (!!!fileName) return
if (!fileName.match(FLEX_USB_MOUNT_FILTER)) return
const fullPath = join(FLEX_USB_DEVICE_DIR, fileName)
const mountPath = join(FLEX_USB_MOUNT_DIR, fileName)
fsPromises.stat(fullPath).catch(() => {
if (prevDirs.includes(mountPath)) {
console.log(`Mass storage device at ${fileName} removed`)
prevDirs = prevDirs.filter(entry => entry !== mountPath)
dispatch(
robotMassStorageDeviceRemoved(join(FLEX_USB_MOUNT_DIR, fileName))
if (!fileName.match(FLEX_USB_DEVICE_FILTER)) return
if (event !== 'rename') {
log.debug(
`devWatcher: ignoring ${event} event for ${fileName} (not rename)`
)
return
}
log.debug(`devWatcher: ${event} event for ${fileName}`)
fsPromises
.readdir(FLEX_USB_DEVICE_DIR)
.then(contents => {
if (contents.includes(fileName)) {
log.debug(
`devWatcher: ${fileName} found in /dev, this is an attach`
)
// this is an attach
return
}
const prevDir = prevDirs.filter(dir => dir.includes(fileName)).at(0)
log.debug(
`devWatcher: ${fileName} not in /dev, this is a remove, previously mounted at ${prevDir}`
)
// we don't care if this fails because it's racing the system removing
// the mount dir in the common case
fsPromises.unlink(mountPath).catch(() => {})
}
})
if (prevDir != null) {
log.info(`Mass storage device at ${fileName} removed`)
prevDirs = prevDirs.filter(entry => entry !== prevDir)
dispatch(robotMassStorageDeviceRemoved(prevDir))
// we don't care if this fails because it's racing the system removing
// the mount dir in the common case
fsPromises.unlink(prevDir).catch(() => {})
}
})
.catch(err => {
log.info(
`Failed to handle mass storage device ${fileName}: ${err.name}: ${err.message}`
)
})
}
)

Expand Down

0 comments on commit d209c91

Please sign in to comment.