{"record":{"id":"c440662e69408818","repo":"vitessio/vitess","slug":"readfile-cannot-be-called-on-read-write-backup","errorCode":null,"errorMessage":"ReadFile cannot be called on read-write backup","messagePattern":"ReadFile cannot be called on read-write backup","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/mysqlctl/filebackupstorage/file.go","lineNumber":134,"sourceCode":"func (fbh *FileBackupHandle) EndBackup(ctx context.Context) error {\n\tif fbh.readOnly {\n\t\treturn errors.New(\"EndBackup cannot be called on read-only backup\")\n\t}\n\treturn nil\n}\n\n// AbortBackup is part of the BackupHandle interface\nfunc (fbh *FileBackupHandle) AbortBackup(ctx context.Context) error {\n\tif fbh.readOnly {\n\t\treturn errors.New(\"AbortBackup cannot be called on read-only backup\")\n\t}\n\treturn fbh.fbs.RemoveBackup(ctx, fbh.dir, fbh.name)\n}\n\n// ReadFile is part of the BackupHandle interface\nfunc (fbh *FileBackupHandle) ReadFile(ctx context.Context, filename string) (io.ReadCloser, error) {\n\tif !fbh.readOnly {\n\t\treturn nil, errors.New(\"ReadFile cannot be called on read-write backup\")\n\t}\n\tp, err := fileutil.SafePathJoin(FileBackupStorageRoot, fbh.dir, fbh.name, filename)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tf, err := os.Open(p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tstat := fbh.fbs.params.Stats.Scope(stats.Operation(\"File:Read\"))\n\treturn ioutil.NewMeteredReadCloser(f, stat.TimedIncrementBytes), nil\n}\n\n// FileBackupStorage implements BackupStorage for local file system.\ntype FileBackupStorage struct {\n\tparams backupstorage.Params\n}\n","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/mysqlctl/filebackupstorage/file.go#L116-L152","documentation":"FileBackupHandle.ReadFile refuses to operate on a backup handle that was opened in read-write mode. Read streaming from a backup is reserved for read-only handles; calling ReadFile on a read-write handle is treated as a programming error by the caller. The guard exists because read-write handles are meant for writing files during backup creation, not reading them back.","triggerScenarios":"Calling ReadFile on a FileBackupHandle obtained without the readOnly option (e.g. via StartBackup or AddBackup with readOnly=false), while a read-only open would have succeeded.","commonSituations":"Tooling that reuses one handle for both reading and writing backup files; code that forgets to request a read-only handle when scanning existing backup contents; refactors that changed a handle's mode but not its call sites.","solutions":["Open the backup handle in read-only mode before calling ReadFile","If the goal is to read an existing backup, use the read-only open path (e.g. ListBackups + read-only handle) instead of the write path","Restructure the code to use separate read-only and read-write handles for read and write phases"],"exampleFix":"// before\nbh := fbs.StartBackup(ctx, dir, name)\nr, err := bh.ReadFile(ctx, \"file\") // error: read-write handle\n// after\nbh := fbs.AddBackup(ctx, dir, name, true /* readOnly */)\nr, err := bh.ReadFile(ctx, \"file\")","handlingStrategy":"validation","validationCode":"if !bh.readOnly {\n    return nil, errors.New(\"need a read-only handle to call ReadFile\")\n}\nr, err := bh.ReadFile(ctx, filename)","typeGuard":"func isReadOnlyFileHandle(bh mysqlctl.BackupHandle) bool {\n    fbh, ok := bh.(*filebackupstorage.FileBackupHandle)\n    return ok && fbh.ReadOnly()\n}","tryCatchPattern":"r, err := bh.ReadFile(ctx, filename)\nif err != nil {\n    if strings.Contains(err.Error(), \"ReadFile cannot be called on read-write backup\") {\n        // re-open read-only and retry\n    }\n    return err\n}","preventionTips":["Always open handles read-only for any read/restore path","Keep read and write phases on separate handles","Audit call sites that mix AddFile/ReadFile on the same handle"],"tags":["go","mysqlctl","backup-storage","api-misuse"],"backgroundTag":"backup-handle-readonly-violation","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}