Ticket #2862: fix syncronization with filelist and tree panels
[midnight-commander.git] / lib / vfs / parse_ls_vga.c
blobe5f7d623260795358c062cfcd80b75431876b33d
1 /*
2 Routines for parsing output from the `ls' command.
4 Copyright (C) 1988, 1992, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
5 2005, 2006, 2007, 2011
6 The Free Software Foundation, Inc.
8 Copyright (C) 1995, 1996 Miguel de Icaza
10 Written by:
11 Miguel de Icaza, 1995, 1996
12 Slava Zanko <slavazanko@gmail.com>, 2011
14 This file is part of the Midnight Commander.
16 The Midnight Commander is free software: you can redistribute it
17 and/or modify it under the terms of the GNU General Public License as
18 published by the Free Software Foundation, either version 3 of the License,
19 or (at your option) any later version.
21 The Midnight Commander is distributed in the hope that it will be useful,
22 but WITHOUT ANY WARRANTY; without even the implied warranty of
23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 GNU General Public License for more details.
26 You should have received a copy of the GNU General Public License
27 along with this program. If not, see <http://www.gnu.org/licenses/>.
30 /**
31 * \file
32 * \brief Source: Utilities for VFS modules
33 * \author Miguel de Icaza
34 * \date 1995, 1996
37 #include <config.h>
39 #include <ctype.h>
40 #include <stdio.h>
41 #include <stdlib.h>
43 #include "lib/global.h"
44 #include "lib/unixcompat.h" /* makedev */
45 #include "lib/widget.h" /* message() */
47 #include "utilvfs.h"
49 /*** global variables ****************************************************************************/
51 /*** file scope macro definitions ****************************************************************/
53 /* Parsing code is used by ftpfs, fish and extfs */
54 #define MAXCOLS 30
56 /*** file scope type declarations ****************************************************************/
58 /*** file scope variables ************************************************************************/
60 static char *columns[MAXCOLS]; /* Points to the string in column n */
61 static int column_ptr[MAXCOLS]; /* Index from 0 to the starting positions of the columns */
62 static size_t vfs_parce_ls_final_num_spaces = 0;
64 /*** file scope functions ************************************************************************/
65 /* --------------------------------------------------------------------------------------------- */
67 static int
68 is_num (int idx)
70 char *column = columns[idx];
72 if (!column || column[0] < '0' || column[0] > '9')
73 return 0;
75 return 1;
78 /* --------------------------------------------------------------------------------------------- */
79 /* Return 1 for MM-DD-YY and MM-DD-YYYY */
81 static int
82 is_dos_date (const char *str)
84 int len;
86 if (!str)
87 return 0;
89 len = strlen (str);
90 if (len != 8 && len != 10)
91 return 0;
93 if (str[2] != str[5])
94 return 0;
96 if (!strchr ("\\-/", (int) str[2]))
97 return 0;
99 return 1;
102 /* --------------------------------------------------------------------------------------------- */
104 static int
105 is_week (const char *str, struct tm *tim)
107 static const char *week = "SunMonTueWedThuFriSat";
108 const char *pos;
110 if (!str)
111 return 0;
113 pos = strstr (week, str);
114 if (pos != NULL)
116 if (tim != NULL)
117 tim->tm_wday = (pos - week) / 3;
118 return 1;
120 return 0;
123 /* --------------------------------------------------------------------------------------------- */
125 static int
126 is_month (const char *str, struct tm *tim)
128 static const char *month = "JanFebMarAprMayJunJulAugSepOctNovDec";
129 const char *pos;
131 if (!str)
132 return 0;
134 pos = strstr (month, str);
135 if (pos != NULL)
137 if (tim != NULL)
138 tim->tm_mon = (pos - month) / 3;
139 return 1;
141 return 0;
144 /* --------------------------------------------------------------------------------------------- */
146 * Check for possible locale's abbreviated month name (Jan..Dec).
147 * Any 3 bytes long string without digit, control and punctuation characters.
148 * isalpha() is locale specific, so it cannot be used if current
149 * locale is "C" and ftp server use Cyrillic.
150 * NB: It is assumed there are no whitespaces in month.
152 static int
153 is_localized_month (const char *month)
155 int i = 0;
157 if (!month)
158 return 0;
160 while ((i < 3) && *month && !isdigit ((unsigned char) *month)
161 && !iscntrl ((unsigned char) *month) && !ispunct ((unsigned char) *month))
163 i++;
164 month++;
166 return ((i == 3) && (*month == 0));
169 /* --------------------------------------------------------------------------------------------- */
171 static int
172 is_time (const char *str, struct tm *tim)
174 const char *p, *p2;
176 if (str == NULL)
177 return 0;
179 p = strchr (str, ':');
180 p2 = strrchr (str, ':');
181 if (p != NULL && p2 != NULL)
183 if (p != p2)
185 if (sscanf (str, "%2d:%2d:%2d", &tim->tm_hour, &tim->tm_min, &tim->tm_sec) != 3)
186 return 0;
188 else
190 if (sscanf (str, "%2d:%2d", &tim->tm_hour, &tim->tm_min) != 2)
191 return 0;
194 else
195 return 0;
197 return 1;
200 /* --------------------------------------------------------------------------------------------- */
202 static int
203 is_year (char *str, struct tm *tim)
205 long year;
207 if (!str)
208 return 0;
210 if (strchr (str, ':'))
211 return 0;
213 if (strlen (str) != 4)
214 return 0;
216 if (sscanf (str, "%ld", &year) != 1)
217 return 0;
219 if (year < 1900 || year > 3000)
220 return 0;
222 tim->tm_year = (int) (year - 1900);
224 return 1;
227 /* --------------------------------------------------------------------------------------------- */
228 /*** public functions ****************************************************************************/
229 /* --------------------------------------------------------------------------------------------- */
231 gboolean
232 vfs_parse_filetype (const char *s, size_t * ret_skipped, mode_t * ret_type)
234 mode_t type;
236 switch (*s)
238 case 'd':
239 type = S_IFDIR;
240 break;
241 case 'b':
242 type = S_IFBLK;
243 break;
244 case 'c':
245 type = S_IFCHR;
246 break;
247 case 'l':
248 type = S_IFLNK;
249 break;
250 #ifdef S_IFSOCK
251 case 's':
252 type = S_IFSOCK;
253 break;
254 #else
255 case 's':
256 type = S_IFIFO;
257 break;
258 #endif
259 #ifdef S_IFDOOR /* Solaris door */
260 case 'D':
261 type = S_IFDOOR;
262 break;
263 #else
264 case 'D':
265 type = S_IFIFO;
266 break;
267 #endif
268 case 'p':
269 type = S_IFIFO;
270 break;
271 #ifdef S_IFNAM /* Special named files */
272 case 'n':
273 type = S_IFNAM;
274 break;
275 #else
276 case 'n':
277 type = S_IFREG;
278 break;
279 #endif
280 case 'm': /* Don't know what these are :-) */
281 case '-':
282 case '?':
283 type = S_IFREG;
284 break;
285 default:
286 return FALSE;
289 *ret_type = type;
290 *ret_skipped = 1;
291 return TRUE;
294 /* --------------------------------------------------------------------------------------------- */
296 gboolean
297 vfs_parse_fileperms (const char *s, size_t * ret_skipped, mode_t * ret_perms)
299 const char *p;
300 mode_t perms;
302 p = s;
303 perms = 0;
305 switch (*p++)
307 case '-':
308 break;
309 case 'r':
310 perms |= S_IRUSR;
311 break;
312 default:
313 return FALSE;
315 switch (*p++)
317 case '-':
318 break;
319 case 'w':
320 perms |= S_IWUSR;
321 break;
322 default:
323 return FALSE;
325 switch (*p++)
327 case '-':
328 break;
329 case 'S':
330 perms |= S_ISUID;
331 break;
332 case 's':
333 perms |= S_IXUSR | S_ISUID;
334 break;
335 case 'x':
336 perms |= S_IXUSR;
337 break;
338 default:
339 return FALSE;
341 switch (*p++)
343 case '-':
344 break;
345 case 'r':
346 perms |= S_IRGRP;
347 break;
348 default:
349 return FALSE;
351 switch (*p++)
353 case '-':
354 break;
355 case 'w':
356 perms |= S_IWGRP;
357 break;
358 default:
359 return FALSE;
361 switch (*p++)
363 case '-':
364 break;
365 case 'S':
366 perms |= S_ISGID;
367 break;
368 case 'l':
369 perms |= S_ISGID;
370 break; /* found on Solaris */
371 case 's':
372 perms |= S_IXGRP | S_ISGID;
373 break;
374 case 'x':
375 perms |= S_IXGRP;
376 break;
377 default:
378 return FALSE;
380 switch (*p++)
382 case '-':
383 break;
384 case 'r':
385 perms |= S_IROTH;
386 break;
387 default:
388 return FALSE;
390 switch (*p++)
392 case '-':
393 break;
394 case 'w':
395 perms |= S_IWOTH;
396 break;
397 default:
398 return FALSE;
400 switch (*p++)
402 case '-':
403 break;
404 case 'T':
405 perms |= S_ISVTX;
406 break;
407 case 't':
408 perms |= S_IXOTH | S_ISVTX;
409 break;
410 case 'x':
411 perms |= S_IXOTH;
412 break;
413 default:
414 return FALSE;
416 if (*p == '+')
417 { /* ACLs on Solaris, HP-UX and others */
418 p++;
421 *ret_skipped = p - s;
422 *ret_perms = perms;
423 return TRUE;
426 /* --------------------------------------------------------------------------------------------- */
428 gboolean
429 vfs_parse_filemode (const char *s, size_t * ret_skipped, mode_t * ret_mode)
431 const char *p;
432 mode_t type, perms;
433 size_t skipped;
435 p = s;
437 if (!vfs_parse_filetype (p, &skipped, &type))
438 return FALSE;
439 p += skipped;
441 if (!vfs_parse_fileperms (p, &skipped, &perms))
442 return FALSE;
443 p += skipped;
445 *ret_skipped = p - s;
446 *ret_mode = type | perms;
447 return TRUE;
450 /* --------------------------------------------------------------------------------------------- */
452 gboolean
453 vfs_parse_raw_filemode (const char *s, size_t * ret_skipped, mode_t * ret_mode)
455 const char *p;
456 mode_t remote_type = 0, local_type, perms = 0;
458 p = s;
460 /* isoctal */
461 while (*p >= '0' && *p <= '7')
463 perms *= 010;
464 perms += (*p - '0');
465 ++p;
468 if (*p++ != ' ')
469 return FALSE;
471 while (*p >= '0' && *p <= '7')
473 remote_type *= 010;
474 remote_type += (*p - '0');
475 ++p;
478 if (*p++ != ' ')
479 return FALSE;
481 /* generated with:
482 $ perl -e 'use Fcntl ":mode";
483 my @modes = (S_IFDIR, S_IFBLK, S_IFCHR, S_IFLNK, S_IFREG);
484 foreach $t (@modes) { printf ("%o\n", $t); };'
485 TODO: S_IFDOOR, S_IFIFO, S_IFSOCK (if supported by os)
486 (see vfs_parse_filetype)
489 switch (remote_type)
491 case 020000:
492 local_type = S_IFCHR;
493 break;
494 case 040000:
495 local_type = S_IFDIR;
496 break;
497 case 060000:
498 local_type = S_IFBLK;
499 break;
500 case 0120000:
501 local_type = S_IFLNK;
502 break;
503 case 0100000:
504 default: /* don't know what is it */
505 local_type = S_IFREG;
506 break;
509 *ret_skipped = p - s;
510 *ret_mode = local_type | perms;
511 return TRUE;
514 /* --------------------------------------------------------------------------------------------- */
515 /** This function parses from idx in the columns[] array */
518 vfs_parse_filedate (int idx, time_t * t)
520 char *p;
521 struct tm tim;
522 int d[3];
523 int got_year = 0;
524 int l10n = 0; /* Locale's abbreviated month name */
525 time_t current_time;
526 struct tm *local_time;
528 /* Let's setup default time values */
529 current_time = time (NULL);
530 local_time = localtime (&current_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;
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 */
540 p = columns[idx++];
542 /* We eat weekday name in case of extfs */
543 if (is_week (p, &tim))
544 p = columns[idx++];
546 /* Month name */
547 if (is_month (p, &tim))
549 /* And we expect, it followed by day number */
550 if (is_num (idx))
551 tim.tm_mday = (int) atol (columns[idx++]);
552 else
553 return 0; /* No day */
556 else
558 /* We expect:
559 3 fields max or we'll see oddities with certain file names.
560 So both year and time is not allowed.
561 Mon DD hh:mm[:ss]
562 Mon DD YYYY
563 But in case of extfs we allow these date formats:
564 MM-DD-YY hh:mm[:ss]
565 where Mon is Jan-Dec, DD, MM, YY two digit day, month, year,
566 YYYY four digit year, hh, mm, ss two digit hour, minute or second. */
568 /* Special case with MM-DD-YY or MM-DD-YYYY */
569 if (is_dos_date (p))
571 p[2] = p[5] = '-';
573 if (sscanf (p, "%2d-%2d-%d", &d[0], &d[1], &d[2]) == 3)
575 /* Months are zero based */
576 if (d[0] > 0)
577 d[0]--;
579 if (d[2] > 1900)
581 d[2] -= 1900;
583 else
585 /* Y2K madness */
586 if (d[2] < 70)
587 d[2] += 100;
590 tim.tm_mon = d[0];
591 tim.tm_mday = d[1];
592 tim.tm_year = d[2];
593 got_year = 1;
595 else
596 return 0; /* sscanf failed */
598 else
600 /* Locale's abbreviated month name followed by day number */
601 if (is_localized_month (p) && (is_num (idx++)))
602 l10n = 1;
603 else
604 return 0; /* unsupported format */
608 /* Here we expect to find time or year */
609 if (is_num (idx) && (is_time (columns[idx], &tim) || (got_year = is_year (columns[idx], &tim))))
610 idx++;
611 else
612 return 0; /* Neither time nor date */
615 * If the date is less than 6 months in the past, it is shown without year
616 * other dates in the past or future are shown with year but without time
617 * This does not check for years before 1900 ... I don't know, how
618 * to represent them at all
620 if (!got_year && local_time->tm_mon < 6
621 && local_time->tm_mon < tim.tm_mon && tim.tm_mon - local_time->tm_mon >= 6)
623 tim.tm_year--;
625 *t = mktime (&tim);
626 if (l10n || (*t < 0))
627 *t = 0;
628 return idx;
631 /* --------------------------------------------------------------------------------------------- */
634 vfs_split_text (char *p)
636 char *original = p;
637 int numcols;
639 memset (columns, 0, sizeof (columns));
641 for (numcols = 0; *p && numcols < MAXCOLS; numcols++)
643 while (*p == ' ' || *p == '\r' || *p == '\n')
645 *p = 0;
646 p++;
648 columns[numcols] = p;
649 column_ptr[numcols] = p - original;
650 while (*p && *p != ' ' && *p != '\r' && *p != '\n')
651 p++;
653 return numcols;
656 /* --------------------------------------------------------------------------------------------- */
658 void
659 vfs_parse_ls_lga_init (void)
661 vfs_parce_ls_final_num_spaces = 1;
664 /* --------------------------------------------------------------------------------------------- */
666 size_t
667 vfs_parse_ls_lga_get_final_spaces (void)
669 return vfs_parce_ls_final_num_spaces;
672 /* --------------------------------------------------------------------------------------------- */
674 gboolean
675 vfs_parse_ls_lga (const char *p, struct stat * s, char **filename, char **linkname,
676 size_t * num_spaces)
678 int idx, idx2, num_cols;
679 int i;
680 char *p_copy = NULL;
681 char *t = NULL;
682 const char *line = p;
683 size_t skipped;
685 if (strncmp (p, "total", 5) == 0)
686 return FALSE;
688 if (!vfs_parse_filetype (p, &skipped, &s->st_mode))
689 goto error;
690 p += skipped;
692 if (*p == ' ') /* Notwell 4 */
693 p++;
694 if (*p == '[')
696 if (strlen (p) <= 8 || p[8] != ']')
697 goto error;
698 /* Should parse here the Notwell permissions :) */
699 if (S_ISDIR (s->st_mode))
700 s->st_mode |= (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IXUSR | S_IXGRP | S_IXOTH);
701 else
702 s->st_mode |= (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR);
703 p += 9;
705 else
707 size_t lc_skipped;
708 mode_t perms;
710 if (!vfs_parse_fileperms (p, &lc_skipped, &perms))
711 goto error;
712 p += lc_skipped;
713 s->st_mode |= perms;
716 p_copy = g_strdup (p);
717 num_cols = vfs_split_text (p_copy);
719 s->st_nlink = atol (columns[0]);
720 if (s->st_nlink <= 0)
721 goto error;
723 if (!is_num (1))
724 s->st_uid = vfs_finduid (columns[1]);
725 else
726 s->st_uid = (uid_t) atol (columns[1]);
728 /* Mhm, the ls -lg did not produce a group field */
729 for (idx = 3; idx <= 5; idx++)
730 if (is_month (columns[idx], NULL) || is_week (columns[idx], NULL)
731 || is_dos_date (columns[idx]) || is_localized_month (columns[idx]))
732 break;
734 if (idx == 6 || (idx == 5 && !S_ISCHR (s->st_mode) && !S_ISBLK (s->st_mode)))
735 goto error;
737 /* We don't have gid */
738 if (idx == 3 || (idx == 4 && (S_ISCHR (s->st_mode) || S_ISBLK (s->st_mode))))
739 idx2 = 2;
740 else
742 /* We have gid field */
743 if (is_num (2))
744 s->st_gid = (gid_t) atol (columns[2]);
745 else
746 s->st_gid = vfs_findgid (columns[2]);
747 idx2 = 3;
750 /* This is device */
751 if (S_ISCHR (s->st_mode) || S_ISBLK (s->st_mode))
753 int maj, min;
755 /* Corner case: there is no whitespace(s) between maj & min */
756 if (!is_num (idx2) && idx2 == 2)
758 if (!is_num (++idx2) || sscanf (columns[idx2], " %d,%d", &maj, &min) != 2)
759 goto error;
761 else
763 if (!is_num (idx2) || sscanf (columns[idx2], " %d,", &maj) != 1)
764 goto error;
766 if (!is_num (++idx2) || sscanf (columns[idx2], " %d", &min) != 1)
767 goto error;
769 #ifdef HAVE_STRUCT_STAT_ST_RDEV
770 s->st_rdev = makedev (maj, min);
771 #endif
772 s->st_size = 0;
775 else
777 /* Common file size */
778 if (!is_num (idx2))
779 goto error;
781 #ifdef HAVE_ATOLL
782 s->st_size = (off_t) atoll (columns[idx2]);
783 #else
784 s->st_size = (off_t) atof (columns[idx2]);
785 #endif
786 #ifdef HAVE_STRUCT_STAT_ST_RDEV
787 s->st_rdev = 0;
788 #endif
791 idx = vfs_parse_filedate (idx, &s->st_mtime);
792 if (!idx)
793 goto error;
794 /* Use resulting time value */
795 s->st_atime = s->st_ctime = s->st_mtime;
796 /* s->st_dev and s->st_ino must be initialized by vfs_s_new_inode () */
797 #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
798 s->st_blksize = 512;
799 #endif
800 #ifdef HAVE_STRUCT_STAT_ST_BLOCKS
801 s->st_blocks = (s->st_size + 511) / 512;
802 #endif
804 if (num_spaces != NULL)
806 *num_spaces = column_ptr[idx] - column_ptr[idx - 1] - strlen (columns[idx - 1]);
807 if (strcmp (columns[idx], "..") == 0)
808 vfs_parce_ls_final_num_spaces = *num_spaces;
811 for (i = idx + 1, idx2 = 0; i < num_cols; i++)
812 if (strcmp (columns[i], "->") == 0)
814 idx2 = i;
815 break;
818 if (((S_ISLNK (s->st_mode) || (num_cols == idx + 3 && s->st_nlink > 1))) /* Maybe a hardlink? (in extfs) */
819 && idx2)
822 if (filename)
824 *filename = g_strndup (p + column_ptr[idx], column_ptr[idx2] - column_ptr[idx] - 1);
826 if (linkname)
828 t = g_strdup (p + column_ptr[idx2 + 1]);
829 *linkname = t;
832 else
834 /* Extract the filename from the string copy, not from the columns
835 * this way we have a chance of entering hidden directories like ". ."
837 if (filename)
840 * filename = g_strdup (columns [idx++]);
843 t = g_strdup (p + column_ptr[idx]);
844 *filename = t;
846 if (linkname)
847 *linkname = NULL;
850 if (t)
852 int p2 = strlen (t);
853 if ((--p2 > 0) && (t[p2] == '\r' || t[p2] == '\n'))
854 t[p2] = 0;
855 if ((--p2 > 0) && (t[p2] == '\r' || t[p2] == '\n'))
856 t[p2] = 0;
859 g_free (p_copy);
860 return TRUE;
862 error:
864 static int errorcount = 0;
866 if (++errorcount < 5)
868 message (D_ERROR, _("Cannot parse:"), "%s", (p_copy && *p_copy) ? p_copy : line);
870 else if (errorcount == 5)
871 message (D_ERROR, MSG_ERROR, _("More parsing errors will be ignored."));
874 g_free (p_copy);
875 return FALSE;
878 /* --------------------------------------------------------------------------------------------- */