// Copyright 2018 Google LLC // // 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 ( "sort" "gvisor.googlesource.com/gvisor/pkg/sentry/device" ) // DentAttr is the metadata of a directory entry. It is a subset of StableAttr. // // +stateify savable type DentAttr struct { // Type is the InodeType of an Inode. Type InodeType // InodeID uniquely identifies an Inode on a device. InodeID uint64 } // GenericDentAttr returns a generic DentAttr where: // // Type == nt // InodeID == the inode id of a new inode on device. func GenericDentAttr(nt InodeType, device *device.Device) DentAttr { return DentAttr{ Type: nt, InodeID: device.NextIno(), } } // DentrySerializer serializes a directory entry. type DentrySerializer interface { // CopyOut serializes a directory entry based on its name and attributes. CopyOut(name string, attributes DentAttr) error // Written returns the number of bytes written. Written() int } // CollectEntriesSerializer copies DentAttrs to Entries. The order in // which entries are encountered is preserved in Order. type CollectEntriesSerializer struct { Entries map[string]DentAttr Order []string } // CopyOut implements DentrySerializer.CopyOut. func (c *CollectEntriesSerializer) CopyOut(name string, attr DentAttr) error { if c.Entries == nil { c.Entries = make(map[string]DentAttr) } c.Entries[name] = attr c.Order = append(c.Order, name) return nil } // Written implements DentrySerializer.Written. func (c *CollectEntriesSerializer) Written() int { return len(c.Entries) } // DirCtx is used by node.Readdir to emit directory entries. It is not // thread-safe. type DirCtx struct { // Serializer is used to serialize the node attributes. Serializer DentrySerializer // attrs are DentAttrs attrs map[string]DentAttr // DirCursor is the directory cursor. // TODO: Once Handles are removed this can just live in the // respective FileOperations implementations and not need to get // plumbed everywhere. DirCursor *string } // DirEmit is called for each directory entry. func (c *DirCtx) DirEmit(name string, attr DentAttr) error { if c.Serializer != nil { if err := c.Serializer.CopyOut(name, attr); err != nil { return err } } if c.attrs == nil { c.attrs = make(map[string]DentAttr) } c.attrs[name] = attr return nil } // DentAttrs returns a map of DentAttrs corresponding to the emitted directory // entries. func (c *DirCtx) DentAttrs() map[string]DentAttr { if c.attrs == nil { c.attrs = make(map[string]DentAttr) } return c.attrs } // GenericReaddir serializes DentAttrs based on a SortedDentryMap that must // contain _all_ up-to-date DentAttrs under a directory. If ctx.DirCursor is // not nil, it is updated to the name of the last DentAttr that was // successfully serialized. // // Returns the number of entries serialized. func GenericReaddir(ctx *DirCtx, s *SortedDentryMap) (int, error) { // Retrieve the next directory entries. var names []string var entries map[string]DentAttr if ctx.DirCursor != nil { names, entries = s.GetNext(*ctx.DirCursor) } else { names, entries = s.GetAll() } // Try to serialize each entry. var serialized int for _, name := range names { // Skip "" per POSIX. Skip "." and ".." which will be added by Dirent.Readdir. if name == "" || name == "." || name == ".." { continue } // Emit the directory entry. if err := ctx.DirEmit(name, entries[name]); err != nil { // Return potentially a partial serialized count. return serialized, err } // We successfully serialized this entry. serialized++ // Update the cursor with the name of the entry last serialized. if ctx.DirCursor != nil { *ctx.DirCursor = name } } // Everything was serialized. return serialized, nil } // SortedDentryMap is a sorted map of names and fs.DentAttr entries. // // +stateify savable type SortedDentryMap struct { // names is always kept in sorted-order. names []string // entries maps names to fs.DentAttrs. entries map[string]DentAttr } // NewSortedDentryMap maintains entries in name sorted order. func NewSortedDentryMap(entries map[string]DentAttr) *SortedDentryMap { s := &SortedDentryMap{ names: make([]string, 0, len(entries)), entries: entries, } // Don't allow s.entries to be nil, because nil maps arn't Saveable. if s.entries == nil { s.entries = make(map[string]DentAttr) } // Collect names from entries and sort them. for name := range s.entries { s.names = append(s.names, name) } sort.Strings(s.names) return s } // GetAll returns all names and entries in s. func (s *SortedDentryMap) GetAll() ([]string, map[string]DentAttr) { return s.names, s.entries } // GetNext returns names after cursor in s and all entries. func (s *SortedDentryMap) GetNext(cursor string) ([]string, map[string]DentAttr) { i := sort.SearchStrings(s.names, cursor) if i == len(s.names) { return nil, s.entries } // Return everything strictly after the cursor. if s.names[i] == cursor { i++ } return s.names[i:], s.entries } // Add adds an entry with the given name to the map, preserving sort order. If // name already exists in the map, its entry will be overwritten. func (s *SortedDentryMap) Add(name string, entry DentAttr) { if _, ok := s.entries[name]; !ok { // Map does not yet contain an entry with this name. We must // insert it in s.names at the appropriate spot. i := sort.SearchStrings(s.names, name) s.names = append(s.names, "") copy(s.names[i+1:], s.names[i:]) s.names[i] = name } s.entries[name] = entry } // Remove removes an entry with the given name from the map, preserving sort order. func (s *SortedDentryMap) Remove(name string) { if _, ok := s.entries[name]; !ok { return } i := sort.SearchStrings(s.names, name) copy(s.names[i:], s.names[i+1:]) s.names = s.names[:len(s.names)-1] delete(s.entries, name) } // Contains reports whether the map contains an entry with the given name. func (s *SortedDentryMap) Contains(name string) bool { _, ok := s.entries[name] return ok }