From 0f9d78ab50aeb10222332dfc60352ffd62872602 Mon Sep 17 00:00:00 2001 From: Paulo Castro Date: Tue, 13 Apr 2021 22:44:58 +0100 Subject: [PATCH] config inject/read/write: Fix umount errors with OS image files Resolves: #1003 Change-type: patch --- doc/cli.markdown | 8 +++---- lib/commands/config/inject.ts | 19 +++++------------ lib/commands/config/read.ts | 19 +++++------------ lib/commands/config/reconfigure.ts | 21 ++++++------------ lib/commands/config/write.ts | 21 ++++++------------ lib/commands/local/configure.ts | 10 +++------ lib/commands/os/initialize.ts | 34 ++++++------------------------ lib/utils/common-flags.ts | 15 ++++++++++++- lib/utils/helpers.ts | 13 ++++++++++++ 9 files changed, 63 insertions(+), 97 deletions(-) diff --git a/doc/cli.markdown b/doc/cli.markdown index 4d70f403..5948113d 100644 --- a/doc/cli.markdown +++ b/doc/cli.markdown @@ -2330,7 +2330,7 @@ device type (Check available types with `balena devices supported`) #### -d, --drive DRIVE -device filesystem or OS image location +path to OS image file (e.g. balena.img) or block device (e.g. /dev/disk2) ## config read @@ -2350,7 +2350,7 @@ device type (Check available types with `balena devices supported`) #### -d, --drive DRIVE -device filesystem or OS image location +path to OS image file (e.g. balena.img) or block device (e.g. /dev/disk2) ## config reconfigure @@ -2370,7 +2370,7 @@ device type (Check available types with `balena devices supported`) #### -d, --drive DRIVE -device filesystem or OS image location +path to OS image file (e.g. balena.img) or block device (e.g. /dev/disk2) #### -v, --advanced @@ -2405,7 +2405,7 @@ device type (Check available types with `balena devices supported`) #### -d, --drive DRIVE -device filesystem or OS image location +path to OS image file (e.g. balena.img) or block device (e.g. /dev/disk2) # Preload diff --git a/lib/commands/config/inject.ts b/lib/commands/config/inject.ts index 56451242..dce9a3bd 100644 --- a/lib/commands/config/inject.ts +++ b/lib/commands/config/inject.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2016-2020 Balena Ltd. + * Copyright 2016-2021 Balena Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,16 +54,8 @@ export default class ConfigInjectCmd extends Command { public static usage = 'config inject '; public static flags: flags.Input = { - type: flags.string({ - description: - 'device type (Check available types with `balena devices supported`)', - char: 't', - required: true, - }), - drive: flags.string({ - description: 'device filesystem or OS image location', - char: 'd', - }), + type: cf.deviceType, + drive: cf.driveOrImg, help: cf.help, }; @@ -76,12 +68,11 @@ export default class ConfigInjectCmd extends Command { ConfigInjectCmd, ); - const { promisify } = await import('util'); - const umountAsync = promisify((await import('umount')).umount); + const { safeUmount } = await import('../../utils/helpers'); const drive = options.drive || (await getVisuals().drive('Select the device/OS drive')); - await umountAsync(drive); + await safeUmount(drive); const fs = await import('fs'); const configJSON = JSON.parse( diff --git a/lib/commands/config/read.ts b/lib/commands/config/read.ts index 64092e03..c6dd1377 100644 --- a/lib/commands/config/read.ts +++ b/lib/commands/config/read.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2016-2020 Balena Ltd. + * Copyright 2016-2021 Balena Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,16 +42,8 @@ export default class ConfigReadCmd extends Command { public static usage = 'config read'; public static flags: flags.Input = { - type: flags.string({ - description: - 'device type (Check available types with `balena devices supported`)', - char: 't', - required: true, - }), - drive: flags.string({ - description: 'device filesystem or OS image location', - char: 'd', - }), + type: cf.deviceType, + drive: cf.driveOrImg, help: cf.help, }; @@ -62,12 +54,11 @@ export default class ConfigReadCmd extends Command { public async run() { const { flags: options } = this.parse(ConfigReadCmd); - const { promisify } = await import('util'); - const umountAsync = promisify((await import('umount')).umount); + const { safeUmount } = await import('../../utils/helpers'); const drive = options.drive || (await getVisuals().drive('Select the device drive')); - await umountAsync(drive); + await safeUmount(drive); const config = await import('balena-config-json'); const configJSON = await config.read(drive, options.type); diff --git a/lib/commands/config/reconfigure.ts b/lib/commands/config/reconfigure.ts index a0962f3e..e09adcbf 100644 --- a/lib/commands/config/reconfigure.ts +++ b/lib/commands/config/reconfigure.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2016-2020 Balena Ltd. + * Copyright 2016-2021 Balena Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,16 +42,8 @@ export default class ConfigReconfigureCmd extends Command { public static usage = 'config reconfigure'; public static flags: flags.Input = { - type: flags.string({ - description: - 'device type (Check available types with `balena devices supported`)', - char: 't', - required: true, - }), - drive: flags.string({ - description: 'device filesystem or OS image location', - char: 'd', - }), + type: cf.deviceType, + drive: cf.driveOrImg, advanced: flags.boolean({ description: 'show advanced commands', char: 'v', @@ -66,16 +58,15 @@ export default class ConfigReconfigureCmd extends Command { public async run() { const { flags: options } = this.parse(ConfigReconfigureCmd); - const { promisify } = await import('util'); - const umountAsync = promisify((await import('umount')).umount); + const { safeUmount } = await import('../../utils/helpers'); const drive = options.drive || (await getVisuals().drive('Select the device drive')); - await umountAsync(drive); + await safeUmount(drive); const config = await import('balena-config-json'); const { uuid } = await config.read(drive, options.type); - await umountAsync(drive); + await safeUmount(drive); const configureCommand = ['os', 'configure', drive, '--device', uuid]; if (options.advanced) { diff --git a/lib/commands/config/write.ts b/lib/commands/config/write.ts index 65b7cd21..65c12581 100644 --- a/lib/commands/config/write.ts +++ b/lib/commands/config/write.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2016-2020 Balena Ltd. + * Copyright 2016-2021 Balena Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,16 +61,8 @@ export default class ConfigWriteCmd extends Command { public static usage = 'config write '; public static flags: flags.Input = { - type: flags.string({ - description: - 'device type (Check available types with `balena devices supported`)', - char: 't', - required: true, - }), - drive: flags.string({ - description: 'device filesystem or OS image location', - char: 'd', - }), + type: cf.deviceType, + drive: cf.driveOrImg, help: cf.help, }; @@ -83,12 +75,11 @@ export default class ConfigWriteCmd extends Command { ConfigWriteCmd, ); - const { promisify } = await import('util'); - const umountAsync = promisify((await import('umount')).umount); + const { safeUmount } = await import('../../utils/helpers'); const drive = options.drive || (await getVisuals().drive('Select the device drive')); - await umountAsync(drive); + await safeUmount(drive); const config = await import('balena-config-json'); const configJSON = await config.read(drive, options.type); @@ -97,7 +88,7 @@ export default class ConfigWriteCmd extends Command { const _ = await import('lodash'); _.set(configJSON, params.key, params.value); - await umountAsync(drive); + await safeUmount(drive); await config.write(drive, options.type, configJSON); diff --git a/lib/commands/local/configure.ts b/lib/commands/local/configure.ts index 2aba20b6..51025d6b 100644 --- a/lib/commands/local/configure.ts +++ b/lib/commands/local/configure.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2016-2020 Balena Ltd. + * Copyright 2016-2021 Balena Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,20 +61,16 @@ export default class LocalConfigureCmd extends Command { const { args: params } = this.parse(LocalConfigureCmd); const path = await import('path'); - const umount = await import('umount'); - const umountAsync = promisify(umount.umount); - const isMountedAsync = promisify(umount.isMounted); const reconfix = await import('reconfix'); const denymount = promisify(await import('denymount')); + const { safeUmount } = await import('../../utils/helpers'); const Logger = await import('../../utils/logger'); const logger = Logger.getLogger(); const configurationSchema = await this.prepareConnectionFile(params.target); - if (await isMountedAsync(params.target)) { - await umountAsync(params.target); - } + await safeUmount(params.target); const dmOpts: any = {}; if (process.pkg) { diff --git a/lib/commands/os/initialize.ts b/lib/commands/os/initialize.ts index 22468616..5deca04a 100644 --- a/lib/commands/os/initialize.ts +++ b/lib/commands/os/initialize.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2016-2020 Balena Ltd. + * Copyright 2016-2021 Balena Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,12 +61,7 @@ export default class OsInitializeCmd extends Command { public static usage = 'os initialize '; public static flags: flags.Input = { - type: flags.string({ - description: - 'device type (Check available types with `balena devices supported`)', - char: 't', - required: true, - }), + type: cf.deviceType, drive: cf.drive, yes: cf.yes, help: cf.help, @@ -79,9 +74,9 @@ export default class OsInitializeCmd extends Command { OsInitializeCmd, ); - const { promisify } = await import('util'); - const umountAsync = promisify((await import('umount')).umount); - const { getManifest, sudo } = await import('../../utils/helpers'); + const { getManifest, safeUmount, sudo } = await import( + '../../utils/helpers' + ); console.info(`Initializing device ${INIT_WARNING_MESSAGE}`); @@ -101,7 +96,7 @@ export default class OsInitializeCmd extends Command { `Going to erase ${answers.drive}.`, true, ); - await umountAsync(answers.drive); + await safeUmount(answers.drive); } await sudo([ @@ -113,22 +108,7 @@ export default class OsInitializeCmd extends Command { ]); if (answers.drive != null) { - // TODO: balena local makes use of ejectAsync, see below - // DO we need this / should we do that here? - - // getDrive = (drive) -> - // driveListAsync().then (drives) -> - // selectedDrive = _.find(drives, device: drive) - - // if not selectedDrive? - // throw new Error("Drive not found: #{drive}") - - // return selectedDrive - // if (os.platform() is 'win32') and selectedDrive.mountpoint? - // ejectAsync = Promise.promisify(require('removedrive').eject) - // return ejectAsync(selectedDrive.mountpoint) - - await umountAsync(answers.drive); + await safeUmount(answers.drive); console.info(`You can safely remove ${answers.drive} now`); } } diff --git a/lib/utils/common-flags.ts b/lib/utils/common-flags.ts index 0dc8309a..eeb7d08e 100644 --- a/lib/utils/common-flags.ts +++ b/lib/utils/common-flags.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Balena Ltd. + * Copyright 2019-2021 Balena Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -82,6 +82,19 @@ export const drive = flags.string({ `, }); +export const driveOrImg = flags.string({ + char: 'd', + description: + 'path to OS image file (e.g. balena.img) or block device (e.g. /dev/disk2)', +}); + +export const deviceType = flags.string({ + description: + 'device type (Check available types with `balena devices supported`)', + char: 't', + required: true, +}); + export const json: IBooleanFlag = flags.boolean({ char: 'j', description: 'produce JSON output instead of tabular output', diff --git a/lib/utils/helpers.ts b/lib/utils/helpers.ts index 175c7ef1..a8151256 100644 --- a/lib/utils/helpers.ts +++ b/lib/utils/helpers.ts @@ -579,3 +579,16 @@ export async function awaitInterruptibleTask< process.removeListener('SIGINT', sigintHandler); } } + +/** Check if `drive` is mounted, and if so umount it. No-op on Windows. */ +export async function safeUmount(drive: string) { + if (!drive) { + return; + } + const { isMounted, umount } = await import('umount'); + const isMountedAsync = promisify(isMounted); + if (await isMountedAsync(drive)) { + const umountAsync = promisify(umount); + await umountAsync(drive); + } +}