github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/backend/smb/smb.go (about)

     1  // Package smb provides an interface to SMB servers
     2  package smb
     3  
     4  import (
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path"
    10  	"strings"
    11  	"sync"
    12  	"sync/atomic"
    13  	"time"
    14  
    15  	"github.com/rclone/rclone/fs"
    16  	"github.com/rclone/rclone/fs/config"
    17  	"github.com/rclone/rclone/fs/config/configmap"
    18  	"github.com/rclone/rclone/fs/config/configstruct"
    19  	"github.com/rclone/rclone/fs/hash"
    20  	"github.com/rclone/rclone/lib/bucket"
    21  	"github.com/rclone/rclone/lib/encoder"
    22  	"github.com/rclone/rclone/lib/env"
    23  	"github.com/rclone/rclone/lib/pacer"
    24  	"github.com/rclone/rclone/lib/readers"
    25  )
    26  
    27  const (
    28  	minSleep      = 100 * time.Millisecond
    29  	maxSleep      = 2 * time.Second
    30  	decayConstant = 2 // bigger for slower decay, exponential
    31  )
    32  
    33  var (
    34  	currentUser = env.CurrentUser()
    35  )
    36  
    37  // Register with Fs
    38  func init() {
    39  	fs.Register(&fs.RegInfo{
    40  		Name:        "smb",
    41  		Description: "SMB / CIFS",
    42  		NewFs:       NewFs,
    43  
    44  		Options: []fs.Option{{
    45  			Name:      "host",
    46  			Help:      "SMB server hostname to connect to.\n\nE.g. \"example.com\".",
    47  			Required:  true,
    48  			Sensitive: true,
    49  		}, {
    50  			Name:      "user",
    51  			Help:      "SMB username.",
    52  			Default:   currentUser,
    53  			Sensitive: true,
    54  		}, {
    55  			Name:    "port",
    56  			Help:    "SMB port number.",
    57  			Default: 445,
    58  		}, {
    59  			Name:       "pass",
    60  			Help:       "SMB password.",
    61  			IsPassword: true,
    62  		}, {
    63  			Name:      "domain",
    64  			Help:      "Domain name for NTLM authentication.",
    65  			Default:   "WORKGROUP",
    66  			Sensitive: true,
    67  		}, {
    68  			Name: "spn",
    69  			Help: `Service principal name.
    70  
    71  Rclone presents this name to the server. Some servers use this as further
    72  authentication, and it often needs to be set for clusters. For example:
    73  
    74      cifs/remotehost:1020
    75  
    76  Leave blank if not sure.
    77  `,
    78  			Sensitive: true,
    79  		}, {
    80  			Name:    "idle_timeout",
    81  			Default: fs.Duration(60 * time.Second),
    82  			Help: `Max time before closing idle connections.
    83  
    84  If no connections have been returned to the connection pool in the time
    85  given, rclone will empty the connection pool.
    86  
    87  Set to 0 to keep connections indefinitely.
    88  `,
    89  			Advanced: true,
    90  		}, {
    91  			Name:     "hide_special_share",
    92  			Help:     "Hide special shares (e.g. print$) which users aren't supposed to access.",
    93  			Default:  true,
    94  			Advanced: true,
    95  		}, {
    96  			Name:     "case_insensitive",
    97  			Help:     "Whether the server is configured to be case-insensitive.\n\nAlways true on Windows shares.",
    98  			Default:  true,
    99  			Advanced: true,
   100  		}, {
   101  			Name:     config.ConfigEncoding,
   102  			Help:     config.ConfigEncodingHelp,
   103  			Advanced: true,
   104  			Default: encoder.EncodeZero |
   105  				// path separator
   106  				encoder.EncodeSlash |
   107  				encoder.EncodeBackSlash |
   108  				// windows
   109  				encoder.EncodeWin |
   110  				encoder.EncodeCtl |
   111  				encoder.EncodeDot |
   112  				// the file turns into 8.3 names (and cannot be converted back)
   113  				encoder.EncodeRightSpace |
   114  				encoder.EncodeRightPeriod |
   115  				//
   116  				encoder.EncodeInvalidUtf8,
   117  		},
   118  		}})
   119  }
   120  
   121  // Options defines the configuration for this backend
   122  type Options struct {
   123  	Host            string      `config:"host"`
   124  	Port            string      `config:"port"`
   125  	User            string      `config:"user"`
   126  	Pass            string      `config:"pass"`
   127  	Domain          string      `config:"domain"`
   128  	SPN             string      `config:"spn"`
   129  	HideSpecial     bool        `config:"hide_special_share"`
   130  	CaseInsensitive bool        `config:"case_insensitive"`
   131  	IdleTimeout     fs.Duration `config:"idle_timeout"`
   132  
   133  	Enc encoder.MultiEncoder `config:"encoding"`
   134  }
   135  
   136  // Fs represents a SMB remote
   137  type Fs struct {
   138  	name     string       // name of this remote
   139  	root     string       // the path we are working on if any
   140  	opt      Options      // parsed config options
   141  	features *fs.Features // optional features
   142  	pacer    *fs.Pacer    // pacer for operations
   143  
   144  	sessions atomic.Int32
   145  	poolMu   sync.Mutex
   146  	pool     []*conn
   147  	drain    *time.Timer // used to drain the pool when we stop using the connections
   148  
   149  	ctx context.Context
   150  }
   151  
   152  // Object describes a file at the server
   153  type Object struct {
   154  	fs         *Fs    // reference to Fs
   155  	remote     string // the remote path
   156  	statResult os.FileInfo
   157  }
   158  
   159  // NewFs constructs an Fs from the path
   160  func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, error) {
   161  	// Parse config into Options struct
   162  	opt := new(Options)
   163  	err := configstruct.Set(m, opt)
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	root = strings.Trim(root, "/")
   169  
   170  	f := &Fs{
   171  		name: name,
   172  		opt:  *opt,
   173  		ctx:  ctx,
   174  		root: root,
   175  	}
   176  	f.features = (&fs.Features{
   177  		CaseInsensitive:         opt.CaseInsensitive,
   178  		CanHaveEmptyDirectories: true,
   179  		BucketBased:             true,
   180  		PartialUploads:          true,
   181  	}).Fill(ctx, f)
   182  
   183  	f.pacer = fs.NewPacer(ctx, pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant)))
   184  	// set the pool drainer timer going
   185  	if opt.IdleTimeout > 0 {
   186  		f.drain = time.AfterFunc(time.Duration(opt.IdleTimeout), func() { _ = f.drainPool(ctx) })
   187  	}
   188  
   189  	// test if the root exists as a file
   190  	share, dir := f.split("")
   191  	if share == "" || dir == "" {
   192  		return f, nil
   193  	}
   194  	cn, err := f.getConnection(ctx, share)
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  	stat, err := cn.smbShare.Stat(f.toSambaPath(dir))
   199  	f.putConnection(&cn)
   200  	if err != nil {
   201  		// ignore stat error here
   202  		return f, nil
   203  	}
   204  	if !stat.IsDir() {
   205  		f.root, err = path.Dir(root), fs.ErrorIsFile
   206  	}
   207  	fs.Debugf(f, "Using root directory %q", f.root)
   208  	return f, err
   209  }
   210  
   211  // Name of the remote (as passed into NewFs)
   212  func (f *Fs) Name() string {
   213  	return f.name
   214  }
   215  
   216  // Root of the remote (as passed into NewFs)
   217  func (f *Fs) Root() string {
   218  	return f.root
   219  }
   220  
   221  // String converts this Fs to a string
   222  func (f *Fs) String() string {
   223  	bucket, file := f.split("")
   224  	if bucket == "" {
   225  		return fmt.Sprintf("smb://%s@%s:%s/", f.opt.User, f.opt.Host, f.opt.Port)
   226  	}
   227  	return fmt.Sprintf("smb://%s@%s:%s/%s/%s", f.opt.User, f.opt.Host, f.opt.Port, bucket, file)
   228  }
   229  
   230  // Features returns the optional features of this Fs
   231  func (f *Fs) Features() *fs.Features {
   232  	return f.features
   233  }
   234  
   235  // Hashes returns nothing as SMB itself doesn't have a way to tell checksums
   236  func (f *Fs) Hashes() hash.Set {
   237  	return hash.NewHashSet()
   238  }
   239  
   240  // Precision returns the precision of mtime
   241  func (f *Fs) Precision() time.Duration {
   242  	return time.Millisecond
   243  }
   244  
   245  // NewObject creates a new file object
   246  func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) {
   247  	share, path := f.split(remote)
   248  	return f.findObjectSeparate(ctx, share, path)
   249  }
   250  
   251  func (f *Fs) findObjectSeparate(ctx context.Context, share, path string) (fs.Object, error) {
   252  	if share == "" || path == "" {
   253  		return nil, fs.ErrorIsDir
   254  	}
   255  	cn, err := f.getConnection(ctx, share)
   256  	if err != nil {
   257  		return nil, err
   258  	}
   259  	stat, err := cn.smbShare.Stat(f.toSambaPath(path))
   260  	f.putConnection(&cn)
   261  	if err != nil {
   262  		return nil, translateError(err, false)
   263  	}
   264  	if stat.IsDir() {
   265  		return nil, fs.ErrorIsDir
   266  	}
   267  
   268  	return f.makeEntry(share, path, stat), nil
   269  }
   270  
   271  // Mkdir creates a directory on the server
   272  func (f *Fs) Mkdir(ctx context.Context, dir string) (err error) {
   273  	share, path := f.split(dir)
   274  	if share == "" || path == "" {
   275  		return nil
   276  	}
   277  	cn, err := f.getConnection(ctx, share)
   278  	if err != nil {
   279  		return err
   280  	}
   281  	err = cn.smbShare.MkdirAll(f.toSambaPath(path), 0o755)
   282  	f.putConnection(&cn)
   283  	return err
   284  }
   285  
   286  // Rmdir removes an empty directory on the server
   287  func (f *Fs) Rmdir(ctx context.Context, dir string) error {
   288  	share, path := f.split(dir)
   289  	if share == "" || path == "" {
   290  		return nil
   291  	}
   292  	cn, err := f.getConnection(ctx, share)
   293  	if err != nil {
   294  		return err
   295  	}
   296  	err = cn.smbShare.Remove(f.toSambaPath(path))
   297  	f.putConnection(&cn)
   298  	return err
   299  }
   300  
   301  // Put uploads a file
   302  func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   303  	o := &Object{
   304  		fs:     f,
   305  		remote: src.Remote(),
   306  	}
   307  
   308  	err := o.Update(ctx, in, src, options...)
   309  	if err == nil {
   310  		return o, nil
   311  	}
   312  
   313  	return nil, err
   314  }
   315  
   316  // PutStream uploads to the remote path with the modTime given of indeterminate size
   317  //
   318  // May create the object even if it returns an error - if so
   319  // will return the object and the error, otherwise will return
   320  // nil and the error
   321  func (f *Fs) PutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   322  	o := &Object{
   323  		fs:     f,
   324  		remote: src.Remote(),
   325  	}
   326  
   327  	err := o.Update(ctx, in, src, options...)
   328  	if err == nil {
   329  		return o, nil
   330  	}
   331  
   332  	return nil, err
   333  }
   334  
   335  // Move src to this remote using server-side move operations.
   336  //
   337  // This is stored with the remote path given.
   338  //
   339  // It returns the destination Object and a possible error.
   340  //
   341  // Will only be called if src.Fs().Name() == f.Name()
   342  //
   343  // If it isn't possible then return fs.ErrorCantMove
   344  func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (_ fs.Object, err error) {
   345  	dstShare, dstPath := f.split(remote)
   346  	srcObj, ok := src.(*Object)
   347  	if !ok {
   348  		fs.Debugf(src, "Can't move - not same remote type")
   349  		return nil, fs.ErrorCantMove
   350  	}
   351  	srcShare, srcPath := srcObj.split()
   352  	if dstShare != srcShare {
   353  		fs.Debugf(src, "Can't move - must be on the same share")
   354  		return nil, fs.ErrorCantMove
   355  	}
   356  
   357  	err = f.ensureDirectory(ctx, dstShare, dstPath)
   358  	if err != nil {
   359  		return nil, fmt.Errorf("failed to make parent directories: %w", err)
   360  	}
   361  
   362  	cn, err := f.getConnection(ctx, dstShare)
   363  	if err != nil {
   364  		return nil, err
   365  	}
   366  	err = cn.smbShare.Rename(f.toSambaPath(srcPath), f.toSambaPath(dstPath))
   367  	f.putConnection(&cn)
   368  	if err != nil {
   369  		return nil, translateError(err, false)
   370  	}
   371  	return f.findObjectSeparate(ctx, dstShare, dstPath)
   372  }
   373  
   374  // DirMove moves src, srcRemote to this remote at dstRemote
   375  // using server-side move operations.
   376  //
   377  // Will only be called if src.Fs().Name() == f.Name()
   378  //
   379  // If it isn't possible then return fs.ErrorCantDirMove
   380  //
   381  // If destination exists then return fs.ErrorDirExists
   382  func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) (err error) {
   383  	dstShare, dstPath := f.split(dstRemote)
   384  	srcFs, ok := src.(*Fs)
   385  	if !ok {
   386  		fs.Debugf(src, "Can't move - not same remote type")
   387  		return fs.ErrorCantDirMove
   388  	}
   389  	srcShare, srcPath := srcFs.split(srcRemote)
   390  	if dstShare != srcShare {
   391  		fs.Debugf(src, "Can't move - must be on the same share")
   392  		return fs.ErrorCantDirMove
   393  	}
   394  
   395  	err = f.ensureDirectory(ctx, dstShare, dstPath)
   396  	if err != nil {
   397  		return fmt.Errorf("failed to make parent directories: %w", err)
   398  	}
   399  
   400  	cn, err := f.getConnection(ctx, dstShare)
   401  	if err != nil {
   402  		return err
   403  	}
   404  	defer f.putConnection(&cn)
   405  
   406  	_, err = cn.smbShare.Stat(dstPath)
   407  	if os.IsNotExist(err) {
   408  		err = cn.smbShare.Rename(f.toSambaPath(srcPath), f.toSambaPath(dstPath))
   409  		return translateError(err, true)
   410  	}
   411  	return fs.ErrorDirExists
   412  }
   413  
   414  // List files and directories in a directory
   415  func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
   416  	share, _path := f.split(dir)
   417  
   418  	cn, err := f.getConnection(ctx, share)
   419  	if err != nil {
   420  		return nil, err
   421  	}
   422  	defer f.putConnection(&cn)
   423  
   424  	if share == "" {
   425  		shares, err := cn.smbSession.ListSharenames()
   426  		for _, shh := range shares {
   427  			shh = f.toNativePath(shh)
   428  			if strings.HasSuffix(shh, "$") && f.opt.HideSpecial {
   429  				continue
   430  			}
   431  			entries = append(entries, fs.NewDir(shh, time.Time{}))
   432  		}
   433  		return entries, err
   434  	}
   435  
   436  	dirents, err := cn.smbShare.ReadDir(f.toSambaPath(_path))
   437  	if err != nil {
   438  		return entries, translateError(err, true)
   439  	}
   440  	for _, file := range dirents {
   441  		nfn := f.toNativePath(file.Name())
   442  		if file.IsDir() {
   443  			entries = append(entries, fs.NewDir(path.Join(dir, nfn), file.ModTime()))
   444  		} else {
   445  			entries = append(entries, f.makeEntryRelative(share, _path, nfn, file))
   446  		}
   447  	}
   448  
   449  	return entries, nil
   450  }
   451  
   452  // About returns things about remaining and used spaces
   453  func (f *Fs) About(ctx context.Context) (_ *fs.Usage, err error) {
   454  	share, dir := f.split("/")
   455  	if share == "" {
   456  		// Just return empty info rather than an error if called on the root
   457  		return &fs.Usage{}, nil
   458  	}
   459  	dir = f.toSambaPath(dir)
   460  
   461  	cn, err := f.getConnection(ctx, share)
   462  	if err != nil {
   463  		return nil, err
   464  	}
   465  	stat, err := cn.smbShare.Statfs(dir)
   466  	f.putConnection(&cn)
   467  	if err != nil {
   468  		return nil, err
   469  	}
   470  
   471  	bs := int64(stat.BlockSize())
   472  	usage := &fs.Usage{
   473  		Total: fs.NewUsageValue(bs * int64(stat.TotalBlockCount())),
   474  		Used:  fs.NewUsageValue(bs * int64(stat.TotalBlockCount()-stat.FreeBlockCount())),
   475  		Free:  fs.NewUsageValue(bs * int64(stat.AvailableBlockCount())),
   476  	}
   477  	return usage, nil
   478  }
   479  
   480  // OpenWriterAt opens with a handle for random access writes
   481  //
   482  // Pass in the remote desired and the size if known.
   483  //
   484  // It truncates any existing object
   485  func (f *Fs) OpenWriterAt(ctx context.Context, remote string, size int64) (fs.WriterAtCloser, error) {
   486  	var err error
   487  	o := &Object{
   488  		fs:     f,
   489  		remote: remote,
   490  	}
   491  	share, filename := o.split()
   492  	if share == "" || filename == "" {
   493  		return nil, fs.ErrorIsDir
   494  	}
   495  
   496  	err = o.fs.ensureDirectory(ctx, share, filename)
   497  	if err != nil {
   498  		return nil, fmt.Errorf("failed to make parent directories: %w", err)
   499  	}
   500  
   501  	filename = o.fs.toSambaPath(filename)
   502  
   503  	o.fs.addSession() // Show session in use
   504  	defer o.fs.removeSession()
   505  
   506  	cn, err := o.fs.getConnection(ctx, share)
   507  	if err != nil {
   508  		return nil, err
   509  	}
   510  
   511  	fl, err := cn.smbShare.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
   512  	if err != nil {
   513  		return nil, fmt.Errorf("failed to open: %w", err)
   514  	}
   515  
   516  	return fl, nil
   517  }
   518  
   519  // Shutdown the backend, closing any background tasks and any
   520  // cached connections.
   521  func (f *Fs) Shutdown(ctx context.Context) error {
   522  	return f.drainPool(ctx)
   523  }
   524  
   525  func (f *Fs) makeEntry(share, _path string, stat os.FileInfo) *Object {
   526  	remote := path.Join(share, _path)
   527  	return &Object{
   528  		fs:         f,
   529  		remote:     trimPathPrefix(remote, f.root),
   530  		statResult: stat,
   531  	}
   532  }
   533  
   534  func (f *Fs) makeEntryRelative(share, _path, relative string, stat os.FileInfo) *Object {
   535  	return f.makeEntry(share, path.Join(_path, relative), stat)
   536  }
   537  
   538  func (f *Fs) ensureDirectory(ctx context.Context, share, _path string) error {
   539  	dir := path.Dir(_path)
   540  	if dir == "." {
   541  		return nil
   542  	}
   543  	cn, err := f.getConnection(ctx, share)
   544  	if err != nil {
   545  		return err
   546  	}
   547  	err = cn.smbShare.MkdirAll(f.toSambaPath(dir), 0o755)
   548  	f.putConnection(&cn)
   549  	return err
   550  }
   551  
   552  /// Object
   553  
   554  // Remote returns the remote path
   555  func (o *Object) Remote() string {
   556  	return o.remote
   557  }
   558  
   559  // ModTime is the last modified time (read-only)
   560  func (o *Object) ModTime(ctx context.Context) time.Time {
   561  	return o.statResult.ModTime()
   562  }
   563  
   564  // Size is the file length
   565  func (o *Object) Size() int64 {
   566  	return o.statResult.Size()
   567  }
   568  
   569  // Fs returns the parent Fs
   570  func (o *Object) Fs() fs.Info {
   571  	return o.fs
   572  }
   573  
   574  // Hash always returns empty value
   575  func (o *Object) Hash(ctx context.Context, ty hash.Type) (string, error) {
   576  	return "", hash.ErrUnsupported
   577  }
   578  
   579  // Storable returns if this object is storable
   580  func (o *Object) Storable() bool {
   581  	return true
   582  }
   583  
   584  // SetModTime sets modTime on a particular file
   585  func (o *Object) SetModTime(ctx context.Context, t time.Time) (err error) {
   586  	share, reqDir := o.split()
   587  	if share == "" || reqDir == "" {
   588  		return fs.ErrorCantSetModTime
   589  	}
   590  	reqDir = o.fs.toSambaPath(reqDir)
   591  
   592  	cn, err := o.fs.getConnection(ctx, share)
   593  	if err != nil {
   594  		return err
   595  	}
   596  	defer o.fs.putConnection(&cn)
   597  
   598  	err = cn.smbShare.Chtimes(reqDir, t, t)
   599  	if err != nil {
   600  		return err
   601  	}
   602  
   603  	fi, err := cn.smbShare.Stat(reqDir)
   604  	if err == nil {
   605  		o.statResult = fi
   606  	}
   607  	return err
   608  }
   609  
   610  // Open an object for read
   611  func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.ReadCloser, err error) {
   612  	share, filename := o.split()
   613  	if share == "" || filename == "" {
   614  		return nil, fs.ErrorIsDir
   615  	}
   616  	filename = o.fs.toSambaPath(filename)
   617  
   618  	var offset, limit int64 = 0, -1
   619  	for _, option := range options {
   620  		switch x := option.(type) {
   621  		case *fs.SeekOption:
   622  			offset = x.Offset
   623  		case *fs.RangeOption:
   624  			offset, limit = x.Decode(o.Size())
   625  		default:
   626  			if option.Mandatory() {
   627  				fs.Logf(o, "Unsupported mandatory option: %v", option)
   628  			}
   629  		}
   630  	}
   631  
   632  	o.fs.addSession() // Show session in use
   633  	defer o.fs.removeSession()
   634  
   635  	cn, err := o.fs.getConnection(ctx, share)
   636  	if err != nil {
   637  		return nil, err
   638  	}
   639  	fl, err := cn.smbShare.OpenFile(filename, os.O_RDONLY, 0)
   640  	if err != nil {
   641  		o.fs.putConnection(&cn)
   642  		return nil, fmt.Errorf("failed to open: %w", err)
   643  	}
   644  	pos, err := fl.Seek(offset, io.SeekStart)
   645  	if err != nil {
   646  		o.fs.putConnection(&cn)
   647  		return nil, fmt.Errorf("failed to seek: %w", err)
   648  	}
   649  	if pos != offset {
   650  		o.fs.putConnection(&cn)
   651  		return nil, fmt.Errorf("failed to seek: wrong position (expected=%d, reported=%d)", offset, pos)
   652  	}
   653  
   654  	in = readers.NewLimitedReadCloser(fl, limit)
   655  	in = &boundReadCloser{
   656  		rc: in,
   657  		close: func() error {
   658  			o.fs.putConnection(&cn)
   659  			return nil
   660  		},
   661  	}
   662  
   663  	return in, nil
   664  }
   665  
   666  // Update the Object from in with modTime and size
   667  func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (err error) {
   668  	share, filename := o.split()
   669  	if share == "" || filename == "" {
   670  		return fs.ErrorIsDir
   671  	}
   672  
   673  	err = o.fs.ensureDirectory(ctx, share, filename)
   674  	if err != nil {
   675  		return fmt.Errorf("failed to make parent directories: %w", err)
   676  	}
   677  
   678  	filename = o.fs.toSambaPath(filename)
   679  
   680  	o.fs.addSession() // Show session in use
   681  	defer o.fs.removeSession()
   682  
   683  	cn, err := o.fs.getConnection(ctx, share)
   684  	if err != nil {
   685  		return err
   686  	}
   687  	defer func() {
   688  		o.statResult, _ = cn.smbShare.Stat(filename)
   689  		o.fs.putConnection(&cn)
   690  	}()
   691  
   692  	fl, err := cn.smbShare.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
   693  	if err != nil {
   694  		return fmt.Errorf("failed to open: %w", err)
   695  	}
   696  
   697  	// remove the file if upload failed
   698  	remove := func() {
   699  		// Windows doesn't allow removal of files without closing file
   700  		removeErr := fl.Close()
   701  		if removeErr != nil {
   702  			fs.Debugf(src, "failed to close the file for delete: %v", removeErr)
   703  			// try to remove the file anyway; the file may be already closed
   704  		}
   705  
   706  		removeErr = cn.smbShare.Remove(filename)
   707  		if removeErr != nil {
   708  			fs.Debugf(src, "failed to remove: %v", removeErr)
   709  		} else {
   710  			fs.Debugf(src, "removed after failed upload: %v", err)
   711  		}
   712  	}
   713  
   714  	_, err = fl.ReadFrom(in)
   715  	if err != nil {
   716  		remove()
   717  		return fmt.Errorf("Update ReadFrom failed: %w", err)
   718  	}
   719  
   720  	err = fl.Close()
   721  	if err != nil {
   722  		remove()
   723  		return fmt.Errorf("Update Close failed: %w", err)
   724  	}
   725  
   726  	// Set the modified time
   727  	err = o.SetModTime(ctx, src.ModTime(ctx))
   728  	if err != nil {
   729  		return fmt.Errorf("Update SetModTime failed: %w", err)
   730  	}
   731  
   732  	return nil
   733  }
   734  
   735  // Remove an object
   736  func (o *Object) Remove(ctx context.Context) (err error) {
   737  	share, filename := o.split()
   738  	if share == "" || filename == "" {
   739  		return fs.ErrorIsDir
   740  	}
   741  	filename = o.fs.toSambaPath(filename)
   742  
   743  	cn, err := o.fs.getConnection(ctx, share)
   744  	if err != nil {
   745  		return err
   746  	}
   747  
   748  	err = cn.smbShare.Remove(filename)
   749  	o.fs.putConnection(&cn)
   750  
   751  	return err
   752  }
   753  
   754  // String converts this Object to a string
   755  func (o *Object) String() string {
   756  	if o == nil {
   757  		return "<nil>"
   758  	}
   759  	return o.remote
   760  }
   761  
   762  /// Misc
   763  
   764  // split returns share name and path in the share from the rootRelativePath
   765  // relative to f.root
   766  func (f *Fs) split(rootRelativePath string) (shareName, filepath string) {
   767  	return bucket.Split(path.Join(f.root, rootRelativePath))
   768  }
   769  
   770  // split returns share name and path in the share from the object
   771  func (o *Object) split() (shareName, filepath string) {
   772  	return o.fs.split(o.remote)
   773  }
   774  
   775  func (f *Fs) toSambaPath(path string) string {
   776  	// 1. encode via Rclone's escaping system
   777  	// 2. convert to backslash-separated path
   778  	return strings.ReplaceAll(f.opt.Enc.FromStandardPath(path), "/", "\\")
   779  }
   780  
   781  func (f *Fs) toNativePath(path string) string {
   782  	// 1. convert *back* to slash-separated path
   783  	// 2. encode via Rclone's escaping system
   784  	return f.opt.Enc.ToStandardPath(strings.ReplaceAll(path, "\\", "/"))
   785  }
   786  
   787  func ensureSuffix(s, suffix string) string {
   788  	if strings.HasSuffix(s, suffix) {
   789  		return s
   790  	}
   791  	return s + suffix
   792  }
   793  
   794  func trimPathPrefix(s, prefix string) string {
   795  	// we need to clean the paths to make tests pass!
   796  	s = betterPathClean(s)
   797  	prefix = betterPathClean(prefix)
   798  	if s == prefix || s == prefix+"/" {
   799  		return ""
   800  	}
   801  	prefix = ensureSuffix(prefix, "/")
   802  	return strings.TrimPrefix(s, prefix)
   803  }
   804  
   805  func betterPathClean(p string) string {
   806  	d := path.Clean(p)
   807  	if d == "." {
   808  		return ""
   809  	}
   810  	return d
   811  }
   812  
   813  type boundReadCloser struct {
   814  	rc    io.ReadCloser
   815  	close func() error
   816  }
   817  
   818  func (r *boundReadCloser) Read(p []byte) (n int, err error) {
   819  	return r.rc.Read(p)
   820  }
   821  
   822  func (r *boundReadCloser) Close() error {
   823  	err1 := r.rc.Close()
   824  	err2 := r.close()
   825  	if err1 != nil {
   826  		return err1
   827  	}
   828  	return err2
   829  }
   830  
   831  func translateError(e error, dir bool) error {
   832  	if os.IsNotExist(e) {
   833  		if dir {
   834  			return fs.ErrorDirNotFound
   835  		}
   836  		return fs.ErrorObjectNotFound
   837  	}
   838  
   839  	return e
   840  }
   841  
   842  var (
   843  	_ fs.Fs          = &Fs{}
   844  	_ fs.PutStreamer = &Fs{}
   845  	_ fs.Mover       = &Fs{}
   846  	_ fs.DirMover    = &Fs{}
   847  	_ fs.Abouter     = &Fs{}
   848  	_ fs.Shutdowner  = &Fs{}
   849  	_ fs.Object      = &Object{}
   850  	_ io.ReadCloser  = &boundReadCloser{}
   851  )