feat(fs/mount): keep open file on mount to avoid external umount (#3998)

This commit is contained in:
Julien Fontanet 2019-02-28 11:52:45 +01:00 committed by Pierre Donias
parent 6aa8e0d4ce
commit c728eeaffa
4 changed files with 58 additions and 28 deletions

View File

@ -24,6 +24,7 @@
"@marsaud/smb2": "^0.13.0", "@marsaud/smb2": "^0.13.0",
"@sindresorhus/df": "^2.1.0", "@sindresorhus/df": "^2.1.0",
"@xen-orchestra/async-map": "^0.0.0", "@xen-orchestra/async-map": "^0.0.0",
"decorator-synchronized": "^0.3.0",
"execa": "^1.0.0", "execa": "^1.0.0",
"fs-extra": "^7.0.0", "fs-extra": "^7.0.0",
"get-stream": "^4.0.0", "get-stream": "^4.0.0",

View File

@ -1,5 +1,6 @@
import execa from 'execa' import execa from 'execa'
import fs from 'fs-extra' import fs from 'fs-extra'
import { ignoreErrors } from 'promise-toolbox'
import { join } from 'path' import { join } from 'path'
import { tmpdir } from 'os' import { tmpdir } from 'os'
@ -21,11 +22,12 @@ export default class MountHandler extends LocalHandler {
super(remote, opts) super(remote, opts)
this._execa = useSudo ? sudoExeca : execa this._execa = useSudo ? sudoExeca : execa
this._keeper = undefined
this._params = { this._params = {
...params, ...params,
options: [params.options, remote.options].filter( options: [params.options, remote.options]
_ => _ !== undefined .filter(_ => _ !== undefined)
).join(','), .join(','),
} }
this._realPath = join( this._realPath = join(
mountsDir, mountsDir,
@ -37,19 +39,20 @@ export default class MountHandler extends LocalHandler {
} }
async _forget() { async _forget() {
await this._execa('umount', ['--force', this._getRealPath()], { const keeper = this._keeper
env: { if (keeper === undefined) {
LANG: 'C', return
}, }
}).catch(error => { this._keeper = undefined
if ( await fs.close(keeper)
error == null ||
typeof error.stderr !== 'string' || await ignoreErrors.call(
!error.stderr.includes('not mounted') this._execa('umount', [this._getRealPath()], {
) { env: {
throw error LANG: 'C',
} },
}) })
)
} }
_getRealPath() { _getRealPath() {
@ -57,18 +60,32 @@ export default class MountHandler extends LocalHandler {
} }
async _sync() { async _sync() {
await fs.ensureDir(this._getRealPath()) // in case of multiple `sync`s, ensure we properly close previous keeper
const { type, device, options, env } = this._params {
return this._execa( const keeper = this._keeper
'mount', if (keeper !== undefined) {
['-t', type, device, this._getRealPath(), '-o', options], this._keeper = undefined
{ ignoreErrors.call(fs.close(keeper))
env: {
LANG: 'C',
...env,
},
} }
).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 let stderr
if ( if (
error == null || error == null ||
@ -77,6 +94,14 @@ export default class MountHandler extends LocalHandler {
) { ) {
throw error 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))
} }
} }

View File

@ -5,6 +5,7 @@ import getStream from 'get-stream'
import asyncMap from '@xen-orchestra/async-map' import asyncMap from '@xen-orchestra/async-map'
import path from 'path' import path from 'path'
import synchronized from 'decorator-synchronized'
import { fromCallback, fromEvent, ignoreErrors, timeout } from 'promise-toolbox' import { fromCallback, fromEvent, ignoreErrors, timeout } from 'promise-toolbox'
import { parse } from 'xo-remote-parser' import { parse } from 'xo-remote-parser'
import { randomBytes } from 'crypto' import { randomBytes } from 'crypto'
@ -216,6 +217,7 @@ export default class RemoteHandlerAbstract {
// FIXME: Some handlers are implemented based on system-wide mecanisms (such // FIXME: Some handlers are implemented based on system-wide mecanisms (such
// as mount), forgetting them might breaking other processes using the same // as mount), forgetting them might breaking other processes using the same
// remote. // remote.
@synchronized()
async forget(): Promise<void> { async forget(): Promise<void> {
await this._forget() await this._forget()
} }
@ -354,6 +356,7 @@ export default class RemoteHandlerAbstract {
// metadata // metadata
// //
// This method MUST ALWAYS be called before using the handler. // This method MUST ALWAYS be called before using the handler.
@synchronized()
async sync(): Promise<void> { async sync(): Promise<void> {
await this._sync() await this._sync()
} }

View File

@ -19,6 +19,7 @@
### Released packages ### Released packages
- @xen-orchestra/fs v0.7.0
- xen-api v0.24.3 - xen-api v0.24.3
- xoa-updater v0.15.2 - xoa-updater v0.15.2
- xo-server v5.36.0 - xo-server v5.36.0