1use super::handles::{BackendReader, ChargedDirIter};
3use super::{
4 HostMutationBudget, MODULE_NAME, append, append_text, backend_error, begin_work, build_info,
5 charge_path_work, effects, f64_arg, i32_flag, list_next, make_handle_iterator, open_writer,
6 peek_file, read_byte_range, read_whole_capped, reader_next, stat_entry, summary, write,
7 write_text, writer_payload,
8};
9use crate::runtime::StoreData;
10use crate::runtime::call_log::{Payload, Side, record_payload};
11use crate::runtime::effects as runtime_effects;
12use crate::runtime::filesystem::FilesystemError as PortError;
13use crate::runtime::fs::ContainError;
14use crate::runtime::fuel;
15use crate::runtime::host::{
16 abi_arg, abi_result, read_string_arg, read_uint8_array_arg, register_host_fn,
17 register_host_fn_async, register_host_value_async, write_submilli_string_struct,
18 write_submilli_uint8array_struct,
19};
20use crate::runtime::intrinsic_types::{IntrinsicTypes, build_intrinsic_types};
21
22use crate::stdlib::shared::{check_and_approve_call, require_writable};
23use wasmtime::{FuncType, HeapType, Linker, RefType, Val, ValType};
24
25pub fn install(linker: &mut Linker<StoreData>) -> wasmtime::Result<()> {
26 let engine = linker.engine().clone();
27 let intr = build_intrinsic_types(&engine)?;
28 let string = ValType::Ref(RefType::new(
29 false,
30 HeapType::ConcreteStruct(intr.string.clone()),
31 ));
32 let uint8 = ValType::Ref(RefType::new(
33 false,
34 HeapType::ConcreteStruct(intr.uint8_array.clone()),
35 ));
36 let object = ValType::Ref(RefType::new(
37 false,
38 HeapType::ConcreteStruct(intr.object.clone()),
39 ));
40 let nullable_object = ValType::Ref(RefType::new(
41 true,
42 HeapType::ConcreteStruct(intr.object.clone()),
43 ));
44
45 let fs_fn = |name: &str| crate::mangle::package_symbol(MODULE_NAME, name);
46
47 register_host_fn(
48 linker,
49 MODULE_NAME,
50 fs_fn("maxReadSize"),
51 FuncType::new(&engine, [], [ValType::F64]),
52 |caller, _params, results| {
53 *abi_result(results, 0)? = Val::F64((caller.data().fs_max_read_size as f64).to_bits());
54 Ok(())
55 },
56 )?;
57
58 register_host_fn(
59 linker,
60 MODULE_NAME,
61 fs_fn("cwd"),
62 FuncType::new(&engine, [], [string.clone()]),
63 |caller, _params, results| {
64 let cwd = caller.data().filesystem.cwd().to_owned();
65 let value = write_submilli_string_struct(&mut *caller, &cwd)?;
66 let result = results.first_mut().ok_or_else(|| {
67 crate::runtime::host::fatal_host_error("fs.cwd: missing result slot")
68 })?;
69 *result = Val::AnyRef(Some(value.to_anyref()));
70 Ok(())
71 },
72 )?;
73
74 register_host_fn(
75 linker,
76 MODULE_NAME,
77 fs_fn("info"),
78 FuncType::new(&engine, [], [nullable_object.clone()]),
79 |caller, _params, results| {
80 *abi_result(results, 0)? = build_info(caller)?;
81 Ok(())
82 },
83 )?;
84
85 register_host_value_async(
86 linker,
87 MODULE_NAME,
88 fs_fn("exists"),
89 FuncType::new(&engine, [string.clone()], [ValType::I32]),
90 |caller, params| {
91 Box::pin(async move {
92 let path = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.exists")?;
93 let ticket = check_and_approve_call(
94 &mut *caller,
95 "fs.stat",
96 serde_json::json!({ "path": &path }),
97 )
98 .await?;
99 let filesystem = caller.data().filesystem.clone();
100 let found = runtime_effects::execute(
109 caller,
110 "fs.exists",
111 path,
112 move |caller, effect_id, path| {
113 Box::pin(async move {
114 charge_path_work(caller, &[&path])?;
115 let work = begin_work(caller)?;
116 let found = match filesystem
117 .metadata(&effect_id, Some(work.clone()), &path, true)
118 .await
119 {
120 Ok(_) => true,
121 Err(
122 PortError::Containment(
123 ContainError::Escape | ContainError::Io(_),
124 )
125 | PortError::Io(_),
126 ) => false,
127 Err(e) => return Err(backend_error("fs.exists", &path, e)),
128 };
129 work.settle(&mut *caller)?;
130 Ok(found)
131 })
132 },
133 )
134 .await?;
135 record_payload(&*caller, ticket, Side::Response, || {
136 Payload::meta(serde_json::json!({ "exists": found }))
137 });
138 Ok(Val::I32(i32::from(found)))
139 })
140 },
141 )?;
142
143 register_host_value_async(
144 linker,
145 MODULE_NAME,
146 fs_fn("size"),
147 FuncType::new(&engine, [string.clone()], [ValType::F64]),
148 |caller, params| {
149 Box::pin(async move {
150 let path = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.size")?;
151 let ticket = check_and_approve_call(
152 &mut *caller,
153 "fs.stat",
154 serde_json::json!({ "path": &path }),
155 )
156 .await?;
157 let size =
158 runtime_effects::execute(caller, "fs.size", path, |caller, effect_id, path| {
159 Box::pin(async move {
160 charge_path_work(caller, &[&path])?;
161 let work = begin_work(caller)?;
162 let meta = caller
163 .data()
164 .filesystem
165 .metadata(&effect_id, Some(work.clone()), &path, true)
166 .await
167 .map_err(|e| backend_error("fs.size", &path, e))?;
168 if meta.is_dir() {
169 wasmtime::bail!("fs.size {}: path is a directory", path);
170 }
171 work.settle(&mut *caller)?;
172 Ok(meta.len())
173 })
174 })
175 .await?;
176 record_payload(&*caller, ticket, Side::Response, || {
177 Payload::meta(serde_json::json!({ "size": size }))
178 });
179 Ok(Val::F64((size as f64).to_bits()))
180 })
181 },
182 )?;
183
184 register_host_value_async(
185 linker,
186 MODULE_NAME,
187 fs_fn("stat"),
188 FuncType::new(&engine, [string.clone()], [nullable_object.clone()]),
189 |caller, params| {
190 Box::pin(async move {
191 let path = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.stat")?;
192 check_and_approve_call(
193 &mut *caller,
194 "fs.stat",
195 serde_json::json!({ "path": &path }),
196 )
197 .await?;
198 stat_entry(caller, &path).await
199 })
200 },
201 )?;
202
203 register_host_value_async(
204 linker,
205 MODULE_NAME,
206 fs_fn("peek"),
207 FuncType::new(&engine, [string.clone()], [nullable_object.clone()]),
208 |caller, params| {
209 Box::pin(async move {
210 let path = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.peek")?;
211 check_and_approve_call(
212 &mut *caller,
213 "fs.stat",
214 serde_json::json!({ "path": &path }),
215 )
216 .await?;
217 peek_file(caller, &path).await
218 })
219 },
220 )?;
221
222 register_host_value_async(
223 linker,
224 MODULE_NAME,
225 fs_fn("read"),
226 FuncType::new(&engine, [string.clone()], [uint8.clone()]),
227 |caller, params| {
228 Box::pin(async move {
229 let path = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.read")?;
230 let output = read_whole_capped(caller, &path, "read").await?;
231 match &output {
232 Some(bytes) => {
233 let arr = write_submilli_uint8array_struct(caller, &bytes.0)?;
234 Ok(Val::AnyRef(Some(arr.to_anyref())))
235 }
236 None => Err(crate::runtime::host::range_error(
237 "file exceeds fs.maxReadSize()",
238 )),
239 }
240 })
241 },
242 )?;
243
244 register_host_value_async(
245 linker,
246 MODULE_NAME,
247 fs_fn("readText"),
248 FuncType::new(&engine, [string.clone()], [string.clone()]),
249 |caller, params| {
250 Box::pin(async move {
251 let path = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.readText")?;
252 let output = read_whole_capped(caller, &path, "readText").await?;
253 match &output {
254 Some(bytes) => {
255 let mut text = String::from_utf8_lossy(&bytes.0).into_owned();
257 if text.starts_with('\u{FEFF}') {
258 text.remove(0);
259 }
260 let st = write_submilli_string_struct(caller, &text)?;
261 Ok(Val::AnyRef(Some(st.to_anyref())))
262 }
263 None => Err(crate::runtime::host::range_error(
264 "file exceeds fs.maxReadSize()",
265 )),
266 }
267 })
268 },
269 )?;
270
271 register_host_value_async(
272 linker,
273 MODULE_NAME,
274 fs_fn("readBytes"),
275 FuncType::new(
276 &engine,
277 [string.clone(), ValType::F64, ValType::F64],
278 [uint8.clone()],
279 ),
280 |caller, params| {
281 Box::pin(async move {
282 let path = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.readBytes")?;
283 let offset = f64_arg(abi_arg(params, 1)?, "fs.readBytes (offset)")? as i64;
284 let length = f64_arg(abi_arg(params, 2)?, "fs.readBytes (length)")? as i64;
285 let ticket = check_and_approve_call(
286 &mut *caller,
287 "fs.read",
288 serde_json::json!({
289 "path": &path,
290 "length": length,
291 }),
292 )
293 .await?;
294 let bytes = read_byte_range(caller, &path, offset, length).await?;
295 record_payload(&*caller, ticket, Side::Response, || {
296 Payload::meta(serde_json::json!({ "offset": offset })).with_body(&bytes.0)
297 });
298 let arr = write_submilli_uint8array_struct(caller, &bytes.0)?;
299 Ok(Val::AnyRef(Some(arr.to_anyref())))
300 })
301 },
302 )?;
303
304 register_host_fn_async(
305 linker,
306 MODULE_NAME,
307 fs_fn("write"),
308 FuncType::new(&engine, [string.clone(), uint8.clone()], []),
309 |caller, params, _results| {
310 Box::pin(async move {
311 let path = read_string_arg(caller, abi_arg(params, 0)?, "fs.write")?;
312 let content = read_uint8_array_arg(caller, abi_arg(params, 1)?, "fs.write")?;
313 write(caller, path, content).await
314 })
315 },
316 )?;
317
318 register_host_fn_async(
319 linker,
320 MODULE_NAME,
321 fs_fn("writeText"),
322 FuncType::new(&engine, [string.clone(), string.clone()], []),
323 |caller, params, _results| {
324 Box::pin(async move {
325 let path = read_string_arg(caller, abi_arg(params, 0)?, "fs.writeText")?;
326 let content = read_string_arg(caller, abi_arg(params, 1)?, "fs.writeText")?;
327 write_text(caller, path, content).await
328 })
329 },
330 )?;
331
332 register_host_fn_async(
333 linker,
334 MODULE_NAME,
335 fs_fn("append"),
336 FuncType::new(&engine, [string.clone(), uint8.clone()], []),
337 |caller, params, _results| {
338 Box::pin(async move {
339 let path = read_string_arg(caller, abi_arg(params, 0)?, "fs.append")?;
340 let content = read_uint8_array_arg(caller, abi_arg(params, 1)?, "fs.append")?;
341 append(caller, path, content).await
342 })
343 },
344 )?;
345
346 register_host_fn_async(
347 linker,
348 MODULE_NAME,
349 fs_fn("appendText"),
350 FuncType::new(&engine, [string.clone(), string.clone()], []),
351 |caller, params, _results| {
352 Box::pin(async move {
353 let path = read_string_arg(caller, abi_arg(params, 0)?, "fs.appendText")?;
354 let content = read_string_arg(caller, abi_arg(params, 1)?, "fs.appendText")?;
355 append_text(caller, path, content).await
356 })
357 },
358 )?;
359
360 register_host_fn_async(
361 linker,
362 MODULE_NAME,
363 fs_fn("mkdir"),
364 FuncType::new(&engine, [string.clone(), nullable_object.clone()], []),
365 |caller, params, _results| {
366 Box::pin(async move {
367 let path = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.mkdir")?;
368 let recursive = recursive_option(caller, abi_arg(params, 1)?).await?;
369 let ticket = check_and_approve_call(
370 &mut *caller,
371 "fs.mkdir",
372 serde_json::json!({ "path": &path, "recursive": recursive }),
373 )
374 .await?;
375 let request = effects::PathMutation { path, recursive };
376 runtime_effects::execute(
377 caller,
378 "fs.mkdir",
379 request,
380 move |caller, effect_id, request| {
381 Box::pin(async move {
382 charge_path_work(caller, &[&request.path])?;
383 let effects::PathMutation { path, recursive } = request;
384 let filesystem = caller.data().filesystem.clone();
385 let work = begin_work(caller)?;
386 let placement = filesystem
387 .placement(&path)
388 .await
389 .map_err(|e| backend_error("fs.mkdir", &path, e))?;
390 if !(recursive
391 && filesystem
392 .metadata(&effect_id, Some(work.clone()), &path, true)
393 .await
394 .is_ok_and(|m| m.is_dir()))
395 {
396 require_writable(&*caller, ticket, &placement, "fs.mkdir", &path)?;
397 }
398 let result = filesystem
399 .mkdir(&effect_id, Some(work.clone()), &path, recursive)
400 .await;
401 work.settle(&mut *caller)?;
402 result.map_err(|e| backend_error("fs.mkdir", &path, e))?;
403 Ok(())
404 })
405 },
406 )
407 .await
408 })
409 },
410 )?;
411
412 register_host_fn_async(
413 linker,
414 MODULE_NAME,
415 fs_fn("remove"),
416 FuncType::new(&engine, [string.clone(), nullable_object.clone()], []),
417 |caller, params, _results| {
418 Box::pin(async move {
419 let path = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.remove")?;
420 let recursive = recursive_option(caller, abi_arg(params, 1)?).await?;
421 let ticket = check_and_approve_call(
422 &mut *caller,
423 "fs.remove",
424 serde_json::json!({ "path": &path, "recursive": recursive }),
425 )
426 .await?;
427 let request = effects::PathMutation { path, recursive };
428 runtime_effects::execute(
429 caller,
430 "fs.remove",
431 request,
432 move |caller, effect_id, request| {
433 Box::pin(async move {
434 charge_path_work(caller, &[&request.path])?;
435 let effects::PathMutation { path, recursive } = request;
436 let filesystem = caller.data().filesystem.clone();
437 let work = begin_work(caller)?;
438 let placement = filesystem
439 .placement(&path)
440 .await
441 .map_err(|e| backend_error("fs.remove", &path, e))?;
442 require_writable(&*caller, ticket, &placement, "fs.remove", &path)?;
443 let mut budget = HostMutationBudget::new(caller);
444 let result = filesystem
445 .remove(
446 &effect_id,
447 Some(work.clone()),
448 &path,
449 recursive,
450 &mut budget,
451 )
452 .await;
453 drop(budget);
454 work.settle(&mut *caller)?;
455 result.map_err(|error| backend_error("fs.remove", &path, error))
456 })
457 },
458 )
459 .await
460 })
461 },
462 )?;
463
464 register_host_fn_async(
465 linker,
466 MODULE_NAME,
467 fs_fn("move"),
468 FuncType::new(&engine, [string.clone(), string.clone()], []),
469 |caller, params, _results| {
470 Box::pin(async move {
471 let from = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.move (from)")?;
472 let to = read_string_arg(&mut *caller, abi_arg(params, 1)?, "fs.move (to)")?;
473 let ticket = check_and_approve_call(
474 &mut *caller,
475 "fs.move",
476 serde_json::json!({ "from": &from, "to": &to }),
477 )
478 .await?;
479 let request = effects::MoveEntry { from, to };
480 runtime_effects::execute(
481 caller,
482 "fs.move",
483 request,
484 move |caller, effect_id, request| {
485 Box::pin(async move {
486 charge_path_work(caller, &[&request.from, &request.to])?;
487 let effects::MoveEntry { from, to } = request;
488 let filesystem = caller.data().filesystem.clone();
489 let work = begin_work(caller)?;
490 let placement = filesystem
491 .placement(&from)
492 .await
493 .map_err(|e| backend_error("fs.move", &from, e))?;
494 require_writable(&*caller, ticket, &placement, "fs.move", &from)?;
495 let placement = filesystem
496 .placement(&to)
497 .await
498 .map_err(|e| backend_error("fs.move", &to, e))?;
499 require_writable(&*caller, ticket, &placement, "fs.move", &to)?;
500 let mut budget = HostMutationBudget::new(caller);
501 let result = filesystem
502 .move_entry(&effect_id, Some(work.clone()), &from, &to, &mut budget)
503 .await;
504 drop(budget);
505 work.settle(&mut *caller)?;
506 result.map_err(|error| backend_error("fs.move", &to, error))
507 })
508 },
509 )
510 .await
511 })
512 },
513 )?;
514
515 register_host_fn_async(
516 linker,
517 MODULE_NAME,
518 fs_fn("copy"),
519 FuncType::new(&engine, [string.clone(), string.clone(), ValType::I32], []),
520 |caller, params, _results| {
521 Box::pin(async move {
522 let from = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.copy (from)")?;
523 let to = read_string_arg(&mut *caller, abi_arg(params, 1)?, "fs.copy (to)")?;
524 let recursive = i32_flag(abi_arg(params, 2)?, "fs.copy (recursive)")?;
525 let ticket = check_and_approve_call(
526 &mut *caller,
527 "fs.copy",
528 serde_json::json!({
529 "from": &from,
530 "to": &to,
531 "recursive": recursive,
532 }),
533 )
534 .await?;
535 let request = effects::CopyEntry {
536 from,
537 to,
538 recursive,
539 };
540 runtime_effects::execute(
541 caller,
542 "fs.copy",
543 request,
544 move |caller, effect_id, request| {
545 Box::pin(async move {
546 charge_path_work(caller, &[&request.from, &request.to])?;
547 let effects::CopyEntry {
548 from,
549 to,
550 recursive,
551 } = request;
552 let filesystem = caller.data().filesystem.clone();
553 let work = begin_work(caller)?;
554 let placement = filesystem
555 .placement(&to)
556 .await
557 .map_err(|e| backend_error("fs.copy", &to, e))?;
558 require_writable(&*caller, ticket, &placement, "fs.copy", &to)?;
559 let mut budget = HostMutationBudget::new(caller);
560 let result = filesystem
561 .copy(
562 &effect_id,
563 Some(work.clone()),
564 &from,
565 &to,
566 recursive,
567 &mut budget,
568 )
569 .await;
570 drop(budget);
571 work.settle(&mut *caller)?;
572 result.map_err(|error| backend_error("fs.copy", &to, error))
573 })
574 },
575 )
576 .await
577 })
578 },
579 )?;
580
581 register_host_value_async(
582 linker,
583 MODULE_NAME,
584 fs_fn("writer"),
585 FuncType::new(&engine, [string.clone()], [nullable_object.clone()]),
586 |caller, params| {
587 Box::pin(async move {
588 let path = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.writer (path)")?;
589 let ticket = check_and_approve_call(
590 &mut *caller,
591 "fs.write",
592 serde_json::json!({ "path": &path }),
593 )
594 .await?;
595 open_writer(caller, ticket, &path).await
596 })
597 },
598 )?;
599
600 register_host_value_async(
601 linker,
602 MODULE_NAME,
603 fs_fn("lines"),
604 FuncType::new(&engine, [string.clone()], [nullable_object.clone()]),
605 |caller, params| {
606 Box::pin(async move {
607 let path = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.lines (path)")?;
608 check_and_approve_call(
609 &mut *caller,
610 "fs.read",
611 serde_json::json!({ "path": &path }),
612 )
613 .await?;
614 let options = crate::runtime::filesystem::ReaderOptions::Lines {
615 maximum_bytes: caller.data().fs_max_read_size,
616 };
617 let reader = effects::open_reader(caller, path, options, "fs.lines").await?;
618 let reader = BackendReader::new(reader, &caller.data().tenant_limits)
619 .map_err(wasmtime::Error::new)?;
620 make_handle_iterator(caller, reader, |caller, params| {
621 Box::new(async move { reader_next(caller, params).await })
622 })
623 })
624 },
625 )?;
626
627 register_host_value_async(
628 linker,
629 MODULE_NAME,
630 fs_fn("bytes"),
631 FuncType::new(
632 &engine,
633 [string.clone(), ValType::F64],
634 [nullable_object.clone()],
635 ),
636 |caller, params| {
637 Box::pin(async move {
638 let path = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.bytes (path)")?;
639 let chunk_size = f64_arg(abi_arg(params, 1)?, "fs.bytes (chunkSize)")? as i64;
640 if chunk_size <= 0 {
641 return Err(crate::runtime::host::range_error(format!(
642 "fs.bytes: chunkSize must be > 0, got {chunk_size}"
643 )));
644 }
645 check_and_approve_call(
646 &mut *caller,
647 "fs.read",
648 serde_json::json!({ "path": &path, "chunkSize": chunk_size }),
649 )
650 .await?;
651 let options = crate::runtime::filesystem::ReaderOptions::Bytes {
652 chunk_size: chunk_size as usize,
653 };
654 let reader = effects::open_reader(caller, path, options, "fs.bytes").await?;
655 let reader = BackendReader::new(reader, &caller.data().tenant_limits)
656 .map_err(wasmtime::Error::new)?;
657 make_handle_iterator(caller, reader, |caller, params| {
658 Box::new(async move { reader_next(caller, params).await })
659 })
660 })
661 },
662 )?;
663
664 register_host_value_async(
665 linker,
666 MODULE_NAME,
667 fs_fn("list"),
668 FuncType::new(
669 &engine,
670 [string.clone(), nullable_object.clone()],
671 [nullable_object.clone()],
672 ),
673 |caller, params| {
674 Box::pin(async move {
675 let path = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.list (path)")?;
676 let recursive = recursive_option(caller, abi_arg(params, 1)?).await?;
677 check_and_approve_call(
678 &mut *caller,
679 "fs.list",
680 serde_json::json!({ "path": &path, "recursive": recursive }),
681 )
682 .await?;
683 let walk = effects::open_directory(caller, path.clone(), recursive).await?;
684 let dir = ChargedDirIter::new(walk, &caller.data().tenant_limits)
685 .map_err(|e| wasmtime::Error::new(e).context(format!("fs.list {path}")))?;
686 make_handle_iterator(caller, dir, |caller, params| {
687 Box::new(async move { list_next(caller, params).await })
688 })
689 })
690 },
691 )?;
692
693 register_host_value_async(
694 linker,
695 MODULE_NAME,
696 fs_fn("summary"),
697 FuncType::new(&engine, [string.clone()], [string.clone()]),
698 |caller, params| {
699 Box::pin(async move {
700 let path = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.summary")?;
701 let text = summary::summarize(caller, &path).await?;
702 let value = write_submilli_string_struct(caller, &text)?;
703 Ok(Val::AnyRef(Some(value.to_anyref())))
704 })
705 },
706 )?;
707
708 register_host_value_async(
709 linker,
710 MODULE_NAME,
711 fs_fn("isEmpty"),
712 FuncType::new(&engine, [string.clone()], [ValType::I32]),
713 |caller, params| {
714 Box::pin(async move {
715 let path = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.isEmpty")?;
716 let empty = summary::is_empty(caller, &path).await?;
717 Ok(Val::I32(i32::from(empty)))
718 })
719 },
720 )?;
721
722 install_file_writer_methods(linker, &engine, &intr, object)?;
723 Ok(())
724}
725
726fn install_file_writer_methods(
731 linker: &mut Linker<StoreData>,
732 engine: &wasmtime::Engine,
733 intr: &IntrinsicTypes,
734 receiver: ValType,
735) -> wasmtime::Result<()> {
736 let string = ValType::Ref(RefType::new(
737 false,
738 HeapType::ConcreteStruct(intr.string.clone()),
739 ));
740 let uint8 = ValType::Ref(RefType::new(
741 false,
742 HeapType::ConcreteStruct(intr.uint8_array.clone()),
743 ));
744 let writer_key = crate::mangle::package_symbol(MODULE_NAME, "FileWriter");
745
746 register_host_fn_async(
747 linker,
748 MODULE_NAME,
749 crate::mangle::extend(&writer_key, "close"),
750 FuncType::new(engine, [receiver.clone()], []),
751 |caller, params, _results| {
752 Box::pin(async move {
753 let receiver = *abi_arg(params, 0)?;
754 let descriptor = writer_payload(caller, &receiver, "fs.writer.close")?.id();
755 let live_receiver = receiver;
756 runtime_effects::execute(
757 caller,
758 "fs.writer.close",
759 descriptor,
760 move |caller, effect_id, _| {
761 let receiver = live_receiver;
762 Box::pin(async move {
763 let work = begin_work(caller)?;
764 let filesystem = caller.data().filesystem.clone();
765 let result = writer_payload(caller, &receiver, "fs.writer.close")?
766 .close(filesystem.as_ref(), &effect_id, Some(work.clone()))
767 .await;
768 work.settle(&mut *caller)?;
769 result.map_err(|error| backend_error("fs.writer.close", "", error))
770 })
771 },
772 )
773 .await?;
774 writer_payload(caller, &receiver, "fs.writer.close")?.release_buffer();
775 Ok(())
776 })
777 },
778 )?;
779
780 register_host_fn_async(
781 linker,
782 MODULE_NAME,
783 crate::mangle::extend(&writer_key, "writeLine"),
784 FuncType::new(engine, [receiver.clone(), string], []),
785 |caller, params, _results| {
786 Box::pin(async move {
787 let line =
788 read_string_arg(&mut *caller, abi_arg(params, 1)?, "fs.writer.writeLine")?;
789 let mut bytes = line.into_bytes();
790 bytes.push(b'\n');
791 let receiver = *abi_arg(params, 0)?;
792 let descriptor = writer_payload(caller, &receiver, "fs.writer.writeLine")?.id();
793 let request = effects::WriterChunk {
794 resource: descriptor,
795 bytes,
796 };
797 runtime_effects::execute(
798 caller,
799 "fs.writer.writeLine",
800 request,
801 move |caller, effect_id, request| {
802 Box::pin(async move {
803 fuel::charge(&mut *caller, fuel::IO, request.bytes.len() as u64)?;
804 let work = begin_work(caller)?;
805 let filesystem = caller.data().filesystem.clone();
806 let result = writer_payload(caller, &receiver, "fs.writer.writeLine")?
807 .write(
808 filesystem.as_ref(),
809 &effect_id,
810 Some(work.clone()),
811 request.bytes,
812 )
813 .await;
814 work.settle(&mut *caller)?;
815 result.map_err(|error| backend_error("fs.writer.writeLine", "", error))
816 })
817 },
818 )
819 .await?;
820 Ok(())
821 })
822 },
823 )?;
824
825 register_host_fn_async(
826 linker,
827 MODULE_NAME,
828 crate::mangle::extend(&writer_key, "writeBytes"),
829 FuncType::new(engine, [receiver, uint8], []),
830 |caller, params, _results| {
831 Box::pin(async move {
832 let bytes = read_uint8_array_arg(
833 &mut *caller,
834 abi_arg(params, 1)?,
835 "fs.writer.writeBytes",
836 )?;
837 let receiver = *abi_arg(params, 0)?;
838 let descriptor = writer_payload(caller, &receiver, "fs.writer.writeBytes")?.id();
839 let request = effects::WriterChunk {
840 resource: descriptor,
841 bytes,
842 };
843 runtime_effects::execute(
844 caller,
845 "fs.writer.writeBytes",
846 request,
847 move |caller, effect_id, request| {
848 Box::pin(async move {
849 fuel::charge(&mut *caller, fuel::IO, request.bytes.len() as u64)?;
850 let work = begin_work(caller)?;
851 let filesystem = caller.data().filesystem.clone();
852 let result = writer_payload(caller, &receiver, "fs.writer.writeBytes")?
853 .write(
854 filesystem.as_ref(),
855 &effect_id,
856 Some(work.clone()),
857 request.bytes,
858 )
859 .await;
860 work.settle(&mut *caller)?;
861 result.map_err(|error| backend_error("fs.writer.writeBytes", "", error))
862 })
863 },
864 )
865 .await?;
866 Ok(())
867 })
868 },
869 )?;
870
871 Ok(())
872}
873
874async fn recursive_option(
875 caller: &mut wasmtime::Caller<'_, StoreData>,
876 options: &Val,
877) -> wasmtime::Result<bool> {
878 use crate::runtime::prelude::{collection, undefined};
879 if undefined::is_undefined(caller, options)? {
880 return Ok(false);
881 }
882 let value =
883 crate::runtime::prelude::object::read_property(caller, options, "recursive").await?;
884 if undefined::is_undefined(caller, &value)? {
885 return Ok(false);
886 }
887 collection::unbox_bool(caller, &value)
888}