use crate::entry::{Entry, EntryHandle};
use crate::handle::{AsBytes, HandleRights};
use crate::sys::{clock, poll};
use crate::wasi::types;
use crate::wasi::wasi_snapshot_preview1::WasiSnapshotPreview1;
use crate::{path, sched, Error, Result, WasiCtx};
use std::convert::TryInto;
use std::io::{self, SeekFrom};
use std::ops::Deref;
use tracing::{debug, trace};
use wiggle::{GuestPtr, GuestSlice};
impl<'a> WasiSnapshotPreview1 for WasiCtx {
fn args_get<'b>(
&self,
argv: &GuestPtr<'b, GuestPtr<'b, u8>>,
argv_buf: &GuestPtr<'b, u8>,
) -> Result<()> {
self.args.write_to_guest(argv_buf, argv)
}
fn args_sizes_get(&self) -> Result<(types::Size, types::Size)> {
Ok((self.args.number_elements, self.args.cumulative_size))
}
fn environ_get<'b>(
&self,
environ: &GuestPtr<'b, GuestPtr<'b, u8>>,
environ_buf: &GuestPtr<'b, u8>,
) -> Result<()> {
self.env.write_to_guest(environ_buf, environ)
}
fn environ_sizes_get(&self) -> Result<(types::Size, types::Size)> {
Ok((self.env.number_elements, self.env.cumulative_size))
}
fn clock_res_get(&self, id: types::Clockid) -> Result<types::Timestamp> {
let resolution = clock::res_get(id)?;
Ok(resolution)
}
fn clock_time_get(
&self,
id: types::Clockid,
_precision: types::Timestamp,
) -> Result<types::Timestamp> {
let time = clock::time_get(id)?;
Ok(time)
}
fn fd_advise(
&self,
fd: types::Fd,
offset: types::Filesize,
len: types::Filesize,
advice: types::Advice,
) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::FD_ADVISE);
let entry = self.get_entry(fd)?;
entry
.as_handle(&required_rights)?
.advise(advice, offset, len)
}
fn fd_allocate(
&self,
fd: types::Fd,
offset: types::Filesize,
len: types::Filesize,
) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::FD_ALLOCATE);
let entry = self.get_entry(fd)?;
entry.as_handle(&required_rights)?.allocate(offset, len)
}
fn fd_close(&self, fd: types::Fd) -> Result<()> {
if let Ok(fe) = self.get_entry(fd) {
if fe.preopen_path.is_some() {
return Err(Error::Notsup);
}
}
self.remove_entry(fd)?;
Ok(())
}
fn fd_datasync(&self, fd: types::Fd) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::FD_DATASYNC);
let entry = self.get_entry(fd)?;
entry.as_handle(&required_rights)?.datasync()
}
fn fd_fdstat_get(&self, fd: types::Fd) -> Result<types::Fdstat> {
let entry = self.get_entry(fd)?;
let file = entry.as_handle(&HandleRights::empty())?;
let fs_flags = file.fdstat_get()?;
let rights = entry.get_rights();
let fdstat = types::Fdstat {
fs_filetype: entry.get_file_type(),
fs_rights_base: rights.base,
fs_rights_inheriting: rights.inheriting,
fs_flags,
};
Ok(fdstat)
}
fn fd_fdstat_set_flags(&self, fd: types::Fd, flags: types::Fdflags) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::FD_FDSTAT_SET_FLAGS);
let entry = self.get_entry(fd)?;
entry.as_handle(&required_rights)?.fdstat_set_flags(flags)
}
fn fd_fdstat_set_rights(
&self,
fd: types::Fd,
fs_rights_base: types::Rights,
fs_rights_inheriting: types::Rights,
) -> Result<()> {
let rights = HandleRights::new(fs_rights_base, fs_rights_inheriting);
let entry = self.get_entry(fd)?;
if !entry.get_rights().contains(&rights) {
return Err(Error::Notcapable);
}
entry.set_rights(rights);
Ok(())
}
fn fd_filestat_get(&self, fd: types::Fd) -> Result<types::Filestat> {
let required_rights = HandleRights::from_base(types::Rights::FD_FILESTAT_GET);
let entry = self.get_entry(fd)?;
let host_filestat = entry.as_handle(&required_rights)?.filestat_get()?;
Ok(host_filestat)
}
fn fd_filestat_set_size(&self, fd: types::Fd, size: types::Filesize) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::FD_FILESTAT_SET_SIZE);
let entry = self.get_entry(fd)?;
entry.as_handle(&required_rights)?.filestat_set_size(size)
}
fn fd_filestat_set_times(
&self,
fd: types::Fd,
atim: types::Timestamp,
mtim: types::Timestamp,
fst_flags: types::Fstflags,
) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::FD_FILESTAT_SET_TIMES);
let entry = self.get_entry(fd)?;
entry
.as_handle(&required_rights)?
.filestat_set_times(atim, mtim, fst_flags)
}
fn fd_pread(
&self,
fd: types::Fd,
iovs: &types::IovecArray<'_>,
offset: types::Filesize,
) -> Result<types::Size> {
let mut guest_slices: Vec<GuestSlice<'_, u8>> = Vec::new();
for iov_ptr in iovs.iter() {
let iov_ptr = iov_ptr?;
let iov: types::Iovec = iov_ptr.read()?;
guest_slices.push(iov.buf.as_array(iov.buf_len).as_slice()?);
}
let required_rights =
HandleRights::from_base(types::Rights::FD_READ | types::Rights::FD_SEEK);
let entry = self.get_entry(fd)?;
if offset > i64::max_value() as u64 {
return Err(Error::Io);
}
let host_nread = {
let mut buf = guest_slices
.iter_mut()
.map(|s| io::IoSliceMut::new(&mut *s))
.collect::<Vec<io::IoSliceMut<'_>>>();
entry
.as_handle(&required_rights)?
.preadv(&mut buf, offset)?
.try_into()?
};
Ok(host_nread)
}
fn fd_prestat_get(&self, fd: types::Fd) -> Result<types::Prestat> {
let entry = self.get_entry(fd)?;
let po_path = entry.preopen_path.as_ref().ok_or(Error::Notsup)?;
if entry.get_file_type() != types::Filetype::Directory {
return Err(Error::Notdir);
}
let path = path::from_host(po_path.as_os_str())?;
let prestat = types::PrestatDir {
pr_name_len: path.len().try_into()?,
};
Ok(types::Prestat::Dir(prestat))
}
fn fd_prestat_dir_name(
&self,
fd: types::Fd,
path: &GuestPtr<u8>,
path_len: types::Size,
) -> Result<()> {
let entry = self.get_entry(fd)?;
let po_path = entry.preopen_path.as_ref().ok_or(Error::Notsup)?;
if entry.get_file_type() != types::Filetype::Directory {
return Err(Error::Notdir);
}
let host_path = path::from_host(po_path.as_os_str())?;
let host_path_len = host_path.len().try_into()?;
if host_path_len > path_len {
return Err(Error::Nametoolong);
}
trace!(" | path='{}'", host_path);
path.as_array(host_path_len)
.copy_from_slice(host_path.as_bytes())?;
Ok(())
}
fn fd_pwrite(
&self,
fd: types::Fd,
ciovs: &types::CiovecArray<'_>,
offset: types::Filesize,
) -> Result<types::Size> {
let mut guest_slices = Vec::new();
for ciov_ptr in ciovs.iter() {
let ciov_ptr = ciov_ptr?;
let ciov: types::Ciovec = ciov_ptr.read()?;
guest_slices.push(ciov.buf.as_array(ciov.buf_len).as_slice()?);
}
let required_rights =
HandleRights::from_base(types::Rights::FD_WRITE | types::Rights::FD_SEEK);
let entry = self.get_entry(fd)?;
if offset > i64::max_value() as u64 {
return Err(Error::Io);
}
let host_nwritten = {
let buf: Vec<io::IoSlice> =
guest_slices.iter().map(|s| io::IoSlice::new(&*s)).collect();
entry
.as_handle(&required_rights)?
.pwritev(&buf, offset)?
.try_into()?
};
Ok(host_nwritten)
}
fn fd_read(&self, fd: types::Fd, iovs: &types::IovecArray<'_>) -> Result<types::Size> {
let mut guest_slices = Vec::new();
for iov_ptr in iovs.iter() {
let iov_ptr = iov_ptr?;
let iov: types::Iovec = iov_ptr.read()?;
guest_slices.push(iov.buf.as_array(iov.buf_len).as_slice()?);
}
let required_rights = HandleRights::from_base(types::Rights::FD_READ);
let entry = self.get_entry(fd)?;
let host_nread = {
let mut slices: Vec<io::IoSliceMut> = guest_slices
.iter_mut()
.map(|s| io::IoSliceMut::new(&mut *s))
.collect();
entry
.as_handle(&required_rights)?
.read_vectored(&mut slices)?
.try_into()?
};
Ok(host_nread)
}
fn fd_readdir(
&self,
fd: types::Fd,
buf: &GuestPtr<u8>,
buf_len: types::Size,
cookie: types::Dircookie,
) -> Result<types::Size> {
let required_rights = HandleRights::from_base(types::Rights::FD_READDIR);
let entry = self.get_entry(fd)?;
let mut bufused = 0;
let mut buf = buf.clone();
for pair in entry.as_handle(&required_rights)?.readdir(cookie)? {
let (dirent, name) = pair?;
let dirent_raw = dirent.as_bytes()?;
let dirent_len: types::Size = dirent_raw.len().try_into()?;
let name_raw = name.as_bytes();
let name_len = name_raw.len().try_into()?;
let offset = dirent_len.checked_add(name_len).ok_or(Error::Overflow)?;
if (buf_len - bufused) < offset {
break;
} else {
buf.as_array(dirent_len).copy_from_slice(&dirent_raw)?;
buf = buf.add(dirent_len)?;
buf.as_array(name_len).copy_from_slice(name_raw)?;
buf = buf.add(name_len)?;
bufused += offset;
}
}
Ok(bufused)
}
fn fd_renumber(&self, from: types::Fd, to: types::Fd) -> Result<()> {
if !self.contains_entry(from) {
return Err(Error::Badf);
}
if let Ok(from_fe) = self.get_entry(from) {
if from_fe.preopen_path.is_some() {
return Err(Error::Notsup);
}
}
if let Ok(to_fe) = self.get_entry(to) {
if to_fe.preopen_path.is_some() {
return Err(Error::Notsup);
}
}
let fe = self.remove_entry(from)?;
self.insert_entry_at(to, fe);
Ok(())
}
fn fd_seek(
&self,
fd: types::Fd,
offset: types::Filedelta,
whence: types::Whence,
) -> Result<types::Filesize> {
let base = if offset == 0 && whence == types::Whence::Cur {
types::Rights::FD_TELL
} else {
types::Rights::FD_SEEK | types::Rights::FD_TELL
};
let required_rights = HandleRights::from_base(base);
let entry = self.get_entry(fd)?;
let pos = match whence {
types::Whence::Cur => SeekFrom::Current(offset),
types::Whence::End => SeekFrom::End(offset),
types::Whence::Set => SeekFrom::Start(offset as u64),
};
let host_newoffset = entry.as_handle(&required_rights)?.seek(pos)?;
Ok(host_newoffset)
}
fn fd_sync(&self, fd: types::Fd) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::FD_SYNC);
let entry = self.get_entry(fd)?;
entry.as_handle(&required_rights)?.sync()
}
fn fd_tell(&self, fd: types::Fd) -> Result<types::Filesize> {
let required_rights = HandleRights::from_base(types::Rights::FD_TELL);
let entry = self.get_entry(fd)?;
let host_offset = entry
.as_handle(&required_rights)?
.seek(SeekFrom::Current(0))?;
Ok(host_offset)
}
fn fd_write(&self, fd: types::Fd, ciovs: &types::CiovecArray<'_>) -> Result<types::Size> {
let mut guest_slices = Vec::new();
for ciov_ptr in ciovs.iter() {
let ciov_ptr = ciov_ptr?;
let ciov: types::Ciovec = ciov_ptr.read()?;
guest_slices.push(ciov.buf.as_array(ciov.buf_len).as_slice()?);
}
let required_rights = HandleRights::from_base(types::Rights::FD_WRITE);
let entry = self.get_entry(fd)?;
let host_nwritten = {
let slices: Vec<io::IoSlice> =
guest_slices.iter().map(|s| io::IoSlice::new(&*s)).collect();
entry
.as_handle(&required_rights)?
.write_vectored(&slices)?
.try_into()?
};
Ok(host_nwritten)
}
fn path_create_directory(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> {
let required_rights = HandleRights::from_base(
types::Rights::PATH_OPEN | types::Rights::PATH_CREATE_DIRECTORY,
);
let entry = self.get_entry(dirfd)?;
let path = path.as_str()?;
let (dirfd, path) = path::get(
&entry,
&required_rights,
types::Lookupflags::empty(),
path.deref(),
false,
)?;
dirfd.create_directory(&path)
}
fn path_filestat_get(
&self,
dirfd: types::Fd,
flags: types::Lookupflags,
path: &GuestPtr<'_, str>,
) -> Result<types::Filestat> {
let required_rights = HandleRights::from_base(types::Rights::PATH_FILESTAT_GET);
let entry = self.get_entry(dirfd)?;
let path = path.as_str()?;
let (dirfd, path) = path::get(&entry, &required_rights, flags, path.deref(), false)?;
let host_filestat =
dirfd.filestat_get_at(&path, flags.contains(&types::Lookupflags::SYMLINK_FOLLOW))?;
Ok(host_filestat)
}
fn path_filestat_set_times(
&self,
dirfd: types::Fd,
flags: types::Lookupflags,
path: &GuestPtr<'_, str>,
atim: types::Timestamp,
mtim: types::Timestamp,
fst_flags: types::Fstflags,
) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::PATH_FILESTAT_SET_TIMES);
let entry = self.get_entry(dirfd)?;
let path = path.as_str()?;
let (dirfd, path) = path::get(&entry, &required_rights, flags, path.deref(), false)?;
dirfd.filestat_set_times_at(
&path,
atim,
mtim,
fst_flags,
flags.contains(&types::Lookupflags::SYMLINK_FOLLOW),
)?;
Ok(())
}
fn path_link(
&self,
old_fd: types::Fd,
old_flags: types::Lookupflags,
old_path: &GuestPtr<'_, str>,
new_fd: types::Fd,
new_path: &GuestPtr<'_, str>,
) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::PATH_LINK_SOURCE);
let old_entry = self.get_entry(old_fd)?;
let (old_dirfd, old_path) = {
let old_path = old_path.as_str()?;
path::get(
&old_entry,
&required_rights,
types::Lookupflags::empty(),
old_path.deref(),
false,
)?
};
let required_rights = HandleRights::from_base(types::Rights::PATH_LINK_TARGET);
let new_entry = self.get_entry(new_fd)?;
let (new_dirfd, new_path) = {
let new_path = new_path.as_str()?;
path::get(
&new_entry,
&required_rights,
types::Lookupflags::empty(),
new_path.deref(),
false,
)?
};
old_dirfd.link(
&old_path,
new_dirfd,
&new_path,
old_flags.contains(&types::Lookupflags::SYMLINK_FOLLOW),
)
}
fn path_open(
&self,
dirfd: types::Fd,
dirflags: types::Lookupflags,
path: &GuestPtr<'_, str>,
oflags: types::Oflags,
fs_rights_base: types::Rights,
fs_rights_inheriting: types::Rights,
fdflags: types::Fdflags,
) -> Result<types::Fd> {
let needed_rights = path::open_rights(
&HandleRights::new(fs_rights_base, fs_rights_inheriting),
oflags,
fdflags,
);
trace!(" | needed_rights={}", needed_rights);
let entry = self.get_entry(dirfd)?;
let (dirfd, path) = {
let path = path.as_str()?;
path::get(
&entry,
&needed_rights,
dirflags,
path.deref(),
oflags & types::Oflags::CREAT != types::Oflags::empty(),
)?
};
let read = fs_rights_base & (types::Rights::FD_READ | types::Rights::FD_READDIR)
!= types::Rights::empty();
let write = fs_rights_base
& (types::Rights::FD_DATASYNC
| types::Rights::FD_WRITE
| types::Rights::FD_ALLOCATE
| types::Rights::FD_FILESTAT_SET_SIZE)
!= types::Rights::empty();
trace!(
" | calling path_open impl: read={}, write={}",
read,
write
);
let fd = dirfd.openat(&path, read, write, oflags, fdflags)?;
let entry = Entry::new(EntryHandle::from(fd));
let mut rights = entry.get_rights();
rights.base &= fs_rights_base;
rights.inheriting &= fs_rights_inheriting;
entry.set_rights(rights);
let guest_fd = self.insert_entry(entry)?;
Ok(guest_fd)
}
fn path_readlink(
&self,
dirfd: types::Fd,
path: &GuestPtr<'_, str>,
buf: &GuestPtr<u8>,
buf_len: types::Size,
) -> Result<types::Size> {
let required_rights = HandleRights::from_base(types::Rights::PATH_READLINK);
let entry = self.get_entry(dirfd)?;
let (dirfd, path) = {
let path = path.as_str()?;
path::get(
&entry,
&required_rights,
types::Lookupflags::empty(),
path.deref(),
false,
)?
};
let mut slice = buf.as_array(buf_len).as_slice()?;
let host_bufused = dirfd.readlink(&path, &mut *slice)?.try_into()?;
Ok(host_bufused)
}
fn path_remove_directory(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::PATH_REMOVE_DIRECTORY);
let entry = self.get_entry(dirfd)?;
let (dirfd, path) = {
let path = path.as_str()?;
path::get(
&entry,
&required_rights,
types::Lookupflags::empty(),
path.deref(),
true,
)?
};
dirfd.remove_directory(&path)
}
fn path_rename(
&self,
old_fd: types::Fd,
old_path: &GuestPtr<'_, str>,
new_fd: types::Fd,
new_path: &GuestPtr<'_, str>,
) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::PATH_RENAME_SOURCE);
let entry = self.get_entry(old_fd)?;
let (old_dirfd, old_path) = {
let old_path = old_path.as_str()?;
path::get(
&entry,
&required_rights,
types::Lookupflags::empty(),
old_path.deref(),
true,
)?
};
let required_rights = HandleRights::from_base(types::Rights::PATH_RENAME_TARGET);
let entry = self.get_entry(new_fd)?;
let (new_dirfd, new_path) = {
let new_path = new_path.as_str()?;
path::get(
&entry,
&required_rights,
types::Lookupflags::empty(),
new_path.deref(),
true,
)?
};
old_dirfd.rename(&old_path, new_dirfd, &new_path)
}
fn path_symlink(
&self,
old_path: &GuestPtr<'_, str>,
dirfd: types::Fd,
new_path: &GuestPtr<'_, str>,
) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::PATH_SYMLINK);
let entry = self.get_entry(dirfd)?;
let (new_fd, new_path) = {
let new_path = new_path.as_str()?;
path::get(
&entry,
&required_rights,
types::Lookupflags::empty(),
new_path.deref(),
true,
)?
};
let old_path = old_path.as_str()?;
trace!(old_path = old_path.deref());
new_fd.symlink(&old_path, &new_path)
}
fn path_unlink_file(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::PATH_UNLINK_FILE);
let entry = self.get_entry(dirfd)?;
let (dirfd, path) = {
let path = path.as_str()?;
path::get(
&entry,
&required_rights,
types::Lookupflags::empty(),
path.deref(),
false,
)?
};
dirfd.unlink_file(&path)?;
Ok(())
}
fn poll_oneoff(
&self,
in_: &GuestPtr<types::Subscription>,
out: &GuestPtr<types::Event>,
nsubscriptions: types::Size,
) -> Result<types::Size> {
if u64::from(nsubscriptions) > types::Filesize::max_value() {
return Err(Error::Inval);
}
let mut subscriptions = Vec::new();
let subs = in_.as_array(nsubscriptions);
for sub_ptr in subs.iter() {
let sub_ptr = sub_ptr?;
let sub: types::Subscription = sub_ptr.read()?;
subscriptions.push(sub);
}
let mut events = Vec::new();
let mut timeout: Option<sched::ClockEventData> = None;
let mut fd_events = Vec::new();
if subscriptions.is_empty() {
return Err(Error::Inval);
}
for subscription in subscriptions {
match subscription.u {
types::SubscriptionU::Clock(clock) => {
let delay = clock::to_relative_ns_delay(&clock)?;
debug!(
clock = tracing::field::debug(&clock),
delay_ns = tracing::field::debug(delay),
"poll_oneoff"
);
let current = sched::ClockEventData {
delay,
userdata: subscription.userdata,
};
let timeout = timeout.get_or_insert(current);
if current.delay < timeout.delay {
*timeout = current;
}
}
types::SubscriptionU::FdRead(fd_read) => {
let fd = fd_read.file_descriptor;
let required_rights = HandleRights::from_base(
types::Rights::FD_READ | types::Rights::POLL_FD_READWRITE,
);
let entry = match self.get_entry(fd) {
Ok(entry) => entry,
Err(error) => {
events.push(types::Event {
userdata: subscription.userdata,
error: error.into(),
type_: types::Eventtype::FdRead,
fd_readwrite: types::EventFdReadwrite {
nbytes: 0,
flags: types::Eventrwflags::empty(),
},
});
continue;
}
};
fd_events.push(sched::FdEventData {
handle: entry.as_handle(&required_rights)?,
r#type: types::Eventtype::FdRead,
userdata: subscription.userdata,
});
}
types::SubscriptionU::FdWrite(fd_write) => {
let fd = fd_write.file_descriptor;
let required_rights = HandleRights::from_base(
types::Rights::FD_WRITE | types::Rights::POLL_FD_READWRITE,
);
let entry = match self.get_entry(fd) {
Ok(entry) => entry,
Err(error) => {
events.push(types::Event {
userdata: subscription.userdata,
error: error.into(),
type_: types::Eventtype::FdWrite,
fd_readwrite: types::EventFdReadwrite {
nbytes: 0,
flags: types::Eventrwflags::empty(),
},
});
continue;
}
};
fd_events.push(sched::FdEventData {
handle: entry.as_handle(&required_rights)?,
r#type: types::Eventtype::FdWrite,
userdata: subscription.userdata,
});
}
}
}
debug!(
events = tracing::field::debug(&events),
timeout = tracing::field::debug(timeout),
"poll_oneoff"
);
poll::oneoff(timeout, fd_events, &mut events)?;
let nevents = events.len().try_into()?;
let out_events = out.as_array(nevents);
for (event, event_ptr) in events.into_iter().zip(out_events.iter()) {
let event_ptr = event_ptr?;
event_ptr.write(event)?;
}
trace!(nevents = nevents);
Ok(nevents)
}
fn proc_exit(&self, _rval: types::Exitcode) -> std::result::Result<(), ()> {
unimplemented!("runtimes are expected to override this implementation")
}
fn proc_raise(&self, _sig: types::Signal) -> Result<()> {
unimplemented!("proc_raise")
}
fn sched_yield(&self) -> Result<()> {
std::thread::yield_now();
Ok(())
}
fn random_get(&self, buf: &GuestPtr<u8>, buf_len: types::Size) -> Result<()> {
let mut slice = buf.as_array(buf_len).as_slice()?;
getrandom::getrandom(&mut *slice)?;
Ok(())
}
fn sock_recv(
&self,
_fd: types::Fd,
_ri_data: &types::IovecArray<'_>,
_ri_flags: types::Riflags,
) -> Result<(types::Size, types::Roflags)> {
unimplemented!("sock_recv")
}
fn sock_send(
&self,
_fd: types::Fd,
_si_data: &types::CiovecArray<'_>,
_si_flags: types::Siflags,
) -> Result<types::Size> {
unimplemented!("sock_send")
}
fn sock_shutdown(&self, _fd: types::Fd, _how: types::Sdflags) -> Result<()> {
unimplemented!("sock_shutdown")
}
}