blob: 4d175b1b62b789a076ff08c4572133ce045e50bf [file] [log] [blame]
/*
* This file is part of the UCB release of Plan 9. It is subject to the license
* terms in the LICENSE file found in the top-level directory of this
* distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
* part of the UCB release of Plan 9, including this file, may be copied,
* modified, propagated, or distributed except according to the terms contained
* in the LICENSE file.
*/
/* posix */
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
/* bsd extensions */
#include <sys/uio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <sys/plan9_helpers.h>
/* Puts the path of the conversation file for 'name' for the Rock in retval,
* which must be a char [Ctlsize]. */
void _sock_get_conv_filename(Rock *r, const char *name, char *retloc)
{
char *p;
strlcpy(retloc, r->ctl, Ctlsize);
p = strrchr(retloc, '/');
assert(p);
p++;
*p = 0;
strlcat(retloc, name, Ctlsize);
}
void
_sock_ingetaddr(Rock *r, struct sockaddr_in *ip, socklen_t *alen,
const char *a)
{
int n, fd;
char *p;
char name[Ctlsize];
int open_flags;
/* get remote address */
_sock_get_conv_filename(r, a, name);
open_flags = O_RDONLY;
open_flags |= (r->sopts & SOCK_CLOEXEC ? O_CLOEXEC : 0);
fd = open(name, open_flags);
if (fd >= 0) {
n = read(fd, name, sizeof(name) - 1);
if (n > 0) {
name[n] = 0;
p = strchr(name, '!');
if (p) {
*p++ = 0;
ip->sin_family = AF_INET;
ip->sin_port = htons(atoi(p));
ip->sin_addr.s_addr = inet_addr(name);
if (alen)
*alen = sizeof(struct sockaddr_in);
}
}
close(fd);
}
}
/*
* return ndb attribute type of an ip name
*/
int _sock_ipattr(const char *name)
{
const char *p;
int dot = 0;
int alpha = 0;
for (p = name; *p; p++) {
if (isdigit(*p))
continue;
else if (isalpha(*p) || *p == '-')
alpha = 1;
else if (*p == '.')
dot = 1;
else
return Tsys;
}
if (alpha) {
if (dot)
return Tdom;
else
return Tsys;
}
if (dot)
return Tip;
else
return Tsys;
}
/* we can't avoid overrunning npath because we don't know how big it is. */
void _sock_srvname(char *npath, char *path)
{
char *p;
strcpy(npath, "/srv/UD.");
p = strrchr(path, '/');
if (p == 0)
p = path;
else
p++;
strcat(npath, p);
}
int _sock_srv(char *path, int fd)
{
int sfd;
char msg[8 + 256 + 1];
/* change the path to something in srv */
_sock_srvname(msg, path);
/* remove any previous instance */
unlink(msg);
/* put the fd in /srv and then close it */
sfd = creat(msg, 0666);
if (sfd < 0) {
close(fd);
return -1;
}
snprintf(msg, sizeof(msg), "%d", fd);
if (write(sfd, msg, strlen(msg)) < 0) {
close(sfd);
close(fd);
return -1;
}
close(sfd);
close(fd);
return 0;
}
#warning "Not threadsafe!"
Rock *_sock_rock;
Rock *_sock_findrock(int fd, struct stat *dp)
{
Rock *r;
struct stat d;
/* Skip the fstat if there are no socket rocks */
if (!_sock_rock)
return 0;
/* If they pass us a struct stat, then they already did an fstat */
if (dp == 0) {
dp = &d;
fstat(fd, dp);
}
for (r = _sock_rock; r; r = r->next) {
if (r->inode == dp->st_ino && r->dev == dp->st_dev)
break;
}
return r;
}
Rock *_sock_newrock(int fd)
{
Rock *r;
struct stat d;
fstat(fd, &d);
r = _sock_findrock(fd, &d);
if (r == 0) {
r = malloc(sizeof(Rock));
if (r == 0)
return 0;
r->dev = d.st_dev;
r->inode = d.st_ino;
/* TODO: this is not thread-safe! */
r->next = _sock_rock;
_sock_rock = r;
}
assert(r->dev == d.st_dev);
assert(r->inode == d.st_ino);
r->domain = 0;
r->stype = 0;
r->sopts = 0;
r->protocol = 0;
memset(&r->addr, 0, sizeof(r->addr_stor));
r->reserved = 0;
memset(&r->raddr, 0, sizeof(r->raddr_stor));
r->ctl[0] = '\0';
r->ctl_fd = -1;
r->other = -1;
r->has_listen_fd = FALSE;
r->listen_fd = -1;
return r;
}
void _sock_fd_closed(int fd)
{
Rock *r = _sock_findrock(fd, 0);
if (!r)
return;
if (r->ctl_fd >= 0)
close(r->ctl_fd);
if (r->has_listen_fd) {
close(r->listen_fd);
/* This shouldn't matter - the rock is being closed anyways. */
r->has_listen_fd = FALSE;
}
}
/* For a ctlfd and a few other settings, it opens and returns the corresponding
* datafd. This will close cfd on error, or store it in the rock o/w. */
int _sock_data(int cfd, const char *net, int domain, int type, int protocol,
Rock **rp)
{
int n, fd;
Rock *r;
char name[Ctlsize];
int open_flags;
/* get the data file name */
n = read(cfd, name, sizeof(name) - 1);
if (n < 0) {
close(cfd);
errno = ENOBUFS;
return -1;
}
name[n] = 0;
n = strtoul(name, 0, 0);
snprintf(name, sizeof(name), "/net/%s/%d/data", net, n);
/* open data file */
open_flags = O_RDWR;
open_flags |= (type & SOCK_NONBLOCK ? O_NONBLOCK : 0);
open_flags |= (type & SOCK_CLOEXEC ? O_CLOEXEC : 0);
fd = open(name, open_flags);
if (fd < 0) {
close(cfd);
errno = ENOBUFS;
return -1;
}
/* hide stuff under the rock */
snprintf(name, sizeof(name), "/net/%s/%d/ctl", net, n);
r = _sock_newrock(fd);
if (r == 0) {
close(cfd);
close(fd);
errno = ENOBUFS;
return -1;
}
if (rp)
*rp = r;
memset(&r->raddr, 0, sizeof(r->raddr_stor));
memset(&r->addr, 0, sizeof(r->addr_stor));
r->domain = domain;
r->stype = _sock_strip_opts(type);
r->sopts = _sock_get_opts(type);
r->protocol = protocol;
strcpy(r->ctl, name);
r->ctl_fd = cfd;
return fd;
}
/* Takes network-byte ordered IPv4 addr and writes it into buf, in the plan 9 IP
* addr format */
void naddr_to_plan9addr(uint32_t sin_addr, uint8_t *buf)
{
uint8_t *sin_bytes = (uint8_t *)&sin_addr;
memset(buf, 0, 10);
buf += 10;
buf[0] = 0xff;
buf[1] = 0xff;
buf += 2;
buf[0] = sin_bytes[0]; /* e.g. 192 */
buf[1] = sin_bytes[1]; /* e.g. 168 */
buf[2] = sin_bytes[2]; /* e.g. 0 */
buf[3] = sin_bytes[3]; /* e.g. 1 */
}
/* does v4 only */
uint32_t plan9addr_to_naddr(uint8_t *buf)
{
buf += 12;
return (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | (buf[0] << 0);
}
/* Returns a rock* if the socket exists and is UDP */
Rock *udp_sock_get_rock(int fd)
{
Rock *r = _sock_findrock(fd, 0);
if (!r) {
errno = ENOTSOCK;
return 0;
}
if ((r->domain == PF_INET) && (r->stype == SOCK_DGRAM))
return r;
return 0;
}
/* In Linux, socket options are multiplexed in the socket type. */
int _sock_strip_opts(int type)
{
return type & ~(SOCK_NONBLOCK | SOCK_CLOEXEC);
}
int _sock_get_opts(int type)
{
return type & (SOCK_NONBLOCK | SOCK_CLOEXEC);
}
/* Opens the FD for "listen", and attaches it to the Rock. When the dfd (and
* thus the Rock) closes, we'll close the listen file too. Returns the FD on
* success, -1 on error. This is racy, like a lot of other Rock stuff. */
static int _rock_open_listen_fd(Rock *r)
{
char listen_file[Ctlsize];
int ret;
int open_flags;
_sock_get_conv_filename(r, "listen", listen_file);
open_flags = O_PATH;
open_flags |= (r->sopts & SOCK_CLOEXEC ? O_CLOEXEC : 0);
ret = open(listen_file, open_flags);
/* Probably a bug in the rock code (or the kernel!) if we couldn't walk
* to our listen. */
assert(ret >= 0);
r->listen_fd = ret;
r->has_listen_fd = TRUE;
return ret;
}
/* Used by user/iplib (e.g. epoll). Returns the FDs for the ctl_fd and for the
* listen file, opened O_PATH, for this conversation. Returns -1 on no FDs. */
void _sock_lookup_rock_fds(int sock_fd, bool can_open_listen_fd,
int *listen_fd_r, int *ctl_fd_r)
{
Rock *r = _sock_findrock(sock_fd, 0);
*listen_fd_r = -1;
*ctl_fd_r = -1;
if (!r || r->domain == PF_UNIX)
return;
if (!r->has_listen_fd && can_open_listen_fd)
_rock_open_listen_fd(r);
*listen_fd_r = r->listen_fd; /* might still be -1. that's OK. */
*ctl_fd_r = r->ctl_fd;
}
/* Used by fcntl for F_SETFL. */
void _sock_mirror_fcntl(int sock_fd, int cmd, long arg)
{
Rock *r = _sock_findrock(sock_fd, 0);
if (!r || r->domain == PF_UNIX)
return;
if (r->ctl_fd >= 0)
syscall(SYS_fcntl, r->ctl_fd, cmd, arg);
if (r->has_listen_fd)
syscall(SYS_fcntl, r->listen_fd, cmd, arg);
}
/* Given an FD, opens the FD with the name 'sibling' in the same directory.
* e.g., you have a data, you open a ctl. Don't use this with cloned FDs (i.e.
* open clone, get a ctl back) until we fix 9p and fd2path.
*
* Careful, this will always open O_CLOEXEC. The rationale is that the callers
* of this are low-level libraries that quickly close the FD, before any
* non-malicious exec. */
int get_sibling_fd(int fd, const char *sibling)
{
char path[MAX_PATH_LEN];
char *graft;
if (syscall(SYS_fd2path, fd, path, sizeof(path)) < 0)
return -1;
graft = strrchr(path, '/');
if (!graft)
return -1;
graft++;
*graft = 0;
snprintf(graft, sizeof(path) - strlen(path), sibling);
return open(path, O_RDWR | O_CLOEXEC);
}
/* Writes num to FD in ASCII in hex format. */
int write_hex_to_fd(int fd, uint64_t num)
{
int ret;
char cmd[50];
char *ptr;
ptr = u64_to_str(num, cmd, sizeof(cmd));
if (!ptr)
return -1;
ret = write(fd, ptr, sizeof(cmd) - (ptr - cmd));
if (ret <= 0)
return -1;
return 0;
}
/* Returns a char representing the lowest 4 bits of x */
static char num_to_nibble(unsigned int x)
{
return "0123456789abcdef"[x & 0xf];
}
/* Converts num to a string, in hex, using buf as storage. Returns a pointer to
* the string from within your buf, or 0 on failure. */
char *u64_to_str(uint64_t num, char *buf, size_t len)
{
char *ptr;
size_t nr_nibbles = sizeof(num) * 8 / 4;
/* 3: 0, x, and \0 */
if (len < nr_nibbles + 3)
return 0;
ptr = &buf[len - 1];
/* Build the string backwards */
*ptr = '\0';
for (int i = 0; i < nr_nibbles; i++) {
ptr--;
*ptr = num_to_nibble(num);
num >>= 4;
}
ptr--;
*ptr = 'x';
ptr--;
*ptr = '0';
return ptr;
}