1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use crate::api::console::Style;
use crate::api::fs;
use crate::api::process::ExitCode;
use crate::api::syscall;
use crate::sys::console;
use crate::{api, usr};
use alloc::borrow::ToOwned;
use alloc::format;
use alloc::vec::Vec;
pub fn main(args: &[&str]) -> Result<(), ExitCode> {
if args.len() != 2 {
help();
return Err(ExitCode::UsageError);
}
if args[1] == "-h" || args[1] == "--help" {
help();
return Ok(());
}
let mut path = args[1];
// The commands `read /usr/alice/` and `read /usr/alice` are equivalent,
// but `read /` should not be modified.
if path.len() > 1 {
path = path.trim_end_matches('/');
}
// TODO: Create device drivers for `/net` hardcoded commands
if path.starts_with("/net/") {
let csi_option = Style::color("aqua");
let csi_title = Style::color("yellow");
let csi_reset = Style::reset();
// Examples:
// > read /net/http/example.com/articles
// > read /net/http/example.com:8080/articles/index.html
// > read /net/daytime/time.nist.gov
// > read /net/tcp/time.nist.gov:13
let parts: Vec<_> = path.split('/').collect();
if parts.len() < 4 {
println!(
"{}Usage:{} read {}/net/<proto>/<host>[:<port>]/<path>{1}",
csi_title, csi_reset, csi_option
);
Err(ExitCode::Failure)
} else {
let host = parts[3];
match parts[2] {
"tcp" => {
if host.contains(':') {
usr::tcp::main(&["tcp", host])
} else {
error!("Missing port number");
Err(ExitCode::Failure)
}
}
"daytime" => {
if host.contains(':') {
usr::tcp::main(&["tcp", host])
} else {
usr::tcp::main(&["tcp", &format!("{}:13", host)])
}
}
"http" => {
let host = parts[3];
let path = "/".to_owned() + &parts[4..].join("/");
usr::http::main(&["http", host, &path])
}
_ => {
error!("Unknown protocol '{}'", parts[2]);
Err(ExitCode::Failure)
}
}
}
} else if path.ends_with(".bmp") {
usr::render::main(args)
} else if let Some(info) = syscall::info(path) {
if info.is_file() {
if let Ok(buf) = api::fs::read_to_bytes(path) {
syscall::write(1, &buf);
Ok(())
} else {
error!("Could not read '{}'", path);
Err(ExitCode::Failure)
}
} else if info.is_dir() {
usr::list::main(args)
} else if info.is_device() {
// TODO: Add a way to read the device file to get its type directly
// instead of relying on the various device file sizes. We could
// maybe allow `sys::fs::file::File::open()` to open a Device file
// as a regular file and read the type in the first byte of the
// file.
let n = info.size();
let is_char_device = n == 4;
let is_block_device = n > 8;
loop {
if console::end_of_text() || console::end_of_transmission() {
println!();
return Ok(());
}
if let Ok(bytes) = fs::read_to_bytes(path) {
if is_char_device && bytes.len() == 1 {
match bytes[0] as char {
api::console::ETX_KEY => {
println!("^C");
return Ok(());
}
api::console::EOT_KEY => {
println!("^D");
return Ok(());
}
_ => {}
}
}
for b in bytes {
print!("{}", b as char);
}
if is_block_device {
println!();
return Ok(());
}
} else {
error!("Could not read '{}'", path);
return Err(ExitCode::Failure);
}
}
} else {
error!("Could not read type of '{}'", path);
Err(ExitCode::Failure)
}
} else {
error!("Could not find file '{}'", path);
Err(ExitCode::Failure)
}
}
fn help() {
let csi_option = Style::color("aqua");
let csi_title = Style::color("yellow");
let csi_reset = Style::reset();
println!(
"{}Usage:{} read {}<path>{}",
csi_title, csi_reset, csi_option, csi_reset
);
println!();
println!("{}Paths:{}", csi_title, csi_reset);
println!(" {0}<dev>{1} Read device", csi_option, csi_reset);
println!(" {0}<dir>[/]{1} Read directory", csi_option, csi_reset);
println!(" {0}<file>{1} Read file", csi_option, csi_reset);
}