config inject/read/write: Fix umount errors with OS image files

Resolves: #1003
Change-type: patch
This commit is contained in:
Paulo Castro 2021-04-13 22:44:58 +01:00
parent 06f7683837
commit 0f9d78ab50
9 changed files with 63 additions and 97 deletions

View File

@ -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

View File

@ -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 <file>';
public static flags: flags.Input<FlagsDef> = {
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(

View File

@ -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<FlagsDef> = {
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<FlagsDef, {}>(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);

View File

@ -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<FlagsDef> = {
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<FlagsDef, {}>(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) {

View File

@ -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 <key> <value>';
public static flags: flags.Input<FlagsDef> = {
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);

View File

@ -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<FlagsDef, ArgsDef>(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) {

View File

@ -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 <image>';
public static flags: flags.Input<FlagsDef> = {
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`);
}
}

View File

@ -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<boolean> = flags.boolean({
char: 'j',
description: 'produce JSON output instead of tabular output',

View File

@ -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);
}
}