UNPKG

40.3 kBJavaScriptView Raw
1'use strict';
2// @ts-check
3// ==================================================================================
4// filesystem.js
5// ----------------------------------------------------------------------------------
6// Description: System Information - library
7// for Node.js
8// Copyright: (c) 2014 - 2020
9// Author: Sebastian Hildebrandt
10// ----------------------------------------------------------------------------------
11// License: MIT
12// ==================================================================================
13// 8. File System
14// ----------------------------------------------------------------------------------
15
16const exec = require('child_process').exec;
17const execSync = require('child_process').execSync;
18const util = require('./util');
19const fs = require('fs');
20
21let _platform = process.platform;
22
23const _linux = (_platform === 'linux');
24const _darwin = (_platform === 'darwin');
25const _windows = (_platform === 'win32');
26const _freebsd = (_platform === 'freebsd');
27const _openbsd = (_platform === 'openbsd');
28const _netbsd = (_platform === 'netbsd');
29const _sunos = (_platform === 'sunos');
30
31const NOT_SUPPORTED = 'not supported';
32
33let _fs_speed = {};
34let _disk_io = {};
35
36// --------------------------
37// FS - mounted file systems
38
39function fsSize(callback) {
40
41 function parseDf(lines) {
42 let data = [];
43 lines.forEach(function (line) {
44 if (line !== '') {
45 line = line.replace(/ +/g, ' ').split(' ');
46 if (line && (line[0].startsWith('/')) || (line[6] && line[6] === '/')) {
47 const fs = line[0];
48 const fstype = ((_linux || _freebsd || _openbsd || _netbsd) ? line[1] : 'HFS');
49 const size = parseInt(((_linux || _freebsd || _openbsd || _netbsd) ? line[2] : line[1])) * 1024;
50 const used = parseInt(((_linux || _freebsd || _openbsd || _netbsd) ? line[3] : line[2])) * 1024;
51 const use = parseFloat((100.0 * ((_linux || _freebsd || _openbsd || _netbsd) ? line[3] : line[2]) / ((_linux || _freebsd || _openbsd || _netbsd) ? line[2] : line[1])).toFixed(2));
52 const mount = line[line.length - 1];
53 if (!data.find(el => (el.fs === fs && el.type === fstype))) {
54 data.push({
55 fs,
56 type: fstype,
57 size,
58 used,
59 use,
60 mount
61 });
62 }
63 }
64 }
65 });
66 return data;
67 }
68
69 return new Promise((resolve) => {
70 process.nextTick(() => {
71 let data = [];
72 if (_linux || _freebsd || _openbsd || _netbsd || _darwin) {
73 let cmd = '';
74 if (_darwin) cmd = 'df -lkP | grep ^/';
75 if (_linux) cmd = 'df -lkPTx squashfs | grep ^/';
76 if (_freebsd || _openbsd || _netbsd) cmd = 'df -lkPT';
77 exec(cmd, function (error, stdout) {
78 if (!error) {
79 let lines = stdout.toString().split('\n');
80 data = parseDf(lines);
81 if (callback) {
82 callback(data);
83 }
84 resolve(data);
85 } else {
86 exec('df -kPT', function (error, stdout) {
87 if (!error) {
88 let lines = stdout.toString().split('\n');
89 data = parseDf(lines);
90 }
91 if (callback) {
92 callback(data);
93 }
94 resolve(data);
95 });
96 }
97 });
98 }
99 if (_sunos) {
100 if (callback) { callback(data); }
101 resolve(data);
102 }
103 if (_windows) {
104 try {
105 util.wmic('logicaldisk get Caption,FileSystem,FreeSpace,Size').then((stdout) => {
106 let lines = stdout.split('\r\n').filter(line => line.trim() !== '').filter((line, idx) => idx > 0);
107 lines.forEach(function (line) {
108 if (line !== '') {
109 line = line.trim().split(/\s\s+/);
110 data.push({
111 'fs': line[0],
112 'type': line[1],
113 'size': parseInt(line[3]),
114 'used': parseInt(line[3]) - parseInt(line[2]),
115 'use': parseFloat((100.0 * (parseInt(line[3]) - parseInt(line[2]))) / parseInt(line[3])),
116 'mount': line[0]
117 });
118 }
119 });
120 if (callback) {
121 callback(data);
122 }
123 resolve(data);
124 });
125 } catch (e) {
126 if (callback) { callback(data); }
127 resolve(data);
128 }
129 }
130 });
131 });
132}
133
134exports.fsSize = fsSize;
135
136// --------------------------
137// FS - open files count
138
139function fsOpenFiles(callback) {
140
141 return new Promise((resolve) => {
142 process.nextTick(() => {
143 const result = {
144 max: -1,
145 allocated: -1,
146 available: -1
147 };
148 if (_freebsd || _openbsd || _netbsd || _darwin) {
149 let cmd = 'sysctl -a | grep \'kern.*files\'';
150 exec(cmd, function (error, stdout) {
151 if (!error) {
152 let lines = stdout.toString().split('\n');
153 result.max = parseInt(util.getValue(lines, 'kern.maxfiles', ':'), 10);
154 result.allocated = parseInt(util.getValue(lines, 'kern.num_files', ':'), 10);
155 }
156 if (callback) {
157 callback(result);
158 }
159 resolve(result);
160 });
161 }
162 if (_linux) {
163 fs.readFile('/proc/sys/fs/file-nr', function (error, stdout) {
164 if (!error) {
165 let lines = stdout.toString().split('\n');
166 if (lines[0]) {
167 const parts = lines[0].replace(/\s+/g, ' ').split(' ');
168 if (parts.length === 3) {
169 result.allocated = parseInt(parts[0], 10);
170 result.available = parseInt(parts[1], 10);
171 result.max = parseInt(parts[2], 10);
172 }
173 }
174 }
175 if (callback) {
176 callback(result);
177 }
178 resolve(result);
179 });
180 }
181 if (_sunos) {
182 if (callback) { callback(result); }
183 resolve(result);
184 }
185 if (_windows) {
186 if (callback) { callback(result); }
187 resolve(result);
188 }
189 });
190 });
191}
192
193exports.fsOpenFiles = fsOpenFiles;
194
195// --------------------------
196// disks
197
198function parseBytes(s) {
199 return parseInt(s.substr(s.indexOf(' (') + 2, s.indexOf(' Bytes)') - 10));
200}
201
202function parseDevices(lines) {
203 let devices = [];
204 let i = 0;
205 lines.forEach(line => {
206 if (line.length > 0) {
207 if (line[0] === '*') {
208 i++;
209 } else {
210 let parts = line.split(':');
211 if (parts.length > 1) {
212 if (!devices[i]) devices[i] = {
213 name: '',
214 identifier: '',
215 type: 'disk',
216 fstype: '',
217 mount: '',
218 size: 0,
219 physical: 'HDD',
220 uuid: '',
221 label: '',
222 model: '',
223 serial: '',
224 removable: false,
225 protocol: ''
226 };
227 parts[0] = parts[0].trim().toUpperCase().replace(/ +/g, '');
228 parts[1] = parts[1].trim();
229 if ('DEVICEIDENTIFIER' === parts[0]) devices[i].identifier = parts[1];
230 if ('DEVICENODE' === parts[0]) devices[i].name = parts[1];
231 if ('VOLUMENAME' === parts[0]) {
232 if (parts[1].indexOf('Not applicable') === -1) devices[i].label = parts[1];
233 }
234 if ('PROTOCOL' === parts[0]) devices[i].protocol = parts[1];
235 if ('DISKSIZE' === parts[0]) devices[i].size = parseBytes(parts[1]);
236 if ('FILESYSTEMPERSONALITY' === parts[0]) devices[i].fstype = parts[1];
237 if ('MOUNTPOINT' === parts[0]) devices[i].mount = parts[1];
238 if ('VOLUMEUUID' === parts[0]) devices[i].uuid = parts[1];
239 if ('READ-ONLYMEDIA' === parts[0] && parts[1] === 'Yes') devices[i].physical = 'CD/DVD';
240 if ('SOLIDSTATE' === parts[0] && parts[1] === 'Yes') devices[i].physical = 'SSD';
241 if ('VIRTUAL' === parts[0]) devices[i].type = 'virtual';
242 if ('REMOVABLEMEDIA' === parts[0]) devices[i].removable = (parts[1] === 'Removable');
243 if ('PARTITIONTYPE' === parts[0]) devices[i].type = 'part';
244 if ('DEVICE/MEDIANAME' === parts[0]) devices[i].model = parts[1];
245 }
246 }
247 }
248 });
249 return devices;
250}
251
252function parseBlk(lines) {
253 let data = [];
254
255 lines.filter(line => line !== '').forEach((line) => {
256 line = decodeURIComponent(line.replace(/\\x/g, '%'));
257 line = line.replace(/\\/g, '\\\\');
258 let disk = JSON.parse(line);
259 data.push({
260 'name': disk.name,
261 'type': disk.type,
262 'fstype': disk.fstype,
263 'mount': disk.mountpoint,
264 'size': parseInt(disk.size),
265 'physical': (disk.type === 'disk' ? (disk.rota === '0' ? 'SSD' : 'HDD') : (disk.type === 'rom' ? 'CD/DVD' : '')),
266 'uuid': disk.uuid,
267 'label': disk.label,
268 'model': disk.model,
269 'serial': disk.serial,
270 'removable': disk.rm === '1',
271 'protocol': disk.tran,
272 'group': disk.group,
273 });
274 });
275
276 data = util.unique(data);
277 data = util.sortByKey(data, ['type', 'name']);
278 return data;
279}
280
281function blkStdoutToObject(stdout) {
282 return stdout.toString()
283 .replace(/NAME=/g, '{"name":')
284 .replace(/FSTYPE=/g, ',"fstype":')
285 .replace(/TYPE=/g, ',"type":')
286 .replace(/SIZE=/g, ',"size":')
287 .replace(/MOUNTPOINT=/g, ',"mountpoint":')
288 .replace(/UUID=/g, ',"uuid":')
289 .replace(/ROTA=/g, ',"rota":')
290 .replace(/RO=/g, ',"ro":')
291 .replace(/RM=/g, ',"rm":')
292 .replace(/TRAN=/g, ',"tran":')
293 .replace(/SERIAL=/g, ',"serial":')
294 .replace(/LABEL=/g, ',"label":')
295 .replace(/MODEL=/g, ',"model":')
296 .replace(/OWNER=/g, ',"owner":')
297 .replace(/GROUP=/g, ',"group":')
298 .replace(/\n/g, '}\n');
299}
300
301function blockDevices(callback) {
302
303 return new Promise((resolve) => {
304 process.nextTick(() => {
305 let data = [];
306 if (_linux) {
307 // see https://wiki.ubuntuusers.de/lsblk/
308 // 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) {
309 exec('lsblk -bPo NAME,TYPE,SIZE,FSTYPE,MOUNTPOINT,UUID,ROTA,RO,RM,TRAN,SERIAL,LABEL,MODEL,OWNER 2>/dev/null', function (error, stdout) {
310 if (!error) {
311 let lines = blkStdoutToObject(stdout).split('\n');
312 data = parseBlk(lines);
313 if (callback) {
314 callback(data);
315 }
316 resolve(data);
317 } else {
318 exec('lsblk -bPo NAME,TYPE,SIZE,FSTYPE,MOUNTPOINT,UUID,ROTA,RO,RM,LABEL,MODEL,OWNER 2>/dev/null', function (error, stdout) {
319 if (!error) {
320 let lines = blkStdoutToObject(stdout).split('\n');
321 data = parseBlk(lines);
322 }
323 if (callback) {
324 callback(data);
325 }
326 resolve(data);
327 });
328 }
329 });
330 }
331 if (_darwin) {
332 exec('diskutil info -all', function (error, stdout) {
333 if (!error) {
334 let lines = stdout.toString().split('\n');
335 // parse lines into temp array of devices
336 data = parseDevices(lines);
337 }
338 if (callback) {
339 callback(data);
340 }
341 resolve(data);
342 });
343 }
344 if (_sunos) {
345 if (callback) { callback(data); }
346 resolve(data);
347 }
348 if (_windows) {
349 let drivetypes = ['Unknown', 'NoRoot', 'Removable', 'Local', 'Network', 'CD/DVD', 'RAM'];
350 try {
351 util.wmic('logicaldisk get Caption,Description,DeviceID,DriveType,FileSystem,FreeSpace,Name,Size,VolumeName,VolumeSerialNumber /value').then((stdout, error) => {
352 if (!error) {
353 let devices = stdout.toString().split(/\n\s*\n/);
354 devices.forEach(function (device) {
355 let lines = device.split('\r\n');
356 let drivetype = util.getValue(lines, 'drivetype', '=');
357 if (drivetype) {
358 data.push({
359 name: util.getValue(lines, 'name', '='),
360 identifier: util.getValue(lines, 'caption', '='),
361 type: 'disk',
362 fstype: util.getValue(lines, 'filesystem', '=').toLowerCase(),
363 mount: util.getValue(lines, 'caption', '='),
364 size: util.getValue(lines, 'size', '='),
365 physical: (drivetype >= 0 && drivetype <= 6) ? drivetypes[drivetype] : drivetypes[0],
366 uuid: util.getValue(lines, 'volumeserialnumber', '='),
367 label: util.getValue(lines, 'volumename', '='),
368 model: '',
369 serial: util.getValue(lines, 'volumeserialnumber', '='),
370 removable: drivetype === '2',
371 protocol: ''
372 });
373 }
374 });
375 }
376 if (callback) {
377 callback(data);
378 }
379 resolve(data);
380 });
381
382 } catch (e) {
383 if (callback) { callback(data); }
384 resolve(data);
385 }
386 }
387 });
388 });
389}
390
391exports.blockDevices = blockDevices;
392
393// --------------------------
394// FS - speed
395
396function calcFsSpeed(rx, wx) {
397 let result = {
398 rx: 0,
399 wx: 0,
400 tx: 0,
401 rx_sec: -1,
402 wx_sec: -1,
403 tx_sec: -1,
404 ms: 0
405 };
406
407 if (_fs_speed && _fs_speed.ms) {
408 result.rx = rx;
409 result.wx = wx;
410 result.tx = result.rx + result.wx;
411 result.ms = Date.now() - _fs_speed.ms;
412 result.rx_sec = (result.rx - _fs_speed.bytes_read) / (result.ms / 1000);
413 result.wx_sec = (result.wx - _fs_speed.bytes_write) / (result.ms / 1000);
414 result.tx_sec = result.rx_sec + result.wx_sec;
415 _fs_speed.rx_sec = result.rx_sec;
416 _fs_speed.wx_sec = result.wx_sec;
417 _fs_speed.tx_sec = result.tx_sec;
418 _fs_speed.bytes_read = result.rx;
419 _fs_speed.bytes_write = result.wx;
420 _fs_speed.bytes_overall = result.rx + result.wx;
421 _fs_speed.ms = Date.now();
422 _fs_speed.last_ms = result.ms;
423 } else {
424 result.rx = rx;
425 result.wx = wx;
426 result.tx = result.rx + result.wx;
427 _fs_speed.rx_sec = -1;
428 _fs_speed.wx_sec = -1;
429 _fs_speed.tx_sec = -1;
430 _fs_speed.bytes_read = result.rx;
431 _fs_speed.bytes_write = result.wx;
432 _fs_speed.bytes_overall = result.rx + result.wx;
433 _fs_speed.ms = Date.now();
434 _fs_speed.last_ms = 0;
435 }
436 return result;
437}
438
439function fsStats(callback) {
440
441 return new Promise((resolve, reject) => {
442 process.nextTick(() => {
443 if (_windows) {
444 let error = new Error(NOT_SUPPORTED);
445 if (callback) {
446 callback(NOT_SUPPORTED);
447 }
448 reject(error);
449 }
450
451 let result = {
452 rx: 0,
453 wx: 0,
454 tx: 0,
455 rx_sec: -1,
456 wx_sec: -1,
457 tx_sec: -1,
458 ms: 0
459 };
460
461 let rx = 0;
462 let wx = 0;
463 if ((_fs_speed && !_fs_speed.ms) || (_fs_speed && _fs_speed.ms && Date.now() - _fs_speed.ms >= 500)) {
464 if (_linux) {
465 // exec("df -k | grep /dev/", function(error, stdout) {
466 exec('lsblk 2>/dev/null | grep /', function (error, stdout) {
467 if (!error) {
468 let lines = stdout.toString().split('\n');
469 let fs_filter = [];
470 lines.forEach(function (line) {
471 if (line !== '') {
472 line = line.replace(/[├─│└]+/g, '').trim().split(' ');
473 if (fs_filter.indexOf(line[0]) === -1) fs_filter.push(line[0]);
474 }
475 });
476
477 let output = fs_filter.join('|');
478 exec('cat /proc/diskstats | egrep "' + output + '"', function (error, stdout) {
479 if (!error) {
480 let lines = stdout.toString().split('\n');
481 lines.forEach(function (line) {
482 line = line.trim();
483 if (line !== '') {
484 line = line.replace(/ +/g, ' ').split(' ');
485
486 rx += parseInt(line[5]) * 512;
487 wx += parseInt(line[9]) * 512;
488 }
489 });
490 result = calcFsSpeed(rx, wx);
491 }
492 if (callback) {
493 callback(result);
494 }
495 resolve(result);
496 });
497 } else {
498 if (callback) {
499 callback(result);
500 }
501 resolve(result);
502 }
503 });
504 }
505 if (_darwin) {
506 exec('ioreg -c IOBlockStorageDriver -k Statistics -r -w0 | sed -n "/IOBlockStorageDriver/,/Statistics/p" | grep "Statistics" | tr -cd "01234567890,\n"', function (error, stdout) {
507 if (!error) {
508 let lines = stdout.toString().split('\n');
509 lines.forEach(function (line) {
510 line = line.trim();
511 if (line !== '') {
512 line = line.split(',');
513
514 rx += parseInt(line[2]);
515 wx += parseInt(line[9]);
516 }
517 });
518 result = calcFsSpeed(rx, wx);
519 }
520 if (callback) {
521 callback(result);
522 }
523 resolve(result);
524 });
525 }
526 } else {
527 result.ms = _fs_speed.last_ms;
528 result.rx = _fs_speed.bytes_read;
529 result.wx = _fs_speed.bytes_write;
530 result.tx = _fs_speed.bytes_read + _fs_speed.bytes_write;
531 result.rx_sec = _fs_speed.rx_sec;
532 result.wx_sec = _fs_speed.wx_sec;
533 result.tx_sec = _fs_speed.tx_sec;
534 if (callback) {
535 callback(result);
536 }
537 resolve(result);
538 }
539 });
540 });
541}
542
543exports.fsStats = fsStats;
544
545function calcDiskIO(rIO, wIO) {
546 let result = {
547 rIO: 0,
548 wIO: 0,
549 tIO: 0,
550 rIO_sec: -1,
551 wIO_sec: -1,
552 tIO_sec: -1,
553 ms: 0
554 };
555 if (_disk_io && _disk_io.ms) {
556 result.rIO = rIO;
557 result.wIO = wIO;
558 result.tIO = rIO + wIO;
559 result.ms = Date.now() - _disk_io.ms;
560 result.rIO_sec = (result.rIO - _disk_io.rIO) / (result.ms / 1000);
561 result.wIO_sec = (result.wIO - _disk_io.wIO) / (result.ms / 1000);
562 result.tIO_sec = result.rIO_sec + result.wIO_sec;
563 _disk_io.rIO = rIO;
564 _disk_io.wIO = wIO;
565 _disk_io.rIO_sec = result.rIO_sec;
566 _disk_io.wIO_sec = result.wIO_sec;
567 _disk_io.tIO_sec = result.tIO_sec;
568 _disk_io.last_ms = result.ms;
569 _disk_io.ms = Date.now();
570 } else {
571 result.rIO = rIO;
572 result.wIO = wIO;
573 result.tIO = rIO + wIO;
574 _disk_io.rIO = rIO;
575 _disk_io.wIO = wIO;
576 _disk_io.rIO_sec = -1;
577 _disk_io.wIO_sec = -1;
578 _disk_io.tIO_sec = -1;
579 _disk_io.last_ms = 0;
580 _disk_io.ms = Date.now();
581 }
582 return result;
583}
584
585function disksIO(callback) {
586
587 return new Promise((resolve, reject) => {
588 process.nextTick(() => {
589 if (_windows) {
590 let error = new Error(NOT_SUPPORTED);
591 if (callback) {
592 callback(NOT_SUPPORTED);
593 }
594 reject(error);
595 }
596 if (_sunos) {
597 let error = new Error(NOT_SUPPORTED);
598 if (callback) {
599 callback(NOT_SUPPORTED);
600 }
601 reject(error);
602 }
603
604 let result = {
605 rIO: 0,
606 wIO: 0,
607 tIO: 0,
608 rIO_sec: -1,
609 wIO_sec: -1,
610 tIO_sec: -1,
611 ms: 0
612 };
613 let rIO = 0;
614 let wIO = 0;
615
616 if ((_disk_io && !_disk_io.ms) || (_disk_io && _disk_io.ms && Date.now() - _disk_io.ms >= 500)) {
617 if (_linux || _freebsd || _openbsd || _netbsd) {
618 // prints Block layer statistics for all mounted volumes
619 // 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";
620 // 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";
621 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';
622
623 exec(cmd, function (error, stdout) {
624 if (!error) {
625 let lines = stdout.split('\n');
626 lines.forEach(function (line) {
627 // ignore empty lines
628 if (!line) return;
629
630 // sum r/wIO of all disks to compute all disks IO
631 let stats = line.split(';');
632 rIO += parseInt(stats[0]);
633 wIO += parseInt(stats[4]);
634 });
635 result = calcDiskIO(rIO, wIO);
636
637 if (callback) {
638 callback(result);
639 }
640 resolve(result);
641 } else {
642 if (callback) {
643 callback(result);
644 }
645 resolve(result);
646 }
647 });
648 }
649 if (_darwin) {
650 exec('ioreg -c IOBlockStorageDriver -k Statistics -r -w0 | sed -n "/IOBlockStorageDriver/,/Statistics/p" | grep "Statistics" | tr -cd "01234567890,\n"', function (error, stdout) {
651 if (!error) {
652 let lines = stdout.toString().split('\n');
653 lines.forEach(function (line) {
654 line = line.trim();
655 if (line !== '') {
656 line = line.split(',');
657
658 rIO += parseInt(line[10]);
659 wIO += parseInt(line[0]);
660 }
661 });
662 result = calcDiskIO(rIO, wIO);
663 }
664 if (callback) {
665 callback(result);
666 }
667 resolve(result);
668 });
669 }
670 } else {
671 result.rIO = _disk_io.rIO;
672 result.wIO = _disk_io.wIO;
673 result.tIO = _disk_io.rIO + _disk_io.wIO;
674 result.ms = _disk_io.last_ms;
675 result.rIO_sec = _disk_io.rIO_sec;
676 result.wIO_sec = _disk_io.wIO_sec;
677 result.tIO_sec = _disk_io.tIO_sec;
678 if (callback) {
679 callback(result);
680 }
681 resolve(result);
682 }
683 });
684 });
685}
686
687exports.disksIO = disksIO;
688
689function diskLayout(callback) {
690
691 function getVendorFromModel(model) {
692 const diskManufacturers = [
693 { pattern: '^WESTERN.+', manufacturer: 'Western Digital' },
694 { pattern: '^WDC.+', manufacturer: 'Western Digital' },
695 { pattern: 'WD.+', manufacturer: 'Western Digital' },
696 { pattern: '^TOSHIBA.+', manufacturer: 'Toshiba' },
697 { pattern: '^HITACHI.+', manufacturer: 'Hitachi' },
698 { pattern: '^IC.+', manufacturer: 'Hitachi' },
699 { pattern: '^HTS.+', manufacturer: 'Hitachi' },
700 { pattern: '^SANDISK.+', manufacturer: 'SanDisk' },
701 { pattern: '^KINGSTON.+', manufacturer: 'Kingston Technonogy' },
702 { pattern: '^SONY.+', manufacturer: 'Sony' },
703 { pattern: '^TRANSCEND.+', manufacturer: 'Transcend' },
704 { pattern: 'SAMSUNG.+', manufacturer: 'Samsung' },
705 { pattern: '^ST(?!I\\ ).+', manufacturer: 'Seagate' },
706 { pattern: '^STI\\ .+', manufacturer: 'SimpleTech' },
707 { pattern: '^D...-.+', manufacturer: 'IBM' },
708 { pattern: '^IBM.+', manufacturer: 'IBM' },
709 { pattern: '^FUJITSU.+', manufacturer: 'Fujitsu' },
710 { pattern: '^MP.+', manufacturer: 'Fujitsu' },
711 { pattern: '^MK.+', manufacturer: 'Toshiba' },
712 { pattern: '^MAXTOR.+', manufacturer: 'Maxtor' },
713 { pattern: '^Pioneer.+', manufacturer: 'Pioneer' },
714 { pattern: '^PHILIPS.+', manufacturer: 'Philips' },
715 { pattern: '^QUANTUM.+', manufacturer: 'Quantum Technology' },
716 { pattern: 'FIREBALL.+', manufacturer: 'Quantum Technology' },
717 { pattern: '^VBOX.+', manufacturer: 'VirtualBox' },
718 { pattern: 'CORSAIR.+', manufacturer: 'Corsair Components' },
719 { pattern: 'CRUCIAL.+', manufacturer: 'Crucial' },
720 { pattern: 'ECM.+', manufacturer: 'ECM' },
721 { pattern: 'INTEL.+', manufacturer: 'INTEL' },
722 ];
723
724 let result = '';
725 if (model) {
726 model = model.toUpperCase();
727 diskManufacturers.forEach((manufacturer) => {
728 const re = RegExp(manufacturer.pattern);
729 if (re.test(model)) { result = manufacturer.manufacturer; }
730 });
731 }
732 return result;
733 }
734
735 return new Promise((resolve) => {
736 process.nextTick(() => {
737
738 const commitResult = res => {
739 for (let i = 0; i < res.length; i++) {
740 delete res[i].BSDName;
741 }
742 if (callback) {
743 callback(res);
744 }
745 resolve(res);
746 };
747
748 let result = [];
749 let cmd = '';
750
751 if (_linux) {
752 let cmdFullSmart = '';
753
754 exec('export LC_ALL=C; lsblk -ablJO 2>/dev/null; unset LC_ALL', function (error, stdout) {
755 if (!error) {
756 try {
757 const out = stdout.toString().trim();
758 let devices = [];
759 try {
760 const outJSON = JSON.parse(out);
761 if (outJSON && {}.hasOwnProperty.call(outJSON, 'blockdevices')) {
762 devices = outJSON.blockdevices.filter(item => { return (item.group === 'disk' || item.type === 'disk') && item.size > 0 && (item.model !== null || (item.mountpoint === null && item.label === null && item.fstype === null && item.parttype === null)); });
763 }
764 } catch (e) {
765 // fallback to older version of lsblk
766 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();
767 let lines = blkStdoutToObject(out2).split('\n');
768 const data = parseBlk(lines);
769 devices = data.filter(item => { return (item.group === 'disk' || item.type === 'disk') && item.size > 0 && ((item.model !== null && item.model !== '') || (item.mountpoint === '' && item.label === '' && item.fstype === '')); });
770 }
771 devices.forEach((device) => {
772 let mediumType = '';
773 const BSDName = '/dev/' + device.name;
774 const logical = device.name;
775 try {
776 mediumType = execSync('cat /sys/block/' + logical + '/queue/rotational 2>/dev/null').toString().split('\n')[0];
777 } catch (e) {
778 util.noop();
779 }
780 let interfaceType = device.tran ? device.tran.toUpperCase().trim() : '';
781 if (interfaceType === 'NVME') {
782 mediumType = '2';
783 interfaceType = 'PCIe';
784 }
785 result.push({
786 device: BSDName,
787 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'))))),
788 name: device.model || '',
789 vendor: getVendorFromModel(device.model) || (device.vendor ? device.vendor.trim() : ''),
790 size: device.size || 0,
791 bytesPerSector: -1,
792 totalCylinders: -1,
793 totalHeads: -1,
794 totalSectors: -1,
795 totalTracks: -1,
796 tracksPerCylinder: -1,
797 sectorsPerTrack: -1,
798 firmwareRevision: device.rev ? device.rev.trim() : '',
799 serialNum: device.serial ? device.serial.trim() : '',
800 interfaceType: interfaceType,
801 smartStatus: 'unknown',
802 BSDName: BSDName
803 });
804 cmd += `printf "\n${BSDName}|"; smartctl -H ${BSDName} | grep overall;`;
805 cmdFullSmart += `${cmdFullSmart ? 'printf ",";' : ''}smartctl -a -j ${BSDName};`;
806 });
807 } catch (e) {
808 util.noop();
809 }
810 }
811 // check S.M.A.R.T. status
812 if (cmdFullSmart) {
813 exec(cmdFullSmart, function (error, stdout) {
814 try {
815 const data = JSON.parse(`[${stdout}]`);
816 data.forEach(disk => {
817 const diskBSDName = disk.smartctl.argv[disk.smartctl.argv.length - 1];
818
819 for (let i = 0; i < result.length; i++) {
820 if (result[i].BSDName === diskBSDName) {
821 result[i].smartStatus = (disk.smart_status.passed ? 'Ok' : (disk.smart_status.passed === false ? 'Predicted Failure' : 'unknown'));
822 result[i].smartData = disk;
823 }
824 }
825 });
826 commitResult(result);
827 } catch (e) {
828 if (cmd) {
829 cmd = cmd + 'printf "\n"';
830 exec(cmd, function (error, stdout) {
831 let lines = stdout.toString().split('\n');
832 lines.forEach(line => {
833 if (line) {
834 let parts = line.split('|');
835 if (parts.length === 2) {
836 let BSDName = parts[0];
837 parts[1] = parts[1].trim();
838 let parts2 = parts[1].split(':');
839 if (parts2.length === 2) {
840 parts2[1] = parts2[1].trim();
841 let status = parts2[1].toLowerCase();
842 for (let i = 0; i < result.length; i++) {
843 if (result[i].BSDName === BSDName) {
844 result[i].smartStatus = (status === 'passed' ? 'Ok' : (status === 'failed!' ? 'Predicted Failure' : 'unknown'));
845 }
846 }
847 }
848 }
849 }
850 });
851 commitResult(result);
852 });
853 } else {
854 commitResult(result);
855 }
856 }
857 });
858 } else {
859 commitResult(result);
860 }
861 });
862 }
863 if (_freebsd || _openbsd || _netbsd) {
864 if (callback) { callback(result); }
865 resolve(result);
866 }
867 if (_sunos) {
868 if (callback) { callback(result); }
869 resolve(result);
870 }
871 if (_darwin) {
872 exec('system_profiler SPSerialATADataType SPNVMeDataType', function (error, stdout) {
873 if (!error) {
874 let parts = stdout.toString().split('NVMExpress:');
875
876 let devices = parts[0].split(' Physical Interconnect: ');
877 devices.shift();
878 devices.forEach(function (device) {
879 device = 'InterfaceType: ' + device;
880 let lines = device.split('\n');
881 const mediumType = util.getValue(lines, 'Medium Type', ':', true).trim();
882 const sizeStr = util.getValue(lines, 'capacity', ':', true).trim();
883 const BSDName = util.getValue(lines, 'BSD Name', ':', true).trim();
884 if (sizeStr) {
885 let sizeValue = 0;
886 if (sizeStr.indexOf('(') >= 0) {
887 sizeValue = parseInt(sizeStr.match(/\(([^)]+)\)/)[1].replace(/\./g, '').replace(/,/g, ''));
888 }
889 if (!sizeValue) {
890 sizeValue = parseInt(sizeStr);
891 }
892 if (sizeValue) {
893 result.push({
894 device: BSDName,
895 type: mediumType.startsWith('Solid') ? 'SSD' : 'HD',
896 name: util.getValue(lines, 'Model', ':', true).trim(),
897 vendor: getVendorFromModel(util.getValue(lines, 'Model', ':', true).trim()),
898 size: sizeValue,
899 bytesPerSector: -1,
900 totalCylinders: -1,
901 totalHeads: -1,
902 totalSectors: -1,
903 totalTracks: -1,
904 tracksPerCylinder: -1,
905 sectorsPerTrack: -1,
906 firmwareRevision: util.getValue(lines, 'Revision', ':', true).trim(),
907 serialNum: util.getValue(lines, 'Serial Number', ':', true).trim(),
908 interfaceType: util.getValue(lines, 'InterfaceType', ':', true).trim(),
909 smartStatus: 'unknown',
910 BSDName: BSDName
911 });
912 cmd = cmd + 'printf "\n' + BSDName + '|"; diskutil info /dev/' + BSDName + ' | grep SMART;';
913 }
914 }
915 });
916 if (parts.length > 1) {
917 let devices = parts[1].split('\n\n Capacity:');
918 devices.shift();
919 devices.forEach(function (device) {
920 device = '!Capacity: ' + device;
921 let lines = device.split('\n');
922 const linkWidth = util.getValue(lines, 'link width', ':', true).trim();
923 const sizeStr = util.getValue(lines, '!capacity', ':', true).trim();
924 const BSDName = util.getValue(lines, 'BSD Name', ':', true).trim();
925 if (sizeStr) {
926 let sizeValue = 0;
927 if (sizeStr.indexOf('(') >= 0) {
928 sizeValue = parseInt(sizeStr.match(/\(([^)]+)\)/)[1].replace(/\./g, '').replace(/,/g, ''));
929 }
930 if (!sizeValue) {
931 sizeValue = parseInt(sizeStr);
932 }
933 if (sizeValue) {
934 result.push({
935 device: BSDName,
936 type: 'NVMe',
937 name: util.getValue(lines, 'Model', ':', true).trim(),
938 vendor: getVendorFromModel(util.getValue(lines, 'Model', ':', true).trim()),
939 size: sizeValue,
940 bytesPerSector: -1,
941 totalCylinders: -1,
942 totalHeads: -1,
943 totalSectors: -1,
944 totalTracks: -1,
945 tracksPerCylinder: -1,
946 sectorsPerTrack: -1,
947 firmwareRevision: util.getValue(lines, 'Revision', ':', true).trim(),
948 serialNum: util.getValue(lines, 'Serial Number', ':', true).trim(),
949 interfaceType: ('PCIe ' + linkWidth).trim(),
950 smartStatus: 'unknown',
951 BSDName: BSDName
952 });
953 cmd = cmd + 'printf "\n' + BSDName + '|"; diskutil info /dev/' + BSDName + ' | grep SMART;';
954 }
955 }
956 });
957 }
958 }
959 if (cmd) {
960 cmd = cmd + 'printf "\n"';
961 exec(cmd, function (error, stdout) {
962 let lines = stdout.toString().split('\n');
963 lines.forEach(line => {
964 if (line) {
965 let parts = line.split('|');
966 if (parts.length === 2) {
967 let BSDName = parts[0];
968 parts[1] = parts[1].trim();
969 let parts2 = parts[1].split(':');
970 if (parts2.length === 2) {
971 parts2[1] = parts2[1].trim();
972 let status = parts2[1].toLowerCase();
973 for (let i = 0; i < result.length; i++) {
974 if (result[i].BSDName === BSDName) {
975 result[i].smartStatus = (status === 'not supported' ? 'not supported' : (status === 'verified' ? 'Ok' : (status === 'failing' ? 'Predicted Failure' : 'unknown')));
976 }
977 }
978 }
979 }
980 }
981 });
982 for (let i = 0; i < result.length; i++) {
983 delete result[i].BSDName;
984 }
985 if (callback) {
986 callback(result);
987 }
988 resolve(result);
989 });
990 } else {
991 for (let i = 0; i < result.length; i++) {
992 delete result[i].BSDName;
993 }
994 if (callback) {
995 callback(result);
996 }
997 resolve(result);
998 }
999 });
1000 }
1001 if (_windows) {
1002 try {
1003 util.wmic('diskdrive get /value').then((stdout, error) => {
1004 let devices = stdout.toString().split(/\n\s*\n/);
1005 devices.forEach(function (device) {
1006 let lines = device.split('\r\n');
1007 const size = util.getValue(lines, 'Size', '=').trim();
1008 const status = util.getValue(lines, 'Status', '=').trim().toLowerCase();
1009 if (size) {
1010 result.push({
1011 device: '',
1012 type: device.indexOf('SSD') > -1 ? 'SSD' : 'HD', // just a starting point ... better: MSFT_PhysicalDisk - Media Type ... see below
1013 name: util.getValue(lines, 'Caption', '='),
1014 vendor: util.getValue(lines, 'Manufacturer', '='),
1015 size: parseInt(size),
1016 bytesPerSector: parseInt(util.getValue(lines, 'BytesPerSector', '=')),
1017 totalCylinders: parseInt(util.getValue(lines, 'TotalCylinders', '=')),
1018 totalHeads: parseInt(util.getValue(lines, 'TotalHeads', '=')),
1019 totalSectors: parseInt(util.getValue(lines, 'TotalSectors', '=')),
1020 totalTracks: parseInt(util.getValue(lines, 'TotalTracks', '=')),
1021 tracksPerCylinder: parseInt(util.getValue(lines, 'TracksPerCylinder', '=')),
1022 sectorsPerTrack: parseInt(util.getValue(lines, 'SectorsPerTrack', '=')),
1023 firmwareRevision: util.getValue(lines, 'FirmwareRevision', '=').trim(),
1024 serialNum: util.getValue(lines, 'SerialNumber', '=').trim(),
1025 interfaceType: util.getValue(lines, 'InterfaceType', '=').trim(),
1026 smartStatus: (status === 'ok' ? 'Ok' : (status === 'degraded' ? 'Degraded' : (status === 'pred fail' ? 'Predicted Failure' : 'Unknown')))
1027 });
1028 }
1029 });
1030 util.powerShell('Get-PhysicalDisk | Format-List')
1031 .then(data => {
1032 let devices = data.split(/\n\s*\n/);
1033 devices.forEach(function (device) {
1034 let lines = device.split('\r\n');
1035 const serialNum = util.getValue(lines, 'SerialNumber', ':').trim();
1036 const name = util.getValue(lines, 'FriendlyName', ':').trim().replace('Msft ', 'Microsoft');
1037 const size = util.getValue(lines, 'Size', ':').trim();
1038 const model = util.getValue(lines, 'Model', ':').trim();
1039 const interfaceType = util.getValue(lines, 'BusType', ':').trim();
1040 let mediaType = util.getValue(lines, 'MediaType', ':').trim();
1041 if (mediaType === '3' || mediaType === 'HDD') { mediaType = 'HD'; }
1042 if (mediaType === '4') { mediaType = 'SSD'; }
1043 if (mediaType === '5') { mediaType = 'SCM'; }
1044 if (mediaType === 'Unspecified' && (model.toLowerCase().indexOf('virtual') > -1 || model.toLowerCase().indexOf('vbox') > -1)) { mediaType = 'Virtual'; }
1045 if (size) {
1046 let i = util.findObjectByKey(result, 'serialNum', serialNum);
1047 if (i === -1 || serialNum === '') {
1048 i = util.findObjectByKey(result, 'name', name);
1049 }
1050 if (i != -1) {
1051 result[i].type = mediaType;
1052 result[i].interfaceType = interfaceType;
1053 }
1054 }
1055 });
1056 if (callback) {
1057 callback(result);
1058 }
1059 resolve(result);
1060 })
1061 .catch(() => {
1062 if (callback) {
1063 callback(result);
1064 }
1065 resolve(result);
1066 });
1067 });
1068 } catch (e) {
1069 if (callback) { callback(result); }
1070 resolve(result);
1071 }
1072 }
1073 });
1074 });
1075}
1076
1077exports.diskLayout = diskLayout;