{"record":{"id":"828c2e6efcf8ee8b","repo":"juicedata/juicefs","slug":"failed-to-read-footer-err-w-read-len-d-expect","errorCode":null,"errorMessage":"failed to read footer: err %w, read len %d, expect len %d","messagePattern":"failed to read footer: err %w, read len (.+?), expect len (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/meta/backup.go","lineNumber":211,"sourceCode":"\t\treturn fmt.Errorf(\"failed to write footer length: err %w, write len %d, expect len 8\", err, n)\n\t}\n\treturn nil\n}\n\nfunc (h *BakFooter) Unmarshal(r io.ReadSeeker) error {\n\tlenSize := int64(unsafe.Sizeof(h.Len))\n\t_, _ = r.Seek(-lenSize, io.SeekEnd)\n\n\tdata := make([]byte, lenSize)\n\tif n, err := r.Read(data); err != nil && n != int(lenSize) {\n\t\treturn fmt.Errorf(\"failed to read footer length: err %w, read len %d, expect len %d\", err, n, lenSize)\n\t}\n\n\th.Len = binary.BigEndian.Uint64(data)\n\t_, _ = r.Seek(-int64(h.Len)-lenSize, io.SeekEnd)\n\tdata = make([]byte, h.Len)\n\tif n, err := r.Read(data); err != nil && n != int(h.Len) {\n\t\treturn fmt.Errorf(\"failed to read footer: err %w, read len %d, expect len %d\", err, n, h.Len)\n\t}\n\n\th.Msg = &pb.Footer{}\n\tif err := proto.Unmarshal(data, h.Msg); err != nil {\n\t\treturn fmt.Errorf(\"failed to unmarshal footer: %w\", err)\n\t}\n\treturn nil\n}\n\ntype BakSegment struct {\n\ttyp uint32\n\tlen uint64\n\tval proto.Message\n}\n\nfunc (s *BakSegment) Name() string {\n\tif name, ok := SegType2Name[int(s.typ)]; ok {\n\t\treturn name","sourceCodeStart":193,"sourceCodeEnd":229,"githubUrl":"https://github.com/juicedata/juicefs/blob/c9a67b23e8e08ec23ec331aa6f1675e2319e921c/pkg/meta/backup.go#L193-L229","documentation":"BakFooter.Unmarshal reads the footer payload of a JuiceFS metadata backup file. After seeking back by the 8-byte big-endian footer length stored at EOF, it reads h.Len bytes containing the protobuf-encoded Footer message. This error is returned when the io.ReadSeeker fails to supply those bytes: the read errored and the number of bytes read does not match h.Len.","triggerScenarios":"Calling ReadFooter (via BakFormat.ReadFooter) on a file whose last 8 bytes encode a footer length larger than the actual remaining data, or on a truncated/corrupted backup file, or on a reader that returns an I/O error mid-read (e.g. network storage disconnect, file deleted/rotated while open).","commonSituations":"Restoring from a metadata backup that was truncated by a failed download or interrupted rsync; reading a backup still being written by another `juicefs dump` process; a corrupt footer-length prefix (bit rot, wrong file) making h.Len impossibly large; an S3/NFS-backed seekable reader hitting an I/O error.","solutions":["Re-obtain the backup file from a known-good source (re-dump metadata with `juicefs dump`) — a footer read failure almost always means the file is truncated or corrupt.","Verify the file size matches the expected size (e.g. compare against the source checksum) to confirm truncation before spending time on recovery.","Ensure the file is not still being written: wait for `juicefs dump` to complete or check for a completed/part marker before reading the footer.","Check the underlying storage health/permissions if the wrapped err indicates I/O failure (disk, NFS, object storage credentials).","Confirm the reader passed to ReadFooter implements io.ReadSeeker correctly and its current position is at EOF (offsets managed outside this function can misalign the seek)."],"exampleFix":"// before: reading a possibly-incomplete backup\nf, _ := os.Open(\"meta.backup\")\nfooter, err := format.ReadFooter(f)\n\n// after: verify size/checksum before reading\nf, _ := os.Open(\"meta.backup\")\nfi, _ := f.Stat()\nif fi.Size() < 12 {\n    return fmt.Errorf(\"backup too small to contain a footer: %d bytes\", fi.Size())\n}\nif got := sha256sum(\"meta.backup\"); got != expectedChecksum {\n    return fmt.Errorf(\"backup corrupt: checksum mismatch\")\n}\nfooter, err := format.ReadFooter(f)","handlingStrategy":"try-catch","validationCode":"fi, err := os.Stat(backupPath)\nif err != nil { return err }\nif fi.Size() < 12 { return fmt.Errorf(\"backup too small for a footer: %d bytes\", fi.Size()) }\n// optionally: verify checksum before loading\nif !checksumMatches(backupPath, expected) { return errors.New(\"backup corrupt; re-dump\") }","typeGuard":"func canReadFooter(f io.ReadSeeker) bool {\n    fi, ok := f.(interface{ Stat() (os.FileInfo, error) })\n    if !ok { return true } // non-file readers: cannot pre-check\n    info, err := fi.Stat()\n    return err == nil && info.Size() >= 12\n}","tryCatchPattern":"footer, err := format.ReadFooter(f)\nif err != nil {\n    if strings.Contains(err.Error(), \"failed to read footer\") {\n        return fmt.Errorf(\"backup file truncated or unreadable, re-run juicefs dump: %w\", err)\n    }\n    return err\n}","preventionTips":["Always verify backup file size/checksum before restoring.","Only read backups whose producing dump process exited successfully.","Store backups with a completion marker or .part suffix until dump finishes.","Check disk/storage health when backups live on network mounts."],"tags":["protobuf","io","backup","corrupt-file","truncated-file"],"backgroundTag":"file-read-failed","analyzedSha":"c9a67b23e8e08ec23ec331aa6f1675e2319e921c","analyzedAt":"2026-09-06T17:55:48.476Z","contentChangedAt":"2026-09-06T17:55:48.476Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}