Netdb
This commit is contained in:
@@ -10,6 +10,7 @@ pub mod fnmatch;
|
||||
pub mod grp;
|
||||
pub mod inttypes;
|
||||
pub mod locale;
|
||||
pub mod netdb;
|
||||
pub mod netinet_in;
|
||||
//pub mod pthread;
|
||||
pub mod pwd;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
sys_includes = ["sys/socket.h", "netinet/in.h"]
|
||||
include_guard = "_NETDB_H"
|
||||
language = "C"
|
||||
style = "Tag"
|
||||
|
||||
[enum]
|
||||
prefix_with_name = true
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use alloc::String;
|
||||
use alloc::Vec;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DnsAnswer {
|
||||
pub name: String,
|
||||
pub a_type: u16,
|
||||
pub a_class: u16,
|
||||
pub ttl_a: u16,
|
||||
pub ttl_b: u16,
|
||||
pub data: Vec<u8>
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
pub use self::answer::DnsAnswer;
|
||||
pub use self::query::DnsQuery;
|
||||
|
||||
use core::slice;
|
||||
use core::u16;
|
||||
use alloc::String;
|
||||
use alloc::Vec;
|
||||
|
||||
mod answer;
|
||||
mod query;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
pub struct n16 {
|
||||
inner: u16
|
||||
}
|
||||
|
||||
impl n16 {
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
unsafe { slice::from_raw_parts((&self.inner as *const u16) as *const u8, 2) }
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Self {
|
||||
n16 {
|
||||
inner: unsafe { slice::from_raw_parts(bytes.as_ptr() as *const u16, bytes.len()/2)[0] }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u16> for n16 {
|
||||
fn from(value: u16) -> Self {
|
||||
n16 {
|
||||
inner: value.to_be()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<n16> for u16 {
|
||||
fn from(value: n16) -> Self {
|
||||
u16::from_be(value.inner)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Dns {
|
||||
pub transaction_id: u16,
|
||||
pub flags: u16,
|
||||
pub queries: Vec<DnsQuery>,
|
||||
pub answers: Vec<DnsAnswer>
|
||||
}
|
||||
|
||||
impl Dns {
|
||||
pub fn compile(&self) -> Vec<u8> {
|
||||
let mut data = Vec::new();
|
||||
|
||||
macro_rules! push_u8 {
|
||||
($value:expr) => {
|
||||
data.push($value);
|
||||
};
|
||||
};
|
||||
|
||||
macro_rules! push_n16 {
|
||||
($value:expr) => {
|
||||
data.extend_from_slice(n16::from($value).as_bytes());
|
||||
};
|
||||
};
|
||||
|
||||
push_n16!(self.transaction_id);
|
||||
push_n16!(self.flags);
|
||||
push_n16!(self.queries.len() as u16);
|
||||
push_n16!(self.answers.len() as u16);
|
||||
push_n16!(0);
|
||||
push_n16!(0);
|
||||
|
||||
for query in self.queries.iter() {
|
||||
for part in query.name.split('.') {
|
||||
push_u8!(part.len() as u8);
|
||||
data.extend_from_slice(part.as_bytes());
|
||||
}
|
||||
push_u8!(0);
|
||||
push_n16!(query.q_type);
|
||||
push_n16!(query.q_class);
|
||||
}
|
||||
data
|
||||
}
|
||||
|
||||
pub fn parse(data: &[u8]) -> Result<Self, String> {
|
||||
let name_ind = 0b11000000;
|
||||
let mut i = 0;
|
||||
|
||||
macro_rules! pop_u8 {
|
||||
() => {
|
||||
{
|
||||
i += 1;
|
||||
if i > data.len() {
|
||||
return Err(format!("{}: {}: pop_u8", file!(), line!()));
|
||||
}
|
||||
data[i - 1]
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
macro_rules! pop_n16 {
|
||||
() => {
|
||||
{
|
||||
i += 2;
|
||||
if i > data.len() {
|
||||
return Err(format!("{}: {}: pop_n16", file!(), line!()));
|
||||
}
|
||||
u16::from(n16::from_bytes(&data[i - 2 .. i]))
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
macro_rules! pop_data {
|
||||
() => {
|
||||
{
|
||||
let mut data = Vec::new();
|
||||
|
||||
let data_len = pop_n16!();
|
||||
for _data_i in 0..data_len {
|
||||
data.push(pop_u8!());
|
||||
}
|
||||
|
||||
data
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
macro_rules! pop_name {
|
||||
() => {
|
||||
{
|
||||
let mut name = String::new();
|
||||
let old_i = i;
|
||||
|
||||
loop {
|
||||
let name_len = pop_u8!();
|
||||
if name_len & name_ind == name_ind {
|
||||
i -= 1;
|
||||
i = (pop_n16!() - ((name_ind as u16) << 8)) as usize;
|
||||
continue;
|
||||
}
|
||||
if name_len == 0 {
|
||||
break;
|
||||
}
|
||||
if ! name.is_empty() {
|
||||
name.push('.');
|
||||
}
|
||||
for _name_i in 0..name_len {
|
||||
name.push(pop_u8!() as char);
|
||||
}
|
||||
}
|
||||
|
||||
if i <= old_i {
|
||||
i = old_i + 2;
|
||||
}
|
||||
|
||||
name
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
let transaction_id = pop_n16!();
|
||||
let flags = pop_n16!();
|
||||
let queries_len = pop_n16!();
|
||||
let answers_len = pop_n16!();
|
||||
pop_n16!();
|
||||
pop_n16!();
|
||||
|
||||
let mut queries = Vec::new();
|
||||
for _query_i in 0..queries_len {
|
||||
queries.push(DnsQuery {
|
||||
name: pop_name!(),
|
||||
q_type: pop_n16!(),
|
||||
q_class: pop_n16!()
|
||||
});
|
||||
}
|
||||
|
||||
let mut answers = Vec::new();
|
||||
for _answer_i in 0..answers_len {
|
||||
answers.push(DnsAnswer {
|
||||
name: pop_name!(),
|
||||
a_type: pop_n16!(),
|
||||
a_class: pop_n16!(),
|
||||
ttl_a: pop_n16!(),
|
||||
ttl_b: pop_n16!(),
|
||||
data: pop_data!()
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Dns {
|
||||
transaction_id: transaction_id,
|
||||
flags: flags,
|
||||
queries: queries,
|
||||
answers: answers,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use alloc::String;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DnsQuery {
|
||||
pub name: String,
|
||||
pub q_type: u16,
|
||||
pub q_class: u16
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
use alloc::string::String;
|
||||
use platform::rawfile::RawFile;
|
||||
use platform::Line;
|
||||
use platform::rlb::RawLineBuffer;
|
||||
use c_str::CString;
|
||||
|
||||
pub fn get_dns_server() -> String {
|
||||
let fd = match RawFile::open(&CString::new("/etc/resolv.conf").unwrap(), 0, 0) {
|
||||
Ok(fd) => fd,
|
||||
Err(_) => return String::new() // TODO: better error handling
|
||||
};
|
||||
|
||||
let mut rlb = RawLineBuffer::new(*fd);
|
||||
while let Line::Some(line) = rlb.next() {
|
||||
if line.starts_with(b"nameserver ") {
|
||||
return String::from_utf8(line[11..].to_vec()).unwrap_or_default();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: better error handling
|
||||
String::new()
|
||||
}
|
||||
@@ -0,0 +1,944 @@
|
||||
//! netdb implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/netdb.h.html
|
||||
|
||||
mod dns;
|
||||
|
||||
use core::{mem, str, ptr};
|
||||
use core::str::FromStr;
|
||||
|
||||
use alloc::{Vec, String};
|
||||
use alloc::str::SplitWhitespace;
|
||||
use alloc::string::ToString;
|
||||
use alloc::vec::IntoIter;
|
||||
use alloc::boxed::Box;
|
||||
|
||||
use c_str::CString;
|
||||
|
||||
use platform;
|
||||
use platform::{Pal, Sys};
|
||||
use platform::types::*;
|
||||
use platform::rlb::{Line, RawLineBuffer};
|
||||
use platform::c_str;
|
||||
|
||||
use self::dns::{Dns, DnsQuery};
|
||||
|
||||
use header::arpa_inet::{htons, ntohs, inet_aton};
|
||||
use header::errno::*;
|
||||
use header::fcntl::O_RDONLY;
|
||||
use header::netinet_in::{in_addr, IPPROTO_UDP, sockaddr_in};
|
||||
use header::stdlib::atoi;
|
||||
use header::strings::strcasecmp;
|
||||
use header::sys_socket;
|
||||
use header::sys_socket::{sockaddr, socklen_t};
|
||||
use header::sys_socket::constants::{SOCK_DGRAM, AF_INET};
|
||||
use header::time;
|
||||
use header::unistd::SEEK_SET;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[path = "linux.rs"]
|
||||
pub mod sys;
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
#[path = "redox.rs"]
|
||||
pub mod sys;
|
||||
|
||||
const MAXADDRS: usize = 35;
|
||||
const MAXALIASES: usize = 35;
|
||||
|
||||
struct LookupHost(IntoIter<in_addr>);
|
||||
|
||||
impl Iterator for LookupHost {
|
||||
type Item = in_addr;
|
||||
fn next (&mut self) -> Option<Self::Item> {
|
||||
self.0.next()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct hostent {
|
||||
h_name: *mut c_char,
|
||||
h_aliases: *mut *mut c_char,
|
||||
h_addrtype: c_int,
|
||||
h_length: c_int,
|
||||
h_addr_list: *mut *mut c_char,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct netent {
|
||||
n_name: *mut c_char, /* official name of net */
|
||||
n_aliases: *mut *mut c_char, /* alias list */
|
||||
n_addrtype: c_int, /* net address type */
|
||||
n_net: c_ulong, /* network # */
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct servent {
|
||||
s_name: *mut c_char, /* official service name */
|
||||
s_aliases: *mut *mut c_char, /* alias list */
|
||||
s_port: c_int, /* port # */
|
||||
s_proto: *mut c_char, /* protocol to use */
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct protoent {
|
||||
p_name: *mut c_char, /* official protocol name */
|
||||
p_aliases: *mut *mut c_char, /* alias list */
|
||||
p_proto: c_int, /* protocol # */
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct addrinfo {
|
||||
ai_flags: c_int, /* AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST */
|
||||
ai_family: c_int, /* PF_xxx */
|
||||
ai_socktype: c_int, /* SOCK_xxx */
|
||||
ai_protocol: c_int, /* 0 or IPPROTO_xxx for IPv4 and IPv6 */
|
||||
ai_addrlen: size_t, /* length of ai_addr */
|
||||
ai_canonname: *mut c_char, /* canonical name for hostname */
|
||||
ai_addr: *mut sockaddr, /* binary address */
|
||||
ai_next: *mut addrinfo, /* next structure in linked list */
|
||||
}
|
||||
|
||||
static mut NETDB: c_int = 0;
|
||||
static mut NET_ENTRY: netent = netent {
|
||||
n_name: ptr::null_mut(),
|
||||
n_aliases: ptr::null_mut(),
|
||||
n_addrtype: 0,
|
||||
n_net: 0,
|
||||
};
|
||||
static mut NET_NAME: Option<Vec<u8>> = None;
|
||||
static mut NET_ALIASES: [*const c_char; MAXALIASES] = [ptr::null(); MAXALIASES];
|
||||
static mut NET_NUM: Option<u64> = None;
|
||||
static mut N_POS: usize = 0;
|
||||
static mut NET_STAYOPEN: c_int = 0;
|
||||
|
||||
static mut HOSTDB: c_int = 0;
|
||||
static mut HOST_ENTRY: hostent = hostent {
|
||||
h_name: ptr::null_mut(),
|
||||
h_aliases: ptr::null_mut(),
|
||||
h_addrtype: 0,
|
||||
h_length: 0,
|
||||
h_addr_list: ptr::null_mut(),
|
||||
};
|
||||
static mut HOST_NAME: Option<Vec<u8>> = None;
|
||||
static mut HOST_ALIASES: Option<Vec<Vec<u8>>> = None;
|
||||
static mut HOST_ADDR: Option<in_addr> = None;
|
||||
static mut HOST_ADDR_LIST: [*mut c_char; 2] = [ptr::null_mut(); 2];
|
||||
static mut _HOST_ADDR_LIST: [u8;4] = [0u8;4];
|
||||
static mut H_POS: usize = 0;
|
||||
static mut HOST_STAYOPEN: c_int = 0;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[no_mangle]
|
||||
pub static mut h_errno: c_int = 0;
|
||||
pub const HOST_NOT_FOUND: c_int = 1;
|
||||
pub const NO_DATA: c_int = 2;
|
||||
pub const NO_RECOVERY: c_int = 3;
|
||||
pub const TRY_AGAIN: c_int = 4;
|
||||
|
||||
static mut PROTODB: c_int = 0;
|
||||
static mut PROTO_ENTRY: protoent = protoent {
|
||||
p_name: ptr::null_mut(),
|
||||
p_aliases: ptr::null_mut(),
|
||||
p_proto: 0 as c_int,
|
||||
};
|
||||
static mut PROTO_NAME: Option<Vec<u8>> = None;
|
||||
static mut PROTO_ALIASES: Option<Vec<Vec<u8>>> = None;
|
||||
static mut PROTO_NUM: Option<c_int> = None;
|
||||
static mut P_POS: usize = 0;
|
||||
static mut PROTO_STAYOPEN: c_int = 0;
|
||||
|
||||
static mut SERVDB: c_int = 0;
|
||||
static mut SERV_ENTRY: servent = servent {
|
||||
s_name: ptr::null_mut(),
|
||||
s_aliases: ptr::null_mut(),
|
||||
s_port: 0 as c_int,
|
||||
s_proto: ptr::null_mut(),
|
||||
|
||||
};
|
||||
static mut SERV_NAME: Option<Vec<u8>> = None;
|
||||
static mut SERV_ALIASES: Option<Vec<Vec<u8>>> = None;
|
||||
static mut SERV_PORT: Option<c_int> = None;
|
||||
static mut SERV_PROTO: Option<Vec<u8>> = None;
|
||||
static mut S_POS: usize = 0;
|
||||
static mut SERV_STAYOPEN: c_int = 0;
|
||||
|
||||
const NULL_ALIASES: [*mut c_char; 2] = [ptr::null_mut(); 2];
|
||||
|
||||
fn bytes_to_box_str(bytes: &[u8]) -> Box<str> {
|
||||
Box::from(core::str::from_utf8(bytes).unwrap_or(""))
|
||||
}
|
||||
|
||||
fn lookup_host(host: &str) -> Result<LookupHost, c_int> {
|
||||
let dns_string = sys::get_dns_server();
|
||||
|
||||
let dns_vec: Vec<u8> = dns_string
|
||||
.trim()
|
||||
.split(".")
|
||||
.map(|octet| octet.parse::<u8>().unwrap_or(0))
|
||||
.collect();
|
||||
|
||||
if dns_vec.len() == 4 {
|
||||
let mut dns_arr = [0u8;4];
|
||||
for (i, octet) in dns_vec.iter().enumerate() {
|
||||
dns_arr[i] = *octet;
|
||||
}
|
||||
let dns_addr = unsafe {mem::transmute::<[u8;4], u32>(dns_arr)};
|
||||
|
||||
let mut timespec = timespec::default();
|
||||
Sys::clock_gettime(time::constants::CLOCK_REALTIME, &mut timespec);
|
||||
let tid = (timespec.tv_nsec >> 16) as u16;
|
||||
|
||||
let packet = Dns {
|
||||
transaction_id: tid,
|
||||
flags: 0x0100,
|
||||
queries: vec![
|
||||
DnsQuery {
|
||||
name: host.to_string(),
|
||||
q_type: 0x0001,
|
||||
q_class: 0x0001,
|
||||
},
|
||||
],
|
||||
answers: vec![],
|
||||
};
|
||||
|
||||
let packet_data = packet.compile();
|
||||
let packet_data_len = packet_data.len();
|
||||
|
||||
let packet_data_box = packet_data.into_boxed_slice();
|
||||
let packet_data_ptr = Box::into_raw(packet_data_box) as *mut _ as *mut c_void;
|
||||
|
||||
let sock = unsafe {sys_socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP as i32)};
|
||||
|
||||
let mut dest = sockaddr_in {
|
||||
sin_family: AF_INET as u16,
|
||||
sin_port: htons(53),
|
||||
sin_addr: in_addr {
|
||||
s_addr: dns_addr,
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let dest_ptr = &mut dest as *mut _ as *mut sockaddr;
|
||||
|
||||
unsafe {
|
||||
if sys_socket::sendto(sock, packet_data_ptr, packet_data_len, 0, dest_ptr, 16) < 0 {
|
||||
Box::from_raw(packet_data_ptr);
|
||||
return Err(EIO);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
Box::from_raw(packet_data_ptr);
|
||||
}
|
||||
|
||||
let mut i = 0 as socklen_t;
|
||||
let mut buf = [0u8;65536];
|
||||
let buf_ptr = buf.as_mut_ptr() as *mut c_void;
|
||||
|
||||
let mut count = -1;
|
||||
|
||||
let mut from: sockaddr = Default::default();
|
||||
let from_ptr = &mut from as *mut sockaddr;
|
||||
|
||||
unsafe {
|
||||
count = sys_socket::recvfrom(sock, buf_ptr, 65536, 0, from_ptr, &mut i as *mut socklen_t);
|
||||
}
|
||||
if count < 0 {
|
||||
return Err(EIO)
|
||||
}
|
||||
|
||||
match Dns::parse(&buf[..count as usize]) {
|
||||
Ok(response) => {
|
||||
let mut addrs = vec![];
|
||||
for answer in response.answers.iter() {
|
||||
if answer.a_type == 0x0001 && answer.a_class == 0x0001 &&
|
||||
answer.data.len() == 4
|
||||
{
|
||||
let addr = in_addr {
|
||||
s_addr: unsafe { mem::transmute::<[u8;4], u32>([
|
||||
answer.data[0],
|
||||
answer.data[1],
|
||||
answer.data[2],
|
||||
answer.data[3],
|
||||
])},
|
||||
};
|
||||
addrs.push(addr);
|
||||
}
|
||||
}
|
||||
Ok(LookupHost(addrs.into_iter()))
|
||||
}
|
||||
Err(_err) => Err(EINVAL),
|
||||
}
|
||||
} else {
|
||||
Err(EINVAL)
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup_addr(addr: in_addr) -> Result<Vec<Vec<u8>>, c_int> {
|
||||
let dns_string = sys::get_dns_server();
|
||||
|
||||
let dns_vec: Vec<u8> = dns_string
|
||||
.trim()
|
||||
.split(".")
|
||||
.map(|octet| octet.parse::<u8>().unwrap_or(0))
|
||||
.collect();
|
||||
|
||||
let mut dns_arr = [0u8;4];
|
||||
|
||||
for (i, octet) in dns_vec.iter().enumerate() {
|
||||
dns_arr[i] = *octet;
|
||||
}
|
||||
|
||||
let mut addr_vec: Vec<u8> = unsafe { mem::transmute::<u32, [u8;4]>(addr.s_addr).to_vec() };
|
||||
addr_vec.reverse();
|
||||
let mut name: Vec<u8> = vec![];
|
||||
for octet in addr_vec {
|
||||
for ch in format!("{}", octet).as_bytes() {
|
||||
name.push(*ch);
|
||||
}
|
||||
name.push(b"."[0]);
|
||||
}
|
||||
name.pop();
|
||||
for ch in b".IN-ADDR.ARPA" {
|
||||
name.push(*ch);
|
||||
}
|
||||
|
||||
if dns_vec.len() == 4 {
|
||||
let mut timespec = timespec::default();
|
||||
Sys::clock_gettime(time::constants::CLOCK_REALTIME, &mut timespec);
|
||||
let tid = (timespec.tv_nsec >> 16) as u16;
|
||||
|
||||
let packet = Dns {
|
||||
transaction_id: tid,
|
||||
flags: 0x0100,
|
||||
queries: vec![
|
||||
DnsQuery {
|
||||
name: String::from_utf8(name).unwrap(),
|
||||
q_type: 0x000C,
|
||||
q_class: 0x0001,
|
||||
},
|
||||
],
|
||||
answers: vec![],
|
||||
};
|
||||
|
||||
let packet_data = packet.compile();
|
||||
use core::fmt::Write;
|
||||
let packet_data_len = packet_data.len();
|
||||
let packet_data_box = packet_data.into_boxed_slice();
|
||||
let packet_data_ptr = Box::into_raw(packet_data_box) as *mut _ as *mut c_void;
|
||||
|
||||
let sock = unsafe {sys_socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP as i32)};
|
||||
|
||||
let mut dest = sockaddr_in {
|
||||
sin_family: AF_INET as u16,
|
||||
sin_port: htons(53),
|
||||
sin_addr: in_addr {
|
||||
s_addr: unsafe { mem::transmute::<[u8;4], u32>(dns_arr) },
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let dest_ptr = &mut dest as *mut _ as *mut sockaddr;
|
||||
|
||||
unsafe {
|
||||
if sys_socket::sendto(sock, packet_data_ptr, packet_data_len, 0, dest_ptr, 16) < 0 {
|
||||
return Err(EIO);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
Box::from_raw(packet_data_ptr);
|
||||
}
|
||||
|
||||
let mut i = mem::size_of::<sockaddr_in>() as socklen_t;
|
||||
let mut buf = [0u8;65536];
|
||||
let buf_ptr = buf.as_mut_ptr() as *mut c_void;
|
||||
|
||||
let mut count = -1;
|
||||
|
||||
unsafe {
|
||||
count = sys_socket::recvfrom(sock, buf_ptr, 65536, 0, dest_ptr, &mut i as *mut socklen_t);
|
||||
}
|
||||
if count < 0 {
|
||||
return Err(EIO)
|
||||
}
|
||||
|
||||
match Dns::parse(&buf[..count as usize]) {
|
||||
Ok(response) => {
|
||||
let mut names = vec![];
|
||||
for answer in response.answers.iter() {
|
||||
if answer.a_type == 0x000C && answer.a_class == 0x0001 {
|
||||
// answer.data is encoded kinda weird.
|
||||
// Basically length-prefixed strings for each
|
||||
// subsection of the domain.
|
||||
// We need to parse this to insert periods where
|
||||
// they belong (ie at the end of each string)
|
||||
let data = parse_revdns_answer(answer.data.clone());
|
||||
names.push(data);
|
||||
}
|
||||
}
|
||||
Ok(names)
|
||||
}
|
||||
Err(_err) => Err(EINVAL),
|
||||
}
|
||||
} else {
|
||||
Err(EINVAL)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_revdns_answer(data: Vec<u8>) -> Vec<u8> {
|
||||
let mut cursor = 0;
|
||||
let mut offset = 0;
|
||||
let mut index = 0;
|
||||
let mut output = data.clone();
|
||||
while index < data.len() - 1 {
|
||||
offset = data[index] as usize;
|
||||
index = cursor + offset + 1;
|
||||
output[index] = '.' as u8;
|
||||
cursor = index;
|
||||
}
|
||||
//we don't want an extra period at the end
|
||||
output.pop();
|
||||
output
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn endhostent() {
|
||||
Sys::close(HOSTDB);
|
||||
HOSTDB = 0;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn endnetent() {
|
||||
Sys::close(NETDB);
|
||||
NETDB = 0;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn endprotoent() {
|
||||
Sys::close(PROTODB);
|
||||
PROTODB = 0;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn endservent() {
|
||||
Sys::close(SERVDB);
|
||||
SERVDB = 0;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn gethostbyaddr(v: *const c_void, length: socklen_t, format:c_int) -> *const hostent {
|
||||
let addr: in_addr = *(v as *mut in_addr);
|
||||
|
||||
// check the hosts file first
|
||||
let mut p: *const hostent;
|
||||
sethostent(HOST_STAYOPEN);
|
||||
while { p=gethostent(); p!=ptr::null()} {
|
||||
let mut cp = (*p).h_addr_list;
|
||||
loop {
|
||||
if cp.is_null() {
|
||||
break;
|
||||
}
|
||||
if (*cp).is_null() {
|
||||
break;
|
||||
}
|
||||
let mut cp_slice: [i8;4] = [0i8;4];
|
||||
(*cp).copy_to(cp_slice.as_mut_ptr(), 4);
|
||||
let cp_s_addr = mem::transmute::<[i8;4], u32>(cp_slice);
|
||||
if cp_s_addr == addr.s_addr {
|
||||
sethostent(HOST_STAYOPEN);
|
||||
return p;
|
||||
}
|
||||
cp = cp.offset(1);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO actually get aliases
|
||||
let mut _host_aliases: Vec<Vec<u8>> = Vec::new();
|
||||
_host_aliases.push(vec![b'\0']);
|
||||
let mut host_aliases: Vec<*mut i8> = Vec::new();
|
||||
host_aliases.push(ptr::null_mut());
|
||||
HOST_ALIASES = Some(_host_aliases);
|
||||
|
||||
|
||||
match lookup_addr(addr) {
|
||||
Ok(s) => {
|
||||
_HOST_ADDR_LIST = mem::transmute::<u32, [u8;4]>(addr.s_addr);
|
||||
HOST_ADDR_LIST = [_HOST_ADDR_LIST.as_mut_ptr() as *mut c_char, ptr::null_mut()];
|
||||
let mut host_name = s[0].to_vec();
|
||||
HOST_NAME = Some(host_name);
|
||||
HOST_ENTRY = hostent {
|
||||
h_name: HOST_NAME.as_mut().unwrap().as_mut_ptr() as *mut c_char,
|
||||
h_aliases: host_aliases.as_mut_slice().as_mut_ptr() as *mut *mut i8,
|
||||
h_addrtype: format,
|
||||
h_length: length as i32,
|
||||
h_addr_list: HOST_ADDR_LIST.as_mut_ptr()
|
||||
};
|
||||
&HOST_ENTRY
|
||||
}
|
||||
Err(e) => { platform::errno = e; return ptr::null(); }
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn gethostbyname(name: *const c_char) -> *const hostent {
|
||||
|
||||
// check if some idiot gave us an address instead of a name
|
||||
let mut octets = str::from_utf8_unchecked(c_str(name)).split('.');
|
||||
let mut s_addr = [0u8;4];
|
||||
let mut is_addr = true;
|
||||
for i in 0..4 {
|
||||
if let Some(n) = octets.next().and_then(|x| u8::from_str(x).ok()) {
|
||||
s_addr[i] = n;
|
||||
} else {
|
||||
is_addr = false;
|
||||
}
|
||||
}
|
||||
if octets.next() != None {
|
||||
is_addr = false;
|
||||
}
|
||||
|
||||
if is_addr == true {
|
||||
let addr = in_addr {
|
||||
s_addr: mem::transmute::<[u8;4], u32>(s_addr),
|
||||
};
|
||||
return gethostbyaddr(&addr as *const _ as *const c_void, 4, AF_INET);
|
||||
}
|
||||
|
||||
// check the hosts file first
|
||||
let mut p: *const hostent;
|
||||
sethostent(HOST_STAYOPEN);
|
||||
while { p = gethostent(); p!=ptr::null() } {
|
||||
if strcasecmp((*p).h_name, name) == 0 {
|
||||
sethostent(HOST_STAYOPEN);
|
||||
return p;
|
||||
}
|
||||
let mut cp = (*p).h_aliases;
|
||||
loop {
|
||||
if cp.is_null() {
|
||||
break;
|
||||
}
|
||||
if (*cp).is_null() {
|
||||
break;
|
||||
}
|
||||
if strcasecmp(*cp, name) == 0 {
|
||||
sethostent(HOST_STAYOPEN);
|
||||
return p;
|
||||
}
|
||||
cp = cp.offset(1);
|
||||
}
|
||||
}
|
||||
|
||||
let mut host = match lookup_host(str::from_utf8_unchecked(c_str(name))) {
|
||||
Ok(lookuphost) => lookuphost,
|
||||
Err(e) => { platform::errno = e; return ptr::null() }
|
||||
};
|
||||
let host_addr = match host.next() {
|
||||
Some(result) => result,
|
||||
None => { platform::errno = ENOENT; return ptr::null() }
|
||||
};
|
||||
|
||||
let host_name: Vec<u8> = c_str(name).to_vec();
|
||||
HOST_NAME = Some(host_name);
|
||||
_HOST_ADDR_LIST = mem::transmute::<u32, [u8;4]>(host_addr.s_addr);
|
||||
HOST_ADDR_LIST = [_HOST_ADDR_LIST.as_mut_ptr() as *mut c_char, ptr::null_mut()];
|
||||
HOST_ADDR = Some(host_addr);
|
||||
|
||||
//TODO actually get aliases
|
||||
let mut _host_aliases: Vec<Vec<u8>> = Vec::new();
|
||||
_host_aliases.push(vec![b'\0']);
|
||||
let mut host_aliases: Vec<*mut i8> = Vec::new();
|
||||
host_aliases.push(ptr::null_mut());
|
||||
host_aliases.push(ptr::null_mut());
|
||||
HOST_ALIASES = Some(_host_aliases);
|
||||
|
||||
HOST_ENTRY = hostent {
|
||||
h_name: HOST_NAME.as_mut().unwrap().as_mut_ptr() as *mut c_char,
|
||||
h_aliases: host_aliases.as_mut_slice().as_mut_ptr() as *mut *mut i8,
|
||||
h_addrtype: AF_INET,
|
||||
h_length: 4,
|
||||
h_addr_list: HOST_ADDR_LIST.as_mut_ptr()
|
||||
};
|
||||
sethostent(HOST_STAYOPEN);
|
||||
&HOST_ENTRY as *const hostent
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn gethostent() -> *const hostent {
|
||||
if HOSTDB == 0 {
|
||||
HOSTDB = Sys::open(&CString::new("/etc/hosts").unwrap(), O_RDONLY, 0);
|
||||
}
|
||||
let mut rlb = RawLineBuffer::new(HOSTDB);
|
||||
rlb.seek(H_POS);
|
||||
|
||||
let mut r: Box<str> = Box::default();
|
||||
while r.is_empty() || r.split_whitespace().next() == None || r.starts_with("#") {
|
||||
r = match rlb.next() {
|
||||
Line::Some(s) => bytes_to_box_str(s),
|
||||
_ => {
|
||||
if HOST_STAYOPEN == 0 { endhostent(); }
|
||||
return ptr::null();
|
||||
}
|
||||
};
|
||||
}
|
||||
rlb.next();
|
||||
H_POS = rlb.line_pos();
|
||||
|
||||
let mut iter: SplitWhitespace = r.split_whitespace();
|
||||
|
||||
let mut addr_vec = iter.next().unwrap().as_bytes().to_vec();
|
||||
addr_vec.push(b'\0');
|
||||
let addr_cstr = addr_vec.as_slice().as_ptr() as *const i8;
|
||||
let mut addr = mem::uninitialized();
|
||||
inet_aton(addr_cstr, &mut addr);
|
||||
|
||||
_HOST_ADDR_LIST = mem::transmute::<u32, [u8;4]>(addr.s_addr);
|
||||
HOST_ADDR_LIST = [_HOST_ADDR_LIST.as_mut_ptr() as *mut c_char, ptr::null_mut()];
|
||||
|
||||
HOST_ADDR = Some(addr);
|
||||
|
||||
let mut host_name = iter.next().unwrap().as_bytes().to_vec();
|
||||
host_name.push(b'\0');
|
||||
|
||||
let mut _host_aliases: Vec<Vec<u8>> = Vec::new();
|
||||
|
||||
loop {
|
||||
let mut alias = match iter.next() {
|
||||
Some(s) => s.as_bytes().to_vec(),
|
||||
_ => break
|
||||
};
|
||||
alias.push(b'\0');
|
||||
_host_aliases.push(alias);
|
||||
}
|
||||
HOST_ALIASES = Some(_host_aliases);
|
||||
|
||||
let mut host_aliases: Vec<*mut i8> = HOST_ALIASES.as_mut().unwrap().iter_mut().map(|x| x.as_mut_ptr() as *mut i8).collect();
|
||||
host_aliases.push(ptr::null_mut());
|
||||
host_aliases.push(ptr::null_mut());
|
||||
|
||||
HOST_NAME = Some(host_name);
|
||||
|
||||
HOST_ENTRY = hostent {
|
||||
h_name: HOST_NAME.as_mut().unwrap().as_mut_ptr() as *mut c_char,
|
||||
h_aliases: host_aliases.as_mut_slice().as_mut_ptr() as *mut *mut i8,
|
||||
h_addrtype: AF_INET,
|
||||
h_length: 4,
|
||||
h_addr_list: HOST_ADDR_LIST.as_mut_ptr()
|
||||
};
|
||||
if HOST_STAYOPEN == 0 {
|
||||
endhostent();
|
||||
}
|
||||
&HOST_ENTRY as *const hostent
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn getnetbyaddr(net: u32, net_type: c_int) -> *const netent {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn getnetbyname(name: *const c_char) -> *const netent {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn getnetent() -> *const netent {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn getprotobyname(name: *const c_char) -> *const protoent {
|
||||
let mut p: *const protoent;
|
||||
setprotoent(PROTO_STAYOPEN);
|
||||
while {p=getprotoent();
|
||||
p!=ptr::null()} {
|
||||
if strcasecmp((*p).p_name, name) == 0 {
|
||||
setprotoent(PROTO_STAYOPEN);
|
||||
return p;
|
||||
}
|
||||
|
||||
let mut cp = (*p).p_aliases;
|
||||
loop {
|
||||
if cp == ptr::null_mut() {
|
||||
setprotoent(PROTO_STAYOPEN);
|
||||
break;
|
||||
}
|
||||
if (*cp) == ptr::null_mut() {
|
||||
setprotoent(PROTO_STAYOPEN);
|
||||
break;
|
||||
}
|
||||
if strcasecmp(*cp, name) == 0 {
|
||||
setprotoent(PROTO_STAYOPEN);
|
||||
return p;
|
||||
}
|
||||
cp = cp.offset(1);
|
||||
}
|
||||
}
|
||||
setprotoent(PROTO_STAYOPEN);
|
||||
|
||||
platform::errno = ENOENT;
|
||||
ptr::null() as *const protoent
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn getprotobynumber(number: c_int) -> *const protoent {
|
||||
setprotoent(PROTO_STAYOPEN);
|
||||
let mut p: *const protoent;
|
||||
while {p=getprotoent();
|
||||
p!=ptr::null()} {
|
||||
if (*p).p_proto == number {
|
||||
setprotoent(PROTO_STAYOPEN);
|
||||
return p;
|
||||
}
|
||||
}
|
||||
setprotoent(PROTO_STAYOPEN);
|
||||
platform::errno = ENOENT;
|
||||
ptr::null() as *const protoent
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn getprotoent() -> *const protoent {
|
||||
if PROTODB == 0 {
|
||||
PROTODB = Sys::open(&CString::new("/etc/protocols").unwrap(), O_RDONLY, 0);
|
||||
}
|
||||
|
||||
let mut rlb = RawLineBuffer::new(PROTODB);
|
||||
rlb.seek(P_POS);
|
||||
|
||||
let mut r: Box<str> = Box::default();
|
||||
while r.is_empty() || r.split_whitespace().next() == None || r.starts_with("#") {
|
||||
r = match rlb.next() {
|
||||
Line::Some(s) => bytes_to_box_str(s),
|
||||
_ => {
|
||||
if PROTO_STAYOPEN == 0 { endprotoent(); }
|
||||
return ptr::null();
|
||||
},
|
||||
};
|
||||
}
|
||||
rlb.next();
|
||||
P_POS = rlb.line_pos();
|
||||
|
||||
let mut iter: SplitWhitespace = r.split_whitespace();
|
||||
|
||||
let mut proto_name: Vec<u8> = iter.next().unwrap().as_bytes().to_vec();
|
||||
proto_name.push(b'\0');
|
||||
|
||||
let mut num = iter.next().unwrap().as_bytes().to_vec();
|
||||
num.push(b'\0');
|
||||
PROTO_NUM = Some(atoi(num.as_mut_slice().as_mut_ptr() as *mut i8));
|
||||
|
||||
let mut _proto_aliases: Vec<Vec<u8>> = Vec::new();
|
||||
loop {
|
||||
let mut alias = match iter.next() {
|
||||
Some(s) => s.as_bytes().to_vec(),
|
||||
None => break
|
||||
};
|
||||
alias.push(b'\0');
|
||||
_proto_aliases.push(alias);
|
||||
}
|
||||
let mut proto_aliases: Vec<*mut i8> = _proto_aliases.iter_mut().map(|x| x.as_mut_ptr() as *mut i8).collect();
|
||||
proto_aliases.push(ptr::null_mut());
|
||||
|
||||
PROTO_ALIASES = Some(_proto_aliases);
|
||||
PROTO_NAME = Some(proto_name);
|
||||
|
||||
|
||||
PROTO_ENTRY = protoent {
|
||||
p_name: PROTO_NAME.as_mut().unwrap().as_mut_slice().as_mut_ptr() as *mut c_char,
|
||||
p_aliases: proto_aliases.as_mut_slice().as_mut_ptr() as *mut *mut i8,
|
||||
p_proto: PROTO_NUM.unwrap()
|
||||
};
|
||||
if PROTO_STAYOPEN == 0 { endprotoent(); }
|
||||
&PROTO_ENTRY as *const protoent
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn getservbyname(
|
||||
name: *const c_char,
|
||||
proto: *const c_char)
|
||||
-> *const servent {
|
||||
setservent(SERV_STAYOPEN);
|
||||
let mut p: *const servent;
|
||||
if proto.is_null() {
|
||||
while { p = getservent(); p!=ptr::null() } {
|
||||
if strcasecmp((*p).s_name, name) == 0 {
|
||||
setservent(SERV_STAYOPEN);
|
||||
return p;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while { p = getservent(); p!=ptr::null() } {
|
||||
if strcasecmp((*p).s_name, name) == 0 && strcasecmp((*p).s_proto, proto) == 0 {
|
||||
setservent(SERV_STAYOPEN);
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
setservent(SERV_STAYOPEN);
|
||||
platform::errno = ENOENT;
|
||||
ptr::null() as *const servent
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn getservbyport(
|
||||
port: c_int,
|
||||
proto: *const c_char)
|
||||
-> *const servent {
|
||||
setservent(SERV_STAYOPEN);
|
||||
let mut p: *const servent;
|
||||
if proto.is_null() {
|
||||
while { p=getservent(); p!=ptr::null()} {
|
||||
if (*p).s_port == port {
|
||||
setservent(SERV_STAYOPEN);
|
||||
return p;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while { p=getservent(); p!=ptr::null()} {
|
||||
if (*p).s_port == port && strcasecmp((*p).s_proto, proto) == 0 {
|
||||
setservent(SERV_STAYOPEN);
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
setservent(SERV_STAYOPEN);
|
||||
platform::errno = ENOENT;
|
||||
ptr::null()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn getservent() -> *const servent {
|
||||
if SERVDB == 0 {
|
||||
SERVDB = Sys::open(&CString::new("/etc/services").unwrap(), O_RDONLY, 0);
|
||||
}
|
||||
let mut rlb = RawLineBuffer::new(SERVDB);
|
||||
rlb.seek(S_POS);
|
||||
|
||||
let mut r: Box<str> = Box::default();
|
||||
while r.is_empty() || r.split_whitespace().next() == None || r.starts_with("#") {
|
||||
r = match rlb.next() {
|
||||
Line::Some(s) => bytes_to_box_str(s),
|
||||
_ => {
|
||||
if SERV_STAYOPEN == 0 { endservent(); }
|
||||
return ptr::null();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
rlb.next();
|
||||
S_POS = rlb.line_pos();
|
||||
|
||||
let mut iter: SplitWhitespace = r.split_whitespace();
|
||||
let mut serv_name: Vec<u8> = iter.next().unwrap().as_bytes().to_vec();
|
||||
serv_name.push(b'\0');
|
||||
let port_proto = iter.next().unwrap();
|
||||
let mut split = port_proto.split("/");
|
||||
let mut port = split.next().unwrap().as_bytes().to_vec();
|
||||
port.push(b'\0');
|
||||
SERV_PORT = Some(htons(atoi(port.as_mut_slice().as_mut_ptr() as *mut i8) as u16) as u32 as i32);
|
||||
let mut proto = split.next().unwrap().as_bytes().to_vec();
|
||||
proto.push(b'\0');
|
||||
|
||||
/*
|
||||
*let mut _serv_aliases: Vec<Vec<u8>> = Vec::new();
|
||||
*loop {
|
||||
* let mut alias = match iter.next() {
|
||||
* Some(s) => s.as_bytes().to_vec(),
|
||||
* _ => break
|
||||
* };
|
||||
* alias.push(b'\0');
|
||||
* _serv_aliases.push(alias);
|
||||
*}
|
||||
*let mut serv_aliases: Vec<*mut i8> = _serv_aliases.iter_mut().map(|x| x.as_mut_ptr() as *mut i8).collect();
|
||||
*serv_aliases.push(ptr::null_mut());
|
||||
*
|
||||
*/
|
||||
let mut _serv_aliases: Vec<Vec<u8>> = Vec::new();
|
||||
_serv_aliases.push(vec![b'\0']);
|
||||
let mut serv_aliases: Vec<*mut i8> = Vec::new();
|
||||
serv_aliases.push(ptr::null_mut());
|
||||
serv_aliases.push(ptr::null_mut());
|
||||
|
||||
SERV_ALIASES = Some(_serv_aliases);
|
||||
SERV_NAME = Some(serv_name);
|
||||
SERV_PROTO = Some(proto);
|
||||
|
||||
SERV_ENTRY = servent {
|
||||
s_name: SERV_NAME.as_mut().unwrap().as_mut_slice().as_mut_ptr() as *mut c_char,
|
||||
s_aliases: serv_aliases.as_mut_slice().as_mut_ptr() as *mut *mut i8,
|
||||
s_port: SERV_PORT.unwrap(),
|
||||
s_proto: SERV_PROTO.as_mut().unwrap().as_mut_slice().as_mut_ptr() as *mut c_char
|
||||
};
|
||||
|
||||
if SERV_STAYOPEN == 0 { endservent(); }
|
||||
&SERV_ENTRY as *const servent
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn sethostent(stayopen: c_int) {
|
||||
HOST_STAYOPEN = stayopen;
|
||||
if HOSTDB == 0 {
|
||||
HOSTDB = Sys::open(&CString::new("/etc/hosts").unwrap(), O_RDONLY, 0)
|
||||
} else {
|
||||
Sys::lseek(HOSTDB, 0, SEEK_SET);
|
||||
}
|
||||
H_POS = 0;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn setnetent(stayopen: c_int) {
|
||||
NET_STAYOPEN = stayopen;
|
||||
if NETDB == 0 {
|
||||
NETDB = Sys::open(&CString::new("/etc/networks").unwrap(), O_RDONLY, 0)
|
||||
} else {
|
||||
Sys::lseek(NETDB, 0, SEEK_SET);
|
||||
N_POS = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn setprotoent(stayopen: c_int) {
|
||||
PROTO_STAYOPEN = stayopen;
|
||||
if PROTODB == 0 {
|
||||
PROTODB = Sys::open(&CString::new("/etc/protocols").unwrap(), O_RDONLY, 0)
|
||||
} else {
|
||||
Sys::lseek(PROTODB, 0, SEEK_SET);
|
||||
P_POS = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn setservent(stayopen: c_int) {
|
||||
SERV_STAYOPEN = stayopen;
|
||||
if SERVDB == 0 {
|
||||
SERVDB = Sys::open(&CString::new("/etc/services").unwrap(), O_RDONLY, 0)
|
||||
} else {
|
||||
Sys::lseek(SERVDB, 0, SEEK_SET);
|
||||
S_POS = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn getaddrinfo(
|
||||
node: *const c_char,
|
||||
service: *const c_char,
|
||||
hints: *const addrinfo,
|
||||
res: *mut *mut addrinfo)
|
||||
-> c_int {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn getnameinfo(
|
||||
addr: *const sockaddr,
|
||||
addrlen: socklen_t,
|
||||
host: *mut c_char,
|
||||
hostlen: socklen_t,
|
||||
serv: *mut c_char,
|
||||
servlen: socklen_t,
|
||||
flags: c_int)
|
||||
-> c_int {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
pub extern "C" fn freeaddrinfo(res: *mut addrinfo) {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
pub extern "C" fn gai_strerror(errcode: c_int) -> *const c_char {
|
||||
unimplemented!();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
use alloc::string::String;
|
||||
use c_str::CString;
|
||||
use platform::rawfile::file_read_all;
|
||||
|
||||
pub fn get_dns_server() -> String {
|
||||
String::from_utf8(file_read_all(&CString::new("/etc/net/dns").unwrap()).unwrap()).unwrap()
|
||||
}
|
||||
@@ -7,7 +7,7 @@ pub type in_addr_t = u32;
|
||||
pub type in_port_t = u16;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct in_addr {
|
||||
pub s_addr: in_addr_t,
|
||||
}
|
||||
@@ -18,10 +18,12 @@ pub struct in6_addr {
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct sockaddr_in {
|
||||
pub sin_family: sa_family_t,
|
||||
pub sin_port: in_port_t,
|
||||
pub sin_addr: in_addr,
|
||||
pub _pad: [c_char; 8],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
||||
+7
-45
@@ -7,7 +7,7 @@ use c_str::CStr;
|
||||
use header::{errno, fcntl};
|
||||
use platform;
|
||||
use platform::types::*;
|
||||
use platform::RawFile;
|
||||
use platform::{Line, RawFile, RawLineBuffer};
|
||||
use platform::{Pal, Sys};
|
||||
|
||||
#[repr(C)]
|
||||
@@ -56,54 +56,16 @@ where
|
||||
Err(_) => return OptionPasswd::Error,
|
||||
};
|
||||
|
||||
let mut buf = Vec::new();
|
||||
let mut newline = None;
|
||||
let mut rlb = RawLineBuffer::new(*file);
|
||||
|
||||
loop {
|
||||
// TODO when nll becomes a thing:
|
||||
// let mut newline;
|
||||
|
||||
// WORKAROUND:
|
||||
if let Some(newline) = newline {
|
||||
buf.drain(..newline + 1);
|
||||
}
|
||||
|
||||
// Read until newline
|
||||
loop {
|
||||
newline = buf.iter().position(|b| *b == b'\n');
|
||||
|
||||
if newline.is_some() {
|
||||
break;
|
||||
}
|
||||
|
||||
let len = buf.len();
|
||||
|
||||
if len >= buf.capacity() {
|
||||
buf.reserve(1024);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let capacity = buf.capacity();
|
||||
buf.set_len(capacity);
|
||||
}
|
||||
|
||||
let read = Sys::read(*file, &mut buf[len..]);
|
||||
|
||||
unsafe {
|
||||
buf.set_len(len + read as usize);
|
||||
}
|
||||
|
||||
if read == 0 {
|
||||
return OptionPasswd::NotFound;
|
||||
}
|
||||
if read < 0 {
|
||||
return OptionPasswd::Error;
|
||||
}
|
||||
}
|
||||
let line = match rlb.next() {
|
||||
Line::Error => return OptionPasswd::Error,
|
||||
Line::EOF => return OptionPasswd::NotFound,
|
||||
Line::Some(line) => line
|
||||
};
|
||||
|
||||
// Parse into passwd
|
||||
let newline = newline.unwrap(); // safe because it doesn't break the loop otherwise
|
||||
let line = &buf[..newline];
|
||||
let mut parts: [&[u8]; 7] = [&[]; 7];
|
||||
for (i, part) in line.splitn(7, |b| *b == b':').enumerate() {
|
||||
parts[i] = part;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use core::fmt::Write as CoreWrite;
|
||||
use core::{slice, str};
|
||||
use core::{slice, str, ptr};
|
||||
|
||||
use platform::types::*;
|
||||
use platform::{self, Write};
|
||||
@@ -61,8 +61,11 @@ pub unsafe fn printf<W: Write>(w: W, format: *const c_char, mut ap: VaList) -> c
|
||||
let a = ap.get::<*const c_char>();
|
||||
|
||||
found_percent = false;
|
||||
|
||||
w.write_str(str::from_utf8_unchecked(platform::c_str(a)))
|
||||
if a != ptr::null() {
|
||||
w.write_str(str::from_utf8_unchecked(platform::c_str(a)))
|
||||
} else {
|
||||
w.write_str("NULL")
|
||||
}
|
||||
}
|
||||
'u' => {
|
||||
let a = ap.get::<c_uint>();
|
||||
|
||||
@@ -109,6 +109,7 @@ pub unsafe extern "C" fn strcasecmp(mut first: *const c_char, mut second: *const
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn strncasecmp(
|
||||
mut first: *const c_char,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
sys_includes = ["stddef.h", "stdint.h", "sys/types.h"]
|
||||
include_guard = "_SYS_SOCKET_H"
|
||||
style = "Tag"
|
||||
style = "Both"
|
||||
language = "C"
|
||||
|
||||
[defines]
|
||||
|
||||
@@ -14,6 +14,7 @@ pub type sa_family_t = u16;
|
||||
pub type socklen_t = u32;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
pub struct sockaddr {
|
||||
pub sa_family: sa_family_t,
|
||||
data: [c_char; 14],
|
||||
|
||||
@@ -44,7 +44,7 @@ pub(crate) const MON_NAMES: [&str; 12] = [
|
||||
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
|
||||
];
|
||||
|
||||
pub(crate) const CLOCK_REALTIME: clockid_t = 0;
|
||||
pub const CLOCK_REALTIME: clockid_t = 0;
|
||||
pub const CLOCK_MONOTONIC: clockid_t = 1;
|
||||
pub(crate) const CLOCK_PROCESS_CPUTIME_ID: clockid_t = 2;
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@ pub use self::rawfile::RawFile;
|
||||
|
||||
pub mod rawfile;
|
||||
|
||||
pub use self::rlb::{Line, RawLineBuffer};
|
||||
pub mod rlb;
|
||||
|
||||
use self::types::*;
|
||||
pub mod types;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use alloc::Vec;
|
||||
use core::ops::Deref;
|
||||
|
||||
use super::{types::*, Pal, Sys};
|
||||
@@ -46,3 +47,34 @@ impl Deref for RawFile {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file_read_all(path: &CStr) -> Result<Vec<u8>, ()> {
|
||||
let file = RawFile::open(path, 0, 0o644)?;
|
||||
|
||||
let mut buf = Vec::new();
|
||||
let mut len = 0;
|
||||
|
||||
loop {
|
||||
if len >= buf.capacity() {
|
||||
buf.reserve(32);
|
||||
|
||||
unsafe {
|
||||
let capacity = buf.capacity();
|
||||
buf.set_len(capacity);
|
||||
}
|
||||
}
|
||||
|
||||
let read = Sys::read(*file, &mut buf[len..]);
|
||||
|
||||
len += read as usize;
|
||||
|
||||
if read == 0 {
|
||||
unsafe { buf.set_len(len); }
|
||||
return Ok(buf);
|
||||
}
|
||||
if read < 0 {
|
||||
unsafe { buf.set_len(len); }
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use platform::{Pal, Sys};
|
||||
use platform::types::*;
|
||||
|
||||
use header::unistd::{lseek, SEEK_SET};
|
||||
/// Implements an `Iterator` which returns on either newline or EOF.
|
||||
#[derive(Clone)]
|
||||
pub struct RawLineBuffer {
|
||||
pub fd: c_int,
|
||||
buf: Vec<u8>,
|
||||
newline: Option<usize>,
|
||||
read: usize
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum Line<'a> {
|
||||
Error,
|
||||
EOF,
|
||||
Some(&'a [u8])
|
||||
}
|
||||
|
||||
impl RawLineBuffer {
|
||||
pub const fn new(fd: c_int) -> Self {
|
||||
Self {
|
||||
fd: fd,
|
||||
buf: Vec::new(),
|
||||
newline: None,
|
||||
read: 0
|
||||
}
|
||||
}
|
||||
|
||||
// Can't use iterators because we want to return a reference.
|
||||
// See https://stackoverflow.com/a/30422716/5069285
|
||||
pub fn next(&mut self) -> Line {
|
||||
// Remove last line
|
||||
if let Some(newline) = self.newline {
|
||||
self.buf.drain(..newline + 1);
|
||||
}
|
||||
|
||||
loop {
|
||||
// Exit if newline was read already
|
||||
self.newline = self.buf.iter().position(|b| *b == b'\n');
|
||||
|
||||
if self.newline.is_some() {
|
||||
break;
|
||||
}
|
||||
|
||||
let len = self.buf.len();
|
||||
|
||||
if len >= self.buf.capacity() {
|
||||
self.buf.reserve(1024);
|
||||
}
|
||||
|
||||
// Create buffer of what's left in the vector, uninitialized memory
|
||||
unsafe {
|
||||
let capacity = self.buf.capacity();
|
||||
self.buf.set_len(capacity);
|
||||
}
|
||||
|
||||
let read = Sys::read(self.fd, &mut self.buf[len..]);
|
||||
|
||||
let read_usize = read.max(0) as usize;
|
||||
|
||||
// Remove all uninitialized memory that wasn't read
|
||||
unsafe {
|
||||
self.buf.set_len(len + read_usize);
|
||||
}
|
||||
|
||||
self.read += read_usize;
|
||||
|
||||
if read == 0 {
|
||||
return Line::EOF;
|
||||
}
|
||||
if read < 0 {
|
||||
return Line::Error;
|
||||
}
|
||||
}
|
||||
|
||||
let newline = self.newline.unwrap(); // safe because it doesn't break the loop otherwise
|
||||
Line::Some(&self.buf[..newline])
|
||||
}
|
||||
|
||||
/// Return the byte position of the start of the line
|
||||
pub fn line_pos(&self) -> usize {
|
||||
self.read - self.buf.len()
|
||||
}
|
||||
|
||||
/// Seek to a byte position in the file
|
||||
pub fn seek(&mut self, pos: usize) -> off_t {
|
||||
let ret = lseek(self.fd, pos as i64, SEEK_SET);
|
||||
if ret != !0 {
|
||||
self.read = pos;
|
||||
}
|
||||
ret
|
||||
}
|
||||
}
|
||||
@@ -107,6 +107,7 @@ impl<'a> From<&'a timespec> for redox_timespec {
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
pub struct stat {
|
||||
pub st_dev: dev_t,
|
||||
pub st_ino: ino_t,
|
||||
|
||||
+2
-1
@@ -10,17 +10,18 @@ EXPECT_BINS=\
|
||||
fnmatch \
|
||||
locale \
|
||||
math \
|
||||
netdb \
|
||||
select \
|
||||
setjmp \
|
||||
signal \
|
||||
stdio/all \
|
||||
stdio/setvbuf \
|
||||
stdio/freopen \
|
||||
stdio/fwrite \
|
||||
stdio/getc_unget \
|
||||
stdio/printf \
|
||||
stdio/rename \
|
||||
stdio/scanf \
|
||||
stdio/setvbuf \
|
||||
stdio/sprintf \
|
||||
stdlib/a64l \
|
||||
stdlib/atof \
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
No visible errors occurred!
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
/* Copyright (C) 1998-2018 Free Software Foundation, Inc.
|
||||
This file is part of the GNU C Library.
|
||||
Contributed by Andreas Jaeger <aj@suse.de>, 1998.
|
||||
The GNU C Library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
The GNU C Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the GNU C Library; if not, see
|
||||
<http://www.gnu.org/licenses/>. */
|
||||
/*
|
||||
Testing of some network related lookup functions.
|
||||
The system databases looked up are:
|
||||
- /etc/services
|
||||
- /etc/hosts
|
||||
- /etc/networks
|
||||
- /etc/protocols
|
||||
The tests try to be fairly generic and simple so that they work on
|
||||
every possible setup (and might therefore not detect some possible
|
||||
errors).
|
||||
*/
|
||||
|
||||
#include <netdb.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
int error_count;
|
||||
static void
|
||||
output_servent (const char *call, struct servent *sptr)
|
||||
{
|
||||
char **pptr;
|
||||
if (sptr == NULL)
|
||||
printf ("Call: %s returned NULL\n", call);
|
||||
else
|
||||
{
|
||||
printf ("Call: %s, returned: s_name: %s, s_port: %d, s_proto: %s\n",
|
||||
call, sptr->s_name, ntohs(sptr->s_port), sptr->s_proto);
|
||||
for (pptr = sptr->s_aliases; *pptr != NULL; pptr++)
|
||||
printf (" alias: %s\n", *pptr);
|
||||
}
|
||||
}
|
||||
static void
|
||||
test_services (void)
|
||||
{
|
||||
struct servent *sptr;
|
||||
sptr = getservbyname ("domain", "tcp");
|
||||
// output_servent ("getservbyname (\"domain\", \"tcp\")", sptr);
|
||||
sptr = getservbyname ("domain", "udp");
|
||||
// output_servent ("getservbyname (\"domain\", \"udp\")", sptr);
|
||||
sptr = getservbyname ("domain", NULL);
|
||||
// output_servent ("getservbyname (\"domain\", NULL)", sptr);
|
||||
sptr = getservbyname ("not-existant", NULL);
|
||||
// output_servent ("getservbyname (\"not-existant\", NULL)", sptr);
|
||||
/* This shouldn't return anything. */
|
||||
sptr = getservbyname ("", "");
|
||||
// output_servent ("getservbyname (\"\", \"\")", sptr);
|
||||
sptr = getservbyname ("", "tcp");
|
||||
// output_servent ("getservbyname (\"\", \"tcp\")", sptr);
|
||||
sptr = getservbyport (htons(53), "tcp");
|
||||
// output_servent ("getservbyport (htons(53), \"tcp\")", sptr);
|
||||
sptr = getservbyport (htons(53), NULL);
|
||||
// output_servent ("getservbyport (htons(53), NULL)", sptr);
|
||||
sptr = getservbyport (htons(1), "udp"); /* shouldn't exist */
|
||||
// output_servent ("getservbyport (htons(1), \"udp\")", sptr);
|
||||
setservent (0);
|
||||
do
|
||||
{
|
||||
sptr = getservent ();
|
||||
//output_servent ("getservent ()", sptr);
|
||||
}
|
||||
while (sptr != NULL);
|
||||
endservent ();
|
||||
}
|
||||
static void
|
||||
output_hostent (const char *call, struct hostent *hptr)
|
||||
{
|
||||
char **pptr;
|
||||
char buf[INET6_ADDRSTRLEN];
|
||||
if (hptr == NULL)
|
||||
printf ("Call: %s returned NULL\n", call);
|
||||
else
|
||||
{
|
||||
printf ("Call: %s returned: name: %s, addr_type: %d\n",
|
||||
call, hptr->h_name, hptr->h_addrtype);
|
||||
if (hptr->h_aliases)
|
||||
for (pptr = hptr->h_aliases; *pptr != NULL; pptr++)
|
||||
printf (" alias: %s\n", *pptr);
|
||||
for (pptr = hptr->h_addr_list; *pptr != NULL; pptr++)
|
||||
printf (" ip: %s\n",
|
||||
inet_ntop (hptr->h_addrtype, *pptr, buf, sizeof (buf)));
|
||||
}
|
||||
}
|
||||
static void
|
||||
test_hosts (void)
|
||||
{
|
||||
struct hostent *hptr1, *hptr2;
|
||||
char *name = NULL;
|
||||
size_t namelen = 0;
|
||||
struct in_addr ip;
|
||||
hptr1 = gethostbyname ("localhost");
|
||||
hptr2 = gethostbyname ("LocalHost");
|
||||
if (hptr1 != NULL || hptr2 != NULL)
|
||||
{
|
||||
if (hptr1 == NULL)
|
||||
{
|
||||
printf ("localhost not found - but LocalHost found:-(\n");
|
||||
++error_count;
|
||||
}
|
||||
else if (hptr2 == NULL)
|
||||
{
|
||||
printf ("LocalHost not found - but localhost found:-(\n");
|
||||
++error_count;
|
||||
}
|
||||
else if (strcmp (hptr1->h_name, hptr2->h_name) != 0)
|
||||
{
|
||||
printf ("localhost and LocalHost have different canoncial name\n");
|
||||
printf ("gethostbyname (\"localhost\")->%s\n", hptr1->h_name);
|
||||
printf ("gethostbyname (\"LocalHost\")->%s\n", hptr2->h_name);
|
||||
++error_count;
|
||||
}
|
||||
//else
|
||||
// output_hostent ("gethostbyname(\"localhost\")", hptr1);
|
||||
}
|
||||
hptr1 = gethostbyname ("127.0.0.1");
|
||||
//output_hostent ("gethostbyname (\"127.0.0.1\")", hptr1);
|
||||
while (gethostname (name, namelen) < 0 && errno == ENAMETOOLONG)
|
||||
{
|
||||
namelen += 2; /* tiny increments to test a lot */
|
||||
name = realloc (name, namelen);
|
||||
}
|
||||
if (gethostname (name, namelen) == 0)
|
||||
{
|
||||
// printf ("Hostname: %s\n", name);
|
||||
if (name != NULL)
|
||||
{
|
||||
hptr1 = gethostbyname (name);
|
||||
// output_hostent ("gethostbyname (gethostname(...))", hptr1);
|
||||
}
|
||||
}
|
||||
ip.s_addr = htonl (INADDR_LOOPBACK);
|
||||
|
||||
hptr1 = gethostbyaddr ((char *) &ip, sizeof(ip), AF_INET);
|
||||
if (hptr1 != NULL)
|
||||
{
|
||||
// printf ("official name of 127.0.0.1: %s\n", hptr1->h_name);
|
||||
}
|
||||
sethostent (0);
|
||||
do
|
||||
{
|
||||
hptr1 = gethostent ();
|
||||
//output_hostent ("gethostent ()", hptr1);
|
||||
}
|
||||
while (hptr1 != NULL);
|
||||
endhostent ();
|
||||
struct hostent* redox = gethostbyname("redox-os.org");
|
||||
if (redox == NULL) {
|
||||
++error_count;
|
||||
}
|
||||
//output_hostent("gethostbyname(\"redox-os.org\")", redox);
|
||||
struct in_addr el_goog;
|
||||
inet_aton("8.8.4.4", &el_goog);
|
||||
struct hostent* google = gethostbyaddr(&el_goog, 4, AF_INET);
|
||||
if (google == NULL) {
|
||||
++error_count;
|
||||
}
|
||||
//output_hostent("gethostbyaddr(\"8.8.4.4\")",google);
|
||||
}
|
||||
|
||||
static void
|
||||
output_protoent (const char *call, struct protoent *prptr)
|
||||
{
|
||||
char **pptr;
|
||||
if (prptr == NULL)
|
||||
printf ("Call: %s returned NULL\n", call);
|
||||
else
|
||||
{
|
||||
printf ("Call: %s, returned: p_name: %s, p_proto: %d\n",
|
||||
call, prptr->p_name, prptr->p_proto);
|
||||
for (pptr = prptr->p_aliases; *pptr != NULL; pptr++)
|
||||
printf (" alias: %s\n", *pptr);
|
||||
}
|
||||
}
|
||||
static void
|
||||
test_protocols (void)
|
||||
{
|
||||
struct protoent *prptr;
|
||||
prptr = getprotobyname ("ICMP");
|
||||
// output_protoent ("getprotobyname (\"ICMP\")", prptr);
|
||||
prptr = getprotobynumber (1);
|
||||
// output_protoent ("getprotobynumber (1)", prptr);
|
||||
setprotoent (0);
|
||||
do
|
||||
{
|
||||
prptr = getprotoent ();
|
||||
// output_protoent ("getprotoent ()", prptr);
|
||||
}
|
||||
while (prptr != NULL);
|
||||
endprotoent ();
|
||||
}
|
||||
static int
|
||||
do_test (void)
|
||||
{
|
||||
/*
|
||||
setdb ("db");
|
||||
*/
|
||||
test_hosts ();
|
||||
//test_network ();
|
||||
test_protocols ();
|
||||
test_services ();
|
||||
if (error_count)
|
||||
printf ("\n %d errors occurred!\n", error_count);
|
||||
else
|
||||
printf ("No visible errors occurred!\n");
|
||||
return (error_count != 0);
|
||||
}
|
||||
|
||||
int main() {
|
||||
do_test();
|
||||
}
|
||||
Reference in New Issue
Block a user