aboutsummaryrefslogtreecommitdiff
path: root/pflocal/socket.c
diff options
context:
space:
mode:
Diffstat (limited to 'pflocal/socket.c')
-rw-r--r--pflocal/socket.c425
1 files changed, 425 insertions, 0 deletions
diff --git a/pflocal/socket.c b/pflocal/socket.c
new file mode 100644
index 00000000..0bc72066
--- /dev/null
+++ b/pflocal/socket.c
@@ -0,0 +1,425 @@
+/* Socket-specific operations
+
+ Copyright (C) 1995 Free Software Foundation, Inc.
+
+ Written by Miles Bader <miles@gnu.ai.mit.edu>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include <sys/socket.h>
+
+#include <hurd/pipe.h>
+
+#include "sock.h"
+#include "connq.h"
+
+#include "socket_S.h"
+
+/* Connect two sockets */
+error_t
+S_socket_connect2 (struct sock_user *user1, struct sock_user *user2)
+{
+ error_t err;
+
+ if (!user1 || !user2)
+ return EOPNOTSUPP;
+
+ err = sock_connect (user1->sock, user2->sock);
+
+ /* Since USER2 isn't in the receiver position in the rpc, we get a send
+ right for it (although we only use the receive right with the same
+ name); be sure it's deallocated! */
+ mach_port_deallocate (mach_task_self (), user2->pi.port_right);
+
+ return err;
+}
+
+/* Make sure we have a queue to listen on. */
+static error_t
+ensure_connq (struct sock *sock)
+{
+ error_t err = 0;
+ mutex_lock (&sock->lock);
+ if (!sock->listen_queue)
+ err = connq_create (&sock->listen_queue);
+ mutex_unlock (&sock->lock);
+ return err;
+}
+
+/* Prepare a socket of appropriate type for future accept operations. */
+error_t
+S_socket_listen (struct sock_user *user, int queue_limit)
+{
+ error_t err;
+ if (!user)
+ return EOPNOTSUPP;
+ if (queue_limit < 0)
+ return EINVAL;
+ err = ensure_connq (user->sock);
+ if (!err)
+ err = connq_set_length (user->sock->listen_queue, queue_limit);
+ return err;
+}
+
+error_t
+S_socket_connect (struct sock_user *user, struct addr *addr)
+{
+ error_t err;
+ struct sock *peer;
+
+ if (! addr)
+ return ECONNREFUSED;
+
+ /* Deallocate ADDR's send right, which we get as a side effect of the rpc. */
+ mach_port_deallocate (mach_task_self (),
+ ((struct port_info *)addr)->port_right);
+
+ if (! user)
+ return EOPNOTSUPP;
+
+ err = addr_get_sock (addr, &peer);
+ if (!err)
+ {
+ struct sock *sock = user->sock;
+ struct connq *cq = peer->listen_queue;
+
+ if (sock->pipe_class->flags & PIPE_CLASS_CONNECTIONLESS)
+ /* For connectionless protocols, connect() just sets where writes
+ will go, so the destination need not be doing an accept. */
+ err = sock_connect (sock, peer);
+ else if (cq)
+ /* For connection-oriented protocols, only connect with sockets that
+ are actually listening. */
+ {
+ mutex_lock (&sock->lock);
+ if (sock->connect_queue)
+ /* SOCK is already doing a connect. */
+ err = EALREADY;
+ else if (sock->flags & SOCK_CONNECTED)
+ /* SOCK_CONNECTED is only set for connection-oriented sockets,
+ which can only ever connect once. [If we didn't do this test
+ here, it would eventually fail when it the listening socket
+ tried to accept our connection request.] */
+ err = EISCONN;
+ else
+ {
+ /* Assert that we're trying to connect, so anyone else trying
+ to do so will fail with EALREADY. */
+ sock->connect_queue = cq;
+ mutex_unlock (&sock->lock); /* Unlock SOCK while waiting. */
+
+ /* Try to connect. */
+ err = connq_connect (cq, sock->flags & SOCK_NONBLOCK, sock);
+
+ /* We can safely set CONNECT_QUEUE to NULL, as no one else can
+ set it until we've done so. */
+ mutex_lock (&sock->lock);
+ sock->connect_queue = NULL;
+ }
+ mutex_unlock (&sock->lock);
+ }
+ else
+ err = ECONNREFUSED;
+
+ sock_deref (peer);
+ }
+
+ return err;
+}
+
+/* Return a new connection from a socket previously listened. */
+error_t
+S_socket_accept (struct sock_user *user,
+ mach_port_t *port, mach_msg_type_name_t *port_type,
+ mach_port_t *peer_addr_port,
+ mach_msg_type_name_t *peer_addr_port_type)
+{
+ error_t err;
+ struct sock *sock;
+
+ if (!user)
+ return EOPNOTSUPP;
+
+ sock = user->sock;
+
+ err = ensure_connq (sock);
+ if (!err)
+ {
+ struct connq_request *req;
+ struct sock *peer_sock;
+
+ err =
+ connq_listen (sock->listen_queue, sock->flags & SOCK_NONBLOCK,
+ &req, &peer_sock);
+ if (!err)
+ {
+ struct sock *conn_sock;
+
+ err = sock_clone (sock, &conn_sock);
+ if (!err)
+ {
+ err = sock_connect (conn_sock, peer_sock);
+ if (!err)
+ {
+ struct addr *peer_addr;
+ *port_type = MACH_MSG_TYPE_MAKE_SEND;
+ err = sock_create_port (conn_sock, port);
+ if (!err)
+ err = sock_get_addr (peer_sock, &peer_addr);
+ if (!err)
+ {
+ *peer_addr_port = ports_get_right (peer_addr);
+ *peer_addr_port_type = MACH_MSG_TYPE_MAKE_SEND;
+ ports_port_deref (peer_addr);
+ }
+ else
+ /* TEAR DOWN THE CONNECTION XXX */;
+ }
+ if (err)
+ sock_free (conn_sock);
+ }
+
+ /* Communicate any error (or success) to the connecting thread. */
+ connq_request_complete (req, err);
+ }
+ }
+
+ return err;
+}
+
+/* Bind a socket to an address. */
+error_t
+S_socket_bind (struct sock_user *user, struct addr *addr)
+{
+ if (! addr)
+ return EADDRNOTAVAIL;
+
+ /* Deallocate ADDR's send right, which we get as a side effect of the rpc. */
+ mach_port_deallocate (mach_task_self (),
+ ((struct port_info *)addr)->port_right);
+
+ if (! user)
+ return EOPNOTSUPP;
+
+ return sock_bind (user->sock, addr);
+}
+
+/* Shutdown a socket for reading or writing. */
+error_t
+S_socket_shutdown (struct sock_user *user, int what)
+{
+ if (! user)
+ return EOPNOTSUPP;
+ sock_shutdown (user->sock,
+ (what != 1 ? SOCK_SHUTDOWN_READ : 0)
+ | (what != 0 ? SOCK_SHUTDOWN_WRITE : 0));
+ return 0;
+}
+
+/* Find out the name of a socket. */
+error_t
+S_socket_name (struct sock_user *user,
+ mach_port_t *addr_port, mach_msg_type_name_t *addr_port_type)
+{
+ error_t err;
+ struct addr *addr;
+
+ if (!user)
+ return EOPNOTSUPP;
+
+ err = sock_get_addr (user->sock, &addr);
+ if (err)
+ return err;
+
+ *addr_port = ports_get_right (addr);
+ *addr_port_type = MACH_MSG_TYPE_MAKE_SEND;
+
+ return 0;
+}
+
+/* Find out the name of the socket's peer. */
+error_t
+S_socket_peername (struct sock_user *user,
+ mach_port_t *addr_port,
+ mach_msg_type_name_t *addr_port_type)
+{
+ return EOPNOTSUPP; /* XXX */
+ if (!user)
+ return EOPNOTSUPP;
+ *addr_port_type = MACH_MSG_TYPE_MAKE_SEND;
+}
+
+/* Send data over a socket, possibly including Mach ports. */
+error_t
+S_socket_send (struct sock_user *user, struct addr *dest_addr, int flags,
+ char *data, size_t data_len,
+ mach_port_t *ports, size_t num_ports,
+ char *control, size_t control_len,
+ size_t *amount)
+{
+ error_t err = 0;
+ struct pipe *pipe;
+ struct sock *sock, *dest_sock;
+ struct addr *source_addr;
+
+ if (!user)
+ return EOPNOTSUPP;
+
+ sock = user->sock;
+
+ if (flags & MSG_OOB)
+ /* BSD local sockets don't support OOB data. */
+ return EOPNOTSUPP;
+
+ if (dest_addr)
+ {
+ err = addr_get_sock (dest_addr, &dest_sock);
+ if (err)
+ return err;
+ if (sock->pipe_class != dest_sock->pipe_class)
+ /* Sending to a different type of socket! */
+ err = EINVAL; /* ? XXX */
+ }
+ else
+ dest_sock = 0;
+
+ /* We could provide a source address for all writes, but we
+ only do so for connectionless sockets because that's the
+ only place it's required, and it's more efficient not to. */
+ if (!err && sock->pipe_class->flags & PIPE_CLASS_CONNECTIONLESS)
+ err = sock_get_addr (sock, &source_addr);
+ else
+ source_addr = NULL;
+
+ if (!err)
+ {
+ if (dest_sock)
+ /* Grab the destination socket's read pipe directly, and stuff data
+ into it. This is not quite the usage sock_acquire_read_pipe was
+ intended for, but it will work, as the only inappropiate errors
+ occur on a broken pipe, which shouldn't be possible with the sort of
+ sockets with which we can use socket_send... XXXX */
+ err = sock_acquire_read_pipe (dest_sock, &pipe);
+ else
+ /* No address, must be a connected socket... */
+ err = sock_acquire_write_pipe (sock, &pipe);
+
+ if (!err)
+ {
+ err = pipe_send (pipe, sock->flags & SOCK_NONBLOCK,
+ source_addr, data, data_len,
+ control, control_len, ports, num_ports,
+ amount);
+ pipe_release_writer (pipe);
+ }
+
+ if (err)
+ /* The send failed, so free any resources it would have consumed
+ (mig gets rid of memory, but we have to do everything else). */
+ {
+ if (source_addr)
+ ports_port_deref (source_addr);
+ while (num_ports-- > 0)
+ mach_port_deallocate (mach_task_self (), *ports++);
+ }
+ }
+
+ if (dest_sock)
+ sock_deref (dest_sock);
+
+ return err;
+}
+
+/* Receive data from a socket, possibly including Mach ports. */
+error_t
+S_socket_recv (struct sock_user *user,
+ mach_port_t *addr, mach_msg_type_name_t *addr_type,
+ int in_flags,
+ char **data, size_t *data_len,
+ mach_port_t **ports, mach_msg_type_name_t *ports_type,
+ size_t *num_ports,
+ char **control, size_t *control_len,
+ int *out_flags, size_t amount)
+{
+ error_t err;
+ unsigned flags;
+ struct pipe *pipe;
+ void *source_addr = NULL;
+
+ if (!user)
+ return EOPNOTSUPP;
+
+ if (in_flags & MSG_OOB)
+ /* BSD local sockets don't support OOB data. */
+ return EINVAL; /* XXX */
+
+ /* Fill in the pipe FLAGS from any corresponding ones in IN_FLAGS. */
+ flags = 0;
+
+ err = sock_acquire_read_pipe (user->sock, &pipe);
+ if (err == EPIPE)
+ /* EOF */
+ {
+ *data_len = 0;
+ if (num_ports)
+ *num_ports = 0;
+ if (control_len)
+ *control_len = 0;
+ }
+ else if (!err)
+ {
+ err =
+ pipe_recv (pipe, user->sock->flags & SOCK_NONBLOCK, &flags,
+ &source_addr, data, data_len, amount,
+ control, control_len, ports, num_ports);
+ pipe_release_reader (pipe);
+ }
+
+ if (!err)
+ /* Setup mach ports for return. */
+ {
+ *addr_type = MACH_MSG_TYPE_MAKE_SEND;
+ *ports_type = MACH_MSG_TYPE_MAKE_SEND;
+ if (source_addr)
+ {
+ *addr = ports_get_right (source_addr);
+ ports_port_deref (source_addr); /* since get_right has one too. */
+ }
+ else
+ *addr = MACH_PORT_NULL;
+ }
+
+ /* Fill in OUT_FLAGS from from any corresponding ones in FLAGS. */
+ out_flags = 0;
+
+ return err;
+}
+
+/* Stubs for currently unsupported rpcs. */
+
+error_t
+S_socket_getopt (struct sock_user *user,
+ int level, int opt,
+ char **value, size_t *value_len)
+{
+ return EOPNOTSUPP;
+}
+
+error_t
+S_socket_setopt (struct sock_user *user,
+ int level, int opt, char *value, size_t value_len)
+{
+ return EOPNOTSUPP;
+}