github.com/gaukas/wazerofs@v0.1.0/memfs/fs.go (about)

     1  // memfs implements a simple fake memory FS for Wazero.
     2  //
     3  // The actual implementation of the FS is from github.com/blang/vfs/memfs,
     4  // this package is just wrapping that for Wazero.
     5  //
     6  // It implements only very small number of functions, because only those were needed
     7  // for my purposes (that is, running ghostscript with WASI), as ghostscript only calls these.
     8  //
     9  // Feel free to make a PR if you need to implement some other functions.
    10  package memfs
    11  
    12  import (
    13  	"errors"
    14  	"io/fs"
    15  	"strings"
    16  
    17  	wasys "github.com/tetratelabs/wazero/sys"
    18  
    19  	"os"
    20  
    21  	"github.com/tetratelabs/wazero/experimental/sys"
    22  
    23  	"github.com/blang/vfs/memfs"
    24  )
    25  
    26  // New creates a new memory filesystem
    27  func New() *MemFS {
    28  	mfs := memfs.Create()
    29  	mmfs := &MemFS{fs: mfs}
    30  	return mmfs
    31  }
    32  
    33  // WriteFile is a helper function that writes a content to a file.
    34  // Errors have the same semantics as wazero errors
    35  func (m *MemFS) WriteFile(path string, content []byte) sys.Errno {
    36  	f, err := m.OpenFile(path, sys.O_WRONLY|sys.O_CREAT, 0)
    37  	if err != 0 {
    38  		return err
    39  	}
    40  
    41  	_, err = f.Write(content)
    42  	return err
    43  }
    44  
    45  // ReadFile is a helper function that returns a content of a file.
    46  // Errors have the same semantics as wazero errors
    47  func (m *MemFS) ReadFile(path string) ([]byte, sys.Errno) {
    48  	f, err := m.OpenFile(path, sys.O_RDONLY, 0)
    49  	if err != 0 {
    50  		return nil, err
    51  	}
    52  
    53  	st, errno := f.Stat()
    54  	if errno != 0 {
    55  		return nil, errno
    56  	}
    57  
    58  	buf := make([]byte, st.Size)
    59  	_, errno = f.Read(buf)
    60  	return buf, errno
    61  }
    62  
    63  // MemFS is a memory-only wazero filesystem, implementing just some basic functions.
    64  type MemFS struct {
    65  	fs *memfs.MemFS
    66  
    67  	sys.UnimplementedFS
    68  }
    69  
    70  // toOsOpenFlag is copied from wazero codebase
    71  func toOsOpenFlag(oflag sys.Oflag) (flag int) {
    72  	// First flags are exclusive
    73  	switch oflag & (sys.O_RDONLY | sys.O_RDWR | sys.O_WRONLY) {
    74  	case sys.O_RDONLY:
    75  		flag |= os.O_RDONLY
    76  	case sys.O_RDWR:
    77  		flag |= os.O_RDWR
    78  	case sys.O_WRONLY:
    79  		flag |= os.O_WRONLY
    80  	}
    81  
    82  	// Run down the flags defined in the os package
    83  	if oflag&sys.O_APPEND != 0 {
    84  		flag |= os.O_APPEND
    85  	}
    86  	if oflag&sys.O_CREAT != 0 {
    87  		flag |= os.O_CREATE
    88  	}
    89  	if oflag&sys.O_EXCL != 0 {
    90  		flag |= os.O_EXCL
    91  	}
    92  	if oflag&sys.O_SYNC != 0 {
    93  		flag |= os.O_SYNC
    94  	}
    95  	if oflag&sys.O_TRUNC != 0 {
    96  		flag |= os.O_TRUNC
    97  	}
    98  	return flag
    99  }
   100  
   101  // OpenFile opens a file as defined in sys.File
   102  func (m *MemFS) OpenFile(path string, flag sys.Oflag, perm fs.FileMode) (sys.File, sys.Errno) {
   103  	f, err := m.fs.OpenFile(path, toOsOpenFlag(flag), perm)
   104  	if err != nil {
   105  		if errors.Is(err, memfs.ErrIsDirectory) {
   106  			if flag&sys.O_WRONLY == 1 || flag&sys.O_RDWR == 1 {
   107  				return nil, sys.EISDIR
   108  			}
   109  			// return directory as a different type
   110  			dir := &memoryFSDir{fs: m.fs, path: path}
   111  			return dir, 0
   112  		}
   113  		if errors.Is(err, os.ErrNotExist) {
   114  			return nil, sys.ENOENT
   115  		}
   116  		if errors.Is(err, os.ErrExist) {
   117  			return nil, sys.EEXIST
   118  		}
   119  		return nil, sys.EINVAL // just general IO error, not that important
   120  	}
   121  	fl := &memoryFSFile{fl: f, path: path, fs: m.fs}
   122  	return fl, 0
   123  }
   124  
   125  func (m *MemFS) Mkdir(path string, perm fs.FileMode) sys.Errno {
   126  	err := m.fs.Mkdir(path, perm)
   127  	// note - this is not 100% correct, but good enough
   128  	// note - we canno "just" call stat here, as mkdir should be atomic; the file maybe doesn't exist anymore
   129  	// just return EEXIST I guess...
   130  	if err != nil {
   131  		if strings.Contains(err.Error(), "already exists") {
   132  			return sys.EEXIST
   133  		}
   134  		return sys.EINVAL
   135  	}
   136  	return 0
   137  }
   138  
   139  func (m *MemFS) Unlink(path string) sys.Errno {
   140  	err := m.fs.Remove(path)
   141  	if err != nil {
   142  		if errors.Is(err, os.ErrNotExist) {
   143  			return sys.ENOENT
   144  		}
   145  		return sys.EINVAL
   146  	}
   147  	return 0
   148  }
   149  
   150  func stat(mfs *memfs.MemFS, path string) (wasys.Stat_t, sys.Errno) {
   151  	fst, err := mfs.Stat(path)
   152  	if err != nil {
   153  		if errors.Is(err, os.ErrNotExist) {
   154  			return wasys.Stat_t{}, sys.ENOENT
   155  
   156  		}
   157  		return wasys.Stat_t{}, sys.EIO // this should "never happen"
   158  	}
   159  	return wasys.NewStat_t(fst), 0
   160  }
   161  
   162  // Stat returns file stat as defined in sys.File
   163  func (m *MemFS) Stat(path string) (wasys.Stat_t, sys.Errno) {
   164  	return stat(m.fs, path)
   165  }