From 9ab5fc59c2ed4883fbbc08179bad3e54af0abf8c Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sun, 14 Jun 2026 17:40:28 +0200 Subject: [PATCH] restic: split FileType for backend.FileType Equality of constants is enforced via internal/repository/filetype.go using compile time checks. --- internal/backend/file.go | 2 ++ internal/repository/checker.go | 2 +- internal/repository/filetype.go | 17 +++++++++ internal/repository/key.go | 7 ++-- internal/repository/raw.go | 2 +- internal/repository/repository.go | 18 +++++----- internal/repository/warmup.go | 5 +-- internal/restic/filetype.go | 57 +++++++++++++++++++++++++++++++ internal/restic/repository.go | 36 ------------------- 9 files changed, 90 insertions(+), 56 deletions(-) create mode 100644 internal/repository/filetype.go create mode 100644 internal/restic/filetype.go diff --git a/internal/backend/file.go b/internal/backend/file.go index 990175f9c..b49ed975b 100644 --- a/internal/backend/file.go +++ b/internal/backend/file.go @@ -7,6 +7,7 @@ import ( ) // FileType is the type of a file in the backend. +// Numeric values must match restic.FileType; enforced in internal/repository/filetype.go. type FileType uint8 // These are the different data types a backend can store. @@ -19,6 +20,7 @@ const ( ConfigFile ) +// Keep in sync with restic.FileType.String(). func (t FileType) String() string { s := "invalid" switch t { diff --git a/internal/repository/checker.go b/internal/repository/checker.go index 9b5824989..9860fb5a4 100644 --- a/internal/repository/checker.go +++ b/internal/repository/checker.go @@ -334,7 +334,7 @@ func checkPack(ctx context.Context, r *Repository, id restic.ID, blobs pack.Blob if err != nil { if r.cache != nil { // ignore error as there's not much we can do here - _ = r.cache.Forget(backend.Handle{Type: restic.PackFile, Name: id.String()}) + _ = r.cache.Forget(backend.Handle{Type: backend.PackFile, Name: id.String()}) } // retry pack verification to detect transient errors diff --git a/internal/repository/filetype.go b/internal/repository/filetype.go new file mode 100644 index 000000000..364ed794d --- /dev/null +++ b/internal/repository/filetype.go @@ -0,0 +1,17 @@ +package repository + +import ( + "github.com/restic/restic/internal/backend" + "github.com/restic/restic/internal/restic" +) + +// Compile-time checks that restic and backend FileType constants match. A constant mismatch +// would be an out-of-bounds access that is detected by the compiler. +var ( + _ = [1]struct{}{}[backend.PackFile-backend.FileType(restic.PackFile)] + _ = [1]struct{}{}[backend.KeyFile-backend.FileType(restic.KeyFile)] + _ = [1]struct{}{}[backend.LockFile-backend.FileType(restic.LockFile)] + _ = [1]struct{}{}[backend.SnapshotFile-backend.FileType(restic.SnapshotFile)] + _ = [1]struct{}{}[backend.IndexFile-backend.FileType(restic.IndexFile)] + _ = [1]struct{}{}[backend.ConfigFile-backend.FileType(restic.ConfigFile)] +) diff --git a/internal/repository/key.go b/internal/repository/key.go index 0f2db3e61..e5d1b0724 100644 --- a/internal/repository/key.go +++ b/internal/repository/key.go @@ -269,10 +269,7 @@ func AddKey(ctx context.Context, s *Repository, password, username, hostname str id := restic.Hash(buf) // store in repository and return - h := backend.Handle{ - Type: restic.KeyFile, - Name: id.String(), - } + h := backend.Handle{Type: backend.KeyFile, Name: id.String()} err = s.be.Save(ctx, h, backend.NewByteReader(buf, s.be.Hasher())) if err != nil { @@ -289,7 +286,7 @@ func RemoveKey(ctx context.Context, repo *Repository, id restic.ID) error { return errors.New("refusing to remove key currently used to access repository") } - h := backend.Handle{Type: restic.KeyFile, Name: id.String()} + h := backend.Handle{Type: backend.KeyFile, Name: id.String()} return repo.be.Remove(ctx, h) } diff --git a/internal/repository/raw.go b/internal/repository/raw.go index c5a4a72b7..fc6726f39 100644 --- a/internal/repository/raw.go +++ b/internal/repository/raw.go @@ -14,7 +14,7 @@ import ( // If the backend returns data that does not match the id, then the buffer is returned // along with an error that is a restic.ErrInvalidData error. func (r *Repository) LoadRaw(ctx context.Context, t restic.FileType, id restic.ID) (buf []byte, err error) { - h := backend.Handle{Type: t, Name: id.String()} + h := backend.Handle{Type: backend.FileType(t), Name: id.String()} buf, err = loadRaw(ctx, r.be, h) diff --git a/internal/repository/repository.go b/internal/repository/repository.go index d8cb82d9d..35f5c78f5 100644 --- a/internal/repository/repository.go +++ b/internal/repository/repository.go @@ -226,7 +226,7 @@ func sortCachedPacksFirst(cache haver, blobs []*pack.PackedBlob) { noncached := make([]*pack.PackedBlob, 0, len(blobs)/2) for _, blob := range blobs { - if cache.Has(backend.Handle{Type: restic.PackFile, Name: blob.PackID().String()}) { + if cache.Has(backend.Handle{Type: backend.PackFile, Name: blob.PackID().String()}) { cached = append(cached, blob) continue } @@ -255,7 +255,7 @@ func (r *Repository) LoadBlob(ctx context.Context, bh restic.BlobHandle, buf []b if err != nil { if r.cache != nil { for _, blob := range blobs { - h := backend.Handle{Type: restic.PackFile, Name: blob.PackID().String(), IsMetadata: blob.Blob.Type.IsMetadata()} + h := backend.Handle{Type: backend.PackFile, Name: blob.PackID().String(), IsMetadata: blob.Blob.Type.IsMetadata()} // ignore errors as there's not much we can do here _ = r.cache.Forget(h) } @@ -271,7 +271,7 @@ func (r *Repository) loadBlob(ctx context.Context, blobs []*pack.PackedBlob, buf for _, blob := range blobs { debug.Log("blob %v found: %v", blob.Handle(), blob) // load blob from pack - h := backend.Handle{Type: restic.PackFile, Name: blob.PackID().String(), IsMetadata: blob.Blob.Type.IsMetadata()} + h := backend.Handle{Type: backend.PackFile, Name: blob.PackID().String(), IsMetadata: blob.Blob.Type.IsMetadata()} switch { case cap(buf) < int(blob.Blob.Length): @@ -514,7 +514,7 @@ func (r *Repository) saveUnpacked(ctx context.Context, t restic.FileType, buf [] } else { id = restic.Hash(ciphertext) } - h := backend.Handle{Type: t, Name: id.String()} + h := backend.Handle{Type: backend.FileType(t), Name: id.String()} err = r.be.Save(ctx, h, backend.NewByteReader(ciphertext, r.be.Hasher())) if err != nil { @@ -558,7 +558,7 @@ func (r *internalRepository) RemoveUnpacked(ctx context.Context, t restic.FileTy } func (r *Repository) removeUnpacked(ctx context.Context, t restic.FileType, id restic.ID) error { - return r.be.Remove(ctx, backend.Handle{Type: t, Name: id.String()}) + return r.be.Remove(ctx, backend.Handle{Type: backend.FileType(t), Name: id.String()}) } func (r *Repository) WithBlobUploader(ctx context.Context, fn func(ctx context.Context, uploader restic.BlobSaverWithAsync) error) error { @@ -894,7 +894,7 @@ func (r *Repository) Init(ctx context.Context, version uint, password string, ch return fmt.Errorf("repository version %v too low", version) } - _, err := r.be.Stat(ctx, backend.Handle{Type: restic.ConfigFile}) + _, err := r.be.Stat(ctx, backend.Handle{Type: backend.ConfigFile}) if err != nil && !r.be.IsNotExist(err) { return err } @@ -956,7 +956,7 @@ func (r *Repository) KeyID() restic.ID { // List runs fn for all files of type t in the repo. func (r *Repository) List(ctx context.Context, t restic.FileType, fn func(restic.ID, int64) error) error { - return r.be.List(ctx, t, func(fi backend.FileInfo) error { + return r.be.List(ctx, backend.FileType(t), func(fi backend.FileInfo) error { id, err := restic.ParseID(fi.Name) if err != nil { debug.Log("unable to parse %v as an ID", fi.Name) @@ -968,7 +968,7 @@ func (r *Repository) List(ctx context.Context, t restic.FileType, fn func(restic // listPack returns blob entries from the pack file header including offsets. func (r *Repository) listPack(ctx context.Context, id restic.ID, size int64) (pack.Blobs, error) { - h := backend.Handle{Type: restic.PackFile, Name: id.String()} + h := backend.Handle{Type: backend.PackFile, Name: id.String()} entries, _, err := pack.List(r.Key(), backend.ReaderAt(ctx, r.be, h), size) if err != nil { @@ -1143,7 +1143,7 @@ func streamPack(ctx context.Context, beLoad backendLoadFn, loadBlobFn loadBlobFn } func streamPackPart(ctx context.Context, beLoad backendLoadFn, loadBlobFn loadBlobFn, dec *zstd.Decoder, key *crypto.Key, packID restic.ID, blobs pack.Blobs, handleBlobFn func(blob restic.BlobHandle, buf []byte, err error) error) error { - h := backend.Handle{Type: restic.PackFile, Name: packID.String(), IsMetadata: blobs[0].Type.IsMetadata()} + h := backend.Handle{Type: backend.PackFile, Name: packID.String(), IsMetadata: blobs[0].Type.IsMetadata()} dataStart := blobs[0].Offset dataEnd := blobs[len(blobs)-1].Offset + blobs[len(blobs)-1].Length diff --git a/internal/repository/warmup.go b/internal/repository/warmup.go index ef78ddaf4..c8abd3a44 100644 --- a/internal/repository/warmup.go +++ b/internal/repository/warmup.go @@ -26,10 +26,7 @@ func (job *warmupJob) Wait(ctx context.Context) error { func (r *Repository) StartWarmup(ctx context.Context, packs restic.IDSet) (restic.WarmupJob, error) { handles := make([]backend.Handle, 0, len(packs)) for pack := range packs { - handles = append( - handles, - backend.Handle{Type: restic.PackFile, Name: pack.String()}, - ) + handles = append(handles, backend.Handle{Type: backend.PackFile, Name: pack.String()}) } handlesWarmingUp, err := r.be.Warmup(ctx, handles) return &warmupJob{ diff --git a/internal/restic/filetype.go b/internal/restic/filetype.go new file mode 100644 index 000000000..fc1013286 --- /dev/null +++ b/internal/restic/filetype.go @@ -0,0 +1,57 @@ +package restic + +// FileType is the type of a file in the repository. +// Numeric values must match backend.FileType; enforced in internal/repository/filetype.go. +type FileType uint8 + +// These are the different data types a backend can store. +const ( + PackFile FileType = 1 + iota + KeyFile + LockFile + SnapshotFile + IndexFile + ConfigFile +) + +// Keep in sync with backend.FileType.String(). +func (t FileType) String() string { + s := "invalid" + switch t { + case PackFile: + // Spelled "data" instead of "pack" for historical reasons. + s = "data" + case KeyFile: + s = "key" + case LockFile: + s = "lock" + case SnapshotFile: + s = "snapshot" + case IndexFile: + s = "index" + case ConfigFile: + s = "config" + } + return s +} + +// WriteableFileType defines the different data types that can be modified via SaveUnpacked or RemoveUnpacked. +type WriteableFileType FileType + +const ( + // WriteableSnapshotFile is the WriteableFileType for snapshots. + WriteableSnapshotFile = WriteableFileType(SnapshotFile) +) + +func (w *WriteableFileType) ToFileType() FileType { + switch *w { + case WriteableSnapshotFile: + return SnapshotFile + default: + panic("invalid WriteableFileType") + } +} + +type FileTypes interface { + FileType | WriteableFileType +} diff --git a/internal/restic/repository.go b/internal/restic/repository.go index 581375848..8cd408202 100644 --- a/internal/restic/repository.go +++ b/internal/restic/repository.go @@ -4,7 +4,6 @@ import ( "context" "iter" - "github.com/restic/restic/internal/backend" "github.com/restic/restic/internal/errors" ) @@ -60,41 +59,6 @@ type Repository interface { StartWarmup(ctx context.Context, packs IDSet) (WarmupJob, error) } -type FileType = backend.FileType - -// These are the different data types a backend can store. Only filetypes contained -// in the `WriteableFileType` subset can be modified via the Repository interface. -// All other filetypes are considered internal datastructures of the Repository. -const ( - PackFile = backend.PackFile - KeyFile = backend.KeyFile - LockFile = backend.LockFile - SnapshotFile = backend.SnapshotFile - IndexFile = backend.IndexFile - ConfigFile = backend.ConfigFile -) - -// WriteableFileType defines the different data types that can be modified via SaveUnpacked or RemoveUnpacked. -type WriteableFileType backend.FileType - -const ( - // WriteableSnapshotFile is the WriteableFileType for snapshots. - WriteableSnapshotFile = WriteableFileType(SnapshotFile) -) - -func (w *WriteableFileType) ToFileType() FileType { - switch *w { - case WriteableSnapshotFile: - return SnapshotFile - default: - panic("invalid WriteableFileType") - } -} - -type FileTypes interface { - FileType | WriteableFileType -} - // LoaderUnpacked allows loading a blob not stored in a pack file type LoaderUnpacked interface { // Connections returns the maximum number of concurrent backend operations