4 Copyright (C) 1994-2024
5 Free Software Foundation, Inc.
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
15 This file is part of the Midnight Commander.
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.
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.
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/>.
32 * \brief Source: various utilities
38 #include <stddef.h> /* ptrdiff_t */
44 #include <sys/types.h>
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"
55 /*** global variables ****************************************************************************/
57 /*** file scope macro definitions ****************************************************************/
59 #define ismode(n,m) ((n & m) == m)
61 /* Number of attempts to create a temporary file */
66 #define TMP_SUFFIX ".tmp"
68 #define ASCII_A (0x40 + 1)
69 #define ASCII_Z (0x40 + 26)
70 #define ASCII_a (0x60 + 1)
71 #define ASCII_z (0x60 + 26)
73 /*** file scope type declarations ****************************************************************/
75 /*** forward declarations (file scope functions) *************************************************/
77 /*** file scope variables ************************************************************************/
79 /* --------------------------------------------------------------------------------------------- */
80 /*** file scope functions ************************************************************************/
81 /* --------------------------------------------------------------------------------------------- */
85 is_7bit_printable (unsigned char c
)
87 return (c
> 31 && c
< 127);
91 /* --------------------------------------------------------------------------------------------- */
94 is_iso_printable (unsigned char c
)
96 return ((c
> 31 && c
< 127) || c
>= 160);
99 /* --------------------------------------------------------------------------------------------- */
102 is_8bit_printable (unsigned char c
)
104 /* "Full 8 bits output" doesn't work on xterm */
105 if (mc_global
.tty
.xterm_flag
)
106 return is_iso_printable (c
);
108 return (c
> 31 && c
!= 127 && c
!= 155);
111 /* --------------------------------------------------------------------------------------------- */
114 resolve_symlinks (const vfs_path_t
*vpath
)
117 char *buf
, *buf2
, *q
, *r
, c
;
123 p
= p2
= g_strdup (vfs_path_as_str (vpath
));
124 r
= buf
= g_malloc (MC_MAXPATHLEN
);
125 buf2
= g_malloc (MC_MAXPATHLEN
);
131 q
= strchr (p
+ 1, PATH_SEP
);
134 q
= strchr (p
+ 1, '\0');
140 if (mc_lstat (vpath
, &mybuf
) < 0)
145 if (!S_ISLNK (mybuf
.st_mode
))
151 len
= mc_readlink (vpath
, buf2
, MC_MAXPATHLEN
- 1);
158 if (IS_PATH_SEP (*buf2
))
163 canonicalize_pathname (buf
);
164 r
= strchr (buf
, '\0');
165 if (*r
== '\0' || !IS_PATH_SEP (r
[-1]))
166 /* FIXME: this condition is always true because r points to the EOL */
177 strcpy (buf
, PATH_SEP_STR
);
178 else if (IS_PATH_SEP (r
[-1]) && r
!= buf
+ 1)
187 /* --------------------------------------------------------------------------------------------- */
190 mc_util_write_backup_content (const char *from_file_name
, const char *to_file_name
)
195 gboolean ret1
= TRUE
;
197 if (!g_file_get_contents (from_file_name
, &contents
, &length
, NULL
))
200 backup_fd
= fopen (to_file_name
, "w");
201 if (backup_fd
== NULL
)
207 if (fwrite ((const void *) contents
, 1, length
, backup_fd
) != length
)
213 /* cppcheck-suppress redundantAssignment */
214 ret2
= fflush (backup_fd
);
215 /* cppcheck-suppress redundantAssignment */
216 ret2
= fclose (backup_fd
);
224 /* --------------------------------------------------------------------------------------------- */
225 /*** public functions ****************************************************************************/
226 /* --------------------------------------------------------------------------------------------- */
234 /* "Display bits" is ignored, since the user controls the output
235 by setting the output codepage */
236 return is_8bit_printable (c
);
238 if (!mc_global
.eight_bit_clean
)
239 return is_7bit_printable (c
);
241 if (mc_global
.full_eight_bits
)
242 return is_8bit_printable (c
);
244 return is_iso_printable (c
);
245 #endif /* !HAVE_CHARSET */
248 /* --------------------------------------------------------------------------------------------- */
250 * Quote the filename for the purpose of inserting it into the command
251 * line. If quote_percent is TRUE, replace "%" with "%%" - the percent is
252 * processed by the mc command line.
255 name_quote (const char *s
, gboolean quote_percent
)
259 if (s
== NULL
|| *s
== '\0')
262 ret
= g_string_sized_new (64);
265 g_string_append (ret
, "." PATH_SEP_STR
);
267 for (; *s
!= '\0'; s
++)
273 g_string_append_c (ret
, '%');
298 g_string_append_c (ret
, '\\');
303 g_string_append_c (ret
, '\\');
308 g_string_append_c (ret
, *s
);
311 return g_string_free (ret
, ret
->len
== 0);
314 /* --------------------------------------------------------------------------------------------- */
317 fake_name_quote (const char *s
, gboolean quote_percent
)
319 (void) quote_percent
;
321 return (s
== NULL
|| *s
== '\0' ? NULL
: g_strdup (s
));
324 /* --------------------------------------------------------------------------------------------- */
326 * path_trunc() is the same as str_trunc() but
327 * it deletes possible password from path for security
332 path_trunc (const char *path
, size_t trunc_len
)
337 vpath
= vfs_path_from_str_flags (path
, VPF_STRIP_PASSWORD
);
338 ret
= str_trunc (vfs_path_as_str (vpath
), trunc_len
);
339 vfs_path_free (vpath
, TRUE
);
344 /* --------------------------------------------------------------------------------------------- */
347 size_trunc (uintmax_t size
, gboolean use_si
)
349 static char x
[BUF_TINY
];
350 uintmax_t divisor
= 1;
351 const char *xtra
= _("B");
353 if (size
> 999999999UL)
355 divisor
= use_si
? 1000 : 1024;
356 xtra
= use_si
? _("kB") : _("KiB");
358 if (size
/ divisor
> 999999999UL)
360 divisor
= use_si
? (1000 * 1000) : (1024 * 1024);
361 xtra
= use_si
? _("MB") : _("MiB");
363 if (size
/ divisor
> 999999999UL)
365 divisor
= use_si
? (1000 * 1000 * 1000) : (1024 * 1024 * 1024);
366 xtra
= use_si
? _("GB") : _("GiB");
370 g_snprintf (x
, sizeof (x
), "%.0f %s", 1.0 * size
/ divisor
, xtra
);
374 /* --------------------------------------------------------------------------------------------- */
377 size_trunc_sep (uintmax_t size
, gboolean use_si
)
384 p
= y
= size_trunc (size
, use_si
);
386 d
= x
+ sizeof (x
) - 1;
388 /* @size format is "size unit", i.e. "[digits][space][letters]".
389 Copy all characters after digits. */
390 while (p
>= y
&& !g_ascii_isdigit (*p
))
392 for (count
= 0; p
>= y
; count
++)
407 /* --------------------------------------------------------------------------------------------- */
409 * Print file SIZE to BUFFER, but don't exceed LEN characters,
410 * not including trailing 0. BUFFER should be at least LEN+1 long.
411 * This function is called for every file on panels, so avoid
412 * floating point by any means.
414 * Units: size units (filesystem sizes are 1K blocks)
415 * 0=bytes, 1=Kbytes, 2=Mbytes, etc.
419 size_trunc_len (char *buffer
, unsigned int len
, uintmax_t size
, int units
, gboolean use_si
)
421 /* Avoid taking power for every file. */
423 static const uintmax_t power10
[] = {
424 /* we hope that size of uintmax_t is 4 bytes at least */
435 /* maximum value of uintmax_t (in case of 4 bytes) is
438 #if SIZEOF_UINTMAX_T == 8
446 10000000000000000ULL,
447 100000000000000000ULL,
448 1000000000000000000ULL,
449 10000000000000000000ULL
450 /* maximum value of uintmax_t (in case of 8 bytes) is
456 static const char *const suffix
[] =
457 { "", "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q", NULL
};
458 static const char *const suffix_lc
[] =
459 { "", "k", "m", "g", "t", "p", "e", "z", "y", "r", "q", NULL
};
462 const char *const *sfx
= use_si
? suffix_lc
: suffix
;
467 #if SIZEOF_UINTMAX_T == 8
468 /* 20 decimal digits are required to represent 8 bytes */
472 /* 10 decimal digits are required to represent 4 bytes */
478 * recalculate from 1024 base to 1000 base if units>0
479 * We can't just multiply by 1024 - that might cause overflow
480 * if uintmax_t type is too small
483 for (j
= 0; j
< units
; j
++)
485 uintmax_t size_remain
;
487 size_remain
= ((size
% 125) * 1024) / 1000; /* size mod 125, recalculated */
488 size
/= 125; /* 128/125 = 1024/1000 */
489 size
*= 128; /* This will convert size from multiple of 1024 to multiple of 1000 */
490 size
+= size_remain
; /* Re-add remainder lost by division/multiplication */
493 for (j
= units
; sfx
[j
] != NULL
; j
++)
499 /* Empty files will print "0" even with minimal width. */
500 g_snprintf (buffer
, len
+ 1, "%s", "0");
504 /* Use "~K" or just "K" if len is 1. Use "B" for bytes. */
505 g_snprintf (buffer
, len
+ 1, (len
> 1) ? "~%s" : "%s", (j
> 1) ? sfx
[j
- 1] : "B");
510 if (size
< power10
[len
- (j
> 0 ? 1 : 0)])
512 g_snprintf (buffer
, len
+ 1, "%" PRIuMAX
"%s", size
, sfx
[j
]);
516 /* Powers of 1000 or 1024, with rounding. */
518 size
= (size
+ 500) / 1000;
520 size
= (size
+ 512) >> 10;
524 /* --------------------------------------------------------------------------------------------- */
527 string_perm (mode_t mode_bits
)
529 static char mode
[11];
531 strcpy (mode
, "----------");
532 if (S_ISDIR (mode_bits
))
534 if (S_ISCHR (mode_bits
))
536 if (S_ISBLK (mode_bits
))
538 if (S_ISLNK (mode_bits
))
540 if (S_ISFIFO (mode_bits
))
542 if (S_ISNAM (mode_bits
))
544 if (S_ISSOCK (mode_bits
))
546 if (S_ISDOOR (mode_bits
))
548 if (ismode (mode_bits
, S_IXOTH
))
550 if (ismode (mode_bits
, S_IWOTH
))
552 if (ismode (mode_bits
, S_IROTH
))
554 if (ismode (mode_bits
, S_IXGRP
))
556 if (ismode (mode_bits
, S_IWGRP
))
558 if (ismode (mode_bits
, S_IRGRP
))
560 if (ismode (mode_bits
, S_IXUSR
))
562 if (ismode (mode_bits
, S_IWUSR
))
564 if (ismode (mode_bits
, S_IRUSR
))
567 if (ismode (mode_bits
, S_ISUID
))
568 mode
[3] = (mode
[3] == 'x') ? 's' : 'S';
571 if (ismode (mode_bits
, S_ISGID
))
572 mode
[6] = (mode
[6] == 'x') ? 's' : 'S';
575 if (ismode (mode_bits
, S_ISVTX
))
576 mode
[9] = (mode
[9] == 'x') ? 't' : 'T';
581 /* --------------------------------------------------------------------------------------------- */
584 extension (const char *filename
)
588 d
= strrchr (filename
, '.');
590 return d
!= NULL
? d
+ 1 : "";
593 /* --------------------------------------------------------------------------------------------- */
596 load_mc_home_file (const char *from
, const char *filename
, char **allocated_filename
,
599 char *hintfile_base
, *hintfile
;
603 hintfile_base
= g_build_filename (from
, filename
, (char *) NULL
);
604 lang
= guess_message_value ();
606 hintfile
= g_strconcat (hintfile_base
, ".", lang
, (char *) NULL
);
607 if (!g_file_get_contents (hintfile
, &data
, length
, NULL
))
609 /* Fall back to the two-letter language code */
610 if (lang
[0] != '\0' && lang
[1] != '\0')
613 hintfile
= g_strconcat (hintfile_base
, ".", lang
, (char *) NULL
);
614 if (!g_file_get_contents (hintfile
, &data
, length
, NULL
))
617 hintfile
= hintfile_base
;
618 g_file_get_contents (hintfile_base
, &data
, length
, NULL
);
624 if (hintfile
!= hintfile_base
)
625 g_free (hintfile_base
);
627 if (allocated_filename
!= NULL
)
628 *allocated_filename
= hintfile
;
635 /* --------------------------------------------------------------------------------------------- */
638 extract_line (const char *s
, const char *top
, size_t *len
)
640 static char tmp_line
[BUF_MEDIUM
];
643 while (*s
!= '\0' && *s
!= '\n' && (size_t) (t
- tmp_line
) < sizeof (tmp_line
) - 1 && s
< top
)
648 *len
= (size_t) (t
- tmp_line
);
653 /* --------------------------------------------------------------------------------------------- */
655 * The basename routine
659 x_basename (const char *s
)
661 const char *url_delim
, *path_sep
;
663 url_delim
= g_strrstr (s
, VFS_PATH_URL_DELIMITER
);
664 path_sep
= strrchr (s
, PATH_SEP
);
666 if (path_sep
== NULL
)
669 if (url_delim
== NULL
670 || url_delim
< path_sep
- strlen (VFS_PATH_URL_DELIMITER
)
671 || url_delim
- s
+ strlen (VFS_PATH_URL_DELIMITER
) < strlen (s
))
673 /* avoid trailing PATH_SEP, if present */
674 if (!IS_PATH_SEP (s
[strlen (s
) - 1]))
677 while (--path_sep
> s
&& !IS_PATH_SEP (*path_sep
))
679 return (path_sep
!= s
) ? path_sep
+ 1 : s
;
682 while (--url_delim
> s
&& !IS_PATH_SEP (*url_delim
))
684 while (--url_delim
> s
&& !IS_PATH_SEP (*url_delim
))
687 return url_delim
== s
? s
: url_delim
+ 1;
690 /* --------------------------------------------------------------------------------------------- */
693 unix_error_string (int error_num
)
695 static char buffer
[BUF_LARGE
];
696 gchar
*strerror_currentlocale
;
698 strerror_currentlocale
= g_locale_from_utf8 (g_strerror (error_num
), -1, NULL
, NULL
, NULL
);
699 g_snprintf (buffer
, sizeof (buffer
), "%s (%d)", strerror_currentlocale
, error_num
);
700 g_free (strerror_currentlocale
);
705 /* --------------------------------------------------------------------------------------------- */
708 skip_separators (const char *s
)
712 for (; *su
!= '\0'; str_cnext_char (&su
))
713 if (!whitespace (*su
) && *su
!= ',')
719 /* --------------------------------------------------------------------------------------------- */
722 skip_numbers (const char *s
)
726 for (; *su
!= '\0'; str_cnext_char (&su
))
727 if (!str_isdigit (su
))
733 /* --------------------------------------------------------------------------------------------- */
735 * Remove all control sequences from the argument string. We define
736 * "control sequence", in a sort of pidgin BNF, as follows:
738 * control-seq = Esc non-'['
739 * | Esc '[' (parameter-byte)* (intermediate-byte)* final-byte
740 * parameter-byte = [\x30-\x3F] # one of "0-9;:<=>?"
741 * intermediate-byte = [\x20–\x2F] # one of " !\"#$%&'()*+,-./"
742 * final-byte = [\x40-\x7e] # one of "@A–Z[\]^_`a–z{|}~"
744 * The 256-color and true-color escape sequences should allow either ';' or ':' inside as separator,
745 * actually, ':' is the more correct according to ECMA-48.
746 * Some terminal emulators (e.g. xterm, gnome-terminal) support this.
748 * Non-printable characters are also removed.
752 strip_ctrl_codes (char *s
)
754 char *w
; /* Current position where the stripped data is written */
755 char *r
; /* Current position where the original data is read */
760 for (w
= s
, r
= s
; *r
!= '\0';)
764 /* Skip the control sequence's arguments */ ;
765 /* '(' need to avoid strange 'B' letter in *Suse (if mc runs under root user) */
766 if (*(++r
) == '[' || *r
== '(')
768 /* strchr() matches trailing binary 0 */
769 while (*(++r
) != '\0' && strchr ("0123456789;:<=>?", *r
) != NULL
)
771 while (*r
!= '\0' && (*r
< 0x40 || *r
> 0x7E))
777 * Skip xterm's OSC (Operating System Command)
778 * http://www.xfree86.org/current/ctlseqs.html
784 for (new_r
= r
; *new_r
!= '\0'; new_r
++)
794 if (new_r
[1] == '\\')
809 * Now we are at the last character of the sequence.
810 * Skip it unless it's binary 0.
819 n
= str_get_next_char (r
);
822 memmove (w
, r
, n
- r
);
833 /* --------------------------------------------------------------------------------------------- */
835 enum compression_type
836 get_compression_type (int fd
, const char *name
)
838 unsigned char magic
[16];
841 /* Read the magic signature */
842 if (mc_read (fd
, (char *) magic
, 4) != 4)
843 return COMPRESSION_NONE
;
845 /* GZIP_MAGIC and OLD_GZIP_MAGIC */
846 if (magic
[0] == 0x1F && (magic
[1] == 0x8B || magic
[1] == 0x9E))
847 return COMPRESSION_GZIP
;
850 if (magic
[0] == 'P' && magic
[1] == 'K' && magic
[2] == 0x03 && magic
[3] == 0x04)
852 /* Read compression type */
853 mc_lseek (fd
, 8, SEEK_SET
);
854 if (mc_read (fd
, (char *) magic
, 2) != 2)
855 return COMPRESSION_NONE
;
857 if ((magic
[0] != 8 && magic
[0] != 0) || magic
[1] != 0)
858 return COMPRESSION_NONE
;
860 return COMPRESSION_ZIP
;
863 /* PACK_MAGIC and LZH_MAGIC and compress magic */
864 if (magic
[0] == 0x1F && (magic
[1] == 0x1E || magic
[1] == 0xA0 || magic
[1] == 0x9D))
865 /* Compatible with gzip */
866 return COMPRESSION_GZIP
;
868 /* BZIP and BZIP2 files */
869 if ((magic
[0] == 'B') && (magic
[1] == 'Z') && (magic
[3] >= '1') && (magic
[3] <= '9'))
873 return COMPRESSION_BZIP
;
875 return COMPRESSION_BZIP2
;
880 /* LZ4 format - v1.5.0 - 0x184D2204 (little endian) */
881 if (magic
[0] == 0x04 && magic
[1] == 0x22 && magic
[2] == 0x4d && magic
[3] == 0x18)
882 return COMPRESSION_LZ4
;
884 if (mc_read (fd
, (char *) magic
+ 4, 2) != 2)
885 return COMPRESSION_NONE
;
890 && magic
[2] == 'I' && magic
[3] == 'P' && (magic
[4] == 0x00 || magic
[4] == 0x01))
891 return COMPRESSION_LZIP
;
893 /* Support for LZMA (only utils format with magic in header).
894 * This is the default format of LZMA utils 4.32.1 and later. */
897 && magic
[2] == 'Z' && magic
[3] == 'M' && magic
[4] == 'A' && magic
[5] == 0x00)
898 return COMPRESSION_LZMA
;
900 /* LZO format - \x89\x4c\x5a\x4f\x00\x0d\x0a\x1a\x0a lzop compressed data */
901 if (magic
[0] == 0x89 && magic
[1] == 0x4c &&
902 magic
[2] == 0x5a && magic
[3] == 0x4f && magic
[4] == 0x00 && magic
[5] == 0x0d)
903 return COMPRESSION_LZO
;
905 /* XZ compression magic */
908 && magic
[2] == 0x7A && magic
[3] == 0x58 && magic
[4] == 0x5A && magic
[5] == 0x00)
909 return COMPRESSION_XZ
;
911 if (magic
[0] == 0x28 && magic
[1] == 0xB5 && magic
[2] == 0x2F && magic
[3] == 0xFD)
912 return COMPRESSION_ZSTD
;
914 str_len
= strlen (name
);
915 /* HACK: we must believe to extension of LZMA file :) ... */
916 if ((str_len
> 5 && strcmp (&name
[str_len
- 5], ".lzma") == 0) ||
917 (str_len
> 4 && strcmp (&name
[str_len
- 4], ".tlz") == 0))
918 return COMPRESSION_LZMA
;
920 return COMPRESSION_NONE
;
923 /* --------------------------------------------------------------------------------------------- */
926 decompress_extension (int type
)
930 case COMPRESSION_ZIP
:
931 return "/uz" VFS_PATH_URL_DELIMITER
;
932 case COMPRESSION_GZIP
:
933 return "/ugz" VFS_PATH_URL_DELIMITER
;
934 case COMPRESSION_BZIP
:
935 return "/ubz" VFS_PATH_URL_DELIMITER
;
936 case COMPRESSION_BZIP2
:
937 return "/ubz2" VFS_PATH_URL_DELIMITER
;
938 case COMPRESSION_LZIP
:
939 return "/ulz" VFS_PATH_URL_DELIMITER
;
940 case COMPRESSION_LZ4
:
941 return "/ulz4" VFS_PATH_URL_DELIMITER
;
942 case COMPRESSION_LZMA
:
943 return "/ulzma" VFS_PATH_URL_DELIMITER
;
944 case COMPRESSION_LZO
:
945 return "/ulzo" VFS_PATH_URL_DELIMITER
;
947 return "/uxz" VFS_PATH_URL_DELIMITER
;
948 case COMPRESSION_ZSTD
:
949 return "/uzst" VFS_PATH_URL_DELIMITER
;
953 /* Should never reach this place */
954 fprintf (stderr
, "Fatal: decompress_extension called with an unknown argument\n");
958 /* --------------------------------------------------------------------------------------------- */
961 wipe_password (char *passwd
)
967 for (p
= passwd
; *p
!= '\0'; p
++)
973 /* --------------------------------------------------------------------------------------------- */
975 * Convert "\E" -> esc character and ^x to control-x key and ^^ to ^ key
977 * @param p pointer to string
979 * @return newly allocated string
983 convert_controls (const char *p
)
988 valcopy
= g_strdup (p
);
990 /* Parse the escape special character */
991 for (q
= valcopy
; *p
!= '\0';)
997 if (*p
== 'e' || *p
== 'E')
1013 if (c
>= 'a' && c
<= 'z')
1018 else if (*p
!= '\0')
1031 /* --------------------------------------------------------------------------------------------- */
1033 * Finds out a relative path from first to second, i.e. goes as many ..
1034 * as needed up in first and then goes down using second
1038 diff_two_paths (const vfs_path_t
*vpath1
, const vfs_path_t
*vpath2
)
1040 int j
, prevlen
= -1, currlen
;
1041 char *my_first
= NULL
, *my_second
= NULL
;
1044 my_first
= resolve_symlinks (vpath1
);
1045 if (my_first
== NULL
)
1048 my_second
= resolve_symlinks (vpath2
);
1049 if (my_second
== NULL
)
1052 for (j
= 0; j
< 2; j
++)
1065 r
= strchr (p
, PATH_SEP
);
1068 s
= strchr (q
, PATH_SEP
);
1073 if (len
!= (s
- q
) || strncmp (p
, q
, (size_t) len
) != 0)
1080 for (i
= 0; (p
= strchr (p
+ 1, PATH_SEP
)) != NULL
; i
++)
1082 currlen
= (i
+ 1) * 3 + strlen (q
) + 1;
1085 if (currlen
< prevlen
)
1090 p
= buf
= g_malloc (currlen
);
1092 for (; i
>= 0; i
--, p
+= 3)
1103 /* --------------------------------------------------------------------------------------------- */
1105 * Append text to GList, remove all entries with the same text
1109 list_append_unique (GList
*list
, char *text
)
1114 * Go to the last position and traverse the list backwards
1115 * starting from the second last entry to make sure that we
1116 * are not removing the current link.
1118 list
= g_list_append (list
, text
);
1119 list
= g_list_last (list
);
1120 lc_link
= g_list_previous (list
);
1122 while (lc_link
!= NULL
)
1126 newlink
= g_list_previous (lc_link
);
1127 if (strcmp ((char *) lc_link
->data
, text
) == 0)
1131 g_free (lc_link
->data
);
1132 tmp
= g_list_remove_link (list
, lc_link
);
1134 g_list_free_1 (lc_link
);
1142 /* --------------------------------------------------------------------------------------------- */
1144 * Read and restore position for the given filename.
1145 * If there is no stored data, return line 1 and col 0.
1149 load_file_position (const vfs_path_t
*filename_vpath
, long *line
, long *column
, off_t
*offset
,
1154 char buf
[MC_MAXPATHLEN
+ 100];
1155 const size_t len
= vfs_path_len (filename_vpath
);
1162 /* open file with positions */
1163 fn
= mc_config_get_full_path (MC_FILEPOS_FILE
);
1164 f
= fopen (fn
, "r");
1169 /* prepare array for serialized bookmarks */
1170 if (bookmarks
!= NULL
)
1171 *bookmarks
= g_array_sized_new (FALSE
, FALSE
, sizeof (size_t), MAX_SAVED_BOOKMARKS
);
1173 while (fgets (buf
, sizeof (buf
), f
) != NULL
)
1178 /* check if the filename matches the beginning of string */
1179 if (strncmp (buf
, vfs_path_as_str (filename_vpath
), len
) != 0)
1182 /* followed by single space */
1183 if (buf
[len
] != ' ')
1186 /* and string without spaces */
1188 if (strchr (p
, ' ') != NULL
)
1191 pos_tokens
= g_strsplit (p
, ";", 3 + MAX_SAVED_BOOKMARKS
);
1192 if (pos_tokens
[0] == NULL
)
1200 *line
= strtol (pos_tokens
[0], NULL
, 10);
1201 if (pos_tokens
[1] == NULL
)
1208 *column
= strtol (pos_tokens
[1], NULL
, 10);
1209 if (pos_tokens
[2] == NULL
)
1211 else if (bookmarks
!= NULL
)
1215 *offset
= (off_t
) g_ascii_strtoll (pos_tokens
[2], NULL
, 10);
1217 for (i
= 0; i
< MAX_SAVED_BOOKMARKS
&& pos_tokens
[3 + i
] != NULL
; i
++)
1221 val
= strtoul (pos_tokens
[3 + i
], NULL
, 10);
1222 g_array_append_val (*bookmarks
, val
);
1228 g_strfreev (pos_tokens
);
1234 /* --------------------------------------------------------------------------------------------- */
1236 * Save position for the given file
1240 save_file_position (const vfs_path_t
*filename_vpath
, long line
, long column
, off_t offset
,
1243 static size_t filepos_max_saved_entries
= 0;
1246 char buf
[MC_MAXPATHLEN
+ 100];
1248 const size_t len
= vfs_path_len (filename_vpath
);
1249 gboolean src_error
= FALSE
;
1251 if (filepos_max_saved_entries
== 0)
1252 filepos_max_saved_entries
= mc_config_get_int (mc_global
.main_config
, CONFIG_APP_SECTION
,
1253 "filepos_max_saved_entries", 1024);
1255 fn
= mc_config_get_full_path (MC_FILEPOS_FILE
);
1259 mc_util_make_backup_if_possible (fn
, TMP_SUFFIX
);
1262 f
= fopen (fn
, "w");
1264 goto open_target_error
;
1266 tmp_fn
= g_strdup_printf ("%s" TMP_SUFFIX
, fn
);
1267 tmp_f
= fopen (tmp_fn
, "r");
1271 goto open_source_error
;
1274 /* put the new record */
1275 if (line
!= 1 || column
!= 0 || bookmarks
!= NULL
)
1278 (f
, "%s %ld;%ld;%" PRIuMAX
, vfs_path_as_str (filename_vpath
), line
, column
,
1279 (uintmax_t) offset
) < 0)
1280 goto write_position_error
;
1281 if (bookmarks
!= NULL
)
1282 for (i
= 0; i
< bookmarks
->len
&& i
< MAX_SAVED_BOOKMARKS
; i
++)
1283 if (fprintf (f
, ";%zu", g_array_index (bookmarks
, size_t, i
)) < 0)
1284 goto write_position_error
;
1286 if (fprintf (f
, "\n") < 0)
1287 goto write_position_error
;
1291 while (fgets (buf
, sizeof (buf
), tmp_f
) != NULL
)
1293 if (buf
[len
] == ' ' && strncmp (buf
, vfs_path_as_str (filename_vpath
), len
) == 0
1294 && strchr (&buf
[len
+ 1], ' ') == NULL
)
1297 fprintf (f
, "%s", buf
);
1298 if (++i
> filepos_max_saved_entries
)
1302 write_position_error
:
1308 mc_util_restore_from_backup_if_possible (fn
, TMP_SUFFIX
);
1310 mc_util_unlink_backup_if_possible (fn
, TMP_SUFFIX
);
1314 if (bookmarks
!= NULL
)
1315 g_array_free (bookmarks
, TRUE
);
1318 /* --------------------------------------------------------------------------------------------- */
1321 ascii_alpha_to_cntrl (int ch
)
1323 if ((ch
>= ASCII_A
&& ch
<= ASCII_Z
) || (ch
>= ASCII_a
&& ch
<= ASCII_z
))
1329 /* --------------------------------------------------------------------------------------------- */
1334 const char *result
, *sep
;
1337 sep
= strchr (result
, '|');
1339 return sep
!= NULL
? sep
+ 1 : result
;
1342 /* --------------------------------------------------------------------------------------------- */
1345 mc_util_make_backup_if_possible (const char *file_name
, const char *backup_suffix
)
1347 struct stat stat_buf
;
1351 if (!exist_file (file_name
))
1354 backup_path
= g_strdup_printf ("%s%s", file_name
, backup_suffix
);
1355 if (backup_path
== NULL
)
1358 ret
= mc_util_write_backup_content (file_name
, backup_path
);
1361 /* Backup file will have same ownership with main file. */
1362 if (stat (file_name
, &stat_buf
) == 0)
1363 chmod (backup_path
, stat_buf
.st_mode
);
1365 chmod (backup_path
, S_IRUSR
| S_IWUSR
);
1368 g_free (backup_path
);
1373 /* --------------------------------------------------------------------------------------------- */
1376 mc_util_restore_from_backup_if_possible (const char *file_name
, const char *backup_suffix
)
1381 backup_path
= g_strdup_printf ("%s%s", file_name
, backup_suffix
);
1382 if (backup_path
== NULL
)
1385 ret
= mc_util_write_backup_content (backup_path
, file_name
);
1386 g_free (backup_path
);
1391 /* --------------------------------------------------------------------------------------------- */
1394 mc_util_unlink_backup_if_possible (const char *file_name
, const char *backup_suffix
)
1398 backup_path
= g_strdup_printf ("%s%s", file_name
, backup_suffix
);
1399 if (backup_path
== NULL
)
1402 if (exist_file (backup_path
))
1406 vpath
= vfs_path_from_str (backup_path
);
1408 vfs_path_free (vpath
, TRUE
);
1411 g_free (backup_path
);
1415 /* --------------------------------------------------------------------------------------------- */
1417 * partly taken from dcigettext.c, returns "" for default locale
1418 * value should be freed by calling function g_free()
1422 guess_message_value (void)
1424 static const char *const var
[] = {
1425 /* Setting of LC_ALL overwrites all other. */
1426 /* Do not use LANGUAGE for check user locale and drowing hints */
1428 /* Next comes the name of the desired category. */
1430 /* Last possibility is the LANG environment variable. */
1432 /* NULL exit loops */
1437 const char *locale
= NULL
;
1439 for (i
= 0; var
[i
] != NULL
; i
++)
1441 locale
= getenv (var
[i
]);
1442 if (locale
!= NULL
&& locale
[0] != '\0')
1449 return g_strdup (locale
);
1452 /* --------------------------------------------------------------------------------------------- */
1455 * The "profile root" is the tree under which all of MC's user data &
1456 * settings are stored.
1458 * It defaults to the user's home dir. The user may override this default
1459 * with the environment variable $MC_PROFILE_ROOT.
1462 mc_get_profile_root (void)
1464 static const char *profile_root
= NULL
;
1466 if (profile_root
== NULL
)
1468 profile_root
= g_getenv ("MC_PROFILE_ROOT");
1469 if (profile_root
== NULL
|| *profile_root
== '\0')
1470 profile_root
= mc_config_get_home_dir ();
1473 return profile_root
;
1476 /* --------------------------------------------------------------------------------------------- */
1478 * Propagate error in simple way.
1480 * @param dest error return location
1481 * @param code error code
1482 * @param format printf()-style format for error message
1483 * @param ... parameters for message format
1487 mc_propagate_error (GError
**dest
, int code
, const char *format
, ...)
1489 if (dest
!= NULL
&& *dest
== NULL
)
1494 va_start (args
, format
);
1495 tmp_error
= g_error_new_valist (MC_ERROR
, code
, format
, args
);
1498 g_propagate_error (dest
, tmp_error
);
1502 /* --------------------------------------------------------------------------------------------- */
1504 * Replace existing error in simple way.
1506 * @param dest error return location
1507 * @param code error code
1508 * @param format printf()-style format for error message
1509 * @param ... parameters for message format
1513 mc_replace_error (GError
**dest
, int code
, const char *format
, ...)
1520 va_start (args
, format
);
1521 tmp_error
= g_error_new_valist (MC_ERROR
, code
, format
, args
);
1524 g_error_free (*dest
);
1526 g_propagate_error (dest
, tmp_error
);
1530 /* --------------------------------------------------------------------------------------------- */
1533 * Returns if the given duration has elapsed since the given timestamp,
1534 * and if it has then updates the timestamp.
1536 * @param timestamp the last timestamp in microseconds, updated if the given time elapsed
1537 * @param delay amount of time in microseconds
1539 * @return TRUE if clock skew detected, FALSE otherwise
1542 mc_time_elapsed (gint64
*timestamp
, gint64 delay
)
1546 now
= g_get_monotonic_time ();
1548 if (now
>= *timestamp
&& now
< *timestamp
+ delay
)
1555 /* --------------------------------------------------------------------------------------------- */