迁移 CLI 不再遮蔽 BeginMigration 的错误,拒绝启动时正确返回失败。规范节点批准错误文案和归档 HTTP 状态常量;tar.Reader 已将旧普通文件类型归一化,无需引用已弃用常量。 验证:CI 固定版本 staticcheck 全量通过;cmd/felis、archivetransfer、backup、distributed 的 go test -race 通过。
70 lines
1.4 KiB
Go
70 lines
1.4 KiB
Go
package backup
|
|
|
|
import (
|
|
"archive/tar"
|
|
"compress/gzip"
|
|
"context"
|
|
"crypto/sha256"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
)
|
|
|
|
// VerifyRestored reads target files back and compares them with every regular archive entry.
|
|
// OpenRoot keeps a malicious path or existing symlink inside the world volume.
|
|
func VerifyRestored(ctx context.Context, ref, world string) error {
|
|
root, err := os.OpenRoot(world)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer root.Close()
|
|
f, err := os.Open(ref)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
gz, err := gzip.NewReader(f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer gz.Close()
|
|
tr := tar.NewReader(gz)
|
|
for {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
h, err := tr.Next()
|
|
if errors.Is(err, io.EOF) {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if h.Typeflag != tar.TypeReg {
|
|
continue
|
|
}
|
|
dst, err := root.Open(h.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
st, err := dst.Stat()
|
|
if err != nil || !st.Mode().IsRegular() || st.Size() != h.Size {
|
|
dst.Close()
|
|
return fmt.Errorf("restored entry %q has the wrong type or size", h.Name)
|
|
}
|
|
sourceHash, targetHash := sha256.New(), sha256.New()
|
|
_, err = io.Copy(sourceHash, tr)
|
|
if err == nil {
|
|
_, err = io.Copy(targetHash, dst)
|
|
}
|
|
dst.Close()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if string(sourceHash.Sum(nil)) != string(targetHash.Sum(nil)) {
|
|
return fmt.Errorf("restored entry %q failed SHA-256 read-back", h.Name)
|
|
}
|
|
}
|
|
}
|