about summary refs log tree commit diff
path: root/library/std/src/sys
diff options
context:
space:
mode:
authorJiahao XU <Jiahao_XU@outlook.com>2024-06-30 18:23:07 +1000
committerJiahao XU <Jiahao_XU@outlook.com>2024-07-23 23:13:56 +1000
commitc9c8a14884c19e51a0eee54ccd98efa7f0f2bddd (patch)
tree1b94b0d4990f0771508383ea979d012270473e53 /library/std/src/sys
parent52f3c71c8dc4aaed71e3035995fcbdd6d78c98c6 (diff)
downloadrust-c9c8a14884c19e51a0eee54ccd98efa7f0f2bddd.tar.gz
rust-c9c8a14884c19e51a0eee54ccd98efa7f0f2bddd.zip
Initial implementation of anonymous_pipe
Co-authored-by: Alphyr <47725341+a1phyr@users.noreply.github.com>
Co-authored-by: Jubilee <46493976+workingjubilee@users.noreply.github.com>
Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
Diffstat (limited to 'library/std/src/sys')
-rw-r--r--library/std/src/sys/anonymous_pipe/mod.rs18
-rw-r--r--library/std/src/sys/anonymous_pipe/tests.rs20
-rw-r--r--library/std/src/sys/anonymous_pipe/unix.rs103
-rw-r--r--library/std/src/sys/anonymous_pipe/unsupported.rs26
-rw-r--r--library/std/src/sys/anonymous_pipe/windows.rs109
-rw-r--r--library/std/src/sys/mod.rs2
-rw-r--r--library/std/src/sys/pal/unix/fd.rs5
-rw-r--r--library/std/src/sys/pal/unix/pipe.rs9
-rw-r--r--library/std/src/sys/pal/unsupported/pipe.rs15
-rw-r--r--library/std/src/sys/pal/windows/c/bindings.txt1
-rw-r--r--library/std/src/sys/pal/windows/c/windows_sys.rs1
-rw-r--r--library/std/src/sys/pal/windows/handle.rs7
-rw-r--r--library/std/src/sys/pal/windows/pipe.rs24
13 files changed, 336 insertions, 4 deletions
diff --git a/library/std/src/sys/anonymous_pipe/mod.rs b/library/std/src/sys/anonymous_pipe/mod.rs
new file mode 100644
index 00000000000..74875677cf3
--- /dev/null
+++ b/library/std/src/sys/anonymous_pipe/mod.rs
@@ -0,0 +1,18 @@
+cfg_if::cfg_if! {
+    if #[cfg(unix)] {
+        mod unix;
+        pub(crate) use unix::{AnonPipe, pipe};
+
+        #[cfg(all(test, not(miri)))]
+        mod tests;
+    } else if #[cfg(windows)] {
+        mod windows;
+        pub(crate) use windows::{AnonPipe, pipe};
+
+        #[cfg(all(test, not(miri)))]
+        mod tests;
+    } else {
+        mod unsupported;
+        pub(crate) use unsupported::{AnonPipe, pipe};
+    }
+}
diff --git a/library/std/src/sys/anonymous_pipe/tests.rs b/library/std/src/sys/anonymous_pipe/tests.rs
new file mode 100644
index 00000000000..f5ea583eefe
--- /dev/null
+++ b/library/std/src/sys/anonymous_pipe/tests.rs
@@ -0,0 +1,20 @@
+use crate::{
+    io::{Read, Write},
+    pipe::pipe,
+};
+
+#[test]
+fn pipe_creation_clone_and_rw() {
+    let (rx, tx) = pipe().unwrap();
+
+    tx.try_clone().unwrap().write_all(b"12345").unwrap();
+    drop(tx);
+
+    let mut rx2 = rx.try_clone().unwrap();
+    drop(rx);
+
+    let mut s = String::new();
+    rx2.read_to_string(&mut s).unwrap();
+    drop(rx2);
+    assert_eq!(s, "12345");
+}
diff --git a/library/std/src/sys/anonymous_pipe/unix.rs b/library/std/src/sys/anonymous_pipe/unix.rs
new file mode 100644
index 00000000000..ddbf1d7334f
--- /dev/null
+++ b/library/std/src/sys/anonymous_pipe/unix.rs
@@ -0,0 +1,103 @@
+use crate::{
+    io,
+    os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd},
+    pipe::{PipeReader, PipeWriter},
+    process::Stdio,
+    sys::{fd::FileDesc, pipe::anon_pipe},
+    sys_common::{FromInner, IntoInner},
+};
+
+pub(crate) type AnonPipe = FileDesc;
+
+#[inline]
+pub(crate) fn pipe() -> io::Result<(AnonPipe, AnonPipe)> {
+    anon_pipe().map(|(rx, wx)| (rx.into_inner(), wx.into_inner()))
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl AsFd for PipeReader {
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.0.as_fd()
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl AsRawFd for PipeReader {
+    fn as_raw_fd(&self) -> RawFd {
+        self.0.as_raw_fd()
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<PipeReader> for OwnedFd {
+    fn from(pipe: PipeReader) -> Self {
+        FileDesc::into_inner(pipe.0)
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl FromRawFd for PipeReader {
+    unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
+        Self(FileDesc::from_raw_fd(raw_fd))
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl IntoRawFd for PipeReader {
+    fn into_raw_fd(self) -> RawFd {
+        self.0.into_raw_fd()
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<PipeReader> for Stdio {
+    fn from(pipe: PipeReader) -> Self {
+        Self::from(OwnedFd::from(pipe))
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl AsFd for PipeWriter {
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.0.as_fd()
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl AsRawFd for PipeWriter {
+    fn as_raw_fd(&self) -> RawFd {
+        self.0.as_raw_fd()
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<PipeWriter> for OwnedFd {
+    fn from(pipe: PipeWriter) -> Self {
+        FileDesc::into_inner(pipe.0)
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl FromRawFd for PipeWriter {
+    unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
+        Self(FileDesc::from_raw_fd(raw_fd))
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl IntoRawFd for PipeWriter {
+    fn into_raw_fd(self) -> RawFd {
+        self.0.into_raw_fd()
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<PipeWriter> for Stdio {
+    fn from(pipe: PipeWriter) -> Self {
+        Self::from(OwnedFd::from(pipe))
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<OwnedFd> for PipeReader {
+    fn from(owned_fd: OwnedFd) -> Self {
+        Self(FileDesc::from_inner(owned_fd))
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<OwnedFd> for PipeWriter {
+    fn from(owned_fd: OwnedFd) -> Self {
+        Self(FileDesc::from_inner(owned_fd))
+    }
+}
diff --git a/library/std/src/sys/anonymous_pipe/unsupported.rs b/library/std/src/sys/anonymous_pipe/unsupported.rs
new file mode 100644
index 00000000000..5962b69203e
--- /dev/null
+++ b/library/std/src/sys/anonymous_pipe/unsupported.rs
@@ -0,0 +1,26 @@
+use crate::{
+    io,
+    pipe::{PipeReader, PipeWriter},
+    process::Stdio,
+};
+
+pub(crate) use crate::sys::pipe::AnonPipe;
+
+#[inline]
+pub(crate) fn pipe() -> io::Result<(AnonPipe, AnonPipe)> {
+    Err(io::Error::UNSUPPORTED_PLATFORM)
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<PipeReader> for Stdio {
+    fn from(pipe: PipeReader) -> Self {
+        pipe.0.diverge()
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<PipeWriter> for Stdio {
+    fn from(pipe: PipeWriter) -> Self {
+        pipe.0.diverge()
+    }
+}
diff --git a/library/std/src/sys/anonymous_pipe/windows.rs b/library/std/src/sys/anonymous_pipe/windows.rs
new file mode 100644
index 00000000000..81f95aa286a
--- /dev/null
+++ b/library/std/src/sys/anonymous_pipe/windows.rs
@@ -0,0 +1,109 @@
+use crate::{
+    io,
+    os::windows::io::{
+        AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, IntoRawHandle, OwnedHandle, RawHandle,
+    },
+    pipe::{PipeReader, PipeWriter},
+    process::Stdio,
+    sys::{handle::Handle, pipe::unnamed_anon_pipe},
+    sys_common::{FromInner, IntoInner},
+};
+
+pub(crate) type AnonPipe = Handle;
+
+#[inline]
+pub(crate) fn pipe() -> io::Result<(AnonPipe, AnonPipe)> {
+    unnamed_anon_pipe().map(|(rx, wx)| (rx.into_inner(), wx.into_inner()))
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl AsHandle for PipeReader {
+    fn as_handle(&self) -> BorrowedHandle<'_> {
+        self.0.as_handle()
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl AsRawHandle for PipeReader {
+    fn as_raw_handle(&self) -> RawHandle {
+        self.0.as_raw_handle()
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl FromRawHandle for PipeReader {
+    unsafe fn from_raw_handle(raw_handle: RawHandle) -> Self {
+        Self(Handle::from_raw_handle(raw_handle))
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl IntoRawHandle for PipeReader {
+    fn into_raw_handle(self) -> RawHandle {
+        self.0.into_raw_handle()
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<PipeReader> for OwnedHandle {
+    fn from(pipe: PipeReader) -> Self {
+        Handle::into_inner(pipe.0)
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<PipeReader> for Stdio {
+    fn from(pipe: PipeReader) -> Self {
+        Self::from(OwnedHandle::from(pipe))
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl AsHandle for PipeWriter {
+    fn as_handle(&self) -> BorrowedHandle<'_> {
+        self.0.as_handle()
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl AsRawHandle for PipeWriter {
+    fn as_raw_handle(&self) -> RawHandle {
+        self.0.as_raw_handle()
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl FromRawHandle for PipeWriter {
+    unsafe fn from_raw_handle(raw_handle: RawHandle) -> Self {
+        Self(Handle::from_raw_handle(raw_handle))
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl IntoRawHandle for PipeWriter {
+    fn into_raw_handle(self) -> RawHandle {
+        self.0.into_raw_handle()
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<PipeWriter> for OwnedHandle {
+    fn from(pipe: PipeWriter) -> Self {
+        Handle::into_inner(pipe.0)
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<PipeWriter> for Stdio {
+    fn from(pipe: PipeWriter) -> Self {
+        Self::from(OwnedHandle::from(pipe))
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<OwnedHandle> for PipeReader {
+    fn from(owned_handle: OwnedHandle) -> Self {
+        Self(Handle::from_inner(owned_handle))
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<OwnedHandle> for PipeWriter {
+    fn from(owned_handle: OwnedHandle) -> Self {
+        Self(Handle::from_inner(owned_handle))
+    }
+}
diff --git a/library/std/src/sys/mod.rs b/library/std/src/sys/mod.rs
index e50758ce00d..202997b7495 100644
--- a/library/std/src/sys/mod.rs
+++ b/library/std/src/sys/mod.rs
@@ -7,6 +7,8 @@ mod pal;
 
 mod personality;
 
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+pub mod anonymous_pipe;
 pub mod backtrace;
 pub mod cmath;
 pub mod exit_guard;
diff --git a/library/std/src/sys/pal/unix/fd.rs b/library/std/src/sys/pal/unix/fd.rs
index 1701717db59..10ae3c3ab57 100644
--- a/library/std/src/sys/pal/unix/fd.rs
+++ b/library/std/src/sys/pal/unix/fd.rs
@@ -82,6 +82,11 @@ const fn max_iov() -> usize {
 }
 
 impl FileDesc {
+    #[inline]
+    pub fn try_clone(&self) -> io::Result<Self> {
+        self.duplicate()
+    }
+
     pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
         let ret = cvt(unsafe {
             libc::read(
diff --git a/library/std/src/sys/pal/unix/pipe.rs b/library/std/src/sys/pal/unix/pipe.rs
index 33db24e77e4..8762af614f1 100644
--- a/library/std/src/sys/pal/unix/pipe.rs
+++ b/library/std/src/sys/pal/unix/pipe.rs
@@ -9,6 +9,7 @@ use crate::sys_common::{FromInner, IntoInner};
 // Anonymous pipes
 ////////////////////////////////////////////////////////////////////////////////
 
+#[derive(Debug)]
 pub struct AnonPipe(FileDesc);
 
 pub fn anon_pipe() -> io::Result<(AnonPipe, AnonPipe)> {
@@ -46,6 +47,10 @@ pub fn anon_pipe() -> io::Result<(AnonPipe, AnonPipe)> {
 }
 
 impl AnonPipe {
+    pub fn try_clone(&self) -> io::Result<Self> {
+        self.0.duplicate().map(Self)
+    }
+
     pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
         self.0.read(buf)
     }
@@ -79,6 +84,10 @@ impl AnonPipe {
     pub fn is_write_vectored(&self) -> bool {
         self.0.is_write_vectored()
     }
+
+    pub fn as_file_desc(&self) -> &FileDesc {
+        &self.0
+    }
 }
 
 impl IntoInner<FileDesc> for AnonPipe {
diff --git a/library/std/src/sys/pal/unsupported/pipe.rs b/library/std/src/sys/pal/unsupported/pipe.rs
index d7d8f297ae5..781eafe2f1a 100644
--- a/library/std/src/sys/pal/unsupported/pipe.rs
+++ b/library/std/src/sys/pal/unsupported/pipe.rs
@@ -1,8 +1,21 @@
-use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut};
+use crate::{
+    fmt,
+    io::{self, BorrowedCursor, IoSlice, IoSliceMut},
+};
 
 pub struct AnonPipe(!);
 
+impl fmt::Debug for AnonPipe {
+    fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.0
+    }
+}
+
 impl AnonPipe {
+    pub fn try_clone(&self) -> io::Result<Self> {
+        self.0
+    }
+
     pub fn read(&self, _buf: &mut [u8]) -> io::Result<usize> {
         self.0
     }
diff --git a/library/std/src/sys/pal/windows/c/bindings.txt b/library/std/src/sys/pal/windows/c/bindings.txt
index 5ad4a3731d8..7423bbe7694 100644
--- a/library/std/src/sys/pal/windows/c/bindings.txt
+++ b/library/std/src/sys/pal/windows/c/bindings.txt
@@ -2462,6 +2462,7 @@ Windows.Win32.System.LibraryLoader.GetProcAddress
 Windows.Win32.System.Performance.QueryPerformanceCounter
 Windows.Win32.System.Performance.QueryPerformanceFrequency
 Windows.Win32.System.Pipes.CreateNamedPipeW
+Windows.Win32.System.Pipes.CreatePipe
 Windows.Win32.System.Pipes.NAMED_PIPE_MODE
 Windows.Win32.System.Pipes.PIPE_ACCEPT_REMOTE_CLIENTS
 Windows.Win32.System.Pipes.PIPE_CLIENT_END
diff --git a/library/std/src/sys/pal/windows/c/windows_sys.rs b/library/std/src/sys/pal/windows/c/windows_sys.rs
index fea00fec9ae..91c7d7ebc5e 100644
--- a/library/std/src/sys/pal/windows/c/windows_sys.rs
+++ b/library/std/src/sys/pal/windows/c/windows_sys.rs
@@ -14,6 +14,7 @@ windows_targets::link!("kernel32.dll" "system" fn CreateEventW(lpeventattributes
 windows_targets::link!("kernel32.dll" "system" fn CreateFileW(lpfilename : PCWSTR, dwdesiredaccess : u32, dwsharemode : FILE_SHARE_MODE, lpsecurityattributes : *const SECURITY_ATTRIBUTES, dwcreationdisposition : FILE_CREATION_DISPOSITION, dwflagsandattributes : FILE_FLAGS_AND_ATTRIBUTES, htemplatefile : HANDLE) -> HANDLE);
 windows_targets::link!("kernel32.dll" "system" fn CreateHardLinkW(lpfilename : PCWSTR, lpexistingfilename : PCWSTR, lpsecurityattributes : *const SECURITY_ATTRIBUTES) -> BOOL);
 windows_targets::link!("kernel32.dll" "system" fn CreateNamedPipeW(lpname : PCWSTR, dwopenmode : FILE_FLAGS_AND_ATTRIBUTES, dwpipemode : NAMED_PIPE_MODE, nmaxinstances : u32, noutbuffersize : u32, ninbuffersize : u32, ndefaulttimeout : u32, lpsecurityattributes : *const SECURITY_ATTRIBUTES) -> HANDLE);
+windows_targets::link!("kernel32.dll" "system" fn CreatePipe(hreadpipe : *mut HANDLE, hwritepipe : *mut HANDLE, lppipeattributes : *const SECURITY_ATTRIBUTES, nsize : u32) -> BOOL);
 windows_targets::link!("kernel32.dll" "system" fn CreateProcessW(lpapplicationname : PCWSTR, lpcommandline : PWSTR, lpprocessattributes : *const SECURITY_ATTRIBUTES, lpthreadattributes : *const SECURITY_ATTRIBUTES, binherithandles : BOOL, dwcreationflags : PROCESS_CREATION_FLAGS, lpenvironment : *const core::ffi::c_void, lpcurrentdirectory : PCWSTR, lpstartupinfo : *const STARTUPINFOW, lpprocessinformation : *mut PROCESS_INFORMATION) -> BOOL);
 windows_targets::link!("kernel32.dll" "system" fn CreateSymbolicLinkW(lpsymlinkfilename : PCWSTR, lptargetfilename : PCWSTR, dwflags : SYMBOLIC_LINK_FLAGS) -> BOOLEAN);
 windows_targets::link!("kernel32.dll" "system" fn CreateThread(lpthreadattributes : *const SECURITY_ATTRIBUTES, dwstacksize : usize, lpstartaddress : LPTHREAD_START_ROUTINE, lpparameter : *const core::ffi::c_void, dwcreationflags : THREAD_CREATION_FLAGS, lpthreadid : *mut u32) -> HANDLE);
diff --git a/library/std/src/sys/pal/windows/handle.rs b/library/std/src/sys/pal/windows/handle.rs
index aaa1831dcc2..706062ab984 100644
--- a/library/std/src/sys/pal/windows/handle.rs
+++ b/library/std/src/sys/pal/windows/handle.rs
@@ -17,6 +17,7 @@ use crate::sys_common::{AsInner, FromInner, IntoInner};
 /// An owned container for `HANDLE` object, closing them on Drop.
 ///
 /// All methods are inherited through a `Deref` impl to `RawHandle`
+#[derive(Debug)]
 pub struct Handle(OwnedHandle);
 
 impl Handle {
@@ -136,6 +137,12 @@ impl Handle {
         }
     }
 
+    pub fn read_to_end(&self, buf: &mut Vec<u8>) -> io::Result<usize> {
+        let mut me = self;
+
+        Read::read_to_end(&mut me, buf)
+    }
+
     pub unsafe fn read_overlapped(
         &self,
         buf: &mut [mem::MaybeUninit<u8>],
diff --git a/library/std/src/sys/pal/windows/pipe.rs b/library/std/src/sys/pal/windows/pipe.rs
index 7a309b9638b..d5e2356116f 100644
--- a/library/std/src/sys/pal/windows/pipe.rs
+++ b/library/std/src/sys/pal/windows/pipe.rs
@@ -1,7 +1,7 @@
 use crate::os::windows::prelude::*;
 
 use crate::ffi::OsStr;
-use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, Read};
+use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut};
 use crate::mem;
 use crate::path::Path;
 use crate::ptr;
@@ -39,6 +39,23 @@ pub struct Pipes {
     pub theirs: AnonPipe,
 }
 
+/// Create true unnamed anonymous pipe.
+pub fn unnamed_anon_pipe() -> io::Result<(AnonPipe, AnonPipe)> {
+    let mut read_pipe = c::INVALID_HANDLE_VALUE;
+    let mut write_pipe = c::INVALID_HANDLE_VALUE;
+
+    let ret = unsafe { c::CreatePipe(&mut read_pipe, &mut write_pipe, ptr::null_mut(), 0) };
+
+    if ret == 0 {
+        Err(io::Error::last_os_error())
+    } else {
+        Ok((
+            AnonPipe::from_inner(unsafe { Handle::from_raw_handle(read_pipe) }),
+            AnonPipe::from_inner(unsafe { Handle::from_raw_handle(write_pipe) }),
+        ))
+    }
+}
+
 /// Although this looks similar to `anon_pipe` in the Unix module it's actually
 /// subtly different. Here we'll return two pipes in the `Pipes` return value,
 /// but one is intended for "us" where as the other is intended for "someone
@@ -181,7 +198,7 @@ pub fn spawn_pipe_relay(
     their_handle_inheritable: bool,
 ) -> io::Result<AnonPipe> {
     // We need this handle to live for the lifetime of the thread spawned below.
-    let source = source.duplicate()?;
+    let source = source.try_clone()?;
 
     // create a new pair of anon pipes.
     let Pipes { theirs, ours } = anon_pipe(ours_readable, their_handle_inheritable)?;
@@ -237,7 +254,8 @@ impl AnonPipe {
     pub fn into_handle(self) -> Handle {
         self.inner
     }
-    fn duplicate(&self) -> io::Result<Self> {
+
+    pub fn try_clone(&self) -> io::Result<Self> {
         self.inner.duplicate(0, false, c::DUPLICATE_SAME_ACCESS).map(|inner| AnonPipe { inner })
     }