2016-11-24 12:07:14 +00:00
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
2017-02-09 07:52:18 +01:00
// You can obtain one at https://mozilla.org/MPL/2.0/.
2016-11-24 12:07:14 +00:00
package fs
import (
2017-04-26 00:15:23 +00:00
"errors"
2019-05-25 21:08:26 +02:00
"fmt"
2016-11-24 12:07:14 +00:00
"os"
2017-08-19 14:36:56 +00:00
"path/filepath"
"runtime"
"strings"
2016-11-24 12:07:14 +00:00
"time"
2017-08-19 14:36:56 +00:00
2019-07-29 20:06:17 +02:00
"github.com/shirou/gopsutil/disk"
2017-08-19 14:36:56 +00:00
)
var (
ErrInvalidFilename = errors . New ( "filename is invalid" )
ErrNotRelative = errors . New ( "not a relative path" )
2016-11-24 12:07:14 +00:00
)
// The BasicFilesystem implements all aspects by delegating to package os.
2017-08-19 14:36:56 +00:00
// All paths are relative to the root and cannot (should not) escape the root directory.
2016-11-24 12:07:14 +00:00
type BasicFilesystem struct {
2018-08-11 22:24:36 +02:00
root string
2017-08-19 14:36:56 +00:00
}
func newBasicFilesystem ( root string ) * BasicFilesystem {
// The reason it's done like this:
// C: -> C:\ -> C:\ (issue that this is trying to fix)
// C:\somedir -> C:\somedir\ -> C:\somedir
// C:\somedir\ -> C:\somedir\\ -> C:\somedir
// This way in the tests, we get away without OS specific separators
// in the test configs.
2018-08-11 22:24:36 +02:00
sep := string ( filepath . Separator )
root = filepath . Dir ( root + sep )
2017-08-19 14:36:56 +00:00
// Attempt tilde expansion; leave unchanged in case of error
if path , err := ExpandTilde ( root ) ; err == nil {
root = path
}
// Attempt absolutification; leave unchanged in case of error
if ! filepath . IsAbs ( root ) {
// Abs() looks like a fairly expensive syscall on Windows, while
// IsAbs() is a whole bunch of string mangling. I think IsAbs() may be
// somewhat faster in the general case, hence the outer if...
if path , err := filepath . Abs ( root ) ; err == nil {
root = path
}
}
// Attempt to enable long filename support on Windows. We may still not
// have an absolute path here if the previous steps failed.
if runtime . GOOS == "windows" {
2018-08-11 22:24:36 +02:00
root = longFilenameSupport ( root )
2017-08-19 14:36:56 +00:00
}
2018-03-28 23:01:25 +02:00
2018-08-11 22:24:36 +02:00
return & BasicFilesystem { root }
2017-08-19 14:36:56 +00:00
}
// rooted expands the relative path to the full path that is then used with os
// package. If the relative path somehow causes the final path to escape the root
2017-09-07 09:40:46 +00:00
// directory, this returns an error, to prevent accessing files that are not in the
2017-08-19 14:36:56 +00:00
// shared directory.
func ( f * BasicFilesystem ) rooted ( rel string ) ( string , error ) {
2018-04-16 20:07:00 +02:00
return rooted ( rel , f . root )
}
func rooted ( rel , root string ) ( string , error ) {
2017-08-19 14:36:56 +00:00
// The root must not be empty.
2018-04-16 20:07:00 +02:00
if root == "" {
2017-08-19 14:36:56 +00:00
return "" , ErrInvalidFilename
}
2018-03-12 13:18:59 +01:00
var err error
2018-08-28 08:18:55 +02:00
// Takes care that rel does not try to escape
2018-03-12 13:18:59 +01:00
rel , err = Canonicalize ( rel )
if err != nil {
return "" , err
2017-08-19 14:36:56 +00:00
}
2018-08-28 08:18:55 +02:00
return filepath . Join ( root , rel ) , nil
2016-11-24 12:07:14 +00:00
}
2017-08-19 14:36:56 +00:00
func ( f * BasicFilesystem ) unrooted ( path string ) string {
2018-03-28 23:01:25 +02:00
return rel ( path , f . root )
}
2016-11-24 12:07:14 +00:00
func ( f * BasicFilesystem ) Chmod ( name string , mode FileMode ) error {
2017-08-19 14:36:56 +00:00
name , err := f . rooted ( name )
if err != nil {
return err
}
2016-11-24 12:07:14 +00:00
return os . Chmod ( name , os . FileMode ( mode ) )
}
2019-01-25 09:52:21 +01:00
func ( f * BasicFilesystem ) Lchown ( name string , uid , gid int ) error {
name , err := f . rooted ( name )
if err != nil {
return err
}
return os . Lchown ( name , uid , gid )
}
2016-11-24 12:07:14 +00:00
func ( f * BasicFilesystem ) Chtimes ( name string , atime time . Time , mtime time . Time ) error {
2017-08-19 14:36:56 +00:00
name , err := f . rooted ( name )
if err != nil {
return err
}
2016-11-24 12:07:14 +00:00
return os . Chtimes ( name , atime , mtime )
}
func ( f * BasicFilesystem ) Mkdir ( name string , perm FileMode ) error {
2017-08-19 14:36:56 +00:00
name , err := f . rooted ( name )
if err != nil {
return err
}
2016-11-24 12:07:14 +00:00
return os . Mkdir ( name , os . FileMode ( perm ) )
}
2018-02-16 15:19:20 +01:00
// MkdirAll creates a directory named path, along with any necessary parents,
// and returns nil, or else returns an error.
// The permission bits perm are used for all directories that MkdirAll creates.
// If path is already a directory, MkdirAll does nothing and returns nil.
func ( f * BasicFilesystem ) MkdirAll ( path string , perm FileMode ) error {
path , err := f . rooted ( path )
if err != nil {
return err
}
return f . mkdirAll ( path , os . FileMode ( perm ) )
}
2016-11-24 12:07:14 +00:00
func ( f * BasicFilesystem ) Lstat ( name string ) ( FileInfo , error ) {
2017-08-19 14:36:56 +00:00
name , err := f . rooted ( name )
if err != nil {
return nil , err
}
2017-04-01 09:04:11 +00:00
fi , err := underlyingLstat ( name )
2016-11-24 12:07:14 +00:00
if err != nil {
return nil , err
}
2019-02-24 18:02:02 +01:00
return basicFileInfo { fi } , err
2016-11-24 12:07:14 +00:00
}
func ( f * BasicFilesystem ) Remove ( name string ) error {
2017-08-19 14:36:56 +00:00
name , err := f . rooted ( name )
if err != nil {
return err
}
2016-11-24 12:07:14 +00:00
return os . Remove ( name )
}
2017-08-19 14:36:56 +00:00
func ( f * BasicFilesystem ) RemoveAll ( name string ) error {
name , err := f . rooted ( name )
if err != nil {
return err
}
return os . RemoveAll ( name )
}
2016-11-24 12:07:14 +00:00
func ( f * BasicFilesystem ) Rename ( oldpath , newpath string ) error {
2017-08-19 14:36:56 +00:00
oldpath , err := f . rooted ( oldpath )
if err != nil {
return err
}
newpath , err = f . rooted ( newpath )
if err != nil {
return err
}
2016-11-24 12:07:14 +00:00
return os . Rename ( oldpath , newpath )
}
func ( f * BasicFilesystem ) Stat ( name string ) ( FileInfo , error ) {
2017-08-19 14:36:56 +00:00
name , err := f . rooted ( name )
if err != nil {
return nil , err
}
2016-11-24 12:07:14 +00:00
fi , err := os . Stat ( name )
if err != nil {
return nil , err
}
2019-02-24 18:02:02 +01:00
return basicFileInfo { fi } , err
2016-11-24 12:07:14 +00:00
}
func ( f * BasicFilesystem ) DirNames ( name string ) ( [ ] string , error ) {
2017-08-19 14:36:56 +00:00
name , err := f . rooted ( name )
if err != nil {
return nil , err
}
fd , err := os . OpenFile ( name , OptReadOnly , 0777 )
2016-11-24 12:07:14 +00:00
if err != nil {
return nil , err
}
defer fd . Close ( )
names , err := fd . Readdirnames ( - 1 )
if err != nil {
return nil , err
}
return names , nil
}
func ( f * BasicFilesystem ) Open ( name string ) ( File , error ) {
2017-08-19 14:36:56 +00:00
rootedName , err := f . rooted ( name )
if err != nil {
return nil , err
}
fd , err := os . Open ( rootedName )
if err != nil {
return nil , err
}
2019-02-24 18:02:02 +01:00
return basicFile { fd , name } , err
2017-08-19 14:36:56 +00:00
}
func ( f * BasicFilesystem ) OpenFile ( name string , flags int , mode FileMode ) ( File , error ) {
rootedName , err := f . rooted ( name )
if err != nil {
return nil , err
}
fd , err := os . OpenFile ( rootedName , flags , os . FileMode ( mode ) )
2017-04-01 09:04:11 +00:00
if err != nil {
return nil , err
}
2019-02-24 18:02:02 +01:00
return basicFile { fd , name } , err
2016-11-24 12:07:14 +00:00
}
func ( f * BasicFilesystem ) Create ( name string ) ( File , error ) {
2017-08-19 14:36:56 +00:00
rootedName , err := f . rooted ( name )
2017-04-01 09:04:11 +00:00
if err != nil {
return nil , err
}
2017-08-19 14:36:56 +00:00
fd , err := os . Create ( rootedName )
if err != nil {
return nil , err
}
2019-02-24 18:02:02 +01:00
return basicFile { fd , name } , err
2017-04-01 09:04:11 +00:00
}
2017-04-26 00:15:23 +00:00
func ( f * BasicFilesystem ) Walk ( root string , walkFn WalkFunc ) error {
// implemented in WalkFilesystem
return errors . New ( "not implemented" )
}
2017-08-19 14:36:56 +00:00
func ( f * BasicFilesystem ) Glob ( pattern string ) ( [ ] string , error ) {
pattern , err := f . rooted ( pattern )
if err != nil {
return nil , err
}
files , err := filepath . Glob ( pattern )
unrooted := make ( [ ] string , len ( files ) )
for i := range files {
unrooted [ i ] = f . unrooted ( files [ i ] )
}
return unrooted , err
}
func ( f * BasicFilesystem ) Usage ( name string ) ( Usage , error ) {
name , err := f . rooted ( name )
if err != nil {
return Usage { } , err
}
2019-07-29 20:06:17 +02:00
u , err := disk . Usage ( name )
if err != nil {
return Usage { } , err
}
2017-08-19 14:36:56 +00:00
return Usage {
2019-07-29 20:06:17 +02:00
Free : int64 ( u . Free ) ,
Total : int64 ( u . Total ) ,
} , nil
2017-08-19 14:36:56 +00:00
}
func ( f * BasicFilesystem ) Type ( ) FilesystemType {
return FilesystemTypeBasic
}
func ( f * BasicFilesystem ) URI ( ) string {
return strings . TrimPrefix ( f . root , ` \\?\ ` )
}
2018-01-05 18:11:09 +00:00
func ( f * BasicFilesystem ) SameFile ( fi1 , fi2 FileInfo ) bool {
// Like os.SameFile, we always return false unless fi1 and fi2 were created
// by this package's Stat/Lstat method.
2019-02-24 18:02:02 +01:00
f1 , ok1 := fi1 . ( basicFileInfo )
f2 , ok2 := fi2 . ( basicFileInfo )
2018-01-05 18:11:09 +00:00
if ! ok1 || ! ok2 {
return false
}
return os . SameFile ( f1 . FileInfo , f2 . FileInfo )
}
2019-02-24 18:02:02 +01:00
// basicFile implements the fs.File interface on top of an os.File
type basicFile struct {
2017-04-01 09:04:11 +00:00
* os . File
2017-08-19 14:36:56 +00:00
name string
}
2019-02-24 18:02:02 +01:00
func ( f basicFile ) Name ( ) string {
2017-08-19 14:36:56 +00:00
return f . name
2017-04-01 09:04:11 +00:00
}
2019-02-24 18:02:02 +01:00
func ( f basicFile ) Stat ( ) ( FileInfo , error ) {
2017-04-01 09:04:11 +00:00
info , err := f . File . Stat ( )
if err != nil {
return nil , err
}
2019-02-24 18:02:02 +01:00
return basicFileInfo { info } , nil
2016-11-24 12:07:14 +00:00
}
2019-02-24 18:02:02 +01:00
// basicFileInfo implements the fs.FileInfo interface on top of an os.FileInfo.
type basicFileInfo struct {
2016-11-24 12:07:14 +00:00
os . FileInfo
}
2019-02-24 18:02:02 +01:00
func ( e basicFileInfo ) IsSymlink ( ) bool {
// Must use basicFileInfo.Mode() because it may apply magic.
2017-12-29 21:23:06 +00:00
return e . Mode ( ) & ModeSymlink != 0
2016-11-24 12:07:14 +00:00
}
2019-02-24 18:02:02 +01:00
func ( e basicFileInfo ) IsRegular ( ) bool {
// Must use basicFileInfo.Mode() because it may apply magic.
2017-12-29 21:23:06 +00:00
return e . Mode ( ) & ModeType == 0
2016-11-24 12:07:14 +00:00
}
2018-08-11 22:24:36 +02:00
// longFilenameSupport adds the necessary prefix to the path to enable long
// filename support on windows if necessary.
// This does NOT check the current system, i.e. will also take effect on unix paths.
func longFilenameSupport ( path string ) string {
if filepath . IsAbs ( path ) && ! strings . HasPrefix ( path , ` \\ ` ) {
return ` \\?\ ` + path
}
return path
}
2019-05-25 21:08:26 +02:00
type ErrWatchEventOutsideRoot struct { msg string }
func ( e * ErrWatchEventOutsideRoot ) Error ( ) string {
return e . msg
}
2019-09-22 09:03:22 +02:00
func ( f * BasicFilesystem ) newErrWatchEventOutsideRoot ( absPath string , roots [ ] string ) * ErrWatchEventOutsideRoot {
return & ErrWatchEventOutsideRoot { fmt . Sprintf ( "Watching for changes encountered an event outside of the filesystem root: f.root==%v, roots==%v, path==%v. This should never happen, please report this message to forum.syncthing.net." , f . root , roots , absPath ) }
2019-05-25 21:08:26 +02:00
}