2 Routines for parsing output from the 'ls' command.
4 Copyright (C) 1988-2024
5 Free Software Foundation, Inc.
7 Copyright (C) 1995, 1996 Miguel de Icaza
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/>.
31 * \brief Source: Utilities for VFS modules
32 * \author Miguel de Icaza
42 #include "lib/global.h"
43 #include "lib/unixcompat.h" /* makedev */
44 #include "lib/widget.h" /* message() */
48 /*** global variables ****************************************************************************/
50 /*** file scope macro definitions ****************************************************************/
52 /* Parsing code is used by ftpfs, shell and extfs */
55 /*** file scope type declarations ****************************************************************/
57 /*** forward declarations (file scope functions) *************************************************/
59 /*** file scope variables ************************************************************************/
61 static char *columns
[MAXCOLS
]; /* Points to the string in column n */
62 static int column_ptr
[MAXCOLS
]; /* Index from 0 to the starting positions of the columns */
63 static size_t vfs_parce_ls_final_num_spaces
= 0;
65 /* --------------------------------------------------------------------------------------------- */
66 /*** file scope functions ************************************************************************/
67 /* --------------------------------------------------------------------------------------------- */
72 char *column
= columns
[idx
];
74 return (column
!= NULL
&& isdigit (column
[0]));
77 /* --------------------------------------------------------------------------------------------- */
78 /* Return TRUE for MM-DD-YY and MM-DD-YYYY */
81 is_dos_date (const char *str
)
89 if (len
!= 8 && len
!= 10)
95 return (strchr ("\\-/", (int) str
[2]) != NULL
);
98 /* --------------------------------------------------------------------------------------------- */
101 is_week (const char *str
, struct tm
*tim
)
103 static const char *week
= "SunMonTueWedThuFriSat";
109 pos
= strstr (week
, str
);
114 tim
->tm_wday
= (pos
- week
) / 3;
119 /* --------------------------------------------------------------------------------------------- */
121 * Check for possible locale's abbreviated month name (Jan..Dec).
122 * Any 3 bytes long string without digit, control and punctuation characters.
123 * isalpha() is locale specific, so it cannot be used if current
124 * locale is "C" and ftp server use Cyrillic.
125 * NB: It is assumed there are no whitespaces in month.
128 is_localized_month (const char *month
)
136 i
< 3 && *month
!= '\0' && !isdigit ((unsigned char) *month
)
137 && !iscntrl ((unsigned char) *month
) && !ispunct ((unsigned char) *month
); i
++, month
++)
140 return (i
== 3 && *month
== '\0');
143 /* --------------------------------------------------------------------------------------------- */
146 is_time (const char *str
, struct tm
*tim
)
153 p
= strchr (str
, ':');
157 p2
= strrchr (str
, ':');
163 if (sscanf (str
, "%2d:%2d:%2d", &tim
->tm_hour
, &tim
->tm_min
, &tim
->tm_sec
) != 3)
168 if (sscanf (str
, "%2d:%2d", &tim
->tm_hour
, &tim
->tm_min
) != 2)
175 /* --------------------------------------------------------------------------------------------- */
178 is_year (char *str
, struct tm
*tim
)
185 if (strchr (str
, ':') != NULL
)
188 if (strlen (str
) != 4)
191 /* cppcheck-suppress invalidscanf */
192 if (sscanf (str
, "%ld", &year
) != 1)
195 if (year
< 1900 || year
> 3000)
198 tim
->tm_year
= (int) (year
- 1900);
203 /* --------------------------------------------------------------------------------------------- */
204 /*** public functions ****************************************************************************/
205 /* --------------------------------------------------------------------------------------------- */
208 vfs_parse_filetype (const char *s
, size_t *ret_skipped
, mode_t
*ret_type
)
235 #ifdef S_IFDOOR /* Solaris door */
247 #ifdef S_IFNAM /* Special named files */
256 case 'm': /* Don't know what these are :-) */
267 if (ret_skipped
!= NULL
)
272 /* --------------------------------------------------------------------------------------------- */
275 vfs_parse_fileperms (const char *s
, size_t *ret_skipped
, mode_t
*ret_perms
)
310 perms
|= S_IXUSR
| S_ISUID
;
350 break; /* found on Solaris */
352 perms
|= S_IXGRP
| S_ISGID
;
391 perms
|= S_IXOTH
| S_ISVTX
;
401 /* ACLs on Solaris, HP-UX and others */
404 if (ret_skipped
!= NULL
)
405 *ret_skipped
= p
- s
;
411 /* --------------------------------------------------------------------------------------------- */
414 vfs_parse_filemode (const char *s
, size_t *ret_skipped
, mode_t
*ret_mode
)
420 if (!vfs_parse_filetype (p
, &skipped
, &type
))
424 if (!vfs_parse_fileperms (p
, &skipped
, &perms
))
428 *ret_skipped
= p
- s
;
429 *ret_mode
= type
| perms
;
434 /* --------------------------------------------------------------------------------------------- */
437 vfs_parse_raw_filemode (const char *s
, size_t *ret_skipped
, mode_t
*ret_mode
)
440 mode_t remote_type
= 0, local_type
, perms
= 0;
443 for (; *p
>= '0' && *p
<= '7'; p
++)
452 for (; *p
>= '0' && *p
<= '7'; p
++)
455 remote_type
+= (*p
- '0');
462 $ perl -e 'use Fcntl ":mode";
463 my @modes = (S_IFDIR, S_IFBLK, S_IFCHR, S_IFLNK, S_IFREG);
464 foreach $t (@modes) { printf ("%o\n", $t); };'
465 TODO: S_IFDOOR, S_IFIFO, S_IFSOCK (if supported by os)
466 (see vfs_parse_filetype)
472 local_type
= S_IFCHR
;
475 local_type
= S_IFDIR
;
478 local_type
= S_IFBLK
;
481 local_type
= S_IFLNK
;
484 default: /* don't know what is it */
485 local_type
= S_IFREG
;
489 *ret_skipped
= p
- s
;
490 *ret_mode
= local_type
| perms
;
495 /* --------------------------------------------------------------------------------------------- */
498 vfs_parse_month (const char *str
, struct tm
*tim
)
500 static const char *month
= "JanFebMarAprMayJunJulAugSepOctNovDec";
506 pos
= strstr (month
, str
);
511 tim
->tm_mon
= (pos
- month
) / 3;
516 /* --------------------------------------------------------------------------------------------- */
517 /** This function parses from idx in the columns[] array */
520 vfs_parse_filedate (int idx
, time_t *t
)
525 gboolean got_year
= FALSE
;
526 gboolean l10n
= FALSE
; /* Locale's abbreviated month name */
528 struct tm
*local_time
;
530 /* Let's setup default time values */
531 current_time
= time (NULL
);
532 local_time
= localtime (¤t_time
);
533 tim
.tm_mday
= local_time
->tm_mday
;
534 tim
.tm_mon
= local_time
->tm_mon
;
535 tim
.tm_year
= local_time
->tm_year
;
540 tim
.tm_isdst
= -1; /* Let mktime() try to guess correct dst offset */
544 /* We eat weekday name in case of extfs */
545 if (is_week (p
, &tim
))
551 We expect 3 fields max or we'll see oddities with certain file names.
553 Formats that contain either year or time (the default 'ls' formats):
558 Formats that contain both year and time, to make it easier to write
561 * MM-DD-YYYY hh:mm[:ss]
562 * MM-DD-YY hh:mm[:ss]
564 ('/' and '\' can be used instead of '-'.)
566 where Mon is Jan-Dec, DD, MM, YY two digit day, month, year,
567 YYYY four digit year, hh, mm, ss two digit hour, minute or second.
569 (As for the "3 fields max" restriction: this prevents, for example, a
570 file name "13:48" from being considered part of a "Sep 19 2016" date
571 string preceding it.)
575 if (vfs_parse_month (p
, &tim
))
577 /* And we expect, it followed by day number */
579 return 0; /* No day */
581 tim
.tm_mday
= (int) atol (columns
[idx
++]);
584 else if (is_dos_date (p
))
586 /* Case with MM-DD-YY or MM-DD-YYYY */
589 /* cppcheck-suppress invalidscanf */
590 if (sscanf (p
, "%2d-%2d-%d", &d
[0], &d
[1], &d
[2]) != 3)
591 return 0; /* sscanf failed */
593 /* Months are zero based */
608 else if (is_localized_month (p
) && is_num (idx
++))
609 /* Locale's abbreviated month name followed by day number */
612 return 0; /* unsupported format */
614 /* Here we expect to find time or year */
616 || !(is_time (columns
[idx
], &tim
) || (got_year
= is_year (columns
[idx
], &tim
))))
617 return 0; /* Neither time nor date */
622 * If the date is less than 6 months in the past, it is shown without year
623 * other dates in the past or future are shown with year but without time
624 * This does not check for years before 1900 ... I don't know, how
625 * to represent them at all
627 if (!got_year
&& local_time
->tm_mon
< 6 && local_time
->tm_mon
< tim
.tm_mon
628 && tim
.tm_mon
- local_time
->tm_mon
>= 6)
632 if (l10n
|| (*t
< 0))
638 /* --------------------------------------------------------------------------------------------- */
641 vfs_split_text (char *p
)
646 memset (columns
, 0, sizeof (columns
));
648 for (numcols
= 0; *p
!= '\0' && numcols
< MAXCOLS
; numcols
++)
650 for (; *p
== ' ' || *p
== '\r' || *p
== '\n'; p
++)
653 columns
[numcols
] = p
;
654 column_ptr
[numcols
] = p
- original
;
656 for (; *p
!= '\0' && *p
!= ' ' && *p
!= '\r' && *p
!= '\n'; p
++)
663 /* --------------------------------------------------------------------------------------------- */
666 vfs_parse_ls_lga_init (void)
668 vfs_parce_ls_final_num_spaces
= 1;
671 /* --------------------------------------------------------------------------------------------- */
674 vfs_parse_ls_lga_get_final_spaces (void)
676 return vfs_parce_ls_final_num_spaces
;
679 /* --------------------------------------------------------------------------------------------- */
682 vfs_parse_ls_lga (const char *p
, struct stat
*s
, char **filename
, char **linkname
,
685 int idx
, idx2
, num_cols
;
689 const char *line
= p
;
692 if (strncmp (p
, "total", 5) == 0)
695 if (!vfs_parse_filetype (p
, &skipped
, &s
->st_mode
))
699 if (*p
== ' ') /* Notwell 4 */
703 if (strlen (p
) <= 8 || p
[8] != ']')
706 /* Should parse here the Novell permissions :) */
707 if (S_ISDIR (s
->st_mode
))
708 s
->st_mode
|= (S_IRUSR
| S_IRGRP
| S_IROTH
| S_IWUSR
| S_IXUSR
| S_IXGRP
| S_IXOTH
);
710 s
->st_mode
|= (S_IRUSR
| S_IRGRP
| S_IROTH
| S_IWUSR
);
718 if (!vfs_parse_fileperms (p
, &lc_skipped
, &perms
))
725 p_copy
= g_strdup (p
);
726 num_cols
= vfs_split_text (p_copy
);
728 s
->st_nlink
= atol (columns
[0]);
729 if (s
->st_nlink
<= 0)
733 s
->st_uid
= vfs_finduid (columns
[1]);
735 s
->st_uid
= (uid_t
) atol (columns
[1]);
737 /* Mhm, the ls -lg did not produce a group field */
738 for (idx
= 3; idx
<= 5; idx
++)
739 if (vfs_parse_month (columns
[idx
], NULL
) || is_week (columns
[idx
], NULL
)
740 || is_dos_date (columns
[idx
]) || is_localized_month (columns
[idx
]))
743 if (idx
== 6 || (idx
== 5 && !S_ISCHR (s
->st_mode
) && !S_ISBLK (s
->st_mode
)))
746 /* We don't have gid */
747 if (idx
== 3 || (idx
== 4 && (S_ISCHR (s
->st_mode
) || S_ISBLK (s
->st_mode
))))
751 /* We have gid field */
753 s
->st_gid
= (gid_t
) atol (columns
[2]);
755 s
->st_gid
= vfs_findgid (columns
[2]);
760 if (S_ISCHR (s
->st_mode
) || S_ISBLK (s
->st_mode
))
764 /* Corner case: there is no whitespace(s) between maj & min */
765 if (!is_num (idx2
) && idx2
== 2)
767 /* cppcheck-suppress invalidscanf */
768 if (!is_num (++idx2
) || sscanf (columns
[idx2
], " %d,%d", &maj
, &min
) != 2)
773 /* cppcheck-suppress invalidscanf */
774 if (!is_num (idx2
) || sscanf (columns
[idx2
], " %d,", &maj
) != 1)
777 /* cppcheck-suppress invalidscanf */
778 if (!is_num (++idx2
) || sscanf (columns
[idx2
], " %d", &min
) != 1)
781 #ifdef HAVE_STRUCT_STAT_ST_RDEV
782 s
->st_rdev
= makedev (maj
, min
);
789 /* Common file size */
793 s
->st_size
= (off_t
) g_ascii_strtoll (columns
[idx2
], NULL
, 10);
794 #ifdef HAVE_STRUCT_STAT_ST_RDEV
799 idx
= vfs_parse_filedate (idx
, &s
->st_mtime
);
803 /* Use resulting time value */
804 s
->st_atime
= s
->st_ctime
= s
->st_mtime
;
805 #ifdef HAVE_STRUCT_STAT_ST_MTIM
806 s
->st_atim
.tv_nsec
= s
->st_mtim
.tv_nsec
= s
->st_ctim
.tv_nsec
= 0;
809 /* s->st_dev and s->st_ino must be initialized by vfs_s_new_inode () */
810 #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
815 if (num_spaces
!= NULL
)
817 *num_spaces
= column_ptr
[idx
] - column_ptr
[idx
- 1] - strlen (columns
[idx
- 1]);
818 if (DIR_IS_DOTDOT (columns
[idx
]))
819 vfs_parce_ls_final_num_spaces
= *num_spaces
;
822 for (i
= idx
+ 1, idx2
= 0; i
< num_cols
; i
++)
823 if (strcmp (columns
[i
], "->") == 0)
829 if (((S_ISLNK (s
->st_mode
) || (num_cols
== idx
+ 3 && s
->st_nlink
> 1))) /* Maybe a hardlink? (in extfs) */
832 if (filename
!= NULL
)
833 *filename
= g_strndup (p
+ column_ptr
[idx
], column_ptr
[idx2
] - column_ptr
[idx
] - 1);
835 if (linkname
!= NULL
)
837 t
= g_strdup (p
+ column_ptr
[idx2
+ 1]);
843 /* Extract the filename from the string copy, not from the columns
844 * this way we have a chance of entering hidden directories like ". ."
846 if (filename
!= NULL
)
848 /* filename = g_strdup (columns [idx++]); */
849 t
= g_strdup (p
+ column_ptr
[idx
]);
853 if (linkname
!= NULL
)
862 if (--p2
> 0 && (t
[p2
] == '\r' || t
[p2
] == '\n'))
864 if (--p2
> 0 && (t
[p2
] == '\r' || t
[p2
] == '\n'))
873 static int errorcount
= 0;
875 if (++errorcount
< 5)
876 message (D_ERROR
, _("Cannot parse:"), "%s",
877 (p_copy
!= NULL
&& *p_copy
!= '\0') ? p_copy
: line
);
878 else if (errorcount
== 5)
879 message (D_ERROR
, MSG_ERROR
, _("More parsing errors will be ignored."));
886 /* --------------------------------------------------------------------------------------------- */