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