hautils

[hare] Set of POSIX utilities
Log | Files | Refs | README | LICENSE

ls.ha (9111B)


      1 use fmt;
      2 use fs;
      3 use getopt;
      4 use main;
      5 use os;
      6 use sort;
      7 use strings;
      8 use time;
      9 use time::chrono;
     10 use time::date;
     11 use unix::passwd;
     12 
     13 type tstamp = (atime | ctime | mtime);
     14 type atime = void; // access time
     15 type ctime = void; // creation time
     16 type mtime = void; // modification time
     17 
     18 type sorttype = (name | time | filesize);
     19 type name = void;
     20 type time = void;
     21 type filesize = void;
     22 
     23 type entry = struct {
     24 	name: str,
     25 	mask: fs::stat_mask,
     26 	mode: fs::mode,
     27 	uid: uint,
     28 	gid: uint,
     29 	sz: size,
     30 	inode: u64,
     31 	time: time::instant,
     32 	target_mode: fs::mode,
     33 	target_inode: u64,
     34 };
     35 
     36 type flags = struct {
     37 	detailed: bool,
     38 	detailedid: bool,
     39 	fileindicator: bool,
     40 	listdir: bool,
     41 	printinode: bool,
     42 	recurse: bool,
     43 	reverseorder: bool,
     44 	showhidden: bool,
     45 	showhiddenexcept: bool,
     46 	sortby: sorttype,
     47 	timestamp: tstamp,
     48 	unsorted: bool,
     49 	usetargetinfo: bool,
     50 };
     51 
     52 type dirstate = struct {
     53 	listdir: bool,
     54 	firstdir: bool,
     55 	showdir: bool,
     56 };
     57 
     58 export fn utilmain() (void | main::error) = {
     59 	const help: []getopt::help = [
     60 		"list directory contents",
     61 		('A', "list hidden files except '.' and '..'"),
     62 		('a', "list hidden files"),
     63 		('c', "use ctime"),
     64 		('d', "list directories themselves"),
     65 		('F', "append a type indicator to filename"),
     66 		('L', "show information for the target of the link"),
     67 		('i', "print the index number for every file"),
     68 		('l', "list detailed information"),
     69 		('n', "like '-l' but with numeric IDs"),
     70 		('R', "list recursively"),
     71 		('r', "reverse order"),
     72 		('S', "sort by size in decreasing order"),
     73 		('t', "sort by timestamp"),
     74 		('U', "unsorted"),
     75 		('u', "use atime"),
     76 		"[dir...]",
     77 	];
     78 	const cmd = getopt::parse(os::args, help...);
     79 	defer getopt::finish(&cmd);
     80 
     81 	let flg = flags{ sortby = name, timestamp = mtime, ... };
     82 	for (let i = 0z; i < len(cmd.opts); i += 1) {
     83 		const opt = cmd.opts[i];
     84 		switch (opt.0) {
     85 		case 'A' =>
     86 			flg.showhiddenexcept = true;
     87 		case 'a' =>
     88 			flg.showhidden = true;
     89 		case 'c' =>
     90 			flg.sortby = time;
     91 			flg.timestamp = ctime;
     92 		case 'd' =>
     93 			flg.listdir = true;
     94 		case 'F' =>
     95 			flg.fileindicator = true;
     96 		case 'i' =>
     97 			flg.printinode = true;
     98 		case 'L' =>
     99 			flg.usetargetinfo = true;
    100 		case 'l' =>
    101 			flg.detailed = true;
    102 		case 'n' =>
    103 			flg.detailed = true;
    104 			flg.detailedid = true;
    105 		case 'R' =>
    106 			flg.recurse = true;
    107 		case 'r' =>
    108 			flg.reverseorder = true;
    109 		case 't' =>
    110 			flg.sortby = time;
    111 			flg.timestamp = mtime;
    112 		case 'S' =>
    113 			flg.sortby = filesize;
    114 		case 'U' =>
    115 			flg.unsorted = true;
    116 		case 'u' =>
    117 			flg.sortby = time;
    118 			flg.timestamp = atime;
    119 		case =>
    120 			main::usage(help);
    121 		};
    122 	};
    123 
    124 	let state = dirstate{...};
    125 	state.firstdir = true;
    126 	switch (len(cmd.args)) {
    127 	case 0 =>
    128 		const ent: entry = mkent(".", flg)?;
    129 		state.listdir = shouldlistdir(ent, flg);
    130 		ls("", ent, &state, flg)?;
    131 	case 1 =>
    132 		const path = cmd.args[0];
    133 		const ent: entry = mkent(path, flg)?;
    134 		state.listdir = shouldlistdir(ent, flg);
    135 		ls("", ent, &state, flg)?;
    136 	case =>
    137 		let fileents: []entry = [];
    138 		let dirents: []entry = [];
    139 		let fs = 0z, ds = 0z;
    140 		defer free(fileents);
    141 		defer free(dirents);
    142 
    143 		for (let i = 0z; i < len(cmd.args); i += 1z) {
    144 			const ent = mkent(cmd.args[i], flg)?;
    145 			if (!flg.listdir && fs::isdir(ent.mode)) {
    146 				append(dirents, ent);
    147 				ds += 1;
    148 			} else {
    149 				append(fileents, ent);
    150 				fs += 1;
    151 			};
    152 		};
    153 
    154 		state.showdir = ds > 1 || (ds > 0 && fs > 0);
    155 		if (!flg.unsorted) {
    156 			ls_sortents(fileents, flg);
    157 			ls_sortents(dirents, flg);
    158 		};
    159 
    160 		for (let i = 0z; i < len(fileents); i += 1z) {
    161 			state.listdir = false;
    162 			ls("", fileents[i], &state, flg)?;
    163 		};
    164 		if (len(fileents) > 0 && len(dirents) > 0) fmt::printf("\n")?;
    165 		for (let i = 0z; i < len(dirents); i += 1z) {
    166 			state.listdir = true;
    167 			ls("", dirents[i], &state, flg)?;
    168 		};
    169 	};
    170 };
    171 
    172 fn mkent(path: str, flg: flags) (entry | main::error) = {
    173 	const stat = if (flg.usetargetinfo) {
    174 		yield os::stat(os::realpath(path)?)?;
    175 	} else {
    176 		yield os::stat(path)?;
    177 	};
    178 
    179 	let f = entry {
    180 		name = path,
    181 		mask = stat.mask,
    182 		mode = stat.mode,
    183 		uid = stat.uid,
    184 		gid = stat.gid,
    185 		sz = stat.sz,
    186 		inode = stat.inode,
    187 		time = match (flg.timestamp) {
    188 		case atime =>
    189 			yield stat.atime;
    190 		case ctime =>
    191 			yield stat.ctime;
    192 		case mtime =>
    193 			yield stat.mtime;
    194 		},
    195 		...
    196 	};
    197 	if (fs::islink(stat.mode)) {
    198 		let targetstat = os::stat(os::readlink(f.name)?)?;
    199 		f.target_mode = targetstat.mode;
    200 		f.target_inode = targetstat.inode;
    201 	};
    202 	return f;
    203 };
    204 
    205 fn ls(path: str, ent: entry, st: *dirstate, flg: flags) (void | main::error) = {
    206 	if (!st.listdir) {
    207 		ls_printent(ent, flg)?;
    208 	} else if (fs::isdir(ent.mode) || (fs::islink(ent.mode)
    209 			&& fs::isdir(ent.target_mode))) {
    210 		if (st.firstdir) {
    211 			st.firstdir = false;
    212 		} else {
    213 			fmt::printf("\n")?;
    214 		};
    215 		lsdir(path, ent, st, flg)?;
    216 	};
    217 };
    218 
    219 fn lsdir(
    220 	path: str,
    221 	dirent: entry,
    222 	st: *dirstate,
    223 	flg: flags,
    224 ) (void | main::error) = {
    225 	const rootdir = os::getcwd();
    226 	const dir = os::readdir(dirent.name)?;
    227 	let ents: []entry = [];
    228 	defer fs::dirents_free(dir);
    229 	defer free(ents);
    230 
    231 	os::chdir(dirent.name)?;
    232 	for (let i = 0z; i < len(dir); i += 1) {
    233 		const path = dir[i].name;
    234 		if (strings::hasprefix(path, ".") && !flg.showhidden
    235 				&& !flg.showhiddenexcept) {
    236 			continue;
    237 		} else if (flg.showhiddenexcept) {
    238 			if (strings::compare(path, "..") == 0 ||
    239 					strings::compare(path, ".") == 0) {
    240 				continue;
    241 			};
    242 		};
    243 		append(ents, mkent(dir[i].name, flg)?);
    244 	};
    245 
    246 	if (!flg.unsorted) ls_sortents(ents, flg);
    247 
    248 	if (path != "" || st.showdir) fmt::printfln("{}{}:", path, dirent.name)?;
    249 	for (let i = 0z; i < len(ents); i += 1) {
    250 		ls_printent(ents[i], flg)?;
    251 	};
    252 
    253 	if (flg.recurse) {
    254 		for (let i = 0z; i < len(ents); i += 1z) {
    255 			const ent = ents[i];
    256 			if (strings::compare(ent.name, "..") == 0 ||
    257 					strings::compare(ent.name, ".") == 0) {
    258 				continue;
    259 			};
    260 			if (fs::islink(ent.mode) && fs::isdir(ent.target_mode) &&
    261 					!flg.usetargetinfo) {
    262 				continue;
    263 			};
    264 
    265 			const prefix = strings::concat(path, dirent.name, "/");
    266 			st.listdir = true;
    267 			ls(prefix, ents[i], st, flg)?;
    268 			free(prefix);
    269 		};
    270 	};
    271 
    272 	os::chdir(rootdir)?;
    273 };
    274 
    275 fn ls_sortents(ents: []entry, flg: flags) void = {
    276 	match (flg.sortby) {
    277 	case name =>
    278 		sort::sort(ents, size(entry), &namecmp);
    279 	case time =>
    280 		sort::sort(ents, size(entry), &timecmp);
    281 	case filesize =>
    282 		sort::sort(ents, size(entry), &sizecmp);
    283 	};
    284 	if (flg.reverseorder) {
    285 		reverse(ents, size(entry));
    286 	};
    287 };
    288 
    289 fn ls_printent(ent: entry, flg: flags) (void | main::error) = {
    290 	if (flg.printinode) fmt::printf("{} ", ent.inode)?;
    291 	if (flg.detailed) {
    292 		fmt::printf("{} ", fs::mode_str(ent.mode))?;
    293 
    294 		if (flg.detailedid) {
    295 			fmt::printf("{:-8} {:-8} ", ent.uid, ent.gid)?;
    296 		} else {
    297 			match (passwd::getuid(ent.uid)) {
    298 			case let pw: passwd::pwent =>
    299 				fmt::printf("{:-8} ", pw.username)?;
    300 				passwd::pwent_finish(&pw);
    301 			case void =>
    302 				fmt::printf("{:-8} ", ent.uid)?;
    303 			};
    304 
    305 			match (passwd::getgid(ent.gid)) {
    306 			case let gr: passwd::grent =>
    307 				fmt::printf("{:-8} ", gr.name)?;
    308 				passwd::grent_finish(&gr);
    309 			case void =>
    310 				fmt::printf("{:-8} ", ent.gid)?;
    311 			};
    312 		};
    313 
    314 		fmt::printf("{:10} ", ent.sz)?;
    315 
    316 		const date = date::from_instant(chrono::LOCAL, ent.time);
    317 		const now = time::now(time::clock::REALTIME);
    318 		if (now.sec > ent.time.sec + (180 * 24 * 60 * 60)) {
    319 			date::format(os::stdout, "%b %d  %Y", &date)!;
    320 		} else {
    321 			date::format(os::stdout, "%b %d %H:%M", &date)!;
    322 		};
    323 		fmt::printf(" ")?;
    324 	};
    325 
    326 	fmt::printf(ent.name)?;
    327 	if (flg.fileindicator) fmt::printf(ls_indicator(ent.mode))?;
    328 	if (fs::islink(ent.mode)) {
    329 		const target = os::readlink(ent.name)?;
    330 		fmt::printf(" -> {}", target)?;
    331 		if (flg.fileindicator) fmt::printf(ls_indicator(ent.target_mode))?;
    332 	};
    333 	fmt::print("\n")?;
    334 };
    335 
    336 fn ls_indicator(m: fs::mode) str = {
    337 	if (fs::isdir(m)) {
    338 		return "/";
    339 	} else if (fs::isfifo(m)) {
    340 		return "@";
    341 	} else if (fs::issocket(m)) {
    342 		return "=";
    343 	} else if (fs::isfile(m)) {
    344 		const perm = fs::mode_perm(m);
    345 		if (perm & fs::mode::USER_X == fs::mode::USER_X
    346 				|| perm & fs::mode::GROUP_X == fs::mode::GROUP_X
    347 				|| perm & fs::mode::OTHER_X == fs::mode::OTHER_X) {
    348 			return "*";
    349 		};
    350 	};
    351 	return "";
    352 };
    353 
    354 fn shouldlistdir(e: entry, flg: flags) bool = {
    355 	return (!flg.listdir && fs::isdir(e.mode)) ||
    356 		(fs::islink(e.mode) && fs::isdir(e.target_mode) &&
    357 		(!flg.listdir && !flg.fileindicator && !flg.detailed));
    358 };
    359 
    360 fn timecmp(a: const *opaque, b: const *opaque) int = {
    361 	const a = a: *entry, b = b: *entry;
    362 	const dist = b.time.sec - a.time.sec;
    363 	if (dist > 0) {
    364 		return 1;
    365 	} else if (dist < 0) {
    366 		return -1;
    367 	} else {
    368 		return 0;
    369 	};
    370 };
    371 
    372 fn sizecmp(a: const *opaque, b: const *opaque) int = {
    373 	const a = a: *entry, b = b: *entry;
    374 	const asize = a.sz: int, bsize = b.sz: int;
    375 	const dist = bsize - asize;
    376 	if (dist > 0) {
    377 		return 1;
    378 	} else if (dist < 0) {
    379 		return -1;
    380 	} else {
    381 		return 0;
    382 	};
    383 };
    384 
    385 fn namecmp(a: const *opaque, b: const *opaque) int = {
    386 	const a = a: *entry, b = b: *entry;
    387 	return strings::compare(a.name, b.name);
    388 };
    389 
    390 fn reverse(ents: []entry, itemsz: size) void = {
    391 	for (let i = 0z, r = len(ents) - 1; i < r) {
    392 		let l = ents[i];
    393 		ents[i] = ents[r];
    394 		ents[r] = l;
    395 		i += 1;
    396 		r -= 1;
    397 	};
    398 };
    399