func (fs *FileSystem) fsckDups(options *backend.FsckOptions) error {
	files, err := ioutil.ReadDir(fs.root)
	if err != nil {
		return err
	}
	digests := make(map[string]int64)
	for _, fi := range files {
		if !fi.IsDir() {
			return fmt.Errorf("Unexpected file %v",
				filepath.Join(fs.root, fi.Name()))
		}
		if fi.Name() == globalDBDir {
			continue
		}
		t, err := time.Parse(fStr, fi.Name())
		if err != nil {
			return fmt.Errorf("invalid timestamp: %v", fi.Name())
		}
		if options.Verbose || options.PrintHashes {
			fmt.Printf("--- Checking: %v (%v)\n", fi.Name(),
				t.Unix())
		}
		err = fs.fsckDup(options, t.Unix(), digests)
		if err != nil {
			return err
		}
		if options.Verbose || options.PrintHashes {
			fmt.Printf("=== Verified: %v (%v)\n", fi.Name(),
				t.Unix())
		}
	}
	return nil
}
fsckDups checks for duplicate digests in all timestamp containers.
package filesystem
import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"time"
	"github.com/davecgh/go-spew/spew"
	"github.com/decred/dcrd/txscript/v3"
	"github.com/decred/dcrdata/api/types/v5"
	"github.com/decred/dcrtime/dcrtimed/backend"
	"github.com/decred/dcrtime/merkle"
	"github.com/syndtr/goleveldb/leveldb"
)
const (
	FilesystemActionVersion = 1  
	FilesystemActionHeader          = "header"
	FilesystemActionDeleteTimestamp = "deletetimestamp"
	FilesystemActionDeleteDigest    = "deletedigest"
	FilesystemActionDeleteDuplicate = "deleteduplicate"
)
type FilesystemAction struct {
	Version   uint64 `json:"version"`    
	Timestamp int64  `json:"timestamp"`  
	Action    string `json:"action"`     
}
type FilesystemHeader struct {
	Version uint64 `json:"version"`  
	Start   int64  `json:"start"`    
	DryRun  bool   `json:"dryrun"`   
}
type FilesystemDeleteTimestamp struct {
	Version   uint64 `json:"version"`    
	Timestamp int64  `json:"timestamp"`  
	Directory string `json:"directory"`  
}
type FilesystemDeleteDigest struct {
	Version         uint64 `json:"version"`          
	Timestamp       int64  `json:"timestamp"`        
	GlobalTimestamp int64  `json:"globaltimestamp"`  
	Digest          string `json:"digest"`           
}
type FilesystemDeleteDuplicate struct {
	Version            uint64 `json:"version"`             
	Digest             string `json:"digest"`              
	Found              int64  `json:"found"`               
	FoundDirectory     string `json:"founddirectory"`      
	Duplicate          int64  `json:"duplicate"`           
	DuplicateDirectory string `json:"duplicatedirectory"`  
}
func validJournalAction(action string) bool {
	switch action {
	case FilesystemActionHeader:
	case FilesystemActionDeleteTimestamp:
	case FilesystemActionDeleteDigest:
	case FilesystemActionDeleteDuplicate:
	default:
		return false
	}
	return true
}
func journal(filename, action string, payload interface{}) error {
	if filename == "" {
		return nil
	}
	if !validJournalAction(action) {
		return fmt.Errorf("invalid journal action: %v", action)
	}
	f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0640)
	if err != nil {
		return err
	}
	defer f.Close()
	e := json.NewEncoder(f)
	rt := FilesystemAction{
		Version:   FilesystemActionVersion,
		Timestamp: time.Now().Unix(),
		Action:    action,
	}
	err = e.Encode(rt)
	if err != nil {
		return err
	}
	err = e.Encode(payload)
	if err != nil {
		return err
	}
	fmt.Fprintf(f, "\n")
	return err
}
func (fs *FileSystem) fsckTimestamp(options *backend.FsckOptions, ts int64, empties map[int64]struct{}) error {
	db, err := fs.openRead(ts)
	if err != nil {
		return err
	}
	defer db.Close()
	digests := make(map[string]int64)
	var flushRecord *backend.FlushRecord
	i := db.NewIterator(nil, nil)
	defer i.Release()
	for i.Next() {
		key := i.Key()
		if string(key) == flushedKey {
			flushRecord, err = DecodeFlushRecord(i.Value())
			if err != nil {
				return err
			}
			continue
		}
		k := hex.EncodeToString(key)
		if _, ok := digests[k]; ok {
			return fmt.Errorf("    *** ERROR duplicate key: %v", k)
		}
		digests[k] = int64(binary.LittleEndian.Uint64(i.Value()))
	}
	if len(digests) == 0 && flushRecord == nil {
		if _, ok := empties[ts]; ok {
			return fmt.Errorf("   *** ERROR duplicate timestamp "+
				"in empties: %v", ts)
		}
		empties[ts] = struct{}{}
		fmt.Printf("   *** ERROR empty timestamp: %v (%v)\n",
			ts2dirname(ts), ts)
		return nil
	}
	if flushRecord != nil {
		if options.Verbose {
			dumpFlushRecord(os.Stdout, flushRecord)
		}
		if len(digests) != len(flushRecord.Hashes) {
			return fmt.Errorf("   *** ERROR mismatched len: %v %v",
				len(digests), len(flushRecord.Hashes))
		}
		for _, v := range flushRecord.Hashes {
			hash := hex.EncodeToString(v[:])
			if _, ok := digests[hash]; !ok {
				return fmt.Errorf("   *** ERROR unknown hash: %v",
					hash)
			}
			if options.PrintHashes {
				fmt.Printf("Hash           : %v\n", hash)
			}
		}
		root := merkle.Root(flushRecord.Hashes)
		if !bytes.Equal(root[:], flushRecord.Root[:]) {
			return fmt.Errorf("   *** ERROR mismatched merkle "+
				"root: %x %x", *root, flushRecord.Root)
		}
		for _, v := range flushRecord.Hashes {
			gdbts, err := fs.db.Get(v[:], nil)
			if err != nil {
				return fmt.Errorf("   *** ERROR not found in "+
					"db: %x", v)
			}
			dbts := int64(binary.LittleEndian.Uint64(gdbts))
			if dbts != ts {
				return fmt.Errorf("   *** ERROR timestamp "+
					"mismatch: %v %v", dbts, ts)
			}
		}
		u := options.URL + flushRecord.Tx.String() + "/out"
		r, err := http.Get(u)
		if err != nil {
			return fmt.Errorf("   *** ERROR HTTP Get: %v", err)
		}
		defer r.Body.Close()
		if r.StatusCode != http.StatusOK {
			body, err := ioutil.ReadAll(r.Body)
			if err != nil {
				return fmt.Errorf("   *** ERROR invalid "+
					"body: %v %v", r.StatusCode, body)
			}
			return fmt.Errorf("   *** ERROR invalid dcrdata "+
				"answer: %v %s", r.StatusCode, body)
		}
		var txOuts []types.TxOut
		d := json.NewDecoder(r.Body)
		if err := d.Decode(&txOuts); err != nil {
			return err
		}
		var done bool
		for _, v := range txOuts {
			if !types.IsNullDataScript(v.ScriptPubKeyDecoded.Type) {
				continue
			}
			script, err := hex.DecodeString(v.ScriptPubKeyDecoded.Hex)
			if err != nil {
				return fmt.Errorf("   *** ERROR invalid "+
					"dcrdata script: %v", err)
			}
			data, err := txscript.PushedData(script)
			if err != nil {
				return fmt.Errorf("   *** ERROR invalid "+
					"script: %v", err)
			}
			if !bytes.Equal(data[0], flushRecord.Root[:]) {
				continue
			}
			done = true
			break
		}
		if !done {
			return fmt.Errorf("   *** ERROR merkle root not "+
				"found: tx %v merkle %x", flushRecord.Tx,
				flushRecord.Root)
		}
		return nil
	}
	for k := range digests {
		key, err := hex.DecodeString(k)
		if err != nil {
			return fmt.Errorf("   *** ERROR internal error on "+
				"key: %v", k)
		}
		if options.PrintHashes {
			fmt.Printf("     Unflushed    : %v\n", k)
		}
		gdbts, err := fs.db.Get(key, nil)
		if err != nil {
			if err == leveldb.ErrNotFound {
				continue
			}
			return fmt.Errorf("   *** ERROR found in db: %v %v",
				k, err)
		}
		dbts := int64(binary.LittleEndian.Uint64(gdbts))
		if dbts != ts {
			fmt.Printf("   *** ERROR timestamp mismatch: %v %v %v\n",
				k, dbts, ts)
			err = journal(options.File, FilesystemActionDeleteDigest,
				FilesystemDeleteDigest{
					Version:         FilesystemActionVersion,
					Timestamp:       ts,
					GlobalTimestamp: dbts,
					Digest:          k,
				})
			if err != nil {
				return fmt.Errorf("   *** ERROR journal: %v",
					err)
			}
			if !options.Fix {
				continue
			}
			fmt.Printf("   *** FIXING timestamp mismatch: delete "+
				"%v %v\n", k, ts)
			err = db.Delete(key, nil)
			if err != nil {
				return fmt.Errorf("   *** ERROR timestamp " +
					"mismatch: delete")
			}
			delete(digests, k)
		}
	}
	if len(digests) == 0 {
		empties[ts] = struct{}{}
	}
	return nil
}
func (fs *FileSystem) fsckTimestamps(options *backend.FsckOptions, empties map[int64]struct{}) error {
	files, err := ioutil.ReadDir(fs.root)
	if err != nil {
		return err
	}
	for _, fi := range files {
		if !fi.IsDir() {
			return fmt.Errorf("Unexpected file %v",
				filepath.Join(fs.root, fi.Name()))
		}
		if fi.Name() == globalDBDir {
			continue
		}
		t, err := time.Parse(fStr, fi.Name())
		if err != nil {
			return fmt.Errorf("invalid timestamp: %v", fi.Name())
		}
		if options.Verbose || options.PrintHashes {
			fmt.Printf("--- Checking: %v (%v)\n", fi.Name(),
				t.Unix())
		}
		err = fs.fsckTimestamp(options, t.Unix(), empties)
		if err != nil {
			return err
		}
		if options.Verbose || options.PrintHashes {
			fmt.Printf("=== Verified: %v (%v)\n", fi.Name(),
				t.Unix())
		}
	}
	return nil
}
func (fs *FileSystem) fsckExists(ts int64, hash []byte) (bool, error) {
	db, err := fs.openRead(ts)
	if err != nil {
		return false, err
	}
	defer db.Close()
	return db.Has(hash, nil)
}
func (fs *FileSystem) fsckGlobal(options *backend.FsckOptions, empties map[int64]struct{}) error {
	i := fs.db.NewIterator(nil, nil)
	defer i.Release()
	for i.Next() {
		key := hex.EncodeToString(i.Key())
		value := int64(binary.LittleEndian.Uint64(i.Value()))
		if options.PrintHashes {
			fmt.Printf("Flushed        : %v\n", key)
		}
		found, err := fs.fsckExists(value, i.Key())
		if err != nil {
			return err
		}
		if !found {
			return fmt.Errorf("   *** ERROR hash not found in "+
				"timestamp : %v %v\n", filepath.Join(fs.root,
				ts2dirname(value)), key)
		}
		if _, ok := empties[value]; ok {
			return fmt.Errorf("   *** ERROR global pointer to "+
				"empty timestamp: %v (%v)", ts2dirname(value),
				value)
		}
	}
	if i.Error() != nil {
		return i.Error()
	}
	for k := range empties {
		err := journal(options.File, FilesystemActionDeleteTimestamp,
			FilesystemDeleteTimestamp{
				Version:   FilesystemActionVersion,
				Timestamp: k,
				Directory: ts2dirname(k),
			})
		if err != nil {
			return fmt.Errorf("   *** ERROR journal: %v",
				err)
		}
		if !options.Fix {
			continue
		}
		fmt.Printf("   *** FIXING removing empty timestamp: %v (%v)\n",
			ts2dirname(k), k)
		path := filepath.Join(fs.root, ts2dirname(k))
		err = os.RemoveAll(path)
		if err != nil {
			return fmt.Errorf("   *** ERROR RemoveAll: %v", err)
		}
		delete(empties, k)
	}
	if len(empties) != 0 && options.Fix {
		return fmt.Errorf("   *** ERROR empties not pruned: %v ",
			spew.Sdump(empties))
	}
	return nil
}
func (fs *FileSystem) fsckDup(options *backend.FsckOptions, ts int64, dups map[string]int64) error {
	db, err := fs.openRead(ts)
	if err != nil {
		return err
	}
	defer db.Close()
	i := db.NewIterator(nil, nil)
	defer i.Release()
	for i.Next() {
		key := i.Key()
		if string(key) == flushedKey {
			flushRecord, err := DecodeFlushRecord(i.Value())
			if err != nil {
				return err
			}
			if options.Verbose {
				dumpFlushRecord(os.Stdout, flushRecord)
			}
			continue
		}
		k := hex.EncodeToString(key)
		if options.PrintHashes {
			fmt.Printf("Hash           : %v\n", k)
		}
		if v, ok := dups[k]; ok {
			fmt.Printf("    *** ERROR duplicate key: %v %v %v\n",
				ts, v, k)
			err := journal(options.File, FilesystemActionDeleteDuplicate,
				FilesystemDeleteDuplicate{
					Version:            FilesystemActionVersion,
					Digest:             k,
					Found:              v,
					FoundDirectory:     ts2dirname(v),
					Duplicate:          ts,
					DuplicateDirectory: ts2dirname(ts),
				})
			if err != nil {
				return fmt.Errorf("   *** ERROR journal: %v",
					err)
			}
			if !options.Fix {
				continue
			}
			ok, err := fs.db.Has(key, nil)
			if err != nil {
				return fmt.Errorf("   *** ERROR duplicate "+
					"key: has %v", err)
			}
			if ok {
				return fmt.Errorf("   *** ERROR duplicate "+
					"key: exists in global %v", k)
			}
			err = db.Delete(key, nil)
			if err != nil {
				return fmt.Errorf("   *** ERROR duplicate " +
					"key: delete")
			}
			fmt.Printf("    *** FIXING duplicate key: delete "+
				"%v %v\n", ts, k)
			continue
		}
		dups[k] = int64(binary.LittleEndian.Uint64(i.Value()))
	}
	return i.Error()
}