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

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