github.com/advanderveer/restic@v0.8.1-0.20171209104529-42a8c19aaea6/internal/backend/local/local.go (about) 1 package local 2 3 import ( 4 "context" 5 "io" 6 "os" 7 "path/filepath" 8 9 "github.com/restic/restic/internal/errors" 10 "github.com/restic/restic/internal/restic" 11 12 "github.com/restic/restic/internal/backend" 13 "github.com/restic/restic/internal/debug" 14 "github.com/restic/restic/internal/fs" 15 ) 16 17 // Local is a backend in a local directory. 18 type Local struct { 19 Config 20 backend.Layout 21 } 22 23 // ensure statically that *Local implements restic.Backend. 24 var _ restic.Backend = &Local{} 25 26 const defaultLayout = "default" 27 28 // dirExists returns true if the name exists and is a directory. 29 func dirExists(name string) bool { 30 f, err := fs.Open(name) 31 if err != nil { 32 return false 33 } 34 35 fi, err := f.Stat() 36 if err != nil { 37 return false 38 } 39 40 if err = f.Close(); err != nil { 41 return false 42 } 43 44 return fi.IsDir() 45 } 46 47 // Open opens the local backend as specified by config. 48 func Open(cfg Config) (*Local, error) { 49 debug.Log("open local backend at %v (layout %q)", cfg.Path, cfg.Layout) 50 l, err := backend.ParseLayout(&backend.LocalFilesystem{}, cfg.Layout, defaultLayout, cfg.Path) 51 if err != nil { 52 return nil, err 53 } 54 55 be := &Local{Config: cfg, Layout: l} 56 57 // if data dir exists, make sure that all subdirs also exist 58 datadir := be.Dirname(restic.Handle{Type: restic.DataFile}) 59 if dirExists(datadir) { 60 debug.Log("datadir %v exists", datadir) 61 for _, d := range be.Paths() { 62 if !fs.HasPathPrefix(datadir, d) { 63 debug.Log("%v is not subdir of datadir %v", d, datadir) 64 continue 65 } 66 67 debug.Log("MkdirAll %v", d) 68 err := fs.MkdirAll(d, backend.Modes.Dir) 69 if err != nil { 70 return nil, errors.Wrap(err, "MkdirAll") 71 } 72 } 73 } 74 75 return be, nil 76 } 77 78 // Create creates all the necessary files and directories for a new local 79 // backend at dir. Afterwards a new config blob should be created. 80 func Create(cfg Config) (*Local, error) { 81 debug.Log("create local backend at %v (layout %q)", cfg.Path, cfg.Layout) 82 83 l, err := backend.ParseLayout(&backend.LocalFilesystem{}, cfg.Layout, defaultLayout, cfg.Path) 84 if err != nil { 85 return nil, err 86 } 87 88 be := &Local{ 89 Config: cfg, 90 Layout: l, 91 } 92 93 // test if config file already exists 94 _, err = fs.Lstat(be.Filename(restic.Handle{Type: restic.ConfigFile})) 95 if err == nil { 96 return nil, errors.New("config file already exists") 97 } 98 99 // create paths for data and refs 100 for _, d := range be.Paths() { 101 err := fs.MkdirAll(d, backend.Modes.Dir) 102 if err != nil { 103 return nil, errors.Wrap(err, "MkdirAll") 104 } 105 } 106 107 return be, nil 108 } 109 110 // Location returns this backend's location (the directory name). 111 func (b *Local) Location() string { 112 return b.Path 113 } 114 115 // IsNotExist returns true if the error is caused by a non existing file. 116 func (b *Local) IsNotExist(err error) bool { 117 return os.IsNotExist(errors.Cause(err)) 118 } 119 120 // Save stores data in the backend at the handle. 121 func (b *Local) Save(ctx context.Context, h restic.Handle, rd io.Reader) error { 122 debug.Log("Save %v", h) 123 if err := h.Valid(); err != nil { 124 return err 125 } 126 127 if h.Type == restic.LockFile { 128 lockDir := b.Dirname(h) 129 if !dirExists(lockDir) { 130 debug.Log("locks/ does not exist yet, creating now.") 131 if err := fs.MkdirAll(lockDir, backend.Modes.Dir); err != nil { 132 return errors.Wrap(err, "MkdirAll") 133 } 134 } 135 } 136 137 filename := b.Filename(h) 138 139 // create new file 140 f, err := fs.OpenFile(filename, os.O_CREATE|os.O_EXCL|os.O_WRONLY, backend.Modes.File) 141 if err != nil { 142 return errors.Wrap(err, "OpenFile") 143 } 144 145 // save data, then sync 146 _, err = io.Copy(f, rd) 147 if err != nil { 148 _ = f.Close() 149 return errors.Wrap(err, "Write") 150 } 151 152 if err = f.Sync(); err != nil { 153 _ = f.Close() 154 return errors.Wrap(err, "Sync") 155 } 156 157 err = f.Close() 158 if err != nil { 159 return errors.Wrap(err, "Close") 160 } 161 162 return setNewFileMode(filename, backend.Modes.File) 163 } 164 165 // Load returns a reader that yields the contents of the file at h at the 166 // given offset. If length is nonzero, only a portion of the file is 167 // returned. rd must be closed after use. 168 func (b *Local) Load(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) { 169 debug.Log("Load %v, length %v, offset %v", h, length, offset) 170 if err := h.Valid(); err != nil { 171 return nil, err 172 } 173 174 if offset < 0 { 175 return nil, errors.New("offset is negative") 176 } 177 178 f, err := fs.Open(b.Filename(h)) 179 if err != nil { 180 return nil, err 181 } 182 183 if offset > 0 { 184 _, err = f.Seek(offset, 0) 185 if err != nil { 186 _ = f.Close() 187 return nil, err 188 } 189 } 190 191 if length > 0 { 192 return backend.LimitReadCloser(f, int64(length)), nil 193 } 194 195 return f, nil 196 } 197 198 // Stat returns information about a blob. 199 func (b *Local) Stat(ctx context.Context, h restic.Handle) (restic.FileInfo, error) { 200 debug.Log("Stat %v", h) 201 if err := h.Valid(); err != nil { 202 return restic.FileInfo{}, err 203 } 204 205 fi, err := fs.Stat(b.Filename(h)) 206 if err != nil { 207 return restic.FileInfo{}, errors.Wrap(err, "Stat") 208 } 209 210 return restic.FileInfo{Size: fi.Size()}, nil 211 } 212 213 // Test returns true if a blob of the given type and name exists in the backend. 214 func (b *Local) Test(ctx context.Context, h restic.Handle) (bool, error) { 215 debug.Log("Test %v", h) 216 _, err := fs.Stat(b.Filename(h)) 217 if err != nil { 218 if os.IsNotExist(errors.Cause(err)) { 219 return false, nil 220 } 221 return false, errors.Wrap(err, "Stat") 222 } 223 224 return true, nil 225 } 226 227 // Remove removes the blob with the given name and type. 228 func (b *Local) Remove(ctx context.Context, h restic.Handle) error { 229 debug.Log("Remove %v", h) 230 fn := b.Filename(h) 231 232 // reset read-only flag 233 err := fs.Chmod(fn, 0666) 234 if err != nil { 235 return errors.Wrap(err, "Chmod") 236 } 237 238 return fs.Remove(fn) 239 } 240 241 func isFile(fi os.FileInfo) bool { 242 return fi.Mode()&(os.ModeType|os.ModeCharDevice) == 0 243 } 244 245 // List returns a channel that yields all names of blobs of type t. A 246 // goroutine is started for this. 247 func (b *Local) List(ctx context.Context, t restic.FileType) <-chan string { 248 debug.Log("List %v", t) 249 250 ch := make(chan string) 251 252 go func() { 253 defer close(ch) 254 255 err := fs.Walk(b.Basedir(t), func(path string, fi os.FileInfo, err error) error { 256 if err != nil { 257 return err 258 } 259 260 if !isFile(fi) { 261 return nil 262 } 263 264 select { 265 case ch <- filepath.Base(path): 266 case <-ctx.Done(): 267 return nil 268 } 269 270 return nil 271 }) 272 273 if err != nil { 274 debug.Log("Walk %v", err) 275 } 276 }() 277 278 return ch 279 } 280 281 // Delete removes the repository and all files. 282 func (b *Local) Delete(ctx context.Context) error { 283 debug.Log("Delete()") 284 return fs.RemoveAll(b.Path) 285 } 286 287 // Close closes all open files. 288 func (b *Local) Close() error { 289 debug.Log("Close()") 290 // this does not need to do anything, all open files are closed within the 291 // same function. 292 return nil 293 }