mirror of
https://github.com/drakkan/sftpgo.git
synced 2025-12-07 14:50:55 +03:00
173
vfs/osfs.go
173
vfs/osfs.go
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/rs/xid"
|
||||
|
||||
"github.com/drakkan/sftpgo/logger"
|
||||
"github.com/drakkan/sftpgo/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -25,19 +24,20 @@ const (
|
||||
|
||||
// OsFs is a Fs implementation that uses functions provided by the os package.
|
||||
type OsFs struct {
|
||||
name string
|
||||
connectionID string
|
||||
rootDir string
|
||||
virtualFolders []VirtualFolder
|
||||
name string
|
||||
connectionID string
|
||||
rootDir string
|
||||
// if not empty this fs is mouted as virtual folder in the specified path
|
||||
mountPath string
|
||||
}
|
||||
|
||||
// NewOsFs returns an OsFs object that allows to interact with local Os filesystem
|
||||
func NewOsFs(connectionID, rootDir string, virtualFolders []VirtualFolder) Fs {
|
||||
func NewOsFs(connectionID, rootDir, mountPath string) Fs {
|
||||
return &OsFs{
|
||||
name: osFsName,
|
||||
connectionID: connectionID,
|
||||
rootDir: rootDir,
|
||||
virtualFolders: virtualFolders,
|
||||
name: osFsName,
|
||||
connectionID: connectionID,
|
||||
rootDir: rootDir,
|
||||
mountPath: mountPath,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,32 +53,12 @@ func (fs *OsFs) ConnectionID() string {
|
||||
|
||||
// Stat returns a FileInfo describing the named file
|
||||
func (fs *OsFs) Stat(name string) (os.FileInfo, error) {
|
||||
fi, err := os.Stat(name)
|
||||
if err != nil {
|
||||
return fi, err
|
||||
}
|
||||
for _, v := range fs.virtualFolders {
|
||||
if v.MappedPath == name {
|
||||
info := NewFileInfo(v.VirtualPath, true, fi.Size(), fi.ModTime(), false)
|
||||
return info, nil
|
||||
}
|
||||
}
|
||||
return fi, err
|
||||
return os.Stat(name)
|
||||
}
|
||||
|
||||
// Lstat returns a FileInfo describing the named file
|
||||
func (fs *OsFs) Lstat(name string) (os.FileInfo, error) {
|
||||
fi, err := os.Lstat(name)
|
||||
if err != nil {
|
||||
return fi, err
|
||||
}
|
||||
for _, v := range fs.virtualFolders {
|
||||
if v.MappedPath == name {
|
||||
info := NewFileInfo(v.VirtualPath, true, fi.Size(), fi.ModTime(), false)
|
||||
return info, nil
|
||||
}
|
||||
}
|
||||
return fi, err
|
||||
return os.Lstat(name)
|
||||
}
|
||||
|
||||
// Open opens the named file for reading
|
||||
@@ -114,6 +94,13 @@ func (*OsFs) Mkdir(name string) error {
|
||||
return os.Mkdir(name, os.ModePerm)
|
||||
}
|
||||
|
||||
// MkdirAll creates a directory named path, along with any necessary parents,
|
||||
// and returns nil, or else returns an error.
|
||||
// If path is already a directory, MkdirAll does nothing and returns nil.
|
||||
func (fs *OsFs) MkdirAll(name string, uid int, gid int) error {
|
||||
return fs.createMissingDirs(name, uid, gid)
|
||||
}
|
||||
|
||||
// Symlink creates source as a symbolic link to target.
|
||||
func (*OsFs) Symlink(source, target string) error {
|
||||
return os.Symlink(source, target)
|
||||
@@ -205,45 +192,13 @@ func (fs *OsFs) CheckRootPath(username string, uid int, gid int) bool {
|
||||
SetPathPermissions(fs, fs.rootDir, uid, gid)
|
||||
}
|
||||
}
|
||||
// create any missing dirs to the defined virtual dirs
|
||||
for _, v := range fs.virtualFolders {
|
||||
p := filepath.Clean(filepath.Join(fs.rootDir, v.VirtualPath))
|
||||
err = fs.createMissingDirs(p, uid, gid)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if _, err = fs.Stat(v.MappedPath); fs.IsNotExist(err) {
|
||||
err = os.MkdirAll(v.MappedPath, os.ModePerm)
|
||||
fsLog(fs, logger.LevelDebug, "virtual directory %#v for user %#v does not exist, try to create, mkdir error: %v",
|
||||
v.MappedPath, username, err)
|
||||
if err == nil {
|
||||
SetPathPermissions(fs, fs.rootDir, uid, gid)
|
||||
}
|
||||
}
|
||||
}
|
||||
return (err == nil)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// ScanRootDirContents returns the number of files contained in the root
|
||||
// directory and their size
|
||||
func (fs *OsFs) ScanRootDirContents() (int, int64, error) {
|
||||
numFiles, size, err := fs.GetDirSize(fs.rootDir)
|
||||
for _, v := range fs.virtualFolders {
|
||||
if !v.IsIncludedInUserQuota() {
|
||||
continue
|
||||
}
|
||||
num, s, err := fs.GetDirSize(v.MappedPath)
|
||||
if err != nil {
|
||||
if fs.IsNotExist(err) {
|
||||
fsLog(fs, logger.LevelWarn, "unable to scan contents for non-existent mapped path: %#v", v.MappedPath)
|
||||
continue
|
||||
}
|
||||
return numFiles, size, err
|
||||
}
|
||||
numFiles += num
|
||||
size += s
|
||||
}
|
||||
return numFiles, size, err
|
||||
return fs.GetDirSize(fs.rootDir)
|
||||
}
|
||||
|
||||
// GetAtomicUploadPath returns the path to use for an atomic upload
|
||||
@@ -254,18 +209,13 @@ func (*OsFs) GetAtomicUploadPath(name string) string {
|
||||
}
|
||||
|
||||
// GetRelativePath returns the path for a file relative to the user's home dir.
|
||||
// This is the path as seen by SFTP users
|
||||
// This is the path as seen by SFTPGo users
|
||||
func (fs *OsFs) GetRelativePath(name string) string {
|
||||
basePath := fs.rootDir
|
||||
virtualPath := "/"
|
||||
for _, v := range fs.virtualFolders {
|
||||
if strings.HasPrefix(name, v.MappedPath+string(os.PathSeparator)) ||
|
||||
filepath.Clean(name) == v.MappedPath {
|
||||
basePath = v.MappedPath
|
||||
virtualPath = v.VirtualPath
|
||||
}
|
||||
if fs.mountPath != "" {
|
||||
virtualPath = fs.mountPath
|
||||
}
|
||||
rel, err := filepath.Rel(basePath, filepath.Clean(name))
|
||||
rel, err := filepath.Rel(fs.rootDir, filepath.Clean(name))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
@@ -287,28 +237,31 @@ func (*OsFs) Join(elem ...string) string {
|
||||
}
|
||||
|
||||
// ResolvePath returns the matching filesystem path for the specified sftp path
|
||||
func (fs *OsFs) ResolvePath(sftpPath string) (string, error) {
|
||||
func (fs *OsFs) ResolvePath(virtualPath string) (string, error) {
|
||||
if !filepath.IsAbs(fs.rootDir) {
|
||||
return "", fmt.Errorf("Invalid root path: %v", fs.rootDir)
|
||||
return "", fmt.Errorf("invalid root path: %v", fs.rootDir)
|
||||
}
|
||||
basePath, r := fs.GetFsPaths(sftpPath)
|
||||
if fs.mountPath != "" {
|
||||
virtualPath = strings.TrimPrefix(virtualPath, fs.mountPath)
|
||||
}
|
||||
r := filepath.Clean(filepath.Join(fs.rootDir, virtualPath))
|
||||
p, err := filepath.EvalSymlinks(r)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return "", err
|
||||
} else if os.IsNotExist(err) {
|
||||
// The requested path doesn't exist, so at this point we need to iterate up the
|
||||
// path chain until we hit a directory that _does_ exist and can be validated.
|
||||
_, err = fs.findFirstExistingDir(r, basePath)
|
||||
_, err = fs.findFirstExistingDir(r)
|
||||
if err != nil {
|
||||
fsLog(fs, logger.LevelWarn, "error resolving non-existent path %#v", err)
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
|
||||
err = fs.isSubDir(p, basePath)
|
||||
err = fs.isSubDir(p)
|
||||
if err != nil {
|
||||
fsLog(fs, logger.LevelWarn, "Invalid path resolution, dir %#v original path %#v resolved %#v err: %v",
|
||||
p, sftpPath, r, err)
|
||||
p, virtualPath, r, err)
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
@@ -339,43 +292,9 @@ func (*OsFs) HasVirtualFolders() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GetFsPaths returns the base path and filesystem path for the given sftpPath.
|
||||
// base path is the root dir or matching the virtual folder dir for the sftpPath.
|
||||
// file path is the filesystem path matching the sftpPath
|
||||
func (fs *OsFs) GetFsPaths(sftpPath string) (string, string) {
|
||||
basePath := fs.rootDir
|
||||
virtualPath, mappedPath := fs.getMappedFolderForPath(sftpPath)
|
||||
if mappedPath != "" {
|
||||
basePath = mappedPath
|
||||
sftpPath = strings.TrimPrefix(utils.CleanPath(sftpPath), virtualPath)
|
||||
}
|
||||
r := filepath.Clean(filepath.Join(basePath, sftpPath))
|
||||
return basePath, r
|
||||
}
|
||||
|
||||
// returns the path for the mapped folders or an empty string
|
||||
func (fs *OsFs) getMappedFolderForPath(p string) (virtualPath, mappedPath string) {
|
||||
if len(fs.virtualFolders) == 0 {
|
||||
return
|
||||
}
|
||||
dirsForPath := utils.GetDirsForSFTPPath(p)
|
||||
// dirsForPath contains all the dirs for a given path in reverse order
|
||||
// for example if the path is: /1/2/3/4 it contains:
|
||||
// [ "/1/2/3/4", "/1/2/3", "/1/2", "/1", "/" ]
|
||||
// so the first match is the one we are interested to
|
||||
for _, val := range dirsForPath {
|
||||
for _, v := range fs.virtualFolders {
|
||||
if val == v.VirtualPath {
|
||||
return v.VirtualPath, v.MappedPath
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *OsFs) findNonexistentDirs(path, rootPath string) ([]string, error) {
|
||||
func (fs *OsFs) findNonexistentDirs(filePath string) ([]string, error) {
|
||||
results := []string{}
|
||||
cleanPath := filepath.Clean(path)
|
||||
cleanPath := filepath.Clean(filePath)
|
||||
parent := filepath.Dir(cleanPath)
|
||||
_, err := os.Stat(parent)
|
||||
|
||||
@@ -391,15 +310,15 @@ func (fs *OsFs) findNonexistentDirs(path, rootPath string) ([]string, error) {
|
||||
if err != nil {
|
||||
return results, err
|
||||
}
|
||||
err = fs.isSubDir(p, rootPath)
|
||||
err = fs.isSubDir(p)
|
||||
if err != nil {
|
||||
fsLog(fs, logger.LevelWarn, "error finding non existing dir: %v", err)
|
||||
}
|
||||
return results, err
|
||||
}
|
||||
|
||||
func (fs *OsFs) findFirstExistingDir(path, rootPath string) (string, error) {
|
||||
results, err := fs.findNonexistentDirs(path, rootPath)
|
||||
func (fs *OsFs) findFirstExistingDir(path string) (string, error) {
|
||||
results, err := fs.findNonexistentDirs(path)
|
||||
if err != nil {
|
||||
fsLog(fs, logger.LevelWarn, "unable to find non existent dirs: %v", err)
|
||||
return "", err
|
||||
@@ -409,7 +328,7 @@ func (fs *OsFs) findFirstExistingDir(path, rootPath string) (string, error) {
|
||||
lastMissingDir := results[len(results)-1]
|
||||
parent = filepath.Dir(lastMissingDir)
|
||||
} else {
|
||||
parent = rootPath
|
||||
parent = fs.rootDir
|
||||
}
|
||||
p, err := filepath.EvalSymlinks(parent)
|
||||
if err != nil {
|
||||
@@ -422,15 +341,15 @@ func (fs *OsFs) findFirstExistingDir(path, rootPath string) (string, error) {
|
||||
if !fileInfo.IsDir() {
|
||||
return "", fmt.Errorf("resolved path is not a dir: %#v", p)
|
||||
}
|
||||
err = fs.isSubDir(p, rootPath)
|
||||
err = fs.isSubDir(p)
|
||||
return p, err
|
||||
}
|
||||
|
||||
func (fs *OsFs) isSubDir(sub, rootPath string) error {
|
||||
// rootPath must exist and it is already a validated absolute path
|
||||
parent, err := filepath.EvalSymlinks(rootPath)
|
||||
func (fs *OsFs) isSubDir(sub string) error {
|
||||
// fs.rootDir must exist and it is already a validated absolute path
|
||||
parent, err := filepath.EvalSymlinks(fs.rootDir)
|
||||
if err != nil {
|
||||
fsLog(fs, logger.LevelWarn, "invalid root path %#v: %v", rootPath, err)
|
||||
fsLog(fs, logger.LevelWarn, "invalid root path %#v: %v", fs.rootDir, err)
|
||||
return err
|
||||
}
|
||||
if parent == sub {
|
||||
@@ -448,7 +367,7 @@ func (fs *OsFs) isSubDir(sub, rootPath string) error {
|
||||
}
|
||||
|
||||
func (fs *OsFs) createMissingDirs(filePath string, uid, gid int) error {
|
||||
dirsToCreate, err := fs.findNonexistentDirs(filePath, fs.rootDir)
|
||||
dirsToCreate, err := fs.findNonexistentDirs(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user