1 /*
2 Routines for parsing output from the 'ls' command.
3
4 Copyright (C) 1988-2021
5 Free Software Foundation, Inc.
6
7 Copyright (C) 1995, 1996 Miguel de Icaza
8
9 Written by:
10 Miguel de Icaza, 1995, 1996
11 Slava Zanko <slavazanko@gmail.com>, 2011
12
13 This file is part of the Midnight Commander.
14
15 The Midnight Commander is free software: you can redistribute it
16 and/or modify it under the terms of the GNU General Public License as
17 published by the Free Software Foundation, either version 3 of the License,
18 or (at your option) any later version.
19
20 The Midnight Commander is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 GNU General Public License for more details.
24
25 You should have received a copy of the GNU General Public License
26 along with this program. If not, see <http://www.gnu.org/licenses/>.
27 */
28
29 /**
30 * \file
31 * \brief Source: Utilities for VFS modules
32 * \author Miguel de Icaza
33 * \date 1995, 1996
34 */
35
36 #include <config.h>
37
38 #include <ctype.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41
42 #include "lib/global.h"
43 #include "lib/unixcompat.h" /* makedev */
44 #include "lib/widget.h" /* message() */
45
46 #include "utilvfs.h"
47
48 /*** global variables ****************************************************************************/
49
50 /*** file scope macro definitions ****************************************************************/
51
52 /* Parsing code is used by ftpfs, fish and extfs */
53 #define MAXCOLS 30
54
55 /*** file scope type declarations ****************************************************************/
56
57 /*** file scope variables ************************************************************************/
58
59 static char *columns[MAXCOLS]; /* Points to the string in column n */
60 static int column_ptr[MAXCOLS]; /* Index from 0 to the starting positions of the columns */
61 static size_t vfs_parce_ls_final_num_spaces = 0;
62
63 /* --------------------------------------------------------------------------------------------- */
64 /*** file scope functions ************************************************************************/
65 /* --------------------------------------------------------------------------------------------- */
66
67 static gboolean
is_num(int idx)68 is_num (int idx)
69 {
70 char *column = columns[idx];
71
72 return (column != NULL && isdigit (column[0]));
73 }
74
75 /* --------------------------------------------------------------------------------------------- */
76 /* Return TRUE for MM-DD-YY and MM-DD-YYYY */
77
78 static gboolean
is_dos_date(const char * str)79 is_dos_date (const char *str)
80 {
81 size_t len;
82
83 if (str == NULL)
84 return FALSE;
85
86 len = strlen (str);
87 if (len != 8 && len != 10)
88 return FALSE;
89
90 if (str[2] != str[5])
91 return FALSE;
92
93 return (strchr ("\\-/", (int) str[2]) != NULL);
94 }
95
96 /* --------------------------------------------------------------------------------------------- */
97
98 static gboolean
is_week(const char * str,struct tm * tim)99 is_week (const char *str, struct tm *tim)
100 {
101 static const char *week = "SunMonTueWedThuFriSat";
102 const char *pos;
103
104 if (str == NULL)
105 return FALSE;
106
107 pos = strstr (week, str);
108 if (pos == NULL)
109 return FALSE;
110
111 if (tim != NULL)
112 tim->tm_wday = (pos - week) / 3;
113
114 return TRUE;
115 }
116
117 /* --------------------------------------------------------------------------------------------- */
118 /**
119 * Check for possible locale's abbreviated month name (Jan..Dec).
120 * Any 3 bytes long string without digit, control and punctuation characters.
121 * isalpha() is locale specific, so it cannot be used if current
122 * locale is "C" and ftp server use Cyrillic.
123 * NB: It is assumed there are no whitespaces in month.
124 */
125 static gboolean
is_localized_month(const char * month)126 is_localized_month (const char *month)
127 {
128 int i;
129
130 if (month == NULL)
131 return FALSE;
132
133 for (i = 0;
134 i < 3 && *month != '\0' && !isdigit ((unsigned char) *month)
135 && !iscntrl ((unsigned char) *month) && !ispunct ((unsigned char) *month); i++, month++)
136 ;
137
138 return (i == 3 && *month == '\0');
139 }
140
141 /* --------------------------------------------------------------------------------------------- */
142
143 static gboolean
is_time(const char * str,struct tm * tim)144 is_time (const char *str, struct tm *tim)
145 {
146 const char *p, *p2;
147
148 if (str == NULL)
149 return FALSE;
150
151 p = strchr (str, ':');
152 if (p == NULL)
153 return FALSE;
154
155 p2 = strrchr (str, ':');
156 if (p2 == NULL)
157 return FALSE;
158
159 if (p != p2)
160 {
161 if (sscanf (str, "%2d:%2d:%2d", &tim->tm_hour, &tim->tm_min, &tim->tm_sec) != 3)
162 return FALSE;
163 }
164 else
165 {
166 if (sscanf (str, "%2d:%2d", &tim->tm_hour, &tim->tm_min) != 2)
167 return FALSE;
168 }
169
170 return TRUE;
171 }
172
173 /* --------------------------------------------------------------------------------------------- */
174
175 static gboolean
is_year(char * str,struct tm * tim)176 is_year (char *str, struct tm *tim)
177 {
178 long year;
179
180 if (str == NULL)
181 return FALSE;
182
183 if (strchr (str, ':') != NULL)
184 return FALSE;
185
186 if (strlen (str) != 4)
187 return FALSE;
188
189 /* cppcheck-suppress invalidscanf */
190 if (sscanf (str, "%ld", &year) != 1)
191 return FALSE;
192
193 if (year < 1900 || year > 3000)
194 return FALSE;
195
196 tim->tm_year = (int) (year - 1900);
197
198 return TRUE;
199 }
200
201 /* --------------------------------------------------------------------------------------------- */
202 /*** public functions ****************************************************************************/
203 /* --------------------------------------------------------------------------------------------- */
204
205 gboolean
vfs_parse_filetype(const char * s,size_t * ret_skipped,mode_t * ret_type)206 vfs_parse_filetype (const char *s, size_t * ret_skipped, mode_t * ret_type)
207 {
208 mode_t type;
209
210 switch (*s)
211 {
212 case 'd':
213 type = S_IFDIR;
214 break;
215 case 'b':
216 type = S_IFBLK;
217 break;
218 case 'c':
219 type = S_IFCHR;
220 break;
221 case 'l':
222 type = S_IFLNK;
223 break;
224 #ifdef S_IFSOCK
225 case 's':
226 type = S_IFSOCK;
227 break;
228 #else
229 case 's':
230 type = S_IFIFO;
231 break;
232 #endif
233 #ifdef S_IFDOOR /* Solaris door */
234 case 'D':
235 type = S_IFDOOR;
236 break;
237 #else
238 case 'D':
239 type = S_IFIFO;
240 break;
241 #endif
242 case 'p':
243 type = S_IFIFO;
244 break;
245 #ifdef S_IFNAM /* Special named files */
246 case 'n':
247 type = S_IFNAM;
248 break;
249 #else
250 case 'n':
251 type = S_IFREG;
252 break;
253 #endif
254 case 'm': /* Don't know what these are :-) */
255 case '-':
256 case '?':
257 type = S_IFREG;
258 break;
259 default:
260 return FALSE;
261 }
262
263 *ret_type = type;
264
265 if (ret_skipped != NULL)
266 *ret_skipped = 1;
267 return TRUE;
268 }
269
270 /* --------------------------------------------------------------------------------------------- */
271
272 gboolean
vfs_parse_fileperms(const char * s,size_t * ret_skipped,mode_t * ret_perms)273 vfs_parse_fileperms (const char *s, size_t * ret_skipped, mode_t * ret_perms)
274 {
275 const char *p = s;
276 mode_t perms = 0;
277
278 switch (*p++)
279 {
280 case '-':
281 break;
282 case 'r':
283 perms |= S_IRUSR;
284 break;
285 default:
286 return FALSE;
287 }
288
289 switch (*p++)
290 {
291 case '-':
292 break;
293 case 'w':
294 perms |= S_IWUSR;
295 break;
296 default:
297 return FALSE;
298 }
299
300 switch (*p++)
301 {
302 case '-':
303 break;
304 case 'S':
305 perms |= S_ISUID;
306 break;
307 case 's':
308 perms |= S_IXUSR | S_ISUID;
309 break;
310 case 'x':
311 perms |= S_IXUSR;
312 break;
313 default:
314 return FALSE;
315 }
316
317 switch (*p++)
318 {
319 case '-':
320 break;
321 case 'r':
322 perms |= S_IRGRP;
323 break;
324 default:
325 return FALSE;
326 }
327
328 switch (*p++)
329 {
330 case '-':
331 break;
332 case 'w':
333 perms |= S_IWGRP;
334 break;
335 default:
336 return FALSE;
337 }
338
339 switch (*p++)
340 {
341 case '-':
342 break;
343 case 'S':
344 perms |= S_ISGID;
345 break;
346 case 'l':
347 perms |= S_ISGID;
348 break; /* found on Solaris */
349 case 's':
350 perms |= S_IXGRP | S_ISGID;
351 break;
352 case 'x':
353 perms |= S_IXGRP;
354 break;
355 default:
356 return FALSE;
357 }
358
359 switch (*p++)
360 {
361 case '-':
362 break;
363 case 'r':
364 perms |= S_IROTH;
365 break;
366 default:
367 return FALSE;
368 }
369
370 switch (*p++)
371 {
372 case '-':
373 break;
374 case 'w':
375 perms |= S_IWOTH;
376 break;
377 default:
378 return FALSE;
379 }
380
381 switch (*p++)
382 {
383 case '-':
384 break;
385 case 'T':
386 perms |= S_ISVTX;
387 break;
388 case 't':
389 perms |= S_IXOTH | S_ISVTX;
390 break;
391 case 'x':
392 perms |= S_IXOTH;
393 break;
394 default:
395 return FALSE;
396 }
397
398 if (*p == '+')
399 /* ACLs on Solaris, HP-UX and others */
400 p++;
401
402 if (ret_skipped != NULL)
403 *ret_skipped = p - s;
404 *ret_perms = perms;
405
406 return TRUE;
407 }
408
409 /* --------------------------------------------------------------------------------------------- */
410
411 gboolean
vfs_parse_filemode(const char * s,size_t * ret_skipped,mode_t * ret_mode)412 vfs_parse_filemode (const char *s, size_t * ret_skipped, mode_t * ret_mode)
413 {
414 const char *p = s;
415 mode_t type, perms;
416 size_t skipped;
417
418 if (!vfs_parse_filetype (p, &skipped, &type))
419 return FALSE;
420
421 p += skipped;
422 if (!vfs_parse_fileperms (p, &skipped, &perms))
423 return FALSE;
424
425 p += skipped;
426 *ret_skipped = p - s;
427 *ret_mode = type | perms;
428
429 return TRUE;
430 }
431
432 /* --------------------------------------------------------------------------------------------- */
433
434 gboolean
vfs_parse_raw_filemode(const char * s,size_t * ret_skipped,mode_t * ret_mode)435 vfs_parse_raw_filemode (const char *s, size_t * ret_skipped, mode_t * ret_mode)
436 {
437 const char *p = s;
438 mode_t remote_type = 0, local_type, perms = 0;
439
440 /* isoctal */
441 for (; *p >= '0' && *p <= '7'; p++)
442 {
443 perms *= 010;
444 perms += (*p - '0');
445 }
446
447 if (*p++ != ' ')
448 return FALSE;
449
450 for (; *p >= '0' && *p <= '7'; p++)
451 {
452 remote_type *= 010;
453 remote_type += (*p - '0');
454 }
455
456 if (*p++ != ' ')
457 return FALSE;
458
459 /* generated with:
460 $ perl -e 'use Fcntl ":mode";
461 my @modes = (S_IFDIR, S_IFBLK, S_IFCHR, S_IFLNK, S_IFREG);
462 foreach $t (@modes) { printf ("%o\n", $t); };'
463 TODO: S_IFDOOR, S_IFIFO, S_IFSOCK (if supported by os)
464 (see vfs_parse_filetype)
465 */
466
467 switch (remote_type)
468 {
469 case 020000:
470 local_type = S_IFCHR;
471 break;
472 case 040000:
473 local_type = S_IFDIR;
474 break;
475 case 060000:
476 local_type = S_IFBLK;
477 break;
478 case 0120000:
479 local_type = S_IFLNK;
480 break;
481 case 0100000:
482 default: /* don't know what is it */
483 local_type = S_IFREG;
484 break;
485 }
486
487 *ret_skipped = p - s;
488 *ret_mode = local_type | perms;
489
490 return TRUE;
491 }
492
493 /* --------------------------------------------------------------------------------------------- */
494
495 gboolean
vfs_parse_month(const char * str,struct tm * tim)496 vfs_parse_month (const char *str, struct tm * tim)
497 {
498 static const char *month = "JanFebMarAprMayJunJulAugSepOctNovDec";
499 const char *pos;
500
501 if (str == NULL)
502 return FALSE;
503
504 pos = strstr (month, str);
505 if (pos == NULL)
506 return FALSE;
507
508 if (tim != NULL)
509 tim->tm_mon = (pos - month) / 3;
510
511 return TRUE;
512 }
513
514 /* --------------------------------------------------------------------------------------------- */
515 /** This function parses from idx in the columns[] array */
516
517 int
vfs_parse_filedate(int idx,time_t * t)518 vfs_parse_filedate (int idx, time_t * t)
519 {
520 char *p;
521 struct tm tim;
522 int d[3];
523 gboolean got_year = FALSE;
524 gboolean l10n = FALSE; /* Locale's abbreviated month name */
525 time_t current_time;
526 struct tm *local_time;
527
528 /* Let's setup default time values */
529 current_time = time (NULL);
530 local_time = localtime (¤t_time);
531 tim.tm_mday = local_time->tm_mday;
532 tim.tm_mon = local_time->tm_mon;
533 tim.tm_year = local_time->tm_year;
534
535 tim.tm_hour = 0;
536 tim.tm_min = 0;
537 tim.tm_sec = 0;
538 tim.tm_isdst = -1; /* Let mktime() try to guess correct dst offset */
539
540 p = columns[idx++];
541
542 /* We eat weekday name in case of extfs */
543 if (is_week (p, &tim))
544 p = columns[idx++];
545
546 /*
547 ALLOWED DATE FORMATS
548
549 We expect 3 fields max or we'll see oddities with certain file names.
550
551 Formats that contain either year or time (the default 'ls' formats):
552
553 * Mon DD hh:mm[:ss]
554 * Mon DD YYYY
555
556 Formats that contain both year and time, to make it easier to write
557 extfs scripts:
558
559 * MM-DD-YYYY hh:mm[:ss]
560 * MM-DD-YY hh:mm[:ss]
561
562 ('/' and '\' can be used instead of '-'.)
563
564 where Mon is Jan-Dec, DD, MM, YY two digit day, month, year,
565 YYYY four digit year, hh, mm, ss two digit hour, minute or second.
566
567 (As for the "3 fields max" restriction: this prevents, for example, a
568 file name "13:48" from being considered part of a "Sep 19 2016" date
569 string preceding it.)
570 */
571
572 /* Month name */
573 if (vfs_parse_month (p, &tim))
574 {
575 /* And we expect, it followed by day number */
576 if (!is_num (idx))
577 return 0; /* No day */
578
579 tim.tm_mday = (int) atol (columns[idx++]);
580
581 }
582 else if (is_dos_date (p))
583 {
584 /* Case with MM-DD-YY or MM-DD-YYYY */
585 p[2] = p[5] = '-';
586
587 /* cppcheck-suppress invalidscanf */
588 if (sscanf (p, "%2d-%2d-%d", &d[0], &d[1], &d[2]) != 3)
589 return 0; /* sscanf failed */
590
591 /* Months are zero based */
592 if (d[0] > 0)
593 d[0]--;
594
595 if (d[2] > 1900)
596 d[2] -= 1900;
597 else if (d[2] < 70)
598 /* Y2K madness */
599 d[2] += 100;
600
601 tim.tm_mon = d[0];
602 tim.tm_mday = d[1];
603 tim.tm_year = d[2];
604 got_year = TRUE;
605 }
606 else if (is_localized_month (p) && is_num (idx++))
607 /* Locale's abbreviated month name followed by day number */
608 l10n = TRUE;
609 else
610 return 0; /* unsupported format */
611
612 /* Here we expect to find time or year */
613 if (!is_num (idx)
614 || !(is_time (columns[idx], &tim) || (got_year = is_year (columns[idx], &tim))))
615 return 0; /* Neither time nor date */
616
617 idx++;
618
619 /*
620 * If the date is less than 6 months in the past, it is shown without year
621 * other dates in the past or future are shown with year but without time
622 * This does not check for years before 1900 ... I don't know, how
623 * to represent them at all
624 */
625 if (!got_year && local_time->tm_mon < 6 && local_time->tm_mon < tim.tm_mon
626 && tim.tm_mon - local_time->tm_mon >= 6)
627 tim.tm_year--;
628
629 *t = mktime (&tim);
630 if (l10n || (*t < 0))
631 *t = 0;
632
633 return idx;
634 }
635
636 /* --------------------------------------------------------------------------------------------- */
637
638 int
vfs_split_text(char * p)639 vfs_split_text (char *p)
640 {
641 char *original = p;
642 int numcols;
643
644 memset (columns, 0, sizeof (columns));
645
646 for (numcols = 0; *p != '\0' && numcols < MAXCOLS; numcols++)
647 {
648 for (; *p == ' ' || *p == '\r' || *p == '\n'; p++)
649 *p = '\0';
650
651 columns[numcols] = p;
652 column_ptr[numcols] = p - original;
653
654 for (; *p != '\0' && *p != ' ' && *p != '\r' && *p != '\n'; p++)
655 ;
656 }
657
658 return numcols;
659 }
660
661 /* --------------------------------------------------------------------------------------------- */
662
663 void
vfs_parse_ls_lga_init(void)664 vfs_parse_ls_lga_init (void)
665 {
666 vfs_parce_ls_final_num_spaces = 1;
667 }
668
669 /* --------------------------------------------------------------------------------------------- */
670
671 size_t
vfs_parse_ls_lga_get_final_spaces(void)672 vfs_parse_ls_lga_get_final_spaces (void)
673 {
674 return vfs_parce_ls_final_num_spaces;
675 }
676
677 /* --------------------------------------------------------------------------------------------- */
678
679 gboolean
vfs_parse_ls_lga(const char * p,struct stat * s,char ** filename,char ** linkname,size_t * num_spaces)680 vfs_parse_ls_lga (const char *p, struct stat * s, char **filename, char **linkname,
681 size_t * num_spaces)
682 {
683 int idx, idx2, num_cols;
684 int i;
685 char *p_copy = NULL;
686 char *t = NULL;
687 const char *line = p;
688 size_t skipped;
689
690 if (strncmp (p, "total", 5) == 0)
691 return FALSE;
692
693 if (!vfs_parse_filetype (p, &skipped, &s->st_mode))
694 goto error;
695
696 p += skipped;
697 if (*p == ' ') /* Notwell 4 */
698 p++;
699 if (*p == '[')
700 {
701 if (strlen (p) <= 8 || p[8] != ']')
702 goto error;
703
704 /* Should parse here the Notwell permissions :) */
705 if (S_ISDIR (s->st_mode))
706 s->st_mode |= (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IXUSR | S_IXGRP | S_IXOTH);
707 else
708 s->st_mode |= (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR);
709 p += 9;
710 }
711 else
712 {
713 size_t lc_skipped;
714 mode_t perms;
715
716 if (!vfs_parse_fileperms (p, &lc_skipped, &perms))
717 goto error;
718
719 p += lc_skipped;
720 s->st_mode |= perms;
721 }
722
723 p_copy = g_strdup (p);
724 num_cols = vfs_split_text (p_copy);
725
726 s->st_nlink = atol (columns[0]);
727 if (s->st_nlink <= 0)
728 goto error;
729
730 if (!is_num (1))
731 s->st_uid = vfs_finduid (columns[1]);
732 else
733 s->st_uid = (uid_t) atol (columns[1]);
734
735 /* Mhm, the ls -lg did not produce a group field */
736 for (idx = 3; idx <= 5; idx++)
737 if (vfs_parse_month (columns[idx], NULL) || is_week (columns[idx], NULL)
738 || is_dos_date (columns[idx]) || is_localized_month (columns[idx]))
739 break;
740
741 if (idx == 6 || (idx == 5 && !S_ISCHR (s->st_mode) && !S_ISBLK (s->st_mode)))
742 goto error;
743
744 /* We don't have gid */
745 if (idx == 3 || (idx == 4 && (S_ISCHR (s->st_mode) || S_ISBLK (s->st_mode))))
746 idx2 = 2;
747 else
748 {
749 /* We have gid field */
750 if (is_num (2))
751 s->st_gid = (gid_t) atol (columns[2]);
752 else
753 s->st_gid = vfs_findgid (columns[2]);
754 idx2 = 3;
755 }
756
757 /* This is device */
758 if (S_ISCHR (s->st_mode) || S_ISBLK (s->st_mode))
759 {
760 int maj, min;
761
762 /* Corner case: there is no whitespace(s) between maj & min */
763 if (!is_num (idx2) && idx2 == 2)
764 {
765 /* cppcheck-suppress invalidscanf */
766 if (!is_num (++idx2) || sscanf (columns[idx2], " %d,%d", &maj, &min) != 2)
767 goto error;
768 }
769 else
770 {
771 /* cppcheck-suppress invalidscanf */
772 if (!is_num (idx2) || sscanf (columns[idx2], " %d,", &maj) != 1)
773 goto error;
774
775 /* cppcheck-suppress invalidscanf */
776 if (!is_num (++idx2) || sscanf (columns[idx2], " %d", &min) != 1)
777 goto error;
778 }
779 #ifdef HAVE_STRUCT_STAT_ST_RDEV
780 s->st_rdev = makedev (maj, min);
781 #endif
782 s->st_size = 0;
783
784 }
785 else
786 {
787 /* Common file size */
788 if (!is_num (idx2))
789 goto error;
790
791 s->st_size = (off_t) g_ascii_strtoll (columns[idx2], NULL, 10);
792 #ifdef HAVE_STRUCT_STAT_ST_RDEV
793 s->st_rdev = 0;
794 #endif
795 }
796
797 idx = vfs_parse_filedate (idx, &s->st_mtime);
798 if (idx == 0)
799 goto error;
800
801 /* Use resulting time value */
802 s->st_atime = s->st_ctime = s->st_mtime;
803 #ifdef HAVE_STRUCT_STAT_ST_MTIM
804 s->st_atim.tv_nsec = s->st_mtim.tv_nsec = s->st_ctim.tv_nsec = 0;
805 #endif
806
807 /* s->st_dev and s->st_ino must be initialized by vfs_s_new_inode () */
808 #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
809 s->st_blksize = 512;
810 #endif
811 vfs_adjust_stat (s);
812
813 if (num_spaces != NULL)
814 {
815 *num_spaces = column_ptr[idx] - column_ptr[idx - 1] - strlen (columns[idx - 1]);
816 if (DIR_IS_DOTDOT (columns[idx]))
817 vfs_parce_ls_final_num_spaces = *num_spaces;
818 }
819
820 for (i = idx + 1, idx2 = 0; i < num_cols; i++)
821 if (strcmp (columns[i], "->") == 0)
822 {
823 idx2 = i;
824 break;
825 }
826
827 if (((S_ISLNK (s->st_mode) || (num_cols == idx + 3 && s->st_nlink > 1))) /* Maybe a hardlink? (in extfs) */
828 && idx2 != 0)
829 {
830 if (filename != NULL)
831 *filename = g_strndup (p + column_ptr[idx], column_ptr[idx2] - column_ptr[idx] - 1);
832
833 if (linkname != NULL)
834 {
835 t = g_strdup (p + column_ptr[idx2 + 1]);
836 *linkname = t;
837 }
838 }
839 else
840 {
841 /* Extract the filename from the string copy, not from the columns
842 * this way we have a chance of entering hidden directories like ". ."
843 */
844 if (filename != NULL)
845 {
846 /* filename = g_strdup (columns [idx++]); */
847 t = g_strdup (p + column_ptr[idx]);
848 *filename = t;
849 }
850
851 if (linkname != NULL)
852 *linkname = NULL;
853 }
854
855 if (t != NULL)
856 {
857 size_t p2;
858
859 p2 = strlen (t);
860 if (--p2 > 0 && (t[p2] == '\r' || t[p2] == '\n'))
861 t[p2] = '\0';
862 if (--p2 > 0 && (t[p2] == '\r' || t[p2] == '\n'))
863 t[p2] = '\0';
864 }
865
866 g_free (p_copy);
867 return TRUE;
868
869 error:
870 {
871 static int errorcount = 0;
872
873 if (++errorcount < 5)
874 message (D_ERROR, _("Cannot parse:"), "%s",
875 (p_copy != NULL && *p_copy != '\0') ? p_copy : line);
876 else if (errorcount == 5)
877 message (D_ERROR, MSG_ERROR, _("More parsing errors will be ignored."));
878 }
879
880 g_free (p_copy);
881 return FALSE;
882 }
883
884 /* --------------------------------------------------------------------------------------------- */
885