feat(@embark/core): store IPC files in a dir within os.tmpdir()

This PR replaces #1202.

When embark is running on Linux and macOS, unix socket files are used for
geth's and embark's IPC files. A problem can arise if a DApp is in a deeply
nested directory structure such that character-length of the path to a socket
file exceeds the maximum supported length. See #450.

Also, if the DApp context is a Linux container running on a Windows Docker
host, and if the DApp is mounted from the host's file system, there is a
problem: unix socket files are incompatible with the Windows file system.

Solve both problems at once by storing a DApp's `.ipc` files in a directory
within `os.tmpdir()`. Introduce `ipcPath()` in `core/fs.js` and use a truncated
SHA-512 hash of the DApp's path in the name of the temporary directory created
for that purpose so that DApps won't collide (with an acceptably low
probability of collision).
This commit is contained in:
Michael Bradley, Jr 2018-12-20 21:27:59 -06:00 committed by Iuri Matias
parent 9c37f9738e
commit a91a4dd7c0
8 changed files with 53 additions and 33 deletions

View File

@ -148,6 +148,19 @@ function diagramPath() {
return anchoredPath(env.DIAGRAM_PATH, ...arguments);
}
function ipcPath(basename, usePipePathOnWindows = false) {
if (!(basename && typeof basename === 'string')) {
throw new TypeError('first argument must be a non-empty string');
}
if (process.platform === 'win32' && usePipePathOnWindows) {
return `\\\\.\\pipe\\${basename}`;
}
return utils.joinPath(
tmpDir(`embark-${utils.sha512(dappPath()).slice(0, 8)}`),
basename
);
}
function pkgPath() {
return anchoredPath(env.PKG_PATH, ...arguments);
}
@ -200,6 +213,7 @@ module.exports = {
existsSync,
ensureFileSync,
ensureDirSync,
ipcPath,
mkdirp,
mkdirpSync,
move,

View File

@ -1,12 +1,12 @@
const fs = require('./fs.js');
const fs = require('./fs');
const ipc = require('node-ipc');
const {parse, stringify} = require('flatted/cjs');
const utils = require('../utils/utils');
class IPC {
constructor(options) {
this.logger = options.logger;
this.socketPath = options.socketPath || fs.dappPath(".embark/embark.ipc");
this.socketPath = options.socketPath || fs.ipcPath('embark.ipc');
this.ipcRole = options.ipcRole;
ipc.config.silent = true;
this.connected = false;
@ -39,7 +39,7 @@ class IPC {
}
serve() {
fs.mkdirpSync(fs.dappPath(".embark"));
fs.mkdirpSync(utils.dirname(this.socketPath));
ipc.serve(this.socketPath, () => {});
ipc.server.start();

View File

@ -1,4 +1,3 @@
const ProcessState = {
Unstarted: 'unstarted',
Starting: 'starting',

View File

@ -1,5 +1,6 @@
const async = require('async');
const {spawn, exec} = require('child_process');
const {exec, spawn} = require('child_process');
const fs = require('../../core/fs');
const GethMiner = require('./miner');
const semver = require('semver');
const constants = require('../../constants');
@ -51,7 +52,7 @@ class GethClient {
needKeepAlive() {
// TODO: check version also (geth version < 1.8.15)
if (this.isDev) {
// Trigger regular txs due to a bug in geth (< 1.8.15) and stuck transactions in --dev mode.
// Trigger regular txs due to a bug in geth (< 1.8.15) and stuck transactions in --dev mode.
return true;
}
return false;
@ -79,6 +80,8 @@ class GethClient {
cmd.push("--verbosity=" + config.verbosity);
}
cmd.push(`--ipcpath=${fs.ipcPath('geth.ipc', true)}`);
return cmd;
}

View File

@ -1,4 +1,5 @@
const async = require('async');
const fs = require('../../core/fs');
const NetcatClient = require('netcat/client');
//Constants
@ -43,14 +44,7 @@ class GethMiner {
}
}
const isWin = process.platform === "win32";
let ipcPath;
if (isWin) {
ipcPath = '\\\\.\\pipe\\geth.ipc';
} else {
ipcPath = this.datadir + '/geth.ipc';
}
const ipcPath = fs.ipcPath('geth.ipc', true);
this.client = new NetcatClient();
this.client.unixSocket(ipcPath)

View File

@ -1,30 +1,29 @@
const {execSync} = require("child_process");
const {hostname} = require("os");
const isDocker = (() => {
let isDockerProcess;
const hostname = require("os").hostname();
const pattern = new RegExp(
"[0-9]+\:[a-z_-]+\:\/docker\/" + hostname + "[0-9a-z]+", "i",
);
// assumption: an Embark container is always a Linux Docker container, though
// the Docker host may be Linux, macOS, or Windows
if (process.platform !== "linux") { return false; }
try {
isDockerProcess = require("child_process")
.execSync(
"cat /proc/self/cgroup",
{stdio: ["ignore", "pipe", "ignore"]},
)
.toString().match(pattern) !== null;
return (
new RegExp(`[0-9]+\:[a-z_-]+\:\/docker\/${hostname()}[0-9a-z]+`, "i")
).test(
execSync(
"cat /proc/self/cgroup",
{stdio: ["ignore", "pipe", "ignore"]},
).toString(),
);
} catch (e) {
isDockerProcess = false;
return false;
}
return isDockerProcess;
})();
const defaultHost = isDocker ? "0.0.0.0" : "localhost";
// when we"re runing in Docker, we can expect (generally, in a development
// when we're runing in Docker, we can expect (generally, in a development
// scenario) that the user would like to connect to the service in the
// container via the **host"s** loopback address, so this helper can be used to
// container via the **host's** loopback address, so this helper can be used to
// swap 0.0.0.0 for localhost in code/messages that pertain to client-side
function canonicalHost(host: string): string {
return isDocker && host === "0.0.0.0" ? "localhost" : host;

View File

@ -390,6 +390,15 @@ function sha3(arg) {
return Web3.utils.sha3(arg);
}
function sha512(arg) {
if (typeof arg !== 'string') {
throw new TypeError('argument must be a string');
}
const crypto = require('crypto');
const hash = crypto.createHash('sha512');
return hash.update(arg).digest('hex');
}
function soliditySha3(arg) {
const Web3 = require('web3');
return Web3.utils.soliditySha3(arg);
@ -640,6 +649,7 @@ module.exports = {
getExternalContractUrl,
toChecksumAddress,
sha3,
sha512,
soliditySha3,
normalizeInput,
buildUrl,

View File

@ -31,6 +31,7 @@ describe('fs', () => {
'dappPath',
'diagramPath',
'embarkPath',
'ipcPath',
'pkgPath',
'tmpDir'
];