Ticket 1551: Update GPL version from 2 to 3
[midnight-commander.git] / lib / vfs / parse_ls_vga.c
blob8091c14284c238954de034423784aee4da365c2f
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/widget.h" /* message() */
46 #include "utilvfs.h"
48 /*** global variables ****************************************************************************/
50 /*** file scope macro definitions ****************************************************************/
52 /* Parsing code is used by ftpfs, fish and extfs */
53 #define MAXCOLS 30
55 /*** file scope type declarations ****************************************************************/
57 /*** file scope variables ************************************************************************/
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;
63 /*** file scope functions ************************************************************************/
64 /* --------------------------------------------------------------------------------------------- */
66 static int
67 is_num (int idx)
69 char *column = columns[idx];
71 if (!column || column[0] < '0' || column[0] > '9')
72 return 0;
74 return 1;
77 /* --------------------------------------------------------------------------------------------- */
78 /* Return 1 for MM-DD-YY and MM-DD-YYYY */
80 static int
81 is_dos_date (const char *str)
83 int len;
85 if (!str)
86 return 0;
88 len = strlen (str);
89 if (len != 8 && len != 10)
90 return 0;
92 if (str[2] != str[5])
93 return 0;
95 if (!strchr ("\\-/", (int) str[2]))
96 return 0;
98 return 1;
101 /* --------------------------------------------------------------------------------------------- */
103 static int
104 is_week (const char *str, struct tm *tim)
106 static const char *week = "SunMonTueWedThuFriSat";
107 const char *pos;
109 if (!str)
110 return 0;
112 pos = strstr (week, str);
113 if (pos != NULL)
115 if (tim != NULL)
116 tim->tm_wday = (pos - week) / 3;
117 return 1;
119 return 0;
122 /* --------------------------------------------------------------------------------------------- */
124 static int
125 is_month (const char *str, struct tm *tim)
127 static const char *month = "JanFebMarAprMayJunJulAugSepOctNovDec";
128 const char *pos;
130 if (!str)
131 return 0;
133 pos = strstr (month, str);
134 if (pos != NULL)
136 if (tim != NULL)
137 tim->tm_mon = (pos - month) / 3;
138 return 1;
140 return 0;
143 /* --------------------------------------------------------------------------------------------- */
145 * Check for possible locale's abbreviated month name (Jan..Dec).
146 * Any 3 bytes long string without digit, control and punctuation characters.
147 * isalpha() is locale specific, so it cannot be used if current
148 * locale is "C" and ftp server use Cyrillic.
149 * NB: It is assumed there are no whitespaces in month.
151 static int
152 is_localized_month (const char *month)
154 int i = 0;
156 if (!month)
157 return 0;
159 while ((i < 3) && *month && !isdigit ((unsigned char) *month)
160 && !iscntrl ((unsigned char) *month) && !ispunct ((unsigned char) *month))
162 i++;
163 month++;
165 return ((i == 3) && (*month == 0));
168 /* --------------------------------------------------------------------------------------------- */
170 static int
171 is_time (const char *str, struct tm *tim)
173 const char *p, *p2;
175 if (str == NULL)
176 return 0;
178 p = strchr (str, ':');
179 p2 = strrchr (str, ':');
180 if (p != NULL && p2 != NULL)
182 if (p != p2)
184 if (sscanf (str, "%2d:%2d:%2d", &tim->tm_hour, &tim->tm_min, &tim->tm_sec) != 3)
185 return 0;
187 else
189 if (sscanf (str, "%2d:%2d", &tim->tm_hour, &tim->tm_min) != 2)
190 return 0;
193 else
194 return 0;
196 return 1;
199 /* --------------------------------------------------------------------------------------------- */
201 static int
202 is_year (char *str, struct tm *tim)
204 long year;
206 if (!str)
207 return 0;
209 if (strchr (str, ':'))
210 return 0;
212 if (strlen (str) != 4)
213 return 0;
215 if (sscanf (str, "%ld", &year) != 1)
216 return 0;
218 if (year < 1900 || year > 3000)
219 return 0;
221 tim->tm_year = (int) (year - 1900);
223 return 1;
226 /* --------------------------------------------------------------------------------------------- */
227 /*** public functions ****************************************************************************/
228 /* --------------------------------------------------------------------------------------------- */
230 gboolean
231 vfs_parse_filetype (const char *s, size_t * ret_skipped, mode_t * ret_type)
233 mode_t type;
235 switch (*s)
237 case 'd':
238 type = S_IFDIR;
239 break;
240 case 'b':
241 type = S_IFBLK;
242 break;
243 case 'c':
244 type = S_IFCHR;
245 break;
246 case 'l':
247 type = S_IFLNK;
248 break;
249 #ifdef S_IFSOCK
250 case 's':
251 type = S_IFSOCK;
252 break;
253 #else
254 case 's':
255 type = S_IFIFO;
256 break;
257 #endif
258 #ifdef S_IFDOOR /* Solaris door */
259 case 'D':
260 type = S_IFDOOR;
261 break;
262 #else
263 case 'D':
264 type = S_IFIFO;
265 break;
266 #endif
267 case 'p':
268 type = S_IFIFO;
269 break;
270 #ifdef S_IFNAM /* Special named files */
271 case 'n':
272 type = S_IFNAM;
273 break;
274 #else
275 case 'n':
276 type = S_IFREG;
277 break;
278 #endif
279 case 'm': /* Don't know what these are :-) */
280 case '-':
281 case '?':
282 type = S_IFREG;
283 break;
284 default:
285 return FALSE;
288 *ret_type = type;
289 *ret_skipped = 1;
290 return TRUE;
293 /* --------------------------------------------------------------------------------------------- */
295 gboolean
296 vfs_parse_fileperms (const char *s, size_t * ret_skipped, mode_t * ret_perms)
298 const char *p;
299 mode_t perms;
301 p = s;
302 perms = 0;
304 switch (*p++)
306 case '-':
307 break;
308 case 'r':
309 perms |= S_IRUSR;
310 break;
311 default:
312 return FALSE;
314 switch (*p++)
316 case '-':
317 break;
318 case 'w':
319 perms |= S_IWUSR;
320 break;
321 default:
322 return FALSE;
324 switch (*p++)
326 case '-':
327 break;
328 case 'S':
329 perms |= S_ISUID;
330 break;
331 case 's':
332 perms |= S_IXUSR | S_ISUID;
333 break;
334 case 'x':
335 perms |= S_IXUSR;
336 break;
337 default:
338 return FALSE;
340 switch (*p++)
342 case '-':
343 break;
344 case 'r':
345 perms |= S_IRGRP;
346 break;
347 default:
348 return FALSE;
350 switch (*p++)
352 case '-':
353 break;
354 case 'w':
355 perms |= S_IWGRP;
356 break;
357 default:
358 return FALSE;
360 switch (*p++)
362 case '-':
363 break;
364 case 'S':
365 perms |= S_ISGID;
366 break;
367 case 'l':
368 perms |= S_ISGID;
369 break; /* found on Solaris */
370 case 's':
371 perms |= S_IXGRP | S_ISGID;
372 break;
373 case 'x':
374 perms |= S_IXGRP;
375 break;
376 default:
377 return FALSE;
379 switch (*p++)
381 case '-':
382 break;
383 case 'r':
384 perms |= S_IROTH;
385 break;
386 default:
387 return FALSE;
389 switch (*p++)
391 case '-':
392 break;
393 case 'w':
394 perms |= S_IWOTH;
395 break;
396 default:
397 return FALSE;
399 switch (*p++)
401 case '-':
402 break;
403 case 'T':
404 perms |= S_ISVTX;
405 break;
406 case 't':
407 perms |= S_IXOTH | S_ISVTX;
408 break;
409 case 'x':
410 perms |= S_IXOTH;
411 break;
412 default:
413 return FALSE;
415 if (*p == '+')
416 { /* ACLs on Solaris, HP-UX and others */
417 p++;
420 *ret_skipped = p - s;
421 *ret_perms = perms;
422 return TRUE;
425 /* --------------------------------------------------------------------------------------------- */
427 gboolean
428 vfs_parse_filemode (const char *s, size_t * ret_skipped, mode_t * ret_mode)
430 const char *p;
431 mode_t type, perms;
432 size_t skipped;
434 p = s;
436 if (!vfs_parse_filetype (p, &skipped, &type))
437 return FALSE;
438 p += skipped;
440 if (!vfs_parse_fileperms (p, &skipped, &perms))
441 return FALSE;
442 p += skipped;
444 *ret_skipped = p - s;
445 *ret_mode = type | perms;
446 return TRUE;
449 /* --------------------------------------------------------------------------------------------- */
451 gboolean
452 vfs_parse_raw_filemode (const char *s, size_t * ret_skipped, mode_t * ret_mode)
454 const char *p;
455 mode_t remote_type = 0, local_type, perms = 0;
457 p = s;
459 /* isoctal */
460 while (*p >= '0' && *p <= '7')
462 perms *= 010;
463 perms += (*p - '0');
464 ++p;
467 if (*p++ != ' ')
468 return FALSE;
470 while (*p >= '0' && *p <= '7')
472 remote_type *= 010;
473 remote_type += (*p - '0');
474 ++p;
477 if (*p++ != ' ')
478 return FALSE;
480 /* generated with:
481 $ perl -e 'use Fcntl ":mode";
482 my @modes = (S_IFDIR, S_IFBLK, S_IFCHR, S_IFLNK, S_IFREG);
483 foreach $t (@modes) { printf ("%o\n", $t); };'
484 TODO: S_IFDOOR, S_IFIFO, S_IFSOCK (if supported by os)
485 (see vfs_parse_filetype)
488 switch (remote_type)
490 case 020000:
491 local_type = S_IFCHR;
492 break;
493 case 040000:
494 local_type = S_IFDIR;
495 break;
496 case 060000:
497 local_type = S_IFBLK;
498 break;
499 case 0120000:
500 local_type = S_IFLNK;
501 break;
502 case 0100000:
503 default: /* don't know what is it */
504 local_type = S_IFREG;
505 break;
508 *ret_skipped = p - s;
509 *ret_mode = local_type | perms;
510 return TRUE;
513 /* --------------------------------------------------------------------------------------------- */
514 /** This function parses from idx in the columns[] array */
517 vfs_parse_filedate (int idx, time_t * t)
519 char *p;
520 struct tm tim;
521 int d[3];
522 int got_year = 0;
523 int l10n = 0; /* Locale's abbreviated month name */
524 time_t current_time;
525 struct tm *local_time;
527 /* Let's setup default time values */
528 current_time = time (NULL);
529 local_time = localtime (&current_time);
530 tim.tm_mday = local_time->tm_mday;
531 tim.tm_mon = local_time->tm_mon;
532 tim.tm_year = local_time->tm_year;
534 tim.tm_hour = 0;
535 tim.tm_min = 0;
536 tim.tm_sec = 0;
537 tim.tm_isdst = -1; /* Let mktime() try to guess correct dst offset */
539 p = columns[idx++];
541 /* We eat weekday name in case of extfs */
542 if (is_week (p, &tim))
543 p = columns[idx++];
545 /* Month name */
546 if (is_month (p, &tim))
548 /* And we expect, it followed by day number */
549 if (is_num (idx))
550 tim.tm_mday = (int) atol (columns[idx++]);
551 else
552 return 0; /* No day */
555 else
557 /* We expect:
558 3 fields max or we'll see oddities with certain file names.
559 So both year and time is not allowed.
560 Mon DD hh:mm[:ss]
561 Mon DD YYYY
562 But in case of extfs we allow these date formats:
563 MM-DD-YY hh:mm[:ss]
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. */
567 /* Special case with MM-DD-YY or MM-DD-YYYY */
568 if (is_dos_date (p))
570 p[2] = p[5] = '-';
572 if (sscanf (p, "%2d-%2d-%d", &d[0], &d[1], &d[2]) == 3)
574 /* Months are zero based */
575 if (d[0] > 0)
576 d[0]--;
578 if (d[2] > 1900)
580 d[2] -= 1900;
582 else
584 /* Y2K madness */
585 if (d[2] < 70)
586 d[2] += 100;
589 tim.tm_mon = d[0];
590 tim.tm_mday = d[1];
591 tim.tm_year = d[2];
592 got_year = 1;
594 else
595 return 0; /* sscanf failed */
597 else
599 /* Locale's abbreviated month name followed by day number */
600 if (is_localized_month (p) && (is_num (idx++)))
601 l10n = 1;
602 else
603 return 0; /* unsupported format */
607 /* Here we expect to find time or year */
608 if (is_num (idx) && (is_time (columns[idx], &tim) || (got_year = is_year (columns[idx], &tim))))
609 idx++;
610 else
611 return 0; /* Neither time nor date */
614 * If the date is less than 6 months in the past, it is shown without year
615 * other dates in the past or future are shown with year but without time
616 * This does not check for years before 1900 ... I don't know, how
617 * to represent them at all
619 if (!got_year && local_time->tm_mon < 6
620 && local_time->tm_mon < tim.tm_mon && tim.tm_mon - local_time->tm_mon >= 6)
622 tim.tm_year--;
624 *t = mktime (&tim);
625 if (l10n || (*t < 0))
626 *t = 0;
627 return idx;
630 /* --------------------------------------------------------------------------------------------- */
633 vfs_split_text (char *p)
635 char *original = p;
636 int numcols;
638 memset (columns, 0, sizeof (columns));
640 for (numcols = 0; *p && numcols < MAXCOLS; numcols++)
642 while (*p == ' ' || *p == '\r' || *p == '\n')
644 *p = 0;
645 p++;
647 columns[numcols] = p;
648 column_ptr[numcols] = p - original;
649 while (*p && *p != ' ' && *p != '\r' && *p != '\n')
650 p++;
652 return numcols;
655 /* --------------------------------------------------------------------------------------------- */
657 void
658 vfs_parse_ls_lga_init (void)
660 vfs_parce_ls_final_num_spaces = 0;
663 /* --------------------------------------------------------------------------------------------- */
665 size_t
666 vfs_parse_ls_lga_get_final_spaces (void)
668 return vfs_parce_ls_final_num_spaces;
671 /* --------------------------------------------------------------------------------------------- */
673 gboolean
674 vfs_parse_ls_lga (const char *p, struct stat * s, char **filename, char **linkname,
675 size_t * num_spaces)
677 int idx, idx2, num_cols;
678 int i;
679 char *p_copy = NULL;
680 char *t = NULL;
681 const char *line = p;
682 size_t skipped;
684 if (strncmp (p, "total", 5) == 0)
685 return FALSE;
687 if (!vfs_parse_filetype (p, &skipped, &s->st_mode))
688 goto error;
689 p += skipped;
691 if (*p == ' ') /* Notwell 4 */
692 p++;
693 if (*p == '[')
695 if (strlen (p) <= 8 || p[8] != ']')
696 goto error;
697 /* Should parse here the Notwell permissions :) */
698 if (S_ISDIR (s->st_mode))
699 s->st_mode |= (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IXUSR | S_IXGRP | S_IXOTH);
700 else
701 s->st_mode |= (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR);
702 p += 9;
704 else
706 size_t lc_skipped;
707 mode_t perms;
709 if (!vfs_parse_fileperms (p, &lc_skipped, &perms))
710 goto error;
711 p += lc_skipped;
712 s->st_mode |= perms;
715 p_copy = g_strdup (p);
716 num_cols = vfs_split_text (p_copy);
718 s->st_nlink = atol (columns[0]);
719 if (s->st_nlink <= 0)
720 goto error;
722 if (!is_num (1))
723 s->st_uid = vfs_finduid (columns[1]);
724 else
725 s->st_uid = (uid_t) atol (columns[1]);
727 /* Mhm, the ls -lg did not produce a group field */
728 for (idx = 3; idx <= 5; idx++)
729 if (is_month (columns[idx], NULL) || is_week (columns[idx], NULL)
730 || is_dos_date (columns[idx]) || is_localized_month (columns[idx]))
731 break;
733 if (idx == 6 || (idx == 5 && !S_ISCHR (s->st_mode) && !S_ISBLK (s->st_mode)))
734 goto error;
736 /* We don't have gid */
737 if (idx == 3 || (idx == 4 && (S_ISCHR (s->st_mode) || S_ISBLK (s->st_mode))))
738 idx2 = 2;
739 else
741 /* We have gid field */
742 if (is_num (2))
743 s->st_gid = (gid_t) atol (columns[2]);
744 else
745 s->st_gid = vfs_findgid (columns[2]);
746 idx2 = 3;
749 /* This is device */
750 if (S_ISCHR (s->st_mode) || S_ISBLK (s->st_mode))
752 int maj, min;
754 /* Corner case: there is no whitespace(s) between maj & min */
755 if (!is_num (idx2) && idx2 == 2)
757 if (!is_num (++idx2) || sscanf (columns[idx2], " %d,%d", &maj, &min) != 2)
758 goto error;
760 else
762 if (!is_num (idx2) || sscanf (columns[idx2], " %d,", &maj) != 1)
763 goto error;
765 if (!is_num (++idx2) || sscanf (columns[idx2], " %d", &min) != 1)
766 goto error;
768 #ifdef HAVE_STRUCT_STAT_ST_RDEV
769 s->st_rdev = makedev (maj, min);
770 #endif
771 s->st_size = 0;
774 else
776 /* Common file size */
777 if (!is_num (idx2))
778 goto error;
780 #ifdef HAVE_ATOLL
781 s->st_size = (off_t) atoll (columns[idx2]);
782 #else
783 s->st_size = (off_t) atof (columns[idx2]);
784 #endif
785 #ifdef HAVE_STRUCT_STAT_ST_RDEV
786 s->st_rdev = 0;
787 #endif
790 idx = vfs_parse_filedate (idx, &s->st_mtime);
791 if (!idx)
792 goto error;
793 /* Use resulting time value */
794 s->st_atime = s->st_ctime = s->st_mtime;
795 /* s->st_dev and s->st_ino must be initialized by vfs_s_new_inode () */
796 #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
797 s->st_blksize = 512;
798 #endif
799 #ifdef HAVE_STRUCT_STAT_ST_BLOCKS
800 s->st_blocks = (s->st_size + 511) / 512;
801 #endif
803 if (num_spaces != NULL)
805 *num_spaces = column_ptr[idx] - column_ptr[idx - 1] - strlen (columns[idx - 1]);
806 if (strcmp (columns[idx], "..") == 0)
807 vfs_parce_ls_final_num_spaces = *num_spaces;
810 for (i = idx + 1, idx2 = 0; i < num_cols; i++)
811 if (strcmp (columns[i], "->") == 0)
813 idx2 = i;
814 break;
817 if (((S_ISLNK (s->st_mode) || (num_cols == idx + 3 && s->st_nlink > 1))) /* Maybe a hardlink? (in extfs) */
818 && idx2)
821 if (filename)
823 *filename = g_strndup (p + column_ptr[idx], column_ptr[idx2] - column_ptr[idx] - 1);
825 if (linkname)
827 t = g_strdup (p + column_ptr[idx2 + 1]);
828 *linkname = t;
831 else
833 /* Extract the filename from the string copy, not from the columns
834 * this way we have a chance of entering hidden directories like ". ."
836 if (filename)
839 * filename = g_strdup (columns [idx++]);
842 t = g_strdup (p + column_ptr[idx]);
843 *filename = t;
845 if (linkname)
846 *linkname = NULL;
849 if (t)
851 int p2 = strlen (t);
852 if ((--p2 > 0) && (t[p2] == '\r' || t[p2] == '\n'))
853 t[p2] = 0;
854 if ((--p2 > 0) && (t[p2] == '\r' || t[p2] == '\n'))
855 t[p2] = 0;
858 g_free (p_copy);
859 return TRUE;
861 error:
863 static int errorcount = 0;
865 if (++errorcount < 5)
867 message (D_ERROR, _("Cannot parse:"), "%s", (p_copy && *p_copy) ? p_copy : line);
869 else if (errorcount == 5)
870 message (D_ERROR, MSG_ERROR, _("More parsing errors will be ignored."));
873 g_free (p_copy);
874 return FALSE;
877 /* --------------------------------------------------------------------------------------------- */