stop using wmic, as it's deprecated #776 #675
Massimo Melina committed
Nov 2, 2024 at 23:21 UTC
fc46293b1f2477e5b40fad13479c46257baf40e8
2 files changed
+19
-44
package.json
+1
@@ -70,6 +70,7 @@
70
]
71
},
72
"dependencies": {
73
+ "@gregoranders/csv": "^0.0.13",
74
"@koa/router": "^13.0.1",
75
"@node-rs/crc32": "^1.10.3",
76
"@rejetto/kvstorage": "^0.12.2",
src/util-os.ts
+18
-44
@@ -1,23 +1,19 @@
1
-import { dirname, resolve } from 'path'
2
-import { existsSync } from 'fs'
3
-import { exec, ExecOptions, execSync, spawnSync } from 'child_process'
4
-import { exists, onlyTruthy, prefix, splitAt } from './misc'
5
-import _ from 'lodash'
1
+import { dirname } from 'path'
2
+import { existsSync, statfsSync } from 'fs'
3
+import { exec, ExecOptions } from 'child_process'
4
+import { isWindowsDrive, onlyTruthy, promiseBestEffort } from './misc'
5
+import Parser from '@gregoranders/csv';
6
import { pid } from 'node:process'
7
import { promisify } from 'util'
8
import { IS_WINDOWS } from './const'
9
10
const DF_TIMEOUT = 2000
11
-const LOGICALDISK_TIMEOUT = DF_TIMEOUT
11
13
-// not using statfsSync because it's not available in node 18.5.0 (latest version with pkg)
12
export function getDiskSpaceSync(path: string) {
15
- if (IS_WINDOWS)
16
- return parseLogicaldisk(execSync(makeLogicaldisk(path), { timeout: LOGICALDISK_TIMEOUT }).toString())[0]
13
while (path && !existsSync(path))
14
path = dirname(path)
19
- try { return parseDfResult(spawnSync('df', ['-k', path], { timeout: DF_TIMEOUT }).stdout.toString())[0] }
20
- catch(e: any) { throw parseDfResult(e) }
15
+ const res = statfsSync(path)
16
+ return { free: res.bavail * res.bsize, total: res.blocks * res.bsize, name: path }
17
}
18
19
export function bashEscape(par: string) {
@@ -29,17 +25,16 @@ export function cmdEscape(par: string) {
25
}
26
27
export async function getDiskSpace(path: string) {
32
- if (IS_WINDOWS)
33
- return parseLogicaldisk(await runCmd(makeLogicaldisk(path), [], { timeout: LOGICALDISK_TIMEOUT }))[0]
34
- while (path && !await exists(path))
28
+ while (path && !isWindowsDrive(path) && !existsSync(path))
29
path = dirname(path)
36
- return parseDfResult(await promisify(exec)(`df -k`, { timeout: DF_TIMEOUT }).then(x => x.stdout, e => e))[0]
30
+ const res = statfsSync(path)
31
+ return { free: res.bavail * res.bsize, total: res.blocks * res.bsize, name: path }
32
}
33
34
export async function getDiskSpaces(): Promise<{ name: string, free: number, total: number, description?: string }[]> {
35
if (IS_WINDOWS) {
41
- const drives = await getDrives() // since network-drives can hang 'wmic' for many seconds checking disk space (issue#648), and a single timeout would make whole operation fail, so we fork the job on each drive
42
- return onlyTruthy(await Promise.all(drives.map(getDiskSpace)))
36
+ const drives = await getDrives()
37
+ return onlyTruthy(await promiseBestEffort(drives.map(getDiskSpace)))
38
}
39
return parseDfResult(await promisify(exec)(`df -k`, { timeout: DF_TIMEOUT }).then(x => x.stdout, e => e))
40
}
@@ -64,8 +59,8 @@ function parseDfResult(result: string | Error) {
59
}
60
61
export async function getDrives() {
67
- const stdout = await runCmd('wmic logicaldisk get name')
68
- return stdout.split('\n').slice(1).map(x => x.trim()).filter(Boolean)
62
+ const res = await runCmd('fsutil fsinfo drives') // example output: `Drives: C:\ D:\ Z:\`
63
+ return res.trim().replaceAll('\\', '').split(' ').slice(1)
64
}
65
66
// execute win32 shell commands
@@ -76,8 +71,10 @@ export async function runCmd(cmd: string, args: string[] = [], options: ExecOpti
71
}
72
73
async function getWindowsServicePids() {
79
- const res = await runCmd(`wmic service get ProcessId`)
80
- return _.uniq(res.split('\n').slice(1).map(x => Number(x.trim())))
74
+ const res = await runCmd('tasklist /svc /fo csv')
75
+ const parsed = new Parser().parse(res)
76
+ const no = parsed?.[1]?.[2]
77
+ return parsed.slice(2).filter(x => x[2] !== no).map(x => Number(x[1]))
78
}
79
80
export const RUNNING_AS_SERVICE = IS_WINDOWS && getWindowsServicePids().then(x => {
@@ -88,26 +85,3 @@ export const RUNNING_AS_SERVICE = IS_WINDOWS && getWindowsServicePids().then(x =
85
console.log("couldn't determine if we are running as a service")
86
console.debug(e)
87
})
91
-
92
-function parseKeyValueObjects<T extends string>(all: string, keySep='=', lineSep='\n', objectSep=/\n\n+/) {
93
- return all.split(objectSep).map(obj =>
94
- Object.fromEntries(obj.split(lineSep).map(kv => splitAt(keySep, kv))) ) as { [k in T]: string }[]
95
-}
96
-
97
-const wmicFields = ['Size','FreeSpace','Name','Description'] as const
98
-
99
-function makeLogicaldisk(path='') {
100
- const drive = resolve(path).slice(0, 2).toUpperCase()
101
- if (!drive.match(/^(|\w:)$/)) throw 'invalid-path'
102
- return `wmic logicaldisk ${prefix(`where "DeviceID = '`, drive, `'"`)} get ${wmicFields.join()} /format:list`
103
-}
104
-
105
-function parseLogicaldisk(out: string) {
106
- const objs = parseKeyValueObjects<typeof wmicFields[number]>(out.replace(/\r/g, ''))
107
- return onlyTruthy(objs.map(x => x.Size && {
108
- total: Number(x.Size),
109
- free: Number(x.FreeSpace),
110
- name: x.Name,
111
- description: x.Description
112
- }))
113
-}