summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/fs/dirent_cache.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/sentry/fs/dirent_cache.go')
-rw-r--r--pkg/sentry/fs/dirent_cache.go175
1 files changed, 175 insertions, 0 deletions
diff --git a/pkg/sentry/fs/dirent_cache.go b/pkg/sentry/fs/dirent_cache.go
new file mode 100644
index 000000000..71f2d11de
--- /dev/null
+++ b/pkg/sentry/fs/dirent_cache.go
@@ -0,0 +1,175 @@
+// Copyright 2018 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package fs
+
+import (
+ "fmt"
+ "sync"
+)
+
+// DirentCache is an LRU cache of Dirents. The Dirent's refCount is
+// incremented when it is added to the cache, and decremented when it is
+// removed.
+//
+// A nil DirentCache corresponds to a cache with size 0. All methods can be
+// called, but nothing is actually cached.
+//
+// +stateify savable
+type DirentCache struct {
+ // Maximum size of the cache. This must be saved manually, to handle the case
+ // when cache is nil.
+ maxSize uint64
+
+ // limit restricts the number of entries in the cache amoung multiple caches.
+ // It may be nil if there are no global limit for this cache.
+ limit *DirentCacheLimiter
+
+ // mu protects currentSize and direntList.
+ mu sync.Mutex `state:"nosave"`
+
+ // currentSize is the number of elements in the cache. It must be zero (i.e.
+ // the cache must be empty) on Save.
+ currentSize uint64 `state:"zerovalue"`
+
+ // list is a direntList, an ilist of Dirents. New Dirents are added
+ // to the front of the list. Old Dirents are removed from the back of
+ // the list. It must be zerovalue (i.e. the cache must be empty) on Save.
+ list direntList `state:"zerovalue"`
+}
+
+// NewDirentCache returns a new DirentCache with the given maxSize.
+func NewDirentCache(maxSize uint64) *DirentCache {
+ return &DirentCache{
+ maxSize: maxSize,
+ }
+}
+
+// Add adds the element to the cache and increments the refCount. If the
+// argument is already in the cache, it is moved to the front. An element is
+// removed from the back if the cache is over capacity.
+func (c *DirentCache) Add(d *Dirent) {
+ if c == nil || c.maxSize == 0 {
+ return
+ }
+
+ c.mu.Lock()
+ if c.contains(d) {
+ // d is already in cache. Bump it to the front.
+ // currentSize and refCount are unaffected.
+ c.list.Remove(d)
+ c.list.PushFront(d)
+ c.mu.Unlock()
+ return
+ }
+
+ // First check against the global limit.
+ for c.limit != nil && !c.limit.tryInc() {
+ if c.currentSize == 0 {
+ // If the global limit is reached, but there is nothing more to drop from
+ // this cache, there is not much else to do.
+ c.mu.Unlock()
+ return
+ }
+ c.remove(c.list.Back())
+ }
+
+ // d is not in cache. Add it and take a reference.
+ c.list.PushFront(d)
+ d.IncRef()
+ c.currentSize++
+
+ c.maybeShrink()
+
+ c.mu.Unlock()
+}
+
+func (c *DirentCache) remove(d *Dirent) {
+ if !c.contains(d) {
+ panic(fmt.Sprintf("trying to remove %v, which is not in the dirent cache", d))
+ }
+ c.list.Remove(d)
+ d.SetPrev(nil)
+ d.SetNext(nil)
+ d.DecRef()
+ c.currentSize--
+ if c.limit != nil {
+ c.limit.dec()
+ }
+}
+
+// Remove removes the element from the cache and decrements its refCount. It
+// also sets the previous and next elements to nil, which allows us to
+// determine if a given element is in the cache.
+func (c *DirentCache) Remove(d *Dirent) {
+ if c == nil || c.maxSize == 0 {
+ return
+ }
+ c.mu.Lock()
+ if !c.contains(d) {
+ c.mu.Unlock()
+ return
+ }
+ c.remove(d)
+ c.mu.Unlock()
+}
+
+// Size returns the number of elements in the cache.
+func (c *DirentCache) Size() uint64 {
+ if c == nil {
+ return 0
+ }
+ c.mu.Lock()
+ size := c.currentSize
+ c.mu.Unlock()
+ return size
+}
+
+func (c *DirentCache) contains(d *Dirent) bool {
+ // If d has a Prev or Next element, then it is in the cache.
+ if d.Prev() != nil || d.Next() != nil {
+ return true
+ }
+ // Otherwise, d is in the cache if it is the only element (and thus the
+ // first element).
+ return c.list.Front() == d
+}
+
+// Invalidate removes all Dirents from the cache, caling DecRef on each.
+func (c *DirentCache) Invalidate() {
+ if c == nil {
+ return
+ }
+ c.mu.Lock()
+ for c.list.Front() != nil {
+ c.remove(c.list.Front())
+ }
+ c.mu.Unlock()
+}
+
+// setMaxSize sets cache max size. If current size is larger than max size, the
+// cache shrinks to acommodate the new max.
+func (c *DirentCache) setMaxSize(max uint64) {
+ c.mu.Lock()
+ c.maxSize = max
+ c.maybeShrink()
+ c.mu.Unlock()
+}
+
+// shrink removes the oldest element until the list is under the size limit.
+func (c *DirentCache) maybeShrink() {
+ for c.maxSize > 0 && c.currentSize > c.maxSize {
+ c.remove(c.list.Back())
+ }
+}