func (node Node) MarshalJSON() ([]byte, error) { if node.ModTime.Year() < 0 || node.ModTime.Year() > 9999 { err := errors.Errorf("node %v has invalid ModTime year %d: %v", node.Path, node.ModTime.Year(), node.ModTime) return nil, err } if node.ChangeTime.Year() < 0 || node.ChangeTime.Year() > 9999 { err := errors.Errorf("node %v has invalid ChangeTime year %d: %v", node.Path, node.ChangeTime.Year(), node.ChangeTime) return nil, err } if node.AccessTime.Year() < 0 || node.AccessTime.Year() > 9999 { err := errors.Errorf("node %v has invalid AccessTime year %d: %v", node.Path, node.AccessTime.Year(), node.AccessTime) return nil, err } type nodeJSON Node nj := nodeJSON(node) name := strconv.Quote(node.Name) nj.Name = name[1 : len(name)-1] return json.Marshal(nj) }
func (r *SFTP) mkdirAll(dir string, mode os.FileMode) error { // check if directory already exists fi, err := r.c.Lstat(dir) if err == nil { if fi.IsDir() { return nil } return errors.Errorf("mkdirAll(%s): entry exists but is not a directory", dir) } // create parent directories errMkdirAll := r.mkdirAll(path.Dir(dir), backend.Modes.Dir) // create directory errMkdir := r.c.Mkdir(dir) // test if directory was created successfully fi, err = r.c.Lstat(dir) if err != nil { // return previous errors return errors.Errorf("mkdirAll(%s): unable to create directories: %v, %v", dir, errMkdirAll, errMkdir) } if !fi.IsDir() { return errors.Errorf("mkdirAll(%s): entry exists but is not a directory", dir) } // set mode return r.c.Chmod(dir, mode) }
func (c *Checker) checkTree(id restic.ID, tree *restic.Tree) (errs []error) { debug.Log("checking tree %v", id.Str()) var blobs []restic.ID for _, node := range tree.Nodes { switch node.Type { case "file": if node.Content == nil { errs = append(errs, Error{TreeID: id, Err: errors.Errorf("file %q has nil blob list", node.Name)}) } for b, blobID := range node.Content { if blobID.IsNull() { errs = append(errs, Error{TreeID: id, Err: errors.Errorf("file %q blob %d has null ID", node.Name, b)}) continue } blobs = append(blobs, blobID) } case "dir": if node.Subtree == nil { errs = append(errs, Error{TreeID: id, Err: errors.Errorf("dir node %q has no subtree", node.Name)}) continue } if node.Subtree.IsNull() { errs = append(errs, Error{TreeID: id, Err: errors.Errorf("dir node %q subtree id is null", node.Name)}) continue } case "symlink", "socket", "chardev", "dev", "fifo": // nothing to check default: errs = append(errs, Error{TreeID: id, Err: errors.Errorf("node %q with invalid type %q", node.Name, node.Type)}) } if node.Name == "" { errs = append(errs, Error{TreeID: id, Err: errors.New("node with empty name")}) } } for _, blobID := range blobs { c.blobRefs.Lock() c.blobRefs.M[blobID]++ debug.Log("blob %v refcount %d", blobID.Str(), c.blobRefs.M[blobID]) c.blobRefs.Unlock() if !c.blobs.Has(blobID) { debug.Log("tree %v references blob %v which isn't contained in index", id.Str(), blobID.Str()) errs = append(errs, Error{TreeID: id, BlobID: blobID, Err: errors.New("not found in index")}) } } return errs }
// FindLatestSnapshot finds latest snapshot with optional target/directory and source filters func FindLatestSnapshot(repo Repository, targets []string, source string) (ID, error) { var ( latest time.Time latestID ID found bool ) for snapshotID := range repo.List(SnapshotFile, make(chan struct{})) { snapshot, err := LoadSnapshot(repo, snapshotID) if err != nil { return ID{}, errors.Errorf("Error listing snapshot: %v", err) } if snapshot.Time.After(latest) && SamePaths(snapshot.Paths, targets) && (source == "" || source == snapshot.Hostname) { latest = snapshot.Time latestID = snapshotID found = true } } if !found { return ID{}, ErrNoSnapshotFound } return latestID, nil }
// Lookup queries the index for the blob ID and returns a restic.PackedBlob. func (idx *Index) Lookup(id restic.ID, tpe restic.BlobType) (blobs []restic.PackedBlob, err error) { idx.m.Lock() defer idx.m.Unlock() h := restic.BlobHandle{ID: id, Type: tpe} if packs, ok := idx.pack[h]; ok { blobs = make([]restic.PackedBlob, 0, len(packs)) for _, p := range packs { debug.Log("id %v found in pack %v at %d, length %d", id.Str(), p.packID.Str(), p.offset, p.length) blob := restic.PackedBlob{ Blob: restic.Blob{ Type: tpe, Length: p.length, ID: id, Offset: p.offset, }, PackID: p.packID, } blobs = append(blobs, blob) } return blobs, nil } debug.Log("id %v not found", id.Str()) return nil, errors.Errorf("id %v not found in index", id) }
// writeHeader constructs and writes the header to wr. func (p *Packer) writeHeader(wr io.Writer) (bytesWritten uint, err error) { for _, b := range p.blobs { entry := headerEntry{ Length: uint32(b.Length), ID: b.ID, } switch b.Type { case restic.DataBlob: entry.Type = 0 case restic.TreeBlob: entry.Type = 1 default: return 0, errors.Errorf("invalid blob type %v", b.Type) } err := binary.Write(wr, binary.LittleEndian, entry) if err != nil { return bytesWritten, errors.Wrap(err, "binary.Write") } bytesWritten += entrySize } return }
// Save stores data in the backend at the handle. func (b *restBackend) Save(h restic.Handle, p []byte) (err error) { if err := h.Valid(); err != nil { return err } <-b.connChan resp, err := b.client.Post(restPath(b.url, h), "binary/octet-stream", bytes.NewReader(p)) b.connChan <- struct{}{} if resp != nil { defer func() { io.Copy(ioutil.Discard, resp.Body) e := resp.Body.Close() if err == nil { err = errors.Wrap(e, "Close") } }() } if err != nil { return errors.Wrap(err, "client.Post") } if resp.StatusCode != 200 { return errors.Errorf("unexpected HTTP response code %v", resp.StatusCode) } return nil }
// Valid returns an error if h is not valid. func (h Handle) Valid() error { if h.Type == "" { return errors.New("type is empty") } switch h.Type { case DataFile: case KeyFile: case LockFile: case SnapshotFile: case IndexFile: case ConfigFile: default: return errors.Errorf("invalid Type %q", h.Type) } if h.Type == ConfigFile { return nil } if h.Name == "" { return errors.New("invalid Name") } return nil }
// Stat returns information about a blob. func (b *restBackend) Stat(h restic.Handle) (restic.FileInfo, error) { if err := h.Valid(); err != nil { return restic.FileInfo{}, err } <-b.connChan resp, err := b.client.Head(restPath(b.url, h)) b.connChan <- struct{}{} if err != nil { return restic.FileInfo{}, errors.Wrap(err, "client.Head") } io.Copy(ioutil.Discard, resp.Body) if err = resp.Body.Close(); err != nil { return restic.FileInfo{}, errors.Wrap(err, "Close") } if resp.StatusCode != 200 { return restic.FileInfo{}, errors.Errorf("unexpected HTTP response code %v", resp.StatusCode) } if resp.ContentLength < 0 { return restic.FileInfo{}, errors.New("negative content length") } bi := restic.FileInfo{ Size: resp.ContentLength, } return bi, nil }
// FindBlob returns a list of packs and positions the blob can be found in. func (idx *Index) FindBlob(h restic.BlobHandle) ([]Location, error) { blob, ok := idx.Blobs[h] if !ok { return nil, ErrBlobNotFound } result := make([]Location, 0, len(blob.Packs)) for packID := range blob.Packs { pack, ok := idx.Packs[packID] if !ok { return nil, errors.Errorf("pack %v not found in index", packID.Str()) } for _, entry := range pack.Entries { if entry.Type != h.Type { continue } if !entry.ID.Equal(h.ID) { continue } loc := Location{PackID: packID, Blob: entry} result = append(result, loc) } } return result, nil }
// Rename temp file to final name according to type and name. func (r *SFTP) renameFile(oldname string, t restic.FileType, name string) error { filename := r.filename(t, name) // create directories if necessary if t == restic.DataFile { err := r.mkdirAll(path.Dir(filename), backend.Modes.Dir) if err != nil { return err } } // test if new file exists if _, err := r.c.Lstat(filename); err == nil { return errors.Errorf("Close(): file %v already exists", filename) } err := r.c.Rename(oldname, filename) if err != nil { return errors.Wrap(err, "Rename") } // set mode to read-only fi, err := r.c.Lstat(filename) if err != nil { return errors.Wrap(err, "Lstat") } err = r.c.Chmod(filename, fi.Mode()&os.FileMode(^uint32(0222))) return errors.Wrap(err, "Chmod") }
// savePacker stores p in the backend. func (r *Repository) savePacker(p *pack.Packer) error { debug.Log("save packer with %d blobs\n", p.Count()) n, err := p.Finalize() if err != nil { return err } tmpfile := p.Writer().(*os.File) f, err := fs.Open(tmpfile.Name()) if err != nil { return errors.Wrap(err, "Open") } data := make([]byte, n) m, err := io.ReadFull(f, data) if err != nil { return errors.Wrap(err, "ReadFul") } if uint(m) != n { return errors.Errorf("read wrong number of bytes from %v: want %v, got %v", tmpfile.Name(), n, m) } if err = f.Close(); err != nil { return errors.Wrap(err, "Close") } id := restic.Hash(data) h := restic.Handle{Type: restic.DataFile, Name: id.String()} err = r.be.Save(h, data) if err != nil { debug.Log("Save(%v) error: %v", h, err) return err } debug.Log("saved as %v", h) err = fs.Remove(tmpfile.Name()) if err != nil { return errors.Wrap(err, "Remove") } // update blobs in the index for _, b := range p.Blobs() { debug.Log(" updating blob %v to pack %v", b.ID.Str(), id.Str()) r.idx.Current().Store(restic.PackedBlob{ Blob: restic.Blob{ Type: b.Type, ID: b.ID, Offset: b.Offset, Length: uint(b.Length), }, PackID: id, }) } return nil }
// checkPack reads a pack and checks the integrity of all blobs. func checkPack(r restic.Repository, id restic.ID) error { debug.Log("checking pack %v", id.Str()) h := restic.Handle{Type: restic.DataFile, Name: id.String()} buf, err := backend.LoadAll(r.Backend(), h, nil) if err != nil { return err } hash := restic.Hash(buf) if !hash.Equal(id) { debug.Log("Pack ID does not match, want %v, got %v", id.Str(), hash.Str()) return errors.Errorf("Pack ID does not match, want %v, got %v", id.Str(), hash.Str()) } blobs, err := pack.List(r.Key(), bytes.NewReader(buf), int64(len(buf))) if err != nil { return err } var errs []error for i, blob := range blobs { debug.Log(" check blob %d: %v", i, blob.ID.Str()) plainBuf := make([]byte, blob.Length) n, err := crypto.Decrypt(r.Key(), plainBuf, buf[blob.Offset:blob.Offset+blob.Length]) if err != nil { debug.Log(" error decrypting blob %v: %v", blob.ID.Str(), err) errs = append(errs, errors.Errorf("blob %v: %v", i, err)) continue } plainBuf = plainBuf[:n] hash := restic.Hash(plainBuf) if !hash.Equal(blob.ID) { debug.Log(" Blob ID does not match, want %v, got %v", blob.ID.Str(), hash.Str()) errs = append(errs, errors.Errorf("Blob ID does not match, want %v, got %v", blob.ID.Str(), hash.Str())) continue } } if len(errs) > 0 { return errors.Errorf("pack %v contains %v errors: %v", id.Str(), len(errs), errs) } return nil }
// Decrypt verifies and decrypts the ciphertext. Ciphertext must be in the form // IV || Ciphertext || MAC. plaintext and ciphertext may point to (exactly) the // same slice. func Decrypt(ks *Key, plaintext []byte, ciphertextWithMac []byte) (int, error) { if !ks.Valid() { return 0, errors.New("invalid key") } // check for plausible length if len(ciphertextWithMac) < ivSize+macSize { panic("trying to decrypt invalid data: ciphertext too small") } // check buffer length for plaintext plaintextLength := len(ciphertextWithMac) - ivSize - macSize if len(plaintext) < plaintextLength { return 0, errors.Errorf("plaintext buffer too small, %d < %d", len(plaintext), plaintextLength) } // extract mac l := len(ciphertextWithMac) - macSize ciphertextWithIV, mac := ciphertextWithMac[:l], ciphertextWithMac[l:] // verify mac if !poly1305Verify(ciphertextWithIV[ivSize:], ciphertextWithIV[:ivSize], &ks.MAC, mac) { return 0, ErrUnauthenticated } // extract iv iv, ciphertext := ciphertextWithIV[:ivSize], ciphertextWithIV[ivSize:] if len(ciphertext) != plaintextLength { return 0, errors.Errorf("plaintext and ciphertext lengths do not match: %d != %d", len(ciphertext), plaintextLength) } // decrypt data c, err := aes.NewCipher(ks.Encrypt[:]) if err != nil { panic(fmt.Sprintf("unable to create cipher: %v", err)) } // decrypt e := cipher.NewCTR(c, iv) plaintext = plaintext[:len(ciphertext)] e.XORKeyStream(plaintext, ciphertext) return plaintextLength, nil }
// Return temp directory in correct directory for this backend. func (r *SFTP) tempFile() (string, *sftp.File, error) { // choose random suffix buf := make([]byte, tempfileRandomSuffixLength) _, err := io.ReadFull(rand.Reader, buf) if err != nil { return "", nil, errors.Errorf("unable to read %d random bytes for tempfile name: %v", tempfileRandomSuffixLength, err) } // construct tempfile name name := Join(r.p, backend.Paths.Temp, "temp-"+hex.EncodeToString(buf)) // create file in temp dir f, err := r.c.Create(name) if err != nil { return "", nil, errors.Errorf("creating tempfile %q failed: %v", name, err) } return name, f, nil }
func startClient(program string, args ...string) (*SFTP, error) { // Connect to a remote host and request the sftp subsystem via the 'ssh' // command. This assumes that passwordless login is correctly configured. cmd := exec.Command(program, args...) // prefix the errors with the program name stderr, err := cmd.StderrPipe() if err != nil { return nil, errors.Wrap(err, "cmd.StderrPipe") } go func() { sc := bufio.NewScanner(stderr) for sc.Scan() { fmt.Fprintf(os.Stderr, "subprocess %v: %v\n", program, sc.Text()) } }() // ignore signals sent to the parent (e.g. SIGINT) cmd.SysProcAttr = ignoreSigIntProcAttr() // get stdin and stdout wr, err := cmd.StdinPipe() if err != nil { return nil, errors.Wrap(err, "cmd.StdinPipe") } rd, err := cmd.StdoutPipe() if err != nil { return nil, errors.Wrap(err, "cmd.StdoutPipe") } // start the process if err := cmd.Start(); err != nil { return nil, errors.Wrap(err, "cmd.Start") } // wait in a different goroutine ch := make(chan error, 1) go func() { err := cmd.Wait() debug.Log("ssh command exited, err %v", err) ch <- errors.Wrap(err, "cmd.Wait") }() // open the SFTP session client, err := sftp.NewClientPipe(rd, wr) if err != nil { return nil, errors.Errorf("unable to start the sftp session, error: %v", err) } return &SFTP{c: client, cmd: cmd, result: ch}, nil }
// LookupSize queries all known Indexes for the ID and returns the first match. func (mi *MasterIndex) LookupSize(id restic.ID, tpe restic.BlobType) (uint, error) { mi.idxMutex.RLock() defer mi.idxMutex.RUnlock() for _, idx := range mi.idx { length, err := idx.LookupSize(id, tpe) if err == nil { return length, nil } } return 0, errors.Errorf("id %v not found in any index", id) }
func waitForResults(resultChannels [](<-chan saveResult)) ([]saveResult, error) { results := []saveResult{} for _, ch := range resultChannels { results = append(results, <-ch) } if len(results) != len(resultChannels) { return nil, errors.Errorf("chunker returned %v chunks, but only %v blobs saved", len(resultChannels), len(results)) } return results, nil }
// KDF derives encryption and message authentication keys from the password // using the supplied parameters N, R and P and the Salt. func KDF(p KDFParams, salt []byte, password string) (*Key, error) { if len(salt) != saltLength { return nil, errors.Errorf("scrypt() called with invalid salt bytes (len %d)", len(salt)) } // make sure we have valid parameters params := sscrypt.Params{ N: p.N, R: p.R, P: p.P, DKLen: sscrypt.DefaultParams.DKLen, SaltLen: len(salt), } if err := params.Check(); err != nil { return nil, errors.Wrap(err, "Check") } derKeys := &Key{} keybytes := macKeySize + aesKeySize scryptKeys, err := scrypt.Key([]byte(password), salt, p.N, p.R, p.P, keybytes) if err != nil { return nil, errors.Wrap(err, "scrypt.Key") } if len(scryptKeys) != keybytes { return nil, errors.Errorf("invalid numbers of bytes expanded from scrypt(): %d", len(scryptKeys)) } // first 32 byte of scrypt output is the encryption key copy(derKeys.Encrypt[:], scryptKeys[:aesKeySize]) // next 32 byte of scrypt output is the mac key, in the form k||r macKeyFromSlice(&derKeys.MAC, scryptKeys[aesKeySize:]) return derKeys, nil }
func loadTreeFromSnapshot(repo restic.Repository, id restic.ID) (restic.ID, error) { sn, err := restic.LoadSnapshot(repo, id) if err != nil { debug.Log("error loading snapshot %v: %v", id.Str(), err) return restic.ID{}, err } if sn.Tree == nil { debug.Log("snapshot %v has no tree", id.Str()) return restic.ID{}, errors.Errorf("snapshot %v has no tree", id) } return *sn.Tree, nil }
// Load returns the data stored in the backend for h at the given offset // and saves it in p. Load has the same semantics as io.ReaderAt. func (b *restBackend) Load(h restic.Handle, p []byte, off int64) (n int, err error) { if err := h.Valid(); err != nil { return 0, err } // invert offset if off < 0 { info, err := b.Stat(h) if err != nil { return 0, errors.Wrap(err, "Stat") } if -off > info.Size { off = 0 } else { off = info.Size + off } } req, err := http.NewRequest("GET", restPath(b.url, h), nil) if err != nil { return 0, errors.Wrap(err, "http.NewRequest") } req.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", off, off+int64(len(p)))) <-b.connChan resp, err := b.client.Do(req) b.connChan <- struct{}{} if resp != nil { defer func() { io.Copy(ioutil.Discard, resp.Body) e := resp.Body.Close() if err == nil { err = errors.Wrap(e, "Close") } }() } if err != nil { return 0, errors.Wrap(err, "client.Do") } if resp.StatusCode != 200 && resp.StatusCode != 206 { return 0, errors.Errorf("unexpected HTTP response code %v", resp.StatusCode) } return io.ReadFull(resp.Body, p) }
// List returns the list of entries found in a pack file. func List(k *crypto.Key, rd io.ReaderAt, size int64) (entries []restic.Blob, err error) { buf, err := readHeader(rd, size) if err != nil { return nil, err } n, err := crypto.Decrypt(k, buf, buf) if err != nil { return nil, err } buf = buf[:n] hdrRd := bytes.NewReader(buf) pos := uint(0) for { e := headerEntry{} err = binary.Read(hdrRd, binary.LittleEndian, &e) if errors.Cause(err) == io.EOF { break } if err != nil { return nil, errors.Wrap(err, "binary.Read") } entry := restic.Blob{ Length: uint(e.Length), ID: e.ID, Offset: pos, } switch e.Type { case 0: entry.Type = restic.DataBlob case 1: entry.Type = restic.TreeBlob default: return nil, errors.Errorf("invalid type %d", e.Type) } entries = append(entries, entry) pos += uint(e.Length) } return entries, nil }
// ParseConfig parses the string s and extracts the sftp config. The // supported configuration formats are sftp://user@host/directory // (with an optional port sftp://user@host:port/directory) and // sftp:user@host:directory. The directory will be path Cleaned and // can be an absolute path if it starts with a '/' // (e.g. sftp://user@host//absolute and sftp:user@host:/absolute). func ParseConfig(s string) (interface{}, error) { var user, host, dir string switch { case strings.HasPrefix(s, "sftp://"): // parse the "sftp://user@host/path" url format url, err := url.Parse(s) if err != nil { return nil, errors.Wrap(err, "url.Parse") } if url.User != nil { user = url.User.Username() } host = url.Host dir = url.Path if dir == "" { return nil, errors.Errorf("invalid backend %q, no directory specified", s) } dir = dir[1:] case strings.HasPrefix(s, "sftp:"): // parse the sftp:user@host:path format, which means we'll get // "user@host:path" in s s = s[5:] // split user@host and path at the colon data := strings.SplitN(s, ":", 2) if len(data) < 2 { return nil, errors.New("sftp: invalid format, hostname or path not found") } host = data[0] dir = data[1] // split user and host at the "@" data = strings.SplitN(host, "@", 2) if len(data) == 2 { user = data[0] host = data[1] } default: return nil, errors.New(`invalid format, does not start with "sftp:"`) } return Config{ User: user, Host: host, Dir: path.Clean(dir), }, nil }
// Lookup queries all known Indexes for the ID and returns the first match. func (mi *MasterIndex) Lookup(id restic.ID, tpe restic.BlobType) (blobs []restic.PackedBlob, err error) { mi.idxMutex.RLock() defer mi.idxMutex.RUnlock() debug.Log("looking up id %v, tpe %v", id.Str(), tpe) for _, idx := range mi.idx { blobs, err = idx.Lookup(id, tpe) if err == nil { debug.Log("MasterIndex.Lookup", "found id %v: %v", id.Str(), blobs) return } } debug.Log("id %v not found in any index", id.Str()) return nil, errors.Errorf("id %v not found in any index", id) }
// RemovePack deletes a pack from the index. func (idx *Index) RemovePack(id restic.ID) error { if _, ok := idx.Packs[id]; !ok { return errors.Errorf("pack %v not found in the index", id.Str()) } for _, blob := range idx.Packs[id].Entries { h := restic.BlobHandle{ID: blob.ID, Type: blob.Type} idx.Blobs[h].Packs.Delete(id) if len(idx.Blobs[h].Packs) == 0 { delete(idx.Blobs, h) } } delete(idx.Packs, id) return nil }
// Open opens an sftp backend. When the command is started via // exec.Command, it is expected to speak sftp on stdin/stdout. The backend // is expected at the given path. `dir` must be delimited by forward slashes // ("/"), which is required by sftp. func Open(dir string, program string, args ...string) (*SFTP, error) { debug.Log("open backend with program %v, %v at %v", program, args, dir) sftp, err := startClient(program, args...) if err != nil { debug.Log("unable to start program: %v", err) return nil, err } // test if all necessary dirs and files are there for _, d := range paths(dir) { if _, err := sftp.c.Lstat(d); err != nil { return nil, errors.Errorf("%s does not exist", d) } } sftp.p = dir return sftp, nil }
func (res *Restorer) restoreTo(dst string, dir string, treeID ID) error { tree, err := res.repo.LoadTree(treeID) if err != nil { return res.Error(dir, nil, err) } for _, node := range tree.Nodes { selectedForRestore := res.SelectFilter(filepath.Join(dir, node.Name), filepath.Join(dst, dir, node.Name), node) debug.Log("SelectForRestore returned %v", selectedForRestore) if selectedForRestore { err := res.restoreNodeTo(node, dir, dst) if err != nil { return err } } if node.Type == "dir" { if node.Subtree == nil { return errors.Errorf("Dir without subtree in tree %v", treeID.Str()) } subp := filepath.Join(dir, node.Name) err = res.restoreTo(dst, subp, *node.Subtree) if err != nil { err = res.Error(subp, node, err) if err != nil { return err } } if selectedForRestore { // Restore directory timestamp at the end. If we would do it earlier, restoring files within // the directory would overwrite the timestamp of the directory they are in. if err := node.RestoreTimestamps(filepath.Join(dst, dir, node.Name)); err != nil { return err } } } } return nil }
// Save stores data in the backend at the handle. func (b *Local) Save(h restic.Handle, p []byte) (err error) { debug.Log("Save %v, length %v", h, len(p)) if err := h.Valid(); err != nil { return err } tmpfile, err := writeToTempfile(filepath.Join(b.p, backend.Paths.Temp), p) debug.Log("saved %v (%d bytes) to %v", h, len(p), tmpfile) if err != nil { return err } filename := filename(b.p, h.Type, h.Name) // test if new path already exists if _, err := fs.Stat(filename); err == nil { return errors.Errorf("Rename(): file %v already exists", filename) } // create directories if necessary, ignore errors if h.Type == restic.DataFile { err = fs.MkdirAll(filepath.Dir(filename), backend.Modes.Dir) if err != nil { return errors.Wrap(err, "MkdirAll") } } err = fs.Rename(tmpfile, filename) debug.Log("save %v: rename %v -> %v: %v", h, filepath.Base(tmpfile), filepath.Base(filename), err) if err != nil { return errors.Wrap(err, "Rename") } // set mode to read-only fi, err := fs.Stat(filename) if err != nil { return errors.Wrap(err, "Stat") } return setNewFileMode(filename, fi) }
// generatePackList returns a list of packs. func (idx *Index) generatePackList() ([]*packJSON, error) { list := []*packJSON{} packs := make(map[restic.ID]*packJSON) for h, packedBlobs := range idx.pack { for _, blob := range packedBlobs { if blob.packID.IsNull() { panic("null pack id") } debug.Log("handle blob %v", h) if blob.packID.IsNull() { debug.Log("blob %v has no packID! (offset %v, length %v)", h, blob.offset, blob.length) return nil, errors.Errorf("unable to serialize index: pack for blob %v hasn't been written yet", h) } // see if pack is already in map p, ok := packs[blob.packID] if !ok { // else create new pack p = &packJSON{ID: blob.packID} // and append it to the list and map list = append(list, p) packs[p.ID] = p } // add blob p.Blobs = append(p.Blobs, blobJSON{ ID: h.ID, Type: h.Type, Offset: blob.offset, Length: blob.length, }) } } debug.Log("done") return list, nil }
// CreateAt creates the node at the given path and restores all the meta data. func (node *Node) CreateAt(path string, repo Repository) error { debug.Log("create node %v at %v", node.Name, path) switch node.Type { case "dir": if err := node.createDirAt(path); err != nil { return err } case "file": if err := node.createFileAt(path, repo); err != nil { return err } case "symlink": if err := node.createSymlinkAt(path); err != nil { return err } case "dev": if err := node.createDevAt(path); err != nil { return err } case "chardev": if err := node.createCharDevAt(path); err != nil { return err } case "fifo": if err := node.createFifoAt(path); err != nil { return err } case "socket": return nil default: return errors.Errorf("filetype %q not implemented!\n", node.Type) } err := node.restoreMetadata(path) if err != nil { debug.Log("restoreMetadata(%s) error %v", path, err) } return err }