1'use strict'
2
3const cp = require('child_process')
4const path = require('path')
5const { openSync, closeSync } = require('graceful-fs')
6const log = require('./log')
7
8const execFile = async (...args) => new Promise((resolve) => {
9  const child = cp.execFile(...args, (...a) => resolve(a))
10  child.stdin.end()
11})
12
13async function regGetValue (key, value, addOpts) {
14  const outReValue = value.replace(/\W/g, '.')
15  const outRe = new RegExp(`^\\s+${outReValue}\\s+REG_\\w+\\s+(\\S.*)$`, 'im')
16  const reg = path.join(process.env.SystemRoot, 'System32', 'reg.exe')
17  const regArgs = ['query', key, '/v', value].concat(addOpts)
18
19  log.silly('reg', 'running', reg, regArgs)
20  const [err, stdout, stderr] = await execFile(reg, regArgs, { encoding: 'utf8' })
21
22  log.silly('reg', 'reg.exe stdout = %j', stdout)
23  if (err || stderr.trim() !== '') {
24    log.silly('reg', 'reg.exe err = %j', err && (err.stack || err))
25    log.silly('reg', 'reg.exe stderr = %j', stderr)
26    if (err) {
27      throw err
28    }
29    throw new Error(stderr)
30  }
31
32  const result = outRe.exec(stdout)
33  if (!result) {
34    log.silly('reg', 'error parsing stdout')
35    throw new Error('Could not parse output of reg.exe')
36  }
37
38  log.silly('reg', 'found: %j', result[1])
39  return result[1]
40}
41
42async function regSearchKeys (keys, value, addOpts) {
43  for (const key of keys) {
44    try {
45      return await regGetValue(key, value, addOpts)
46    } catch {
47      continue
48    }
49  }
50}
51
52/**
53 * Returns the first file or directory from an array of candidates that is
54 * readable by the current user, or undefined if none of the candidates are
55 * readable.
56 */
57function findAccessibleSync (logprefix, dir, candidates) {
58  for (let next = 0; next < candidates.length; next++) {
59    const candidate = path.resolve(dir, candidates[next])
60    let fd
61    try {
62      fd = openSync(candidate, 'r')
63    } catch (e) {
64      // this candidate was not found or not readable, do nothing
65      log.silly(logprefix, 'Could not open %s: %s', candidate, e.message)
66      continue
67    }
68    closeSync(fd)
69    log.silly(logprefix, 'Found readable %s', candidate)
70    return candidate
71  }
72
73  return undefined
74}
75
76module.exports = {
77  execFile,
78  regGetValue,
79  regSearchKeys,
80  findAccessibleSync
81}
82