The branch, master has been updated
       via  bb4dd1f src: Handle stale fds in dup() and dup2().
       via  9c03935 src: Add timerfd_create() to handle stale fds.
       via  f850820 src: Add eventfd() to handle stale fds.
       via  dc00232 src: Add signalfd() to handle stale fds.
       via  95520f6 src: Add socketpair() to handle stale fds.
       via  ea45102 src: Add pipe() to handle stale fds.
       via  0627527 src: Check for stale fds in swrap_accept().
       via  5acfcfe src: Check for stale fds in swrap_socket().
       via  05cb607 src: Handle stale fds in swrap_recvmsg_after().
       via  3ab00f5 src: Handle stale fds in swrap_sendmsg_after().
       via  de94be7 src: Try to recover when reading from a fd returns ENOTSOCK.
       via  27b2055 src: Try to recover when writing to fd returns ENOTSOCK.
       via  1462a69 src: Try to recover when sockets are closed elsewhere.
       via  019e4f3 src: Use swrap_recvmsg_(before|after) for swrap_readv().
       via  e200ed8 Update TODO.
       via  553d8a2 Update ChangeLog.
      from  bede01d Update TODO.

http://gitweb.samba.org/?p=socket_wrapper.git;a=shortlog;h=master


- Log -----------------------------------------------------------------
commit bb4dd1f1733fe80493e3f165f206df675fb32beb
Author: Andreas Schneider <a...@samba.org>
Date:   Tue Jan 28 14:10:53 2014 +0100

    src: Handle stale fds in dup() and dup2().
    
    Reviewed-by: Stefan Metzmacher <me...@samba.org>

commit 9c03935fc0abb033a32683f5e9d8bd6b2136b8fd
Author: Andreas Schneider <a...@samba.org>
Date:   Tue Jan 28 13:48:52 2014 +0100

    src: Add timerfd_create() to handle stale fds.
    
    Reviewed-by: Stefan Metzmacher <me...@samba.org>

commit f8508200e90048523fc7c78467dc8e565744f7f9
Author: Andreas Schneider <a...@samba.org>
Date:   Tue Jan 28 13:42:38 2014 +0100

    src: Add eventfd() to handle stale fds.
    
    Reviewed-by: Stefan Metzmacher <me...@samba.org>

commit dc0023293292d5247a5984e78fc10de9399da5f5
Author: Andreas Schneider <a...@samba.org>
Date:   Tue Jan 28 13:33:23 2014 +0100

    src: Add signalfd() to handle stale fds.
    
    Reviewed-by: Stefan Metzmacher <me...@samba.org>

commit 95520f672f3cbf311b503d61e2ac9e2a22d209e9
Author: Andreas Schneider <a...@samba.org>
Date:   Tue Jan 28 13:20:20 2014 +0100

    src: Add socketpair() to handle stale fds.
    
    Reviewed-by: Stefan Metzmacher <me...@samba.org>

commit ea45102993c0800a8a62137da09dbb2787834ed4
Author: Andreas Schneider <a...@samba.org>
Date:   Tue Jan 28 13:15:34 2014 +0100

    src: Add pipe() to handle stale fds.
    
    Reviewed-by: Stefan Metzmacher <me...@samba.org>

commit 0627527da3abbafdb6f171dbdbc3b719a96a2b62
Author: Andreas Schneider <a...@samba.org>
Date:   Tue Jan 28 13:10:01 2014 +0100

    src: Check for stale fds in swrap_accept().
    
    Reviewed-by: Stefan Metzmacher <me...@samba.org>

commit 5acfcfea50ce2dd1b0388c33c849ce19007cf77b
Author: Andreas Schneider <a...@samba.org>
Date:   Tue Jan 28 10:33:36 2014 +0100

    src: Check for stale fds in swrap_socket().
    
    Reviewed-by: Stefan Metzmacher <me...@samba.org>

commit 05cb607f08321b1680e51cb3bb33222922f46401
Author: Andreas Schneider <a...@samba.org>
Date:   Tue Jan 28 11:34:03 2014 +0100

    src: Handle stale fds in swrap_recvmsg_after().
    
    Reviewed-by: Stefan Metzmacher <me...@samba.org>

commit 3ab00f5deaefa2dbdac9ba71841e178a78988dd2
Author: Andreas Schneider <a...@samba.org>
Date:   Tue Jan 28 11:30:20 2014 +0100

    src: Handle stale fds in swrap_sendmsg_after().
    
    Reviewed-by: Stefan Metzmacher <me...@samba.org>

commit de94be7e4b83283f3b46320706fef19b8203b52c
Author: Nalin Dahyabhai <na...@dahyabhai.net>
Date:   Tue Jan 28 11:25:40 2014 +0100

    src: Try to recover when reading from a fd returns ENOTSOCK.
    
    When attempting to read from a descriptor, if an underlying
    autobind fails because it's not a socket, stop intercepting uses of that
    descriptor.
    
    Reviewed-by: Andreas Schneider <a...@samba.org>
    Reviewed-by: Stefan Metzmacher <me...@samba.org>

commit 27b2055291caf67c6a23e829f596cc6751ce5aad
Author: Nalin Dahyabhai <na...@dahyabhai.net>
Date:   Tue Jan 28 11:24:27 2014 +0100

    src: Try to recover when writing to fd returns ENOTSOCK.
    
    When attempting to write to a descriptor, if an underlying autobind
    fails because it's not a socket, stop intercepting uses of that
    descriptor.
    
    Reviewed-by: Andreas Schneider <a...@samba.org>
    Reviewed-by: Stefan Metzmacher <me...@samba.org>

commit 1462a69c0a96605081f9f1c0dc5bb54aa98aa202
Author: Nalin Dahyabhai <na...@dahyabhai.net>
Date:   Tue Jan 28 11:22:26 2014 +0100

    src: Try to recover when sockets are closed elsewhere.
    
    There are methods for closing descriptors (libc-internal code paths,
    direct syscalls) which close descriptors in ways that we
    can't intercept, so try to recover when we notice that that's happened:
      * If we see a descriptor being handed back from open() that we thought
        was a socket, stop intercepting uses of that descriptor.
    
    Reviewed-by: Andreas Schneider <a...@samba.org>
    Reviewed-by: Stefan Metzmacher <me...@samba.org>

commit 019e4f3bb6e5589ad8fe0ca0c07b197cb8cbb677
Author: Andreas Schneider <a...@samba.org>
Date:   Tue Jan 28 10:06:18 2014 +0100

    src: Use swrap_recvmsg_(before|after) for swrap_readv().
    
    Reviewed-by: Stefan Metzmacher <me...@samba.org>

commit e200ed88dbf6c9969daee83f9fcdca2c5bd1c0e7
Author: Andreas Schneider <a...@samba.org>
Date:   Tue Jan 28 10:14:08 2014 +0100

    Update TODO.
    
    Reviewed-by: Stefan Metzmacher <me...@samba.org>

commit 553d8a25f86db7935617fdf4326ac9859d5ebb31
Author: Andreas Schneider <a...@samba.org>
Date:   Tue Jan 28 10:14:17 2014 +0100

    Update ChangeLog.
    
    Reviewed-by: Stefan Metzmacher <me...@samba.org>

-----------------------------------------------------------------------

Summary of changes:
 ChangeLog             |    5 +
 ConfigureChecks.cmake |   13 +-
 TODO                  |    3 +
 config.h.cmake        |    7 +
 src/socket_wrapper.c  |  448 +++++++++++++++++++++++++++++++++++++++---------
 5 files changed, 385 insertions(+), 91 deletions(-)


Changeset truncated at 500 lines:

diff --git a/ChangeLog b/ChangeLog
index e69de29..d2f5d4e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -0,0 +1,5 @@
+ChangeLog
+==========
+
+version 1.0.0 (released 2014-02-02)
+  * Initial release
diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake
index 2d8e690..c05cbb9 100644
--- a/ConfigureChecks.cmake
+++ b/ConfigureChecks.cmake
@@ -48,18 +48,17 @@ endif(CMAKE_COMPILER_IS_GNUCC AND NOT MINGW AND NOT OS2)
 
 # HEADERS
 check_include_file(sys/filio.h HAVE_SYS_FILIO_H)
+check_include_file(sys/signalfd.h HAVE_SYS_SIGNALFD_H)
+check_include_file(sys/eventfd.h HAVE_SYS_EVENTFD_H)
+check_include_file(sys/timerfd.h HAVE_SYS_TIMERFD_H)
 
 # FUNCTIONS
 check_function_exists(strncpy HAVE_STRNCPY)
 check_function_exists(vsnprintf HAVE_VSNPRINTF)
 check_function_exists(snprintf HAVE_SNPRINTF)
-
-if (WIN32)
-    check_function_exists(_vsnprintf_s HAVE__VSNPRINTF_S)
-    check_function_exists(_vsnprintf HAVE__VSNPRINTF)
-    check_function_exists(_snprintf HAVE__SNPRINTF)
-    check_function_exists(_snprintf_s HAVE__SNPRINTF_S)
-endif (WIN32)
+check_function_exists(signalfd HAVE_SIGNALFD)
+check_function_exists(eventfd HAVE_EVENTFD)
+check_function_exists(timerfd_create HAVE_TIMERFD_CREATE)
 
 if (UNIX)
     if (NOT LINUX)
diff --git a/TODO b/TODO
index 2ce46ff..205cfaf 100644
--- a/TODO
+++ b/TODO
@@ -10,6 +10,8 @@ Library:
   We accept a connection from a client and need to pass the fd to another
   child we forked. socket_wrapper then needs to send the 'struct socket_info'
   to the child first and set it up there.
+  Or do it like swrap_accept() and call getpeername() and getsockname().
+* Add support for threading.
 
 Testing:
 ---------
@@ -17,3 +19,4 @@ Testing:
 * Add a test for sento() to broadcast 255.255.255.255.
 * Add a test to check that read/readv/send/ only work on connected sockets.
 * Add unit tests for conversion functions like convert_in_un_remote().
+* Add threaded tests.
diff --git a/config.h.cmake b/config.h.cmake
index 63bbdb4..abbf133 100644
--- a/config.h.cmake
+++ b/config.h.cmake
@@ -15,6 +15,9 @@
 /************************** HEADER FILES *************************/
 
 #cmakedefine HAVE_SYS_FILIO_H 1
+#cmakedefine HAVE_SYS_SIGNALFD_H 1
+#cmakedefine HAVE_SYS_EVENTFD_H 1
+#cmakedefine HAVE_SYS_TIMERFD_H 1
 
 /************************ STRUCT MEMBERS *************************/
 
@@ -25,6 +28,10 @@
 
 /* Define to 1 if you have the `getaddrinfo' function. */
 #cmakedefine HAVE_GETADDRINFO 1
+#cmakedefine HAVE_SIGNALFD 1
+#cmakedefine HAVE_EVENTFD 1
+#cmakedefine HAVE_TIMERFD_CREATE 1
+
 #cmakedefine HAVE_ACCEPT_PSOCKLEN_T 1
 #cmakedefine HAVE_IOCTL_INT 1
 
diff --git a/src/socket_wrapper.c b/src/socket_wrapper.c
index 2ea35a9..353a9a5 100644
--- a/src/socket_wrapper.c
+++ b/src/socket_wrapper.c
@@ -50,6 +50,15 @@
 #ifdef HAVE_SYS_FILIO_H
 #include <sys/filio.h>
 #endif
+#ifdef HAVE_SYS_SIGNALFD_H
+#include <sys/signalfd.h>
+#endif
+#ifdef HAVE_SYS_EVENTFD_H
+#include <sys/eventfd.h>
+#endif
+#ifdef HAVE_SYS_TIMERFD_H
+#include <sys/timerfd.h>
+#endif
 #include <sys/uio.h>
 #include <errno.h>
 #include <sys/un.h>
@@ -283,6 +292,9 @@ struct swrap_libc_fns {
                            socklen_t addrlen);
        int (*libc_dup)(int fd);
        int (*libc_dup2)(int oldfd, int newfd);
+#ifdef HAVE_EVENTFD
+       int (*libc_eventfd)(int count, int flags);
+#endif
        int (*libc_getpeername)(int sockfd,
                                struct sockaddr *addr,
                                socklen_t *addrlen);
@@ -296,6 +308,8 @@ struct swrap_libc_fns {
                               socklen_t *optlen);
        int (*libc_ioctl)(int d, unsigned long int request, ...);
        int (*libc_listen)(int sockfd, int backlog);
+       int (*libc_open)(const char *pathname, int flags, mode_t mode);
+       int (*libc_pipe)(int pipefd[2]);
        int (*libc_read)(int fd, void *buf, size_t count);
        ssize_t (*libc_readv)(int fd, const struct iovec *iov, int iovcnt);
        int (*libc_recv)(int sockfd, void *buf, size_t len, int flags);
@@ -319,7 +333,14 @@ struct swrap_libc_fns {
                               int optname,
                               const void *optval,
                               socklen_t optlen);
+#ifdef HAVE_SIGNALFD
+       int (*libc_signalfd)(int fd, const sigset_t *mask, int flags);
+#endif
        int (*libc_socket)(int domain, int type, int protocol);
+       int (*libc_socketpair)(int domain, int type, int protocol, int sv[2]);
+#ifdef HAVE_TIMERFD_CREATE
+       int (*libc_timerfd_create)(int clockid, int flags);
+#endif
        ssize_t (*libc_writev)(int fd, const struct iovec *iov, int iovcnt);
 };
 
@@ -505,6 +526,15 @@ static int libc_dup2(int oldfd, int newfd)
        return swrap.fns.libc_dup2(oldfd, newfd);
 }
 
+#ifdef HAVE_EVENTFD
+static int libc_eventfd(int count, int flags)
+{
+       swrap_load_lib_function(SWRAP_LIBC, eventfd);
+
+       return swrap.fns.libc_eventfd(count, flags);
+}
+#endif
+
 static int libc_getpeername(int sockfd,
                            struct sockaddr *addr,
                            socklen_t *addrlen)
@@ -563,6 +593,20 @@ static int libc_listen(int sockfd, int backlog)
        return swrap.fns.libc_listen(sockfd, backlog);
 }
 
+static int libc_open(const char *pathname, int flags, mode_t mode)
+{
+       swrap_load_lib_function(SWRAP_LIBC, open);
+
+       return swrap.fns.libc_open(pathname, flags, mode);
+}
+
+static int libc_pipe(int pipefd[2])
+{
+       swrap_load_lib_function(SWRAP_LIBSOCKET, pipe);
+
+       return swrap.fns.libc_pipe(pipefd);
+}
+
 static int libc_read(int fd, void *buf, size_t count)
 {
        swrap_load_lib_function(SWRAP_LIBC, read);
@@ -640,6 +684,15 @@ static int libc_setsockopt(int sockfd,
        return swrap.fns.libc_setsockopt(sockfd, level, optname, optval, 
optlen);
 }
 
+#ifdef HAVE_SIGNALFD
+static int libc_signalfd(int fd, const sigset_t *mask, int flags)
+{
+       swrap_load_lib_function(SWRAP_LIBSOCKET, signalfd);
+
+       return swrap.fns.libc_signalfd(fd, mask, flags);
+}
+#endif
+
 static int libc_socket(int domain, int type, int protocol)
 {
        swrap_load_lib_function(SWRAP_LIBSOCKET, socket);
@@ -647,6 +700,22 @@ static int libc_socket(int domain, int type, int protocol)
        return swrap.fns.libc_socket(domain, type, protocol);
 }
 
+static int libc_socketpair(int domain, int type, int protocol, int sv[2])
+{
+       swrap_load_lib_function(SWRAP_LIBSOCKET, socketpair);
+
+       return swrap.fns.libc_socketpair(domain, type, protocol, sv);
+}
+
+#ifdef HAVE_TIMERFD_CREATE
+static int libc_timerfd_create(int clockid, int flags)
+{
+       swrap_load_lib_function(SWRAP_LIBC, timerfd_create);
+
+       return swrap.fns.libc_timerfd_create(clockid, flags);
+}
+#endif
+
 static ssize_t libc_writev(int fd, const struct iovec *iov, int iovcnt)
 {
        swrap_load_lib_function(SWRAP_LIBSOCKET, writev);
@@ -1095,6 +1164,27 @@ static struct socket_info *find_socket_info(int fd)
        return NULL;
 }
 
+static void swrap_remove_stale(int fd)
+{
+       struct socket_info *si = find_socket_info(fd);
+       struct socket_info_fd *fi;
+
+       if (si != NULL) {
+               for (fi = si->fds; fi; fi = fi->next) {
+                       if (fi->fd == fd) {
+                               SWRAP_LOG(SWRAP_LOG_TRACE, "remove stale 
wrapper for %d", fd);
+                               SWRAP_DLIST_REMOVE(si->fds, fi);
+                               free(fi);
+                               break;
+                       }
+               }
+
+               if (si->fds == NULL) {
+                       SWRAP_DLIST_REMOVE(sockets, si);
+               }
+       }
+}
+
 static int sockaddr_convert_to_un(struct socket_info *si,
                                  const struct sockaddr *in_addr,
                                  socklen_t in_len,
@@ -1921,6 +2011,29 @@ static void swrap_dump_packet(struct socket_info *si,
 }
 
 /****************************************************************************
+ *   SIGNALFD
+ ***************************************************************************/
+
+#ifdef HAVE_SIGNALFD
+static int swrap_signalfd(int fd, const sigset_t *mask, int flags)
+{
+       int rc;
+
+       rc = libc_signalfd(fd, mask, flags);
+       if (rc != -1) {
+               swrap_remove_stale(fd);
+       }
+
+       return rc;
+}
+
+int signalfd(int fd, const sigset_t *mask, int flags)
+{
+       return swrap_signalfd(fd, mask, flags);
+}
+#endif
+
+/****************************************************************************
  *   SOCKET
  ***************************************************************************/
 
@@ -1994,7 +2107,15 @@ static int swrap_socket(int family, int type, int 
protocol)
         */
        fd = libc_socket(AF_UNIX, type, 0);
 
-       if (fd == -1) return -1;
+       if (fd == -1) {
+               return -1;
+       }
+
+       /* Check if we have a stale fd and remove it */
+       si = find_socket_info(fd);
+       if (si != NULL) {
+               swrap_remove_stale(fd);
+       }
 
        si = (struct socket_info *)malloc(sizeof(struct socket_info));
        memset(si, 0, sizeof(struct socket_info));
@@ -2031,6 +2152,73 @@ int socket(int family, int type, int protocol)
 }
 
 /****************************************************************************
+ *   SOCKETPAIR
+ ***************************************************************************/
+
+static int swrap_socketpair(int family, int type, int protocol, int sv[2])
+{
+       int rc;
+
+       rc = libc_socketpair(family, type, protocol, sv);
+       if (rc != -1) {
+               swrap_remove_stale(sv[0]);
+               swrap_remove_stale(sv[1]);
+       }
+
+       return rc;
+}
+
+int socketpair(int family, int type, int protocol, int sv[2])
+{
+       return swrap_socketpair(family, type, protocol, sv);
+}
+
+/****************************************************************************
+ *   SOCKETPAIR
+ ***************************************************************************/
+
+#ifdef HAVE_TIMERFD_CREATE
+static int swrap_timerfd_create(int clockid, int flags)
+{
+       int fd;
+
+       fd = libc_timerfd_create(clockid, flags);
+       if (fd != -1) {
+               swrap_remove_stale(fd);
+       }
+
+       return fd;
+}
+
+int timerfd_create(int clockid, int flags)
+{
+       return swrap_timerfd_create(clockid, flags);
+}
+#endif
+
+/****************************************************************************
+ *   PIPE
+ ***************************************************************************/
+
+static int swrap_pipe(int pipefd[2])
+{
+       int rc;
+
+       rc = libc_pipe(pipefd);
+       if (rc != -1) {
+               swrap_remove_stale(pipefd[0]);
+               swrap_remove_stale(pipefd[1]);
+       }
+
+       return rc;
+}
+
+int pipe(int pipefd[2])
+{
+       return swrap_pipe(pipefd);
+}
+
+/****************************************************************************
  *   ACCEPT
  ***************************************************************************/
 
@@ -2072,6 +2260,10 @@ static int swrap_accept(int s, struct sockaddr *addr, 
socklen_t *addrlen)
 
        ret = libc_accept(s, (struct sockaddr *)(void *)&un_addr, &un_addrlen);
        if (ret == -1) {
+               if (errno == ENOTSOCK) {
+                       /* Remove stale fds */
+                       swrap_remove_stale(s);
+               }
                free(my_addr);
                return ret;
        }
@@ -2435,6 +2627,38 @@ int listen(int s, int backlog)
 }
 
 /****************************************************************************
+ *   OPEN
+ ***************************************************************************/
+
+static int swrap_open(const char *pathname, int flags, mode_t mode)
+{
+       int ret;
+
+       ret = libc_open(pathname, flags, mode);
+       if (ret != -1) {
+               /*
+                * There are methods for closing descriptors (libc-internal code
+                * paths, direct syscalls) which close descriptors in ways that
+                * we can't intercept, so try to recover when we notice that
+                * that's happened
+                */
+               swrap_remove_stale(ret);
+       }
+       return ret;
+}
+
+int open(const char *pathname, int flags, ...)
+{
+       mode_t mode;
+       va_list ap;
+
+       va_start(ap, flags);
+       mode = va_arg(ap, mode_t);
+       va_end(ap);
+       return swrap_open(pathname, flags, mode);
+}
+
+/****************************************************************************
  *   GETPEERNAME
  ***************************************************************************/
 
@@ -2711,7 +2935,15 @@ static ssize_t swrap_sendmsg_before(int fd,
 
                if (si->bound == 0) {
                        ret = swrap_auto_bind(fd, si, si->family);
-                       if (ret == -1) return -1;
+                       if (ret == -1) {
+                               if (errno == ENOTSOCK) {
+                                       swrap_remove_stale(fd);
+                                       return -ENOTSOCK;
+                               } else {
+                                       SWRAP_LOG(SWRAP_LOG_ERROR, 
"swrap_sendmsg_before failed");
+                                       return -1;
+                               }
+                       }
                }
 
                if (!si->defer_connect) {
@@ -2745,7 +2977,8 @@ static ssize_t swrap_sendmsg_before(int fd,
        return 0;
 }
 
-static void swrap_sendmsg_after(struct socket_info *si,
+static void swrap_sendmsg_after(int fd,
+                               struct socket_info *si,
                                struct msghdr *msg,
                                const struct sockaddr *to,
                                ssize_t ret)
@@ -2758,8 +2991,13 @@ static void swrap_sendmsg_after(struct socket_info *si,
        size_t remain;
 
        /* to give better errors */
-       if (ret == -1 && saved_errno == ENOENT) {
-               saved_errno = EHOSTUNREACH;
+       if (ret == -1) {
+               if (saved_errno == ENOENT) {
+                       saved_errno = EHOSTUNREACH;
+               } else if (saved_errno == ENOTSOCK) {
+                       /* If the fd is not a socket, remove it */
+                       swrap_remove_stale(fd);
+               }
        }
 
        for (i = 0; i < (size_t)msg->msg_iovlen; i++) {
@@ -2867,7 +3105,20 @@ static int swrap_recvmsg_before(int fd,
                if (si->bound == 0) {
                        ret = swrap_auto_bind(fd, si, si->family);
                        if (ret == -1) {
-                               return -1;
+                               /*
+                                * When attempting to read or write to a
+                                * descriptor, if an underlying autobind fails
+                                * because it's not a socket, stop intercepting
+                                * uses of that descriptor.
+                                */
+                               if (errno == ENOTSOCK) {
+                                       swrap_remove_stale(fd);
+                                       return -ENOTSOCK;
+                               } else {
+                                       SWRAP_LOG(SWRAP_LOG_ERROR,
+                                                 "swrap_recvmsg_before 
failed");
+                                       return -1;
+                               }
                        }
                }
                break;
@@ -2879,7 +3130,8 @@ static int swrap_recvmsg_before(int fd,
        return 0;
 }
 
-static int swrap_recvmsg_after(struct socket_info *si,
+static int swrap_recvmsg_after(int fd,
+                              struct socket_info *si,
                               struct msghdr *msg,
                               const struct sockaddr_un *un_addr,
                               socklen_t un_addrlen,
@@ -2893,8 +3145,13 @@ static int swrap_recvmsg_after(struct socket_info *si,
        size_t remain;
 
        /* to give better errors */
-       if (ret == -1 && saved_errno == ENOENT) {
-               saved_errno = EHOSTUNREACH;
+       if (ret == -1) {
+               if (saved_errno == ENOENT) {
+                       saved_errno = EHOSTUNREACH;
+               } else if (saved_errno == ENOTSOCK) {
+                       /* If the fd is not a socket, remove it */
+                       swrap_remove_stale(fd);
+               }
        }


-- 
Socket Wrapper Repository

Reply via email to