use std::env; use std::fs; use std::io::Write; use std::os::unix::fs::MetadataExt; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::mem; use std::fs::Metadata; use std::time::UNIX_EPOCH; const FILE_SYSTEM_MAX_PATH: usize = 0xfff; const NR_OF_SUPPORTET_LANG: usize = 13; const CARD_LEN: usize = 12; const ERRR: i32 = 0x0FFF_AFFF_u32 as i32; const LLL_CLI_HELP_MESSAGE: &str = "\ rustc -O -C panic=abort lll.rs -o lll -s --size sort by size -l --ls ls like output -h --help print help -p --path set path default is current -q --qqqq print all absolut paths of a folder @VERSION 10.0.0 "; #[repr(C)] struct Passwd { pw_name: *mut LllLibcChar, pw_passwd: *mut LllLibcChar, pw_uid: u32, pw_gid: u32, pw_gecos: *mut LllLibcChar, pw_dir: *mut LllLibcChar, pw_shell: *mut LllLibcChar, } #[repr(C)] struct Group { gr_name: *mut LllLibcChar, gr_passwd: *mut LllLibcChar, gr_gid: u32, gr_mem: *mut *mut LllLibcChar, } type LllLibcChar = i8; extern "C" { fn getpwuid(uid: u32) -> *mut Passwd; fn getgrgid(gid: u32) -> *mut Group; } #[derive(Clone)] struct TypeEntry { name: &'static str, countet_size: u64, globalsumindex: i32, } static mut TYPES: [TypeEntry; NR_OF_SUPPORTET_LANG] = [ TypeEntry { name: "", countet_size: 0, globalsumindex: 0 }, TypeEntry { name: "c", countet_size: 0, globalsumindex: 1 }, TypeEntry { name: "cpp", countet_size: 0, globalsumindex: 2 }, TypeEntry { name: "ino", countet_size: 0, globalsumindex: 3 }, TypeEntry { name: "py", countet_size: 0, globalsumindex: 4 }, TypeEntry { name: "java", countet_size: 0, globalsumindex: 5 }, TypeEntry { name: "sh", countet_size: 0, globalsumindex: 6 }, TypeEntry { name: "js", countet_size: 0, globalsumindex: 7 }, TypeEntry { name: "html", countet_size: 0, globalsumindex: 8 }, TypeEntry { name: "asm", countet_size: 0, globalsumindex: 9 }, TypeEntry { name: "rs", countet_size: 0, globalsumindex: 10 }, TypeEntry { name: "sql", countet_size: 0, globalsumindex: 11 }, TypeEntry { name: "h", countet_size: 0, globalsumindex: 1 }, ]; static NAME_LIST: [&str; NR_OF_SUPPORTET_LANG] = [ "Undefined", "C", "C++", "ESP", "Python", "Java", "ShellScript", "JavaScript", "HTML", "Assembly", "Rust", "SQL", "C", ]; static COLOR_LIST: [u64; NR_OF_SUPPORTET_LANG] = [ 0x2222_0778_77CC, 0x2222_0566_6666, 0x2222_0A22_22FF, 0x2222_0FEE_AA00, 0x2222_00A8_A862, 0x2222_00FF_FF00, 0x2222_00FF_0010, 0x2222_00BB_0010, 0x2222_0088_8888, 0x2222_00FF_8800, 0x2222_00DE_A584, 0x2222_0000_00FF, 0x2222_0566_6666, ]; fn coloramaprintifarma(ptext: &str, col: u64) -> String { let bg_r = ((col >> 40) & 0xFF) as u8; let bg_g = ((col >> 32) & 0xFF) as u8; let bg_b = ((col >> 24) & 0xFF) as u8; let fg_r = ((col >> 16) & 0xFF) as u8; let fg_g = ((col >> 8) & 0xFF) as u8; let fg_b = (col & 0xFF) as u8; format!( "\x1b[38;2;{};{};{}m\x1b[48;2;{};{};{}m{}\x1b[0m", fg_r, fg_g, fg_b, bg_r, bg_g, bg_b, ptext ) } fn utf8_char_len_local(c: u8) -> usize { if c < 0x80 { 1 } else if (c >> 5) == 0x6 { 2 } else if (c >> 4) == 0xE { 3 } else if (c >> 3) == 0x1E { 4 } else { 1 } } fn pad_utf8(text: &str, mse: usize) -> String { let bytes = text.as_bytes(); let mut res = Vec::with_capacity(mse + 4); let mut pos = 0usize; let mut i = 0usize; while pos < mse && i < bytes.len() { let clen = utf8_char_len_local(bytes[i]); if pos + clen > mse { break; } if i + clen > bytes.len() { break; } res.extend_from_slice(&bytes[i..i + clen]); pos += clen; i += clen; } while pos < mse { res.push(b' '); pos += 1; } String::from_utf8_lossy(&res).into_owned() } fn coloramaprintifarmafillup(text: &str, advansed_color: u64) -> String { let padded = pad_utf8(text, CARD_LEN); coloramaprintifarma(&padded, advansed_color) } fn coll(col: u64, ptext: &str) -> String { let r = ((col >> 16) & 0xFF) as u8; let g = ((col >> 8) & 0xFF) as u8; let b = (col & 0xFF) as u8; format!("\x1b[38;2;{};{};{}m{}\x1b[0m", r, g, b, ptext) } fn chngg(pp: &str, color: u64, more_space: i32) -> String { let mse = match more_space { 1 => CARD_LEN * 3, 2 => CARD_LEN + 3, 3 => CARD_LEN.saturating_sub(3), _ => CARD_LEN, }; let padded = pad_utf8(pp, mse); coll(color, &padded) } fn chngg_wrapper(index: i32) -> String { let idx = if index < 0 || index as usize >= NR_OF_SUPPORTET_LANG { 0 } else { index as usize }; coloramaprintifarmafillup(NAME_LIST[idx], COLOR_LIST[idx]) } fn mode_to_str(mode: u32) -> String { let mut out = String::with_capacity(10); let ft = mode & 0o170000; out.push(match ft { 0o040000 => 'd', 0o120000 => 'l', 0o020000 => 'c', 0o060000 => 'b', 0o010000 => 'p', 0o140000 => 's', _ => '-', }); out.push(if mode & 0o400 != 0 { 'r' } else { '-' }); out.push(if mode & 0o200 != 0 { 'w' } else { '-' }); out.push(if mode & 0o100 != 0 { 'x' } else { '-' }); out.push(if mode & 0o040 != 0 { 'r' } else { '-' }); out.push(if mode & 0o020 != 0 { 'w' } else { '-' }); out.push(if mode & 0o010 != 0 { 'x' } else { '-' }); out.push(if mode & 0o004 != 0 { 'r' } else { '-' }); out.push(if mode & 0o002 != 0 { 'w' } else { '-' }); out.push(if mode & 0o001 != 0 { 'x' } else { '-' }); out } fn process_size(size: u64) -> String { let units = ["B", "KB", "MB", "GB", "TB"]; let mut s = size as f64; let mut u = 0usize; while s >= 1024.0 && u < 4 { s /= 1024.0; u += 1; } format!("{:.1} {}", s, units[u]) } fn get_type_ind_by_name(type_s: &str) -> i32 { unsafe { for i in 1..NR_OF_SUPPORTET_LANG { if !TYPES[i].name.is_empty() && TYPES[i].name == type_s { return i as i32; } } } ERRR } fn add_type_value(pp_index: i32, filesize: u64) -> i32 { if pp_index < 0 || pp_index as usize >= NR_OF_SUPPORTET_LANG { return -1; } unsafe { TYPES[pp_index as usize].countet_size = TYPES[pp_index as usize].countet_size.saturating_add(filesize); } 0 } fn get_type(fullpath: &str, filesize: u64) { let bytes = fullpath.as_bytes(); let size = bytes.len() as i32; if size <= 0 { return; } let mut point_index: i32 = -1; let mut i = size - 1; loop { if bytes[i as usize] == b'.' { point_index = i; break; } if bytes[i as usize] == b'/' { point_index = -1; break; } if i == 0 { break; } i -= 1; } if point_index <= 0 { return; } let start = (point_index + 1) as usize; if start >= bytes.len() { return; } let type_s = match std::str::from_utf8(&bytes[start..]) { Ok(s) => s, Err(_) => return, }; let index = get_type_ind_by_name(type_s); if index != ERRR { add_type_value(index, filesize); } } fn is_mount_point(path: &Path) -> bool { let st_path = match fs::metadata(path) { Ok(m) => m, Err(_) => return false, }; let parent = path.join(".."); let st_parent = match fs::metadata(&parent) { Ok(m) => m, Err(_) => return false, }; st_path.dev() != st_parent.dev() } fn store_modification_timestamp(metadata: &Metadata, timestamp: &mut i64) { if let Ok(modified) = metadata.modified() { if let Ok(duration) = modified.duration_since(UNIX_EPOCH) { if *timestamp < duration.as_secs() as i64 { *timestamp = duration.as_secs() as i64; } } } } fn folder_size(path: &Path, ts_max: &mut i64) -> u64 { if is_mount_point(path) { return 0; } let mut total: u64 = 0; let rd = match fs::read_dir(path) { Ok(d) => d, Err(_) => return 0, }; for entry in rd.flatten() { let name = entry.file_name(); if name == "." || name == ".." { continue; } let full = entry.path(); let st = match fs::symlink_metadata(&full) { Ok(m) => m, Err(_) => continue, }; if st.file_type().is_dir() { total = total.saturating_add(folder_size(&full, ts_max)); } else if st.file_type().is_symlink() { continue; } else { store_modification_timestamp(&st, ts_max); let sz = st.len(); total = total.saturating_add(sz); if let Some(s) = full.to_str() { get_type(s, sz); } } } total } fn get_dom_lang_index() -> i32 { let mut local_sums = [0u64; NR_OF_SUPPORTET_LANG]; unsafe { for i in 1..NR_OF_SUPPORTET_LANG { let g = TYPES[i].globalsumindex; if g >= 0 && (g as usize) < NR_OF_SUPPORTET_LANG { local_sums[g as usize] = local_sums[g as usize].saturating_add(TYPES[i].countet_size); } } } let mut max_idx = 0i32; let mut max_val = 0u64; for i in 0..NR_OF_SUPPORTET_LANG { if local_sums[i] > max_val { max_val = local_sums[i]; max_idx = i as i32; } } unsafe { for i in 0..NR_OF_SUPPORTET_LANG { TYPES[i].countet_size = 0; } } max_idx } fn get_dom_single_file_index_from_path(path: &str) -> i32 { let ext = Path::new(path) .extension() .and_then(|e| e.to_str()) .unwrap_or(""); unsafe { for i in 0..NR_OF_SUPPORTET_LANG { if !TYPES[i].name.is_empty() && TYPES[i].name == ext { return i as i32; } } } 0 } fn clipper(content: &str) { let mut child = match Command::new("xclip") .arg("-selection") .arg("clipboard") .stdin(Stdio::piped()) .spawn() { Ok(c) => c, Err(_) => return, }; if let Some(mut stdin) = child.stdin.take() { let _ = stdin.write_all(content.as_bytes()); } let _ = child.wait(); } fn uid_name(uid: u32) -> String { unsafe { let pw = getpwuid(uid); if pw.is_null() { return "unknown".to_string(); } let name = (*pw).pw_name; if name.is_null() { return "unknown".to_string(); } std::ffi::CStr::from_ptr(name) .to_string_lossy() .into_owned() } } fn gid_name(gid: u32) -> String { unsafe { let gr = getgrgid(gid); if gr.is_null() { return "unknown".to_string(); } let name = (*gr).gr_name; if name.is_null() { return "unknown".to_string(); } std::ffi::CStr::from_ptr(name) .to_string_lossy() .into_owned() } } fn format_mtime(secs: i64) -> String { extern "C" { fn localtime_r(timep: *const i64, result: *mut LibcTm) -> *mut LibcTm; fn strftime( s: *mut i8, max: usize, format: *const i8, tm: *const LibcTm, ) -> usize; } #[repr(C)] struct LibcTm { tm_sec: i32, tm_min: i32, tm_hour: i32, tm_mday: i32, tm_mon: i32, tm_year: i32, tm_wday: i32, tm_yday: i32, tm_isdst: i32, tm_gmtoff: i64, tm_zone: *const i8, } unsafe { let mut tm: LibcTm = mem::zeroed(); let t = secs; if localtime_r(&t, &mut tm).is_null() { return String::from("?"); } let mut buf = [0i8; 64]; let fmt = b"%e. %b %H:%M\0"; let n = strftime( buf.as_mut_ptr(), buf.len(), fmt.as_ptr() as *const i8, &tm, ); if n == 0 { return String::from("?"); } std::ffi::CStr::from_ptr(buf.as_ptr()) .to_string_lossy() .trim() .to_string() } } fn get_absolut_path(name: &str) { let cwd = match env::current_dir() { Ok(p) => p, Err(e) => { eprintln!("ERROR: getcwd: {}", e); return; } }; let mut absolut = cwd; absolut.push(name); let s = absolut.to_string_lossy().into_owned(); println!("{}", s); clipper(&s); } fn sort_by_timestamp(unix_timestamps: &mut [i64], rows: &mut [String]) { let n = unix_timestamps.len(); if n <= 1 { return; } for i in 0..n - 1 { let mut max_index = i; for j in i + 1..n { if unix_timestamps[j] > unix_timestamps[max_index] { max_index = j; } } if max_index != i { unix_timestamps.swap(i, max_index); rows.swap(i, max_index); } } rows.reverse(); unix_timestamps.reverse(); } fn sort_by_size(sizes: &mut [u64], rows: &mut [String]) { let n = sizes.len(); if n <= 1 { return; } for i in 0..n - 1 { let mut max_index = i; for j in i + 1..n { if sizes[j] < sizes[max_index] { max_index = j; } } if max_index != i { sizes.swap(i, max_index); rows.swap(i, max_index); } } } fn print_absolut_paths(path_to_process: &str) { let rd = match fs::read_dir(path_to_process) { Ok(d) => d, Err(e) => { eprintln!("opendir: {}", e); return; } }; let is_dot = path_to_process.starts_with('.'); let cwd = env::current_dir().ok(); for entry in rd.flatten() { let name = entry.file_name(); if name == "." || name == ".." { continue; } let name_s = name.to_string_lossy(); if is_dot { if let Some(ref p) = cwd { println!("{}/{}", p.display(), name_s); } } else { println!("{}/{}", path_to_process.trim_end_matches('/'), name_s); } } } fn main_lll_impl(sort_by_size_flag: bool, path_to_process: &str) -> i32 { let rd = match fs::read_dir(path_to_process) { Ok(d) => d, Err(e) => { eprintln!("opendir: {}", e); return 1; } }; let mut rows: Vec = Vec::new(); let mut sizes: Vec = Vec::new(); let mut unix_timestamps: Vec = Vec::new(); for entry in rd.flatten() { let name_os = entry.file_name(); if name_os == "." || name_os == ".." { continue; } let name = name_os.to_string_lossy(); let full: PathBuf = Path::new(path_to_process).join(&*name); let st = match fs::symlink_metadata(&full) { Ok(m) => m, Err(_) => continue, }; let mode = st.mode(); let perms = mode_to_str(mode); let links = st.nlink(); let owner = uid_name(st.uid()); let group = gid_name(st.gid()); let ft = st.file_type(); let raw_size: u64; let sizebuf: String; let mut timestamp: i64 = 0; if ft.is_dir() { raw_size = folder_size(&full, &mut timestamp); sizebuf = process_size(raw_size); if let Some(s) = full.to_str() { get_type(s, raw_size); } } else { store_modification_timestamp(&st, &mut timestamp); raw_size = st.len(); sizebuf = process_size(raw_size); if let Some(s) = full.to_str() { get_type(s, raw_size); } } let timestr = format_mtime(timestamp); let ja_index = get_dom_lang_index(); let line = if ft.is_symlink() { match fs::read_link(&full) { Ok(target) => { let t = target.to_string_lossy(); format!( "{} {:3} {} {} {:>8} {} {} -> {}", perms, links, owner, group, sizebuf, timestr, name, t ) } Err(_) => format!( "{} {:3} {} {} {:>8} {} {}", perms, links, owner, group, sizebuf, timestr, name ), } } else { let owner_col = chngg(&owner, 0x44FF44, 0); let group_col = chngg(&group, 0x88FF88, 0); let size_col = chngg(&sizebuf, 0xFF0000, 3); let time_col = chngg(×tr, 0x888866, 2); let type_fld = chngg_wrapper(ja_index); let name_col = chngg( &name, if ft.is_dir() { 0x9999FF } else { 0xFF33FF }, 1, ); format!( "{} {:3} {} {} {:>8} {} {} {}", perms, links, owner_col, group_col, size_col, time_col, type_fld, name_col ) }; rows.push(line); sizes.push(raw_size); unix_timestamps.push(timestamp); } if !rows.is_empty() { if sort_by_size_flag { sort_by_size(&mut sizes, &mut rows); } else { sort_by_timestamp(&mut unix_timestamps, &mut rows); } } for row in &rows { println!("{}", row); } 0 } fn main_ls_impl() -> i32 { let rd = match fs::read_dir(".") { Ok(d) => d, Err(e) => { eprintln!("opendir: {}", e); return 1; } }; let mut entries: Vec = Vec::new(); for entry in rd.flatten() { let name_os = entry.file_name(); if name_os == "." || name_os == ".." { continue; } let name = name_os.to_string_lossy(); let full = PathBuf::from(format!("./{}", name)); let st = match fs::symlink_metadata(&full) { Ok(m) => m, Err(_) => continue, }; let ft = st.file_type(); let mut max_date_ts: i64 = 0; if ft.is_dir() { let _ = folder_size(&full, &mut max_date_ts); } let links = st.nlink(); let lang_idx = if ft.is_dir() { get_dom_lang_index() } else { get_dom_single_file_index_from_path(full.to_str().unwrap_or("")) }; let name_color: u64 = if ft.is_dir() { 0x9999FF } else { 0xFF33FF }; let type_fld = chngg_wrapper(lang_idx); let project_name = chngg(&name, name_color, 1); let _ = { let b = project_name.as_bytes(); let take = b.len().min(45); String::from_utf8_lossy(&b[..take]).into_owned() }; let line = format!("{:5} {:>8} {:>46}", links, type_fld, project_name); entries.push(line); } let mut i = 0usize; while i < entries.len() { let end = (i + 4).min(entries.len()); let mut line = String::new(); for j in i..end { if j > i { line.push(' '); } line.push_str(&entries[j]); } println!("{}", line); i = end; } 0 } fn main() { let args: Vec = env::args().collect(); let argc = args.len(); let exit_code = if argc == 1 { main_lll_impl(false, ".") } else if argc == 2 || argc == 3 { let a1 = args[1].as_str(); if a1 == "-s" || a1 == "--size" { main_lll_impl(true, ".") } else if a1 == "-l" || a1 == "--ls" { main_ls_impl() } else if a1 == "-h" || a1 == "--help" { print!("{}", LLL_CLI_HELP_MESSAGE); println!("FILE_SYSTEM_MAX_PATH : {}", FILE_SYSTEM_MAX_PATH); 0 } else if a1 == "-p" || a1 == "--path" { if argc != 3 { eprintln!("Unbekannter Parameter oder falsche Nutzung"); 1 } else { main_lll_impl(false, &args[2]) } } else if a1 == "-q" || a1 == "--qqqq" { if argc == 2 { print_absolut_paths("."); } else { print_absolut_paths(&args[2]); } 0 } else { get_absolut_path(&args[1]); 0 } } else { eprintln!("Unbekannter Parameter oder falsche Nutzung"); 1 }; std::process::exit(exit_code); }