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",
"@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",

View File

@ -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()], {
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',
},
}).catch(error => {
if (
error == null ||
typeof error.stderr !== 'string' ||
!error.stderr.includes('not mounted')
) {
throw error
}
})
)
}
_getRealPath() {
@ -57,18 +60,32 @@ export default class MountHandler extends LocalHandler {
}
async _sync() {
await fs.ensureDir(this._getRealPath())
// 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))
}
}
const realPath = this._getRealPath()
await fs.ensureDir(realPath)
try {
const { type, device, options, env } = this._params
return this._execa(
await this._execa(
'mount',
['-t', type, device, this._getRealPath(), '-o', options],
['-t', type, device, realPath, '-o', options],
{
env: {
LANG: 'C',
...env,
},
}
).catch(error => {
)
} 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))
}
}

View File

@ -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<void> {
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<void> {
await this._sync()
}

View File

@ -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