diff --git a/cmd/stfs/cmd/serve_ftp.go b/cmd/stfs/cmd/serve_ftp.go index 34fccfc..3dd3434 100644 --- a/cmd/stfs/cmd/serve_ftp.go +++ b/cmd/stfs/cmd/serve_ftp.go @@ -122,11 +122,6 @@ var serveFTPCmd = &cobra.Command{ return err } - root, err := metadataPersister.GetRootPath(context.Background()) - if err != nil { - return err - } - jsonLogger := logging.NewJSONLogger(viper.GetInt(verboseFlag)) readOps := operations.NewOperations( @@ -216,6 +211,20 @@ var serveFTPCmd = &cobra.Command{ jsonLogger, ) + root, err := metadataPersister.GetRootPath(context.Background()) + if err != nil { + if err == config.ErrNoRootDirectory { + // FIXME: Re-index first, and only `Mkdir` if it still fails after indexing, otherwise this would prevent usage of non-indexed, existing tar files + + root = "/" + if err := stfs.MkdirRoot(root, os.ModePerm); err != nil { + return err + } + } else { + return err + } + } + fs, err := cache.NewCacheFilesystem( stfs, root, diff --git a/pkg/config/config.go b/pkg/config/config.go index 7b65ad6..7583bf2 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1,11 +1,12 @@ package config import ( + "context" "io" "io/fs" "os" - "github.com/pojntfx/stfs/pkg/persisters" + models "github.com/pojntfx/stfs/internal/db/sqlite/models/metadata" ) type DriveReaderConfig struct { @@ -34,8 +35,22 @@ type BackendConfig struct { CloseDrive func() error } +type MetadataPersister interface { + UpsertHeader(ctx context.Context, dbhdr *models.Header) error + UpdateHeaderMetadata(ctx context.Context, dbhdr *models.Header) error + MoveHeader(ctx context.Context, oldName string, newName string, lastknownrecord, lastknownblock int64) error + GetHeaders(ctx context.Context) (models.HeaderSlice, error) + GetHeader(ctx context.Context, name string) (*models.Header, error) + GetHeaderChildren(ctx context.Context, name string) (models.HeaderSlice, error) + GetRootPath(ctx context.Context) (string, error) + GetHeaderDirectChildren(ctx context.Context, name string, limit int) (models.HeaderSlice, error) + DeleteHeader(ctx context.Context, name string, lastknownrecord, lastknownblock int64) (*models.Header, error) + GetLastIndexedRecordAndBlock(ctx context.Context, recordSize int) (int64, int64, error) + PurgeAllHeaders(ctx context.Context) error +} + type MetadataConfig struct { - Metadata *persisters.MetadataPersister + Metadata MetadataPersister } type PipeConfig struct { diff --git a/pkg/config/error.go b/pkg/config/error.go index 75c9350..01be43d 100644 --- a/pkg/config/error.go +++ b/pkg/config/error.go @@ -41,4 +41,6 @@ var ( ErrWriteCacheTypeUnsupported = errors.New("write cache type unsupported") ErrWriteCacheTypeUnknown = errors.New("write cache type unknown") + + ErrNoRootDirectory = errors.New("root directory could not be found") ) diff --git a/pkg/fs/filesystem.go b/pkg/fs/filesystem.go index 0f49fdb..0052f9d 100644 --- a/pkg/fs/filesystem.go +++ b/pkg/fs/filesystem.go @@ -47,7 +47,7 @@ func NewSTFS( onHeader func(hdr *models.Header), log *logging.JSONLogger, -) afero.Fs { +) *STFS { return &STFS{ readOps: readOps, writeOps: writeOps, @@ -79,7 +79,7 @@ func (f *STFS) Create(name string) (afero.File, error) { return os.OpenFile(name, os.O_CREATE, 0666) } -func (f *STFS) mknode(dir bool, name string, perm os.FileMode) error { +func (f *STFS) mknode(dir bool, name string, perm os.FileMode, overwrite bool) error { f.log.Trace("FileSystem.mknode", map[string]interface{}{ "name": name, "perm": perm, @@ -148,7 +148,7 @@ func (f *STFS) mknode(dir bool, name string, perm os.FileMode) error { }, nil }, f.compressionLevel, - false, + overwrite, ); err != nil { return err } @@ -156,13 +156,22 @@ func (f *STFS) mknode(dir bool, name string, perm os.FileMode) error { return nil } +func (f *STFS) MkdirRoot(name string, perm os.FileMode) error { + f.log.Debug("FileSystem.MkdirRoot", map[string]interface{}{ + "name": name, + "perm": perm, + }) + + return f.mknode(true, name, perm, true) +} + func (f *STFS) Mkdir(name string, perm os.FileMode) error { f.log.Debug("FileSystem.Mkdir", map[string]interface{}{ "name": name, "perm": perm, }) - return f.mknode(true, name, perm) + return f.mknode(true, name, perm, false) } func (f *STFS) MkdirAll(path string, perm os.FileMode) error { @@ -181,7 +190,7 @@ func (f *STFS) MkdirAll(path string, perm os.FileMode) error { currentPath = filepath.Join(currentPath, part) } - if err := f.mknode(true, currentPath, perm); err != nil { + if err := f.mknode(true, currentPath, perm, false); err != nil { return err } } @@ -241,7 +250,7 @@ func (f *STFS) OpenFile(name string, flag int, perm os.FileMode) (afero.File, er if err != nil { if err == sql.ErrNoRows { if flag&os.O_CREATE != 0 && flag&os.O_EXCL == 0 { - if err := f.mknode(false, name, perm); err != nil { + if err := f.mknode(false, name, perm, false); err != nil { return nil, err } diff --git a/pkg/persisters/metadata.go b/pkg/persisters/metadata.go index 395dddc..8128105 100644 --- a/pkg/persisters/metadata.go +++ b/pkg/persisters/metadata.go @@ -15,6 +15,7 @@ import ( models "github.com/pojntfx/stfs/internal/db/sqlite/models/metadata" "github.com/pojntfx/stfs/internal/pathext" ipersisters "github.com/pojntfx/stfs/internal/persisters" + "github.com/pojntfx/stfs/pkg/config" migrate "github.com/rubenv/sql-migrate" "github.com/volatiletech/sqlboiler/v4/boil" "github.com/volatiletech/sqlboiler/v4/queries" @@ -203,6 +204,10 @@ func (p *MetadataPersister) GetRootPath(ctx context.Context) (string, error) { models.HeaderColumns.Deleted, ), ).Bind(ctx, p.DB, &root); err != nil { + if strings.Contains(err.Error(), "converting NULL to string is unsupported") { + return "", config.ErrNoRootDirectory + } + return "", err } diff --git a/pkg/recovery/index.go b/pkg/recovery/index.go index acd5f33..4c747bf 100644 --- a/pkg/recovery/index.go +++ b/pkg/recovery/index.go @@ -17,7 +17,6 @@ import ( "github.com/pojntfx/stfs/internal/records" "github.com/pojntfx/stfs/internal/suffix" "github.com/pojntfx/stfs/pkg/config" - "github.com/pojntfx/stfs/pkg/persisters" ) func Index( @@ -237,7 +236,7 @@ func Index( func indexHeader( record, block int64, hdr *tar.Header, - metadataPersister *persisters.MetadataPersister, + metadataPersister config.MetadataPersister, compressionFormat string, encryptionFormat string, onHeader func(hdr *models.Header),