github.com/ethersphere/bee/v2@v2.2.0/pkg/sharky/recovery.go (about) 1 // Copyright 2021 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package sharky 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "io/fs" 12 "os" 13 "path" 14 "sync" 15 16 "github.com/hashicorp/go-multierror" 17 ) 18 19 // Recovery allows disaster recovery. 20 type Recovery struct { 21 mtx sync.Mutex 22 shards []*slots 23 shardFiles []*os.File 24 datasize int 25 } 26 27 var ErrShardNotFound = errors.New("shard not found") 28 29 func NewRecovery(dir string, shardCnt int, datasize int) (*Recovery, error) { 30 shards := make([]*slots, shardCnt) 31 shardFiles := make([]*os.File, shardCnt) 32 33 for i := 0; i < shardCnt; i++ { 34 file, err := os.OpenFile(path.Join(dir, fmt.Sprintf("shard_%03d", i)), os.O_RDWR, 0666) 35 if errors.Is(err, fs.ErrNotExist) { 36 return nil, fmt.Errorf("index %d: %w", i, ErrShardNotFound) 37 } 38 if err != nil { 39 return nil, err 40 } 41 fi, err := file.Stat() 42 if err != nil { 43 return nil, err 44 } 45 size := uint32(fi.Size() / int64(datasize)) 46 ffile, err := os.OpenFile(path.Join(dir, fmt.Sprintf("free_%03d", i)), os.O_RDWR|os.O_CREATE, 0666) 47 if err != nil { 48 return nil, err 49 } 50 sl := newSlots(ffile, nil) 51 sl.data = make([]byte, size/8) 52 shards[i] = sl 53 shardFiles[i] = file 54 } 55 return &Recovery{shards: shards, shardFiles: shardFiles, datasize: datasize}, nil 56 } 57 58 // Add marks a location as used (not free). 59 func (r *Recovery) Add(loc Location) error { 60 r.mtx.Lock() 61 defer r.mtx.Unlock() 62 63 sh := r.shards[loc.Shard] 64 l := len(sh.data) 65 if diff := int(loc.Slot/8) - l; diff >= 0 { 66 sh.extend(diff + 1) 67 for i := 0; i <= diff; i++ { 68 sh.data[l+i] = 0x0 69 } 70 } 71 sh.push(loc.Slot) 72 return nil 73 } 74 75 func (r *Recovery) Read(ctx context.Context, loc Location, buf []byte) error { 76 r.mtx.Lock() 77 defer r.mtx.Unlock() 78 _, err := r.shardFiles[loc.Shard].ReadAt(buf, int64(loc.Slot)*int64(r.datasize)) 79 return err 80 } 81 82 func (r *Recovery) Move(ctx context.Context, from Location, to Location) error { 83 r.mtx.Lock() 84 defer r.mtx.Unlock() 85 86 chData := make([]byte, from.Length) 87 _, err := r.shardFiles[from.Shard].ReadAt(chData, int64(from.Slot)*int64(r.datasize)) 88 if err != nil { 89 return err 90 } 91 92 _, err = r.shardFiles[to.Shard].WriteAt(chData, int64(to.Slot)*int64(r.datasize)) 93 return err 94 } 95 96 func (r *Recovery) TruncateAt(ctx context.Context, shard uint8, slot uint32) error { 97 r.mtx.Lock() 98 defer r.mtx.Unlock() 99 100 return r.shardFiles[shard].Truncate(int64(slot) * int64(r.datasize)) 101 } 102 103 // Save saves all free slots files of the recovery (without closing). 104 func (r *Recovery) Save() error { 105 r.mtx.Lock() 106 defer r.mtx.Unlock() 107 108 err := new(multierror.Error) 109 for _, sh := range r.shards { 110 for i := range sh.data { 111 sh.data[i] ^= 0xff 112 } 113 err = multierror.Append(err, sh.save()) 114 } 115 return err.ErrorOrNil() 116 } 117 118 // Close closes data and free slots files of the recovery (without saving). 119 func (r *Recovery) Close() error { 120 r.mtx.Lock() 121 defer r.mtx.Unlock() 122 123 err := new(multierror.Error) 124 for idx, sh := range r.shards { 125 err = multierror.Append(err, sh.file.Close(), r.shardFiles[idx].Close()) 126 } 127 return err.ErrorOrNil() 128 }