github.com/tetratelabs/wazero@v1.2.1/internal/sysfs/readfs.go (about) 1 package sysfs 2 3 import ( 4 "io/fs" 5 "os" 6 "syscall" 7 "time" 8 9 "github.com/tetratelabs/wazero/internal/fsapi" 10 ) 11 12 // NewReadFS is used to mask an existing api.FS for reads. Notably, this allows 13 // the CLI to do read-only mounts of directories the host user can write, but 14 // doesn't want the guest wasm to. For example, Python libraries shouldn't be 15 // written to at runtime by the python wasm file. 16 func NewReadFS(fs fsapi.FS) fsapi.FS { 17 if _, ok := fs.(*readFS); ok { 18 return fs 19 } else if _, ok = fs.(fsapi.UnimplementedFS); ok { 20 return fs // unimplemented is read-only 21 } 22 return &readFS{fs: fs} 23 } 24 25 type readFS struct { 26 fs fsapi.FS 27 } 28 29 // String implements fmt.Stringer 30 func (r *readFS) String() string { 31 return r.fs.String() 32 } 33 34 // OpenFile implements the same method as documented on api.FS 35 func (r *readFS) OpenFile(path string, flag int, perm fs.FileMode) (fsapi.File, syscall.Errno) { 36 // TODO: Once the real implementation is complete, move the below to 37 // /RATIONALE.md. Doing this while the type is unstable creates 38 // documentation drift as we expect a lot of reshaping meanwhile. 39 // 40 // Callers of this function expect to either open a valid file handle, or 41 // get an error, if they can't. We want to return ENOSYS if opened for 42 // anything except reads. 43 // 44 // Instead, we could return a fake no-op file on O_WRONLY. However, this 45 // hurts observability because a later write error to that file will be on 46 // a different source code line than the root cause which is opening with 47 // an unsupported flag. 48 // 49 // The tricky part is os.RD_ONLY is typically defined as zero, so while the 50 // parameter is named flag, the part about opening read vs write isn't a 51 // typical bitflag. We can't compare against zero anyway, because even if 52 // there isn't a current flag to OR in with that, there may be in the 53 // future. What we do instead is mask the flags about read/write mode and 54 // check if they are the opposite of read or not. 55 switch flag & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR) { 56 case os.O_WRONLY, os.O_RDWR: 57 if flag&fsapi.O_DIRECTORY != 0 { 58 return nil, syscall.EISDIR 59 } 60 return nil, syscall.ENOSYS 61 default: // os.O_RDONLY (or no flag) so we are ok! 62 } 63 64 f, errno := r.fs.OpenFile(path, flag, perm) 65 if errno != 0 { 66 return nil, errno 67 } 68 return &readFile{f: f}, 0 69 } 70 71 // compile-time check to ensure readFile implements api.File. 72 var _ fsapi.File = (*readFile)(nil) 73 74 type readFile struct { 75 f fsapi.File 76 } 77 78 // Ino implements the same method as documented on fsapi.File. 79 func (r *readFile) Ino() (uint64, syscall.Errno) { 80 return r.f.Ino() 81 } 82 83 // IsNonblock implements the same method as documented on fsapi.File. 84 func (r *readFile) IsNonblock() bool { 85 return r.f.IsNonblock() 86 } 87 88 // SetNonblock implements the same method as documented on fsapi.File. 89 func (r *readFile) SetNonblock(enabled bool) syscall.Errno { 90 return r.f.SetNonblock(enabled) 91 } 92 93 // IsAppend implements the same method as documented on fsapi.File. 94 func (r *readFile) IsAppend() bool { 95 return r.f.IsAppend() 96 } 97 98 // SetAppend implements the same method as documented on fsapi.File. 99 func (r *readFile) SetAppend(enabled bool) syscall.Errno { 100 return r.f.SetAppend(enabled) 101 } 102 103 // Stat implements the same method as documented on fsapi.File. 104 func (r *readFile) Stat() (fsapi.Stat_t, syscall.Errno) { 105 return r.f.Stat() 106 } 107 108 // IsDir implements the same method as documented on fsapi.File. 109 func (r *readFile) IsDir() (bool, syscall.Errno) { 110 return r.f.IsDir() 111 } 112 113 // Read implements the same method as documented on fsapi.File. 114 func (r *readFile) Read(buf []byte) (int, syscall.Errno) { 115 return r.f.Read(buf) 116 } 117 118 // Pread implements the same method as documented on fsapi.File. 119 func (r *readFile) Pread(buf []byte, offset int64) (int, syscall.Errno) { 120 return r.f.Pread(buf, offset) 121 } 122 123 // Seek implements the same method as documented on fsapi.File. 124 func (r *readFile) Seek(offset int64, whence int) (int64, syscall.Errno) { 125 return r.f.Seek(offset, whence) 126 } 127 128 // Readdir implements the same method as documented on fsapi.File. 129 func (r *readFile) Readdir(n int) (dirents []fsapi.Dirent, errno syscall.Errno) { 130 return r.f.Readdir(n) 131 } 132 133 // Write implements the same method as documented on fsapi.File. 134 func (r *readFile) Write([]byte) (int, syscall.Errno) { 135 return 0, r.writeErr() 136 } 137 138 // Pwrite implements the same method as documented on fsapi.File. 139 func (r *readFile) Pwrite([]byte, int64) (n int, errno syscall.Errno) { 140 return 0, r.writeErr() 141 } 142 143 // Truncate implements the same method as documented on fsapi.File. 144 func (r *readFile) Truncate(int64) syscall.Errno { 145 return r.writeErr() 146 } 147 148 // Sync implements the same method as documented on fsapi.File. 149 func (r *readFile) Sync() syscall.Errno { 150 return syscall.EBADF 151 } 152 153 // Datasync implements the same method as documented on fsapi.File. 154 func (r *readFile) Datasync() syscall.Errno { 155 return syscall.EBADF 156 } 157 158 // Chmod implements the same method as documented on fsapi.File. 159 func (r *readFile) Chmod(fs.FileMode) syscall.Errno { 160 return syscall.EBADF 161 } 162 163 // Chown implements the same method as documented on fsapi.File. 164 func (r *readFile) Chown(int, int) syscall.Errno { 165 return syscall.EBADF 166 } 167 168 // Utimens implements the same method as documented on fsapi.File. 169 func (r *readFile) Utimens(*[2]syscall.Timespec) syscall.Errno { 170 return syscall.EBADF 171 } 172 173 func (r *readFile) writeErr() syscall.Errno { 174 if isDir, errno := r.IsDir(); errno != 0 { 175 return errno 176 } else if isDir { 177 return syscall.EISDIR 178 } 179 return syscall.EBADF 180 } 181 182 // Close implements the same method as documented on fsapi.File. 183 func (r *readFile) Close() syscall.Errno { 184 return r.f.Close() 185 } 186 187 // PollRead implements File.PollRead 188 func (r *readFile) PollRead(timeout *time.Duration) (ready bool, errno syscall.Errno) { 189 return r.f.PollRead(timeout) 190 } 191 192 // Lstat implements the same method as documented on api.FS 193 func (r *readFS) Lstat(path string) (fsapi.Stat_t, syscall.Errno) { 194 return r.fs.Lstat(path) 195 } 196 197 // Stat implements the same method as documented on api.FS 198 func (r *readFS) Stat(path string) (fsapi.Stat_t, syscall.Errno) { 199 return r.fs.Stat(path) 200 } 201 202 // Readlink implements the same method as documented on api.FS 203 func (r *readFS) Readlink(path string) (dst string, err syscall.Errno) { 204 return r.fs.Readlink(path) 205 } 206 207 // Mkdir implements the same method as documented on api.FS 208 func (r *readFS) Mkdir(path string, perm fs.FileMode) syscall.Errno { 209 return syscall.EROFS 210 } 211 212 // Chmod implements the same method as documented on api.FS 213 func (r *readFS) Chmod(path string, perm fs.FileMode) syscall.Errno { 214 return syscall.EROFS 215 } 216 217 // Chown implements the same method as documented on api.FS 218 func (r *readFS) Chown(path string, uid, gid int) syscall.Errno { 219 return syscall.EROFS 220 } 221 222 // Lchown implements the same method as documented on api.FS 223 func (r *readFS) Lchown(path string, uid, gid int) syscall.Errno { 224 return syscall.EROFS 225 } 226 227 // Rename implements the same method as documented on api.FS 228 func (r *readFS) Rename(from, to string) syscall.Errno { 229 return syscall.EROFS 230 } 231 232 // Rmdir implements the same method as documented on api.FS 233 func (r *readFS) Rmdir(path string) syscall.Errno { 234 return syscall.EROFS 235 } 236 237 // Link implements the same method as documented on api.FS 238 func (r *readFS) Link(_, _ string) syscall.Errno { 239 return syscall.EROFS 240 } 241 242 // Symlink implements the same method as documented on api.FS 243 func (r *readFS) Symlink(_, _ string) syscall.Errno { 244 return syscall.EROFS 245 } 246 247 // Unlink implements the same method as documented on api.FS 248 func (r *readFS) Unlink(path string) syscall.Errno { 249 return syscall.EROFS 250 } 251 252 // Utimens implements the same method as documented on api.FS 253 func (r *readFS) Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) syscall.Errno { 254 return syscall.EROFS 255 } 256 257 // Truncate implements the same method as documented on api.FS 258 func (r *readFS) Truncate(string, int64) syscall.Errno { 259 return syscall.EROFS 260 }