sysiddr5 1.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sysiddr5 might be problematic. Click here for more details.

@@ -0,0 +1,1495 @@
1
+ 'use strict';
2
+ // @ts-check
3
+ // ==================================================================================
4
+ // filesystem.js
5
+ // ----------------------------------------------------------------------------------
6
+ // Description: System Information - library
7
+ // for Node.js
8
+ // Copyright: (c) 2014 - 2023
9
+ // Author: Sebastian Hildebrandt
10
+ // ----------------------------------------------------------------------------------
11
+ // License: MIT
12
+ // ==================================================================================
13
+ // 8. File System
14
+ // ----------------------------------------------------------------------------------
15
+
16
+ const util = require('./util');
17
+ const fs = require('fs');
18
+
19
+ const exec = require('child_process').exec;
20
+ const execSync = require('child_process').execSync;
21
+ const execPromiseSave = util.promisifySave(require('child_process').exec);
22
+
23
+ let _platform = process.platform;
24
+
25
+ const _linux = (_platform === 'linux' || _platform === 'android');
26
+ const _darwin = (_platform === 'darwin');
27
+ const _windows = (_platform === 'win32');
28
+ const _freebsd = (_platform === 'freebsd');
29
+ const _openbsd = (_platform === 'openbsd');
30
+ const _netbsd = (_platform === 'netbsd');
31
+ const _sunos = (_platform === 'sunos');
32
+
33
+ let _fs_speed = {};
34
+ let _disk_io = {};
35
+
36
+ // --------------------------
37
+ // FS - mounted file systems
38
+
39
+ function fsSize(drive, callback) {
40
+
41
+ if (util.isFunction(drive)) {
42
+ callback = drive;
43
+ drive = '';
44
+ }
45
+
46
+ let macOsDisks = [];
47
+ let osMounts = [];
48
+
49
+ function getmacOsFsType(fs) {
50
+ if (!fs.startsWith('/')) { return 'NFS'; }
51
+ const parts = fs.split('/');
52
+ const fsShort = parts[parts.length - 1];
53
+ const macOsDisksSingle = macOsDisks.filter(item => item.indexOf(fsShort) >= 0);
54
+ if (macOsDisksSingle.length === 1 && macOsDisksSingle[0].indexOf('APFS') >= 0) { return 'APFS'; }
55
+ return 'HFS';
56
+ }
57
+
58
+ function isLinuxTmpFs(fs) {
59
+ const linuxTmpFileSystems = ['rootfs', 'unionfs', 'squashfs', 'cramfs', 'initrd', 'initramfs', 'devtmpfs', 'tmpfs', 'udev', 'devfs', 'specfs', 'type', 'appimaged'];
60
+ let result = false;
61
+ linuxTmpFileSystems.forEach(linuxFs => {
62
+ if (fs.toLowerCase().indexOf(linuxFs) >= 0) { result = true; }
63
+ });
64
+ return result;
65
+ }
66
+
67
+ function filterLines(stdout) {
68
+ let lines = stdout.toString().split('\n');
69
+ lines.shift();
70
+ if (stdout.toString().toLowerCase().indexOf('filesystem')) {
71
+ let removeLines = 0;
72
+ for (let i = 0; i < lines.length; i++) {
73
+ if (lines[i] && lines[i].toLowerCase().startsWith('filesystem')) {
74
+ removeLines = i;
75
+ }
76
+ }
77
+ for (let i = 0; i < removeLines; i++) {
78
+ lines.shift();
79
+ }
80
+ }
81
+ return lines;
82
+ }
83
+
84
+ function parseDf(lines) {
85
+ let data = [];
86
+ lines.forEach(function (line) {
87
+ if (line !== '') {
88
+ line = line.replace(/ +/g, ' ').split(' ');
89
+ if (line && ((line[0].startsWith('/')) || (line[6] && line[6] === '/') || (line[0].indexOf('/') > 0) || (line[0].indexOf(':') === 1) || !_darwin && !isLinuxTmpFs(line[1]))) {
90
+ const fs = line[0];
91
+ const fsType = ((_linux || _freebsd || _openbsd || _netbsd) ? line[1] : getmacOsFsType(line[0]));
92
+ const size = parseInt(((_linux || _freebsd || _openbsd || _netbsd) ? line[2] : line[1])) * 1024;
93
+ const used = parseInt(((_linux || _freebsd || _openbsd || _netbsd) ? line[3] : line[2])) * 1024;
94
+ const available = parseInt(((_linux || _freebsd || _openbsd || _netbsd) ? line[4] : line[3])) * 1024;
95
+ const use = parseFloat((100.0 * (used / (used + available))).toFixed(2));
96
+ let rw = osMounts && Object.keys(osMounts).length > 0 ? osMounts[fs] || false : null;
97
+ line.splice(0, (_linux || _freebsd || _openbsd || _netbsd) ? 6 : 5);
98
+ const mount = line.join(' ');
99
+ if (!data.find(el => (el.fs === fs && el.type === fsType))) {
100
+ data.push({
101
+ fs,
102
+ type: fsType,
103
+ size,
104
+ used,
105
+ available,
106
+ use,
107
+ mount,
108
+ rw
109
+ });
110
+ }
111
+ }
112
+ }
113
+ });
114
+ return data;
115
+ }
116
+
117
+ return new Promise((resolve) => {
118
+ process.nextTick(() => {
119
+ let data = [];
120
+ if (_linux || _freebsd || _openbsd || _netbsd || _darwin) {
121
+ let cmd = '';
122
+ macOsDisks = [];
123
+ osMounts = {};
124
+ if (_darwin) {
125
+ cmd = 'df -kP';
126
+ try {
127
+ macOsDisks = execSync('diskutil list').toString().split('\n').filter(line => {
128
+ return !line.startsWith('/') && line.indexOf(':') > 0;
129
+ });
130
+ execSync('mount').toString().split('\n').filter(line => {
131
+ return line.startsWith('/');
132
+ }).forEach((line) => {
133
+ osMounts[line.split(' ')[0]] = line.toLowerCase().indexOf('read-only') === -1;
134
+ });
135
+ } catch (e) {
136
+ util.noop();
137
+ }
138
+ }
139
+ if (_linux) {
140
+ cmd = 'export LC_ALL=C; df -lkPTx squashfs; unset LC_ALL';
141
+ execSync('cat /proc/mounts 2>/dev/null').toString().split('\n').filter(line => {
142
+ return line.startsWith('/');
143
+ }).forEach((line) => {
144
+ osMounts[line.split(' ')[0]] = line.toLowerCase().indexOf('rw') >= 0;
145
+ });
146
+ }
147
+ if (_freebsd || _openbsd || _netbsd) {
148
+ cmd = 'df -lkPT';
149
+ execSync('mount').toString().split('\n').forEach((line) => {
150
+ osMounts[line.split(' ')[0]] = line.toLowerCase().indexOf('read-only') === -1;
151
+ });
152
+ }
153
+ exec(cmd, { maxBuffer: 1024 * 1024 }, function (error, stdout) {
154
+ let lines = filterLines(stdout);
155
+ data = parseDf(lines);
156
+ if (drive) {
157
+ data = data.filter(item => {
158
+ return item.fs.toLowerCase().indexOf(drive.toLowerCase()) >= 0 || item.mount.toLowerCase().indexOf(drive.toLowerCase()) >= 0;
159
+ });
160
+ }
161
+ if ((!error || data.length) && stdout.toString().trim() !== '') {
162
+ if (callback) {
163
+ callback(data);
164
+ }
165
+ resolve(data);
166
+ } else {
167
+ exec('df -kPT', { maxBuffer: 1024 * 1024 }, function (error, stdout) {
168
+ if (!error) {
169
+ let lines = filterLines(stdout);
170
+ data = parseDf(lines);
171
+ }
172
+ if (callback) {
173
+ callback(data);
174
+ }
175
+ resolve(data);
176
+ });
177
+ }
178
+ });
179
+ }
180
+ if (_sunos) {
181
+ if (callback) { callback(data); }
182
+ resolve(data);
183
+ }
184
+ if (_windows) {
185
+ try {
186
+ const cmd = `Get-WmiObject Win32_logicaldisk | select Caption,FileSystem,FreeSpace,Size ${drive ? '| where -property Caption -eq ' + drive : ''} | fl`;
187
+ util.powerShell(cmd).then((stdout, error) => {
188
+ if (!error) {
189
+ let devices = stdout.toString().split(/\n\s*\n/);
190
+ devices.forEach(function (device) {
191
+ let lines = device.split('\r\n');
192
+ const size = util.toInt(util.getValue(lines, 'size', ':'));
193
+ const free = util.toInt(util.getValue(lines, 'freespace', ':'));
194
+ const caption = util.getValue(lines, 'caption', ':');
195
+ const rwValue = util.getValue(lines, 'access', ':');
196
+ const rw = rwValue ? (util.toInt(rwValue) !== 1) : null;
197
+ if (size) {
198
+ data.push({
199
+ fs: caption,
200
+ type: util.getValue(lines, 'filesystem', ':'),
201
+ size,
202
+ used: size - free,
203
+ available: free,
204
+ use: parseFloat(((100.0 * (size - free)) / size).toFixed(2)),
205
+ mount: caption,
206
+ rw
207
+ });
208
+ }
209
+ });
210
+ }
211
+ if (callback) {
212
+ callback(data);
213
+ }
214
+ resolve(data);
215
+ });
216
+ } catch (e) {
217
+ if (callback) { callback(data); }
218
+ resolve(data);
219
+ }
220
+ }
221
+ });
222
+ });
223
+ }
224
+
225
+ exports.fsSize = fsSize;
226
+
227
+ // --------------------------
228
+ // FS - open files count
229
+
230
+ function fsOpenFiles(callback) {
231
+
232
+ return new Promise((resolve) => {
233
+ process.nextTick(() => {
234
+ const result = {
235
+ max: null,
236
+ allocated: null,
237
+ available: null
238
+ };
239
+ if (_freebsd || _openbsd || _netbsd || _darwin) {
240
+ let cmd = 'sysctl -i kern.maxfiles kern.num_files kern.open_files';
241
+ exec(cmd, { maxBuffer: 1024 * 1024 }, function (error, stdout) {
242
+ if (!error) {
243
+ let lines = stdout.toString().split('\n');
244
+ result.max = parseInt(util.getValue(lines, 'kern.maxfiles', ':'), 10);
245
+ result.allocated = parseInt(util.getValue(lines, 'kern.num_files', ':'), 10) || parseInt(util.getValue(lines, 'kern.open_files', ':'), 10);
246
+ result.available = result.max - result.allocated;
247
+ }
248
+ if (callback) {
249
+ callback(result);
250
+ }
251
+ resolve(result);
252
+ });
253
+ }
254
+ if (_linux) {
255
+ fs.readFile('/proc/sys/fs/file-nr', function (error, stdout) {
256
+ if (!error) {
257
+ let lines = stdout.toString().split('\n');
258
+ if (lines[0]) {
259
+ const parts = lines[0].replace(/\s+/g, ' ').split(' ');
260
+ if (parts.length === 3) {
261
+ result.allocated = parseInt(parts[0], 10);
262
+ result.available = parseInt(parts[1], 10);
263
+ result.max = parseInt(parts[2], 10);
264
+ if (!result.available) { result.available = result.max - result.allocated; }
265
+ }
266
+ }
267
+ if (callback) {
268
+ callback(result);
269
+ }
270
+ resolve(result);
271
+ } else {
272
+ fs.readFile('/proc/sys/fs/file-max', function (error, stdout) {
273
+ if (!error) {
274
+ let lines = stdout.toString().split('\n');
275
+ if (lines[0]) {
276
+ result.max = parseInt(lines[0], 10);
277
+ }
278
+ }
279
+ if (callback) {
280
+ callback(result);
281
+ }
282
+ resolve(result);
283
+ });
284
+ }
285
+ });
286
+ }
287
+ if (_sunos) {
288
+ if (callback) { callback(null); }
289
+ resolve(null);
290
+ }
291
+ if (_windows) {
292
+ if (callback) { callback(null); }
293
+ resolve(null);
294
+ }
295
+ });
296
+ });
297
+ }
298
+
299
+ exports.fsOpenFiles = fsOpenFiles;
300
+
301
+ // --------------------------
302
+ // disks
303
+
304
+ function parseBytes(s) {
305
+ return parseInt(s.substr(s.indexOf(' (') + 2, s.indexOf(' Bytes)') - 10));
306
+ }
307
+
308
+ function parseDevices(lines) {
309
+ let devices = [];
310
+ let i = 0;
311
+ lines.forEach(line => {
312
+ if (line.length > 0) {
313
+ if (line[0] === '*') {
314
+ i++;
315
+ } else {
316
+ let parts = line.split(':');
317
+ if (parts.length > 1) {
318
+ if (!devices[i]) {
319
+ devices[i] = {
320
+ name: '',
321
+ identifier: '',
322
+ type: 'disk',
323
+ fsType: '',
324
+ mount: '',
325
+ size: 0,
326
+ physical: 'HDD',
327
+ uuid: '',
328
+ label: '',
329
+ model: '',
330
+ serial: '',
331
+ removable: false,
332
+ protocol: '',
333
+ group: '',
334
+ device: ''
335
+ };
336
+ }
337
+ parts[0] = parts[0].trim().toUpperCase().replace(/ +/g, '');
338
+ parts[1] = parts[1].trim();
339
+ if ('DEVICEIDENTIFIER' === parts[0]) { devices[i].identifier = parts[1]; }
340
+ if ('DEVICENODE' === parts[0]) { devices[i].name = parts[1]; }
341
+ if ('VOLUMENAME' === parts[0]) {
342
+ if (parts[1].indexOf('Not applicable') === -1) { devices[i].label = parts[1]; }
343
+ }
344
+ if ('PROTOCOL' === parts[0]) { devices[i].protocol = parts[1]; }
345
+ if ('DISKSIZE' === parts[0]) { devices[i].size = parseBytes(parts[1]); }
346
+ if ('FILESYSTEMPERSONALITY' === parts[0]) { devices[i].fsType = parts[1]; }
347
+ if ('MOUNTPOINT' === parts[0]) { devices[i].mount = parts[1]; }
348
+ if ('VOLUMEUUID' === parts[0]) { devices[i].uuid = parts[1]; }
349
+ if ('READ-ONLYMEDIA' === parts[0] && parts[1] === 'Yes') { devices[i].physical = 'CD/DVD'; }
350
+ if ('SOLIDSTATE' === parts[0] && parts[1] === 'Yes') { devices[i].physical = 'SSD'; }
351
+ if ('VIRTUAL' === parts[0]) { devices[i].type = 'virtual'; }
352
+ if ('REMOVABLEMEDIA' === parts[0]) { devices[i].removable = (parts[1] === 'Removable'); }
353
+ if ('PARTITIONTYPE' === parts[0]) { devices[i].type = 'part'; }
354
+ if ('DEVICE/MEDIANAME' === parts[0]) { devices[i].model = parts[1]; }
355
+ }
356
+ }
357
+ }
358
+ });
359
+ return devices;
360
+ }
361
+
362
+ function parseBlk(lines) {
363
+ let data = [];
364
+
365
+ lines.filter(line => line !== '').forEach((line) => {
366
+ try {
367
+ line = decodeURIComponent(line.replace(/\\x/g, '%'));
368
+ line = line.replace(/\\/g, '\\\\');
369
+ let disk = JSON.parse(line);
370
+ data.push({
371
+ 'name': disk.name,
372
+ 'type': disk.type,
373
+ 'fsType': disk.fsType,
374
+ 'mount': disk.mountpoint,
375
+ 'size': parseInt(disk.size),
376
+ 'physical': (disk.type === 'disk' ? (disk.rota === '0' ? 'SSD' : 'HDD') : (disk.type === 'rom' ? 'CD/DVD' : '')),
377
+ 'uuid': disk.uuid,
378
+ 'label': disk.label,
379
+ 'model': (disk.model || '').trim(),
380
+ 'serial': disk.serial,
381
+ 'removable': disk.rm === '1',
382
+ 'protocol': disk.tran,
383
+ 'group': disk.group || '',
384
+ });
385
+ } catch (e) {
386
+ util.noop();
387
+ }
388
+ });
389
+ data = util.unique(data);
390
+ data = util.sortByKey(data, ['type', 'name']);
391
+ return data;
392
+ }
393
+
394
+ function decodeMdabmData(lines) {
395
+ const raid = util.getValue(lines, 'md_level', '=');
396
+ const label = util.getValue(lines, 'md_name', '='); // <- get label info
397
+ const uuid = util.getValue(lines, 'md_uuid', '='); // <- get uuid info
398
+ const members = [];
399
+ lines.forEach(line => {
400
+ if (line.toLowerCase().startsWith('md_device_dev') && line.toLowerCase().indexOf('/dev/') > 0) {
401
+ members.push(line.split('/dev/')[1]);
402
+ }
403
+ });
404
+ return {
405
+ raid,
406
+ label,
407
+ uuid,
408
+ members
409
+ };
410
+ }
411
+
412
+ function raidMatchLinux(data) {
413
+ // for all block devices of type "raid%"
414
+ let result = data;
415
+ try {
416
+ data.forEach(element => {
417
+ if (element.type.startsWith('raid')) {
418
+ const lines = execSync(`mdadm --export --detail /dev/${element.name}`).toString().split('\n');
419
+ const mdData = decodeMdabmData(lines);
420
+
421
+ element.label = mdData.label; // <- assign label info
422
+ element.uuid = mdData.uuid; // <- assign uuid info
423
+
424
+ if (mdData.members && mdData.members.length && mdData.raid === element.type) {
425
+ result = result.map(blockdevice => {
426
+ if (blockdevice.fsType === 'linux_raid_member' && mdData.members.indexOf(blockdevice.name) >= 0) {
427
+ blockdevice.group = element.name;
428
+ }
429
+ return blockdevice;
430
+ });
431
+ }
432
+ }
433
+ });
434
+ } catch (e) {
435
+ util.noop();
436
+ }
437
+ return result;
438
+ }
439
+
440
+ function getDevicesLinux(data) {
441
+ const result = [];
442
+ data.forEach(element => {
443
+ if (element.type.startsWith('disk')) {
444
+ result.push(element.name);
445
+ }
446
+ });
447
+ return result;
448
+ }
449
+
450
+ function matchDevicesLinux(data) {
451
+ let result = data;
452
+ try {
453
+ const devices = getDevicesLinux(data);
454
+ result = result.map(blockdevice => {
455
+ if (blockdevice.type.startsWith('part') || blockdevice.type.startsWith('disk')) {
456
+ devices.forEach(element => {
457
+ if (blockdevice.name.startsWith(element)) {
458
+ blockdevice.device = '/dev/' + element;
459
+ }
460
+ });
461
+ }
462
+ return blockdevice;
463
+ });
464
+ } catch (e) {
465
+ util.noop();
466
+ }
467
+ return result;
468
+ }
469
+
470
+ function getDevicesMac(data) {
471
+ const result = [];
472
+ data.forEach(element => {
473
+ if (element.type.startsWith('disk')) {
474
+ result.push({ name: element.name, model: element.model, device: element.name });
475
+ }
476
+ if (element.type.startsWith('virtual')) {
477
+ let device = '';
478
+ result.forEach(e => {
479
+ if (e.model === element.model) {
480
+ device = e.device;
481
+ }
482
+ });
483
+ if (device) {
484
+ result.push({ name: element.name, model: element.model, device });
485
+ }
486
+ }
487
+ });
488
+ return result;
489
+ }
490
+
491
+ function matchDevicesMac(data) {
492
+ let result = data;
493
+ try {
494
+ const devices = getDevicesMac(data);
495
+ result = result.map(blockdevice => {
496
+ if (blockdevice.type.startsWith('part') || blockdevice.type.startsWith('disk') || blockdevice.type.startsWith('virtual')) {
497
+ devices.forEach(element => {
498
+ if (blockdevice.name.startsWith(element.name)) {
499
+ blockdevice.device = element.device;
500
+ }
501
+ });
502
+ }
503
+ return blockdevice;
504
+ });
505
+ } catch (e) {
506
+ util.noop();
507
+ }
508
+ return result;
509
+ }
510
+
511
+ function getDevicesWin(diskDrives) {
512
+ const result = [];
513
+ diskDrives.forEach(element => {
514
+ const lines = element.split('\r\n');
515
+ const device = util.getValue(lines, 'DeviceID', ':');
516
+ let partitions = element.split('@{DeviceID=');
517
+ if (partitions.length > 1) {
518
+ partitions = partitions.slice(1);
519
+ partitions.forEach(partition => {
520
+ result.push({ name: partition.split(';')[0].toUpperCase(), device });
521
+ });
522
+ }
523
+ });
524
+ return result;
525
+ }
526
+
527
+ function matchDevicesWin(data, diskDrives) {
528
+ const devices = getDevicesWin(diskDrives);
529
+ data.map(element => {
530
+ const filteresDevices = devices.filter((e) => { return e.name === element.name.toUpperCase(); });
531
+ if (filteresDevices.length > 0) {
532
+ element.device = filteresDevices[0].device;
533
+ }
534
+ return element;
535
+ });
536
+ return data;
537
+ }
538
+
539
+ function blkStdoutToObject(stdout) {
540
+ return stdout.toString()
541
+ .replace(/NAME=/g, '{"name":')
542
+ .replace(/FSTYPE=/g, ',"fsType":')
543
+ .replace(/TYPE=/g, ',"type":')
544
+ .replace(/SIZE=/g, ',"size":')
545
+ .replace(/MOUNTPOINT=/g, ',"mountpoint":')
546
+ .replace(/UUID=/g, ',"uuid":')
547
+ .replace(/ROTA=/g, ',"rota":')
548
+ .replace(/RO=/g, ',"ro":')
549
+ .replace(/RM=/g, ',"rm":')
550
+ .replace(/TRAN=/g, ',"tran":')
551
+ .replace(/SERIAL=/g, ',"serial":')
552
+ .replace(/LABEL=/g, ',"label":')
553
+ .replace(/MODEL=/g, ',"model":')
554
+ .replace(/OWNER=/g, ',"owner":')
555
+ .replace(/GROUP=/g, ',"group":')
556
+ .replace(/\n/g, '}\n');
557
+ }
558
+
559
+ function blockDevices(callback) {
560
+
561
+ return new Promise((resolve) => {
562
+ process.nextTick(() => {
563
+ let data = [];
564
+ if (_linux) {
565
+ // see https://wiki.ubuntuusers.de/lsblk/
566
+ // exec("lsblk -bo NAME,TYPE,SIZE,FSTYPE,MOUNTPOINT,UUID,ROTA,RO,TRAN,SERIAL,LABEL,MODEL,OWNER,GROUP,MODE,ALIGNMENT,MIN-IO,OPT-IO,PHY-SEC,LOG-SEC,SCHED,RQ-SIZE,RA,WSAME", function (error, stdout) {
567
+ exec('lsblk -bPo NAME,TYPE,SIZE,FSTYPE,MOUNTPOINT,UUID,ROTA,RO,RM,TRAN,SERIAL,LABEL,MODEL,OWNER 2>/dev/null', { maxBuffer: 1024 * 1024 }, function (error, stdout) {
568
+ if (!error) {
569
+ let lines = blkStdoutToObject(stdout).split('\n');
570
+ data = parseBlk(lines);
571
+ data = raidMatchLinux(data);
572
+ data = matchDevicesLinux(data);
573
+ if (callback) {
574
+ callback(data);
575
+ }
576
+ resolve(data);
577
+ } else {
578
+ exec('lsblk -bPo NAME,TYPE,SIZE,FSTYPE,MOUNTPOINT,UUID,ROTA,RO,RM,LABEL,MODEL,OWNER 2>/dev/null', { maxBuffer: 1024 * 1024 }, function (error, stdout) {
579
+ if (!error) {
580
+ let lines = blkStdoutToObject(stdout).split('\n');
581
+ data = parseBlk(lines);
582
+ data = raidMatchLinux(data);
583
+ }
584
+ if (callback) {
585
+ callback(data);
586
+ }
587
+ resolve(data);
588
+ });
589
+ }
590
+ });
591
+ }
592
+ if (_darwin) {
593
+ exec('diskutil info -all', { maxBuffer: 1024 * 1024 }, function (error, stdout) {
594
+ if (!error) {
595
+ let lines = stdout.toString().split('\n');
596
+ // parse lines into temp array of devices
597
+ data = parseDevices(lines);
598
+ data = matchDevicesMac(data);
599
+ }
600
+ if (callback) {
601
+ callback(data);
602
+ }
603
+ resolve(data);
604
+ });
605
+ }
606
+ if (_sunos) {
607
+ if (callback) { callback(data); }
608
+ resolve(data);
609
+ }
610
+ if (_windows) {
611
+ let drivetypes = ['Unknown', 'NoRoot', 'Removable', 'Local', 'Network', 'CD/DVD', 'RAM'];
612
+ try {
613
+ // util.wmic('logicaldisk get Caption,Description,DeviceID,DriveType,FileSystem,FreeSpace,Name,Size,VolumeName,VolumeSerialNumber /value').then((stdout, error) => {
614
+ // util.powerShell('Get-CimInstance Win32_logicaldisk | select Caption,DriveType,Name,FileSystem,Size,VolumeSerialNumber,VolumeName | fl').then((stdout, error) => {
615
+ const workload = [];
616
+ workload.push(util.powerShell('Get-CimInstance -ClassName Win32_LogicalDisk | select Caption,DriveType,Name,FileSystem,Size,VolumeSerialNumber,VolumeName | fl'));
617
+ workload.push(util.powerShell('Get-WmiObject -Class Win32_diskdrive | Select-Object -Property PNPDeviceId,DeviceID, Model, Size, @{L=\'Partitions\'; E={$_.GetRelated(\'Win32_DiskPartition\').GetRelated(\'Win32_LogicalDisk\') | Select-Object -Property DeviceID, VolumeName, Size, FreeSpace}} | fl'));
618
+ util.promiseAll(
619
+ workload
620
+ ).then((res) => {
621
+ let logicalDisks = res.results[0].toString().split(/\n\s*\n/);
622
+ let diskDrives = res.results[1].toString().split(/\n\s*\n/);
623
+ logicalDisks.forEach(function (device) {
624
+ let lines = device.split('\r\n');
625
+ let drivetype = util.getValue(lines, 'drivetype', ':');
626
+ if (drivetype) {
627
+ data.push({
628
+ name: util.getValue(lines, 'name', ':'),
629
+ identifier: util.getValue(lines, 'caption', ':'),
630
+ type: 'disk',
631
+ fsType: util.getValue(lines, 'filesystem', ':').toLowerCase(),
632
+ mount: util.getValue(lines, 'caption', ':'),
633
+ size: util.getValue(lines, 'size', ':'),
634
+ physical: (drivetype >= 0 && drivetype <= 6) ? drivetypes[drivetype] : drivetypes[0],
635
+ uuid: util.getValue(lines, 'volumeserialnumber', ':'),
636
+ label: util.getValue(lines, 'volumename', ':'),
637
+ model: '',
638
+ serial: util.getValue(lines, 'volumeserialnumber', ':'),
639
+ removable: drivetype === '2',
640
+ protocol: '',
641
+ group: '',
642
+ device: ''
643
+ });
644
+ }
645
+ });
646
+ // match devices
647
+ data = matchDevicesWin(data, diskDrives);
648
+ if (callback) {
649
+ callback(data);
650
+ }
651
+ resolve(data);
652
+ });
653
+ } catch (e) {
654
+ if (callback) { callback(data); }
655
+ resolve(data);
656
+ }
657
+ }
658
+ if (_freebsd || _openbsd || _netbsd) {
659
+ // will follow
660
+ if (callback) { callback(null); }
661
+ resolve(null);
662
+ }
663
+
664
+ });
665
+ });
666
+ }
667
+
668
+ exports.blockDevices = blockDevices;
669
+
670
+ // --------------------------
671
+ // FS - speed
672
+
673
+ function calcFsSpeed(rx, wx) {
674
+ let result = {
675
+ rx: 0,
676
+ wx: 0,
677
+ tx: 0,
678
+ rx_sec: null,
679
+ wx_sec: null,
680
+ tx_sec: null,
681
+ ms: 0
682
+ };
683
+
684
+ if (_fs_speed && _fs_speed.ms) {
685
+ result.rx = rx;
686
+ result.wx = wx;
687
+ result.tx = result.rx + result.wx;
688
+ result.ms = Date.now() - _fs_speed.ms;
689
+ result.rx_sec = (result.rx - _fs_speed.bytes_read) / (result.ms / 1000);
690
+ result.wx_sec = (result.wx - _fs_speed.bytes_write) / (result.ms / 1000);
691
+ result.tx_sec = result.rx_sec + result.wx_sec;
692
+ _fs_speed.rx_sec = result.rx_sec;
693
+ _fs_speed.wx_sec = result.wx_sec;
694
+ _fs_speed.tx_sec = result.tx_sec;
695
+ _fs_speed.bytes_read = result.rx;
696
+ _fs_speed.bytes_write = result.wx;
697
+ _fs_speed.bytes_overall = result.rx + result.wx;
698
+ _fs_speed.ms = Date.now();
699
+ _fs_speed.last_ms = result.ms;
700
+ } else {
701
+ result.rx = rx;
702
+ result.wx = wx;
703
+ result.tx = result.rx + result.wx;
704
+ _fs_speed.rx_sec = null;
705
+ _fs_speed.wx_sec = null;
706
+ _fs_speed.tx_sec = null;
707
+ _fs_speed.bytes_read = result.rx;
708
+ _fs_speed.bytes_write = result.wx;
709
+ _fs_speed.bytes_overall = result.rx + result.wx;
710
+ _fs_speed.ms = Date.now();
711
+ _fs_speed.last_ms = 0;
712
+ }
713
+ return result;
714
+ }
715
+
716
+ function fsStats(callback) {
717
+
718
+ return new Promise((resolve) => {
719
+ process.nextTick(() => {
720
+ if (_windows || _freebsd || _openbsd || _netbsd || _sunos) {
721
+ return resolve(null);
722
+ }
723
+
724
+ let result = {
725
+ rx: 0,
726
+ wx: 0,
727
+ tx: 0,
728
+ rx_sec: null,
729
+ wx_sec: null,
730
+ tx_sec: null,
731
+ ms: 0
732
+ };
733
+
734
+ let rx = 0;
735
+ let wx = 0;
736
+ if ((_fs_speed && !_fs_speed.ms) || (_fs_speed && _fs_speed.ms && Date.now() - _fs_speed.ms >= 500)) {
737
+ if (_linux) {
738
+ // exec("df -k | grep /dev/", function(error, stdout) {
739
+ exec('lsblk -r 2>/dev/null | grep /', { maxBuffer: 1024 * 1024 }, function (error, stdout) {
740
+ if (!error) {
741
+ let lines = stdout.toString().split('\n');
742
+ let fs_filter = [];
743
+ lines.forEach(function (line) {
744
+ if (line !== '') {
745
+ line = line.trim().split(' ');
746
+ if (fs_filter.indexOf(line[0]) === -1) { fs_filter.push(line[0]); }
747
+ }
748
+ });
749
+
750
+ let output = fs_filter.join('|');
751
+ exec('cat /proc/diskstats | egrep "' + output + '"', { maxBuffer: 1024 * 1024 }, function (error, stdout) {
752
+ if (!error) {
753
+ let lines = stdout.toString().split('\n');
754
+ lines.forEach(function (line) {
755
+ line = line.trim();
756
+ if (line !== '') {
757
+ line = line.replace(/ +/g, ' ').split(' ');
758
+
759
+ rx += parseInt(line[5]) * 512;
760
+ wx += parseInt(line[9]) * 512;
761
+ }
762
+ });
763
+ result = calcFsSpeed(rx, wx);
764
+ }
765
+ if (callback) {
766
+ callback(result);
767
+ }
768
+ resolve(result);
769
+ });
770
+ } else {
771
+ if (callback) {
772
+ callback(result);
773
+ }
774
+ resolve(result);
775
+ }
776
+ });
777
+ }
778
+ if (_darwin) {
779
+ exec('ioreg -c IOBlockStorageDriver -k Statistics -r -w0 | sed -n "/IOBlockStorageDriver/,/Statistics/p" | grep "Statistics" | tr -cd "01234567890,\n"', { maxBuffer: 1024 * 1024 }, function (error, stdout) {
780
+ if (!error) {
781
+ let lines = stdout.toString().split('\n');
782
+ lines.forEach(function (line) {
783
+ line = line.trim();
784
+ if (line !== '') {
785
+ line = line.split(',');
786
+
787
+ rx += parseInt(line[2]);
788
+ wx += parseInt(line[9]);
789
+ }
790
+ });
791
+ result = calcFsSpeed(rx, wx);
792
+ }
793
+ if (callback) {
794
+ callback(result);
795
+ }
796
+ resolve(result);
797
+ });
798
+ }
799
+ } else {
800
+ result.ms = _fs_speed.last_ms;
801
+ result.rx = _fs_speed.bytes_read;
802
+ result.wx = _fs_speed.bytes_write;
803
+ result.tx = _fs_speed.bytes_read + _fs_speed.bytes_write;
804
+ result.rx_sec = _fs_speed.rx_sec;
805
+ result.wx_sec = _fs_speed.wx_sec;
806
+ result.tx_sec = _fs_speed.tx_sec;
807
+ if (callback) {
808
+ callback(result);
809
+ }
810
+ resolve(result);
811
+ }
812
+ });
813
+ });
814
+ }
815
+
816
+ exports.fsStats = fsStats;
817
+
818
+ function calcDiskIO(rIO, wIO, rWaitTime, wWaitTime, tWaitTime) {
819
+ let result = {
820
+ rIO: 0,
821
+ wIO: 0,
822
+ tIO: 0,
823
+ rIO_sec: null,
824
+ wIO_sec: null,
825
+ tIO_sec: null,
826
+ rWaitTime: 0,
827
+ wWaitTime: 0,
828
+ tWaitTime: 0,
829
+ rWaitPercent: null,
830
+ wWaitPercent: null,
831
+ tWaitPercent: null,
832
+ ms: 0
833
+ };
834
+ if (_disk_io && _disk_io.ms) {
835
+ result.rIO = rIO;
836
+ result.wIO = wIO;
837
+ result.tIO = rIO + wIO;
838
+ result.ms = Date.now() - _disk_io.ms;
839
+ result.rIO_sec = (result.rIO - _disk_io.rIO) / (result.ms / 1000);
840
+ result.wIO_sec = (result.wIO - _disk_io.wIO) / (result.ms / 1000);
841
+ result.tIO_sec = result.rIO_sec + result.wIO_sec;
842
+ result.rWaitTime = rWaitTime;
843
+ result.wWaitTime = wWaitTime;
844
+ result.tWaitTime = tWaitTime;
845
+ result.rWaitPercent = (result.rWaitTime - _disk_io.rWaitTime) * 100 / (result.ms);
846
+ result.wWaitPercent = (result.wWaitTime - _disk_io.wWaitTime) * 100 / (result.ms);
847
+ result.tWaitPercent = (result.tWaitTime - _disk_io.tWaitTime) * 100 / (result.ms);
848
+ _disk_io.rIO = rIO;
849
+ _disk_io.wIO = wIO;
850
+ _disk_io.rIO_sec = result.rIO_sec;
851
+ _disk_io.wIO_sec = result.wIO_sec;
852
+ _disk_io.tIO_sec = result.tIO_sec;
853
+ _disk_io.rWaitTime = rWaitTime;
854
+ _disk_io.wWaitTime = wWaitTime;
855
+ _disk_io.tWaitTime = tWaitTime;
856
+ _disk_io.rWaitPercent = result.rWaitPercent;
857
+ _disk_io.wWaitPercent = result.wWaitPercent;
858
+ _disk_io.tWaitPercent = result.tWaitPercent;
859
+ _disk_io.last_ms = result.ms;
860
+ _disk_io.ms = Date.now();
861
+ } else {
862
+ result.rIO = rIO;
863
+ result.wIO = wIO;
864
+ result.tIO = rIO + wIO;
865
+ result.rWaitTime = rWaitTime;
866
+ result.wWaitTime = wWaitTime;
867
+ result.tWaitTime = tWaitTime;
868
+ _disk_io.rIO = rIO;
869
+ _disk_io.wIO = wIO;
870
+ _disk_io.rIO_sec = null;
871
+ _disk_io.wIO_sec = null;
872
+ _disk_io.tIO_sec = null;
873
+ _disk_io.rWaitTime = rWaitTime;
874
+ _disk_io.wWaitTime = wWaitTime;
875
+ _disk_io.tWaitTime = tWaitTime;
876
+ _disk_io.rWaitPercent = null;
877
+ _disk_io.wWaitPercent = null;
878
+ _disk_io.tWaitPercent = null;
879
+ _disk_io.last_ms = 0;
880
+ _disk_io.ms = Date.now();
881
+ }
882
+ return result;
883
+ }
884
+
885
+ function disksIO(callback) {
886
+
887
+ return new Promise((resolve) => {
888
+ process.nextTick(() => {
889
+ if (_windows) {
890
+ return resolve(null);
891
+ }
892
+ if (_sunos) {
893
+ return resolve(null);
894
+ }
895
+
896
+ let result = {
897
+ rIO: 0,
898
+ wIO: 0,
899
+ tIO: 0,
900
+ rIO_sec: null,
901
+ wIO_sec: null,
902
+ tIO_sec: null,
903
+ rWaitTime: 0,
904
+ wWaitTime: 0,
905
+ tWaitTime: 0,
906
+ rWaitPercent: null,
907
+ wWaitPercent: null,
908
+ tWaitPercent: null,
909
+ ms: 0
910
+ };
911
+ let rIO = 0;
912
+ let wIO = 0;
913
+ let rWaitTime = 0;
914
+ let wWaitTime = 0;
915
+ let tWaitTime = 0;
916
+
917
+ if ((_disk_io && !_disk_io.ms) || (_disk_io && _disk_io.ms && Date.now() - _disk_io.ms >= 500)) {
918
+ if (_linux || _freebsd || _openbsd || _netbsd) {
919
+ // prints Block layer statistics for all mounted volumes
920
+ // var cmd = "for mount in `lsblk | grep / | sed -r 's/│ └─//' | cut -d ' ' -f 1`; do cat /sys/block/$mount/stat | sed -r 's/ +/;/g' | sed -r 's/^;//'; done";
921
+ // var cmd = "for mount in `lsblk | grep / | sed 's/[│└─├]//g' | awk '{$1=$1};1' | cut -d ' ' -f 1 | sort -u`; do cat /sys/block/$mount/stat | sed -r 's/ +/;/g' | sed -r 's/^;//'; done";
922
+ let cmd = 'for mount in `lsblk 2>/dev/null | grep " disk " | sed "s/[│└─├]//g" | awk \'{$1=$1};1\' | cut -d " " -f 1 | sort -u`; do cat /sys/block/$mount/stat | sed -r "s/ +/;/g" | sed -r "s/^;//"; done';
923
+
924
+ exec(cmd, { maxBuffer: 1024 * 1024 }, function (error, stdout) {
925
+ if (!error) {
926
+ let lines = stdout.split('\n');
927
+ lines.forEach(function (line) {
928
+ // ignore empty lines
929
+ if (!line) { return; }
930
+
931
+ // sum r/wIO of all disks to compute all disks IO
932
+ let stats = line.split(';');
933
+ rIO += parseInt(stats[0]);
934
+ wIO += parseInt(stats[4]);
935
+ rWaitTime += parseInt(stats[3]);
936
+ wWaitTime += parseInt(stats[7]);
937
+ tWaitTime += parseInt(stats[10]);
938
+ });
939
+ result = calcDiskIO(rIO, wIO, rWaitTime, wWaitTime, tWaitTime);
940
+
941
+ if (callback) {
942
+ callback(result);
943
+ }
944
+ resolve(result);
945
+ } else {
946
+ if (callback) {
947
+ callback(result);
948
+ }
949
+ resolve(result);
950
+ }
951
+ });
952
+ }
953
+ if (_darwin) {
954
+ exec('ioreg -c IOBlockStorageDriver -k Statistics -r -w0 | sed -n "/IOBlockStorageDriver/,/Statistics/p" | grep "Statistics" | tr -cd "01234567890,\n"', { maxBuffer: 1024 * 1024 }, function (error, stdout) {
955
+ if (!error) {
956
+ let lines = stdout.toString().split('\n');
957
+ lines.forEach(function (line) {
958
+ line = line.trim();
959
+ if (line !== '') {
960
+ line = line.split(',');
961
+
962
+ rIO += parseInt(line[10]);
963
+ wIO += parseInt(line[0]);
964
+ }
965
+ });
966
+ result = calcDiskIO(rIO, wIO, rWaitTime, wWaitTime, tWaitTime);
967
+ }
968
+ if (callback) {
969
+ callback(result);
970
+ }
971
+ resolve(result);
972
+ });
973
+ }
974
+ } else {
975
+ result.rIO = _disk_io.rIO;
976
+ result.wIO = _disk_io.wIO;
977
+ result.tIO = _disk_io.rIO + _disk_io.wIO;
978
+ result.ms = _disk_io.last_ms;
979
+ result.rIO_sec = _disk_io.rIO_sec;
980
+ result.wIO_sec = _disk_io.wIO_sec;
981
+ result.tIO_sec = _disk_io.tIO_sec;
982
+ result.rWaitTime = _disk_io.rWaitTime;
983
+ result.wWaitTime = _disk_io.wWaitTime;
984
+ result.tWaitTime = _disk_io.tWaitTime;
985
+ result.rWaitPercent = _disk_io.rWaitPercent;
986
+ result.wWaitPercent = _disk_io.wWaitPercent;
987
+ result.tWaitPercent = _disk_io.tWaitPercent;
988
+ if (callback) {
989
+ callback(result);
990
+ }
991
+ resolve(result);
992
+ }
993
+ });
994
+ });
995
+ }
996
+
997
+ exports.disksIO = disksIO;
998
+
999
+ function diskLayout(callback) {
1000
+
1001
+ function getVendorFromModel(model) {
1002
+ const diskManufacturers = [
1003
+ { pattern: 'WESTERN.*', manufacturer: 'Western Digital' },
1004
+ { pattern: '^WDC.*', manufacturer: 'Western Digital' },
1005
+ { pattern: 'WD.*', manufacturer: 'Western Digital' },
1006
+ { pattern: 'TOSHIBA.*', manufacturer: 'Toshiba' },
1007
+ { pattern: 'HITACHI.*', manufacturer: 'Hitachi' },
1008
+ { pattern: '^IC.*', manufacturer: 'Hitachi' },
1009
+ { pattern: '^HTS.*', manufacturer: 'Hitachi' },
1010
+ { pattern: 'SANDISK.*', manufacturer: 'SanDisk' },
1011
+ { pattern: 'KINGSTON.*', manufacturer: 'Kingston Technology' },
1012
+ { pattern: '^SONY.*', manufacturer: 'Sony' },
1013
+ { pattern: 'TRANSCEND.*', manufacturer: 'Transcend' },
1014
+ { pattern: 'SAMSUNG.*', manufacturer: 'Samsung' },
1015
+ { pattern: '^ST(?!I\\ ).*', manufacturer: 'Seagate' },
1016
+ { pattern: '^STI\\ .*', manufacturer: 'SimpleTech' },
1017
+ { pattern: '^D...-.*', manufacturer: 'IBM' },
1018
+ { pattern: '^IBM.*', manufacturer: 'IBM' },
1019
+ { pattern: '^FUJITSU.*', manufacturer: 'Fujitsu' },
1020
+ { pattern: '^MP.*', manufacturer: 'Fujitsu' },
1021
+ { pattern: '^MK.*', manufacturer: 'Toshiba' },
1022
+ { pattern: 'MAXTO.*', manufacturer: 'Maxtor' },
1023
+ { pattern: 'PIONEER.*', manufacturer: 'Pioneer' },
1024
+ { pattern: 'PHILIPS.*', manufacturer: 'Philips' },
1025
+ { pattern: 'QUANTUM.*', manufacturer: 'Quantum Technology' },
1026
+ { pattern: 'FIREBALL.*', manufacturer: 'Quantum Technology' },
1027
+ { pattern: '^VBOX.*', manufacturer: 'VirtualBox' },
1028
+ { pattern: 'CORSAIR.*', manufacturer: 'Corsair Components' },
1029
+ { pattern: 'CRUCIAL.*', manufacturer: 'Crucial' },
1030
+ { pattern: 'ECM.*', manufacturer: 'ECM' },
1031
+ { pattern: 'INTEL.*', manufacturer: 'INTEL' },
1032
+ { pattern: 'EVO.*', manufacturer: 'Samsung' },
1033
+ { pattern: 'APPLE.*', manufacturer: 'Apple' },
1034
+ ];
1035
+
1036
+ let result = '';
1037
+ if (model) {
1038
+ model = model.toUpperCase();
1039
+ diskManufacturers.forEach((manufacturer) => {
1040
+ const re = RegExp(manufacturer.pattern);
1041
+ if (re.test(model)) { result = manufacturer.manufacturer; }
1042
+ });
1043
+ }
1044
+ return result;
1045
+ }
1046
+
1047
+ return new Promise((resolve) => {
1048
+ process.nextTick(() => {
1049
+
1050
+ const commitResult = res => {
1051
+ for (let i = 0; i < res.length; i++) {
1052
+ delete res[i].BSDName;
1053
+ }
1054
+ if (callback) {
1055
+ callback(res);
1056
+ }
1057
+ resolve(res);
1058
+ };
1059
+
1060
+ let result = [];
1061
+ let cmd = '';
1062
+
1063
+ if (_linux) {
1064
+ let cmdFullSmart = '';
1065
+
1066
+ exec('export LC_ALL=C; lsblk -ablJO 2>/dev/null; unset LC_ALL', { maxBuffer: 1024 * 1024 }, function (error, stdout) {
1067
+ if (!error) {
1068
+ try {
1069
+ const out = stdout.toString().trim();
1070
+ let devices = [];
1071
+ try {
1072
+ const outJSON = JSON.parse(out);
1073
+ if (outJSON && {}.hasOwnProperty.call(outJSON, 'blockdevices')) {
1074
+ devices = outJSON.blockdevices.filter(item => { return (item.type === 'disk') && item.size > 0 && (item.model !== null || (item.mountpoint === null && item.label === null && item.fstype === null && item.parttype === null && item.path && item.path.indexOf('/ram') !== 0 && item.path.indexOf('/loop') !== 0 && item['disc-max'] && item['disc-max'] !== 0)); });
1075
+ }
1076
+ } catch (e) {
1077
+ // fallback to older version of lsblk
1078
+ const out2 = execSync('export LC_ALL=C; lsblk -bPo NAME,TYPE,SIZE,FSTYPE,MOUNTPOINT,UUID,ROTA,RO,RM,LABEL,MODEL,OWNER,GROUP 2>/dev/null; unset LC_ALL').toString();
1079
+ let lines = blkStdoutToObject(out2).split('\n');
1080
+ const data = parseBlk(lines);
1081
+ devices = data.filter(item => { return (item.type === 'disk') && item.size > 0 && ((item.model !== null && item.model !== '') || (item.mount === '' && item.label === '' && item.fsType === '')); });
1082
+ }
1083
+ devices.forEach((device) => {
1084
+ let mediumType = '';
1085
+ const BSDName = '/dev/' + device.name;
1086
+ const logical = device.name;
1087
+ try {
1088
+ mediumType = execSync('cat /sys/block/' + logical + '/queue/rotational 2>/dev/null').toString().split('\n')[0];
1089
+ } catch (e) {
1090
+ util.noop();
1091
+ }
1092
+ let interfaceType = device.tran ? device.tran.toUpperCase().trim() : '';
1093
+ if (interfaceType === 'NVME') {
1094
+ mediumType = '2';
1095
+ interfaceType = 'PCIe';
1096
+ }
1097
+ result.push({
1098
+ device: BSDName,
1099
+ type: (mediumType === '0' ? 'SSD' : (mediumType === '1' ? 'HD' : (mediumType === '2' ? 'NVMe' : (device.model && device.model.indexOf('SSD') > -1 ? 'SSD' : (device.model && device.model.indexOf('NVM') > -1 ? 'NVMe' : 'HD'))))),
1100
+ name: device.model || '',
1101
+ vendor: getVendorFromModel(device.model) || (device.vendor ? device.vendor.trim() : ''),
1102
+ size: device.size || 0,
1103
+ bytesPerSector: null,
1104
+ totalCylinders: null,
1105
+ totalHeads: null,
1106
+ totalSectors: null,
1107
+ totalTracks: null,
1108
+ tracksPerCylinder: null,
1109
+ sectorsPerTrack: null,
1110
+ firmwareRevision: device.rev ? device.rev.trim() : '',
1111
+ serialNum: device.serial ? device.serial.trim() : '',
1112
+ interfaceType: interfaceType,
1113
+ smartStatus: 'unknown',
1114
+ temperature: null,
1115
+ BSDName: BSDName
1116
+ });
1117
+ cmd += `printf "\n${BSDName}|"; smartctl -H ${BSDName} | grep overall;`;
1118
+ cmdFullSmart += `${cmdFullSmart ? 'printf ",";' : ''}smartctl -a -j ${BSDName};`;
1119
+ });
1120
+ } catch (e) {
1121
+ util.noop();
1122
+ }
1123
+ }
1124
+ // check S.M.A.R.T. status
1125
+ if (cmdFullSmart) {
1126
+ exec(cmdFullSmart, { maxBuffer: 1024 * 1024 }, function (error, stdout) {
1127
+ try {
1128
+ const data = JSON.parse(`[${stdout}]`);
1129
+ data.forEach(disk => {
1130
+ const diskBSDName = disk.smartctl.argv[disk.smartctl.argv.length - 1];
1131
+
1132
+ for (let i = 0; i < result.length; i++) {
1133
+ if (result[i].BSDName === diskBSDName) {
1134
+ result[i].smartStatus = (disk.smart_status.passed ? 'Ok' : (disk.smart_status.passed === false ? 'Predicted Failure' : 'unknown'));
1135
+ if (disk.temperature && disk.temperature.current) {
1136
+ result[i].temperature = disk.temperature.current;
1137
+ }
1138
+ result[i].smartData = disk;
1139
+ }
1140
+ }
1141
+ });
1142
+ commitResult(result);
1143
+ } catch (e) {
1144
+ if (cmd) {
1145
+ cmd = cmd + 'printf "\n"';
1146
+ exec(cmd, { maxBuffer: 1024 * 1024 }, function (error, stdout) {
1147
+ let lines = stdout.toString().split('\n');
1148
+ lines.forEach(line => {
1149
+ if (line) {
1150
+ let parts = line.split('|');
1151
+ if (parts.length === 2) {
1152
+ let BSDName = parts[0];
1153
+ parts[1] = parts[1].trim();
1154
+ let parts2 = parts[1].split(':');
1155
+ if (parts2.length === 2) {
1156
+ parts2[1] = parts2[1].trim();
1157
+ let status = parts2[1].toLowerCase();
1158
+ for (let i = 0; i < result.length; i++) {
1159
+ if (result[i].BSDName === BSDName) {
1160
+ result[i].smartStatus = (status === 'passed' ? 'Ok' : (status === 'failed!' ? 'Predicted Failure' : 'unknown'));
1161
+ }
1162
+ }
1163
+ }
1164
+ }
1165
+ }
1166
+ });
1167
+ commitResult(result);
1168
+ });
1169
+ } else {
1170
+ commitResult(result);
1171
+ }
1172
+ }
1173
+ });
1174
+ } else {
1175
+ commitResult(result);
1176
+ }
1177
+ });
1178
+ }
1179
+ if (_freebsd || _openbsd || _netbsd) {
1180
+ if (callback) { callback(result); }
1181
+ resolve(result);
1182
+ }
1183
+ if (_sunos) {
1184
+ if (callback) { callback(result); }
1185
+ resolve(result);
1186
+ }
1187
+ if (_darwin) {
1188
+ exec('system_profiler SPSerialATADataType SPNVMeDataType SPUSBDataType', { maxBuffer: 1024 * 1024 }, function (error, stdout) {
1189
+ if (!error) {
1190
+ // split by type:
1191
+ let lines = stdout.toString().split('\n');
1192
+ let linesSATA = [];
1193
+ let linesNVMe = [];
1194
+ let linesUSB = [];
1195
+ let dataType = 'SATA';
1196
+ lines.forEach(line => {
1197
+ if (line === 'NVMExpress:') { dataType = 'NVMe'; }
1198
+ else if (line === 'USB:') { dataType = 'USB'; }
1199
+ else if (line === 'SATA/SATA Express:') { dataType = 'SATA'; }
1200
+ else if (dataType === 'SATA') { linesSATA.push(line); }
1201
+ else if (dataType === 'NVMe') { linesNVMe.push(line); }
1202
+ else if (dataType === 'USB') { linesUSB.push(line); }
1203
+ });
1204
+ try {
1205
+ // Serial ATA Drives
1206
+ let devices = linesSATA.join('\n').split(' Physical Interconnect: ');
1207
+ devices.shift();
1208
+ devices.forEach(function (device) {
1209
+ device = 'InterfaceType: ' + device;
1210
+ let lines = device.split('\n');
1211
+ const mediumType = util.getValue(lines, 'Medium Type', ':', true).trim();
1212
+ const sizeStr = util.getValue(lines, 'capacity', ':', true).trim();
1213
+ const BSDName = util.getValue(lines, 'BSD Name', ':', true).trim();
1214
+ if (sizeStr) {
1215
+ let sizeValue = 0;
1216
+ if (sizeStr.indexOf('(') >= 0) {
1217
+ sizeValue = parseInt(sizeStr.match(/\(([^)]+)\)/)[1].replace(/\./g, '').replace(/,/g, '').replace(/\s/g, ''));
1218
+ }
1219
+ if (!sizeValue) {
1220
+ sizeValue = parseInt(sizeStr);
1221
+ }
1222
+ if (sizeValue) {
1223
+ const smartStatusString = util.getValue(lines, 'S.M.A.R.T. status', ':', true).trim().toLowerCase();
1224
+ result.push({
1225
+ device: BSDName,
1226
+ type: mediumType.startsWith('Solid') ? 'SSD' : 'HD',
1227
+ name: util.getValue(lines, 'Model', ':', true).trim(),
1228
+ vendor: getVendorFromModel(util.getValue(lines, 'Model', ':', true).trim()) || util.getValue(lines, 'Manufacturer', ':', true),
1229
+ size: sizeValue,
1230
+ bytesPerSector: null,
1231
+ totalCylinders: null,
1232
+ totalHeads: null,
1233
+ totalSectors: null,
1234
+ totalTracks: null,
1235
+ tracksPerCylinder: null,
1236
+ sectorsPerTrack: null,
1237
+ firmwareRevision: util.getValue(lines, 'Revision', ':', true).trim(),
1238
+ serialNum: util.getValue(lines, 'Serial Number', ':', true).trim(),
1239
+ interfaceType: util.getValue(lines, 'InterfaceType', ':', true).trim(),
1240
+ smartStatus: smartStatusString === 'verified' ? 'OK' : smartStatusString || 'unknown',
1241
+ temperature: null,
1242
+ BSDName: BSDName
1243
+ });
1244
+ cmd = cmd + 'printf "\n' + BSDName + '|"; diskutil info /dev/' + BSDName + ' | grep SMART;';
1245
+ }
1246
+ }
1247
+ });
1248
+ } catch (e) {
1249
+ util.noop();
1250
+ }
1251
+
1252
+ // NVME Drives
1253
+ try {
1254
+ let devices = linesNVMe.join('\n').split('\n\n Capacity:');
1255
+ devices.shift();
1256
+ devices.forEach(function (device) {
1257
+ device = '!Capacity: ' + device;
1258
+ let lines = device.split('\n');
1259
+ const linkWidth = util.getValue(lines, 'link width', ':', true).trim();
1260
+ const sizeStr = util.getValue(lines, '!capacity', ':', true).trim();
1261
+ const BSDName = util.getValue(lines, 'BSD Name', ':', true).trim();
1262
+ if (sizeStr) {
1263
+ let sizeValue = 0;
1264
+ if (sizeStr.indexOf('(') >= 0) {
1265
+ sizeValue = parseInt(sizeStr.match(/\(([^)]+)\)/)[1].replace(/\./g, '').replace(/,/g, '').replace(/\s/g, ''));
1266
+ }
1267
+ if (!sizeValue) {
1268
+ sizeValue = parseInt(sizeStr);
1269
+ }
1270
+ if (sizeValue) {
1271
+ const smartStatusString = util.getValue(lines, 'S.M.A.R.T. status', ':', true).trim().toLowerCase();
1272
+ result.push({
1273
+ device: BSDName,
1274
+ type: 'NVMe',
1275
+ name: util.getValue(lines, 'Model', ':', true).trim(),
1276
+ vendor: getVendorFromModel(util.getValue(lines, 'Model', ':', true).trim()),
1277
+ size: sizeValue,
1278
+ bytesPerSector: null,
1279
+ totalCylinders: null,
1280
+ totalHeads: null,
1281
+ totalSectors: null,
1282
+ totalTracks: null,
1283
+ tracksPerCylinder: null,
1284
+ sectorsPerTrack: null,
1285
+ firmwareRevision: util.getValue(lines, 'Revision', ':', true).trim(),
1286
+ serialNum: util.getValue(lines, 'Serial Number', ':', true).trim(),
1287
+ interfaceType: ('PCIe ' + linkWidth).trim(),
1288
+ smartStatus: smartStatusString === 'verified' ? 'OK' : smartStatusString || 'unknown',
1289
+ temperature: null,
1290
+ BSDName: BSDName
1291
+ });
1292
+ cmd = cmd + 'printf "\n' + BSDName + '|"; diskutil info /dev/' + BSDName + ' | grep SMART;';
1293
+ }
1294
+ }
1295
+ });
1296
+ } catch (e) {
1297
+ util.noop();
1298
+ }
1299
+ // USB Drives
1300
+ try {
1301
+ let devices = linesUSB.join('\n').replaceAll('Media:\n ', 'Model:').split('\n\n Product ID:');
1302
+ devices.shift();
1303
+ devices.forEach(function (device) {
1304
+ let lines = device.split('\n');
1305
+ const sizeStr = util.getValue(lines, 'Capacity', ':', true).trim();
1306
+ const BSDName = util.getValue(lines, 'BSD Name', ':', true).trim();
1307
+ if (sizeStr) {
1308
+ let sizeValue = 0;
1309
+ if (sizeStr.indexOf('(') >= 0) {
1310
+ sizeValue = parseInt(sizeStr.match(/\(([^)]+)\)/)[1].replace(/\./g, '').replace(/,/g, '').replace(/\s/g, ''));
1311
+ }
1312
+ if (!sizeValue) {
1313
+ sizeValue = parseInt(sizeStr);
1314
+ }
1315
+ if (sizeValue) {
1316
+ const smartStatusString = util.getValue(lines, 'S.M.A.R.T. status', ':', true).trim().toLowerCase();
1317
+ result.push({
1318
+ device: BSDName,
1319
+ type: 'USB',
1320
+ name: util.getValue(lines, 'Model', ':', true).trim().replaceAll(':', ''),
1321
+ vendor: getVendorFromModel(util.getValue(lines, 'Model', ':', true).trim()),
1322
+ size: sizeValue,
1323
+ bytesPerSector: null,
1324
+ totalCylinders: null,
1325
+ totalHeads: null,
1326
+ totalSectors: null,
1327
+ totalTracks: null,
1328
+ tracksPerCylinder: null,
1329
+ sectorsPerTrack: null,
1330
+ firmwareRevision: util.getValue(lines, 'Revision', ':', true).trim(),
1331
+ serialNum: util.getValue(lines, 'Serial Number', ':', true).trim(),
1332
+ interfaceType: 'USB',
1333
+ smartStatus: smartStatusString === 'verified' ? 'OK' : smartStatusString || 'unknown',
1334
+ temperature: null,
1335
+ BSDName: BSDName
1336
+ });
1337
+ cmd = cmd + 'printf "\n' + BSDName + '|"; diskutil info /dev/' + BSDName + ' | grep SMART;';
1338
+ }
1339
+ }
1340
+ });
1341
+ } catch (e) {
1342
+ util.noop();
1343
+ }
1344
+ if (cmd) {
1345
+ cmd = cmd + 'printf "\n"';
1346
+ exec(cmd, { maxBuffer: 1024 * 1024 }, function (error, stdout) {
1347
+ let lines = stdout.toString().split('\n');
1348
+ lines.forEach(line => {
1349
+ if (line) {
1350
+ let parts = line.split('|');
1351
+ if (parts.length === 2) {
1352
+ let BSDName = parts[0];
1353
+ parts[1] = parts[1].trim();
1354
+ let parts2 = parts[1].split(':');
1355
+ if (parts2.length === 2) {
1356
+ parts2[1] = parts2[1].trim();
1357
+ let status = parts2[1].toLowerCase();
1358
+ for (let i = 0; i < result.length; i++) {
1359
+ if (result[i].BSDName === BSDName) {
1360
+ result[i].smartStatus = (status === 'not supported' ? 'not supported' : (status === 'verified' ? 'Ok' : (status === 'failing' ? 'Predicted Failure' : 'unknown')));
1361
+ }
1362
+ }
1363
+ }
1364
+ }
1365
+ }
1366
+ });
1367
+ for (let i = 0; i < result.length; i++) {
1368
+ delete result[i].BSDName;
1369
+ }
1370
+ if (callback) {
1371
+ callback(result);
1372
+ }
1373
+ resolve(result);
1374
+ });
1375
+ } else {
1376
+ for (let i = 0; i < result.length; i++) {
1377
+ delete result[i].BSDName;
1378
+ }
1379
+ if (callback) {
1380
+ callback(result);
1381
+ }
1382
+ resolve(result);
1383
+ }
1384
+ }
1385
+ });
1386
+ }
1387
+ if (_windows) {
1388
+ try {
1389
+ const workload = [];
1390
+ workload.push(util.powerShell('Get-CimInstance Win32_DiskDrive | select Caption,Size,Status,PNPDeviceId,DeviceId,BytesPerSector,TotalCylinders,TotalHeads,TotalSectors,TotalTracks,TracksPerCylinder,SectorsPerTrack,FirmwareRevision,SerialNumber,InterfaceType | fl'));
1391
+ workload.push(util.powerShell('Get-PhysicalDisk | select BusType,MediaType,FriendlyName,Model,SerialNumber,Size | fl'));
1392
+ if (util.smartMonToolsInstalled()) {
1393
+ try {
1394
+ const smartDev = JSON.parse(execSync('smartctl --scan -j'));
1395
+ if (smartDev && smartDev.devices && smartDev.devices.length > 0) {
1396
+ smartDev.devices.forEach((dev) => {
1397
+ workload.push(execPromiseSave(`smartctl -j -a ${dev.name}`, util.execOptsWin));
1398
+ });
1399
+ }
1400
+ } catch (e) {
1401
+ util.noop();
1402
+ }
1403
+ }
1404
+ util.promiseAll(
1405
+ workload
1406
+ ).then((data) => {
1407
+ let devices = data.results[0].toString().split(/\n\s*\n/);
1408
+ devices.forEach(function (device) {
1409
+ let lines = device.split('\r\n');
1410
+ const size = util.getValue(lines, 'Size', ':').trim();
1411
+ const status = util.getValue(lines, 'Status', ':').trim().toLowerCase();
1412
+ if (size) {
1413
+ result.push({
1414
+ device: util.getValue(lines, 'DeviceId', ':'), // changed from PNPDeviceId to DeviceID (be be able to match devices)
1415
+ type: device.indexOf('SSD') > -1 ? 'SSD' : 'HD', // just a starting point ... better: MSFT_PhysicalDisk - Media Type ... see below
1416
+ name: util.getValue(lines, 'Caption', ':'),
1417
+ vendor: getVendorFromModel(util.getValue(lines, 'Caption', ':', true).trim()),
1418
+ size: parseInt(size),
1419
+ bytesPerSector: parseInt(util.getValue(lines, 'BytesPerSector', ':')),
1420
+ totalCylinders: parseInt(util.getValue(lines, 'TotalCylinders', ':')),
1421
+ totalHeads: parseInt(util.getValue(lines, 'TotalHeads', ':')),
1422
+ totalSectors: parseInt(util.getValue(lines, 'TotalSectors', ':')),
1423
+ totalTracks: parseInt(util.getValue(lines, 'TotalTracks', ':')),
1424
+ tracksPerCylinder: parseInt(util.getValue(lines, 'TracksPerCylinder', ':')),
1425
+ sectorsPerTrack: parseInt(util.getValue(lines, 'SectorsPerTrack', ':')),
1426
+ firmwareRevision: util.getValue(lines, 'FirmwareRevision', ':').trim(),
1427
+ serialNum: util.getValue(lines, 'SerialNumber', ':').trim(),
1428
+ interfaceType: util.getValue(lines, 'InterfaceType', ':').trim(),
1429
+ smartStatus: (status === 'ok' ? 'Ok' : (status === 'degraded' ? 'Degraded' : (status === 'pred fail' ? 'Predicted Failure' : 'Unknown'))),
1430
+ temperature: null,
1431
+ });
1432
+ }
1433
+ });
1434
+ devices = data.results[1].split(/\n\s*\n/);
1435
+ devices.forEach(function (device) {
1436
+ let lines = device.split('\r\n');
1437
+ const serialNum = util.getValue(lines, 'SerialNumber', ':').trim();
1438
+ const name = util.getValue(lines, 'FriendlyName', ':').trim().replace('Msft ', 'Microsoft');
1439
+ const size = util.getValue(lines, 'Size', ':').trim();
1440
+ const model = util.getValue(lines, 'Model', ':').trim();
1441
+ const interfaceType = util.getValue(lines, 'BusType', ':').trim();
1442
+ let mediaType = util.getValue(lines, 'MediaType', ':').trim();
1443
+ if (mediaType === '3' || mediaType === 'HDD') { mediaType = 'HD'; }
1444
+ if (mediaType === '4') { mediaType = 'SSD'; }
1445
+ if (mediaType === '5') { mediaType = 'SCM'; }
1446
+ if (mediaType === 'Unspecified' && (model.toLowerCase().indexOf('virtual') > -1 || model.toLowerCase().indexOf('vbox') > -1)) { mediaType = 'Virtual'; }
1447
+ if (size) {
1448
+ let i = util.findObjectByKey(result, 'serialNum', serialNum);
1449
+ if (i === -1 || serialNum === '') {
1450
+ i = util.findObjectByKey(result, 'name', name);
1451
+ }
1452
+ if (i != -1) {
1453
+ result[i].type = mediaType;
1454
+ result[i].interfaceType = interfaceType;
1455
+ }
1456
+ }
1457
+ });
1458
+ // S.M.A.R.T
1459
+ data.results.shift();
1460
+ data.results.shift();
1461
+ if (data.results.length) {
1462
+ data.results.forEach((smartStr) => {
1463
+ try {
1464
+ const smartData = JSON.parse(smartStr);
1465
+ if (smartData.serial_number) {
1466
+ const serialNum = smartData.serial_number;
1467
+ let i = util.findObjectByKey(result, 'serialNum', serialNum);
1468
+ if (i != -1) {
1469
+ result[i].smartStatus = (smartData.smart_status && smartData.smart_status.passed ? 'Ok' : (smartData.smart_status && smartData.smart_status.passed === false ? 'Predicted Failure' : 'unknown'));
1470
+ if (smartData.temperature && smartData.temperature.current) {
1471
+ result[i].temperature = smartData.temperature.current;
1472
+ }
1473
+ result[i].smartData = smartData;
1474
+ }
1475
+ }
1476
+ } catch (e) {
1477
+ util.noop();
1478
+ }
1479
+ });
1480
+ }
1481
+ if (callback) {
1482
+ callback(result);
1483
+ }
1484
+ resolve(result);
1485
+ });
1486
+ } catch (e) {
1487
+ if (callback) { callback(result); }
1488
+ resolve(result);
1489
+ }
1490
+ }
1491
+ });
1492
+ });
1493
+ }
1494
+
1495
+ exports.diskLayout = diskLayout;