github.com/pkg/sftp@v1.13.6/request-example.go (about)

     1  package sftp
     2  
     3  // This serves as an example of how to implement the request server handler as
     4  // well as a dummy backend for testing. It implements an in-memory backend that
     5  // works as a very simple filesystem with simple flat key-value lookup system.
     6  
     7  import (
     8  	"errors"
     9  	"io"
    10  	"os"
    11  	"path"
    12  	"sort"
    13  	"strings"
    14  	"sync"
    15  	"syscall"
    16  	"time"
    17  )
    18  
    19  const maxSymlinkFollows = 5
    20  
    21  var errTooManySymlinks = errors.New("too many symbolic links")
    22  
    23  // InMemHandler returns a Hanlders object with the test handlers.
    24  func InMemHandler() Handlers {
    25  	root := &root{
    26  		rootFile: &memFile{name: "/", modtime: time.Now(), isdir: true},
    27  		files:    make(map[string]*memFile),
    28  	}
    29  	return Handlers{root, root, root, root}
    30  }
    31  
    32  // Example Handlers
    33  func (fs *root) Fileread(r *Request) (io.ReaderAt, error) {
    34  	flags := r.Pflags()
    35  	if !flags.Read {
    36  		// sanity check
    37  		return nil, os.ErrInvalid
    38  	}
    39  
    40  	return fs.OpenFile(r)
    41  }
    42  
    43  func (fs *root) Filewrite(r *Request) (io.WriterAt, error) {
    44  	flags := r.Pflags()
    45  	if !flags.Write {
    46  		// sanity check
    47  		return nil, os.ErrInvalid
    48  	}
    49  
    50  	return fs.OpenFile(r)
    51  }
    52  
    53  func (fs *root) OpenFile(r *Request) (WriterAtReaderAt, error) {
    54  	if fs.mockErr != nil {
    55  		return nil, fs.mockErr
    56  	}
    57  	_ = r.WithContext(r.Context()) // initialize context for deadlock testing
    58  
    59  	fs.mu.Lock()
    60  	defer fs.mu.Unlock()
    61  
    62  	return fs.openfile(r.Filepath, r.Flags)
    63  }
    64  
    65  func (fs *root) putfile(pathname string, file *memFile) error {
    66  	pathname, err := fs.canonName(pathname)
    67  	if err != nil {
    68  		return err
    69  	}
    70  
    71  	if !strings.HasPrefix(pathname, "/") {
    72  		return os.ErrInvalid
    73  	}
    74  
    75  	if _, err := fs.lfetch(pathname); err != os.ErrNotExist {
    76  		return os.ErrExist
    77  	}
    78  
    79  	file.name = pathname
    80  	fs.files[pathname] = file
    81  
    82  	return nil
    83  }
    84  
    85  func (fs *root) openfile(pathname string, flags uint32) (*memFile, error) {
    86  	pflags := newFileOpenFlags(flags)
    87  
    88  	file, err := fs.fetch(pathname)
    89  	if err == os.ErrNotExist {
    90  		if !pflags.Creat {
    91  			return nil, os.ErrNotExist
    92  		}
    93  
    94  		var count int
    95  		// You can create files through dangling symlinks.
    96  		link, err := fs.lfetch(pathname)
    97  		for err == nil && link.symlink != "" {
    98  			if pflags.Excl {
    99  				// unless you also passed in O_EXCL
   100  				return nil, os.ErrInvalid
   101  			}
   102  
   103  			if count++; count > maxSymlinkFollows {
   104  				return nil, errTooManySymlinks
   105  			}
   106  
   107  			pathname = link.symlink
   108  			link, err = fs.lfetch(pathname)
   109  		}
   110  
   111  		file := &memFile{
   112  			modtime: time.Now(),
   113  		}
   114  
   115  		if err := fs.putfile(pathname, file); err != nil {
   116  			return nil, err
   117  		}
   118  
   119  		return file, nil
   120  	}
   121  
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	if pflags.Creat && pflags.Excl {
   127  		return nil, os.ErrExist
   128  	}
   129  
   130  	if file.IsDir() {
   131  		return nil, os.ErrInvalid
   132  	}
   133  
   134  	if pflags.Trunc {
   135  		if err := file.Truncate(0); err != nil {
   136  			return nil, err
   137  		}
   138  	}
   139  
   140  	return file, nil
   141  }
   142  
   143  func (fs *root) Filecmd(r *Request) error {
   144  	if fs.mockErr != nil {
   145  		return fs.mockErr
   146  	}
   147  	_ = r.WithContext(r.Context()) // initialize context for deadlock testing
   148  
   149  	fs.mu.Lock()
   150  	defer fs.mu.Unlock()
   151  
   152  	switch r.Method {
   153  	case "Setstat":
   154  		file, err := fs.openfile(r.Filepath, sshFxfWrite)
   155  		if err != nil {
   156  			return err
   157  		}
   158  
   159  		if r.AttrFlags().Size {
   160  			return file.Truncate(int64(r.Attributes().Size))
   161  		}
   162  
   163  		return nil
   164  
   165  	case "Rename":
   166  		// SFTP-v2: "It is an error if there already exists a file with the name specified by newpath."
   167  		// This varies from the POSIX specification, which allows limited replacement of target files.
   168  		if fs.exists(r.Target) {
   169  			return os.ErrExist
   170  		}
   171  
   172  		return fs.rename(r.Filepath, r.Target)
   173  
   174  	case "Rmdir":
   175  		return fs.rmdir(r.Filepath)
   176  
   177  	case "Remove":
   178  		// IEEE 1003.1 remove explicitly can unlink files and remove empty directories.
   179  		// We use instead here the semantics of unlink, which is allowed to be restricted against directories.
   180  		return fs.unlink(r.Filepath)
   181  
   182  	case "Mkdir":
   183  		return fs.mkdir(r.Filepath)
   184  
   185  	case "Link":
   186  		return fs.link(r.Filepath, r.Target)
   187  
   188  	case "Symlink":
   189  		// NOTE: r.Filepath is the target, and r.Target is the linkpath.
   190  		return fs.symlink(r.Filepath, r.Target)
   191  	}
   192  
   193  	return errors.New("unsupported")
   194  }
   195  
   196  func (fs *root) rename(oldpath, newpath string) error {
   197  	file, err := fs.lfetch(oldpath)
   198  	if err != nil {
   199  		return err
   200  	}
   201  
   202  	newpath, err = fs.canonName(newpath)
   203  	if err != nil {
   204  		return err
   205  	}
   206  
   207  	if !strings.HasPrefix(newpath, "/") {
   208  		return os.ErrInvalid
   209  	}
   210  
   211  	target, err := fs.lfetch(newpath)
   212  	if err != os.ErrNotExist {
   213  		if target == file {
   214  			// IEEE 1003.1: if oldpath and newpath are the same directory entry,
   215  			// then return no error, and perform no further action.
   216  			return nil
   217  		}
   218  
   219  		switch {
   220  		case file.IsDir():
   221  			// IEEE 1003.1: if oldpath is a directory, and newpath exists,
   222  			// then newpath must be a directory, and empty.
   223  			// It is to be removed prior to rename.
   224  			if err := fs.rmdir(newpath); err != nil {
   225  				return err
   226  			}
   227  
   228  		case target.IsDir():
   229  			// IEEE 1003.1: if oldpath is not a directory, and newpath exists,
   230  			// then newpath may not be a directory.
   231  			return syscall.EISDIR
   232  		}
   233  	}
   234  
   235  	fs.files[newpath] = file
   236  
   237  	if file.IsDir() {
   238  		dirprefix := file.name + "/"
   239  
   240  		for name, file := range fs.files {
   241  			if strings.HasPrefix(name, dirprefix) {
   242  				newname := path.Join(newpath, strings.TrimPrefix(name, dirprefix))
   243  
   244  				fs.files[newname] = file
   245  				file.name = newname
   246  				delete(fs.files, name)
   247  			}
   248  		}
   249  	}
   250  
   251  	file.name = newpath
   252  	delete(fs.files, oldpath)
   253  
   254  	return nil
   255  }
   256  
   257  func (fs *root) PosixRename(r *Request) error {
   258  	if fs.mockErr != nil {
   259  		return fs.mockErr
   260  	}
   261  	_ = r.WithContext(r.Context()) // initialize context for deadlock testing
   262  
   263  	fs.mu.Lock()
   264  	defer fs.mu.Unlock()
   265  
   266  	return fs.rename(r.Filepath, r.Target)
   267  }
   268  
   269  func (fs *root) StatVFS(r *Request) (*StatVFS, error) {
   270  	if fs.mockErr != nil {
   271  		return nil, fs.mockErr
   272  	}
   273  
   274  	return getStatVFSForPath(r.Filepath)
   275  }
   276  
   277  func (fs *root) mkdir(pathname string) error {
   278  	dir := &memFile{
   279  		modtime: time.Now(),
   280  		isdir:   true,
   281  	}
   282  
   283  	return fs.putfile(pathname, dir)
   284  }
   285  
   286  func (fs *root) rmdir(pathname string) error {
   287  	// IEEE 1003.1: If pathname is a symlink, then rmdir should fail with ENOTDIR.
   288  	dir, err := fs.lfetch(pathname)
   289  	if err != nil {
   290  		return err
   291  	}
   292  
   293  	if !dir.IsDir() {
   294  		return syscall.ENOTDIR
   295  	}
   296  
   297  	// use the dir‘s internal name not the pathname we passed in.
   298  	// the dir.name is always the canonical name of a directory.
   299  	pathname = dir.name
   300  
   301  	for name := range fs.files {
   302  		if path.Dir(name) == pathname {
   303  			return errors.New("directory not empty")
   304  		}
   305  	}
   306  
   307  	delete(fs.files, pathname)
   308  
   309  	return nil
   310  }
   311  
   312  func (fs *root) link(oldpath, newpath string) error {
   313  	file, err := fs.lfetch(oldpath)
   314  	if err != nil {
   315  		return err
   316  	}
   317  
   318  	if file.IsDir() {
   319  		return errors.New("hard link not allowed for directory")
   320  	}
   321  
   322  	return fs.putfile(newpath, file)
   323  }
   324  
   325  // symlink() creates a symbolic link named `linkpath` which contains the string `target`.
   326  // NOTE! This would be called with `symlink(req.Filepath, req.Target)` due to different semantics.
   327  func (fs *root) symlink(target, linkpath string) error {
   328  	link := &memFile{
   329  		modtime: time.Now(),
   330  		symlink: target,
   331  	}
   332  
   333  	return fs.putfile(linkpath, link)
   334  }
   335  
   336  func (fs *root) unlink(pathname string) error {
   337  	// does not follow symlinks!
   338  	file, err := fs.lfetch(pathname)
   339  	if err != nil {
   340  		return err
   341  	}
   342  
   343  	if file.IsDir() {
   344  		// IEEE 1003.1: implementations may opt out of allowing the unlinking of directories.
   345  		// SFTP-v2: SSH_FXP_REMOVE may not remove directories.
   346  		return os.ErrInvalid
   347  	}
   348  
   349  	// DO NOT use the file’s internal name.
   350  	// because of hard-links files cannot have a single canonical name.
   351  	delete(fs.files, pathname)
   352  
   353  	return nil
   354  }
   355  
   356  type listerat []os.FileInfo
   357  
   358  // Modeled after strings.Reader's ReadAt() implementation
   359  func (f listerat) ListAt(ls []os.FileInfo, offset int64) (int, error) {
   360  	var n int
   361  	if offset >= int64(len(f)) {
   362  		return 0, io.EOF
   363  	}
   364  	n = copy(ls, f[offset:])
   365  	if n < len(ls) {
   366  		return n, io.EOF
   367  	}
   368  	return n, nil
   369  }
   370  
   371  func (fs *root) Filelist(r *Request) (ListerAt, error) {
   372  	if fs.mockErr != nil {
   373  		return nil, fs.mockErr
   374  	}
   375  	_ = r.WithContext(r.Context()) // initialize context for deadlock testing
   376  
   377  	fs.mu.Lock()
   378  	defer fs.mu.Unlock()
   379  
   380  	switch r.Method {
   381  	case "List":
   382  		files, err := fs.readdir(r.Filepath)
   383  		if err != nil {
   384  			return nil, err
   385  		}
   386  		return listerat(files), nil
   387  
   388  	case "Stat":
   389  		file, err := fs.fetch(r.Filepath)
   390  		if err != nil {
   391  			return nil, err
   392  		}
   393  		return listerat{file}, nil
   394  	}
   395  
   396  	return nil, errors.New("unsupported")
   397  }
   398  
   399  func (fs *root) readdir(pathname string) ([]os.FileInfo, error) {
   400  	dir, err := fs.fetch(pathname)
   401  	if err != nil {
   402  		return nil, err
   403  	}
   404  
   405  	if !dir.IsDir() {
   406  		return nil, syscall.ENOTDIR
   407  	}
   408  
   409  	var files []os.FileInfo
   410  
   411  	for name, file := range fs.files {
   412  		if path.Dir(name) == dir.name {
   413  			files = append(files, file)
   414  		}
   415  	}
   416  
   417  	sort.Slice(files, func(i, j int) bool { return files[i].Name() < files[j].Name() })
   418  
   419  	return files, nil
   420  }
   421  
   422  func (fs *root) Readlink(pathname string) (string, error) {
   423  	file, err := fs.lfetch(pathname)
   424  	if err != nil {
   425  		return "", err
   426  	}
   427  
   428  	if file.symlink == "" {
   429  		return "", os.ErrInvalid
   430  	}
   431  
   432  	return file.symlink, nil
   433  }
   434  
   435  // implements LstatFileLister interface
   436  func (fs *root) Lstat(r *Request) (ListerAt, error) {
   437  	if fs.mockErr != nil {
   438  		return nil, fs.mockErr
   439  	}
   440  	_ = r.WithContext(r.Context()) // initialize context for deadlock testing
   441  
   442  	fs.mu.Lock()
   443  	defer fs.mu.Unlock()
   444  
   445  	file, err := fs.lfetch(r.Filepath)
   446  	if err != nil {
   447  		return nil, err
   448  	}
   449  	return listerat{file}, nil
   450  }
   451  
   452  // In memory file-system-y thing that the Hanlders live on
   453  type root struct {
   454  	rootFile *memFile
   455  	mockErr  error
   456  
   457  	mu    sync.Mutex
   458  	files map[string]*memFile
   459  }
   460  
   461  // Set a mocked error that the next handler call will return.
   462  // Set to nil to reset for no error.
   463  func (fs *root) returnErr(err error) {
   464  	fs.mockErr = err
   465  }
   466  
   467  func (fs *root) lfetch(path string) (*memFile, error) {
   468  	if path == "/" {
   469  		return fs.rootFile, nil
   470  	}
   471  
   472  	file, ok := fs.files[path]
   473  	if file == nil {
   474  		if ok {
   475  			delete(fs.files, path)
   476  		}
   477  
   478  		return nil, os.ErrNotExist
   479  	}
   480  
   481  	return file, nil
   482  }
   483  
   484  // canonName returns the “canonical” name of a file, that is:
   485  // if the directory of the pathname is a symlink, it follows that symlink to the valid directory name.
   486  // this is relatively easy, since `dir.name` will be the only valid canonical path for a directory.
   487  func (fs *root) canonName(pathname string) (string, error) {
   488  	dirname, filename := path.Dir(pathname), path.Base(pathname)
   489  
   490  	dir, err := fs.fetch(dirname)
   491  	if err != nil {
   492  		return "", err
   493  	}
   494  
   495  	if !dir.IsDir() {
   496  		return "", syscall.ENOTDIR
   497  	}
   498  
   499  	return path.Join(dir.name, filename), nil
   500  }
   501  
   502  func (fs *root) exists(path string) bool {
   503  	path, err := fs.canonName(path)
   504  	if err != nil {
   505  		return false
   506  	}
   507  
   508  	_, err = fs.lfetch(path)
   509  
   510  	return err != os.ErrNotExist
   511  }
   512  
   513  func (fs *root) fetch(pathname string) (*memFile, error) {
   514  	file, err := fs.lfetch(pathname)
   515  	if err != nil {
   516  		return nil, err
   517  	}
   518  
   519  	var count int
   520  	for file.symlink != "" {
   521  		if count++; count > maxSymlinkFollows {
   522  			return nil, errTooManySymlinks
   523  		}
   524  
   525  		linkTarget := file.symlink
   526  		if !path.IsAbs(linkTarget) {
   527  			linkTarget = path.Join(path.Dir(file.name), linkTarget)
   528  		}
   529  
   530  		file, err = fs.lfetch(linkTarget)
   531  		if err != nil {
   532  			return nil, err
   533  		}
   534  	}
   535  
   536  	return file, nil
   537  }
   538  
   539  // Implements os.FileInfo, io.ReaderAt and io.WriterAt interfaces.
   540  // These are the 3 interfaces necessary for the Handlers.
   541  // Implements the optional interface TransferError.
   542  type memFile struct {
   543  	name    string
   544  	modtime time.Time
   545  	symlink string
   546  	isdir   bool
   547  
   548  	mu      sync.RWMutex
   549  	content []byte
   550  	err     error
   551  }
   552  
   553  // These are helper functions, they must be called while holding the memFile.mu mutex
   554  func (f *memFile) size() int64  { return int64(len(f.content)) }
   555  func (f *memFile) grow(n int64) { f.content = append(f.content, make([]byte, n)...) }
   556  
   557  // Have memFile fulfill os.FileInfo interface
   558  func (f *memFile) Name() string { return path.Base(f.name) }
   559  func (f *memFile) Size() int64 {
   560  	f.mu.Lock()
   561  	defer f.mu.Unlock()
   562  
   563  	return f.size()
   564  }
   565  func (f *memFile) Mode() os.FileMode {
   566  	if f.isdir {
   567  		return os.FileMode(0755) | os.ModeDir
   568  	}
   569  	if f.symlink != "" {
   570  		return os.FileMode(0777) | os.ModeSymlink
   571  	}
   572  	return os.FileMode(0644)
   573  }
   574  func (f *memFile) ModTime() time.Time { return f.modtime }
   575  func (f *memFile) IsDir() bool        { return f.isdir }
   576  func (f *memFile) Sys() interface{} {
   577  	return fakeFileInfoSys()
   578  }
   579  
   580  func (f *memFile) ReadAt(b []byte, off int64) (int, error) {
   581  	f.mu.Lock()
   582  	defer f.mu.Unlock()
   583  
   584  	if f.err != nil {
   585  		return 0, f.err
   586  	}
   587  
   588  	if off < 0 {
   589  		return 0, errors.New("memFile.ReadAt: negative offset")
   590  	}
   591  
   592  	if off >= f.size() {
   593  		return 0, io.EOF
   594  	}
   595  
   596  	n := copy(b, f.content[off:])
   597  	if n < len(b) {
   598  		return n, io.EOF
   599  	}
   600  
   601  	return n, nil
   602  }
   603  
   604  func (f *memFile) WriteAt(b []byte, off int64) (int, error) {
   605  	// fmt.Println(string(p), off)
   606  	// mimic write delays, should be optional
   607  	time.Sleep(time.Microsecond * time.Duration(len(b)))
   608  
   609  	f.mu.Lock()
   610  	defer f.mu.Unlock()
   611  
   612  	if f.err != nil {
   613  		return 0, f.err
   614  	}
   615  
   616  	grow := int64(len(b)) + off - f.size()
   617  	if grow > 0 {
   618  		f.grow(grow)
   619  	}
   620  
   621  	return copy(f.content[off:], b), nil
   622  }
   623  
   624  func (f *memFile) Truncate(size int64) error {
   625  	f.mu.Lock()
   626  	defer f.mu.Unlock()
   627  
   628  	if f.err != nil {
   629  		return f.err
   630  	}
   631  
   632  	grow := size - f.size()
   633  	if grow <= 0 {
   634  		f.content = f.content[:size]
   635  	} else {
   636  		f.grow(grow)
   637  	}
   638  
   639  	return nil
   640  }
   641  
   642  func (f *memFile) TransferError(err error) {
   643  	f.mu.Lock()
   644  	defer f.mu.Unlock()
   645  
   646  	f.err = err
   647  }