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

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