mirror of
https://github.com/balena-io/balena-cli.git
synced 2024-12-20 06:07:55 +00:00
123 lines
3.5 KiB
TypeScript
123 lines
3.5 KiB
TypeScript
|
/**
|
||
|
* @license
|
||
|
* Copyright 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.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* This module was inspired by the npm `umount` package:
|
||
|
* https://www.npmjs.com/package/umount
|
||
|
* With some important changes:
|
||
|
* - Fix "Command Injection" security advisory 1512
|
||
|
* https://www.npmjs.com/advisories/1512
|
||
|
* - Port from CoffeeScript to TypeScript
|
||
|
* - Convert callbacks to async/await
|
||
|
*/
|
||
|
|
||
|
import { promisify } from 'util';
|
||
|
import * as child_process from 'child_process';
|
||
|
|
||
|
const execFile = promisify(child_process.execFile);
|
||
|
|
||
|
/**
|
||
|
* Unmount a device on Linux or macOS. No-op on Windows.
|
||
|
* @param device Device path, e.g. '/dev/disk2'
|
||
|
*/
|
||
|
export async function umount(device: string): Promise<void> {
|
||
|
if (process.platform === 'win32') {
|
||
|
return;
|
||
|
}
|
||
|
const { sanitizePath, whichBin } = await import('./which');
|
||
|
// sanitize user's input (regular expression attacks ?)
|
||
|
device = sanitizePath(device);
|
||
|
const cmd: string[] = [];
|
||
|
|
||
|
if (process.platform === 'darwin') {
|
||
|
cmd.push('/usr/sbin/diskutil', 'unmountDisk', 'force', device);
|
||
|
} else {
|
||
|
// Linux
|
||
|
const glob = promisify(await import('glob'));
|
||
|
// '?*' expands a base device path like '/dev/sdb' to an array of paths
|
||
|
// like '/dev/sdb1', '/dev/sdb2', ..., '/dev/sdb11', ... (partitions)
|
||
|
// that exist for balenaOS images and are needed as arguments to 'umount'
|
||
|
// on Linux (otherwise, umount produces an error "/dev/sdb: not mounted")
|
||
|
const devices = await glob(`${device}?*`, { nodir: true, nonull: true });
|
||
|
cmd.push(await whichBin('umount'), ...devices);
|
||
|
}
|
||
|
if (cmd.length > 1) {
|
||
|
let stderr = '';
|
||
|
try {
|
||
|
const proc = await execFile(cmd[0], cmd.slice(1));
|
||
|
stderr = proc.stderr;
|
||
|
} catch (err) {
|
||
|
const msg = [
|
||
|
'',
|
||
|
`Error executing "${cmd.join(' ')}"`,
|
||
|
stderr || '',
|
||
|
err.message || '',
|
||
|
];
|
||
|
if (process.platform === 'linux') {
|
||
|
// ignore errors like: "umount: /dev/sdb4: not mounted."
|
||
|
if (process.env.DEBUG) {
|
||
|
console.error(msg.join('\n[debug] '));
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
const { ExpectedError } = await import('../errors');
|
||
|
throw new ExpectedError(msg.join('\n'));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check if a device is mounted on Linux or macOS. Always true on Windows.
|
||
|
* @param device Device path, e.g. '/dev/disk2'
|
||
|
*/
|
||
|
export async function isMounted(device: string): Promise<boolean> {
|
||
|
if (process.platform === 'win32') {
|
||
|
return true;
|
||
|
}
|
||
|
if (!device) {
|
||
|
return false;
|
||
|
}
|
||
|
const { whichBin } = await import('./which');
|
||
|
const mountCmd = await whichBin('mount');
|
||
|
let stdout = '';
|
||
|
let stderr = '';
|
||
|
try {
|
||
|
const proc = await execFile(mountCmd);
|
||
|
stdout = proc.stdout;
|
||
|
stderr = proc.stderr;
|
||
|
} catch (err) {
|
||
|
const { ExpectedError } = await import('../errors');
|
||
|
throw new ExpectedError(
|
||
|
`Error executing "${mountCmd}":\n${stderr}\n${err.message}`,
|
||
|
);
|
||
|
}
|
||
|
const result = (stdout || '')
|
||
|
.split('\n')
|
||
|
.some((line) => line.startsWith(device));
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/** Check if `drive` is mounted and, if so, umount it. No-op on Windows. */
|
||
|
export async function safeUmount(drive: string) {
|
||
|
if (!drive) {
|
||
|
return;
|
||
|
}
|
||
|
if (await isMounted(drive)) {
|
||
|
await umount(drive);
|
||
|
}
|
||
|
}
|