blob: 22d085571cad5a19200cfeefce9f738d2917ce19 [file] [log] [blame]
/* Copyright (c) 2015 Google Inc.
* Barret Rhoden <brho@cs.berkeley.edu>
* See LICENSE for details.
*
* User FDs
*
* There are a bunch of Linux APIs that we can implement in userspace that use
* FDs as a handle. Eventually that handle gets passed to close(). User FDs
* are a chunk of reserved numbers in the space of FDs, used by various
* userspace libraries that use FDs as their API.
*
* The user FD space starts where the kernel's leaves off. Currently, the
* kernel claims 19 bits of the 32 bit int for an FD. The MSB flags whether it
* is negative or not. That leaves 12 bits for us. */
#include <sys/user_fd.h>
#include <parlib/arch/atomic.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
static struct user_fd **ufds = 0;
static size_t nr_ufds = 0;
/* Finds a free user FD and stores ufd there. Returns the FD, or -1 on error
* (out of FDs). You can remove it with a close(). */
int ufd_get_fd(struct user_fd *ufd)
{
struct user_fd **new_ufds;
int fd;
if (!ufds) {
nr_ufds = 1 << (sizeof(int) * 8 - LOG2_UP(NR_FILE_DESC_MAX)
- 1);
/* Two things: instead of worrying about growing and reallocing
* (which would need a lock), let's just alloc the entire 2^15
* bytes (32KB). Also, it's unlikely, but we might have two
* threads trying to init at once. First one wins, second one
* aborts (and frees). */
new_ufds = malloc(sizeof(struct user_fd*) * nr_ufds);
memset(new_ufds, 0, sizeof(struct user_fd*) * nr_ufds);
if (!atomic_cas_ptr((void**)&ufds, 0, new_ufds))
free(new_ufds);
cmb();
}
/* At this point, ufds is set. Just do a linear search for an empty
* slot. We're not actually bound to return the lowest number
* available, so in the future we could do things like partition the
* space based on vcoreid so we start in different areas, or maintain a
* 'last used' hint FD. */
for (int i = 0; i < nr_ufds; i++) {
if (!ufds[i]) {
if (atomic_cas_ptr((void**)&ufds[i], 0, ufd)) {
fd = i + USER_FD_BASE;
ufds[i]->fd = fd;
return fd;
}
}
}
__set_errno(ENFILE);
return -1;
}
/* Given an FD, returns the user_fd struct. Returns 0 and sets errno if there's
* an error. There's no protection for concurrent closes, just like how you
* shouldn't attempt to use an FD after closing. So don't do stuff like:
* foo = ufd_lookup(7);
* close(7);
* foo->whatever = 6; // foo could be free!
*
* or
* close(7);
* foo = ufd_lookup(7);
* // this might succeed if it races with close()
* foo->whatever = 6; // foo could be free!
*/
struct user_fd *ufd_lookup(int fd)
{
if (!ufds || (fd - USER_FD_BASE >= nr_ufds)) {
__set_errno(EBADF);
return 0;
}
return ufds[fd - USER_FD_BASE];
}
/* Removes the user_fd from the FD space, calls its callback. Returns 0 on
* success. -1 and sets errno o/w (e.g. EBADF). */
int glibc_close_helper(int fd)
{
struct user_fd *ufd = ufd_lookup(fd);
if (!ufd)
return -1;
ufds[fd - USER_FD_BASE] = 0;
ufd->close(ufd);
return 0;
}