Fix spawn() failure on Windows when paths contain spaces

This commit is contained in:
2020-02-11 17:19:30 -06:00
parent c7ca05d43c
commit bd17c1429d
3 changed files with 59 additions and 35 deletions

View File

@@ -3,6 +3,7 @@
* CentOS 8 support * CentOS 8 support
* Support remote send and receive timeouts * Support remote send and receive timeouts
* Support WinFSP mount manager * Support WinFSP mount manager
* Fix `spawn()` failure on Windows when paths contain spaces
## 1.1.2 ## 1.1.2
* Style changes * Style changes

View File

@@ -5,14 +5,15 @@
"author": "scott.e.graves@protonmail.com", "author": "scott.e.graves@protonmail.com",
"description": "GUI for Repertory - Repertory allows you to mount Sia and/or ScPrime blockchain storage solutions via FUSE on Linux/OS X or via WinFSP on Windows.", "description": "GUI for Repertory - Repertory allows you to mount Sia and/or ScPrime blockchain storage solutions via FUSE on Linux/OS X or via WinFSP on Windows.",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.26", "@fortawesome/fontawesome-svg-core": "^1.2.27",
"@fortawesome/free-solid-svg-icons": "^5.12.0", "@fortawesome/free-solid-svg-icons": "^5.12.1",
"@fortawesome/react-fontawesome": "^0.1.8", "@fortawesome/react-fontawesome": "^0.1.8",
"@reduxjs/toolkit": "^1.2.3", "@reduxjs/toolkit": "^1.2.4",
"auto-launch": "^5.0.5", "auto-launch": "^5.0.5",
"axios": "^0.19.2", "axios": "^0.19.2",
"devtron": "^1.4.0", "devtron": "^1.4.0",
"electron-debug": "^3.0.1", "electron-debug": "^3.0.1",
"electron-log": "^4.0.6",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"node-schedule": "^1.3.2", "node-schedule": "^1.3.2",
"randomstring": "^1.1.5", "randomstring": "^1.1.5",
@@ -21,10 +22,10 @@
"react-loader-spinner": "^3.1.5", "react-loader-spinner": "^3.1.5",
"react-redux": "^7.1.3", "react-redux": "^7.1.3",
"react-scripts": "3.3.1", "react-scripts": "3.3.1",
"react-tooltip": "^3.11.4", "react-tooltip": "^4.0.3",
"redux": "^4.0.5", "redux": "^4.0.5",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
"unzipper": "^0.10.7", "unzipper": "^0.10.8",
"winreg": "^1.2.4" "winreg": "^1.2.4"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -8,12 +8,15 @@ const spawn = require('child_process').spawn;
const Constants = require('./constants'); const Constants = require('./constants');
const RandomString = require('randomstring'); const RandomString = require('randomstring');
const _executeProcess = (command, args=[]) => { const _executeProcess = (command, working, args=[]) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const processOptions = { let processOptions = {
detached: true, detached: true,
shell: false, shell: false,
}; };
if (working) {
processOptions.cwd = working;
}
const process = new spawn(command, args, processOptions); const process = new spawn(command, args, processOptions);
const pid = process.pid; const pid = process.pid;
@@ -30,12 +33,16 @@ const _executeProcess = (command, args=[]) => {
}); });
}; };
const _execProcessGetOutput = (cmd, args) => { const _execProcessGetOutput = (cmd, working, args) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const proc = spawn(cmd, args, { let processOptions = {
env: process.env, env: process.env,
stdio: ['ignore', 'pipe', 'pipe'] stdio: ['ignore', 'pipe', 'pipe']
}); };
if (working) {
processOptions.cwd = working;
}
const proc = spawn(cmd, args, processOptions);
let output; let output;
proc.stdout.on('data', data => { proc.stdout.on('data', data => {
@@ -74,7 +81,10 @@ const _getDefaultRepertoryArgs = (provider, remote) => {
}; };
const _getRepertoryExec = version => { const _getRepertoryExec = version => {
return path.join(_getDataDirectory(), version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory'); return {
cmd: (os.platform() === 'win32') ? 'repertory.exe' : 'repertory',
working: path.join(_getDataDirectory(), version),
};
}; };
const _resolvePath = str => { const _resolvePath = str => {
@@ -97,17 +107,17 @@ const _tryParse = (j, def) => {
module.exports.checkDaemonVersion = (version, provider) => { module.exports.checkDaemonVersion = (version, provider) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const repertoryExec = _getRepertoryExec(version);
const processOptions = { const processOptions = {
cwd: repertoryExec.working,
detached: true, detached: true,
shell: false, shell: false,
windowsHide: true, windowsHide: true,
}; };
const command = _getRepertoryExec(version);
const args = _getDefaultRepertoryArgs(provider, false); const args = _getDefaultRepertoryArgs(provider, false);
args.push('-cv'); args.push('-cv');
const process = new spawn(repertoryExec.cmd, args, processOptions);
const process = new spawn(command, args, processOptions);
process.on('error', err => { process.on('error', err => {
reject(err); reject(err);
@@ -159,17 +169,18 @@ module.exports.detectRepertoryMounts = (version, providerList) => {
resolve(mountState); resolve(mountState);
} else { } else {
const provider = providerList[index]; const provider = providerList[index];
const repertoryExec = _getRepertoryExec(version);
const processOptions = { const processOptions = {
cwd: repertoryExec.working,
detached: true, detached: true,
shell: false, shell: false,
windowsHide: true, windowsHide: true,
}; };
const command = _getRepertoryExec(version);
const args = _getDefaultRepertoryArgs(provider, !Constants.PROVIDER_LIST.includes(provider)); const args = _getDefaultRepertoryArgs(provider, !Constants.PROVIDER_LIST.includes(provider));
args.push('-status'); args.push('-status');
const process = new spawn(command, args, processOptions); const process = new spawn(repertoryExec.cmd, args, processOptions);
let result = ''; let result = '';
process.on('error', (err) => { process.on('error', (err) => {
@@ -277,10 +288,15 @@ module.exports.executeAndWait = (command, ignoreResult) => {
module.exports.executeAsync = (command, args=[]) => { module.exports.executeAsync = (command, args=[]) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const launchProcess = (count, timeout) => { const launchProcess = (count, timeout) => {
const processOptions = { const working = path.dirname(command);
command = path.basename(command);
let processOptions = {
detached: true, detached: true,
shell: false, shell: false,
}; };
if (working) {
processOptions.cwd = working;
}
const process = new spawn(command, args, processOptions); const process = new spawn(command, args, processOptions);
const pid = process.pid; const pid = process.pid;
@@ -345,13 +361,14 @@ module.exports.executeScript = script => {
module.exports.executeMount = (version, provider, remote, location, noConsoleSupported, exitCallback) => { module.exports.executeMount = (version, provider, remote, location, noConsoleSupported, exitCallback) => {
return new Promise((resolve) => { return new Promise((resolve) => {
const repertoryExec = _getRepertoryExec(version);
const processOptions = { const processOptions = {
cwd: repertoryExec.working,
detached: false, detached: false,
shell: os.platform() !== 'darwin', shell: os.platform() !== 'darwin',
stdio: 'ignore', stdio: 'ignore',
}; };
const command = _getRepertoryExec(version);
const args = _getDefaultRepertoryArgs(provider, remote); const args = _getDefaultRepertoryArgs(provider, remote);
if ((os.platform() === 'linux') || (os.platform() === 'darwin')) { if ((os.platform() === 'linux') || (os.platform() === 'darwin')) {
@@ -366,7 +383,7 @@ module.exports.executeMount = (version, provider, remote, location, noConsoleSup
} }
args.push(location); args.push(location);
let process = new spawn(command, args, processOptions); let process = new spawn(repertoryExec.cmd, args, processOptions);
const pid = process.pid; const pid = process.pid;
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
@@ -387,17 +404,18 @@ module.exports.executeMount = (version, provider, remote, location, noConsoleSup
module.exports.getConfig = (version, provider, remote) => { module.exports.getConfig = (version, provider, remote) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const repertoryExec = _getRepertoryExec(version);
const processOptions = { const processOptions = {
cwd: repertoryExec.working,
detached: true, detached: true,
shell: false, shell: false,
windowsHide: true, windowsHide: true,
}; };
const command = _getRepertoryExec(version);
const args = _getDefaultRepertoryArgs(provider, remote); const args = _getDefaultRepertoryArgs(provider, remote);
args.push('-dc'); args.push('-dc');
const process = new spawn(command, args, processOptions); const process = new spawn(repertoryExec.cmd, args, processOptions);
let result = ''; let result = '';
process.on('error', (err) => { process.on('error', (err) => {
@@ -430,17 +448,18 @@ module.exports.getConfig = (version, provider, remote) => {
module.exports.getConfigTemplate = (version, provider, remote) => { module.exports.getConfigTemplate = (version, provider, remote) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const repertoryExec = _getRepertoryExec(version);
const processOptions = { const processOptions = {
cwd: repertoryExec.working,
detached: true, detached: true,
shell: false, shell: false,
windowsHide: true, windowsHide: true,
}; };
const command = _getRepertoryExec(version);
const args = _getDefaultRepertoryArgs(provider, remote); const args = _getDefaultRepertoryArgs(provider, remote);
args.push('-gt'); args.push('-gt');
const process = new spawn(command, args, processOptions); const process = new spawn(repertoryExec.cmd, args, processOptions);
let result = ''; let result = '';
process.on('error', (err) => { process.on('error', (err) => {
@@ -579,7 +598,7 @@ module.exports.performWindowsUninstall = names => {
} else { } else {
const cmd = path.join(process.env.windir, 'system32', 'reg.exe'); const cmd = path.join(process.env.windir, 'system32', 'reg.exe');
const args = ["QUERY", "HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"]; const args = ["QUERY", "HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"];
_execProcessGetOutput(cmd, args) _execProcessGetOutput(cmd, null, args)
.then(lines => { .then(lines => {
const parseLine = index => { const parseLine = index => {
if (index < lines.length) { if (index < lines.length) {
@@ -591,13 +610,13 @@ module.exports.performWindowsUninstall = names => {
args2.push('DisplayName'); args2.push('DisplayName');
args2.push('/t'); args2.push('/t');
args2.push('REG_SZ'); args2.push('REG_SZ');
_execProcessGetOutput(cmd, args2) _execProcessGetOutput(cmd, null, args2)
.then(lines => { .then(lines => {
const value = lines[2].trim().substr(args2[3].length).trim().substr(6).trim(); const value = lines[2].trim().substr(args2[3].length).trim().substr(6).trim();
if (names.includes(value)) { if (names.includes(value)) {
const items = line.split('\\'); const items = line.split('\\');
const productCode = items[items.length - 1]; const productCode = items[items.length - 1];
_executeProcess('msiexec.exe', ['/x', productCode, '/norestart']) _executeProcess('msiexec.exe', null,['/x', productCode, '/norestart'])
.then(code => { .then(code => {
if ((code === 0) || (code === 3010) || (code === 1641)) { if ((code === 0) || (code === 3010) || (code === 1641)) {
resolve(true); resolve(true);
@@ -651,19 +670,20 @@ module.exports.resolvePath = _resolvePath;
module.exports.setConfigValue = (name, value, provider, remote, version) => { module.exports.setConfigValue = (name, value, provider, remote, version) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const repertoryExec = _getRepertoryExec(version);
const processOptions = { const processOptions = {
cwd: repertoryExec.working,
detached: true, detached: true,
shell: false, shell: false,
windowsHide: true, windowsHide: true,
}; };
const command = _getRepertoryExec(version);
const args = _getDefaultRepertoryArgs(provider, remote); const args = _getDefaultRepertoryArgs(provider, remote);
args.push('-set'); args.push('-set');
args.push(name); args.push(name);
args.push(value); args.push(value);
const process = new spawn(command, args, processOptions); const process = new spawn(repertoryExec.cmd, args, processOptions);
process.on('error', (err) => { process.on('error', (err) => {
reject(err); reject(err);
@@ -679,17 +699,18 @@ module.exports.setConfigValue = (name, value, provider, remote, version) => {
module.exports.stopMountProcess = (version, provider, remote) => { module.exports.stopMountProcess = (version, provider, remote) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const repertoryExec = _getRepertoryExec(version);
const processOptions = { const processOptions = {
cwd: repertoryExec.working,
detached: os.platform() === 'darwin', detached: os.platform() === 'darwin',
shell: os.platform() !== 'darwin', shell: os.platform() !== 'darwin',
windowsHide: true, windowsHide: true,
}; };
const command = _getRepertoryExec(version);
const args = _getDefaultRepertoryArgs(provider, remote); const args = _getDefaultRepertoryArgs(provider, remote);
args.push('-unmount'); args.push('-unmount');
const process = new spawn(command, args, processOptions); const process = new spawn(repertoryExec.cmd, args, processOptions);
const pid = process.pid; const pid = process.pid;
process.on('error', (err) => { process.on('error', (err) => {
reject(err); reject(err);
@@ -708,24 +729,25 @@ module.exports.stopMountProcess = (version, provider, remote) => {
}; };
module.exports.stopMountProcessSync = (version, provider, remote) => { module.exports.stopMountProcessSync = (version, provider, remote) => {
const repertoryExec = _getRepertoryExec(version);
const processOptions = { const processOptions = {
cwd: repertoryExec.working,
detached: true, detached: true,
shell: os.platform() !== 'darwin', shell: os.platform() !== 'darwin',
windowsHide: true, windowsHide: true,
}; };
const command = _getRepertoryExec(version);
const args = _getDefaultRepertoryArgs(provider, remote); const args = _getDefaultRepertoryArgs(provider, remote);
args.push('-unmount'); args.push('-unmount');
const process = new spawn(command, args, processOptions); const process = new spawn(repertoryExec.cmd, args, processOptions);
process.unref(); process.unref();
}; };
module.exports.testRepertoryBinary = version => { module.exports.testRepertoryBinary = version => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const command = _getRepertoryExec(version); const repertoryExec = _getRepertoryExec(version);
_executeProcess(command, ['-dc']) _executeProcess(repertoryExec.cmd, repertoryExec.working, ['-dc'])
.then(code => { .then(code => {
if (code === 0) { if (code === 0) {
resolve(); resolve();
@@ -809,4 +831,4 @@ module.exports.verifySignature = (file, signatureFile, publicKeyFile) => {
reject(Error('Platform not supported: ' + os.platform())) reject(Error('Platform not supported: ' + os.platform()))
} }
}); });
}; };