1
0
Fork 0
mirror of https://github.com/Luzifer/ansible-role-version.git synced 2024-12-24 11:31:21 +00:00
ansible-role-version/vendor/gopkg.in/src-d/go-billy.v4/helper/mount/mount.go
Knut Ahlers 209b813c5b
Update dependencies
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2018-03-19 18:16:10 +01:00

266 lines
5.5 KiB
Go

package mount
import (
"io"
"os"
"path/filepath"
"strings"
"fmt"
"gopkg.in/src-d/go-billy.v4"
"gopkg.in/src-d/go-billy.v4/helper/polyfill"
)
var separator = string(filepath.Separator)
// Mount is a helper that allows to emulate the behavior of mount in memory.
// Very usufull to create a temporal dir, on filesystem where is a performance
// penalty in doing so.
type Mount struct {
underlying billy.Filesystem
source billy.Filesystem
mountpoint string
}
// New creates a new filesystem wrapping up 'fs' the intercepts all the calls
// made to `mountpoint` path and redirecting it to `source` filesystem.
func New(fs billy.Basic, mountpoint string, source billy.Basic) *Mount {
return &Mount{
underlying: polyfill.New(fs),
source: polyfill.New(source),
mountpoint: cleanPath(mountpoint),
}
}
func (h *Mount) Create(path string) (billy.File, error) {
fs, fullpath := h.getBasicAndPath(path)
if fullpath == "." {
return nil, os.ErrInvalid
}
f, err := fs.Create(fullpath)
return wrapFile(f, path), err
}
func (h *Mount) Open(path string) (billy.File, error) {
fs, fullpath := h.getBasicAndPath(path)
if fullpath == "." {
return nil, os.ErrInvalid
}
f, err := fs.Open(fullpath)
return wrapFile(f, path), err
}
func (h *Mount) OpenFile(path string, flag int, mode os.FileMode) (billy.File, error) {
fs, fullpath := h.getBasicAndPath(path)
if fullpath == "." {
return nil, os.ErrInvalid
}
f, err := fs.OpenFile(fullpath, flag, mode)
return wrapFile(f, path), err
}
func (h *Mount) Rename(from, to string) error {
fromInSource := h.isMountpoint(from)
toInSource := h.isMountpoint(to)
var fromFS, toFS billy.Basic
switch {
case fromInSource && toInSource:
from = h.mustRelToMountpoint(from)
to = h.mustRelToMountpoint(to)
return h.source.Rename(from, to)
case !fromInSource && !toInSource:
return h.underlying.Rename(from, to)
case fromInSource && !toInSource:
fromFS = h.source
from = h.mustRelToMountpoint(from)
toFS = h.underlying
to = cleanPath(to)
case !fromInSource && toInSource:
fromFS = h.underlying
from = cleanPath(from)
toFS = h.source
to = h.mustRelToMountpoint(to)
}
if err := copyPath(fromFS, toFS, from, to); err != nil {
return err
}
return fromFS.Remove(from)
}
func (h *Mount) Stat(path string) (os.FileInfo, error) {
fs, fullpath := h.getBasicAndPath(path)
return fs.Stat(fullpath)
}
func (h *Mount) Remove(path string) error {
fs, fullpath := h.getBasicAndPath(path)
if fullpath == "." {
return os.ErrInvalid
}
return fs.Remove(fullpath)
}
func (h *Mount) ReadDir(path string) ([]os.FileInfo, error) {
fs, fullpath, err := h.getDirAndPath(path)
if err != nil {
return nil, err
}
return fs.ReadDir(fullpath)
}
func (h *Mount) MkdirAll(filename string, perm os.FileMode) error {
fs, fullpath, err := h.getDirAndPath(filename)
if err != nil {
return err
}
return fs.MkdirAll(fullpath, perm)
}
func (h *Mount) Symlink(target, link string) error {
fs, fullpath, err := h.getSymlinkAndPath(link)
if err != nil {
return err
}
resolved := filepath.Join(filepath.Dir(link), target)
if h.isMountpoint(resolved) != h.isMountpoint(link) {
return fmt.Errorf("invalid symlink, target is crossing filesystems")
}
return fs.Symlink(target, fullpath)
}
func (h *Mount) Join(elem ...string) string {
return h.underlying.Join(elem...)
}
func (h *Mount) Readlink(link string) (string, error) {
fs, fullpath, err := h.getSymlinkAndPath(link)
if err != nil {
return "", err
}
return fs.Readlink(fullpath)
}
func (h *Mount) Lstat(path string) (os.FileInfo, error) {
fs, fullpath, err := h.getSymlinkAndPath(path)
if err != nil {
return nil, err
}
return fs.Lstat(fullpath)
}
func (h *Mount) Underlying() billy.Basic {
return h.underlying
}
func (fs *Mount) getBasicAndPath(path string) (billy.Basic, string) {
path = cleanPath(path)
if !fs.isMountpoint(path) {
return fs.underlying, path
}
return fs.source, fs.mustRelToMountpoint(path)
}
func (fs *Mount) getDirAndPath(path string) (billy.Dir, string, error) {
path = cleanPath(path)
if !fs.isMountpoint(path) {
return fs.underlying.(billy.Dir), path, nil
}
return fs.source.(billy.Dir), fs.mustRelToMountpoint(path), nil
}
func (fs *Mount) getSymlinkAndPath(path string) (billy.Symlink, string, error) {
path = cleanPath(path)
if !fs.isMountpoint(path) {
return fs.underlying.(billy.Symlink), path, nil
}
return fs.source.(billy.Symlink), fs.mustRelToMountpoint(path), nil
}
func (fs *Mount) mustRelToMountpoint(path string) string {
path = cleanPath(path)
fullpath, err := filepath.Rel(fs.mountpoint, path)
if err != nil {
panic(err)
}
return fullpath
}
func (fs *Mount) isMountpoint(path string) bool {
path = cleanPath(path)
return strings.HasPrefix(path, fs.mountpoint)
}
func cleanPath(path string) string {
path = filepath.FromSlash(path)
rel, err := filepath.Rel(separator, path)
if err == nil {
path = rel
}
return filepath.Clean(path)
}
// copyPath copies a file across filesystems.
func copyPath(src, dst billy.Basic, srcPath, dstPath string) error {
dstFile, err := dst.Create(dstPath)
if err != nil {
return err
}
srcFile, err := src.Open(srcPath)
if err != nil {
return nil
}
_, err = io.Copy(dstFile, srcFile)
if err != nil {
return nil
}
err = dstFile.Close()
if err != nil {
_ = srcFile.Close()
return err
}
return srcFile.Close()
}
type file struct {
billy.File
name string
}
func wrapFile(f billy.File, filename string) billy.File {
if f == nil {
return nil
}
return &file{
File: f,
name: cleanPath(filename),
}
}
func (f *file) Name() string {
return f.name
}