file_wasi.odin 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. #+private
  2. package os2
  3. import "base:runtime"
  4. import "core:io"
  5. import "core:sys/wasm/wasi"
  6. import "core:time"
  7. // NOTE: Don't know if there is a max in wasi.
  8. MAX_RW :: 1 << 30
  9. File_Impl :: struct {
  10. file: File,
  11. name: string,
  12. fd: wasi.fd_t,
  13. allocator: runtime.Allocator,
  14. }
  15. // WASI works with "preopened" directories, the environment retrieves directories
  16. // (for example with `wasmtime --dir=. module.wasm`) and those given directories
  17. // are the only ones accessible by the application.
  18. //
  19. // So in order to facilitate the `os` API (absolute paths etc.) we keep a list
  20. // of the given directories and match them when needed (notably `os.open`).
  21. Preopen :: struct {
  22. fd: wasi.fd_t,
  23. prefix: string,
  24. }
  25. preopens: []Preopen
  26. @(init)
  27. init_std_files :: proc() {
  28. new_std :: proc(impl: ^File_Impl, fd: wasi.fd_t, name: string) -> ^File {
  29. impl.file.impl = impl
  30. impl.allocator = runtime.nil_allocator()
  31. impl.fd = fd
  32. impl.name = string(name)
  33. impl.file.stream = {
  34. data = impl,
  35. procedure = _file_stream_proc,
  36. }
  37. impl.file.fstat = _fstat
  38. return &impl.file
  39. }
  40. @(static) files: [3]File_Impl
  41. stdin = new_std(&files[0], 0, "/dev/stdin")
  42. stdout = new_std(&files[1], 1, "/dev/stdout")
  43. stderr = new_std(&files[2], 2, "/dev/stderr")
  44. }
  45. @(init)
  46. init_preopens :: proc() {
  47. strip_prefixes :: proc(path: string) -> string {
  48. path := path
  49. loop: for len(path) > 0 {
  50. switch {
  51. case path[0] == '/':
  52. path = path[1:]
  53. case len(path) > 2 && path[0] == '.' && path[1] == '/':
  54. path = path[2:]
  55. case len(path) == 1 && path[0] == '.':
  56. path = path[1:]
  57. case:
  58. break loop
  59. }
  60. }
  61. return path
  62. }
  63. n: int
  64. n_loop: for fd := wasi.fd_t(3); ; fd += 1 {
  65. _, err := wasi.fd_prestat_get(fd)
  66. #partial switch err {
  67. case .BADF: break n_loop
  68. case .SUCCESS: n += 1
  69. case:
  70. print_error(stderr, _get_platform_error(err), "unexpected error from wasi_prestat_get")
  71. break n_loop
  72. }
  73. }
  74. alloc_err: runtime.Allocator_Error
  75. preopens, alloc_err = make([]Preopen, n, file_allocator())
  76. if alloc_err != nil {
  77. print_error(stderr, alloc_err, "could not allocate memory for wasi preopens")
  78. return
  79. }
  80. loop: for &preopen, i in preopens {
  81. fd := wasi.fd_t(3 + i)
  82. desc, err := wasi.fd_prestat_get(fd)
  83. assert(err == .SUCCESS)
  84. switch desc.tag {
  85. case .DIR:
  86. buf: []byte
  87. buf, alloc_err = make([]byte, desc.dir.pr_name_len, file_allocator())
  88. if alloc_err != nil {
  89. print_error(stderr, alloc_err, "could not allocate memory for wasi preopen dir name")
  90. continue loop
  91. }
  92. if err = wasi.fd_prestat_dir_name(fd, buf); err != .SUCCESS {
  93. print_error(stderr, _get_platform_error(err), "could not get filesystem preopen dir name")
  94. continue loop
  95. }
  96. preopen.fd = fd
  97. preopen.prefix = strip_prefixes(string(buf))
  98. }
  99. }
  100. }
  101. @(require_results)
  102. match_preopen :: proc(path: string) -> (wasi.fd_t, string, bool) {
  103. @(require_results)
  104. prefix_matches :: proc(prefix, path: string) -> bool {
  105. // Empty is valid for any relative path.
  106. if len(prefix) == 0 && len(path) > 0 && path[0] != '/' {
  107. return true
  108. }
  109. if len(path) < len(prefix) {
  110. return false
  111. }
  112. if path[:len(prefix)] != prefix {
  113. return false
  114. }
  115. // Only match on full components.
  116. i := len(prefix)
  117. for i > 0 && prefix[i-1] == '/' {
  118. i -= 1
  119. }
  120. return path[i] == '/'
  121. }
  122. path := path
  123. if path == "" {
  124. return 0, "", false
  125. }
  126. for len(path) > 0 && path[0] == '/' {
  127. path = path[1:]
  128. }
  129. match: Preopen
  130. #reverse for preopen in preopens {
  131. if (match.fd == 0 || len(preopen.prefix) > len(match.prefix)) && prefix_matches(preopen.prefix, path) {
  132. match = preopen
  133. }
  134. }
  135. if match.fd == 0 {
  136. return 0, "", false
  137. }
  138. relative := path[len(match.prefix):]
  139. for len(relative) > 0 && relative[0] == '/' {
  140. relative = relative[1:]
  141. }
  142. if len(relative) == 0 {
  143. relative = "."
  144. }
  145. return match.fd, relative, true
  146. }
  147. _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
  148. dir_fd, relative, ok := match_preopen(name)
  149. if !ok {
  150. return nil, .Invalid_Path
  151. }
  152. oflags: wasi.oflags_t
  153. if .Create in flags { oflags += {.CREATE} }
  154. if .Excl in flags { oflags += {.EXCL} }
  155. if .Trunc in flags { oflags += {.TRUNC} }
  156. fdflags: wasi.fdflags_t
  157. if .Append in flags { fdflags += {.APPEND} }
  158. if .Sync in flags { fdflags += {.SYNC} }
  159. // NOTE: rights are adjusted to what this package's functions might want to call.
  160. rights: wasi.rights_t
  161. if .Read in flags { rights += {.FD_READ, .FD_FILESTAT_GET, .PATH_FILESTAT_GET} }
  162. if .Write in flags { rights += {.FD_WRITE, .FD_SYNC, .FD_FILESTAT_SET_SIZE, .FD_FILESTAT_SET_TIMES, .FD_SEEK} }
  163. fd, fderr := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, oflags, rights, {}, fdflags)
  164. if fderr != nil {
  165. err = _get_platform_error(fderr)
  166. return
  167. }
  168. return _new_file(uintptr(fd), name, file_allocator())
  169. }
  170. _new_file :: proc(handle: uintptr, name: string, allocator: runtime.Allocator) -> (f: ^File, err: Error) {
  171. if name == "" {
  172. err = .Invalid_Path
  173. return
  174. }
  175. impl := new(File_Impl, allocator) or_return
  176. defer if err != nil { free(impl, allocator) }
  177. impl.allocator = allocator
  178. // NOTE: wasi doesn't really do full paths afact.
  179. impl.name = clone_string(name, allocator) or_return
  180. impl.fd = wasi.fd_t(handle)
  181. impl.file.impl = impl
  182. impl.file.stream = {
  183. data = impl,
  184. procedure = _file_stream_proc,
  185. }
  186. impl.file.fstat = _fstat
  187. return &impl.file, nil
  188. }
  189. _clone :: proc(f: ^File) -> (clone: ^File, err: Error) {
  190. if f == nil || f.impl == nil {
  191. return
  192. }
  193. dir_fd, relative, ok := match_preopen(name(f))
  194. if !ok {
  195. return nil, .Invalid_Path
  196. }
  197. fd, fderr := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, {}, {}, {}, {})
  198. if fderr != nil {
  199. err = _get_platform_error(fderr)
  200. return
  201. }
  202. defer if err != nil { wasi.fd_close(fd) }
  203. fderr = wasi.fd_renumber((^File_Impl)(f.impl).fd, fd)
  204. if fderr != nil {
  205. err = _get_platform_error(fderr)
  206. return
  207. }
  208. return _new_file(uintptr(fd), name(f), file_allocator())
  209. }
  210. _close :: proc(f: ^File_Impl) -> (err: Error) {
  211. if errno := wasi.fd_close(f.fd); errno != nil {
  212. err = _get_platform_error(errno)
  213. }
  214. delete(f.name, f.allocator)
  215. free(f, f.allocator)
  216. return
  217. }
  218. _fd :: proc(f: ^File) -> uintptr {
  219. return uintptr(__fd(f))
  220. }
  221. __fd :: proc(f: ^File) -> wasi.fd_t {
  222. if f != nil && f.impl != nil {
  223. return (^File_Impl)(f.impl).fd
  224. }
  225. return -1
  226. }
  227. _name :: proc(f: ^File) -> string {
  228. if f != nil && f.impl != nil {
  229. return (^File_Impl)(f.impl).name
  230. }
  231. return ""
  232. }
  233. _sync :: proc(f: ^File) -> Error {
  234. return _get_platform_error(wasi.fd_sync(__fd(f)))
  235. }
  236. _truncate :: proc(f: ^File, size: i64) -> Error {
  237. return _get_platform_error(wasi.fd_filestat_set_size(__fd(f), wasi.filesize_t(size)))
  238. }
  239. _remove :: proc(name: string) -> Error {
  240. dir_fd, relative, ok := match_preopen(name)
  241. if !ok {
  242. return .Invalid_Path
  243. }
  244. err := wasi.path_remove_directory(dir_fd, relative)
  245. if err == .NOTDIR {
  246. err = wasi.path_unlink_file(dir_fd, relative)
  247. }
  248. return _get_platform_error(err)
  249. }
  250. _rename :: proc(old_path, new_path: string) -> Error {
  251. src_dir_fd, src_relative, src_ok := match_preopen(old_path)
  252. if !src_ok {
  253. return .Invalid_Path
  254. }
  255. new_dir_fd, new_relative, new_ok := match_preopen(new_path)
  256. if !new_ok {
  257. return .Invalid_Path
  258. }
  259. return _get_platform_error(wasi.path_rename(src_dir_fd, src_relative, new_dir_fd, new_relative))
  260. }
  261. _link :: proc(old_name, new_name: string) -> Error {
  262. src_dir_fd, src_relative, src_ok := match_preopen(old_name)
  263. if !src_ok {
  264. return .Invalid_Path
  265. }
  266. new_dir_fd, new_relative, new_ok := match_preopen(new_name)
  267. if !new_ok {
  268. return .Invalid_Path
  269. }
  270. return _get_platform_error(wasi.path_link(src_dir_fd, {.SYMLINK_FOLLOW}, src_relative, new_dir_fd, new_relative))
  271. }
  272. _symlink :: proc(old_name, new_name: string) -> Error {
  273. src_dir_fd, src_relative, src_ok := match_preopen(old_name)
  274. if !src_ok {
  275. return .Invalid_Path
  276. }
  277. new_dir_fd, new_relative, new_ok := match_preopen(new_name)
  278. if !new_ok {
  279. return .Invalid_Path
  280. }
  281. if src_dir_fd != new_dir_fd {
  282. return .Invalid_Path
  283. }
  284. return _get_platform_error(wasi.path_symlink(src_relative, src_dir_fd, new_relative))
  285. }
  286. _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) {
  287. dir_fd, relative, ok := match_preopen(name)
  288. if !ok {
  289. return "", .Invalid_Path
  290. }
  291. n, _err := wasi.path_readlink(dir_fd, relative, nil)
  292. if _err != nil {
  293. err = _get_platform_error(_err)
  294. return
  295. }
  296. buf := make([]byte, n, allocator) or_return
  297. _, _err = wasi.path_readlink(dir_fd, relative, buf)
  298. s = string(buf)
  299. err = _get_platform_error(_err)
  300. return
  301. }
  302. _chdir :: proc(name: string) -> Error {
  303. return .Unsupported
  304. }
  305. _fchdir :: proc(f: ^File) -> Error {
  306. return .Unsupported
  307. }
  308. _fchmod :: proc(f: ^File, mode: int) -> Error {
  309. return .Unsupported
  310. }
  311. _chmod :: proc(name: string, mode: int) -> Error {
  312. return .Unsupported
  313. }
  314. _fchown :: proc(f: ^File, uid, gid: int) -> Error {
  315. return .Unsupported
  316. }
  317. _chown :: proc(name: string, uid, gid: int) -> Error {
  318. return .Unsupported
  319. }
  320. _lchown :: proc(name: string, uid, gid: int) -> Error {
  321. return .Unsupported
  322. }
  323. _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
  324. dir_fd, relative, ok := match_preopen(name)
  325. if !ok {
  326. return .Invalid_Path
  327. }
  328. _atime := wasi.timestamp_t(atime._nsec)
  329. _mtime := wasi.timestamp_t(mtime._nsec)
  330. return _get_platform_error(wasi.path_filestat_set_times(dir_fd, {.SYMLINK_FOLLOW}, relative, _atime, _mtime, {.MTIM, .ATIM}))
  331. }
  332. _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
  333. _atime := wasi.timestamp_t(atime._nsec)
  334. _mtime := wasi.timestamp_t(mtime._nsec)
  335. return _get_platform_error(wasi.fd_filestat_set_times(__fd(f), _atime, _mtime, {.ATIM, .MTIM}))
  336. }
  337. _exists :: proc(path: string) -> bool {
  338. dir_fd, relative, ok := match_preopen(path)
  339. if !ok {
  340. return false
  341. }
  342. _, err := wasi.path_filestat_get(dir_fd, {.SYMLINK_FOLLOW}, relative)
  343. if err != nil {
  344. return false
  345. }
  346. return true
  347. }
  348. _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
  349. f := (^File_Impl)(stream_data)
  350. fd := f.fd
  351. switch mode {
  352. case .Read:
  353. if len(p) <= 0 {
  354. return
  355. }
  356. to_read := min(len(p), MAX_RW)
  357. _n, _err := wasi.fd_read(fd, {p[:to_read]})
  358. n = i64(_n)
  359. if _err != nil {
  360. err = .Unknown
  361. } else if n == 0 {
  362. err = .EOF
  363. }
  364. return
  365. case .Read_At:
  366. if len(p) <= 0 {
  367. return
  368. }
  369. if offset < 0 {
  370. err = .Invalid_Offset
  371. return
  372. }
  373. to_read := min(len(p), MAX_RW)
  374. _n, _err := wasi.fd_pread(fd, {p[:to_read]}, wasi.filesize_t(offset))
  375. n = i64(_n)
  376. if _err != nil {
  377. err = .Unknown
  378. } else if n == 0 {
  379. err = .EOF
  380. }
  381. return
  382. case .Write:
  383. p := p
  384. for len(p) > 0 {
  385. to_write := min(len(p), MAX_RW)
  386. _n, _err := wasi.fd_write(fd, {p[:to_write]})
  387. if _err != nil {
  388. err = .Unknown
  389. return
  390. }
  391. p = p[_n:]
  392. n += i64(_n)
  393. }
  394. return
  395. case .Write_At:
  396. p := p
  397. offset := offset
  398. if offset < 0 {
  399. err = .Invalid_Offset
  400. return
  401. }
  402. for len(p) > 0 {
  403. to_write := min(len(p), MAX_RW)
  404. _n, _err := wasi.fd_pwrite(fd, {p[:to_write]}, wasi.filesize_t(offset))
  405. if _err != nil {
  406. err = .Unknown
  407. return
  408. }
  409. p = p[_n:]
  410. n += i64(_n)
  411. offset += i64(_n)
  412. }
  413. return
  414. case .Seek:
  415. #assert(int(wasi.whence_t.SET) == int(io.Seek_From.Start))
  416. #assert(int(wasi.whence_t.CUR) == int(io.Seek_From.Current))
  417. #assert(int(wasi.whence_t.END) == int(io.Seek_From.End))
  418. switch whence {
  419. case .Start, .Current, .End:
  420. break
  421. case:
  422. err = .Invalid_Whence
  423. return
  424. }
  425. _n, _err := wasi.fd_seek(fd, wasi.filedelta_t(offset), wasi.whence_t(whence))
  426. #partial switch _err {
  427. case .INVAL:
  428. err = .Invalid_Offset
  429. case:
  430. err = .Unknown
  431. case .SUCCESS:
  432. n = i64(_n)
  433. }
  434. return
  435. case .Size:
  436. stat, _err := wasi.fd_filestat_get(fd)
  437. if _err != nil {
  438. err = .Unknown
  439. return
  440. }
  441. n = i64(stat.size)
  442. return
  443. case .Flush:
  444. ferr := _sync(&f.file)
  445. err = error_to_io_error(ferr)
  446. return
  447. case .Close, .Destroy:
  448. ferr := _close(f)
  449. err = error_to_io_error(ferr)
  450. return
  451. case .Query:
  452. return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Destroy, .Query})
  453. case:
  454. return 0, .Empty
  455. }
  456. }