hosts.ha (3031B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use bufio; 5 use encoding::utf8; 6 use fs; 7 use io; 8 use memio; 9 use net::ip; 10 use os; 11 use strings; 12 13 // Represents a host line in /etc/hosts, guaranteed to have at least a single 14 // name. The first name is the canonical one. 15 export type host = struct { 16 addr: ip::addr, 17 names: []str, 18 }; 19 20 // Iterator through the host lines in a host file. 21 export type iterator = struct { 22 handle: io::handle, 23 }; 24 25 // Returned when an invalid host line was found. 26 export type invalid = !void; 27 28 // All possible errors returned from this module. 29 export type error = !(io::error | invalid | utf8::invalid | ip::invalid 30 | fs::error); 31 32 // Converts an [[error]] to a human-friendly representation. 33 export fn strerror(err: error) const str = match (err) { 34 case invalid => 35 return "Host file format is invalid"; 36 case utf8::invalid => 37 return "File is invalid UTF-8"; 38 case ip::invalid => 39 return "IP address is invalid"; 40 case let err: io::error => 41 return io::strerror(err); 42 case let err: fs::error => 43 return fs::strerror(err); 44 }; 45 46 // Creates an [[iterator]] for a provided [[io::handle]] pointing to 47 // the /etc/hosts file. The user should call [[next]] to iterate through 48 // host lines. 49 export fn iter(in: io::handle) iterator = iterator { 50 handle = in, 51 }; 52 53 // Returns the next host line as a [[host]] type. 54 export fn next(it: *iterator) (host | void | error) = for (true) { 55 const line = match (bufio::read_line(it.handle)) { 56 case io::EOF => 57 return void; 58 case let line: []u8 => 59 yield line; 60 }; 61 defer free(line); 62 if (len(line) == 0 || line[0] == '#') { 63 continue; 64 }; 65 66 const scanner = memio::fixed(line); 67 const tok = match (bufio::read_tok(&scanner, ' ', '\t')?) { 68 case io::EOF => 69 return void; 70 case let tok: []u8 => 71 yield tok; 72 }; 73 defer free(tok); 74 const addr = ip::parse(strings::fromutf8(tok)?)?; 75 76 let names: []str = []; 77 for (true) { 78 const tok = match (bufio::read_tok(&scanner, ' ', '\t')?) { 79 case io::EOF => 80 break; 81 case let tok: []u8 => 82 yield tok; 83 }; 84 if (len(tok) == 0) continue; 85 86 append(names, strings::fromutf8(tok)?); 87 }; 88 if (len(names) == 0) { 89 return invalid; 90 }; 91 92 return host { 93 addr = addr, 94 names = names, 95 }; 96 }; 97 98 // Looks up a slice of addresses from /etc/hosts. 99 export fn lookup(name: const str) ([]ip::addr | error) = { 100 const file = os::open(PATH)?; 101 defer io::close(file)!; 102 let it = iter(file); 103 return iter_lookup(&it, name); 104 }; 105 106 // Looks up a slice of addresses given an [[iterator]] to /etc/hosts. 107 export fn iter_lookup(it: *iterator, name: const str) ([]ip::addr | error) = { 108 let addrs: []ip::addr = []; 109 for (true) match(next(it)?) { 110 case void => break; 111 case let h: host => 112 defer finish(h); 113 for (let i = 0z; i < len(h.names); i += 1) { 114 if (h.names[i] == name) { 115 append(addrs, h.addr); 116 }; 117 }; 118 }; 119 return addrs; 120 }; 121 122 // Frees resources associated with a [[host]]. 123 export fn finish(host: host) void = { 124 for (let i = 0z; i < len(host.names); i += 1) { 125 free(host.names[i]); 126 }; 127 free(host.names); 128 };