wardite 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/wardite/wasi.rb CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  require "wardite/wasm_module"
4
4
  require "wardite/wasi/consts"
5
+ require "wardite/wasi/preopens"
6
+ require "wardite/wasi/dirent_cache"
5
7
  require "securerandom"
6
8
  require "fcntl"
7
9
 
@@ -10,18 +12,136 @@ module Wardite
10
12
  include ValueHelper
11
13
  include WasmModule
12
14
 
13
- attr_accessor :fd_table #: Array[(IO|File)]
15
+ attr_accessor :fd_table #: Hash[Integer, (IO|File|::Wardite::Wasi::PreopenedDir)]
16
+ attr_accessor :fd_count #: Integer
14
17
  attr_accessor :argv #: Array[String]
18
+ attr_accessor :mapdir #: Hash[String, String]
19
+ attr_accessor :dirent_cache #: Hash[Integer, ::Wardite::Wasi::DirentCache]
15
20
 
16
21
  # @rbs argv: Array[String]
22
+ # @rbs mapdir: Hash[String, String]
17
23
  # @rbs return: void
18
- def initialize(argv: [])
19
- @fd_table = [
20
- STDIN,
21
- STDOUT,
22
- STDERR,
23
- ]
24
+ def initialize(argv: [], mapdir: {})
25
+ @fd_table = {
26
+ 0 => STDIN,
27
+ 1 => STDOUT,
28
+ 2 => STDERR,
29
+ }
30
+ @fd_count = 3
24
31
  @argv = argv
32
+ @mapdir = mapdir
33
+
34
+ @dirent_cache = {}
35
+ end
36
+
37
+ # @rbs file: IO|File|::Wardite::Wasi::PreopenedDir
38
+ # @rbs return: Integer
39
+ def set_fd(file)
40
+ fd = @fd_count
41
+ @fd_table[fd] = file
42
+ @fd_count += 1
43
+ fd
44
+ end
45
+
46
+ # @rbs path: String
47
+ # @rbs guest_path: String
48
+ # @rbs return: void
49
+ def set_preopened_dir(path, guest_path)
50
+ fd = @fd_count
51
+ set_fd(::Wardite::Wasi::PreopenedDir.new(path, guest_path, fd))
52
+ end
53
+
54
+ # @rbs orig_path: String
55
+ # @rbs return: String
56
+ def resolv_path(orig_path)
57
+ @mapdir.each do |k, v|
58
+ if orig_path.start_with?(k)
59
+ return v + orig_path[k.size..-1].to_s
60
+ end
61
+ end
62
+
63
+ return orig_path
64
+ end
65
+
66
+ # @rbs atfd: Integer
67
+ # @rbs target: String
68
+ # @rbs return: String
69
+ def get_path_at(atfd, target)
70
+ target = resolv_path(target)
71
+
72
+ at = @fd_table[atfd]
73
+ pwd = case at
74
+ when Wasi::PreopenedDir
75
+ at.guest_path
76
+ when File
77
+ Dir.fchdir(at.fileno) do
78
+ Dir.pwd
79
+ end
80
+ else
81
+ raise ArgumentError, "invalid fd: #{atfd}"
82
+ end
83
+
84
+ ret = File.expand_path(target, pwd)
85
+ ret
86
+ end
87
+
88
+ # @rbs dirflags: Integer
89
+ # @rbs oflags: Integer
90
+ # @rbs fdflags: Integer
91
+ # @rbs rights: Integer
92
+ def interpret_open_flags(dirflags, oflags, fdflags, rights)
93
+ open_flags = 0
94
+ if dirflags & Wasi::LOOKUP_SYMLINK_FOLLOW == 0
95
+ open_flags |= File::Constants::NOFOLLOW
96
+ end
97
+ if oflags & Wasi::O_DIRECTORY != 0
98
+ # open_flags |= File::Constants::DIRECTORY
99
+ $stderr.puts "O_DIRECTORY is not supported, ignore" if ENV["WARDITE_TRACE"]
100
+ elsif oflags & Wasi::O_EXCL != 0
101
+ open_flags |= File::Constants::EXCL
102
+ end
103
+
104
+ default_mode = File::Constants::RDONLY
105
+ if oflags & Wasi::O_TRUNC != 0
106
+ open_flags |= File::Constants::TRUNC
107
+ default_mode = File::Constants::RDWR
108
+ end
109
+ if oflags & Wasi::O_CREAT != 0
110
+ open_flags |= File::Constants::CREAT
111
+ default_mode = File::Constants::RDWR
112
+ end
113
+ if fdflags & Wasi::FD_NONBLOCK != 0
114
+ open_flags |= File::Constants::NONBLOCK
115
+ end
116
+ if fdflags & Wasi::FD_APPEND != 0
117
+ open_flags |= File::Constants::APPEND
118
+ default_mode = File::Constants::RDWR
119
+ end
120
+ if fdflags & Wasi::FD_DSYNC != 0
121
+ open_flags |= File::Constants::DSYNC
122
+ end
123
+ if fdflags & Wasi::FD_RSYNC != 0
124
+ open_flags |= File::Constants::RSYNC
125
+ end
126
+ if fdflags & Wasi::FD_SYNC != 0
127
+ open_flags |= File::Constants::SYNC
128
+ end
129
+
130
+ r = Wasi::RIGHT_FD_READ
131
+ w = Wasi::RIGHT_FD_WRITE
132
+ rw = r | w
133
+ case
134
+ when (rights & rw) == rw
135
+ open_flags |= File::Constants::RDWR
136
+ when (rights & w) == w
137
+ open_flags |= File::Constants::WRONLY
138
+ when (rights & r) == r
139
+ open_flags |= File::Constants::RDONLY
140
+ else
141
+ open_flags |= default_mode
142
+ end
143
+
144
+ open_flags
25
145
  end
26
146
 
27
147
  # @rbs store: Store
@@ -137,17 +257,216 @@ module Wardite
137
257
  0
138
258
  end
139
259
 
260
+ # @rbs store: Store
261
+ # @rbs args: Array[wasmValue]
262
+ # @rbs return: Object
263
+ def path_create_directory(store, args)
264
+ fd = args[0].value.to_i
265
+ path = args[1].value.to_i
266
+ path_len = args[2].value.to_i
267
+ path_str = store.memories[0].data[path...(path+path_len)]
268
+ if !path_str
269
+ return Wasi::ENOENT
270
+ end
271
+
272
+ target = get_path_at(fd, path_str)
273
+ Dir.mkdir(target, 0700)
274
+ 0
275
+ # TODO; rescue EBADF, ENOTDIR
276
+ end
277
+
278
+ # @rbs store: Store
279
+ # @rbs args: Array[wasmValue]
280
+ # @rbs return: Object
281
+ def path_filestat_get(store, args)
282
+ fd = args[0].value.to_i
283
+ flags = args[1].value.to_i
284
+ path = args[2].value.to_i
285
+ path_len = args[3].value.to_i
286
+ target = get_path_at(fd, store.memories[0].data[path...(path+path_len)].to_s)
287
+
288
+ stat = File.stat(target)
289
+ memory = store.memories[0]
290
+ binformat = [
291
+ stat.dev, stat.ino, Wasi.to_ftype(stat.ftype), stat.nlink,
292
+ stat.size, stat.atime.to_i, stat.mtime.to_i, stat.ctime.to_i
293
+ ].pack("Q8")
294
+ memory.data[flags...(flags+binformat.size)] = binformat
295
+ 0
296
+ rescue Errno::ENOENT
297
+ return Wasi::ENOENT
298
+ end
299
+
300
+ # @rbs store: Store
301
+ # @rbs args: Array[wasmValue]
302
+ # @rbs return: Object
303
+ def path_filestat_set_times(store, args)
304
+ fd = args[0].value.to_i
305
+ # TODO: flags support
306
+ _flags = args[1].value.to_i
307
+ path = args[2].value.to_i
308
+ path_len = args[3].value.to_i
309
+ atim = args[4].value.to_i # nanoseconds
310
+ mtim = args[5].value.to_i # nanoseconds
311
+ target = get_path_at(fd, store.memories[0].data[path...(path+path_len)].to_s)
312
+
313
+ atime = Time.at(atim.to_f / 1_000_000_000)
314
+ mtime = Time.at(mtim.to_f / 1_000_000_000)
315
+ File.utime(atime, mtime, target)
316
+ 0
317
+ end
318
+
319
+ # @rbs store: Store
320
+ # @rbs args: Array[wasmValue]
321
+ # @rbs return: Object
322
+ def path_link(store, args)
323
+ old_fd = args[0].value.to_i
324
+ old_path = args[1].value.to_i
325
+ old_path_len = args[2].value.to_i
326
+ old_name = get_path_at(old_fd, store.memories[0].data[old_path...(old_path+old_path_len)].to_s)
327
+
328
+ new_fd = args[3].value.to_i
329
+ new_path = args[4].value.to_i
330
+ new_path_len = args[5].value.to_i
331
+ new_name = get_path_at(new_fd, store.memories[0].data[new_path...(new_path+new_path_len)].to_s)
332
+
333
+ File.link(old_name, new_name)
334
+ 0
335
+ end
336
+
337
+ # @rbs store: Store
338
+ # @rbs args: Array[wasmValue]
339
+ # @rbs return: Object
340
+ def path_open(store, args)
341
+ dirfd = args[0].value.to_i
342
+ dirflags = args[1].value.to_i
343
+ path = args[2].value.to_i
344
+ path_len = args[3].value.to_i
345
+ oflags = args[4].value.to_i
346
+ fs_rights_base = args[5].value.to_i
347
+ _fs_rights_inheriting = args[6].value.to_i
348
+ fs_flags = args[7].value.to_i
349
+ fd_off = args[8].value.to_i
350
+
351
+ path_name = get_path_at(dirfd, store.memories[0].data[path...(path+path_len)].to_s)
352
+ open_flags = interpret_open_flags(dirflags, oflags, fs_flags, fs_rights_base)
353
+ is_dir = (oflags & Wasi::O_DIRECTORY) != 0
354
+ if is_dir && (oflags & Wasi::O_CREAT) != 0
355
+ return Wasi::EINVAL
356
+ end
357
+
358
+ file = File.open(path_name, open_flags, 0600)
359
+ fd = set_fd file
360
+
361
+ memory = store.memories[0]
362
+ memory.data[fd_off...(fd_off+4)] = [fd].pack("I!")
363
+ 0
364
+ rescue Errno::ENOENT
365
+ return Wasi::ENOENT
366
+ end
367
+
368
+ # @rbs store: Store
369
+ # @rbs args: Array[wasmValue]
370
+ # @rbs return: Object
371
+ def path_readlink(store, args)
372
+ fd = args[0].value.to_i
373
+ path = args[1].value.to_i
374
+ path_len = args[2].value.to_i
375
+ buf = args[3].value.to_i
376
+ buf_len = args[4].value.to_i
377
+ result_buf = args[5].value.to_i
378
+
379
+ if buf_len <= 0 || path_len <= 0
380
+ return Wasi::EINVAL
381
+ end
382
+ target = get_path_at(fd, store.memories[0].data[path...(path+path_len)].to_s)
383
+
384
+ link_target = File.readlink(target)
385
+ if link_target.size > buf_len
386
+ return Wasi::ENAMETOOLONG
387
+ end
388
+
389
+ memory = store.memories[0]
390
+ memory.data[buf...(buf+link_target.size)] = link_target
391
+ memory.data[result_buf...(result_buf+4)] = [link_target.size].pack("I!")
392
+ 0
393
+ end
394
+
395
+ # @rbs store: Store
396
+ # @rbs args: Array[wasmValue]
397
+ # @rbs return: Object
398
+ def path_remove_directory(store, args)
399
+ fd = args[0].value.to_i
400
+ path = args[1].value.to_i
401
+ path_len = args[2].value.to_i
402
+ path_str = store.memories[0].data[path...(path+path_len)].to_s
403
+ target = get_path_at(fd, path_str)
404
+
405
+ Dir.rmdir(target)
406
+ 0
407
+ end
408
+
409
+ # @rbs store: Store
410
+ # @rbs args: Array[wasmValue]
411
+ # @rbs return: Object
412
+ def path_rename(store, args)
413
+ fd = args[0].value.to_i
414
+ old_path = args[1].value.to_i
415
+ old_path_len = args[2].value.to_i
416
+
417
+ new_fd = args[3].value.to_i
418
+ new_path = args[4].value.to_i
419
+ new_path_len = args[5].value.to_i
420
+
421
+ old_target = get_path_at(fd, store.memories[0].data[old_path...(old_path+old_path_len)].to_s)
422
+ new_target = get_path_at(new_fd, store.memories[0].data[new_path...(new_path+new_path_len)].to_s)
423
+
424
+ File.rename(old_target, new_target)
425
+ 0
426
+ end
427
+
428
+ # @rbs store: Store
429
+ # @rbs args: Array[wasmValue]
430
+ # @rbs return: Object
431
+ def path_symlink(store, args)
432
+ old_path = args[0].value.to_i
433
+ old_path_len = args[1].value.to_i
434
+ old_name = store.memories[0].data[old_path...(old_path+old_path_len)].to_s
435
+
436
+ fd = args[2].value.to_i
437
+ new_path = args[3].value.to_i
438
+ new_path_len = args[4].value.to_i
439
+ new_name = get_path_at(fd, store.memories[0].data[new_path...(new_path+new_path_len)].to_s)
440
+
441
+ File.symlink(old_name, new_name)
442
+ 0
443
+ end
444
+
445
+ # @rbs store: Store
446
+ # @rbs args: Array[wasmValue]
447
+ # @rbs return: Object
448
+ def path_unlink_file(store, args)
449
+ fd = args[0].value.to_i
450
+ path = args[1].value.to_i
451
+ path_len = args[2].value.to_i
452
+ path_str = store.memories[0].data[path...(path+path_len)].to_s
453
+ target = get_path_at(fd, path_str)
454
+
455
+ File.unlink(target)
456
+ 0
457
+ end
458
+
140
459
  # @rbs store: Store
141
460
  # @rbs args: Array[wasmValue]
142
461
  # @rbs return: Object
143
462
  def fd_prestat_get(store, args)
144
463
  fd = args[0].value.to_i
145
464
  prestat_offset = args[1].value.to_i
146
- if fd >= @fd_table.size
465
+ if fd >= fd_count
147
466
  return Wasi::EBADF
148
467
  end
149
468
  file = @fd_table[fd]
150
- if !file.is_a?(File)
469
+ if !file.is_a?(Wasi::PreopenedDir)
151
470
  return Wasi::EBADF
152
471
  end
153
472
  name = file.path
@@ -158,6 +477,31 @@ module Wardite
158
477
  0
159
478
  end
160
479
 
480
+ # @rbs store: Store
481
+ # @rbs args: Array[wasmValue]
482
+ # @rbs return: Object
483
+ def fd_prestat_dir_name(store, args)
484
+ fd = args[0].value.to_i
485
+ path = args[1].value.to_i
486
+ path_len = args[2].value.to_i
487
+ if fd >= fd_count
488
+ return Wasi::EBADF
489
+ end
490
+ file = @fd_table[fd]
491
+ if !file.is_a?(Wasi::PreopenedDir)
492
+ return Wasi::EBADF
493
+ end
494
+ name = file.path
495
+ if name.size > path_len
496
+ return Wasi::ENAMETOOLONG
497
+ end
498
+ name += ("\0" * (path_len - name.size))
499
+
500
+ memory = store.memories[0]
501
+ memory.data[path...(path+name.size)] = name
502
+ 0
503
+ end
504
+
161
505
  # @rbs store: Store
162
506
  # @rbs args: Array[wasmValue]
163
507
  # @rbs return: Object
@@ -174,7 +518,7 @@ module Wardite
174
518
  raise Wardite::ArgumentError, "args too short"
175
519
  end
176
520
  file = self.fd_table[fd]
177
- return Wasi::EBADF if !file
521
+ return Wasi::EBADF if !file || file.is_a?(Wasi::PreopenedDir)
178
522
  memory = store.memories[0]
179
523
  nwritten = 0
180
524
  iovs_len.times do
@@ -207,7 +551,7 @@ module Wardite
207
551
  raise Wardite::ArgumentError, "args too short"
208
552
  end
209
553
  file = self.fd_table[fd]
210
- return Wasi::EBADF if !file
554
+ return Wasi::EBADF if !file || file.is_a?(Wasi::PreopenedDir)
211
555
  memory = store.memories[0]
212
556
  nread = 0
213
557
 
@@ -218,10 +562,10 @@ module Wardite
218
562
  iovs += 4
219
563
  buf = file.read(slen)
220
564
  if !buf
221
- return Wasi::EFAULT
565
+ break 0
222
566
  end
223
- memory.data[start...(start+slen)] = buf
224
- nread += slen
567
+ memory.data[start...(start+buf.size)] = buf
568
+ nread += buf.size
225
569
  end
226
570
 
227
571
  memory.data[rp...(rp+4)] = [nread].pack("I!")
@@ -234,12 +578,16 @@ module Wardite
234
578
  def fd_fdstat_get(store, args)
235
579
  fd = args[0].value.to_i
236
580
  fdstat_offset = args[1].value.to_i
237
- if fd >= @fd_table.size
581
+ if fd >= fd_count
238
582
  return Wasi::EBADF
239
583
  end
240
584
  file = @fd_table[fd]
241
585
  fdflags = 0
242
- if file.is_a?(IO)
586
+ if !file
587
+ return Wasi::EBADF
588
+ elsif file.is_a?(Wasi::PreopenedDir)
589
+ file = File.open(file.guest_path) # reopen directory
590
+ elsif file.is_a?(IO) && !file.is_a?(File)
243
591
  fdflags |= Wasi::FD_APPEND
244
592
  else
245
593
  if (Fcntl::O_APPEND & file.fcntl(Fcntl::F_GETFL, 0)) != 0
@@ -282,10 +630,14 @@ module Wardite
282
630
  def fd_filestat_get(store, args)
283
631
  fd = args[0].value.to_i
284
632
  filestat_offset = args[1].value.to_i
285
- if fd >= @fd_table.size
633
+ if fd >= fd_count
286
634
  return Wasi::EBADF
287
635
  end
288
636
  file = @fd_table[fd]
637
+ if !file || file.is_a?(Wasi::PreopenedDir)
638
+ return Wasi::EBADF
639
+ end
640
+
289
641
  stat = file.stat #: File::Stat
290
642
  memory = store.memories[0]
291
643
  binformat = [stat.dev, stat.ino, Wasi.to_ftype(stat.ftype), stat.nlink, stat.size, stat.atime.to_i, stat.mtime.to_i, stat.ctime.to_i].pack("Q8")
@@ -293,6 +645,94 @@ module Wardite
293
645
  0
294
646
  end
295
647
 
648
+ # @rbs store: Store
649
+ # @rbs args: Array[wasmValue]
650
+ # @rbs return: Object
651
+ def fd_readdir(store, args)
652
+ fd = args[0].value.to_i
653
+ buf = args[1].value.to_i
654
+ buf_len = args[2].value.to_i
655
+ cookie = args[3].value.to_i
656
+ result_buf_used = args[4].value.to_i
657
+
658
+ if buf_len < 24 # when smaller than Dirent header size: Q! Q! I! I!
659
+ return Wasi::EINVAL
660
+ end
661
+
662
+ memory = store.memories[0]
663
+
664
+ if dirent_cache[fd]&.eof
665
+ dirent_cache.delete(fd)
666
+ end
667
+ dir = @fd_table[fd]
668
+ if dir.is_a?(Wasi::PreopenedDir) || (dir.is_a?(File) && dir.stat.ftype == "directory")
669
+ dirent_cache[fd] ||= Wasi::DirentCache.new(dir.path)
670
+ else
671
+ return Wasi::EBADF
672
+ end
673
+
674
+ dirent = dirent_cache[fd]
675
+ bindata, is_truncated = dirent.fetch_entries_binary(buf_len, cookie)
676
+
677
+ bufused = bindata.size
678
+ # bufused == buf_len means more dirents exist
679
+ if is_truncated
680
+ bufused = buf_len
681
+ memory.data[buf...(buf+buf_len)] = bindata + "\0" * (buf_len - bindata.size)
682
+ else
683
+ dirent.eof = true
684
+ memory.data[buf...(buf+bindata.size)] = bindata
685
+ end
686
+
687
+ memory.data[result_buf_used...(result_buf_used+4)] = [bufused].pack("I!")
688
+ 0
689
+ end
690
+
691
+ # @rbs store: Store
692
+ # @rbs args: Array[wasmValue]
693
+ def fd_tell(store, args)
694
+ fd = args[0].value.to_i
695
+ result_buf = args[1].value.to_i
696
+ if fd >= fd_count
697
+ return Wasi::EBADF
698
+ end
699
+ file = @fd_table[fd]
700
+ if !file || file.is_a?(Wasi::PreopenedDir)
701
+ return Wasi::EBADF
702
+ end
703
+
704
+ memory = store.memories[0]
705
+ memory.data[result_buf...(result_buf+4)] = [file.tell].pack("I!")
706
+ 0
707
+ rescue Errno::EBADF
708
+ return Wasi::EBADF
709
+ end
710
+
711
+ # @rbs store: Store
712
+ # @rbs args: Array[wasmValue]
713
+ # @rbs return: Object
714
+ def fd_close(store, args)
715
+ fd = args[0].value.to_i
716
+ if fd >= fd_count
717
+ return Wasi::EBADF
718
+ end
719
+ file = @fd_table[fd]
720
+ if !file
721
+ return Wasi::EBADF
722
+ end
723
+ if file.is_a?(Wasi::PreopenedDir)
724
+ # do nothing for preopened dir...?
725
+ $stderr.puts "close preopened dir?: #{file.guest_path}"
726
+ @fd_table.delete(fd)
727
+ return 0
728
+ end
729
+ file.close
730
+ @fd_table.delete(fd)
731
+ 0
732
+ rescue Errno::EBADF
733
+ return Wasi::EBADF
734
+ end
735
+
296
736
  # @rbs store: Store
297
737
  # @rbs args: Array[wasmValue]
298
738
  # @rbs return: Object
data/lib/wardite.rb CHANGED
@@ -38,7 +38,7 @@ module Wardite
38
38
 
39
39
  attr_reader :import_object #: Hash[Symbol, wasmModule]
40
40
 
41
- attr_accessor :wasi #: WasiSnapshotPreview1?
41
+ attr_accessor :wasi #: ::Wardite::WasiSnapshotPreview1?
42
42
 
43
43
  # @rbs import_object: Hash[Symbol, wasmModuleSrc]
44
44
  # @rbs &blk: (Instance) -> void
@@ -354,6 +354,7 @@ module Wardite
354
354
  # @rbs external_function: ExternalFunction
355
355
  # @rbs return: wasmValue|nil
356
356
  def invoke_external(external_function)
357
+ $stderr.puts "[trace] call external function: #{external_function.name}" if ENV["WARDITE_TRACE"]
357
358
  local_start = stack.size - external_function.callsig.size
358
359
  args = stack[local_start..]
359
360
  if !args
@@ -803,6 +804,12 @@ module Wardite
803
804
  def respond_to? name
804
805
  callable?(name) || super
805
806
  end
807
+
808
+ # @rbs args: Array[Object]
809
+ # @rbs return: Object?
810
+ def _start(*args)
811
+ call(:_start, args)
812
+ end
806
813
  end
807
814
 
808
815
  class Type