diff --git a/src/mounts.js b/src/mounts.js index 0918eb7f5..8a251f89a 100644 --- a/src/mounts.js +++ b/src/mounts.js @@ -1,11 +1,10 @@ 'use strict'; exports = module.exports = { - writeMountFile, + tryAddMount, removeMountFile, validateMountOptions, getStatus, - waitForMount }; const assert = require('assert'), @@ -15,9 +14,9 @@ const assert = require('assert'), fs = require('fs'), path = require('path'), paths = require('./paths.js'), + promiseRetry = require('./promise-retry.js'), safe = require('safetydance'), shell = require('./shell.js'); -const promiseRetry = require('./promise-retry.js'); const ADD_MOUNT_CMD = path.join(__dirname, 'scripts/addmount.sh'); const RM_MOUNT_CMD = path.join(__dirname, 'scripts/rmmount.sh'); @@ -57,11 +56,9 @@ function validateMountOptions(type, options) { } } -async function writeMountFile(volume) { +function renderMountFile(volume) { assert.strictEqual(typeof volume, 'object'); - if (constants.TEST) return; - const {name, hostPath, mountType, mountOptions} = volume; let options, what, type; @@ -97,8 +94,7 @@ async function writeMountFile(volume) { return; } - const mountFileContents = ejs.render(SYSTEMD_MOUNT_EJS, { name, what, where: hostPath, options, type }); - await shell.promises.sudo('generateMountFile', [ ADD_MOUNT_CMD, mountFileContents ], {}); + return ejs.render(SYSTEMD_MOUNT_EJS, { name, what, where: hostPath, options, type }); } async function removeMountFile(hostPath) { @@ -141,16 +137,30 @@ async function getStatus(mountType, hostPath) { return { state, message }; } -async function waitForMount(mountType, hostPath, options) { - assert.strictEqual(typeof mountType, 'string'); - assert.strictEqual(typeof hostPath, 'string'); +async function tryAddMount(volume, oldVolume, options) { + assert.strictEqual(typeof volume, 'object'); + assert.strictEqual(typeof oldVolume, 'object'); // can be null assert.strictEqual(typeof options, 'object'); - if (mountType === 'noop' || mountType === 'mountpoint') return; // noop is from volume provider and mountpoint is from backup provider + if (volume.mountType === 'noop' || volume.mountType === 'mountpoint') return; // noop is from volume provider and mountpoint is from backup provider - await promiseRetry(options, async function () { - const status = await getStatus(mountType, hostPath); + if (constants.TEST) return; + + await shell.promises.sudo('addMount', [ ADD_MOUNT_CMD, renderMountFile(volume) ], {}); + + const [error] = await safe(promiseRetry(options, async function () { + const status = await getStatus(volume.mountType, volume.hostPath); if (status.state === 'active') return true; throw new BoxError(BoxError.MOUNT_ERROR, `Mount is not active (${status.state}): ${status.message}`); - }); + })); + + if (!error) return; // success + + // revert to old configuration + if (oldVolume) { + await safe(shell.promises.sudo('revertMount', [ ADD_MOUNT_CMD, renderMountFile(oldVolume) ], {})); + } else { + await removeMountFile(volume.hostPath); + } + throw error; } diff --git a/src/scripts/addmount.sh b/src/scripts/addmount.sh index 82e933c91..529d3d902 100755 --- a/src/scripts/addmount.sh +++ b/src/scripts/addmount.sh @@ -23,9 +23,11 @@ mount_file_contents="$1" where=$(echo "${mount_file_contents}" | grep "^Where=" | cut -d'=' -f 2) mount_filename=$(systemd-escape -p --suffix=mount "$where") - -echo "$mount_file_contents" > "/etc/systemd/system/${mount_filename}" +mount_file="/etc/systemd/system/${mount_filename}" systemctl stop "${mount_filename}" || true + +echo "$mount_file_contents" > "${mount_file}" + systemctl daemon-reload systemctl enable --no-block --now "${mount_filename}" || true diff --git a/src/volumes.js b/src/volumes.js index 3d8f73363..f63d995e8 100644 --- a/src/volumes.js +++ b/src/volumes.js @@ -86,13 +86,7 @@ async function add(volume, auditSource) { const id = uuid.v4(); - // try mounting and unmount on fail - await mounts.writeMountFile(volume); - [error] = await safe(mounts.waitForMount(mountType, hostPath, { times: 20, interval: 500 })); // 10 seconds - if (error) { - await safe(mounts.removeMountFile(hostPath)); - throw error; - } + if (volume.mountType !== 'noop') await mounts.tryAddMount(volume, null /* oldVolume */, { times: 20, interval: 500 }); // 10 seconds try { await database.query('INSERT INTO volumes (id, name, hostPath, mountType, mountOptionsJson) VALUES (?, ?, ?, ?, ?)', [ id, name, hostPath, mountType, JSON.stringify(mountOptions) ]); @@ -123,16 +117,10 @@ async function update(volume, mountType, mountOptions) { if (mountType === 'noop') { await mounts.removeMountFile(volume.hostPath); } else { - await mounts.writeMountFile(Object.assign({}, volume, { mountType, mountOptions })); - - [error] = await safe(mounts.waitForMount(mountType, volume.hostPath, { times: 20, interval: 500 })); // 10 seconds - if (error) { - await safe(mounts.removeMountFile(volume.hostPath)); - throw error; - } + await mounts.tryAddMount(Object.assign({}, volume, { mountType, mountOptions }), volume, { times: 20, interval: 500 }); // 10 seconds } - const [, result] = await database.query('UPDATE volumes SET mountType=?, mountOptionsJson=? WHERE id=?', [ mountType, JSON.stringify(mountOptions), volume.id ]); + const result = await database.query('UPDATE volumes SET mountOptionsJson=? WHERE id=?', [ JSON.stringify(mountOptions), volume.id ]); if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'Volume not found'); }