Ticket #1767: Custom/locale-based date format
[midnight-commander.git] / lib / util.c
blob012feebd2a5eecf065f71322e3f40a9c1bd36156
1 /* Various utilities
2 Copyright (C) 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003,
3 2004, 2005, 2007, 2009 Free Software Foundation, Inc.
4 Written 1994, 1995, 1996 by:
5 Miguel de Icaza, Janne Kukonlehto, Dugan Porter,
6 Jakub Jelinek, Mauricio Plaza.
8 The file_date routine is mostly from GNU's fileutils package,
9 written by Richard Stallman and David MacKenzie.
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
25 /** \file
26 * \brief Source: various utilities
29 #include <config.h>
31 #include <ctype.h>
32 #include <limits.h>
33 #include <stdarg.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <fcntl.h>
38 #include <sys/time.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <unistd.h>
43 #include "lib/global.h"
44 #include "lib/tty/win.h" /* xterm_flag */
45 #include "lib/search.h"
46 #include "lib/mcconfig.h"
47 #include "lib/timefmt.h"
48 #include "lib/fileloc.h"
49 #include "lib/vfs/mc-vfs/vfs.h"
50 #include "lib/strutil.h"
52 #include "src/file.h" /* copy_file_file() */
53 #ifndef HAVE_CHARSET
54 #include "src/main.h" /* eight_bit_clean */
55 #endif
57 int easy_patterns = 1;
60 * If true, SI units (1000 based) will be used for
61 * larger units (kilobyte, megabyte, ...).
62 * If false binary units (1024 based) will be used.
64 int kilobyte_si = 0;
66 char *user_recent_timeformat = NULL; /* time format string for recent dates */
67 char *user_old_timeformat = NULL; /* time format string for older dates */
69 extern void str_replace(char *s, char from, char to)
71 for (; *s != '\0'; s++) {
72 if (*s == from)
73 *s = to;
77 static inline int
78 is_7bit_printable (unsigned char c)
80 return (c > 31 && c < 127);
83 static inline int
84 is_iso_printable (unsigned char c)
86 return ((c > 31 && c < 127) || c >= 160);
89 static inline int
90 is_8bit_printable (unsigned char c)
92 /* "Full 8 bits output" doesn't work on xterm */
93 if (xterm_flag)
94 return is_iso_printable (c);
96 return (c > 31 && c != 127 && c != 155);
99 int
100 is_printable (int c)
102 c &= 0xff;
104 #ifdef HAVE_CHARSET
105 /* "Display bits" is ignored, since the user controls the output
106 by setting the output codepage */
107 return is_8bit_printable (c);
108 #else
109 if (!eight_bit_clean)
110 return is_7bit_printable (c);
112 if (full_eight_bits) {
113 return is_8bit_printable (c);
114 } else
115 return is_iso_printable (c);
116 #endif /* !HAVE_CHARSET */
119 /* Calculates the message dimensions (lines and columns) */
120 void
121 msglen (const char *text, int *lines, int *columns)
123 int nlines = 1; /* even the empty string takes one line */
124 int ncolumns = 0;
125 int colindex = 0;
127 for (; *text != '\0'; text++) {
128 if (*text == '\n') {
129 nlines++;
130 colindex = 0;
131 } else {
132 colindex++;
133 if (colindex > ncolumns)
134 ncolumns = colindex;
138 *lines = nlines;
139 *columns = ncolumns;
143 * Copy from s to d, and trim the beginning if necessary, and prepend
144 * "..." in this case. The destination string can have at most len
145 * bytes, not counting trailing 0.
147 char *
148 trim (const char *s, char *d, int len)
150 int source_len;
152 /* Sanity check */
153 len = max (len, 0);
155 source_len = strlen (s);
156 if (source_len > len) {
157 /* Cannot fit the whole line */
158 if (len <= 3) {
159 /* We only have room for the dots */
160 memset (d, '.', len);
161 d[len] = 0;
162 return d;
163 } else {
164 /* Begin with ... and add the rest of the source string */
165 memset (d, '.', 3);
166 strcpy (d + 3, s + 3 + source_len - len);
168 } else
169 /* We can copy the whole line */
170 strcpy (d, s);
171 return d;
175 * Quote the filename for the purpose of inserting it into the command
176 * line. If quote_percent is 1, replace "%" with "%%" - the percent is
177 * processed by the mc command line.
179 char *
180 name_quote (const char *s, int quote_percent)
182 char *ret, *d;
184 d = ret = g_malloc (strlen (s) * 2 + 2 + 1);
185 if (*s == '-') {
186 *d++ = '.';
187 *d++ = '/';
190 for (; *s; s++, d++) {
191 switch (*s) {
192 case '%':
193 if (quote_percent)
194 *d++ = '%';
195 break;
196 case '\'':
197 case '\\':
198 case '\r':
199 case '\n':
200 case '\t':
201 case '"':
202 case ';':
203 case ' ':
204 case '?':
205 case '|':
206 case '[':
207 case ']':
208 case '{':
209 case '}':
210 case '<':
211 case '>':
212 case '`':
213 case '!':
214 case '$':
215 case '&':
216 case '*':
217 case '(':
218 case ')':
219 *d++ = '\\';
220 break;
221 case '~':
222 case '#':
223 if (d == ret)
224 *d++ = '\\';
225 break;
227 *d = *s;
229 *d = '\0';
230 return ret;
233 char *
234 fake_name_quote (const char *s, int quote_percent)
236 (void) quote_percent;
237 return g_strdup (s);
241 * Remove the middle part of the string to fit given length.
242 * Use "~" to show where the string was truncated.
243 * Return static buffer, no need to free() it.
245 const char *
246 name_trunc (const char *txt, size_t trunc_len)
248 return str_trunc (txt, trunc_len);
252 * path_trunc() is the same as name_trunc() above but
253 * it deletes possible password from path for security
254 * reasons.
256 const char *
257 path_trunc (const char *path, size_t trunc_len) {
258 char *secure_path = strip_password (g_strdup (path), 1);
260 const char *ret = str_trunc (secure_path, trunc_len);
261 g_free (secure_path);
263 return ret;
266 const char *
267 size_trunc (double size)
269 static char x [BUF_TINY];
270 long int divisor = 1;
271 const char *xtra = "";
273 if (size > 999999999L){
274 divisor = kilobyte_si?1000:1024;
275 xtra = kilobyte_si?"k":"K";
276 if (size/divisor > 999999999L){
277 divisor = kilobyte_si?(1000*1000):(1024*1024);
278 xtra = kilobyte_si?"m":"M";
281 g_snprintf (x, sizeof (x), "%.0f%s", (size/divisor), xtra);
282 return x;
285 const char *
286 size_trunc_sep (double size)
288 static char x [60];
289 int count;
290 const char *p, *y;
291 char *d;
293 p = y = size_trunc (size);
294 p += strlen (p) - 1;
295 d = x + sizeof (x) - 1;
296 *d-- = 0;
297 while (p >= y && isalpha ((unsigned char) *p))
298 *d-- = *p--;
299 for (count = 0; p >= y; count++){
300 if (count == 3){
301 *d-- = ',';
302 count = 0;
304 *d-- = *p--;
306 d++;
307 if (*d == ',')
308 d++;
309 return d;
313 * Print file SIZE to BUFFER, but don't exceed LEN characters,
314 * not including trailing 0. BUFFER should be at least LEN+1 long.
315 * This function is called for every file on panels, so avoid
316 * floating point by any means.
318 * Units: size units (filesystem sizes are 1K blocks)
319 * 0=bytes, 1=Kbytes, 2=Mbytes, etc.
321 void
322 size_trunc_len (char *buffer, unsigned int len, off_t size, int units)
324 /* Avoid taking power for every file. */
325 static const off_t power10 [] =
326 {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000,
327 1000000000};
328 static const char * const suffix [] =
329 {"", "K", "M", "G", "T", "P", "E", "Z", "Y", NULL};
330 static const char * const suffix_lc [] =
331 {"", "k", "m", "g", "t", "p", "e", "z", "y", NULL};
332 int j = 0;
333 int size_remain;
335 if (len == 0)
336 len = 9;
339 * recalculate from 1024 base to 1000 base if units>0
340 * We can't just multiply by 1024 - that might cause overflow
341 * if off_t type is too small
343 if (units && kilobyte_si) {
344 for (j = 0; j < units; j++) {
345 size_remain=((size % 125)*1024)/1000; /* size mod 125, recalculated */
346 size = size / 125; /* 128/125 = 1024/1000 */
347 size = size * 128; /* This will convert size from multiple of 1024 to multiple of 1000 */
348 size += size_remain; /* Re-add remainder lost by division/multiplication */
352 for (j = units; suffix [j] != NULL; j++) {
353 if (size == 0) {
354 if (j == units) {
355 /* Empty files will print "0" even with minimal width. */
356 g_snprintf (buffer, len + 1, "0");
357 break;
360 /* Use "~K" or just "K" if len is 1. Use "B" for bytes. */
361 g_snprintf (buffer, len + 1, (len > 1) ? "~%s" : "%s",
362 (j > 1) ? (kilobyte_si ? suffix_lc[j - 1] : suffix[j - 1]) : "B");
363 break;
366 if (size < power10 [len - (j > 0)]) {
367 g_snprintf (buffer, len + 1, "%lu%s", (unsigned long) size, kilobyte_si ? suffix_lc[j] : suffix[j]);
368 break;
371 /* Powers of 1000 or 1024, with rounding. */
372 if (kilobyte_si) {
373 size = (size + 500) / 1000;
374 } else {
375 size = (size + 512) >> 10;
381 is_exe (mode_t mode)
383 if ((S_IXUSR & mode) || (S_IXGRP & mode) || (S_IXOTH & mode))
384 return 1;
385 return 0;
388 #define ismode(n,m) ((n & m) == m)
390 const char *
391 string_perm (mode_t mode_bits)
393 static char mode[11];
395 strcpy (mode, "----------");
396 if (S_ISDIR (mode_bits))
397 mode[0] = 'd';
398 if (S_ISCHR (mode_bits))
399 mode[0] = 'c';
400 if (S_ISBLK (mode_bits))
401 mode[0] = 'b';
402 if (S_ISLNK (mode_bits))
403 mode[0] = 'l';
404 if (S_ISFIFO (mode_bits))
405 mode[0] = 'p';
406 if (S_ISNAM (mode_bits))
407 mode[0] = 'n';
408 if (S_ISSOCK (mode_bits))
409 mode[0] = 's';
410 if (S_ISDOOR (mode_bits))
411 mode[0] = 'D';
412 if (ismode (mode_bits, S_IXOTH))
413 mode[9] = 'x';
414 if (ismode (mode_bits, S_IWOTH))
415 mode[8] = 'w';
416 if (ismode (mode_bits, S_IROTH))
417 mode[7] = 'r';
418 if (ismode (mode_bits, S_IXGRP))
419 mode[6] = 'x';
420 if (ismode (mode_bits, S_IWGRP))
421 mode[5] = 'w';
422 if (ismode (mode_bits, S_IRGRP))
423 mode[4] = 'r';
424 if (ismode (mode_bits, S_IXUSR))
425 mode[3] = 'x';
426 if (ismode (mode_bits, S_IWUSR))
427 mode[2] = 'w';
428 if (ismode (mode_bits, S_IRUSR))
429 mode[1] = 'r';
430 #ifdef S_ISUID
431 if (ismode (mode_bits, S_ISUID))
432 mode[3] = (mode[3] == 'x') ? 's' : 'S';
433 #endif /* S_ISUID */
434 #ifdef S_ISGID
435 if (ismode (mode_bits, S_ISGID))
436 mode[6] = (mode[6] == 'x') ? 's' : 'S';
437 #endif /* S_ISGID */
438 #ifdef S_ISVTX
439 if (ismode (mode_bits, S_ISVTX))
440 mode[9] = (mode[9] == 'x') ? 't' : 'T';
441 #endif /* S_ISVTX */
442 return mode;
445 /* p: string which might contain an url with a password (this parameter is
446 modified in place).
447 has_prefix = 0: The first parameter is an url without a prefix
448 (user[:pass]@]machine[:port][remote-dir). Delete
449 the password.
450 has_prefix = 1: Search p for known url prefixes. If found delete
451 the password from the url.
452 Caveat: only the first url is found
454 char *
455 strip_password (char *p, int has_prefix)
457 static const struct {
458 const char *name;
459 size_t len;
460 } prefixes[] = { {"/#ftp:", 6},
461 {"ftp://", 6},
462 {"/#mc:", 5},
463 {"mc://", 5},
464 {"/#smb:", 6},
465 {"smb://", 6},
466 {"/#sh:", 5},
467 {"sh://", 5},
468 {"ssh://", 6}
470 char *at, *inner_colon, *dir;
471 size_t i;
472 char *result = p;
474 for (i = 0; i < sizeof (prefixes)/sizeof (prefixes[0]); i++) {
475 char *q;
477 if (has_prefix) {
478 if((q = strstr (p, prefixes[i].name)) == 0)
479 continue;
480 else
481 p = q + prefixes[i].len;
484 if ((dir = strchr (p, PATH_SEP)) != NULL)
485 *dir = '\0';
487 /* search for any possible user */
488 at = strrchr (p, '@');
490 if (dir)
491 *dir = PATH_SEP;
493 /* We have a username */
494 if (at) {
495 inner_colon = memchr (p, ':', at - p);
496 if (inner_colon)
497 memmove (inner_colon, at, strlen(at) + 1);
499 break;
501 return (result);
504 const char *
505 strip_home_and_password(const char *dir)
507 size_t len;
508 static char newdir [MC_MAXPATHLEN];
510 if (home_dir && !strncmp (dir, home_dir, len = strlen (home_dir)) &&
511 (dir[len] == PATH_SEP || dir[len] == '\0')){
512 newdir [0] = '~';
513 g_strlcpy (&newdir [1], &dir [len], sizeof(newdir) - 1);
514 return newdir;
517 /* We do not strip homes in /#ftp tree, I do not like ~'s there
518 (see ftpfs.c why) */
519 g_strlcpy (newdir, dir, sizeof(newdir));
520 strip_password (newdir, 1);
521 return newdir;
524 const char *
525 extension (const char *filename)
527 const char *d = strrchr (filename, '.');
528 return (d != NULL) ? d + 1 : "";
532 exist_file (const char *name)
534 return access (name, R_OK) == 0;
538 check_for_default (const char *default_file, const char *file)
540 if (!exist_file (file)) {
541 FileOpContext *ctx;
542 off_t count = 0;
543 double bytes = 0.0;
545 if (!exist_file (default_file))
546 return -1;
548 ctx = file_op_context_new (OP_COPY);
549 file_op_context_create_ui (ctx, 0);
550 copy_file_file (ctx, default_file, file, 1, &count, &bytes, 1);
551 file_op_context_destroy (ctx);
554 return 0;
559 char *
560 load_file (const char *filename)
562 FILE *data_file;
563 struct stat s;
564 char *data;
565 long read_size;
567 if ((data_file = fopen (filename, "r")) == NULL){
568 return 0;
570 if (fstat (fileno (data_file), &s) != 0){
571 fclose (data_file);
572 return 0;
574 data = g_malloc (s.st_size+1);
575 read_size = fread (data, 1, s.st_size, data_file);
576 data [read_size] = 0;
577 fclose (data_file);
579 if (read_size > 0)
580 return data;
581 else {
582 g_free (data);
583 return 0;
587 char *
588 load_mc_home_file (const char *_mc_home, const char *_mc_home_alt, const char *filename, char **allocated_filename)
590 char *hintfile_base, *hintfile;
591 char *lang;
592 char *data;
594 hintfile_base = concat_dir_and_file (_mc_home, filename);
595 lang = guess_message_value ();
597 hintfile = g_strconcat (hintfile_base, ".", lang, (char *) NULL);
598 data = load_file (hintfile);
600 if (!data) {
601 g_free (hintfile);
602 g_free (hintfile_base);
603 hintfile_base = concat_dir_and_file (_mc_home_alt, filename);
605 hintfile = g_strconcat (hintfile_base, ".", lang, (char *) NULL);
606 data = load_file (hintfile);
608 if (!data) {
609 /* Fall back to the two-letter language code */
610 if (lang[0] && lang[1])
611 lang[2] = 0;
612 hintfile = g_strconcat (hintfile_base, ".", lang, (char *) NULL);
613 data = load_file (hintfile);
615 if (!data) {
616 g_free (hintfile);
617 hintfile = hintfile_base;
618 data = load_file (hintfile_base);
623 g_free (lang);
625 if (hintfile != hintfile_base)
626 g_free (hintfile_base);
628 if (allocated_filename)
629 *allocated_filename = hintfile;
630 else
631 g_free (hintfile);
633 return data;
636 /* Check strftime() results. Some systems (i.e. Solaris) have different
637 short-month-name sizes for different locales */
638 size_t
639 i18n_checktimelength (void)
641 size_t length;
642 time_t testtime = time (NULL);
643 struct tm* lt = localtime(&testtime);
645 if (lt == NULL) {
646 /* huh, localtime() doesnt seem to work ... falling back to "(invalid)" */
647 length = str_term_width1 (_(INVALID_TIME_TEXT));
648 } else {
649 char buf [MB_LEN_MAX * MAX_I18NTIMELENGTH + 1];
650 size_t a, b;
652 strftime (buf, sizeof(buf) - 1, user_recent_timeformat, lt);
653 a = str_term_width1 (buf);
654 strftime (buf, sizeof(buf) - 1, user_old_timeformat, lt);
655 b = str_term_width1 (buf);
657 length = max (a, b);
658 length = max ((size_t)str_term_width1 (_(INVALID_TIME_TEXT)), length);
661 /* Don't handle big differences. Use standard value (email bug, please) */
662 if (length > MAX_I18NTIMELENGTH || length < MIN_I18NTIMELENGTH)
663 length = STD_I18NTIMELENGTH;
665 return length;
668 const char *
669 file_date (time_t when)
671 static char timebuf [MB_LEN_MAX * MAX_I18NTIMELENGTH + 1];
672 time_t current_time = time ((time_t) 0);
673 const char *fmt;
675 if (current_time > when + 6L * 30L * 24L * 60L * 60L /* Old. */
676 || current_time < when - 60L * 60L) /* In the future. */
677 /* The file is fairly old or in the future.
678 POSIX says the cutoff is 6 months old;
679 approximate this by 6*30 days.
680 Allow a 1 hour slop factor for what is considered "the future",
681 to allow for NFS server/client clock disagreement.
682 Show the year instead of the time of day. */
684 fmt = user_old_timeformat;
685 else
686 fmt = user_recent_timeformat;
688 FMT_LOCALTIME(timebuf, sizeof (timebuf), fmt, when);
690 return timebuf;
693 const char *
694 extract_line (const char *s, const char *top)
696 static char tmp_line [BUF_MEDIUM];
697 char *t = tmp_line;
699 while (*s && *s != '\n' && (size_t) (t - tmp_line) < sizeof (tmp_line)-1 && s < top)
700 *t++ = *s++;
701 *t = 0;
702 return tmp_line;
705 /* The basename routine */
706 const char *
707 x_basename (const char *s)
709 const char *where;
710 return ((where = strrchr (s, PATH_SEP))) ? where + 1 : s;
714 const char *
715 unix_error_string (int error_num)
717 static char buffer [BUF_LARGE];
718 gchar *strerror_currentlocale;
720 strerror_currentlocale = g_locale_from_utf8(g_strerror (error_num), -1, NULL, NULL, NULL);
721 g_snprintf (buffer, sizeof (buffer), "%s (%d)",
722 strerror_currentlocale, error_num);
723 g_free(strerror_currentlocale);
725 return buffer;
728 const char *
729 skip_separators (const char *s)
731 const char *su = s;
733 for (;*su; str_cnext_char (&su))
734 if (*su != ' ' && *su != '\t' && *su != ',') break;
736 return su;
739 const char *
740 skip_numbers (const char *s)
742 const char *su = s;
744 for (;*su; str_cnext_char (&su))
745 if (!str_isdigit (su)) break;
747 return su;
750 /* Remove all control sequences from the argument string. We define
751 * "control sequence", in a sort of pidgin BNF, as follows:
753 * control-seq = Esc non-'['
754 * | Esc '[' (0 or more digits or ';' or '?') (any other char)
756 * This scheme works for all the terminals described in my termcap /
757 * terminfo databases, except the Hewlett-Packard 70092 and some Wyse
758 * terminals. If I hear from a single person who uses such a terminal
759 * with MC, I'll be glad to add support for it. (Dugan)
760 * Non-printable characters are also removed.
763 char *
764 strip_ctrl_codes (char *s)
766 char *w; /* Current position where the stripped data is written */
767 char *r; /* Current position where the original data is read */
768 char *n;
770 if (!s)
771 return 0;
773 for (w = s, r = s; *r; ) {
774 if (*r == ESC_CHAR) {
775 /* Skip the control sequence's arguments */ ;
776 /* '(' need to avoid strange 'B' letter in *Suse (if mc runs under root user) */
777 if (*(++r) == '[' || *r == '(') {
778 /* strchr() matches trailing binary 0 */
779 while (*(++r) && strchr ("0123456789;?", *r));
780 } else
781 if (*r == ']') {
783 * Skip xterm's OSC (Operating System Command)
784 * http://www.xfree86.org/current/ctlseqs.html
785 * OSC P s ; P t ST
786 * OSC P s ; P t BEL
788 char * new_r = r;
790 for (; *new_r; ++new_r)
792 switch (*new_r)
794 /* BEL */
795 case '\a':
796 r = new_r;
797 goto osc_out;
798 case ESC_CHAR:
799 /* ST */
800 if (*(new_r + 1) == '\\')
802 r = new_r + 1;
803 goto osc_out;
807 osc_out:;
811 * Now we are at the last character of the sequence.
812 * Skip it unless it's binary 0.
814 if (*r)
815 r++;
816 continue;
819 n = str_get_next_char (r);
820 if (str_isprint (r)) {
821 memmove (w, r, n - r);
822 w+= n - r;
824 r = n;
826 *w = 0;
827 return s;
831 #ifndef ENABLE_VFS
832 char *
833 get_current_wd (char *buffer, int size)
835 char *p;
836 int len;
838 p = g_get_current_dir ();
839 len = strlen(p) + 1;
841 if (len > size) {
842 g_free (p);
843 return NULL;
846 memcpy (buffer, p, len);
847 g_free (p);
849 return buffer;
851 #endif /* !ENABLE_VFS */
853 enum compression_type
854 get_compression_type (int fd, const char * name)
856 unsigned char magic[16];
857 size_t str_len;
859 /* Read the magic signature */
860 if (mc_read (fd, (char *) magic, 4) != 4)
861 return COMPRESSION_NONE;
863 /* GZIP_MAGIC and OLD_GZIP_MAGIC */
864 if (magic[0] == 037 && (magic[1] == 0213 || magic[1] == 0236)) {
865 return COMPRESSION_GZIP;
868 /* PKZIP_MAGIC */
869 if (magic[0] == 0120 && magic[1] == 0113 && magic[2] == 003
870 && magic[3] == 004) {
871 /* Read compression type */
872 mc_lseek (fd, 8, SEEK_SET);
873 if (mc_read (fd, (char *) magic, 2) != 2)
874 return COMPRESSION_NONE;
876 /* Gzip can handle only deflated (8) or stored (0) files */
877 if ((magic[0] != 8 && magic[0] != 0) || magic[1] != 0)
878 return COMPRESSION_NONE;
880 /* Compatible with gzip */
881 return COMPRESSION_GZIP;
884 /* PACK_MAGIC and LZH_MAGIC and compress magic */
885 if (magic[0] == 037
886 && (magic[1] == 036 || magic[1] == 0240 || magic[1] == 0235)) {
887 /* Compatible with gzip */
888 return COMPRESSION_GZIP;
891 /* BZIP and BZIP2 files */
892 if ((magic[0] == 'B') && (magic[1] == 'Z') &&
893 (magic[3] >= '1') && (magic[3] <= '9')) {
894 switch (magic[2]) {
895 case '0':
896 return COMPRESSION_BZIP;
897 case 'h':
898 return COMPRESSION_BZIP2;
902 /* Support for LZMA (only utils format with magic in header).
903 * This is the default format of LZMA utils 4.32.1 and later. */
905 if (mc_read(fd, (char *) magic+4, 2) != 2)
906 return COMPRESSION_NONE;
908 /* LZMA utils format */
909 if (
910 magic[0] == 0xFF
911 && magic[1] == 'L'
912 && magic[2] == 'Z'
913 && magic[3] == 'M'
914 && magic[4] == 'A'
915 && magic[5] == 0x00
917 return COMPRESSION_LZMA;
919 /* XZ compression magic */
920 if (
921 magic[0] == 0xFD
922 && magic[1] == 0x37
923 && magic[2] == 0x7A
924 && magic[3] == 0x58
925 && magic[4] == 0x5A
926 && magic[5] == 0x00
928 return COMPRESSION_XZ;
930 str_len = strlen(name);
931 /* HACK: we must belive to extention of LZMA file :) ...*/
932 if ( (str_len > 5 && strcmp(&name[str_len-5],".lzma") == 0) ||
933 (str_len > 4 && strcmp(&name[str_len-4],".tlz") == 0))
934 return COMPRESSION_LZMA;
936 return COMPRESSION_NONE;
939 const char *
940 decompress_extension (int type)
942 switch (type){
943 case COMPRESSION_GZIP: return "#ugz";
944 case COMPRESSION_BZIP: return "#ubz";
945 case COMPRESSION_BZIP2: return "#ubz2";
946 case COMPRESSION_LZMA: return "#ulzma";
947 case COMPRESSION_XZ: return "#uxz";
949 /* Should never reach this place */
950 fprintf (stderr, "Fatal: decompress_extension called with an unknown argument\n");
951 return 0;
954 /* Hooks */
955 void
956 add_hook (Hook **hook_list, void (*hook_fn)(void *), void *data)
958 Hook *new_hook = g_new (Hook, 1);
960 new_hook->hook_fn = hook_fn;
961 new_hook->next = *hook_list;
962 new_hook->hook_data = data;
964 *hook_list = new_hook;
967 void
968 execute_hooks (Hook *hook_list)
970 Hook *new_hook = 0;
971 Hook *p;
973 /* We copy the hook list first so tahat we let the hook
974 * function call delete_hook
977 while (hook_list){
978 add_hook (&new_hook, hook_list->hook_fn, hook_list->hook_data);
979 hook_list = hook_list->next;
981 p = new_hook;
983 while (new_hook){
984 (*new_hook->hook_fn)(new_hook->hook_data);
985 new_hook = new_hook->next;
988 for (hook_list = p; hook_list;){
989 p = hook_list;
990 hook_list = hook_list->next;
991 g_free (p);
995 void
996 delete_hook (Hook **hook_list, void (*hook_fn)(void *))
998 Hook *current, *new_list, *next;
1000 new_list = 0;
1002 for (current = *hook_list; current; current = next){
1003 next = current->next;
1004 if (current->hook_fn == hook_fn)
1005 g_free (current);
1006 else
1007 add_hook (&new_list, current->hook_fn, current->hook_data);
1009 *hook_list = new_list;
1013 hook_present (Hook *hook_list, void (*hook_fn)(void *))
1015 Hook *p;
1017 for (p = hook_list; p; p = p->next)
1018 if (p->hook_fn == hook_fn)
1019 return 1;
1020 return 0;
1023 void
1024 wipe_password (char *passwd)
1026 char *p = passwd;
1028 if (!p)
1029 return;
1030 for (;*p ; p++)
1031 *p = 0;
1032 g_free (passwd);
1035 /* Convert "\E" -> esc character and ^x to control-x key and ^^ to ^ key */
1036 /* Returns a newly allocated string */
1037 char *
1038 convert_controls (const char *p)
1040 char *valcopy = g_strdup (p);
1041 char *q;
1043 /* Parse the escape special character */
1044 for (q = valcopy; *p;){
1045 if (*p == '\\'){
1046 p++;
1047 if ((*p == 'e') || (*p == 'E')){
1048 p++;
1049 *q++ = ESC_CHAR;
1051 } else {
1052 if (*p == '^'){
1053 p++;
1054 if (*p == '^')
1055 *q++ = *p++;
1056 else {
1057 char c = (*p | 0x20);
1058 if (c >= 'a' && c <= 'z') {
1059 *q++ = c - 'a' + 1;
1060 p++;
1061 } else if (*p)
1062 p++;
1064 } else
1065 *q++ = *p++;
1068 *q = 0;
1069 return valcopy;
1072 static char *
1073 resolve_symlinks (const char *path)
1075 char *buf, *buf2, *q, *r, c;
1076 int len;
1077 struct stat mybuf;
1078 const char *p;
1080 if (*path != PATH_SEP)
1081 return NULL;
1082 r = buf = g_malloc (MC_MAXPATHLEN);
1083 buf2 = g_malloc (MC_MAXPATHLEN);
1084 *r++ = PATH_SEP;
1085 *r = 0;
1086 p = path;
1087 for (;;) {
1088 q = strchr (p + 1, PATH_SEP);
1089 if (!q) {
1090 q = strchr (p + 1, 0);
1091 if (q == p + 1)
1092 break;
1094 c = *q;
1095 *q = 0;
1096 if (mc_lstat (path, &mybuf) < 0) {
1097 g_free (buf);
1098 g_free (buf2);
1099 *q = c;
1100 return NULL;
1102 if (!S_ISLNK (mybuf.st_mode))
1103 strcpy (r, p + 1);
1104 else {
1105 len = mc_readlink (path, buf2, MC_MAXPATHLEN - 1);
1106 if (len < 0) {
1107 g_free (buf);
1108 g_free (buf2);
1109 *q = c;
1110 return NULL;
1112 buf2 [len] = 0;
1113 if (*buf2 == PATH_SEP)
1114 strcpy (buf, buf2);
1115 else
1116 strcpy (r, buf2);
1118 canonicalize_pathname (buf);
1119 r = strchr (buf, 0);
1120 if (!*r || *(r - 1) != PATH_SEP) {
1121 *r++ = PATH_SEP;
1122 *r = 0;
1124 *q = c;
1125 p = q;
1126 if (!c)
1127 break;
1129 if (!*buf)
1130 strcpy (buf, PATH_SEP_STR);
1131 else if (*(r - 1) == PATH_SEP && r != buf + 1)
1132 *(r - 1) = 0;
1133 g_free (buf2);
1134 return buf;
1137 static gboolean
1138 mc_util_write_backup_content(const char *from_file_name, const char *to_file_name)
1140 FILE *backup_fd;
1141 char *contents;
1142 gsize length;
1144 if (!g_file_get_contents (from_file_name, &contents, &length, NULL))
1145 return FALSE;
1147 backup_fd = fopen (to_file_name, "w");
1148 if (backup_fd == NULL) {
1149 g_free(contents);
1150 return FALSE;
1153 fwrite ( (const void *) contents, length, 1, backup_fd);
1155 fflush(backup_fd);
1156 fclose(backup_fd);
1157 g_free(contents);
1158 return TRUE;
1161 /* Finds out a relative path from first to second, i.e. goes as many ..
1162 * as needed up in first and then goes down using second */
1163 char *
1164 diff_two_paths (const char *first, const char *second)
1166 char *p, *q, *r, *s, *buf = NULL;
1167 int i, j, prevlen = -1, currlen;
1168 char *my_first = NULL, *my_second = NULL;
1170 my_first = resolve_symlinks (first);
1171 if (my_first == NULL)
1172 return NULL;
1173 my_second = resolve_symlinks (second);
1174 if (my_second == NULL) {
1175 g_free (my_first);
1176 return NULL;
1178 for (j = 0; j < 2; j++) {
1179 p = my_first;
1180 q = my_second;
1181 for (;;) {
1182 r = strchr (p, PATH_SEP);
1183 s = strchr (q, PATH_SEP);
1184 if (!r || !s)
1185 break;
1186 *r = 0; *s = 0;
1187 if (strcmp (p, q)) {
1188 *r = PATH_SEP; *s = PATH_SEP;
1189 break;
1190 } else {
1191 *r = PATH_SEP; *s = PATH_SEP;
1193 p = r + 1;
1194 q = s + 1;
1196 p--;
1197 for (i = 0; (p = strchr (p + 1, PATH_SEP)) != NULL; i++);
1198 currlen = (i + 1) * 3 + strlen (q) + 1;
1199 if (j) {
1200 if (currlen < prevlen)
1201 g_free (buf);
1202 else {
1203 g_free (my_first);
1204 g_free (my_second);
1205 return buf;
1208 p = buf = g_malloc (currlen);
1209 prevlen = currlen;
1210 for (; i >= 0; i--, p += 3)
1211 strcpy (p, "../");
1212 strcpy (p, q);
1214 g_free (my_first);
1215 g_free (my_second);
1216 return buf;
1219 /* If filename is NULL, then we just append PATH_SEP to the dir */
1220 char *
1221 concat_dir_and_file (const char *dir, const char *file)
1223 int i = strlen (dir);
1225 if (dir [i-1] == PATH_SEP)
1226 return g_strconcat (dir, file, (char *) NULL);
1227 else
1228 return g_strconcat (dir, PATH_SEP_STR, file, (char *) NULL);
1231 /* Append text to GList, remove all entries with the same text */
1232 GList *
1233 list_append_unique (GList *list, char *text)
1235 GList *lc_link;
1238 * Go to the last position and traverse the list backwards
1239 * starting from the second last entry to make sure that we
1240 * are not removing the current link.
1242 list = g_list_append (list, text);
1243 list = g_list_last (list);
1244 lc_link = g_list_previous (list);
1246 while (lc_link != NULL) {
1247 GList *newlink;
1249 newlink = g_list_previous (lc_link);
1250 if (strcmp ((char *) lc_link->data, text) == 0) {
1251 GList *tmp;
1253 g_free (lc_link->data);
1254 tmp = g_list_remove_link (list, lc_link);
1255 g_list_free_1 (lc_link);
1257 lc_link = newlink;
1260 return list;
1263 /* Following code heavily borrows from libiberty, mkstemps.c */
1265 /* Number of attempts to create a temporary file */
1266 #ifndef TMP_MAX
1267 #define TMP_MAX 16384
1268 #endif /* !TMP_MAX */
1271 * Arguments:
1272 * pname (output) - pointer to the name of the temp file (needs g_free).
1273 * NULL if the function fails.
1274 * prefix - part of the filename before the random part.
1275 * Prepend $TMPDIR or /tmp if there are no path separators.
1276 * suffix - if not NULL, part of the filename after the random part.
1278 * Result:
1279 * handle of the open file or -1 if couldn't open any.
1282 mc_mkstemps (char **pname, const char *prefix, const char *suffix)
1284 static const char letters[]
1285 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
1286 static unsigned long value;
1287 struct timeval tv;
1288 char *tmpbase;
1289 char *tmpname;
1290 char *XXXXXX;
1291 int count;
1293 if (strchr (prefix, PATH_SEP) == NULL) {
1294 /* Add prefix first to find the position of XXXXXX */
1295 tmpbase = concat_dir_and_file (mc_tmpdir (), prefix);
1296 } else {
1297 tmpbase = g_strdup (prefix);
1300 tmpname = g_strconcat (tmpbase, "XXXXXX", suffix, (char *) NULL);
1301 *pname = tmpname;
1302 XXXXXX = &tmpname[strlen (tmpbase)];
1303 g_free (tmpbase);
1305 /* Get some more or less random data. */
1306 gettimeofday (&tv, NULL);
1307 value += (tv.tv_usec << 16) ^ tv.tv_sec ^ getpid ();
1309 for (count = 0; count < TMP_MAX; ++count) {
1310 unsigned long v = value;
1311 int fd;
1313 /* Fill in the random bits. */
1314 XXXXXX[0] = letters[v % 62];
1315 v /= 62;
1316 XXXXXX[1] = letters[v % 62];
1317 v /= 62;
1318 XXXXXX[2] = letters[v % 62];
1319 v /= 62;
1320 XXXXXX[3] = letters[v % 62];
1321 v /= 62;
1322 XXXXXX[4] = letters[v % 62];
1323 v /= 62;
1324 XXXXXX[5] = letters[v % 62];
1326 fd = open (tmpname, O_RDWR | O_CREAT | O_TRUNC | O_EXCL,
1327 S_IRUSR | S_IWUSR);
1328 if (fd >= 0) {
1329 /* Successfully created. */
1330 return fd;
1333 /* This is a random value. It is only necessary that the next
1334 TMP_MAX values generated by adding 7777 to VALUE are different
1335 with (module 2^32). */
1336 value += 7777;
1339 /* Unsuccessful. Free the filename. */
1340 g_free (tmpname);
1341 *pname = NULL;
1343 return -1;
1347 * Read and restore position for the given filename.
1348 * If there is no stored data, return line 1 and col 0.
1350 void
1351 load_file_position (const char *filename, long *line, long *column, off_t *offset)
1353 char *fn;
1354 FILE *f;
1355 char buf[MC_MAXPATHLEN + 20];
1356 int len;
1358 /* defaults */
1359 *line = 1;
1360 *column = 0;
1361 *offset = 0;
1363 /* open file with positions */
1364 fn = g_build_filename (home_dir, MC_USERCONF_DIR, MC_FILEPOS_FILE, NULL);
1365 f = fopen (fn, "r");
1366 g_free (fn);
1367 if (!f)
1368 return;
1370 len = strlen (filename);
1372 while (fgets (buf, sizeof (buf), f)) {
1373 const char *p;
1374 gchar **pos_tokens;
1376 /* check if the filename matches the beginning of string */
1377 if (strncmp (buf, filename, len) != 0)
1378 continue;
1380 /* followed by single space */
1381 if (buf[len] != ' ')
1382 continue;
1384 /* and string without spaces */
1385 p = &buf[len + 1];
1386 if (strchr (p, ' '))
1387 continue;
1389 pos_tokens = g_strsplit_set (p, ";", 3);
1390 if (pos_tokens[0] != NULL) {
1391 *line = strtol (pos_tokens[0], NULL, 10);
1392 if (pos_tokens[1] != NULL) {
1393 *column = strtol (pos_tokens[1], NULL, 10);
1394 if (pos_tokens[2] != NULL)
1395 *offset = strtoll (pos_tokens[2], NULL, 10);
1396 else
1397 *offset = 0;
1398 } else {
1399 *column = 0;
1400 *offset = 0;
1402 } else {
1403 *line = 1;
1404 *column = 0;
1405 *offset = 0;
1407 g_strfreev(pos_tokens);
1409 fclose (f);
1412 /* Save position for the given file */
1413 #define TMP_SUFFIX ".tmp"
1414 void
1415 save_file_position (const char *filename, long line, long column, off_t offset)
1417 static int filepos_max_saved_entries = 0;
1418 char *fn, *tmp_fn;
1419 FILE *f, *tmp_f;
1420 char buf[MC_MAXPATHLEN + 20];
1421 int i = 1;
1422 gsize len;
1424 if (filepos_max_saved_entries == 0)
1425 filepos_max_saved_entries = mc_config_get_int(mc_main_config, CONFIG_APP_SECTION, "filepos_max_saved_entries", 1024);
1427 fn = g_build_filename (home_dir, MC_USERCONF_DIR, MC_FILEPOS_FILE, NULL);
1428 if (fn == NULL)
1429 goto early_error;
1431 len = strlen (filename);
1433 mc_util_make_backup_if_possible (fn, TMP_SUFFIX);
1435 /* open file */
1436 f = fopen (fn, "w");
1437 if (f == NULL)
1438 goto open_target_error;
1440 tmp_fn = g_strdup_printf("%s" TMP_SUFFIX ,fn);
1441 tmp_f = fopen (tmp_fn, "r");
1442 if (tmp_f == NULL)
1443 goto open_source_error;
1445 /* put the new record */
1446 if (line != 1 || column != 0) {
1447 if (fprintf (f, "%s %ld;%ld;%lli\n", filename, line, column, offset) < 0)
1448 goto write_position_error;
1451 while (fgets (buf, sizeof (buf), tmp_f)) {
1452 if (
1453 buf[len] == ' ' &&
1454 strncmp (buf, filename, len) == 0 &&
1455 !strchr (&buf[len + 1], ' ')
1457 continue;
1459 fprintf (f, "%s", buf);
1460 if (++i > filepos_max_saved_entries)
1461 break;
1463 fclose (tmp_f);
1464 g_free(tmp_fn);
1465 fclose (f);
1466 mc_util_unlink_backup_if_possible (fn, TMP_SUFFIX);
1467 g_free (fn);
1468 return;
1470 write_position_error:
1471 fclose (tmp_f);
1472 open_source_error:
1473 g_free(tmp_fn);
1474 fclose (f);
1475 mc_util_restore_from_backup_if_possible (fn, TMP_SUFFIX);
1476 open_target_error:
1477 g_free (fn);
1478 early_error:
1479 return;
1481 #undef TMP_SUFFIX
1482 extern const char *
1483 cstrcasestr (const char *haystack, const char *needle)
1485 char *nee = str_create_search_needle (needle, 0);
1486 const char *result = str_search_first (haystack, nee, 0);
1487 str_release_search_needle (nee, 0);
1488 return result;
1491 const char *
1492 cstrstr (const char *haystack, const char *needle)
1494 return strstr(haystack, needle);
1497 extern char *
1498 str_unconst (const char *s)
1500 return (char *) s;
1503 #define ASCII_A (0x40 + 1)
1504 #define ASCII_Z (0x40 + 26)
1505 #define ASCII_a (0x60 + 1)
1506 #define ASCII_z (0x60 + 26)
1508 extern int
1509 ascii_alpha_to_cntrl (int ch)
1511 if ((ch >= ASCII_A && ch <= ASCII_Z)
1512 || (ch >= ASCII_a && ch <= ASCII_z)) {
1513 ch &= 0x1f;
1515 return ch;
1518 const char *
1519 Q_ (const char *s)
1521 const char *result, *sep;
1523 result = _(s);
1524 sep = strchr(result, '|');
1525 return (sep != NULL) ? sep + 1 : result;
1529 gboolean
1530 mc_util_make_backup_if_possible (const char *file_name, const char *backup_suffix)
1532 struct stat stat_buf;
1533 char *backup_path;
1534 gboolean ret;
1535 if (!exist_file (file_name))
1536 return FALSE;
1538 backup_path = g_strdup_printf("%s%s",file_name,backup_suffix);
1540 if (backup_path == NULL)
1541 return FALSE;
1543 ret = mc_util_write_backup_content (file_name, backup_path);
1545 if (ret) {
1546 /* Backup file will have same ownership with main file. */
1547 if (stat (file_name, &stat_buf) == 0)
1548 chmod (backup_path, stat_buf.st_mode);
1549 else
1550 chmod (backup_path, S_IRUSR | S_IWUSR);
1553 g_free(backup_path);
1555 return ret;
1558 gboolean
1559 mc_util_restore_from_backup_if_possible (const char *file_name, const char *backup_suffix)
1561 gboolean ret;
1562 char *backup_path;
1564 backup_path = g_strdup_printf("%s%s",file_name,backup_suffix);
1565 if (backup_path == NULL)
1566 return FALSE;
1568 ret = mc_util_write_backup_content (backup_path, file_name);
1569 g_free(backup_path);
1571 return ret;
1574 gboolean
1575 mc_util_unlink_backup_if_possible (const char *file_name, const char *backup_suffix)
1577 char *backup_path;
1579 backup_path = g_strdup_printf("%s%s",file_name,backup_suffix);
1580 if (backup_path == NULL)
1581 return FALSE;
1583 if (exist_file (backup_path))
1584 mc_unlink (backup_path);
1586 g_free(backup_path);
1587 return TRUE;
1590 /* partly taken from dcigettext.c, returns "" for default locale */
1591 /* value should be freed by calling function g_free() */
1592 char *guess_message_value (void)
1594 static const char * const var[] = {
1595 /* Setting of LC_ALL overwrites all other. */
1596 /* Do not use LANGUAGE for check user locale and drowing hints */
1597 "LC_ALL",
1598 /* Next comes the name of the desired category. */
1599 "LC_MESSAGES",
1600 /* Last possibility is the LANG environment variable. */
1601 "LANG",
1602 /* NULL exit loops */
1603 NULL
1606 unsigned i = 0;
1607 const char *locale = NULL;
1609 while (var[i] != NULL) {
1610 locale = getenv (var[i]);
1611 if (locale != NULL && locale[0] != '\0')
1612 break;
1613 i++;
1616 if (locale == NULL)
1617 locale = "";
1619 return g_strdup (locale);