1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package wal
16
17 import (
18 "errors"
19 "io"
20 "os"
21 "path/filepath"
22 "time"
23
24 "go.etcd.io/etcd/client/pkg/v3/fileutil"
25 "go.etcd.io/etcd/server/v3/wal/walpb"
26 "go.uber.org/zap"
27 )
28
29
30
31 func Repair(lg *zap.Logger, dirpath string) bool {
32 if lg == nil {
33 lg = zap.NewNop()
34 }
35 f, err := openLast(lg, dirpath)
36 if err != nil {
37 return false
38 }
39 defer f.Close()
40
41 lg.Info("repairing", zap.String("path", f.Name()))
42
43 rec := &walpb.Record{}
44 decoder := newDecoder(fileutil.NewFileReader(f.File))
45 for {
46 lastOffset := decoder.lastOffset()
47 err := decoder.decode(rec)
48 switch {
49 case err == nil:
50
51 switch rec.Type {
52 case crcType:
53 crc := decoder.crc.Sum32()
54
55
56 if crc != 0 && rec.Validate(crc) != nil {
57 return false
58 }
59 decoder.updateCRC(rec.Crc)
60 }
61 continue
62
63 case errors.Is(err, io.EOF):
64 lg.Info("repaired", zap.String("path", f.Name()), zap.Error(io.EOF))
65 return true
66
67 case errors.Is(err, io.ErrUnexpectedEOF):
68 bf, bferr := os.Create(f.Name() + ".broken")
69 if bferr != nil {
70 lg.Warn("failed to create backup file", zap.String("path", f.Name()+".broken"), zap.Error(bferr))
71 return false
72 }
73 defer bf.Close()
74
75 if _, err = f.Seek(0, io.SeekStart); err != nil {
76 lg.Warn("failed to read file", zap.String("path", f.Name()), zap.Error(err))
77 return false
78 }
79
80 if _, err = io.Copy(bf, f); err != nil {
81 lg.Warn("failed to copy", zap.String("from", f.Name()+".broken"), zap.String("to", f.Name()), zap.Error(err))
82 return false
83 }
84
85 if err = f.Truncate(lastOffset); err != nil {
86 lg.Warn("failed to truncate", zap.String("path", f.Name()), zap.Error(err))
87 return false
88 }
89
90 start := time.Now()
91 if err = fileutil.Fsync(f.File); err != nil {
92 lg.Warn("failed to fsync", zap.String("path", f.Name()), zap.Error(err))
93 return false
94 }
95 walFsyncSec.Observe(time.Since(start).Seconds())
96
97 lg.Info("repaired", zap.String("path", f.Name()), zap.Error(io.ErrUnexpectedEOF))
98 return true
99
100 default:
101 lg.Warn("failed to repair", zap.String("path", f.Name()), zap.Error(err))
102 return false
103 }
104 }
105 }
106
107
108 func openLast(lg *zap.Logger, dirpath string) (*fileutil.LockedFile, error) {
109 names, err := readWALNames(lg, dirpath)
110 if err != nil {
111 return nil, err
112 }
113 last := filepath.Join(dirpath, names[len(names)-1])
114 return fileutil.LockFile(last, os.O_RDWR, fileutil.PrivateFileMode)
115 }
116
View as plain text