2022-10-15 14:23:39 +00:00
package layout
2017-03-26 19:52:49 +00:00
import (
2020-09-19 20:01:32 +00:00
"context"
2017-04-02 15:25:22 +00:00
"fmt"
"os"
"path/filepath"
"regexp"
2017-07-23 12:21:03 +00:00
2023-10-01 09:40:12 +00:00
"github.com/restic/restic/internal/backend"
2017-07-23 12:21:03 +00:00
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/errors"
2024-03-29 12:51:59 +00:00
"github.com/restic/restic/internal/feature"
2017-07-23 12:21:03 +00:00
"github.com/restic/restic/internal/fs"
2017-07-24 15:42:25 +00:00
"github.com/restic/restic/internal/restic"
2017-03-26 19:52:49 +00:00
)
// Layout computes paths for file name storage.
type Layout interface {
2023-10-01 09:40:12 +00:00
Filename ( backend . Handle ) string
Dirname ( backend . Handle ) string
Basedir ( backend . FileType ) ( dir string , subdirs bool )
2017-03-26 19:52:49 +00:00
Paths ( ) [ ] string
2017-06-07 19:59:41 +00:00
Name ( ) string
2017-03-26 19:52:49 +00:00
}
2017-04-02 15:25:22 +00:00
// Filesystem is the abstraction of a file system used for a backend.
type Filesystem interface {
Join ( ... string ) string
2020-09-19 20:01:32 +00:00
ReadDir ( context . Context , string ) ( [ ] os . FileInfo , error )
2017-04-10 20:16:50 +00:00
IsNotExist ( error ) bool
2017-04-02 15:25:22 +00:00
}
// ensure statically that *LocalFilesystem implements Filesystem.
var _ Filesystem = & LocalFilesystem { }
// LocalFilesystem implements Filesystem in a local path.
type LocalFilesystem struct {
}
// ReadDir returns all entries of a directory.
2023-05-18 17:18:09 +00:00
func ( l * LocalFilesystem ) ReadDir ( _ context . Context , dir string ) ( [ ] os . FileInfo , error ) {
2017-04-02 15:25:22 +00:00
f , err := fs . Open ( dir )
if err != nil {
return nil , err
}
entries , err := f . Readdir ( - 1 )
if err != nil {
2017-04-10 20:16:50 +00:00
return nil , errors . Wrap ( err , "Readdir" )
2017-04-02 15:25:22 +00:00
}
err = f . Close ( )
if err != nil {
2017-04-10 20:16:50 +00:00
return nil , errors . Wrap ( err , "Close" )
2017-04-02 15:25:22 +00:00
}
return entries , nil
}
// Join combines several path components to one.
func ( l * LocalFilesystem ) Join ( paths ... string ) string {
return filepath . Join ( paths ... )
}
2017-04-10 20:16:50 +00:00
// IsNotExist returns true for errors that are caused by not existing files.
func ( l * LocalFilesystem ) IsNotExist ( err error ) bool {
return os . IsNotExist ( err )
}
2017-04-02 15:25:22 +00:00
var backendFilenameLength = len ( restic . ID { } ) * 2
var backendFilename = regexp . MustCompile ( fmt . Sprintf ( "^[a-fA-F0-9]{%d}$" , backendFilenameLength ) )
2020-09-19 20:01:32 +00:00
func hasBackendFile ( ctx context . Context , fs Filesystem , dir string ) ( bool , error ) {
entries , err := fs . ReadDir ( ctx , dir )
2022-10-08 10:37:18 +00:00
if err != nil && fs . IsNotExist ( err ) {
2017-04-02 15:25:22 +00:00
return false , nil
}
if err != nil {
return false , errors . Wrap ( err , "ReadDir" )
}
for _ , e := range entries {
if backendFilename . MatchString ( e . Name ( ) ) {
return true , nil
}
}
return false , nil
}
2017-04-02 17:53:55 +00:00
// ErrLayoutDetectionFailed is returned by DetectLayout() when the layout
// cannot be detected automatically.
var ErrLayoutDetectionFailed = errors . New ( "auto-detecting the filesystem layout failed" )
2024-03-29 12:51:59 +00:00
var ErrLegacyLayoutFound = errors . New ( "detected legacy S3 layout. Use `RESTIC_FEATURES=deprecate-s3-legacy-layout=false restic migrate s3_layout` to migrate your repository" )
2017-04-02 15:25:22 +00:00
// DetectLayout tries to find out which layout is used in a local (or sftp)
2017-04-02 15:57:28 +00:00
// filesystem at the given path. If repo is nil, an instance of LocalFilesystem
// is used.
2020-09-19 20:01:32 +00:00
func DetectLayout ( ctx context . Context , repo Filesystem , dir string ) ( Layout , error ) {
2017-04-10 20:16:50 +00:00
debug . Log ( "detect layout at %v" , dir )
2017-04-02 15:57:28 +00:00
if repo == nil {
repo = & LocalFilesystem { }
}
2017-05-15 20:01:49 +00:00
// key file in the "keys" dir (DefaultLayout)
2023-10-01 09:40:12 +00:00
foundKeysFile , err := hasBackendFile ( ctx , repo , repo . Join ( dir , defaultLayoutPaths [ backend . KeyFile ] ) )
2017-04-02 15:25:22 +00:00
if err != nil {
return nil , err
}
2017-05-15 20:01:49 +00:00
// key file in the "key" dir (S3LegacyLayout)
2023-10-01 09:40:12 +00:00
foundKeyFile , err := hasBackendFile ( ctx , repo , repo . Join ( dir , s3LayoutPaths [ backend . KeyFile ] ) )
2017-04-02 15:25:22 +00:00
if err != nil {
return nil , err
}
2017-05-15 21:36:23 +00:00
if foundKeysFile && ! foundKeyFile {
2017-04-02 17:18:03 +00:00
debug . Log ( "found default layout at %v" , dir )
return & DefaultLayout {
Path : dir ,
Join : repo . Join ,
} , nil
2017-04-02 15:25:22 +00:00
}
2017-05-15 21:36:23 +00:00
if foundKeyFile && ! foundKeysFile {
2024-03-29 12:51:59 +00:00
if feature . Flag . Enabled ( feature . DeprecateS3LegacyLayout ) {
return nil , ErrLegacyLayoutFound
}
2017-04-02 17:18:03 +00:00
debug . Log ( "found s3 layout at %v" , dir )
2017-05-15 19:58:03 +00:00
return & S3LegacyLayout {
2017-04-02 17:18:03 +00:00
Path : dir ,
Join : repo . Join ,
} , nil
2017-04-02 15:25:22 +00:00
}
2017-04-10 20:16:50 +00:00
debug . Log ( "layout detection failed" )
2017-04-02 17:53:55 +00:00
return nil , ErrLayoutDetectionFailed
2017-04-02 15:25:22 +00:00
}
2017-04-02 15:57:28 +00:00
// ParseLayout parses the config string and returns a Layout. When layout is
2017-04-02 17:53:55 +00:00
// the empty string, DetectLayout is used. If that fails, defaultLayout is used.
2020-09-19 20:01:32 +00:00
func ParseLayout ( ctx context . Context , repo Filesystem , layout , defaultLayout , path string ) ( l Layout , err error ) {
2017-04-02 17:18:03 +00:00
debug . Log ( "parse layout string %q for backend at %v" , layout , path )
2017-04-02 15:57:28 +00:00
switch layout {
case "default" :
l = & DefaultLayout {
Path : path ,
Join : repo . Join ,
}
2017-05-15 19:58:03 +00:00
case "s3legacy" :
2024-03-29 12:51:59 +00:00
if feature . Flag . Enabled ( feature . DeprecateS3LegacyLayout ) {
return nil , ErrLegacyLayoutFound
}
2017-05-15 19:58:03 +00:00
l = & S3LegacyLayout {
2017-04-02 15:57:28 +00:00
Path : path ,
Join : repo . Join ,
}
case "" :
2020-09-19 20:01:32 +00:00
l , err = DetectLayout ( ctx , repo , path )
2017-04-02 17:53:55 +00:00
// use the default layout if auto detection failed
2022-06-13 18:35:37 +00:00
if errors . Is ( err , ErrLayoutDetectionFailed ) && defaultLayout != "" {
2017-05-15 21:36:23 +00:00
debug . Log ( "error: %v, use default layout %v" , err , defaultLayout )
2020-09-19 20:01:32 +00:00
return ParseLayout ( ctx , repo , defaultLayout , "" , path )
2017-04-02 17:53:55 +00:00
}
2017-04-10 20:16:50 +00:00
if err != nil {
return nil , err
}
debug . Log ( "layout detected: %v" , l )
2017-04-02 15:57:28 +00:00
default :
2017-05-15 20:01:49 +00:00
return nil , errors . Errorf ( "unknown backend layout string %q, may be one of: default, s3legacy" , layout )
2017-04-02 15:57:28 +00:00
}
return l , nil
}