mirror of
https://github.com/cmclark00/retro-imager.git
synced 2025-05-19 00:15:21 +01:00
Qt/QML edition
This commit is contained in:
commit
d7b361ba44
2168 changed files with 721948 additions and 0 deletions
93
dependencies/drivelist/lib/lsblk/index.ts
vendored
Normal file
93
dependencies/drivelist/lib/lsblk/index.ts
vendored
Normal file
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright 2018 Balena.io
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { execFile } from 'mz/child_process';
|
||||
import { readdir, readlink } from 'mz/fs';
|
||||
import { join, resolve } from 'path';
|
||||
|
||||
import { Drive } from '..';
|
||||
import { parse as parseJSON } from './json';
|
||||
import { parse as parsePairs } from './pairs';
|
||||
|
||||
const DISK_PATH_DIR = '/dev/disk/by-path/';
|
||||
|
||||
let SUPPORTS_JSON = true;
|
||||
|
||||
async function getDevicePaths(): Promise<Map<string, string>> {
|
||||
const mapping = new Map();
|
||||
for (const filename of await readdir(DISK_PATH_DIR)) {
|
||||
const linkPath = join(DISK_PATH_DIR, filename);
|
||||
let link: string;
|
||||
try {
|
||||
link = await readlink(linkPath);
|
||||
} catch (error) {
|
||||
continue;
|
||||
}
|
||||
const devicePath = resolve(DISK_PATH_DIR, link);
|
||||
mapping.set(devicePath, linkPath);
|
||||
}
|
||||
return mapping;
|
||||
}
|
||||
|
||||
async function addDevicePaths(devices: Drive[]): Promise<void> {
|
||||
const devicePaths = await getDevicePaths();
|
||||
for (const device of devices) {
|
||||
device.devicePath = devicePaths.get(device.device) || null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getOutput(command: string, ...args: string[]) {
|
||||
const [stdout] = await execFile(command, args);
|
||||
return stdout;
|
||||
}
|
||||
|
||||
async function lsblkJSON(): Promise<Drive[]> {
|
||||
return parseJSON(
|
||||
await getOutput(
|
||||
'lsblk',
|
||||
'--bytes',
|
||||
'--all',
|
||||
'--json',
|
||||
'--paths',
|
||||
'--output-all',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async function lsblkPairs(): Promise<Drive[]> {
|
||||
return parsePairs(await getOutput('lsblk', '--bytes', '--all', '--pairs'));
|
||||
}
|
||||
|
||||
async function $lsblk(): Promise<Drive[]> {
|
||||
if (SUPPORTS_JSON) {
|
||||
try {
|
||||
return await lsblkJSON();
|
||||
} catch (error) {
|
||||
SUPPORTS_JSON = false;
|
||||
}
|
||||
}
|
||||
return await lsblkPairs();
|
||||
}
|
||||
|
||||
export async function lsblk(): Promise<Drive[]> {
|
||||
const drives = await $lsblk();
|
||||
try {
|
||||
await addDevicePaths(drives);
|
||||
} catch (error) {
|
||||
// Couldn't add device paths
|
||||
}
|
||||
return drives;
|
||||
}
|
155
dependencies/drivelist/lib/lsblk/json.ts
vendored
Normal file
155
dependencies/drivelist/lib/lsblk/json.ts
vendored
Normal file
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright 2018 Balena.io
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { posix } from 'path';
|
||||
|
||||
import { Drive, Mountpoint } from '..';
|
||||
|
||||
interface LsblkJsonOutput {
|
||||
blockdevices: LsblkJsonOutputDevice[];
|
||||
}
|
||||
|
||||
interface LsblkJsonOutputDevice {
|
||||
children: LsblkJsonOutputDeviceChild[];
|
||||
hotplug?: string;
|
||||
kname?: string;
|
||||
label: string | null;
|
||||
'log-sec'?: string;
|
||||
model: string | null;
|
||||
mountpoint: string | null;
|
||||
name: string;
|
||||
partlabel: string | null;
|
||||
'phy-sec'?: string;
|
||||
rm?: string;
|
||||
ro?: string;
|
||||
size?: string;
|
||||
subsystems?: string;
|
||||
tran?: string;
|
||||
vendor: string | null;
|
||||
}
|
||||
|
||||
interface LsblkJsonOutputDeviceChild {
|
||||
label: string | null;
|
||||
mountpoint?: string;
|
||||
partlabel: string | null;
|
||||
}
|
||||
|
||||
function getMountpoints(
|
||||
children: Array<LsblkJsonOutputDeviceChild | LsblkJsonOutputDevice>,
|
||||
): Mountpoint[] {
|
||||
return children
|
||||
.filter(child => {
|
||||
return child.mountpoint;
|
||||
})
|
||||
.map(child => {
|
||||
return {
|
||||
path: child.mountpoint!,
|
||||
label: child.label || child.partlabel,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function getDescription(device: LsblkJsonOutputDevice): string {
|
||||
const description = [
|
||||
device.label || '',
|
||||
device.vendor || '',
|
||||
device.model || '',
|
||||
];
|
||||
if (device.children) {
|
||||
let subLabels = device.children
|
||||
.filter(c => (c.label && c.label !== device.label) || c.mountpoint)
|
||||
.map(c => c.label || c.mountpoint);
|
||||
subLabels = Array.from(new Set(subLabels));
|
||||
if (subLabels.length) {
|
||||
description.push(`(${subLabels.join(', ')})`);
|
||||
}
|
||||
}
|
||||
return description
|
||||
.join(' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function resolveDeviceName(name?: string): string | null {
|
||||
if (!name) {
|
||||
return null;
|
||||
}
|
||||
if (!posix.isAbsolute(name)) {
|
||||
return posix.resolve('/dev', name);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
export function transform(data: LsblkJsonOutput): Drive[] {
|
||||
return data.blockdevices
|
||||
.map(device =>
|
||||
Object.assign({}, device, {
|
||||
name: resolveDeviceName(device.name),
|
||||
kname: resolveDeviceName(device.kname),
|
||||
}),
|
||||
)
|
||||
.filter(
|
||||
device =>
|
||||
// Omit loop devices, CD/DVD drives, and RAM
|
||||
!device.name.startsWith('/dev/loop') &&
|
||||
!device.name.startsWith('/dev/sr') &&
|
||||
!device.name.startsWith('/dev/ram'),
|
||||
)
|
||||
.map(
|
||||
(device: LsblkJsonOutputDevice): Drive => {
|
||||
const isVirtual = device.subsystems
|
||||
? /^(block)$/i.test(device.subsystems)
|
||||
: null;
|
||||
const isSCSI = device.tran
|
||||
? /^(sata|scsi|ata|ide|pci)$/i.test(device.tran)
|
||||
: null;
|
||||
const isUSB = device.tran ? /^(usb)$/i.test(device.tran) : null;
|
||||
const isReadOnly = Number(device.ro) === 1;
|
||||
const isRemovable =
|
||||
Number(device.rm) === 1 ||
|
||||
Number(device.hotplug) === 1 ||
|
||||
Boolean(isVirtual);
|
||||
return {
|
||||
enumerator: 'lsblk:json',
|
||||
busType: (device.tran || 'UNKNOWN').toUpperCase(),
|
||||
busVersion: null,
|
||||
device: device.name,
|
||||
devicePath: null,
|
||||
raw: device.kname || device.name,
|
||||
description: getDescription(device),
|
||||
error: null,
|
||||
size: Number(device.size) || null,
|
||||
blockSize: Number(device['phy-sec']) || 512,
|
||||
logicalBlockSize: Number(device['log-sec']) || 512,
|
||||
mountpoints: device.children
|
||||
? getMountpoints(device.children)
|
||||
: getMountpoints([device]),
|
||||
isReadOnly,
|
||||
isSystem: !isRemovable && !isVirtual,
|
||||
isVirtual,
|
||||
isRemovable,
|
||||
isCard: null,
|
||||
isSCSI,
|
||||
isUSB,
|
||||
isUAS: null,
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function parse(stdout: string): Drive[] {
|
||||
return transform(JSON.parse(stdout));
|
||||
}
|
182
dependencies/drivelist/lib/lsblk/pairs.ts
vendored
Normal file
182
dependencies/drivelist/lib/lsblk/pairs.ts
vendored
Normal file
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* Copyright 2018 Balena.io
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
import { Drive, Mountpoint } from '..';
|
||||
|
||||
interface Dict<T> {
|
||||
[K: string]: T;
|
||||
}
|
||||
|
||||
function parseLsblkLine(line: string): Dict<string> {
|
||||
const data: Dict<string> = {};
|
||||
let offset = 0;
|
||||
let key = '';
|
||||
let value = '';
|
||||
|
||||
const keyChar = /[^"=]/;
|
||||
const whitespace = /\s+/;
|
||||
const escape = '\\';
|
||||
let state = 'key';
|
||||
|
||||
while (offset < line.length) {
|
||||
if (state === 'key') {
|
||||
while (keyChar.test(line[offset])) {
|
||||
key += line[offset];
|
||||
offset += 1;
|
||||
}
|
||||
if (line[offset] === '=') {
|
||||
state = 'value';
|
||||
offset += 1;
|
||||
}
|
||||
} else if (state === 'value') {
|
||||
if (line[offset] !== '"') {
|
||||
throw new Error(`Expected '"', saw "${line[offset]}"`);
|
||||
}
|
||||
offset += 1;
|
||||
while (line[offset] !== '"' && line[offset - 1] !== escape) {
|
||||
value += line[offset];
|
||||
offset += 1;
|
||||
}
|
||||
if (line[offset] !== '"') {
|
||||
throw new Error(`Expected '"', saw "${line[offset]}"`);
|
||||
}
|
||||
offset += 1;
|
||||
data[key.toLowerCase()] = value.trim();
|
||||
key = '';
|
||||
value = '';
|
||||
state = 'space';
|
||||
} else if (state === 'space') {
|
||||
while (whitespace.test(line[offset])) {
|
||||
offset += 1;
|
||||
}
|
||||
state = 'key';
|
||||
} else {
|
||||
throw new Error(`Undefined state "${state}"`);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function parseLsblk(output: string): Array<Dict<string>> {
|
||||
return output
|
||||
.trim()
|
||||
.split(/\r?\n/g)
|
||||
.map(parseLsblkLine);
|
||||
}
|
||||
|
||||
function consolidate(
|
||||
devices: Array<Dict<string>>,
|
||||
): Array<Dict<string> & { mountpoints: Mountpoint[] }> {
|
||||
const primaries = devices.filter(device => {
|
||||
return (
|
||||
device.type === 'disk' &&
|
||||
!device.name.startsWith('ram') &&
|
||||
!device.name.startsWith('sr')
|
||||
);
|
||||
});
|
||||
|
||||
return primaries.map(device => {
|
||||
return Object.assign({}, device, {
|
||||
mountpoints: devices
|
||||
.filter(child => {
|
||||
return (
|
||||
['disk', 'part'].includes(child.type) &&
|
||||
child.mountpoint &&
|
||||
child.name.startsWith(device.name)
|
||||
);
|
||||
})
|
||||
.map(
|
||||
(child): Mountpoint => {
|
||||
return {
|
||||
path: child.mountpoint,
|
||||
label: child.label,
|
||||
};
|
||||
},
|
||||
),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getDescription(
|
||||
device: Dict<string> & { mountpoints: Mountpoint[] },
|
||||
): string {
|
||||
const description = [
|
||||
device.label || '',
|
||||
device.vendor || '',
|
||||
device.model || '',
|
||||
];
|
||||
if (device.mountpoints.length) {
|
||||
let subLabels = device.mountpoints
|
||||
.filter(c => {
|
||||
return (c.label && c.label !== device.label) || c.path;
|
||||
})
|
||||
.map(c => {
|
||||
return c.label || c.path;
|
||||
});
|
||||
subLabels = Array.from(new Set(subLabels));
|
||||
if (subLabels.length) {
|
||||
description.push(`(${subLabels.join(', ')})`);
|
||||
}
|
||||
}
|
||||
return description
|
||||
.join(' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
export function parse(stdout: string): Drive[] {
|
||||
const devices = consolidate(parseLsblk(stdout));
|
||||
|
||||
return devices.map(
|
||||
(device: Dict<string> & { mountpoints: Mountpoint[] }): Drive => {
|
||||
const isVirtual = device.subsystems
|
||||
? /^block$/i.test(device.subsystems)
|
||||
: null;
|
||||
const isSCSI = device.tran
|
||||
? /^(?:sata|scsi|ata|ide|pci)$/i.test(device.tran)
|
||||
: null;
|
||||
const isUSB = device.tran ? /^usb$/i.test(device.tran) : null;
|
||||
const isReadOnly = Number(device.ro) === 1;
|
||||
const isRemovable =
|
||||
Number(device.rm) === 1 ||
|
||||
Number(device.hotplug) === 1 ||
|
||||
Boolean(isVirtual);
|
||||
|
||||
return {
|
||||
enumerator: 'lsblk:pairs',
|
||||
busType: (device.tran || 'UNKNOWN').toUpperCase(),
|
||||
busVersion: null,
|
||||
device: '/dev/' + device.name,
|
||||
devicePath: null,
|
||||
raw: '/dev/' + device.name,
|
||||
description: getDescription(device) || device.name,
|
||||
error: null,
|
||||
size: Number(device.size) || null,
|
||||
blockSize: Number(device['phy-sec']) || 512,
|
||||
logicalBlockSize: Number(device['log-sec']) || 512,
|
||||
mountpoints: device.mountpoints,
|
||||
isReadOnly,
|
||||
isSystem: !isRemovable && !isVirtual,
|
||||
isVirtual,
|
||||
isRemovable,
|
||||
isCard: null,
|
||||
isSCSI,
|
||||
isUSB,
|
||||
isUAS: null,
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue