Merge branch '4600_filter_segfault'
[midnight-commander.git] / lib / util.c
blobfdd758cf8c8b96b971a8377d27001bcd5975aeb8
1 /*
2 Various utilities
4 Copyright (C) 1994-2024
5 Free Software Foundation, Inc.
7 Written by:
8 Miguel de Icaza, 1994, 1995, 1996
9 Janne Kukonlehto, 1994, 1995, 1996
10 Dugan Porter, 1994, 1995, 1996
11 Jakub Jelinek, 1994, 1995, 1996
12 Mauricio Plaza, 1994, 1995, 1996
13 Slava Zanko <slavazanko@gmail.com>, 2013
15 This file is part of the Midnight Commander.
17 The Midnight Commander is free software: you can redistribute it
18 and/or modify it under the terms of the GNU General Public License as
19 published by the Free Software Foundation, either version 3 of the License,
20 or (at your option) any later version.
22 The Midnight Commander is distributed in the hope that it will be useful,
23 but WITHOUT ANY WARRANTY; without even the implied warranty of
24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 GNU General Public License for more details.
27 You should have received a copy of the GNU General Public License
28 along with this program. If not, see <http://www.gnu.org/licenses/>.
31 /** \file lib/util.c
32 * \brief Source: various utilities
35 #include <config.h>
37 #include <ctype.h>
38 #include <stddef.h> /* ptrdiff_t */
39 #include <limits.h>
40 #include <stdarg.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <sys/types.h>
45 #include <sys/stat.h>
46 #include <unistd.h>
48 #include "lib/global.h"
49 #include "lib/mcconfig.h"
50 #include "lib/fileloc.h"
51 #include "lib/vfs/vfs.h"
52 #include "lib/strutil.h"
53 #include "lib/util.h"
55 /*** global variables ****************************************************************************/
57 /*** file scope macro definitions ****************************************************************/
59 #define ismode(n,m) ((n & m) == m)
61 /* Number of attempts to create a temporary file */
62 #ifndef TMP_MAX
63 #define TMP_MAX 16384
64 #endif /* !TMP_MAX */
66 #define TMP_SUFFIX ".tmp"
68 #define ASCII_A (0x40 + 1)
69 #define ASCII_Z (0x40 + 26)
70 #define ASCII_a (0x60 + 1)
71 #define ASCII_z (0x60 + 26)
73 /*** file scope type declarations ****************************************************************/
75 /*** forward declarations (file scope functions) *************************************************/
77 /*** file scope variables ************************************************************************/
79 /* --------------------------------------------------------------------------------------------- */
80 /*** file scope functions ************************************************************************/
81 /* --------------------------------------------------------------------------------------------- */
83 #ifndef HAVE_CHARSET
84 static inline int
85 is_7bit_printable (unsigned char c)
87 return (c > 31 && c < 127);
89 #endif
91 /* --------------------------------------------------------------------------------------------- */
93 static inline int
94 is_iso_printable (unsigned char c)
96 return ((c > 31 && c < 127) || c >= 160);
99 /* --------------------------------------------------------------------------------------------- */
101 static inline int
102 is_8bit_printable (unsigned char c)
104 /* "Full 8 bits output" doesn't work on xterm */
105 if (mc_global.tty.xterm_flag)
106 return is_iso_printable (c);
108 return (c > 31 && c != 127 && c != 155);
111 /* --------------------------------------------------------------------------------------------- */
113 static char *
114 resolve_symlinks (const vfs_path_t *vpath)
116 char *p, *p2;
117 char *buf, *buf2, *q, *r, c;
118 struct stat mybuf;
120 if (vpath->relative)
121 return NULL;
123 p = p2 = g_strdup (vfs_path_as_str (vpath));
124 r = buf = g_malloc (MC_MAXPATHLEN);
125 buf2 = g_malloc (MC_MAXPATHLEN);
126 *r++ = PATH_SEP;
127 *r = '\0';
131 q = strchr (p + 1, PATH_SEP);
132 if (q == NULL)
134 q = strchr (p + 1, '\0');
135 if (q == p + 1)
136 break;
138 c = *q;
139 *q = '\0';
140 if (mc_lstat (vpath, &mybuf) < 0)
142 MC_PTR_FREE (buf);
143 goto ret;
145 if (!S_ISLNK (mybuf.st_mode))
146 strcpy (r, p + 1);
147 else
149 int len;
151 len = mc_readlink (vpath, buf2, MC_MAXPATHLEN - 1);
152 if (len < 0)
154 MC_PTR_FREE (buf);
155 goto ret;
157 buf2[len] = '\0';
158 if (IS_PATH_SEP (*buf2))
159 strcpy (buf, buf2);
160 else
161 strcpy (r, buf2);
163 canonicalize_pathname (buf);
164 r = strchr (buf, '\0');
165 if (*r == '\0' || !IS_PATH_SEP (r[-1]))
166 /* FIXME: this condition is always true because r points to the EOL */
168 *r++ = PATH_SEP;
169 *r = '\0';
171 *q = c;
172 p = q;
174 while (c != '\0');
176 if (*buf == '\0')
177 strcpy (buf, PATH_SEP_STR);
178 else if (IS_PATH_SEP (r[-1]) && r != buf + 1)
179 r[-1] = '\0';
181 ret:
182 g_free (buf2);
183 g_free (p2);
184 return buf;
187 /* --------------------------------------------------------------------------------------------- */
189 static gboolean
190 mc_util_write_backup_content (const char *from_file_name, const char *to_file_name)
192 FILE *backup_fd;
193 char *contents;
194 gsize length;
195 gboolean ret1 = TRUE;
197 if (!g_file_get_contents (from_file_name, &contents, &length, NULL))
198 return FALSE;
200 backup_fd = fopen (to_file_name, "w");
201 if (backup_fd == NULL)
203 g_free (contents);
204 return FALSE;
207 if (fwrite ((const void *) contents, 1, length, backup_fd) != length)
208 ret1 = FALSE;
211 int ret2;
213 /* cppcheck-suppress redundantAssignment */
214 ret2 = fflush (backup_fd);
215 /* cppcheck-suppress redundantAssignment */
216 ret2 = fclose (backup_fd);
217 (void) ret2;
220 g_free (contents);
221 return ret1;
224 /* --------------------------------------------------------------------------------------------- */
225 /*** public functions ****************************************************************************/
226 /* --------------------------------------------------------------------------------------------- */
229 is_printable (int c)
231 c &= 0xff;
233 #ifdef HAVE_CHARSET
234 /* "Display bits" is ignored, since the user controls the output
235 by setting the output codepage */
236 return is_8bit_printable (c);
237 #else
238 if (!mc_global.eight_bit_clean)
239 return is_7bit_printable (c);
241 if (mc_global.full_eight_bits)
242 return is_8bit_printable (c);
244 return is_iso_printable (c);
245 #endif /* !HAVE_CHARSET */
248 /* --------------------------------------------------------------------------------------------- */
250 * Quote the filename for the purpose of inserting it into the command
251 * line. If quote_percent is TRUE, replace "%" with "%%" - the percent is
252 * processed by the mc command line.
254 char *
255 name_quote (const char *s, gboolean quote_percent)
257 GString *ret;
259 if (s == NULL || *s == '\0')
260 return NULL;
262 ret = g_string_sized_new (64);
264 if (*s == '-')
265 g_string_append (ret, "." PATH_SEP_STR);
267 for (; *s != '\0'; s++)
269 switch (*s)
271 case '%':
272 if (quote_percent)
273 g_string_append_c (ret, '%');
274 break;
275 case '\'':
276 case '\\':
277 case '\r':
278 case '\n':
279 case '\t':
280 case '"':
281 case ';':
282 case ' ':
283 case '?':
284 case '|':
285 case '[':
286 case ']':
287 case '{':
288 case '}':
289 case '<':
290 case '>':
291 case '`':
292 case '!':
293 case '$':
294 case '&':
295 case '*':
296 case '(':
297 case ')':
298 g_string_append_c (ret, '\\');
299 break;
300 case '~':
301 case '#':
302 if (ret->len == 0)
303 g_string_append_c (ret, '\\');
304 break;
305 default:
306 break;
308 g_string_append_c (ret, *s);
311 return g_string_free (ret, ret->len == 0);
314 /* --------------------------------------------------------------------------------------------- */
316 char *
317 fake_name_quote (const char *s, gboolean quote_percent)
319 (void) quote_percent;
321 return (s == NULL || *s == '\0' ? NULL : g_strdup (s));
324 /* --------------------------------------------------------------------------------------------- */
326 * path_trunc() is the same as str_trunc() but
327 * it deletes possible password from path for security
328 * reasons.
331 const char *
332 path_trunc (const char *path, size_t trunc_len)
334 vfs_path_t *vpath;
335 const char *ret;
337 vpath = vfs_path_from_str_flags (path, VPF_STRIP_PASSWORD);
338 ret = str_trunc (vfs_path_as_str (vpath), trunc_len);
339 vfs_path_free (vpath, TRUE);
341 return ret;
344 /* --------------------------------------------------------------------------------------------- */
346 const char *
347 size_trunc (uintmax_t size, gboolean use_si)
349 static char x[BUF_TINY];
350 uintmax_t divisor = 1;
351 const char *xtra = _("B");
353 if (size > 999999999UL)
355 divisor = use_si ? 1000 : 1024;
356 xtra = use_si ? _("kB") : _("KiB");
358 if (size / divisor > 999999999UL)
360 divisor = use_si ? (1000 * 1000) : (1024 * 1024);
361 xtra = use_si ? _("MB") : _("MiB");
363 if (size / divisor > 999999999UL)
365 divisor = use_si ? (1000 * 1000 * 1000) : (1024 * 1024 * 1024);
366 xtra = use_si ? _("GB") : _("GiB");
370 g_snprintf (x, sizeof (x), "%.0f %s", 1.0 * size / divisor, xtra);
371 return x;
374 /* --------------------------------------------------------------------------------------------- */
376 const char *
377 size_trunc_sep (uintmax_t size, gboolean use_si)
379 static char x[60];
380 int count;
381 const char *p, *y;
382 char *d;
384 p = y = size_trunc (size, use_si);
385 p += strlen (p) - 1;
386 d = x + sizeof (x) - 1;
387 *d-- = '\0';
388 /* @size format is "size unit", i.e. "[digits][space][letters]".
389 Copy all characters after digits. */
390 while (p >= y && !g_ascii_isdigit (*p))
391 *d-- = *p--;
392 for (count = 0; p >= y; count++)
394 if (count == 3)
396 *d-- = ',';
397 count = 0;
399 *d-- = *p--;
401 d++;
402 if (*d == ',')
403 d++;
404 return d;
407 /* --------------------------------------------------------------------------------------------- */
409 * Print file SIZE to BUFFER, but don't exceed LEN characters,
410 * not including trailing 0. BUFFER should be at least LEN+1 long.
411 * This function is called for every file on panels, so avoid
412 * floating point by any means.
414 * Units: size units (filesystem sizes are 1K blocks)
415 * 0=bytes, 1=Kbytes, 2=Mbytes, etc.
418 void
419 size_trunc_len (char *buffer, unsigned int len, uintmax_t size, int units, gboolean use_si)
421 /* Avoid taking power for every file. */
422 /* *INDENT-OFF* */
423 static const uintmax_t power10[] = {
424 /* we hope that size of uintmax_t is 4 bytes at least */
425 1ULL,
426 10ULL,
427 100ULL,
428 1000ULL,
429 10000ULL,
430 100000ULL,
431 1000000ULL,
432 10000000ULL,
433 100000000ULL,
434 1000000000ULL
435 /* maximum value of uintmax_t (in case of 4 bytes) is
436 4294967295
438 #if SIZEOF_UINTMAX_T == 8
440 10000000000ULL,
441 100000000000ULL,
442 1000000000000ULL,
443 10000000000000ULL,
444 100000000000000ULL,
445 1000000000000000ULL,
446 10000000000000000ULL,
447 100000000000000000ULL,
448 1000000000000000000ULL,
449 10000000000000000000ULL
450 /* maximum value of uintmax_t (in case of 8 bytes) is
451 18447644073710439615
453 #endif
456 static const char *const suffix[] =
457 { "", "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q", NULL };
458 static const char *const suffix_lc[] =
459 { "", "k", "m", "g", "t", "p", "e", "z", "y", "r", "q", NULL };
460 /* *INDENT-ON* */
462 const char *const *sfx = use_si ? suffix_lc : suffix;
463 int j = 0;
465 if (len == 0)
466 len = 9;
467 #if SIZEOF_UINTMAX_T == 8
468 /* 20 decimal digits are required to represent 8 bytes */
469 else if (len > 19)
470 len = 19;
471 #else
472 /* 10 decimal digits are required to represent 4 bytes */
473 else if (len > 9)
474 len = 9;
475 #endif
478 * recalculate from 1024 base to 1000 base if units>0
479 * We can't just multiply by 1024 - that might cause overflow
480 * if uintmax_t type is too small
482 if (use_si)
483 for (j = 0; j < units; j++)
485 uintmax_t size_remain;
487 size_remain = ((size % 125) * 1024) / 1000; /* size mod 125, recalculated */
488 size /= 125; /* 128/125 = 1024/1000 */
489 size *= 128; /* This will convert size from multiple of 1024 to multiple of 1000 */
490 size += size_remain; /* Re-add remainder lost by division/multiplication */
493 for (j = units; sfx[j] != NULL; j++)
495 if (size == 0)
497 if (j == units)
499 /* Empty files will print "0" even with minimal width. */
500 g_snprintf (buffer, len + 1, "%s", "0");
502 else
504 /* Use "~K" or just "K" if len is 1. Use "B" for bytes. */
505 g_snprintf (buffer, len + 1, (len > 1) ? "~%s" : "%s", (j > 1) ? sfx[j - 1] : "B");
507 break;
510 if (size < power10[len - (j > 0 ? 1 : 0)])
512 g_snprintf (buffer, len + 1, "%" PRIuMAX "%s", size, sfx[j]);
513 break;
516 /* Powers of 1000 or 1024, with rounding. */
517 if (use_si)
518 size = (size + 500) / 1000;
519 else
520 size = (size + 512) >> 10;
524 /* --------------------------------------------------------------------------------------------- */
526 const char *
527 string_perm (mode_t mode_bits)
529 static char mode[11];
531 strcpy (mode, "----------");
532 if (S_ISDIR (mode_bits))
533 mode[0] = 'd';
534 if (S_ISCHR (mode_bits))
535 mode[0] = 'c';
536 if (S_ISBLK (mode_bits))
537 mode[0] = 'b';
538 if (S_ISLNK (mode_bits))
539 mode[0] = 'l';
540 if (S_ISFIFO (mode_bits))
541 mode[0] = 'p';
542 if (S_ISNAM (mode_bits))
543 mode[0] = 'n';
544 if (S_ISSOCK (mode_bits))
545 mode[0] = 's';
546 if (S_ISDOOR (mode_bits))
547 mode[0] = 'D';
548 if (ismode (mode_bits, S_IXOTH))
549 mode[9] = 'x';
550 if (ismode (mode_bits, S_IWOTH))
551 mode[8] = 'w';
552 if (ismode (mode_bits, S_IROTH))
553 mode[7] = 'r';
554 if (ismode (mode_bits, S_IXGRP))
555 mode[6] = 'x';
556 if (ismode (mode_bits, S_IWGRP))
557 mode[5] = 'w';
558 if (ismode (mode_bits, S_IRGRP))
559 mode[4] = 'r';
560 if (ismode (mode_bits, S_IXUSR))
561 mode[3] = 'x';
562 if (ismode (mode_bits, S_IWUSR))
563 mode[2] = 'w';
564 if (ismode (mode_bits, S_IRUSR))
565 mode[1] = 'r';
566 #ifdef S_ISUID
567 if (ismode (mode_bits, S_ISUID))
568 mode[3] = (mode[3] == 'x') ? 's' : 'S';
569 #endif /* S_ISUID */
570 #ifdef S_ISGID
571 if (ismode (mode_bits, S_ISGID))
572 mode[6] = (mode[6] == 'x') ? 's' : 'S';
573 #endif /* S_ISGID */
574 #ifdef S_ISVTX
575 if (ismode (mode_bits, S_ISVTX))
576 mode[9] = (mode[9] == 'x') ? 't' : 'T';
577 #endif /* S_ISVTX */
578 return mode;
581 /* --------------------------------------------------------------------------------------------- */
583 const char *
584 extension (const char *filename)
586 const char *d;
588 d = strrchr (filename, '.');
590 return d != NULL ? d + 1 : "";
593 /* --------------------------------------------------------------------------------------------- */
595 char *
596 load_mc_home_file (const char *from, const char *filename, char **allocated_filename,
597 size_t *length)
599 char *hintfile_base, *hintfile;
600 char *lang;
601 char *data;
603 hintfile_base = g_build_filename (from, filename, (char *) NULL);
604 lang = guess_message_value ();
606 hintfile = g_strconcat (hintfile_base, ".", lang, (char *) NULL);
607 if (!g_file_get_contents (hintfile, &data, length, NULL))
609 /* Fall back to the two-letter language code */
610 if (lang[0] != '\0' && lang[1] != '\0')
611 lang[2] = '\0';
612 g_free (hintfile);
613 hintfile = g_strconcat (hintfile_base, ".", lang, (char *) NULL);
614 if (!g_file_get_contents (hintfile, &data, length, NULL))
616 g_free (hintfile);
617 hintfile = hintfile_base;
618 g_file_get_contents (hintfile_base, &data, length, NULL);
622 g_free (lang);
624 if (hintfile != hintfile_base)
625 g_free (hintfile_base);
627 if (allocated_filename != NULL)
628 *allocated_filename = hintfile;
629 else
630 g_free (hintfile);
632 return data;
635 /* --------------------------------------------------------------------------------------------- */
637 const char *
638 extract_line (const char *s, const char *top, size_t *len)
640 static char tmp_line[BUF_MEDIUM];
641 char *t = tmp_line;
643 while (*s != '\0' && *s != '\n' && (size_t) (t - tmp_line) < sizeof (tmp_line) - 1 && s < top)
644 *t++ = *s++;
645 *t = '\0';
647 if (len != NULL)
648 *len = (size_t) (t - tmp_line);
650 return tmp_line;
653 /* --------------------------------------------------------------------------------------------- */
655 * The basename routine
658 const char *
659 x_basename (const char *s)
661 const char *url_delim, *path_sep;
663 url_delim = g_strrstr (s, VFS_PATH_URL_DELIMITER);
664 path_sep = strrchr (s, PATH_SEP);
666 if (path_sep == NULL)
667 return s;
669 if (url_delim == NULL
670 || url_delim < path_sep - strlen (VFS_PATH_URL_DELIMITER)
671 || url_delim - s + strlen (VFS_PATH_URL_DELIMITER) < strlen (s))
673 /* avoid trailing PATH_SEP, if present */
674 if (!IS_PATH_SEP (s[strlen (s) - 1]))
675 return path_sep + 1;
677 while (--path_sep > s && !IS_PATH_SEP (*path_sep))
679 return (path_sep != s) ? path_sep + 1 : s;
682 while (--url_delim > s && !IS_PATH_SEP (*url_delim))
684 while (--url_delim > s && !IS_PATH_SEP (*url_delim))
687 return url_delim == s ? s : url_delim + 1;
690 /* --------------------------------------------------------------------------------------------- */
692 const char *
693 unix_error_string (int error_num)
695 static char buffer[BUF_LARGE];
696 gchar *strerror_currentlocale;
698 strerror_currentlocale = g_locale_from_utf8 (g_strerror (error_num), -1, NULL, NULL, NULL);
699 g_snprintf (buffer, sizeof (buffer), "%s (%d)", strerror_currentlocale, error_num);
700 g_free (strerror_currentlocale);
702 return buffer;
705 /* --------------------------------------------------------------------------------------------- */
707 const char *
708 skip_separators (const char *s)
710 const char *su = s;
712 for (; *su != '\0'; str_cnext_char (&su))
713 if (!whitespace (*su) && *su != ',')
714 break;
716 return su;
719 /* --------------------------------------------------------------------------------------------- */
721 const char *
722 skip_numbers (const char *s)
724 const char *su = s;
726 for (; *su != '\0'; str_cnext_char (&su))
727 if (!str_isdigit (su))
728 break;
730 return su;
733 /* --------------------------------------------------------------------------------------------- */
735 * Remove all control sequences from the argument string. We define
736 * "control sequence", in a sort of pidgin BNF, as follows:
738 * control-seq = Esc non-'['
739 * | Esc '[' (0 or more digits or ';' or ':' or '?') (any other char)
741 * The 256-color and true-color escape sequences should allow either ';' or ':' inside as separator,
742 * actually, ':' is the more correct according to ECMA-48.
743 * Some terminal emulators (e.g. xterm, gnome-terminal) support this.
745 * Non-printable characters are also removed.
748 char *
749 strip_ctrl_codes (char *s)
751 char *w; /* Current position where the stripped data is written */
752 char *r; /* Current position where the original data is read */
754 if (s == NULL)
755 return NULL;
757 for (w = s, r = s; *r != '\0';)
759 if (*r == ESC_CHAR)
761 /* Skip the control sequence's arguments */ ;
762 /* '(' need to avoid strange 'B' letter in *Suse (if mc runs under root user) */
763 if (*(++r) == '[' || *r == '(')
765 /* strchr() matches trailing binary 0 */
766 while (*(++r) != '\0' && strchr ("0123456789;:?", *r) != NULL)
769 else if (*r == ']')
772 * Skip xterm's OSC (Operating System Command)
773 * http://www.xfree86.org/current/ctlseqs.html
774 * OSC P s ; P t ST
775 * OSC P s ; P t BEL
777 char *new_r;
779 for (new_r = r; *new_r != '\0'; new_r++)
781 switch (*new_r)
783 /* BEL */
784 case '\a':
785 r = new_r;
786 goto osc_out;
787 case ESC_CHAR:
788 /* ST */
789 if (new_r[1] == '\\')
791 r = new_r + 1;
792 goto osc_out;
794 break;
795 default:
796 break;
799 osc_out:
804 * Now we are at the last character of the sequence.
805 * Skip it unless it's binary 0.
807 if (*r != '\0')
808 r++;
810 else
812 char *n;
814 n = str_get_next_char (r);
815 if (str_isprint (r))
817 memmove (w, r, n - r);
818 w += n - r;
820 r = n;
824 *w = '\0';
825 return s;
828 /* --------------------------------------------------------------------------------------------- */
830 enum compression_type
831 get_compression_type (int fd, const char *name)
833 unsigned char magic[16];
834 size_t str_len;
836 /* Read the magic signature */
837 if (mc_read (fd, (char *) magic, 4) != 4)
838 return COMPRESSION_NONE;
840 /* GZIP_MAGIC and OLD_GZIP_MAGIC */
841 if (magic[0] == 0x1F && (magic[1] == 0x8B || magic[1] == 0x9E))
842 return COMPRESSION_GZIP;
844 /* PKZIP_MAGIC */
845 if (magic[0] == 'P' && magic[1] == 'K' && magic[2] == 0x03 && magic[3] == 0x04)
847 /* Read compression type */
848 mc_lseek (fd, 8, SEEK_SET);
849 if (mc_read (fd, (char *) magic, 2) != 2)
850 return COMPRESSION_NONE;
852 if ((magic[0] != 8 && magic[0] != 0) || magic[1] != 0)
853 return COMPRESSION_NONE;
855 return COMPRESSION_ZIP;
858 /* PACK_MAGIC and LZH_MAGIC and compress magic */
859 if (magic[0] == 0x1F && (magic[1] == 0x1E || magic[1] == 0xA0 || magic[1] == 0x9D))
860 /* Compatible with gzip */
861 return COMPRESSION_GZIP;
863 /* BZIP and BZIP2 files */
864 if ((magic[0] == 'B') && (magic[1] == 'Z') && (magic[3] >= '1') && (magic[3] <= '9'))
865 switch (magic[2])
867 case '0':
868 return COMPRESSION_BZIP;
869 case 'h':
870 return COMPRESSION_BZIP2;
871 default:
872 break;
875 /* LZ4 format - v1.5.0 - 0x184D2204 (little endian) */
876 if (magic[0] == 0x04 && magic[1] == 0x22 && magic[2] == 0x4d && magic[3] == 0x18)
877 return COMPRESSION_LZ4;
879 if (mc_read (fd, (char *) magic + 4, 2) != 2)
880 return COMPRESSION_NONE;
882 /* LZIP files */
883 if (magic[0] == 'L'
884 && magic[1] == 'Z'
885 && magic[2] == 'I' && magic[3] == 'P' && (magic[4] == 0x00 || magic[4] == 0x01))
886 return COMPRESSION_LZIP;
888 /* Support for LZMA (only utils format with magic in header).
889 * This is the default format of LZMA utils 4.32.1 and later. */
890 if (magic[0] == 0xFF
891 && magic[1] == 'L'
892 && magic[2] == 'Z' && magic[3] == 'M' && magic[4] == 'A' && magic[5] == 0x00)
893 return COMPRESSION_LZMA;
895 /* LZO format - \x89\x4c\x5a\x4f\x00\x0d\x0a\x1a\x0a lzop compressed data */
896 if (magic[0] == 0x89 && magic[1] == 0x4c &&
897 magic[2] == 0x5a && magic[3] == 0x4f && magic[4] == 0x00 && magic[5] == 0x0d)
898 return COMPRESSION_LZO;
900 /* XZ compression magic */
901 if (magic[0] == 0xFD
902 && magic[1] == 0x37
903 && magic[2] == 0x7A && magic[3] == 0x58 && magic[4] == 0x5A && magic[5] == 0x00)
904 return COMPRESSION_XZ;
906 if (magic[0] == 0x28 && magic[1] == 0xB5 && magic[2] == 0x2F && magic[3] == 0xFD)
907 return COMPRESSION_ZSTD;
909 str_len = strlen (name);
910 /* HACK: we must believe to extension of LZMA file :) ... */
911 if ((str_len > 5 && strcmp (&name[str_len - 5], ".lzma") == 0) ||
912 (str_len > 4 && strcmp (&name[str_len - 4], ".tlz") == 0))
913 return COMPRESSION_LZMA;
915 return COMPRESSION_NONE;
918 /* --------------------------------------------------------------------------------------------- */
920 const char *
921 decompress_extension (int type)
923 switch (type)
925 case COMPRESSION_ZIP:
926 return "/uz" VFS_PATH_URL_DELIMITER;
927 case COMPRESSION_GZIP:
928 return "/ugz" VFS_PATH_URL_DELIMITER;
929 case COMPRESSION_BZIP:
930 return "/ubz" VFS_PATH_URL_DELIMITER;
931 case COMPRESSION_BZIP2:
932 return "/ubz2" VFS_PATH_URL_DELIMITER;
933 case COMPRESSION_LZIP:
934 return "/ulz" VFS_PATH_URL_DELIMITER;
935 case COMPRESSION_LZ4:
936 return "/ulz4" VFS_PATH_URL_DELIMITER;
937 case COMPRESSION_LZMA:
938 return "/ulzma" VFS_PATH_URL_DELIMITER;
939 case COMPRESSION_LZO:
940 return "/ulzo" VFS_PATH_URL_DELIMITER;
941 case COMPRESSION_XZ:
942 return "/uxz" VFS_PATH_URL_DELIMITER;
943 case COMPRESSION_ZSTD:
944 return "/uzst" VFS_PATH_URL_DELIMITER;
945 default:
946 break;
948 /* Should never reach this place */
949 fprintf (stderr, "Fatal: decompress_extension called with an unknown argument\n");
950 return 0;
953 /* --------------------------------------------------------------------------------------------- */
955 void
956 wipe_password (char *passwd)
958 if (passwd != NULL)
960 char *p;
962 for (p = passwd; *p != '\0'; p++)
963 *p = '\0';
964 g_free (passwd);
968 /* --------------------------------------------------------------------------------------------- */
970 * Convert "\E" -> esc character and ^x to control-x key and ^^ to ^ key
972 * @param p pointer to string
974 * @return newly allocated string
977 char *
978 convert_controls (const char *p)
980 char *valcopy;
981 char *q;
983 valcopy = g_strdup (p);
985 /* Parse the escape special character */
986 for (q = valcopy; *p != '\0';)
987 switch (*p)
989 case '\\':
990 p++;
992 if (*p == 'e' || *p == 'E')
994 p++;
995 *q++ = ESC_CHAR;
997 break;
999 case '^':
1000 p++;
1001 if (*p == '^')
1002 *q++ = *p++;
1003 else
1005 char c;
1007 c = *p | 0x20;
1008 if (c >= 'a' && c <= 'z')
1010 *q++ = c - 'a' + 1;
1011 p++;
1013 else if (*p != '\0')
1014 p++;
1016 break;
1018 default:
1019 *q++ = *p++;
1022 *q = '\0';
1023 return valcopy;
1026 /* --------------------------------------------------------------------------------------------- */
1028 * Finds out a relative path from first to second, i.e. goes as many ..
1029 * as needed up in first and then goes down using second
1032 char *
1033 diff_two_paths (const vfs_path_t *vpath1, const vfs_path_t *vpath2)
1035 int j, prevlen = -1, currlen;
1036 char *my_first = NULL, *my_second = NULL;
1037 char *buf = NULL;
1039 my_first = resolve_symlinks (vpath1);
1040 if (my_first == NULL)
1041 goto ret;
1043 my_second = resolve_symlinks (vpath2);
1044 if (my_second == NULL)
1045 goto ret;
1047 for (j = 0; j < 2; j++)
1049 char *p, *q;
1050 int i;
1052 p = my_first;
1053 q = my_second;
1055 while (TRUE)
1057 char *r, *s;
1058 ptrdiff_t len;
1060 r = strchr (p, PATH_SEP);
1061 if (r == NULL)
1062 break;
1063 s = strchr (q, PATH_SEP);
1064 if (s == NULL)
1065 break;
1067 len = r - p;
1068 if (len != (s - q) || strncmp (p, q, (size_t) len) != 0)
1069 break;
1071 p = r + 1;
1072 q = s + 1;
1074 p--;
1075 for (i = 0; (p = strchr (p + 1, PATH_SEP)) != NULL; i++)
1077 currlen = (i + 1) * 3 + strlen (q) + 1;
1078 if (j != 0)
1080 if (currlen < prevlen)
1081 g_free (buf);
1082 else
1083 goto ret;
1085 p = buf = g_malloc (currlen);
1086 prevlen = currlen;
1087 for (; i >= 0; i--, p += 3)
1088 strcpy (p, "../");
1089 strcpy (p, q);
1092 ret:
1093 g_free (my_first);
1094 g_free (my_second);
1095 return buf;
1098 /* --------------------------------------------------------------------------------------------- */
1100 * Append text to GList, remove all entries with the same text
1103 GList *
1104 list_append_unique (GList *list, char *text)
1106 GList *lc_link;
1109 * Go to the last position and traverse the list backwards
1110 * starting from the second last entry to make sure that we
1111 * are not removing the current link.
1113 list = g_list_append (list, text);
1114 list = g_list_last (list);
1115 lc_link = g_list_previous (list);
1117 while (lc_link != NULL)
1119 GList *newlink;
1121 newlink = g_list_previous (lc_link);
1122 if (strcmp ((char *) lc_link->data, text) == 0)
1124 GList *tmp;
1126 g_free (lc_link->data);
1127 tmp = g_list_remove_link (list, lc_link);
1128 (void) tmp;
1129 g_list_free_1 (lc_link);
1131 lc_link = newlink;
1134 return list;
1137 /* --------------------------------------------------------------------------------------------- */
1139 * Read and restore position for the given filename.
1140 * If there is no stored data, return line 1 and col 0.
1143 void
1144 load_file_position (const vfs_path_t *filename_vpath, long *line, long *column, off_t *offset,
1145 GArray **bookmarks)
1147 char *fn;
1148 FILE *f;
1149 char buf[MC_MAXPATHLEN + 100];
1150 const size_t len = vfs_path_len (filename_vpath);
1152 /* defaults */
1153 *line = 1;
1154 *column = 0;
1155 *offset = 0;
1157 /* open file with positions */
1158 fn = mc_config_get_full_path (MC_FILEPOS_FILE);
1159 f = fopen (fn, "r");
1160 g_free (fn);
1161 if (f == NULL)
1162 return;
1164 /* prepare array for serialized bookmarks */
1165 if (bookmarks != NULL)
1166 *bookmarks = g_array_sized_new (FALSE, FALSE, sizeof (size_t), MAX_SAVED_BOOKMARKS);
1168 while (fgets (buf, sizeof (buf), f) != NULL)
1170 const char *p;
1171 gchar **pos_tokens;
1173 /* check if the filename matches the beginning of string */
1174 if (strncmp (buf, vfs_path_as_str (filename_vpath), len) != 0)
1175 continue;
1177 /* followed by single space */
1178 if (buf[len] != ' ')
1179 continue;
1181 /* and string without spaces */
1182 p = &buf[len + 1];
1183 if (strchr (p, ' ') != NULL)
1184 continue;
1186 pos_tokens = g_strsplit (p, ";", 3 + MAX_SAVED_BOOKMARKS);
1187 if (pos_tokens[0] == NULL)
1189 *line = 1;
1190 *column = 0;
1191 *offset = 0;
1193 else
1195 *line = strtol (pos_tokens[0], NULL, 10);
1196 if (pos_tokens[1] == NULL)
1198 *column = 0;
1199 *offset = 0;
1201 else
1203 *column = strtol (pos_tokens[1], NULL, 10);
1204 if (pos_tokens[2] == NULL)
1205 *offset = 0;
1206 else if (bookmarks != NULL)
1208 size_t i;
1210 *offset = (off_t) g_ascii_strtoll (pos_tokens[2], NULL, 10);
1212 for (i = 0; i < MAX_SAVED_BOOKMARKS && pos_tokens[3 + i] != NULL; i++)
1214 size_t val;
1216 val = strtoul (pos_tokens[3 + i], NULL, 10);
1217 g_array_append_val (*bookmarks, val);
1223 g_strfreev (pos_tokens);
1226 fclose (f);
1229 /* --------------------------------------------------------------------------------------------- */
1231 * Save position for the given file
1234 void
1235 save_file_position (const vfs_path_t *filename_vpath, long line, long column, off_t offset,
1236 GArray *bookmarks)
1238 static size_t filepos_max_saved_entries = 0;
1239 char *fn, *tmp_fn;
1240 FILE *f, *tmp_f;
1241 char buf[MC_MAXPATHLEN + 100];
1242 size_t i;
1243 const size_t len = vfs_path_len (filename_vpath);
1244 gboolean src_error = FALSE;
1246 if (filepos_max_saved_entries == 0)
1247 filepos_max_saved_entries = mc_config_get_int (mc_global.main_config, CONFIG_APP_SECTION,
1248 "filepos_max_saved_entries", 1024);
1250 fn = mc_config_get_full_path (MC_FILEPOS_FILE);
1251 if (fn == NULL)
1252 goto early_error;
1254 mc_util_make_backup_if_possible (fn, TMP_SUFFIX);
1256 /* open file */
1257 f = fopen (fn, "w");
1258 if (f == NULL)
1259 goto open_target_error;
1261 tmp_fn = g_strdup_printf ("%s" TMP_SUFFIX, fn);
1262 tmp_f = fopen (tmp_fn, "r");
1263 if (tmp_f == NULL)
1265 src_error = TRUE;
1266 goto open_source_error;
1269 /* put the new record */
1270 if (line != 1 || column != 0 || bookmarks != NULL)
1272 if (fprintf
1273 (f, "%s %ld;%ld;%" PRIuMAX, vfs_path_as_str (filename_vpath), line, column,
1274 (uintmax_t) offset) < 0)
1275 goto write_position_error;
1276 if (bookmarks != NULL)
1277 for (i = 0; i < bookmarks->len && i < MAX_SAVED_BOOKMARKS; i++)
1278 if (fprintf (f, ";%zu", g_array_index (bookmarks, size_t, i)) < 0)
1279 goto write_position_error;
1281 if (fprintf (f, "\n") < 0)
1282 goto write_position_error;
1285 i = 1;
1286 while (fgets (buf, sizeof (buf), tmp_f) != NULL)
1288 if (buf[len] == ' ' && strncmp (buf, vfs_path_as_str (filename_vpath), len) == 0
1289 && strchr (&buf[len + 1], ' ') == NULL)
1290 continue;
1292 fprintf (f, "%s", buf);
1293 if (++i > filepos_max_saved_entries)
1294 break;
1297 write_position_error:
1298 fclose (tmp_f);
1299 open_source_error:
1300 g_free (tmp_fn);
1301 fclose (f);
1302 if (src_error)
1303 mc_util_restore_from_backup_if_possible (fn, TMP_SUFFIX);
1304 else
1305 mc_util_unlink_backup_if_possible (fn, TMP_SUFFIX);
1306 open_target_error:
1307 g_free (fn);
1308 early_error:
1309 if (bookmarks != NULL)
1310 g_array_free (bookmarks, TRUE);
1313 /* --------------------------------------------------------------------------------------------- */
1315 extern int
1316 ascii_alpha_to_cntrl (int ch)
1318 if ((ch >= ASCII_A && ch <= ASCII_Z) || (ch >= ASCII_a && ch <= ASCII_z))
1319 ch &= 0x1f;
1321 return ch;
1324 /* --------------------------------------------------------------------------------------------- */
1326 const char *
1327 Q_ (const char *s)
1329 const char *result, *sep;
1331 result = _(s);
1332 sep = strchr (result, '|');
1334 return sep != NULL ? sep + 1 : result;
1337 /* --------------------------------------------------------------------------------------------- */
1339 gboolean
1340 mc_util_make_backup_if_possible (const char *file_name, const char *backup_suffix)
1342 struct stat stat_buf;
1343 char *backup_path;
1344 gboolean ret;
1346 if (!exist_file (file_name))
1347 return FALSE;
1349 backup_path = g_strdup_printf ("%s%s", file_name, backup_suffix);
1350 if (backup_path == NULL)
1351 return FALSE;
1353 ret = mc_util_write_backup_content (file_name, backup_path);
1354 if (ret)
1356 /* Backup file will have same ownership with main file. */
1357 if (stat (file_name, &stat_buf) == 0)
1358 chmod (backup_path, stat_buf.st_mode);
1359 else
1360 chmod (backup_path, S_IRUSR | S_IWUSR);
1363 g_free (backup_path);
1365 return ret;
1368 /* --------------------------------------------------------------------------------------------- */
1370 gboolean
1371 mc_util_restore_from_backup_if_possible (const char *file_name, const char *backup_suffix)
1373 gboolean ret;
1374 char *backup_path;
1376 backup_path = g_strdup_printf ("%s%s", file_name, backup_suffix);
1377 if (backup_path == NULL)
1378 return FALSE;
1380 ret = mc_util_write_backup_content (backup_path, file_name);
1381 g_free (backup_path);
1383 return ret;
1386 /* --------------------------------------------------------------------------------------------- */
1388 gboolean
1389 mc_util_unlink_backup_if_possible (const char *file_name, const char *backup_suffix)
1391 char *backup_path;
1393 backup_path = g_strdup_printf ("%s%s", file_name, backup_suffix);
1394 if (backup_path == NULL)
1395 return FALSE;
1397 if (exist_file (backup_path))
1399 vfs_path_t *vpath;
1401 vpath = vfs_path_from_str (backup_path);
1402 mc_unlink (vpath);
1403 vfs_path_free (vpath, TRUE);
1406 g_free (backup_path);
1407 return TRUE;
1410 /* --------------------------------------------------------------------------------------------- */
1412 * partly taken from dcigettext.c, returns "" for default locale
1413 * value should be freed by calling function g_free()
1416 char *
1417 guess_message_value (void)
1419 static const char *const var[] = {
1420 /* Setting of LC_ALL overwrites all other. */
1421 /* Do not use LANGUAGE for check user locale and drowing hints */
1422 "LC_ALL",
1423 /* Next comes the name of the desired category. */
1424 "LC_MESSAGES",
1425 /* Last possibility is the LANG environment variable. */
1426 "LANG",
1427 /* NULL exit loops */
1428 NULL
1431 size_t i;
1432 const char *locale = NULL;
1434 for (i = 0; var[i] != NULL; i++)
1436 locale = getenv (var[i]);
1437 if (locale != NULL && locale[0] != '\0')
1438 break;
1441 if (locale == NULL)
1442 locale = "";
1444 return g_strdup (locale);
1447 /* --------------------------------------------------------------------------------------------- */
1450 * The "profile root" is the tree under which all of MC's user data &
1451 * settings are stored.
1453 * It defaults to the user's home dir. The user may override this default
1454 * with the environment variable $MC_PROFILE_ROOT.
1456 const char *
1457 mc_get_profile_root (void)
1459 static const char *profile_root = NULL;
1461 if (profile_root == NULL)
1463 profile_root = g_getenv ("MC_PROFILE_ROOT");
1464 if (profile_root == NULL || *profile_root == '\0')
1465 profile_root = mc_config_get_home_dir ();
1468 return profile_root;
1471 /* --------------------------------------------------------------------------------------------- */
1473 * Propagate error in simple way.
1475 * @param dest error return location
1476 * @param code error code
1477 * @param format printf()-style format for error message
1478 * @param ... parameters for message format
1481 void
1482 mc_propagate_error (GError **dest, int code, const char *format, ...)
1484 if (dest != NULL && *dest == NULL)
1486 GError *tmp_error;
1487 va_list args;
1489 va_start (args, format);
1490 tmp_error = g_error_new_valist (MC_ERROR, code, format, args);
1491 va_end (args);
1493 g_propagate_error (dest, tmp_error);
1497 /* --------------------------------------------------------------------------------------------- */
1499 * Replace existing error in simple way.
1501 * @param dest error return location
1502 * @param code error code
1503 * @param format printf()-style format for error message
1504 * @param ... parameters for message format
1507 void
1508 mc_replace_error (GError **dest, int code, const char *format, ...)
1510 if (dest != NULL)
1512 GError *tmp_error;
1513 va_list args;
1515 va_start (args, format);
1516 tmp_error = g_error_new_valist (MC_ERROR, code, format, args);
1517 va_end (args);
1519 g_error_free (*dest);
1520 *dest = NULL;
1521 g_propagate_error (dest, tmp_error);
1525 /* --------------------------------------------------------------------------------------------- */
1528 * Returns if the given duration has elapsed since the given timestamp,
1529 * and if it has then updates the timestamp.
1531 * @param timestamp the last timestamp in microseconds, updated if the given time elapsed
1532 * @param delay amount of time in microseconds
1534 * @return TRUE if clock skew detected, FALSE otherwise
1536 gboolean
1537 mc_time_elapsed (gint64 *timestamp, gint64 delay)
1539 gint64 now;
1541 now = g_get_monotonic_time ();
1543 if (now >= *timestamp && now < *timestamp + delay)
1544 return FALSE;
1546 *timestamp = now;
1547 return TRUE;
1550 /* --------------------------------------------------------------------------------------------- */