1use std::io::Read;
2
3use std::os::unix::fs::MetadataExt;
4use std::os::unix::fs::PermissionsExt;
5
6pub mod args;
7
8mod color;
9use color::Colorize;
10
11const OWNER_READ: u16 = 0o0400;
13const OWNER_WRITE: u16 = 0o0200;
14const OWNER_EXECUTE: u16 = 0o0100;
15
16const GROUP_READ: u16 = 0o0040;
17const GROUP_WRITE: u16 = 0o0020;
18const GROUP_EXECUTE: u16 = 0o0010;
19
20const OTHER_READ: u16 = 0o0004;
21const OTHER_WRITE: u16 = 0o0002;
22const OTHER_EXECUTE: u16 = 0o0001;
23
24pub enum Format {
25 Horizontal,
27
28 Vertical,
34
35 VerticalMatrix(usize),
38
39 Long,
43}
44
45pub struct ReadDirOptions {
46 pub dotfiles: bool,
48
49 pub implied: bool,
51
52 pub sort: bool,
54
55 pub reverse: bool,
57
58 pub colored: bool,
60
61 pub format: Option<Format>,
63
64 pub directory: String,
66}
67
68impl ReadDirOptions {
69 pub fn new() -> Self {
70 Self {
71 dotfiles: false,
72 implied: false,
73 sort: true,
74 reverse: false,
75 colored: true,
76 format: Some(Format::VerticalMatrix(5)),
77 directory: String::from("./"),
78 }
79 }
80}
81
82pub fn read_dir_to_string(options: ReadDirOptions) -> String {
83 let mut paths: Vec<String> = match std::fs::read_dir(options.directory.as_str()) {
84 Ok(entry) => entry,
85 Err(err) => match err.kind() {
86 std::io::ErrorKind::PermissionDenied => {
87 eprintln!("Error: you don't have permission to read {}", &options.directory);
88 std::process::exit(1);
89 }
90 _ => {
91 eprintln!("Internal Error: failed to directory {}", &options.directory);
92 std::process::exit(1);
93 }
94 },
95 }
96 .map(|entry| match entry {
97 Ok(entry) => entry
98 .file_name()
99 .into_string()
100 .unwrap_or(String::from("invalid unicode").blue_fg().red_bg().bold()),
101 Err(_) => {
102 eprintln!("Internal Error: failed to read entry");
103 std::process::exit(1);
104 }
105 })
106 .collect();
107
108 if !options.dotfiles {
109 paths = paths
110 .into_iter()
111 .filter(|path| path.as_bytes()[0] != b'.')
112 .collect();
113 }
114
115 if options.sort {
116 paths.sort();
117 }
118
119 if options.reverse {
120 paths.reverse();
121 }
122
123 let paths_bland = paths.clone(); if options.colored {
126 for path in &mut paths {
127 if std::path::Path::new(&(format!("{}/{path}", &options.directory))).is_dir() {
128 *path = path.bold().blue_fg();
129 }
130 }
131 }
132
133 match options.format {
134 Some(format) => match format {
135 Format::Horizontal => paths.join(" "),
136
137 Format::Long => std::iter::zip(paths, paths_bland)
140 .map(|(path, bland)| {
141 let metadata = std::fs::metadata(&bland).unwrap_or_else(|_| {
142 eprintln!("Internal Error: unable to read metadata for file {bland:?}");
143 std::process::exit(1);
144 });
145
146 let is_dir = if metadata.is_dir() { 'd' } else { '-' };
147
148 let mode = metadata.permissions().mode() as u16;
149
150 let ur = if mode & OWNER_READ == 0 { '-' } else { 'r' };
152 let uw = if mode & OWNER_WRITE == 0 { '-' } else { 'w' };
153 let ux = if mode & OWNER_EXECUTE == 0 { '-' } else { 'x' };
154
155 let gr = if mode & GROUP_READ == 0 { '-' } else { 'r' };
157 let gw = if mode & GROUP_WRITE == 0 { '-' } else { 'w' };
158 let gx = if mode & GROUP_EXECUTE == 0 { '-' } else { 'x' };
159
160 let or = if mode & OTHER_READ == 0 { '-' } else { 'r' };
162 let ow = if mode & OTHER_WRITE == 0 { '-' } else { 'w' };
163 let ox = if mode & OTHER_EXECUTE == 0 { '-' } else { 'x' };
164
165 let owner = match uid_to_user(metadata.uid()) {
166 Ok(user) => user,
167 Err(err) => match err.kind() {
168 std::io::ErrorKind::NotFound => {
169 eprintln!("Internal Error: could not open /etc/passwd; file not found");
170 eprintln!("opened due to -l needing user id information");
171 std::process::exit(1);
172 }
173
174 std::io::ErrorKind::PermissionDenied => {
175 eprintln!("Internal Error: could not open /etc/passwd; permission denied;");
176 eprintln!("opened due to -l needing user id information");
177 std::process::exit(1);
178 }
179
180 std::io::ErrorKind::InvalidData => {
181 eprintln!("Internal Error: /etc/passwd did not contain UTF-8 valid data;");
182 eprintln!("opened due to -l needing user id information");
183 std::process::exit(1);
184 }
185
186 _ => {
187 eprintln!("Internal Error: unknown error opening /etc/passwd;");
188 eprintln!("opened due to -l needing user id information");
189 std::process::exit(1);
190 }
191 }
192 };
193
194 let group = match gid_to_group(metadata.uid()) {
195 Ok(group) => group,
196 Err(err) => match err.kind() {
197 std::io::ErrorKind::NotFound => {
198 eprintln!("Internal Error: could not open /etc/group; file not found");
199 eprintln!("opened due to -l needing user id information");
200 std::process::exit(1);
201 }
202
203 std::io::ErrorKind::PermissionDenied => {
204 eprintln!("Internal Error: could not open /etc/group; permission denied;");
205 eprintln!("opened due to -l needing user id information");
206 std::process::exit(1);
207 }
208
209 std::io::ErrorKind::InvalidData => {
210 eprintln!("Internal Error: /etc/group did not contain UTF-8 valid data;");
211 eprintln!("opened due to -l needing user id information");
212 std::process::exit(1);
213 }
214
215 _ => {
216 eprintln!("Internal Error: unknown error opening /etc/group;");
217 eprintln!("opened due to -l needing user id information");
218 std::process::exit(1);
219 }
220 }
221 };
222
223 let size = metadata.size();
224
225 format!("{is_dir}{ur}{uw}{ux}{gr}{gw}{gx}{or}{ow}{ox} {owner} {group} {size} {path}")
226 })
227 .collect::<Vec<String>>()
228 .join("\n"),
229
230 Format::Vertical => paths.join("\n"),
231
232 Format::VerticalMatrix(length) => {
233 paths
234 .into_iter()
235 .enumerate()
236 .map(|(i, mut path)| {
237 if i % length != 0 {
238 if (i + 1) % length == 0 {
240 path.push('\n');
241 }
242
243 let longest = paths_bland
245 .iter()
246 .enumerate()
247 .filter(|(j, _)| (i - 1) % length == j % length)
248 .map(|(_, path)| path.len())
249 .max()
250 .expect("Iterator should never be empty");
251
252 return String::from(
253 " ".repeat(longest - paths_bland[i - 1].len() + 1),
254 ) + &path;
255 }
256
257 path
258 })
259 .collect::<Vec<String>>()
260 .join("")
261 }
262 }
263
264 None => paths.join(""),
265 }
266}
267
268fn uid_to_user(id: u32) -> Result<String, std::io::Error> {
269 let mut etc_passwd_file = std::fs::File::open("/etc/passwd")?;
270 let mut etc_passwd = String::new();
271
272 etc_passwd_file.read_to_string(&mut etc_passwd)?;
273
274 let mut users: Vec<(String, u32)> = Vec::new();
275 let mut user = (String::new(), 0);
276
277 for entry in etc_passwd.split("\n") {
278 for str in entry
279 .split_inclusive(":")
280 .enumerate()
281 .filter(|(i, _)| i % 7 == 2 || i % 7 == 0)
282 .map(|(_, str)| str.to_string())
283 .collect::<String>()
284 .split(":")
285 {
286 if str.chars().all(|char| char.is_numeric()) && !str.is_empty() {
287 user.1 = str.parse().expect("This should be a number");
288 users.push(user.clone());
289 } else {
290 user.0 = str.to_string();
291 }
292 }
293 }
294
295 for user in users {
296 if user.1 == id {
297 return Ok(user.0);
298 }
299 }
300
301 Ok(String::new())
302}
303
304fn gid_to_group(id: u32) -> Result<String, std::io::Error> {
305 let mut etc_group_file = std::fs::File::open("/etc/passwd")?;
306 let mut etc_group = String::new();
307
308 etc_group_file.read_to_string(&mut etc_group)?;
309
310 let mut groups: Vec<(String, u32)> = Vec::new();
311 let mut group = (String::new(), 0);
312
313 for entry in etc_group.split("\n") {
314 for str in entry
315 .split_inclusive(":")
316 .enumerate()
317 .filter(|(i, _)| i % 7 == 2 || i % 7 == 0)
318 .map(|(_, str)| str.to_string())
319 .collect::<String>()
320 .split(":")
321 {
322 if str.chars().all(|char| char.is_numeric()) && !str.is_empty() {
323 group.1 = str.parse().expect("This should be a number");
324 groups.push(group.clone());
325 } else {
326 group.0 = str.to_string();
327 }
328 }
329 }
330
331 for group in groups {
332 if group.1 == id {
333 return Ok(group.0);
334 }
335 }
336
337 Ok(String::new())
338}