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

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