From c728eeaffa4874098c2454b5eeb8d73db312258c Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 28 Feb 2019 11:52:45 +0100 Subject: [PATCH] feat(fs/mount): keep open file on mount to avoid external umount (#3998) --- @xen-orchestra/fs/package.json | 1 + @xen-orchestra/fs/src/_mount.js | 81 ++++++++++++++++++++----------- @xen-orchestra/fs/src/abstract.js | 3 ++ CHANGELOG.unreleased.md | 1 + 4 files changed, 58 insertions(+), 28 deletions(-) diff --git a/@xen-orchestra/fs/package.json b/@xen-orchestra/fs/package.json index d636d7323..5b0c829ca 100644 --- a/@xen-orchestra/fs/package.json +++ b/@xen-orchestra/fs/package.json @@ -24,6 +24,7 @@ "@marsaud/smb2": "^0.13.0", "@sindresorhus/df": "^2.1.0", "@xen-orchestra/async-map": "^0.0.0", + "decorator-synchronized": "^0.3.0", "execa": "^1.0.0", "fs-extra": "^7.0.0", "get-stream": "^4.0.0", diff --git a/@xen-orchestra/fs/src/_mount.js b/@xen-orchestra/fs/src/_mount.js index 0b0048423..aefd80dcd 100644 --- a/@xen-orchestra/fs/src/_mount.js +++ b/@xen-orchestra/fs/src/_mount.js @@ -1,5 +1,6 @@ import execa from 'execa' import fs from 'fs-extra' +import { ignoreErrors } from 'promise-toolbox' import { join } from 'path' import { tmpdir } from 'os' @@ -21,11 +22,12 @@ export default class MountHandler extends LocalHandler { super(remote, opts) this._execa = useSudo ? sudoExeca : execa + this._keeper = undefined this._params = { ...params, - options: [params.options, remote.options].filter( - _ => _ !== undefined - ).join(','), + options: [params.options, remote.options] + .filter(_ => _ !== undefined) + .join(','), } this._realPath = join( mountsDir, @@ -37,19 +39,20 @@ export default class MountHandler extends LocalHandler { } async _forget() { - await this._execa('umount', ['--force', this._getRealPath()], { - env: { - LANG: 'C', - }, - }).catch(error => { - if ( - error == null || - typeof error.stderr !== 'string' || - !error.stderr.includes('not mounted') - ) { - throw error - } - }) + const keeper = this._keeper + if (keeper === undefined) { + return + } + this._keeper = undefined + await fs.close(keeper) + + await ignoreErrors.call( + this._execa('umount', [this._getRealPath()], { + env: { + LANG: 'C', + }, + }) + ) } _getRealPath() { @@ -57,18 +60,32 @@ export default class MountHandler extends LocalHandler { } async _sync() { - await fs.ensureDir(this._getRealPath()) - const { type, device, options, env } = this._params - return this._execa( - 'mount', - ['-t', type, device, this._getRealPath(), '-o', options], - { - env: { - LANG: 'C', - ...env, - }, + // in case of multiple `sync`s, ensure we properly close previous keeper + { + const keeper = this._keeper + if (keeper !== undefined) { + this._keeper = undefined + ignoreErrors.call(fs.close(keeper)) } - ).catch(error => { + } + + const realPath = this._getRealPath() + + await fs.ensureDir(realPath) + + try { + const { type, device, options, env } = this._params + await this._execa( + 'mount', + ['-t', type, device, realPath, '-o', options], + { + env: { + LANG: 'C', + ...env, + }, + } + ) + } catch (error) { let stderr if ( error == null || @@ -77,6 +94,14 @@ export default class MountHandler extends LocalHandler { ) { throw error } - }) + } + + // keep an open file on the mount to prevent it from being unmounted if used + // by another handler/process + const keeperPath = `${realPath}/.keeper_${Math.random() + .toString(36) + .slice(2)}` + this._keeper = await fs.open(keeperPath, 'w') + ignoreErrors.call(fs.unlink(keeperPath)) } } diff --git a/@xen-orchestra/fs/src/abstract.js b/@xen-orchestra/fs/src/abstract.js index c890fdb5a..422e15c29 100644 --- a/@xen-orchestra/fs/src/abstract.js +++ b/@xen-orchestra/fs/src/abstract.js @@ -5,6 +5,7 @@ import getStream from 'get-stream' import asyncMap from '@xen-orchestra/async-map' import path from 'path' +import synchronized from 'decorator-synchronized' import { fromCallback, fromEvent, ignoreErrors, timeout } from 'promise-toolbox' import { parse } from 'xo-remote-parser' import { randomBytes } from 'crypto' @@ -216,6 +217,7 @@ export default class RemoteHandlerAbstract { // FIXME: Some handlers are implemented based on system-wide mecanisms (such // as mount), forgetting them might breaking other processes using the same // remote. + @synchronized() async forget(): Promise { await this._forget() } @@ -354,6 +356,7 @@ export default class RemoteHandlerAbstract { // metadata // // This method MUST ALWAYS be called before using the handler. + @synchronized() async sync(): Promise { await this._sync() } diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 2e0a613b5..cc5080228 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -19,6 +19,7 @@ ### Released packages +- @xen-orchestra/fs v0.7.0 - xen-api v0.24.3 - xoa-updater v0.15.2 - xo-server v5.36.0