@ohos-ports/drivelist 12.0.2-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +732 -0
- package/LICENSE +177 -0
- package/README.md +215 -0
- package/build/Release/drivelist.node +0 -0
- package/js/index.d.ts +39 -0
- package/js/index.js +84 -0
- package/js/index.js.map +1 -0
- package/js/lsblk/index.d.ts +3 -0
- package/js/lsblk/index.js +104 -0
- package/js/lsblk/index.js.map +1 -0
- package/js/lsblk/json.d.ts +31 -0
- package/js/lsblk/json.js +114 -0
- package/js/lsblk/json.js.map +1 -0
- package/js/lsblk/pairs.d.ts +2 -0
- package/js/lsblk/pairs.js +158 -0
- package/js/lsblk/pairs.js.map +1 -0
- package/lib/index.ts +113 -0
- package/lib/lsblk/index.ts +126 -0
- package/lib/lsblk/json.ts +155 -0
- package/lib/lsblk/pairs.ts +188 -0
- package/package.json +84 -0
- package/src/darwin/REDiskList.h +41 -0
- package/src/darwin/REDiskList.m +61 -0
- package/src/darwin/list.mm +185 -0
- package/src/device-descriptor.cpp +125 -0
- package/src/drivelist.cpp +69 -0
- package/src/drivelist.hpp +65 -0
- package/src/linux/list.cpp +29 -0
- package/src/windows/list.cpp +741 -0
- package/src/windows/list.hpp +108 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2018 Balena.io
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { getPartitionTableType } from '.';
|
|
18
|
+
import { Drive, Mountpoint } from '..';
|
|
19
|
+
|
|
20
|
+
interface Dict<T> {
|
|
21
|
+
[K: string]: T;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function parseLsblkLine(line: string): Dict<string> {
|
|
25
|
+
const data: Dict<string> = {};
|
|
26
|
+
let offset = 0;
|
|
27
|
+
let key = '';
|
|
28
|
+
let value = '';
|
|
29
|
+
|
|
30
|
+
const keyChar = /[^"=]/;
|
|
31
|
+
const whitespace = /\s+/;
|
|
32
|
+
const escape = '\\';
|
|
33
|
+
let state = 'key';
|
|
34
|
+
|
|
35
|
+
while (offset < line.length) {
|
|
36
|
+
if (state === 'key') {
|
|
37
|
+
while (keyChar.test(line[offset])) {
|
|
38
|
+
key += line[offset];
|
|
39
|
+
offset += 1;
|
|
40
|
+
}
|
|
41
|
+
if (line[offset] === '=') {
|
|
42
|
+
state = 'value';
|
|
43
|
+
offset += 1;
|
|
44
|
+
}
|
|
45
|
+
} else if (state === 'value') {
|
|
46
|
+
if (line[offset] !== '"') {
|
|
47
|
+
throw new Error(`Expected '"', saw "${line[offset]}"`);
|
|
48
|
+
}
|
|
49
|
+
offset += 1;
|
|
50
|
+
while (
|
|
51
|
+
line[offset - 1] === escape ||
|
|
52
|
+
(line[offset - 1] !== escape && line[offset] !== '"')
|
|
53
|
+
) {
|
|
54
|
+
value += line[offset];
|
|
55
|
+
offset += 1;
|
|
56
|
+
}
|
|
57
|
+
if (line[offset] !== '"') {
|
|
58
|
+
throw new Error(`Expected '"', saw "${line[offset]}"`);
|
|
59
|
+
}
|
|
60
|
+
offset += 1;
|
|
61
|
+
data[key.toLowerCase()] = value.trim();
|
|
62
|
+
key = '';
|
|
63
|
+
value = '';
|
|
64
|
+
state = 'space';
|
|
65
|
+
} else if (state === 'space') {
|
|
66
|
+
while (whitespace.test(line[offset])) {
|
|
67
|
+
offset += 1;
|
|
68
|
+
}
|
|
69
|
+
state = 'key';
|
|
70
|
+
} else {
|
|
71
|
+
throw new Error(`Undefined state "${state}"`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return data;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function parseLsblk(output: string): Array<Dict<string>> {
|
|
79
|
+
return output.trim().split(/\r?\n/g).map(parseLsblkLine);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function consolidate(
|
|
83
|
+
devices: Array<Dict<string>>,
|
|
84
|
+
): Array<Dict<string> & { mountpoints: Mountpoint[] }> {
|
|
85
|
+
const primaries = devices.filter((device) => {
|
|
86
|
+
return (
|
|
87
|
+
device.type === 'disk' &&
|
|
88
|
+
!device.name.startsWith('ram') &&
|
|
89
|
+
!device.name.startsWith('sr')
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return primaries.map((device) => {
|
|
94
|
+
const children = devices.filter((child) => {
|
|
95
|
+
return (
|
|
96
|
+
['disk', 'part'].includes(child.type) &&
|
|
97
|
+
child.name.startsWith(device.name)
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
return Object.assign({}, device, {
|
|
101
|
+
mountpoints: children
|
|
102
|
+
.filter((child) => child.mountpoint)
|
|
103
|
+
.map(
|
|
104
|
+
(child): Mountpoint => {
|
|
105
|
+
return {
|
|
106
|
+
path: child.mountpoint,
|
|
107
|
+
label: child.label,
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
),
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getDescription(
|
|
116
|
+
device: Dict<string> & { mountpoints: Mountpoint[] },
|
|
117
|
+
): string {
|
|
118
|
+
const description = [
|
|
119
|
+
device.label || '',
|
|
120
|
+
device.vendor || '',
|
|
121
|
+
device.model || '',
|
|
122
|
+
];
|
|
123
|
+
if (device.mountpoints.length) {
|
|
124
|
+
let subLabels = device.mountpoints
|
|
125
|
+
.filter((c) => {
|
|
126
|
+
return (c.label && c.label !== device.label) || c.path;
|
|
127
|
+
})
|
|
128
|
+
.map((c) => {
|
|
129
|
+
return c.label || c.path;
|
|
130
|
+
});
|
|
131
|
+
subLabels = Array.from(new Set(subLabels));
|
|
132
|
+
if (subLabels.length) {
|
|
133
|
+
description.push(`(${subLabels.join(', ')})`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return description.join(' ').replace(/\s+/g, ' ').trim();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function parse(stdout: string): Drive[] {
|
|
140
|
+
const devices = consolidate(parseLsblk(stdout));
|
|
141
|
+
|
|
142
|
+
return devices.map(
|
|
143
|
+
(
|
|
144
|
+
device: Dict<string> & {
|
|
145
|
+
mountpoints: Mountpoint[];
|
|
146
|
+
},
|
|
147
|
+
): Drive => {
|
|
148
|
+
const isVirtual = device.subsystems
|
|
149
|
+
? /^block$/i.test(device.subsystems)
|
|
150
|
+
: null;
|
|
151
|
+
const isSCSI = device.tran
|
|
152
|
+
? /^(?:sata|scsi|ata|ide|pci)$/i.test(device.tran)
|
|
153
|
+
: null;
|
|
154
|
+
const isUSB = device.tran ? /^usb$/i.test(device.tran) : null;
|
|
155
|
+
const isReadOnly = Number(device.ro) === 1;
|
|
156
|
+
const isRemovable =
|
|
157
|
+
Number(device.rm) === 1 ||
|
|
158
|
+
Number(device.hotplug) === 1 ||
|
|
159
|
+
Boolean(isVirtual);
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
enumerator: 'lsblk:pairs',
|
|
163
|
+
busType: (device.tran || 'UNKNOWN').toUpperCase(),
|
|
164
|
+
busVersion: null,
|
|
165
|
+
device: '/dev/' + device.name,
|
|
166
|
+
devicePath: null,
|
|
167
|
+
raw: '/dev/' + device.name,
|
|
168
|
+
description: getDescription(device) || device.name,
|
|
169
|
+
error: null,
|
|
170
|
+
size: Number(device.size) || null,
|
|
171
|
+
blockSize: Number(device['phy-sec']) || 512,
|
|
172
|
+
logicalBlockSize: Number(device['log-sec']) || 512,
|
|
173
|
+
mountpoints: device.mountpoints,
|
|
174
|
+
isReadOnly,
|
|
175
|
+
isSystem: !isRemovable && !isVirtual,
|
|
176
|
+
isVirtual,
|
|
177
|
+
isRemovable,
|
|
178
|
+
isCard: null,
|
|
179
|
+
isSCSI,
|
|
180
|
+
isUSB,
|
|
181
|
+
isUAS: null,
|
|
182
|
+
partitionTableType: getPartitionTableType(
|
|
183
|
+
device.pttype as 'gpt' | 'dos' | undefined,
|
|
184
|
+
),
|
|
185
|
+
};
|
|
186
|
+
},
|
|
187
|
+
);
|
|
188
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ohos-ports/drivelist",
|
|
3
|
+
"version": "12.0.2-beta.1",
|
|
4
|
+
"description": "List all connected drives in your computer, in all major operating systems (OpenHarmony port)",
|
|
5
|
+
"main": "js/index.js",
|
|
6
|
+
"homepage": "https://github.com/balena-io-modules/drivelist",
|
|
7
|
+
"gypfile": true,
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/ohos-ports/ohos-ports.git",
|
|
11
|
+
"directory": "ports/drivelist/12.0.2"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"disk",
|
|
15
|
+
"cross",
|
|
16
|
+
"platform",
|
|
17
|
+
"physical",
|
|
18
|
+
"drive",
|
|
19
|
+
"list"
|
|
20
|
+
],
|
|
21
|
+
"directories": {
|
|
22
|
+
"test": "tests"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"test": "mocha -r ts-node/register tests/**/*.spec.ts -R spec",
|
|
26
|
+
"lint": "npm run lint-cpp && npm run lint-ts",
|
|
27
|
+
"lint-cpp": "cpplint --recursive src",
|
|
28
|
+
"lint-ts": "balena-lint --typescript lib tests",
|
|
29
|
+
"prettier": "balena-lint --typescript --fix lib tests",
|
|
30
|
+
"readme": "jsdoc2md --template doc/README.hbs js/index.js > README.md",
|
|
31
|
+
"build": "node-gyp rebuild && tsc",
|
|
32
|
+
"build-ts": "tsc",
|
|
33
|
+
"prepublishOnly": "npm run build-ts && npm run readme",
|
|
34
|
+
"install": "prebuild-install --runtime napi || node-gyp rebuild",
|
|
35
|
+
"rebuild": "node-gyp rebuild"
|
|
36
|
+
},
|
|
37
|
+
"author": "Juan Cruz Viotti <juan@balena.io>",
|
|
38
|
+
"license": "Apache-2.0",
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@balena/lint": "^6.2.1",
|
|
44
|
+
"@types/bindings": "^1.5.1",
|
|
45
|
+
"@types/chai": "^4.3.4",
|
|
46
|
+
"@types/mocha": "^10.0.1",
|
|
47
|
+
"@types/sinon": "^10.0.13",
|
|
48
|
+
"chai": "^4.3.7",
|
|
49
|
+
"eslint": "^8.31.0",
|
|
50
|
+
"jsdoc-to-markdown": "^8.0.0",
|
|
51
|
+
"mocha": "^10.2.0",
|
|
52
|
+
"node-gyp": "^10.0.1",
|
|
53
|
+
"prebuild": "^11.0.4",
|
|
54
|
+
"sinon": "^15.0.1",
|
|
55
|
+
"ts-node": "^10.9.1",
|
|
56
|
+
"typescript": "^4.9.4"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"bindings": "^1.5.0",
|
|
60
|
+
"debug": "^4.3.4",
|
|
61
|
+
"node-addon-api": "^8.0.0",
|
|
62
|
+
"prebuild-install": "^7.1.1"
|
|
63
|
+
},
|
|
64
|
+
"binary": {
|
|
65
|
+
"napi_versions": [
|
|
66
|
+
8
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
"versionist": {
|
|
70
|
+
"publishedAt": "2024-04-08T15:45:36.490Z"
|
|
71
|
+
},
|
|
72
|
+
"bugs": {
|
|
73
|
+
"url": "https://github.com/ohos-ports/ohos-ports/issues"
|
|
74
|
+
},
|
|
75
|
+
"files": [
|
|
76
|
+
"js/",
|
|
77
|
+
"lib/",
|
|
78
|
+
"src/",
|
|
79
|
+
"build/Release/*.node",
|
|
80
|
+
"README.md",
|
|
81
|
+
"LICENSE",
|
|
82
|
+
"CHANGELOG.md"
|
|
83
|
+
]
|
|
84
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2019 balena.io
|
|
3
|
+
* Copyright 2018 Robin Andersson <me@robinwassen.com>
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
#ifndef SRC_DARWIN_REDISKLIST_H_
|
|
19
|
+
#define SRC_DARWIN_REDISKLIST_H_
|
|
20
|
+
|
|
21
|
+
#import <Foundation/Foundation.h>
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Class to return a list of disks synchronously
|
|
25
|
+
* To use the class, just init an instance of it and
|
|
26
|
+
* it will populate the disks property with NSStrings
|
|
27
|
+
*
|
|
28
|
+
* @author Robin Andersson
|
|
29
|
+
*/
|
|
30
|
+
@interface REDiskList : NSObject
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* NSArray of disks and partitions
|
|
34
|
+
* Disks are in the format disk0, disk1 etc
|
|
35
|
+
* Partitions are in the format disk0s1, disk1s1 etc
|
|
36
|
+
*/
|
|
37
|
+
@property(readonly) NSArray *disks;
|
|
38
|
+
|
|
39
|
+
@end
|
|
40
|
+
|
|
41
|
+
#endif // SRC_DARWIN_REDISKLIST_H_
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2019 balena.io
|
|
3
|
+
* Copyright 2018 Robin Andersson <me@robinwassen.com>
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
#import "REDiskList.h"
|
|
19
|
+
#import <DiskArbitration/DiskArbitration.h>
|
|
20
|
+
|
|
21
|
+
@implementation REDiskList
|
|
22
|
+
|
|
23
|
+
- (id)init {
|
|
24
|
+
self = [super init];
|
|
25
|
+
|
|
26
|
+
if (self) {
|
|
27
|
+
_disks = [[NSMutableArray alloc] init];
|
|
28
|
+
[self populateDisksBlocking];
|
|
29
|
+
[_disks sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return self;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
-(void)dealloc {
|
|
36
|
+
[_disks release];
|
|
37
|
+
[super dealloc];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
void appendDisk(DADiskRef disk, void *context) {
|
|
41
|
+
NSMutableArray *_disks = (__bridge NSMutableArray*)context;
|
|
42
|
+
const char *bsdName = DADiskGetBSDName(disk);
|
|
43
|
+
if (bsdName != nil) {
|
|
44
|
+
[_disks addObject:[NSString stringWithUTF8String:bsdName]];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
- (void)populateDisksBlocking {
|
|
49
|
+
DASessionRef session = DASessionCreate(kCFAllocatorDefault);
|
|
50
|
+
if (session) {
|
|
51
|
+
DARegisterDiskAppearedCallback(session, NULL, appendDisk, (void*)_disks);
|
|
52
|
+
CFRunLoopRef runLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
|
|
53
|
+
DASessionScheduleWithRunLoop(session, runLoop, kCFRunLoopDefaultMode);
|
|
54
|
+
CFRunLoopStop(runLoop);
|
|
55
|
+
CFRunLoopRunInMode((CFStringRef)NSDefaultRunLoopMode, 0.05, NO);
|
|
56
|
+
DAUnregisterCallback(session, appendDisk, (void*)_disks);
|
|
57
|
+
CFRelease(session);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@end
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2019 balena.io
|
|
3
|
+
* Copyright 2018 Robin Andersson <me@robinwassen.com>
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
#include <napi.h>
|
|
19
|
+
#include "../drivelist.hpp"
|
|
20
|
+
|
|
21
|
+
#import "REDiskList.h"
|
|
22
|
+
#import <Cocoa/Cocoa.h>
|
|
23
|
+
#import <DiskArbitration/DiskArbitration.h>
|
|
24
|
+
|
|
25
|
+
namespace Drivelist {
|
|
26
|
+
bool IsDiskPartition(NSString *disk) {
|
|
27
|
+
NSPredicate *partitionRegEx = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", @"disk\\d+s\\d+"];
|
|
28
|
+
return [partitionRegEx evaluateWithObject:disk];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
bool IsCard(CFDictionaryRef diskDescription) {
|
|
32
|
+
CFDictionaryRef mediaIconDict = (CFDictionaryRef)CFDictionaryGetValue(
|
|
33
|
+
diskDescription,
|
|
34
|
+
kDADiskDescriptionMediaIconKey
|
|
35
|
+
);
|
|
36
|
+
if (mediaIconDict == nil) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
CFStringRef iconFileNameKeyRef = CFStringCreateWithCString(NULL, "IOBundleResourceFile", kCFStringEncodingUTF8);
|
|
41
|
+
CFStringRef iconFileNameRef = (CFStringRef)CFDictionaryGetValue(mediaIconDict, iconFileNameKeyRef);
|
|
42
|
+
CFRelease(iconFileNameKeyRef);
|
|
43
|
+
|
|
44
|
+
if (iconFileNameRef == nil) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// macOS 10.14.3 - External SD card reader provides `Removable.icns`, not `SD.icns`.
|
|
49
|
+
// But we can't use it to detect SD card, because external drive has `Removable.icns` as well.
|
|
50
|
+
return [(NSString *)iconFileNameRef isEqualToString:@"SD.icns"];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
NSNumber *DictionaryGetNumber(CFDictionaryRef dict, const void *key) {
|
|
54
|
+
return (NSNumber*)CFDictionaryGetValue(dict, key);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
DeviceDescriptor CreateDeviceDescriptorFromDiskDescription(std::string diskBsdName, CFDictionaryRef diskDescription) {
|
|
58
|
+
NSString *deviceProtocol = (NSString*)CFDictionaryGetValue(diskDescription, kDADiskDescriptionDeviceProtocolKey);
|
|
59
|
+
NSNumber *blockSize = DictionaryGetNumber(diskDescription, kDADiskDescriptionMediaBlockSizeKey);
|
|
60
|
+
bool isInternal = [DictionaryGetNumber(diskDescription, kDADiskDescriptionDeviceInternalKey) boolValue];
|
|
61
|
+
bool isRemovable = [DictionaryGetNumber(diskDescription, kDADiskDescriptionMediaRemovableKey) boolValue];
|
|
62
|
+
bool isEjectable = [DictionaryGetNumber(diskDescription, kDADiskDescriptionMediaEjectableKey) boolValue];
|
|
63
|
+
|
|
64
|
+
DeviceDescriptor device = DeviceDescriptor();
|
|
65
|
+
NSString *mediaContent = (NSString*)CFDictionaryGetValue(diskDescription, kDADiskDescriptionMediaContentKey);
|
|
66
|
+
if ([mediaContent isEqualToString:@"GUID_partition_scheme"]) {
|
|
67
|
+
device.partitionTableType = "gpt";
|
|
68
|
+
} else if ([mediaContent isEqualToString:@"FDisk_partition_scheme"]) {
|
|
69
|
+
device.partitionTableType = "mbr";
|
|
70
|
+
}
|
|
71
|
+
device.enumerator = "DiskArbitration";
|
|
72
|
+
device.busType = (deviceProtocol != nil) ? [deviceProtocol UTF8String] : "";
|
|
73
|
+
device.busVersion = "";
|
|
74
|
+
device.busVersionNull = true;
|
|
75
|
+
device.device = "/dev/" + diskBsdName;
|
|
76
|
+
NSString *devicePath = (NSString*)CFDictionaryGetValue(diskDescription, kDADiskDescriptionBusPathKey);
|
|
77
|
+
device.devicePath = (devicePath != nil) ? [devicePath UTF8String] : "";
|
|
78
|
+
device.raw = "/dev/r" + diskBsdName;
|
|
79
|
+
NSString *description = (NSString*)CFDictionaryGetValue(diskDescription, kDADiskDescriptionMediaNameKey);
|
|
80
|
+
device.description = (description != nil) ? [description UTF8String] : "";
|
|
81
|
+
device.error = "";
|
|
82
|
+
// NOTE: Not sure if kDADiskDescriptionMediaBlockSizeKey returns
|
|
83
|
+
// the physical or logical block size since both values are equal
|
|
84
|
+
// on my machine
|
|
85
|
+
//
|
|
86
|
+
// The can be checked with the following command:
|
|
87
|
+
// diskutil info / | grep "Block Size"
|
|
88
|
+
device.blockSize = [blockSize unsignedIntValue];
|
|
89
|
+
device.logicalBlockSize = [blockSize unsignedIntValue];
|
|
90
|
+
device.size = [DictionaryGetNumber(diskDescription, kDADiskDescriptionMediaSizeKey) unsignedLongValue];
|
|
91
|
+
device.isReadOnly = ![DictionaryGetNumber(diskDescription, kDADiskDescriptionMediaWritableKey) boolValue];
|
|
92
|
+
device.isSystem = isInternal && !isRemovable;
|
|
93
|
+
device.isVirtual = ((deviceProtocol != nil) && [deviceProtocol isEqualToString:@"Virtual Interface"]);
|
|
94
|
+
device.isRemovable = isRemovable || isEjectable;
|
|
95
|
+
device.isCard = IsCard(diskDescription);
|
|
96
|
+
// NOTE(robin): Not convinced that these bus types should result
|
|
97
|
+
// in device.isSCSI = true, it is rather "not usb or sd drive" bool
|
|
98
|
+
// But the old implementation was like this so kept it this way
|
|
99
|
+
NSArray *scsiTypes = [NSArray arrayWithObjects:@"SATA", @"SCSI", @"ATA", @"IDE", @"PCI", nil];
|
|
100
|
+
device.isSCSI = ((deviceProtocol != nil) && [scsiTypes containsObject:deviceProtocol]);
|
|
101
|
+
device.isUSB = ((deviceProtocol != nil) && [deviceProtocol isEqualToString:@"USB"]);
|
|
102
|
+
device.isUAS = false;
|
|
103
|
+
device.isUASNull = true;
|
|
104
|
+
|
|
105
|
+
return device;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
std::vector<DeviceDescriptor> ListStorageDevices() {
|
|
109
|
+
std::vector<DeviceDescriptor> deviceList;
|
|
110
|
+
|
|
111
|
+
DASessionRef session = DASessionCreate(kCFAllocatorDefault);
|
|
112
|
+
if (session == nil) {
|
|
113
|
+
return deviceList;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
REDiskList *dl = [[REDiskList alloc] init];
|
|
117
|
+
for (NSString* diskBsdName in dl.disks) {
|
|
118
|
+
if (IsDiskPartition(diskBsdName)) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
std::string diskBsdNameStr = [diskBsdName UTF8String];
|
|
123
|
+
DADiskRef disk = DADiskCreateFromBSDName(kCFAllocatorDefault, session, diskBsdNameStr.c_str());
|
|
124
|
+
if (disk == nil) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
CFDictionaryRef diskDescription = DADiskCopyDescription(disk);
|
|
129
|
+
if (diskDescription == nil) {
|
|
130
|
+
CFRelease(disk);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
DeviceDescriptor device = CreateDeviceDescriptorFromDiskDescription(diskBsdNameStr, diskDescription);
|
|
135
|
+
deviceList.push_back(device);
|
|
136
|
+
|
|
137
|
+
CFRelease(diskDescription);
|
|
138
|
+
CFRelease(disk);
|
|
139
|
+
}
|
|
140
|
+
[dl release];
|
|
141
|
+
|
|
142
|
+
// Add mount points
|
|
143
|
+
NSArray *volumeKeys = [NSArray arrayWithObjects:NSURLVolumeNameKey, NSURLVolumeLocalizedNameKey, nil];
|
|
144
|
+
NSArray *volumePaths = [
|
|
145
|
+
[NSFileManager defaultManager]
|
|
146
|
+
mountedVolumeURLsIncludingResourceValuesForKeys:volumeKeys
|
|
147
|
+
options:0
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
for (NSURL *path in volumePaths) {
|
|
151
|
+
DADiskRef disk = DADiskCreateFromVolumePath(kCFAllocatorDefault, session, (__bridge CFURLRef)path);
|
|
152
|
+
if (disk == nil) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const char *bsdnameChar = DADiskGetBSDName(disk);
|
|
157
|
+
if (bsdnameChar == nil) {
|
|
158
|
+
CFRelease(disk);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
NSString *volumeName;
|
|
163
|
+
[path getResourceValue:&volumeName forKey:NSURLVolumeLocalizedNameKey error:nil];
|
|
164
|
+
|
|
165
|
+
std::string partitionBsdName = std::string(bsdnameChar);
|
|
166
|
+
std::string diskBsdName = partitionBsdName.substr(0, partitionBsdName.find("s", 5));
|
|
167
|
+
|
|
168
|
+
for(std::vector<int>::size_type i = 0; i != deviceList.size(); i++) {
|
|
169
|
+
DeviceDescriptor *dd = &deviceList[i];
|
|
170
|
+
|
|
171
|
+
if (dd->device == "/dev/" + diskBsdName) {
|
|
172
|
+
dd->mountpoints.push_back([[path path] UTF8String]);
|
|
173
|
+
dd->mountpointLabels.push_back([volumeName UTF8String]);
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
CFRelease(disk);
|
|
179
|
+
}
|
|
180
|
+
CFRelease(session);
|
|
181
|
+
|
|
182
|
+
return deviceList;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
} // namespace Drivelist
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2017 balena.io
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
#include <napi.h>
|
|
18
|
+
|
|
19
|
+
#include "drivelist.hpp"
|
|
20
|
+
|
|
21
|
+
using Napi::Boolean;
|
|
22
|
+
using Napi::Number;
|
|
23
|
+
using Napi::String;
|
|
24
|
+
using Napi::Value;
|
|
25
|
+
|
|
26
|
+
namespace Drivelist {
|
|
27
|
+
|
|
28
|
+
Napi::Object PackDriveDescriptor(Napi::Env env,
|
|
29
|
+
const DeviceDescriptor *instance) {
|
|
30
|
+
Napi::Object object = Napi::Object::New(env);
|
|
31
|
+
|
|
32
|
+
object.Set(String::New(env, "enumerator"),
|
|
33
|
+
String::New(env, instance->enumerator));
|
|
34
|
+
|
|
35
|
+
object.Set(String::New(env, "busType"), String::New(env, instance->busType));
|
|
36
|
+
|
|
37
|
+
Napi::Value busVersion =
|
|
38
|
+
instance->busVersionNull
|
|
39
|
+
? (Napi::Value)env.Null()
|
|
40
|
+
: (Napi::Value)String::New(env, instance->busVersion);
|
|
41
|
+
|
|
42
|
+
object.Set(String::New(env, "busVersion"), busVersion);
|
|
43
|
+
|
|
44
|
+
object.Set(String::New(env, "device"), String::New(env, instance->device));
|
|
45
|
+
|
|
46
|
+
Napi::Value devicePath =
|
|
47
|
+
instance->devicePathNull
|
|
48
|
+
? (Napi::Value)env.Null()
|
|
49
|
+
: (Napi::Value)String::New(env, instance->devicePath);
|
|
50
|
+
|
|
51
|
+
object.Set(String::New(env, "devicePath"), devicePath);
|
|
52
|
+
|
|
53
|
+
object.Set(String::New(env, "raw"), String::New(env, instance->raw));
|
|
54
|
+
|
|
55
|
+
object.Set(String::New(env, "description"),
|
|
56
|
+
String::New(env, instance->description));
|
|
57
|
+
|
|
58
|
+
if (instance->partitionTableType != "") {
|
|
59
|
+
object.Set(String::New(env, "partitionTableType"),
|
|
60
|
+
String::New(env, instance->partitionTableType));
|
|
61
|
+
} else {
|
|
62
|
+
object.Set(String::New(env, "partitionTableType"), env.Null());
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (instance->error != "") {
|
|
66
|
+
object.Set(String::New(env, "error"), String::New(env, instance->error));
|
|
67
|
+
} else {
|
|
68
|
+
object.Set(String::New(env, "error"), env.Null());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
object.Set(String::New(env, "size"),
|
|
72
|
+
Number::New(env, static_cast<double>(instance->size)));
|
|
73
|
+
|
|
74
|
+
object.Set(String::New(env, "blockSize"),
|
|
75
|
+
Number::New(env, static_cast<double>(instance->blockSize)));
|
|
76
|
+
|
|
77
|
+
object.Set(String::New(env, "logicalBlockSize"),
|
|
78
|
+
Number::New(env, static_cast<double>(instance->logicalBlockSize)));
|
|
79
|
+
|
|
80
|
+
Napi::Object mountpoints = Napi::Array::New(env);
|
|
81
|
+
|
|
82
|
+
uint32_t index = 0;
|
|
83
|
+
for (std::string mountpointPath : instance->mountpoints) {
|
|
84
|
+
Napi::Object mountpoint = Napi::Object::New(env);
|
|
85
|
+
mountpoint.Set(String::New(env, "path"), String::New(env, mountpointPath));
|
|
86
|
+
|
|
87
|
+
if (index < instance->mountpointLabels.size()) {
|
|
88
|
+
mountpoint.Set(String::New(env, "label"),
|
|
89
|
+
String::New(env, instance->mountpointLabels[index]));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
mountpoints.Set(index, mountpoint);
|
|
93
|
+
index++;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
object.Set(String::New(env, "mountpoints"), mountpoints);
|
|
97
|
+
|
|
98
|
+
object.Set(String::New(env, "isReadOnly"),
|
|
99
|
+
Boolean::New(env, instance->isReadOnly));
|
|
100
|
+
|
|
101
|
+
object.Set(String::New(env, "isSystem"),
|
|
102
|
+
Boolean::New(env, instance->isSystem));
|
|
103
|
+
|
|
104
|
+
object.Set(String::New(env, "isVirtual"),
|
|
105
|
+
Boolean::New(env, instance->isVirtual));
|
|
106
|
+
|
|
107
|
+
object.Set(String::New(env, "isRemovable"),
|
|
108
|
+
Boolean::New(env, instance->isRemovable));
|
|
109
|
+
|
|
110
|
+
object.Set(String::New(env, "isCard"), Boolean::New(env, instance->isCard));
|
|
111
|
+
|
|
112
|
+
object.Set(String::New(env, "isSCSI"), Boolean::New(env, instance->isSCSI));
|
|
113
|
+
|
|
114
|
+
object.Set(String::New(env, "isUSB"), Boolean::New(env, instance->isUSB));
|
|
115
|
+
|
|
116
|
+
Napi::Value isUAS = instance->isUASNull
|
|
117
|
+
? (Napi::Value)env.Null()
|
|
118
|
+
: (Napi::Value)Boolean::New(env, instance->isUAS);
|
|
119
|
+
|
|
120
|
+
object.Set(String::New(env, "isUAS"), isUAS);
|
|
121
|
+
|
|
122
|
+
return object;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
} // namespace Drivelist
|