Files
kopia/open.go

195 lines
5.0 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.
f, err := readAndCacheFormatBlock(ctx, st, caching.CacheDirectory)
if err != nil {
return nil, fmt.Errorf("unable to read format block: %v", err)
}
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)
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,
}, 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)
}
f, err := readAndCacheFormatBlock(ctx, st, "")
if err != nil {
return fmt.Errorf("can't read 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 readAndCacheFormatBlock(ctx context.Context, st storage.Storage, cacheDirectory string) (*formatBlock, error) {
cachedFile := filepath.Join(cacheDirectory, "kopia.repository")
if cacheDirectory != "" {
b, err := ioutil.ReadFile(cachedFile)
if err == nil {
// read from cache.
return parseFormatBlock(b)
}
}
b, err := st.GetBlock(ctx, FormatBlockID, 0, -1)
if err != nil {
return nil, err
}
// block successfully read from storage.
f, err := parseFormatBlock(b)
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 f, nil
}