github.com/scaleoutsean/fusego@v0.0.0-20220224074057-4a6429e46bb8/samples/memfs/inode.go (about)

     1  // Copyright 2015 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package memfs
    16  
    17  import (
    18  	"fmt"
    19  	"io"
    20  	"os"
    21  	"time"
    22  
    23  	"github.com/scaleoutsean/fusego"
    24  	"github.com/scaleoutsean/fusego/fuseops"
    25  	"github.com/scaleoutsean/fusego/fuseutil"
    26  )
    27  
    28  // Common attributes for files and directories.
    29  //
    30  // External synchronization is required.
    31  type inode struct {
    32  	/////////////////////////
    33  	// Mutable state
    34  	/////////////////////////
    35  
    36  	// The current attributes of this inode.
    37  	//
    38  	// INVARIANT: attrs.Mode &^ (os.ModePerm|os.ModeDir|os.ModeSymlink) == 0
    39  	// INVARIANT: !(isDir() && isSymlink())
    40  	// INVARIANT: attrs.Size == len(contents)
    41  	attrs fuseops.InodeAttributes
    42  
    43  	// For directories, entries describing the children of the directory. Unused
    44  	// entries are of type DT_Unknown.
    45  	//
    46  	// This array can never be shortened, nor can its elements be moved, because
    47  	// we use its indices for Dirent.Offset, which is exposed to the user who
    48  	// might be calling readdir in a loop while concurrently modifying the
    49  	// directory. Unused entries can, however, be reused.
    50  	//
    51  	// INVARIANT: If !isDir(), len(entries) == 0
    52  	// INVARIANT: For each i, entries[i].Offset == i+1
    53  	// INVARIANT: Contains no duplicate names in used entries.
    54  	entries []fuseutil.Dirent
    55  
    56  	// For files, the current contents of the file.
    57  	//
    58  	// INVARIANT: If !isFile(), len(contents) == 0
    59  	contents []byte
    60  
    61  	// For symlinks, the target of the symlink.
    62  	//
    63  	// INVARIANT: If !isSymlink(), len(target) == 0
    64  	target string
    65  
    66  	// extended attributes and values
    67  	xattrs map[string][]byte
    68  }
    69  
    70  ////////////////////////////////////////////////////////////////////////
    71  // Helpers
    72  ////////////////////////////////////////////////////////////////////////
    73  
    74  // Create a new inode with the supplied attributes, which need not contain
    75  // time-related information (the inode object will take care of that).
    76  func newInode(attrs fuseops.InodeAttributes) *inode {
    77  	// Update time info.
    78  	now := time.Now()
    79  	attrs.Mtime = now
    80  	attrs.Crtime = now
    81  
    82  	// Create the object.
    83  	return &inode{
    84  		attrs:  attrs,
    85  		xattrs: make(map[string][]byte),
    86  	}
    87  }
    88  
    89  func (in *inode) CheckInvariants() {
    90  	// INVARIANT: attrs.Mode &^ (os.ModePerm|os.ModeDir|os.ModeSymlink) == 0
    91  	if !(in.attrs.Mode&^(os.ModePerm|os.ModeDir|os.ModeSymlink) == 0) {
    92  		panic(fmt.Sprintf("Unexpected mode: %v", in.attrs.Mode))
    93  	}
    94  
    95  	// INVARIANT: !(isDir() && isSymlink())
    96  	if in.isDir() && in.isSymlink() {
    97  		panic(fmt.Sprintf("Unexpected mode: %v", in.attrs.Mode))
    98  	}
    99  
   100  	// INVARIANT: attrs.Size == len(contents)
   101  	if in.attrs.Size != uint64(len(in.contents)) {
   102  		panic(fmt.Sprintf(
   103  			"Size mismatch: %d vs. %d",
   104  			in.attrs.Size,
   105  			len(in.contents)))
   106  	}
   107  
   108  	// INVARIANT: If !isDir(), len(entries) == 0
   109  	if !in.isDir() && len(in.entries) != 0 {
   110  		panic(fmt.Sprintf("Unexpected entries length: %d", len(in.entries)))
   111  	}
   112  
   113  	// INVARIANT: For each i, entries[i].Offset == i+1
   114  	for i, e := range in.entries {
   115  		if !(e.Offset == fuseops.DirOffset(i+1)) {
   116  			panic(fmt.Sprintf("Unexpected offset for index %d: %d", i, e.Offset))
   117  		}
   118  	}
   119  
   120  	// INVARIANT: Contains no duplicate names in used entries.
   121  	childNames := make(map[string]struct{})
   122  	for _, e := range in.entries {
   123  		if e.Type != fuseutil.DT_Unknown {
   124  			if _, ok := childNames[e.Name]; ok {
   125  				panic(fmt.Sprintf("Duplicate name: %s", e.Name))
   126  			}
   127  
   128  			childNames[e.Name] = struct{}{}
   129  		}
   130  	}
   131  
   132  	// INVARIANT: If !isFile(), len(contents) == 0
   133  	if !in.isFile() && len(in.contents) != 0 {
   134  		panic(fmt.Sprintf("Unexpected length: %d", len(in.contents)))
   135  	}
   136  
   137  	// INVARIANT: If !isSymlink(), len(target) == 0
   138  	if !in.isSymlink() && len(in.target) != 0 {
   139  		panic(fmt.Sprintf("Unexpected target length: %d", len(in.target)))
   140  	}
   141  
   142  	return
   143  }
   144  
   145  func (in *inode) isDir() bool {
   146  	return in.attrs.Mode&os.ModeDir != 0
   147  }
   148  
   149  func (in *inode) isSymlink() bool {
   150  	return in.attrs.Mode&os.ModeSymlink != 0
   151  }
   152  
   153  func (in *inode) isFile() bool {
   154  	return !(in.isDir() || in.isSymlink())
   155  }
   156  
   157  // Return the index of the child within in.entries, if it exists.
   158  //
   159  // REQUIRES: in.isDir()
   160  func (in *inode) findChild(name string) (i int, ok bool) {
   161  	if !in.isDir() {
   162  		panic("findChild called on non-directory.")
   163  	}
   164  
   165  	var e fuseutil.Dirent
   166  	for i, e = range in.entries {
   167  		if e.Name == name {
   168  			return i, true
   169  		}
   170  	}
   171  
   172  	return 0, false
   173  }
   174  
   175  ////////////////////////////////////////////////////////////////////////
   176  // Public methods
   177  ////////////////////////////////////////////////////////////////////////
   178  
   179  // Return the number of children of the directory.
   180  //
   181  // REQUIRES: in.isDir()
   182  func (in *inode) Len() int {
   183  	var n int
   184  	for _, e := range in.entries {
   185  		if e.Type != fuseutil.DT_Unknown {
   186  			n++
   187  		}
   188  	}
   189  
   190  	return n
   191  }
   192  
   193  // Find an entry for the given child name and return its inode ID.
   194  //
   195  // REQUIRES: in.isDir()
   196  func (in *inode) LookUpChild(name string) (
   197  	id fuseops.InodeID,
   198  	typ fuseutil.DirentType,
   199  	ok bool) {
   200  	index, ok := in.findChild(name)
   201  	if ok {
   202  		id = in.entries[index].Inode
   203  		typ = in.entries[index].Type
   204  	}
   205  
   206  	return id, typ, ok
   207  }
   208  
   209  // Add an entry for a child.
   210  //
   211  // REQUIRES: in.isDir()
   212  // REQUIRES: dt != fuseutil.DT_Unknown
   213  func (in *inode) AddChild(
   214  	id fuseops.InodeID,
   215  	name string,
   216  	dt fuseutil.DirentType) {
   217  	var index int
   218  
   219  	// Update the modification time.
   220  	in.attrs.Mtime = time.Now()
   221  
   222  	// No matter where we place the entry, make sure it has the correct Offset
   223  	// field.
   224  	defer func() {
   225  		in.entries[index].Offset = fuseops.DirOffset(index + 1)
   226  	}()
   227  
   228  	// Set up the entry.
   229  	e := fuseutil.Dirent{
   230  		Inode: id,
   231  		Name:  name,
   232  		Type:  dt,
   233  	}
   234  
   235  	// Look for a gap in which we can insert it.
   236  	for index = range in.entries {
   237  		if in.entries[index].Type == fuseutil.DT_Unknown {
   238  			in.entries[index] = e
   239  			return
   240  		}
   241  	}
   242  
   243  	// Append it to the end.
   244  	index = len(in.entries)
   245  	in.entries = append(in.entries, e)
   246  }
   247  
   248  // Remove an entry for a child.
   249  //
   250  // REQUIRES: in.isDir()
   251  // REQUIRES: An entry for the given name exists.
   252  func (in *inode) RemoveChild(name string) {
   253  	// Update the modification time.
   254  	in.attrs.Mtime = time.Now()
   255  
   256  	// Find the entry.
   257  	i, ok := in.findChild(name)
   258  	if !ok {
   259  		panic(fmt.Sprintf("Unknown child: %s", name))
   260  	}
   261  
   262  	// Mark it as unused.
   263  	in.entries[i] = fuseutil.Dirent{
   264  		Type:   fuseutil.DT_Unknown,
   265  		Offset: fuseops.DirOffset(i + 1),
   266  	}
   267  }
   268  
   269  // Serve a ReadDir request.
   270  //
   271  // REQUIRES: in.isDir()
   272  func (in *inode) ReadDir(p []byte, offset int) int {
   273  	if !in.isDir() {
   274  		panic("ReadDir called on non-directory.")
   275  	}
   276  
   277  	var n int
   278  	for i := offset; i < len(in.entries); i++ {
   279  		e := in.entries[i]
   280  
   281  		// Skip unused entries.
   282  		if e.Type == fuseutil.DT_Unknown {
   283  			continue
   284  		}
   285  
   286  		tmp := fuseutil.WriteDirent(p[n:], in.entries[i])
   287  		if tmp == 0 {
   288  			break
   289  		}
   290  
   291  		n += tmp
   292  	}
   293  
   294  	return n
   295  }
   296  
   297  // Read from the file's contents. See documentation for ioutil.ReaderAt.
   298  //
   299  // REQUIRES: in.isFile()
   300  func (in *inode) ReadAt(p []byte, off int64) (int, error) {
   301  	if !in.isFile() {
   302  		panic("ReadAt called on non-file.")
   303  	}
   304  
   305  	// Ensure the offset is in range.
   306  	if off > int64(len(in.contents)) {
   307  		return 0, io.EOF
   308  	}
   309  
   310  	// Read what we can.
   311  	n := copy(p, in.contents[off:])
   312  	if n < len(p) {
   313  		return n, io.EOF
   314  	}
   315  
   316  	return n, nil
   317  }
   318  
   319  // Write to the file's contents. See documentation for ioutil.WriterAt.
   320  //
   321  // REQUIRES: in.isFile()
   322  func (in *inode) WriteAt(p []byte, off int64) (int, error) {
   323  	if !in.isFile() {
   324  		panic("WriteAt called on non-file.")
   325  	}
   326  
   327  	// Update the modification time.
   328  	in.attrs.Mtime = time.Now()
   329  
   330  	// Ensure that the contents slice is long enough.
   331  	newLen := int(off) + len(p)
   332  	if len(in.contents) < newLen {
   333  		padding := make([]byte, newLen-len(in.contents))
   334  		in.contents = append(in.contents, padding...)
   335  		in.attrs.Size = uint64(newLen)
   336  	}
   337  
   338  	// Copy in the data.
   339  	n := copy(in.contents[off:], p)
   340  
   341  	// Sanity check.
   342  	if n != len(p) {
   343  		panic(fmt.Sprintf("Unexpected short copy: %v", n))
   344  	}
   345  
   346  	return n, nil
   347  }
   348  
   349  // Update attributes from non-nil parameters.
   350  func (in *inode) SetAttributes(
   351  	size *uint64,
   352  	mode *os.FileMode,
   353  	mtime *time.Time) {
   354  	// Update the modification time.
   355  	in.attrs.Mtime = time.Now()
   356  
   357  	// Truncate?
   358  	if size != nil {
   359  		intSize := int(*size)
   360  
   361  		// Update contents.
   362  		if intSize <= len(in.contents) {
   363  			in.contents = in.contents[:intSize]
   364  		} else {
   365  			padding := make([]byte, intSize-len(in.contents))
   366  			in.contents = append(in.contents, padding...)
   367  		}
   368  
   369  		// Update attributes.
   370  		in.attrs.Size = *size
   371  	}
   372  
   373  	// Change mode?
   374  	if mode != nil {
   375  		in.attrs.Mode = *mode
   376  	}
   377  
   378  	// Change mtime?
   379  	if mtime != nil {
   380  		in.attrs.Mtime = *mtime
   381  	}
   382  }
   383  
   384  func (in *inode) Fallocate(mode uint32, offset uint64, length uint64) error {
   385  	if mode != 0 {
   386  		return fuse.ENOSYS
   387  	}
   388  	newSize := int(offset + length)
   389  	if newSize > len(in.contents) {
   390  		padding := make([]byte, newSize-len(in.contents))
   391  		in.contents = append(in.contents, padding...)
   392  		in.attrs.Size = offset + length
   393  	}
   394  	return nil
   395  }