mharj-diskinfo 0.2.0 → 0.2.2
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/README.md +23 -3
- package/dist/index.cjs +94 -46
- package/dist/index.d.cts +14 -5
- package/dist/index.d.mts +14 -5
- package/dist/index.mjs +94 -46
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,19 +1,39 @@
|
|
|
1
1
|
# diskinfo
|
|
2
2
|
|
|
3
|
+
[](https://github.com/ellerbrock/typescript-badges/)
|
|
4
|
+
[](https://badge.fury.io/js/mharj-diskinfo)
|
|
5
|
+
[](https://qlty.sh/gh/mharj/projects/diskinfo)
|
|
6
|
+
[](https://qlty.sh/gh/mharj/projects/diskinfo)
|
|
7
|
+
[](https://github.com/mharj/diskinfo/actions/workflows/main.yml)
|
|
8
|
+
|
|
3
9
|
read disk partition and filesystem information with NodeJS
|
|
4
10
|
Requires: NodeJS 10.4 as using Bigint
|
|
5
11
|
|
|
12
|
+
### Installation
|
|
13
|
+
|
|
6
14
|
```bash
|
|
7
15
|
npm install mharj-diskinfo --save
|
|
8
16
|
```
|
|
9
17
|
|
|
18
|
+
### Magic usage
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import {open} from 'node:fs/promises';
|
|
22
|
+
import {Magic} from 'mharj-diskinfo';
|
|
23
|
+
const handle = await open('\\\\.\\PHYSICALDRIVE0', 'rs+');
|
|
24
|
+
const magic = new Magic(handle);
|
|
25
|
+
console.log(magic.haveNtfs(0)); // true if NTFS magic found at offset 0
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Partition scan usage
|
|
29
|
+
|
|
10
30
|
```javascript
|
|
11
|
-
const
|
|
31
|
+
const fsp = require('fs/promises');
|
|
12
32
|
const scan = require('mharj-diskinfo').scan;
|
|
13
33
|
const device = '\\\\.\\PHYSICALDRIVE0';
|
|
14
34
|
//const device = '/dev/sda';
|
|
15
|
-
const
|
|
16
|
-
let data = await scan(
|
|
35
|
+
const handle = await fsp.open(device, 'rs+');
|
|
36
|
+
let data = await scan(handle);
|
|
17
37
|
console.log(data);
|
|
18
38
|
```
|
|
19
39
|
|
package/dist/index.cjs
CHANGED
|
@@ -23,24 +23,59 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
//#endregion
|
|
24
24
|
let node_fs = require("node:fs");
|
|
25
25
|
node_fs = __toESM(node_fs);
|
|
26
|
+
//#region \0@oxc-project+runtime@0.129.0/helpers/checkPrivateRedeclaration.js
|
|
27
|
+
function _checkPrivateRedeclaration(e, t) {
|
|
28
|
+
if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object");
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region \0@oxc-project+runtime@0.129.0/helpers/classPrivateFieldInitSpec.js
|
|
32
|
+
function _classPrivateFieldInitSpec(e, t, a) {
|
|
33
|
+
_checkPrivateRedeclaration(e, t), t.set(e, a);
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region \0@oxc-project+runtime@0.129.0/helpers/assertClassBrand.js
|
|
37
|
+
function _assertClassBrand(e, t, n) {
|
|
38
|
+
if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n;
|
|
39
|
+
throw new TypeError("Private element is not present on this object");
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region \0@oxc-project+runtime@0.129.0/helpers/classPrivateFieldSet2.js
|
|
43
|
+
function _classPrivateFieldSet2(s, a, r) {
|
|
44
|
+
return s.set(_assertClassBrand(s, a), r), r;
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region \0@oxc-project+runtime@0.129.0/helpers/classPrivateFieldGet2.js
|
|
48
|
+
function _classPrivateFieldGet2(s, a) {
|
|
49
|
+
return s.get(_assertClassBrand(s, a));
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
26
52
|
//#region src/Magic.ts
|
|
53
|
+
var _handle = /* @__PURE__ */ new WeakMap();
|
|
54
|
+
/**
|
|
55
|
+
* Class to check for magic numbers of various filesystems at given offsets.
|
|
56
|
+
* @example
|
|
57
|
+
* const fh = await fsp.open(device, 'rs+');
|
|
58
|
+
* const magic = new Magic(fh);
|
|
59
|
+
* console.log(magic.haveExt(0)); // check for EXT at offset 0
|
|
60
|
+
*/
|
|
27
61
|
var Magic = class {
|
|
28
|
-
constructor(
|
|
29
|
-
this
|
|
62
|
+
constructor(fileHandle) {
|
|
63
|
+
_classPrivateFieldInitSpec(this, _handle, void 0);
|
|
64
|
+
_classPrivateFieldSet2(_handle, this, fileHandle);
|
|
30
65
|
}
|
|
31
66
|
haveExt(offset) {
|
|
32
67
|
const data = Buffer.allocUnsafe(2048);
|
|
33
|
-
node_fs.default.readSync(this.fd, data, 0, data.length, 512 * offset);
|
|
68
|
+
node_fs.default.readSync(_classPrivateFieldGet2(_handle, this).fd, data, 0, data.length, 512 * offset);
|
|
34
69
|
return data.readInt16BE(1080) === 21487;
|
|
35
70
|
}
|
|
36
71
|
haveNtfs(offset) {
|
|
37
72
|
const data = Buffer.allocUnsafe(512);
|
|
38
|
-
node_fs.default.readSync(this.fd, data, 0, data.length, 512 * offset);
|
|
73
|
+
node_fs.default.readSync(_classPrivateFieldGet2(_handle, this).fd, data, 0, data.length, 512 * offset);
|
|
39
74
|
return data.readInt32BE(3) === 1314145875;
|
|
40
75
|
}
|
|
41
76
|
haveLvm2(offset) {
|
|
42
77
|
const data = Buffer.allocUnsafe(1024);
|
|
43
|
-
node_fs.default.readSync(this.fd, data, 0, data.length, 512 * offset);
|
|
78
|
+
node_fs.default.readSync(_classPrivateFieldGet2(_handle, this).fd, data, 0, data.length, 512 * offset);
|
|
44
79
|
return data.readInt32BE(536) === 1280724274;
|
|
45
80
|
}
|
|
46
81
|
};
|
|
@@ -93,13 +128,13 @@ function readUuidBytes(buf, pos) {
|
|
|
93
128
|
buf[pos + 15]
|
|
94
129
|
]);
|
|
95
130
|
}
|
|
96
|
-
function
|
|
131
|
+
function readUuidAsString(buf, pos) {
|
|
97
132
|
const uuid = readUuidBytes(buf, pos).toString("hex");
|
|
98
133
|
return `${uuid.slice(0, 8)}-${uuid.slice(8, 12)}-${uuid.slice(12, 16)}-${uuid.slice(16, 20)}-${uuid.slice(20, 32)}`;
|
|
99
134
|
}
|
|
100
135
|
function parseGPTable(buf) {
|
|
101
|
-
const typeId =
|
|
102
|
-
const uuid =
|
|
136
|
+
const typeId = readUuidAsString(buf, 0);
|
|
137
|
+
const uuid = readUuidAsString(buf, 16);
|
|
103
138
|
const startLBA = buf.readBigUInt64LE(32);
|
|
104
139
|
const endLBA = buf.readBigUInt64LE(40);
|
|
105
140
|
return {
|
|
@@ -124,7 +159,7 @@ function parseGPT(buf) {
|
|
|
124
159
|
backupLBA: buf.readBigUInt64LE(32),
|
|
125
160
|
firstUsableLBA: buf.readBigUInt64LE(40),
|
|
126
161
|
lastUsableLBA: buf.readBigUInt64LE(48),
|
|
127
|
-
uuid:
|
|
162
|
+
uuid: readUuidAsString(buf, 56),
|
|
128
163
|
tableLBA: buf.readBigUInt64LE(72),
|
|
129
164
|
partitions: buf.readUInt32LE(80),
|
|
130
165
|
partitionSize: buf.readUInt32LE(84),
|
|
@@ -133,7 +168,7 @@ function parseGPT(buf) {
|
|
|
133
168
|
}
|
|
134
169
|
//#endregion
|
|
135
170
|
//#region src/mbrPart.ts
|
|
136
|
-
const partTypes = {
|
|
171
|
+
const partTypes = Object.freeze({
|
|
137
172
|
EMPTY: 0,
|
|
138
173
|
EXTENDED: 5,
|
|
139
174
|
NTFS: 7,
|
|
@@ -150,7 +185,7 @@ const partTypes = {
|
|
|
150
185
|
return acc;
|
|
151
186
|
}, "Unknown");
|
|
152
187
|
}
|
|
153
|
-
};
|
|
188
|
+
});
|
|
154
189
|
function parseMBR(mbr) {
|
|
155
190
|
if (mbr.length < 512 || mbr[510] !== 85 || mbr[511] !== 170) throw Error("no MBR signature or buffer is less than 512 bytes");
|
|
156
191
|
const ret = {
|
|
@@ -164,7 +199,7 @@ function parseMBR(mbr) {
|
|
|
164
199
|
partitions: [],
|
|
165
200
|
type: "MBR"
|
|
166
201
|
};
|
|
167
|
-
for (let i = 446; i <= 508; i += 16) ret.partitions.push(parseMBRPartition(mbr.
|
|
202
|
+
for (let i = 446; i <= 508; i += 16) ret.partitions.push(parseMBRPartition(mbr.subarray(i, i + 16)));
|
|
168
203
|
return ret;
|
|
169
204
|
}
|
|
170
205
|
function parseMBRPartition(part) {
|
|
@@ -182,45 +217,58 @@ function isMbrPartition(part) {
|
|
|
182
217
|
return typeof part.type === "number";
|
|
183
218
|
}
|
|
184
219
|
//#endregion
|
|
185
|
-
//#region src/util.ts
|
|
186
|
-
function readFile(fd, offset, length, position) {
|
|
187
|
-
return new Promise((resolve, reject) => {
|
|
188
|
-
const buffer = Buffer.allocUnsafe(length);
|
|
189
|
-
node_fs.default.read(fd, buffer, offset, buffer.length, position, (err) => {
|
|
190
|
-
if (err) reject(err);
|
|
191
|
-
else resolve(buffer);
|
|
192
|
-
});
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
//#endregion
|
|
196
220
|
//#region src/scan.ts
|
|
197
|
-
async function
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
221
|
+
async function getMbrPartitions(handle, startLBA) {
|
|
222
|
+
const buffer = Buffer.allocUnsafe(512);
|
|
223
|
+
await handle.read(buffer, 0, 512, startLBA * 512);
|
|
224
|
+
const output = [];
|
|
225
|
+
const extparts = parseMBR(buffer);
|
|
226
|
+
for (const extpart of extparts.partitions) {
|
|
227
|
+
if (!isMbrPartition(extpart)) throw TypeError("we did get GPT partition as extended");
|
|
228
|
+
if (extpart.type !== partTypes.EMPTY) {
|
|
229
|
+
extpart.startLBA = extpart.startLBA + startLBA;
|
|
230
|
+
output.push(extpart);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return output;
|
|
234
|
+
}
|
|
235
|
+
async function getGptPartitions(handle) {
|
|
236
|
+
const buffer = Buffer.allocUnsafe(512);
|
|
237
|
+
await handle.read(buffer, 0, 512, 512);
|
|
238
|
+
const gpt = parseGPT(buffer);
|
|
239
|
+
const gBuff = Buffer.allocUnsafe(gpt.partitions * gpt.partitionSize);
|
|
240
|
+
const partitions = [];
|
|
241
|
+
for (let i = 0; i < gpt.partitions * gpt.partitionSize; i += gpt.partitionSize) {
|
|
242
|
+
const table = parseGPTable(gBuff.subarray(i, i + gpt.partitionSize));
|
|
243
|
+
if (table.typeId !== gptPartTypes.EMPTY) partitions.push(table);
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
uuid: gpt.uuid,
|
|
247
|
+
partitions
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function isGptPartition(root, part) {
|
|
251
|
+
return part.type === partTypes.GPT;
|
|
252
|
+
}
|
|
253
|
+
function isExtendedPartition(part) {
|
|
254
|
+
return part.type === partTypes.EXTENDED;
|
|
255
|
+
}
|
|
256
|
+
async function scan(handle) {
|
|
257
|
+
const buffer = Buffer.allocUnsafe(512);
|
|
258
|
+
await handle.read(buffer, 0, 512, 0);
|
|
259
|
+
const rootMbr = parseMBR(buffer);
|
|
260
|
+
for (const p of rootMbr.partitions) {
|
|
261
|
+
if (isExtendedPartition(p)) {
|
|
262
|
+
const partitions = await getMbrPartitions(handle, p.startLBA);
|
|
263
|
+
rootMbr.partitions.push(...partitions);
|
|
209
264
|
}
|
|
210
|
-
if (p
|
|
265
|
+
if (isGptPartition(rootMbr, p)) {
|
|
266
|
+
const { partitions, uuid } = await getGptPartitions(handle);
|
|
211
267
|
rootMbr.type = "GPT";
|
|
212
|
-
const gpt = parseGPT(await readFile(fd, 0, 512, 512));
|
|
213
|
-
rootMbr.uuid = gpt.uuid;
|
|
214
|
-
const gBuff = Buffer.allocUnsafe(gpt.partitions * gpt.partitionSize);
|
|
215
|
-
node_fs.default.readSync(fd, gBuff, 0, gBuff.length, Number(gpt.tableLBA) * 512);
|
|
216
|
-
const partitions = [];
|
|
217
|
-
for (let i = 0; i < gpt.partitions * gpt.partitionSize; i += gpt.partitionSize) {
|
|
218
|
-
const table = parseGPTable(gBuff.slice(i, i + gpt.partitionSize));
|
|
219
|
-
if (table.typeId !== gptPartTypes.EMPTY) partitions.push(table);
|
|
220
|
-
}
|
|
221
268
|
rootMbr.partitions = partitions;
|
|
269
|
+
rootMbr.uuid = uuid;
|
|
222
270
|
}
|
|
223
|
-
}
|
|
271
|
+
}
|
|
224
272
|
return rootMbr;
|
|
225
273
|
}
|
|
226
274
|
//#endregion
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
import { FileHandle } from "node:fs/promises";
|
|
2
|
+
|
|
1
3
|
//#region src/Magic.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Class to check for magic numbers of various filesystems at given offsets.
|
|
6
|
+
* @example
|
|
7
|
+
* const fh = await fsp.open(device, 'rs+');
|
|
8
|
+
* const magic = new Magic(fh);
|
|
9
|
+
* console.log(magic.haveExt(0)); // check for EXT at offset 0
|
|
10
|
+
*/
|
|
2
11
|
declare class Magic {
|
|
3
|
-
private
|
|
4
|
-
constructor(
|
|
12
|
+
#private;
|
|
13
|
+
constructor(fileHandle: FileHandle);
|
|
5
14
|
haveExt(offset: number): boolean;
|
|
6
15
|
haveNtfs(offset: number): boolean;
|
|
7
16
|
haveLvm2(offset: number): boolean;
|
|
@@ -31,18 +40,18 @@ type GPTPartition = {
|
|
|
31
40
|
type GptData = {
|
|
32
41
|
copyProtected: boolean;
|
|
33
42
|
uuid: string;
|
|
34
|
-
type:
|
|
43
|
+
type: "GPT";
|
|
35
44
|
partitions: GPTPartition[];
|
|
36
45
|
};
|
|
37
46
|
type MbrData = {
|
|
38
47
|
copyProtected: boolean;
|
|
39
48
|
uuid: string;
|
|
40
|
-
type:
|
|
49
|
+
type: "MBR";
|
|
41
50
|
partitions: MBRPartition[];
|
|
42
51
|
};
|
|
43
52
|
type IMbrData = MbrData | GptData;
|
|
44
53
|
//#endregion
|
|
45
54
|
//#region src/scan.d.ts
|
|
46
|
-
declare function scan(
|
|
55
|
+
declare function scan(handle: FileHandle): Promise<IMbrData>;
|
|
47
56
|
//#endregion
|
|
48
57
|
export { Magic, scan };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
import { FileHandle } from "node:fs/promises";
|
|
2
|
+
|
|
1
3
|
//#region src/Magic.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Class to check for magic numbers of various filesystems at given offsets.
|
|
6
|
+
* @example
|
|
7
|
+
* const fh = await fsp.open(device, 'rs+');
|
|
8
|
+
* const magic = new Magic(fh);
|
|
9
|
+
* console.log(magic.haveExt(0)); // check for EXT at offset 0
|
|
10
|
+
*/
|
|
2
11
|
declare class Magic {
|
|
3
|
-
private
|
|
4
|
-
constructor(
|
|
12
|
+
#private;
|
|
13
|
+
constructor(fileHandle: FileHandle);
|
|
5
14
|
haveExt(offset: number): boolean;
|
|
6
15
|
haveNtfs(offset: number): boolean;
|
|
7
16
|
haveLvm2(offset: number): boolean;
|
|
@@ -31,18 +40,18 @@ type GPTPartition = {
|
|
|
31
40
|
type GptData = {
|
|
32
41
|
copyProtected: boolean;
|
|
33
42
|
uuid: string;
|
|
34
|
-
type:
|
|
43
|
+
type: "GPT";
|
|
35
44
|
partitions: GPTPartition[];
|
|
36
45
|
};
|
|
37
46
|
type MbrData = {
|
|
38
47
|
copyProtected: boolean;
|
|
39
48
|
uuid: string;
|
|
40
|
-
type:
|
|
49
|
+
type: "MBR";
|
|
41
50
|
partitions: MBRPartition[];
|
|
42
51
|
};
|
|
43
52
|
type IMbrData = MbrData | GptData;
|
|
44
53
|
//#endregion
|
|
45
54
|
//#region src/scan.d.ts
|
|
46
|
-
declare function scan(
|
|
55
|
+
declare function scan(handle: FileHandle): Promise<IMbrData>;
|
|
47
56
|
//#endregion
|
|
48
57
|
export { Magic, scan };
|
package/dist/index.mjs
CHANGED
|
@@ -1,22 +1,57 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
//#region \0@oxc-project+runtime@0.129.0/helpers/checkPrivateRedeclaration.js
|
|
3
|
+
function _checkPrivateRedeclaration(e, t) {
|
|
4
|
+
if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object");
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region \0@oxc-project+runtime@0.129.0/helpers/classPrivateFieldInitSpec.js
|
|
8
|
+
function _classPrivateFieldInitSpec(e, t, a) {
|
|
9
|
+
_checkPrivateRedeclaration(e, t), t.set(e, a);
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region \0@oxc-project+runtime@0.129.0/helpers/assertClassBrand.js
|
|
13
|
+
function _assertClassBrand(e, t, n) {
|
|
14
|
+
if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n;
|
|
15
|
+
throw new TypeError("Private element is not present on this object");
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region \0@oxc-project+runtime@0.129.0/helpers/classPrivateFieldSet2.js
|
|
19
|
+
function _classPrivateFieldSet2(s, a, r) {
|
|
20
|
+
return s.set(_assertClassBrand(s, a), r), r;
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region \0@oxc-project+runtime@0.129.0/helpers/classPrivateFieldGet2.js
|
|
24
|
+
function _classPrivateFieldGet2(s, a) {
|
|
25
|
+
return s.get(_assertClassBrand(s, a));
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
2
28
|
//#region src/Magic.ts
|
|
29
|
+
var _handle = /* @__PURE__ */ new WeakMap();
|
|
30
|
+
/**
|
|
31
|
+
* Class to check for magic numbers of various filesystems at given offsets.
|
|
32
|
+
* @example
|
|
33
|
+
* const fh = await fsp.open(device, 'rs+');
|
|
34
|
+
* const magic = new Magic(fh);
|
|
35
|
+
* console.log(magic.haveExt(0)); // check for EXT at offset 0
|
|
36
|
+
*/
|
|
3
37
|
var Magic = class {
|
|
4
|
-
constructor(
|
|
5
|
-
this
|
|
38
|
+
constructor(fileHandle) {
|
|
39
|
+
_classPrivateFieldInitSpec(this, _handle, void 0);
|
|
40
|
+
_classPrivateFieldSet2(_handle, this, fileHandle);
|
|
6
41
|
}
|
|
7
42
|
haveExt(offset) {
|
|
8
43
|
const data = Buffer.allocUnsafe(2048);
|
|
9
|
-
fs.readSync(this.fd, data, 0, data.length, 512 * offset);
|
|
44
|
+
fs.readSync(_classPrivateFieldGet2(_handle, this).fd, data, 0, data.length, 512 * offset);
|
|
10
45
|
return data.readInt16BE(1080) === 21487;
|
|
11
46
|
}
|
|
12
47
|
haveNtfs(offset) {
|
|
13
48
|
const data = Buffer.allocUnsafe(512);
|
|
14
|
-
fs.readSync(this.fd, data, 0, data.length, 512 * offset);
|
|
49
|
+
fs.readSync(_classPrivateFieldGet2(_handle, this).fd, data, 0, data.length, 512 * offset);
|
|
15
50
|
return data.readInt32BE(3) === 1314145875;
|
|
16
51
|
}
|
|
17
52
|
haveLvm2(offset) {
|
|
18
53
|
const data = Buffer.allocUnsafe(1024);
|
|
19
|
-
fs.readSync(this.fd, data, 0, data.length, 512 * offset);
|
|
54
|
+
fs.readSync(_classPrivateFieldGet2(_handle, this).fd, data, 0, data.length, 512 * offset);
|
|
20
55
|
return data.readInt32BE(536) === 1280724274;
|
|
21
56
|
}
|
|
22
57
|
};
|
|
@@ -69,13 +104,13 @@ function readUuidBytes(buf, pos) {
|
|
|
69
104
|
buf[pos + 15]
|
|
70
105
|
]);
|
|
71
106
|
}
|
|
72
|
-
function
|
|
107
|
+
function readUuidAsString(buf, pos) {
|
|
73
108
|
const uuid = readUuidBytes(buf, pos).toString("hex");
|
|
74
109
|
return `${uuid.slice(0, 8)}-${uuid.slice(8, 12)}-${uuid.slice(12, 16)}-${uuid.slice(16, 20)}-${uuid.slice(20, 32)}`;
|
|
75
110
|
}
|
|
76
111
|
function parseGPTable(buf) {
|
|
77
|
-
const typeId =
|
|
78
|
-
const uuid =
|
|
112
|
+
const typeId = readUuidAsString(buf, 0);
|
|
113
|
+
const uuid = readUuidAsString(buf, 16);
|
|
79
114
|
const startLBA = buf.readBigUInt64LE(32);
|
|
80
115
|
const endLBA = buf.readBigUInt64LE(40);
|
|
81
116
|
return {
|
|
@@ -100,7 +135,7 @@ function parseGPT(buf) {
|
|
|
100
135
|
backupLBA: buf.readBigUInt64LE(32),
|
|
101
136
|
firstUsableLBA: buf.readBigUInt64LE(40),
|
|
102
137
|
lastUsableLBA: buf.readBigUInt64LE(48),
|
|
103
|
-
uuid:
|
|
138
|
+
uuid: readUuidAsString(buf, 56),
|
|
104
139
|
tableLBA: buf.readBigUInt64LE(72),
|
|
105
140
|
partitions: buf.readUInt32LE(80),
|
|
106
141
|
partitionSize: buf.readUInt32LE(84),
|
|
@@ -109,7 +144,7 @@ function parseGPT(buf) {
|
|
|
109
144
|
}
|
|
110
145
|
//#endregion
|
|
111
146
|
//#region src/mbrPart.ts
|
|
112
|
-
const partTypes = {
|
|
147
|
+
const partTypes = Object.freeze({
|
|
113
148
|
EMPTY: 0,
|
|
114
149
|
EXTENDED: 5,
|
|
115
150
|
NTFS: 7,
|
|
@@ -126,7 +161,7 @@ const partTypes = {
|
|
|
126
161
|
return acc;
|
|
127
162
|
}, "Unknown");
|
|
128
163
|
}
|
|
129
|
-
};
|
|
164
|
+
});
|
|
130
165
|
function parseMBR(mbr) {
|
|
131
166
|
if (mbr.length < 512 || mbr[510] !== 85 || mbr[511] !== 170) throw Error("no MBR signature or buffer is less than 512 bytes");
|
|
132
167
|
const ret = {
|
|
@@ -140,7 +175,7 @@ function parseMBR(mbr) {
|
|
|
140
175
|
partitions: [],
|
|
141
176
|
type: "MBR"
|
|
142
177
|
};
|
|
143
|
-
for (let i = 446; i <= 508; i += 16) ret.partitions.push(parseMBRPartition(mbr.
|
|
178
|
+
for (let i = 446; i <= 508; i += 16) ret.partitions.push(parseMBRPartition(mbr.subarray(i, i + 16)));
|
|
144
179
|
return ret;
|
|
145
180
|
}
|
|
146
181
|
function parseMBRPartition(part) {
|
|
@@ -158,45 +193,58 @@ function isMbrPartition(part) {
|
|
|
158
193
|
return typeof part.type === "number";
|
|
159
194
|
}
|
|
160
195
|
//#endregion
|
|
161
|
-
//#region src/util.ts
|
|
162
|
-
function readFile(fd, offset, length, position) {
|
|
163
|
-
return new Promise((resolve, reject) => {
|
|
164
|
-
const buffer = Buffer.allocUnsafe(length);
|
|
165
|
-
fs.read(fd, buffer, offset, buffer.length, position, (err) => {
|
|
166
|
-
if (err) reject(err);
|
|
167
|
-
else resolve(buffer);
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
//#endregion
|
|
172
196
|
//#region src/scan.ts
|
|
173
|
-
async function
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
197
|
+
async function getMbrPartitions(handle, startLBA) {
|
|
198
|
+
const buffer = Buffer.allocUnsafe(512);
|
|
199
|
+
await handle.read(buffer, 0, 512, startLBA * 512);
|
|
200
|
+
const output = [];
|
|
201
|
+
const extparts = parseMBR(buffer);
|
|
202
|
+
for (const extpart of extparts.partitions) {
|
|
203
|
+
if (!isMbrPartition(extpart)) throw TypeError("we did get GPT partition as extended");
|
|
204
|
+
if (extpart.type !== partTypes.EMPTY) {
|
|
205
|
+
extpart.startLBA = extpart.startLBA + startLBA;
|
|
206
|
+
output.push(extpart);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return output;
|
|
210
|
+
}
|
|
211
|
+
async function getGptPartitions(handle) {
|
|
212
|
+
const buffer = Buffer.allocUnsafe(512);
|
|
213
|
+
await handle.read(buffer, 0, 512, 512);
|
|
214
|
+
const gpt = parseGPT(buffer);
|
|
215
|
+
const gBuff = Buffer.allocUnsafe(gpt.partitions * gpt.partitionSize);
|
|
216
|
+
const partitions = [];
|
|
217
|
+
for (let i = 0; i < gpt.partitions * gpt.partitionSize; i += gpt.partitionSize) {
|
|
218
|
+
const table = parseGPTable(gBuff.subarray(i, i + gpt.partitionSize));
|
|
219
|
+
if (table.typeId !== gptPartTypes.EMPTY) partitions.push(table);
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
uuid: gpt.uuid,
|
|
223
|
+
partitions
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function isGptPartition(root, part) {
|
|
227
|
+
return part.type === partTypes.GPT;
|
|
228
|
+
}
|
|
229
|
+
function isExtendedPartition(part) {
|
|
230
|
+
return part.type === partTypes.EXTENDED;
|
|
231
|
+
}
|
|
232
|
+
async function scan(handle) {
|
|
233
|
+
const buffer = Buffer.allocUnsafe(512);
|
|
234
|
+
await handle.read(buffer, 0, 512, 0);
|
|
235
|
+
const rootMbr = parseMBR(buffer);
|
|
236
|
+
for (const p of rootMbr.partitions) {
|
|
237
|
+
if (isExtendedPartition(p)) {
|
|
238
|
+
const partitions = await getMbrPartitions(handle, p.startLBA);
|
|
239
|
+
rootMbr.partitions.push(...partitions);
|
|
185
240
|
}
|
|
186
|
-
if (p
|
|
241
|
+
if (isGptPartition(rootMbr, p)) {
|
|
242
|
+
const { partitions, uuid } = await getGptPartitions(handle);
|
|
187
243
|
rootMbr.type = "GPT";
|
|
188
|
-
const gpt = parseGPT(await readFile(fd, 0, 512, 512));
|
|
189
|
-
rootMbr.uuid = gpt.uuid;
|
|
190
|
-
const gBuff = Buffer.allocUnsafe(gpt.partitions * gpt.partitionSize);
|
|
191
|
-
fs.readSync(fd, gBuff, 0, gBuff.length, Number(gpt.tableLBA) * 512);
|
|
192
|
-
const partitions = [];
|
|
193
|
-
for (let i = 0; i < gpt.partitions * gpt.partitionSize; i += gpt.partitionSize) {
|
|
194
|
-
const table = parseGPTable(gBuff.slice(i, i + gpt.partitionSize));
|
|
195
|
-
if (table.typeId !== gptPartTypes.EMPTY) partitions.push(table);
|
|
196
|
-
}
|
|
197
244
|
rootMbr.partitions = partitions;
|
|
245
|
+
rootMbr.uuid = uuid;
|
|
198
246
|
}
|
|
199
|
-
}
|
|
247
|
+
}
|
|
200
248
|
return rootMbr;
|
|
201
249
|
}
|
|
202
250
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mharj-diskinfo",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Node.js library to parse MBR and GPT partition information from disk images or devices.",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
20
|
"prepare": "lefthook install",
|
|
21
|
-
"build": "tsdown src/index.ts --format cjs,esm --dts --clean",
|
|
21
|
+
"build": "tsdown src/index.ts --format cjs,esm --dts --clean -c tsconfig.build.json",
|
|
22
22
|
"prepublishOnly": "npm run build",
|
|
23
23
|
"test": "vitest test --typecheck --run --no-isolate --coverage",
|
|
24
24
|
"coverage": "vitest test --run --no-isolate --reporter=dot --coverage --coverage.reporter=lcov",
|
|
25
25
|
"lint": "biome check",
|
|
26
|
-
"validate": "
|
|
26
|
+
"validate": "tsgo --noEmit"
|
|
27
27
|
},
|
|
28
28
|
"repository": "github:mharj/diskinfo",
|
|
29
29
|
"files": [
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"@tsconfig/node16": "^16.1.8",
|
|
44
44
|
"@types/buffer-crc32": "^0.2.0",
|
|
45
45
|
"@types/node": "^22.19.1",
|
|
46
|
+
"@typescript/native-preview": "7.0.0-dev.20260421.2",
|
|
46
47
|
"@vitest/coverage-v8": "^4.1.6",
|
|
47
48
|
"buffer-crc32": "^0.2.13",
|
|
48
49
|
"c8": "^11.0.0",
|