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 .await
383 .map_err(|e| backend_error("fs.mkdir", &path, e))?;
384 if !(recursive
385 && filesystem
386 .metadata(&effect_id, Some(work.clone()), &path, true)
387 .await
388 .is_ok_and(|m| m.is_dir()))
389 {
390 require_writable(&*caller, ticket, &placement, "fs.mkdir", &path)?;
391 }
392 let result = filesystem
393 .mkdir(&effect_id, Some(work.clone()), &path, recursive)
394 .await;
395 work.settle(&mut *caller)?;
396 result.map_err(|e| backend_error("fs.mkdir", &path, e))?;
397 Ok(())
398 })
399 },
400 )
401 .await
402 })
403 },
404 )?;
405
406 register_host_fn_async(
407 linker,
408 MODULE_NAME,
409 fs_fn("remove"),
410 FuncType::new(&engine, [string.clone(), ValType::I32], []),
411 |caller, params, _results| {
412 Box::pin(async move {
413 let path = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.remove")?;
414 let recursive = i32_flag(abi_arg(params, 1)?, "fs.remove (recursive)")?;
415 let ticket = check_security_call(
416 &mut *caller,
417 "fs.remove",
418 serde_json::json!({ "path": &path, "recursive": recursive }),
419 )?;
420 let request = effects::PathMutation { path, recursive };
421 runtime_effects::execute(
422 caller,
423 "fs.remove",
424 request,
425 move |caller, effect_id, request| {
426 Box::pin(async move {
427 charge_path_work(caller, &[&request.path])?;
428 let effects::PathMutation { path, recursive } = request;
429 let filesystem = caller.data().filesystem.clone();
430 let work = begin_work(caller)?;
431 let placement = filesystem
432 .placement(&path)
433 .await
434 .map_err(|e| backend_error("fs.remove", &path, e))?;
435 require_writable(&*caller, ticket, &placement, "fs.remove", &path)?;
436 let mut budget = HostMutationBudget::new(caller);
437 let result = filesystem
438 .remove(
439 &effect_id,
440 Some(work.clone()),
441 &path,
442 recursive,
443 &mut budget,
444 )
445 .await;
446 drop(budget);
447 work.settle(&mut *caller)?;
448 result.map_err(|error| backend_error("fs.remove", &path, error))
449 })
450 },
451 )
452 .await
453 })
454 },
455 )?;
456
457 register_host_fn_async(
458 linker,
459 MODULE_NAME,
460 fs_fn("move"),
461 FuncType::new(&engine, [string.clone(), string.clone()], []),
462 |caller, params, _results| {
463 Box::pin(async move {
464 let from = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.move (from)")?;
465 let to = read_string_arg(&mut *caller, abi_arg(params, 1)?, "fs.move (to)")?;
466 let ticket = check_security_call(
467 &mut *caller,
468 "fs.move",
469 serde_json::json!({ "from": &from, "to": &to }),
470 )?;
471 let request = effects::MoveEntry { from, to };
472 runtime_effects::execute(
473 caller,
474 "fs.move",
475 request,
476 move |caller, effect_id, request| {
477 Box::pin(async move {
478 charge_path_work(caller, &[&request.from, &request.to])?;
479 let effects::MoveEntry { from, to } = request;
480 let filesystem = caller.data().filesystem.clone();
481 let work = begin_work(caller)?;
482 let placement = filesystem
483 .placement(&from)
484 .await
485 .map_err(|e| backend_error("fs.move", &from, e))?;
486 require_writable(&*caller, ticket, &placement, "fs.move", &from)?;
487 let placement = filesystem
488 .placement(&to)
489 .await
490 .map_err(|e| backend_error("fs.move", &to, e))?;
491 require_writable(&*caller, ticket, &placement, "fs.move", &to)?;
492 let mut budget = HostMutationBudget::new(caller);
493 let result = filesystem
494 .move_entry(&effect_id, Some(work.clone()), &from, &to, &mut budget)
495 .await;
496 drop(budget);
497 work.settle(&mut *caller)?;
498 result.map_err(|error| backend_error("fs.move", &to, error))
499 })
500 },
501 )
502 .await
503 })
504 },
505 )?;
506
507 register_host_fn_async(
508 linker,
509 MODULE_NAME,
510 fs_fn("copy"),
511 FuncType::new(&engine, [string.clone(), string.clone(), ValType::I32], []),
512 |caller, params, _results| {
513 Box::pin(async move {
514 let from = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.copy (from)")?;
515 let to = read_string_arg(&mut *caller, abi_arg(params, 1)?, "fs.copy (to)")?;
516 let recursive = i32_flag(abi_arg(params, 2)?, "fs.copy (recursive)")?;
517 let ticket = check_security_call(
518 &mut *caller,
519 "fs.copy",
520 serde_json::json!({
521 "from": &from,
522 "to": &to,
523 "recursive": recursive,
524 }),
525 )?;
526 let request = effects::CopyEntry {
527 from,
528 to,
529 recursive,
530 };
531 runtime_effects::execute(
532 caller,
533 "fs.copy",
534 request,
535 move |caller, effect_id, request| {
536 Box::pin(async move {
537 charge_path_work(caller, &[&request.from, &request.to])?;
538 let effects::CopyEntry {
539 from,
540 to,
541 recursive,
542 } = request;
543 let filesystem = caller.data().filesystem.clone();
544 let work = begin_work(caller)?;
545 let placement = filesystem
546 .placement(&to)
547 .await
548 .map_err(|e| backend_error("fs.copy", &to, e))?;
549 require_writable(&*caller, ticket, &placement, "fs.copy", &to)?;
550 let mut budget = HostMutationBudget::new(caller);
551 let result = filesystem
552 .copy(
553 &effect_id,
554 Some(work.clone()),
555 &from,
556 &to,
557 recursive,
558 &mut budget,
559 )
560 .await;
561 drop(budget);
562 work.settle(&mut *caller)?;
563 result.map_err(|error| backend_error("fs.copy", &to, error))
564 })
565 },
566 )
567 .await
568 })
569 },
570 )?;
571
572 register_host_value_async(
573 linker,
574 MODULE_NAME,
575 fs_fn("writer"),
576 FuncType::new(&engine, [string.clone()], [nullable_object.clone()]),
577 |caller, params| {
578 Box::pin(async move {
579 let path = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.writer (path)")?;
580 let ticket = check_security_call(
581 &mut *caller,
582 "fs.write",
583 serde_json::json!({ "path": &path }),
584 )?;
585 open_writer(caller, ticket, &path).await
586 })
587 },
588 )?;
589
590 register_host_value_async(
591 linker,
592 MODULE_NAME,
593 fs_fn("lines"),
594 FuncType::new(&engine, [string.clone()], [nullable_object.clone()]),
595 |caller, params| {
596 Box::pin(async move {
597 let path = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.lines (path)")?;
598 check_security_call(
599 &mut *caller,
600 "fs.read",
601 serde_json::json!({ "path": &path }),
602 )?;
603 let options = crate::runtime::filesystem::ReaderOptions::Lines {
604 maximum_bytes: caller.data().fs_max_read_size,
605 };
606 let reader = effects::open_reader(caller, path, options, "fs.lines").await?;
607 let reader = BackendReader::new(reader, &caller.data().tenant_limits)
608 .map_err(wasmtime::Error::new)?;
609 make_handle_iterator(caller, reader, |caller, params| {
610 Box::new(async move { reader_next(caller, params).await })
611 })
612 })
613 },
614 )?;
615
616 register_host_value_async(
617 linker,
618 MODULE_NAME,
619 fs_fn("bytes"),
620 FuncType::new(
621 &engine,
622 [string.clone(), ValType::F64],
623 [nullable_object.clone()],
624 ),
625 |caller, params| {
626 Box::pin(async move {
627 let path = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.bytes (path)")?;
628 let chunk_size = f64_arg(abi_arg(params, 1)?, "fs.bytes (chunkSize)")? as i64;
629 if chunk_size <= 0 {
630 return Err(crate::runtime::host::range_error(format!(
631 "fs.bytes: chunkSize must be > 0, got {chunk_size}"
632 )));
633 }
634 check_security_call(
635 &mut *caller,
636 "fs.read",
637 serde_json::json!({ "path": &path, "chunkSize": chunk_size }),
638 )?;
639 let options = crate::runtime::filesystem::ReaderOptions::Bytes {
640 chunk_size: chunk_size as usize,
641 };
642 let reader = effects::open_reader(caller, path, options, "fs.bytes").await?;
643 let reader = BackendReader::new(reader, &caller.data().tenant_limits)
644 .map_err(wasmtime::Error::new)?;
645 make_handle_iterator(caller, reader, |caller, params| {
646 Box::new(async move { reader_next(caller, params).await })
647 })
648 })
649 },
650 )?;
651
652 register_host_value_async(
653 linker,
654 MODULE_NAME,
655 fs_fn("list"),
656 FuncType::new(
657 &engine,
658 [string.clone(), ValType::I32],
659 [nullable_object.clone()],
660 ),
661 |caller, params| {
662 Box::pin(async move {
663 let path = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.list (path)")?;
664 let recursive = i32_flag(abi_arg(params, 1)?, "fs.list (recursive)")?;
665 check_security_call(
666 &mut *caller,
667 "fs.list",
668 serde_json::json!({ "path": &path, "recursive": recursive }),
669 )?;
670 let walk = effects::open_directory(caller, path.clone(), recursive).await?;
671 let dir = ChargedDirIter::new(walk, &caller.data().tenant_limits)
672 .map_err(|e| wasmtime::Error::new(e).context(format!("fs.list {path}")))?;
673 make_handle_iterator(caller, dir, |caller, params| {
674 Box::new(async move { list_next(caller, params).await })
675 })
676 })
677 },
678 )?;
679
680 register_host_value_async(
681 linker,
682 MODULE_NAME,
683 fs_fn("summary"),
684 FuncType::new(&engine, [string.clone()], [string.clone()]),
685 |caller, params| {
686 Box::pin(async move {
687 let path = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.summary")?;
688 let text = summary::summarize(caller, &path).await?;
689 let value = write_submilli_string_struct(caller, &text)?;
690 Ok(Val::AnyRef(Some(value.to_anyref())))
691 })
692 },
693 )?;
694
695 register_host_value_async(
696 linker,
697 MODULE_NAME,
698 fs_fn("isEmpty"),
699 FuncType::new(&engine, [string.clone()], [ValType::I32]),
700 |caller, params| {
701 Box::pin(async move {
702 let path = read_string_arg(&mut *caller, abi_arg(params, 0)?, "fs.isEmpty")?;
703 let empty = summary::is_empty(caller, &path).await?;
704 Ok(Val::I32(i32::from(empty)))
705 })
706 },
707 )?;
708
709 for view in HOST_VIEWS {
710 install_field_getters(linker, &engine, &object, view)?;
711 }
712 install_file_writer_methods(linker, &engine, &intr, object)?;
713 Ok(())
714}
715
716const STAT_VIEW: HostView = HostView {
721 module: MODULE_NAME,
722 interface: "Stat",
723 backing: stat_backing_struct,
724 properties: &[
725 ("kind", STAT_KIND),
726 ("size", STAT_SIZE),
727 ("modifiedAt", STAT_MODIFIED_AT),
728 ],
729};
730
731const PEEK_VIEW: HostView = HostView {
732 module: MODULE_NAME,
733 interface: "Peek",
734 backing: peek_backing_struct,
735 properties: &[
736 ("preview", PEEK_PREVIEW),
737 ("encoding", PEEK_ENCODING),
738 ("lineEnding", PEEK_LINE_ENDING),
739 ("size", PEEK_SIZE),
740 ],
741};
742
743const DIR_ENTRY_VIEW: HostView = HostView {
744 module: MODULE_NAME,
745 interface: "DirEntry",
746 backing: dir_entry_backing_struct,
747 properties: &[
748 ("kind", ENTRY_KIND),
749 ("name", ENTRY_NAME),
750 ("path", ENTRY_PATH),
751 ("size", ENTRY_SIZE),
752 ],
753};
754
755const INFO_VIEW: HostView = HostView {
756 module: MODULE_NAME,
757 interface: "Info",
758 backing: info_backing_struct,
759 properties: &[
760 ("mode", INFO_MODE),
761 ("sizeLimit", INFO_SIZE_LIMIT),
762 ("access", INFO_ACCESS),
763 ("volume", INFO_VOLUME),
764 ("mounts", INFO_MOUNTS),
765 ],
766};
767
768const MOUNT_INFO_VIEW: HostView = HostView {
769 module: MODULE_NAME,
770 interface: "MountInfo",
771 backing: mount_info_backing_struct,
772 properties: &[
773 ("path", MOUNT_PATH),
774 ("mode", MOUNT_MODE),
775 ("volume", MOUNT_VOLUME),
776 ("access", MOUNT_ACCESS),
777 ("sizeLimit", MOUNT_SIZE_LIMIT),
778 ],
779};
780
781pub(crate) const HOST_VIEWS: &[&HostView] = &[
783 &STAT_VIEW,
784 &PEEK_VIEW,
785 &DIR_ENTRY_VIEW,
786 &INFO_VIEW,
787 &MOUNT_INFO_VIEW,
788];
789
790fn install_file_writer_methods(
791 linker: &mut Linker<StoreData>,
792 engine: &wasmtime::Engine,
793 intr: &IntrinsicTypes,
794 receiver: ValType,
795) -> wasmtime::Result<()> {
796 let string = ValType::Ref(RefType::new(
797 false,
798 HeapType::ConcreteStruct(intr.string.clone()),
799 ));
800 let uint8 = ValType::Ref(RefType::new(
801 false,
802 HeapType::ConcreteStruct(intr.uint8_array.clone()),
803 ));
804 let writer_key = crate::mangle::package_symbol(MODULE_NAME, "FileWriter");
805
806 register_host_fn_async(
807 linker,
808 MODULE_NAME,
809 crate::mangle::extend(&writer_key, "close"),
810 FuncType::new(engine, [receiver.clone()], []),
811 |caller, params, _results| {
812 Box::pin(async move {
813 let receiver = *abi_arg(params, 0)?;
814 let descriptor = writer_payload(caller, &receiver, "fs.writer.close")?.id();
815 let live_receiver = receiver;
816 runtime_effects::execute(
817 caller,
818 "fs.writer.close",
819 descriptor,
820 move |caller, effect_id, _| {
821 let receiver = live_receiver;
822 Box::pin(async move {
823 let work = begin_work(caller)?;
824 let filesystem = caller.data().filesystem.clone();
825 let result = writer_payload(caller, &receiver, "fs.writer.close")?
826 .close(filesystem.as_ref(), &effect_id, Some(work.clone()))
827 .await;
828 work.settle(&mut *caller)?;
829 result.map_err(|error| backend_error("fs.writer.close", "", error))
830 })
831 },
832 )
833 .await?;
834 writer_payload(caller, &receiver, "fs.writer.close")?.release_buffer();
835 Ok(())
836 })
837 },
838 )?;
839
840 register_host_fn_async(
841 linker,
842 MODULE_NAME,
843 crate::mangle::extend(&writer_key, "writeLine"),
844 FuncType::new(engine, [receiver.clone(), string], []),
845 |caller, params, _results| {
846 Box::pin(async move {
847 let line =
848 read_string_arg(&mut *caller, abi_arg(params, 1)?, "fs.writer.writeLine")?;
849 let mut bytes = line.into_bytes();
850 bytes.push(b'\n');
851 let receiver = *abi_arg(params, 0)?;
852 let descriptor = writer_payload(caller, &receiver, "fs.writer.writeLine")?.id();
853 let request = effects::WriterChunk {
854 resource: descriptor,
855 bytes,
856 };
857 runtime_effects::execute(
858 caller,
859 "fs.writer.writeLine",
860 request,
861 move |caller, effect_id, request| {
862 Box::pin(async move {
863 fuel::charge(&mut *caller, fuel::IO, request.bytes.len() as u64)?;
864 let work = begin_work(caller)?;
865 let filesystem = caller.data().filesystem.clone();
866 let result = writer_payload(caller, &receiver, "fs.writer.writeLine")?
867 .write(
868 filesystem.as_ref(),
869 &effect_id,
870 Some(work.clone()),
871 request.bytes,
872 )
873 .await;
874 work.settle(&mut *caller)?;
875 result.map_err(|error| backend_error("fs.writer.writeLine", "", error))
876 })
877 },
878 )
879 .await?;
880 Ok(())
881 })
882 },
883 )?;
884
885 register_host_fn_async(
886 linker,
887 MODULE_NAME,
888 crate::mangle::extend(&writer_key, "writeBytes"),
889 FuncType::new(engine, [receiver, uint8], []),
890 |caller, params, _results| {
891 Box::pin(async move {
892 let bytes = read_uint8_array_arg(
893 &mut *caller,
894 abi_arg(params, 1)?,
895 "fs.writer.writeBytes",
896 )?;
897 let receiver = *abi_arg(params, 0)?;
898 let descriptor = writer_payload(caller, &receiver, "fs.writer.writeBytes")?.id();
899 let request = effects::WriterChunk {
900 resource: descriptor,
901 bytes,
902 };
903 runtime_effects::execute(
904 caller,
905 "fs.writer.writeBytes",
906 request,
907 move |caller, effect_id, request| {
908 Box::pin(async move {
909 fuel::charge(&mut *caller, fuel::IO, request.bytes.len() as u64)?;
910 let work = begin_work(caller)?;
911 let filesystem = caller.data().filesystem.clone();
912 let result = writer_payload(caller, &receiver, "fs.writer.writeBytes")?
913 .write(
914 filesystem.as_ref(),
915 &effect_id,
916 Some(work.clone()),
917 request.bytes,
918 )
919 .await;
920 work.settle(&mut *caller)?;
921 result.map_err(|error| backend_error("fs.writer.writeBytes", "", error))
922 })
923 },
924 )
925 .await?;
926 Ok(())
927 })
928 },
929 )?;
930
931 Ok(())
932}