| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345 |
- // 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,
- // You can obtain one at https://mozilla.org/MPL/2.0/.
- package fs
- import (
- "errors"
- "os"
- "path/filepath"
- "runtime"
- "strings"
- "time"
- "github.com/calmh/du"
- )
- var (
- ErrInvalidFilename = errors.New("filename is invalid")
- ErrNotRelative = errors.New("not a relative path")
- )
- // The BasicFilesystem implements all aspects by delegating to package os.
- // All paths are relative to the root and cannot (should not) escape the root directory.
- type BasicFilesystem struct {
- root string
- }
- 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.
- root = filepath.Dir(root + string(filepath.Separator))
- // 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" {
- if filepath.IsAbs(root) && !strings.HasPrefix(root, `\\`) {
- root = `\\?\` + root
- }
- // If we're not on Windows, we want the path to end with a slash to
- // penetrate symlinks. On Windows, paths must not end with a slash.
- } else if root[len(root)-1] != filepath.Separator {
- root = root + string(filepath.Separator)
- }
- return &BasicFilesystem{
- root: root,
- }
- }
- // 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
- // directory, this returns an error, to prevent accessing files that are not in the
- // shared directory.
- func (f *BasicFilesystem) rooted(rel string) (string, error) {
- // The root must not be empty.
- if f.root == "" {
- return "", ErrInvalidFilename
- }
- pathSep := string(PathSeparator)
- // The expected prefix for the resulting path is the root, with a path
- // separator at the end.
- expectedPrefix := filepath.FromSlash(f.root)
- if !strings.HasSuffix(expectedPrefix, pathSep) {
- expectedPrefix += pathSep
- }
- // The relative path should be clean from internal dotdots and similar
- // funkyness.
- rel = filepath.FromSlash(rel)
- if filepath.Clean(rel) != rel {
- return "", ErrInvalidFilename
- }
- // It is not acceptable to attempt to traverse upwards.
- switch rel {
- case "..", pathSep:
- return "", ErrNotRelative
- }
- if strings.HasPrefix(rel, ".."+pathSep) {
- return "", ErrNotRelative
- }
- if strings.HasPrefix(rel, pathSep+pathSep) {
- // The relative path may pretend to be an absolute path within the
- // root, but the double path separator on Windows implies something
- // else. It would get cleaned by the Join below, but it's out of
- // spec anyway.
- return "", ErrNotRelative
- }
- // The supposedly correct path is the one filepath.Join will return, as
- // it does cleaning and so on. Check that one first to make sure no
- // obvious escape attempts have been made.
- joined := filepath.Join(f.root, rel)
- if rel == "." && !strings.HasSuffix(joined, pathSep) {
- joined += pathSep
- }
- if !strings.HasPrefix(joined, expectedPrefix) {
- return "", ErrNotRelative
- }
- return joined, nil
- }
- func (f *BasicFilesystem) unrooted(path string) string {
- return strings.TrimPrefix(strings.TrimPrefix(path, f.root), string(PathSeparator))
- }
- func (f *BasicFilesystem) Chmod(name string, mode FileMode) error {
- name, err := f.rooted(name)
- if err != nil {
- return err
- }
- return os.Chmod(name, os.FileMode(mode))
- }
- func (f *BasicFilesystem) Chtimes(name string, atime time.Time, mtime time.Time) error {
- name, err := f.rooted(name)
- if err != nil {
- return err
- }
- return os.Chtimes(name, atime, mtime)
- }
- func (f *BasicFilesystem) Mkdir(name string, perm FileMode) error {
- name, err := f.rooted(name)
- if err != nil {
- return err
- }
- return os.Mkdir(name, os.FileMode(perm))
- }
- func (f *BasicFilesystem) Lstat(name string) (FileInfo, error) {
- name, err := f.rooted(name)
- if err != nil {
- return nil, err
- }
- fi, err := underlyingLstat(name)
- if err != nil {
- return nil, err
- }
- return fsFileInfo{fi}, err
- }
- func (f *BasicFilesystem) Remove(name string) error {
- name, err := f.rooted(name)
- if err != nil {
- return err
- }
- return os.Remove(name)
- }
- func (f *BasicFilesystem) RemoveAll(name string) error {
- name, err := f.rooted(name)
- if err != nil {
- return err
- }
- return os.RemoveAll(name)
- }
- func (f *BasicFilesystem) Rename(oldpath, newpath string) error {
- oldpath, err := f.rooted(oldpath)
- if err != nil {
- return err
- }
- newpath, err = f.rooted(newpath)
- if err != nil {
- return err
- }
- return os.Rename(oldpath, newpath)
- }
- func (f *BasicFilesystem) Stat(name string) (FileInfo, error) {
- name, err := f.rooted(name)
- if err != nil {
- return nil, err
- }
- fi, err := os.Stat(name)
- if err != nil {
- return nil, err
- }
- return fsFileInfo{fi}, err
- }
- func (f *BasicFilesystem) DirNames(name string) ([]string, error) {
- name, err := f.rooted(name)
- if err != nil {
- return nil, err
- }
- fd, err := os.OpenFile(name, OptReadOnly, 0777)
- 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) {
- rootedName, err := f.rooted(name)
- if err != nil {
- return nil, err
- }
- fd, err := os.Open(rootedName)
- if err != nil {
- return nil, err
- }
- return fsFile{fd, name}, err
- }
- 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))
- if err != nil {
- return nil, err
- }
- return fsFile{fd, name}, err
- }
- func (f *BasicFilesystem) Create(name string) (File, error) {
- rootedName, err := f.rooted(name)
- if err != nil {
- return nil, err
- }
- fd, err := os.Create(rootedName)
- if err != nil {
- return nil, err
- }
- return fsFile{fd, name}, err
- }
- func (f *BasicFilesystem) Walk(root string, walkFn WalkFunc) error {
- // implemented in WalkFilesystem
- return errors.New("not implemented")
- }
- 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
- }
- u, err := du.Get(name)
- return Usage{
- Free: u.FreeBytes,
- Total: u.TotalBytes,
- }, err
- }
- func (f *BasicFilesystem) Type() FilesystemType {
- return FilesystemTypeBasic
- }
- func (f *BasicFilesystem) URI() string {
- return strings.TrimPrefix(f.root, `\\?\`)
- }
- 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.
- f1, ok1 := fi1.(fsFileInfo)
- f2, ok2 := fi2.(fsFileInfo)
- if !ok1 || !ok2 {
- return false
- }
- return os.SameFile(f1.FileInfo, f2.FileInfo)
- }
- // fsFile implements the fs.File interface on top of an os.File
- type fsFile struct {
- *os.File
- name string
- }
- func (f fsFile) Name() string {
- return f.name
- }
- func (f fsFile) Stat() (FileInfo, error) {
- info, err := f.File.Stat()
- if err != nil {
- return nil, err
- }
- return fsFileInfo{info}, nil
- }
- // fsFileInfo implements the fs.FileInfo interface on top of an os.FileInfo.
- type fsFileInfo struct {
- os.FileInfo
- }
- func (e fsFileInfo) IsSymlink() bool {
- // Must use fsFileInfo.Mode() because it may apply magic.
- return e.Mode()&ModeSymlink != 0
- }
- func (e fsFileInfo) IsRegular() bool {
- // Must use fsFileInfo.Mode() because it may apply magic.
- return e.Mode()&ModeType == 0
- }
|