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

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