/* eslint-disable curly */
/* eslint-disable new-cap */
/* eslint-disable no-buffer-constructor */
/* eslint-disable no-multi-spaces */
/* eslint-disable no-underscore-dangle */
/* eslint-disable prefer-rest-params */
/* eslint-disable prefer-spread */

/* global EXECPATH_FD */
/* global PAYLOAD_POSITION */
/* global PAYLOAD_SIZE */
/* global REQUIRE_COMMON */
/* global VIRTUAL_FILESYSTEM */
/* global DEFAULT_ENTRYPOINT */

'use strict';

var common = {};
REQUIRE_COMMON(common);

var STORE_BLOB = common.STORE_BLOB;
var STORE_CONTENT = common.STORE_CONTENT;
var STORE_LINKS = common.STORE_LINKS;
var STORE_STAT = common.STORE_STAT;

var isRootPath = common.isRootPath;
var normalizePath = common.normalizePath;
var insideSnapshot = common.insideSnapshot;
var stripSnapshot = common.stripSnapshot;
var removeUplevels = common.removeUplevels;

var FLAG_ENABLE_PROJECT = false;
var NODE_VERSION_MAJOR = process.version.match(/^v(\d+)/)[1] | 0;

// /////////////////////////////////////////////////////////////////
// ENTRYPOINT //////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////

// set ENTRYPOINT and ARGV0 here because
// they can be altered during process run
var ARGV0 = process.argv[0];
var EXECPATH = process.execPath;
var ENTRYPOINT = process.argv[1];

if (process.env.PKG_EXECPATH === 'PKG_INVOKE_NODEJS') {
  return { undoPatch: true };
}

if (NODE_VERSION_MAJOR < 12 || require('worker_threads').isMainThread) {
  if (process.argv[1] !== 'PKG_DUMMY_ENTRYPOINT') {
    // expand once patchless is introduced, that
    // will obviously lack any work in node_main.cc
    throw new Error('PKG_DUMMY_ENTRYPOINT EXPECTED');
  }
}

if (process.env.PKG_EXECPATH === EXECPATH) {
  process.argv.splice(1, 1);

  if (process.argv[1] && process.argv[1] !== '-') {
    // https://github.com/nodejs/node/blob/1a96d83a223ff9f05f7d942fb84440d323f7b596/lib/internal/bootstrap/node.js#L269
    process.argv[1] = require('path').resolve(process.argv[1]);
  }
} else {
  process.argv[1] = DEFAULT_ENTRYPOINT;
}

ENTRYPOINT = process.argv[1];
delete process.env.PKG_EXECPATH;

// /////////////////////////////////////////////////////////////////
// EXECSTAT ////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////

var EXECSTAT = require('fs').statSync(EXECPATH);
EXECSTAT.atimeMs = EXECSTAT.atime.getTime();
EXECSTAT.mtimeMs = EXECSTAT.mtime.getTime();
EXECSTAT.ctimeMs = EXECSTAT.ctime.getTime();
EXECSTAT.birthtimeMs = EXECSTAT.birthtime.getTime();

// /////////////////////////////////////////////////////////////////
// MOUNTPOINTS /////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////

var mountpoints = [];

function insideMountpoint (f) {
  if (!insideSnapshot(f)) return null;
  var file = normalizePath(f);
  var found = mountpoints.map(function (mountpoint) {
    var interior = mountpoint.interior;
    var exterior = mountpoint.exterior;
    if (interior === file) return exterior;
    var left = interior + require('path').sep;
    if (file.slice(0, left.length) !== left) return null;
    return exterior + file.slice(left.length - 1);
  }).filter(function (result) {
    return result;
  });
  if (found.length >= 2) throw new Error('UNEXPECTED-00');
  if (found.length === 0) return null;
  return found[0];
}

function readdirMountpoints (path) {
  return mountpoints.map(function (mountpoint) {
    return mountpoint.interior;
  }).filter(function (interior) {
    return require('path').dirname(interior) === path;
  }).map(function (interior) {
    return require('path').basename(interior);
  });
}

function translate (f) {
  var result = insideMountpoint(f);
  if (!result) throw new Error('UNEXPECTED-05');
  return result;
}

function cloneArgs (args_) {
  return Array.prototype.slice.call(args_);
}

function translateNth (args_, index, f) {
  var args = cloneArgs(args_);
  args[index] = translate(f);
  return args;
}

function createMountpoint (interior, exterior) {
  // TODO validate
  mountpoints.push({ interior: interior, exterior: exterior });
}

/*

// TODO move to some test

createMountpoint("d:\\snapshot\\countly\\plugins-ext", "d:\\deploy\\countly\\v16.02\\plugins-ext");

console.log(insideMountpoint("d:\\snapshot"));
console.log(insideMountpoint("d:\\snapshot\\"));
console.log(insideMountpoint("d:\\snapshot\\countly"));
console.log(insideMountpoint("d:\\snapshot\\countly\\"));
console.log(insideMountpoint("d:\\snapshot\\countly\\plugins-ext"));
console.log(insideMountpoint("d:\\snapshot\\countly\\plugins-ext\\"));
console.log(insideMountpoint("d:\\snapshot\\countly\\plugins-ext\\1234"));

console.log(translate("d:\\snapshot\\countly\\plugins-ext"));
console.log(translate("d:\\snapshot\\countly\\plugins-ext\\"));
console.log(translate("d:\\snapshot\\countly\\plugins-ext\\1234"));

console.log(translateNth([], 0, "d:\\snapshot\\countly\\plugins-ext"));
console.log(translateNth([], 0, "d:\\snapshot\\countly\\plugins-ext\\"));
console.log(translateNth([], 0, "d:\\snapshot\\countly\\plugins-ext\\1234"));

console.log(translateNth(["", "r+"], 0, "d:\\snapshot\\countly\\plugins-ext"));
console.log(translateNth(["", "rw"], 0, "d:\\snapshot\\countly\\plugins-ext\\"));
console.log(translateNth(["", "a+"], 0, "d:\\snapshot\\countly\\plugins-ext\\1234"));
*/

// /////////////////////////////////////////////////////////////////
// PROJECT /////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////

function projectToFilesystem (f) {
  var xpdn = require('path').dirname(
    EXECPATH
  );

  var relatives = [];
  relatives.push(
    removeUplevels(
      require('path').relative(
        require('path').dirname(
          DEFAULT_ENTRYPOINT
        ), f
      )
    )
  );

  if (relatives[0].slice(0, 'node_modules'.length) === 'node_modules') {
    // one more relative without starting 'node_modules'
    relatives.push(relatives[0].slice('node_modules'.length + 1));
  }

  var uplevels = [];
  var maxUplevels = xpdn.split(require('path').sep).length;
  for (var i = 0, u = ''; i < maxUplevels; i += 1) {
    uplevels.push(u);
    u += '/..';
  }

  var results = [];
  uplevels.forEach(function (uplevel) {
    relatives.forEach(function (relative) {
      results.push(require('path').join(
        xpdn,
        uplevel,
        relative
      ));
    });
  });
  return results;
}

function projectToNearby (f) {
  return require('path').join(
    require('path').dirname(
      EXECPATH
    ),
    require('path').basename(
      f
    )
  );
}

function findNativeAddonSyncFreeFromRequire (path) {
  if (!insideSnapshot(path)) throw new Error('UNEXPECTED-10');
  if (path.slice(-5) !== '.node') return null; // leveldown.node.js
  // check mearby first to prevent .node tampering
  var projector = projectToNearby(path);
  if (require('fs').existsSync(projector)) return projector;
  var projectors = projectToFilesystem(path);
  for (var i = 0; i < projectors.length; i += 1) {
    if (require('fs').existsSync(projectors[i])) return projectors[i];
  }
  return null;
}

function findNativeAddonSyncUnderRequire (path) {
  if (!FLAG_ENABLE_PROJECT) return null;
  return findNativeAddonSyncFreeFromRequire(path);
}

// /////////////////////////////////////////////////////////////////
// FLOW UTILS //////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////

function asap (cb) {
  process.nextTick(cb);
}

function dezalgo (cb) {
  if (!cb) return cb;

  var sync = true;
  asap(function () {
    sync = false;
  });

  return function zalgoSafe () {
    var args = arguments;
    if (sync) {
      asap(function () {
        cb.apply(undefined, args);
      });
    } else {
      cb.apply(undefined, args);
    }
  };
}

function rethrow (error, arg) {
  if (error) throw error;
  return arg;
}

// /////////////////////////////////////////////////////////////////
// PAYLOAD /////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////

if (typeof PAYLOAD_POSITION !== 'number' ||
    typeof PAYLOAD_SIZE !== 'number') {
  throw new Error('MUST HAVE PAYLOAD');
}

var readPayload = function (buffer, offset, length, position, callback) {
  require('fs').read(EXECPATH_FD,
    buffer, offset, length, PAYLOAD_POSITION + position, callback);
};

var readPayloadSync = function (buffer, offset, length, position) {
  return require('fs').readSync(EXECPATH_FD,
    buffer, offset, length, PAYLOAD_POSITION + position);
};

function payloadCopyUni (source, target, targetStart, sourceStart, sourceEnd, cb) {
  var cb2 = cb || rethrow;
  if (sourceStart >= source[1]) return cb2(null, 0);
  if (sourceEnd >= source[1]) sourceEnd = source[1];
  var payloadPos = source[0] + sourceStart;
  var targetPos = targetStart;
  var targetEnd = targetStart + sourceEnd - sourceStart;
  if (cb) {
    readPayload(target, targetPos, targetEnd - targetPos, payloadPos, cb);
  } else {
    return readPayloadSync(target, targetPos, targetEnd - targetPos, payloadPos);
  }
}

function payloadCopyMany (source, target, targetStart, sourceStart, cb) {
  var payloadPos = source[0] + sourceStart;
  var targetPos = targetStart;
  var targetEnd = targetStart + source[1] - sourceStart;
  readPayload(target, targetPos, targetEnd - targetPos, payloadPos, function (error, chunkSize) {
    if (error) return cb(error);
    sourceStart += chunkSize;
    targetPos += chunkSize;
    if (chunkSize !== 0 && targetPos < targetEnd) {
      payloadCopyMany(source, target, targetPos, sourceStart, cb);
    } else {
      return cb();
    }
  });
}

function payloadCopyManySync (source, target, targetStart, sourceStart) {
  var payloadPos = source[0] + sourceStart;
  var targetPos = targetStart;
  var targetEnd = targetStart + source[1] - sourceStart;
  var chunkSize;
  while (true) {
    chunkSize = readPayloadSync(target, targetPos, targetEnd - targetPos, payloadPos);
    payloadPos += chunkSize;
    targetPos += chunkSize;
    if (!(chunkSize !== 0 && targetPos < targetEnd)) break;
  }
}

function payloadFile (pointer, cb) {
  var target = Buffer.alloc(pointer[1]);
  payloadCopyMany(pointer, target, 0, 0, function (error) {
    if (error) return cb(error);
    cb(null, target);
  });
}

function payloadFileSync (pointer) {
  var target = Buffer.alloc(pointer[1]);
  payloadCopyManySync(pointer, target, 0, 0);
  return target;
}

// /////////////////////////////////////////////////////////////////
// SETUP PROCESS ///////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////

(function () {
  process.pkg = {};
  process.versions.pkg = '%VERSION%';
  process.pkg.mount = createMountpoint;
  process.pkg.entrypoint = ENTRYPOINT;
  process.pkg.defaultEntrypoint = DEFAULT_ENTRYPOINT;
}());

// /////////////////////////////////////////////////////////////////
// PATH.RESOLVE REPLACEMENT ////////////////////////////////////////
// /////////////////////////////////////////////////////////////////

(function () {
  var path = require('path');

  process.pkg.path = {};
  process.pkg.path.resolve = function () {
    var args = cloneArgs(arguments);
    args.unshift(path.dirname(ENTRYPOINT));
    return path.resolve.apply(path, args); // eslint-disable-line prefer-spread
  };
}());

// /////////////////////////////////////////////////////////////////
// PATCH FS ////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////

(function () {
  var fs = require('fs');
  var ancestor = {};
  ancestor.openSync =         fs.openSync;
  ancestor.open =             fs.open;
  ancestor.readSync =         fs.readSync;
  ancestor.read =             fs.read;
  ancestor.writeSync =        fs.writeSync;
  ancestor.write =            fs.write;
  ancestor.closeSync =        fs.closeSync;
  ancestor.close =            fs.close;
  ancestor.readFileSync =     fs.readFileSync;
  ancestor.readFile =         fs.readFile;
  // ancestor.writeFileSync = fs.writeFileSync; // based on openSync/writeSync/closeSync
  // ancestor.writeFile =     fs.writeFile; // based on open/write/close
  ancestor.readdirSync =      fs.readdirSync;
  ancestor.readdir =          fs.readdir;
  ancestor.realpathSync =     fs.realpathSync;
  ancestor.realpath =         fs.realpath;
  ancestor.statSync =         fs.statSync;
  ancestor.stat =             fs.stat;
  ancestor.lstatSync =        fs.lstatSync;
  ancestor.lstat =            fs.lstat;
  ancestor.fstatSync =        fs.fstatSync;
  ancestor.fstat =            fs.fstat;
  ancestor.existsSync =       fs.existsSync;
  ancestor.exists =           fs.exists;
  ancestor.accessSync =       fs.accessSync;
  ancestor.access =           fs.access;

  var windows = process.platform === 'win32';

  var docks = {};
  var ENOTDIR = windows ? 4052 : 20;
  var ENOENT = windows ? 4058 : 2;
  var EISDIR = windows ? 4068 : 21;

  function assertEncoding (encoding) {
    if (encoding && !Buffer.isEncoding(encoding)) {
      throw new Error('Unknown encoding: ' + encoding);
    }
  }

  function maybeCallback (args) {
    var cb = args[args.length - 1];
    return typeof cb === 'function' ? cb : rethrow;
  }

  function error_ENOENT (fileOrDirectory, path) { // eslint-disable-line camelcase
    var error = new Error(
      fileOrDirectory + ' \'' + stripSnapshot(path) + '\' ' +
      'was not included into executable at compilation stage. ' +
      'Please recompile adding it as asset or script.'
    );
    error.errno = -ENOENT;
    error.code = 'ENOENT';
    error.path = path;
    error.pkg = true;
    return error;
  }

  function error_EISDIR (path) { // eslint-disable-line camelcase
    var error = new Error(
      'EISDIR: illegal operation on a directory, read'
    );
    error.errno = -EISDIR;
    error.code = 'EISDIR';
    error.path = path;
    error.pkg = true;
    return error;
  }

  function error_ENOTDIR (path) { // eslint-disable-line camelcase
    var error = new Error(
      'ENOTDIR: not a directory, scandir \'' + path + '\''
    );
    error.errno = -ENOTDIR;
    error.code = 'ENOTDIR';
    error.path = path;
    error.pkg = true;
    return error;
  }

  // ///////////////////////////////////////////////////////////////
  // open //////////////////////////////////////////////////////////
  // ///////////////////////////////////////////////////////////////

  function openFromSnapshot (path_, cb) {
    var cb2 = cb || rethrow;
    var path = normalizePath(path_);
    // console.log("openFromSnapshot", path);
    var entity = VIRTUAL_FILESYSTEM[path];
    if (!entity) return cb2(error_ENOENT('File or directory', path));
    var dock = { path: path, entity: entity, position: 0 };
    var nullDevice = windows ? '\\\\.\\NUL' : '/dev/null';
    if (cb) {
      ancestor.open.call(fs, nullDevice, 'r', function (error, fd) {
        if (error) return cb(error);
        docks[fd] = dock;
        cb(null, fd);
      });
    } else {
      var fd = ancestor.openSync.call(fs, nullDevice, 'r');
      docks[fd] = dock;
      return fd;
    }
  }

  fs.openSync = function (path) {
    if (!insideSnapshot(path)) {
      return ancestor.openSync.apply(fs, arguments);
    }
    if (insideMountpoint(path)) {
      return ancestor.openSync.apply(fs, translateNth(arguments, 0, path));
    }

    return openFromSnapshot(path);
  };

  fs.open = function (path) {
    if (!insideSnapshot(path)) {
      return ancestor.open.apply(fs, arguments);
    }
    if (insideMountpoint(path)) {
      return ancestor.open.apply(fs, translateNth(arguments, 0, path));
    }

    var callback = dezalgo(maybeCallback(arguments));
    openFromSnapshot(path, callback);
  };

  // ///////////////////////////////////////////////////////////////
  // read //////////////////////////////////////////////////////////
  // ///////////////////////////////////////////////////////////////

  function readFromSnapshotSub (entityContent, dock, buffer, offset, length, position, cb) {
    var p;
    if ((position !== null) && (position !== undefined)) {
      p = position;
    } else {
      p = dock.position;
    }
    if (cb) {
      payloadCopyUni(entityContent, buffer, offset, p, p + length, function (error, bytesRead, buffer2) {
        if (error) return cb(error);
        dock.position = p + bytesRead;
        cb(null, bytesRead, buffer2);
      });
    } else {
      var bytesRead = payloadCopyUni(entityContent, buffer, offset, p, p + length);
      dock.position = p + bytesRead;
      return bytesRead;
    }
  }

  function readFromSnapshot (fd, buffer, offset, length, position, cb) {
    var cb2 = cb || rethrow;
    if ((offset < 0) && (NODE_VERSION_MAJOR >= 10)) return cb2(new Error(
      'The value of "offset" is out of range. It must be >= 0 && <= ' + buffer.length.toString() + '. Received ' + offset));
    if (offset < 0) return cb2(new Error('Offset is out of bounds'));
    if ((offset >= buffer.length) && (NODE_VERSION_MAJOR >= 6)) return cb2(null, 0);
    if (offset >= buffer.length) return cb2(new Error('Offset is out of bounds'));
    if ((offset + length > buffer.length) && (NODE_VERSION_MAJOR >= 10)) return cb2(new Error(
      'The value of "length" is out of range. It must be >= 0 && <= ' + (buffer.length - offset).toString() + '. Received ' + length.toString()));
    if (offset + length > buffer.length) return cb2(new Error('Length extends beyond buffer'));

    var dock = docks[fd];
    var entity = dock.entity;
    var entityLinks = entity[STORE_LINKS];
    if (entityLinks) return cb2(error_EISDIR(dock.path));
    var entityContent = entity[STORE_CONTENT];
    if (entityContent) return readFromSnapshotSub(entityContent, dock, buffer, offset, length, position, cb);
    return cb2(new Error('UNEXPECTED-15'));
  }

  fs.readSync = function (fd, buffer, offset, length, position) {
    if (!docks[fd]) {
      return ancestor.readSync.apply(fs, arguments);
    }

    return readFromSnapshot(fd, buffer, offset, length, position);
  };

  fs.read = function (fd, buffer, offset, length, position) {
    if (!docks[fd]) {
      return ancestor.read.apply(fs, arguments);
    }

    var callback = dezalgo(maybeCallback(arguments));
    readFromSnapshot(fd, buffer, offset, length, position, callback);
  };

  // ///////////////////////////////////////////////////////////////
  // write /////////////////////////////////////////////////////////
  // ///////////////////////////////////////////////////////////////

  function writeToSnapshot (cb) {
    var cb2 = cb || rethrow;
    return cb2(new Error('Cannot write to packaged file'));
  }

  fs.writeSync = function (fd) {
    if (!docks[fd]) {
      return ancestor.writeSync.apply(fs, arguments);
    }

    return writeToSnapshot();
  };

  fs.write = function (fd) {
    if (!docks[fd]) {
      return ancestor.write.apply(fs, arguments);
    }

    var callback = dezalgo(maybeCallback(arguments));
    writeToSnapshot(callback);
  };

  // ///////////////////////////////////////////////////////////////
  // close /////////////////////////////////////////////////////////
  // ///////////////////////////////////////////////////////////////

  function closeFromSnapshot (fd, cb) {
    delete docks[fd];
    if (cb) {
      ancestor.close.call(fs, fd, cb);
    } else {
      return ancestor.closeSync.call(fs, fd);
    }
  }

  fs.closeSync = function (fd) {
    if (!docks[fd]) {
      return ancestor.closeSync.apply(fs, arguments);
    }

    return closeFromSnapshot(fd);
  };

  fs.close = function (fd) {
    if (!docks[fd]) {
      return ancestor.close.apply(fs, arguments);
    }

    var callback = dezalgo(maybeCallback(arguments));
    closeFromSnapshot(fd, callback);
  };

  // ///////////////////////////////////////////////////////////////
  // readFile //////////////////////////////////////////////////////
  // ///////////////////////////////////////////////////////////////

  function readFileOptions (options, hasCallback) {
    if (!options || (hasCallback && typeof options === 'function')) {
      return { encoding: null, flag: 'r' };
    } else if (typeof options === 'string') {
      return { encoding: options, flag: 'r' };
    } else if (typeof options === 'object') {
      return options;
    } else {
      return null;
    }
  }

  function readFileFromSnapshotSub (entityContent, cb) {
    if (cb) {
      payloadFile(entityContent, cb);
    } else {
      return payloadFileSync(entityContent);
    }
  }

  function readFileFromSnapshot (path_, cb) {
    var cb2 = cb || rethrow;
    var path = normalizePath(path_);
    // console.log("readFileFromSnapshot", path);
    var entity = VIRTUAL_FILESYSTEM[path];
    if (!entity) return cb2(error_ENOENT('File', path));
    var entityLinks = entity[STORE_LINKS];
    if (entityLinks) return cb2(error_EISDIR(path));
    var entityContent = entity[STORE_CONTENT];
    if (entityContent) return readFileFromSnapshotSub(entityContent, cb);
    var entityBlob = entity[STORE_BLOB];
    if (entityBlob) return cb2(null, Buffer.from('source-code-not-available'));

    // why return empty buffer?
    // otherwise this error will arise:
    // Error: UNEXPECTED-20
    //     at readFileFromSnapshot (e:0)
    //     at Object.fs.readFileSync (e:0)
    //     at Object.Module._extensions..js (module.js:421:20)
    //     at Module.load (module.js:357:32)
    //     at Function.Module._load (module.js:314:12)
    //     at Function.Module.runMain (e:0)
    //     at startup (node.js:140:18)
    //     at node.js:1001:3

    return cb2(new Error('UNEXPECTED-20'));
  }

  fs.readFileSync = function (path, options_) {
    if (path === 'dirty-hack-for-testing-purposes') {
      return path;
    }

    if (!insideSnapshot(path)) {
      return ancestor.readFileSync.apply(fs, arguments);
    }
    if (insideMountpoint(path)) {
      return ancestor.readFileSync.apply(fs, translateNth(arguments, 0, path));
    }

    var options = readFileOptions(options_, false);

    if (!options) {
      return ancestor.readFileSync.apply(fs, arguments);
    }

    var encoding = options.encoding;
    assertEncoding(encoding);

    var buffer = readFileFromSnapshot(path);
    if (encoding) buffer = buffer.toString(encoding);
    return buffer;
  };

  fs.readFile = function (path, options_) {
    if (!insideSnapshot(path)) {
      return ancestor.readFile.apply(fs, arguments);
    }
    if (insideMountpoint(path)) {
      return ancestor.readFile.apply(fs, translateNth(arguments, 0, path));
    }

    var options = readFileOptions(options_, true);

    if (!options) {
      return ancestor.readFile.apply(fs, arguments);
    }

    var encoding = options.encoding;
    assertEncoding(encoding);

    var callback = dezalgo(maybeCallback(arguments));
    readFileFromSnapshot(path, function (error, buffer) {
      if (error) return callback(error);
      if (encoding) buffer = buffer.toString(encoding);
      callback(null, buffer);
    });
  };

  // ///////////////////////////////////////////////////////////////
  // writeFile /////////////////////////////////////////////////////
  // ///////////////////////////////////////////////////////////////

  // writeFileSync based on openSync/writeSync/closeSync
  // writeFile based on open/write/close

  // ///////////////////////////////////////////////////////////////
  // readdir ///////////////////////////////////////////////////////
  // ///////////////////////////////////////////////////////////////

  function readdirOptions (options, hasCallback) {
    if (!options || (hasCallback && typeof options === 'function')) {
      return { encoding: null };
    } else if (typeof options === 'string') {
      return { encoding: options };
    } else if (typeof options === 'object') {
      return options;
    } else {
      return null;
    }
  }

  function Dirent (name, type) {
    this.name = name;
    this.type = type;
  }

  Dirent.prototype.isDirectory = function () {
    return this.type === 2;
  };

  Dirent.prototype.isFile = function () {
    return this.type === 1;
  };

  Dirent.prototype.isBlockDevice =
  Dirent.prototype.isCharacterDevice =
  Dirent.prototype.isSymbolicLink =
  Dirent.prototype.isFIFO =
  Dirent.prototype.isSocket = function () {
    return false;
  };

  function getFileTypes (path_, entries) {
    return entries.map(function (entry) {
      var path = require('path').join(path_, entry);
      var entity = VIRTUAL_FILESYSTEM[path];
      if (entity[STORE_BLOB] || entity[STORE_CONTENT]) return new Dirent(entry, 1);
      if (entity[STORE_LINKS]) return new Dirent(entry, 2);
      throw new Error('UNEXPECTED-24');
    });
  }

  function readdirRoot (path, cb) {
    if (cb) {
      ancestor.readdir(path, function (error, entries) {
        if (error) return cb(error);
        entries.push('snapshot');
        cb(null, entries);
      });
    } else {
      var entries = ancestor.readdirSync(path);
      entries.push('snapshot');
      return entries;
    }
  }

  function readdirFromSnapshotSub (entityLinks, path, cb) {
    if (cb) {
      payloadFile(entityLinks, function (error, buffer) {
        if (error) return cb(error);
        cb(null, JSON.parse(buffer).concat(readdirMountpoints(path)));
      });
    } else {
      var buffer = payloadFileSync(entityLinks);
      return JSON.parse(buffer).concat(readdirMountpoints(path));
    }
  }

  function readdirFromSnapshot (path_, isRoot, cb) {
    var cb2 = cb || rethrow;
    if (isRoot) return readdirRoot(path_, cb);
    var path = normalizePath(path_);
    // console.log("readdirFromSnapshot", path);
    var entity = VIRTUAL_FILESYSTEM[path];
    if (!entity) return cb2(error_ENOENT('Directory', path));
    var entityBlob = entity[STORE_BLOB];
    if (entityBlob) return cb2(error_ENOTDIR(path));
    var entityContent = entity[STORE_CONTENT];
    if (entityContent) return cb2(error_ENOTDIR(path));
    var entityLinks = entity[STORE_LINKS];
    if (entityLinks) return readdirFromSnapshotSub(entityLinks, path, cb);
    return cb2(new Error('UNEXPECTED-25'));
  }

  fs.readdirSync = function (path, options_) {
    var isRoot = isRootPath(path);

    if (!insideSnapshot(path) && !isRoot) {
      return ancestor.readdirSync.apply(fs, arguments);
    }
    if (insideMountpoint(path)) {
      return ancestor.readdirSync.apply(fs, translateNth(arguments, 0, path));
    }

    var options = readdirOptions(options_, false);

    if (!options) {
      return ancestor.readdirSync.apply(fs, arguments);
    }

    var entries = readdirFromSnapshot(path, isRoot);
    if (options.withFileTypes) entries = getFileTypes(path, entries);
    return entries;
  };

  fs.readdir = function (path, options_) {
    var isRoot = isRootPath(path);

    if (!insideSnapshot(path) && !isRoot) {
      return ancestor.readdir.apply(fs, arguments);
    }
    if (insideMountpoint(path)) {
      return ancestor.readdir.apply(fs, translateNth(arguments, 0, path));
    }

    var options = readdirOptions(options_, true);

    if (!options) {
      return ancestor.readdir.apply(fs, arguments);
    }

    var callback = dezalgo(maybeCallback(arguments));
    readdirFromSnapshot(path, isRoot, function (error, entries) {
      if (error) return callback(error);
      if (options.withFileTypes) entries = getFileTypes(path, entries);
      callback(null, entries);
    });
  };

  // ///////////////////////////////////////////////////////////////
  // realpath //////////////////////////////////////////////////////
  // ///////////////////////////////////////////////////////////////

  function realpathFromSnapshot (path_) {
    var path = normalizePath(path_);
    // console.log("realpathFromSnapshot", path);
    return path;
  }

  fs.realpathSync = function (path) {
    if (!insideSnapshot(path)) {
      return ancestor.realpathSync.apply(fs, arguments);
    }
    if (insideMountpoint(path)) {
      // app should not know real file name
    }

    return realpathFromSnapshot(path);
  };

  fs.realpath = function (path) {
    if (!insideSnapshot(path)) {
      return ancestor.realpath.apply(fs, arguments);
    }
    if (insideMountpoint(path)) {
      // app should not know real file name
    }

    var callback = dezalgo(maybeCallback(arguments));
    callback(null, realpathFromSnapshot(path));
  };

  // ///////////////////////////////////////////////////////////////
  // stat //////////////////////////////////////////////////////////
  // ///////////////////////////////////////////////////////////////

  function restore (s) {
    s.blksize = 4096;
    s.blocks = 0;
    s.dev = 0;
    s.gid = 20;
    s.ino = 0;
    s.nlink = 0;
    s.rdev = 0;
    s.uid = 500;

    s.atime = new Date(EXECSTAT.atime);
    s.mtime = new Date(EXECSTAT.mtime);
    s.ctime = new Date(EXECSTAT.ctime);
    s.birthtime = new Date(EXECSTAT.birthtime);

    s.atimeMs = EXECSTAT.atimeMs;
    s.mtimeMs = EXECSTAT.mtimeMs;
    s.ctimeMs = EXECSTAT.ctimeMs;
    s.birthtimeMs = EXECSTAT.birthtimeMs;

    var isFileValue = s.isFileValue;
    var isDirectoryValue = s.isDirectoryValue;
    delete s.isFileValue;
    delete s.isDirectoryValue;

    s.isFile = function () {
      return isFileValue;
    };
    s.isDirectory = function () {
      return isDirectoryValue;
    };
    s.isSymbolicLink = function () {
      return false;
    };
    s.isFIFO = function () {
      return false;
    };

    return s;
  }

  function findNativeAddonForStat (path, cb) {
    var cb2 = cb || rethrow;
    var foundPath = findNativeAddonSyncUnderRequire(path);
    if (!foundPath) return cb2(error_ENOENT('File or directory', path));
    if (cb) {
      ancestor.stat.call(fs, foundPath, cb);
    } else {
      return ancestor.statSync.call(fs, foundPath);
    }
  }

  function statFromSnapshotSub (entityStat, cb) {
    if (cb) {
      payloadFile(entityStat, function (error, buffer) {
        if (error) return cb(error);
        cb(null, restore(JSON.parse(buffer)));
      });
    } else {
      var buffer = payloadFileSync(entityStat);
      return restore(JSON.parse(buffer));
    }
  }

  function statFromSnapshot (path_, cb) {
    var cb2 = cb || rethrow;
    var path = normalizePath(path_);
    // console.log("statFromSnapshot", path);
    var entity = VIRTUAL_FILESYSTEM[path];
    if (!entity) return findNativeAddonForStat(path, cb);
    var entityStat = entity[STORE_STAT];
    if (entityStat) return statFromSnapshotSub(entityStat, cb);
    return cb2(new Error('UNEXPECTED-35'));
  }

  fs.statSync = function (path) {
    if (!insideSnapshot(path)) {
      return ancestor.statSync.apply(fs, arguments);
    }
    if (insideMountpoint(path)) {
      return ancestor.statSync.apply(fs, translateNth(arguments, 0, path));
    }

    return statFromSnapshot(path);
  };

  fs.stat = function (path) {
    if (!insideSnapshot(path)) {
      return ancestor.stat.apply(fs, arguments);
    }
    if (insideMountpoint(path)) {
      return ancestor.stat.apply(fs, translateNth(arguments, 0, path));
    }

    var callback = dezalgo(maybeCallback(arguments));
    statFromSnapshot(path, callback);
  };

  // ///////////////////////////////////////////////////////////////
  // lstat /////////////////////////////////////////////////////////
  // ///////////////////////////////////////////////////////////////

  fs.lstatSync = function (path) {
    if (!insideSnapshot(path)) {
      return ancestor.lstatSync.apply(fs, arguments);
    }
    if (insideMountpoint(path)) {
      return ancestor.lstatSync.apply(fs, translateNth(arguments, 0, path));
    }

    return statFromSnapshot(path);
  };

  fs.lstat = function (path) {
    if (!insideSnapshot(path)) {
      return ancestor.lstat.apply(fs, arguments);
    }
    if (insideMountpoint(path)) {
      return ancestor.lstat.apply(fs, translateNth(arguments, 0, path));
    }

    var callback = dezalgo(maybeCallback(arguments));
    statFromSnapshot(path, callback);
  };

  // ///////////////////////////////////////////////////////////////
  // fstat /////////////////////////////////////////////////////////
  // ///////////////////////////////////////////////////////////////

  function fstatFromSnapshot (fd, cb) {
    var cb2 = cb || rethrow;
    var entity = docks[fd].entity;
    var entityStat = entity[STORE_STAT];
    if (entityStat) return statFromSnapshotSub(entityStat, cb);
    return cb2(new Error('UNEXPECTED-40'));
  }

  fs.fstatSync = function (fd) {
    if (!docks[fd]) {
      return ancestor.fstatSync.apply(fs, arguments);
    }

    return fstatFromSnapshot(fd);
  };

  fs.fstat = function (fd) {
    if (!docks[fd]) {
      return ancestor.fstat.apply(fs, arguments);
    }

    var callback = dezalgo(maybeCallback(arguments));
    fstatFromSnapshot(fd, callback);
  };

  // ///////////////////////////////////////////////////////////////
  // exists ////////////////////////////////////////////////////////
  // ///////////////////////////////////////////////////////////////

  function findNativeAddonForExists (path) {
    var foundPath = findNativeAddonSyncFreeFromRequire(path);
    if (!foundPath) return false;
    return ancestor.existsSync.call(fs, foundPath);
  }

  function existsFromSnapshot (path_) {
    var path = normalizePath(path_);
    // console.log("existsFromSnapshot", path);
    var entity = VIRTUAL_FILESYSTEM[path];
    if (!entity) return findNativeAddonForExists(path);
    return true;
  }

  fs.existsSync = function (path) {
    if (!insideSnapshot(path)) {
      return ancestor.existsSync.apply(fs, arguments);
    }
    if (insideMountpoint(path)) {
      return ancestor.existsSync.apply(fs, translateNth(arguments, 0, path));
    }

    return existsFromSnapshot(path);
  };

  fs.exists = function (path) {
    if (!insideSnapshot(path)) {
      return ancestor.exists.apply(fs, arguments);
    }
    if (insideMountpoint(path)) {
      return ancestor.exists.apply(fs, translateNth(arguments, 0, path));
    }

    var callback = dezalgo(maybeCallback(arguments));
    callback(existsFromSnapshot(path));
  };

  // ///////////////////////////////////////////////////////////////
  // access ////////////////////////////////////////////////////////
  // ///////////////////////////////////////////////////////////////

  function accessFromSnapshot (path_, cb) {
    var cb2 = cb || rethrow;
    var path = normalizePath(path_);
    // console.log("accessFromSnapshot", path);
    var entity = VIRTUAL_FILESYSTEM[path];
    if (!entity) return cb2(error_ENOENT('File or directory', path));
    return cb2(null, undefined);
  }

  fs.accessSync = function (path) {
    if (!insideSnapshot(path)) {
      return ancestor.accessSync.apply(fs, arguments);
    }
    if (insideMountpoint(path)) {
      return ancestor.accessSync.apply(fs, translateNth(arguments, 0, path));
    }

    return accessFromSnapshot(path);
  };

  fs.access = function (path) {
    if (!insideSnapshot(path)) {
      return ancestor.access.apply(fs, arguments);
    }
    if (insideMountpoint(path)) {
      return ancestor.access.apply(fs, translateNth(arguments, 0, path));
    }

    var callback = dezalgo(maybeCallback(arguments));
    accessFromSnapshot(path, callback);
  };

  // ///////////////////////////////////////////////////////////////
  // INTERNAL //////////////////////////////////////////////////////
  // ///////////////////////////////////////////////////////////////

  function makeLong (f) {
    return require('path')._makeLong(f);
  }

  function revertMakingLong (f) {
    if (/^\\\\\?\\/.test(f)) return f.slice(4);
    return f;
  }

  function findNativeAddonForInternalModuleStat (path_) {
    var path = findNativeAddonSyncUnderRequire(path_);
    if (!path) return -ENOENT;
    return process.binding('fs').internalModuleStat(makeLong(path));
  }

  fs.internalModuleStat = function (long) {
    // from node comments:
    // Used to speed up module loading. Returns 0 if the path refers to
    // a file, 1 when it's a directory or < 0 on error (usually -ENOENT).
    // The speedup comes from not creating thousands of Stat and Error objects.

    var path = revertMakingLong(long);

    if (!insideSnapshot(path)) {
      return process.binding('fs').internalModuleStat(long);
    }
    if (insideMountpoint(path)) {
      return process.binding('fs').internalModuleStat(makeLong(translate(path)));
    }

    path = normalizePath(path);
    // console.log("internalModuleStat", path);
    var entity = VIRTUAL_FILESYSTEM[path];
    if (!entity) return findNativeAddonForInternalModuleStat(path);
    var entityBlob = entity[STORE_BLOB];
    if (entityBlob) return 0;
    var entityContent = entity[STORE_CONTENT];
    if (entityContent) return 0;
    var entityLinks = entity[STORE_LINKS];
    if (entityLinks) return 1;
    return -ENOENT;
  };

  fs.internalModuleReadFile = fs.internalModuleReadJSON = function (long) {
    // from node comments:
    // Used to speed up module loading. Returns the contents of the file as
    // a string or undefined when the file cannot be opened. The speedup
    // comes from not creating Error objects on failure.

    var path = revertMakingLong(long);
    var bindingFs = process.binding('fs');
    var readFile = (bindingFs.internalModuleReadFile ||
                    bindingFs.internalModuleReadJSON).bind(bindingFs);
    if (!insideSnapshot(path)) {
      return readFile(long);
    }
    if (insideMountpoint(path)) {
      return readFile(makeLong(translate(path)));
    }

    path = normalizePath(path);
    // console.log("internalModuleReadFile", path);
    var entity = VIRTUAL_FILESYSTEM[path];
    if (!entity) return undefined;
    var entityContent = entity[STORE_CONTENT];
    if (!entityContent) return undefined;
    return payloadFileSync(entityContent).toString();
  };
}());

// /////////////////////////////////////////////////////////////////
// PATCH MODULE ////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////

(function () {
  var Module = require('module');
  var ancestor = {};
  ancestor.require =          Module.prototype.require;
  ancestor._compile =         Module.prototype._compile;
  ancestor._resolveFilename = Module._resolveFilename;
  ancestor.runMain =          Module.runMain;

  Module.prototype.require = function (path) {
    try {
      return ancestor.require.apply(this, arguments);
    } catch (error) {
      if (((error.code === 'ENOENT') ||
           (error.code === 'MODULE_NOT_FOUND')) &&
          (!insideSnapshot(path)) &&
          (!require('path').isAbsolute(path))) {
        if (!error.pkg) {
          error.pkg = true;
          error.message += '\n' +
            '1) If you want to compile the package/file into ' +
            'executable, please pay attention to compilation ' +
            'warnings and specify a literal in \'require\' call. ' +
            '2) If you don\'t want to compile the package/file ' +
            'into executable and want to \'require\' it from ' +
            'filesystem (likely plugin), specify an absolute ' +
            'path in \'require\' call using process.cwd() or ' +
            'process.execPath.';
        }
      }
      throw error;
    }
  };

  var im, makeRequireFunction;

  if (NODE_VERSION_MAJOR === 0) {
    makeRequireFunction = function (self) {
      function rqfn (path) {
        return self.require(path);
      }
      rqfn.resolve = function (request) {
        return Module._resolveFilename(request, self);
      };
      rqfn.main = process.mainModule;
      rqfn.extensions = Module._extensions;
      rqfn.cache = Module._cache;
      return rqfn;
    };
  } else
  if (NODE_VERSION_MAJOR <= 9) {
    im = require('internal/module');
    if (NODE_VERSION_MAJOR <= 7) {
      makeRequireFunction = function (m) {
        return im.makeRequireFunction.call(m);
      };
    } else {
      makeRequireFunction = im.makeRequireFunction;
    }
  } else {
    im = require('internal/modules/cjs/helpers');
    makeRequireFunction = im.makeRequireFunction;
    // TODO esm modules along with cjs
  }

  Module.prototype._compile = function (content, filename_) {
    if (!insideSnapshot(filename_)) {
      return ancestor._compile.apply(this, arguments);
    }
    if (insideMountpoint(filename_)) {
      // DONT TRANSLATE! otherwise __dirname gets real name
      return ancestor._compile.apply(this, arguments);
    }

    var filename = normalizePath(filename_);
    // console.log("_compile", filename);
    var entity = VIRTUAL_FILESYSTEM[filename];

    if (!entity) {
      // let user try to "_compile" a packaged file
      return ancestor._compile.apply(this, arguments);
    }

    var entityBlob = entity[STORE_BLOB];
    var entityContent = entity[STORE_CONTENT];

    if (entityBlob) {
      var options = {
        filename: filename,
        lineOffset: 0,
        displayErrors: true,
        cachedData: payloadFileSync(entityBlob),
        sourceless: !entityContent
      };

      var Script = require('vm').Script;
      var code = entityContent
        ? require('module').wrap(payloadFileSync(entityContent))
        : undefined;

      var script = new Script(code, options);
      var wrapper = script.runInThisContext(options);
      if (!wrapper) process.exit(4); // for example VERSION_MISMATCH
      var dirname = require('path').dirname(filename);
      var rqfn = makeRequireFunction(this);
      var args = [ this.exports, rqfn, this, filename, dirname ];
      return wrapper.apply(this.exports, args);
    }

    if (entityContent) {
      if (entityBlob) throw new Error('UNEXPECTED-50');
      // content is already in utf8 and without BOM (that is expected
      // by stock _compile), but entityContent is still a Buffer
      return ancestor._compile.apply(this, arguments);
    }

    throw new Error('UNEXPECTED-55');
  };

  Module._resolveFilename = function () {
    var filename;
    var flagWasOn = false;

    try {
      filename = ancestor._resolveFilename.apply(this, arguments);
    } catch (error) {
      if (error.code !== 'MODULE_NOT_FOUND') throw error;

      FLAG_ENABLE_PROJECT = true;
      var savePathCache = Module._pathCache;
      Module._pathCache = Object.create(null);
      try {
        filename = ancestor._resolveFilename.apply(this, arguments);
        flagWasOn = true;
      } finally {
        Module._pathCache = savePathCache;
        FLAG_ENABLE_PROJECT = false;
      }
    }

    if (!insideSnapshot(filename)) {
      return filename;
    }
    if (insideMountpoint(filename)) {
      return filename;
    }

    if (flagWasOn) {
      FLAG_ENABLE_PROJECT = true;
      try {
        var found = findNativeAddonSyncUnderRequire(filename);
        if (found) filename = found;
      } finally {
        FLAG_ENABLE_PROJECT = false;
      }
    }

    return filename;
  };

  Module.runMain = function () {
    Module._load(ENTRYPOINT, null, true);
    process._tickCallback();
  };
}());

// /////////////////////////////////////////////////////////////////
// PATCH CHILD_PROCESS /////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////

(function () {
  var childProcess = require('child_process');
  var ancestor = {};
  ancestor.spawn = childProcess.spawn;
  ancestor.spawnSync = childProcess.spawnSync;
  ancestor.execFile = childProcess.execFile;
  ancestor.execFileSync = childProcess.execFileSync;
  ancestor.exec = childProcess.exec;
  ancestor.execSync = childProcess.execSync;

  function setOptsEnv (args) {
    var pos = args.length - 1;
    if (typeof args[pos] === 'function') pos -= 1;
    if (typeof args[pos] !== 'object' || Array.isArray(args[pos])) {
      pos += 1;
      args.splice(pos, 0, {});
    }
    var opts = args[pos];
    if (!opts.env) opts.env = require('util')._extend({}, process.env);
    if (opts.env.PKG_EXECPATH === 'PKG_INVOKE_NODEJS') return;
    opts.env.PKG_EXECPATH = EXECPATH;
  }

  function startsWith2 (args, index, name, impostor) {
    var qsName = '"' + name + ' ';
    if (args[index].slice(0, qsName.length) === qsName) {
      args[index] = '"' + impostor + ' ' + args[index].slice(qsName.length);
      return true;
    }
    var sName = name + ' ';
    if (args[index].slice(0, sName.length) === sName) {
      args[index] = impostor + ' ' + args[index].slice(sName.length);
      return true;
    }
    if (args[index] === name) {
      args[index] = impostor;
      return true;
    }
    return false;
  }

  function startsWith (args, index, name) {
    var qName = '"' + name + '"';
    var qEXECPATH = '"' + EXECPATH + '"';
    var jsName = JSON.stringify(name);
    var jsEXECPATH = JSON.stringify(EXECPATH);
    return startsWith2(args, index, name, EXECPATH) ||
           startsWith2(args, index, qName, qEXECPATH) ||
           startsWith2(args, index, jsName, jsEXECPATH);
  }

  function modifyLong (args, index) {
    if (!args[index]) return;
    return (startsWith(args, index, 'node') ||
            startsWith(args, index, ARGV0) ||
            startsWith(args, index, ENTRYPOINT) ||
            startsWith(args, index, EXECPATH));
  }

  function modifyShort (args) {
    if (!args[0]) return;
    if (!Array.isArray(args[1])) {
      args.splice(1, 0, []);
    }
    if (args[0] === 'node' ||
        args[0] === ARGV0 ||
        args[0] === ENTRYPOINT ||
        args[0] === EXECPATH) {
      args[0] = EXECPATH;
      if (NODE_VERSION_MAJOR === 0) {
        args[1] = args[1].filter(function (a) {
          return (a.slice(0, 13) !== '--debug-port=');
        });
      }
    } else {
      for (var i = 1; i < args[1].length; i += 1) {
        var mbc = args[1][i - 1];
        if (mbc === '-c' || mbc === '/c') {
          modifyLong(args[1], i);
        }
      }
    }
  }

  childProcess.spawn = function () {
    var args = cloneArgs(arguments);
    setOptsEnv(args);
    modifyShort(args);
    return ancestor.spawn.apply(childProcess, args);
  };

  childProcess.spawnSync = function () {
    var args = cloneArgs(arguments);
    setOptsEnv(args);
    modifyShort(args);
    return ancestor.spawnSync.apply(childProcess, args);
  };

  childProcess.execFile = function () {
    var args = cloneArgs(arguments);
    setOptsEnv(args);
    modifyShort(args);
    return ancestor.execFile.apply(childProcess, args);
  };

  childProcess.execFileSync = function () {
    var args = cloneArgs(arguments);
    setOptsEnv(args);
    modifyShort(args);
    return ancestor.execFileSync.apply(childProcess, args);
  };

  childProcess.exec = function () {
    var args = cloneArgs(arguments);
    setOptsEnv(args);
    modifyLong(args, 0);
    return ancestor.exec.apply(childProcess, args);
  };

  childProcess.execSync = function () {
    var args = cloneArgs(arguments);
    setOptsEnv(args);
    modifyLong(args, 0);
    return ancestor.execSync.apply(childProcess, args);
  };
}());

// /////////////////////////////////////////////////////////////////
// PROMISIFY ///////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////

(function () {
  var util = require('util');
  var promisify = util.promisify;
  if (promisify) {
    var custom = promisify.custom;
    var customPromisifyArgs = require('internal/util').customPromisifyArgs;

    // /////////////////////////////////////////////////////////////
    // FS //////////////////////////////////////////////////////////
    // /////////////////////////////////////////////////////////////

    Object.defineProperty(require('fs').exists, custom, {
      value: function (path) {
        return new Promise(function (resolve) {
          require('fs').exists(path, function (exists) {
            resolve(exists);
          });
        });
      }
    });

    Object.defineProperty(require('fs').read, customPromisifyArgs, {
      value: [ 'bytesRead', 'buffer' ]
    });

    Object.defineProperty(require('fs').write, customPromisifyArgs, {
      value: [ 'bytesWritten', 'buffer' ]
    });

    // /////////////////////////////////////////////////////////////
    // CHILD_PROCESS ///////////////////////////////////////////////
    // /////////////////////////////////////////////////////////////

    var customPromiseExecFunction = function (o) {
      return function () {
        var args = Array.from(arguments);
        return new Promise(function (resolve, reject) {
          o.apply(undefined, args.concat(function (error, stdout, stderr) {
            if (error !== null) {
              error.stdout = stdout;
              error.stderr = stderr;
              reject(error);
            } else {
              resolve({ stdout: stdout, stderr: stderr });
            }
          }));
        });
      };
    };

    Object.defineProperty(require('child_process').exec, custom, {
      value: customPromiseExecFunction(require('child_process').exec)
    });

    Object.defineProperty(require('child_process').execFile, custom, {
      value: customPromiseExecFunction(require('child_process').execFile)
    });
  }
}());
