mceditor: refactoring.
[midnight-commander.git] / lib / util.c
blobb766f7ca1a2af75b3050566cd5813d3cdab15c0f
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 char *secure_path;
336 const char *ret;
338 vpath = vfs_path_from_str (path);
339 secure_path = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_PASSWORD);
340 vfs_path_free (vpath, TRUE);
342 ret = str_trunc (secure_path, trunc_len);
343 g_free (secure_path);
345 return ret;
348 /* --------------------------------------------------------------------------------------------- */
350 const char *
351 size_trunc (uintmax_t size, gboolean use_si)
353 static char x[BUF_TINY];
354 uintmax_t divisor = 1;
355 const char *xtra = _("B");
357 if (size > 999999999UL)
359 divisor = use_si ? 1000 : 1024;
360 xtra = use_si ? _("kB") : _("KiB");
362 if (size / divisor > 999999999UL)
364 divisor = use_si ? (1000 * 1000) : (1024 * 1024);
365 xtra = use_si ? _("MB") : _("MiB");
367 if (size / divisor > 999999999UL)
369 divisor = use_si ? (1000 * 1000 * 1000) : (1024 * 1024 * 1024);
370 xtra = use_si ? _("GB") : _("GiB");
374 g_snprintf (x, sizeof (x), "%.0f %s", 1.0 * size / divisor, xtra);
375 return x;
378 /* --------------------------------------------------------------------------------------------- */
380 const char *
381 size_trunc_sep (uintmax_t size, gboolean use_si)
383 static char x[60];
384 int count;
385 const char *p, *y;
386 char *d;
388 p = y = size_trunc (size, use_si);
389 p += strlen (p) - 1;
390 d = x + sizeof (x) - 1;
391 *d-- = '\0';
392 /* @size format is "size unit", i.e. "[digits][space][letters]".
393 Copy all characters after digits. */
394 while (p >= y && !g_ascii_isdigit (*p))
395 *d-- = *p--;
396 for (count = 0; p >= y; count++)
398 if (count == 3)
400 *d-- = ',';
401 count = 0;
403 *d-- = *p--;
405 d++;
406 if (*d == ',')
407 d++;
408 return d;
411 /* --------------------------------------------------------------------------------------------- */
413 * Print file SIZE to BUFFER, but don't exceed LEN characters,
414 * not including trailing 0. BUFFER should be at least LEN+1 long.
415 * This function is called for every file on panels, so avoid
416 * floating point by any means.
418 * Units: size units (filesystem sizes are 1K blocks)
419 * 0=bytes, 1=Kbytes, 2=Mbytes, etc.
422 void
423 size_trunc_len (char *buffer, unsigned int len, uintmax_t size, int units, gboolean use_si)
425 /* Avoid taking power for every file. */
426 /* *INDENT-OFF* */
427 static const uintmax_t power10[] = {
428 /* we hope that size of uintmax_t is 4 bytes at least */
429 1ULL,
430 10ULL,
431 100ULL,
432 1000ULL,
433 10000ULL,
434 100000ULL,
435 1000000ULL,
436 10000000ULL,
437 100000000ULL,
438 1000000000ULL
439 /* maximum value of uintmax_t (in case of 4 bytes) is
440 4294967295
442 #if SIZEOF_UINTMAX_T == 8
444 10000000000ULL,
445 100000000000ULL,
446 1000000000000ULL,
447 10000000000000ULL,
448 100000000000000ULL,
449 1000000000000000ULL,
450 10000000000000000ULL,
451 100000000000000000ULL,
452 1000000000000000000ULL,
453 10000000000000000000ULL
454 /* maximum value of uintmax_t (in case of 8 bytes) is
455 18447644073710439615
457 #endif
459 /* *INDENT-ON* */
460 static const char *const suffix[] =
461 { "", "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q", NULL };
462 static const char *const suffix_lc[] =
463 { "", "k", "m", "g", "t", "p", "e", "z", "y", "r", "q", NULL };
465 const char *const *sfx = use_si ? suffix_lc : suffix;
466 int j = 0;
468 if (len == 0)
469 len = 9;
470 #if SIZEOF_UINTMAX_T == 8
471 /* 20 decimal digits are required to represent 8 bytes */
472 else if (len > 19)
473 len = 19;
474 #else
475 /* 10 decimal digits are required to represent 4 bytes */
476 else if (len > 9)
477 len = 9;
478 #endif
481 * recalculate from 1024 base to 1000 base if units>0
482 * We can't just multiply by 1024 - that might cause overflow
483 * if uintmax_t type is too small
485 if (use_si)
486 for (j = 0; j < units; j++)
488 uintmax_t size_remain;
490 size_remain = ((size % 125) * 1024) / 1000; /* size mod 125, recalculated */
491 size /= 125; /* 128/125 = 1024/1000 */
492 size *= 128; /* This will convert size from multiple of 1024 to multiple of 1000 */
493 size += size_remain; /* Re-add remainder lost by division/multiplication */
496 for (j = units; sfx[j] != NULL; j++)
498 if (size == 0)
500 if (j == units)
502 /* Empty files will print "0" even with minimal width. */
503 g_snprintf (buffer, len + 1, "%s", "0");
505 else
507 /* Use "~K" or just "K" if len is 1. Use "B" for bytes. */
508 g_snprintf (buffer, len + 1, (len > 1) ? "~%s" : "%s", (j > 1) ? sfx[j - 1] : "B");
510 break;
513 if (size < power10[len - (j > 0 ? 1 : 0)])
515 g_snprintf (buffer, len + 1, "%" PRIuMAX "%s", size, sfx[j]);
516 break;
519 /* Powers of 1000 or 1024, with rounding. */
520 if (use_si)
521 size = (size + 500) / 1000;
522 else
523 size = (size + 512) >> 10;
527 /* --------------------------------------------------------------------------------------------- */
529 const char *
530 string_perm (mode_t mode_bits)
532 static char mode[11];
534 strcpy (mode, "----------");
535 if (S_ISDIR (mode_bits))
536 mode[0] = 'd';
537 if (S_ISCHR (mode_bits))
538 mode[0] = 'c';
539 if (S_ISBLK (mode_bits))
540 mode[0] = 'b';
541 if (S_ISLNK (mode_bits))
542 mode[0] = 'l';
543 if (S_ISFIFO (mode_bits))
544 mode[0] = 'p';
545 if (S_ISNAM (mode_bits))
546 mode[0] = 'n';
547 if (S_ISSOCK (mode_bits))
548 mode[0] = 's';
549 if (S_ISDOOR (mode_bits))
550 mode[0] = 'D';
551 if (ismode (mode_bits, S_IXOTH))
552 mode[9] = 'x';
553 if (ismode (mode_bits, S_IWOTH))
554 mode[8] = 'w';
555 if (ismode (mode_bits, S_IROTH))
556 mode[7] = 'r';
557 if (ismode (mode_bits, S_IXGRP))
558 mode[6] = 'x';
559 if (ismode (mode_bits, S_IWGRP))
560 mode[5] = 'w';
561 if (ismode (mode_bits, S_IRGRP))
562 mode[4] = 'r';
563 if (ismode (mode_bits, S_IXUSR))
564 mode[3] = 'x';
565 if (ismode (mode_bits, S_IWUSR))
566 mode[2] = 'w';
567 if (ismode (mode_bits, S_IRUSR))
568 mode[1] = 'r';
569 #ifdef S_ISUID
570 if (ismode (mode_bits, S_ISUID))
571 mode[3] = (mode[3] == 'x') ? 's' : 'S';
572 #endif /* S_ISUID */
573 #ifdef S_ISGID
574 if (ismode (mode_bits, S_ISGID))
575 mode[6] = (mode[6] == 'x') ? 's' : 'S';
576 #endif /* S_ISGID */
577 #ifdef S_ISVTX
578 if (ismode (mode_bits, S_ISVTX))
579 mode[9] = (mode[9] == 'x') ? 't' : 'T';
580 #endif /* S_ISVTX */
581 return mode;
584 /* --------------------------------------------------------------------------------------------- */
586 const char *
587 extension (const char *filename)
589 const char *d;
591 d = strrchr (filename, '.');
593 return d != NULL ? d + 1 : "";
596 /* --------------------------------------------------------------------------------------------- */
598 char *
599 load_mc_home_file (const char *from, const char *filename, char **allocated_filename,
600 size_t * length)
602 char *hintfile_base, *hintfile;
603 char *lang;
604 char *data;
606 hintfile_base = g_build_filename (from, filename, (char *) NULL);
607 lang = guess_message_value ();
609 hintfile = g_strconcat (hintfile_base, ".", lang, (char *) NULL);
610 if (!g_file_get_contents (hintfile, &data, length, NULL))
612 /* Fall back to the two-letter language code */
613 if (lang[0] != '\0' && lang[1] != '\0')
614 lang[2] = '\0';
615 g_free (hintfile);
616 hintfile = g_strconcat (hintfile_base, ".", lang, (char *) NULL);
617 if (!g_file_get_contents (hintfile, &data, length, NULL))
619 g_free (hintfile);
620 hintfile = hintfile_base;
621 g_file_get_contents (hintfile_base, &data, length, NULL);
625 g_free (lang);
627 if (hintfile != hintfile_base)
628 g_free (hintfile_base);
630 if (allocated_filename != NULL)
631 *allocated_filename = hintfile;
632 else
633 g_free (hintfile);
635 return data;
638 /* --------------------------------------------------------------------------------------------- */
640 const char *
641 extract_line (const char *s, const char *top)
643 static char tmp_line[BUF_MEDIUM];
644 char *t = tmp_line;
646 while (*s != '\0' && *s != '\n' && (size_t) (t - tmp_line) < sizeof (tmp_line) - 1 && s < top)
647 *t++ = *s++;
648 *t = '\0';
649 return tmp_line;
652 /* --------------------------------------------------------------------------------------------- */
654 * The basename routine
657 const char *
658 x_basename (const char *s)
660 const char *url_delim, *path_sep;
662 url_delim = g_strrstr (s, VFS_PATH_URL_DELIMITER);
663 path_sep = strrchr (s, PATH_SEP);
665 if (path_sep == NULL)
666 return s;
668 if (url_delim == NULL
669 || url_delim < path_sep - strlen (VFS_PATH_URL_DELIMITER)
670 || url_delim - s + strlen (VFS_PATH_URL_DELIMITER) < strlen (s))
672 /* avoid trailing PATH_SEP, if present */
673 if (!IS_PATH_SEP (s[strlen (s) - 1]))
674 return path_sep + 1;
676 while (--path_sep > s && !IS_PATH_SEP (*path_sep))
678 return (path_sep != s) ? path_sep + 1 : s;
681 while (--url_delim > s && !IS_PATH_SEP (*url_delim))
683 while (--url_delim > s && !IS_PATH_SEP (*url_delim))
686 return url_delim == s ? s : url_delim + 1;
689 /* --------------------------------------------------------------------------------------------- */
691 const char *
692 unix_error_string (int error_num)
694 static char buffer[BUF_LARGE];
695 gchar *strerror_currentlocale;
697 strerror_currentlocale = g_locale_from_utf8 (g_strerror (error_num), -1, NULL, NULL, NULL);
698 g_snprintf (buffer, sizeof (buffer), "%s (%d)", strerror_currentlocale, error_num);
699 g_free (strerror_currentlocale);
701 return buffer;
704 /* --------------------------------------------------------------------------------------------- */
706 const char *
707 skip_separators (const char *s)
709 const char *su = s;
711 for (; *su != '\0'; str_cnext_char (&su))
712 if (!whitespace (*su) && *su != ',')
713 break;
715 return su;
718 /* --------------------------------------------------------------------------------------------- */
720 const char *
721 skip_numbers (const char *s)
723 const char *su = s;
725 for (; *su != '\0'; str_cnext_char (&su))
726 if (!str_isdigit (su))
727 break;
729 return su;
732 /* --------------------------------------------------------------------------------------------- */
734 * Remove all control sequences from the argument string. We define
735 * "control sequence", in a sort of pidgin BNF, as follows:
737 * control-seq = Esc non-'['
738 * | Esc '[' (0 or more digits or ';' or ':' or '?') (any other char)
740 * The 256-color and true-color escape sequences should allow either ';' or ':' inside as separator,
741 * actually, ':' is the more correct according to ECMA-48.
742 * Some terminal emulators (e.g. xterm, gnome-terminal) support this.
744 * Non-printable characters are also removed.
747 char *
748 strip_ctrl_codes (char *s)
750 char *w; /* Current position where the stripped data is written */
751 char *r; /* Current position where the original data is read */
753 if (s == NULL)
754 return NULL;
756 for (w = s, r = s; *r != '\0';)
758 if (*r == ESC_CHAR)
760 /* Skip the control sequence's arguments */ ;
761 /* '(' need to avoid strange 'B' letter in *Suse (if mc runs under root user) */
762 if (*(++r) == '[' || *r == '(')
764 /* strchr() matches trailing binary 0 */
765 while (*(++r) != '\0' && strchr ("0123456789;:?", *r) != NULL)
768 else if (*r == ']')
771 * Skip xterm's OSC (Operating System Command)
772 * http://www.xfree86.org/current/ctlseqs.html
773 * OSC P s ; P t ST
774 * OSC P s ; P t BEL
776 char *new_r;
778 for (new_r = r; *new_r != '\0'; new_r++)
780 switch (*new_r)
782 /* BEL */
783 case '\a':
784 r = new_r;
785 goto osc_out;
786 case ESC_CHAR:
787 /* ST */
788 if (new_r[1] == '\\')
790 r = new_r + 1;
791 goto osc_out;
793 break;
794 default:
795 break;
798 osc_out:
803 * Now we are at the last character of the sequence.
804 * Skip it unless it's binary 0.
806 if (*r != '\0')
807 r++;
809 else
811 char *n;
813 n = str_get_next_char (r);
814 if (str_isprint (r))
816 memmove (w, r, n - r);
817 w += n - r;
819 r = n;
823 *w = '\0';
824 return s;
827 /* --------------------------------------------------------------------------------------------- */
829 enum compression_type
830 get_compression_type (int fd, const char *name)
832 unsigned char magic[16];
833 size_t str_len;
835 /* Read the magic signature */
836 if (mc_read (fd, (char *) magic, 4) != 4)
837 return COMPRESSION_NONE;
839 /* GZIP_MAGIC and OLD_GZIP_MAGIC */
840 if (magic[0] == 0x1F && (magic[1] == 0x8B || magic[1] == 0x9E))
841 return COMPRESSION_GZIP;
843 /* PKZIP_MAGIC */
844 if (magic[0] == 'P' && magic[1] == 'K' && magic[2] == 0x03 && magic[3] == 0x04)
846 /* Read compression type */
847 mc_lseek (fd, 8, SEEK_SET);
848 if (mc_read (fd, (char *) magic, 2) != 2)
849 return COMPRESSION_NONE;
851 if ((magic[0] != 8 && magic[0] != 0) || magic[1] != 0)
852 return COMPRESSION_NONE;
854 return COMPRESSION_ZIP;
857 /* PACK_MAGIC and LZH_MAGIC and compress magic */
858 if (magic[0] == 0x1F && (magic[1] == 0x1E || magic[1] == 0xA0 || magic[1] == 0x9D))
859 /* Compatible with gzip */
860 return COMPRESSION_GZIP;
862 /* BZIP and BZIP2 files */
863 if ((magic[0] == 'B') && (magic[1] == 'Z') && (magic[3] >= '1') && (magic[3] <= '9'))
864 switch (magic[2])
866 case '0':
867 return COMPRESSION_BZIP;
868 case 'h':
869 return COMPRESSION_BZIP2;
870 default:
871 break;
874 /* LZ4 format - v1.5.0 - 0x184D2204 (little endian) */
875 if (magic[0] == 0x04 && magic[1] == 0x22 && magic[2] == 0x4d && magic[3] == 0x18)
876 return COMPRESSION_LZ4;
878 if (mc_read (fd, (char *) magic + 4, 2) != 2)
879 return COMPRESSION_NONE;
881 /* LZIP files */
882 if (magic[0] == 'L'
883 && magic[1] == 'Z'
884 && magic[2] == 'I' && magic[3] == 'P' && (magic[4] == 0x00 || magic[4] == 0x01))
885 return COMPRESSION_LZIP;
887 /* Support for LZMA (only utils format with magic in header).
888 * This is the default format of LZMA utils 4.32.1 and later. */
889 if (magic[0] == 0xFF
890 && magic[1] == 'L'
891 && magic[2] == 'Z' && magic[3] == 'M' && magic[4] == 'A' && magic[5] == 0x00)
892 return COMPRESSION_LZMA;
894 /* LZO format - \x89\x4c\x5a\x4f\x00\x0d\x0a\x1a\x0a lzop compressed data */
895 if (magic[0] == 0x89 && magic[1] == 0x4c &&
896 magic[2] == 0x5a && magic[3] == 0x4f && magic[4] == 0x00 && magic[5] == 0x0d)
897 return COMPRESSION_LZO;
899 /* XZ compression magic */
900 if (magic[0] == 0xFD
901 && magic[1] == 0x37
902 && magic[2] == 0x7A && magic[3] == 0x58 && magic[4] == 0x5A && magic[5] == 0x00)
903 return COMPRESSION_XZ;
905 if (magic[0] == 0x28 && magic[1] == 0xB5 && magic[2] == 0x2F && magic[3] == 0xFD)
906 return COMPRESSION_ZSTD;
908 str_len = strlen (name);
909 /* HACK: we must believe to extension of LZMA file :) ... */
910 if ((str_len > 5 && strcmp (&name[str_len - 5], ".lzma") == 0) ||
911 (str_len > 4 && strcmp (&name[str_len - 4], ".tlz") == 0))
912 return COMPRESSION_LZMA;
914 return COMPRESSION_NONE;
917 /* --------------------------------------------------------------------------------------------- */
919 const char *
920 decompress_extension (int type)
922 switch (type)
924 case COMPRESSION_ZIP:
925 return "/uz" VFS_PATH_URL_DELIMITER;
926 case COMPRESSION_GZIP:
927 return "/ugz" VFS_PATH_URL_DELIMITER;
928 case COMPRESSION_BZIP:
929 return "/ubz" VFS_PATH_URL_DELIMITER;
930 case COMPRESSION_BZIP2:
931 return "/ubz2" VFS_PATH_URL_DELIMITER;
932 case COMPRESSION_LZIP:
933 return "/ulz" VFS_PATH_URL_DELIMITER;
934 case COMPRESSION_LZ4:
935 return "/ulz4" VFS_PATH_URL_DELIMITER;
936 case COMPRESSION_LZMA:
937 return "/ulzma" VFS_PATH_URL_DELIMITER;
938 case COMPRESSION_LZO:
939 return "/ulzo" VFS_PATH_URL_DELIMITER;
940 case COMPRESSION_XZ:
941 return "/uxz" VFS_PATH_URL_DELIMITER;
942 case COMPRESSION_ZSTD:
943 return "/uzst" VFS_PATH_URL_DELIMITER;
944 default:
945 break;
947 /* Should never reach this place */
948 fprintf (stderr, "Fatal: decompress_extension called with an unknown argument\n");
949 return 0;
952 /* --------------------------------------------------------------------------------------------- */
954 void
955 wipe_password (char *passwd)
957 if (passwd != NULL)
959 char *p;
961 for (p = passwd; *p != '\0'; p++)
962 *p = '\0';
963 g_free (passwd);
967 /* --------------------------------------------------------------------------------------------- */
969 * Convert "\E" -> esc character and ^x to control-x key and ^^ to ^ key
971 * @param p pointer to string
973 * @return newly allocated string
976 char *
977 convert_controls (const char *p)
979 char *valcopy;
980 char *q;
982 valcopy = g_strdup (p);
984 /* Parse the escape special character */
985 for (q = valcopy; *p != '\0';)
986 switch (*p)
988 case '\\':
989 p++;
991 if (*p == 'e' || *p == 'E')
993 p++;
994 *q++ = ESC_CHAR;
996 break;
998 case '^':
999 p++;
1000 if (*p == '^')
1001 *q++ = *p++;
1002 else
1004 char c;
1006 c = *p | 0x20;
1007 if (c >= 'a' && c <= 'z')
1009 *q++ = c - 'a' + 1;
1010 p++;
1012 else if (*p != '\0')
1013 p++;
1015 break;
1017 default:
1018 *q++ = *p++;
1021 *q = '\0';
1022 return valcopy;
1025 /* --------------------------------------------------------------------------------------------- */
1027 * Finds out a relative path from first to second, i.e. goes as many ..
1028 * as needed up in first and then goes down using second
1031 char *
1032 diff_two_paths (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
1034 int j, prevlen = -1, currlen;
1035 char *my_first = NULL, *my_second = NULL;
1036 char *buf = NULL;
1038 my_first = resolve_symlinks (vpath1);
1039 if (my_first == NULL)
1040 goto ret;
1042 my_second = resolve_symlinks (vpath2);
1043 if (my_second == NULL)
1044 goto ret;
1046 for (j = 0; j < 2; j++)
1048 char *p, *q;
1049 int i;
1051 p = my_first;
1052 q = my_second;
1054 while (TRUE)
1056 char *r, *s;
1057 ptrdiff_t len;
1059 r = strchr (p, PATH_SEP);
1060 if (r == NULL)
1061 break;
1062 s = strchr (q, PATH_SEP);
1063 if (s == NULL)
1064 break;
1066 len = r - p;
1067 if (len != (s - q) || strncmp (p, q, (size_t) len) != 0)
1068 break;
1070 p = r + 1;
1071 q = s + 1;
1073 p--;
1074 for (i = 0; (p = strchr (p + 1, PATH_SEP)) != NULL; i++)
1076 currlen = (i + 1) * 3 + strlen (q) + 1;
1077 if (j != 0)
1079 if (currlen < prevlen)
1080 g_free (buf);
1081 else
1082 goto ret;
1084 p = buf = g_malloc (currlen);
1085 prevlen = currlen;
1086 for (; i >= 0; i--, p += 3)
1087 strcpy (p, "../");
1088 strcpy (p, q);
1091 ret:
1092 g_free (my_first);
1093 g_free (my_second);
1094 return buf;
1097 /* --------------------------------------------------------------------------------------------- */
1099 * Append text to GList, remove all entries with the same text
1102 GList *
1103 list_append_unique (GList * list, char *text)
1105 GList *lc_link;
1108 * Go to the last position and traverse the list backwards
1109 * starting from the second last entry to make sure that we
1110 * are not removing the current link.
1112 list = g_list_append (list, text);
1113 list = g_list_last (list);
1114 lc_link = g_list_previous (list);
1116 while (lc_link != NULL)
1118 GList *newlink;
1120 newlink = g_list_previous (lc_link);
1121 if (strcmp ((char *) lc_link->data, text) == 0)
1123 GList *tmp;
1125 g_free (lc_link->data);
1126 tmp = g_list_remove_link (list, lc_link);
1127 (void) tmp;
1128 g_list_free_1 (lc_link);
1130 lc_link = newlink;
1133 return list;
1136 /* --------------------------------------------------------------------------------------------- */
1138 * Read and restore position for the given filename.
1139 * If there is no stored data, return line 1 and col 0.
1142 void
1143 load_file_position (const vfs_path_t * filename_vpath, long *line, long *column, off_t * offset,
1144 GArray ** bookmarks)
1146 char *fn;
1147 FILE *f;
1148 char buf[MC_MAXPATHLEN + 100];
1149 const size_t len = vfs_path_len (filename_vpath);
1151 /* defaults */
1152 *line = 1;
1153 *column = 0;
1154 *offset = 0;
1156 /* open file with positions */
1157 fn = mc_config_get_full_path (MC_FILEPOS_FILE);
1158 f = fopen (fn, "r");
1159 g_free (fn);
1160 if (f == NULL)
1161 return;
1163 /* prepare array for serialized bookmarks */
1164 if (bookmarks != NULL)
1165 *bookmarks = g_array_sized_new (FALSE, FALSE, sizeof (size_t), MAX_SAVED_BOOKMARKS);
1167 while (fgets (buf, sizeof (buf), f) != NULL)
1169 const char *p;
1170 gchar **pos_tokens;
1172 /* check if the filename matches the beginning of string */
1173 if (strncmp (buf, vfs_path_as_str (filename_vpath), len) != 0)
1174 continue;
1176 /* followed by single space */
1177 if (buf[len] != ' ')
1178 continue;
1180 /* and string without spaces */
1181 p = &buf[len + 1];
1182 if (strchr (p, ' ') != NULL)
1183 continue;
1185 pos_tokens = g_strsplit (p, ";", 3 + MAX_SAVED_BOOKMARKS);
1186 if (pos_tokens[0] == NULL)
1188 *line = 1;
1189 *column = 0;
1190 *offset = 0;
1192 else
1194 *line = strtol (pos_tokens[0], NULL, 10);
1195 if (pos_tokens[1] == NULL)
1197 *column = 0;
1198 *offset = 0;
1200 else
1202 *column = strtol (pos_tokens[1], NULL, 10);
1203 if (pos_tokens[2] == NULL)
1204 *offset = 0;
1205 else if (bookmarks != NULL)
1207 size_t i;
1209 *offset = (off_t) g_ascii_strtoll (pos_tokens[2], NULL, 10);
1211 for (i = 0; i < MAX_SAVED_BOOKMARKS && pos_tokens[3 + i] != NULL; i++)
1213 size_t val;
1215 val = strtoul (pos_tokens[3 + i], NULL, 10);
1216 g_array_append_val (*bookmarks, val);
1222 g_strfreev (pos_tokens);
1225 fclose (f);
1228 /* --------------------------------------------------------------------------------------------- */
1230 * Save position for the given file
1233 void
1234 save_file_position (const vfs_path_t * filename_vpath, long line, long column, off_t offset,
1235 GArray * bookmarks)
1237 static size_t filepos_max_saved_entries = 0;
1238 char *fn, *tmp_fn;
1239 FILE *f, *tmp_f;
1240 char buf[MC_MAXPATHLEN + 100];
1241 size_t i;
1242 const size_t len = vfs_path_len (filename_vpath);
1243 gboolean src_error = FALSE;
1245 if (filepos_max_saved_entries == 0)
1246 filepos_max_saved_entries = mc_config_get_int (mc_global.main_config, CONFIG_APP_SECTION,
1247 "filepos_max_saved_entries", 1024);
1249 fn = mc_config_get_full_path (MC_FILEPOS_FILE);
1250 if (fn == NULL)
1251 goto early_error;
1253 mc_util_make_backup_if_possible (fn, TMP_SUFFIX);
1255 /* open file */
1256 f = fopen (fn, "w");
1257 if (f == NULL)
1258 goto open_target_error;
1260 tmp_fn = g_strdup_printf ("%s" TMP_SUFFIX, fn);
1261 tmp_f = fopen (tmp_fn, "r");
1262 if (tmp_f == NULL)
1264 src_error = TRUE;
1265 goto open_source_error;
1268 /* put the new record */
1269 if (line != 1 || column != 0 || bookmarks != NULL)
1271 if (fprintf
1272 (f, "%s %ld;%ld;%" PRIuMAX, vfs_path_as_str (filename_vpath), line, column,
1273 (uintmax_t) offset) < 0)
1274 goto write_position_error;
1275 if (bookmarks != NULL)
1276 for (i = 0; i < bookmarks->len && i < MAX_SAVED_BOOKMARKS; i++)
1277 if (fprintf (f, ";%zu", g_array_index (bookmarks, size_t, i)) < 0)
1278 goto write_position_error;
1280 if (fprintf (f, "\n") < 0)
1281 goto write_position_error;
1284 i = 1;
1285 while (fgets (buf, sizeof (buf), tmp_f) != NULL)
1287 if (buf[len] == ' ' && strncmp (buf, vfs_path_as_str (filename_vpath), len) == 0
1288 && strchr (&buf[len + 1], ' ') == NULL)
1289 continue;
1291 fprintf (f, "%s", buf);
1292 if (++i > filepos_max_saved_entries)
1293 break;
1296 write_position_error:
1297 fclose (tmp_f);
1298 open_source_error:
1299 g_free (tmp_fn);
1300 fclose (f);
1301 if (src_error)
1302 mc_util_restore_from_backup_if_possible (fn, TMP_SUFFIX);
1303 else
1304 mc_util_unlink_backup_if_possible (fn, TMP_SUFFIX);
1305 open_target_error:
1306 g_free (fn);
1307 early_error:
1308 if (bookmarks != NULL)
1309 g_array_free (bookmarks, TRUE);
1312 /* --------------------------------------------------------------------------------------------- */
1314 extern int
1315 ascii_alpha_to_cntrl (int ch)
1317 if ((ch >= ASCII_A && ch <= ASCII_Z) || (ch >= ASCII_a && ch <= ASCII_z))
1318 ch &= 0x1f;
1320 return ch;
1323 /* --------------------------------------------------------------------------------------------- */
1325 const char *
1326 Q_ (const char *s)
1328 const char *result, *sep;
1330 result = _(s);
1331 sep = strchr (result, '|');
1333 return sep != NULL ? sep + 1 : result;
1336 /* --------------------------------------------------------------------------------------------- */
1338 gboolean
1339 mc_util_make_backup_if_possible (const char *file_name, const char *backup_suffix)
1341 struct stat stat_buf;
1342 char *backup_path;
1343 gboolean ret;
1345 if (!exist_file (file_name))
1346 return FALSE;
1348 backup_path = g_strdup_printf ("%s%s", file_name, backup_suffix);
1349 if (backup_path == NULL)
1350 return FALSE;
1352 ret = mc_util_write_backup_content (file_name, backup_path);
1353 if (ret)
1355 /* Backup file will have same ownership with main file. */
1356 if (stat (file_name, &stat_buf) == 0)
1357 chmod (backup_path, stat_buf.st_mode);
1358 else
1359 chmod (backup_path, S_IRUSR | S_IWUSR);
1362 g_free (backup_path);
1364 return ret;
1367 /* --------------------------------------------------------------------------------------------- */
1369 gboolean
1370 mc_util_restore_from_backup_if_possible (const char *file_name, const char *backup_suffix)
1372 gboolean ret;
1373 char *backup_path;
1375 backup_path = g_strdup_printf ("%s%s", file_name, backup_suffix);
1376 if (backup_path == NULL)
1377 return FALSE;
1379 ret = mc_util_write_backup_content (backup_path, file_name);
1380 g_free (backup_path);
1382 return ret;
1385 /* --------------------------------------------------------------------------------------------- */
1387 gboolean
1388 mc_util_unlink_backup_if_possible (const char *file_name, const char *backup_suffix)
1390 char *backup_path;
1392 backup_path = g_strdup_printf ("%s%s", file_name, backup_suffix);
1393 if (backup_path == NULL)
1394 return FALSE;
1396 if (exist_file (backup_path))
1398 vfs_path_t *vpath;
1400 vpath = vfs_path_from_str (backup_path);
1401 mc_unlink (vpath);
1402 vfs_path_free (vpath, TRUE);
1405 g_free (backup_path);
1406 return TRUE;
1409 /* --------------------------------------------------------------------------------------------- */
1411 * partly taken from dcigettext.c, returns "" for default locale
1412 * value should be freed by calling function g_free()
1415 char *
1416 guess_message_value (void)
1418 static const char *const var[] = {
1419 /* Setting of LC_ALL overwrites all other. */
1420 /* Do not use LANGUAGE for check user locale and drowing hints */
1421 "LC_ALL",
1422 /* Next comes the name of the desired category. */
1423 "LC_MESSAGES",
1424 /* Last possibility is the LANG environment variable. */
1425 "LANG",
1426 /* NULL exit loops */
1427 NULL
1430 size_t i;
1431 const char *locale = NULL;
1433 for (i = 0; var[i] != NULL; i++)
1435 locale = getenv (var[i]);
1436 if (locale != NULL && locale[0] != '\0')
1437 break;
1440 if (locale == NULL)
1441 locale = "";
1443 return g_strdup (locale);
1446 /* --------------------------------------------------------------------------------------------- */
1449 * The "profile root" is the tree under which all of MC's user data &
1450 * settings are stored.
1452 * It defaults to the user's home dir. The user may override this default
1453 * with the environment variable $MC_PROFILE_ROOT.
1455 const char *
1456 mc_get_profile_root (void)
1458 static const char *profile_root = NULL;
1460 if (profile_root == NULL)
1462 profile_root = g_getenv ("MC_PROFILE_ROOT");
1463 if (profile_root == NULL || *profile_root == '\0')
1464 profile_root = mc_config_get_home_dir ();
1467 return profile_root;
1470 /* --------------------------------------------------------------------------------------------- */
1472 * Propagate error in simple way.
1474 * @param dest error return location
1475 * @param code error code
1476 * @param format printf()-style format for error message
1477 * @param ... parameters for message format
1480 void
1481 mc_propagate_error (GError ** dest, int code, const char *format, ...)
1483 if (dest != NULL && *dest == NULL)
1485 GError *tmp_error;
1486 va_list args;
1488 va_start (args, format);
1489 tmp_error = g_error_new_valist (MC_ERROR, code, format, args);
1490 va_end (args);
1492 g_propagate_error (dest, tmp_error);
1496 /* --------------------------------------------------------------------------------------------- */
1498 * Replace existing error in simple way.
1500 * @param dest error return location
1501 * @param code error code
1502 * @param format printf()-style format for error message
1503 * @param ... parameters for message format
1506 void
1507 mc_replace_error (GError ** dest, int code, const char *format, ...)
1509 if (dest != NULL)
1511 GError *tmp_error;
1512 va_list args;
1514 va_start (args, format);
1515 tmp_error = g_error_new_valist (MC_ERROR, code, format, args);
1516 va_end (args);
1518 g_error_free (*dest);
1519 *dest = NULL;
1520 g_propagate_error (dest, tmp_error);
1524 /* --------------------------------------------------------------------------------------------- */
1527 * Returns if the given duration has elapsed since the given timestamp,
1528 * and if it has then updates the timestamp.
1530 * @param timestamp the last timestamp in microseconds, updated if the given time elapsed
1531 * @param delay amount of time in microseconds
1533 * @return TRUE if clock skew detected, FALSE otherwise
1535 gboolean
1536 mc_time_elapsed (gint64 * timestamp, gint64 delay)
1538 gint64 now;
1540 now = g_get_monotonic_time ();
1542 if (now >= *timestamp && now < *timestamp + delay)
1543 return FALSE;
1545 *timestamp = now;
1546 return TRUE;
1549 /* --------------------------------------------------------------------------------------------- */