1// Copyright 2018 Keybase Inc. All rights reserved.
2// Use of this source code is governed by a BSD
3// license that can be found in the LICENSE file.
4
5package libkbfs
6
7import (
8	"context"
9	"sync"
10
11	"github.com/keybase/client/go/kbfs/kbfsblock"
12	"github.com/keybase/client/go/kbfs/kbfscodec"
13	"github.com/keybase/client/go/kbfs/ldbutils"
14	"github.com/keybase/client/go/logger"
15	"github.com/pkg/errors"
16	ldberrors "github.com/syndtr/goleveldb/leveldb/errors"
17)
18
19// XattrType represents the xattr type.
20type XattrType int
21
22// New types can only be added at end.
23const (
24	_ XattrType = iota
25	XattrAppleQuarantine
26)
27
28const (
29	initialBlockMetadataStoreVersion uint64 = 1
30	currentBlockMetadataStoreVersion uint64 = initialBlockMetadataStoreVersion
31	blockMetadataFolderName          string = "kbfs_block_metadata"
32	blockMetadataDbFilename          string = "diskBlockMetadata.leveldb"
33)
34
35type diskBlockMetadataStoreConfig interface {
36	Codec() kbfscodec.Codec
37	MakeLogger(module string) logger.Logger
38	StorageRoot() string
39	Mode() InitMode
40}
41
42// diskBlockMetadataStore interacts with BlockMetadata data storage on disk.
43type diskBlockMetadataStore struct {
44	log    logger.Logger
45	config diskBlockMetadataStoreConfig
46
47	// Track the hit rate and eviction rate. These are goroutine safe.
48	hitMeter  *ldbutils.CountMeter
49	missMeter *ldbutils.CountMeter
50	putMeter  *ldbutils.CountMeter
51
52	lock       sync.RWMutex
53	db         *ldbutils.LevelDb
54	shutdownCh chan struct{}
55}
56
57// newDiskBlockMetadataStore creates a new disk BlockMetadata storage.
58func newDiskBlockMetadataStore(
59	config diskBlockMetadataStoreConfig, mode InitMode, storageRoot string) (
60	BlockMetadataStore, error) {
61	log := config.MakeLogger("BMS")
62	db, err := ldbutils.OpenVersionedLevelDb(
63		log, storageRoot, blockMetadataFolderName,
64		currentBlockMetadataStoreVersion, blockMetadataDbFilename, mode)
65	if err != nil {
66		return nil, err
67	}
68	return &diskBlockMetadataStore{
69		log:        log,
70		config:     config,
71		hitMeter:   ldbutils.NewCountMeter(),
72		missMeter:  ldbutils.NewCountMeter(),
73		putMeter:   ldbutils.NewCountMeter(),
74		db:         db,
75		shutdownCh: make(chan struct{}),
76	}, err
77}
78
79// Shutdown shuts done this storae.
80func (s *diskBlockMetadataStore) Shutdown() {
81	s.log.Debug("Shutting down diskBlockMetadataStore")
82	s.lock.Lock()
83	defer s.lock.Unlock()
84	// shutdownCh has to be checked under lock, otherwise we can race.
85	select {
86	case <-s.shutdownCh:
87		s.log.Warning("Shutdown called more than once")
88	default:
89	}
90	close(s.shutdownCh)
91	if s.db == nil {
92		return
93	}
94	s.db.Close()
95	s.db = nil
96	s.hitMeter.Shutdown()
97	s.missMeter.Shutdown()
98	s.putMeter.Shutdown()
99}
100
101var _ BlockMetadataStore = (*diskBlockMetadataStore)(nil)
102
103// ErrBlockMetadataStoreShutdown is returned when methods are called on
104// diskBlockMetadataStore when it's already shutdown.
105type ErrBlockMetadataStoreShutdown struct{}
106
107// Error implements the error interface.
108func (ErrBlockMetadataStoreShutdown) Error() string {
109	return "disk block metadata store has shutdown"
110}
111
112// GetMetadata implements the BlockMetadataStore interface.
113func (s *diskBlockMetadataStore) GetMetadata(ctx context.Context,
114	blockID kbfsblock.ID) (value BlockMetadataValue, err error) {
115	s.lock.RLock()
116	defer s.lock.RUnlock()
117
118	select {
119	case <-s.shutdownCh:
120		return BlockMetadataValue{}, ErrBlockMetadataStoreShutdown{}
121	default:
122	}
123
124	encoded, err := s.db.GetWithMeter(blockID.Bytes(), s.hitMeter, s.missMeter)
125	switch errors.Cause(err) {
126	case ldberrors.ErrNotFound:
127		return BlockMetadataValue{}, err
128	case nil:
129		if err = s.config.Codec().Decode(encoded, &value); err != nil {
130			s.log.CWarningf(ctx, "decoding block metadata error: %v", err)
131			return BlockMetadataValue{}, ldberrors.ErrNotFound
132		}
133		return value, nil
134	default:
135		s.log.CWarningf(ctx, "GetMetadata error: %v", err)
136		return BlockMetadataValue{}, ldberrors.ErrNotFound
137	}
138}
139
140// UpdateMetadata implements the BlockMetadataStore interface.
141func (s *diskBlockMetadataStore) UpdateMetadata(ctx context.Context,
142	blockID kbfsblock.ID, updater BlockMetadataUpdater) error {
143	bid := blockID.Bytes()
144
145	s.lock.Lock()
146	defer s.lock.Unlock()
147
148	select {
149	case <-s.shutdownCh:
150		return ErrBlockMetadataStoreShutdown{}
151	default:
152	}
153
154	var value BlockMetadataValue
155	encoded, err := s.db.Get(bid, nil)
156	switch errors.Cause(err) {
157	case ldberrors.ErrNotFound:
158	case nil:
159		if err = s.config.Codec().Decode(encoded, &value); err != nil {
160			s.log.CWarningf(ctx, "decoding block metadata error: %v", err)
161		}
162	default:
163		s.log.CWarningf(ctx, "GetMetadata error: %v", err)
164	}
165
166	if err = updater(&value); err != nil {
167		return err
168	}
169
170	if encoded, err = s.config.Codec().Encode(value); err != nil {
171		return err
172	}
173	return s.db.PutWithMeter(bid, encoded, s.putMeter)
174}
175
176// xattrStore is a wrapper around BlockMetadataStore that handles xattr
177// values.
178type xattrStore struct {
179	store BlockMetadataStore
180
181	// Track the hit rate and eviction rate. These are goroutine safe.
182	hitMeter  *ldbutils.CountMeter
183	missMeter *ldbutils.CountMeter
184	putMeter  *ldbutils.CountMeter
185}
186
187// NewXattrStoreFromBlockMetadataStore returns a XattrStore which is a wrapper
188// around the passed in store.
189func NewXattrStoreFromBlockMetadataStore(store BlockMetadataStore) XattrStore {
190	return xattrStore{
191		store:     store,
192		hitMeter:  ldbutils.NewCountMeter(),
193		missMeter: ldbutils.NewCountMeter(),
194		putMeter:  ldbutils.NewCountMeter(),
195	}
196}
197
198var _ XattrStore = (*xattrStore)(nil)
199
200// GetXattr implements the XattrStore interface.
201func (s xattrStore) GetXattr(ctx context.Context,
202	blockID kbfsblock.ID, xattrType XattrType) ([]byte, error) {
203	blockMetadata, err := s.store.GetMetadata(ctx, blockID)
204	switch errors.Cause(err) {
205	case ldberrors.ErrNotFound:
206		s.missMeter.Mark(1)
207		return nil, err
208	case nil:
209	default:
210		return nil, err
211	}
212
213	v, ok := blockMetadata.Xattr[xattrType]
214	if !ok {
215		s.missMeter.Mark(1)
216		return nil, ldberrors.ErrNotFound
217	}
218
219	s.hitMeter.Mark(1)
220	return v, nil
221}
222
223// SetXattr implements the XattrStore interface.
224func (s xattrStore) SetXattr(ctx context.Context,
225	blockID kbfsblock.ID, xattrType XattrType, xattrValue []byte) (err error) {
226	if err = s.store.UpdateMetadata(ctx, blockID,
227		func(v *BlockMetadataValue) error {
228			if v.Xattr == nil {
229				v.Xattr = make(map[XattrType][]byte)
230			}
231			v.Xattr[xattrType] = xattrValue
232			return nil
233		}); err != nil {
234		return err
235	}
236
237	s.putMeter.Mark(1)
238	return nil
239}
240
241// NoopBlockMetadataStore satisfies the BlockMetadataStore interface but
242// does nothing.
243type NoopBlockMetadataStore struct{}
244
245var _ BlockMetadataStore = NoopBlockMetadataStore{}
246
247// GetMetadata always returns ldberrors.ErrNotFound.
248func (NoopBlockMetadataStore) GetMetadata(ctx context.Context,
249	blockID kbfsblock.ID) (value BlockMetadataValue, err error) {
250	return BlockMetadataValue{}, ldberrors.ErrNotFound
251}
252
253// UpdateMetadata returns nil error but does nothing.
254func (NoopBlockMetadataStore) UpdateMetadata(ctx context.Context,
255	blockID kbfsblock.ID, updater BlockMetadataUpdater) error {
256	return nil
257}
258
259// Shutdown does nothing.
260func (NoopBlockMetadataStore) Shutdown() {}
261