restic: split FileType for backend.FileType

Equality of constants is enforced via internal/repository/filetype.go
using compile time checks.
This commit is contained in:
Michael Eischer
2026-06-14 17:40:28 +02:00
parent 6c509f7ac1
commit 9ab5fc59c2
9 changed files with 90 additions and 56 deletions
+2
View File
@@ -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 {
+1 -1
View File
@@ -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
+17
View File
@@ -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)]
)
+2 -5
View File
@@ -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)
}
+1 -1
View File
@@ -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)
+9 -9
View File
@@ -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
+1 -4
View File
@@ -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{
+57
View File
@@ -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
}
-36
View File
@@ -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