1 /* aux.c -- functions that do not fit in any other file */
2
3 /*
4 * This file is part of CliFM
5 *
6 * Copyright (C) 2016-2021, L. Abramovich <johndoe.arch@outlook.com>
7 * All rights reserved.
8
9 * CliFM is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * CliFM is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22 * MA 02110-1301, USA.
23 */
24
25 #include "helpers.h"
26
27 #include <ctype.h>
28 #include <dirent.h>
29 #include <errno.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <sys/stat.h>
33 #include <sys/types.h>
34 #include <termios.h>
35 #include <unistd.h>
36 #include <fcntl.h>
37 #include <time.h>
38 #include <limits.h>
39 #include <readline/readline.h>
40
41 #include "aux.h"
42 #include "exec.h"
43 #include "misc.h"
44 #ifndef _NO_HIGHLIGHT
45 #include "highlight.h"
46 #endif
47
48 #ifdef RL81
49 /* Sleep for MSEC milliseconds */
50 /* Taken from https://stackoverflow.com/questions/1157209/is-there-an-alternative-sleep-function-in-c-to-milliseconds */
51 static int
msleep(long msec)52 msleep(long msec)
53 {
54 struct timespec ts;
55 int res;
56
57 if (msec < 0) {
58 errno = EINVAL;
59 return (-1);
60 }
61
62 ts.tv_sec = msec / 1000;
63 ts.tv_nsec = (msec % 1000) * 1000000;
64
65 do {
66 res = nanosleep(&ts, &ts);
67 } while (res && errno == EINTR);
68
69 return res;
70 }
71 #endif
72
73 void
rl_ring_bell(void)74 rl_ring_bell(void)
75 {
76 switch(bell) {
77 case BELL_NONE: return;
78
79 case BELL_AUDIBLE:
80 fputs("\007", stderr);
81 fflush(stderr);
82 return;
83
84 /* rl_activate_mark and rl_deactivate mark are available only since
85 * readline 8.1 */
86 #ifdef RL81
87 case BELL_VISIBLE: {
88 int point = rl_point;
89 rl_mark = rl_last_word_start;
90 if (rl_end > 1 && rl_line_buffer[rl_end - 1] == ' ')
91 rl_point--;
92 rl_activate_mark();
93 rl_redisplay();
94 msleep(VISIBLE_BELL_DELAY);
95 rl_deactivate_mark();
96 #ifndef _NO_HIGHLIGHT
97 if (highlight && !wrong_cmd) {
98 rl_point = rl_mark;
99 recolorize_line();
100 }
101 #endif /* !_NO_HIGHLIGHT */
102 rl_point = point;
103 return;
104 }
105 #endif /* RL81 */
106
107 default: return;
108 }
109
110 return;
111 }
112
113 /* The following three functions were taken from
114 * https://github.com/antirez/linenoise/blob/master/linenoise.c
115 * and modified to fir our needs: they are used to get current cursor
116 * position (both vertical and horizontal) by the suggestions system */
117
118 /* Set the terminal into raw mode. Return 0 on success and -1 on error */
119 static int
enable_raw_mode(const int fd)120 enable_raw_mode(const int fd)
121 {
122 struct termios raw;
123
124 if (!isatty(STDIN_FILENO))
125 goto FAIL;
126
127 if (tcgetattr(fd, &orig_termios) == -1)
128 goto FAIL;
129
130 raw = orig_termios; /* modify the original mode */
131 /* input modes: no break, no CR to NL, no parity check, no strip char,
132 * * no start/stop output control. */
133 raw.c_iflag &= (tcflag_t)~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
134 /* output modes - disable post processing */
135 raw.c_oflag &= (tcflag_t)~(OPOST);
136 /* control modes - set 8 bit chars */
137 raw.c_cflag |= (CS8);
138 /* local modes - choing off, canonical off, no extended functions,
139 * no signal chars (^Z,^C) */
140 raw.c_lflag &= (tcflag_t)~(ECHO | ICANON | IEXTEN | ISIG);
141 /* control chars - set return condition: min number of bytes and timer.
142 * We want read to return every single byte, without timeout. */
143 raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
144
145 /* put terminal in raw mode after flushing */
146 if (tcsetattr(fd, TCSAFLUSH, &raw) < 0)
147 goto FAIL;
148
149 return 0;
150
151 FAIL:
152 errno = ENOTTY;
153 return -1;
154 }
155
156 static int
disable_raw_mode(const int fd)157 disable_raw_mode(const int fd)
158 {
159 if (tcsetattr(fd, TCSAFLUSH, &orig_termios) != -1)
160 return EXIT_SUCCESS;
161 return EXIT_FAILURE;
162 }
163
164 /* Use the "ESC [6n" escape sequence to query the cursor position (both
165 * vertical and horizontal) and store both values into global variables.
166 * Return 0 on success and 1 on error */
167 int
get_cursor_position(const int ifd,const int ofd)168 get_cursor_position(const int ifd, const int ofd)
169 {
170 char buf[32];
171 int cols, rows;
172 unsigned int i = 0;
173
174 if (enable_raw_mode(ifd) == -1)
175 return EXIT_FAILURE;
176
177 /* Report cursor location */
178 if (write(ofd, "\x1b[6n", 4) != 4)
179 goto FAIL;
180
181 /* Read the response: "ESC [ rows ; cols R" */
182 while (i < sizeof(buf) - 1) {
183 if (read(ifd, buf + i, 1) != 1)
184 break;
185 if (buf[i] == 'R')
186 break;
187 i++;
188 }
189 buf[i] = '\0';
190
191 /* Parse it */
192 if (buf[0] != _ESC || buf[1] != '[')
193 goto FAIL;
194 if (sscanf(buf + 2, "%d;%d", &rows, &cols) != 2)
195 goto FAIL;
196
197 currow = rows;
198 curcol = cols;
199
200 disable_raw_mode(ifd);
201 return EXIT_SUCCESS;
202
203 FAIL:
204 disable_raw_mode(ifd);
205 return EXIT_FAILURE;
206 }
207 /*
208 int
209 get_term_bgcolor(const int ifd, const int ofd)
210 {
211 char buf[32] = {0};
212 unsigned int i = 0;
213
214 if (enable_raw_mode(ifd) == -1)
215 return EXIT_FAILURE;
216
217 // Report terminal background color
218 if (write(ofd, "\x1b]11;?\007", 7) != 7)
219 goto FAIL;
220
221 // Read the response: "ESC ] 11 ; rgb:COLOR BEL"
222 while (i < sizeof(buf) - 1) {
223 if (i > 22)
224 break;
225 if (read(ifd, buf + i, 1) != 1)
226 break;
227 printf("%d:'%c'\n", buf[i], buf[i]);
228 i++;
229 }
230 buf[i] = '\0';
231
232 char *p = strchr(buf, ':');
233 if (!p || !*(++p))
234 goto FAIL;
235 term_bgcolor = savestring(p, strlen(p));
236
237 disable_raw_mode(ifd);
238 return EXIT_SUCCESS;
239
240 FAIL:
241 disable_raw_mode(ifd);
242 return EXIT_FAILURE;
243 } */
244
245 char *
gen_date_suffix(struct tm tm)246 gen_date_suffix(struct tm tm)
247 {
248 char date[64] = "";
249 strftime(date, sizeof(date), "%b %d %H:%M:%S %Y", &tm);
250
251 char *suffix = (char *)xnmalloc(68, sizeof(char));
252 snprintf(suffix, 67, "%d%d%d%d%d%d", tm.tm_year + 1900,
253 tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
254
255 return suffix;
256 }
257
258 /* Create directory DIR with permissions set to MODE */
259 int
xmkdir(char * dir,mode_t mode)260 xmkdir(char *dir, mode_t mode)
261 {
262 mode_t old_mask = umask(0);
263 int ret = mkdirat(AT_FDCWD, dir, mode);
264 umask(old_mask);
265 if (ret == -1)
266 return EXIT_FAILURE;
267 return EXIT_SUCCESS;
268 }
269
270 /* Open a file for read only. Return a file stream associated to a file
271 * descriptor (FD) for the file named NAME */
272 FILE *
open_fstream_r(char * name,int * fd)273 open_fstream_r(char *name, int *fd)
274 {
275 if (!name || !*name)
276 return (FILE *)NULL;
277
278 *fd = open(name, O_RDONLY);
279 if (*fd == -1)
280 return (FILE *)NULL;
281
282 FILE *fp = fdopen(*fd, "r");
283 if (!fp) {
284 close(*fd);
285 return (FILE *)NULL;
286 }
287
288 return fp;
289 }
290
291 /* Create a file for writing. Return a file stream associated to a file
292 * descriptor (FD) for the file named NAME */
293 FILE *
open_fstream_w(char * name,int * fd)294 open_fstream_w(char *name, int *fd)
295 {
296 if (!name || !*name)
297 return (FILE *)NULL;
298
299 *fd = open(name, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
300 if (*fd == -1)
301 return (FILE *)NULL;
302
303 FILE *fp = fdopen(*fd, "w");
304 if (!fp) {
305 close(*fd);
306 return (FILE *)NULL;
307 }
308
309 return fp;
310 }
311
312 /* Close file stream FP and file descriptor FD */
313 void
close_fstream(FILE * fp,int fd)314 close_fstream(FILE *fp, int fd)
315 {
316 fclose(fp);
317 close(fd);
318 }
319
320 /* Transform S_IFXXX (MODE) into the corresponding DT_XXX constant */
321 inline mode_t
get_dt(const mode_t mode)322 get_dt(const mode_t mode)
323 {
324 switch (mode & S_IFMT) {
325 case S_IFBLK: return DT_BLK;
326 case S_IFCHR: return DT_CHR;
327 case S_IFDIR: return DT_DIR;
328 case S_IFIFO: return DT_FIFO;
329 case S_IFLNK: return DT_LNK;
330 case S_IFREG: return DT_REG;
331 case S_IFSOCK: return DT_SOCK;
332 default: return DT_UNKNOWN;
333 }
334 }
335
336 /*
337 static int
338 hex2int(char *str)
339 {
340 int i, n[2] = { 0 };
341 for (i = 1; i >= 0; i--) {
342 if (str[i] >= '0' && str[i] <= '9') {
343 n[i] = str[i] - '0';
344 } else {
345 switch (str[i]) {
346 case 'A':
347 case 'a':
348 n[i] = 10;
349 break;
350 case 'B':
351 case 'b':
352 n[i] = 11;
353 break;
354 case 'C':
355 case 'c':
356 n[i] = 12;
357 break;
358 case 'D':
359 case 'd':
360 n[i] = 13;
361 break;
362 case 'E':
363 case 'e':
364 n[i] = 14;
365 break;
366 case 'F':
367 case 'f':
368 n[i] = 15;
369 break;
370 default:
371 break;
372 }
373 }
374 }
375
376 return ((n[0] * 16) + n[1]);
377 } */
378
379 /* Given this value: \xA0\xA1\xA2, return an array of integers with
380 * the integer values for A0, A1, and A2 respectivelly */
381 /*int *
382 get_hex_num(const char *str)
383 {
384 size_t i = 0;
385 int *hex_n = (int *)xnmalloc(3, sizeof(int));
386
387 while (*str) {
388 if (*str != '\\') {
389 str++;
390 continue;
391 }
392
393 if (*(str + 1) != 'x')
394 break;
395
396 str += 2;
397 char *tmp = xnmalloc(3, sizeof(char));
398 xstrsncpy(tmp, str, 2);
399
400 if (i >= 3)
401 hex_n = xrealloc(hex_n, (i + 1) * sizeof(int *));
402
403 hex_n[i++] = hex2int(tmp);
404
405 free(tmp);
406 tmp = (char *)NULL;
407 str++;
408 }
409
410 hex_n = xrealloc(hex_n, (i + 1) * sizeof(int));
411 hex_n[i] = -1; // -1 marks the end of the int array
412
413 return hex_n;
414 } */
415
416 /* Count files in DIR_PATH, including self and parent. If POP is set to 1,
417 * The function will just check if the directory is populated (it has at
418 * least 3 files, including self and parent)*/
419 int
count_dir(const char * dir,int pop)420 count_dir(const char *dir, int pop)
421 {
422 if (!dir)
423 return (-1);
424
425 DIR *p;
426 if ((p = opendir(dir)) == NULL) {
427 if (errno == ENOMEM)
428 exit(EXIT_FAILURE);
429 else
430 return (-1);
431 }
432
433 int c = 0;
434
435 while (readdir(p)) {
436 c++;
437 if (pop && c > 2)
438 break;
439 }
440
441 closedir(p);
442 return c;
443 }
444
445 /* Get the path of a given command from the PATH environment variable.
446 * It basically does the same as the 'which' Unix command */
447 char *
get_cmd_path(const char * cmd)448 get_cmd_path(const char *cmd)
449 {
450 char *cmd_path = (char *)xnmalloc(PATH_MAX + 1, sizeof(char));
451
452 size_t i;
453 for (i = 0; i < path_n; i++) { /* Check each path in PATH */
454 /* Append cmd to each path and check if it exists and is
455 * executable */
456 snprintf(cmd_path, PATH_MAX, "%s/%s", paths[i], cmd);
457 if (access(cmd_path, X_OK) == 0)
458 return cmd_path;
459 }
460
461 free(cmd_path);
462 return (char *)NULL;
463 }
464
465 /* Convert FILE_SIZE to human readeable form */
466 char *
get_size_unit(off_t size)467 get_size_unit(off_t size)
468 {
469 #define MAX_UNIT_SIZE 9
470 /* Max size type length == 9 == "1023.99K\0" */
471 char *str = xnmalloc(MAX_UNIT_SIZE, sizeof(char));
472
473 size_t n = 0;
474 float s = (float)size;
475
476 while (s > 1024) {
477 s = s / 1024;
478 ++n;
479 }
480
481 int x = (int)s;
482 /* If s - x == 0, then S has no reminder (zero)
483 * We don't want to print the reminder when it is zero */
484
485 const char *const u = "BKMGTPEZY";
486 snprintf(str, MAX_UNIT_SIZE, "%.*f%c", (s == 0 || s - (float)x == 0)
487 ? 0 : 2, (double)s, u[n]);
488
489 return str;
490 }
491
492 off_t
dir_size(char * dir)493 dir_size(char *dir)
494 {
495 if (!dir || !*dir)
496 return (-1);
497
498 char file[PATH_MAX];
499 snprintf(file, PATH_MAX, "%s/duXXXXXX", P_tmpdir);
500
501 int fd = mkstemp(file);
502 if (fd == -1)
503 return (-1);
504
505 int stdout_bk = dup(STDOUT_FILENO); /* Save original stdout */
506 dup2(fd, STDOUT_FILENO); /* Redirect stdout to the desired file */
507 close(fd);
508
509 char *cmd[] = {"du", "-ks", dir, NULL};
510 launch_execve(cmd, FOREGROUND, E_NOSTDERR);
511
512 dup2(stdout_bk, STDOUT_FILENO); /* Restore original stdout */
513 close(stdout_bk);
514
515 FILE *fp = open_fstream_r(file, &fd);
516 if (!fp) {
517 unlink(file);
518 return (-1);
519 }
520
521 off_t retval = -1;
522 /* I only need here the first field of the line, which is a
523 * file size and could only take a few bytes, so that 32
524 * bytes is more than enough */
525 char line[32];
526 if (fgets(line, (int)sizeof(line), fp) == NULL) {
527 close_fstream(fp, fd);
528 unlink(file);
529 return (-1);
530 }
531
532 char *p = strchr(line, '\t');
533 if (p && p != line) {
534 *p = '\0';
535 retval = (off_t)atoll(line);
536 }
537
538 close_fstream(fp, fd);
539 unlink(file);
540 return retval;
541 }
542
543 /* Return the file type of the file pointed to by LINK, or -1 in case of
544 * error. Possible return values:
545 S_IFDIR: 40000 (octal) / 16384 (decimal, integer)
546 S_IFREG: 100000 / 32768
547 S_IFLNK: 120000 / 40960
548 S_IFSOCK: 140000 / 49152
549 S_IFBLK: 60000 / 24576
550 S_IFCHR: 20000 / 8192
551 S_IFIFO: 10000 / 4096
552 * See the inode manpage */
553 int
get_link_ref(const char * link)554 get_link_ref(const char *link)
555 {
556 if (!link)
557 return (-1);
558
559 char *linkname = realpath(link, (char *)NULL);
560 if (!linkname)
561 return (-1);
562
563 struct stat attr;
564 int ret = stat(linkname, &attr);
565 free(linkname);
566 if (ret == -1)
567 return (-1);
568 return (int)(attr.st_mode & S_IFMT);
569 }
570
571 /* Transform an integer (N) into a string of chars
572 * This exists because some Operating systems do not support itoa */
573 char *
xitoa(int n)574 xitoa(int n)
575 {
576 if (!n)
577 return "0";
578
579 static char buf[32] = {0};
580 int i = 30;
581
582 while (n && i) {
583 int rem = n / 10;
584 buf[i] = (char)('0' + (n - (rem * 10)));
585 n = rem;
586 --i;
587 }
588
589 return &buf[++i];
590 }
591
592 int
xatoi(const char * s)593 xatoi(const char *s)
594 {
595 long ret = strtol(s, NULL, 10);
596 if (ret == LONG_MAX || ret == LONG_MIN) {
597 fprintf(stderr, "%s: strtol: %s: %s\n", PROGRAM_NAME, s, strerror(errno));
598 exit(EXIT_FAILURE);
599 }
600 return (int)ret;
601 }
602
603 /* Some memory wrapper functions */
604 void *
xrealloc(void * ptr,size_t size)605 xrealloc(void *ptr, size_t size)
606 {
607 void *p = realloc(ptr, size);
608
609 if (!p) {
610 _err(0, NOPRINT_PROMPT, _("%s: %s failed to allocate %zu bytes\n"),
611 PROGRAM_NAME, __func__, size);
612 exit(EXIT_FAILURE);
613 }
614
615 return p;
616 }
617
618 void *
xcalloc(size_t nmemb,size_t size)619 xcalloc(size_t nmemb, size_t size)
620 {
621 void *p = calloc(nmemb, size);
622
623 if (!p) {
624 _err(0, NOPRINT_PROMPT, _("%s: %s failed to allocate %zu bytes\n"),
625 PROGRAM_NAME, __func__, nmemb * size);
626 exit(EXIT_FAILURE);
627 }
628
629 return p;
630 }
631
632 void *
xnmalloc(size_t nmemb,size_t size)633 xnmalloc(size_t nmemb, size_t size)
634 {
635 void *p = malloc(nmemb * size);
636
637 if (!p) {
638 _err(0, NOPRINT_PROMPT, _("%s: %s failed to allocate %zu bytes\n"),
639 PROGRAM_NAME, __func__, nmemb * size);
640 exit(EXIT_FAILURE);
641 }
642
643 return p;
644 }
645
646 /* Unlike getchar this does not wait for newline('\n')
647 https://stackoverflow.com/questions/12710582/how-can-i-capture-a-key-stroke-immediately-in-linux
648 */
649 char
xgetchar(void)650 xgetchar(void)
651 {
652 struct termios oldt, newt;
653 char c;
654
655 tcgetattr(STDIN_FILENO, &oldt);
656 newt = oldt;
657 newt.c_lflag &= (tcflag_t)~(ICANON | ECHO);
658 tcsetattr(STDIN_FILENO, TCSANOW, &newt);
659 c = (char)getchar();
660 tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
661
662 return c;
663 }
664
665 /* The following four functions (from_hex, to_hex, url_encode, and
666 * url_decode) were taken from "http://www.geekhideout.com/urlcode.shtml"
667 * and modified to comform to RFC 2395, as recommended by the
668 * freedesktop trash specification */
669
670 /* Converts a hex char to its integer value */
671 char
from_hex(char c)672 from_hex(char c)
673 {
674 return (char)(isdigit(c) ? c - '0' : tolower(c) - 'a' + 10);
675 }
676
677 /* Converts an integer value to its hex form */
678 static char
to_hex(char c)679 to_hex(char c)
680 {
681 static char hex[] = "0123456789ABCDEF";
682 return hex[c & 15];
683 }
684
685 /* Returns a url-encoded version of str */
686 char *
url_encode(char * str)687 url_encode(char *str)
688 {
689 if (!str || !*str)
690 return (char *)NULL;
691
692 char *buf = (char *)xnmalloc((strlen(str) * 3) + 1, sizeof(char));
693 /* The max lenght of our buffer is 3 times the length of STR plus
694 * 1 extra byte for the null byte terminator: each char in STR will
695 * be, if encoded, %XX (3 chars) */
696
697 /* Copies of STR and BUF pointers to be able
698 * to increase and/or decrease them without loosing the original
699 * memory location */
700 char *pstr, *pbuf;
701 pstr = str;
702 pbuf = buf;
703
704 for (; *pstr; pstr++) {
705 if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.'
706 || *pstr == '~' || *pstr == '/') {
707 /* Do not encode any of the above chars */
708 *pbuf++ = *pstr;
709 } else {
710 /* Encode char to URL format. Example: space char to %20 */
711 *pbuf++ = '%';
712 *pbuf++ = to_hex(*pstr >> 4); /* Right shift operation */
713 *pbuf++ = to_hex(*pstr & 15); /* Bitwise AND operation */
714 }
715 }
716
717 *pbuf = '\0';
718 return buf;
719 }
720
721 /* Returns a url-decoded version of str */
722 char *
url_decode(char * str)723 url_decode(char *str)
724 {
725 if (!str || !*str)
726 return (char *)NULL;
727
728 char *buf = (char *)xnmalloc(strlen(str) + 1, sizeof(char));
729 /* The decoded string will be at most as long as the encoded
730 * string */
731
732 char *pstr, *pbuf;
733 pstr = str;
734 pbuf = buf;
735 for (; *pstr; pstr++) {
736 if (*pstr == '%') {
737 if (pstr[1] && pstr[2]) {
738 /* Decode URL code. Example: %20 to space char */
739 /* Left shift and bitwise OR operations */
740 *pbuf++ = (char)(from_hex(pstr[1]) << 4 | from_hex(pstr[2]));
741 pstr += 2;
742 }
743 } else {
744 *pbuf++ = *pstr;
745 }
746 }
747
748 *pbuf = '\0';
749 return buf;
750 }
751
752 /* Convert octal string into integer.
753 * Taken from: https://www.geeksforgeeks.org/program-octal-decimal-conversion/
754 * Used by decode_prompt() to make things like this work: \033[1;34m */
755 int
read_octal(char * str)756 read_octal(char *str)
757 {
758 if (!str || !*str)
759 return (-1);
760
761 int n = atoi(str);
762 int num = n;
763 int dec_value = 0;
764
765 /* Initializing base value to 1, i.e 8^0 */
766 int base = 1;
767
768 int temp = num;
769 while (temp) {
770 /* Extracting last digit */
771 int last_digit = temp % 10;
772 temp = temp / 10;
773
774 /* Multiplying last digit with appropriate
775 * base value and adding it to dec_value */
776 dec_value += last_digit * base;
777 base = base * 8;
778 }
779
780 return dec_value;
781 }
782