// SaveDBFrom saves snapshot of the database from the given reader. It // guarantees the save operation is atomic. func (s *Snapshotter) SaveDBFrom(r io.Reader, id uint64) error { f, err := ioutil.TempFile(s.dir, "tmp") if err != nil { return err } var n int64 n, err = io.Copy(f, r) if err == nil { err = fileutil.Fsync(f) } f.Close() if err != nil { os.Remove(f.Name()) return err } fn := path.Join(s.dir, fmt.Sprintf("%016x.snap.db", id)) if fileutil.Exist(fn) { os.Remove(f.Name()) return nil } err = os.Rename(f.Name(), fn) if err != nil { os.Remove(f.Name()) return err } plog.Infof("saved database snapshot to disk [total bytes: %d]", n) return nil }
func snapshotSaveCommandFunc(cmd *cobra.Command, args []string) { if len(args) != 1 { err := fmt.Errorf("snapshot save expects one argument") ExitWithError(ExitBadArgs, err) } path := args[0] partpath := path + ".part" f, err := os.Create(partpath) defer f.Close() if err != nil { exiterr := fmt.Errorf("could not open %s (%v)", partpath, err) ExitWithError(ExitBadArgs, exiterr) } c := mustClientFromCmd(cmd) r, serr := c.Snapshot(context.TODO()) if serr != nil { os.RemoveAll(partpath) ExitWithError(ExitInterrupted, serr) } if _, rerr := io.Copy(f, r); rerr != nil { os.RemoveAll(partpath) ExitWithError(ExitInterrupted, rerr) } fileutil.Fsync(f) if rerr := os.Rename(partpath, path); rerr != nil { exiterr := fmt.Errorf("could not rename %s to %s (%v)", partpath, path, rerr) ExitWithError(ExitIO, exiterr) } fmt.Printf("Snapshot saved at %s\n", path) }
// WriteAndSyncFile behaves just like ioutil.WriteFile in the standard library, // but calls Sync before closing the file. WriteAndSyncFile guarantees the data // is synced if there is no error returned. func WriteAndSyncFile(filename string, data []byte, perm os.FileMode) error { f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) if err != nil { return err } n, err := f.Write(data) if err == nil && n < len(data) { err = io.ErrShortWrite } if err == nil { err = fileutil.Fsync(f) } if err1 := f.Close(); err == nil { err = err1 } return err }
// Create creates a WAL ready for appending records. The given metadata is // recorded at the head of each WAL file, and can be retrieved with ReadAll. func Create(dirpath string, metadata []byte) (*WAL, error) { if Exist(dirpath) { return nil, os.ErrExist } // keep temporary wal directory so WAL initialization appears atomic tmpdirpath := path.Clean(dirpath) + ".tmp" if fileutil.Exist(tmpdirpath) { if err := os.RemoveAll(tmpdirpath); err != nil { return nil, err } } if err := fileutil.CreateDirAll(tmpdirpath); err != nil { return nil, err } p := path.Join(tmpdirpath, walName(0, 0)) f, err := fileutil.LockFile(p, os.O_WRONLY|os.O_CREATE, fileutil.PrivateFileMode) if err != nil { return nil, err } if _, err = f.Seek(0, os.SEEK_END); err != nil { return nil, err } if err = fileutil.Preallocate(f.File, SegmentSizeBytes, true); err != nil { return nil, err } w := &WAL{ dir: dirpath, metadata: metadata, encoder: newEncoder(f, 0), } w.locks = append(w.locks, f) if err = w.saveCrc(0); err != nil { return nil, err } if err = w.encoder.encode(&walpb.Record{Type: metadataType, Data: metadata}); err != nil { return nil, err } if err = w.SaveSnapshot(walpb.Snapshot{}); err != nil { return nil, err } if w, err = w.renameWal(tmpdirpath); err != nil { return nil, err } // directory was renamed; sync parent dir to persist rename pdir, perr := fileutil.OpenDir(path.Dir(w.dir)) if perr != nil { return nil, perr } if perr = fileutil.Fsync(pdir); perr != nil { return nil, perr } if perr = pdir.Close(); err != nil { return nil, perr } return w, nil }
// cut closes current file written and creates a new one ready to append. // cut first creates a temp wal file and writes necessary headers into it. // Then cut atomically rename temp wal file to a wal file. func (w *WAL) cut() error { // close old wal file; truncate to avoid wasting space if an early cut off, serr := w.tail().Seek(0, os.SEEK_CUR) if serr != nil { return serr } if err := w.tail().Truncate(off); err != nil { return err } if err := w.sync(); err != nil { return err } fpath := path.Join(w.dir, walName(w.seq()+1, w.enti+1)) // create a temp wal file with name sequence + 1, or truncate the existing one newTail, err := w.fp.Open() if err != nil { return err } // update writer and save the previous crc w.locks = append(w.locks, newTail) prevCrc := w.encoder.crc.Sum32() w.encoder = newEncoder(w.tail(), prevCrc) if err = w.saveCrc(prevCrc); err != nil { return err } if err = w.encoder.encode(&walpb.Record{Type: metadataType, Data: w.metadata}); err != nil { return err } if err = w.saveState(&w.state); err != nil { return err } // atomically move temp wal file to wal file if err = w.sync(); err != nil { return err } off, err = w.tail().Seek(0, os.SEEK_CUR) if err != nil { return err } if err = os.Rename(newTail.Name(), fpath); err != nil { return err } if err = fileutil.Fsync(w.dirFile); err != nil { return err } newTail.Close() if newTail, err = fileutil.LockFile(fpath, os.O_WRONLY, fileutil.PrivateFileMode); err != nil { return err } if _, err = newTail.Seek(off, os.SEEK_SET); err != nil { return err } w.locks[len(w.locks)-1] = newTail prevCrc = w.encoder.crc.Sum32() w.encoder = newEncoder(w.tail(), prevCrc) plog.Infof("segmented wal file %v is created", fpath) return nil }
// Repair tries to repair ErrUnexpectedEOF in the // last wal file by truncating. func Repair(dirpath string) bool { f, err := openLast(dirpath) if err != nil { return false } defer f.Close() rec := &walpb.Record{} decoder := newDecoder(f) for { lastOffset := decoder.lastOffset() err := decoder.decode(rec) switch err { case nil: // update crc of the decoder when necessary switch rec.Type { case crcType: crc := decoder.crc.Sum32() // current crc of decoder must match the crc of the record. // do no need to match 0 crc, since the decoder is a new one at this case. if crc != 0 && rec.Validate(crc) != nil { return false } decoder.updateCRC(rec.Crc) } continue case io.EOF: return true case io.ErrUnexpectedEOF: plog.Noticef("repairing %v", f.Name()) bf, bferr := os.Create(f.Name() + ".broken") if bferr != nil { plog.Errorf("could not repair %v, failed to create backup file", f.Name()) return false } defer bf.Close() if _, err = f.Seek(0, os.SEEK_SET); err != nil { plog.Errorf("could not repair %v, failed to read file", f.Name()) return false } if _, err = io.Copy(bf, f); err != nil { plog.Errorf("could not repair %v, failed to copy file", f.Name()) return false } if err = f.Truncate(int64(lastOffset)); err != nil { plog.Errorf("could not repair %v, failed to truncate file", f.Name()) return false } if err = fileutil.Fsync(f.File); err != nil { plog.Errorf("could not repair %v, failed to sync file", f.Name()) return false } return true default: plog.Errorf("could not repair error (%v)", err) return false } } }