Skip to content

Commit

Permalink
Add ability to set file permissions after writing an SFTP file. Resol… (
Browse files Browse the repository at this point in the history
#203)

* Add ability to set file permissions after writing an SFTP file. Resolves #202.

* fixed version in changelog

* updated Options to use string for file permissions

* removed unused helper funcs

* correct doc
  • Loading branch information
funkyshu authored Sep 16, 2024
1 parent 5ba83da commit a405c69
Show file tree
Hide file tree
Showing 13 changed files with 1,286 additions and 259 deletions.
3 changes: 3 additions & 0 deletions .mockery.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ packages:
github.com/c2fo/vfs/v6/backend/s3:
config:
all: true
github.com/c2fo/vfs/v6/backend/sftp:
config:
all: true
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]

## [6.19.0] - 2024-09-13
### Added
- Add ability to set file permissions after writing an SFTP file. Resolves #202.

## [6.18.0] - 2024-09-12
### Added
- Updated mocks to use mockery Expecter. Resolves #200.
Expand Down
17 changes: 17 additions & 0 deletions backend/sftp/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,23 @@ Example:
},
)
# FilePermissions
The `FilePermissions` option allows you to specify the file permissions for files created or modified using the SFTP backend.
These permissions will override the sftp server or underlying filesystem's umask (default permissions). Permissions should
be specified using an octal literal (e.g., `0777` for full read, write, and execute permissions for all users).
Example:
fs = fs.WithOptions(
sftp.Options{
FilePermissions: "0777", // Correctly specify permissions as octal (in string form)
// other settings
},
)
When a file is opened for Write() or Touch()'d, the specified `FilePermissions` will be applied to the file.
# AutoDisconnect
When dialing a TCP connection, Go doesn't disconnect for you. This is true even when the connection falls out of scope, and even when
Expand Down
75 changes: 58 additions & 17 deletions backend/sftp/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package sftp

import (
"errors"
"fmt"
"io"
"os"
"path"
Expand Down Expand Up @@ -78,15 +79,16 @@ func (f *File) Exists() (bool, error) {
// Touch creates a zero-length file on the vfs.File if no File exists. Update File's last modified timestamp.
// Returns error if unable to touch File.
func (f *File) Touch() error {
// restart timer once action is completed
f.fileSystem.connTimerStop()
defer f.fileSystem.connTimerStart()

exists, err := f.Exists()
if err != nil {
return err
}

if !exists {
// restart timer once action is completed
f.fileSystem.connTimerStop()
defer f.fileSystem.connTimerStart()
file, err := f.openFile(os.O_WRONLY | os.O_CREATE)
if err != nil {
return err
Expand All @@ -99,10 +101,15 @@ func (f *File) Touch() error {
if err != nil {
return err
}
// start timer once action is completed
defer f.fileSystem.connTimerStart()
now := time.Now()

// set permissions if default permissions are set
err = f.setPermissions(client, f.fileSystem.options)
if err != nil {
return err
}

// update last accessed and last modified times
now := time.Now()
return client.Chtimes(f.Path(), now, now)
}

Expand Down Expand Up @@ -140,11 +147,6 @@ func (f *File) Location() vfs.Location {
// If the given location is also sftp AND for the same user and host, the sftp Rename method is used, otherwise
// we'll do a an io.Copy to the destination file then delete source file.
func (f *File) MoveToFile(t vfs.File) error {
// validate seek is at 0,0 before doing copy
// TODO: Fix this later
// if err := backend.ValidateCopySeekPosition(f); err != nil {
// return err
// }
// sftp rename if vfs is sftp and for the same user/host
if f.fileSystem.Scheme() == t.Location().FileSystem().Scheme() &&
f.Authority.UserInfo().Username() == t.(*File).Authority.UserInfo().Username() &&
Expand Down Expand Up @@ -204,11 +206,6 @@ func (f *File) MoveToLocation(location vfs.Location) (vfs.File, error) {

// CopyToFile puts the contents of File into the targetFile passed.
func (f *File) CopyToFile(file vfs.File) (err error) {
// validate seek is at 0,0 before doing copy
// TODO: Fix this later
// if err := backend.ValidateCopySeekPosition(f); err != nil {
// return err
// }

// Close file (f) reader regardless of an error
defer func() {
Expand Down Expand Up @@ -286,6 +283,7 @@ func (f *File) Close() error {
}
f.sftpfile = nil
}

// no op for unopened file
return nil
}
Expand Down Expand Up @@ -490,8 +488,51 @@ func (f *File) _open(flags int) (ReadWriteSeekCloser, error) {
opener = defaultOpenFile
}

return opener(client, f.Path(), flags)
rwsc, err := opener(client, f.Path(), flags)
if err != nil {
return nil, err
}

// chmod file if default permissions are set and opening for write
if flags&os.O_WRONLY != 0 {
err = f.setPermissions(client, f.fileSystem.options)
if err != nil {
return nil, err
}
}

return rwsc, nil
}

// setPermissions sets the file permissions if they are set in the options
func (f *File) setPermissions(client Client, opts vfs.Options) error {
if opts == nil {
return nil
}

// ensure we're dealing with pointer to Options
ptrOpts, ok := opts.(*Options)
if !ok {
p := opts.(Options)
ptrOpts = &p
}

// if file permissions are not set, return early
if ptrOpts.FilePermissions == nil {
return nil
}

// get file mode
perms, err := ptrOpts.GetFileMode()
if err != nil {
return fmt.Errorf("get file mode err: %w", err)
}

if err := client.Chmod(f.Path(), *perms); err != nil {
return fmt.Errorf("chmod err: %w", err)
}

return nil
}

// defaultOpenFile uses sftp.Client to open a file and returns an sftp.File
Expand Down
1 change: 1 addition & 0 deletions backend/sftp/fileSystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ func init() {

// Client is an interface to make it easier to test
type Client interface {
Chmod(path string, mode os.FileMode) error
Chtimes(path string, atime, mtime time.Time) error
Create(path string) (*_sftp.File, error)
MkdirAll(path string) error
Expand Down
Loading

0 comments on commit a405c69

Please sign in to comment.