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

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