root/lib/util.c

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

DEFINITIONS

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

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