Manual pages: mcmcdiffmceditmcview

root/lib/util.c

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

DEFINITIONS

This source file includes following definitions.
  1. is_iso_printable
  2. is_8bit_printable
  3. resolve_symlinks
  4. mc_util_write_backup_content
  5. is_printable
  6. name_quote
  7. fake_name_quote
  8. path_trunc
  9. size_trunc
  10. size_trunc_sep
  11. size_trunc_len
  12. string_perm
  13. extension
  14. load_mc_home_file
  15. extract_line
  16. x_basename
  17. unix_error_string
  18. skip_separators
  19. skip_numbers
  20. get_compression_type
  21. decompress_extension
  22. wipe_password
  23. diff_two_paths
  24. list_append_unique
  25. load_file_position
  26. save_file_position
  27. keycode_to_cntrl
  28. Q_
  29. mc_util_make_backup_if_possible
  30. mc_util_restore_from_backup_if_possible
  31. mc_util_unlink_backup_if_possible
  32. guess_message_value
  33. mc_get_profile_root
  34. mc_propagate_error
  35. mc_replace_error
  36. mc_time_elapsed

   1 /*
   2    Various utilities
   3 
   4    Copyright (C) 1994-2026
   5    Free Software Foundation, Inc.
   6 
   7    Written by:
   8    Miguel de Icaza, 1994, 1995, 1996
   9    Janne Kukonlehto, 1994, 1995, 1996
  10    Dugan Porter, 1994, 1995, 1996
  11    Jakub Jelinek, 1994, 1995, 1996
  12    Mauricio Plaza, 1994, 1995, 1996
  13    Slava Zanko <slavazanko@gmail.com>, 2013
  14 
  15    This file is part of the Midnight Commander.
  16 
  17    The Midnight Commander is free software: you can redistribute it
  18    and/or modify it under the terms of the GNU General Public License as
  19    published by the Free Software Foundation, either version 3 of the License,
  20    or (at your option) any later version.
  21 
  22    The Midnight Commander is distributed in the hope that it will be useful,
  23    but WITHOUT ANY WARRANTY; without even the implied warranty of
  24    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  25    GNU General Public License for more details.
  26 
  27    You should have received a copy of the GNU General Public License
  28    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  29  */
  30 
  31 /** \file lib/util.c
  32  *  \brief Source: various utilities
  33  */
  34 
  35 #include <config.h>
  36 
  37 #include <ctype.h>
  38 #include <stddef.h>  // ptrdiff_t
  39 #include <limits.h>
  40 #include <stdarg.h>
  41 #include <stdio.h>
  42 #include <stdlib.h>
  43 #include <string.h>
  44 #include <sys/types.h>
  45 #include <sys/stat.h>
  46 #include <unistd.h>
  47 
  48 #include "lib/global.h"
  49 #include "lib/mcconfig.h"
  50 #include "lib/fileloc.h"
  51 #include "lib/vfs/vfs.h"
  52 #include "lib/strutil.h"
  53 #include "lib/util.h"
  54 #include "lib/tty/key.h"  // KEY_M_CTRL
  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
  66 
  67 #define TMP_SUFFIX ".tmp"
  68 
  69 /*** file scope type declarations ****************************************************************/
  70 
  71 /*** forward declarations (file scope functions) *************************************************/
  72 
  73 /*** file scope variables ************************************************************************/
  74 
  75 /* --------------------------------------------------------------------------------------------- */
  76 /*** file scope functions ************************************************************************/
  77 /* --------------------------------------------------------------------------------------------- */
  78 
  79 static inline int
  80 is_iso_printable (unsigned char c)
     /* [previous][next][first][last][top][bottom][index][help]  */
  81 {
  82     return ((c > 31 && c < 127) || c >= 160);
  83 }
  84 
  85 /* --------------------------------------------------------------------------------------------- */
  86 
  87 static inline int
  88 is_8bit_printable (unsigned char c)
     /* [previous][next][first][last][top][bottom][index][help]  */
  89 {
  90     // "Full 8 bits output" doesn't work on xterm
  91     if (mc_global.tty.xterm_flag)
  92         return is_iso_printable (c);
  93 
  94     return (c > 31 && c != 127 && c != 155);
  95 }
  96 
  97 /* --------------------------------------------------------------------------------------------- */
  98 
  99 static char *
 100 resolve_symlinks (const vfs_path_t *vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
 101 {
 102     char *p, *p2;
 103     char *buf, *buf2, *q, *r, c;
 104     struct stat mybuf;
 105 
 106     if (vpath->relative)
 107         return NULL;
 108 
 109     p = p2 = g_strdup (vfs_path_as_str (vpath));
 110     r = buf = g_malloc (MC_MAXPATHLEN);
 111     buf2 = g_malloc (MC_MAXPATHLEN);
 112     *r++ = PATH_SEP;
 113     *r = '\0';
 114 
 115     do
 116     {
 117         q = strchr (p + 1, PATH_SEP);
 118         if (q == NULL)
 119         {
 120             q = strchr (p + 1, '\0');
 121             if (q == p + 1)
 122                 break;
 123         }
 124         c = *q;
 125         *q = '\0';
 126         if (mc_lstat (vpath, &mybuf) < 0)
 127         {
 128             MC_PTR_FREE (buf);
 129             goto ret;
 130         }
 131         if (!S_ISLNK (mybuf.st_mode))
 132             strcpy (r, p + 1);
 133         else
 134         {
 135             int len;
 136 
 137             len = mc_readlink (vpath, buf2, MC_MAXPATHLEN - 1);
 138             if (len < 0)
 139             {
 140                 MC_PTR_FREE (buf);
 141                 goto ret;
 142             }
 143             buf2[len] = '\0';
 144             if (IS_PATH_SEP (*buf2))
 145                 strcpy (buf, buf2);
 146             else
 147                 strcpy (r, buf2);
 148         }
 149         canonicalize_pathname (buf);
 150         r = strchr (buf, '\0');
 151         if (*r == '\0' || !IS_PATH_SEP (r[-1]))
 152         // FIXME: this condition is always true because r points to the EOL
 153         {
 154             *r++ = PATH_SEP;
 155             *r = '\0';
 156         }
 157         *q = c;
 158         p = q;
 159     }
 160     while (c != '\0');
 161 
 162     if (*buf == '\0')
 163         strcpy (buf, PATH_SEP_STR);
 164     else if (IS_PATH_SEP (r[-1]) && r != buf + 1)
 165         r[-1] = '\0';
 166 
 167 ret:
 168     g_free (buf2);
 169     g_free (p2);
 170     return buf;
 171 }
 172 
 173 /* --------------------------------------------------------------------------------------------- */
 174 
 175 static gboolean
 176 mc_util_write_backup_content (const char *from_file_name, const char *to_file_name)
     /* [previous][next][first][last][top][bottom][index][help]  */
 177 {
 178     FILE *backup_fd;
 179     char *contents;
 180     gsize length;
 181     gboolean ret1 = TRUE;
 182 
 183     if (!g_file_get_contents (from_file_name, &contents, &length, NULL))
 184         return FALSE;
 185 
 186     backup_fd = fopen (to_file_name, "w");
 187     if (backup_fd == NULL)
 188     {
 189         g_free (contents);
 190         return FALSE;
 191     }
 192 
 193     if (fwrite ((const void *) contents, 1, length, backup_fd) != length)
 194         ret1 = FALSE;
 195 
 196     fflush (backup_fd);
 197     fclose (backup_fd);
 198 
 199     g_free (contents);
 200     return ret1;
 201 }
 202 
 203 /* --------------------------------------------------------------------------------------------- */
 204 /*** public functions ****************************************************************************/
 205 /* --------------------------------------------------------------------------------------------- */
 206 
 207 int
 208 is_printable (int c)
     /* [previous][next][first][last][top][bottom][index][help]  */
 209 {
 210     return is_8bit_printable (c & 0xff);
 211 }
 212 
 213 /* --------------------------------------------------------------------------------------------- */
 214 /**
 215  * Quote the filename for the purpose of inserting it into the command
 216  * line.  If quote_percent is TRUE, replace "%" with "%%" - the percent is
 217  * processed by the mc command line.
 218  */
 219 GString *
 220 name_quote (const char *s, gboolean quote_percent)
     /* [previous][next][first][last][top][bottom][index][help]  */
 221 {
 222     GString *ret;
 223 
 224     if (s == NULL || *s == '\0')
 225         return NULL;
 226 
 227     ret = g_string_sized_new (64);
 228 
 229     if (*s == '-')
 230         g_string_append (ret, "." PATH_SEP_STR);
 231 
 232     for (; *s != '\0'; s++)
 233     {
 234         switch (*s)
 235         {
 236         case '%':
 237             if (quote_percent)
 238                 g_string_append_c (ret, '%');
 239             break;
 240         case '\'':
 241         case '\\':
 242         case '\r':
 243         case '\n':
 244         case '\t':
 245         case '"':
 246         case ';':
 247         case ' ':
 248         case '?':
 249         case '|':
 250         case '[':
 251         case ']':
 252         case '{':
 253         case '}':
 254         case '<':
 255         case '>':
 256         case '`':
 257         case '!':
 258         case '$':
 259         case '&':
 260         case '*':
 261         case '(':
 262         case ')':
 263             g_string_append_c (ret, '\\');
 264             break;
 265         case '~':
 266         case '#':
 267             if (ret->len == 0)
 268                 g_string_append_c (ret, '\\');
 269             break;
 270         default:
 271             break;
 272         }
 273 
 274         g_string_append_c (ret, *s);
 275     }
 276 
 277     if (ret->len != 0)
 278         return ret;
 279 
 280     g_string_free (ret, TRUE);
 281     return NULL;
 282 }
 283 
 284 /* --------------------------------------------------------------------------------------------- */
 285 
 286 GString *
 287 fake_name_quote (const char *s, gboolean quote_percent)
     /* [previous][next][first][last][top][bottom][index][help]  */
 288 {
 289     (void) quote_percent;
 290 
 291     return (s == NULL || *s == '\0' ? NULL : g_string_new (s));
 292 }
 293 
 294 /* --------------------------------------------------------------------------------------------- */
 295 /**
 296  * path_trunc() is the same as str_trunc() but it deletes possible password from path
 297  * for security reasons.
 298  *
 299  * @param path file path to trancate
 300  * @param width width (in characters) of truncation result. If negative, full path will be kept.
 301  *
 302  * @returns pointer to trancated path. It points to the internal static buffer, do not call
 303             g_free() to free it.
 304  */
 305 
 306 const char *
 307 path_trunc (const char *path, const ssize_t width)
     /* [previous][next][first][last][top][bottom][index][help]  */
 308 {
 309     vfs_path_t *vpath;
 310     const char *ret;
 311 
 312     vpath = vfs_path_from_str_flags (path, VPF_STRIP_PASSWORD);
 313 
 314     const char *p = vfs_path_as_str (vpath);
 315 
 316     if (width < 0)
 317         ret = str_trunc (p, -1);
 318     else
 319         ret = str_trunc (p, (size_t) width);
 320 
 321     vfs_path_free (vpath, TRUE);
 322 
 323     return ret;
 324 }
 325 
 326 /* --------------------------------------------------------------------------------------------- */
 327 
 328 const char *
 329 size_trunc (uintmax_t size, gboolean use_si)
     /* [previous][next][first][last][top][bottom][index][help]  */
 330 {
 331     static char x[BUF_TINY];
 332     uintmax_t divisor = 1;
 333     const char *xtra = _ ("B");
 334 
 335     if (size > 999999999UL)
 336     {
 337         divisor = use_si ? 1000 : 1024;
 338         xtra = use_si ? _ ("kB") : _ ("KiB");
 339 
 340         if (size / divisor > 999999999UL)
 341         {
 342             divisor = use_si ? (1000 * 1000) : (1024 * 1024);
 343             xtra = use_si ? _ ("MB") : _ ("MiB");
 344 
 345             if (size / divisor > 999999999UL)
 346             {
 347                 divisor = use_si ? (1000 * 1000 * 1000) : (1024 * 1024 * 1024);
 348                 xtra = use_si ? _ ("GB") : _ ("GiB");
 349             }
 350         }
 351     }
 352     g_snprintf (x, sizeof (x), "%.0f %s", 1.0 * size / divisor, xtra);
 353     return x;
 354 }
 355 
 356 /* --------------------------------------------------------------------------------------------- */
 357 
 358 const char *
 359 size_trunc_sep (uintmax_t size, gboolean use_si)
     /* [previous][next][first][last][top][bottom][index][help]  */
 360 {
 361     static char x[60];
 362     int count;
 363     const char *p, *y;
 364     char *d;
 365 
 366     p = y = size_trunc (size, use_si);
 367     p += strlen (p) - 1;
 368     d = x + sizeof (x) - 1;
 369     *d-- = '\0';
 370     /* @size format is "size unit", i.e. "[digits][space][letters]".
 371        Copy all characters after digits. */
 372     while (p >= y && !g_ascii_isdigit (*p))
 373         *d-- = *p--;
 374     for (count = 0; p >= y; count++)
 375     {
 376         if (count == 3)
 377         {
 378             *d-- = ',';
 379             count = 0;
 380         }
 381         *d-- = *p--;
 382     }
 383     d++;
 384     if (*d == ',')
 385         d++;
 386     return d;
 387 }
 388 
 389 /* --------------------------------------------------------------------------------------------- */
 390 /**
 391  * Print file SIZE to BUFFER, but don't exceed LEN characters,
 392  * not including trailing 0. BUFFER should be at least LEN+1 long.
 393  * This function is called for every file on panels, so avoid
 394  * floating point by any means.
 395  *
 396  * Units: size units (filesystem sizes are 1K blocks)
 397  *    0=bytes, 1=Kbytes, 2=Mbytes, etc.
 398  */
 399 
 400 void
 401 size_trunc_len (char *buffer, unsigned int len, uintmax_t size, int units, gboolean use_si)
     /* [previous][next][first][last][top][bottom][index][help]  */
 402 {
 403     // Avoid taking power for every file.
 404     static const uintmax_t power10[] = {
 405         // we hope that size of uintmax_t is 4 bytes at least
 406         1ULL,
 407         10ULL,
 408         100ULL,
 409         1000ULL,
 410         10000ULL,
 411         100000ULL,
 412         1000000ULL,
 413         10000000ULL,
 414         100000000ULL,
 415         1000000000ULL
 416     /* maximum value of uintmax_t (in case of 4 bytes) is
 417         4294967295
 418      */
 419 #if SIZEOF_UINTMAX_T == 8
 420         ,
 421         10000000000ULL,
 422         100000000000ULL,
 423         1000000000000ULL,
 424         10000000000000ULL,
 425         100000000000000ULL,
 426         1000000000000000ULL,
 427         10000000000000000ULL,
 428         100000000000000000ULL,
 429         1000000000000000000ULL,
 430         10000000000000000000ULL,
 431     /* maximum value of uintmax_t (in case of 8 bytes) is
 432         18447644073710439615
 433      */
 434 #endif
 435     };
 436 
 437     static const char *const suffix[] = {
 438         "", "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q", NULL
 439     };
 440     static const char *const suffix_lc[] = {
 441         "", "k", "m", "g", "t", "p", "e", "z", "y", "r", "q", NULL,
 442     };
 443 
 444     static int sfx_last = -1;
 445 
 446     const char *const *sfx = use_si ? suffix_lc : suffix;
 447     int j = 0;
 448 
 449     if (sfx_last < 0)
 450     {
 451         for (sfx_last = 0; sfx[sfx_last] != NULL; sfx_last++)
 452             ;
 453 
 454         sfx_last--;
 455     }
 456 
 457     if (len == 0)
 458         len = 9;
 459 #if SIZEOF_UINTMAX_T == 8
 460     // 20 decimal digits are required to represent 8 bytes
 461     else if (len > 19)
 462         len = 19;
 463 #else
 464     // 10 decimal digits are required to represent 4 bytes
 465     else if (len > 9)
 466         len = 9;
 467 #endif
 468 
 469     const int units_safe = MIN (units, sfx_last);
 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_safe; 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_safe; 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
 564 #ifdef S_ISGID
 565     if (ismode (mode_bits, S_ISGID))
 566         mode[6] = (mode[6] == 'x') ? 's' : 'S';
 567 #endif
 568 #ifdef S_ISVTX
 569     if (ismode (mode_bits, S_ISVTX))
 570         mode[9] = (mode[9] == 'x') ? 't' : 'T';
 571 #endif
 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, size_t *len)
     /* [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 
 641     if (len != NULL)
 642         *len = (size_t) (t - tmp_line);
 643 
 644     return tmp_line;
 645 }
 646 
 647 /* --------------------------------------------------------------------------------------------- */
 648 /**
 649  * The basename routine
 650  */
 651 
 652 const char *
 653 x_basename (const char *s)
     /* [previous][next][first][last][top][bottom][index][help]  */
 654 {
 655     const char *url_delim, *path_sep;
 656 
 657     url_delim = g_strrstr (s, VFS_PATH_URL_DELIMITER);
 658     path_sep = strrchr (s, PATH_SEP);
 659 
 660     if (path_sep == NULL)
 661         return s;
 662 
 663     if (url_delim == NULL || url_delim < path_sep - strlen (VFS_PATH_URL_DELIMITER)
 664         || url_delim - s + strlen (VFS_PATH_URL_DELIMITER) < strlen (s))
 665     {
 666         // avoid trailing PATH_SEP, if present
 667         if (!IS_PATH_SEP (s[strlen (s) - 1]))
 668             return path_sep + 1;
 669 
 670         while (--path_sep > s && !IS_PATH_SEP (*path_sep))
 671             ;
 672         return (path_sep != s) ? path_sep + 1 : s;
 673     }
 674 
 675     while (--url_delim > s && !IS_PATH_SEP (*url_delim))
 676         ;
 677     while (--url_delim > s && !IS_PATH_SEP (*url_delim))
 678         ;
 679 
 680     return url_delim == s ? s : url_delim + 1;
 681 }
 682 
 683 /* --------------------------------------------------------------------------------------------- */
 684 
 685 const char *
 686 unix_error_string (int error_num)
     /* [previous][next][first][last][top][bottom][index][help]  */
 687 {
 688     static char buffer[BUF_LARGE];
 689     gchar *strerror_currentlocale;
 690 
 691     strerror_currentlocale = g_locale_from_utf8 (g_strerror (error_num), -1, NULL, NULL, NULL);
 692     g_snprintf (buffer, sizeof (buffer), "%s (%d)", strerror_currentlocale, error_num);
 693     g_free (strerror_currentlocale);
 694 
 695     return buffer;
 696 }
 697 
 698 /* --------------------------------------------------------------------------------------------- */
 699 
 700 const char *
 701 skip_separators (const char *s)
     /* [previous][next][first][last][top][bottom][index][help]  */
 702 {
 703     const char *su = s;
 704 
 705     for (; *su != '\0'; str_cnext_char (&su))
 706         if (!whitespace (*su) && *su != ',')
 707             break;
 708 
 709     return su;
 710 }
 711 
 712 /* --------------------------------------------------------------------------------------------- */
 713 
 714 const char *
 715 skip_numbers (const char *s)
     /* [previous][next][first][last][top][bottom][index][help]  */
 716 {
 717     const char *su = s;
 718 
 719     for (; *su != '\0'; str_cnext_char (&su))
 720         if (!str_isdigit (su))
 721             break;
 722 
 723     return su;
 724 }
 725 
 726 /* --------------------------------------------------------------------------------------------- */
 727 
 728 enum compression_type
 729 get_compression_type (int fd, const char *name)
     /* [previous][next][first][last][top][bottom][index][help]  */
 730 {
 731     unsigned char magic[16];
 732     size_t str_len;
 733 
 734     // Read the magic signature
 735     if (mc_read (fd, (char *) magic, 4) != 4)
 736         return COMPRESSION_NONE;
 737 
 738     // GZIP_MAGIC and OLD_GZIP_MAGIC
 739     if (magic[0] == 0x1F && (magic[1] == 0x8B || magic[1] == 0x9E))
 740         return COMPRESSION_GZIP;
 741 
 742     // PKZIP_MAGIC
 743     if (magic[0] == 'P' && magic[1] == 'K' && magic[2] == 0x03 && magic[3] == 0x04)
 744     {
 745         // Read compression type
 746         mc_lseek (fd, 8, SEEK_SET);
 747         if (mc_read (fd, (char *) magic, 2) != 2)
 748             return COMPRESSION_NONE;
 749 
 750         if ((magic[0] != 8 && magic[0] != 0) || magic[1] != 0)
 751             return COMPRESSION_NONE;
 752 
 753         return COMPRESSION_ZIP;
 754     }
 755 
 756     // PACK_MAGIC and LZH_MAGIC and compress magic
 757     if (magic[0] == 0x1F && (magic[1] == 0x1E || magic[1] == 0xA0 || magic[1] == 0x9D))
 758         // Compatible with gzip
 759         return COMPRESSION_GZIP;
 760 
 761     // BZIP and BZIP2 files
 762     if ((magic[0] == 'B') && (magic[1] == 'Z') && (magic[3] >= '1') && (magic[3] <= '9'))
 763         switch (magic[2])
 764         {
 765         case '0':
 766             return COMPRESSION_BZIP;
 767         case 'h':
 768             return COMPRESSION_BZIP2;
 769         default:
 770             break;
 771         }
 772 
 773     // LZ4 format - v1.5.0 - 0x184D2204 (little endian)
 774     if (magic[0] == 0x04 && magic[1] == 0x22 && magic[2] == 0x4d && magic[3] == 0x18)
 775         return COMPRESSION_LZ4;
 776 
 777     if (mc_read (fd, (char *) magic + 4, 2) != 2)
 778         return COMPRESSION_NONE;
 779 
 780     // LZIP files
 781     if (magic[0] == 'L' && magic[1] == 'Z' && magic[2] == 'I' && magic[3] == 'P'
 782         && (magic[4] == 0x00 || magic[4] == 0x01))
 783         return COMPRESSION_LZIP;
 784 
 785     /* Support for LZMA (only utils format with magic in header).
 786      * This is the default format of LZMA utils 4.32.1 and later. */
 787     if (magic[0] == 0xFF && magic[1] == 'L' && magic[2] == 'Z' && magic[3] == 'M' && magic[4] == 'A'
 788         && magic[5] == 0x00)
 789         return COMPRESSION_LZMA;
 790 
 791     // LZO format - \x89\x4c\x5a\x4f\x00\x0d\x0a\x1a\x0a    lzop compressed data
 792     if (magic[0] == 0x89 && magic[1] == 0x4c && magic[2] == 0x5a && magic[3] == 0x4f
 793         && magic[4] == 0x00 && magic[5] == 0x0d)
 794         return COMPRESSION_LZO;
 795 
 796     // XZ compression magic
 797     if (magic[0] == 0xFD && magic[1] == 0x37 && magic[2] == 0x7A && magic[3] == 0x58
 798         && magic[4] == 0x5A && magic[5] == 0x00)
 799         return COMPRESSION_XZ;
 800 
 801     if (magic[0] == 0x28 && magic[1] == 0xB5 && magic[2] == 0x2F && magic[3] == 0xFD)
 802         return COMPRESSION_ZSTD;
 803 
 804     str_len = strlen (name);
 805     // HACK: we must believe to extension of LZMA file :) ...
 806     if ((str_len > 5 && strcmp (&name[str_len - 5], ".lzma") == 0)
 807         || (str_len > 4 && strcmp (&name[str_len - 4], ".tlz") == 0))
 808         return COMPRESSION_LZMA;
 809 
 810     return COMPRESSION_NONE;
 811 }
 812 
 813 /* --------------------------------------------------------------------------------------------- */
 814 
 815 const char *
 816 decompress_extension (int type)
     /* [previous][next][first][last][top][bottom][index][help]  */
 817 {
 818     switch (type)
 819     {
 820     case COMPRESSION_ZIP:
 821         return "/uz" VFS_PATH_URL_DELIMITER;
 822     case COMPRESSION_GZIP:
 823         return "/ugz" VFS_PATH_URL_DELIMITER;
 824     case COMPRESSION_BZIP:
 825         return "/ubz" VFS_PATH_URL_DELIMITER;
 826     case COMPRESSION_BZIP2:
 827         return "/ubz2" VFS_PATH_URL_DELIMITER;
 828     case COMPRESSION_LZIP:
 829         return "/ulz" VFS_PATH_URL_DELIMITER;
 830     case COMPRESSION_LZ4:
 831         return "/ulz4" VFS_PATH_URL_DELIMITER;
 832     case COMPRESSION_LZMA:
 833         return "/ulzma" VFS_PATH_URL_DELIMITER;
 834     case COMPRESSION_LZO:
 835         return "/ulzo" VFS_PATH_URL_DELIMITER;
 836     case COMPRESSION_XZ:
 837         return "/uxz" VFS_PATH_URL_DELIMITER;
 838     case COMPRESSION_ZSTD:
 839         return "/uzst" VFS_PATH_URL_DELIMITER;
 840     default:
 841         break;
 842     }
 843     // Should never reach this place
 844     fprintf (stderr, "Fatal: decompress_extension called with an unknown argument\n");
 845     return 0;
 846 }
 847 
 848 /* --------------------------------------------------------------------------------------------- */
 849 
 850 void
 851 wipe_password (char *passwd)
     /* [previous][next][first][last][top][bottom][index][help]  */
 852 {
 853     if (passwd != NULL)
 854     {
 855         char *p;
 856 
 857         for (p = passwd; *p != '\0'; p++)
 858             *p = '\0';
 859         g_free (passwd);
 860     }
 861 }
 862 
 863 /* --------------------------------------------------------------------------------------------- */
 864 /**
 865  * Finds out a relative path from first to second, i.e. goes as many ..
 866  * as needed up in first and then goes down using second
 867  */
 868 
 869 char *
 870 diff_two_paths (const vfs_path_t *vpath1, const vfs_path_t *vpath2)
     /* [previous][next][first][last][top][bottom][index][help]  */
 871 {
 872     int j, prevlen = -1, currlen;
 873     char *my_first = NULL, *my_second = NULL;
 874     char *buf = NULL;
 875 
 876     my_first = resolve_symlinks (vpath1);
 877     if (my_first == NULL)
 878         goto ret;
 879 
 880     my_second = resolve_symlinks (vpath2);
 881     if (my_second == NULL)
 882         goto ret;
 883 
 884     for (j = 0; j < 2; j++)
 885     {
 886         char *p, *q;
 887         int i;
 888 
 889         p = my_first;
 890         q = my_second;
 891 
 892         while (TRUE)
 893         {
 894             char *r, *s;
 895             ptrdiff_t len;
 896 
 897             r = strchr (p, PATH_SEP);
 898             if (r == NULL)
 899                 break;
 900             s = strchr (q, PATH_SEP);
 901             if (s == NULL)
 902                 break;
 903 
 904             len = r - p;
 905             if (len != (s - q) || strncmp (p, q, (size_t) len) != 0)
 906                 break;
 907 
 908             p = r + 1;
 909             q = s + 1;
 910         }
 911         p--;
 912         for (i = 0; (p = strchr (p + 1, PATH_SEP)) != NULL; i++)
 913             ;
 914         currlen = (i + 1) * 3 + strlen (q) + 1;
 915         if (j != 0)
 916         {
 917             if (currlen < prevlen)
 918                 g_free (buf);
 919             else
 920                 goto ret;
 921         }
 922         p = buf = g_malloc (currlen);
 923         prevlen = currlen;
 924         for (; i >= 0; i--, p += 3)
 925             strcpy (p, "../");
 926         strcpy (p, q);
 927     }
 928 
 929 ret:
 930     g_free (my_first);
 931     g_free (my_second);
 932     return buf;
 933 }
 934 
 935 /* --------------------------------------------------------------------------------------------- */
 936 /**
 937  * Append text to GList, remove all entries with the same text
 938  */
 939 
 940 GList *
 941 list_append_unique (GList *list, char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 942 {
 943     GList *lc_link;
 944 
 945     /*
 946      * Go to the last position and traverse the list backwards
 947      * starting from the second last entry to make sure that we
 948      * are not removing the current link.
 949      */
 950     list = g_list_append (list, text);
 951     list = g_list_last (list);
 952     lc_link = g_list_previous (list);
 953 
 954     while (lc_link != NULL)
 955     {
 956         GList *newlink;
 957 
 958         newlink = g_list_previous (lc_link);
 959         if (strcmp ((char *) lc_link->data, text) == 0)
 960         {
 961             GList *tmp;
 962 
 963             g_free (lc_link->data);
 964             tmp = g_list_remove_link (list, lc_link);
 965             (void) tmp;
 966             g_list_free_1 (lc_link);
 967         }
 968         lc_link = newlink;
 969     }
 970 
 971     return list;
 972 }
 973 
 974 /* --------------------------------------------------------------------------------------------- */
 975 /**
 976  * Read and restore position for the given filename.
 977  * If there is no stored data, return line 1 and col 0.
 978  */
 979 
 980 void
 981 load_file_position (const vfs_path_t *filename_vpath, long *line, long *column, off_t *offset,
     /* [previous][next][first][last][top][bottom][index][help]  */
 982                     GArray **bookmarks)
 983 {
 984     char *fn, *filename_str;
 985     FILE *f;
 986     char buf[MC_MAXPATHLEN + 100];
 987     const size_t len = vfs_path_len (filename_vpath);
 988 
 989     // defaults
 990     *line = 1;
 991     *column = 0;
 992     *offset = 0;
 993 
 994     // open file with positions
 995     fn = mc_config_get_full_path (MC_FILEPOS_FILE);
 996     f = fopen (fn, "r");
 997     g_free (fn);
 998     if (f == NULL)
 999         return;
1000 
1001     // prepare array for serialized bookmarks
1002     if (bookmarks != NULL)
1003         *bookmarks = g_array_sized_new (FALSE, FALSE, sizeof (size_t), MAX_SAVED_BOOKMARKS);
1004 
1005     filename_str = str_escape (vfs_path_as_str (filename_vpath), -1, "", TRUE);
1006 
1007     while (fgets (buf, sizeof (buf), f) != NULL)
1008     {
1009         const char *p;
1010         gchar **pos_tokens;
1011 
1012         // check if the filename matches the beginning of string
1013         if (strncmp (buf, filename_str, len) != 0)
1014             continue;
1015 
1016         // followed by single space
1017         if (buf[len] != ' ')
1018             continue;
1019 
1020         // and string without spaces
1021         p = &buf[len + 1];
1022         if (strchr (p, ' ') != NULL)
1023             continue;
1024 
1025         pos_tokens = g_strsplit (p, ";", 3 + MAX_SAVED_BOOKMARKS);
1026         if (pos_tokens[0] == NULL)
1027         {
1028             *line = 1;
1029             *column = 0;
1030             *offset = 0;
1031         }
1032         else
1033         {
1034             *line = strtol (pos_tokens[0], NULL, 10);
1035             if (pos_tokens[1] == NULL)
1036             {
1037                 *column = 0;
1038                 *offset = 0;
1039             }
1040             else
1041             {
1042                 *column = strtol (pos_tokens[1], NULL, 10);
1043                 if (pos_tokens[2] == NULL)
1044                     *offset = 0;
1045                 else if (bookmarks != NULL)
1046                 {
1047                     size_t i;
1048 
1049                     *offset = (off_t) g_ascii_strtoll (pos_tokens[2], NULL, 10);
1050 
1051                     for (i = 0; i < MAX_SAVED_BOOKMARKS && pos_tokens[3 + i] != NULL; i++)
1052                     {
1053                         size_t val;
1054 
1055                         val = strtoul (pos_tokens[3 + i], NULL, 10);
1056                         g_array_append_val (*bookmarks, val);
1057                     }
1058                 }
1059             }
1060         }
1061 
1062         g_strfreev (pos_tokens);
1063     }
1064 
1065     g_free (filename_str);
1066     fclose (f);
1067 }
1068 
1069 /* --------------------------------------------------------------------------------------------- */
1070 /**
1071  * Save position for the given file
1072  */
1073 
1074 void
1075 save_file_position (const vfs_path_t *filename_vpath, long line, long column, off_t offset,
     /* [previous][next][first][last][top][bottom][index][help]  */
1076                     GArray *bookmarks)
1077 {
1078     static size_t filepos_max_saved_entries = 0;
1079     char *fn, *tmp_fn;
1080     char *filename_str = NULL;
1081     FILE *f, *tmp_f;
1082     char buf[MC_MAXPATHLEN + 100];
1083     size_t i;
1084     const size_t len = vfs_path_len (filename_vpath);
1085     gboolean src_error = FALSE;
1086 
1087     if (filepos_max_saved_entries == 0)
1088         filepos_max_saved_entries = mc_config_get_int (mc_global.main_config, CONFIG_APP_SECTION,
1089                                                        "filepos_max_saved_entries", 1024);
1090 
1091     fn = mc_config_get_full_path (MC_FILEPOS_FILE);
1092     if (fn == NULL)
1093         goto early_error;
1094 
1095     mc_util_make_backup_if_possible (fn, TMP_SUFFIX);
1096 
1097     // open file
1098     f = fopen (fn, "w");
1099     if (f == NULL)
1100         goto open_target_error;
1101 
1102     tmp_fn = g_strdup_printf ("%s" TMP_SUFFIX, fn);
1103     tmp_f = fopen (tmp_fn, "r");
1104     if (tmp_f == NULL)
1105     {
1106         src_error = TRUE;
1107         goto open_source_error;
1108     }
1109 
1110     filename_str = str_escape (vfs_path_as_str (filename_vpath), -1, "", TRUE);
1111 
1112     // put the new record
1113     if (line != 1 || column != 0 || bookmarks != NULL)
1114     {
1115         if (fprintf (f, "%s %ld;%ld;%" PRIuMAX, filename_str, line, column, (uintmax_t) offset) < 0)
1116             goto write_position_error;
1117         if (bookmarks != NULL)
1118             for (i = 0; i < bookmarks->len && i < MAX_SAVED_BOOKMARKS; i++)
1119                 if (fprintf (f, ";%zu", g_array_index (bookmarks, size_t, i)) < 0)
1120                     goto write_position_error;
1121 
1122         if (fprintf (f, "\n") < 0)
1123             goto write_position_error;
1124     }
1125 
1126     i = 1;
1127     while (fgets (buf, sizeof (buf), tmp_f) != NULL)
1128     {
1129         if (buf[len] == ' ' && strncmp (buf, filename_str, len) == 0
1130             && strchr (&buf[len + 1], ' ') == NULL)
1131             continue;
1132 
1133         fprintf (f, "%s", buf);
1134         if (++i > filepos_max_saved_entries)
1135             break;
1136     }
1137 
1138 write_position_error:
1139     g_free (filename_str);
1140     fclose (tmp_f);
1141 open_source_error:
1142     g_free (tmp_fn);
1143     fclose (f);
1144     if (src_error)
1145         mc_util_restore_from_backup_if_possible (fn, TMP_SUFFIX);
1146     else
1147         mc_util_unlink_backup_if_possible (fn, TMP_SUFFIX);
1148 open_target_error:
1149     g_free (fn);
1150 early_error:
1151     if (bookmarks != NULL)
1152         g_array_free (bookmarks, TRUE);
1153 }
1154 
1155 /* --------------------------------------------------------------------------------------------- */
1156 
1157 int
1158 keycode_to_cntrl (int ch)
     /* [previous][next][first][last][top][bottom][index][help]  */
1159 {
1160     // Extended keys such as F_KEY(n), Page Up and Page Down are not allowed.
1161     if ((ch & ~(KEY_M_CTRL | 0x7f)) != 0)
1162         return -1;
1163 
1164     // User pressed Ctrl+(ch) or '\t', '\'n
1165     if ((ch & KEY_M_CTRL) != 0 || ch < 0x1b)
1166         return ch & 0x1f;
1167 
1168     if (ch >= '@' && ch <= '_')
1169         return ch - '@';
1170 
1171     if (ch >= 'a' && ch <= 'z')
1172         return ch - '`';
1173 
1174     if (ch == '?')
1175         return 0x7f;
1176 
1177     return -1;
1178 }
1179 
1180 /* --------------------------------------------------------------------------------------------- */
1181 
1182 const char *
1183 Q_ (const char *s)
     /* [previous][next][first][last][top][bottom][index][help]  */
1184 {
1185     const char *result, *sep;
1186 
1187     result = _ (s);
1188     sep = strchr (result, '|');
1189 
1190     return sep != NULL ? sep + 1 : result;
1191 }
1192 
1193 /* --------------------------------------------------------------------------------------------- */
1194 
1195 gboolean
1196 mc_util_make_backup_if_possible (const char *file_name, const char *backup_suffix)
     /* [previous][next][first][last][top][bottom][index][help]  */
1197 {
1198     struct stat stat_buf;
1199     char *backup_path;
1200     gboolean ret;
1201 
1202     if (!exist_file (file_name))
1203         return FALSE;
1204 
1205     backup_path = g_strdup_printf ("%s%s", file_name, backup_suffix);
1206     if (backup_path == NULL)
1207         return FALSE;
1208 
1209     ret = mc_util_write_backup_content (file_name, backup_path);
1210     if (ret)
1211     {
1212         // Backup file will have same ownership with main file.
1213         if (stat (file_name, &stat_buf) == 0)
1214             chmod (backup_path, stat_buf.st_mode);
1215         else
1216             chmod (backup_path, S_IRUSR | S_IWUSR);
1217     }
1218 
1219     g_free (backup_path);
1220 
1221     return ret;
1222 }
1223 
1224 /* --------------------------------------------------------------------------------------------- */
1225 
1226 gboolean
1227 mc_util_restore_from_backup_if_possible (const char *file_name, const char *backup_suffix)
     /* [previous][next][first][last][top][bottom][index][help]  */
1228 {
1229     gboolean ret;
1230     char *backup_path;
1231 
1232     backup_path = g_strdup_printf ("%s%s", file_name, backup_suffix);
1233     if (backup_path == NULL)
1234         return FALSE;
1235 
1236     ret = mc_util_write_backup_content (backup_path, file_name);
1237     g_free (backup_path);
1238 
1239     return ret;
1240 }
1241 
1242 /* --------------------------------------------------------------------------------------------- */
1243 
1244 gboolean
1245 mc_util_unlink_backup_if_possible (const char *file_name, const char *backup_suffix)
     /* [previous][next][first][last][top][bottom][index][help]  */
1246 {
1247     char *backup_path;
1248 
1249     backup_path = g_strdup_printf ("%s%s", file_name, backup_suffix);
1250     if (backup_path == NULL)
1251         return FALSE;
1252 
1253     if (exist_file (backup_path))
1254     {
1255         vfs_path_t *vpath;
1256 
1257         vpath = vfs_path_from_str (backup_path);
1258         mc_unlink (vpath);
1259         vfs_path_free (vpath, TRUE);
1260     }
1261 
1262     g_free (backup_path);
1263     return TRUE;
1264 }
1265 
1266 /* --------------------------------------------------------------------------------------------- */
1267 /**
1268  * partly taken from dcigettext.c, returns "" for default locale
1269  * value should be freed by calling function g_free()
1270  */
1271 
1272 char *
1273 guess_message_value (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1274 {
1275     static const char *const var[] = {
1276         // Setting of LC_ALL overwrites all other.
1277         // Do not use LANGUAGE for check user locale and drowing hints
1278         "LC_ALL",
1279         // Next comes the name of the desired category.
1280         "LC_MESSAGES",
1281         // Last possibility is the LANG environment variable.
1282         "LANG",
1283         // NULL exit loops
1284         NULL
1285     };
1286 
1287     size_t i;
1288     const char *locale = NULL;
1289 
1290     for (i = 0; var[i] != NULL; i++)
1291     {
1292         locale = getenv (var[i]);
1293         if (locale != NULL && locale[0] != '\0')
1294             break;
1295     }
1296 
1297     if (locale == NULL)
1298         locale = "";
1299 
1300     return g_strdup (locale);
1301 }
1302 
1303 /* --------------------------------------------------------------------------------------------- */
1304 
1305 /**
1306  * The "profile root" is the tree under which all of MC's user data &
1307  * settings are stored.
1308  *
1309  * It defaults to the user's home dir. The user may override this default
1310  * with the environment variable $MC_PROFILE_ROOT.
1311  */
1312 const char *
1313 mc_get_profile_root (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1314 {
1315     static const char *profile_root = NULL;
1316 
1317     if (profile_root == NULL)
1318     {
1319         profile_root = g_getenv ("MC_PROFILE_ROOT");
1320         if (profile_root == NULL || *profile_root == '\0')
1321             profile_root = mc_config_get_home_dir ();
1322     }
1323 
1324     return profile_root;
1325 }
1326 
1327 /* --------------------------------------------------------------------------------------------- */
1328 /**
1329  * Propagate error in simple way.
1330  *
1331  * @param dest error return location
1332  * @param code error code
1333  * @param format printf()-style format for error message
1334  * @param ... parameters for message format
1335  */
1336 
1337 void
1338 mc_propagate_error (GError **dest, int code, const char *format, ...)
     /* [previous][next][first][last][top][bottom][index][help]  */
1339 {
1340     if (dest != NULL && *dest == NULL)
1341     {
1342         GError *tmp_error;
1343         va_list args;
1344 
1345         va_start (args, format);
1346         tmp_error = g_error_new_valist (MC_ERROR, code, format, args);
1347         va_end (args);
1348 
1349         g_propagate_error (dest, tmp_error);
1350     }
1351 }
1352 
1353 /* --------------------------------------------------------------------------------------------- */
1354 /**
1355  * Replace existing error in simple way.
1356  *
1357  * @param dest error return location
1358  * @param code error code
1359  * @param format printf()-style format for error message
1360  * @param ... parameters for message format
1361  */
1362 
1363 void
1364 mc_replace_error (GError **dest, int code, const char *format, ...)
     /* [previous][next][first][last][top][bottom][index][help]  */
1365 {
1366     if (dest != NULL)
1367     {
1368         GError *tmp_error;
1369         va_list args;
1370 
1371         va_start (args, format);
1372         tmp_error = g_error_new_valist (MC_ERROR, code, format, args);
1373         va_end (args);
1374 
1375         g_error_free (*dest);
1376         *dest = NULL;
1377         g_propagate_error (dest, tmp_error);
1378     }
1379 }
1380 
1381 /* --------------------------------------------------------------------------------------------- */
1382 
1383 /**
1384  * Returns if the given duration has elapsed since the given timestamp,
1385  * and if it has then updates the timestamp.
1386  *
1387  * @param timestamp the last timestamp in microseconds, updated if the given time elapsed
1388  * @param delay amount of time in microseconds
1389 
1390  * @return TRUE if clock skew detected, FALSE otherwise
1391  */
1392 gboolean
1393 mc_time_elapsed (gint64 *timestamp, gint64 delay)
     /* [previous][next][first][last][top][bottom][index][help]  */
1394 {
1395     gint64 now;
1396 
1397     now = g_get_monotonic_time ();
1398 
1399     if (now >= *timestamp && now < *timestamp + delay)
1400         return FALSE;
1401 
1402     *timestamp = now;
1403     return TRUE;
1404 }
1405 
1406 /* --------------------------------------------------------------------------------------------- */

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