restic/internal/pack/pack.go

319 lines
7.9 KiB
Go
Raw Normal View History

2015-04-26 13:36:49 +00:00
package pack
import (
"encoding/binary"
"fmt"
"io"
"sync"
2017-07-23 12:21:03 +00:00
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/errors"
2017-07-24 15:42:25 +00:00
"github.com/restic/restic/internal/restic"
2017-07-23 12:21:03 +00:00
"github.com/restic/restic/internal/crypto"
2015-04-26 13:36:49 +00:00
)
// Packer is used to create a new Pack.
type Packer struct {
2016-08-31 20:39:36 +00:00
blobs []restic.Blob
2015-04-26 13:36:49 +00:00
bytes uint
k *crypto.Key
wr io.Writer
2015-04-26 13:36:49 +00:00
m sync.Mutex
}
// NewPacker returns a new Packer that can be used to pack blobs together.
func NewPacker(k *crypto.Key, wr io.Writer) *Packer {
return &Packer{k: k, wr: wr}
2015-04-26 13:36:49 +00:00
}
// Add saves the data read from rd as a new blob to the packer. Returned is the
// number of bytes written to the pack.
2016-08-31 18:58:57 +00:00
func (p *Packer) Add(t restic.BlobType, id restic.ID, data []byte) (int, error) {
2015-04-26 13:36:49 +00:00
p.m.Lock()
defer p.m.Unlock()
2020-11-05 20:52:34 +00:00
c := restic.Blob{BlobHandle: restic.BlobHandle{Type: t, ID: id}}
2015-04-26 13:36:49 +00:00
n, err := p.wr.Write(data)
c.Length = uint(n)
2015-04-26 13:36:49 +00:00
c.Offset = p.bytes
p.bytes += uint(n)
p.blobs = append(p.blobs, c)
2016-08-29 20:16:58 +00:00
return n, errors.Wrap(err, "Write")
2015-04-26 13:36:49 +00:00
}
2020-11-16 03:18:55 +00:00
var EntrySize = uint(binary.Size(restic.BlobType(0)) + headerLengthSize + len(restic.ID{}))
2015-04-26 13:36:49 +00:00
// headerEntry describes the format of header entries. It serves only as
// documentation.
2015-04-26 13:36:49 +00:00
type headerEntry struct {
Type uint8
2015-04-26 13:36:49 +00:00
Length uint32
ID restic.ID
2015-04-26 13:36:49 +00:00
}
// Finalize writes the header for all added blobs and finalizes the pack.
// Returned are the number of bytes written, including the header.
func (p *Packer) Finalize() (uint, error) {
2015-04-26 13:36:49 +00:00
p.m.Lock()
defer p.m.Unlock()
2016-01-24 18:30:14 +00:00
bytesWritten := p.bytes
2015-04-26 13:36:49 +00:00
header, err := p.makeHeader()
2015-04-29 22:36:36 +00:00
if err != nil {
return 0, err
2015-04-26 13:36:49 +00:00
}
encryptedHeader := make([]byte, 0, len(header)+p.k.Overhead()+p.k.NonceSize())
2017-10-29 10:33:57 +00:00
nonce := crypto.NewRandomNonce()
encryptedHeader = append(encryptedHeader, nonce...)
encryptedHeader = p.k.Seal(encryptedHeader, nonce, header, nil)
2015-04-29 22:41:11 +00:00
2016-01-24 18:30:14 +00:00
// append the header
n, err := p.wr.Write(encryptedHeader)
2015-04-26 13:36:49 +00:00
if err != nil {
2016-08-29 20:16:58 +00:00
return 0, errors.Wrap(err, "Write")
2016-01-24 18:30:14 +00:00
}
hdrBytes := restic.CiphertextLength(len(header))
if n != hdrBytes {
return 0, errors.New("wrong number of bytes written")
2015-04-26 13:36:49 +00:00
}
bytesWritten += uint(hdrBytes)
2015-04-26 13:36:49 +00:00
// write length
2020-11-16 03:18:55 +00:00
err = binary.Write(p.wr, binary.LittleEndian, uint32(restic.CiphertextLength(len(p.blobs)*int(EntrySize))))
2015-04-26 13:36:49 +00:00
if err != nil {
2016-08-29 20:16:58 +00:00
return 0, errors.Wrap(err, "binary.Write")
2015-04-26 13:36:49 +00:00
}
2015-04-29 22:19:56 +00:00
bytesWritten += uint(binary.Size(uint32(0)))
2015-04-26 13:36:49 +00:00
p.bytes = uint(bytesWritten)
return bytesWritten, nil
2015-04-26 13:36:49 +00:00
}
// makeHeader constructs the header for p.
func (p *Packer) makeHeader() ([]byte, error) {
2020-11-16 03:18:55 +00:00
buf := make([]byte, 0, len(p.blobs)*int(EntrySize))
2015-04-29 22:36:36 +00:00
for _, b := range p.blobs {
switch b.Type {
2016-08-31 18:58:57 +00:00
case restic.DataBlob:
buf = append(buf, 0)
2016-08-31 18:58:57 +00:00
case restic.TreeBlob:
buf = append(buf, 1)
default:
return nil, errors.Errorf("invalid blob type %v", b.Type)
2015-04-29 22:36:36 +00:00
}
var lenLE [4]byte
binary.LittleEndian.PutUint32(lenLE[:], uint32(b.Length))
buf = append(buf, lenLE[:]...)
buf = append(buf, b.ID[:]...)
2015-04-29 22:36:36 +00:00
}
return buf, nil
2015-04-29 22:36:36 +00:00
}
2015-04-26 13:36:49 +00:00
// Size returns the number of bytes written so far.
func (p *Packer) Size() uint {
p.m.Lock()
defer p.m.Unlock()
return p.bytes
}
// Count returns the number of blobs in this packer.
func (p *Packer) Count() int {
p.m.Lock()
defer p.m.Unlock()
return len(p.blobs)
}
// Blobs returns the slice of blobs that have been written.
2016-08-31 20:39:36 +00:00
func (p *Packer) Blobs() []restic.Blob {
2015-04-26 13:36:49 +00:00
p.m.Lock()
defer p.m.Unlock()
return p.blobs
}
func (p *Packer) String() string {
return fmt.Sprintf("<Packer %d blobs, %d bytes>", len(p.blobs), p.bytes)
}
var (
// we require at least one entry in the header, and one blob for a pack file
2020-11-16 03:18:55 +00:00
minFileSize = EntrySize + crypto.Extension + uint(headerLengthSize)
)
const (
2020-05-01 20:56:34 +00:00
// size of the header-length field at the end of the file; it is a uint32
headerLengthSize = 4
// constant overhead of the header independent of #entries
HeaderSize = headerLengthSize + crypto.Extension
maxHeaderSize = 16 * 1024 * 1024
// number of header enries to download as part of header-length request
eagerEntries = 15
)
2018-02-22 17:37:10 +00:00
// readRecords reads up to max records from the underlying ReaderAt, returning
// the raw header, the total number of records in the header, and any error.
// If the header contains fewer than max entries, the header is truncated to
// the appropriate size.
func readRecords(rd io.ReaderAt, size int64, max int) ([]byte, int, error) {
var bufsize int
2020-11-16 03:18:55 +00:00
bufsize += max * int(EntrySize)
bufsize += crypto.Extension
bufsize += headerLengthSize
if bufsize > int(size) {
bufsize = int(size)
}
2016-08-25 19:51:07 +00:00
b := make([]byte, bufsize)
off := size - int64(bufsize)
if _, err := rd.ReadAt(b, off); err != nil {
return nil, 0, err
}
2018-02-22 17:37:10 +00:00
hlen := binary.LittleEndian.Uint32(b[len(b)-headerLengthSize:])
b = b[:len(b)-headerLengthSize]
2018-02-22 17:37:10 +00:00
debug.Log("header length: %v", hlen)
var err error
switch {
2018-02-22 17:37:10 +00:00
case hlen == 0:
err = InvalidFileError{Message: "header length is zero"}
2018-02-22 17:37:10 +00:00
case hlen < crypto.Extension:
err = InvalidFileError{Message: "header length is too small"}
2020-11-16 03:18:55 +00:00
case (hlen-crypto.Extension)%uint32(EntrySize) != 0:
err = InvalidFileError{Message: "header length is invalid"}
2018-02-22 17:37:10 +00:00
case int64(hlen) > size-int64(headerLengthSize):
err = InvalidFileError{Message: "header is larger than file"}
2018-02-22 17:37:10 +00:00
case int64(hlen) > maxHeaderSize:
err = InvalidFileError{Message: "header is larger than maxHeaderSize"}
}
if err != nil {
return nil, 0, errors.Wrap(err, "readHeader")
}
2020-11-16 03:18:55 +00:00
total := (int(hlen) - crypto.Extension) / int(EntrySize)
2018-02-22 17:37:10 +00:00
if total < max {
// truncate to the beginning of the pack header
b = b[len(b)-int(hlen):]
}
2017-06-08 18:40:12 +00:00
2018-02-22 17:37:10 +00:00
return b, total, nil
}
2016-08-25 19:51:07 +00:00
// readHeader reads the header at the end of rd. size is the length of the
// whole data accessible in rd.
func readHeader(rd io.ReaderAt, size int64) ([]byte, error) {
2017-06-08 19:04:07 +00:00
debug.Log("size: %v", size)
2017-06-08 18:40:12 +00:00
if size < int64(minFileSize) {
2017-06-08 19:04:07 +00:00
err := InvalidFileError{Message: "file is too small"}
2017-06-08 18:40:12 +00:00
return nil, errors.Wrap(err, "readHeader")
}
// assuming extra request is significantly slower than extra bytes download,
// eagerly download eagerEntries header entries as part of header-length request.
// only make second request if actual number of entries is greater than eagerEntries
b, c, err := readRecords(rd, size, eagerEntries)
if err != nil {
2016-08-25 19:51:07 +00:00
return nil, err
}
if c <= eagerEntries {
// eager read sufficed, return what we got
return b, nil
2016-08-23 20:21:29 +00:00
}
b, _, err = readRecords(rd, size, c)
if err != nil {
return nil, err
}
return b, nil
2016-08-25 19:51:07 +00:00
}
2017-06-08 18:40:12 +00:00
// InvalidFileError is return when a file is found that is not a pack file.
type InvalidFileError struct {
Message string
}
func (e InvalidFileError) Error() string {
return e.Message
}
2020-11-16 03:03:45 +00:00
// List returns the list of entries found in a pack file and the length of the
// header (including header size and crypto overhead)
func List(k *crypto.Key, rd io.ReaderAt, size int64) (entries []restic.Blob, hdrSize uint32, err error) {
2016-08-25 19:51:07 +00:00
buf, err := readHeader(rd, size)
if err != nil {
2020-11-16 03:03:45 +00:00
return nil, 0, err
2016-08-25 19:51:07 +00:00
}
2015-04-26 13:36:49 +00:00
2017-10-29 10:33:57 +00:00
if len(buf) < k.NonceSize()+k.Overhead() {
2020-11-16 03:03:45 +00:00
return nil, 0, errors.New("invalid header, too small")
2017-10-29 10:33:57 +00:00
}
2020-11-16 03:03:45 +00:00
hdrSize = headerLengthSize + uint32(len(buf))
2017-10-29 10:33:57 +00:00
nonce, buf := buf[:k.NonceSize()], buf[k.NonceSize():]
buf, err = k.Open(buf[:0], nonce, buf, nil)
2015-04-26 13:36:49 +00:00
if err != nil {
2020-11-16 03:03:45 +00:00
return nil, 0, err
2015-04-26 13:36:49 +00:00
}
2020-11-16 03:18:55 +00:00
entries = make([]restic.Blob, 0, uint(len(buf))/EntrySize)
2017-01-15 14:27:58 +00:00
pos := uint(0)
for len(buf) > 0 {
entry, err := parseHeaderEntry(buf)
if err != nil {
2020-11-16 03:03:45 +00:00
return nil, 0, err
}
entry.Offset = pos
entries = append(entries, entry)
pos += entry.Length
2020-11-16 03:18:55 +00:00
buf = buf[EntrySize:]
2015-04-26 13:36:49 +00:00
}
2020-11-16 03:03:45 +00:00
return entries, hdrSize, nil
2015-04-26 13:36:49 +00:00
}
2020-05-01 20:56:34 +00:00
// PackedSizeOfBlob returns the size a blob actually uses when saved in a pack
func PackedSizeOfBlob(blobLength uint) uint {
2020-11-16 03:18:55 +00:00
return blobLength + EntrySize
2020-05-01 20:56:34 +00:00
}
func parseHeaderEntry(p []byte) (b restic.Blob, err error) {
2020-11-16 03:18:55 +00:00
if uint(len(p)) < EntrySize {
err = errors.Errorf("parseHeaderEntry: buffer of size %d too short", len(p))
return b, err
}
2020-11-16 03:18:55 +00:00
p = p[:EntrySize]
switch p[0] {
case 0:
b.Type = restic.DataBlob
case 1:
b.Type = restic.TreeBlob
default:
return b, errors.Errorf("invalid type %d", p[0])
}
b.Length = uint(binary.LittleEndian.Uint32(p[1:5]))
copy(b.ID[:], p[5:])
return b, nil
}