(mc_search__recode_str): minor optimization.
[midnight-commander.git] / lib / vfs / parse_ls_vga.c
blobc29db5ad7d8ced3f5b379dc4b26b422b332e3b64
1 /*
2 Routines for parsing output from the 'ls' command.
4 Copyright (C) 1988-2018
5 Free Software Foundation, Inc.
7 Copyright (C) 1995, 1996 Miguel de Icaza
9 Written by:
10 Miguel de Icaza, 1995, 1996
11 Slava Zanko <slavazanko@gmail.com>, 2011
13 This file is part of the Midnight Commander.
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.
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.
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/>.
29 /**
30 * \file
31 * \brief Source: Utilities for VFS modules
32 * \author Miguel de Icaza
33 * \date 1995, 1996
36 #include <config.h>
38 #include <ctype.h>
39 #include <stdio.h>
40 #include <stdlib.h>
42 #include "lib/global.h"
43 #include "lib/unixcompat.h" /* makedev */
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 /* cppcheck-suppress invalidscanf */
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++];
547 ALLOWED DATE FORMATS
549 We expect 3 fields max or we'll see oddities with certain file names.
551 Formats that contain either year or time (the default 'ls' formats):
553 * Mon DD hh:mm[:ss]
554 * Mon DD YYYY
556 Formats that contain both year and time, to make it easier to write
557 extfs scripts:
559 * MM-DD-YYYY hh:mm[:ss]
560 * MM-DD-YY hh:mm[:ss]
562 ('/' and '\' can be used instead of '-'.)
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 (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.)
572 /* Month name */
573 if (is_month (p, &tim))
575 /* And we expect, it followed by day number */
576 if (is_num (idx))
577 tim.tm_mday = (int) atol (columns[idx++]);
578 else
579 return 0; /* No day */
582 else
584 /* Case with MM-DD-YY or MM-DD-YYYY */
585 if (is_dos_date (p))
587 p[2] = p[5] = '-';
589 /* cppcheck-suppress invalidscanf */
590 if (sscanf (p, "%2d-%2d-%d", &d[0], &d[1], &d[2]) == 3)
592 /* Months are zero based */
593 if (d[0] > 0)
594 d[0]--;
596 if (d[2] > 1900)
598 d[2] -= 1900;
600 else
602 /* Y2K madness */
603 if (d[2] < 70)
604 d[2] += 100;
607 tim.tm_mon = d[0];
608 tim.tm_mday = d[1];
609 tim.tm_year = d[2];
610 got_year = 1;
612 else
613 return 0; /* sscanf failed */
615 else
617 /* Locale's abbreviated month name followed by day number */
618 if (is_localized_month (p) && (is_num (idx++)))
619 l10n = 1;
620 else
621 return 0; /* unsupported format */
625 /* Here we expect to find time or year */
626 if (is_num (idx) && (is_time (columns[idx], &tim) || (got_year = is_year (columns[idx], &tim))))
627 idx++;
628 else
629 return 0; /* Neither time nor date */
632 * If the date is less than 6 months in the past, it is shown without year
633 * other dates in the past or future are shown with year but without time
634 * This does not check for years before 1900 ... I don't know, how
635 * to represent them at all
637 if (!got_year && local_time->tm_mon < 6
638 && local_time->tm_mon < tim.tm_mon && tim.tm_mon - local_time->tm_mon >= 6)
640 tim.tm_year--;
642 *t = mktime (&tim);
643 if (l10n || (*t < 0))
644 *t = 0;
645 return idx;
648 /* --------------------------------------------------------------------------------------------- */
651 vfs_split_text (char *p)
653 char *original = p;
654 int numcols;
656 memset (columns, 0, sizeof (columns));
658 for (numcols = 0; *p && numcols < MAXCOLS; numcols++)
660 while (*p == ' ' || *p == '\r' || *p == '\n')
662 *p = 0;
663 p++;
665 columns[numcols] = p;
666 column_ptr[numcols] = p - original;
667 while (*p && *p != ' ' && *p != '\r' && *p != '\n')
668 p++;
670 return numcols;
673 /* --------------------------------------------------------------------------------------------- */
675 void
676 vfs_parse_ls_lga_init (void)
678 vfs_parce_ls_final_num_spaces = 1;
681 /* --------------------------------------------------------------------------------------------- */
683 size_t
684 vfs_parse_ls_lga_get_final_spaces (void)
686 return vfs_parce_ls_final_num_spaces;
689 /* --------------------------------------------------------------------------------------------- */
691 gboolean
692 vfs_parse_ls_lga (const char *p, struct stat * s, char **filename, char **linkname,
693 size_t * num_spaces)
695 int idx, idx2, num_cols;
696 int i;
697 char *p_copy = NULL;
698 char *t = NULL;
699 const char *line = p;
700 size_t skipped;
702 if (strncmp (p, "total", 5) == 0)
703 return FALSE;
705 if (!vfs_parse_filetype (p, &skipped, &s->st_mode))
706 goto error;
707 p += skipped;
709 if (*p == ' ') /* Notwell 4 */
710 p++;
711 if (*p == '[')
713 if (strlen (p) <= 8 || p[8] != ']')
714 goto error;
715 /* Should parse here the Notwell permissions :) */
716 if (S_ISDIR (s->st_mode))
717 s->st_mode |= (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IXUSR | S_IXGRP | S_IXOTH);
718 else
719 s->st_mode |= (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR);
720 p += 9;
722 else
724 size_t lc_skipped;
725 mode_t perms;
727 if (!vfs_parse_fileperms (p, &lc_skipped, &perms))
728 goto error;
729 p += lc_skipped;
730 s->st_mode |= perms;
733 p_copy = g_strdup (p);
734 num_cols = vfs_split_text (p_copy);
736 s->st_nlink = atol (columns[0]);
737 if (s->st_nlink <= 0)
738 goto error;
740 if (!is_num (1))
741 s->st_uid = vfs_finduid (columns[1]);
742 else
743 s->st_uid = (uid_t) atol (columns[1]);
745 /* Mhm, the ls -lg did not produce a group field */
746 for (idx = 3; idx <= 5; idx++)
747 if (is_month (columns[idx], NULL) || is_week (columns[idx], NULL)
748 || is_dos_date (columns[idx]) || is_localized_month (columns[idx]))
749 break;
751 if (idx == 6 || (idx == 5 && !S_ISCHR (s->st_mode) && !S_ISBLK (s->st_mode)))
752 goto error;
754 /* We don't have gid */
755 if (idx == 3 || (idx == 4 && (S_ISCHR (s->st_mode) || S_ISBLK (s->st_mode))))
756 idx2 = 2;
757 else
759 /* We have gid field */
760 if (is_num (2))
761 s->st_gid = (gid_t) atol (columns[2]);
762 else
763 s->st_gid = vfs_findgid (columns[2]);
764 idx2 = 3;
767 /* This is device */
768 if (S_ISCHR (s->st_mode) || S_ISBLK (s->st_mode))
770 int maj, min;
772 /* Corner case: there is no whitespace(s) between maj & min */
773 if (!is_num (idx2) && idx2 == 2)
775 /* cppcheck-suppress invalidscanf */
776 if (!is_num (++idx2) || sscanf (columns[idx2], " %d,%d", &maj, &min) != 2)
777 goto error;
779 else
781 /* cppcheck-suppress invalidscanf */
782 if (!is_num (idx2) || sscanf (columns[idx2], " %d,", &maj) != 1)
783 goto error;
785 /* cppcheck-suppress invalidscanf */
786 if (!is_num (++idx2) || sscanf (columns[idx2], " %d", &min) != 1)
787 goto error;
789 #ifdef HAVE_STRUCT_STAT_ST_RDEV
790 s->st_rdev = makedev (maj, min);
791 #endif
792 s->st_size = 0;
795 else
797 /* Common file size */
798 if (!is_num (idx2))
799 goto error;
801 s->st_size = (off_t) g_ascii_strtoll (columns[idx2], NULL, 10);
802 #ifdef HAVE_STRUCT_STAT_ST_RDEV
803 s->st_rdev = 0;
804 #endif
807 idx = vfs_parse_filedate (idx, &s->st_mtime);
808 if (!idx)
809 goto error;
810 /* Use resulting time value */
811 s->st_atime = s->st_ctime = s->st_mtime;
812 #ifdef HAVE_STRUCT_STAT_ST_MTIM
813 s->st_atim.tv_nsec = s->st_mtim.tv_nsec = s->st_ctim.tv_nsec = 0;
814 #endif
816 /* s->st_dev and s->st_ino must be initialized by vfs_s_new_inode () */
817 #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
818 s->st_blksize = 512;
819 #endif
820 vfs_adjust_stat (s);
822 if (num_spaces != NULL)
824 *num_spaces = column_ptr[idx] - column_ptr[idx - 1] - strlen (columns[idx - 1]);
825 if (DIR_IS_DOTDOT (columns[idx]))
826 vfs_parce_ls_final_num_spaces = *num_spaces;
829 for (i = idx + 1, idx2 = 0; i < num_cols; i++)
830 if (strcmp (columns[i], "->") == 0)
832 idx2 = i;
833 break;
836 if (((S_ISLNK (s->st_mode) || (num_cols == idx + 3 && s->st_nlink > 1))) /* Maybe a hardlink? (in extfs) */
837 && idx2)
840 if (filename)
842 *filename = g_strndup (p + column_ptr[idx], column_ptr[idx2] - column_ptr[idx] - 1);
844 if (linkname)
846 t = g_strdup (p + column_ptr[idx2 + 1]);
847 *linkname = t;
850 else
852 /* Extract the filename from the string copy, not from the columns
853 * this way we have a chance of entering hidden directories like ". ."
855 if (filename)
858 * filename = g_strdup (columns [idx++]);
861 t = g_strdup (p + column_ptr[idx]);
862 *filename = t;
864 if (linkname)
865 *linkname = NULL;
868 if (t)
870 int p2 = strlen (t);
871 if ((--p2 > 0) && (t[p2] == '\r' || t[p2] == '\n'))
872 t[p2] = 0;
873 if ((--p2 > 0) && (t[p2] == '\r' || t[p2] == '\n'))
874 t[p2] = 0;
877 g_free (p_copy);
878 return TRUE;
880 error:
882 static int errorcount = 0;
884 if (++errorcount < 5)
886 message (D_ERROR, _("Cannot parse:"), "%s", (p_copy && *p_copy) ? p_copy : line);
888 else if (errorcount == 5)
889 message (D_ERROR, MSG_ERROR, _("More parsing errors will be ignored."));
892 g_free (p_copy);
893 return FALSE;
896 /* --------------------------------------------------------------------------------------------- */