feat(@xen-orchestra/fs): implement SMB on top of mount (#3708)

Fixes #2257
This commit is contained in:
Enishowk 2019-01-07 10:05:18 +01:00 committed by Julien Fontanet
parent 6c2e493576
commit e8a98945f5
7 changed files with 140 additions and 97 deletions

View File

@ -0,0 +1,76 @@
import execa from 'execa'
import fs from 'fs-extra'
import { join } from 'path'
import { tmpdir } from 'os'
import LocalHandler from './local'
const sudoExeca = (command, args, opts) =>
execa('sudo', [command, ...args], opts)
export default class MountHandler extends LocalHandler {
constructor(
remote,
{
mountsDir = join(tmpdir(), 'xo-fs-mounts'),
useSudo = false,
...opts
} = {},
params
) {
super(remote, opts)
this._execa = useSudo ? sudoExeca : execa
this._params = params
this._realPath = join(
mountsDir,
remote.id ||
Math.random()
.toString(36)
.slice(2)
)
}
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
}
})
}
_getRealPath() {
return this._realPath
}
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,
},
}
).catch(error => {
if (
error == null ||
typeof error.stderr !== 'string' ||
!error.stderr.includes('already mounted')
) {
throw error
}
})
}
}

View File

@ -0,0 +1,9 @@
import path from 'path'
const { resolve } = path.posix
// normalize the path:
// - does not contains `.` or `..` (cannot escape root dir)
// - always starts with `/`
const normalizePath = path => resolve('/', path)
export { normalizePath as default }

View File

@ -10,9 +10,10 @@ import { parse } from 'xo-remote-parser'
import { randomBytes } from 'crypto'
import { type Readable, type Writable } from 'stream'
import normalizePath from './_normalizePath'
import { createChecksumStream, validChecksumOfReadStream } from './checksum'
const { dirname, resolve } = path.posix
const { dirname } = path.posix
type Data = Buffer | Readable | string
type FileDescriptor = {| fd: mixed, path: string |}
@ -23,11 +24,6 @@ type File = FileDescriptor | string
const checksumFile = file => file + '.checksum'
// normalize the path:
// - does not contains `.` or `..` (cannot escape root dir)
// - always starts with `/`
const normalizePath = path => resolve('/', path)
const DEFAULT_TIMEOUT = 6e5 // 10 min
const ignoreEnoent = error => {

View File

@ -1,19 +1,27 @@
// @flow
import execa from 'execa'
import type RemoteHandler from './abstract'
import RemoteHandlerLocal from './local'
import RemoteHandlerNfs from './nfs'
import RemoteHandlerSmb from './smb'
import RemoteHandlerSmbMount from './smb-mount'
export type { default as RemoteHandler } from './abstract'
export type Remote = { url: string }
const HANDLERS = {
file: RemoteHandlerLocal,
smb: RemoteHandlerSmb,
nfs: RemoteHandlerNfs,
}
try {
execa.sync('mount.cifs', ['-V'])
HANDLERS.smb = RemoteHandlerSmbMount
} catch (_) {
HANDLERS.smb = RemoteHandlerSmb
}
export const getHandler = (remote: Remote, ...rest: any): RemoteHandler => {
// FIXME: should be done in xo-remote-parser.
const type = remote.url.split('://')[0]

View File

@ -1,100 +1,21 @@
import execa from 'execa'
import fs from 'fs-extra'
import { join } from 'path'
import { tmpdir } from 'os'
import { parse } from 'xo-remote-parser'
import LocalHandler from './local'
import MountHandler from './_mount'
const DEFAULT_NFS_OPTIONS = 'vers=3'
const sudoExeca = (command, args, opts) =>
execa('sudo', [command, ...args], opts)
export default class NfsHandler extends LocalHandler {
constructor(
remote,
{
mountsDir = join(tmpdir(), 'xo-fs-mounts'),
useSudo = false,
...opts
} = {}
) {
super(remote, opts)
this._realPath = join(
mountsDir,
remote.id ||
Math.random()
.toString(36)
.slice(2)
)
this._execa = useSudo ? sudoExeca : execa
export default class NfsHandler extends MountHandler {
constructor(remote, opts) {
const { host, port, path, options } = parse(remote.url)
super(remote, opts, {
type: 'nfs',
device: `${host}${port !== undefined ? ':' + port : ''}:${path}`,
options:
DEFAULT_NFS_OPTIONS + (options !== undefined ? `,${options}` : ''),
})
}
get type() {
return 'nfs'
}
_getRealPath() {
return this._realPath
}
async _mount() {
await fs.ensureDir(this._getRealPath())
const { host, path, port, options } = this._remote
return this._execa(
'mount',
[
'-t',
'nfs',
'-o',
DEFAULT_NFS_OPTIONS + (options !== undefined ? `,${options}` : ''),
`${host}${port !== undefined ? ':' + port : ''}:${path}`,
this._getRealPath(),
],
{
env: {
LANG: 'C',
},
}
).catch(error => {
if (
error == null ||
typeof error.stderr !== 'string' ||
!error.stderr.includes('already mounted')
) {
throw error
}
})
}
async _umount() {
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
}
})
}
async _forget() {
try {
await this._umount(this._remote)
} catch (_) {
// We have to go on...
}
}
async _sync() {
await this._mount()
return this._remote
}
}

View File

@ -0,0 +1,31 @@
import { parse } from 'xo-remote-parser'
import MountHandler from './_mount'
import normalizePath from './_normalizePath'
export default class SmbMountHandler extends MountHandler {
constructor(remote, opts) {
const {
domain = 'WORKGROUP',
host,
options,
password,
path,
username,
} = parse(remote.url)
super(remote, opts, {
type: 'cifs',
device: '//' + host + normalizePath(path),
options:
`domain=${domain}` + (options !== undefined ? `,${options}` : ''),
env: {
USER: username,
PASSWD: password,
},
})
}
get type() {
return 'smb'
}
}

View File

@ -5,11 +5,13 @@
### Enhancements
- [Backup NG] Restore logs moved to restore tab [#3772](https://github.com/vatesfr/xen-orchestra/issues/3772) (PR [#3802](https://github.com/vatesfr/xen-orchestra/pull/3802))
- [Remotes] New SMB implementation that provides better stability and performance [#2257](https://github.com/vatesfr/xen-orchestra/issues/2257) (PR [#3708](https://github.com/vatesfr/xen-orchestra/pull/3708))
### Bug fixes
### Released packages
- @xen-orchestra/fs v0.6.0
- xo-server v5.33.0
- xo-web v5.33.0