SchOS/kernel/sys/sys_net.c
Nexoniarz d0f7acf9eb Initial commit: Schunix as it stands before the SchOS rename
An x86-64 operating system built from xv6, now largely rewritten: SMP,
a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF
rasteriser, and a Linux-numbered syscall ABI.

No history before this point -- the tree was never under version control.
This commit exists so the rename that follows has something to undo to.
2026-08-09 13:47:01 +02:00

346 lines
8.2 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.
//
// Only AF_UNIX is answered. AF_INET is recognised and refused with
// EAFNOSUPPORT rather than EINVAL, because a program that probes for IPv6 and
// falls back to IPv4 needs to be able to tell "this address family does not
// exist 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.
static int
sock_to_fd(struct socket *s)
{
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;
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)
return -EAFNOSUPPORT;
// SOCK_CLOEXEC / SOCK_NONBLOCK ride in the high bits of `type`. Neither
// is honoured yet, but masking them off means a musl program that always
// sets SOCK_CLOEXEC still gets a socket instead of EINVAL.
type &= 0xFF;
if (type != SOCK_STREAM)
return -EPROTOTYPE;
if (protocol != 0)
return -EPROTONOSUPPORT;
if ((s = socknew(domain, type)) == 0)
return -ENFILE;
if ((fd = sock_to_fd(s)) < 0) {
sockclose(s);
return fd;
}
return fd;
}
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 ((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 ((r = fetch_sun_path(uaddr, ulen, path)) < 0)
return r;
return sockconnect(s, path);
}
SYSCALL_DEFINE(accept)
{
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)) < 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(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;
type &= 0xFF;
if (type != SOCK_STREAM)
return -EPROTOTYPE;
if ((r = sockpair(&a, &b)) < 0)
return r;
if ((fd0 = sock_to_fd(a)) < 0) {
sockclose(a);
sockclose(b);
return fd0;
}
if ((fd1 = sock_to_fd(b)) < 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;
}
// send/recv with no flags are read/write. Implementing them here rather than
// leaving them ENOSYS costs four lines and saves every ported program from
// having to be rewritten to use write().
SYSCALL_DEFINE(sendto)
{
struct socket *s;
addr_t ubuf;
int len, flags, r;
if ((r = argsock(0, &s)) < 0)
return r;
argaddr(1, &ubuf);
argint(2, &len);
argint(3, &flags);
if (len < 0)
return -EINVAL;
char *p = (char *)user_ptr(ubuf, (uint64)len);
if (len && !p)
return -EFAULT;
return sockwrite(s, p, len);
}
SYSCALL_DEFINE(recvfrom)
{
struct socket *s;
addr_t ubuf;
int len, flags, r;
if ((r = argsock(0, &s)) < 0)
return r;
argaddr(1, &ubuf);
argint(2, &len);
argint(3, &flags);
if (len < 0)
return -EINVAL;
char *p = (char *)user_ptr(ubuf, (uint64)len);
if (len && !p)
return -EFAULT;
return sockread(s, p, len);
}
// 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);
}
// getsockopt/setsockopt: accepted and ignored. A server that sets SO_REUSEADDR
// and treats failure as fatal is common enough to be worth not failing.
SYSCALL_DEFINE(setsockopt) { return 0; }
SYSCALL_DEFINE(getsockopt)
{
addr_t uval, ulen;
int zero = 0, len = 0;
argaddr(3, &uval);
argaddr(4, &ulen);
if (uval && ulen) {
if (get_user(len, ulen) == 0 && len >= (int)sizeof(int)) {
put_user(zero, uval);
len = sizeof(int);
put_user(len, ulen);
}
}
return 0;
}