text: /*
   * CDDL HEADER START
   *
   * The contents of this file are subject to the terms of the
   * Common Development and Distribution License (the "License").
   * You may not use this file except in compliance with the License.
   *
   * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   * or http://www.opensolaris.org/os/licensing.
   * See the License for the specific language governing permissions
   * and limitations under the License.
   *
   * When distributing Covered Code, include this CDDL HEADER in each
   * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
   * If applicable, add the following below this CDDL HEADER, with the
   * fields enclosed by brackets "[]" replaced with your own identifying
   * information: Portions Copyright [yyyy] [name of copyright owner]
   *
   * CDDL HEADER END
   */
  
  /*
   * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
   * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
   */
  
  #include <sys/types.h>
  #include <sys/systm.h>
  #include <sys/cred.h>
  #include <sys/errno.h>
  #include <sys/socket.h>
  #include <sys/ksocket.h>
  #include <sys/debug.h>
  #include <sys/kmem.h>
  #include <limits.h>
  #include <unistd.h>
  #include <errno.h>
  #include <umem.h>
  
  #define	_KSOCKET_MAGIC 0xabcdef09
  
  #define	KSOCKET_VALID(ks) (ks->kso_magic == _KSOCKET_MAGIC)
  #define	KSTOSO(ks) (ks->kso_fd)
  
  #ifndef	SS_CLOSING
  #define	SS_CLOSING 0x00010000
  #endif
  
  /*
   * NB: you can't cast this into a sonode like you can with a normal
   * ksocket_t, but no correct code should ever do that anyway.
   * The ksocket_t type is opaque to prevent exactly that.
   */
  struct __ksocket {
  	uint32_t kso_magic;
  	uint32_t kso_count;
  	uint32_t kso_state;
  	int kso_fd;
  	kmutex_t kso_lock;
  	kcondvar_t kso_closing_cv;
  };
  
  static umem_cache_t *ksocket_cache = NULL;
  
  /*ARGSUSED*/
  static int
  _ksocket_ctor(void *buf, void *arg, int flags)
  {
  	ksocket_t sock = buf;
  
  	bzero(sock, sizeof (*sock));
  	mutex_init(&sock->kso_lock, NULL, MUTEX_DEFAULT, NULL);
  	cv_init(&sock->kso_closing_cv, NULL, CV_DEFAULT, NULL);
  	return (0);
  }
  
  /*ARGSUSED*/
  static void
  _ksocket_dtor(void *buf, void *arg)
  {
  	ksocket_t sock = buf;
  
  	mutex_destroy(&sock->kso_lock);
  	cv_destroy(&sock->kso_closing_cv);
  }
  
  #pragma init(_ksocket_init)
  int
  _ksocket_init(void)
  {
  	ksocket_cache = umem_cache_create("ksocket",
  	    sizeof (struct __ksocket), 0,
  	    _ksocket_ctor, _ksocket_dtor, NULL, NULL, NULL, 0);
  	VERIFY(ksocket_cache != NULL);
  	return (0);
  }
  
  #pragma fini(_ksocket_fini)
  int
  _ksocket_fini(void)
  {
  	umem_cache_destroy(ksocket_cache);
  	return (0);
  }
  
  static ksocket_t
  _ksocket_create(int fd)
  {
  	ksocket_t ks;
  
  	ks = umem_cache_alloc(ksocket_cache, 0);
  	VERIFY(ks != NULL);
  	ks->kso_magic = _KSOCKET_MAGIC;
  	ks->kso_count = 1;
  	ks->kso_fd = fd;
  	return (ks);
  }
  
  static void
  _ksocket_destroy(ksocket_t ks)
  {
  	ASSERT(ks->kso_count == 1);
  	umem_cache_free(ksocket_cache, ks);
  }
  
  int
  ksocket_socket(ksocket_t *ksp, int domain, int type, int protocol, int flags,
      struct cred *cr)
  {
  	int fd;
  	ksocket_t ks;
  
  	/* All Solaris components should pass a cred for this operation. */
  	ASSERT(cr != NULL);
  
  	ASSERT(flags == KSOCKET_SLEEP || flags == KSOCKET_NOSLEEP);
  
  	fd = socket(domain, type, protocol);
  	if (fd < 0) {
  		*ksp = NULL;
  		return (errno);
  	}
  
  	ks = _ksocket_create(fd);
  	*ksp = ks;
  	return (0);
  }
  
  /*
   * This is marked NODIRECT so the main program linking with this library
   * can provide its own "bind helper" function.  See: fksmbd_ksock.c
   */
  /* ARGSUSED */
  int
  ksocket_bind_helper(int fd, struct sockaddr *addr, uint_t addrlen)
  {
  	return (EACCES);
  }
  
  int
  ksocket_bind(ksocket_t ks, struct sockaddr *addr, socklen_t addrlen,
      struct cred *cr)
  {
  	int err = 0;
  
  	/* All Solaris components should pass a cred for this operation. */
  	ASSERT(cr != NULL);
  
  	if (!KSOCKET_VALID(ks))
  		return (ENOTSOCK);
  
  	if (bind(KSTOSO(ks), addr, addrlen) != 0)
  		err = errno;
  
  	if (err == EACCES) {
  		err = ksocket_bind_helper(KSTOSO(ks), addr, addrlen);
  	}
  
  	return (err);
  }
  
  int
  ksocket_listen(ksocket_t ks, int backlog, struct cred *cr)
  {
  	/* All Solaris components should pass a cred for this operation. */
  	ASSERT(cr != NULL);
  
  	if (!KSOCKET_VALID(ks))
  		return (ENOTSOCK);
  
  	if (listen(KSTOSO(ks), backlog) != 0)
  		return (errno);
  
  	return (0);
  }
  
  int
  ksocket_accept(ksocket_t ks, struct sockaddr *addr,
      socklen_t *addrlenp, ksocket_t *nks, struct cred *cr)
  {
  	int fd;
  
  	/* All Solaris components should pass a cred for this operation. */
  	ASSERT(cr != NULL);
  
  	*nks = NULL;
  
  	if (!KSOCKET_VALID(ks))
  		return (ENOTSOCK);
  
  	if (addr != NULL && addrlenp == NULL)
  		return (EFAULT);
  
  	fd = accept(KSTOSO(ks), addr, addrlenp);
  	if (fd < 0)
  		return (errno);
  
  	*nks = _ksocket_create(fd);
  
  	return (0);
  }
  
  int
  ksocket_connect(ksocket_t ks, struct sockaddr *addr, socklen_t addrlen,
      struct cred *cr)
  {
  	/* All Solaris components should pass a cred for this operation. */
  	ASSERT(cr != NULL);
  
  	if (!KSOCKET_VALID(ks))
  		return (ENOTSOCK);
  
  	if (connect(KSTOSO(ks), addr, addrlen) != 0)
  		return (errno);
  
  	return (0);
  }
  
  int
  ksocket_send(ksocket_t ks, void *msg, size_t msglen, int flags,
      size_t *sent, struct cred *cr)
  {
  	ssize_t error;
  
  	/* All Solaris components should pass a cred for this operation. */
  	ASSERT(cr != NULL);
  
  	if (!KSOCKET_VALID(ks)) {
  		if (sent != NULL)
  			*sent = 0;
  		return (ENOTSOCK);
  	}
  
  	error = send(KSTOSO(ks), msg, msglen, flags);
  	if (error < 0) {
  		if (sent != NULL)
  			*sent = 0;
  		return (errno);
  	}
  
  	if (sent != NULL)
  		*sent = (size_t)error;
  	return (0);
  }
  
  int
  ksocket_sendto(ksocket_t ks, void *msg, size_t msglen, int flags,
      struct sockaddr *name, socklen_t namelen, size_t *sent, struct cred *cr)
  {
  	ssize_t error;
  
  	/* All Solaris components should pass a cred for this operation. */
  	ASSERT(cr != NULL);
  
  	if (!KSOCKET_VALID(ks)) {
  		if (sent != NULL)
  			*sent = 0;
  		return (ENOTSOCK);
  	}
  
  	error = sendto(KSTOSO(ks), msg, msglen, flags, name, namelen);
  	if (error < 0) {
  		if (sent != NULL)
  			*sent = 0;
  		return (errno);
  	}
  
  	if (sent != NULL)
  		*sent = (size_t)error;
  	return (0);
  }
  
  int
  ksocket_sendmsg(ksocket_t ks, struct nmsghdr *msg, int flags,
      size_t *sent, struct cred *cr)
  {
  	uio_t uio;
  	ssize_t len;
  
  	/* All Solaris components should pass a cred for this operation. */
  	ASSERT(cr != NULL);
  
  	if (!KSOCKET_VALID(ks)) {
  		if (sent != NULL)
  			*sent = 0;
  		return (ENOTSOCK);
  	}
  
  	/* socksyscalls.c uses MSG_MAXIOVLEN (local macro), both are 16 */
  	ASSERT3U(msg->msg_iovlen, <=, IOV_MAX);
  	len = sendmsg(KSTOSO(ks), msg, flags);
  	if (len < 0) {
  		if (sent != NULL)
  			*sent = 0;
  		return (errno);
  	}
  
  	/*
  	 * The user-level sendmsg() does NOT update msg->iov like
  	 * ksocket_sendmsg().  It's unclear whether that's a bug
  	 * or if that was intentional.  Anyway, update it here.
  	 */
  	if (msg->msg_iov != NULL) {
  		bzero(&uio, sizeof (uio));
  		uio.uio_iov = msg->msg_iov;
  		uio.uio_iovcnt = msg->msg_iovlen;
  		uio.uio_resid = len;
  
  		uioskip(&uio, len);
  		ASSERT(uio.uio_resid == 0);
  
  		msg->msg_iov = uio.uio_iov;
  		msg->msg_iovlen = uio.uio_iovcnt;
  	}
  
  	if (sent != NULL)
  		*sent = (size_t)len;
  	return (0);
  }
  
  int
  ksocket_recv(ksocket_t ks, void *msg, size_t msglen, int flags,
      size_t *recvd, struct cred *cr)
  {
  	ssize_t error;
  
  	/* All Solaris components should pass a cred for this operation. */
  	ASSERT(cr != NULL);
  
  	if (!KSOCKET_VALID(ks)) {
  		if (recvd != NULL)
  			*recvd = 0;
  		return (ENOTSOCK);
  	}
  
  	error = recv(KSTOSO(ks), msg, msglen, flags);
  	if (error < 0) {
  		if (recvd != NULL)
  			*recvd = 0;
  		return (errno);
  	}
  
  	if (recvd != NULL)
  		*recvd = (size_t)error;
  	return (0);
  }
  
  int
  ksocket_recvfrom(ksocket_t ks, void *msg, size_t msglen, int flags,
      struct sockaddr *name, socklen_t *namelen, size_t *recvd, struct cred *cr)
  {
  	ssize_t error;
  
  	/* All Solaris components should pass a cred for this operation. */
  	ASSERT(cr != NULL);
  
  	if (!KSOCKET_VALID(ks)) {
  		if (recvd != NULL)
  			*recvd = 0;
  		return (ENOTSOCK);
  	}
  
  	error = recvfrom(KSTOSO(ks), msg, msglen, flags, name, namelen);
  	if (error != 0) {
  		if (recvd != NULL)
  			*recvd = 0;
  		return (errno);
  	}
  
  	if (recvd != NULL)
  		*recvd = (ssize_t)error;
  	return (0);
  }
  
  int
  ksocket_recvmsg(ksocket_t ks, struct nmsghdr *msg, int flags, size_t *recvd,
      struct cred *cr)
  {
  	ssize_t error;
  
  	/* All Solaris components should pass a cred for this operation. */
  	ASSERT(cr != NULL);
  
  	if (!KSOCKET_VALID(ks)) {
  		if (recvd != NULL)
  			*recvd = 0;
  		return (ENOTSOCK);
  	}
  
  	error = recvmsg(KSTOSO(ks), msg, flags);
  	if (error < 0) {
  		if (recvd != NULL)
  			*recvd = 0;
  		return (errno);
  	}
  
  	if (recvd != NULL)
  		*recvd = (size_t)error;
  	return (0);
  }
  
  int
  ksocket_shutdown(ksocket_t ks, int how, struct cred *cr)
  {
  	/* All Solaris components should pass a cred for this operation. */
  	ASSERT(cr != NULL);
  
  	if (!KSOCKET_VALID(ks))
  		return (ENOTSOCK);
  
  	if (shutdown(KSTOSO(ks), how) != 0)
  		return (errno);
  
  	return (0);
  }
  
  int
  ksocket_close(ksocket_t ks, struct cred *cr)
  {
  	int fd;
  
  	/* All Solaris components should pass a cred for this operation. */
  	ASSERT(cr != NULL);
  
  	mutex_enter(&ks->kso_lock);
  
  	if (!KSOCKET_VALID(ks)) {
  		mutex_exit(&ks->kso_lock);
  		return (ENOTSOCK);
  	}
  
  	ks->kso_state |= SS_CLOSING;
  
  	/*
  	 * The real ksocket wakes up everything.
  	 * It seems the only way we can do that
  	 * is to go ahead and close the FD.
  	 */
  	fd = ks->kso_fd;
  	ks->kso_fd = -1;
  	(void) close(fd);
  
  	while (ks->kso_count > 1)
  		cv_wait(&ks->kso_closing_cv, &ks->kso_lock);
  
  	mutex_exit(&ks->kso_lock);
  	_ksocket_destroy(ks);
  
  	return (0);
  }
  
  int
  ksocket_getsockname(ksocket_t ks, struct sockaddr *addr, socklen_t *addrlen,
      struct cred *cr)
  {
  	/* All Solaris components should pass a cred for this operation. */
  	ASSERT(cr != NULL);
  
  	if (!KSOCKET_VALID(ks))
  		return (ENOTSOCK);
  
  	if (addrlen == NULL || (addr == NULL && *addrlen != 0))
  		return (EFAULT);
  
  	if (getsockname(KSTOSO(ks), addr, addrlen) != 0)
  		return (errno);
  
  	return (0);
  }
  
  int
  ksocket_getpeername(ksocket_t ks, struct sockaddr *addr, socklen_t *addrlen,
      struct cred *cr)
  {
  	/* All Solaris components should pass a cred for this operation. */
  	ASSERT(cr != NULL);
  
  	if (!KSOCKET_VALID(ks))
  		return (ENOTSOCK);
  
  	if (addrlen == NULL || (addr == NULL && *addrlen != 0))
  		return (EFAULT);
  
  	if (getpeername(KSTOSO(ks), addr, addrlen) != 0)
  		return (errno);
  
  	return (0);
  }
  
  int
  ksocket_setsockopt(ksocket_t ks, int level, int optname, const void *optval,
      int optlen, struct cred *cr)
  {
  	/* All Solaris components should pass a cred for this operation. */
  	ASSERT(cr != NULL);
  
  	if (!KSOCKET_VALID(ks))
  		return (ENOTSOCK);
  
  	if (optval == NULL)
  		optlen = 0;
  
  	if (setsockopt(KSTOSO(ks), level, optname, optval, optlen) != 0)
  		return (errno);
  
  	return (0);
  }
  
  int
  ksocket_ioctl(ksocket_t ks, int cmd, intptr_t arg, int *rvp, struct cred *cr)
  {
  	int rval;
  
  	/* All Solaris components should pass a cred for this operation. */
  	ASSERT(cr != NULL);
  
  	if (!KSOCKET_VALID(ks))
  		return (ENOTSOCK);
  
  	rval = ioctl(KSTOSO(ks), cmd, arg);
  	if (rvp != NULL)
  		*rvp = rval;
  
  	if (rval != 0)
  		rval = errno;
  
  	return (rval);
  }
  
  void
  ksocket_hold(ksocket_t ks)
  {
  	if (!mutex_owned(&ks->kso_lock)) {
  		mutex_enter(&ks->kso_lock);
  		ks->kso_count++;
  		mutex_exit(&ks->kso_lock);
  	} else
  		ks->kso_count++;
  }
  
  void
  ksocket_rele(ksocket_t ks)
  {
  	/*
  	 * When so_count equals 1 means no thread working on this ksocket
  	 */
  	VERIFY3U(ks->kso_count, >, 1);
  
  	if (!mutex_owned(&ks->kso_lock)) {
  		mutex_enter(&ks->kso_lock);
  		if (--ks->kso_count == 1)
  			cv_signal(&ks->kso_closing_cv);
  		mutex_exit(&ks->kso_lock);
  	} else {
  		if (--ks->kso_count == 1)
  			cv_signal(&ks->kso_closing_cv);
  	}
  }
  