Files
kopia/open.go
Jarek Kowalski c35037601d repo: embed format block bytes in all pack indexes
This allows format block to be recovered if any block
indexes survives. The block is not encrypted, but has HMAC is
followed by 2-byte size.
2019-01-12 14:24:09 -08:00

209 lines
5.3 KiB
Go

package repo
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"path/filepath"
"github.com/kopia/repo/block"
"github.com/kopia/repo/internal/repologging"
"github.com/kopia/repo/manifest"
"github.com/kopia/repo/object"
"github.com/kopia/repo/storage"
"github.com/kopia/repo/storage/logging"
)
var (
log = repologging.Logger("kopia/repo")
)
// Options provides configuration parameters for connection to a repository.
type Options struct {
TraceStorage func(f string, args ...interface{}) // Logs all storage access using provided Printf-style function
ObjectManagerOptions object.ManagerOptions
}
// Open opens a Repository specified in the configuration file.
func Open(ctx context.Context, configFile string, password string, options *Options) (rep *Repository, err error) {
log.Debugf("opening repository from %v", configFile)
defer func() {
if err == nil {
log.Debugf("opened repository")
} else {
log.Errorf("failed to open repository: %v", err)
}
}()
if options == nil {
options = &Options{}
}
configFile, err = filepath.Abs(configFile)
if err != nil {
return nil, err
}
log.Debugf("loading config from file: %v", configFile)
lc, err := loadConfigFromFile(configFile)
if err != nil {
return nil, err
}
log.Debugf("opening storage: %v", lc.Storage.Type)
st, err := storage.NewStorage(ctx, lc.Storage)
if err != nil {
return nil, fmt.Errorf("cannot open storage: %v", err)
}
if options.TraceStorage != nil {
st = logging.NewWrapper(st, logging.Prefix("[STORAGE] "), logging.Output(options.TraceStorage))
}
r, err := OpenWithConfig(ctx, st, lc, password, options, lc.Caching)
if err != nil {
st.Close(ctx) //nolint:errcheck
return nil, err
}
r.ConfigFile = configFile
return r, nil
}
// OpenWithConfig opens the repository with a given configuration, avoiding the need for a config file.
func OpenWithConfig(ctx context.Context, st storage.Storage, lc *LocalConfig, password string, options *Options, caching block.CachingOptions) (*Repository, error) {
log.Debugf("reading encrypted format block")
// Read cache block, potentially from cache.
fb, err := readAndCacheFormatBlockBytes(ctx, st, caching.CacheDirectory)
if err != nil {
return nil, fmt.Errorf("unable to read format block: %v", err)
}
f, err := parseFormatBlock(fb)
if err != nil {
return nil, fmt.Errorf("can't parse format block: %v", err)
}
fb, err = addFormatBlockChecksumAndLength(fb)
if err != nil {
return nil, fmt.Errorf("unable to add checksum")
}
masterKey, err := f.deriveMasterKeyFromPassword(password)
if err != nil {
return nil, err
}
repoConfig, err := f.decryptFormatBytes(masterKey)
if err != nil {
return nil, fmt.Errorf("unable to decrypt repository config: %v", err)
}
caching.HMACSecret = deriveKeyFromMasterKey(masterKey, f.UniqueID, []byte("local-cache-integrity"), 16)
fo := repoConfig.FormattingOptions
if fo.MaxPackSize == 0 {
fo.MaxPackSize = repoConfig.MaxBlockSize
}
log.Debugf("initializing block manager")
bm, err := block.NewManager(ctx, st, fo, caching, fb)
if err != nil {
return nil, fmt.Errorf("unable to open block manager: %v", err)
}
log.Debugf("initializing object manager")
om, err := object.NewObjectManager(ctx, bm, repoConfig.Format, options.ObjectManagerOptions)
if err != nil {
return nil, fmt.Errorf("unable to open object manager: %v", err)
}
log.Debugf("initializing manifest manager")
manifests, err := manifest.NewManager(ctx, bm)
if err != nil {
return nil, fmt.Errorf("unable to open manifests: %v", err)
}
return &Repository{
Blocks: bm,
Objects: om,
Storage: st,
Manifests: manifests,
CacheDirectory: caching.CacheDirectory,
UniqueID: f.UniqueID,
formatBlock: f,
masterKey: masterKey,
}, nil
}
// SetCachingConfig changes caching configuration for a given repository config file.
func SetCachingConfig(ctx context.Context, configFile string, opt block.CachingOptions) error {
configFile, err := filepath.Abs(configFile)
if err != nil {
return err
}
lc, err := loadConfigFromFile(configFile)
if err != nil {
return err
}
st, err := storage.NewStorage(ctx, lc.Storage)
if err != nil {
return fmt.Errorf("cannot open storage: %v", err)
}
fb, err := readAndCacheFormatBlockBytes(ctx, st, "")
if err != nil {
return fmt.Errorf("can't read format block: %v", err)
}
f, err := parseFormatBlock(fb)
if err != nil {
return fmt.Errorf("can't parse format block: %v", err)
}
if err = setupCaching(configFile, lc, opt, f.UniqueID); err != nil {
return fmt.Errorf("unable to set up caching: %v", err)
}
d, err := json.MarshalIndent(&lc, "", " ")
if err != nil {
return err
}
if err := ioutil.WriteFile(configFile, d, 0600); err != nil {
return nil
}
return nil
}
func readAndCacheFormatBlockBytes(ctx context.Context, st storage.Storage, cacheDirectory string) ([]byte, error) {
cachedFile := filepath.Join(cacheDirectory, "kopia.repository")
if cacheDirectory != "" {
b, err := ioutil.ReadFile(cachedFile)
if err == nil {
// read from cache.
return b, nil
}
}
b, err := st.GetBlock(ctx, FormatBlockID, 0, -1)
if err != nil {
return nil, err
}
if cacheDirectory != "" {
if err := ioutil.WriteFile(cachedFile, b, 0600); err != nil {
log.Warningf("warning: unable to write cache: %v", err)
}
}
return b, nil
}