1use std::task::Waker;
2
3use super::*;
4#[cfg(feature = "journal")]
5use crate::{
6 journal::{JournalEffector, JournalEntry},
7 utils::map_snapshot_err,
8};
9use crate::{net::MAX_SOCKET_PAYLOAD, net::socket::TimeType, syscalls::*};
10
11#[instrument(level = "trace", skip_all, fields(%fd, nwritten = field::Empty), ret)]
26pub fn fd_write<M: MemorySize>(
27 mut ctx: FunctionEnvMut<'_, WasiEnv>,
28 fd: WasiFd,
29 iovs: WasmPtr<__wasi_ciovec_t<M>, M>,
30 iovs_len: M::Offset,
31 nwritten: WasmPtr<M::Offset, M>,
32) -> Result<Errno, WasiError> {
33 WasiEnv::do_pending_operations(&mut ctx)?;
34
35 let env = ctx.data();
36 let enable_journal = env.enable_journal;
37 let fd_entry = {
38 let state = env.state.clone();
39 wasi_try_ok!(state.fs.get_fd(fd))
40 };
41 let offset = fd_entry.inner.offset.load(Ordering::Acquire) as usize;
42
43 let bytes_written = wasi_try_ok!(fd_write_internal::<M>(
44 &mut ctx,
45 fd,
46 fd_entry,
47 FdWriteSource::Iovs { iovs, iovs_len },
48 offset as u64,
49 true,
50 enable_journal,
51 )?);
52
53 Span::current().record("nwritten", bytes_written);
54
55 let mut env = ctx.data();
56 let memory = unsafe { env.memory_view(&ctx) };
57 let nwritten_ref = nwritten.deref(&memory);
58 let bytes_written: M::Offset =
59 wasi_try_ok!(bytes_written.try_into().map_err(|_| Errno::Overflow));
60 wasi_try_mem_ok!(nwritten_ref.write(bytes_written));
61
62 Ok(Errno::Success)
63}
64
65#[instrument(level = "trace", skip_all, fields(%fd, %offset, nwritten = field::Empty), ret)]
80pub fn fd_pwrite<M: MemorySize>(
81 mut ctx: FunctionEnvMut<'_, WasiEnv>,
82 fd: WasiFd,
83 iovs: WasmPtr<__wasi_ciovec_t<M>, M>,
84 iovs_len: M::Offset,
85 offset: Filesize,
86 nwritten: WasmPtr<M::Offset, M>,
87) -> Result<Errno, WasiError> {
88 WasiEnv::do_pending_operations(&mut ctx)?;
89
90 let enable_snapshot_capture = ctx.data().enable_journal;
91
92 let fd_entry = {
93 let env = ctx.data();
94 let state = env.state.clone();
95 wasi_try_ok!(state.fs.get_fd(fd))
96 };
97 let bytes_written = wasi_try_ok!(fd_write_internal::<M>(
98 &mut ctx,
99 fd,
100 fd_entry,
101 FdWriteSource::Iovs { iovs, iovs_len },
102 offset,
103 false,
104 enable_snapshot_capture,
105 )?);
106
107 Span::current().record("nwritten", bytes_written);
108
109 let mut env = ctx.data();
110 let memory = unsafe { env.memory_view(&ctx) };
111 let nwritten_ref = nwritten.deref(&memory);
112 let bytes_written: M::Offset =
113 wasi_try_ok!(bytes_written.try_into().map_err(|_| Errno::Overflow));
114 wasi_try_mem_ok!(nwritten_ref.write(bytes_written));
115
116 Ok(Errno::Success)
117}
118
119pub(crate) enum FdWriteSource<'a, M: MemorySize> {
120 Iovs {
121 iovs: WasmPtr<__wasi_ciovec_t<M>, M>,
122 iovs_len: M::Offset,
123 },
124 Buffer(Cow<'a, [u8]>),
125}
126
127pub(crate) const MAX_STREAM_COALESCE: usize = 1024 * 1024;
133
134const _: () = assert!(MAX_STREAM_COALESCE < 8 * 1024 * 1024);
135const _: () = assert!(MAX_SOCKET_PAYLOAD < MAX_STREAM_COALESCE);
138
139impl<'a, M: MemorySize> FdWriteSource<'a, M> {
140 pub(crate) fn coalesce(
141 &self,
142 memory: &MemoryView,
143 max_len: usize,
144 ) -> Result<Cow<'a, [u8]>, Errno> {
145 match self {
146 FdWriteSource::Iovs { iovs, iovs_len } => {
147 let iovs_arr = iovs.slice(memory, *iovs_len).map_err(mem_error_to_wasi)?;
148 let iovs_arr = iovs_arr.access().map_err(mem_error_to_wasi)?;
149
150 let mut total_len = 0usize;
151 for iov in iovs_arr.iter() {
152 let len = iov.buf_len.into() as usize;
153 total_len = total_len.checked_add(len).ok_or(Errno::Msgsize)?;
154 }
155 if total_len > max_len {
156 return Err(Errno::Msgsize);
157 }
158
159 let mut coalesced = Vec::new();
160 coalesced
161 .try_reserve_exact(total_len)
162 .map_err(|_| Errno::Nomem)?;
163
164 for iov in iovs_arr.iter() {
165 let buf = WasmPtr::<u8, M>::new(iov.buf)
166 .slice(memory, iov.buf_len)
167 .map_err(mem_error_to_wasi)?
168 .access()
169 .map_err(mem_error_to_wasi)?;
170 coalesced.extend_from_slice(buf.as_ref());
171 }
172
173 Ok(Cow::Owned(coalesced))
174 }
175 FdWriteSource::Buffer(cow) => {
176 if cow.len() > max_len {
177 return Err(Errno::Msgsize);
178 }
179 Ok(cow.clone())
180 }
181 }
182 }
183
184 pub(crate) fn coalesce_from(
189 &self,
190 memory: &MemoryView,
191 skip: usize,
192 limit: usize,
193 ) -> Result<Cow<'a, [u8]>, Errno> {
194 match self {
195 FdWriteSource::Iovs { iovs, iovs_len } => {
196 let iovs_arr = iovs.slice(memory, *iovs_len).map_err(mem_error_to_wasi)?;
197 let iovs_arr = iovs_arr.access().map_err(mem_error_to_wasi)?;
198
199 let mut coalesced: Vec<u8> = Vec::new();
200 let mut consumed = 0usize;
201
202 for iov in iovs_arr.iter() {
203 let room = limit - coalesced.len();
204 if room == 0 {
205 break;
206 }
207
208 let iov_start = consumed;
209 let len = iov.buf_len.into() as usize;
210 consumed = consumed.checked_add(len).ok_or(Errno::Msgsize)?;
211 if consumed <= skip {
212 continue;
213 }
214
215 let buf = WasmPtr::<u8, M>::new(iov.buf)
216 .slice(memory, iov.buf_len)
217 .map_err(mem_error_to_wasi)?
218 .access()
219 .map_err(mem_error_to_wasi)?;
220 let buf = buf.as_ref();
221
222 let start = skip.saturating_sub(iov_start);
223 if start >= buf.len() {
224 continue;
225 }
226 let take = room.min(buf.len() - start);
227 coalesced.try_reserve(take).map_err(|_| Errno::Nomem)?;
228 coalesced.extend_from_slice(&buf[start..start + take]);
229 }
230
231 Ok(Cow::Owned(coalesced))
232 }
233 FdWriteSource::Buffer(cow) => {
234 let start = skip.min(cow.len());
235 let end = start.saturating_add(limit).min(cow.len());
236 Ok(Cow::Owned(cow[start..end].to_vec()))
237 }
238 }
239 }
240}
241
242#[allow(clippy::await_holding_lock)]
243pub(crate) fn fd_write_internal<M: MemorySize>(
244 mut ctx: &mut FunctionEnvMut<'_, WasiEnv>,
245 fd: WasiFd,
246 fd_entry: Fd,
247 data: FdWriteSource<'_, M>,
248 offset: u64,
249 should_update_cursor: bool,
250 should_snapshot: bool,
251) -> Result<Result<usize, Errno>, WasiError> {
252 let mut offset = offset;
253 let mut env = ctx.data();
254 let state = env.state.clone();
255 let is_stdio = fd_entry.is_stdio;
256
257 let bytes_written = {
258 if !is_stdio && !fd_entry.inner.rights.contains(Rights::FD_WRITE) {
259 return Ok(Err(Errno::Access));
260 }
261
262 let fd_flags = fd_entry.inner.flags;
263 let mut memory = unsafe { env.memory_view(&ctx) };
264
265 let (bytes_written, is_file, can_snapshot) = {
266 let (mut memory, _) = unsafe { env.get_memory_and_wasi_state(&ctx, 0) };
267 let mut guard = fd_entry.inode.write();
268 match guard.deref_mut() {
269 Kind::File { handle, .. } => {
270 if let Some(handle) = handle {
271 let handle = handle.clone();
272 drop(guard);
273
274 let res = __asyncify_light(
275 env,
276 if fd_entry.inner.flags.contains(Fdflags::NONBLOCK) {
277 Some(Duration::ZERO)
278 } else {
279 None
280 },
281 async {
282 let mut handle = handle.write().unwrap();
283 if !is_stdio {
284 if fd_entry.inner.flags.contains(Fdflags::APPEND) {
285 offset = fd_entry.inode.stat.read().unwrap().st_size;
287 fd_entry.inner.offset.store(offset, Ordering::Release);
288 }
289
290 handle
291 .seek(std::io::SeekFrom::Start(offset))
292 .await
293 .map_err(map_io_err)?;
294 }
295
296 let mut written = 0usize;
297
298 match &data {
299 FdWriteSource::Iovs { iovs, iovs_len } => {
300 let iovs_arr = iovs
301 .slice(&memory, *iovs_len)
302 .map_err(mem_error_to_wasi)?;
303 let iovs_arr =
304 iovs_arr.access().map_err(mem_error_to_wasi)?;
305 for iovs in iovs_arr.iter() {
306 let buf = WasmPtr::<u8, M>::new(iovs.buf)
307 .slice(&memory, iovs.buf_len)
308 .map_err(mem_error_to_wasi)?
309 .access()
310 .map_err(mem_error_to_wasi)?;
311 let local_written =
312 match handle.write(buf.as_ref()).await {
313 Ok(s) => s,
314 Err(_) if written > 0 => break,
315 Err(err) => return Err(map_io_err(err)),
316 };
317 written += local_written;
318 if local_written != buf.len() {
319 break;
320 }
321 }
322 }
323 FdWriteSource::Buffer(data) => {
324 handle.write_all(data).await?;
325 written += data.len();
326 }
327 }
328
329 if is_stdio {
330 handle.flush().await.map_err(map_io_err)?;
331 }
332 Ok(written)
333 },
334 );
335 let written = wasi_try_ok_ok!(res?.map_err(|err| match err {
336 Errno::Timedout => Errno::Again,
337 a => a,
338 }));
339
340 (written, true, true)
341 } else {
342 return Ok(Err(Errno::Inval));
343 }
344 }
345 Kind::Socket { socket } => {
346 let socket = socket.clone();
347 drop(guard);
348
349 let nonblocking = fd_flags.contains(Fdflags::NONBLOCK);
350 let timeout = socket
351 .opt_time(TimeType::WriteTimeout)
352 .ok()
353 .flatten()
354 .unwrap_or(Duration::from_secs(30));
355
356 let tasks = env.tasks().clone();
357
358 let res = __asyncify_light(env, None, async {
359 let mut sent = 0usize;
360
361 if socket.is_dgram() {
366 let data = data.coalesce(&memory, MAX_SOCKET_PAYLOAD)?;
367 sent += socket
368 .send(tasks.deref(), data.as_ref(), Some(timeout), nonblocking)
369 .await?;
370 return Ok(sent);
371 }
372
373 loop {
379 let chunk = data.coalesce_from(&memory, sent, MAX_STREAM_COALESCE)?;
380 if chunk.is_empty() {
381 break;
382 }
383 let chunk_len = chunk.len();
384 let local_sent = match socket
385 .send(tasks.deref(), chunk.as_ref(), Some(timeout), nonblocking)
386 .await
387 {
388 Ok(local_sent) => local_sent,
389 Err(_) if sent > 0 => break,
392 Err(err) => return Err(err),
393 };
394 sent += local_sent;
395
396 if local_sent != chunk_len {
397 if nonblocking || local_sent == 0 {
403 break;
404 }
405 }
406 }
407 Ok(sent)
408 });
409 let written = wasi_try_ok_ok!(res?);
410 (written, false, false)
411 }
412 Kind::PipeRx { .. } => {
413 return Ok(Err(Errno::Badf));
414 }
415 Kind::PipeTx { tx } => {
416 let mut written = 0usize;
417
418 match &data {
419 FdWriteSource::Iovs { iovs, iovs_len } => {
420 let mut raise_sigpipe = false;
421 let iovs_arr = wasi_try_ok_ok!(
422 iovs.slice(&memory, *iovs_len).map_err(mem_error_to_wasi)
423 );
424 let iovs_arr =
425 wasi_try_ok_ok!(iovs_arr.access().map_err(mem_error_to_wasi));
426 for iovs in iovs_arr.iter() {
427 let buf = wasi_try_ok_ok!(
428 WasmPtr::<u8, M>::new(iovs.buf)
429 .slice(&memory, iovs.buf_len)
430 .map_err(mem_error_to_wasi)
431 );
432 let buf = wasi_try_ok_ok!(buf.access().map_err(mem_error_to_wasi));
433 let write_result = std::io::Write::write(tx, buf.as_ref());
434 let local_written = match write_result {
435 Ok(w) => w,
436 Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => {
437 raise_sigpipe = true;
439 break;
440 }
441 Err(e) => return Ok(Err(map_io_err(e))),
442 };
443
444 written += local_written;
445 if local_written != buf.len() {
446 break;
447 }
448 }
449
450 drop(iovs_arr);
451
452 if raise_sigpipe {
453 env.process.signal_process(Signal::Sigpipe);
454 wasi_try_ok_ok!(WasiEnv::process_signals_and_exit(ctx)?);
455 return Ok(Err(Errno::Pipe));
456 }
457 }
458 FdWriteSource::Buffer(data) => {
459 match std::io::Write::write_all(tx, data) {
460 Ok(()) => (),
461 Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => {
462 env.process.signal_process(Signal::Sigpipe);
463 wasi_try_ok_ok!(WasiEnv::process_signals_and_exit(ctx)?);
464 return Ok(Err(Errno::Pipe));
465 }
466 Err(e) => return Ok(Err(map_io_err(e))),
467 };
468 written += data.len();
469 }
470 }
471
472 (written, false, true)
473 }
474 Kind::DuplexPipe { pipe } => {
475 let mut written = 0usize;
476
477 match &data {
478 FdWriteSource::Iovs { iovs, iovs_len } => {
479 let mut raise_sigpipe = false;
480 let iovs_arr = wasi_try_ok_ok!(
481 iovs.slice(&memory, *iovs_len).map_err(mem_error_to_wasi)
482 );
483 let iovs_arr =
484 wasi_try_ok_ok!(iovs_arr.access().map_err(mem_error_to_wasi));
485 for iovs in iovs_arr.iter() {
486 let buf = wasi_try_ok_ok!(
487 WasmPtr::<u8, M>::new(iovs.buf)
488 .slice(&memory, iovs.buf_len)
489 .map_err(mem_error_to_wasi)
490 );
491 let buf = wasi_try_ok_ok!(buf.access().map_err(mem_error_to_wasi));
492 let write_result = std::io::Write::write(pipe, buf.as_ref());
493 let local_written = match write_result {
494 Ok(w) => w,
495 Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => {
496 raise_sigpipe = true;
498 break;
499 }
500 Err(e) => return Ok(Err(map_io_err(e))),
501 };
502
503 written += local_written;
504 if local_written != buf.len() {
505 break;
506 }
507 }
508
509 drop(iovs_arr);
510
511 if raise_sigpipe {
512 env.process.signal_process(Signal::Sigpipe);
513 wasi_try_ok_ok!(WasiEnv::process_signals_and_exit(ctx)?);
514 return Ok(Err(Errno::Pipe));
515 }
516 }
517 FdWriteSource::Buffer(data) => {
518 match std::io::Write::write_all(pipe, data) {
519 Ok(()) => (),
520 Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => {
521 env.process.signal_process(Signal::Sigpipe);
522 wasi_try_ok_ok!(WasiEnv::process_signals_and_exit(ctx)?);
523 return Ok(Err(Errno::Pipe));
524 }
525 Err(e) => return Ok(Err(map_io_err(e))),
526 };
527 written += data.len();
528 }
529 }
530
531 (written, false, true)
532 }
533 Kind::Dir { .. } | Kind::Root { .. } => {
534 return Ok(Err(Errno::Isdir));
536 }
537 Kind::EventNotifications { inner } => {
538 let mut written = 0usize;
539
540 match &data {
541 FdWriteSource::Iovs { iovs, iovs_len } => {
542 let iovs_arr = wasi_try_ok_ok!(
543 iovs.slice(&memory, *iovs_len).map_err(mem_error_to_wasi)
544 );
545 let iovs_arr =
546 wasi_try_ok_ok!(iovs_arr.access().map_err(mem_error_to_wasi));
547 for iovs in iovs_arr.iter() {
548 let buf_len: usize = wasi_try_ok_ok!(
549 iovs.buf_len.try_into().map_err(|_| Errno::Inval)
550 );
551 let will_be_written = buf_len;
552
553 let val_cnt = buf_len / std::mem::size_of::<u64>();
554 let val_cnt: M::Offset =
555 wasi_try_ok_ok!(val_cnt.try_into().map_err(|_| Errno::Inval));
556
557 let vals = wasi_try_ok_ok!(
558 WasmPtr::<u64, M>::new(iovs.buf)
559 .slice(&memory, val_cnt as M::Offset)
560 .map_err(mem_error_to_wasi)
561 );
562 let vals =
563 wasi_try_ok_ok!(vals.access().map_err(mem_error_to_wasi));
564 for val in vals.iter() {
565 inner.write(*val);
566 }
567
568 written += will_be_written;
569 }
570 }
571 FdWriteSource::Buffer(data) => {
572 let cnt = data.len() / std::mem::size_of::<u64>();
573 for n in 0..cnt {
574 let start = n * std::mem::size_of::<u64>();
575 let data = [
576 data[start],
577 data[start + 1],
578 data[start + 2],
579 data[start + 3],
580 data[start + 4],
581 data[start + 5],
582 data[start + 6],
583 data[start + 7],
584 ];
585 inner.write(u64::from_ne_bytes(data));
586 }
587 }
588 }
589
590 (written, false, true)
591 }
592 Kind::Symlink { .. } | Kind::Epoll { .. } => return Ok(Err(Errno::Inval)),
593 Kind::Buffer { buffer } => {
594 let mut written = 0usize;
595
596 match &data {
597 FdWriteSource::Iovs { iovs, iovs_len } => {
598 let iovs_arr = wasi_try_ok_ok!(
599 iovs.slice(&memory, *iovs_len).map_err(mem_error_to_wasi)
600 );
601 let iovs_arr =
602 wasi_try_ok_ok!(iovs_arr.access().map_err(mem_error_to_wasi));
603 for iovs in iovs_arr.iter() {
604 let buf = wasi_try_ok_ok!(
605 WasmPtr::<u8, M>::new(iovs.buf)
606 .slice(&memory, iovs.buf_len)
607 .map_err(mem_error_to_wasi)
608 );
609 let buf = wasi_try_ok_ok!(buf.access().map_err(mem_error_to_wasi));
610 let local_written = wasi_try_ok_ok!(
611 std::io::Write::write(buffer, buf.as_ref()).map_err(map_io_err)
612 );
613 written += local_written;
614 if local_written != buf.len() {
615 break;
616 }
617 }
618 }
619 FdWriteSource::Buffer(data) => {
620 wasi_try_ok_ok!(
621 std::io::Write::write_all(buffer, data).map_err(map_io_err)
622 );
623 written += data.len();
624 }
625 }
626
627 (written, false, true)
628 }
629 }
630 };
631
632 #[cfg(feature = "journal")]
633 if should_snapshot
634 && can_snapshot
635 && bytes_written > 0
636 && let FdWriteSource::Iovs { iovs, iovs_len } = data
637 {
638 JournalEffector::save_fd_write(ctx, fd, offset, bytes_written, iovs, iovs_len)
639 .map_err(|err| {
640 tracing::error!("failed to save terminal data - {}", err);
641 WasiError::Exit(ExitCode::from(Errno::Fault))
642 })?;
643 }
644
645 env = ctx.data();
646 memory = unsafe { env.memory_view(&ctx) };
647
648 if !is_stdio {
650 let curr_offset = if is_file && should_update_cursor {
651 let bytes_written = bytes_written as u64;
652 fd_entry
653 .inner
654 .offset
655 .fetch_add(bytes_written, Ordering::AcqRel)
656 + bytes_written
658 } else {
659 fd_entry.inner.offset.load(Ordering::Acquire)
660 };
661
662 let (mut memory, _, inodes) =
665 unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) };
666 if is_file {
667 let mut stat = fd_entry.inode.stat.write().unwrap();
668 if should_update_cursor {
669 stat.st_size = stat.st_size.max(curr_offset);
673 } else {
674 stat.st_size = stat.st_size.max(offset + bytes_written as u64);
678 }
679 } else {
680 fd_entry.inode.stat.write().unwrap().st_size += bytes_written as u64;
682 }
683 }
684 bytes_written
685 };
686
687 Ok(Ok(bytes_written))
688}
689
690#[cfg(all(test, feature = "sys"))]
691mod tests {
692 use wasmer::{Memory, MemoryType, Store, WasmPtr};
693 use wasmer_wasix_types::wasi::Errno;
694
695 use super::{FdWriteSource, MAX_STREAM_COALESCE};
696
697 struct Guest {
698 store: Store,
699 memory: Memory,
700 }
701
702 impl Guest {
703 fn new() -> Self {
704 let mut store = Store::default();
705 let memory = Memory::new(&mut store, MemoryType::new(1, Some(1), false)).unwrap();
706 Self { store, memory }
707 }
708
709 fn write_iovs(&mut self, payloads: &[&[u8]], claimed: Option<u32>) -> (u32, u32) {
713 let view = self.memory.view(&self.store);
714 let iovs_at = 8u32;
715 let mut data_at = iovs_at + (payloads.len() as u32 * 8);
716
717 for (i, payload) in payloads.iter().enumerate() {
718 view.write(data_at as u64, payload).unwrap();
719 let entry = iovs_at + (i as u32 * 8);
720 view.write(entry as u64, &data_at.to_le_bytes()).unwrap();
721 let len = claimed.unwrap_or(payload.len() as u32);
722 view.write(entry as u64 + 4, &len.to_le_bytes()).unwrap();
723 data_at += payload.len() as u32;
724 }
725
726 (iovs_at, payloads.len() as u32)
727 }
728
729 fn source(&self, iovs_at: u32, iovs_len: u32) -> FdWriteSource<'_, wasmer::Memory32> {
730 FdWriteSource::Iovs {
731 iovs: WasmPtr::new(iovs_at),
732 iovs_len,
733 }
734 }
735 }
736
737 #[test]
738 fn a_bounded_gather_walks_the_whole_writev() {
739 let mut guest = Guest::new();
740 let (iovs_at, iovs_len) = guest.write_iovs(&[b"abcd", b"efgh", b"ijkl"], None);
741 let view = guest.memory.view(&guest.store);
742 let source = guest.source(iovs_at, iovs_len);
743
744 let mut seen = Vec::new();
747 let mut skip = 0;
748 loop {
749 let chunk = source.coalesce_from(&view, skip, 5).unwrap();
750 if chunk.is_empty() {
751 break;
752 }
753 assert!(chunk.len() <= 5);
754 skip += chunk.len();
755 seen.extend_from_slice(&chunk);
756 }
757 assert_eq!(seen, b"abcdefghijkl");
758 }
759
760 #[test]
761 fn a_bounded_gather_starts_mid_iovec() {
762 let mut guest = Guest::new();
763 let (iovs_at, iovs_len) = guest.write_iovs(&[b"abcd", b"efgh"], None);
764 let view = guest.memory.view(&guest.store);
765 let source = guest.source(iovs_at, iovs_len);
766
767 assert_eq!(&*source.coalesce_from(&view, 2, 4).unwrap(), b"cdef");
768 assert_eq!(&*source.coalesce_from(&view, 8, 4).unwrap(), b"");
769 }
770
771 #[test]
772 fn a_lying_iovec_length_cannot_size_the_host_allocation() {
773 let mut guest = Guest::new();
777 let (iovs_at, iovs_len) = guest.write_iovs(&[b"abcd"], Some(u32::MAX));
778 let view = guest.memory.view(&guest.store);
779 let source = guest.source(iovs_at, iovs_len);
780
781 assert_eq!(
784 source
785 .coalesce_from(&view, 0, MAX_STREAM_COALESCE)
786 .unwrap_err(),
787 Errno::Memviolation
788 );
789
790 let (iovs_at, iovs_len) = guest.write_iovs(&[b"abcd"], None);
792 let view = guest.memory.view(&guest.store);
793 let source = guest.source(iovs_at, iovs_len);
794 assert_eq!(source.coalesce_from(&view, 0, 2).unwrap().len(), 2);
795 }
796
797 #[test]
798 fn a_datagram_is_refused_rather_than_split() {
799 let mut guest = Guest::new();
800 let (iovs_at, iovs_len) = guest.write_iovs(&[b"abcd", b"efgh"], None);
801 let view = guest.memory.view(&guest.store);
802 let source = guest.source(iovs_at, iovs_len);
803
804 assert_eq!(source.coalesce(&view, 4).unwrap_err(), Errno::Msgsize);
805 assert_eq!(&*source.coalesce(&view, 8).unwrap(), b"abcdefgh");
806 }
807}