Skip to main content

wasmer_wasix/syscalls/wasi/
fd_write.rs

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/// ### `fd_write()`
12/// Write data to the file descriptor
13/// Inputs:
14/// - `Fd`
15///     File descriptor (opened with writing) to write to
16/// - `const __wasi_ciovec_t *iovs`
17///     List of vectors to read data from
18/// - `u32 iovs_len`
19///     Length of data in `iovs`
20/// Output:
21/// - `u32 *nwritten`
22///     Number of bytes written
23/// Errors:
24///
25#[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/// ### `fd_pwrite()`
66/// Write to a file without adjusting its offset
67/// Inputs:
68/// - `Fd`
69///     File descriptor (opened with writing) to write to
70/// - `const __wasi_ciovec_t *iovs`
71///     List of vectors to read data from
72/// - `u32 iovs_len`
73///     Length of data in `iovs`
74/// - `Filesize offset`
75///     The offset to write at
76/// Output:
77/// - `u32 *nwritten`
78///     Number of bytes written
79#[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
127/// Cap on how much of a `writev` one coalescing buffer holds. The guest
128/// declares the iovec lengths and nothing has validated the matching pointers
129/// yet, so the buffer is sized by this rather than by what the guest claims.
130/// It also keeps a coalesced write clear of the 8 MiB frame limit that
131/// `LengthDelimitedCodec` imposes on remote virtual-net sockets.
132pub(crate) const MAX_STREAM_COALESCE: usize = 1024 * 1024;
133
134const _: () = assert!(MAX_STREAM_COALESCE < 8 * 1024 * 1024);
135/// A datagram is refused past `MAX_SOCKET_PAYLOAD`, so it never reaches the
136/// bounded gather that streams use.
137const _: () = 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    /// Gathers at most `limit` bytes starting `skip` bytes into the iovecs,
185    /// treating them as one stream. Unlike [`Self::coalesce`] this never sizes
186    /// an allocation by a guest-declared length, so a caller can walk a large
187    /// `writev` in bounded steps instead of refusing it.
188    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                                        // `fdflags::append` means we need to seek to the end before writing.
286                                        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                        // VirtualConnectedSocket exposes one contiguous send operation. Preserve
362                        // writev's single-operation ordering by coalescing guest iovecs before
363                        // crossing that boundary; issuing one send per iovec lets the peer react
364                        // between logically adjacent protocol frames.
365                        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                        // One send per chunk keeps writev's ordering for
374                        // anything that fits in a chunk, while the bound keeps
375                        // a guest-declared length from sizing the buffer. The
376                        // loop leaves the returned count to the socket, as a
377                        // single unbounded send did.
378                        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                                // Report the progress already made, as a write
390                                // that fails part way through does.
391                                Err(_) if sent > 0 => break,
392                                Err(err) => return Err(err),
393                            };
394                            sent += local_sent;
395
396                            if local_sent != chunk_len {
397                                // A blocking write runs to completion on Unix,
398                                // waiting for room rather than reporting a
399                                // short count, so keep pushing the remainder.
400                                // A non-blocking one reports what it managed.
401                                // `local_sent == 0` would not make progress.
402                                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                                        // Need to do this to avoid double borrow on ctx with iovs_arr
438                                        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                                        // Need to do this to avoid double borrow on ctx with iovs_arr
497                                        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                    // TODO: verify
535                    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        // reborrow and update the size
649        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                    // fetch_add returns the previous value, we have to add bytes_written again here
657                    + bytes_written
658            } else {
659                fd_entry.inner.offset.load(Ordering::Acquire)
660            };
661
662            // we set the size but we don't return any errors if it fails as
663            // pipes and sockets will not do anything with this
664            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                    // If we wrote before the end, the current size is still correct.
670                    // Otherwise, we only got as far as the current cursor. So, the
671                    // max of the two is the correct new size.
672                    stat.st_size = stat.st_size.max(curr_offset);
673                } else {
674                    // pwrite does not update the cursor of the file so to calculate the final
675                    // size of the file we compute where the cursor would have been if it was updated,
676                    // and get the max value between it and the current size.
677                    stat.st_size = stat.st_size.max(offset + bytes_written as u64);
678                }
679            } else {
680                // Cast is valid because we don't support 128 bit systems...
681                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        /// Lays `payloads` out in guest memory, then writes an iovec array
710        /// describing them. Each declared length is the payload length unless a
711        /// test is lying about it.
712        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        // Chunks smaller than the total let a caller walk it in steps, so the
745        // bound never shortens the write the guest asked for.
746        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        // A guest owning one page can still claim 4 GiB. `coalesce` sizes its
774        // buffer by that claim, which is why the stream path gathers in bounded
775        // steps instead: a chunk never exceeds the limit, whatever is claimed.
776        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        // The declared length outruns the page, so the read is caught; what
782        // matters is that nothing reserved 4 GiB to find that out.
783        assert_eq!(
784            source
785                .coalesce_from(&view, 0, MAX_STREAM_COALESCE)
786                .unwrap_err(),
787            Errno::Memviolation
788        );
789
790        // A truthful but oversized claim is gathered a chunk at a time.
791        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}