netstack: Move the smolnetd source
This commit is contained in:
@@ -0,0 +1,539 @@
|
||||
#[macro_use]
|
||||
mod nodes;
|
||||
mod notifier;
|
||||
|
||||
use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::File;
|
||||
use std::io::{ErrorKind, Read, Write};
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
use std::str;
|
||||
use std::str::FromStr;
|
||||
use syscall;
|
||||
use syscall::data::Stat;
|
||||
use syscall::flag::{MODE_DIR, MODE_FILE};
|
||||
use syscall::{
|
||||
Error as SyscallError, EventFlags as SyscallEventFlags, Packet as SyscallPacket,
|
||||
Result as SyscallResult, SchemeMut,
|
||||
};
|
||||
|
||||
use crate::link::DeviceList;
|
||||
use crate::router::route_table::{RouteTable, Rule};
|
||||
|
||||
use self::nodes::*;
|
||||
use self::notifier::*;
|
||||
use super::{post_fevent, Interface};
|
||||
use redox_netstack::error::{Error, Result};
|
||||
|
||||
const WRITE_BUFFER_MAX_SIZE: usize = 0xffff;
|
||||
|
||||
fn gateway_cidr() -> IpCidr {
|
||||
// TODO: const fn
|
||||
IpCidr::new(IpAddress::v4(0, 0, 0, 0), 0)
|
||||
}
|
||||
|
||||
fn parse_route(value: &str, route_table: &RouteTable) -> SyscallResult<Rule> {
|
||||
let mut parts = value.split_whitespace();
|
||||
let cidr_str = parts.next().ok_or(SyscallError::new(syscall::EINVAL))?;
|
||||
let cidr = match cidr_str {
|
||||
"default" => gateway_cidr(),
|
||||
cidr_str => cidr_str
|
||||
.parse()
|
||||
.map_err(|_| SyscallError::new(syscall::EINVAL))?,
|
||||
};
|
||||
|
||||
let via: IpAddress = match parts.next().ok_or(SyscallError::new(syscall::EINVAL))? {
|
||||
"via" => parts
|
||||
.next()
|
||||
.ok_or(SyscallError::new(syscall::EINVAL))?
|
||||
.parse()
|
||||
.map_err(|_| SyscallError::new(syscall::EINVAL))?,
|
||||
_ => return Err(SyscallError::new(syscall::EINVAL)),
|
||||
};
|
||||
|
||||
if !via.is_unicast() {
|
||||
return Err(SyscallError::new(syscall::EINVAL));
|
||||
}
|
||||
|
||||
let rule = route_table
|
||||
.lookup_rule(&via)
|
||||
.ok_or(SyscallError::new(syscall::EINVAL))?;
|
||||
|
||||
Ok(Rule::new(cidr, Some(via), rule.dev.clone(), rule.src))
|
||||
}
|
||||
|
||||
fn mk_root_node(
|
||||
iface: Interface,
|
||||
notifier: NotifierRef,
|
||||
dns_config: DNSConfigRef,
|
||||
route_table: Rc<RefCell<RouteTable>>,
|
||||
devices: Rc<RefCell<DeviceList>>,
|
||||
) -> CfgNodeRef {
|
||||
cfg_node! {
|
||||
"resolv" => {
|
||||
"nameserver" => {
|
||||
rw [dns_config, notifier] (Option<Ipv4Address>, None)
|
||||
|| {
|
||||
format!("{}\n", dns_config.borrow().name_server)
|
||||
}
|
||||
|cur_value, line| {
|
||||
if cur_value.is_none() {
|
||||
let ip = Ipv4Address::from_str(line.trim())
|
||||
.map_err(|_| SyscallError::new(syscall::EINVAL))?;
|
||||
if ip.is_broadcast() || ip.is_multicast() || ip.is_unspecified() {
|
||||
return Err(SyscallError::new(syscall::EINVAL));
|
||||
}
|
||||
*cur_value = Some(ip);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(SyscallError::new(syscall::EINVAL))
|
||||
}
|
||||
}
|
||||
|cur_value| {
|
||||
if let Some(ip) = *cur_value {
|
||||
dns_config.borrow_mut().name_server = ip;
|
||||
notifier.borrow_mut().schedule_notify("resolv/nameserver");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
},
|
||||
"route" => {
|
||||
"list" => {
|
||||
ro [route_table] || {
|
||||
format!("{}", route_table.borrow())
|
||||
}
|
||||
},
|
||||
"add" => {
|
||||
wo [iface, notifier, route_table] (Option<Rule>, None)
|
||||
|cur_value, line| {
|
||||
if cur_value.is_none() {
|
||||
let route = parse_route(line, &route_table.borrow())?;
|
||||
*cur_value = Some(route);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(SyscallError::new(syscall::EINVAL))
|
||||
}
|
||||
}
|
||||
|cur_value| {
|
||||
if let Some(route) = cur_value.take() {
|
||||
route_table.borrow_mut().insert_rule(route);
|
||||
notifier.borrow_mut().schedule_notify("route/list");
|
||||
Ok(())
|
||||
} else {
|
||||
Err(SyscallError::new(syscall::EINVAL))
|
||||
}
|
||||
}
|
||||
},
|
||||
"rm" => {
|
||||
wo [iface, notifier, route_table] (Option<IpCidr>, None)
|
||||
|cur_value, line| {
|
||||
if cur_value.is_none() {
|
||||
match line.parse() {
|
||||
Ok(cidr) => {
|
||||
*cur_value = Some(cidr);
|
||||
Ok(())
|
||||
}
|
||||
Err(_) => Err(SyscallError::new(syscall::EINVAL))
|
||||
}
|
||||
} else {
|
||||
Err(SyscallError::new(syscall::EINVAL))
|
||||
}
|
||||
}
|
||||
|cur_value| {
|
||||
if let Some(cidr) = *cur_value {
|
||||
route_table.borrow_mut().remove_rule(cidr);
|
||||
notifier.borrow_mut().schedule_notify("route/list");
|
||||
Ok(())
|
||||
} else {
|
||||
Err(SyscallError::new(syscall::EINVAL))
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
"ifaces" => {
|
||||
"eth0" => {
|
||||
"mac" => {
|
||||
rw [iface, notifier, devices] (Option<EthernetAddress>, None)
|
||||
|| {
|
||||
match devices.borrow().get("eth0") {
|
||||
Some(dev) => {
|
||||
match dev.mac_address() {
|
||||
Some(addr) => format!("{addr}\n"),
|
||||
None => "Not configured\n".into(),
|
||||
}
|
||||
}
|
||||
None => "Device not found\n".into(),
|
||||
}
|
||||
}
|
||||
|cur_value, line| {
|
||||
if cur_value.is_none() {
|
||||
let mac = EthernetAddress::from_str(line).
|
||||
map_err(|_| SyscallError::new(syscall::EINVAL))?;
|
||||
if !mac.is_unicast() {
|
||||
return Err(SyscallError::new(syscall::EINVAL));
|
||||
}
|
||||
*cur_value = Some(mac);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(SyscallError::new(syscall::EINVAL))
|
||||
}
|
||||
}
|
||||
|cur_value| {
|
||||
if let Some(mac) = *cur_value {
|
||||
if let Some(dev) = devices.borrow_mut().get_mut("eth0") {
|
||||
dev.set_mac_address(mac);
|
||||
notifier.borrow_mut().schedule_notify("ifaces/eth0/mac");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
"addr" => {
|
||||
"list" => {
|
||||
ro [devices]
|
||||
|| {
|
||||
let res = match devices.borrow().get("eth0") {
|
||||
Some(dev) => {
|
||||
match dev.ip_address() {
|
||||
Some(addr) => format!("{addr}\n"),
|
||||
None => "Not configured\n".into(),
|
||||
}
|
||||
}
|
||||
None => "Device not found\n".into(),
|
||||
};
|
||||
res
|
||||
}
|
||||
},
|
||||
"set" => {
|
||||
wo [iface, notifier, devices, route_table] (Option<IpCidr>, None)
|
||||
|cur_value, line| {
|
||||
if cur_value.is_none() {
|
||||
let cidr = IpCidr::from_str(line)
|
||||
.map_err(|_| SyscallError::new(syscall::EINVAL))?;
|
||||
if !cidr.address().is_unicast() {
|
||||
return Err(SyscallError::new(syscall::EINVAL));
|
||||
}
|
||||
*cur_value = Some(cidr);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(SyscallError::new(syscall::EINVAL))
|
||||
}
|
||||
}
|
||||
|cur_value| {
|
||||
// TODO: Multiple IPs
|
||||
if let Some(cidr) = cur_value.take() {
|
||||
if let Some(dev) = devices.borrow_mut().get_mut("eth0") {
|
||||
|
||||
let mut route_table = route_table.borrow_mut();
|
||||
if let Some(old_addr) = dev.ip_address() {
|
||||
let IpCidr::Ipv4(old_v4_cidr) = old_addr;
|
||||
let old_network = IpCidr::Ipv4(old_v4_cidr.network());
|
||||
|
||||
route_table.remove_rule(old_network);
|
||||
route_table.change_src(old_addr.address(), cidr.address());
|
||||
iface.borrow_mut().update_ip_addrs(|addrs| addrs.retain(|addr| *addr != old_addr))
|
||||
}
|
||||
|
||||
dev.set_ip_address(cidr);
|
||||
// FIXME: Here, the insert 0 is a workaround to let UDP sockets
|
||||
// work with this interface only.
|
||||
// Smoltcp takes the first ip address when looking for a source
|
||||
// ip address when sending UDP packets.
|
||||
// This behavior will have to be fixed as it's our route table
|
||||
// job to find give this source.
|
||||
iface.borrow_mut().update_ip_addrs(|addrs| addrs.insert(0, cidr).unwrap());
|
||||
|
||||
let IpCidr::Ipv4(v4_cidr) = cidr;
|
||||
let network_cidr = IpCidr::Ipv4(v4_cidr.network());
|
||||
route_table.insert_rule(Rule::new(network_cidr, None, dev.name().clone(), cidr.address()))
|
||||
}
|
||||
notifier.borrow_mut().schedule_notify("ifaces/eth0/addr/list");
|
||||
notifier.borrow_mut().schedule_notify("route/list");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DNSConfig {
|
||||
name_server: Ipv4Address,
|
||||
}
|
||||
|
||||
type DNSConfigRef = Rc<RefCell<DNSConfig>>;
|
||||
|
||||
struct NetCfgFile {
|
||||
path: String,
|
||||
is_dir: bool,
|
||||
is_writable: bool,
|
||||
is_readable: bool,
|
||||
node_writer: Option<Box<dyn NodeWriter>>,
|
||||
read_buf: Vec<u8>,
|
||||
write_buf: Vec<u8>,
|
||||
pos: usize,
|
||||
uid: u32,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
impl NetCfgFile {
|
||||
fn commit(&mut self) -> SyscallResult<()> {
|
||||
if let Some(ref mut node_writer) = self.node_writer {
|
||||
if !self.write_buf.is_empty() {
|
||||
let line = str::from_utf8(&self.write_buf)
|
||||
.or_else(|_| Err(SyscallError::new(syscall::EINVAL)))?;
|
||||
node_writer.write_line(line)?;
|
||||
}
|
||||
node_writer.commit()?;
|
||||
self.write_buf.clear();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn consume_lines(&mut self) -> SyscallResult<()> {
|
||||
if let Some(ref mut node_writer) = self.node_writer {
|
||||
let mut swap_with = None;
|
||||
{
|
||||
let mut lines = self.write_buf.split(|&c| c == b'\n');
|
||||
if let Some(mut cur_line) = lines.next() {
|
||||
let mut consumed = false;
|
||||
for next_line in lines {
|
||||
let line = str::from_utf8(cur_line)
|
||||
.or_else(|_| Err(SyscallError::new(syscall::EINVAL)))?;
|
||||
trace!("writing line {}", line);
|
||||
node_writer.write_line(line)?;
|
||||
cur_line = next_line;
|
||||
consumed = true;
|
||||
}
|
||||
if consumed {
|
||||
swap_with = Some(From::from(cur_line))
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(ref mut new_vec) = swap_with {
|
||||
mem::swap(&mut self.write_buf, new_vec);
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(SyscallError::new(syscall::EBADF))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NetCfgScheme {
|
||||
scheme_file: File,
|
||||
next_fd: usize,
|
||||
files: BTreeMap<usize, NetCfgFile>,
|
||||
root_node: CfgNodeRef,
|
||||
notifier: NotifierRef,
|
||||
}
|
||||
|
||||
impl NetCfgScheme {
|
||||
pub fn new(
|
||||
iface: Interface,
|
||||
scheme_file: File,
|
||||
route_table: Rc<RefCell<RouteTable>>,
|
||||
devices: Rc<RefCell<DeviceList>>,
|
||||
) -> NetCfgScheme {
|
||||
let notifier = Notifier::new_ref();
|
||||
let dns_config = Rc::new(RefCell::new(DNSConfig {
|
||||
name_server: Ipv4Address::new(8, 8, 8, 8),
|
||||
}));
|
||||
NetCfgScheme {
|
||||
scheme_file,
|
||||
next_fd: 1,
|
||||
files: BTreeMap::new(),
|
||||
root_node: mk_root_node(
|
||||
iface,
|
||||
Rc::clone(¬ifier),
|
||||
dns_config,
|
||||
route_table,
|
||||
devices,
|
||||
),
|
||||
notifier,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_scheme_event(&mut self) -> Result<Option<()>> {
|
||||
let result = loop {
|
||||
let mut packet = SyscallPacket::default();
|
||||
match self.scheme_file.read(&mut packet) {
|
||||
Ok(0) => {
|
||||
//TODO: Cleanup must occur
|
||||
break Some(());
|
||||
}
|
||||
Ok(_) => (),
|
||||
Err(err) => {
|
||||
if err.kind() == ErrorKind::WouldBlock {
|
||||
break None;
|
||||
} else {
|
||||
return Err(Error::from(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
self.handle(&mut packet);
|
||||
self.scheme_file.write_all(&packet)?;
|
||||
};
|
||||
self.notify_scheduled_fds();
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn notify_scheduled_fds(&mut self) {
|
||||
let fds_to_notify = self.notifier.borrow_mut().get_notified_fds();
|
||||
for fd in fds_to_notify {
|
||||
let _ = post_fevent(&mut self.scheme_file, fd, syscall::EVENT_READ.bits(), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemeMut for NetCfgScheme {
|
||||
fn open(&mut self, path: &str, _flags: usize, uid: u32, _gid: u32) -> SyscallResult<usize> {
|
||||
let mut current_node = Rc::clone(&self.root_node);
|
||||
for part in path.split('/') {
|
||||
if part.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let next_node = current_node
|
||||
.borrow_mut()
|
||||
.open(part)
|
||||
.ok_or_else(|| SyscallError::new(syscall::EINVAL))?;
|
||||
current_node = next_node;
|
||||
}
|
||||
let current_node = current_node.borrow();
|
||||
let read_buf = Vec::from(current_node.read());
|
||||
let fd = self.next_fd;
|
||||
trace!("open {} {}", fd, path);
|
||||
self.next_fd += 1;
|
||||
self.files.insert(
|
||||
fd,
|
||||
NetCfgFile {
|
||||
path: path.to_owned(),
|
||||
is_dir: current_node.is_dir(),
|
||||
is_writable: current_node.is_writable(),
|
||||
is_readable: current_node.is_readable(),
|
||||
node_writer: if current_node.is_writable() {
|
||||
current_node.new_writer()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
uid,
|
||||
pos: 0,
|
||||
read_buf,
|
||||
write_buf: vec![],
|
||||
done: false,
|
||||
},
|
||||
);
|
||||
Ok(fd)
|
||||
}
|
||||
|
||||
fn close(&mut self, fd: usize) -> SyscallResult<usize> {
|
||||
trace!("close {}", fd);
|
||||
if let Some(mut file) = self.files.remove(&fd) {
|
||||
self.notifier.borrow_mut().unsubscribe(&file.path, fd);
|
||||
if !file.done {
|
||||
file.commit().map(|_| 0)
|
||||
} else {
|
||||
Ok(0)
|
||||
}
|
||||
} else {
|
||||
Err(SyscallError::new(syscall::EBADF))
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, fd: usize, buf: &[u8]) -> SyscallResult<usize> {
|
||||
let file = self
|
||||
.files
|
||||
.get_mut(&fd)
|
||||
.ok_or_else(|| SyscallError::new(syscall::EBADF))?;
|
||||
|
||||
if file.done {
|
||||
return Err(SyscallError::new(syscall::EBADF));
|
||||
}
|
||||
|
||||
if file.uid != 0 {
|
||||
return Err(SyscallError::new(syscall::EACCES));
|
||||
}
|
||||
|
||||
if (WRITE_BUFFER_MAX_SIZE - file.write_buf.len()) < buf.len() {
|
||||
return Err(SyscallError::new(syscall::EMSGSIZE));
|
||||
}
|
||||
|
||||
file.write_buf.extend_from_slice(buf);
|
||||
|
||||
if let Err(e) = file.consume_lines() {
|
||||
trace!("Failed write {} {}", fd, e);
|
||||
file.done = true;
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn read(&mut self, fd: usize, buf: &mut [u8]) -> SyscallResult<usize> {
|
||||
let file = self
|
||||
.files
|
||||
.get_mut(&fd)
|
||||
.ok_or_else(|| SyscallError::new(syscall::EBADF))?;
|
||||
|
||||
let mut i = 0;
|
||||
while i < buf.len() && file.pos < file.read_buf.len() {
|
||||
buf[i] = file.read_buf[file.pos];
|
||||
i += 1;
|
||||
file.pos += 1;
|
||||
}
|
||||
Ok(i)
|
||||
}
|
||||
|
||||
fn fstat(&mut self, fd: usize, stat: &mut Stat) -> SyscallResult<usize> {
|
||||
let file = self
|
||||
.files
|
||||
.get_mut(&fd)
|
||||
.ok_or_else(|| SyscallError::new(syscall::EBADF))?;
|
||||
|
||||
stat.st_mode = if file.is_dir { MODE_DIR } else { MODE_FILE };
|
||||
if file.is_writable {
|
||||
stat.st_mode |= 0o222;
|
||||
}
|
||||
if file.is_readable {
|
||||
stat.st_mode |= 0o444;
|
||||
}
|
||||
stat.st_uid = 0;
|
||||
stat.st_gid = 0;
|
||||
stat.st_size = file.read_buf.len() as u64;
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn fevent(&mut self, fd: usize, events: SyscallEventFlags) -> SyscallResult<SyscallEventFlags> {
|
||||
let file = self
|
||||
.files
|
||||
.get_mut(&fd)
|
||||
.ok_or_else(|| SyscallError::new(syscall::EBADF))?;
|
||||
if events.contains(syscall::EVENT_READ) {
|
||||
self.notifier.borrow_mut().subscribe(&file.path, fd);
|
||||
} else {
|
||||
self.notifier.borrow_mut().unsubscribe(&file.path, fd);
|
||||
}
|
||||
Ok(SyscallEventFlags::empty())
|
||||
}
|
||||
|
||||
fn fsync(&mut self, fd: usize) -> SyscallResult<usize> {
|
||||
let file = self
|
||||
.files
|
||||
.get_mut(&fd)
|
||||
.ok_or_else(|| SyscallError::new(syscall::EBADF))?;
|
||||
|
||||
if !file.done {
|
||||
let res = file.commit().map(|_| 0);
|
||||
file.done = true;
|
||||
res
|
||||
} else {
|
||||
Err(SyscallError::new(syscall::EBADF))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
use std::rc::Rc;
|
||||
use syscall::Result as SyscallResult;
|
||||
|
||||
pub type CfgNodeRef = Rc<RefCell<dyn CfgNode>>;
|
||||
|
||||
pub trait NodeWriter {
|
||||
fn write_line(&mut self, _: &str) -> SyscallResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn commit(&mut self) -> SyscallResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SimpleWriter<T, WL, C>
|
||||
where
|
||||
WL: 'static + Fn(&mut T, &str) -> SyscallResult<()>,
|
||||
C: 'static + Fn(&mut T) -> SyscallResult<()>,
|
||||
{
|
||||
data: T,
|
||||
write_line: WL,
|
||||
commit: C,
|
||||
}
|
||||
|
||||
impl<T, WL, C> NodeWriter for SimpleWriter<T, WL, C>
|
||||
where
|
||||
WL: 'static + Fn(&mut T, &str) -> SyscallResult<()>,
|
||||
C: 'static + Fn(&mut T) -> SyscallResult<()>,
|
||||
{
|
||||
fn write_line(&mut self, line: &str) -> SyscallResult<()> {
|
||||
(self.write_line)(&mut self.data, line)
|
||||
}
|
||||
|
||||
fn commit(&mut self) -> SyscallResult<()> {
|
||||
(self.commit)(&mut self.data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, WL, C> SimpleWriter<T, WL, C>
|
||||
where
|
||||
WL: 'static + Fn(&mut T, &str) -> SyscallResult<()>,
|
||||
C: 'static + Fn(&mut T) -> SyscallResult<()>,
|
||||
{
|
||||
pub fn new_boxed(data: T, write_line: WL, commit: C) -> Box<Self> {
|
||||
Box::new(SimpleWriter {
|
||||
data,
|
||||
write_line,
|
||||
commit,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CfgNode {
|
||||
fn is_dir(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_writable(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_readable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn read(&self) -> String {
|
||||
String::new()
|
||||
}
|
||||
|
||||
fn open(&self, _file: &str) -> Option<CfgNodeRef> {
|
||||
None
|
||||
}
|
||||
|
||||
fn new_writer(&self) -> Option<Box<dyn NodeWriter>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RONode<F>
|
||||
where
|
||||
F: Fn() -> String,
|
||||
{
|
||||
read_fun: F,
|
||||
}
|
||||
|
||||
impl<F> CfgNode for RONode<F>
|
||||
where
|
||||
F: Fn() -> String,
|
||||
{
|
||||
fn read(&self) -> String {
|
||||
(self.read_fun)()
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> RONode<F>
|
||||
where
|
||||
F: 'static + Fn() -> String,
|
||||
{
|
||||
pub fn new_ref(read_fun: F) -> CfgNodeRef {
|
||||
Rc::new(RefCell::new(RONode { read_fun }))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WONode<W>
|
||||
where
|
||||
W: 'static + Fn() -> Box<dyn NodeWriter>,
|
||||
{
|
||||
new_writer: W,
|
||||
}
|
||||
|
||||
impl<W> CfgNode for WONode<W>
|
||||
where
|
||||
W: 'static + Fn() -> Box<dyn NodeWriter>,
|
||||
{
|
||||
fn is_readable(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_writable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn new_writer(&self) -> Option<Box<dyn NodeWriter>> {
|
||||
Some((self.new_writer)())
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> WONode<W>
|
||||
where
|
||||
W: 'static + Fn() -> Box<dyn NodeWriter>,
|
||||
{
|
||||
pub fn new_ref(new_writer: W) -> CfgNodeRef {
|
||||
Rc::new(RefCell::new(WONode { new_writer }))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RWNode<F, W>
|
||||
where
|
||||
F: Fn() -> String,
|
||||
W: 'static + Fn() -> Box<dyn NodeWriter>,
|
||||
{
|
||||
read_fun: F,
|
||||
new_writer: W,
|
||||
}
|
||||
|
||||
impl<F, W> CfgNode for RWNode<F, W>
|
||||
where
|
||||
F: Fn() -> String,
|
||||
W: 'static + Fn() -> Box<dyn NodeWriter>,
|
||||
{
|
||||
fn read(&self) -> String {
|
||||
(self.read_fun)()
|
||||
}
|
||||
|
||||
fn is_writable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn new_writer(&self) -> Option<Box<dyn NodeWriter>> {
|
||||
Some((self.new_writer)())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, W> RWNode<F, W>
|
||||
where
|
||||
F: 'static + Fn() -> String,
|
||||
W: 'static + Fn() -> Box<dyn NodeWriter>,
|
||||
{
|
||||
pub fn new_ref(read_fun: F, new_writer: W) -> CfgNodeRef {
|
||||
Rc::new(RefCell::new(RWNode {
|
||||
read_fun,
|
||||
new_writer,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StaticDirNode {
|
||||
child_nodes: BTreeMap<String, CfgNodeRef>,
|
||||
}
|
||||
|
||||
impl CfgNode for StaticDirNode {
|
||||
fn is_dir(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn read(&self) -> String {
|
||||
let mut files = String::new();
|
||||
for child in self.child_nodes.keys() {
|
||||
if !files.is_empty() {
|
||||
files.push('\n');
|
||||
}
|
||||
files += child;
|
||||
}
|
||||
files
|
||||
}
|
||||
|
||||
fn open(&self, file: &str) -> Option<CfgNodeRef> {
|
||||
self.child_nodes.get(file).map(|node| Rc::clone(node))
|
||||
}
|
||||
}
|
||||
|
||||
impl StaticDirNode {
|
||||
pub fn new_ref(child_nodes: BTreeMap<String, CfgNodeRef>) -> CfgNodeRef {
|
||||
Rc::new(RefCell::new(StaticDirNode { child_nodes }))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_node {
|
||||
(val $e:expr) => {
|
||||
$e
|
||||
};
|
||||
(ro [ $($c:ident),* ] || $b:block ) => {
|
||||
{
|
||||
$(let $c = $c.clone();)*
|
||||
RONode::new_ref(move|| $b)
|
||||
}
|
||||
};
|
||||
(wo [ $($c:ident),* ] ( $et:ty , $e:expr ) |$data_i:ident, $line_i:ident|
|
||||
$write_line:block |$data_i2:ident| $commit:block) => {
|
||||
{
|
||||
$(#[allow(unused_variables)] let $c = $c.clone();)*
|
||||
let new_writer = move || -> Box<dyn NodeWriter> {
|
||||
let write_line = {
|
||||
$(#[allow(unused_variables)] let $c = $c.clone();)*
|
||||
move |$data_i: &mut $et, $line_i: &str| $write_line
|
||||
};
|
||||
let commit = {
|
||||
$(#[allow(unused_variables)] let $c = $c.clone();)*
|
||||
move |$data_i2: &mut $et| $commit
|
||||
};
|
||||
let data: $et = $e;
|
||||
SimpleWriter::new_boxed(data, write_line, commit)
|
||||
};
|
||||
WONode::new_ref(new_writer)
|
||||
}
|
||||
};
|
||||
(rw [ $($c:ident),* ] ( $et:ty , $e:expr ) || $read_fun:block |$data_i:ident, $line_i:ident|
|
||||
$write_line:block |$data_i2:ident| $commit:block) => {
|
||||
{
|
||||
let read_fun = {
|
||||
$(#[allow(unused_variables)] let $c = $c.clone();)*
|
||||
move || $read_fun
|
||||
};
|
||||
$(#[allow(unused_variables)] let $c = $c.clone();)*
|
||||
let new_writer = move || -> Box<dyn NodeWriter> {
|
||||
let write_line = {
|
||||
$(#[allow(unused_variables)] let $c = $c.clone();)*
|
||||
move |$data_i: &mut $et, $line_i: &str| $write_line
|
||||
};
|
||||
let commit = {
|
||||
$(#[allow(unused_variables)] let $c = $c.clone();)*
|
||||
move |$data_i2: &mut $et| $commit
|
||||
};
|
||||
let data: $et = $e;
|
||||
SimpleWriter::new_boxed(data, write_line, commit)
|
||||
};
|
||||
RWNode::new_ref(read_fun, new_writer)
|
||||
}
|
||||
};
|
||||
($($e:expr => { $($t:tt)* }),* $(,)*) => {
|
||||
{
|
||||
let mut children = BTreeMap::new();
|
||||
$(children.insert($e.into(), cfg_node!($($t)*));)*
|
||||
StaticDirNode::new_ref(children)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::btree_map::Entry;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct Notifier {
|
||||
listeners: BTreeMap<String, BTreeSet<usize>>,
|
||||
notified: BTreeSet<usize>,
|
||||
}
|
||||
|
||||
pub type NotifierRef = Rc<RefCell<Notifier>>;
|
||||
|
||||
impl Notifier {
|
||||
pub fn new_ref() -> NotifierRef {
|
||||
Rc::new(RefCell::new(Notifier {
|
||||
listeners: BTreeMap::new(),
|
||||
notified: BTreeSet::new(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn subscribe(&mut self, path: &str, fd: usize) {
|
||||
trace!("Sub fd {} to {}", fd, path);
|
||||
match self.listeners.entry(path.to_owned()) {
|
||||
Entry::Occupied(mut e) => {
|
||||
e.get_mut().insert(fd);
|
||||
}
|
||||
Entry::Vacant(e) => {
|
||||
let mut fds = BTreeSet::new();
|
||||
fds.insert(fd);
|
||||
e.insert(fds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unsubscribe(&mut self, path: &str, fd: usize) {
|
||||
let empty = if let Some(fds) = self.listeners.get_mut(path) {
|
||||
if fds.remove(&fd) {
|
||||
trace!("Unsub fd {} from {}", fd, path);
|
||||
}
|
||||
fds.is_empty()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if empty {
|
||||
self.listeners.remove(path);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn schedule_notify(&mut self, path: &str) {
|
||||
trace!("Notifying {}", path);
|
||||
if let Some(fds) = self.listeners.get(path) {
|
||||
self.notified.extend(fds);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_notified_fds(&mut self) -> BTreeSet<usize> {
|
||||
use std::mem::swap;
|
||||
let mut notified = BTreeSet::new();
|
||||
swap(&mut self.notified, &mut notified);
|
||||
notified
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user