123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384 |
- // Copyright 2015 Google Inc. All Rights Reserved.
- //
- // 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 memfs
- import (
- "fmt"
- "io"
- "os"
- "time"
- "github.com/jacobsa/fuse/fuseops"
- "github.com/jacobsa/fuse/fuseutil"
- )
- // Common attributes for files and directories.
- //
- // External synchronization is required.
- type inode struct {
- /////////////////////////
- // Mutable state
- /////////////////////////
- // The current attributes of this inode.
- //
- // INVARIANT: attrs.Mode &^ (os.ModePerm|os.ModeDir|os.ModeSymlink) == 0
- // INVARIANT: !(isDir() && isSymlink())
- // INVARIANT: attrs.Size == len(contents)
- attrs fuseops.InodeAttributes
- // For directories, entries describing the children of the directory. Unused
- // entries are of type DT_Unknown.
- //
- // This array can never be shortened, nor can its elements be moved, because
- // we use its indices for Dirent.Offset, which is exposed to the user who
- // might be calling readdir in a loop while concurrently modifying the
- // directory. Unused entries can, however, be reused.
- //
- // INVARIANT: If !isDir(), len(entries) == 0
- // INVARIANT: For each i, entries[i].Offset == i+1
- // INVARIANT: Contains no duplicate names in used entries.
- entries []fuseutil.Dirent
- // For files, the current contents of the file.
- //
- // INVARIANT: If !isFile(), len(contents) == 0
- contents []byte
- // For symlinks, the target of the symlink.
- //
- // INVARIANT: If !isSymlink(), len(target) == 0
- target string
- // extended attributes and values
- xattrs map[string][]byte
- }
- ////////////////////////////////////////////////////////////////////////
- // Helpers
- ////////////////////////////////////////////////////////////////////////
- // Create a new inode with the supplied attributes, which need not contain
- // time-related information (the inode object will take care of that).
- func newInode(
- attrs fuseops.InodeAttributes) (in *inode) {
- // Update time info.
- now := time.Now()
- attrs.Mtime = now
- attrs.Crtime = now
- // Create the object.
- in = &inode{
- attrs: attrs,
- xattrs: make(map[string][]byte),
- }
- return
- }
- func (in *inode) CheckInvariants() {
- // INVARIANT: attrs.Mode &^ (os.ModePerm|os.ModeDir|os.ModeSymlink) == 0
- if !(in.attrs.Mode&^(os.ModePerm|os.ModeDir|os.ModeSymlink) == 0) {
- panic(fmt.Sprintf("Unexpected mode: %v", in.attrs.Mode))
- }
- // INVARIANT: !(isDir() && isSymlink())
- if in.isDir() && in.isSymlink() {
- panic(fmt.Sprintf("Unexpected mode: %v", in.attrs.Mode))
- }
- // INVARIANT: attrs.Size == len(contents)
- if in.attrs.Size != uint64(len(in.contents)) {
- panic(fmt.Sprintf(
- "Size mismatch: %d vs. %d",
- in.attrs.Size,
- len(in.contents)))
- }
- // INVARIANT: If !isDir(), len(entries) == 0
- if !in.isDir() && len(in.entries) != 0 {
- panic(fmt.Sprintf("Unexpected entries length: %d", len(in.entries)))
- }
- // INVARIANT: For each i, entries[i].Offset == i+1
- for i, e := range in.entries {
- if !(e.Offset == fuseops.DirOffset(i+1)) {
- panic(fmt.Sprintf("Unexpected offset for index %d: %d", i, e.Offset))
- }
- }
- // INVARIANT: Contains no duplicate names in used entries.
- childNames := make(map[string]struct{})
- for _, e := range in.entries {
- if e.Type != fuseutil.DT_Unknown {
- if _, ok := childNames[e.Name]; ok {
- panic(fmt.Sprintf("Duplicate name: %s", e.Name))
- }
- childNames[e.Name] = struct{}{}
- }
- }
- // INVARIANT: If !isFile(), len(contents) == 0
- if !in.isFile() && len(in.contents) != 0 {
- panic(fmt.Sprintf("Unexpected length: %d", len(in.contents)))
- }
- // INVARIANT: If !isSymlink(), len(target) == 0
- if !in.isSymlink() && len(in.target) != 0 {
- panic(fmt.Sprintf("Unexpected target length: %d", len(in.target)))
- }
- return
- }
- func (in *inode) isDir() bool {
- return in.attrs.Mode&os.ModeDir != 0
- }
- func (in *inode) isSymlink() bool {
- return in.attrs.Mode&os.ModeSymlink != 0
- }
- func (in *inode) isFile() bool {
- return !(in.isDir() || in.isSymlink())
- }
- // Return the index of the child within in.entries, if it exists.
- //
- // REQUIRES: in.isDir()
- func (in *inode) findChild(name string) (i int, ok bool) {
- if !in.isDir() {
- panic("findChild called on non-directory.")
- }
- var e fuseutil.Dirent
- for i, e = range in.entries {
- if e.Name == name {
- ok = true
- return
- }
- }
- return
- }
- ////////////////////////////////////////////////////////////////////////
- // Public methods
- ////////////////////////////////////////////////////////////////////////
- // Return the number of children of the directory.
- //
- // REQUIRES: in.isDir()
- func (in *inode) Len() (n int) {
- for _, e := range in.entries {
- if e.Type != fuseutil.DT_Unknown {
- n++
- }
- }
- return
- }
- // Find an entry for the given child name and return its inode ID.
- //
- // REQUIRES: in.isDir()
- func (in *inode) LookUpChild(name string) (
- id fuseops.InodeID,
- typ fuseutil.DirentType,
- ok bool) {
- index, ok := in.findChild(name)
- if ok {
- id = in.entries[index].Inode
- typ = in.entries[index].Type
- }
- return
- }
- // Add an entry for a child.
- //
- // REQUIRES: in.isDir()
- // REQUIRES: dt != fuseutil.DT_Unknown
- func (in *inode) AddChild(
- id fuseops.InodeID,
- name string,
- dt fuseutil.DirentType) {
- var index int
- // Update the modification time.
- in.attrs.Mtime = time.Now()
- // No matter where we place the entry, make sure it has the correct Offset
- // field.
- defer func() {
- in.entries[index].Offset = fuseops.DirOffset(index + 1)
- }()
- // Set up the entry.
- e := fuseutil.Dirent{
- Inode: id,
- Name: name,
- Type: dt,
- }
- // Look for a gap in which we can insert it.
- for index = range in.entries {
- if in.entries[index].Type == fuseutil.DT_Unknown {
- in.entries[index] = e
- return
- }
- }
- // Append it to the end.
- index = len(in.entries)
- in.entries = append(in.entries, e)
- }
- // Remove an entry for a child.
- //
- // REQUIRES: in.isDir()
- // REQUIRES: An entry for the given name exists.
- func (in *inode) RemoveChild(name string) {
- // Update the modification time.
- in.attrs.Mtime = time.Now()
- // Find the entry.
- i, ok := in.findChild(name)
- if !ok {
- panic(fmt.Sprintf("Unknown child: %s", name))
- }
- // Mark it as unused.
- in.entries[i] = fuseutil.Dirent{
- Type: fuseutil.DT_Unknown,
- Offset: fuseops.DirOffset(i + 1),
- }
- }
- // Serve a ReadDir request.
- //
- // REQUIRES: in.isDir()
- func (in *inode) ReadDir(p []byte, offset int) (n int) {
- if !in.isDir() {
- panic("ReadDir called on non-directory.")
- }
- for i := offset; i < len(in.entries); i++ {
- e := in.entries[i]
- // Skip unused entries.
- if e.Type == fuseutil.DT_Unknown {
- continue
- }
- tmp := fuseutil.WriteDirent(p[n:], in.entries[i])
- if tmp == 0 {
- break
- }
- n += tmp
- }
- return
- }
- // Read from the file's contents. See documentation for ioutil.ReaderAt.
- //
- // REQUIRES: in.isFile()
- func (in *inode) ReadAt(p []byte, off int64) (n int, err error) {
- if !in.isFile() {
- panic("ReadAt called on non-file.")
- }
- // Ensure the offset is in range.
- if off > int64(len(in.contents)) {
- err = io.EOF
- return
- }
- // Read what we can.
- n = copy(p, in.contents[off:])
- if n < len(p) {
- err = io.EOF
- }
- return
- }
- // Write to the file's contents. See documentation for ioutil.WriterAt.
- //
- // REQUIRES: in.isFile()
- func (in *inode) WriteAt(p []byte, off int64) (n int, err error) {
- if !in.isFile() {
- panic("WriteAt called on non-file.")
- }
- // Update the modification time.
- in.attrs.Mtime = time.Now()
- // Ensure that the contents slice is long enough.
- newLen := int(off) + len(p)
- if len(in.contents) < newLen {
- padding := make([]byte, newLen-len(in.contents))
- in.contents = append(in.contents, padding...)
- in.attrs.Size = uint64(newLen)
- }
- // Copy in the data.
- n = copy(in.contents[off:], p)
- // Sanity check.
- if n != len(p) {
- panic(fmt.Sprintf("Unexpected short copy: %v", n))
- }
- return
- }
- // Update attributes from non-nil parameters.
- func (in *inode) SetAttributes(
- size *uint64,
- mode *os.FileMode,
- mtime *time.Time) {
- // Update the modification time.
- in.attrs.Mtime = time.Now()
- // Truncate?
- if size != nil {
- intSize := int(*size)
- // Update contents.
- if intSize <= len(in.contents) {
- in.contents = in.contents[:intSize]
- } else {
- padding := make([]byte, intSize-len(in.contents))
- in.contents = append(in.contents, padding...)
- }
- // Update attributes.
- in.attrs.Size = *size
- }
- // Change mode?
- if mode != nil {
- in.attrs.Mode = *mode
- }
- // Change mtime?
- if mtime != nil {
- in.attrs.Mtime = *mtime
- }
- }
|