Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

filestore: add dirs(0775 def) and files(0664 def) chmod(2) perms cmdline options #1155

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions cmd/tusd/cli/composer.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,14 @@ func CreateComposer() {
}

stdout.Printf("Using '%s' as directory storage.\n", dir)
if err := os.MkdirAll(dir, os.FileMode(0774)); err != nil {
if err := os.MkdirAll(dir, os.FileMode(Flags.DirPerms)); err != nil {
stderr.Fatalf("Unable to ensure directory exists: %s", err)
}

store := filestore.New(dir)
store := filestore.NewWithOptions(dir, &filestore.FileStoreOptions{
DirPerm: Flags.DirPerms,
FilePerm: Flags.FilePerms,
})
store.UseIn(Composer)

locker := filelocker.New(dir)
Expand Down
30 changes: 30 additions & 0 deletions cmd/tusd/cli/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package cli

import (
"flag"
"fmt"
"path/filepath"
"strconv"
"strings"
"time"

"github.com/tus/tusd/v2/internal/grouped_flags"
"github.com/tus/tusd/v2/pkg/filestore"
"github.com/tus/tusd/v2/pkg/hooks"
"golang.org/x/exp/slices"
)
Expand Down Expand Up @@ -74,11 +77,36 @@ var Flags struct {
AcquireLockTimeout time.Duration
FilelockHolderPollInterval time.Duration
FilelockAcquirerPollInterval time.Duration
FilePerms uint32
DirPerms uint32
GracefulRequestCompletionTimeout time.Duration
ExperimentalProtocol bool
}

type ChmodPermsValue struct {
perms *uint32
}

func (v ChmodPermsValue) String() string {
if v.perms != nil {
return fmt.Sprintf("%o", *v.perms)
}
return ""
}

func (v ChmodPermsValue) Set(s string) error {
if u, err := strconv.ParseUint(s, 8, 32); err != nil {
return err
} else {
*v.perms = uint32(u)
}
return nil
}

func ParseFlags() {
Flags.DirPerms = filestore.DefaultDirPerm
Flags.FilePerms = filestore.DefaultFilePerm

fs := grouped_flags.NewFlagGroupSet(flag.ExitOnError)

fs.AddGroup("Listening options", func(f *flag.FlagSet) {
Expand Down Expand Up @@ -116,6 +144,8 @@ func ParseFlags() {
f.StringVar(&Flags.UploadDir, "upload-dir", "./data", "Directory to store uploads in")
f.DurationVar(&Flags.FilelockHolderPollInterval, "filelock-holder-poll-interval", 5*time.Second, "The holder of a lock polls regularly to see if another request handler needs the lock. This flag specifies the poll interval.")
f.DurationVar(&Flags.FilelockAcquirerPollInterval, "filelock-acquirer-poll-interval", 2*time.Second, "The acquirer of a lock polls regularly to see if the lock has been released. This flag specifies the poll interval.")
f.Var(&ChmodPermsValue{&Flags.DirPerms}, "dir-perms", "The created directory chmod(2) OCTAL value permissions.")
f.Var(&ChmodPermsValue{&Flags.FilePerms}, "file-perms", "The created file chmod(2) OCTAL value permissions.")
})

fs.AddGroup("AWS S3 storage options", func(f *flag.FlagSet) {
Expand Down
4 changes: 4 additions & 0 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ set -o pipefail

. /usr/local/share/load-env.sh

if printenv UMASK >/dev/null; then
umask "$UMASK"
fi

exec tusd "$@"
5 changes: 5 additions & 0 deletions docs/_advanced-topics/usage-package.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ func main() {
// a remote FTP server, you can implement your own storage backend
// by implementing the tusd.DataStore interface.
store := filestore.New("./uploads")
// or use options
// filestore.NewWithOptions(dir, &filestore.FileStoreOptions{
// DirPerm: 0777, // see also umask(2)
// FilePerm: 0666, // see also umask(2)
// })

// A locking mechanism helps preventing data loss or corruption from
// parallel requests to a upload resource. A good match for the disk-based
Expand Down
74 changes: 56 additions & 18 deletions pkg/filestore/filestore.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,60 @@ import (
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"

"github.com/tus/tusd/v2/internal/uid"
"github.com/tus/tusd/v2/pkg/handler"
)

var defaultFilePerm = os.FileMode(0664)
var defaultDirectoryPerm = os.FileMode(0754)

// See the handler.DataStore interface for documentation about the different
// methods.

const DefaultDirPerm = 0775
const DefaultFilePerm = 0664

type FileStoreOptions struct {
DirPerm uint32
FilePerm uint32
}

var defaultOptions = FileStoreOptions{
DirPerm: DefaultDirPerm,
FilePerm: DefaultFilePerm,
}

type FileStore struct {
// Relative or absolute path to store files in. FileStore does not check
// whether the path exists, use os.MkdirAll in this case on your own.
Path string

DirModePerm fs.FileMode
FileModePerm fs.FileMode
}

// New creates a new file based storage backend. The directory specified will
// be used as the only storage entry. This method does not check
// whether the path exists, use os.MkdirAll to ensure.
func New(path string) FileStore {
return FileStore{path}
return FileStore{
Path: path,
DirModePerm: os.FileMode(defaultOptions.DirPerm) & os.ModePerm,
FileModePerm: os.FileMode(defaultOptions.FilePerm) & os.ModePerm,
}
}

func NewWithOptions(path string, options *FileStoreOptions) FileStore {
if options == nil {
options = &defaultOptions
}

return FileStore{
Path: path,
DirModePerm: os.FileMode(options.DirPerm) & os.ModePerm,
FileModePerm: os.FileMode(options.FilePerm) & os.ModePerm,
}
}

// UseIn sets this store as the core data store in the passed composer and adds
Expand Down Expand Up @@ -73,14 +104,16 @@ func (store FileStore) NewUpload(ctx context.Context, info handler.FileInfo) (ha
}

// Create binary file with no content
if err := createFile(binPath, nil); err != nil {
if err := createFile(binPath, store.DirModePerm, store.FileModePerm, nil); err != nil {
return nil, err
}

upload := &fileUpload{
info: info,
infoPath: infoPath,
binPath: binPath,
info: info,
infoPath: infoPath,
binPath: binPath,
dirModePerm: store.DirModePerm,
fileModePerm: store.FileModePerm,
}

// writeInfo creates the file by itself if necessary
Expand Down Expand Up @@ -130,9 +163,11 @@ func (store FileStore) GetUpload(ctx context.Context, id string) (handler.Upload
info.Offset = stat.Size()

return &fileUpload{
info: info,
binPath: binPath,
infoPath: infoPath,
info: info,
binPath: binPath,
infoPath: infoPath,
dirModePerm: store.DirModePerm,
fileModePerm: store.FileModePerm,
}, nil
}

Expand Down Expand Up @@ -166,14 +201,17 @@ type fileUpload struct {
infoPath string
// binPath is the path to the binary file (which has no extension)
binPath string

dirModePerm fs.FileMode
fileModePerm fs.FileMode
}

func (upload *fileUpload) GetInfo(ctx context.Context) (handler.FileInfo, error) {
return upload.info, nil
}

func (upload *fileUpload) WriteChunk(ctx context.Context, offset int64, src io.Reader) (int64, error) {
file, err := os.OpenFile(upload.binPath, os.O_WRONLY|os.O_APPEND, defaultFilePerm)
file, err := os.OpenFile(upload.binPath, os.O_WRONLY|os.O_APPEND, upload.fileModePerm)
if err != nil {
return 0, err
}
Expand Down Expand Up @@ -212,7 +250,7 @@ func (upload *fileUpload) Terminate(ctx context.Context) error {
}

func (upload *fileUpload) ConcatUploads(ctx context.Context, uploads []handler.Upload) (err error) {
file, err := os.OpenFile(upload.binPath, os.O_WRONLY|os.O_APPEND, defaultFilePerm)
file, err := os.OpenFile(upload.binPath, os.O_WRONLY|os.O_APPEND, upload.fileModePerm)
if err != nil {
return err
}
Expand Down Expand Up @@ -253,7 +291,7 @@ func (upload *fileUpload) writeInfo() error {
if err != nil {
return err
}
return createFile(upload.infoPath, data)
return createFile(upload.infoPath, upload.dirModePerm, upload.fileModePerm, data)
}

func (upload *fileUpload) FinishUpload(ctx context.Context) error {
Expand All @@ -262,19 +300,19 @@ func (upload *fileUpload) FinishUpload(ctx context.Context) error {

// createFile creates the file with the content. If the corresponding directory does not exist,
// it is created. If the file already exists, its content is removed.
func createFile(path string, content []byte) error {
file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, defaultFilePerm)
func createFile(path string, dirPerm fs.FileMode, filePerm fs.FileMode, content []byte) error {
file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, filePerm)
if err != nil {
if os.IsNotExist(err) {
// An upload ID containing slashes is mapped onto different directories on disk,
// for example, `myproject/uploadA` should be put into a folder called `myproject`.
// If we get an error indicating that a directory is missing, we try to create it.
if err := os.MkdirAll(filepath.Dir(path), defaultDirectoryPerm); err != nil {
if err := os.MkdirAll(filepath.Dir(path), dirPerm); err != nil {
return fmt.Errorf("failed to create directory for %s: %s", path, err)
}

// Try creating the file again.
file, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, defaultFilePerm)
file, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, filePerm)
if err != nil {
// If that still doesn't work, error out.
return err
Expand Down
5 changes: 4 additions & 1 deletion pkg/handler/composer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import (
func ExampleNewStoreComposer() {
composer := handler.NewStoreComposer()

fs := filestore.New("./data")
fs := filestore.New("./data", &filestore.FileStoreOptions{
DirPerm: 0775,
FilePerm: 0664,
})
fs.UseIn(composer)

ml := memorylocker.New()
Expand Down
5 changes: 4 additions & 1 deletion pkg/hooks/hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ func TestNewHandlerWithHooks(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

store := filestore.New("some-path")
store := filestore.NewWithOptions("some-path", &filestore.FileStoreOptions{
DirPerm: 0775,
FilePerm: 0664,
})
config := handler.Config{
StoreComposer: handler.NewStoreComposer(),
}
Expand Down