Skip to content

Commit 7f9035c

Browse files
committed
Use jocker module to create and manage per-user root filesystems
1 parent d0221ec commit 7f9035c

File tree

2 files changed

+93
-105
lines changed

2 files changed

+93
-105
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@
2222
"dependencies": {
2323
"async": "^2.0.1",
2424
"mkdirp": "^0.5.1",
25+
"nodeos-mount-utils": "^0.2.0",
2526
"rimraf": "^2.5.4",
2627
"prompt": "*"
2728
},
2829
"peerDependencies": {
29-
"nodeos-mount-utils": "^0.2.0"
30+
"jocker": "^0.0.0"
3031
}
3132
}

server.js

Lines changed: 91 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const resolve = require('path').resolve
55
const spawn = require('child_process').spawn
66

77
const async = require('async')
8+
const jocker = require('jocker')
89
const mkdirp = require('mkdirp')
910
const prompt = require('prompt')
1011
const rimraf = require('rimraf').sync
@@ -20,15 +21,8 @@ const EXCLFS_BIN = '/bin/exclfs'
2021
const HOME = '/tmp'
2122

2223

23-
var ROOT_HOME = ''
2424
var single
2525

26-
/**
27-
* This callback is part of the `mountDevProcTmp_ExecInit` function
28-
* @callback mountDevProcCallback
29-
* @param {Error} error The callback is called with a error if the devices
30-
* couldnt be mounted
31-
*/
3226

3327
/**
3428
* This error handler traces the error and starts a node.js repl
@@ -37,12 +31,8 @@ var single
3731
*/
3832
function onerror(error)
3933
{
40-
if(error)
41-
{
42-
// Error mounting the root filesystem or executing init, enable REPL
43-
console.trace(error)
44-
utils.startRepl('NodeOS-mount-filesystems')
45-
}
34+
console.trace(error)
35+
utils.startRepl('NodeOS-mount-filesystems')
4636
}
4737

4838
/**
@@ -130,33 +120,33 @@ function mkdirMoveInfo(info, callback)
130120
}
131121

132122
/**
133-
* Mounts the user filesystems
123+
* Mounts the root filesystems and exec its `/init`
124+
*
134125
* @access private
135-
* @param {Array} arr A array containing objects with
136-
* the mounting information **For more Information
137-
* see mkdirMountInfo**
138-
* @param {String} upperdir Path to the Init file
139-
* The path must contain a init file
140-
* Because execInit checks the gid & uid of the file
141-
* and of the "upperdir"
126+
*
127+
* @param {Array} arr An array containing objects with the mounting information
128+
* **For more Information see mkdirMountInfo**
129+
* @param {String} home Path to the `root` home. It must contain an `/init` file
130+
*
142131
* @example
143132
* let infos = [ mountInfo1, mountInfo2 ] // see under mkdirMountInfo
144133
* // for more Info
145134
*
146135
* // Its necessary to exclude the init file from the path because
147-
* // mountUserFilesystems does that for you
148-
* mountUserFilesystems(infos, 'path/to/initfile', callback)
136+
* // `mountRootFilesystems()` does that for you
137+
* mountRootFilesystems(infos, 'path/to/initfile', callback)
149138
*/
150-
function mountUserFilesystems(arr, upperdir, callback)
139+
function mountRootFilesystems(arr, home, callback)
151140
{
152141
async.each(arr, mkdirMountInfo, function(error)
153142
{
154143
if(error) return callback(error)
155144

145+
// System started in `single` mode, launch REPL
156146
if(single) return callback()
157147

158-
// Execute init
159-
utils.execInit(upperdir, function(error)
148+
// Execute `root` user init in un-priviledged environment
149+
jocker.exec(home, '/init', {PATH: '/bin'}, function(error)
160150
{
161151
if(error) console.warn(error)
162152

@@ -166,15 +156,15 @@ function mountUserFilesystems(arr, upperdir, callback)
166156
}
167157

168158
/**
169-
* Waits until dev is mounted and then executes `mountUserFilesystems` to
170-
* mount `${upperdir}/proc` and `${upperdir}/tmp`
159+
* Waits until `/dev` is mounted and then executes `mountRootFilesystems()` to
160+
* mount `root`'s `${upperdir}/proc` and `${upperdir}/tmp`
161+
*
171162
* @access private
172-
* @param {String} upperdir The upperdir
173-
* @param {Boolean} isRoot True if user is root, false if not
174-
* @param {Function} callback The callback function
175-
* @return {mountDevProcCallback} Returns the callback function
163+
*
164+
* @param {String} upperdir The upperdir
165+
* @param {mountDevProcCallback} callback The callback function
176166
*/
177-
function mountDevProcTmp_ExecInit(upperdir, isRoot, callback)
167+
function prepareRootFilesystems(upperdir, callback)
178168
{
179169
var arr =
180170
[
@@ -190,11 +180,23 @@ function mountDevProcTmp_ExecInit(upperdir, isRoot, callback)
190180
}
191181
]
192182

193-
var path = upperdir+'/dev'
183+
// Using ExclFS filesystem
184+
fs.access(EXCLFS_BIN, fs.constants.X_OK, function(error)
185+
{
186+
var path = upperdir+'/dev'
194187

195-
// Root user
196-
if(isRoot && fs.existsSync(EXCLFS_BIN))
197-
return mkdirp(path, '0000', function(error)
188+
if(error)
189+
{
190+
arr.unshift({
191+
path: path,
192+
flags: MS_BIND,
193+
extras: {devFile: '/dev'}
194+
})
195+
196+
return mountRootFilesystems(arr, upperdir, callback)
197+
}
198+
199+
mkdirp(path, '0000', function(error)
198200
{
199201
if(error && error.code !== 'EEXIST') return callback(error)
200202

@@ -217,33 +219,32 @@ function mountDevProcTmp_ExecInit(upperdir, isRoot, callback)
217219
rimraf(EXCLFS_BIN)
218220
rimraf('/lib/node_modules/exclfs')
219221

220-
mountUserFilesystems(arr, upperdir, callback)
222+
mountRootFilesystems(arr, upperdir, callback)
221223
})
222224
})
223-
224-
// Regular user
225-
arr.unshift({
226-
path: path,
227-
flags: MS_BIND,
228-
extras: {devFile: ROOT_HOME+'/dev'}
229225
})
230-
231-
mountUserFilesystems(arr, upperdir, callback)
232226
}
227+
/**
228+
* @callback mountDevProcCallback
229+
*
230+
* @param {Error} error The callback is called with an error if the devices
231+
* couldn't be mounted
232+
*/
233233

234234
/**
235-
* `overlay_user` first creates the workdir (with `0100` permission)
236-
* which is a string out of the folder where all users are located, a
237-
* constant `.workdirs` and the username e.g. `${usersFolder}/.workdirs/${user}`
235+
* Creates the workdir (with `0100` permission) which is a string out of the
236+
* folder where all users are located, a constant `.workdirs` and the username
237+
* e.g. `${usersFolder}/.workdirs/${user}`
238+
*
238239
* @access private
240+
*
239241
* @param {String} usersFolder The folder where all user folders are
240-
* @param {String} user The name of the user
241242
* @param {Function} callback The callback function
242243
*/
243-
function overlay_user(usersFolder, user, callback)
244+
function overlay_root(usersFolder, callback)
244245
{
245-
var upperdir = usersFolder+'/'+user
246-
var workdir = usersFolder+'/.workdirs/'+user
246+
var upperdir = usersFolder+'/root'
247+
var workdir = usersFolder+'/.workdirs/root'
247248

248249
mkdirp(workdir, '0100', function(error)
249250
{
@@ -256,17 +257,14 @@ function overlay_user(usersFolder, user, callback)
256257
lowerdir: '/',
257258
upperdir: upperdir,
258259
workdir : workdir
259-
};
260+
}
260261

261-
if(user === 'root') upperdir = '/root'
262+
upperdir = '/root'
262263

263264
utils.mkdirMount(upperdir, type, MS_NOSUID, extras, function(error)
264265
{
265266
if(error) return callback(error)
266267

267-
if(user !== 'root')
268-
return mountDevProcTmp_ExecInit(upperdir, false, callback)
269-
270268
// Allow root to access to the content of the users filesystem
271269
async.eachSeries(
272270
[
@@ -284,12 +282,10 @@ function overlay_user(usersFolder, user, callback)
284282
{
285283
if(error) return callback(error)
286284

287-
mountDevProcTmp_ExecInit(HOME, true, function(error)
285+
prepareRootFilesystems(HOME, function(error)
288286
{
289287
if(error) return callback(error)
290288

291-
ROOT_HOME = HOME
292-
293289
callback(null, HOME+'/home')
294290
})
295291
})
@@ -309,39 +305,6 @@ function filterUser(user)
309305
return user[0] !== '.' && user !== 'root' && user !== 'lost+found'
310306
}
311307

312-
/**
313-
* Mount users directories and exec their `init` files
314-
* @access private
315-
* @param {String} usersFolder The path to all user directories
316-
* @param {Function} callback The callback function
317-
* @return {Function} Returns the callback either with a error
318-
* or with null if everything was fine
319-
*/
320-
function overlay_users(usersFolder, callback)
321-
{
322-
function done(error)
323-
{
324-
// Remove the modules from initramfs to free memory
325-
// rimraf('/lib/node_modules')
326-
rimraf('/lib/node_modules/nodeos-mount-utils')
327-
328-
// Make '/usr' a opaque folder (OverlayFS feature)
329-
rimraf('/usr')
330-
331-
callback(error)
332-
}
333-
334-
// Mount users directories and exec their init files
335-
fs.readdir(usersFolder, function(error, users)
336-
{
337-
if(error) return done(error)
338-
339-
async.each(users.filter(filterUser),
340-
overlay_user.bind(undefined, usersFolder),
341-
done)
342-
})
343-
}
344-
345308
/**
346309
* This helper waits with a limit of tries until the path exists
347310
* @access private
@@ -419,15 +382,38 @@ function askLocation(error)
419382
* If the `single` key is set in the cmdline it starts a admin repl
420383
* If not it just overlays the users filesystem
421384
* @access private
422-
* @param {String} home The path to folder of the users
385+
* @param {String} usersFolder The path to folder of the users
423386
*/
424-
function adminOrUsers(home)
387+
function adminOrUsers(usersFolder)
425388
{
426389
// Enter administrator mode
427390
if(single) return utils.startRepl('Administrator mode')
428391

429392
// Users filesystem don't have a root user, just overlay users folders
430-
overlay_users(home, onerror)
393+
394+
function done(error)
395+
{
396+
// Remove the modules from initramfs to free memory
397+
// rimraf('/lib/node_modules')
398+
rimraf('/lib/node_modules/jocker')
399+
400+
// Make '/usr' a opaque folder (OverlayFS feature)
401+
rimraf('/usr')
402+
403+
if(error) onerror(error)
404+
}
405+
406+
// Mount users directories and exec their init files
407+
fs.readdir(usersFolder, function(error, users)
408+
{
409+
if(error) return done(error)
410+
411+
async.each(users.filter(filterUser), function(username, callback)
412+
{
413+
jocker.run(usersFolder+'/'+username, '/init', {PATH: '/bin'}, callback)
414+
},
415+
done)
416+
})
431417
}
432418

433419
/**
@@ -455,16 +441,17 @@ function prepareSessions()
455441
{
456442
if(error)
457443
{
458-
if(error.code != 'ENOENT') return onerror(error)
444+
if(error.code !== 'ENOENT') return onerror(error)
459445

460446
return adminOrUsers(HOME)
461447
}
462448

463-
overlay_user(HOME, 'root', function(error, home)
449+
// There's an administrator account, prepare it first
450+
overlay_root(HOME, function(error, newHome)
464451
{
465452
if(error) return onerror(error)
466453

467-
adminOrUsers(home)
454+
adminOrUsers(newHome)
468455
})
469456
})
470457
}
@@ -489,11 +476,10 @@ function mountUsersFS(cmdline)
489476
if(usersDev === undefined) usersDev = cmdline.root
490477

491478
// Running on a container (Docker, vagga), don't mount the users filesystem
492-
if(usersDev === 'container')
493-
prepareSessions()
479+
if(usersDev === 'container') return prepareSessions()
494480

495481
// Running on real hardware or virtual machine, mount the users filesystem
496-
else if(usersDev)
482+
if(usersDev)
497483
waitUntilExists(usersDev, 5, function(error)
498484
{
499485
if(error) return askLocation.call(cmdline, error)
@@ -514,8 +500,9 @@ function mountUsersFS(cmdline)
514500
else
515501
fs.readFile('resources/readonly_warning.txt', 'utf8', function(error, data)
516502
{
517-
if(!error) console.warn(data)
503+
if(error) return onerror(error)
518504

505+
console.warn(data)
519506
utils.startRepl('NodeOS-mount-filesystems')
520507
})
521508
}

0 commit comments

Comments
 (0)