1 /* See LICENSE file for copyright and license details. */
2 
3 #if defined(__linux__)
4 #define _GNU_SOURCE
5 #elif defined(__APPLE__)
6 #define _DARWIN_C_SOURCE
7 #elif defined(__FreeBSD__)
8 #define __BSD_VISIBLE 1
9 #endif
10 #include <sys/types.h>
11 #include <sys/resource.h>
12 #include <sys/stat.h>
13 #include <sys/time.h>
14 #include <sys/wait.h>
15 #if defined(__linux__)
16 #include <sys/inotify.h>
17 #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) || \
18 	defined(__APPLE__)
19 #include <sys/event.h>
20 #endif
21 
22 #include <dirent.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <grp.h>
26 #include <libgen.h>
27 #include <pthread.h>
28 #include <pwd.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdint.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <time.h>
36 #include <unistd.h>
37 
38 #include "termbox.h"
39 #include "util.h"
40 
41 /* macros */
42 #define MAX_P 4096
43 #define MAX_N 255
44 #define MAX_USRI 32
45 #define MAX_EXT 4
46 #define MAX_STATUS 255
47 #define MAX_LINE 4096
48 #define MAX_USRN 32
49 #define MAX_GRPN 32
50 #define MAX_DTF 32
51 #define CURSOR(x) (x)->direntr[(x)->hdir - 1]
52 
53 /* typedef */
54 typedef struct {
55 	char name[MAX_N];
56 	gid_t group;
57 	mode_t mode;
58 	off_t size;
59 	time_t dt;
60 	uid_t user;
61 } Entry;
62 
63 typedef struct {
64 	uint16_t fg;
65 	uint16_t bg;
66 } Cpair;
67 
68 typedef struct {
69 	int pane_id;
70 	char dirn[MAX_P]; // dir name cwd
71 	char *filter;
72 	Entry *direntr; // dir entries
73 	int dirc; // dir entries sum
74 	int hdir; // highlighted dir
75 	int x_srt;
76 	int x_end;
77 	int firstrow;
78 	int parent_firstrow;
79 	int parent_row; // FIX
80 	Cpair dircol;
81 	int inotify_wd;
82 	int event_fd;
83 } Pane;
84 
85 typedef struct {
86 	const char **ext;
87 	size_t exlen;
88 	const void *v;
89 	size_t vlen;
90 } Rule;
91 
92 typedef union {
93 	uint16_t key; /* one of the TB_KEY_* constants */
94 	uint32_t ch; /* unicode character */
95 } Evkey;
96 
97 typedef union {
98 	int i;
99 	const void *v;
100 } Arg;
101 
102 typedef struct {
103 	const Evkey evkey;
104 	void (*func)(const Arg *);
105 	const Arg arg;
106 } Key;
107 
108 /* function declarations */
109 static void print_tb(const char *, int, int, uint16_t, uint16_t);
110 static void printf_tb(int, int, Cpair, const char *, ...);
111 static void print_status(Cpair, const char *, ...);
112 static void print_xstatus(char, int);
113 static void print_error(char *);
114 static void print_prompt(char *);
115 static void print_info(Pane *, char *);
116 static void print_row(Pane *, size_t, Cpair);
117 static void clear(int, int, int, uint16_t);
118 static void clear_status(void);
119 static void clear_pane(Pane *);
120 static void add_hi(Pane *, size_t);
121 static void rm_hi(Pane *, size_t);
122 static int check_dir(char *);
123 static int sort_name(const void *const, const void *const);
124 static void get_dirp(char *);
125 static char *get_ext(char *);
126 static int get_fdt(char *, time_t);
127 static char *get_fgrp(gid_t);
128 static char *get_fperm(mode_t);
129 static char *get_fsize(off_t);
130 static char *get_fullpath(char *, char *);
131 static char *get_fusr(uid_t);
132 static void get_dirsize(char *, off_t *);
133 static void get_hicol(Cpair *, mode_t);
134 static void delent(const Arg *arg);
135 static void calcdir(const Arg *arg);
136 static void crnd(const Arg *arg);
137 static void crnf(const Arg *arg);
138 static void mv_ver(const Arg *arg);
139 static void mvbk(const Arg *arg);
140 static void mvbtm(const Arg *arg);
141 static void mvfwd(const Arg *arg);
142 static void mvtop(const Arg *arg);
143 static void bkmrk(const Arg *arg);
144 static int get_usrinput(char *, size_t, const char *, ...);
145 static int frules(char *);
146 static int spawn(const void *, size_t, const void *, size_t, char *, int);
147 static int opnf(char *);
148 static int fsev_init(void);
149 static int addwatch(Pane *);
150 static int read_events(void);
151 static void rmwatch(Pane *);
152 static void fsev_shdn(void);
153 static void toggle_df(const Arg *arg);
154 static void start_filter(const Arg *arg);
155 static void start_vmode(const Arg *arg);
156 static void exit_vmode(const Arg *arg);
157 static void start_change(const Arg *arg);
158 static void exit_change(const Arg *arg);
159 static void selup(const Arg *arg);
160 static void seldwn(const Arg *arg);
161 static void selall(const Arg *arg);
162 static void selref(void);
163 static void selynk(const Arg *arg);
164 static void selcalc(void);
165 static void paste(const Arg *arg);
166 static void selmv(const Arg *arg);
167 static void seldel(const Arg *arg);
168 static void init_files(void);
169 static void free_files(void);
170 static void yank(const Arg *arg);
171 static void rname(const Arg *arg);
172 static void chngo(const Arg *arg);
173 static void chngm(const Arg *arg);
174 static void chngf(const Arg *arg);
175 static void dupl(const Arg *arg);
176 static void switch_pane(const Arg *arg);
177 static void quit(const Arg *arg);
178 static void grabkeys(struct tb_event *, Key *, size_t);
179 static void *read_th(void *arg);
180 static void start_ev(void);
181 static void refresh_pane(Pane *);
182 static void set_direntr(Pane *, struct dirent *, DIR *, char *);
183 static int listdir(Pane *);
184 static void t_resize(void);
185 static void get_shell(void);
186 static void opnsh(const Arg *arg);
187 static void set_panes(void);
188 static void draw_frame(void);
189 static void refresh(const Arg *arg);
190 static void start(void);
191 
192 /* global variables */
193 static pthread_t fsev_thread;
194 static Pane panes[2];
195 static Pane *cpane;
196 static int pane_idx;
197 static char *editor[2];
198 static char fed[] = "vi";
199 static char *shell[2];
200 static char sh[] = "/bin/sh";
201 static int theight, twidth, hwidth, scrheight;
202 static int *sel_indexes;
203 static size_t sel_len = 0;
204 static char **sel_files;
205 static int cont_vmode = 0;
206 static int cont_change = 0;
207 static pid_t fork_pid = 0, main_pid;
208 #if defined(_SYS_INOTIFY_H)
209 #define READEVSZ 16
210 static int inotify_fd;
211 #elif defined(_SYS_EVENT_H_)
212 #define READEVSZ 0
213 static int kq;
214 struct kevent evlist[2]; /* events we want to monitor */
215 struct kevent chlist[2]; /* events that were triggered */
216 static struct timespec gtimeout;
217 #endif
218 #if defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__)
219 #define OFF_T "%ld"
220 #elif defined(__NetBSD__) || defined(__OpenBSD__) || defined(__APPLE__)
221 #define OFF_T "%lld"
222 #endif
223 enum { Left, Right }; /* panes */
224 enum { Wait, DontWait }; /* spawn forks */
225 
226 /* configuration, allows nested code to access above variables */
227 #include "config.h"
228 
229 /* function implementations */
230 static void
print_tb(const char * str,int x,int y,uint16_t fg,uint16_t bg)231 print_tb(const char *str, int x, int y, uint16_t fg, uint16_t bg)
232 {
233 	while (*str != '\0') {
234 		uint32_t uni = 0;
235 		str += tb_utf8_char_to_unicode(&uni, str);
236 		tb_change_cell(x, y, uni, fg, bg);
237 		x++;
238 	}
239 }
240 
241 static void
printf_tb(int x,int y,Cpair col,const char * fmt,...)242 printf_tb(int x, int y, Cpair col, const char *fmt, ...)
243 {
244 	char buf[MAX_LINE];
245 	va_list vl;
246 	va_start(vl, fmt);
247 	(void)vsnprintf(buf, MAX_LINE, fmt, vl);
248 	va_end(vl);
249 	print_tb(buf, x, y, col.fg, col.bg);
250 }
251 
252 static void
print_status(Cpair col,const char * fmt,...)253 print_status(Cpair col, const char *fmt, ...)
254 {
255 	char buf[MAX_STATUS];
256 	va_list vl;
257 	va_start(vl, fmt);
258 	(void)vsnprintf(buf, MAX_STATUS, fmt, vl);
259 	va_end(vl);
260 	clear_status();
261 	print_tb(buf, 1, theight - 1, col.fg, col.bg);
262 }
263 
264 static void
print_xstatus(char c,int x)265 print_xstatus(char c, int x)
266 {
267 	uint32_t uni = 0;
268 	(void)tb_utf8_char_to_unicode(&uni, &c);
269 	tb_change_cell(x, theight - 1, uni, cstatus.fg, cstatus.bg);
270 }
271 
272 static void
print_error(char * errmsg)273 print_error(char *errmsg)
274 {
275 	print_status(cerr, errmsg);
276 }
277 
278 static void
print_prompt(char * prompt)279 print_prompt(char *prompt)
280 {
281 	print_status(cprompt, prompt);
282 }
283 
284 static void
print_info(Pane * pane,char * dirsize)285 print_info(Pane *pane, char *dirsize)
286 {
287 	char *sz, *ur, *gr, *dt, *prm;
288 
289 	dt = ecalloc(MAX_DTF, sizeof(char));
290 
291 	prm = get_fperm(CURSOR(pane).mode);
292 	ur = get_fusr(CURSOR(pane).user);
293 	gr = get_fgrp(CURSOR(pane).group);
294 
295 	if (get_fdt(dt, CURSOR(pane).dt) < 0)
296 		*dt = '\0';
297 
298 	if (S_ISREG(CURSOR(pane).mode)) {
299 		sz = get_fsize(CURSOR(pane).size);
300 	} else {
301 		if (dirsize == NULL) {
302 			sz = ecalloc(1, sizeof(char));
303 			*sz = '\0';
304 		} else {
305 			sz = dirsize;
306 		}
307 	}
308 
309 	print_status(cstatus, "%02d/%02d %s %s:%s %s %s", pane->hdir,
310 		pane->dirc, prm, ur, gr, dt, sz);
311 
312 	free(prm);
313 	free(ur);
314 	free(gr);
315 	free(dt);
316 	free(sz);
317 }
318 
319 static void
print_row(Pane * pane,size_t entpos,Cpair col)320 print_row(Pane *pane, size_t entpos, Cpair col)
321 {
322 	int x, y;
323 	char *full_str, *rez_pth;
324 	char lnk_full[MAX_N];
325 
326 	full_str = basename(pane->direntr[entpos].name);
327 	x = pane->x_srt;
328 	y = entpos - pane->firstrow + 1;
329 
330 	if (S_ISLNK(pane->direntr[entpos].mode) != 0) {
331 		rez_pth = ecalloc(MAX_P, sizeof(char));
332 		if (realpath(pane->direntr[entpos].name, rez_pth) != NULL) {
333 			snprintf(
334 				lnk_full, MAX_N, "%s -> %s", full_str, rez_pth);
335 			full_str = lnk_full;
336 		}
337 		free(rez_pth);
338 	}
339 
340 	printf_tb(x, y, col, "%*.*s", ~hwidth, hwidth, full_str);
341 }
342 
343 static void
clear(int sx,int ex,int y,uint16_t bg)344 clear(int sx, int ex, int y, uint16_t bg)
345 {
346 	/* clear line from to */
347 	/* x = line number vertical */
348 	/* y = column number horizontal */
349 	int i;
350 	for (i = sx; i < ex; i++) {
351 		tb_change_cell(i, y, 0x0000, TB_DEFAULT, bg);
352 	}
353 }
354 
355 static void
clear_status(void)356 clear_status(void)
357 {
358 	clear(1, twidth - 1, theight - 1, cstatus.bg);
359 }
360 
361 static void
clear_pane(Pane * pane)362 clear_pane(Pane *pane)
363 {
364 	int i, y;
365 	y = 0, i = 0;
366 
367 	while (i < scrheight) {
368 		clear(pane->x_srt, pane->x_end, y, TB_DEFAULT);
369 		i++;
370 		y++;
371 	}
372 
373 	/* draw top line */
374 	for (y = pane->x_srt; y < pane->x_end; ++y) {
375 		tb_change_cell(y, 0, u_hl, cframe.fg, cframe.bg);
376 	}
377 }
378 
379 static void
add_hi(Pane * pane,size_t entpos)380 add_hi(Pane *pane, size_t entpos)
381 {
382 	Cpair col;
383 	get_hicol(&col, pane->direntr[entpos].mode);
384 	col.fg |= TB_REVERSE | TB_BOLD;
385 	col.bg |= TB_REVERSE;
386 	print_row(pane, entpos, col);
387 }
388 
389 static void
rm_hi(Pane * pane,size_t entpos)390 rm_hi(Pane *pane, size_t entpos)
391 {
392 	Cpair col;
393 	get_hicol(&col, pane->direntr[entpos].mode);
394 	print_row(pane, entpos, col);
395 }
396 
397 static int
check_dir(char * path)398 check_dir(char *path)
399 {
400 	DIR *dir;
401 	dir = opendir(path);
402 
403 	if (dir == NULL) {
404 		if (errno == ENOTDIR) {
405 			return 1;
406 		} else {
407 			return -1;
408 		}
409 	}
410 
411 	if (closedir(dir) < 0)
412 		return -1;
413 
414 	return 0;
415 }
416 
417 static int
sort_name(const void * const A,const void * const B)418 sort_name(const void *const A, const void *const B)
419 {
420 	int result;
421 	mode_t data1 = (*(Entry *)A).mode;
422 	mode_t data2 = (*(Entry *)B).mode;
423 
424 	if (data1 < data2) {
425 		return -1;
426 	} else if (data1 == data2) {
427 		result = strncmp((*(Entry *)A).name, (*(Entry *)B).name, MAX_N);
428 		return result;
429 	} else {
430 		return 1;
431 	}
432 }
433 
434 static void
get_dirp(char * cdir)435 get_dirp(char *cdir)
436 {
437 	int counter, len, i;
438 
439 	counter = 0;
440 	len = strnlen(cdir, MAX_P);
441 	if (len == 1)
442 		return;
443 
444 	for (i = len - 1; i > 1; i--) {
445 		if (cdir[i] == '/')
446 			break;
447 		else
448 			counter++;
449 	}
450 
451 	cdir[len - counter - 1] = '\0';
452 }
453 
454 static char *
get_ext(char * str)455 get_ext(char *str)
456 {
457 	char *ext;
458 	char dot;
459 	size_t counter, len, i;
460 
461 	dot = '.';
462 	counter = 0;
463 	len = strnlen(str, MAX_N);
464 
465 	for (i = len - 1; i > 0; i--) {
466 		if (str[i] == dot) {
467 			break;
468 		} else {
469 			counter++;
470 		}
471 	}
472 
473 	ext = ecalloc(MAX_EXT + 1, sizeof(char));
474 	strncpy(ext, &str[len - counter], MAX_EXT);
475 	ext[MAX_EXT] = '\0';
476 	return ext;
477 }
478 
479 static int
get_fdt(char * result,time_t status)480 get_fdt(char *result, time_t status)
481 {
482 	struct tm lt;
483 	localtime_r(&status, &lt);
484 	return strftime(result, MAX_DTF, dtfmt, &lt);
485 }
486 
487 static char *
get_fgrp(gid_t status)488 get_fgrp(gid_t status)
489 {
490 	char *result;
491 	struct group *gr;
492 
493 	result = ecalloc(MAX_GRPN, sizeof(char));
494 	gr = getgrgid(status);
495 	if (gr == NULL)
496 		(void)snprintf(result, MAX_GRPN, "%u", status);
497 	else
498 		strncpy(result, gr->gr_name, MAX_GRPN);
499 
500 	result[MAX_GRPN - 1] = '\0';
501 	return result;
502 }
503 
504 static char *
get_fperm(mode_t mode)505 get_fperm(mode_t mode)
506 {
507 	char *buf;
508 	size_t i;
509 
510 	const char chars[] = "rwxrwxrwx";
511 	buf = ecalloc(11, sizeof(char));
512 
513 	if (S_ISDIR(mode))
514 		buf[0] = 'd';
515 	else if (S_ISREG(mode))
516 		buf[0] = '-';
517 	else if (S_ISLNK(mode))
518 		buf[0] = 'l';
519 	else if (S_ISBLK(mode))
520 		buf[0] = 'b';
521 	else if (S_ISCHR(mode))
522 		buf[0] = 'c';
523 	else if (S_ISFIFO(mode))
524 		buf[0] = 'p';
525 	else if (S_ISSOCK(mode))
526 		buf[0] = 's';
527 	else
528 		buf[0] = '?';
529 
530 	for (i = 1; i < 10; i++) {
531 		buf[i] = (mode & (1 << (9 - i))) ? chars[i - 1] : '-';
532 	}
533 	buf[10] = '\0';
534 
535 	return buf;
536 }
537 
538 static char *
get_fsize(off_t size)539 get_fsize(off_t size)
540 {
541 	char *result; /* need to be freed */
542 	char unit;
543 	int result_len;
544 	int counter;
545 
546 	counter = 0;
547 	result_len = 6; /* 9999X/0 */
548 	result = ecalloc(result_len, sizeof(char));
549 
550 	while (size >= 1000) {
551 		size /= 1024;
552 		++counter;
553 	}
554 
555 	switch (counter) {
556 	case 0:
557 		unit = 'B';
558 		break;
559 	case 1:
560 		unit = 'K';
561 		break;
562 	case 2:
563 		unit = 'M';
564 		break;
565 	case 3:
566 		unit = 'G';
567 		break;
568 	case 4:
569 		unit = 'T';
570 		break;
571 	default:
572 		unit = '?';
573 	}
574 
575 	if (snprintf(result, result_len, OFF_T "%c", size, unit) < 0)
576 		strncat(result, "???", result_len);
577 
578 	return result;
579 }
580 
581 static char *
get_fullpath(char * first,char * second)582 get_fullpath(char *first, char *second)
583 {
584 	char *full_path;
585 
586 	full_path = ecalloc(MAX_P, sizeof(char));
587 
588 	if (strncmp(first, "/", MAX_P) == 0)
589 		(void)snprintf(full_path, MAX_P, "/%s", second);
590 	else
591 		(void)snprintf(full_path, MAX_P, "%s/%s", first, second);
592 
593 	return full_path;
594 }
595 
596 static char *
get_fusr(uid_t status)597 get_fusr(uid_t status)
598 {
599 	char *result;
600 	struct passwd *pw;
601 
602 	result = ecalloc(MAX_USRN, sizeof(char));
603 	pw = getpwuid(status);
604 	if (pw == NULL)
605 		(void)snprintf(result, MAX_USRN, "%u", status);
606 	else
607 		strncpy(result, pw->pw_name, MAX_USRN);
608 
609 	result[MAX_USRN - 1] = '\0';
610 	return result;
611 }
612 
613 static void
get_dirsize(char * fullpath,off_t * fullsize)614 get_dirsize(char *fullpath, off_t *fullsize)
615 {
616 	DIR *dir;
617 	char *ent_full;
618 	mode_t mode;
619 	struct dirent *entry;
620 	struct stat status;
621 
622 	dir = opendir(fullpath);
623 	if (dir == NULL) {
624 		return;
625 	}
626 
627 	while ((entry = readdir(dir)) != 0) {
628 		if ((strncmp(entry->d_name, ".", 2) == 0 ||
629 			    strncmp(entry->d_name, "..", 3) == 0))
630 			continue;
631 
632 		ent_full = get_fullpath(fullpath, entry->d_name);
633 		if (lstat(ent_full, &status) == 0) {
634 			mode = status.st_mode;
635 			if (S_ISDIR(mode)) {
636 				get_dirsize(ent_full, fullsize);
637 				free(ent_full);
638 			} else {
639 				*fullsize += status.st_size;
640 				free(ent_full);
641 			}
642 		}
643 	}
644 
645 	closedir(dir);
646 	clear_status();
647 }
648 
649 static void
get_hicol(Cpair * col,mode_t mode)650 get_hicol(Cpair *col, mode_t mode)
651 {
652 	switch (mode & S_IFMT) {
653 	case S_IFREG:
654 		*col = cfile;
655 		if ((S_IXUSR | S_IXGRP | S_IXOTH) & mode)
656 			*col = cexec;
657 		break;
658 	case S_IFDIR:
659 		*col = cdir;
660 		break;
661 	case S_IFLNK:
662 		*col = clnk;
663 		break;
664 	case S_IFBLK:
665 		*col = cblk;
666 		break;
667 	case S_IFCHR:
668 		*col = cchr;
669 		break;
670 	case S_IFIFO:
671 		*col = cifo;
672 		break;
673 	case S_IFSOCK:
674 		*col = csock;
675 		break;
676 	default:
677 		*col = cother;
678 		break;
679 	}
680 }
681 
682 static void
delent(const Arg * arg)683 delent(const Arg *arg)
684 {
685 	if (cpane->dirc < 1)
686 		return;
687 	char *inp_conf;
688 
689 	inp_conf = ecalloc(delconf_len, sizeof(char));
690 	if ((get_usrinput(inp_conf, delconf_len, "delete files(s) (%s) ?",
691 		     delconf) < 0) ||
692 		(strncmp(inp_conf, delconf, delconf_len) != 0)) {
693 		free(inp_conf);
694 		return; /* canceled by user or wrong inp_conf */
695 	}
696 	free(inp_conf);
697 
698 	char *tmp[1];
699 	tmp[0] = CURSOR(cpane).name;
700 	if (spawn(rm_cmd, rm_cmd_len, tmp, 1, NULL, DontWait) < 0) {
701 		print_error(strerror(errno));
702 		return;
703 	}
704 }
705 
706 static void
calcdir(const Arg * arg)707 calcdir(const Arg *arg)
708 {
709 	if (cpane->dirc < 1)
710 		return;
711 	if (!S_ISDIR(CURSOR(cpane).mode))
712 		return;
713 
714 	off_t *fullsize;
715 	char *csize;
716 
717 	fullsize = ecalloc(1, sizeof(off_t));
718 	get_dirsize(CURSOR(cpane).name, fullsize);
719 	csize = get_fsize(*fullsize);
720 
721 	CURSOR(cpane).size = *fullsize;
722 	print_info(cpane, csize);
723 	free(fullsize);
724 }
725 
726 static void
crnd(const Arg * arg)727 crnd(const Arg *arg)
728 {
729 	char *user_input, *path;
730 
731 	user_input = ecalloc(MAX_USRI, sizeof(char));
732 	if (get_usrinput(user_input, MAX_USRI, "new dir") < 0) {
733 		free(user_input);
734 		return;
735 	}
736 
737 	path = ecalloc(MAX_P, sizeof(char));
738 	if (snprintf(path, MAX_P, "%s/%s", cpane->dirn, user_input) < 0) {
739 		free(user_input);
740 		free(path);
741 		return;
742 	}
743 
744 	PERROR(mkdir(path, ndir_perm) < 0);
745 
746 	free(user_input);
747 	free(path);
748 }
749 
750 static void
crnf(const Arg * arg)751 crnf(const Arg *arg)
752 {
753 	char *user_input, *path;
754 	int rf;
755 
756 	user_input = ecalloc(MAX_USRI, sizeof(char));
757 	if (get_usrinput(user_input, MAX_USRI, "new file") < 0) {
758 		free(user_input);
759 		return;
760 	}
761 
762 	path = ecalloc(MAX_P, sizeof(char));
763 	if (snprintf(path, MAX_P, "%s/%s", cpane->dirn, user_input) < 0) {
764 		free(user_input);
765 		free(path);
766 		return;
767 	}
768 
769 	rf = open(path, O_CREAT | O_EXCL, nf_perm);
770 
771 	if (rf < 0)
772 		print_error(strerror(errno));
773 	else if (close(rf) < 0)
774 		print_error(strerror(errno));
775 
776 	free(user_input);
777 	free(path);
778 }
779 static void
mv_ver(const Arg * arg)780 mv_ver(const Arg *arg)
781 {
782 
783 	if (cpane->dirc < 1)
784 		return;
785 	if (cpane->hdir - arg->i < 1) /* first line */
786 		return;
787 
788 	if (cpane->hdir - arg->i > cpane->dirc) /* last line */
789 		return;
790 
791 	if (cpane->firstrow > 0 && arg->i > 0 &&
792 		cpane->hdir <= (cpane->firstrow + arg->i)) { /* scroll up */
793 		cpane->firstrow = cpane->firstrow - arg->i;
794 		rm_hi(cpane, cpane->hdir - 1);
795 		cpane->hdir = cpane->hdir - arg->i;
796 		refresh_pane(cpane);
797 		add_hi(cpane, cpane->hdir - 1);
798 		return;
799 	}
800 
801 	if (cpane->hdir - cpane->firstrow >= scrheight + arg->i &&
802 		arg->i < 0) { /* scroll down */
803 		cpane->firstrow = cpane->firstrow - arg->i;
804 		rm_hi(cpane, cpane->hdir - 1);
805 		cpane->hdir = cpane->hdir - arg->i;
806 		refresh_pane(cpane);
807 		add_hi(cpane, cpane->hdir - 1);
808 		return;
809 	}
810 
811 	rm_hi(cpane, cpane->hdir - 1);
812 	cpane->hdir = cpane->hdir - arg->i;
813 	add_hi(cpane, cpane->hdir - 1);
814 	print_info(cpane, NULL);
815 }
816 
817 static void
mvbk(const Arg * arg)818 mvbk(const Arg *arg)
819 {
820 	if (cpane->dirn[0] == '/' && cpane->dirn[1] == '\0') { /* cwd = / */
821 		return;
822 	}
823 
824 	get_dirp(cpane->dirn);
825 	if (check_dir(cpane->dirn) < 0) {
826 		print_error(strerror(errno));
827 		return;
828 	}
829 
830 	cpane->firstrow = cpane->parent_firstrow;
831 	cpane->hdir = cpane->parent_row;
832 	PERROR(listdir(cpane) < 0);
833 	cpane->parent_firstrow = 0;
834 	cpane->parent_row = 1;
835 }
836 
837 static void
mvbtm(const Arg * arg)838 mvbtm(const Arg *arg)
839 {
840 	if (cpane->dirc < 1)
841 		return;
842 	if (cpane->dirc > scrheight) {
843 		rm_hi(cpane, cpane->hdir - 1);
844 		cpane->hdir = cpane->dirc;
845 		cpane->firstrow = cpane->dirc - scrheight + 1;
846 		refresh_pane(cpane);
847 		add_hi(cpane, cpane->hdir - 1);
848 	} else {
849 		rm_hi(cpane, cpane->hdir - 1);
850 		cpane->hdir = cpane->dirc;
851 		add_hi(cpane, cpane->hdir - 1);
852 	}
853 	print_info(cpane, NULL);
854 }
855 
856 static void
mvfwd(const Arg * arg)857 mvfwd(const Arg *arg)
858 {
859 	if (cpane->dirc < 1)
860 		return;
861 	int s;
862 
863 	switch (check_dir(CURSOR(cpane).name)) {
864 	case 0:
865 		strncpy(cpane->dirn, CURSOR(cpane).name, MAX_P);
866 		cpane->parent_row = cpane->hdir;
867 		cpane->parent_firstrow = cpane->firstrow;
868 		cpane->hdir = 1;
869 		cpane->firstrow = 0;
870 		PERROR(listdir(cpane) < 0);
871 		break;
872 	case 1: /* not a directory open file */
873 		tb_shutdown();
874 		s = opnf(CURSOR(cpane).name);
875 		if (tb_init() != 0)
876 			die("tb_init");
877 		t_resize();
878 		if (s < 0)
879 			print_error("process failed non-zero exit");
880 		break;
881 	case -1: /* failed to open directory */
882 		print_error(strerror(errno));
883 	}
884 }
885 
886 static void
mvtop(const Arg * arg)887 mvtop(const Arg *arg)
888 {
889 	if (cpane->dirc < 1)
890 		return;
891 	if (cpane->dirc > scrheight) {
892 		rm_hi(cpane, cpane->hdir - 1);
893 		cpane->hdir = 1;
894 		cpane->firstrow = 0;
895 		refresh_pane(cpane);
896 		add_hi(cpane, cpane->hdir - 1);
897 	} else {
898 		rm_hi(cpane, cpane->hdir - 1);
899 		cpane->hdir = 1;
900 		add_hi(cpane, cpane->hdir - 1);
901 		print_info(cpane, NULL);
902 	}
903 }
904 
905 static void
bkmrk(const Arg * arg)906 bkmrk(const Arg *arg)
907 {
908 	if (check_dir((char *)arg->v) != 0) {
909 		print_error(strerror(errno));
910 		return;
911 	}
912 
913 	strncpy(cpane->dirn, (char *)arg->v, MAX_P);
914 	cpane->firstrow = 0;
915 	cpane->parent_row = 1;
916 	cpane->hdir = 1;
917 	PERROR(listdir(cpane) < 0);
918 }
919 
920 static int
get_usrinput(char * result,size_t max_chars,const char * fmt,...)921 get_usrinput(char *result, size_t max_chars, const char *fmt, ...)
922 {
923 	char msg[MAX_N];
924 	size_t i, cpos, startat;
925 	struct tb_event fev;
926 	va_list vl;
927 
928 	i = 0;
929 	cpos = 1;
930 
931 	va_start(vl, fmt);
932 	startat = vsnprintf(msg, MAX_N, fmt, vl) + 1;
933 	va_end(vl);
934 
935 	clear_status();
936 	print_tb(msg, 1, theight - 1, cprompt.fg, cprompt.bg);
937 	tb_set_cursor(startat + 1, theight - 1);
938 	tb_present();
939 
940 	while (tb_poll_event(&fev) != 0) {
941 		switch (fev.type) {
942 		case TB_EVENT_KEY:
943 			if (fev.key == TB_KEY_ESC) {
944 				tb_set_cursor(-1, -1);
945 				clear_status();
946 				return -1;
947 			}
948 
949 			if (fev.key == TB_KEY_BACKSPACE ||
950 				fev.key == TB_KEY_BACKSPACE2) {
951 				if (BETWEEN(cpos, 2, max_chars)) {
952 					result[i - 1] = '\0';
953 					cpos--;
954 					i--;
955 					print_xstatus(' ', startat + cpos);
956 					tb_set_cursor(
957 						startat + cpos, theight - 1);
958 				}
959 
960 			} else if (fev.key == TB_KEY_ENTER) {
961 				tb_set_cursor(-1, -1);
962 				result[cpos - 1] = '\0';
963 				return 0;
964 
965 			} else if (fev.key) { /* disable other TB_KEY_* */
966 				break;
967 
968 			} else {
969 				if (cpos < max_chars) {
970 					print_xstatus(
971 						(char)fev.ch, (startat + cpos));
972 					result[i] = (char)fev.ch;
973 					tb_set_cursor((startat + cpos + 1),
974 						theight - 1);
975 					cpos++;
976 					i++;
977 				}
978 			}
979 
980 			tb_present();
981 			break;
982 
983 		case TB_EVENT_RESIZE:
984 			t_resize();
985 			clear_status();
986 			print_tb(msg, 1, theight - 1, cprompt.fg, cprompt.bg);
987 			print_tb(result, startat + 1, theight - 1, cstatus.fg,
988 				cstatus.bg);
989 			tb_present();
990 			break;
991 
992 		default:
993 			return -1;
994 		}
995 	}
996 
997 	return -1;
998 }
999 
1000 static int
frules(char * ex)1001 frules(char *ex)
1002 {
1003 	size_t c, d;
1004 
1005 	for (c = 0; c < LEN(rules); c++)
1006 		for (d = 0; d < rules[c].exlen; d++)
1007 			if (strncmp(rules[c].ext[d], ex, MAX_EXT) == 0)
1008 				return c;
1009 	return -1;
1010 }
1011 
1012 static int
spawn(const void * com_argv,size_t com_argc,const void * f_argv,size_t f_argc,char * fn,int waiting)1013 spawn(const void *com_argv, size_t com_argc, const void *f_argv, size_t f_argc,
1014 	char *fn, int waiting)
1015 {
1016 	int ws;
1017 	size_t argc;
1018 	pid_t r;
1019 
1020 	argc = com_argc + f_argc + 2;
1021 	char *argv[argc];
1022 
1023 	memcpy(argv, com_argv, com_argc * sizeof(char *)); /* command */
1024 	memcpy(&argv[com_argc], f_argv, f_argc * sizeof(char *)); /* files */
1025 
1026 	argv[argc - 2] = fn;
1027 	argv[argc - 1] = NULL;
1028 
1029 	fork_pid = fork();
1030 	switch (fork_pid) {
1031 	case -1:
1032 		return -1;
1033 	case 0:
1034 		execvp(argv[0], argv);
1035 		exit(EXIT_SUCCESS);
1036 	default:
1037 		if (waiting == Wait) {
1038 			while ((r = waitpid(fork_pid, &ws, 0)) == -1 &&
1039 				errno == EINTR)
1040 				continue;
1041 			if (r == -1)
1042 				return -1;
1043 			if ((WIFEXITED(ws) != 0) && (WEXITSTATUS(ws) != 0))
1044 				return -1;
1045 		}
1046 	}
1047 	fork_pid = 0; /* enable th_handler() */
1048 	return 0;
1049 }
1050 
1051 static int
opnf(char * fn)1052 opnf(char *fn)
1053 {
1054 	char *ex;
1055 	int c;
1056 
1057 	ex = get_ext(fn);
1058 	c = frules(ex);
1059 	free(ex);
1060 
1061 	if (c < 0) /* extension not found open in editor */
1062 		return spawn(editor, 1, NULL, 0, fn, Wait);
1063 	else
1064 		return spawn(
1065 			(char **)rules[c].v, rules[c].vlen, NULL, 0, fn, Wait);
1066 }
1067 
1068 static void
opnsh(const Arg * arg)1069 opnsh(const Arg *arg)
1070 {
1071 	int s;
1072 
1073 	tb_shutdown();
1074 	chdir(cpane->dirn);
1075 	s = spawn(shell, 1, NULL, 0, NULL, Wait);
1076 	if (tb_init() != 0)
1077 		die("tb_init");
1078 	t_resize();
1079 	if (s < 0)
1080 		print_error("process failed non-zero exit");
1081 }
1082 
1083 static int
fsev_init(void)1084 fsev_init(void)
1085 {
1086 #if defined(_SYS_INOTIFY_H)
1087 	inotify_fd = inotify_init();
1088 	if (inotify_fd < 0)
1089 		return -1;
1090 #elif defined(_SYS_EVENT_H_)
1091 	gtimeout.tv_sec = 1;
1092 	kq = kqueue();
1093 	if (kq < 0)
1094 		return -1;
1095 #endif
1096 	return 0;
1097 }
1098 
1099 static int
addwatch(Pane * pane)1100 addwatch(Pane *pane)
1101 {
1102 #if defined(_SYS_INOTIFY_H)
1103 	return pane->inotify_wd = inotify_add_watch(inotify_fd, pane->dirn,
1104 		       IN_MODIFY | IN_MOVED_FROM | IN_MOVED_TO | IN_CREATE |
1105 			       IN_ATTRIB | IN_DELETE | IN_DELETE_SELF |
1106 			       IN_MOVE_SELF);
1107 #elif defined(_SYS_EVENT_H_)
1108 	pane->event_fd = open(pane->dirn, O_RDONLY);
1109 	if (pane->event_fd < 0)
1110 		return pane->event_fd;
1111 	EV_SET(&evlist[pane->pane_id], pane->event_fd, EVFILT_VNODE,
1112 		EV_ADD | EV_CLEAR,
1113 		NOTE_DELETE | NOTE_EXTEND | NOTE_LINK | NOTE_RENAME |
1114 			NOTE_ATTRIB | NOTE_REVOKE | NOTE_WRITE,
1115 		0, NULL);
1116 	return 0;
1117 #endif
1118 }
1119 
1120 static int
read_events(void)1121 read_events(void)
1122 {
1123 #if defined(_SYS_INOTIFY_H)
1124 	char *p;
1125 	ssize_t r;
1126 	struct inotify_event *event;
1127 	const size_t events = 32;
1128 	const size_t evbuflen =
1129 		events * (sizeof(struct inotify_event) + MAX_N + 1);
1130 	char buf[evbuflen];
1131 
1132 	if (cpane->inotify_wd < 0)
1133 		return -1;
1134 	r = read(inotify_fd, buf, evbuflen);
1135 	if (r <= 0)
1136 		return r;
1137 
1138 	for (p = buf; p < buf + r;) {
1139 		event = (struct inotify_event *)p;
1140 		if (!event->wd)
1141 			break;
1142 		if (event->mask) {
1143 			return r;
1144 		}
1145 
1146 		p += sizeof(struct inotify_event) + event->len;
1147 	}
1148 #elif defined(_SYS_EVENT_H_)
1149 	return kevent(kq, evlist, 2, chlist, 2, &gtimeout);
1150 #endif
1151 	return -1;
1152 }
1153 
1154 static void
rmwatch(Pane * pane)1155 rmwatch(Pane *pane)
1156 {
1157 #if defined(_SYS_INOTIFY_H)
1158 	if (pane->inotify_wd >= 0)
1159 		inotify_rm_watch(inotify_fd, pane->inotify_wd);
1160 #elif defined(_SYS_EVENT_H_)
1161 	close(pane->event_fd);
1162 #endif
1163 }
1164 
1165 static void
fsev_shdn(void)1166 fsev_shdn(void)
1167 {
1168 	pthread_cancel(fsev_thread);
1169 #if defined(__linux__)
1170 	pthread_join(fsev_thread, NULL);
1171 #endif
1172 	rmwatch(&panes[Left]);
1173 	rmwatch(&panes[Right]);
1174 #if defined(_SYS_INOTIFY_H)
1175 	close(inotify_fd);
1176 #elif defined(_SYS_EVENT_H_)
1177 	close(kq);
1178 #endif
1179 }
1180 
1181 static void
toggle_df(const Arg * arg)1182 toggle_df(const Arg *arg)
1183 {
1184 	show_dotfiles = !show_dotfiles;
1185 	PERROR(listdir(&panes[Left]));
1186 	PERROR(listdir(&panes[Right]));
1187 	tb_present();
1188 }
1189 
1190 static void
start_filter(const Arg * arg)1191 start_filter(const Arg *arg)
1192 {
1193 	if (cpane->dirc < 1)
1194 		return;
1195 	char *user_input;
1196 	user_input = ecalloc(MAX_USRI, sizeof(char));
1197 	if (get_usrinput(user_input, MAX_USRI, "filter") < 0) {
1198 		free(user_input);
1199 		return;
1200 	}
1201 	cpane->filter = user_input;
1202 	if (listdir(cpane) < 0)
1203 		print_error("no match");
1204 	cpane->filter = NULL;
1205 	free(user_input);
1206 }
1207 
1208 static void
start_vmode(const Arg * arg)1209 start_vmode(const Arg *arg)
1210 {
1211 	if (cpane->dirc < 1)
1212 		return;
1213 	struct tb_event fev;
1214 	if (sel_indexes != NULL) {
1215 		free(sel_indexes);
1216 		sel_indexes = NULL;
1217 	}
1218 
1219 	sel_indexes = ecalloc(cpane->dirc, sizeof(size_t));
1220 	sel_indexes[0] = cpane->hdir;
1221 	cont_vmode = 0;
1222 	print_prompt("-- VISUAL --");
1223 	tb_present();
1224 	while (tb_poll_event(&fev) != 0) {
1225 		switch (fev.type) {
1226 		case TB_EVENT_KEY:
1227 			grabkeys(&fev, vkeys, vkeyslen);
1228 			if (cont_vmode == -1)
1229 				return;
1230 			tb_present();
1231 			break;
1232 		}
1233 	}
1234 }
1235 
1236 static void
exit_vmode(const Arg * arg)1237 exit_vmode(const Arg *arg)
1238 {
1239 	refresh_pane(cpane);
1240 	add_hi(cpane, cpane->hdir - 1);
1241 	cont_vmode = -1;
1242 }
1243 
1244 static void
start_change(const Arg * arg)1245 start_change(const Arg *arg)
1246 {
1247 	if (cpane->dirc < 1)
1248 		return;
1249 	struct tb_event fev;
1250 
1251 	cont_change = 0;
1252 	print_prompt("c [womf]");
1253 	tb_present();
1254 	while (tb_poll_event(&fev) != 0) {
1255 		switch (fev.type) {
1256 		case TB_EVENT_KEY:
1257 			grabkeys(&fev, ckeys, ckeyslen);
1258 			if (cont_change == -1)
1259 				return;
1260 			tb_present();
1261 			break;
1262 		}
1263 	}
1264 }
1265 
1266 static void
exit_change(const Arg * arg)1267 exit_change(const Arg *arg)
1268 {
1269 	cont_change = -1;
1270 	print_info(cpane, NULL);
1271 }
1272 
1273 static void
selup(const Arg * arg)1274 selup(const Arg *arg)
1275 {
1276 	mv_ver(arg);
1277 	print_prompt("-- VISUAL --");
1278 	int index = abs(cpane->hdir - sel_indexes[0]);
1279 
1280 	if (cpane->hdir < sel_indexes[0]) {
1281 		sel_indexes[index] = cpane->hdir;
1282 		add_hi(cpane, sel_indexes[index]);
1283 	} else if (index < cpane->dirc) {
1284 		sel_indexes[index + 1] = 0;
1285 	}
1286 	if (cpane->dirc >= scrheight ||
1287 		cpane->hdir <= 1) { /* rehighlight all if scrolling */
1288 		selref();
1289 	}
1290 }
1291 
1292 static void
seldwn(const Arg * arg)1293 seldwn(const Arg *arg)
1294 {
1295 	mv_ver(arg);
1296 	print_prompt("-- VISUAL --");
1297 	int index = abs(cpane->hdir - sel_indexes[0]);
1298 
1299 	if (cpane->hdir > sel_indexes[0]) {
1300 		sel_indexes[index] = cpane->hdir;
1301 		add_hi(cpane, sel_indexes[index] - 2);
1302 	} else {
1303 		sel_indexes[index + 1] = 0;
1304 	}
1305 	if (cpane->dirc >= scrheight ||
1306 		cpane->hdir >= cpane->dirc) { /* rehighlight all if scrolling */
1307 		selref();
1308 	}
1309 }
1310 
1311 static void
selall(const Arg * arg)1312 selall(const Arg *arg)
1313 {
1314 	int i;
1315 	for (i = 0; i < cpane->dirc; i++) {
1316 		sel_indexes[i] = i + 1;
1317 	}
1318 	selref();
1319 }
1320 
1321 static void
selref(void)1322 selref(void)
1323 {
1324 	int i;
1325 	for (i = 0; i < cpane->dirc; i++) {
1326 		if (sel_indexes[i] < (scrheight + cpane->firstrow) &&
1327 			sel_indexes[i] >
1328 				cpane->firstrow) { /* checks if in the frame of the directories */
1329 			add_hi(cpane, sel_indexes[i] - 1);
1330 		}
1331 	}
1332 }
1333 
1334 static void
selcalc(void)1335 selcalc(void)
1336 {
1337 	int j;
1338 	sel_len = 0;
1339 
1340 	for (j = 0; j < cpane->dirc; j++) { /* calculate used selection size */
1341 		if (sel_indexes[j] != 0)
1342 			sel_len++;
1343 		else
1344 			break;
1345 	}
1346 }
1347 
1348 static void
free_files(void)1349 free_files(void)
1350 {
1351 	size_t i;
1352 
1353 	if (sel_files != NULL) {
1354 		for (i = 0; i < sel_len; i++) {
1355 			free(sel_files[i]);
1356 			sel_files[i] = NULL;
1357 		}
1358 		free(sel_files);
1359 		sel_files = NULL;
1360 	}
1361 }
1362 
1363 static void
init_files(void)1364 init_files(void)
1365 {
1366 	size_t i;
1367 	free_files();
1368 
1369 	selcalc();
1370 	sel_files = ecalloc(sel_len, sizeof(char *));
1371 
1372 	for (i = 0; i < sel_len; i++) {
1373 		sel_files[i] = ecalloc(MAX_P, sizeof(char));
1374 		strncpy(sel_files[i], cpane->direntr[sel_indexes[i] - 1].name,
1375 			MAX_P);
1376 	}
1377 }
1378 
1379 static void
selynk(const Arg * arg)1380 selynk(const Arg *arg)
1381 {
1382 	init_files();
1383 	refresh_pane(cpane);
1384 	add_hi(cpane, cpane->hdir - 1);
1385 	print_status(cprompt, "%zu files are yanked", sel_len);
1386 	cont_vmode = -1;
1387 }
1388 
1389 static void
seldel(const Arg * arg)1390 seldel(const Arg *arg)
1391 {
1392 	char *inp_conf;
1393 
1394 	inp_conf = ecalloc(delconf_len, sizeof(char));
1395 	if ((get_usrinput(inp_conf, delconf_len, "delete files(s) (%s) ?",
1396 		     delconf) < 0) ||
1397 		(strncmp(inp_conf, delconf, delconf_len) != 0)) {
1398 		free(inp_conf);
1399 		return; /* canceled by user or wrong inp_conf */
1400 	}
1401 	free(inp_conf);
1402 
1403 	init_files();
1404 
1405 	if (spawn(rm_cmd, rm_cmd_len, sel_files, sel_len, NULL, DontWait) < 0)
1406 		print_error(strerror(errno));
1407 	else
1408 		print_status(cprompt, "%zu files are deleted", sel_len);
1409 
1410 	free_files();
1411 	cont_vmode = -1;
1412 }
1413 
1414 static void
paste(const Arg * arg)1415 paste(const Arg *arg)
1416 {
1417 	if (sel_files == NULL) {
1418 		print_error("nothing to paste");
1419 		return;
1420 	}
1421 
1422 	if (spawn(cp_cmd, cp_cmd_len, sel_files, sel_len, cpane->dirn,
1423 		    DontWait) < 0)
1424 		print_error(strerror(errno));
1425 	else
1426 		print_status(cprompt, "%zu files are copied", sel_len);
1427 
1428 	free_files();
1429 }
1430 
1431 static void
selmv(const Arg * arg)1432 selmv(const Arg *arg)
1433 {
1434 	if (sel_files == NULL) {
1435 		print_error("nothing to move");
1436 		return;
1437 	}
1438 
1439 	if (spawn(mv_cmd, mv_cmd_len, sel_files, sel_len, cpane->dirn,
1440 		    DontWait) < 0)
1441 		print_error(strerror(errno));
1442 	else
1443 		print_status(cprompt, "%zu files are moved", sel_len);
1444 
1445 	free_files();
1446 }
1447 
1448 static void
rname(const Arg * arg)1449 rname(const Arg *arg)
1450 {
1451 	if (cpane->dirc < 1)
1452 		return;
1453 	char new_name[MAX_P];
1454 	char *input_name;
1455 
1456 	input_name = ecalloc(MAX_N, sizeof(char));
1457 
1458 	if (get_usrinput(input_name, MAX_N, "rename: %s",
1459 		    basename(CURSOR(cpane).name)) < 0) {
1460 		exit_change(0);
1461 		free(input_name);
1462 		return;
1463 	}
1464 
1465 	if (snprintf(new_name, MAX_P, "%s/%s", cpane->dirn, input_name) < 0) {
1466 		free(input_name);
1467 		print_error(strerror(errno));
1468 		return;
1469 	}
1470 
1471 	char *rename_cmd[] = { "mv", CURSOR(cpane).name, new_name };
1472 	PERROR(spawn(rename_cmd, 3, NULL, 0, NULL, DontWait) < 0);
1473 
1474 	free(input_name);
1475 	exit_change(0);
1476 }
1477 
1478 static void
chngo(const Arg * arg)1479 chngo(const Arg *arg)
1480 {
1481 	if (cpane->dirc < 1)
1482 		return;
1483 	char *input_og;
1484 	char *tmp[1];
1485 
1486 	input_og = ecalloc(MAX_N, sizeof(char));
1487 
1488 	if (get_usrinput(input_og, MAX_N, "OWNER:GROUP %s",
1489 		    basename(CURSOR(cpane).name)) < 0) {
1490 		exit_change(0);
1491 		free(input_og);
1492 		return;
1493 	}
1494 
1495 	tmp[0] = input_og;
1496 	if (spawn(chown_cmd, chown_cmd_len, tmp, 1, CURSOR(cpane).name,
1497 		    DontWait) < 0) {
1498 		print_error(strerror(errno));
1499 		return;
1500 	}
1501 
1502 	free(input_og);
1503 	exit_change(0);
1504 }
1505 
1506 static void
chngm(const Arg * arg)1507 chngm(const Arg *arg)
1508 {
1509 	if (cpane->dirc < 1)
1510 		return;
1511 	char *input_og;
1512 	char *tmp[1];
1513 
1514 	input_og = ecalloc(MAX_N, sizeof(char));
1515 
1516 	if (get_usrinput(input_og, MAX_N, "chmod %s",
1517 		    basename(CURSOR(cpane).name)) < 0) {
1518 		exit_change(0);
1519 		free(input_og);
1520 		return;
1521 	}
1522 
1523 	tmp[0] = input_og;
1524 	if (spawn(chmod_cmd, chmod_cmd_len, tmp, 1, CURSOR(cpane).name,
1525 		    DontWait) < 0) {
1526 		print_error(strerror(errno));
1527 		return;
1528 	}
1529 
1530 	free(input_og);
1531 	exit_change(0);
1532 }
1533 
1534 static void
chngf(const Arg * arg)1535 chngf(const Arg *arg)
1536 {
1537 	if (cpane->dirc < 1)
1538 		return;
1539 	char *input_og;
1540 	char *tmp[1];
1541 
1542 	input_og = ecalloc(MAX_N, sizeof(char));
1543 
1544 	if (get_usrinput(input_og, MAX_N, CHFLAG " %s",
1545 		    basename(CURSOR(cpane).name)) < 0) {
1546 		exit_change(0);
1547 		free(input_og);
1548 		return;
1549 	}
1550 
1551 	tmp[0] = input_og;
1552 	if (spawn(chflags_cmd, chflags_cmd_len, tmp, 1, CURSOR(cpane).name,
1553 		    DontWait) < 0) {
1554 		print_error(strerror(errno));
1555 		return;
1556 	}
1557 
1558 	free(input_og);
1559 	exit_change(0);
1560 }
1561 
1562 static void
dupl(const Arg * arg)1563 dupl(const Arg *arg)
1564 {
1565 	if (cpane->dirc < 1)
1566 		return;
1567 	char new_name[MAX_P];
1568 	char *input_name;
1569 
1570 	input_name = ecalloc(MAX_N, sizeof(char));
1571 
1572 	if (get_usrinput(input_name, MAX_N, "new name: %s",
1573 		    basename(CURSOR(cpane).name)) < 0) {
1574 		free(input_name);
1575 		return;
1576 	}
1577 
1578 	if (snprintf(new_name, MAX_P, "%s/%s", cpane->dirn, input_name) < 0) {
1579 		free(input_name);
1580 		print_error(strerror(errno));
1581 		return;
1582 	}
1583 
1584 	char *tmp[1];
1585 	tmp[0] = CURSOR(cpane).name;
1586 	if (spawn(cp_cmd, cp_cmd_len, tmp, 1, new_name, DontWait) < 0) {
1587 		print_error(strerror(errno));
1588 		return;
1589 	}
1590 
1591 	free(input_name);
1592 }
1593 
1594 static void
yank(const Arg * arg)1595 yank(const Arg *arg)
1596 {
1597 	if (cpane->dirc < 1)
1598 		return;
1599 
1600 	free_files();
1601 	sel_len = 1;
1602 	sel_files = ecalloc(sel_len, sizeof(char *));
1603 	sel_files[0] = ecalloc(MAX_P, sizeof(char));
1604 	strncpy(sel_files[0], CURSOR(cpane).name, MAX_P);
1605 	print_status(cprompt, "1 file is yanked", sel_len);
1606 }
1607 
1608 static void
switch_pane(const Arg * arg)1609 switch_pane(const Arg *arg)
1610 {
1611 	if (cpane->dirc > 0)
1612 		rm_hi(cpane, cpane->hdir - 1);
1613 	cpane = &panes[pane_idx ^= 1];
1614 	if (cpane->dirc > 0) {
1615 		add_hi(cpane, cpane->hdir - 1);
1616 		print_info(cpane, NULL);
1617 	} else {
1618 		clear_status();
1619 	}
1620 }
1621 
1622 static void
quit(const Arg * arg)1623 quit(const Arg *arg)
1624 {
1625 	if (cont_vmode == -1) { /* check if selection was allocated */
1626 		free(sel_indexes);
1627 		if (sel_files != NULL)
1628 			free_files();
1629 	}
1630 	free(panes[Left].direntr);
1631 	free(panes[Right].direntr);
1632 	fsev_shdn();
1633 	tb_shutdown();
1634 	exit(EXIT_SUCCESS);
1635 }
1636 
1637 static void
grabkeys(struct tb_event * event,Key * key,size_t max_keys)1638 grabkeys(struct tb_event *event, Key *key, size_t max_keys)
1639 {
1640 	size_t i;
1641 
1642 	for (i = 0; i < max_keys; i++) {
1643 		if (event->ch != 0) {
1644 			if (event->ch == key[i].evkey.ch) {
1645 				key[i].func(&key[i].arg);
1646 				return;
1647 			}
1648 		} else if (event->key != 0) {
1649 			if (event->key == key[i].evkey.key) {
1650 				key[i].func(&key[i].arg);
1651 				return;
1652 			}
1653 		}
1654 	}
1655 }
1656 
1657 void *
read_th(void * arg)1658 read_th(void *arg)
1659 {
1660 	struct timespec tim;
1661 	tim.tv_sec = 0;
1662 	tim.tv_nsec = 5000000L; /* 0.005 sec */
1663 
1664 	while (1)
1665 		if (read_events() > READEVSZ) {
1666 			kill(main_pid, SIGUSR1);
1667 			nanosleep(&tim, NULL);
1668 		}
1669 	return arg;
1670 }
1671 
1672 static void
start_ev(void)1673 start_ev(void)
1674 {
1675 	struct tb_event ev;
1676 
1677 	while (tb_poll_event(&ev) != 0) {
1678 		switch (ev.type) {
1679 		case TB_EVENT_KEY:
1680 			grabkeys(&ev, nkeys, nkeyslen);
1681 			tb_present();
1682 			break;
1683 		case TB_EVENT_RESIZE:
1684 			t_resize();
1685 			break;
1686 		default:
1687 			break;
1688 		}
1689 	}
1690 	tb_shutdown();
1691 }
1692 
1693 static void
refresh_pane(Pane * pane)1694 refresh_pane(Pane *pane)
1695 {
1696 	size_t y, dyn_max, start_from;
1697 	hwidth = (twidth / 2) - 4;
1698 	Cpair col;
1699 
1700 	y = 1;
1701 	start_from = pane->firstrow;
1702 	dyn_max = MIN(pane->dirc, (scrheight - 1) + pane->firstrow);
1703 
1704 	/* print each entry in directory */
1705 	while (start_from < dyn_max) {
1706 		get_hicol(&col, pane->direntr[start_from].mode);
1707 		print_row(pane, start_from, col);
1708 		start_from++;
1709 		y++;
1710 	}
1711 
1712 	if (pane->dirc > 0)
1713 		print_info(pane, NULL);
1714 	else
1715 		clear_status();
1716 
1717 	/* print current directory title */
1718 	pane->dircol.fg |= TB_BOLD;
1719 	printf_tb(pane->x_srt, 0, pane->dircol, " %.*s", hwidth, pane->dirn);
1720 }
1721 
1722 static void
set_direntr(Pane * pane,struct dirent * entry,DIR * dir,char * filter)1723 set_direntr(Pane *pane, struct dirent *entry, DIR *dir, char *filter)
1724 {
1725 	int i;
1726 	char *tmpfull;
1727 	struct stat status;
1728 
1729 #define ADD_ENTRY                                          \
1730 	tmpfull = get_fullpath(pane->dirn, entry->d_name); \
1731 	strncpy(pane->direntr[i].name, tmpfull, MAX_N);    \
1732 	if (lstat(tmpfull, &status) == 0) {                \
1733 		pane->direntr[i].size = status.st_size;    \
1734 		pane->direntr[i].mode = status.st_mode;    \
1735 		pane->direntr[i].group = status.st_gid;    \
1736 		pane->direntr[i].user = status.st_uid;     \
1737 		pane->direntr[i].dt = status.st_mtime;     \
1738 	}                                                  \
1739 	i++;                                               \
1740 	free(tmpfull);
1741 
1742 	i = 0;
1743 	pane->direntr =
1744 		erealloc(pane->direntr, (10 + pane->dirc) * sizeof(Entry));
1745 	while ((entry = readdir(dir)) != 0) {
1746 		if (show_dotfiles == 1) {
1747 			if (entry->d_name[0] == '.' &&
1748 				(entry->d_name[1] == '\0' ||
1749 					entry->d_name[1] == '.'))
1750 				continue;
1751 		} else {
1752 			if (entry->d_name[0] == '.')
1753 				continue;
1754 		}
1755 
1756 		if (filter == NULL) {
1757 			ADD_ENTRY
1758 		} else if (filter != NULL) {
1759 			if (strcasestr(entry->d_name, filter) != NULL) {
1760 				ADD_ENTRY
1761 			}
1762 		}
1763 	}
1764 
1765 	pane->dirc = i;
1766 }
1767 
1768 static int
listdir(Pane * pane)1769 listdir(Pane *pane)
1770 {
1771 	DIR *dir;
1772 	struct dirent *entry;
1773 	int filtercount = 0;
1774 	size_t oldc = pane->dirc;
1775 
1776 	pane->dirc = 0;
1777 
1778 	dir = opendir(pane->dirn);
1779 	if (dir == NULL)
1780 		return -1;
1781 
1782 	/* get content and filter sum */
1783 	while ((entry = readdir(dir)) != 0) {
1784 		if (pane->filter != NULL) {
1785 			if (strcasestr(entry->d_name, pane->filter) != NULL)
1786 				filtercount++;
1787 		} else { /* no filter */
1788 			pane->dirc++;
1789 		}
1790 	}
1791 
1792 	if (pane->filter == NULL) {
1793 		clear_pane(pane);
1794 		pane->dirc -= 2;
1795 	}
1796 
1797 	if (pane->filter != NULL) {
1798 		if (filtercount > 0) {
1799 			pane->dirc = filtercount;
1800 			clear_pane(pane);
1801 			pane->hdir = 1;
1802 		} else if (filtercount == 0) {
1803 			if (closedir(dir) < 0)
1804 				return -1;
1805 			pane->dirc = oldc;
1806 			return -1;
1807 		}
1808 	}
1809 
1810 	/* print current directory title */
1811 	pane->dircol.fg |= TB_BOLD;
1812 	printf_tb(pane->x_srt, 0, pane->dircol, " %.*s", hwidth, pane->dirn);
1813 
1814 	if (pane->filter == NULL) /* dont't watch when filtering */
1815 		if (addwatch(pane) < 0)
1816 			print_error("can't add watch");
1817 
1818 	/* empty directory */
1819 	if (pane->dirc == 0) {
1820 		clear_status();
1821 		if (closedir(dir) < 0)
1822 			return -1;
1823 		return 0;
1824 	}
1825 
1826 	rewinddir(dir); /* reset position */
1827 	set_direntr(
1828 		pane, entry, dir, pane->filter); /* create array of entries */
1829 	qsort(pane->direntr, pane->dirc, sizeof(Entry), sort_name);
1830 	refresh_pane(pane);
1831 
1832 	if (pane->hdir > pane->dirc)
1833 		pane->hdir = pane->dirc;
1834 
1835 	if (pane == cpane && pane->dirc > 0)
1836 		add_hi(pane, pane->hdir - 1);
1837 
1838 	if (closedir(dir) < 0)
1839 		return -1;
1840 	return 0;
1841 }
1842 
1843 static void
t_resize(void)1844 t_resize(void)
1845 {
1846 	tb_clear();
1847 	draw_frame();
1848 	panes[Left].x_end = (twidth / 2) - 1;
1849 	panes[Right].x_end = twidth - 1;
1850 	panes[Right].x_srt = (twidth / 2) + 2;
1851 	refresh_pane(&panes[Left]);
1852 	refresh_pane(&panes[Right]);
1853 	if (cpane->dirc > 0)
1854 		add_hi(cpane, cpane->hdir - 1);
1855 	tb_present();
1856 }
1857 
1858 static void
get_editor(void)1859 get_editor(void)
1860 {
1861 	editor[0] = getenv("EDITOR");
1862 	editor[1] = NULL;
1863 
1864 	if (editor[0] == NULL)
1865 		editor[0] = fed;
1866 }
1867 
1868 static void
get_shell(void)1869 get_shell(void)
1870 {
1871 	shell[0] = getenv("SHELL");
1872 	shell[1] = NULL;
1873 
1874 	if (shell[0] == NULL)
1875 		shell[0] = sh;
1876 }
1877 
1878 static void
set_panes(void)1879 set_panes(void)
1880 {
1881 	char *home;
1882 	char cwd[MAX_P];
1883 
1884 	home = getenv("HOME");
1885 	if (home == NULL)
1886 		home = "/";
1887 	if ((getcwd(cwd, sizeof(cwd)) == NULL))
1888 		strncpy(cwd, home, MAX_P);
1889 
1890 	pane_idx = Left; /* cursor pane */
1891 	cpane = &panes[pane_idx];
1892 
1893 	panes[Left].pane_id = 0;
1894 	panes[Left].x_srt = 2;
1895 	panes[Left].x_end = (twidth / 2) - 1;
1896 	panes[Left].dircol = cpanell;
1897 	panes[Left].firstrow = 0;
1898 	panes[Left].direntr = ecalloc(0, sizeof(Entry));
1899 	strncpy(panes[Left].dirn, cwd, MAX_P);
1900 	panes[Left].hdir = 1;
1901 	panes[Left].inotify_wd = -1;
1902 	panes[Left].parent_row = 1;
1903 
1904 	panes[Right].pane_id = 1;
1905 	panes[Right].x_srt = (twidth / 2) + 2;
1906 	panes[Right].x_end = twidth - 1;
1907 	panes[Right].dircol = cpanelr;
1908 	panes[Right].firstrow = 0;
1909 	panes[Right].direntr = ecalloc(0, sizeof(Entry));
1910 	strncpy(panes[Right].dirn, home, MAX_P);
1911 	panes[Right].hdir = 1;
1912 	panes[Right].inotify_wd = -1;
1913 	panes[Right].parent_row = 1;
1914 }
1915 
1916 static void
draw_frame(void)1917 draw_frame(void)
1918 {
1919 	int i;
1920 	theight = tb_height();
1921 	twidth = tb_width();
1922 	hwidth = (twidth / 2) - 4;
1923 	scrheight = theight - 2;
1924 
1925 	/* 2 horizontal lines */
1926 	for (i = 1; i < twidth - 1; ++i) {
1927 		tb_change_cell(i, 0, u_hl, cframe.fg, cframe.bg);
1928 		tb_change_cell(i, theight - 2, u_hl, cframe.fg, cframe.bg);
1929 	}
1930 
1931 	/* 4 vertical lines */
1932 	for (i = 1; i < theight - 1; ++i) {
1933 		tb_change_cell(0, i, u_vl, cframe.fg, cframe.bg);
1934 		tb_change_cell(
1935 			(twidth - 1) / 2, i - 1, u_vl, cframe.fg, cframe.bg);
1936 		tb_change_cell(((twidth - 1) / 2) + 1, i - 1, u_vl, cframe.fg,
1937 			cframe.bg);
1938 		tb_change_cell(twidth - 1, i, u_vl, cframe.fg, cframe.bg);
1939 	}
1940 
1941 	/* 4 corners */
1942 	tb_change_cell(0, 0, u_cnw, cframe.fg, cframe.bg);
1943 	tb_change_cell(twidth - 1, 0, u_cne, cframe.fg, cframe.bg);
1944 	tb_change_cell(0, theight - 2, u_csw, cframe.fg, cframe.bg);
1945 	tb_change_cell(twidth - 1, theight - 2, u_cse, cframe.fg, cframe.bg);
1946 
1947 	/* 2 middel top and bottom */
1948 	tb_change_cell((twidth - 1) / 2, 0, u_mn, cframe.fg, cframe.bg);
1949 	tb_change_cell(
1950 		(twidth - 1) / 2, theight - 2, u_ms, cframe.fg, cframe.bg);
1951 }
1952 
1953 void
th_handler(int num)1954 th_handler(int num)
1955 {
1956 	if (fork_pid > 0) /* while forking don't listdir() */
1957 		return;
1958 	(void)num;
1959 	PERROR(listdir(&panes[Left]));
1960 	PERROR(listdir(&panes[Right]));
1961 	tb_present();
1962 }
1963 
1964 static int
start_signal(void)1965 start_signal(void)
1966 {
1967 	struct sigaction sa;
1968 
1969 	main_pid = getpid();
1970 	sa.sa_handler = th_handler;
1971 	sigemptyset(&sa.sa_mask);
1972 	sa.sa_flags = SA_RESTART;
1973 	return sigaction(SIGUSR1, &sa, NULL);
1974 }
1975 
1976 static void
refresh(const Arg * arg)1977 refresh(const Arg *arg)
1978 {
1979 	kill(main_pid, SIGWINCH);
1980 }
1981 
1982 static void
start(void)1983 start(void)
1984 {
1985 	switch (tb_init()) {
1986 	case TB_EFAILED_TO_OPEN_TTY:
1987 		die("TB_EFAILED_TO_OPEN_TTY");
1988 		break;
1989 	case TB_EUNSUPPORTED_TERMINAL:
1990 		die("TB_EUNSUPPORTED_TERMINAL");
1991 		break;
1992 	case TB_EPIPE_TRAP_ERROR:
1993 		die("TB_EUNSUPPORTED_TERMINAL");
1994 		break;
1995 	case 0:
1996 		break;
1997 	default:
1998 		die("UNKNOWN FAILURE");
1999 	}
2000 
2001 	if (tb_select_output_mode(TB_OUTPUT_256) != TB_OUTPUT_256)
2002 		if (tb_select_output_mode(TB_OUTPUT_NORMAL) != TB_OUTPUT_NORMAL)
2003 			die("output error");
2004 	draw_frame();
2005 	set_panes();
2006 	get_editor();
2007 	get_shell();
2008 	PERROR(start_signal() < 0);
2009 	PERROR(fsev_init() < 0);
2010 	PERROR(listdir(&panes[Left]) < 0);
2011 	PERROR(listdir(&panes[Right]) < 0);
2012 	tb_present();
2013 
2014 	pthread_create(&fsev_thread, NULL, read_th, NULL);
2015 	start_ev();
2016 }
2017 
2018 int
main(int argc,char * argv[])2019 main(int argc, char *argv[])
2020 {
2021 #if defined(__OpenBSD__)
2022 	if (pledge("cpath exec getpw proc rpath stdio tmppath tty wpath",
2023 		    NULL) == -1)
2024 		die("pledge");
2025 #endif /* __OpenBSD__ */
2026 	if (argc == 1)
2027 		start();
2028 	else if (argc == 2 && strncmp("-v", argv[1], 2) == 0)
2029 		die("sfm-" VERSION);
2030 	else
2031 		die("usage: sfm [-v]");
2032 	return 0;
2033 }
2034