setsockopt was `return 0;` and getsockopt wrote a zero for whatever was asked. The comment said it was so a server setting SO_REUSEADDR and treating failure as fatal would not fail. That trade is the wrong way round: a caller told "no" adapts, a caller told "yes" changes its behaviour on the strength of it -- sets SO_RCVTIMEO and then blocks forever in a read it believes will time out, sets TCP_NODELAY and reports latency numbers that measure Nagle. The failure is silent, remote from the call, and indistinguishable from a bug in the stack. Six options are honoured now and everything else is ENOPROTOOPT. SO_REUSEADDR, SO_KEEPALIVE, SO_BROADCAST and TCP_NODELAY reach lwIP's pcb; SO_SNDBUF/SO_RCVBUF are accepted and report the real size on read. SO_REUSEADDR is implemented rather than refused precisely because of the original comment's concern -- it is the one option a server really does set and really does check. accept4 becomes its own table entry rather than an alias for accept. It has to be: accept takes three arguments, so a shared handler reading a fourth register reads whatever the caller last left there and invents a SOCK_NONBLOCK out of stack litter. SOCK_CLOEXEC and SOCK_NONBLOCK are now honoured by socket(), socketpair() and accept4() -- a dropped CLOEXEC leaks a listening socket across every exec, and a dropped NONBLOCK has the caller block forever in what it believes is a poll loop. ICMP ping sockets: socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP), on lwIP's raw API, with the callback stripping the variable-length IP header. Only echo replies are eaten, and that restriction is load-bearing -- raw_input runs before lwIP's own icmp_input, so consuming every ICMP message would swallow the echo *requests* and this machine would silently stop answering pings from outside. Loopback, so 127.0.0.1 is a real destination. LWIP_NETIF_LOOPBACK is the half that is easy to miss: without it a packet addressed to an interface's own address goes out of the wire and never comes back. With NO_SYS=1 nothing drains the loopback queue, so the network thread calls netif_poll_all(). cmd/ping is first-party rather than toybox's, and the reason is a limitation not a preference: toybox reads replies with recvmsg(2) and wants the TTL through an IP_RECVTTL control message, and SchOS has neither. recvmsg is item 1 of docs/plan/foundations.md, where it is wanted for SCM_RIGHTS anyway, and should not be rushed to make one applet run. Two bugs found on the way. Every AF_INET socket failed with ENFILE until DHCP bound, because inet_new() demanded an *address* before handing out a socket and reported it as "file table overflow"; net_is_ready() (lwIP up) is now distinct from net_is_up() (DHCP bound). And that bug was hiding a bad test: socktest asserted "AF_INET is refused, not faked", true when written, false since AF_INET landed, and green the whole time because the suite runs before DHCP binds and the ENFILE made the refusal look real. Fixing the kernel is what turned it red. Verified: ping to 127.0.0.1, localhost, the gateway, 1.1.1.1 and 9.9.9.9, all 0% loss; socktest ALL CHECKS PASSED with ten new checks, including a setsockopt round trip and an unknown option that must be refused -- a permissive stub cannot pass both at once. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
578 lines
17 KiB
C
578 lines
17 KiB
C
// Socket syscalls.
|
|
//
|
|
// The kernel side is in kernel/net/unix.c; this file is only the translation
|
|
// between a syscall's user-supplied `struct sockaddr` and the path string the
|
|
// socket layer actually wants, plus the file-descriptor bookkeeping.
|
|
//
|
|
// Two families now. AF_UNIX is answered by kernel/net/unix.c, AF_INET by
|
|
// kernel/net/inet.c on top of lwIP; the socket layer dispatches on the domain
|
|
// the socket was created with, so this file's only extra job is to read the
|
|
// right shape of sockaddr out of user memory. AF_INET6 is still refused with
|
|
// EAFNOSUPPORT rather than EINVAL, because a program that probes for IPv6 and
|
|
// falls back to IPv4 needs to tell "not here" from "you passed nonsense".
|
|
|
|
#include "types.h"
|
|
#include "defs.h"
|
|
#include "param.h"
|
|
#include "mmu.h"
|
|
#include "proc.h"
|
|
#include "fs.h"
|
|
#include "spinlock.h"
|
|
#include "sleeplock.h"
|
|
#include "file.h"
|
|
#include "posix.h"
|
|
#include "syscall.h"
|
|
#include "uaccess.h"
|
|
#include "errno.h"
|
|
#include "socket.h"
|
|
|
|
// Pull the sun_path out of a user `struct sockaddr_un`, NUL-terminated.
|
|
//
|
|
// The length the caller gives is checked rather than trusted: musl passes
|
|
// `offsetof(sockaddr_un, sun_path) + strlen(path) + 1`, which is shorter than
|
|
// the full struct, so reading sizeof(struct sockaddr_un) from user memory
|
|
// would fault on a path that sits near the end of a page.
|
|
static int
|
|
fetch_sun_path(addr_t uaddr, int ulen, char *out)
|
|
{
|
|
struct sockaddr_un sa;
|
|
int plen;
|
|
|
|
if (ulen < (int)sizeof(sa.sun_family) || ulen > (int)sizeof(sa))
|
|
return -EINVAL;
|
|
|
|
memset(&sa, 0, sizeof(sa));
|
|
if (copy_from_user(&sa, uaddr, (uint64)ulen) < 0)
|
|
return -EFAULT;
|
|
|
|
if (sa.sun_family == AF_INET)
|
|
return -EAFNOSUPPORT;
|
|
if (sa.sun_family != AF_UNIX)
|
|
return -EAFNOSUPPORT;
|
|
|
|
plen = ulen - (int)sizeof(sa.sun_family);
|
|
if (plen <= 0)
|
|
return -EINVAL;
|
|
if (plen > UNIX_PATH_MAX - 1)
|
|
plen = UNIX_PATH_MAX - 1;
|
|
|
|
memmove(out, sa.sun_path, (uint)plen);
|
|
out[plen] = '\0';
|
|
|
|
// A NUL inside the copied region is fine -- it just ends the path early,
|
|
// which is what a caller passing the full struct length does.
|
|
out[UNIX_PATH_MAX - 1] = '\0';
|
|
return 0;
|
|
}
|
|
|
|
// Wrap an established socket in a file and a descriptor. Consumes the
|
|
// socket's reference on success; on failure the caller still owns it.
|
|
//
|
|
// `sflags` carries SOCK_CLOEXEC and SOCK_NONBLOCK, which socket(), socketpair()
|
|
// and accept4() all take and all used to discard.
|
|
//
|
|
// Discarding them is not harmless in either direction. A dropped SOCK_CLOEXEC
|
|
// leaks the descriptor across every exec the process makes -- so a server that
|
|
// forks a child holds its listening socket open in it, and the port stays bound
|
|
// after the server dies. A dropped SOCK_NONBLOCK is worse: the caller believes
|
|
// it has a non-blocking socket, does not select() first, and blocks forever in
|
|
// what it thinks is a poll loop.
|
|
//
|
|
// Both are one line each, and they were masked off with a comment saying they
|
|
// were not honoured -- which is exactly the class of quiet lie the rest of this
|
|
// file has just been cleaned of.
|
|
static int
|
|
sock_to_fd(struct socket *s, int sflags)
|
|
{
|
|
struct file *f;
|
|
int fd;
|
|
|
|
if ((f = filealloc()) == 0)
|
|
return -ENFILE;
|
|
if ((fd = fdalloc(f)) < 0) {
|
|
f->ref = 0;
|
|
return -EMFILE;
|
|
}
|
|
f->type = FD_SOCKET;
|
|
f->sock = s;
|
|
f->readable = 1;
|
|
f->writable = 1;
|
|
f->off = 0;
|
|
f->nonblock = (sflags & SOCK_NONBLOCK) != 0;
|
|
if (sflags & SOCK_CLOEXEC)
|
|
myproc()->ofile_flags[fd] |= FD_CLOEXEC;
|
|
return fd;
|
|
}
|
|
|
|
// Resolve a descriptor to the socket behind it.
|
|
static int
|
|
argsock(int n, struct socket **ps)
|
|
{
|
|
int fd;
|
|
struct file *f;
|
|
|
|
if (argint(n, &fd) < 0)
|
|
return -EBADF;
|
|
if (fd < 0 || fd >= NOFILE || !(f = myproc()->ofile[fd]))
|
|
return -EBADF;
|
|
if (f->type != FD_SOCKET)
|
|
return -ENOTSOCK;
|
|
*ps = f->sock;
|
|
return 0;
|
|
}
|
|
|
|
SYSCALL_DEFINE(socket)
|
|
{
|
|
int domain, type, protocol;
|
|
struct socket *s;
|
|
int fd;
|
|
|
|
argint(0, &domain);
|
|
argint(1, &type);
|
|
argint(2, &protocol);
|
|
|
|
if (domain != AF_UNIX && domain != AF_INET)
|
|
return -EAFNOSUPPORT;
|
|
|
|
// SOCK_CLOEXEC / SOCK_NONBLOCK ride in the high bits of `type`. Split them
|
|
// off rather than discarding them: the type check below wants the low byte,
|
|
// and sock_to_fd wants the flags.
|
|
int sflags = type & (SOCK_CLOEXEC | SOCK_NONBLOCK);
|
|
if (type & ~(0xFF | SOCK_CLOEXEC | SOCK_NONBLOCK))
|
|
return -EINVAL;
|
|
type &= 0xFF;
|
|
if (domain == AF_UNIX && type != SOCK_STREAM)
|
|
return -EPROTOTYPE;
|
|
if (domain == AF_INET && type != SOCK_STREAM && type != SOCK_DGRAM)
|
|
return -EPROTOTYPE;
|
|
|
|
// Protocol 0 means "the default for this type". The one non-default we
|
|
// support is a ping socket -- SOCK_DGRAM with IPPROTO_ICMP, Linux's way of
|
|
// letting an unprivileged process do the one thing raw sockets were used
|
|
// for. Anything else is still refused rather than quietly given a socket
|
|
// that speaks a different protocol than the caller asked for.
|
|
if (protocol != 0) {
|
|
if (!(domain == AF_INET && type == SOCK_DGRAM &&
|
|
protocol == IPPROTO_ICMP))
|
|
return -EPROTONOSUPPORT;
|
|
}
|
|
|
|
// ENETDOWN rather than ENFILE when the stack itself is not running: the
|
|
// old blanket ENFILE said "file table overflow" for a machine with no
|
|
// network, which is a long way from the truth and a longer way from the
|
|
// fix.
|
|
if (domain == AF_INET && !net_is_ready())
|
|
return -ENETDOWN;
|
|
|
|
if ((s = socknew(domain, type, protocol)) == 0)
|
|
return -ENFILE;
|
|
|
|
if ((fd = sock_to_fd(s, sflags)) < 0) {
|
|
sockclose(s);
|
|
return fd;
|
|
}
|
|
return fd;
|
|
}
|
|
|
|
// Pull an address and port out of a user `struct sockaddr_in`.
|
|
//
|
|
// Both come back in network byte order and stay that way: lwIP wants them that
|
|
// way, so converting here and back again would be two swaps that cancel.
|
|
static int
|
|
fetch_sin(addr_t uaddr, int ulen, uint *addr, ushort *port)
|
|
{
|
|
struct sockaddr_in sa;
|
|
|
|
if (ulen < (int)sizeof(sa))
|
|
return -EINVAL;
|
|
if (copy_from_user(&sa, uaddr, sizeof(sa)) < 0)
|
|
return -EFAULT;
|
|
if (sa.sin_family != AF_INET)
|
|
return -EAFNOSUPPORT;
|
|
|
|
*addr = sa.sin_addr;
|
|
*port = sa.sin_port;
|
|
return 0;
|
|
}
|
|
|
|
// Network byte order to host, for a port. The kernel is little-endian and the
|
|
// wire is big-endian; lwIP's own ntohs is not reachable from here without
|
|
// dragging its headers into the syscall layer.
|
|
static ushort
|
|
port_from_net(ushort p)
|
|
{
|
|
return (ushort)(((p & 0xFF) << 8) | ((p >> 8) & 0xFF));
|
|
}
|
|
|
|
SYSCALL_DEFINE(bind)
|
|
{
|
|
struct socket *s;
|
|
addr_t uaddr;
|
|
int ulen, r;
|
|
char path[UNIX_PATH_MAX];
|
|
|
|
if ((r = argsock(0, &s)) < 0)
|
|
return r;
|
|
argaddr(1, &uaddr);
|
|
argint(2, &ulen);
|
|
|
|
if (sock_domain(s) == AF_INET) {
|
|
uint ip;
|
|
ushort port;
|
|
if ((r = fetch_sin(uaddr, ulen, &ip, &port)) < 0)
|
|
return r;
|
|
return sockbind_in(s, ip, port_from_net(port));
|
|
}
|
|
|
|
if ((r = fetch_sun_path(uaddr, ulen, path)) < 0)
|
|
return r;
|
|
return sockbind(s, path);
|
|
}
|
|
|
|
SYSCALL_DEFINE(listen)
|
|
{
|
|
struct socket *s;
|
|
int backlog, r;
|
|
|
|
if ((r = argsock(0, &s)) < 0)
|
|
return r;
|
|
argint(1, &backlog);
|
|
return socklisten(s, backlog);
|
|
}
|
|
|
|
SYSCALL_DEFINE(connect)
|
|
{
|
|
struct socket *s;
|
|
addr_t uaddr;
|
|
int ulen, r;
|
|
char path[UNIX_PATH_MAX];
|
|
|
|
if ((r = argsock(0, &s)) < 0)
|
|
return r;
|
|
argaddr(1, &uaddr);
|
|
argint(2, &ulen);
|
|
|
|
if (sock_domain(s) == AF_INET) {
|
|
uint ip;
|
|
ushort port;
|
|
if ((r = fetch_sin(uaddr, ulen, &ip, &port)) < 0)
|
|
return r;
|
|
return sockconnect_in(s, ip, port_from_net(port));
|
|
}
|
|
|
|
if ((r = fetch_sun_path(uaddr, ulen, path)) < 0)
|
|
return r;
|
|
return sockconnect(s, path);
|
|
}
|
|
|
|
// accept(2) and accept4(2) share everything but the flags word.
|
|
//
|
|
// They are two table entries rather than one with an alias, and the reason is
|
|
// the argument count: accept takes three, so a handler that read a fourth
|
|
// register would be reading whatever the caller happened to leave there and
|
|
// would invent a SOCK_NONBLOCK out of stack litter. That is precisely the bug
|
|
// the old `SYSCALL_ALIAS(288, accept)` would have grown the moment the flags
|
|
// started being honoured.
|
|
static addr_t
|
|
do_accept(int sflags)
|
|
{
|
|
struct socket *s, *c;
|
|
addr_t uaddr, ulenp;
|
|
int r, fd;
|
|
|
|
if ((r = argsock(0, &s)) < 0)
|
|
return r;
|
|
argaddr(1, &uaddr);
|
|
argaddr(2, &ulenp);
|
|
|
|
if ((c = sockaccept(s)) == 0)
|
|
return -EINVAL;
|
|
|
|
if ((fd = sock_to_fd(c, sflags)) < 0) {
|
|
sockclose(c);
|
|
return fd;
|
|
}
|
|
|
|
// The peer address of a Unix socket that never bound a name is the empty
|
|
// one: family AF_UNIX and a zero-length path. Reporting it is optional
|
|
// (both pointers may be null) but a caller that asks must not be left
|
|
// with an uninitialised struct it then prints.
|
|
if (uaddr && ulenp) {
|
|
struct sockaddr_un sa;
|
|
int len = (int)sizeof(sa.sun_family);
|
|
int want = 0;
|
|
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.sun_family = AF_UNIX;
|
|
safestrcpy(sa.sun_path, sockname(c), UNIX_PATH_MAX);
|
|
len += (int)strlen(sa.sun_path) + 1;
|
|
|
|
if (get_user(want, ulenp) < 0)
|
|
return fd; // the fd is already live; do not fail it
|
|
if (want > len)
|
|
want = len;
|
|
if (want > 0)
|
|
copy_to_user(uaddr, &sa, (uint64)want);
|
|
put_user(len, ulenp);
|
|
}
|
|
return fd;
|
|
}
|
|
|
|
SYSCALL_DEFINE(accept) { return do_accept(0); }
|
|
|
|
SYSCALL_DEFINE(accept4)
|
|
{
|
|
int flags = 0;
|
|
|
|
argint(3, &flags);
|
|
if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
|
|
return -EINVAL;
|
|
return do_accept(flags);
|
|
}
|
|
|
|
SYSCALL_DEFINE(socketpair)
|
|
{
|
|
int domain, type, protocol;
|
|
addr_t usv;
|
|
struct socket *a, *b;
|
|
int fd0, fd1, r;
|
|
|
|
argint(0, &domain);
|
|
argint(1, &type);
|
|
argint(2, &protocol);
|
|
argaddr(3, &usv);
|
|
|
|
if (domain != AF_UNIX)
|
|
return -EAFNOSUPPORT;
|
|
int sflags = type & (SOCK_CLOEXEC | SOCK_NONBLOCK);
|
|
type &= 0xFF;
|
|
if (type != SOCK_STREAM)
|
|
return -EPROTOTYPE;
|
|
|
|
if ((r = sockpair(&a, &b)) < 0)
|
|
return r;
|
|
|
|
if ((fd0 = sock_to_fd(a, sflags)) < 0) {
|
|
sockclose(a);
|
|
sockclose(b);
|
|
return fd0;
|
|
}
|
|
if ((fd1 = sock_to_fd(b, sflags)) < 0) {
|
|
myproc()->ofile[fd0] = 0;
|
|
sockclose(a);
|
|
sockclose(b);
|
|
return fd1;
|
|
}
|
|
|
|
int sv[2] = { fd0, fd1 };
|
|
if (copy_to_user(usv, sv, sizeof(sv)) < 0) {
|
|
myproc()->ofile[fd0] = 0;
|
|
myproc()->ofile[fd1] = 0;
|
|
sockclose(a);
|
|
sockclose(b);
|
|
return -EFAULT;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Host byte order to network, for a port on its way back to userland. The
|
|
// inverse of port_from_net above, and the same two lines: a sockaddr always
|
|
// carries the port big-endian, whichever direction it is travelling.
|
|
static ushort
|
|
port_to_net(ushort p)
|
|
{
|
|
return (ushort)(((p & 0xFF) << 8) | ((p >> 8) & 0xFF));
|
|
}
|
|
|
|
// send/recv with no flags are read/write, and sendto/recvfrom are those plus an
|
|
// address. Implementing them here rather than leaving them ENOSYS saves every
|
|
// ported program from being rewritten to use read() and write().
|
|
SYSCALL_DEFINE(sendto)
|
|
{
|
|
struct socket *s;
|
|
addr_t ubuf, uaddr;
|
|
int len, flags, ulen, r;
|
|
uint ip = 0;
|
|
ushort port = 0;
|
|
|
|
if ((r = argsock(0, &s)) < 0)
|
|
return r;
|
|
argaddr(1, &ubuf);
|
|
argint(2, &len);
|
|
argint(3, &flags);
|
|
argaddr(4, &uaddr);
|
|
argint(5, &ulen);
|
|
|
|
if (len < 0)
|
|
return -EINVAL;
|
|
char *p = (char *)user_ptr(ubuf, (uint64)len);
|
|
if (len && !p)
|
|
return -EFAULT;
|
|
|
|
// A null address is send(), not an error: the socket already knows where
|
|
// it is pointed, which for TCP is the only possibility anyway.
|
|
if (uaddr) {
|
|
if ((r = fetch_sin(uaddr, ulen, &ip, &port)) < 0)
|
|
return r;
|
|
port = port_from_net(port);
|
|
}
|
|
return socksendto(s, p, len, ip, port);
|
|
}
|
|
|
|
SYSCALL_DEFINE(recvfrom)
|
|
{
|
|
struct socket *s;
|
|
addr_t ubuf, uaddr, ulenp;
|
|
int len, flags, r;
|
|
uint ip = 0;
|
|
ushort port = 0;
|
|
|
|
if ((r = argsock(0, &s)) < 0)
|
|
return r;
|
|
argaddr(1, &ubuf);
|
|
argint(2, &len);
|
|
argint(3, &flags);
|
|
argaddr(4, &uaddr);
|
|
argaddr(5, &ulenp);
|
|
|
|
if (len < 0)
|
|
return -EINVAL;
|
|
char *p = (char *)user_ptr(ubuf, (uint64)len);
|
|
if (len && !p)
|
|
return -EFAULT;
|
|
|
|
r = sockrecvfrom(s, p, len, &ip, &port);
|
|
if (r < 0 || uaddr == 0)
|
|
return r;
|
|
|
|
// The address is reported only for the family that has one to report. A
|
|
// Unix socket leaves ip at zero, and the honest answer there is a length of
|
|
// zero rather than a sockaddr full of nothing that a caller would read as
|
|
// 0.0.0.0:0 and believe.
|
|
int alen = 0;
|
|
if (sock_domain(s) == AF_INET) {
|
|
struct sockaddr_in sa;
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.sin_family = AF_INET;
|
|
sa.sin_addr = ip;
|
|
sa.sin_port = port_to_net(port);
|
|
alen = (int)sizeof(sa);
|
|
|
|
// Truncated rather than refused if the caller's buffer is smaller,
|
|
// which is what accept() and getsockname() do everywhere: the length
|
|
// written back tells them what they got.
|
|
int cap = alen;
|
|
if (ulenp && get_user(cap, ulenp) < 0)
|
|
return -EFAULT;
|
|
if (cap > alen)
|
|
cap = alen;
|
|
if (cap > 0 && copy_to_user(uaddr, &sa, (uint64)cap) < 0)
|
|
return -EFAULT;
|
|
}
|
|
|
|
if (ulenp && copy_to_user(ulenp, &alen, sizeof(alen)) < 0)
|
|
return -EFAULT;
|
|
return r;
|
|
}
|
|
|
|
// shutdown(2). Half-close is not modelled -- there is one buffer per
|
|
// direction and no per-direction flag -- so SHUT_WR and SHUT_RDWR both mean
|
|
// "the peer sees end-of-file", which is the behaviour programs depend on.
|
|
SYSCALL_DEFINE(sockshutdown)
|
|
{
|
|
struct socket *s;
|
|
int how, r;
|
|
|
|
if ((r = argsock(0, &s)) < 0)
|
|
return r;
|
|
argint(1, &how);
|
|
return sock_shutdown(s, how);
|
|
}
|
|
|
|
// setsockopt / getsockopt.
|
|
//
|
|
// These both used to succeed unconditionally -- setsockopt was `return 0;` and
|
|
// getsockopt wrote a zero for every option asked about. The comment said it was
|
|
// so that a server setting SO_REUSEADDR and treating failure as fatal would not
|
|
// fail.
|
|
//
|
|
// That trade is the wrong way round, and it is worth being precise about why,
|
|
// because "accept and ignore" looks like the kind thing to do. A caller that
|
|
// gets ENOPROTOOPT knows the option did not take effect and can decide what to
|
|
// do. A caller that gets success *changes its behaviour on the strength of it*:
|
|
// it sets SO_RCVTIMEO and then blocks forever in a read it believes will time
|
|
// out; it sets TCP_NODELAY and reports latency numbers that measure Nagle. The
|
|
// failure is silent, remote from the call, and indistinguishable from a bug in
|
|
// the protocol stack.
|
|
//
|
|
// So the rule here is the one the rest of this kernel already follows: do it,
|
|
// or say you did not. The four options below are genuinely applied to lwIP's
|
|
// pcb; the readable ones report the truth; everything else is ENOPROTOOPT.
|
|
//
|
|
// SO_REUSEADDR is honoured rather than refused precisely because of the
|
|
// original comment's concern -- it is the one option a server really does set
|
|
// and really does check.
|
|
|
|
// Fetch the int a setsockopt call is passing. Every option handled here is an
|
|
// int; anything wanting a struct (SO_RCVTIMEO and friends) is refused above.
|
|
static int
|
|
opt_in(addr_t uval, int ulen, int *out)
|
|
{
|
|
if (!uval || ulen < (int)sizeof(int))
|
|
return -EINVAL;
|
|
return get_user(*out, uval) < 0 ? -EFAULT : 0;
|
|
}
|
|
|
|
// Hand one int back, and report how many bytes that was.
|
|
static int
|
|
opt_out(addr_t uval, addr_t ulenp, int val)
|
|
{
|
|
int want = 0;
|
|
|
|
if (!uval || !ulenp)
|
|
return -EFAULT;
|
|
if (get_user(want, ulenp) < 0)
|
|
return -EFAULT;
|
|
if (want < (int)sizeof(int))
|
|
return -EINVAL;
|
|
if (put_user(val, uval) < 0)
|
|
return -EFAULT;
|
|
want = sizeof(int);
|
|
return put_user(want, ulenp) < 0 ? -EFAULT : 0;
|
|
}
|
|
|
|
SYSCALL_DEFINE(setsockopt)
|
|
{
|
|
struct socket *s;
|
|
addr_t uval;
|
|
int level, name, ulen, val, r;
|
|
|
|
if ((r = argsock(0, &s)) < 0)
|
|
return r;
|
|
argint(1, &level);
|
|
argint(2, &name);
|
|
argaddr(3, &uval);
|
|
argint(4, &ulen);
|
|
|
|
if ((r = opt_in(uval, ulen, &val)) < 0)
|
|
return r;
|
|
return sockopt_set(s, level, name, val);
|
|
}
|
|
|
|
SYSCALL_DEFINE(getsockopt)
|
|
{
|
|
struct socket *s;
|
|
addr_t uval, ulenp;
|
|
int level, name, val = 0, r;
|
|
|
|
if ((r = argsock(0, &s)) < 0)
|
|
return r;
|
|
argint(1, &level);
|
|
argint(2, &name);
|
|
argaddr(3, &uval);
|
|
argaddr(4, &ulenp);
|
|
|
|
if ((r = sockopt_get(s, level, name, &val)) < 0)
|
|
return r;
|
|
return opt_out(uval, ulenp, val);
|
|
}
|