Manual pages: mcmcdiffmceditmcview

root/lib/util.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. is_iso_printable
  2. is_8bit_printable
  3. resolve_symlinks
  4. mc_util_write_backup_content
  5. is_printable
  6. name_quote
  7. fake_name_quote
  8. path_trunc
  9. size_trunc
  10. size_trunc_sep
  11. size_trunc_len
  12. string_perm
  13. extension
  14. load_mc_home_file
  15. extract_line
  16. x_basename
  17. unix_error_string
  18. skip_separators
  19. skip_numbers
  20. get_compression_type
  21. decompress_extension
  22. wipe_password
  23. diff_two_paths
  24. list_append_unique
  25. load_file_position
  26. save_file_position
  27. ascii_alpha_to_cntrl
  28. Q_
  29. mc_util_make_backup_if_possible
  30. mc_util_restore_from_backup_if_possible
  31. mc_util_unlink_backup_if_possible
  32. guess_message_value
  33. mc_get_profile_root
  34. mc_propagate_error
  35. mc_replace_error
  36. mc_time_elapsed

   1 /*
   2    Various utilities
   3 
   4    Copyright (C) 1994-2025
   5    Free Software Foundation, Inc.
   6 
   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
  14 
  15    This file is part of the Midnight Commander.
  16 
  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.
  21 
  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.
  26 
  27    You should have received a copy of the GNU General Public License
  28    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  29  */
  30 
  31 /** \file lib/util.c
  32  *  \brief Source: various utilities
  33  */
  34 
  35 #include <config.h>
  36 
  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>
  47 
  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"
  54 
  55 /*** global variables ****************************************************************************/
  56 
  57 /*** file scope macro definitions ****************************************************************/
  58 
  59 #define ismode(n, m) ((n & m) == m)
  60 
  61 /* Number of attempts to create a temporary file */
  62 #ifndef TMP_MAX
  63 #define TMP_MAX 16384
  64 #endif
  65 
  66 #define TMP_SUFFIX ".tmp"
  67 
  68 #define ASCII_A    (0x40 + 1)
  69 #define ASCII_Z    (0x40 + 26)
  70 #define ASCII_a    (0x60 + 1)
  71 #define ASCII_z    (0x60 + 26)
  72 
  73 /*** file scope type declarations ****************************************************************/
  74 
  75 /*** forward declarations (file scope functions) *************************************************/
  76 
  77 /*** file scope variables ************************************************************************/
  78 
  79 /* --------------------------------------------------------------------------------------------- */
  80 /*** file scope functions ************************************************************************/
  81 /* --------------------------------------------------------------------------------------------- */
  82 
  83 static inline int
  84 is_iso_printable (unsigned char c)
     /* [previous][next][first][last][top][bottom][index][help]  */
  85 {
  86     return ((c > 31 && c < 127) || c >= 160);
  87 }
  88 
  89 /* --------------------------------------------------------------------------------------------- */
  90 
  91 static inline int
  92 is_8bit_printable (unsigned char c)
     /* [previous][next][first][last][top][bottom][index][help]  */
  93 {
  94     // "Full 8 bits output" doesn't work on xterm
  95     if (mc_global.tty.xterm_flag)
  96         return is_iso_printable (c);
  97 
  98     return (c > 31 && c != 127 && c != 155);
  99 }
 100 
 101 /* --------------------------------------------------------------------------------------------- */
 102 
 103 static char *
 104 resolve_symlinks (const vfs_path_t *vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
 105 {
 106     char *p, *p2;
 107     char *buf, *buf2, *q, *r, c;
 108     struct stat mybuf;
 109 
 110     if (vpath->relative)
 111         return NULL;
 112 
 113     p = p2 = g_strdup (vfs_path_as_str (vpath));
 114     r = buf = g_malloc (MC_MAXPATHLEN);
 115     buf2 = g_malloc (MC_MAXPATHLEN);
 116     *r++ = PATH_SEP;
 117     *r = '\0';
 118 
 119     do
 120     {
 121         q = strchr (p + 1, PATH_SEP);
 122         if (q == NULL)
 123         {
 124             q = strchr (p + 1, '\0');
 125             if (q == p + 1)
 126                 break;
 127         }
 128         c = *q;
 129         *q = '\0';
 130         if (mc_lstat (vpath, &mybuf) < 0)
 131         {
 132             MC_PTR_FREE (buf);
 133             goto ret;
 134         }
 135         if (!S_ISLNK (mybuf.st_mode))
 136             strcpy (r, p + 1);
 137         else
 138         {
 139             int len;
 140 
 141             len = mc_readlink (vpath, buf2, MC_MAXPATHLEN - 1);
 142             if (len < 0)
 143             {
 144                 MC_PTR_FREE (buf);
 145                 goto ret;
 146             }
 147             buf2[len] = '\0';
 148             if (IS_PATH_SEP (*buf2))
 149                 strcpy (buf, buf2);
 150             else
 151                 strcpy (r, buf2);
 152         }
 153         canonicalize_pathname (buf);
 154         r = strchr (buf, '\0');
 155         if (*r == '\0' || !IS_PATH_SEP (r[-1]))
 156         // FIXME: this condition is always true because r points to the EOL
 157         {
 158             *r++ = PATH_SEP;
 159             *r = '\0';
 160         }
 161         *q = c;
 162         p = q;
 163     }
 164     while (c != '\0');
 165 
 166     if (*buf == '\0')
 167         strcpy (buf, PATH_SEP_STR);
 168     else if (IS_PATH_SEP (r[-1]) && r != buf + 1)
 169         r[-1] = '\0';
 170 
 171 ret:
 172     g_free (buf2);
 173     g_free (p2);
 174     return buf;
 175 }
 176 
 177 /* --------------------------------------------------------------------------------------------- */
 178 
 179 static gboolean
 180 mc_util_write_backup_content (const char *from_file_name, const char *to_file_name)
     /* [previous][next][first][last][top][bottom][index][help]  */
 181 {
 182     FILE *backup_fd;
 183     char *contents;
 184     gsize length;
 185     gboolean ret1 = TRUE;
 186 
 187     if (!g_file_get_contents (from_file_name, &contents, &length, NULL))
 188         return FALSE;
 189 
 190     backup_fd = fopen (to_file_name, "w");
 191     if (backup_fd == NULL)
 192     {
 193         g_free (contents);
 194         return FALSE;
 195     }
 196 
 197     if (fwrite ((const void *) contents, 1, length, backup_fd) != length)
 198         ret1 = FALSE;
 199 
 200     {
 201         int ret2;
 202 
 203         // cppcheck-suppress redundantAssignment
 204         ret2 = fflush (backup_fd);
 205         // cppcheck-suppress redundantAssignment
 206         ret2 = fclose (backup_fd);
 207         (void) ret2;
 208     }
 209 
 210     g_free (contents);
 211     return ret1;
 212 }
 213 
 214 /* --------------------------------------------------------------------------------------------- */
 215 /*** public functions ****************************************************************************/
 216 /* --------------------------------------------------------------------------------------------- */
 217 
 218 int
 219 is_printable (int c)
     /* [previous][next][first][last][top][bottom][index][help]  */
 220 {
 221     /* "Display bits" is ignored, since the user controls the output
 222        by setting the output codepage */
 223     return is_8bit_printable (c & 0xff);
 224 }
 225 
 226 /* --------------------------------------------------------------------------------------------- */
 227 /**
 228  * Quote the filename for the purpose of inserting it into the command
 229  * line.  If quote_percent is TRUE, replace "%" with "%%" - the percent is
 230  * processed by the mc command line.
 231  */
 232 char *
 233 name_quote (const char *s, gboolean quote_percent)
     /* [previous][next][first][last][top][bottom][index][help]  */
 234 {
 235     GString *ret;
 236 
 237     if (s == NULL || *s == '\0')
 238         return NULL;
 239 
 240     ret = g_string_sized_new (64);
 241 
 242     if (*s == '-')
 243         g_string_append (ret, "." PATH_SEP_STR);
 244 
 245     for (; *s != '\0'; s++)
 246     {
 247         switch (*s)
 248         {
 249         case '%':
 250             if (quote_percent)
 251                 g_string_append_c (ret, '%');
 252             break;
 253         case '\'':
 254         case '\\':
 255         case '\r':
 256         case '\n':
 257         case '\t':
 258         case '"':
 259         case ';':
 260         case ' ':
 261         case '?':
 262         case '|':
 263         case '[':
 264         case ']':
 265         case '{':
 266         case '}':
 267         case '<':
 268         case '>':
 269         case '`':
 270         case '!':
 271         case '$':
 272         case '&':
 273         case '*':
 274         case '(':
 275         case ')':
 276             g_string_append_c (ret, '\\');
 277             break;
 278         case '~':
 279         case '#':
 280             if (ret->len == 0)
 281                 g_string_append_c (ret, '\\');
 282             break;
 283         default:
 284             break;
 285         }
 286         g_string_append_c (ret, *s);
 287     }
 288 
 289     return g_string_free (ret, ret->len == 0);
 290 }
 291 
 292 /* --------------------------------------------------------------------------------------------- */
 293 
 294 char *
 295 fake_name_quote (const char *s, gboolean quote_percent)
     /* [previous][next][first][last][top][bottom][index][help]  */
 296 {
 297     (void) quote_percent;
 298 
 299     return (s == NULL || *s == '\0' ? NULL : g_strdup (s));
 300 }
 301 
 302 /* --------------------------------------------------------------------------------------------- */
 303 /**
 304  * path_trunc() is the same as str_trunc() but
 305  * it deletes possible password from path for security
 306  * reasons.
 307  */
 308 
 309 const char *
 310 path_trunc (const char *path, size_t trunc_len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 311 {
 312     vfs_path_t *vpath;
 313     const char *ret;
 314 
 315     vpath = vfs_path_from_str_flags (path, VPF_STRIP_PASSWORD);
 316     ret = str_trunc (vfs_path_as_str (vpath), trunc_len);
 317     vfs_path_free (vpath, TRUE);
 318 
 319     return ret;
 320 }
 321 
 322 /* --------------------------------------------------------------------------------------------- */
 323 
 324 const char *
 325 size_trunc (uintmax_t size, gboolean use_si)
     /* [previous][next][first][last][top][bottom][index][help]  */
 326 {
 327     static char x[BUF_TINY];
 328     uintmax_t divisor = 1;
 329     const char *xtra = _ ("B");
 330 
 331     if (size > 999999999UL)
 332     {
 333         divisor = use_si ? 1000 : 1024;
 334         xtra = use_si ? _ ("kB") : _ ("KiB");
 335 
 336         if (size / divisor > 999999999UL)
 337         {
 338             divisor = use_si ? (1000 * 1000) : (1024 * 1024);
 339             xtra = use_si ? _ ("MB") : _ ("MiB");
 340 
 341             if (size / divisor > 999999999UL)
 342             {
 343                 divisor = use_si ? (1000 * 1000 * 1000) : (1024 * 1024 * 1024);
 344                 xtra = use_si ? _ ("GB") : _ ("GiB");
 345             }
 346         }
 347     }
 348     g_snprintf (x, sizeof (x), "%.0f %s", 1.0 * size / divisor, xtra);
 349     return x;
 350 }
 351 
 352 /* --------------------------------------------------------------------------------------------- */
 353 
 354 const char *
 355 size_trunc_sep (uintmax_t size, gboolean use_si)
     /* [previous][next][first][last][top][bottom][index][help]  */
 356 {
 357     static char x[60];
 358     int count;
 359     const char *p, *y;
 360     char *d;
 361 
 362     p = y = size_trunc (size, use_si);
 363     p += strlen (p) - 1;
 364     d = x + sizeof (x) - 1;
 365     *d-- = '\0';
 366     /* @size format is "size unit", i.e. "[digits][space][letters]".
 367        Copy all characters after digits. */
 368     while (p >= y && !g_ascii_isdigit (*p))
 369         *d-- = *p--;
 370     for (count = 0; p >= y; count++)
 371     {
 372         if (count == 3)
 373         {
 374             *d-- = ',';
 375             count = 0;
 376         }
 377         *d-- = *p--;
 378     }
 379     d++;
 380     if (*d == ',')
 381         d++;
 382     return d;
 383 }
 384 
 385 /* --------------------------------------------------------------------------------------------- */
 386 /**
 387  * Print file SIZE to BUFFER, but don't exceed LEN characters,
 388  * not including trailing 0. BUFFER should be at least LEN+1 long.
 389  * This function is called for every file on panels, so avoid
 390  * floating point by any means.
 391  *
 392  * Units: size units (filesystem sizes are 1K blocks)
 393  *    0=bytes, 1=Kbytes, 2=Mbytes, etc.
 394  */
 395 
 396 void
 397 size_trunc_len (char *buffer, unsigned int len, uintmax_t size, int units, gboolean use_si)
     /* [previous][next][first][last][top][bottom][index][help]  */
 398 {
 399     // Avoid taking power for every file.
 400     static const uintmax_t power10[] = {
 401         // we hope that size of uintmax_t is 4 bytes at least
 402         1ULL,
 403         10ULL,
 404         100ULL,
 405         1000ULL,
 406         10000ULL,
 407         100000ULL,
 408         1000000ULL,
 409         10000000ULL,
 410         100000000ULL,
 411         1000000000ULL
 412     /* maximum value of uintmax_t (in case of 4 bytes) is
 413         4294967295
 414      */
 415 #if SIZEOF_UINTMAX_T == 8
 416         ,
 417         10000000000ULL,
 418         100000000000ULL,
 419         1000000000000ULL,
 420         10000000000000ULL,
 421         100000000000000ULL,
 422         1000000000000000ULL,
 423         10000000000000000ULL,
 424         100000000000000000ULL,
 425         1000000000000000000ULL,
 426         10000000000000000000ULL,
 427     /* maximum value of uintmax_t (in case of 8 bytes) is
 428         18447644073710439615
 429      */
 430 #endif
 431     };
 432 
 433     static const char *const suffix[] = {
 434         "", "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q", NULL
 435     };
 436     static const char *const suffix_lc[] = {
 437         "", "k", "m", "g", "t", "p", "e", "z", "y", "r", "q", NULL,
 438     };
 439 
 440     static int sfx_last = -1;
 441 
 442     const char *const *sfx = use_si ? suffix_lc : suffix;
 443     int j = 0;
 444 
 445     if (sfx_last < 0)
 446     {
 447         for (sfx_last = 0; sfx[sfx_last] != NULL; sfx_last++)
 448             ;
 449 
 450         sfx_last--;
 451     }
 452 
 453     if (len == 0)
 454         len = 9;
 455 #if SIZEOF_UINTMAX_T == 8
 456     // 20 decimal digits are required to represent 8 bytes
 457     else if (len > 19)
 458         len = 19;
 459 #else
 460     // 10 decimal digits are required to represent 4 bytes
 461     else if (len > 9)
 462         len = 9;
 463 #endif
 464 
 465     const int units_safe = MIN (units, sfx_last);
 466 
 467     /*
 468      * recalculate from 1024 base to 1000 base if units>0
 469      * We can't just multiply by 1024 - that might cause overflow
 470      * if uintmax_t type is too small
 471      */
 472     if (use_si)
 473         for (j = 0; j < units_safe; j++)
 474         {
 475             uintmax_t size_remain;
 476 
 477             size_remain = ((size % 125) * 1024) / 1000;  // size mod 125, recalculated
 478             size /= 125;                                 // 128/125 = 1024/1000
 479             size *= 128;  // This will convert size from multiple of 1024 to multiple of 1000
 480             size += size_remain;  // Re-add remainder lost by division/multiplication
 481         }
 482 
 483     for (j = units_safe; sfx[j] != NULL; j++)
 484     {
 485         if (size == 0)
 486         {
 487             if (j == units)
 488             {
 489                 // Empty files will print "0" even with minimal width.
 490                 g_snprintf (buffer, len + 1, "%s", "0");
 491             }
 492             else
 493             {
 494                 // Use "~K" or just "K" if len is 1.  Use "B" for bytes.
 495                 g_snprintf (buffer, len + 1, (len > 1) ? "~%s" : "%s", (j > 1) ? sfx[j - 1] : "B");
 496             }
 497             break;
 498         }
 499 
 500         if (size < power10[len - (j > 0 ? 1 : 0)])
 501         {
 502             g_snprintf (buffer, len + 1, "%" PRIuMAX "%s", size, sfx[j]);
 503             break;
 504         }
 505 
 506         // Powers of 1000 or 1024, with rounding.
 507         if (use_si)
 508             size = (size + 500) / 1000;
 509         else
 510             size = (size + 512) >> 10;
 511     }
 512 }
 513 
 514 /* --------------------------------------------------------------------------------------------- */
 515 
 516 const char *
 517 string_perm (mode_t mode_bits)
     /* [previous][next][first][last][top][bottom][index][help]  */
 518 {
 519     static char mode[11];
 520 
 521     strcpy (mode, "----------");
 522     if (S_ISDIR (mode_bits))
 523         mode[0] = 'd';
 524     if (S_ISCHR (mode_bits))
 525         mode[0] = 'c';
 526     if (S_ISBLK (mode_bits))
 527         mode[0] = 'b';
 528     if (S_ISLNK (mode_bits))
 529         mode[0] = 'l';
 530     if (S_ISFIFO (mode_bits))
 531         mode[0] = 'p';
 532     if (S_ISNAM (mode_bits))
 533         mode[0] = 'n';
 534     if (S_ISSOCK (mode_bits))
 535         mode[0] = 's';
 536     if (S_ISDOOR (mode_bits))
 537         mode[0] = 'D';
 538     if (ismode (mode_bits, S_IXOTH))
 539         mode[9] = 'x';
 540     if (ismode (mode_bits, S_IWOTH))
 541         mode[8] = 'w';
 542     if (ismode (mode_bits, S_IROTH))
 543         mode[7] = 'r';
 544     if (ismode (mode_bits, S_IXGRP))
 545         mode[6] = 'x';
 546     if (ismode (mode_bits, S_IWGRP))
 547         mode[5] = 'w';
 548     if (ismode (mode_bits, S_IRGRP))
 549         mode[4] = 'r';
 550     if (ismode (mode_bits, S_IXUSR))
 551         mode[3] = 'x';
 552     if (ismode (mode_bits, S_IWUSR))
 553         mode[2] = 'w';
 554     if (ismode (mode_bits, S_IRUSR))
 555         mode[1] = 'r';
 556 #ifdef S_ISUID
 557     if (ismode (mode_bits, S_ISUID))
 558         mode[3] = (mode[3] == 'x') ? 's' : 'S';
 559 #endif
 560 #ifdef S_ISGID
 561     if (ismode (mode_bits, S_ISGID))
 562         mode[6] = (mode[6] == 'x') ? 's' : 'S';
 563 #endif
 564 #ifdef S_ISVTX
 565     if (ismode (mode_bits, S_ISVTX))
 566         mode[9] = (mode[9] == 'x') ? 't' : 'T';
 567 #endif
 568     return mode;
 569 }
 570 
 571 /* --------------------------------------------------------------------------------------------- */
 572 
 573 const char *
 574 extension (const char *filename)
     /* [previous][next][first][last][top][bottom][index][help]  */
 575 {
 576     const char *d;
 577 
 578     d = strrchr (filename, '.');
 579 
 580     return d != NULL ? d + 1 : "";
 581 }
 582 
 583 /* --------------------------------------------------------------------------------------------- */
 584 
 585 char *
 586 load_mc_home_file (const char *from, const char *filename, char **allocated_filename,
     /* [previous][next][first][last][top][bottom][index][help]  */
 587                    size_t *length)
 588 {
 589     char *hintfile_base, *hintfile;
 590     char *lang;
 591     char *data;
 592 
 593     hintfile_base = g_build_filename (from, filename, (char *) NULL);
 594     lang = guess_message_value ();
 595 
 596     hintfile = g_strconcat (hintfile_base, ".", lang, (char *) NULL);
 597     if (!g_file_get_contents (hintfile, &data, length, NULL))
 598     {
 599         // Fall back to the two-letter language code
 600         if (lang[0] != '\0' && lang[1] != '\0')
 601             lang[2] = '\0';
 602         g_free (hintfile);
 603         hintfile = g_strconcat (hintfile_base, ".", lang, (char *) NULL);
 604         if (!g_file_get_contents (hintfile, &data, length, NULL))
 605         {
 606             g_free (hintfile);
 607             hintfile = hintfile_base;
 608             g_file_get_contents (hintfile_base, &data, length, NULL);
 609         }
 610     }
 611 
 612     g_free (lang);
 613 
 614     if (hintfile != hintfile_base)
 615         g_free (hintfile_base);
 616 
 617     if (allocated_filename != NULL)
 618         *allocated_filename = hintfile;
 619     else
 620         g_free (hintfile);
 621 
 622     return data;
 623 }
 624 
 625 /* --------------------------------------------------------------------------------------------- */
 626 
 627 const char *
 628 extract_line (const char *s, const char *top, size_t *len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 629 {
 630     static char tmp_line[BUF_MEDIUM];
 631     char *t = tmp_line;
 632 
 633     while (*s != '\0' && *s != '\n' && (size_t) (t - tmp_line) < sizeof (tmp_line) - 1 && s < top)
 634         *t++ = *s++;
 635     *t = '\0';
 636 
 637     if (len != NULL)
 638         *len = (size_t) (t - tmp_line);
 639 
 640     return tmp_line;
 641 }
 642 
 643 /* --------------------------------------------------------------------------------------------- */
 644 /**
 645  * The basename routine
 646  */
 647 
 648 const char *
 649 x_basename (const char *s)
     /* [previous][next][first][last][top][bottom][index][help]  */
 650 {
 651     const char *url_delim, *path_sep;
 652 
 653     url_delim = g_strrstr (s, VFS_PATH_URL_DELIMITER);
 654     path_sep = strrchr (s, PATH_SEP);
 655 
 656     if (path_sep == NULL)
 657         return s;
 658 
 659     if (url_delim == NULL || url_delim < path_sep - strlen (VFS_PATH_URL_DELIMITER)
 660         || url_delim - s + strlen (VFS_PATH_URL_DELIMITER) < strlen (s))
 661     {
 662         // avoid trailing PATH_SEP, if present
 663         if (!IS_PATH_SEP (s[strlen (s) - 1]))
 664             return path_sep + 1;
 665 
 666         while (--path_sep > s && !IS_PATH_SEP (*path_sep))
 667             ;
 668         return (path_sep != s) ? path_sep + 1 : s;
 669     }
 670 
 671     while (--url_delim > s && !IS_PATH_SEP (*url_delim))
 672         ;
 673     while (--url_delim > s && !IS_PATH_SEP (*url_delim))
 674         ;
 675 
 676     return url_delim == s ? s : url_delim + 1;
 677 }
 678 
 679 /* --------------------------------------------------------------------------------------------- */
 680 
 681 const char *
 682 unix_error_string (int error_num)
     /* [previous][next][first][last][top][bottom][index][help]  */
 683 {
 684     static char buffer[BUF_LARGE];
 685     gchar *strerror_currentlocale;
 686 
 687     strerror_currentlocale = g_locale_from_utf8 (g_strerror (error_num), -1, NULL, NULL, NULL);
 688     g_snprintf (buffer, sizeof (buffer), "%s (%d)", strerror_currentlocale, error_num);
 689     g_free (strerror_currentlocale);
 690 
 691     return buffer;
 692 }
 693 
 694 /* --------------------------------------------------------------------------------------------- */
 695 
 696 const char *
 697 skip_separators (const char *s)
     /* [previous][next][first][last][top][bottom][index][help]  */
 698 {
 699     const char *su = s;
 700 
 701     for (; *su != '\0'; str_cnext_char (&su))
 702         if (!whitespace (*su) && *su != ',')
 703             break;
 704 
 705     return su;
 706 }
 707 
 708 /* --------------------------------------------------------------------------------------------- */
 709 
 710 const char *
 711 skip_numbers (const char *s)
     /* [previous][next][first][last][top][bottom][index][help]  */
 712 {
 713     const char *su = s;
 714 
 715     for (; *su != '\0'; str_cnext_char (&su))
 716         if (!str_isdigit (su))
 717             break;
 718 
 719     return su;
 720 }
 721 
 722 /* --------------------------------------------------------------------------------------------- */
 723 
 724 enum compression_type
 725 get_compression_type (int fd, const char *name)
     /* [previous][next][first][last][top][bottom][index][help]  */
 726 {
 727     unsigned char magic[16];
 728     size_t str_len;
 729 
 730     // Read the magic signature
 731     if (mc_read (fd, (char *) magic, 4) != 4)
 732         return COMPRESSION_NONE;
 733 
 734     // GZIP_MAGIC and OLD_GZIP_MAGIC
 735     if (magic[0] == 0x1F && (magic[1] == 0x8B || magic[1] == 0x9E))
 736         return COMPRESSION_GZIP;
 737 
 738     // PKZIP_MAGIC
 739     if (magic[0] == 'P' && magic[1] == 'K' && magic[2] == 0x03 && magic[3] == 0x04)
 740     {
 741         // Read compression type
 742         mc_lseek (fd, 8, SEEK_SET);
 743         if (mc_read (fd, (char *) magic, 2) != 2)
 744             return COMPRESSION_NONE;
 745 
 746         if ((magic[0] != 8 && magic[0] != 0) || magic[1] != 0)
 747             return COMPRESSION_NONE;
 748 
 749         return COMPRESSION_ZIP;
 750     }
 751 
 752     // PACK_MAGIC and LZH_MAGIC and compress magic
 753     if (magic[0] == 0x1F && (magic[1] == 0x1E || magic[1] == 0xA0 || magic[1] == 0x9D))
 754         // Compatible with gzip
 755         return COMPRESSION_GZIP;
 756 
 757     // BZIP and BZIP2 files
 758     if ((magic[0] == 'B') && (magic[1] == 'Z') && (magic[3] >= '1') && (magic[3] <= '9'))
 759         switch (magic[2])
 760         {
 761         case '0':
 762             return COMPRESSION_BZIP;
 763         case 'h':
 764             return COMPRESSION_BZIP2;
 765         default:
 766             break;
 767         }
 768 
 769     // LZ4 format - v1.5.0 - 0x184D2204 (little endian)
 770     if (magic[0] == 0x04 && magic[1] == 0x22 && magic[2] == 0x4d && magic[3] == 0x18)
 771         return COMPRESSION_LZ4;
 772 
 773     if (mc_read (fd, (char *) magic + 4, 2) != 2)
 774         return COMPRESSION_NONE;
 775 
 776     // LZIP files
 777     if (magic[0] == 'L' && magic[1] == 'Z' && magic[2] == 'I' && magic[3] == 'P'
 778         && (magic[4] == 0x00 || magic[4] == 0x01))
 779         return COMPRESSION_LZIP;
 780 
 781     /* Support for LZMA (only utils format with magic in header).
 782      * This is the default format of LZMA utils 4.32.1 and later. */
 783     if (magic[0] == 0xFF && magic[1] == 'L' && magic[2] == 'Z' && magic[3] == 'M' && magic[4] == 'A'
 784         && magic[5] == 0x00)
 785         return COMPRESSION_LZMA;
 786 
 787     // LZO format - \x89\x4c\x5a\x4f\x00\x0d\x0a\x1a\x0a    lzop compressed data
 788     if (magic[0] == 0x89 && magic[1] == 0x4c && magic[2] == 0x5a && magic[3] == 0x4f
 789         && magic[4] == 0x00 && magic[5] == 0x0d)
 790         return COMPRESSION_LZO;
 791 
 792     // XZ compression magic
 793     if (magic[0] == 0xFD && magic[1] == 0x37 && magic[2] == 0x7A && magic[3] == 0x58
 794         && magic[4] == 0x5A && magic[5] == 0x00)
 795         return COMPRESSION_XZ;
 796 
 797     if (magic[0] == 0x28 && magic[1] == 0xB5 && magic[2] == 0x2F && magic[3] == 0xFD)
 798         return COMPRESSION_ZSTD;
 799 
 800     str_len = strlen (name);
 801     // HACK: we must believe to extension of LZMA file :) ...
 802     if ((str_len > 5 && strcmp (&name[str_len - 5], ".lzma") == 0)
 803         || (str_len > 4 && strcmp (&name[str_len - 4], ".tlz") == 0))
 804         return COMPRESSION_LZMA;
 805 
 806     return COMPRESSION_NONE;
 807 }
 808 
 809 /* --------------------------------------------------------------------------------------------- */
 810 
 811 const char *
 812 decompress_extension (int type)
     /* [previous][next][first][last][top][bottom][index][help]  */
 813 {
 814     switch (type)
 815     {
 816     case COMPRESSION_ZIP:
 817         return "/uz" VFS_PATH_URL_DELIMITER;
 818     case COMPRESSION_GZIP:
 819         return "/ugz" VFS_PATH_URL_DELIMITER;
 820     case COMPRESSION_BZIP:
 821         return "/ubz" VFS_PATH_URL_DELIMITER;
 822     case COMPRESSION_BZIP2:
 823         return "/ubz2" VFS_PATH_URL_DELIMITER;
 824     case COMPRESSION_LZIP:
 825         return "/ulz" VFS_PATH_URL_DELIMITER;
 826     case COMPRESSION_LZ4:
 827         return "/ulz4" VFS_PATH_URL_DELIMITER;
 828     case COMPRESSION_LZMA:
 829         return "/ulzma" VFS_PATH_URL_DELIMITER;
 830     case COMPRESSION_LZO:
 831         return "/ulzo" VFS_PATH_URL_DELIMITER;
 832     case COMPRESSION_XZ:
 833         return "/uxz" VFS_PATH_URL_DELIMITER;
 834     case COMPRESSION_ZSTD:
 835         return "/uzst" VFS_PATH_URL_DELIMITER;
 836     default:
 837         break;
 838     }
 839     // Should never reach this place
 840     fprintf (stderr, "Fatal: decompress_extension called with an unknown argument\n");
 841     return 0;
 842 }
 843 
 844 /* --------------------------------------------------------------------------------------------- */
 845 
 846 void
 847 wipe_password (char *passwd)
     /* [previous][next][first][last][top][bottom][index][help]  */
 848 {
 849     if (passwd != NULL)
 850     {
 851         char *p;
 852 
 853         for (p = passwd; *p != '\0'; p++)
 854             *p = '\0';
 855         g_free (passwd);
 856     }
 857 }
 858 
 859 /* --------------------------------------------------------------------------------------------- */
 860 /**
 861  * Finds out a relative path from first to second, i.e. goes as many ..
 862  * as needed up in first and then goes down using second
 863  */
 864 
 865 char *
 866 diff_two_paths (const vfs_path_t *vpath1, const vfs_path_t *vpath2)
     /* [previous][next][first][last][top][bottom][index][help]  */
 867 {
 868     int j, prevlen = -1, currlen;
 869     char *my_first = NULL, *my_second = NULL;
 870     char *buf = NULL;
 871 
 872     my_first = resolve_symlinks (vpath1);
 873     if (my_first == NULL)
 874         goto ret;
 875 
 876     my_second = resolve_symlinks (vpath2);
 877     if (my_second == NULL)
 878         goto ret;
 879 
 880     for (j = 0; j < 2; j++)
 881     {
 882         char *p, *q;
 883         int i;
 884 
 885         p = my_first;
 886         q = my_second;
 887 
 888         while (TRUE)
 889         {
 890             char *r, *s;
 891             ptrdiff_t len;
 892 
 893             r = strchr (p, PATH_SEP);
 894             if (r == NULL)
 895                 break;
 896             s = strchr (q, PATH_SEP);
 897             if (s == NULL)
 898                 break;
 899 
 900             len = r - p;
 901             if (len != (s - q) || strncmp (p, q, (size_t) len) != 0)
 902                 break;
 903 
 904             p = r + 1;
 905             q = s + 1;
 906         }
 907         p--;
 908         for (i = 0; (p = strchr (p + 1, PATH_SEP)) != NULL; i++)
 909             ;
 910         currlen = (i + 1) * 3 + strlen (q) + 1;
 911         if (j != 0)
 912         {
 913             if (currlen < prevlen)
 914                 g_free (buf);
 915             else
 916                 goto ret;
 917         }
 918         p = buf = g_malloc (currlen);
 919         prevlen = currlen;
 920         for (; i >= 0; i--, p += 3)
 921             strcpy (p, "../");
 922         strcpy (p, q);
 923     }
 924 
 925 ret:
 926     g_free (my_first);
 927     g_free (my_second);
 928     return buf;
 929 }
 930 
 931 /* --------------------------------------------------------------------------------------------- */
 932 /**
 933  * Append text to GList, remove all entries with the same text
 934  */
 935 
 936 GList *
 937 list_append_unique (GList *list, char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 938 {
 939     GList *lc_link;
 940 
 941     /*
 942      * Go to the last position and traverse the list backwards
 943      * starting from the second last entry to make sure that we
 944      * are not removing the current link.
 945      */
 946     list = g_list_append (list, text);
 947     list = g_list_last (list);
 948     lc_link = g_list_previous (list);
 949 
 950     while (lc_link != NULL)
 951     {
 952         GList *newlink;
 953 
 954         newlink = g_list_previous (lc_link);
 955         if (strcmp ((char *) lc_link->data, text) == 0)
 956         {
 957             GList *tmp;
 958 
 959             g_free (lc_link->data);
 960             tmp = g_list_remove_link (list, lc_link);
 961             (void) tmp;
 962             g_list_free_1 (lc_link);
 963         }
 964         lc_link = newlink;
 965     }
 966 
 967     return list;
 968 }
 969 
 970 /* --------------------------------------------------------------------------------------------- */
 971 /**
 972  * Read and restore position for the given filename.
 973  * If there is no stored data, return line 1 and col 0.
 974  */
 975 
 976 void
 977 load_file_position (const vfs_path_t *filename_vpath, long *line, long *column, off_t *offset,
     /* [previous][next][first][last][top][bottom][index][help]  */
 978                     GArray **bookmarks)
 979 {
 980     char *fn;
 981     FILE *f;
 982     char buf[MC_MAXPATHLEN + 100];
 983     const size_t len = vfs_path_len (filename_vpath);
 984 
 985     // defaults
 986     *line = 1;
 987     *column = 0;
 988     *offset = 0;
 989 
 990     // open file with positions
 991     fn = mc_config_get_full_path (MC_FILEPOS_FILE);
 992     f = fopen (fn, "r");
 993     g_free (fn);
 994     if (f == NULL)
 995         return;
 996 
 997     // prepare array for serialized bookmarks
 998     if (bookmarks != NULL)
 999         *bookmarks = g_array_sized_new (FALSE, FALSE, sizeof (size_t), MAX_SAVED_BOOKMARKS);
1000 
1001     while (fgets (buf, sizeof (buf), f) != NULL)
1002     {
1003         const char *p;
1004         gchar **pos_tokens;
1005 
1006         // check if the filename matches the beginning of string
1007         if (strncmp (buf, vfs_path_as_str (filename_vpath), len) != 0)
1008             continue;
1009 
1010         // followed by single space
1011         if (buf[len] != ' ')
1012             continue;
1013 
1014         // and string without spaces
1015         p = &buf[len + 1];
1016         if (strchr (p, ' ') != NULL)
1017             continue;
1018 
1019         pos_tokens = g_strsplit (p, ";", 3 + MAX_SAVED_BOOKMARKS);
1020         if (pos_tokens[0] == NULL)
1021         {
1022             *line = 1;
1023             *column = 0;
1024             *offset = 0;
1025         }
1026         else
1027         {
1028             *line = strtol (pos_tokens[0], NULL, 10);
1029             if (pos_tokens[1] == NULL)
1030             {
1031                 *column = 0;
1032                 *offset = 0;
1033             }
1034             else
1035             {
1036                 *column = strtol (pos_tokens[1], NULL, 10);
1037                 if (pos_tokens[2] == NULL)
1038                     *offset = 0;
1039                 else if (bookmarks != NULL)
1040                 {
1041                     size_t i;
1042 
1043                     *offset = (off_t) g_ascii_strtoll (pos_tokens[2], NULL, 10);
1044 
1045                     for (i = 0; i < MAX_SAVED_BOOKMARKS && pos_tokens[3 + i] != NULL; i++)
1046                     {
1047                         size_t val;
1048 
1049                         val = strtoul (pos_tokens[3 + i], NULL, 10);
1050                         g_array_append_val (*bookmarks, val);
1051                     }
1052                 }
1053             }
1054         }
1055 
1056         g_strfreev (pos_tokens);
1057     }
1058 
1059     fclose (f);
1060 }
1061 
1062 /* --------------------------------------------------------------------------------------------- */
1063 /**
1064  * Save position for the given file
1065  */
1066 
1067 void
1068 save_file_position (const vfs_path_t *filename_vpath, long line, long column, off_t offset,
     /* [previous][next][first][last][top][bottom][index][help]  */
1069                     GArray *bookmarks)
1070 {
1071     static size_t filepos_max_saved_entries = 0;
1072     char *fn, *tmp_fn;
1073     FILE *f, *tmp_f;
1074     char buf[MC_MAXPATHLEN + 100];
1075     size_t i;
1076     const size_t len = vfs_path_len (filename_vpath);
1077     gboolean src_error = FALSE;
1078 
1079     if (filepos_max_saved_entries == 0)
1080         filepos_max_saved_entries = mc_config_get_int (mc_global.main_config, CONFIG_APP_SECTION,
1081                                                        "filepos_max_saved_entries", 1024);
1082 
1083     fn = mc_config_get_full_path (MC_FILEPOS_FILE);
1084     if (fn == NULL)
1085         goto early_error;
1086 
1087     mc_util_make_backup_if_possible (fn, TMP_SUFFIX);
1088 
1089     // open file
1090     f = fopen (fn, "w");
1091     if (f == NULL)
1092         goto open_target_error;
1093 
1094     tmp_fn = g_strdup_printf ("%s" TMP_SUFFIX, fn);
1095     tmp_f = fopen (tmp_fn, "r");
1096     if (tmp_f == NULL)
1097     {
1098         src_error = TRUE;
1099         goto open_source_error;
1100     }
1101 
1102     // put the new record
1103     if (line != 1 || column != 0 || bookmarks != NULL)
1104     {
1105         if (fprintf (f, "%s %ld;%ld;%" PRIuMAX, vfs_path_as_str (filename_vpath), line, column,
1106                      (uintmax_t) offset)
1107             < 0)
1108             goto write_position_error;
1109         if (bookmarks != NULL)
1110             for (i = 0; i < bookmarks->len && i < MAX_SAVED_BOOKMARKS; i++)
1111                 if (fprintf (f, ";%zu", g_array_index (bookmarks, size_t, i)) < 0)
1112                     goto write_position_error;
1113 
1114         if (fprintf (f, "\n") < 0)
1115             goto write_position_error;
1116     }
1117 
1118     i = 1;
1119     while (fgets (buf, sizeof (buf), tmp_f) != NULL)
1120     {
1121         if (buf[len] == ' ' && strncmp (buf, vfs_path_as_str (filename_vpath), len) == 0
1122             && strchr (&buf[len + 1], ' ') == NULL)
1123             continue;
1124 
1125         fprintf (f, "%s", buf);
1126         if (++i > filepos_max_saved_entries)
1127             break;
1128     }
1129 
1130 write_position_error:
1131     fclose (tmp_f);
1132 open_source_error:
1133     g_free (tmp_fn);
1134     fclose (f);
1135     if (src_error)
1136         mc_util_restore_from_backup_if_possible (fn, TMP_SUFFIX);
1137     else
1138         mc_util_unlink_backup_if_possible (fn, TMP_SUFFIX);
1139 open_target_error:
1140     g_free (fn);
1141 early_error:
1142     if (bookmarks != NULL)
1143         g_array_free (bookmarks, TRUE);
1144 }
1145 
1146 /* --------------------------------------------------------------------------------------------- */
1147 
1148 extern int
1149 ascii_alpha_to_cntrl (int ch)
     /* [previous][next][first][last][top][bottom][index][help]  */
1150 {
1151     if ((ch >= ASCII_A && ch <= ASCII_Z) || (ch >= ASCII_a && ch <= ASCII_z))
1152         ch &= 0x1f;
1153 
1154     return ch;
1155 }
1156 
1157 /* --------------------------------------------------------------------------------------------- */
1158 
1159 const char *
1160 Q_ (const char *s)
     /* [previous][next][first][last][top][bottom][index][help]  */
1161 {
1162     const char *result, *sep;
1163 
1164     result = _ (s);
1165     sep = strchr (result, '|');
1166 
1167     return sep != NULL ? sep + 1 : result;
1168 }
1169 
1170 /* --------------------------------------------------------------------------------------------- */
1171 
1172 gboolean
1173 mc_util_make_backup_if_possible (const char *file_name, const char *backup_suffix)
     /* [previous][next][first][last][top][bottom][index][help]  */
1174 {
1175     struct stat stat_buf;
1176     char *backup_path;
1177     gboolean ret;
1178 
1179     if (!exist_file (file_name))
1180         return FALSE;
1181 
1182     backup_path = g_strdup_printf ("%s%s", file_name, backup_suffix);
1183     if (backup_path == NULL)
1184         return FALSE;
1185 
1186     ret = mc_util_write_backup_content (file_name, backup_path);
1187     if (ret)
1188     {
1189         // Backup file will have same ownership with main file.
1190         if (stat (file_name, &stat_buf) == 0)
1191             chmod (backup_path, stat_buf.st_mode);
1192         else
1193             chmod (backup_path, S_IRUSR | S_IWUSR);
1194     }
1195 
1196     g_free (backup_path);
1197 
1198     return ret;
1199 }
1200 
1201 /* --------------------------------------------------------------------------------------------- */
1202 
1203 gboolean
1204 mc_util_restore_from_backup_if_possible (const char *file_name, const char *backup_suffix)
     /* [previous][next][first][last][top][bottom][index][help]  */
1205 {
1206     gboolean ret;
1207     char *backup_path;
1208 
1209     backup_path = g_strdup_printf ("%s%s", file_name, backup_suffix);
1210     if (backup_path == NULL)
1211         return FALSE;
1212 
1213     ret = mc_util_write_backup_content (backup_path, file_name);
1214     g_free (backup_path);
1215 
1216     return ret;
1217 }
1218 
1219 /* --------------------------------------------------------------------------------------------- */
1220 
1221 gboolean
1222 mc_util_unlink_backup_if_possible (const char *file_name, const char *backup_suffix)
     /* [previous][next][first][last][top][bottom][index][help]  */
1223 {
1224     char *backup_path;
1225 
1226     backup_path = g_strdup_printf ("%s%s", file_name, backup_suffix);
1227     if (backup_path == NULL)
1228         return FALSE;
1229 
1230     if (exist_file (backup_path))
1231     {
1232         vfs_path_t *vpath;
1233 
1234         vpath = vfs_path_from_str (backup_path);
1235         mc_unlink (vpath);
1236         vfs_path_free (vpath, TRUE);
1237     }
1238 
1239     g_free (backup_path);
1240     return TRUE;
1241 }
1242 
1243 /* --------------------------------------------------------------------------------------------- */
1244 /**
1245  * partly taken from dcigettext.c, returns "" for default locale
1246  * value should be freed by calling function g_free()
1247  */
1248 
1249 char *
1250 guess_message_value (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1251 {
1252     static const char *const var[] = {
1253         // Setting of LC_ALL overwrites all other.
1254         // Do not use LANGUAGE for check user locale and drowing hints
1255         "LC_ALL",
1256         // Next comes the name of the desired category.
1257         "LC_MESSAGES",
1258         // Last possibility is the LANG environment variable.
1259         "LANG",
1260         // NULL exit loops
1261         NULL
1262     };
1263 
1264     size_t i;
1265     const char *locale = NULL;
1266 
1267     for (i = 0; var[i] != NULL; i++)
1268     {
1269         locale = getenv (var[i]);
1270         if (locale != NULL && locale[0] != '\0')
1271             break;
1272     }
1273 
1274     if (locale == NULL)
1275         locale = "";
1276 
1277     return g_strdup (locale);
1278 }
1279 
1280 /* --------------------------------------------------------------------------------------------- */
1281 
1282 /**
1283  * The "profile root" is the tree under which all of MC's user data &
1284  * settings are stored.
1285  *
1286  * It defaults to the user's home dir. The user may override this default
1287  * with the environment variable $MC_PROFILE_ROOT.
1288  */
1289 const char *
1290 mc_get_profile_root (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1291 {
1292     static const char *profile_root = NULL;
1293 
1294     if (profile_root == NULL)
1295     {
1296         profile_root = g_getenv ("MC_PROFILE_ROOT");
1297         if (profile_root == NULL || *profile_root == '\0')
1298             profile_root = mc_config_get_home_dir ();
1299     }
1300 
1301     return profile_root;
1302 }
1303 
1304 /* --------------------------------------------------------------------------------------------- */
1305 /**
1306  * Propagate error in simple way.
1307  *
1308  * @param dest error return location
1309  * @param code error code
1310  * @param format printf()-style format for error message
1311  * @param ... parameters for message format
1312  */
1313 
1314 void
1315 mc_propagate_error (GError **dest, int code, const char *format, ...)
     /* [previous][next][first][last][top][bottom][index][help]  */
1316 {
1317     if (dest != NULL && *dest == NULL)
1318     {
1319         GError *tmp_error;
1320         va_list args;
1321 
1322         va_start (args, format);
1323         tmp_error = g_error_new_valist (MC_ERROR, code, format, args);
1324         va_end (args);
1325 
1326         g_propagate_error (dest, tmp_error);
1327     }
1328 }
1329 
1330 /* --------------------------------------------------------------------------------------------- */
1331 /**
1332  * Replace existing error in simple way.
1333  *
1334  * @param dest error return location
1335  * @param code error code
1336  * @param format printf()-style format for error message
1337  * @param ... parameters for message format
1338  */
1339 
1340 void
1341 mc_replace_error (GError **dest, int code, const char *format, ...)
     /* [previous][next][first][last][top][bottom][index][help]  */
1342 {
1343     if (dest != NULL)
1344     {
1345         GError *tmp_error;
1346         va_list args;
1347 
1348         va_start (args, format);
1349         tmp_error = g_error_new_valist (MC_ERROR, code, format, args);
1350         va_end (args);
1351 
1352         g_error_free (*dest);
1353         *dest = NULL;
1354         g_propagate_error (dest, tmp_error);
1355     }
1356 }
1357 
1358 /* --------------------------------------------------------------------------------------------- */
1359 
1360 /**
1361  * Returns if the given duration has elapsed since the given timestamp,
1362  * and if it has then updates the timestamp.
1363  *
1364  * @param timestamp the last timestamp in microseconds, updated if the given time elapsed
1365  * @param delay amount of time in microseconds
1366 
1367  * @return TRUE if clock skew detected, FALSE otherwise
1368  */
1369 gboolean
1370 mc_time_elapsed (gint64 *timestamp, gint64 delay)
     /* [previous][next][first][last][top][bottom][index][help]  */
1371 {
1372     gint64 now;
1373 
1374     now = g_get_monotonic_time ();
1375 
1376     if (now >= *timestamp && now < *timestamp + delay)
1377         return FALSE;
1378 
1379     *timestamp = now;
1380     return TRUE;
1381 }
1382 
1383 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */