genode/repos/libports/src/lib/vfs/lwip/vfs.cc

2095 lines
52 KiB
C++

/*
* \brief LwIP VFS plugin
* \author Emery Hemingway
* \date 2016-09-27
*/
/*
* Copyright (C) 2016-2019 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* Genode includes */
#include <vfs/directory_service.h>
#include <vfs/file_io_service.h>
#include <vfs/file_system_factory.h>
#include <vfs/vfs_handle.h>
#include <vfs/print.h>
#include <timer_session/connection.h>
#include <util/fifo.h>
#include <base/tslab.h>
#include <base/registry.h>
#include <base/log.h>
/* LwIP includes */
#include <lwip/genode_init.h>
#include <lwip/nic_netif.h>
namespace Lwip {
extern "C" {
#include <lwip/udp.h>
#include <lwip/tcp.h>
#include <lwip/dns.h>
}
using namespace Vfs;
typedef Vfs::File_io_service::Read_result Read_result;
typedef Vfs::File_io_service::Write_result Write_result;
typedef Vfs::File_io_service::Sync_result Sync_result;
typedef Genode::String<8> Socket_name;
char const *get_port(char const *s)
{
char const *p = s;
while (*++p) {
if (*p == ':')
return ++p;
}
return nullptr;
}
int remove_port(char *p)
{
long tmp = -1;
while (*++p)
if (*p == ':') {
*p++ = '\0';
Genode::ascii_to_unsigned(p, tmp, 10);
break;
}
return tmp;
}
class Socket_dir;
class Udp_socket_dir;
class Tcp_socket_dir;
#define Udp_socket_dir_list Genode::List<Udp_socket_dir>
#define Tcp_socket_dir_list Genode::List<Tcp_socket_dir>
struct Protocol_dir;
template <typename, typename> class Protocol_dir_impl;
enum {
MAX_SOCKETS = 128, /* 3 */
MAX_SOCKET_NAME_LEN = 3 + 1, /* + \0 */
MAX_FD_STR_LEN = 3 + 1 +1, /* + \n + \0 */
MAX_DATA_LEN = 32, /* 255.255.255.255:65536 + something */
};
struct Lwip_handle;
struct Lwip_nameserver_handle;
struct Lwip_address_handle;
struct Lwip_netmask_handle;
struct Lwip_file_handle;
struct Lwip_dir_handle;
typedef Genode::Registry<Lwip_nameserver_handle> Nameserver_registry;
#define Lwip_handle_list List<Lwip_file_handle>
class File_system;
typedef Vfs::Directory_service::Open_result Open_result;
typedef Vfs::Directory_service::Opendir_result Opendir_result;
typedef Vfs::Directory_service::Unlink_result Unlink_result;
extern "C" {
static void udp_recv_callback(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port);
static err_t tcp_connect_callback(void *arg, struct tcp_pcb *tpcb, err_t err);
static err_t tcp_accept_callback(void *arg, struct tcp_pcb *newpcb, err_t err);
static err_t tcp_recv_callback(void *arg, struct tcp_pcb *tpcb,
struct pbuf *p, err_t err);
static err_t tcp_delayed_recv_callback(void *arg, struct tcp_pcb *tpcb,
struct pbuf *p, err_t err);
static err_t tcp_sent_callback(void *arg, struct tcp_pcb *tpcb, u16_t len);
static void tcp_err_callback(void *arg, err_t err);
}
typedef Genode::Path<24> Path;
enum {
PORT_STRLEN_MAX = 6, /* :65536 */
ENDPOINT_STRLEN_MAX = IPADDR_STRLEN_MAX+PORT_STRLEN_MAX,
ADDRESS_FILE_SIZE = IPADDR_STRLEN_MAX+2,
};
struct Directory;
}
/**
* Synthetic directory interface
*/
struct Lwip::Directory
{
virtual ~Directory() { }
virtual Read_result readdir(char *dst, file_size count,
file_size &out_count) = 0;
virtual bool directory(char const *path) = 0;
};
struct Lwip::Lwip_handle : Vfs::Vfs_handle
{
Lwip_handle(Vfs::File_system &fs, Allocator &alloc, int status_flags)
: Vfs_handle(fs, fs, alloc, status_flags) { }
virtual Read_result read(char *dst, file_size count,
file_size &out_count) = 0;
virtual Write_result write(char const *, file_size,
file_size &) {
return Write_result::WRITE_ERR_INVALID; }
};
struct Lwip::Lwip_dir_handle final : Lwip_handle
{
/*
* Noncopyable
*/
Lwip_dir_handle(Lwip_dir_handle const &);
Lwip_dir_handle &operator = (Lwip_dir_handle const &);
Directory *dir;
void print(Genode::Output &output) const;
Lwip_dir_handle(Vfs::File_system &fs, Allocator &alloc, Directory &dir)
: Lwip_handle(fs, alloc, 0), dir(&dir) { }
Read_result read(char *dst, file_size count,
file_size &out_count) override
{
return (dir)
? dir->readdir(dst, count, out_count)
: Read_result::READ_ERR_INVALID;
}
};
struct Lwip::Lwip_nameserver_handle final : Lwip_handle, private Nameserver_registry::Element
{
Lwip_nameserver_handle(Vfs::File_system &fs, Allocator &alloc, Nameserver_registry &registry)
: Lwip_handle(fs, alloc, Vfs::Directory_service::OPEN_MODE_RDONLY),
Nameserver_registry::Element(registry, *this) { }
Read_result read(char *dst, file_size count,
file_size &out_count) override
{
memset(dst, 0x00, min(file_size(IPADDR_STRLEN_MAX), count));
ipaddr_ntoa_r(dns_getserver(0), dst, count);
auto n = strlen(dst);
if (n < count)
dst[n] = '\n';
out_count = n+1;
return Read_result::READ_OK;
}
};
struct Lwip::Lwip_address_handle final : Lwip_handle
{
Lwip::netif const &netif;
Lwip_address_handle(Vfs::File_system &fs, Allocator &alloc,
Lwip::netif &netif)
: Lwip_handle(fs, alloc, Vfs::Directory_service::OPEN_MODE_RDONLY)
, netif(netif)
{ }
Read_result read(char *dst, file_size count,
file_size &out_count) override
{
using namespace Genode;
char address[IPADDR_STRLEN_MAX] { '\0' };
ipaddr_ntoa_r(&netif.ip_addr, address, IPADDR_STRLEN_MAX);
Genode::String<ADDRESS_FILE_SIZE>
line((char const *)address, "\n");
size_t n = min(line.length(), count);
memcpy(dst, line.string(), n);
out_count = n;
return Read_result::READ_OK;
}
};
struct Lwip::Lwip_netmask_handle final : Lwip_handle
{
Lwip::netif const &netif;
Lwip_netmask_handle(Vfs::File_system &fs, Allocator &alloc,
Lwip::netif &netif)
: Lwip_handle(fs, alloc, Vfs::Directory_service::OPEN_MODE_RDONLY)
, netif(netif)
{ }
Read_result read(char *dst, file_size count,
file_size &out_count) override
{
using namespace Genode;
char netmask[IPADDR_STRLEN_MAX] { '\0' };
ipaddr_ntoa_r(&netif.netmask, netmask, IPADDR_STRLEN_MAX);
Genode::String<ADDRESS_FILE_SIZE>
line((char const *)netmask, "\n");
size_t n = min(line.length(), count);
memcpy(dst, line.string(), n);
out_count = n;
return Read_result::READ_OK;
}
};
struct Lwip::Lwip_file_handle final : Lwip_handle, private Lwip_handle_list::Element
{
friend class Lwip_handle_list;
friend class Lwip_handle_list::Element;
using Lwip_handle_list::Element::next;
enum Kind {
INVALID = 0,
ACCEPT = 1 << 0,
BIND = 1 << 1,
CONNECT = 1 << 2,
DATA = 1 << 3,
LISTEN = 1 << 4,
LOCAL = 1 << 5,
PEEK = 1 << 6,
REMOTE = 1 << 7,
LOCATION = 1 << 8,
PENDING = 1 << 9,
};
enum { DATA_READY = DATA | PEEK };
/*
* Noncopyable
*/
Lwip_file_handle(Lwip_file_handle const &);
Lwip_file_handle &operator = (Lwip_file_handle const &);
static Kind kind_from_name(Path const &p)
{
if (p == "/accept_socket") return PENDING;
if (p == "/accept") return ACCEPT;
if (p == "/bind") return BIND;
if (p == "/connect") return CONNECT;
if (p == "/data") return DATA;
if (p == "/listen") return LISTEN;
if (p == "/local") return LOCAL;
if (p == "/peek") return PEEK;
if (p == "/remote") return REMOTE;
return INVALID;
}
Socket_dir *socket;
typedef Genode::Fifo_element<Lwip_file_handle> Fifo_element;
typedef Genode::Fifo<Lwip_file_handle::Fifo_element> Fifo;
Fifo_element _read_ready_waiter { *this };
Fifo_element _io_progress_waiter { *this };
int in_transit = 0;
Kind kind;
void print(Genode::Output &output) const;
Lwip_file_handle(Vfs::File_system &fs, Allocator &alloc, int status_flags,
Socket_dir &s, Kind k);
~Lwip_file_handle();
Read_result read(char *dst, file_size count,
file_size &out_count) override;
Write_result write(char const *src, file_size count,
file_size &out_count) override;
bool notify_read_ready();
};
struct Lwip::Socket_dir : Lwip::Directory
{
friend class Lwip_handle;
friend class Lwip_handle_list;
friend class Lwip_handle_list::Element;
static Socket_name name_from_num(unsigned num)
{
char buf[Socket_name::capacity()];
return Socket_name(Genode::Cstring(
buf, Genode::snprintf(buf, Socket_name::capacity(), "%x", num)));
}
Genode::Allocator &alloc;
unsigned const _num;
Socket_name const _name { name_from_num(_num) };
/* lists of handles opened at this socket */
Lwip_handle_list handles { };
Lwip_file_handle::Fifo read_ready_queue { };
Lwip_file_handle::Fifo io_progress_queue { };
enum State {
NEW,
BOUND,
CONNECT,
LISTEN,
READY,
CLOSING,
CLOSED
};
Socket_dir(unsigned num, Genode::Allocator &alloc)
: alloc(alloc), _num(num) { };
~Socket_dir()
{
/*
* Remove socket from handles
*/
while (Lwip_file_handle *handle = handles.first()) {
handle->socket = nullptr;
handles.remove(handle);
}
}
Socket_name const &name() const { return _name; }
bool operator == (unsigned other) const {
return _num == other; }
bool operator == (char const *other) const {
return _name == other; }
virtual Open_result _accept_new_socket(Vfs::File_system &,
Genode::Allocator &,
Vfs::Vfs_handle **) = 0;
Open_result open(Vfs::File_system &fs,
Path const &name,
unsigned mode,
Vfs_handle **out_handle,
Allocator &alloc)
{
Lwip_file_handle::Kind kind = Lwip_file_handle::kind_from_name(name);
if (kind == Lwip_file_handle::INVALID)
return Open_result::OPEN_ERR_UNACCESSIBLE;
if (kind == Lwip_file_handle::LOCATION || kind == Lwip_file_handle::PENDING)
return _accept_new_socket(fs, alloc, out_handle);
*out_handle = new (alloc) Lwip_file_handle(fs, alloc, mode, *this, kind);
return Open_result::OPEN_OK;
}
Read_result readdir(char *, file_size, file_size &) override
{
Genode::warning(__func__, " NOT_IMPLEMENTED");
return Read_result::READ_ERR_INVALID;
}
bool directory(char const *path) override
{
/* empty path is this directory */
return (!*path);
}
virtual Read_result read(Lwip_file_handle&,
char *dst, file_size count,
file_size &out_count) = 0;
virtual Write_result write(Lwip_file_handle&,
char const *src, file_size count,
file_size &out_count) = 0;
virtual bool read_ready(Lwip_file_handle&) = 0;
/**
* Notify handles waiting for this PCB / socket to be ready
*/
void process_read_ready()
{
/* invoke all handles waiting for read_ready */
read_ready_queue.dequeue_all([] (Lwip_file_handle::Fifo_element &elem) {
elem.object().read_ready_response(); });
}
/**
* Notify handles blocked by operations on this PCB / socket
*/
void process_io()
{
/* invoke all handles waiting for IO progress */
io_progress_queue.dequeue_all([] (Lwip_file_handle::Fifo_element &elem) {
elem.object().io_progress_response(); });
}
};
Lwip::Lwip_file_handle::Lwip_file_handle(Vfs::File_system &fs, Allocator &alloc,
int status_flags,
Socket_dir &s, Lwip_file_handle::Kind k)
: Lwip_handle(fs, alloc, status_flags), socket(&s), kind(k)
{
socket->handles.insert(this);
}
Lwip::Lwip_file_handle::~Lwip_file_handle()
{
if (socket) {
socket->handles.remove(this);
if (_read_ready_waiter.enqueued()) {
socket->read_ready_queue.remove(_read_ready_waiter);
}
if (_io_progress_waiter.enqueued()) {
socket->io_progress_queue.remove(_io_progress_waiter);
}
}
}
Lwip::Read_result Lwip::Lwip_file_handle::read(char *dst, file_size count,
file_size &out_count)
{
Lwip::Read_result result = Read_result::READ_ERR_INVALID;
if (socket) {
result = socket->read(*this, dst, count, out_count);
if (result == Read_result::READ_QUEUED && !_io_progress_waiter.enqueued())
socket->io_progress_queue.enqueue(_io_progress_waiter);
}
return result;
}
Lwip::Write_result Lwip::Lwip_file_handle::write(char const *src, file_size count,
file_size &out_count)
{
Write_result result = Write_result::WRITE_ERR_INVALID;
if (socket) {
result = socket->write(*this, src, count, out_count);
if (result == Write_result::WRITE_ERR_WOULD_BLOCK && !_io_progress_waiter.enqueued())
socket->io_progress_queue.enqueue(_io_progress_waiter);
}
return result;
}
bool Lwip::Lwip_file_handle::notify_read_ready()
{
if (socket) {
if (!_read_ready_waiter.enqueued())
socket->read_ready_queue.enqueue(_read_ready_waiter);
return true;
}
return false;
}
void Lwip::Lwip_file_handle::print(Genode::Output &output) const
{
output.out_string(socket->name().string());
switch (kind) {
case Lwip_file_handle::ACCEPT: output.out_string("/accept"); break;
case Lwip_file_handle::BIND: output.out_string("/bind"); break;
case Lwip_file_handle::CONNECT: output.out_string("/connect"); break;
case Lwip_file_handle::DATA: output.out_string("/data"); break;
case Lwip_file_handle::INVALID: output.out_string("/invalid"); break;
case Lwip_file_handle::LISTEN: output.out_string("/listen"); break;
case Lwip_file_handle::LOCAL: output.out_string("/local"); break;
case Lwip_file_handle::LOCATION: output.out_string("(location)"); break;
case Lwip_file_handle::PENDING: output.out_string("/accept_socket"); break;
case Lwip_file_handle::PEEK: output.out_string("/peek"); break;
case Lwip_file_handle::REMOTE: output.out_string("/remote"); break;
}
}
struct Lwip::Protocol_dir : Lwip::Directory
{
virtual bool leaf_path(char const *) = 0;
virtual Directory_service::Stat_result stat(char const*, Directory_service::Stat&) = 0;
virtual void adopt_socket(Socket_dir &socket) = 0;
virtual Open_result open(Vfs::File_system &fs,
char const *path,
unsigned mode,
Vfs_handle **out_handle,
Allocator &alloc) = 0;
virtual Opendir_result opendir(Vfs::File_system &fs,
char const *path,
Vfs_handle **out_handle,
Allocator &alloc) = 0;
virtual ~Protocol_dir() { }
};
template <typename SOCKET_DIR, typename PCB>
class Lwip::Protocol_dir_impl final : public Protocol_dir
{
private:
Genode::Allocator &_alloc;
Genode::Entrypoint &_ep;
Genode::List<SOCKET_DIR> _socket_dirs { };
public:
friend class Genode::List<SOCKET_DIR>;
friend class Genode::List<SOCKET_DIR>::Element;
friend class Tcp_socket_dir;
friend class Udp_socket_dir;
Protocol_dir_impl(Vfs::Env &vfs_env)
: _alloc(vfs_env.alloc()), _ep(vfs_env.env().ep()) { }
SOCKET_DIR *lookup(char const *name)
{
if (*name == '/') ++name;
/* make sure it is only a name */
for (char const *p = name; *p; ++p)
if (*p == '/')
return nullptr;
for (SOCKET_DIR *sd = _socket_dirs.first(); sd; sd = sd->next())
if (*sd == name)
return sd;
return nullptr;
}
bool leaf_path(char const *path) override
{
Path subpath(path);
subpath.strip_last_element();
if ((subpath == "/") || (subpath == "/new_socket"))
return true;
if (lookup(subpath.string())) {
subpath.import(path);
subpath.keep_only_last_element();
auto kind = Lwip_file_handle::kind_from_name(subpath);
return (kind != Lwip_file_handle::INVALID);
}
return false;
}
Directory_service::Stat_result stat(char const *path, Directory_service::Stat &st) override
{
Path subpath(path);
if (subpath == "/") {
st = { .size = 1,
.type = Node_type::DIRECTORY,
.rwx = Node_rwx::rwx(),
.inode = (Genode::addr_t)this,
.device = 0,
.modification_time = { 0 } };
return Directory_service::STAT_OK;
}
if (subpath == "/new_socket") {
st = { .size = 1,
.type = Node_type::TRANSACTIONAL_FILE,
.rwx = Node_rwx::rw(),
.inode = (Genode::addr_t)this + 1,
.device = 0,
.modification_time = { 0 } };
return Directory_service::STAT_OK;
}
if (!subpath.has_single_element())
subpath.strip_last_element();
if (SOCKET_DIR *dir = lookup(subpath.string())) {
Path filename(path);
filename.keep_only_last_element();
if (filename == subpath.base()) {
st = { .size = 0,
.type = Node_type::DIRECTORY,
.rwx = Node_rwx::rwx(),
.inode = (Genode::addr_t)dir,
.device = 0,
.modification_time = { 0 } };
return Directory_service::STAT_OK;
}
Lwip_file_handle::Kind k = Lwip_file_handle::kind_from_name(filename);
if (k != Lwip_file_handle::INVALID) {
st = { .size = 0,
.type = (filename == "/data")
? Node_type::CONTINUOUS_FILE
: Node_type::TRANSACTIONAL_FILE,
.rwx = Node_rwx::rw(),
.inode = (Genode::addr_t)dir + k,
.device = 0,
.modification_time = { 0 } };
return Directory_service::STAT_OK;
}
}
return Directory_service::STAT_ERR_NO_ENTRY;
}
Read_result readdir(char *, file_size, file_size &) override
{
Genode::warning(__func__, " NOT_IMPLEMENTED");
return Read_result::READ_ERR_INVALID;
};
bool directory(char const *path) override
{
/* empty path is the protocol directory */
return *path ? (lookup(path+1) != nullptr) : true;
}
SOCKET_DIR &alloc_socket(Genode::Allocator &alloc, PCB *pcb = nullptr)
{
/*
* use the equidistribution RNG to hide the socket count,
* see src/lib/lwip/platform/rand.cc
*/
unsigned id = LWIP_RAND();
/* check for collisions */
for (SOCKET_DIR *dir = _socket_dirs.first(); dir;) {
if (*dir == id) {
id = LWIP_RAND();
dir = _socket_dirs.first();
} else {
dir = dir->next();
}
}
SOCKET_DIR *new_socket = new (alloc)
SOCKET_DIR(id, *this, alloc, _ep, pcb);
_socket_dirs.insert(new_socket);
return *new_socket;
}
void adopt_socket(Socket_dir &socket) override {
_socket_dirs.insert(static_cast<SOCKET_DIR*>(&socket)); }
void release(SOCKET_DIR *socket) {
_socket_dirs.remove(socket); }
Open_result open(Vfs::File_system &fs,
char const *path,
unsigned mode,
Vfs_handle **out_handle,
Allocator &alloc) override
{
Path subpath(path);
if (subpath == "/new_socket") {
Socket_dir &new_dir = alloc_socket(alloc);
*out_handle = new (alloc) Lwip_file_handle(
fs, alloc, Vfs::Directory_service::OPEN_MODE_RDONLY,
new_dir, Lwip_file_handle::LOCATION);
return Open_result::OPEN_OK;
}
subpath.strip_last_element();
if (SOCKET_DIR *dir = lookup(subpath.base()+1)) {
subpath.import(path);
subpath.keep_only_last_element();
return dir->open(fs, subpath, mode, out_handle, alloc);
}
return Open_result::OPEN_ERR_UNACCESSIBLE;
}
Opendir_result opendir(Vfs::File_system &fs,
char const *path,
Vfs_handle **out_handle,
Allocator &alloc) override
{
if (!*path) {
*out_handle = new (alloc) Lwip_dir_handle(fs, alloc, *this);
return Opendir_result::OPENDIR_OK;
}
if (SOCKET_DIR *dir = lookup(path)) {
*out_handle = new (alloc) Lwip_dir_handle(fs, alloc, *dir);
return Opendir_result::OPENDIR_OK;
}
return Opendir_result::OPENDIR_ERR_LOOKUP_FAILED;
}
void notify()
{
for (SOCKET_DIR *sd = _socket_dirs.first(); sd; sd = sd->next()) {
sd->process_io();
}
}
};
namespace Lwip {
typedef Protocol_dir_impl<Udp_socket_dir, udp_pcb> Udp_proto_dir;
typedef Protocol_dir_impl<Tcp_socket_dir, tcp_pcb> Tcp_proto_dir;
}
/*********
** UDP **
*********/
class Lwip::Udp_socket_dir final :
public Socket_dir,
private Udp_socket_dir_list::Element
{
private:
/*
* Noncopyable
*/
Udp_socket_dir(Udp_socket_dir const &);
Udp_socket_dir &operator = (Udp_socket_dir const &);
Udp_proto_dir &_proto_dir;
udp_pcb *_pcb;
/**
* Handle on a received UDP packet
*/
class Packet : public Genode::Fifo<Packet>::Element
{
public:
Packet(Packet const &);
Packet &operator = (Packet const &);
ip_addr_t const addr;
u16_t const port;
private:
u16_t offset = 0;
pbuf *buf;
public:
Packet(ip_addr_t const *addr, u16_t port, pbuf *buf)
: addr(*addr), port(port), buf(buf) { }
~Packet() { pbuf_free(buf); }
u16_t read(void *dst, size_t count)
{
count = min((size_t)buf->tot_len, count);
auto n = pbuf_copy_partial(buf, dst, count, offset);
offset += n;
return n;
}
u16_t peek(void *dst, size_t count) const
{
count = min((size_t)buf->tot_len, count);
return pbuf_copy_partial(buf, dst, count, offset);
}
bool empty() { return offset >= buf->tot_len; }
};
Genode::Tslab<Packet, sizeof(Packet)*64> _packet_slab { &alloc };
/* Queue of received UDP packets */
Genode::Fifo<Packet> _packet_queue { };
/* destination addressing */
ip_addr_t _to_addr { };
u16_t _to_port = 0;
/**
* New sockets from accept not avaiable for UDP
*/
Open_result _accept_new_socket(Vfs::File_system &,
Genode::Allocator &,
Vfs::Vfs_handle **) override {
return Open_result::OPEN_ERR_UNACCESSIBLE; }
public:
friend class Udp_socket_dir_list;
friend class Udp_socket_dir_list::Element;
using Udp_socket_dir_list::Element::next;
Udp_socket_dir(unsigned num, Udp_proto_dir &proto_dir,
Genode::Allocator &alloc,
Genode::Entrypoint &,
udp_pcb *pcb)
: Socket_dir(num, alloc),
_proto_dir(proto_dir), _pcb(pcb ? pcb : udp_new())
{
ip_addr_set_zero(&_to_addr);
/* 'this' will be the argument to the LwIP recv callback */
udp_recv(_pcb, udp_recv_callback, this);
}
virtual ~Udp_socket_dir()
{
udp_remove(_pcb);
_pcb = NULL;
_proto_dir.release(this);
}
/**
* Stuff a packet in the queue and notify the handle
*/
void queue(ip_addr_t const *addr, u16_t port, pbuf *buf)
{
try {
Packet *pkt = new (_packet_slab) Packet(addr, port, buf);
_packet_queue.enqueue(*pkt);
} catch (...) {
Genode::warning("failed to queue UDP packet, dropping");
pbuf_free(buf);
}
process_io();
process_read_ready();
}
/**************************
** Socket_dir interface **
**************************/
bool read_ready(Lwip_file_handle &handle) override
{
switch (handle.kind) {
case Lwip_file_handle::DATA:
case Lwip_file_handle::REMOTE:
case Lwip_file_handle::PEEK:
return !_packet_queue.empty();
default:
break;
}
return true;
}
Read_result read(Lwip_file_handle &handle,
char *dst, file_size count,
file_size &out_count) override
{
Genode::Lock::Guard g { Lwip::lock() };
Read_result result = Read_result::READ_ERR_INVALID;
switch(handle.kind) {
case Lwip_file_handle::DATA: {
result = Read_result::READ_QUEUED;
_packet_queue.head([&] (Packet &pkt) {
out_count = pkt.read(dst, count);
if (pkt.empty()) {
_packet_queue.remove(pkt);
destroy(_packet_slab, &pkt);
}
result = Read_result::READ_OK;
});
break;
}
case Lwip_file_handle::PEEK:
_packet_queue.head([&] (Packet const &pkt) {
out_count = pkt.peek(dst, count);
result = Read_result::READ_OK;
});
break;
case Lwip_file_handle::LOCAL:
case Lwip_file_handle::BIND: {
if (count < ENDPOINT_STRLEN_MAX)
return Read_result::READ_ERR_INVALID;
char const *ip_str = ipaddr_ntoa(&_pcb->local_ip);
/* TODO: [IPv6]:port */
out_count = Genode::snprintf(dst, count, "%s:%d\n",
ip_str, _pcb->local_port);
return Read_result::READ_OK;
}
case Lwip_file_handle::CONNECT: {
/* check if the PCB was connected */
if (!ip_addr_isany(&_pcb->remote_ip))
out_count = Genode::snprintf(dst, count, "connected");
else
out_count = Genode::snprintf(dst, count, "not connected");
return Read_result::READ_OK;
}
case Lwip_file_handle::REMOTE:
if (count < ENDPOINT_STRLEN_MAX) {
Genode::error("VFS LwIP: accept file read buffer is too small");
result = Read_result::READ_ERR_INVALID;
} else
if (ip_addr_isany(&_pcb->remote_ip)) {
_packet_queue.head([&] (Packet &pkt) {
char const *ip_str = ipaddr_ntoa(&pkt.addr);
/* TODO: IPv6 */
out_count = Genode::snprintf(dst, count, "%s:%d\n",
ip_str, pkt.port);
result = Read_result::READ_OK;
});
} else {
char const *ip_str = ipaddr_ntoa(&_pcb->remote_ip);
/* TODO: [IPv6]:port */
out_count = Genode::snprintf(dst, count, "%s:%d\n",
ip_str, _pcb->remote_port);
result = Read_result::READ_OK;
}
break;
case Lwip_file_handle::LOCATION:
/*
* Print the location of this socket directory
*/
out_count = Genode::snprintf(
dst, count, "udp/%s\n", name().string());
return Read_result::READ_OK;
break;
default: break;
}
return result;
}
Write_result write(Lwip_file_handle &handle,
char const *src, file_size count,
file_size &out_count) override
{
Genode::Lock::Guard g { Lwip::lock() };
switch(handle.kind) {
case Lwip_file_handle::DATA: {
if (ip_addr_isany(&_to_addr)) break;
file_size remain = count;
while (remain) {
pbuf *buf = pbuf_alloc(PBUF_RAW, remain, PBUF_RAM);
pbuf_take(buf, src, buf->tot_len);
err_t err = udp_sendto(_pcb, buf, &_to_addr, _to_port);
pbuf_free(buf);
if (err != ERR_OK)
return Write_result::WRITE_ERR_IO;
remain -= buf->tot_len;
src += buf->tot_len;
}
out_count = count;
return Write_result::WRITE_OK;
}
case Lwip_file_handle::REMOTE: {
if (!ip_addr_isany(&_pcb->remote_ip)) {
return Write_result::WRITE_ERR_INVALID;
} else {
char buf[ENDPOINT_STRLEN_MAX];
Genode::strncpy(buf, src, min(count+1, sizeof(buf)));
_to_port = remove_port(buf);
out_count = count;
if (ipaddr_aton(buf, &_to_addr)) {
out_count = count;
return Write_result::WRITE_OK;
}
}
break;
}
case Lwip_file_handle::BIND: {
if (count < ENDPOINT_STRLEN_MAX) {
char buf[ENDPOINT_STRLEN_MAX];
ip_addr_t addr;
u16_t port;
Genode::strncpy(buf, src, min(count+1, sizeof(buf)));
port = remove_port(buf);
if (!ipaddr_aton(buf, &addr))
break;
err_t err = udp_bind(_pcb, &addr, port);
if (err == ERR_OK) {
out_count = count;
return Write_result::WRITE_OK;
}
return Write_result::WRITE_ERR_IO;
}
break;
}
case Lwip_file_handle::CONNECT: {
if (count < ENDPOINT_STRLEN_MAX) {
char buf[ENDPOINT_STRLEN_MAX];
Genode::strncpy(buf, src, min(count+1, sizeof(buf)));
_to_port = remove_port(buf);
if (!ipaddr_aton(buf, &_to_addr))
break;
err_t err = udp_connect(_pcb, &_to_addr, _to_port);
if (err != ERR_OK) {
Genode::error("lwIP: failed to connect UDP socket, error ", (int)-err);
return Write_result::WRITE_ERR_IO;
}
out_count = count;
return Write_result::WRITE_OK;
}
break;
}
default: break;
}
return Write_result::WRITE_ERR_INVALID;
}
};
/*********
** TCP **
*********/
class Lwip::Tcp_socket_dir final :
public Socket_dir,
private Tcp_socket_dir_list::Element
{
public:
struct Pcb_pending : Genode::List<Pcb_pending>::Element
{
tcp_pcb *pcb;
pbuf *buf = nullptr;
Pcb_pending(tcp_pcb *p) : pcb(p) { }
};
private:
/*
* Noncopyable
*/
Tcp_socket_dir(Tcp_socket_dir const &);
Tcp_socket_dir &operator = (Tcp_socket_dir const &);
Tcp_proto_dir &_proto_dir;
Genode::Entrypoint &_ep;
typedef Genode::List<Pcb_pending> Pcb_pending_list;
Pcb_pending_list _pcb_pending { };
tcp_pcb *_pcb;
/* queue of received data */
pbuf *_recv_pbuf = nullptr;
u16_t _recv_off = 0;
Open_result _accept_new_socket(Vfs::File_system &fs,
Genode::Allocator &alloc,
Vfs::Vfs_handle **out_handle) override
{
*out_handle = new (alloc) Lwip_file_handle(
fs, alloc, Vfs::Directory_service::OPEN_MODE_RDONLY,
*this, Lwip_file_handle::PENDING);
return Open_result::OPEN_OK;
}
public:
friend class Tcp_socket_dir_list;
friend class Tcp_socket_dir_list::Element;
using Tcp_socket_dir_list::Element::next;
State state;
Tcp_socket_dir(unsigned num, Tcp_proto_dir &proto_dir,
Genode::Allocator &alloc,
Genode::Entrypoint &ep,
tcp_pcb *pcb)
: Socket_dir(num, alloc), _proto_dir(proto_dir),
_ep(ep), _pcb(pcb ? pcb : tcp_new()), state(pcb ? READY : NEW)
{
/* 'this' will be the argument to LwIP callbacks */
tcp_arg(_pcb, this);
tcp_recv(_pcb, tcp_recv_callback);
tcp_sent(_pcb, tcp_sent_callback);
tcp_err(_pcb, tcp_err_callback);
}
~Tcp_socket_dir()
{
tcp_arg(_pcb, NULL);
for (Pcb_pending *p = _pcb_pending.first(); p; p->next()) {
destroy(alloc, p);
}
if (_pcb != NULL) {
tcp_arg(_pcb, NULL);
tcp_close(_pcb);
}
_proto_dir.release(this);
}
/**
* Accept new connection from callback
*/
err_t accept(struct tcp_pcb *newpcb, err_t)
{
Pcb_pending *elem = new (alloc) Pcb_pending(newpcb);
_pcb_pending.insert(elem);
tcp_backlog_delayed(newpcb);
tcp_arg(newpcb, elem);
tcp_recv(newpcb, tcp_delayed_recv_callback);
process_io();
process_read_ready();
return ERR_OK;
}
/**
* chain a buffer to the queue
*/
void recv(struct pbuf *buf)
{
if (_recv_pbuf && buf) {
pbuf_cat(_recv_pbuf, buf);
} else {
_recv_pbuf = buf;
}
}
/**
* Close the connection by error
*
* Triggered by error callback, usually
* just by an aborted connection.
* The corresponding pcb is already freed
* when this callback is called!
*/
void error()
{
state = CLOSED;
_pcb = NULL;
/* churn the application */
process_io();
process_read_ready();
}
/**
* Close the connection
*
* Can be triggered by remote shutdown via callback
*/
void shutdown()
{
state = CLOSING;
if (_recv_pbuf)
return;
if (_pcb) {
tcp_arg(_pcb, NULL);
tcp_close(_pcb);
state = CLOSED;
_pcb = NULL;
}
}
/**************************
** Socket_dir interface **
**************************/
bool read_ready(Lwip_file_handle &handle) override
{
switch (handle.kind) {
case Lwip_file_handle::DATA:
case Lwip_file_handle::PEEK:
switch (state) {
case READY:
return _recv_pbuf != NULL;
case CLOSING:
case CLOSED:
/* time for the application to find out */
return true;
default: break;
}
break;
case Lwip_file_handle::ACCEPT:
case Lwip_file_handle::PENDING:
return _pcb_pending.first() != nullptr;
case Lwip_file_handle::BIND:
return state != NEW;
case Lwip_file_handle::REMOTE:
switch (state) {
case NEW:
case BOUND:
case LISTEN:
break;
default:
return true;
}
break;
case Lwip_file_handle::CONNECT:
/*
* The connect file is considered readable when the socket is
* writeable (connected or error).
*/
return ((state == READY) || (state == CLOSED));
case Lwip_file_handle::LOCATION:
case Lwip_file_handle::LOCAL:
return true;
default: break;
}
return false;
}
Read_result read(Lwip_file_handle &handle,
char *dst, file_size count,
file_size &out_count) override
{
Genode::Lock::Guard g { Lwip::lock() };
switch(handle.kind) {
case Lwip_file_handle::DATA:
{
if (_recv_pbuf == nullptr) {
/*
* queue the read if the PCB is active and
* there is nothing to read, otherwise return
* zero to indicate the connection is closed
*/
return (state == READY)
? Read_result::READ_QUEUED
: Read_result::READ_OK;
}
u16_t const ucount = count;
u16_t const n = pbuf_copy_partial(_recv_pbuf, dst, ucount, _recv_off);
_recv_off += n;
{
u16_t new_off;
pbuf *new_head = pbuf_skip(_recv_pbuf, _recv_off, &new_off);
if (new_head != NULL && new_head != _recv_pbuf) {
/* increment the references on the new head */
pbuf_ref(new_head);
/* free the buffers chained to the old head */
pbuf_free(_recv_pbuf);
}
if (!new_head)
pbuf_free(_recv_pbuf);
_recv_pbuf = new_head;
_recv_off = new_off;
}
/* ACK the remote */
if (_pcb)
tcp_recved(_pcb, n);
if (state == CLOSING)
shutdown();
out_count = n;
return Read_result::READ_OK;
}
break;
case Lwip_file_handle::PEEK:
if (_recv_pbuf != nullptr) {
u16_t const ucount = count;
u16_t const n = pbuf_copy_partial(_recv_pbuf, dst, ucount, _recv_off);
out_count = n;
}
return Read_result::READ_OK;
case Lwip_file_handle::REMOTE:
if (state == READY) {
if (count < ENDPOINT_STRLEN_MAX)
return Read_result::READ_ERR_INVALID;
char const *ip_str = ipaddr_ntoa(&_pcb->remote_ip);
/* TODO: [IPv6]:port */
out_count = Genode::snprintf(dst, count, "%s:%d\n",
ip_str, _pcb->remote_port);
return Read_result::READ_OK;
} else if (state == CLOSED) {
return Read_result::READ_OK;
}
break;
case Lwip_file_handle::PENDING: {
if (Pcb_pending *pp = _pcb_pending.first()) {
Tcp_socket_dir &new_dir = _proto_dir.alloc_socket(alloc, pp->pcb);
new_dir._recv_pbuf = pp->buf;
handles.remove(&handle);
handle.socket = &new_dir;
new_dir.handles.insert(&handle);
tcp_backlog_accepted(pp->pcb);
_pcb_pending.remove(pp);
destroy(alloc, pp);
handle.kind = Lwip_file_handle::LOCATION;
/* read the location of the new socket directory */
Lwip::lock().unlock();
Read_result result = handle.read(dst, count, out_count);
Lwip::lock().lock();
return result;
}
return Read_result::READ_QUEUED;
}
case Lwip_file_handle::LOCATION:
/*
* Print the location of this socket directory
*/
out_count = Genode::snprintf(
dst, count, "tcp/%s\n", name().string());
return Read_result::READ_OK;
break;
case Lwip_file_handle::ACCEPT: {
/*
* Print the number of pending connections
*/
int pending_count = 0;
for (Pcb_pending *p = _pcb_pending.first(); p; p = p->next())
++pending_count;
out_count = Genode::snprintf(
dst, count, "%d\n", pending_count);
return Read_result::READ_OK;
}
case Lwip_file_handle::LOCAL:
case Lwip_file_handle::BIND:
if (state != CLOSED) {
if (count < ENDPOINT_STRLEN_MAX)
return Read_result::READ_ERR_INVALID;
char const *ip_str = ipaddr_ntoa(&_pcb->local_ip);
/* TODO: [IPv6]:port */
out_count = Genode::snprintf(
dst, count, "%s:%d\n", ip_str, _pcb->local_port);
return Read_result::READ_OK;
}
break;
case Lwip_file_handle::CONNECT:
switch (state) {
case READY:
out_count = Genode::snprintf(dst, count, "connected");
break;
default:
out_count = Genode::snprintf(dst, count, "connection refused");
break;
}
return Read_result::READ_OK;
case Lwip_file_handle::LISTEN:
case Lwip_file_handle::INVALID: break;
}
return Read_result::READ_ERR_INVALID;
}
Write_result write(Lwip_file_handle &handle,
char const *src, file_size count,
file_size &out_count) override
{
Genode::Lock::Guard g { Lwip::lock() };
if (_pcb == NULL) {
/* socket is closed */
return Write_result::WRITE_ERR_IO;
}
switch(handle.kind) {
case Lwip_file_handle::DATA:
if (state == READY) {
Write_result res = Write_result::WRITE_ERR_WOULD_BLOCK;
file_size out = 0;
/*
* write in a loop to account for LwIP chunking
* and the availability of send buffer
*/
while (count && tcp_sndbuf(_pcb)) {
u16_t n = min(count, tcp_sndbuf(_pcb));
/* queue data to outgoing TCP buffer */
err_t err = tcp_write(_pcb, src, n, TCP_WRITE_FLAG_COPY);
if (err != ERR_OK) {
Genode::error("lwIP: tcp_write failed, error ", (int)-err);
res = Write_result::WRITE_ERR_IO;
break;
}
count -= n;
src += n;
out += n;
/* pending_ack += n; */
res = Write_result::WRITE_OK;
}
/* send queued data */
if (out > 0) tcp_output(_pcb);
out_count = out;
return res;
}
break;
case Lwip_file_handle::BIND:
if ((state == NEW) && (count < ENDPOINT_STRLEN_MAX)) {
char buf[ENDPOINT_STRLEN_MAX];
ip_addr_t addr;
u16_t port = 0;
Genode::strncpy(buf, src, min(count+1, sizeof(buf)));
port = remove_port(buf);
if (!ipaddr_aton(buf, &addr))
break;
err_t err = tcp_bind(_pcb, &addr, port);
if (err == ERR_OK) {
state = BOUND;
out_count = count;
return Write_result::WRITE_OK;
}
}
break;
case Lwip_file_handle::CONNECT:
if (((state == NEW) || (state == BOUND)) && (count < ENDPOINT_STRLEN_MAX-1)) {
char buf[ENDPOINT_STRLEN_MAX];
ip_addr_t addr;
u16_t port = 0;
Genode::strncpy(buf, src, min(count+1, sizeof(buf)));
port = remove_port(buf);
if (!ipaddr_aton(buf, &addr))
break;
err_t err = tcp_connect(_pcb, &addr, port, tcp_connect_callback);
if (err != ERR_OK) {
Genode::error("lwIP: failed to connect TCP socket, error ", (int)-err);
return Write_result::WRITE_ERR_IO;
}
state = CONNECT;
out_count = count;
return Write_result::WRITE_OK;
}
break;
case Lwip_file_handle::LISTEN:
if ((state == BOUND) && (count < 7)) {
unsigned long backlog = TCP_DEFAULT_LISTEN_BACKLOG;
char buf[8];
Genode::strncpy(buf, src, min(count+1, sizeof(buf)));
Genode::ascii_to_unsigned(buf, backlog, 10);
/* this replaces the PCB so set the callbacks again */
_pcb = tcp_listen_with_backlog(_pcb, backlog);
tcp_arg(_pcb, this);
tcp_accept(_pcb, tcp_accept_callback);
state = LISTEN;
out_count = count;
return Write_result::WRITE_OK;
}
break;
default: break;
}
return Write_result::WRITE_ERR_INVALID;
}
};
/********************
** LwIP callbacks **
********************/
namespace Lwip {
extern "C" {
static
void udp_recv_callback(void *arg, struct udp_pcb*, struct pbuf *buf, const ip_addr_t *addr, u16_t port)
{
if (arg) {
Lwip::Udp_socket_dir *socket_dir =
static_cast<Lwip::Udp_socket_dir *>(arg);
socket_dir->queue(addr, port, buf);
} else {
pbuf_free(buf);
}
}
static
err_t tcp_connect_callback(void *arg, struct tcp_pcb *pcb, err_t)
{
if (!arg) {
tcp_abort(pcb);
return ERR_ABRT;
}
Lwip::Tcp_socket_dir *socket_dir = static_cast<Lwip::Tcp_socket_dir *>(arg);
socket_dir->state = Lwip::Tcp_socket_dir::READY;
socket_dir->process_io();
socket_dir->process_read_ready();
return ERR_OK;
}
static
err_t tcp_accept_callback(void *arg, struct tcp_pcb *newpcb, err_t err)
{
if (!arg) {
tcp_abort(newpcb);
return ERR_ABRT;
}
Lwip::Tcp_socket_dir *socket_dir = static_cast<Lwip::Tcp_socket_dir *>(arg);
err_t e = socket_dir->accept(newpcb, err);
return e;
};
static
err_t tcp_recv_callback(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t)
{
if (!arg) {
tcp_abort(pcb);
return ERR_ABRT;
}
Lwip::Tcp_socket_dir *socket_dir = static_cast<Lwip::Tcp_socket_dir *>(arg);
if (p == NULL) {
socket_dir->shutdown();
} else {
socket_dir->recv(p);
}
socket_dir->process_io();
socket_dir->process_read_ready();
return ERR_OK;
}
static
err_t tcp_delayed_recv_callback(void *arg, struct tcp_pcb *pcb, struct pbuf *buf, err_t)
{
if (!arg) {
tcp_abort(pcb);
return ERR_ABRT;
}
Lwip::Tcp_socket_dir::Pcb_pending *pending =
static_cast<Lwip::Tcp_socket_dir::Pcb_pending *>(arg);
if (pending->buf && buf) {
pbuf_cat(pending->buf, buf);
} else {
pending->buf = buf;
}
return ERR_OK;
};
/**
* This would be the ACK callback, we could defer sync completion
* until then, but performance is expected to be unacceptable.
*/
static
err_t tcp_sent_callback(void *arg, struct tcp_pcb *pcb, u16_t)
{
if (!arg) {
tcp_abort(pcb);
return ERR_ABRT;
}
Lwip::Tcp_socket_dir *socket_dir = static_cast<Lwip::Tcp_socket_dir *>(arg);
socket_dir->process_io();
return ERR_OK;
}
static
void tcp_err_callback(void *arg, err_t)
{
if (!arg) return;
Lwip::Tcp_socket_dir *socket_dir = static_cast<Lwip::Tcp_socket_dir *>(arg);
socket_dir->error();
/* the error is ERR_ABRT or ERR_RST, both end the session */
}
}
}
/*********************
** VFS file-system **
*********************/
class Lwip::File_system final : public Vfs::File_system, public Lwip::Directory
{
private:
Genode::Entrypoint &_ep;
/**
* LwIP connection to Nic service
*/
struct Vfs_netif : Lwip::Nic_netif
{
Tcp_proto_dir tcp_dir;
Udp_proto_dir udp_dir;
Nameserver_registry nameserver_handles { };
typedef Genode::Fifo_element<Vfs_handle> Handle_element;
typedef Genode::Fifo<Vfs_netif::Handle_element> Handle_queue;
Handle_queue blocked_handles { };
Genode::Mutex blocked_handles_mutex { };
Vfs_netif(Vfs::Env &vfs_env,
Genode::Xml_node config)
: Lwip::Nic_netif(vfs_env.env(), vfs_env.alloc(), config),
tcp_dir(vfs_env), udp_dir(vfs_env)
{ }
~Vfs_netif()
{
/* free the allocated qeueue elements */
status_callback();
}
void enqueue(Vfs_handle &handle)
{
Handle_element *elem = new (handle.alloc())
Handle_element(handle);
Genode::Mutex::Guard guard(blocked_handles_mutex);
blocked_handles.enqueue(*elem);
}
/**
* Wake the application when the interface changes.
*/
void status_callback() override
{
tcp_dir.notify();
udp_dir.notify();
nameserver_handles.for_each([&] (Lwip_nameserver_handle &h) {
h.io_progress_response(); });
Genode::Mutex::Guard guard(blocked_handles_mutex);
blocked_handles.dequeue_all([] (Handle_element &elem) {
Vfs_handle &handle = elem.object();
destroy(elem.object().alloc(), &elem);
handle.io_progress_response();
});
}
void drop(Vfs_handle &handle)
{
Genode::Mutex::Guard guard(blocked_handles_mutex);
blocked_handles.for_each([&] (Handle_element &elem) {
if (&elem.object() == &handle) {
blocked_handles.remove(elem);
destroy(elem.object().alloc(), &elem);
}
});
}
} _netif;
/**
* Walk a path to a protocol directory and apply procedure
*/
template <typename PROC>
void apply_walk(char const *path, PROC const proc)
{
if (Genode::strcmp(path, "tcp", 3) == 0)
proc(path+3, _netif.tcp_dir);
else
if (Genode::strcmp(path, "udp", 3) == 0)
proc(path+3, _netif.udp_dir);
}
static bool match_address(char const *name) {
return (!strcmp(name, "address")); }
static bool match_netmask(char const *name) {
return (!strcmp(name, "netmask")); }
static bool match_nameserver(char const *name) {
return (!strcmp(name, "nameserver")); }
public:
File_system(Vfs::Env &vfs_env, Genode::Xml_node config)
: _ep(vfs_env.env().ep()), _netif(vfs_env, config)
{ }
/**
* Reconfigure the LwIP Nic interface with the VFS config hook
*/
void apply_config(Genode::Xml_node const &node) override {
_netif.configure(node); }
/*********************
** Lwip::Directory **
*********************/
Read_result readdir(char *, file_size, file_size &) override
{
Genode::warning(__func__, " NOT_IMPLEMENTED");
return Read_result::READ_ERR_INVALID;
};
/***********************
** Directory_service **
***********************/
char const *leaf_path(char const *path) override
{
if (*path == '/') ++path;
if (match_address(path)
|| match_netmask(path)
|| match_nameserver(path))
return path;
char const *r = nullptr;
apply_walk(path, [&] (char const *subpath, Protocol_dir &dir) {
if (dir.leaf_path(subpath)) r = path;
});
return r;
}
Stat_result stat(char const *path, Stat &st) override
{
if (*path == '/') ++path;
st = Stat { };
st.device = (Genode::addr_t)this;
if (match_address(path) || match_netmask(path)) {
st = { .size = ADDRESS_FILE_SIZE,
.type = Node_type::TRANSACTIONAL_FILE,
.rwx = Node_rwx::rw(),
.inode = (Genode::addr_t)this,
.device = 0,
.modification_time = { 0 } };
return STAT_OK;
}
if (match_nameserver(path)) {
st = { .size = IPADDR_STRLEN_MAX,
.type = Node_type::TRANSACTIONAL_FILE,
.rwx = Node_rwx::rw(),
.inode = 0,
.device = 0,
.modification_time = { 0 } };
return STAT_OK;
}
Stat_result r = STAT_ERR_NO_PERM;
apply_walk(path, [&] (char const *subpath, Protocol_dir &dir) {
r = dir.stat(subpath, st);
});
return r;
}
bool directory(char const *path) override
{
if (*path == '/') ++path;
if (*path == '\0') return true;
bool r = false;
apply_walk(path, [&] (char const *subpath, Protocol_dir &dir) {
r = dir.directory(subpath);
});
return r;
}
Open_result open(char const *path,
unsigned mode,
Vfs_handle **out_handle,
Allocator &alloc) override
{
if (*path == '/') ++path;
/**
* No files may be created here
*/
if (mode & OPEN_MODE_CREATE) return OPEN_ERR_NO_PERM;
if (match_address(path)) {
*out_handle = new (alloc)
Lwip_address_handle(*this, alloc, _netif.lwip_netif());
return OPEN_OK;
}
if (match_netmask(path)) {
*out_handle = new (alloc)
Lwip_netmask_handle(*this, alloc, _netif.lwip_netif());
return OPEN_OK;
}
if (match_nameserver(path)) {
*out_handle = new (alloc)
Lwip_nameserver_handle(*this, alloc, _netif.nameserver_handles);
return OPEN_OK;
}
Open_result r = OPEN_ERR_UNACCESSIBLE;
apply_walk(path, [&] (char const *subpath, Protocol_dir &dir) {
r = dir.open(*this, subpath, mode, out_handle, alloc);
});
return r;
}
Opendir_result opendir(char const *path, bool create,
Vfs_handle **out_handle,
Allocator &alloc) override
{
/**
* No directories may be created here
*/
if (create) return OPENDIR_ERR_PERMISSION_DENIED;
if (*path == '/') ++path;
try {
if (*path == '\0') {
*out_handle = new (alloc) Lwip_dir_handle(*this, alloc, *this);
return OPENDIR_OK;
}
Opendir_result r = OPENDIR_ERR_LOOKUP_FAILED;
apply_walk(path, [&] (char const *subpath, Protocol_dir &dir) {
r = dir.opendir(*this, subpath, out_handle, alloc);
});
return r;
}
catch (Genode::Out_of_ram) { return OPENDIR_ERR_OUT_OF_RAM; }
catch (Genode::Out_of_caps) { return OPENDIR_ERR_OUT_OF_CAPS; }
}
void close(Vfs_handle *vfs_handle) override
{
Socket_dir *socket = nullptr;
/* if the inteface is down this handle may be queued */
_netif.drop(*vfs_handle);
if (Lwip_handle *handle = dynamic_cast<Lwip_handle*>(vfs_handle)) {
if (Lwip_file_handle *file_handle = dynamic_cast<Lwip_file_handle*>(handle)) {
socket = file_handle->socket;
}
destroy(handle->alloc(), handle);
} else {
Genode::error("refusing to destroy strange handle");
}
/* destroy sockets that are not referenced by any handles */
if (socket && !socket->handles.first()) {
destroy(socket->alloc, socket);
}
}
Unlink_result unlink(char const *) override {
return UNLINK_ERR_NO_PERM; }
/********************************
** File I/O service interface **
********************************/
Write_result write(Vfs_handle *vfs_handle,
char const *src, file_size count,
file_size &out_count) override
{
Write_result res = Write_result::WRITE_ERR_INVALID;
out_count = 0;
if ((vfs_handle->status_flags() & OPEN_MODE_ACCMODE) == OPEN_MODE_RDONLY)
return Write_result::WRITE_ERR_INVALID;
if (Lwip_handle *handle = dynamic_cast<Lwip_handle*>(vfs_handle)) {
res = handle->write(src, count, out_count);
if (res == WRITE_ERR_WOULD_BLOCK) throw Insufficient_buffer();
}
return res;
}
Read_result complete_read(Vfs_handle *vfs_handle,
char *dst, file_size count,
file_size &out_count) override
{
/*
* LwIP buffer operations are limited to sizes that
* can be expressed in sixteen bits
*/
count = Genode::min(count, 0xffffU);
out_count = 0;
if ((!vfs_handle) ||
((vfs_handle->status_flags() & OPEN_MODE_ACCMODE) == OPEN_MODE_WRONLY)) {
return Read_result::READ_ERR_INVALID;
}
if (Lwip_handle *handle = dynamic_cast<Lwip_handle*>(vfs_handle))
return handle->read(dst, count, out_count);
return Read_result::READ_ERR_INVALID;
}
/**
* All reads are unavailable while the network is down
*/
bool queue_read(Vfs_handle *vfs_handle, file_size) override
{
if (_netif.ready()) return true;
/* handle must be woken when the interface comes up */
_netif.enqueue(*vfs_handle);
return false;
}
bool read_ready(Vfs_handle *vfs_handle) override
{
if (Lwip_file_handle *handle = dynamic_cast<Lwip_file_handle*>(vfs_handle)) {
if (handle->socket)
return handle->socket->read_ready(*handle);
}
/*
* in this case the polled file is a 'new_socket'
* or a file with no associated socket
*/
return true;
}
bool notify_read_ready(Vfs_handle *vfs_handle) override
{
if (Lwip_file_handle *handle = dynamic_cast<Lwip_file_handle*>(vfs_handle))
return handle->notify_read_ready();
return false;
}
bool check_unblock(Vfs_handle*, bool, bool, bool) override
{
Genode::error("VFS lwIP: ",__func__," not implemented");
return true;
}
Sync_result complete_sync(Vfs_handle *vfs_handle) override
{
return (dynamic_cast<Lwip_file_handle*>(vfs_handle))
? SYNC_OK : SYNC_ERR_INVALID;
}
/***********************
** File system stubs **
***********************/
Rename_result rename(char const *, char const *) override {
return RENAME_ERR_NO_PERM; }
file_size num_dirent(char const *) override {
return 0; }
Dataspace_capability dataspace(char const *) override {
return Dataspace_capability(); }
void release(char const *, Dataspace_capability) override { };
Ftruncate_result ftruncate(Vfs_handle *, file_size) override
{
/* report ok because libc always executes ftruncate() when opening rw */
return FTRUNCATE_OK;
}
char const *type() override { return "lwip"; }
};
extern "C" Vfs::File_system_factory *vfs_file_system_factory(void)
{
struct Factory : Vfs::File_system_factory
{
Genode::Constructible<Timer::Connection> timer { };
Vfs::File_system *create(Vfs::Env &vfs_env, Genode::Xml_node config) override
{
if (!timer.constructed()) {
timer.construct(vfs_env.env(), "vfs_lwip");
Lwip::genode_init(vfs_env.alloc(), *timer);
}
return new (vfs_env.alloc()) Lwip::File_system(vfs_env, config);
}
};
static Factory f;
return &f;
}