UNPKG

jocker

Version:

Docker written in Javascript

239 lines (201 loc) 6.22 kB
const fs = require('fs') const proc = require('child_process') const each = require('async').each const utils = require('nodeos-mount-utils') const mkdirp = require('mkdirp') const constants = fs.constants const S_IX = constants.S_IXUSR | constants.S_IXGRP | constants.S_IXOTH const flags = utils.flags const MS_BIND = flags.MS_BIND const MS_NODEV = flags.MS_NODEV const MS_NOSUID = flags.MS_NOSUID /** * Exec a command on a `chroot`ed directory with un-priviledged permissions * * @param {String} command Path of the command file inside the home folder * @param {String[]} [argv] Command arguments * @param {Object} [opts] Extra options * @param {Function} callback */ function chrootSpawn(command, argv, opts, callback) { argv = [opts.uid, opts.gid, command].concat(argv) const options = { cwd: opts.cwd, env: opts.env, stdio: 'inherit' } proc.spawn(`${__dirname}/chrootSpawn`, argv, options).on('exit', callback) } /** * This functions mounts the provided path to the device. * **If no device is available then it uses the type** * @access private * @param {Object} info This object holds information * about the folder to create * @property {String} info.dev Device-File being mounted * (located in `/dev`) a.k.a. devFile. * @property {String} info.path Directory to mount the device to. * @property {String} info.type Filesystem identificator * (one of `/proc/filesystems`). * @property {Array|Number} info.[flags] Flags for mounting * @property {String} info.[extras] The data argument is * interpreted by the different * file systems. Typically it is a * string of comma-separated options * understood by this file system. * @param {Function} callback Function called after the * mount operation finishes. * Receives only one argument err. */ function mkdirMountInfo(info, callback) { utils.mkdirMount(info.path, info.type, info.flags, info.extras, callback) } // // Public API // /** * Execute the command file * * @param {String} upperdir Path of the root of the container * @param {Function} callback */ function create(upperdir, callback) { var workdir = upperdir.split('/') var user = workdir.pop() var workdir = workdir.join('/')+'/.workdirs/'+user mkdirp(workdir, '0100', function(error) { if(error && error.code !== 'EEXIST') return callback(error) // Craft overlayed filesystem var type = 'overlay' var extras = { lowerdir: '/', upperdir: upperdir, workdir : workdir } utils.mkdirMount(upperdir, type, MS_NOSUID, extras, function(error) { if(error) return callback(error) var arr = [ { path: upperdir+'/dev', flags: MS_BIND, extras: {devFile: '/tmp/dev'} }, { path: upperdir+'/proc', flags: MS_BIND, extras: {devFile: '/proc'} }, { path: upperdir+'/tmp', type: 'tmpfs', flags: MS_NODEV | MS_NOSUID } ] each(arr, mkdirMountInfo, callback) }) }) } /** * Execute the command file * * @param {String} home Path of the home folder where the command file is located * @param {String} command Path of the command file inside the home folder * @param {String[]} [argv] Command arguments * @param {Object} [env] Extra environment variables * @param {Function} callback */ function exec(home, command, argv, env, callback) { if(argv != null && !(argv instanceof Array)) { callback = env env = argv argv = null } if(env instanceof Function) { callback = env env = null } argv = argv || [] env = env || {} env.__proto__ = process.env // get a stat of the home folder fs.stat(home, function(error, homeStat) { if(error) { // Return every error but no ENOENT if(error.code !== 'ENOENT') return callback(error) return callback(`${home} not found`) } // path to the command file const commandPath = `${home}${command}` fs.stat(commandPath, function(error, commandStat) { if(error) { // Return every error but no ENOENT if(error.code !== 'ENOENT') return callback(error) return callback(`${commandPath} not found`) } // check if the command file is an actual file if(!commandStat.isFile()) return callback(`${commandPath} is not a file`) // check if the command file uid & gid are the same of its parent folder if(homeStat.uid !== commandStat.uid || homeStat.gid !== commandStat.gid) return callback(`${home} uid & gid don't match with ${command}`) // check if the command file is executable if(!(commandStat.mode & S_IX)) return callback(`${command} is not executable`) // Exec command const options = { cwd: home, env: env, uid: homeStat.uid, gid: homeStat.gid } chrootSpawn(command, argv, options, callback) }) }) } /** * Execute the command file * * @param {String} home Path of the home folder where the command file is located * @param {String} command Path of the command file inside the home folder * @param {String[]} [argv] Command arguments * @param {Object} [env] Extra environment variables * @param {Function} callback */ function run(home, command, argv, env, callback) { if(argv != null && !(argv instanceof Array)) { callback = env env = argv argv = null } if(env instanceof Function) { callback = env env = null } create(home, function(error) { if(error) return callback(error) exec(home, command, argv, env, callback) }) } exports.create = create exports.exec = exec exports.run = run