diskutil
Version:
diskutil tool for MacOS
642 lines (573 loc) • 23.9 kB
JavaScript
/**
* @author service@ntfstool.com
* Copyright (c) 2020 ntfstool.com
* Copyright (c) 2020 alfw.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the MIT General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* MIT General Public License for more details.
*
* You should have received a copy of the MIT General Public License
* along with this program (in the main directory of the NTFS Tool
* distribution in the file COPYING); if not, write to the service@ntfstool.com
*/
var exec = require('child_process').exec;
var fs = require("fs")
var reMountLock = [];//global lock
try{
var Debug = process.env.NODE_ENV === 'development' ? true : false;
}catch (e) {
console.error("process not undefined!");
var Debug = true;
}
const saveLog = require('electron-log');
const NodeCache = require( "node-cache" );
const myCache = new NodeCache();
const md5 = require('md5');
if(Debug){
saveLog.warn = console.warn;
saveLog.log = console.log;
saveLog.error = console.error;
}
/**
* getDiskInfo
* @param index
*/
function getDiskInfo(index) {
let disk_path = "/dev/" + index;
if(!fs.existsSync(disk_path)){
saveLog.error("getDiskInfo file not found",disk_path);
return;
}
return new Promise((resolve, reject) => {
var cache_key = "getDiskInfo_" + md5(disk_path);
var cache_value = myCache.get(cache_key);
if ( cache_value != undefined){
console.info("getDiskInfo[Cache]",cache_value);
resolve(cache_value)
return;
}
execShell("diskutil info " + disk_path).then(async (info) => {
try {
var infoArr = info.split("\n").map(item => {
return item.trim();
}).filter((item) => {
return item;
});
var infoArr2 = [];
for (let i in infoArr) {
infoArr[i] = infoArr[i].split(":").map(item => {
return item.trim();
});
infoArr2[infoArr[i][0]] = infoArr[i][1];
}
//Filter the key information
var infoArr3 = {
"volumename": "",
"mounted": "",
"mountpoint": "",
"typebundle": "",
"protocol": "",
"uuid": "",
"total_size": "",
"total_size_wei": "",
"used_size": "",
"used_size_wei": "",
"readonly": "",
"percentage": ""
};
var disk_dize;
var disk_size_wei;
for (let i in infoArr2) {
let key = i.toLowerCase().replace(/\s+/g, "");
if (key.indexOf("volumename") >= 0) {
infoArr3.volumename = infoArr2[i];
}
if (key.indexOf("mounted") >= 0) {
infoArr3.mounted = infoArr2[i].toLowerCase() == "yes" ? true : false;
}
if (key.indexOf("mountpoint") >= 0) {
infoArr3.mountpoint = infoArr2[i];
}
if (key.indexOf("filesystempersonality") >= 0) {
infoArr3.typebundle = infoArr2[i].toLowerCase();
}
if (key.indexOf("Type (Bundle)") >= 0) {
infoArr3.typebundle = infoArr2[i].toLowerCase();
}
if (key.indexOf("uuid") >= 0) {
infoArr3.uuid = infoArr2[i];
}
if (key.indexOf("protocol") >= 0) {
infoArr3.protocol = infoArr2[i];
}
if (key.indexOf("totalspace") >= 0) {
let total_size_arr = infoArr2[i].replace(/([^\(]*).*/, "$1").trim().split(" ").map(item => {
return item.trim();
});
infoArr3.total_size = Math.round(parseFloat(total_size_arr[0]) * 100) / 100;
infoArr3.total_size_wei = total_size_arr[1];
}
if (key.indexOf("disksize") >= 0) {
let total_size_arr = infoArr2[i].replace(/([^\(]*).*/, "$1").trim().split(" ").map(item => {
return item.trim();
});
disk_dize = Math.round(parseFloat(total_size_arr[0]) * 100) / 100;
disk_size_wei = total_size_arr[1];
}
if (key.indexOf("usedspace") >= 0) {
let used_size_arr = infoArr2[i].replace(/([^\(]*).*/, "$1").trim().split(" ").map(item => {
return item.trim();
});
infoArr3.used_size = Math.round(parseFloat(used_size_arr[0]) * 100) / 100;
infoArr3.used_size_wei = used_size_arr[1];
}
if (infoArr3.total_size > 0) {
infoArr3.percentage = Math.round((infoArr3.used_size / infoArr3.total_size * 100) * 100) / 100;
}
if (key.indexOf("read-onlyvolume") >= 0) {
infoArr3.readonly = infoArr2[i].toLowerCase() == "yes" ? true : false;
}
}
if (!infoArr3.total_size && disk_dize) {
infoArr3.total_size = disk_dize;
infoArr3.total_size_wei = disk_size_wei;
}
//If disk information has not been obtained
if ((!infoArr3.total_size || !infoArr3.used_size) && info.length > 20) {
var sizeData = formatDiskSize(info);
if (sizeData["total"]) {
infoArr3.total_size = sizeData["total"];
infoArr3.total_size_wei = sizeData["wei"];
}
if (!infoArr3.used_size && sizeData["used"]) {
infoArr3.used_size = sizeData["used"];
infoArr3.used_size_wei = sizeData["wei"];
}
if (!infoArr3.percentage && sizeData["percentage"]) {
infoArr3.percentage = sizeData["percentage"];
}
}
myCache.set( cache_key,infoArr3, 20);
resolve(infoArr3);
} catch (e) {
Debug && saveLog.error(e, "getDiskInfo");
}
})
})
}
function tryFindSpace(string,index){
if(!string) return -1;
var min = 0;
var max = string.length - 1;
if(index > max) return false;
while(true){
if(index <= 0){
return index + "@@";
}
if(string[index] == " "){
// saveLog.warn(index,"tryFindSpace index1")
return index + 1;
}
index--;
}
}
/**
* Get the disk list
* @returns {Promise<any>}
*/
function getDiskList(setDebug) {
if(setDebug === true){
Debug = true;
}
return new Promise((resolve, reject) => {
execShell(`diskutil list`).then(async (res) => {
var log_data = {};
try {
log_data.list0 = res;//print log
var disk_list = [];
let diskArr = res.split("/dev/disk").filter(function (s) {
return s.trim() ? true : false;
});
for (var key in diskArr) {
var NAME_IDX = 0;
var SIZE_IDX = 0;
var IDEN_IDX = 0;
var index_check_i = 0;
var diskArr2 = diskArr[key].split("\n").filter(function (s) {
s = s.trim();
if (s.indexOf("0:") !== 0) {
if (s.indexOf(":") >= 0) {
return true;
}
}
return false;
})
for (var i in diskArr2) {
if (diskArr2[i].indexOf("#:") >= 0) {
index_check_i = i;
NAME_IDX = diskArr2[i].indexOf("NAME");
SIZE_IDX = diskArr2[i].indexOf("SIZE") - 8;
IDEN_IDX = diskArr2[i].indexOf("IDEN");
}
if (index_check_i > 0 && i > index_check_i) {
var _name_index = tryFindSpace(diskArr2[i],NAME_IDX);
var _size_index = tryFindSpace(diskArr2[i],SIZE_IDX);
var _iden_index = tryFindSpace(diskArr2[i],IDEN_IDX);
var string = diskArr2[i];
var string_len = diskArr2[i].length;
//saveLog.warn({NAME_IDX,_name_index,SIZE_IDX,_size_index,IDEN_IDX,_iden_index,string,string_len})
diskArr2[i] = diskArr2[i].substring(0, _name_index) + "|" + diskArr2[i].substring(_name_index);
diskArr2[i] = diskArr2[i].substring(0, _size_index) + "|" + diskArr2[i].substring(_size_index);
diskArr2[i] = diskArr2[i].substring(0, _iden_index) + "|" + diskArr2[i].substring(_iden_index);
}
}
if(typeof log_data.list0_sub == "undefined"){
log_data.list0_sub = {};
}
log_data.list0_sub[key] = diskArr2;//print log
//'2 (external, physical):' parse
let disk_mount = "";
if (typeof diskArr2[0] != "undefined") {
disk_mount = diskArr2[0].replace(/.*\((.*)\).*/i, "$1").split(",").map(item => {
return item.trim()
});
}
// saveLog.log(diskArr2, "diskArr2");
for (var i = 2; i < diskArr2.length; i++) {
if (diskArr2[i]) {
var item = diskArr2[i].split("|").map(item => {
return item.trim()
})
let disk_map = {
disk_mount: disk_mount,
canPush: false,
type: "",
name: "",
size: "",
size_wei: "",
index: "",
info: [],
};
if (item.length != 4) {
saveLog.error("please check the format for diskutil list");
}
if (item.length < 4) {
alert("diskutil format error");
reject("diskutil format error")
return;
}
disk_map.type = item[0].split(":")[1].trim();
disk_map.name = item[1].trim();
disk_map.size = item[2].split(" ")[0].trim();
disk_map.size_wei = item[2].split(" ")[1].trim();
disk_map.index = item[3].trim();
disk_list.push(disk_map);
}
}
}
log_data.disk_list1 = disk_list;//print log
// saveLog.log(disk_list,"disk_list1");
disk_list = _ignore(disk_list);
disk_list = _checkPushable(disk_list);
let disk_list_group = _marktype(disk_list);
log_data.disk_list2 = disk_list;//print log
// saveLog.log(disk_list,"disk_list2");
//Update details
getDiskFullInfo(disk_list_group).then((diskList) => {
log_data.disk_list3_full_back = diskList;//print log
saveLog.log(log_data,"getDiskList package");
resolve(diskList)
}).catch((err) => {
saveLog.warn(log_data,"getDiskList catch");
reject(err)
});
} catch (e) {
Debug && saveLog.error(e, "getDiskList");
}
}).catch((e) => {
Debug && saveLog.error(e, "getDiskList");
reject(e)
})
})
}
/**
* execShell
* @param shell
* @returns {Promise<any>}
*/
function execShell(shell) {
return new Promise((resolve, reject) => {
try {
exec(shell, (error, stdout, stderr) => {
Debug && saveLog.log("diskutil execShell", {
code: shell,
stdout: stdout,
stderr: stderr,
})
if (stderr) {
reject(stdout + error);
return;
}
if (!stdout && stderr) {
stdout = stderr;
}
resolve(stdout, stderr)
});
} catch (e) {
Debug && saveLog.error(e, "execShell");
}
})
}
/**
* _ignore
* @param disk_list
* @returns {*}
*/
function _ignore(disk_list) {
return disk_list.filter(function (list) {
try {
if(typeof list.index == "undefined" || !list.index){
return false;
}
//APFS: Preboot Recovery VM ignore
if (typeof list.type != "undefined" && list.type.toLowerCase().indexOf("apfs") >= 0) {
if (typeof list.name != "undefined") {
if (list.name.toLowerCase().indexOf("preboot") == 0) {
return false;
}
if (list.name.toLowerCase().indexOf("recovery") == 0) {
return false;
}
if (list.name.toLowerCase().indexOf("vm") == 0) {
return false;
}
// Apple_APFS Container disk1 type
if (list.name.toLowerCase().indexOf("container") >= 0 && list.name.toLowerCase().indexOf("disk") >= 0) {
return false;
}
}
}
//EFI: efi Ignore
if (typeof list.type != "undefined" && list.type.toLowerCase().indexOf("efi") >= 0) {
if (typeof list.name != "undefined") {
if (list.name.toLowerCase().indexOf("efi") == 0) {
return false;
}
}
}
return true;
} catch (e) {
Debug && saveLog.error(e, "_ignore");
}
});
}
/**
* Is it possible to push
* @param disk_list
* @returns {*}
*/
function _checkPushable(disk_list) {
try {
for (var i in disk_list) {
if (disk_list[i]['disk_mount'][0].indexOf("image") >= 0 || disk_list[i]['disk_mount'][0].indexOf("ext") >= 0) {
disk_list[i]["canPush"] = true;
continue;
}
}
return disk_list;
} catch (e) {
Debug && saveLog.error(e, "_marktype");
}
}
/**
* show_type: image ext inner
* @param disk_list
* @returns {*}
*/
function _marktype(disk_list) {
try {
var disk_list_group = {
inner: [],
ext: [],
image: [],
};
for (var i in disk_list) {
if (disk_list[i]['type'].indexOf("APFS") >= 0) {
disk_list[i]["group"] = "inner";
disk_list_group.inner.push(disk_list[i]);
continue;
}
if (disk_list[i]['disk_mount'][0].indexOf("image") >= 0) {
disk_list[i]["group"] = "image";
disk_list_group.image.push(disk_list[i]);
continue;
}
//The rest are in ext mode
disk_list[i]["group"] = "ext";
disk_list_group.ext.push(disk_list[i]);
}
return disk_list_group;
} catch (e) {
Debug && saveLog.error(e, "_marktype");
}
}
/**
* getDiskFullInfo
* @param disklist
*/
async function getDiskFullInfo(disklist) {
try {
for (var key in disklist) {
for (var disk_index in disklist[key]) {
let info = await getDiskInfo(disklist[key][disk_index]["index"]);
disklist[key][disk_index]["info"] = info;
if (!disklist[key][disk_index]["name"] && info.mountpoint) {
disklist[key][disk_index]["name"] = info.mountpoint.replace(/\/Volumes\/(.*)/i, "$1");
}
}
}
return disklist;
} catch (e) {
Debug && saveLog.error(e, "getDiskFullInfo");
}
}
/**
* Analysis and filtering out disk data
* @param str
*/
function formatDiskSize(str) {
var data = _formatDiskSizeGb(str);
if (!data.total) {
var data = _formatDiskSizeMb(str);
}
// Debug && saveLog.log("formatDiskSize", {data, str});
return data;
}
function _formatDiskSizeGb(str) {
var data = str.split("\n");
//Get the possible data set
var matchData = [];
for (var key in data) {
if (data[key].trim().length > 10 && data[key].toLowerCase().indexOf("gb") >= 0) {
//Rule algorithm: 1.Remove all characters after gb 2.Backtrack 3.Gb truncates everything except. And digits 4.Count down 5 trim away.
var _match_value = data[key];
_match_value = _match_value.toLowerCase().replace(/\s+/g, "");//Remove all spaces, lower case
_match_value = _match_value.replace(/(.*[\d\.]*gb).*/i, "$1");//Remove all characters after gb
_match_value = _match_value.split("").reverse().join("");//flashback
_match_value = _match_value.replace(/(bg[\d.]*).*/g, "$1");
_match_value = _match_value.split("").reverse().join("").trim();//Flashback
_match_value = _match_value.replace("gb", "");//Remove gb string
if ((_match_value.lastIndexOf('.') + 1) == _match_value.length) {
// Trim off the last possible occurrence .
_match_value = _match_value.substring(0, _match_value.lastIndexOf('.') - 1);
}
if (typeof matchData[_match_value] != "undefined") {
matchData[_match_value] = matchData[_match_value] + "|" + data[key];
} else {
matchData[_match_value] = data[key];
}
matchData[_match_value] = matchData[_match_value].toLowerCase().replace(/\s+/g, "");
}
}
//Possible data set to filter out the exact number
var resData = {total: 0, used: 0, free: 0, percentage: 0, wei: "GB"};
for (var j in matchData) {
if (matchData[j].indexOf("total") >= 0 || matchData[j].indexOf("disksize") >= 0) {
//Possible keywords, get here
resData["total"] = formatSize(j);
}
if (matchData[j].indexOf("free") >= 0) {
//Possible keywords, get here
resData["free"] = formatSize(j);
}
if (matchData[j].indexOf("used") >= 0) {
//Possible keywords, get here
resData["used"] = formatSize(j);
}
}
if (resData["total"]) {
if (!resData["free"] && resData["used"] && resData["used"] <= resData["total"]) {
resData["free"] = formatSize(resData["total"] - resData["used"]);
}
if (!resData["used"] && resData["free"] && resData["free"] <= resData["total"]) {
resData["used"] = formatSize(resData["total"] - resData["free"]);
}
}
if (!resData["total"] && resData["used"] && resData["free"]) {
resData["total"] = formatSize(resData["used"] + resData["free"]);
}
if (!resData["percentage"] && resData["used"] && resData["total"]) {
resData["percentage"] = formatSize(resData["used"] / resData["total"] * 100);
}
return resData;
}
function _formatDiskSizeMb(str) {
var data = str.split("\n");
//Get the possible data set
var matchData = [];
for (var key in data) {
if (data[key].trim().length > 10 && data[key].toLowerCase().indexOf("mb") >= 0) {
//Rule algorithm: 1.Remove all characters after mb 2.Flashback 3.Except for. And numbers, all truncated after mb 4.Count down 5 trim away.
var _match_value = data[key];
_match_value = _match_value.toLowerCase().replace(/\s+/g, "");//Remove all spaces, lower case
_match_value = _match_value.replace(/(.*[\d\.]*mb).*/i, "$1");//Remove all characters after gb
_match_value = _match_value.split("").reverse().join("");//reverse
_match_value = _match_value.replace(/(bm[\d.]*).*/g, "$1");
_match_value = _match_value.split("").reverse().join("").trim();//reverse
_match_value = _match_value.replace("mb", "");
if ((_match_value.lastIndexOf('.') + 1) == _match_value.length) {
_match_value = _match_value.substring(0, _match_value.lastIndexOf('.') - 1);
}
if (typeof matchData[_match_value] != "undefined") {
matchData[_match_value] = matchData[_match_value] + "|" + data[key];
} else {
matchData[_match_value] = data[key];
}
matchData[_match_value] = matchData[_match_value].toLowerCase().replace(/\s+/g, "");//Remove all spaces, lower case
}
}
var resData = {total: 0, used: 0, free: 0, percentage: 0, wei: "MB"};
for (var j in matchData) {
if (matchData[j].indexOf("total") >= 0 || matchData[j].indexOf("disksize") >= 0) {
resData["total"] = formatSize(j);
}
if (matchData[j].indexOf("free") >= 0) {
resData["free"] = formatSize(j);
}
if (matchData[j].indexOf("used") >= 0) {
resData["used"] = formatSize(j);
}
}
if (resData["total"]) {
if (!resData["free"] && resData["used"] && resData["used"] <= resData["total"]) {
resData["free"] = formatSize(resData["total"] - resData["used"]);
}
if (!resData["used"] && resData["free"] && resData["free"] <= resData["total"]) {
resData["used"] = formatSize(resData["total"] - resData["free"]);
}
}
if (!resData["total"] && resData["used"] && resData["free"]) {
resData["total"] = formatSize(resData["used"] + resData["free"]);
}
if (!resData["percentage"] && resData["used"] && resData["total"]) {
resData["percentage"] = formatSize(resData["used"] / resData["total"] * 100);
}
return resData;
}
function formatSize(num) {
var res = Math.round(parseFloat(num) * 100) / 100;
if (isNaN(res)) {
res = 0;
}
return res;
}
exports.getDiskInfo = getDiskInfo;
exports.getDiskList = getDiskList;