summaryrefslogtreecommitdiffstats
path: root/lib/util.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/util.c1538
1 files changed, 1538 insertions, 0 deletions
diff --git a/lib/util.c b/lib/util.c
new file mode 100644
index 0000000..744bd9e
--- /dev/null
+++ b/lib/util.c
@@ -0,0 +1,1538 @@
+/*
+ Various utilities
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1996
+ Janne Kukonlehto, 1994, 1995, 1996
+ Dugan Porter, 1994, 1995, 1996
+ Jakub Jelinek, 1994, 1995, 1996
+ Mauricio Plaza, 1994, 1995, 1996
+ Slava Zanko <slavazanko@gmail.com>, 2013
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file lib/util.c
+ * \brief Source: various utilities
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <stddef.h> /* ptrdiff_t */
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+#include "lib/mcconfig.h"
+#include "lib/fileloc.h"
+#include "lib/vfs/vfs.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define ismode(n,m) ((n & m) == m)
+
+/* Number of attempts to create a temporary file */
+#ifndef TMP_MAX
+#define TMP_MAX 16384
+#endif /* !TMP_MAX */
+
+#define TMP_SUFFIX ".tmp"
+
+#define ASCII_A (0x40 + 1)
+#define ASCII_Z (0x40 + 26)
+#define ASCII_a (0x60 + 1)
+#define ASCII_z (0x60 + 26)
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+#ifndef HAVE_CHARSET
+static inline int
+is_7bit_printable (unsigned char c)
+{
+ return (c > 31 && c < 127);
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline int
+is_iso_printable (unsigned char c)
+{
+ return ((c > 31 && c < 127) || c >= 160);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline int
+is_8bit_printable (unsigned char c)
+{
+ /* "Full 8 bits output" doesn't work on xterm */
+ if (mc_global.tty.xterm_flag)
+ return is_iso_printable (c);
+
+ return (c > 31 && c != 127 && c != 155);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+resolve_symlinks (const vfs_path_t * vpath)
+{
+ char *p, *p2;
+ char *buf, *buf2, *q, *r, c;
+ struct stat mybuf;
+
+ if (vpath->relative)
+ return NULL;
+
+ p = p2 = g_strdup (vfs_path_as_str (vpath));
+ r = buf = g_malloc (MC_MAXPATHLEN);
+ buf2 = g_malloc (MC_MAXPATHLEN);
+ *r++ = PATH_SEP;
+ *r = '\0';
+
+ do
+ {
+ q = strchr (p + 1, PATH_SEP);
+ if (q == NULL)
+ {
+ q = strchr (p + 1, '\0');
+ if (q == p + 1)
+ break;
+ }
+ c = *q;
+ *q = '\0';
+ if (mc_lstat (vpath, &mybuf) < 0)
+ {
+ MC_PTR_FREE (buf);
+ goto ret;
+ }
+ if (!S_ISLNK (mybuf.st_mode))
+ strcpy (r, p + 1);
+ else
+ {
+ int len;
+
+ len = mc_readlink (vpath, buf2, MC_MAXPATHLEN - 1);
+ if (len < 0)
+ {
+ MC_PTR_FREE (buf);
+ goto ret;
+ }
+ buf2[len] = '\0';
+ if (IS_PATH_SEP (*buf2))
+ strcpy (buf, buf2);
+ else
+ strcpy (r, buf2);
+ }
+ canonicalize_pathname (buf);
+ r = strchr (buf, '\0');
+ if (*r == '\0' || !IS_PATH_SEP (r[-1]))
+ /* FIXME: this condition is always true because r points to the EOL */
+ {
+ *r++ = PATH_SEP;
+ *r = '\0';
+ }
+ *q = c;
+ p = q;
+ }
+ while (c != '\0');
+
+ if (*buf == '\0')
+ strcpy (buf, PATH_SEP_STR);
+ else if (IS_PATH_SEP (r[-1]) && r != buf + 1)
+ r[-1] = '\0';
+
+ ret:
+ g_free (buf2);
+ g_free (p2);
+ return buf;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mc_util_write_backup_content (const char *from_file_name, const char *to_file_name)
+{
+ FILE *backup_fd;
+ char *contents;
+ gsize length;
+ gboolean ret1 = TRUE;
+
+ if (!g_file_get_contents (from_file_name, &contents, &length, NULL))
+ return FALSE;
+
+ backup_fd = fopen (to_file_name, "w");
+ if (backup_fd == NULL)
+ {
+ g_free (contents);
+ return FALSE;
+ }
+
+ if (fwrite ((const void *) contents, 1, length, backup_fd) != length)
+ ret1 = FALSE;
+
+ {
+ int ret2;
+
+ /* cppcheck-suppress redundantAssignment */
+ ret2 = fflush (backup_fd);
+ /* cppcheck-suppress redundantAssignment */
+ ret2 = fclose (backup_fd);
+ (void) ret2;
+ }
+
+ g_free (contents);
+ return ret1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+int
+is_printable (int c)
+{
+ c &= 0xff;
+
+#ifdef HAVE_CHARSET
+ /* "Display bits" is ignored, since the user controls the output
+ by setting the output codepage */
+ return is_8bit_printable (c);
+#else
+ if (!mc_global.eight_bit_clean)
+ return is_7bit_printable (c);
+
+ if (mc_global.full_eight_bits)
+ return is_8bit_printable (c);
+
+ return is_iso_printable (c);
+#endif /* !HAVE_CHARSET */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Quote the filename for the purpose of inserting it into the command
+ * line. If quote_percent is TRUE, replace "%" with "%%" - the percent is
+ * processed by the mc command line.
+ */
+char *
+name_quote (const char *s, gboolean quote_percent)
+{
+ GString *ret;
+
+ ret = g_string_sized_new (64);
+
+ if (*s == '-')
+ g_string_append (ret, "." PATH_SEP_STR);
+
+ for (; *s != '\0'; s++)
+ {
+ switch (*s)
+ {
+ case '%':
+ if (quote_percent)
+ g_string_append_c (ret, '%');
+ break;
+ case '\'':
+ case '\\':
+ case '\r':
+ case '\n':
+ case '\t':
+ case '"':
+ case ';':
+ case ' ':
+ case '?':
+ case '|':
+ case '[':
+ case ']':
+ case '{':
+ case '}':
+ case '<':
+ case '>':
+ case '`':
+ case '!':
+ case '$':
+ case '&':
+ case '*':
+ case '(':
+ case ')':
+ g_string_append_c (ret, '\\');
+ break;
+ case '~':
+ case '#':
+ if (ret->len == 0)
+ g_string_append_c (ret, '\\');
+ break;
+ default:
+ break;
+ }
+ g_string_append_c (ret, *s);
+ }
+
+ return g_string_free (ret, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+fake_name_quote (const char *s, gboolean quote_percent)
+{
+ (void) quote_percent;
+ return g_strdup (s);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * path_trunc() is the same as str_trunc() but
+ * it deletes possible password from path for security
+ * reasons.
+ */
+
+const char *
+path_trunc (const char *path, size_t trunc_len)
+{
+ vfs_path_t *vpath;
+ char *secure_path;
+ const char *ret;
+
+ vpath = vfs_path_from_str (path);
+ secure_path = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_PASSWORD);
+ vfs_path_free (vpath, TRUE);
+
+ ret = str_trunc (secure_path, trunc_len);
+ g_free (secure_path);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+size_trunc (uintmax_t size, gboolean use_si)
+{
+ static char x[BUF_TINY];
+ uintmax_t divisor = 1;
+ const char *xtra = _("B");
+
+ if (size > 999999999UL)
+ {
+ divisor = use_si ? 1000 : 1024;
+ xtra = use_si ? _("kB") : _("KiB");
+
+ if (size / divisor > 999999999UL)
+ {
+ divisor = use_si ? (1000 * 1000) : (1024 * 1024);
+ xtra = use_si ? _("MB") : _("MiB");
+
+ if (size / divisor > 999999999UL)
+ {
+ divisor = use_si ? (1000 * 1000 * 1000) : (1024 * 1024 * 1024);
+ xtra = use_si ? _("GB") : _("GiB");
+ }
+ }
+ }
+ g_snprintf (x, sizeof (x), "%.0f %s", 1.0 * size / divisor, xtra);
+ return x;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+size_trunc_sep (uintmax_t size, gboolean use_si)
+{
+ static char x[60];
+ int count;
+ const char *p, *y;
+ char *d;
+
+ p = y = size_trunc (size, use_si);
+ p += strlen (p) - 1;
+ d = x + sizeof (x) - 1;
+ *d-- = '\0';
+ /* @size format is "size unit", i.e. "[digits][space][letters]".
+ Copy all characters after digits. */
+ while (p >= y && !g_ascii_isdigit (*p))
+ *d-- = *p--;
+ for (count = 0; p >= y; count++)
+ {
+ if (count == 3)
+ {
+ *d-- = ',';
+ count = 0;
+ }
+ *d-- = *p--;
+ }
+ d++;
+ if (*d == ',')
+ d++;
+ return d;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Print file SIZE to BUFFER, but don't exceed LEN characters,
+ * not including trailing 0. BUFFER should be at least LEN+1 long.
+ * This function is called for every file on panels, so avoid
+ * floating point by any means.
+ *
+ * Units: size units (filesystem sizes are 1K blocks)
+ * 0=bytes, 1=Kbytes, 2=Mbytes, etc.
+ */
+
+void
+size_trunc_len (char *buffer, unsigned int len, uintmax_t size, int units, gboolean use_si)
+{
+ /* Avoid taking power for every file. */
+ /* *INDENT-OFF* */
+ static const uintmax_t power10[] = {
+ /* we hope that size of uintmax_t is 4 bytes at least */
+ 1ULL,
+ 10ULL,
+ 100ULL,
+ 1000ULL,
+ 10000ULL,
+ 100000ULL,
+ 1000000ULL,
+ 10000000ULL,
+ 100000000ULL,
+ 1000000000ULL
+ /* maximum value of uintmax_t (in case of 4 bytes) is
+ 4294967295
+ */
+#if SIZEOF_UINTMAX_T == 8
+ ,
+ 10000000000ULL,
+ 100000000000ULL,
+ 1000000000000ULL,
+ 10000000000000ULL,
+ 100000000000000ULL,
+ 1000000000000000ULL,
+ 10000000000000000ULL,
+ 100000000000000000ULL,
+ 1000000000000000000ULL,
+ 10000000000000000000ULL
+ /* maximum value of uintmax_t (in case of 8 bytes) is
+ 18447644073710439615
+ */
+#endif
+ };
+ /* *INDENT-ON* */
+ static const char *const suffix[] =
+ { "", "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q", NULL };
+ static const char *const suffix_lc[] =
+ { "", "k", "m", "g", "t", "p", "e", "z", "y", "r", "q", NULL };
+
+ const char *const *sfx = use_si ? suffix_lc : suffix;
+ int j = 0;
+
+ if (len == 0)
+ len = 9;
+#if SIZEOF_UINTMAX_T == 8
+ /* 20 decimal digits are required to represent 8 bytes */
+ else if (len > 19)
+ len = 19;
+#else
+ /* 10 decimal digits are required to represent 4 bytes */
+ else if (len > 9)
+ len = 9;
+#endif
+
+ /*
+ * recalculate from 1024 base to 1000 base if units>0
+ * We can't just multiply by 1024 - that might cause overflow
+ * if uintmax_t type is too small
+ */
+ if (use_si)
+ for (j = 0; j < units; j++)
+ {
+ uintmax_t size_remain;
+
+ size_remain = ((size % 125) * 1024) / 1000; /* size mod 125, recalculated */
+ size /= 125; /* 128/125 = 1024/1000 */
+ size *= 128; /* This will convert size from multiple of 1024 to multiple of 1000 */
+ size += size_remain; /* Re-add remainder lost by division/multiplication */
+ }
+
+ for (j = units; sfx[j] != NULL; j++)
+ {
+ if (size == 0)
+ {
+ if (j == units)
+ {
+ /* Empty files will print "0" even with minimal width. */
+ g_snprintf (buffer, len + 1, "%s", "0");
+ }
+ else
+ {
+ /* Use "~K" or just "K" if len is 1. Use "B" for bytes. */
+ g_snprintf (buffer, len + 1, (len > 1) ? "~%s" : "%s", (j > 1) ? sfx[j - 1] : "B");
+ }
+ break;
+ }
+
+ if (size < power10[len - (j > 0 ? 1 : 0)])
+ {
+ g_snprintf (buffer, len + 1, "%" PRIuMAX "%s", size, sfx[j]);
+ break;
+ }
+
+ /* Powers of 1000 or 1024, with rounding. */
+ if (use_si)
+ size = (size + 500) / 1000;
+ else
+ size = (size + 512) >> 10;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+string_perm (mode_t mode_bits)
+{
+ static char mode[11];
+
+ strcpy (mode, "----------");
+ if (S_ISDIR (mode_bits))
+ mode[0] = 'd';
+ if (S_ISCHR (mode_bits))
+ mode[0] = 'c';
+ if (S_ISBLK (mode_bits))
+ mode[0] = 'b';
+ if (S_ISLNK (mode_bits))
+ mode[0] = 'l';
+ if (S_ISFIFO (mode_bits))
+ mode[0] = 'p';
+ if (S_ISNAM (mode_bits))
+ mode[0] = 'n';
+ if (S_ISSOCK (mode_bits))
+ mode[0] = 's';
+ if (S_ISDOOR (mode_bits))
+ mode[0] = 'D';
+ if (ismode (mode_bits, S_IXOTH))
+ mode[9] = 'x';
+ if (ismode (mode_bits, S_IWOTH))
+ mode[8] = 'w';
+ if (ismode (mode_bits, S_IROTH))
+ mode[7] = 'r';
+ if (ismode (mode_bits, S_IXGRP))
+ mode[6] = 'x';
+ if (ismode (mode_bits, S_IWGRP))
+ mode[5] = 'w';
+ if (ismode (mode_bits, S_IRGRP))
+ mode[4] = 'r';
+ if (ismode (mode_bits, S_IXUSR))
+ mode[3] = 'x';
+ if (ismode (mode_bits, S_IWUSR))
+ mode[2] = 'w';
+ if (ismode (mode_bits, S_IRUSR))
+ mode[1] = 'r';
+#ifdef S_ISUID
+ if (ismode (mode_bits, S_ISUID))
+ mode[3] = (mode[3] == 'x') ? 's' : 'S';
+#endif /* S_ISUID */
+#ifdef S_ISGID
+ if (ismode (mode_bits, S_ISGID))
+ mode[6] = (mode[6] == 'x') ? 's' : 'S';
+#endif /* S_ISGID */
+#ifdef S_ISVTX
+ if (ismode (mode_bits, S_ISVTX))
+ mode[9] = (mode[9] == 'x') ? 't' : 'T';
+#endif /* S_ISVTX */
+ return mode;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+extension (const char *filename)
+{
+ const char *d;
+
+ d = strrchr (filename, '.');
+
+ return d != NULL ? d + 1 : "";
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+load_mc_home_file (const char *from, const char *filename, char **allocated_filename,
+ size_t * length)
+{
+ char *hintfile_base, *hintfile;
+ char *lang;
+ char *data;
+
+ hintfile_base = g_build_filename (from, filename, (char *) NULL);
+ lang = guess_message_value ();
+
+ hintfile = g_strconcat (hintfile_base, ".", lang, (char *) NULL);
+ if (!g_file_get_contents (hintfile, &data, length, NULL))
+ {
+ /* Fall back to the two-letter language code */
+ if (lang[0] != '\0' && lang[1] != '\0')
+ lang[2] = '\0';
+ g_free (hintfile);
+ hintfile = g_strconcat (hintfile_base, ".", lang, (char *) NULL);
+ if (!g_file_get_contents (hintfile, &data, length, NULL))
+ {
+ g_free (hintfile);
+ hintfile = hintfile_base;
+ g_file_get_contents (hintfile_base, &data, length, NULL);
+ }
+ }
+
+ g_free (lang);
+
+ if (hintfile != hintfile_base)
+ g_free (hintfile_base);
+
+ if (allocated_filename != NULL)
+ *allocated_filename = hintfile;
+ else
+ g_free (hintfile);
+
+ return data;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+extract_line (const char *s, const char *top)
+{
+ static char tmp_line[BUF_MEDIUM];
+ char *t = tmp_line;
+
+ while (*s != '\0' && *s != '\n' && (size_t) (t - tmp_line) < sizeof (tmp_line) - 1 && s < top)
+ *t++ = *s++;
+ *t = '\0';
+ return tmp_line;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * The basename routine
+ */
+
+const char *
+x_basename (const char *s)
+{
+ const char *url_delim, *path_sep;
+
+ url_delim = g_strrstr (s, VFS_PATH_URL_DELIMITER);
+ path_sep = strrchr (s, PATH_SEP);
+
+ if (path_sep == NULL)
+ return s;
+
+ if (url_delim == NULL
+ || url_delim < path_sep - strlen (VFS_PATH_URL_DELIMITER)
+ || url_delim - s + strlen (VFS_PATH_URL_DELIMITER) < strlen (s))
+ {
+ /* avoid trailing PATH_SEP, if present */
+ if (!IS_PATH_SEP (s[strlen (s) - 1]))
+ return path_sep + 1;
+
+ while (--path_sep > s && !IS_PATH_SEP (*path_sep))
+ ;
+ return (path_sep != s) ? path_sep + 1 : s;
+ }
+
+ while (--url_delim > s && !IS_PATH_SEP (*url_delim))
+ ;
+ while (--url_delim > s && !IS_PATH_SEP (*url_delim))
+ ;
+
+ return url_delim == s ? s : url_delim + 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+unix_error_string (int error_num)
+{
+ static char buffer[BUF_LARGE];
+ gchar *strerror_currentlocale;
+
+ strerror_currentlocale = g_locale_from_utf8 (g_strerror (error_num), -1, NULL, NULL, NULL);
+ g_snprintf (buffer, sizeof (buffer), "%s (%d)", strerror_currentlocale, error_num);
+ g_free (strerror_currentlocale);
+
+ return buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+skip_separators (const char *s)
+{
+ const char *su = s;
+
+ for (; *su != '\0'; str_cnext_char (&su))
+ if (!whitespace (*su) && *su != ',')
+ break;
+
+ return su;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+skip_numbers (const char *s)
+{
+ const char *su = s;
+
+ for (; *su != '\0'; str_cnext_char (&su))
+ if (!str_isdigit (su))
+ break;
+
+ return su;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Remove all control sequences from the argument string. We define
+ * "control sequence", in a sort of pidgin BNF, as follows:
+ *
+ * control-seq = Esc non-'['
+ * | Esc '[' (0 or more digits or ';' or ':' or '?') (any other char)
+ *
+ * The 256-color and true-color escape sequences should allow either ';' or ':' inside as separator,
+ * actually, ':' is the more correct according to ECMA-48.
+ * Some terminal emulators (e.g. xterm, gnome-terminal) support this.
+ *
+ * Non-printable characters are also removed.
+ */
+
+char *
+strip_ctrl_codes (char *s)
+{
+ char *w; /* Current position where the stripped data is written */
+ char *r; /* Current position where the original data is read */
+
+ if (s == NULL)
+ return NULL;
+
+ for (w = s, r = s; *r != '\0';)
+ {
+ if (*r == ESC_CHAR)
+ {
+ /* Skip the control sequence's arguments */ ;
+ /* '(' need to avoid strange 'B' letter in *Suse (if mc runs under root user) */
+ if (*(++r) == '[' || *r == '(')
+ {
+ /* strchr() matches trailing binary 0 */
+ while (*(++r) != '\0' && strchr ("0123456789;:?", *r) != NULL)
+ ;
+ }
+ else if (*r == ']')
+ {
+ /*
+ * Skip xterm's OSC (Operating System Command)
+ * http://www.xfree86.org/current/ctlseqs.html
+ * OSC P s ; P t ST
+ * OSC P s ; P t BEL
+ */
+ char *new_r;
+
+ for (new_r = r; *new_r != '\0'; new_r++)
+ {
+ switch (*new_r)
+ {
+ /* BEL */
+ case '\a':
+ r = new_r;
+ goto osc_out;
+ case ESC_CHAR:
+ /* ST */
+ if (new_r[1] == '\\')
+ {
+ r = new_r + 1;
+ goto osc_out;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ osc_out:
+ ;
+ }
+
+ /*
+ * Now we are at the last character of the sequence.
+ * Skip it unless it's binary 0.
+ */
+ if (*r != '\0')
+ r++;
+ }
+ else
+ {
+ char *n;
+
+ n = str_get_next_char (r);
+ if (str_isprint (r))
+ {
+ memmove (w, r, n - r);
+ w += n - r;
+ }
+ r = n;
+ }
+ }
+
+ *w = '\0';
+ return s;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+enum compression_type
+get_compression_type (int fd, const char *name)
+{
+ unsigned char magic[16];
+ size_t str_len;
+
+ /* Read the magic signature */
+ if (mc_read (fd, (char *) magic, 4) != 4)
+ return COMPRESSION_NONE;
+
+ /* GZIP_MAGIC and OLD_GZIP_MAGIC */
+ if (magic[0] == 0x1F && (magic[1] == 0x8B || magic[1] == 0x9E))
+ return COMPRESSION_GZIP;
+
+ /* PKZIP_MAGIC */
+ if (magic[0] == 'P' && magic[1] == 'K' && magic[2] == 0x03 && magic[3] == 0x04)
+ {
+ /* Read compression type */
+ mc_lseek (fd, 8, SEEK_SET);
+ if (mc_read (fd, (char *) magic, 2) != 2)
+ return COMPRESSION_NONE;
+
+ if ((magic[0] != 8 && magic[0] != 0) || magic[1] != 0)
+ return COMPRESSION_NONE;
+
+ return COMPRESSION_ZIP;
+ }
+
+ /* PACK_MAGIC and LZH_MAGIC and compress magic */
+ if (magic[0] == 0x1F && (magic[1] == 0x1E || magic[1] == 0xA0 || magic[1] == 0x9D))
+ /* Compatible with gzip */
+ return COMPRESSION_GZIP;
+
+ /* BZIP and BZIP2 files */
+ if ((magic[0] == 'B') && (magic[1] == 'Z') && (magic[3] >= '1') && (magic[3] <= '9'))
+ switch (magic[2])
+ {
+ case '0':
+ return COMPRESSION_BZIP;
+ case 'h':
+ return COMPRESSION_BZIP2;
+ default:
+ break;
+ }
+
+ /* LZ4 format - v1.5.0 - 0x184D2204 (little endian) */
+ if (magic[0] == 0x04 && magic[1] == 0x22 && magic[2] == 0x4d && magic[3] == 0x18)
+ return COMPRESSION_LZ4;
+
+ if (mc_read (fd, (char *) magic + 4, 2) != 2)
+ return COMPRESSION_NONE;
+
+ /* LZIP files */
+ if (magic[0] == 'L'
+ && magic[1] == 'Z'
+ && magic[2] == 'I' && magic[3] == 'P' && (magic[4] == 0x00 || magic[4] == 0x01))
+ return COMPRESSION_LZIP;
+
+ /* Support for LZMA (only utils format with magic in header).
+ * This is the default format of LZMA utils 4.32.1 and later. */
+ if (magic[0] == 0xFF
+ && magic[1] == 'L'
+ && magic[2] == 'Z' && magic[3] == 'M' && magic[4] == 'A' && magic[5] == 0x00)
+ return COMPRESSION_LZMA;
+
+ /* XZ compression magic */
+ if (magic[0] == 0xFD
+ && magic[1] == 0x37
+ && magic[2] == 0x7A && magic[3] == 0x58 && magic[4] == 0x5A && magic[5] == 0x00)
+ return COMPRESSION_XZ;
+
+ if (magic[0] == 0x28 && magic[1] == 0xB5 && magic[2] == 0x2F && magic[3] == 0xFD)
+ return COMPRESSION_ZSTD;
+
+ str_len = strlen (name);
+ /* HACK: we must believe to extension of LZMA file :) ... */
+ if ((str_len > 5 && strcmp (&name[str_len - 5], ".lzma") == 0) ||
+ (str_len > 4 && strcmp (&name[str_len - 4], ".tlz") == 0))
+ return COMPRESSION_LZMA;
+
+ return COMPRESSION_NONE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+decompress_extension (int type)
+{
+ switch (type)
+ {
+ case COMPRESSION_ZIP:
+ return "/uz" VFS_PATH_URL_DELIMITER;
+ case COMPRESSION_GZIP:
+ return "/ugz" VFS_PATH_URL_DELIMITER;
+ case COMPRESSION_BZIP:
+ return "/ubz" VFS_PATH_URL_DELIMITER;
+ case COMPRESSION_BZIP2:
+ return "/ubz2" VFS_PATH_URL_DELIMITER;
+ case COMPRESSION_LZIP:
+ return "/ulz" VFS_PATH_URL_DELIMITER;
+ case COMPRESSION_LZ4:
+ return "/ulz4" VFS_PATH_URL_DELIMITER;
+ case COMPRESSION_LZMA:
+ return "/ulzma" VFS_PATH_URL_DELIMITER;
+ case COMPRESSION_XZ:
+ return "/uxz" VFS_PATH_URL_DELIMITER;
+ case COMPRESSION_ZSTD:
+ return "/uzst" VFS_PATH_URL_DELIMITER;
+ default:
+ break;
+ }
+ /* Should never reach this place */
+ fprintf (stderr, "Fatal: decompress_extension called with an unknown argument\n");
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+wipe_password (char *passwd)
+{
+ if (passwd != NULL)
+ {
+ char *p;
+
+ for (p = passwd; *p != '\0'; p++)
+ *p = '\0';
+ g_free (passwd);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Convert "\E" -> esc character and ^x to control-x key and ^^ to ^ key
+ *
+ * @param p pointer to string
+ *
+ * @return newly allocated string
+ */
+
+char *
+convert_controls (const char *p)
+{
+ char *valcopy;
+ char *q;
+
+ valcopy = g_strdup (p);
+
+ /* Parse the escape special character */
+ for (q = valcopy; *p != '\0';)
+ switch (*p)
+ {
+ case '\\':
+ p++;
+
+ if (*p == 'e' || *p == 'E')
+ {
+ p++;
+ *q++ = ESC_CHAR;
+ }
+ break;
+
+ case '^':
+ p++;
+ if (*p == '^')
+ *q++ = *p++;
+ else
+ {
+ char c;
+
+ c = *p | 0x20;
+ if (c >= 'a' && c <= 'z')
+ {
+ *q++ = c - 'a' + 1;
+ p++;
+ }
+ else if (*p != '\0')
+ p++;
+ }
+ break;
+
+ default:
+ *q++ = *p++;
+ }
+
+ *q = '\0';
+ return valcopy;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Finds out a relative path from first to second, i.e. goes as many ..
+ * as needed up in first and then goes down using second
+ */
+
+char *
+diff_two_paths (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ int j, prevlen = -1, currlen;
+ char *my_first = NULL, *my_second = NULL;
+ char *buf = NULL;
+
+ my_first = resolve_symlinks (vpath1);
+ if (my_first == NULL)
+ goto ret;
+
+ my_second = resolve_symlinks (vpath2);
+ if (my_second == NULL)
+ goto ret;
+
+ for (j = 0; j < 2; j++)
+ {
+ char *p, *q;
+ int i;
+
+ p = my_first;
+ q = my_second;
+
+ while (TRUE)
+ {
+ char *r, *s;
+ ptrdiff_t len;
+
+ r = strchr (p, PATH_SEP);
+ if (r == NULL)
+ break;
+ s = strchr (q, PATH_SEP);
+ if (s == NULL)
+ break;
+
+ len = r - p;
+ if (len != (s - q) || strncmp (p, q, (size_t) len) != 0)
+ break;
+
+ p = r + 1;
+ q = s + 1;
+ }
+ p--;
+ for (i = 0; (p = strchr (p + 1, PATH_SEP)) != NULL; i++)
+ ;
+ currlen = (i + 1) * 3 + strlen (q) + 1;
+ if (j != 0)
+ {
+ if (currlen < prevlen)
+ g_free (buf);
+ else
+ goto ret;
+ }
+ p = buf = g_malloc (currlen);
+ prevlen = currlen;
+ for (; i >= 0; i--, p += 3)
+ strcpy (p, "../");
+ strcpy (p, q);
+ }
+
+ ret:
+ g_free (my_first);
+ g_free (my_second);
+ return buf;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Append text to GList, remove all entries with the same text
+ */
+
+GList *
+list_append_unique (GList * list, char *text)
+{
+ GList *lc_link;
+
+ /*
+ * Go to the last position and traverse the list backwards
+ * starting from the second last entry to make sure that we
+ * are not removing the current link.
+ */
+ list = g_list_append (list, text);
+ list = g_list_last (list);
+ lc_link = g_list_previous (list);
+
+ while (lc_link != NULL)
+ {
+ GList *newlink;
+
+ newlink = g_list_previous (lc_link);
+ if (strcmp ((char *) lc_link->data, text) == 0)
+ {
+ GList *tmp;
+
+ g_free (lc_link->data);
+ tmp = g_list_remove_link (list, lc_link);
+ (void) tmp;
+ g_list_free_1 (lc_link);
+ }
+ lc_link = newlink;
+ }
+
+ return list;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Read and restore position for the given filename.
+ * If there is no stored data, return line 1 and col 0.
+ */
+
+void
+load_file_position (const vfs_path_t * filename_vpath, long *line, long *column, off_t * offset,
+ GArray ** bookmarks)
+{
+ char *fn;
+ FILE *f;
+ char buf[MC_MAXPATHLEN + 100];
+ const size_t len = vfs_path_len (filename_vpath);
+
+ /* defaults */
+ *line = 1;
+ *column = 0;
+ *offset = 0;
+
+ /* open file with positions */
+ fn = mc_config_get_full_path (MC_FILEPOS_FILE);
+ f = fopen (fn, "r");
+ g_free (fn);
+ if (f == NULL)
+ return;
+
+ /* prepare array for serialized bookmarks */
+ if (bookmarks != NULL)
+ *bookmarks = g_array_sized_new (FALSE, FALSE, sizeof (size_t), MAX_SAVED_BOOKMARKS);
+
+ while (fgets (buf, sizeof (buf), f) != NULL)
+ {
+ const char *p;
+ gchar **pos_tokens;
+
+ /* check if the filename matches the beginning of string */
+ if (strncmp (buf, vfs_path_as_str (filename_vpath), len) != 0)
+ continue;
+
+ /* followed by single space */
+ if (buf[len] != ' ')
+ continue;
+
+ /* and string without spaces */
+ p = &buf[len + 1];
+ if (strchr (p, ' ') != NULL)
+ continue;
+
+ pos_tokens = g_strsplit (p, ";", 3 + MAX_SAVED_BOOKMARKS);
+ if (pos_tokens[0] == NULL)
+ {
+ *line = 1;
+ *column = 0;
+ *offset = 0;
+ }
+ else
+ {
+ *line = strtol (pos_tokens[0], NULL, 10);
+ if (pos_tokens[1] == NULL)
+ {
+ *column = 0;
+ *offset = 0;
+ }
+ else
+ {
+ *column = strtol (pos_tokens[1], NULL, 10);
+ if (pos_tokens[2] == NULL)
+ *offset = 0;
+ else if (bookmarks != NULL)
+ {
+ size_t i;
+
+ *offset = (off_t) g_ascii_strtoll (pos_tokens[2], NULL, 10);
+
+ for (i = 0; i < MAX_SAVED_BOOKMARKS && pos_tokens[3 + i] != NULL; i++)
+ {
+ size_t val;
+
+ val = strtoul (pos_tokens[3 + i], NULL, 10);
+ g_array_append_val (*bookmarks, val);
+ }
+ }
+ }
+ }
+
+ g_strfreev (pos_tokens);
+ }
+
+ fclose (f);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Save position for the given file
+ */
+
+void
+save_file_position (const vfs_path_t * filename_vpath, long line, long column, off_t offset,
+ GArray * bookmarks)
+{
+ static size_t filepos_max_saved_entries = 0;
+ char *fn, *tmp_fn;
+ FILE *f, *tmp_f;
+ char buf[MC_MAXPATHLEN + 100];
+ size_t i;
+ const size_t len = vfs_path_len (filename_vpath);
+ gboolean src_error = FALSE;
+
+ if (filepos_max_saved_entries == 0)
+ filepos_max_saved_entries = mc_config_get_int (mc_global.main_config, CONFIG_APP_SECTION,
+ "filepos_max_saved_entries", 1024);
+
+ fn = mc_config_get_full_path (MC_FILEPOS_FILE);
+ if (fn == NULL)
+ goto early_error;
+
+ mc_util_make_backup_if_possible (fn, TMP_SUFFIX);
+
+ /* open file */
+ f = fopen (fn, "w");
+ if (f == NULL)
+ goto open_target_error;
+
+ tmp_fn = g_strdup_printf ("%s" TMP_SUFFIX, fn);
+ tmp_f = fopen (tmp_fn, "r");
+ if (tmp_f == NULL)
+ {
+ src_error = TRUE;
+ goto open_source_error;
+ }
+
+ /* put the new record */
+ if (line != 1 || column != 0 || bookmarks != NULL)
+ {
+ if (fprintf
+ (f, "%s %ld;%ld;%" PRIuMAX, vfs_path_as_str (filename_vpath), line, column,
+ (uintmax_t) offset) < 0)
+ goto write_position_error;
+ if (bookmarks != NULL)
+ for (i = 0; i < bookmarks->len && i < MAX_SAVED_BOOKMARKS; i++)
+ if (fprintf (f, ";%zu", g_array_index (bookmarks, size_t, i)) < 0)
+ goto write_position_error;
+
+ if (fprintf (f, "\n") < 0)
+ goto write_position_error;
+ }
+
+ i = 1;
+ while (fgets (buf, sizeof (buf), tmp_f) != NULL)
+ {
+ if (buf[len] == ' ' && strncmp (buf, vfs_path_as_str (filename_vpath), len) == 0
+ && strchr (&buf[len + 1], ' ') == NULL)
+ continue;
+
+ fprintf (f, "%s", buf);
+ if (++i > filepos_max_saved_entries)
+ break;
+ }
+
+ write_position_error:
+ fclose (tmp_f);
+ open_source_error:
+ g_free (tmp_fn);
+ fclose (f);
+ if (src_error)
+ mc_util_restore_from_backup_if_possible (fn, TMP_SUFFIX);
+ else
+ mc_util_unlink_backup_if_possible (fn, TMP_SUFFIX);
+ open_target_error:
+ g_free (fn);
+ early_error:
+ if (bookmarks != NULL)
+ g_array_free (bookmarks, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+extern int
+ascii_alpha_to_cntrl (int ch)
+{
+ if ((ch >= ASCII_A && ch <= ASCII_Z) || (ch >= ASCII_a && ch <= ASCII_z))
+ ch &= 0x1f;
+
+ return ch;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+Q_ (const char *s)
+{
+ const char *result, *sep;
+
+ result = _(s);
+ sep = strchr (result, '|');
+
+ return sep != NULL ? sep + 1 : result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_util_make_backup_if_possible (const char *file_name, const char *backup_suffix)
+{
+ struct stat stat_buf;
+ char *backup_path;
+ gboolean ret;
+
+ if (!exist_file (file_name))
+ return FALSE;
+
+ backup_path = g_strdup_printf ("%s%s", file_name, backup_suffix);
+ if (backup_path == NULL)
+ return FALSE;
+
+ ret = mc_util_write_backup_content (file_name, backup_path);
+ if (ret)
+ {
+ /* Backup file will have same ownership with main file. */
+ if (stat (file_name, &stat_buf) == 0)
+ chmod (backup_path, stat_buf.st_mode);
+ else
+ chmod (backup_path, S_IRUSR | S_IWUSR);
+ }
+
+ g_free (backup_path);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_util_restore_from_backup_if_possible (const char *file_name, const char *backup_suffix)
+{
+ gboolean ret;
+ char *backup_path;
+
+ backup_path = g_strdup_printf ("%s%s", file_name, backup_suffix);
+ if (backup_path == NULL)
+ return FALSE;
+
+ ret = mc_util_write_backup_content (backup_path, file_name);
+ g_free (backup_path);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_util_unlink_backup_if_possible (const char *file_name, const char *backup_suffix)
+{
+ char *backup_path;
+
+ backup_path = g_strdup_printf ("%s%s", file_name, backup_suffix);
+ if (backup_path == NULL)
+ return FALSE;
+
+ if (exist_file (backup_path))
+ {
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str (backup_path);
+ mc_unlink (vpath);
+ vfs_path_free (vpath, TRUE);
+ }
+
+ g_free (backup_path);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * partly taken from dcigettext.c, returns "" for default locale
+ * value should be freed by calling function g_free()
+ */
+
+char *
+guess_message_value (void)
+{
+ static const char *const var[] = {
+ /* Setting of LC_ALL overwrites all other. */
+ /* Do not use LANGUAGE for check user locale and drowing hints */
+ "LC_ALL",
+ /* Next comes the name of the desired category. */
+ "LC_MESSAGES",
+ /* Last possibility is the LANG environment variable. */
+ "LANG",
+ /* NULL exit loops */
+ NULL
+ };
+
+ size_t i;
+ const char *locale = NULL;
+
+ for (i = 0; var[i] != NULL; i++)
+ {
+ locale = getenv (var[i]);
+ if (locale != NULL && locale[0] != '\0')
+ break;
+ }
+
+ if (locale == NULL)
+ locale = "";
+
+ return g_strdup (locale);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * The "profile root" is the tree under which all of MC's user data &
+ * settings are stored.
+ *
+ * It defaults to the user's home dir. The user may override this default
+ * with the environment variable $MC_PROFILE_ROOT.
+ */
+const char *
+mc_get_profile_root (void)
+{
+ static const char *profile_root = NULL;
+
+ if (profile_root == NULL)
+ {
+ profile_root = g_getenv ("MC_PROFILE_ROOT");
+ if (profile_root == NULL || *profile_root == '\0')
+ profile_root = mc_config_get_home_dir ();
+ }
+
+ return profile_root;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Propagate error in simple way.
+ *
+ * @param dest error return location
+ * @param code error code
+ * @param format printf()-style format for error message
+ * @param ... parameters for message format
+ */
+
+void
+mc_propagate_error (GError ** dest, int code, const char *format, ...)
+{
+ if (dest != NULL && *dest == NULL)
+ {
+ GError *tmp_error;
+ va_list args;
+
+ va_start (args, format);
+ tmp_error = g_error_new_valist (MC_ERROR, code, format, args);
+ va_end (args);
+
+ g_propagate_error (dest, tmp_error);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Replace existing error in simple way.
+ *
+ * @param dest error return location
+ * @param code error code
+ * @param format printf()-style format for error message
+ * @param ... parameters for message format
+ */
+
+void
+mc_replace_error (GError ** dest, int code, const char *format, ...)
+{
+ if (dest != NULL)
+ {
+ GError *tmp_error;
+ va_list args;
+
+ va_start (args, format);
+ tmp_error = g_error_new_valist (MC_ERROR, code, format, args);
+ va_end (args);
+
+ g_error_free (*dest);
+ *dest = NULL;
+ g_propagate_error (dest, tmp_error);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Returns if the given duration has elapsed since the given timestamp,
+ * and if it has then updates the timestamp.
+ *
+ * @param timestamp the last timestamp in microseconds, updated if the given time elapsed
+ * @param delay amount of time in microseconds
+
+ * @return TRUE if clock skew detected, FALSE otherwise
+ */
+gboolean
+mc_time_elapsed (gint64 * timestamp, gint64 delay)
+{
+ gint64 now;
+
+ now = g_get_monotonic_time ();
+
+ if (now >= *timestamp && now < *timestamp + delay)
+ return FALSE;
+
+ *timestamp = now;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */