diff --git a/arch/amd64/syscall.c b/arch/amd64/syscall.c index 5202145..bef9920 100644 --- a/arch/amd64/syscall.c +++ b/arch/amd64/syscall.c @@ -89,12 +89,14 @@ void *syscall_table[256] = { [SYSCALL_NRX_MODULE_UNLOAD] = sys_module_unload, // Network -// [SYSCALL_NR_SOCKET] = sys_socket, -// [SYSCALL_NR_SENDTO] = sys_sendto, -// [SYSCALL_NR_RECVFROM] = sys_recvfrom, -// [SYSCALL_NR_BIND] = sys_bind, -// [SYSCALL_NR_SETSOCKOPT] = sys_setsockopt, -// [SYSCALL_NRX_NETCTL] = sys_netctl, + [SYSCALL_NR_SOCKET] = sys_socket, + [SYSCALL_NR_CONNECT] = sys_connect, + [SYSCALL_NR_ACCEPT] = sys_accept, + [SYSCALL_NR_SENDTO] = sys_sendto, + [SYSCALL_NR_RECVFROM] = sys_recvfrom, + [SYSCALL_NR_BIND] = sys_bind, + [SYSCALL_NR_SETSOCKOPT] = sys_setsockopt, + [SYSCALL_NRX_NETCTL] = sys_netctl, // Extension [SYSCALL_NRX_TRACE] = sys_debug_trace, diff --git a/etc/make/conf.mk b/etc/make/conf.mk index ed6677d..e1f0b96 100644 --- a/etc/make/conf.mk +++ b/etc/make/conf.mk @@ -112,8 +112,9 @@ OBJS+=$(O)/net/net.o \ $(O)/net/if.o \ $(O)/net/socket.o \ $(O)/net/util.o \ - $(O)/net/ports.o -#$(O)/sys/sys_net.o + $(O)/net/ports.o \ + $(O)/net/unix.o \ + $(O)/sys/sys_net.o DIRS+=$(O)/net diff --git a/fs/ofile.c b/fs/ofile.c index 37e7995..80daae0 100644 --- a/fs/ofile.c +++ b/fs/ofile.c @@ -29,7 +29,7 @@ void ofile_close(struct vfs_ioctx *ioctx, struct ofile *of) { --of->refcount; if (of->refcount == 0) { if (of->flags & OF_SOCKET) { - //net_close(&thr->ioctx, thr->fds[fd]); + net_close(ioctx, of); } else { vfs_close(ioctx, of); } diff --git a/fs/vfs_ops.c b/fs/vfs_ops.c index d266002..8eb6f64 100644 --- a/fs/vfs_ops.c +++ b/fs/vfs_ops.c @@ -388,6 +388,9 @@ int vfs_mknod(struct vfs_ioctx *ctx, const char *path, mode_t mode, struct vnode case S_IFIFO: type = VN_FIFO; break; + case S_IFSOCK: + type = VN_SOCK; + break; default: panic("mknod: unsupported node type\n"); } diff --git a/include/fs/node.h b/include/fs/node.h index 230bcfa..2c34ba9 100644 --- a/include/fs/node.h +++ b/include/fs/node.h @@ -32,6 +32,7 @@ enum vnode_type { VN_CHR, VN_LNK, VN_FIFO, + VN_SOCK, VN_UNK, VN_MNT, }; diff --git a/include/net/class.h b/include/net/class.h index d143a36..5e0ab59 100644 --- a/include/net/class.h +++ b/include/net/class.h @@ -13,7 +13,9 @@ struct sockops { void (*close) (struct socket *); int (*bind) (struct socket *, struct sockaddr *, size_t); + int (*connect) (struct socket *, struct sockaddr *, size_t); int (*setsockopt) (struct socket *, int, void *, size_t); + int (*accept) (struct socket *, struct socket *); int (*count_pending) (struct socket *); }; diff --git a/include/net/socket.h b/include/net/socket.h index 7ab72c3..97df79a 100644 --- a/include/net/socket.h +++ b/include/net/socket.h @@ -27,7 +27,13 @@ ssize_t net_recvfrom(struct vfs_ioctx *ioctx, size_t len, struct sockaddr *sa, size_t *salen); +int net_accept(struct vfs_ioctx *ioctx, + struct ofile *fd, + struct ofile **fd2, + struct sockaddr *sa, + size_t *salen); int net_bind(struct vfs_ioctx *ioctx, struct ofile *fd, struct sockaddr *sa, size_t len); +int net_connect(struct vfs_ioctx *ioctx, struct ofile *fd, struct sockaddr *sa, size_t len); int net_setsockopt(struct vfs_ioctx *ioctx, struct ofile *fd, int optname, void *optval, size_t optlen); void net_close(struct vfs_ioctx *ioctx, struct ofile *fd); diff --git a/include/sys/sys_net.h b/include/sys/sys_net.h index c9228e8..78ab1d2 100644 --- a/include/sys/sys_net.h +++ b/include/sys/sys_net.h @@ -8,4 +8,6 @@ int sys_socket(int domain, int type, int protocol); ssize_t sys_sendto(int fd, const void *buf, size_t len, struct sockaddr *sa, size_t salen); ssize_t sys_recvfrom(int fd, void *buf, size_t len, struct sockaddr *sa, size_t *salen); int sys_bind(int fd, struct sockaddr *sa, size_t len); +int sys_connect(int fd, struct sockaddr *sa, size_t len); int sys_setsockopt(int fd, int level, int optname, void *optval, size_t len); +int sys_accept(int fd, struct sockaddr *sa, size_t *salen); diff --git a/include/user/errno.h b/include/user/errno.h index 3c48d48..8fd630a 100644 --- a/include/user/errno.h +++ b/include/user/errno.h @@ -38,6 +38,10 @@ #define ENOSYS 38 #define ELOOP 40 +#define EADDRINUSE 98 +#define ECONNRESET 104 +#define ECONNREFUSED 111 + #if defined(__KERNEL__) const char *kstrerror(int e); #endif diff --git a/include/user/socket.h b/include/user/socket.h index b81b66e..624ec0d 100644 --- a/include/user/socket.h +++ b/include/user/socket.h @@ -2,6 +2,10 @@ #include "sys/types.h" /* Protocol families */ +#define PF_LOCAL 1 +#define PF_UNIX PF_LOCAL +#define AF_UNIX PF_UNIX + #define PF_INET 2 #define AF_INET PF_INET diff --git a/include/user/syscall.h b/include/user/syscall.h index f8f3e2a..123c9b9 100644 --- a/include/user/syscall.h +++ b/include/user/syscall.h @@ -53,6 +53,8 @@ #define SYSCALL_NRX_WAITPID 247 #define SYSCALL_NR_SOCKET 41 +#define SYSCALL_NR_CONNECT 42 +#define SYSCALL_NR_ACCEPT 43 #define SYSCALL_NR_SENDTO 44 #define SYSCALL_NR_RECVFROM 45 #define SYSCALL_NR_BIND 49 diff --git a/include/user/un.h b/include/user/un.h new file mode 100644 index 0000000..23d3de0 --- /dev/null +++ b/include/user/un.h @@ -0,0 +1,8 @@ +#pragma once + +#define UNIX_PATH_MAX 108 + +struct sockaddr_un { + uint16_t sun_family; + char sun_path[UNIX_PATH_MAX]; +} __attribute__((packed)); diff --git a/install-headers.sh b/install-headers.sh index 91b1602..b6264b2 100755 --- a/install-headers.sh +++ b/install-headers.sh @@ -21,6 +21,7 @@ KERNEL_HEADERS="include/user/fcntl.h \ include/user/netctl.h \ include/user/socket.h \ include/user/inet.h \ + include/user/un.h \ include/user/mman.h \ include/user/video.h \ include/user/select.h \ diff --git a/net/socket.c b/net/socket.c index 8572126..26dc44f 100644 --- a/net/socket.c +++ b/net/socket.c @@ -97,6 +97,43 @@ ssize_t net_recvfrom(struct vfs_ioctx *ioctx, } } +int net_connect(struct vfs_ioctx *ioctx, struct ofile *fd, struct sockaddr *sa, size_t len) { + _assert(fd); + _assert(fd->flags & OF_SOCKET); + _assert(fd->socket.ioctx == ioctx); + _assert(fd->socket.op); + + if (fd->socket.op->connect) { + return fd->socket.op->connect(&fd->socket, sa, len); + } else { + return -EINVAL; + } +} + +int net_accept(struct vfs_ioctx *ioctx, struct ofile *fd, struct ofile **res, struct sockaddr *sa, size_t *len) { + _assert(fd); + _assert(fd->flags & OF_SOCKET); + _assert(fd->socket.ioctx == ioctx); + _assert(fd->socket.op); + + int ret; + + if (fd->socket.op->accept) { + struct ofile *ofile = ofile_create(); + ofile->flags |= OF_SOCKET; + + if ((ret = fd->socket.op->accept(&fd->socket, &ofile->socket)) != 0) { + return ret; + } + + *res = ofile; + + return 0; + } else { + return -EINVAL; + } +} + int net_bind(struct vfs_ioctx *ioctx, struct ofile *fd, struct sockaddr *sa, size_t len) { _assert(fd); _assert(fd->flags & OF_SOCKET); diff --git a/net/unix.c b/net/unix.c new file mode 100644 index 0000000..eb3e3ff --- /dev/null +++ b/net/unix.c @@ -0,0 +1,372 @@ +#include "arch/amd64/cpu.h" +#include "sys/char/ring.h" +#include "user/socket.h" +#include "user/errno.h" +#include "sys/assert.h" +#include "sys/thread.h" +#include "net/socket.h" +#include "sys/string.h" +#include "sys/debug.h" +#include "user/stat.h" +#include "net/class.h" +#include "sys/heap.h" +#include "sys/attr.h" +#include "user/un.h" +#include "fs/node.h" +#include "fs/vfs.h" + +// TODO: check for possible races +// TODO: SOCK_DGRAM +static int unix_class_supports(int proto) { + return proto == 0; +} + +static int unix_socket_open(struct socket *sock); +static void unix_socket_close(struct socket *sock); +static int unix_socket_bind(struct socket *sock, struct sockaddr *sa, size_t len); +static int unix_socket_connect(struct socket *sock, struct sockaddr *sa, size_t len); +static int unix_socket_accept(struct socket *serv, struct socket *client); +static ssize_t unix_socket_sendto(struct socket *s, + const void *buf, size_t lim, + struct sockaddr *dst, size_t salen); +static ssize_t unix_socket_recvfrom(struct socket *s, + void *buf, size_t lim, + struct sockaddr *dst, size_t *salen); + +static struct sockops unix_socket_ops = { + .open = unix_socket_open, + .close = unix_socket_close, + + .sendto = unix_socket_sendto, + .recvfrom = unix_socket_recvfrom, + + .bind = unix_socket_bind, + .connect = unix_socket_connect, + + .accept = unix_socket_accept, +}; +static struct socket_class unix_socket_class = { + .name = "unix", + .ops = &unix_socket_ops, + .domain = AF_UNIX, + .type = SOCK_STREAM, + .supports = unix_class_supports +}; + +static ssize_t unix_conn_sendto(struct socket *s, + const void *buf, size_t lim, + struct sockaddr *dst, size_t salen); +static ssize_t unix_conn_recvfrom(struct socket *s, + void *buf, size_t lim, + struct sockaddr *dst, size_t *salen); +static void unix_conn_close(struct socket *s); + +static struct sockops unix_conn_ops = { + .sendto = unix_conn_sendto, + .recvfrom = unix_conn_recvfrom, + .close = unix_conn_close +}; + +//// + +enum unix_conn_state { + STATE_NEW = 1, + STATE_PRE_ESTABLISHED, + STATE_ESTABLISHED, + STATE_TERMINATED +}; + +struct unix_socket { + int type; + + // Shared vnode + struct vnode *vnode; + + // If server: + // receive notify when client attempts connections + // If client: + // receive notify when server acknowledges connection + struct io_notify client_notify; + + // If server + size_t remote_count; + + // If server: + // attempt of remote connection + // If client: + // remote side + struct unix_conn *remote; +}; + +struct unix_conn { + struct unix_socket *server; + struct unix_socket *client; + + int state; + + struct ring client_tx; + struct ring server_tx; +}; + +static int unix_socket_open(struct socket *sock) { + struct unix_socket *data = kmalloc(sizeof(struct unix_socket)); + _assert(data); + sock->data = data; + data->type = 0; + data->remote = NULL; + data->remote_count = 0; + thread_wait_io_init(&data->client_notify); + return 0; +} + +static void unix_socket_close(struct socket *sock) { + struct unix_socket *data = sock->data; + _assert(data); + + if (data->type == 1) { + kdebug("Closing with remote_count = %d\n", data->remote_count); + _assert(data->remote_count == 0); + struct vnode *vn = data->vnode; + _assert(vn); + vn->fs_data = NULL; + } else { + // Hangup connection + if (data->remote) { + struct unix_conn *conn = data->remote; + // That's us + _assert(conn->client); + + if (conn->state == STATE_ESTABLISHED) { + conn->state = STATE_TERMINATED; + // Send EOF to remote + _assert(conn->server); + ring_signal(&conn->client_tx, RING_SIGNAL_EOF); + } + conn->client = NULL; + + if (!conn->server) { + kinfo("Client side removes the connection\n"); + memset(conn, 0, sizeof(struct unix_conn)); + kfree(conn); + } + } + } + + kfree(data); +} + +static void unix_conn_close(struct socket *s) { + struct unix_conn *conn = s->data; + _assert(conn); + + // That's us + _assert(conn->server); + + if (conn->state == STATE_ESTABLISHED) { + conn->state = STATE_TERMINATED; + // Send EOF to remote + _assert(conn->client); + ring_signal(&conn->server_tx, RING_SIGNAL_EOF); + conn->server = NULL; + } + + if (!conn->client) { + _assert(conn->server->remote_count > 0); + --conn->server->remote_count; + + kinfo("Server side removes the connection\n"); + memset(conn, 0, sizeof(struct unix_conn)); + kfree(conn); + } +} + +static int unix_socket_bind(struct socket *sock, struct sockaddr *sa, size_t len) { + if (sa->sa_family != AF_UNIX) { + return -EINVAL; + } + struct sockaddr_un *sun = (struct sockaddr_un *) sa; + int res; + + struct unix_socket *data = sock->data; + _assert(data); + + // If socket already exists + if ((res = vfs_find(&thread_self->proc->ioctx, NULL, sun->sun_path, 0, &data->vnode)) == 0) { + data->vnode = NULL; + return -EADDRINUSE; + } + + // Create socket vnode + if ((res = vfs_mknod(&thread_self->proc->ioctx, sun->sun_path, 0777 | S_IFSOCK, &data->vnode)) != 0) { + return res; + } + + // Setup server socket params + data->type = 1; + data->vnode->fs_data = data; + + return 0; +} + +static int unix_socket_connect(struct socket *sock, struct sockaddr *sa, size_t len) { + if (sa->sa_family != AF_UNIX) { + return -EINVAL; + } + struct sockaddr_un *sun = (struct sockaddr_un *) sa; + struct unix_socket *data = sock->data; + int res; + _assert(data); + + if ((res = vfs_find(&thread_self->proc->ioctx, NULL, sun->sun_path, 0, &data->vnode)) != 0) { + return -ENOENT; + } + + if (!data->vnode->fs_data) { + return -ECONNREFUSED; + } + + struct unix_conn *conn = kmalloc(sizeof(struct unix_conn)); + conn->client = data; + conn->server = data->vnode->fs_data; + conn->state = STATE_NEW; + ring_init(&conn->client_tx, 1024); + ring_init(&conn->server_tx, 1024); + + // Notify server of connection + _assert(!conn->server->remote); + conn->server->remote = conn; + thread_notify_io(&conn->server->client_notify); + + // Await server connection ack + while ((conn->state == STATE_NEW)) { + thread_wait_io(thread_self, &data->client_notify); + } + _assert(conn->state == STATE_PRE_ESTABLISHED); + data->remote = conn; + + // Confirm we're ready + conn->state = STATE_ESTABLISHED; + thread_notify_io(&conn->server->client_notify); + + return 0; +} + +static int unix_socket_accept(struct socket *serv, struct socket *client) { + struct unix_socket *data_serv; + struct unix_conn *conn; + data_serv = serv->data; + _assert(data_serv); + + // Wait for incoming connection attempts + _assert(!(data_serv->remote)); + while (!(conn = data_serv->remote)) { + thread_wait_io(thread_self, &data_serv->client_notify); + } + data_serv->remote = NULL; + + // Setup client socket + client->data = conn; + client->op = &unix_conn_ops; + client->ioctx = serv->ioctx; + ++data_serv->remote_count; + + // Acknowledge connection attempt + conn->state = STATE_PRE_ESTABLISHED; + thread_notify_io(&conn->client->client_notify); + + // Acknowledge client ready to prevent server from terminating + // connection too early + while (conn->state == STATE_PRE_ESTABLISHED) { + thread_wait_io(thread_self, &data_serv->client_notify); + } + + return 0; +} + +static ssize_t unix_ring_recv(struct ring *r, void *buf, size_t lim) { + size_t rd = 0; + char c; + size_t rem = lim; + char *wr = buf; + + while (rem) { + if (ring_getc(thread_self, r, &c, 0) < 0) { + break; + } + + *wr++ = c; + ++rd; + --rem; + + if (!ring_readable(r) && (r->flags & RING_SIGNAL_RET)) { + r->flags &= ~RING_SIGNAL_RET; + return rd; + } + } + + return rd; +} + +static ssize_t unix_socket_sendto(struct socket *s, + const void *buf, size_t lim, + struct sockaddr *dst, size_t salen) { + struct unix_socket *data = s->data; + _assert(data); + _assert(data->type == 0); + struct unix_conn *conn = data->remote; + _assert(conn); + if (conn->state != STATE_ESTABLISHED) { + return -ECONNRESET; + } + + ssize_t res = ring_write(thread_self, &conn->client_tx, buf, lim, 1); + ring_signal(&conn->client_tx, RING_SIGNAL_RET); + + return res; +} + +static ssize_t unix_socket_recvfrom(struct socket *s, + void *buf, size_t lim, + struct sockaddr *dst, size_t *salen) { + struct unix_socket *data = s->data; + _assert(data); + _assert(data->type == 0); + struct unix_conn *conn = data->remote; + _assert(conn); + if (conn->state != STATE_ESTABLISHED && !ring_readable(&conn->server_tx)) { + return -ECONNRESET; + } + return unix_ring_recv(&conn->server_tx, buf, lim); +} + +static ssize_t unix_conn_sendto(struct socket *s, + const void *buf, size_t lim, + struct sockaddr *dst, size_t salen) { + struct unix_conn *conn = s->data; + _assert(conn); + if (conn->state != STATE_ESTABLISHED) { + return -ECONNRESET; + } + + ssize_t res = ring_write(thread_self, &conn->server_tx, buf, lim, 1); + ring_signal(&conn->server_tx, RING_SIGNAL_RET); + + return res; +} + +static ssize_t unix_conn_recvfrom(struct socket *s, + void *buf, size_t lim, + struct sockaddr *dst, size_t *salen) { + struct unix_conn *conn = s->data; + _assert(conn); + if (conn->state != STATE_ESTABLISHED && !ring_readable(&conn->client_tx)) { + return -ECONNRESET; + } + return unix_ring_recv(&conn->client_tx, buf, lim); +} + +//// + +static __init void unix_class_register(void) { + socket_class_register(&unix_socket_class); +} diff --git a/sys/sys_net.c b/sys/sys_net.c index fd2d927..d188444 100644 --- a/sys/sys_net.c +++ b/sys/sys_net.c @@ -22,14 +22,13 @@ int sys_netctl(const char *name, uint32_t op, void *arg) { } int sys_socket(int domain, int type, int protocol) { - struct thread *thr = get_cpu()->thread; - _assert(thr); + struct process *proc = thread_self->proc; int fd = -1; int res; // XXX: This should be atomic for (int i = 0; i < THREAD_MAX_FDS; ++i) { - if (!thr->fds[i]) { + if (!proc->fds[i]) { fd = i; break; } @@ -40,25 +39,24 @@ int sys_socket(int domain, int type, int protocol) { struct ofile *ofile = ofile_create(); - if ((res = net_open(&thr->ioctx, ofile, domain, type, protocol)) != 0) { + if ((res = net_open(&proc->ioctx, ofile, domain, type, protocol)) != 0) { ofile_destroy(ofile); return res; } - thr->fds[fd] = ofile; + proc->fds[fd] = ofile_dup(ofile); return fd; } ssize_t sys_sendto(int fd, const void *buf, size_t len, struct sockaddr *sa, size_t salen) { - struct thread *thr = thread_self; + struct process *proc = thread_self->proc; struct ofile *of; - _assert(thr); if (fd < 0 || fd >= THREAD_MAX_FDS) { return -EBADF; } - if ((of = thr->fds[fd]) == NULL) { + if ((of = proc->fds[fd]) == NULL) { return -EBADF; } @@ -67,19 +65,18 @@ ssize_t sys_sendto(int fd, const void *buf, size_t len, struct sockaddr *sa, siz return -EINVAL; } - return net_sendto(&thr->ioctx, of, buf, len, sa, salen); + return net_sendto(&proc->ioctx, of, buf, len, sa, salen); } ssize_t sys_recvfrom(int fd, void *buf, size_t len, struct sockaddr *sa, size_t *salen) { - struct thread *thr = thread_self; + struct process *proc = thread_self->proc; struct ofile *of; - _assert(thr); if (fd < 0 || fd >= THREAD_MAX_FDS) { return -EBADF; } - if ((of = thr->fds[fd]) == NULL) { + if ((of = proc->fds[fd]) == NULL) { return -EBADF; } @@ -88,19 +85,18 @@ ssize_t sys_recvfrom(int fd, void *buf, size_t len, struct sockaddr *sa, size_t return -EINVAL; } - return net_recvfrom(&thr->ioctx, of, buf, len, sa, salen); + return net_recvfrom(&proc->ioctx, of, buf, len, sa, salen); } int sys_bind(int fd, struct sockaddr *sa, size_t salen) { - struct thread *thr = thread_self; + struct process *proc = thread_self->proc; struct ofile *of; - _assert(thr); if (fd < 0 || fd >= THREAD_MAX_FDS) { return -EBADF; } - if ((of = thr->fds[fd]) == NULL) { + if ((of = proc->fds[fd]) == NULL) { return -EBADF; } @@ -109,13 +105,70 @@ int sys_bind(int fd, struct sockaddr *sa, size_t salen) { return -EINVAL; } - return net_bind(&thr->ioctx, of, sa, salen); + return net_bind(&proc->ioctx, of, sa, salen); +} + +int sys_connect(int fd, struct sockaddr *sa, size_t salen) { + struct process *proc = thread_self->proc; + struct ofile *of; + + if (fd < 0 || fd >= THREAD_MAX_FDS) { + return -EBADF; + } + + if ((of = proc->fds[fd]) == NULL) { + return -EBADF; + } + + if (!(of->flags & OF_SOCKET)) { + kwarn("Invalid operation on non-socket\n"); + return -EINVAL; + } + + return net_connect(&proc->ioctx, of, sa, salen); +} + +int sys_accept(int fd, struct sockaddr *sa, size_t *salen) { + struct process *proc = thread_self->proc; + struct ofile *of; + int res, client_fd = -1; + + if (fd < 0 || fd >= THREAD_MAX_FDS) { + return -EBADF; + } + + if ((of = proc->fds[fd]) == NULL) { + return -EBADF; + } + + if (!(of->flags & OF_SOCKET)) { + kwarn("Invalid operation on non-socket\n"); + return -EINVAL; + } + + struct ofile *client_ofile = NULL; + if ((res = net_accept(&proc->ioctx, of, &client_ofile, sa, salen)) != 0) { + return res; + } + _assert(client_ofile); + // XXX: This should be atomic + for (int i = 0; i < THREAD_MAX_FDS; ++i) { + if (!proc->fds[i]) { + client_fd = i; + break; + } + } + if (client_fd == -1) { + return -EMFILE; + } + proc->fds[client_fd] = ofile_dup(client_ofile); + + return client_fd; } int sys_setsockopt(int fd, int level, int optname, void *optval, size_t optlen) { - struct thread *thr = thread_self; + struct process *proc = thread_self->proc; struct ofile *of; - _assert(thr); // XXX: level is ignored (only 1 is used) @@ -123,7 +176,7 @@ int sys_setsockopt(int fd, int level, int optname, void *optval, size_t optlen) return -EBADF; } - if ((of = thr->fds[fd]) == NULL) { + if ((of = proc->fds[fd]) == NULL) { return -EBADF; } @@ -132,5 +185,5 @@ int sys_setsockopt(int fd, int level, int optname, void *optval, size_t optlen) return -EINVAL; } - return net_setsockopt(&thr->ioctx, of, optname, optval, optlen); + return net_setsockopt(&proc->ioctx, of, optname, optval, optlen); }