github.com/mckael/restic@v0.8.3/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 return &Local{Config: cfg, Layout: l}, nil 56 } 57 58 // Create creates all the necessary files and directories for a new local 59 // backend at dir. Afterwards a new config blob should be created. 60 func Create(cfg Config) (*Local, error) { 61 debug.Log("create local backend at %v (layout %q)", cfg.Path, cfg.Layout) 62 63 l, err := backend.ParseLayout(&backend.LocalFilesystem{}, cfg.Layout, defaultLayout, cfg.Path) 64 if err != nil { 65 return nil, err 66 } 67 68 be := &Local{ 69 Config: cfg, 70 Layout: l, 71 } 72 73 // test if config file already exists 74 _, err = fs.Lstat(be.Filename(restic.Handle{Type: restic.ConfigFile})) 75 if err == nil { 76 return nil, errors.New("config file already exists") 77 } 78 79 // create paths for data and refs 80 for _, d := range be.Paths() { 81 err := fs.MkdirAll(d, backend.Modes.Dir) 82 if err != nil { 83 return nil, errors.Wrap(err, "MkdirAll") 84 } 85 } 86 87 return be, nil 88 } 89 90 // Location returns this backend's location (the directory name). 91 func (b *Local) Location() string { 92 return b.Path 93 } 94 95 // IsNotExist returns true if the error is caused by a non existing file. 96 func (b *Local) IsNotExist(err error) bool { 97 return os.IsNotExist(errors.Cause(err)) 98 } 99 100 // Save stores data in the backend at the handle. 101 func (b *Local) Save(ctx context.Context, h restic.Handle, rd io.Reader) error { 102 debug.Log("Save %v", h) 103 if err := h.Valid(); err != nil { 104 return err 105 } 106 107 filename := b.Filename(h) 108 109 // create new file 110 f, err := fs.OpenFile(filename, os.O_CREATE|os.O_EXCL|os.O_WRONLY, backend.Modes.File) 111 112 if b.IsNotExist(err) { 113 debug.Log("error %v: creating dir", err) 114 115 // error is caused by a missing directory, try to create it 116 mkdirErr := os.MkdirAll(filepath.Dir(filename), backend.Modes.Dir) 117 if mkdirErr != nil { 118 debug.Log("error creating dir %v: %v", filepath.Dir(filename), mkdirErr) 119 } else { 120 // try again 121 f, err = fs.OpenFile(filename, os.O_CREATE|os.O_EXCL|os.O_WRONLY, backend.Modes.File) 122 } 123 } 124 125 if err != nil { 126 return errors.Wrap(err, "OpenFile") 127 } 128 129 // save data, then sync 130 _, err = io.Copy(f, rd) 131 if err != nil { 132 _ = f.Close() 133 return errors.Wrap(err, "Write") 134 } 135 136 if err = f.Sync(); err != nil { 137 _ = f.Close() 138 return errors.Wrap(err, "Sync") 139 } 140 141 err = f.Close() 142 if err != nil { 143 return errors.Wrap(err, "Close") 144 } 145 146 return setNewFileMode(filename, backend.Modes.File) 147 } 148 149 // Load runs fn with a reader that yields the contents of the file at h at the 150 // given offset. 151 func (b *Local) Load(ctx context.Context, h restic.Handle, length int, offset int64, fn func(rd io.Reader) error) error { 152 return backend.DefaultLoad(ctx, h, length, offset, b.openReader, fn) 153 } 154 155 func (b *Local) openReader(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) { 156 debug.Log("Load %v, length %v, offset %v", h, length, offset) 157 if err := h.Valid(); err != nil { 158 return nil, err 159 } 160 161 if offset < 0 { 162 return nil, errors.New("offset is negative") 163 } 164 165 f, err := fs.Open(b.Filename(h)) 166 if err != nil { 167 return nil, err 168 } 169 170 if offset > 0 { 171 _, err = f.Seek(offset, 0) 172 if err != nil { 173 _ = f.Close() 174 return nil, err 175 } 176 } 177 178 if length > 0 { 179 return backend.LimitReadCloser(f, int64(length)), nil 180 } 181 182 return f, nil 183 } 184 185 // Stat returns information about a blob. 186 func (b *Local) Stat(ctx context.Context, h restic.Handle) (restic.FileInfo, error) { 187 debug.Log("Stat %v", h) 188 if err := h.Valid(); err != nil { 189 return restic.FileInfo{}, err 190 } 191 192 fi, err := fs.Stat(b.Filename(h)) 193 if err != nil { 194 return restic.FileInfo{}, errors.Wrap(err, "Stat") 195 } 196 197 return restic.FileInfo{Size: fi.Size(), Name: h.Name}, nil 198 } 199 200 // Test returns true if a blob of the given type and name exists in the backend. 201 func (b *Local) Test(ctx context.Context, h restic.Handle) (bool, error) { 202 debug.Log("Test %v", h) 203 _, err := fs.Stat(b.Filename(h)) 204 if err != nil { 205 if os.IsNotExist(errors.Cause(err)) { 206 return false, nil 207 } 208 return false, errors.Wrap(err, "Stat") 209 } 210 211 return true, nil 212 } 213 214 // Remove removes the blob with the given name and type. 215 func (b *Local) Remove(ctx context.Context, h restic.Handle) error { 216 debug.Log("Remove %v", h) 217 fn := b.Filename(h) 218 219 // reset read-only flag 220 err := fs.Chmod(fn, 0666) 221 if err != nil { 222 return errors.Wrap(err, "Chmod") 223 } 224 225 return fs.Remove(fn) 226 } 227 228 func isFile(fi os.FileInfo) bool { 229 return fi.Mode()&(os.ModeType|os.ModeCharDevice) == 0 230 } 231 232 // List runs fn for each file in the backend which has the type t. When an 233 // error occurs (or fn returns an error), List stops and returns it. 234 func (b *Local) List(ctx context.Context, t restic.FileType, fn func(restic.FileInfo) error) error { 235 debug.Log("List %v", t) 236 237 basedir, subdirs := b.Basedir(t) 238 return fs.Walk(basedir, func(path string, fi os.FileInfo, err error) error { 239 debug.Log("walk on %v\n", path) 240 if err != nil { 241 return err 242 } 243 244 if path == basedir { 245 return nil 246 } 247 248 if !isFile(fi) { 249 return nil 250 } 251 252 if fi.IsDir() && !subdirs { 253 return filepath.SkipDir 254 } 255 256 debug.Log("send %v\n", filepath.Base(path)) 257 258 rfi := restic.FileInfo{ 259 Name: filepath.Base(path), 260 Size: fi.Size(), 261 } 262 263 if ctx.Err() != nil { 264 return ctx.Err() 265 } 266 267 err = fn(rfi) 268 if err != nil { 269 return err 270 } 271 272 return ctx.Err() 273 }) 274 } 275 276 // Delete removes the repository and all files. 277 func (b *Local) Delete(ctx context.Context) error { 278 debug.Log("Delete()") 279 return fs.RemoveAll(b.Path) 280 } 281 282 // Close closes all open files. 283 func (b *Local) Close() error { 284 debug.Log("Close()") 285 // this does not need to do anything, all open files are closed within the 286 // same function. 287 return nil 288 }