github.com/fawick/restic@v0.1.1-0.20171126184616-c02923fbfc79/internal/backend/b2/b2.go (about) 1 package b2 2 3 import ( 4 "context" 5 "io" 6 "net/http" 7 "path" 8 "strings" 9 10 "github.com/restic/restic/internal/backend" 11 "github.com/restic/restic/internal/debug" 12 "github.com/restic/restic/internal/errors" 13 "github.com/restic/restic/internal/restic" 14 15 "github.com/kurin/blazer/b2" 16 ) 17 18 // b2Backend is a backend which stores its data on Backblaze B2. 19 type b2Backend struct { 20 client *b2.Client 21 bucket *b2.Bucket 22 cfg Config 23 listMaxItems int 24 backend.Layout 25 sem *backend.Semaphore 26 } 27 28 const defaultListMaxItems = 1000 29 30 // ensure statically that *b2Backend implements restic.Backend. 31 var _ restic.Backend = &b2Backend{} 32 33 func newClient(ctx context.Context, cfg Config, rt http.RoundTripper) (*b2.Client, error) { 34 opts := []b2.ClientOption{b2.Transport(rt)} 35 36 c, err := b2.NewClient(ctx, cfg.AccountID, cfg.Key, opts...) 37 if err != nil { 38 return nil, errors.Wrap(err, "b2.NewClient") 39 } 40 return c, nil 41 } 42 43 // Open opens a connection to the B2 service. 44 func Open(cfg Config, rt http.RoundTripper) (restic.Backend, error) { 45 debug.Log("cfg %#v", cfg) 46 47 ctx, cancel := context.WithCancel(context.TODO()) 48 defer cancel() 49 50 client, err := newClient(ctx, cfg, rt) 51 if err != nil { 52 return nil, err 53 } 54 55 bucket, err := client.Bucket(ctx, cfg.Bucket) 56 if err != nil { 57 return nil, errors.Wrap(err, "Bucket") 58 } 59 60 sem, err := backend.NewSemaphore(cfg.Connections) 61 if err != nil { 62 return nil, err 63 } 64 65 be := &b2Backend{ 66 client: client, 67 bucket: bucket, 68 cfg: cfg, 69 Layout: &backend.DefaultLayout{ 70 Join: path.Join, 71 Path: cfg.Prefix, 72 }, 73 listMaxItems: defaultListMaxItems, 74 sem: sem, 75 } 76 77 return be, nil 78 } 79 80 // Create opens a connection to the B2 service. If the bucket does not exist yet, 81 // it is created. 82 func Create(cfg Config, rt http.RoundTripper) (restic.Backend, error) { 83 debug.Log("cfg %#v", cfg) 84 85 ctx, cancel := context.WithCancel(context.TODO()) 86 defer cancel() 87 88 client, err := newClient(ctx, cfg, rt) 89 if err != nil { 90 return nil, err 91 } 92 93 attr := b2.BucketAttrs{ 94 Type: b2.Private, 95 } 96 bucket, err := client.NewBucket(ctx, cfg.Bucket, &attr) 97 if err != nil { 98 return nil, errors.Wrap(err, "NewBucket") 99 } 100 101 sem, err := backend.NewSemaphore(cfg.Connections) 102 if err != nil { 103 return nil, err 104 } 105 106 be := &b2Backend{ 107 client: client, 108 bucket: bucket, 109 cfg: cfg, 110 Layout: &backend.DefaultLayout{ 111 Join: path.Join, 112 Path: cfg.Prefix, 113 }, 114 listMaxItems: defaultListMaxItems, 115 sem: sem, 116 } 117 118 present, err := be.Test(context.TODO(), restic.Handle{Type: restic.ConfigFile}) 119 if err != nil { 120 return nil, err 121 } 122 123 if present { 124 return nil, errors.New("config already exists") 125 } 126 127 return be, nil 128 } 129 130 // SetListMaxItems sets the number of list items to load per request. 131 func (be *b2Backend) SetListMaxItems(i int) { 132 be.listMaxItems = i 133 } 134 135 // Location returns the location for the backend. 136 func (be *b2Backend) Location() string { 137 return be.cfg.Bucket 138 } 139 140 // IsNotExist returns true if the error is caused by a non-existing file. 141 func (be *b2Backend) IsNotExist(err error) bool { 142 return b2.IsNotExist(errors.Cause(err)) 143 } 144 145 // Load returns the data stored in the backend for h at the given offset 146 // and saves it in p. Load has the same semantics as io.ReaderAt. 147 func (be *b2Backend) Load(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) { 148 debug.Log("Load %v, length %v, offset %v from %v", h, length, offset, be.Filename(h)) 149 if err := h.Valid(); err != nil { 150 return nil, err 151 } 152 153 if offset < 0 { 154 return nil, errors.New("offset is negative") 155 } 156 157 if length < 0 { 158 return nil, errors.Errorf("invalid length %d", length) 159 } 160 161 ctx, cancel := context.WithCancel(ctx) 162 163 be.sem.GetToken() 164 165 name := be.Layout.Filename(h) 166 obj := be.bucket.Object(name) 167 168 if offset == 0 && length == 0 { 169 rd := obj.NewReader(ctx) 170 return be.sem.ReleaseTokenOnClose(rd, cancel), nil 171 } 172 173 // pass a negative length to NewRangeReader so that the remainder of the 174 // file is read. 175 if length == 0 { 176 length = -1 177 } 178 179 rd := obj.NewRangeReader(ctx, offset, int64(length)) 180 return be.sem.ReleaseTokenOnClose(rd, cancel), nil 181 } 182 183 // Save stores data in the backend at the handle. 184 func (be *b2Backend) Save(ctx context.Context, h restic.Handle, rd io.Reader) error { 185 ctx, cancel := context.WithCancel(ctx) 186 defer cancel() 187 188 if err := h.Valid(); err != nil { 189 return err 190 } 191 192 be.sem.GetToken() 193 defer be.sem.ReleaseToken() 194 195 name := be.Filename(h) 196 debug.Log("Save %v, name %v", h, name) 197 obj := be.bucket.Object(name) 198 199 _, err := obj.Attrs(ctx) 200 if err == nil { 201 debug.Log(" %v already exists", h) 202 return errors.New("key already exists") 203 } 204 205 w := obj.NewWriter(ctx) 206 n, err := io.Copy(w, rd) 207 debug.Log(" saved %d bytes, err %v", n, err) 208 209 if err != nil { 210 _ = w.Close() 211 return errors.Wrap(err, "Copy") 212 } 213 214 return errors.Wrap(w.Close(), "Close") 215 } 216 217 // Stat returns information about a blob. 218 func (be *b2Backend) Stat(ctx context.Context, h restic.Handle) (bi restic.FileInfo, err error) { 219 debug.Log("Stat %v", h) 220 221 be.sem.GetToken() 222 defer be.sem.ReleaseToken() 223 224 name := be.Filename(h) 225 obj := be.bucket.Object(name) 226 info, err := obj.Attrs(ctx) 227 if err != nil { 228 debug.Log("Attrs() err %v", err) 229 return restic.FileInfo{}, errors.Wrap(err, "Stat") 230 } 231 return restic.FileInfo{Size: info.Size}, nil 232 } 233 234 // Test returns true if a blob of the given type and name exists in the backend. 235 func (be *b2Backend) Test(ctx context.Context, h restic.Handle) (bool, error) { 236 debug.Log("Test %v", h) 237 238 be.sem.GetToken() 239 defer be.sem.ReleaseToken() 240 241 found := false 242 name := be.Filename(h) 243 obj := be.bucket.Object(name) 244 info, err := obj.Attrs(ctx) 245 if err == nil && info != nil && info.Status == b2.Uploaded { 246 found = true 247 } 248 return found, nil 249 } 250 251 // Remove removes the blob with the given name and type. 252 func (be *b2Backend) Remove(ctx context.Context, h restic.Handle) error { 253 debug.Log("Remove %v", h) 254 255 be.sem.GetToken() 256 defer be.sem.ReleaseToken() 257 258 obj := be.bucket.Object(be.Filename(h)) 259 return errors.Wrap(obj.Delete(ctx), "Delete") 260 } 261 262 // List returns a channel that yields all names of blobs of type t. A 263 // goroutine is started for this. If the channel done is closed, sending 264 // stops. 265 func (be *b2Backend) List(ctx context.Context, t restic.FileType) <-chan string { 266 debug.Log("List %v", t) 267 ch := make(chan string) 268 269 ctx, cancel := context.WithCancel(ctx) 270 271 go func() { 272 defer close(ch) 273 defer cancel() 274 275 prefix := be.Dirname(restic.Handle{Type: t}) 276 cur := &b2.Cursor{Prefix: prefix} 277 278 for { 279 be.sem.GetToken() 280 objs, c, err := be.bucket.ListCurrentObjects(ctx, be.listMaxItems, cur) 281 be.sem.ReleaseToken() 282 if err != nil && err != io.EOF { 283 // TODO: return err to caller once err handling in List() is improved 284 debug.Log("List: %v", err) 285 return 286 } 287 debug.Log("returned %v items", len(objs)) 288 for _, obj := range objs { 289 // Skip objects returned that do not have the specified prefix. 290 if !strings.HasPrefix(obj.Name(), prefix) { 291 continue 292 } 293 294 m := path.Base(obj.Name()) 295 if m == "" { 296 continue 297 } 298 299 select { 300 case ch <- m: 301 case <-ctx.Done(): 302 return 303 } 304 } 305 if err == io.EOF { 306 return 307 } 308 cur = c 309 } 310 }() 311 312 return ch 313 } 314 315 // Remove keys for a specified backend type. 316 func (be *b2Backend) removeKeys(ctx context.Context, t restic.FileType) error { 317 debug.Log("removeKeys %v", t) 318 for key := range be.List(ctx, t) { 319 err := be.Remove(ctx, restic.Handle{Type: t, Name: key}) 320 if err != nil { 321 return err 322 } 323 } 324 return nil 325 } 326 327 // Delete removes all restic keys in the bucket. It will not remove the bucket itself. 328 func (be *b2Backend) Delete(ctx context.Context) error { 329 alltypes := []restic.FileType{ 330 restic.DataFile, 331 restic.KeyFile, 332 restic.LockFile, 333 restic.SnapshotFile, 334 restic.IndexFile} 335 336 for _, t := range alltypes { 337 err := be.removeKeys(ctx, t) 338 if err != nil { 339 return nil 340 } 341 } 342 err := be.Remove(ctx, restic.Handle{Type: restic.ConfigFile}) 343 if err != nil && b2.IsNotExist(errors.Cause(err)) { 344 err = nil 345 } 346 347 return err 348 } 349 350 // Close does nothing 351 func (be *b2Backend) Close() error { return nil }