mirror of
https://github.com/restic/restic.git
synced 2024-09-08 20:39:25 +02:00
Add new generic_attributes attribute in Node. Use the generic attributes to add support for creation time and file attributes like hidden, readonly, encrypted in windows. Handle permission errors for readonly files in windows. Handle backup and restore of encrypted attributes using windows system calls.
128 lines
3.2 KiB
Go
128 lines
3.2 KiB
Go
package restorer
|
|
|
|
import (
|
|
"os"
|
|
"sync"
|
|
|
|
"github.com/cespare/xxhash/v2"
|
|
"github.com/restic/restic/internal/debug"
|
|
"github.com/restic/restic/internal/fs"
|
|
)
|
|
|
|
// writes blobs to target files.
|
|
// multiple files can be written to concurrently.
|
|
// multiple blobs can be concurrently written to the same file.
|
|
// TODO I am not 100% convinced this is necessary, i.e. it may be okay
|
|
// to use multiple os.File to write to the same target file
|
|
type filesWriter struct {
|
|
buckets []filesWriterBucket
|
|
}
|
|
|
|
type filesWriterBucket struct {
|
|
lock sync.Mutex
|
|
files map[string]*partialFile
|
|
}
|
|
|
|
type partialFile struct {
|
|
*os.File
|
|
users int // Reference count.
|
|
sparse bool
|
|
}
|
|
|
|
func newFilesWriter(count int) *filesWriter {
|
|
buckets := make([]filesWriterBucket, count)
|
|
for b := 0; b < count; b++ {
|
|
buckets[b].files = make(map[string]*partialFile)
|
|
}
|
|
return &filesWriter{
|
|
buckets: buckets,
|
|
}
|
|
}
|
|
|
|
func (w *filesWriter) writeToFile(path string, blob []byte, offset int64, createSize int64, sparse bool) error {
|
|
bucket := &w.buckets[uint(xxhash.Sum64String(path))%uint(len(w.buckets))]
|
|
|
|
acquireWriter := func() (*partialFile, error) {
|
|
bucket.lock.Lock()
|
|
defer bucket.lock.Unlock()
|
|
|
|
if wr, ok := bucket.files[path]; ok {
|
|
bucket.files[path].users++
|
|
return wr, nil
|
|
}
|
|
var f *os.File
|
|
var err error
|
|
if createSize >= 0 {
|
|
if f, err = os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600); err != nil {
|
|
if fs.IsAccessDenied(err) {
|
|
// If file is readonly, clear the readonly flag by resetting the
|
|
// permissions of the file and try again
|
|
// as the metadata will be set again in the second pass and the
|
|
// readonly flag will be applied again if needed.
|
|
if err = fs.ResetPermissions(path); err != nil {
|
|
return nil, err
|
|
}
|
|
if f, err = os.OpenFile(path, os.O_TRUNC|os.O_WRONLY, 0600); err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
return nil, err
|
|
}
|
|
}
|
|
} else if f, err = os.OpenFile(path, os.O_WRONLY, 0600); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
wr := &partialFile{File: f, users: 1, sparse: sparse}
|
|
bucket.files[path] = wr
|
|
|
|
if createSize >= 0 {
|
|
if sparse {
|
|
err = truncateSparse(f, createSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
err := fs.PreallocateFile(wr.File, createSize)
|
|
if err != nil {
|
|
// Just log the preallocate error but don't let it cause the restore process to fail.
|
|
// Preallocate might return an error if the filesystem (implementation) does not
|
|
// support preallocation or our parameters combination to the preallocate call
|
|
// This should yield a syscall.ENOTSUP error, but some other errors might also
|
|
// show up.
|
|
debug.Log("Failed to preallocate %v with size %v: %v", path, createSize, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return wr, nil
|
|
}
|
|
|
|
releaseWriter := func(wr *partialFile) error {
|
|
bucket.lock.Lock()
|
|
defer bucket.lock.Unlock()
|
|
|
|
if bucket.files[path].users == 1 {
|
|
delete(bucket.files, path)
|
|
return wr.Close()
|
|
}
|
|
bucket.files[path].users--
|
|
return nil
|
|
}
|
|
|
|
wr, err := acquireWriter()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = wr.WriteAt(blob, offset)
|
|
|
|
if err != nil {
|
|
// ignore subsequent errors
|
|
_ = releaseWriter(wr)
|
|
return err
|
|
}
|
|
|
|
return releaseWriter(wr)
|
|
}
|