| // Copyright 2015 The etcd Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package wal |
| |
| import ( |
| "errors" |
| "fmt" |
| "strings" |
| |
| "go.etcd.io/etcd/pkg/fileutil" |
| |
| "go.uber.org/zap" |
| ) |
| |
| var errBadWALName = errors.New("bad wal name") |
| |
| // Exist returns true if there are any files in a given directory. |
| func Exist(dir string) bool { |
| names, err := fileutil.ReadDir(dir, fileutil.WithExt(".wal")) |
| if err != nil { |
| return false |
| } |
| return len(names) != 0 |
| } |
| |
| // searchIndex returns the last array index of names whose raft index section is |
| // equal to or smaller than the given index. |
| // The given names MUST be sorted. |
| func searchIndex(lg *zap.Logger, names []string, index uint64) (int, bool) { |
| for i := len(names) - 1; i >= 0; i-- { |
| name := names[i] |
| _, curIndex, err := parseWALName(name) |
| if err != nil { |
| if lg != nil { |
| lg.Panic("failed to parse WAL file name", zap.String("path", name), zap.Error(err)) |
| } else { |
| plog.Panicf("parse correct name should never fail: %v", err) |
| } |
| } |
| if index >= curIndex { |
| return i, true |
| } |
| } |
| return -1, false |
| } |
| |
| // names should have been sorted based on sequence number. |
| // isValidSeq checks whether seq increases continuously. |
| func isValidSeq(lg *zap.Logger, names []string) bool { |
| var lastSeq uint64 |
| for _, name := range names { |
| curSeq, _, err := parseWALName(name) |
| if err != nil { |
| if lg != nil { |
| lg.Panic("failed to parse WAL file name", zap.String("path", name), zap.Error(err)) |
| } else { |
| plog.Panicf("parse correct name should never fail: %v", err) |
| } |
| } |
| if lastSeq != 0 && lastSeq != curSeq-1 { |
| return false |
| } |
| lastSeq = curSeq |
| } |
| return true |
| } |
| |
| func readWALNames(lg *zap.Logger, dirpath string) ([]string, error) { |
| names, err := fileutil.ReadDir(dirpath) |
| if err != nil { |
| return nil, err |
| } |
| wnames := checkWalNames(lg, names) |
| if len(wnames) == 0 { |
| return nil, ErrFileNotFound |
| } |
| return wnames, nil |
| } |
| |
| func checkWalNames(lg *zap.Logger, names []string) []string { |
| wnames := make([]string, 0) |
| for _, name := range names { |
| if _, _, err := parseWALName(name); err != nil { |
| // don't complain about left over tmp files |
| if !strings.HasSuffix(name, ".tmp") { |
| if lg != nil { |
| lg.Warn( |
| "ignored file in WAL directory", |
| zap.String("path", name), |
| ) |
| } else { |
| plog.Warningf("ignored file %v in wal", name) |
| } |
| } |
| continue |
| } |
| wnames = append(wnames, name) |
| } |
| return wnames |
| } |
| |
| func parseWALName(str string) (seq, index uint64, err error) { |
| if !strings.HasSuffix(str, ".wal") { |
| return 0, 0, errBadWALName |
| } |
| _, err = fmt.Sscanf(str, "%016x-%016x.wal", &seq, &index) |
| return seq, index, err |
| } |
| |
| func walName(seq, index uint64) string { |
| return fmt.Sprintf("%016x-%016x.wal", seq, index) |
| } |