1 /* Utilities for VFS modules.
3 Copyright (C) 1988, 1992, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
4 2005, 2006, 2007 Free Software Foundation, Inc.
5 Copyright (C) 1995, 1996 Miguel de Icaza
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public License
9 as published by the Free Software Foundation; either version 2 of
10 the License, or (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU Library General Public License for more details.
17 You should have received a copy of the GNU Library General Public
18 License along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
23 * \brief Source: Utilities for VFS modules
24 * \author Miguel de Icaza
31 #include <sys/types.h>
38 #include "lib/global.h"
39 #include "lib/unixcompat.h"
40 #include "lib/util.h" /* mc_mkstemps() */
41 #include "lib/widget.h" /* message() */
46 /*** global variables ****************************************************************************/
48 /*** file scope macro definitions ****************************************************************/
57 #define myuid ( my_uid < 0? (my_uid = getuid()): my_uid )
58 #define mygid ( my_gid < 0? (my_gid = getgid()): my_gid )
60 /* Parsing code is used by ftpfs, fish and extfs */
63 #define MC_HISTORY_VFS_PASSWORD "mc.vfs.password"
65 /*** file scope type declarations ****************************************************************/
67 /*** file scope variables ************************************************************************/
69 static char *columns
[MAXCOLS
]; /* Points to the string in column n */
70 static int column_ptr
[MAXCOLS
]; /* Index from 0 to the starting positions of the columns */
72 /*** file scope functions ************************************************************************/
73 /* --------------------------------------------------------------------------------------------- */
78 char *column
= columns
[idx
];
80 if (!column
|| column
[0] < '0' || column
[0] > '9')
86 /* --------------------------------------------------------------------------------------------- */
87 /* Return 1 for MM-DD-YY and MM-DD-YYYY */
90 is_dos_date (const char *str
)
98 if (len
!= 8 && len
!= 10)
101 if (str
[2] != str
[5])
104 if (!strchr ("\\-/", (int) str
[2]))
110 /* --------------------------------------------------------------------------------------------- */
113 is_week (const char *str
, struct tm
*tim
)
115 static const char *week
= "SunMonTueWedThuFriSat";
121 pos
= strstr (week
, str
);
125 tim
->tm_wday
= (pos
- week
) / 3;
131 /* --------------------------------------------------------------------------------------------- */
134 is_month (const char *str
, struct tm
*tim
)
136 static const char *month
= "JanFebMarAprMayJunJulAugSepOctNovDec";
142 pos
= strstr (month
, str
);
146 tim
->tm_mon
= (pos
- month
) / 3;
152 /* --------------------------------------------------------------------------------------------- */
154 * Check for possible locale's abbreviated month name (Jan..Dec).
155 * Any 3 bytes long string without digit, control and punctuation characters.
156 * isalpha() is locale specific, so it cannot be used if current
157 * locale is "C" and ftp server use Cyrillic.
158 * NB: It is assumed there are no whitespaces in month.
161 is_localized_month (const char *month
)
168 while ((i
< 3) && *month
&& !isdigit ((unsigned char) *month
)
169 && !iscntrl ((unsigned char) *month
) && !ispunct ((unsigned char) *month
))
174 return ((i
== 3) && (*month
== 0));
177 /* --------------------------------------------------------------------------------------------- */
180 is_time (const char *str
, struct tm
*tim
)
187 p
= strchr (str
, ':');
188 p2
= strrchr (str
, ':');
189 if (p
!= NULL
&& p2
!= NULL
)
193 if (sscanf (str
, "%2d:%2d:%2d", &tim
->tm_hour
, &tim
->tm_min
, &tim
->tm_sec
) != 3)
198 if (sscanf (str
, "%2d:%2d", &tim
->tm_hour
, &tim
->tm_min
) != 2)
208 /* --------------------------------------------------------------------------------------------- */
211 is_year (char *str
, struct tm
*tim
)
218 if (strchr (str
, ':'))
221 if (strlen (str
) != 4)
224 if (sscanf (str
, "%ld", &year
) != 1)
227 if (year
< 1900 || year
> 3000)
230 tim
->tm_year
= (int) (year
- 1900);
235 /* --------------------------------------------------------------------------------------------- */
236 /*** public functions ****************************************************************************/
237 /* --------------------------------------------------------------------------------------------- */
238 /** Get current username
240 * @return g_malloc()ed string with the name of the currently logged in
241 * user ("anonymous" if uid is not registered in the system)
245 vfs_get_local_username (void)
249 p_i
= getpwuid (geteuid ());
251 return (p_i
&& p_i
->pw_name
) ? g_strdup (p_i
->pw_name
) : g_strdup ("anonymous"); /* Unknown UID, strange */
254 /* --------------------------------------------------------------------------------------------- */
256 * Look up a user or group name from a uid/gid, maintaining a cache.
257 * FIXME, for now it's a one-entry cache.
258 * FIXME2, the "-993" is to reduce the chance of a hit on the first lookup.
259 * This file should be modified for non-unix systems to do something
263 /* --------------------------------------------------------------------------------------------- */
266 vfs_finduid (const char *uname
)
268 static int saveuid
= -993;
269 static char saveuname
[TUNMLEN
];
270 static int my_uid
= -993;
274 if (uname
[0] != saveuname
[0] /* Quick test w/o proc call */
275 || 0 != strncmp (uname
, saveuname
, TUNMLEN
))
277 g_strlcpy (saveuname
, uname
, TUNMLEN
);
278 pw
= getpwnam (uname
);
281 saveuid
= pw
->pw_uid
;
291 /* --------------------------------------------------------------------------------------------- */
294 vfs_findgid (const char *gname
)
296 static int savegid
= -993;
297 static char savegname
[TGNMLEN
];
298 static int my_gid
= -993;
302 if (gname
[0] != savegname
[0] /* Quick test w/o proc call */
303 || 0 != strncmp (gname
, savegname
, TUNMLEN
))
305 g_strlcpy (savegname
, gname
, TUNMLEN
);
306 gr
= getgrnam (gname
);
309 savegid
= gr
->gr_gid
;
319 /* --------------------------------------------------------------------------------------------- */
321 * Create a temporary file with a name resembling the original.
322 * This is needed e.g. for local copies requested by extfs.
323 * Some extfs scripts may look at the extension.
324 * We also protect stupid scripts agains dangerous names.
328 vfs_mkstemps (char **pname
, const char *prefix
, const char *param_basename
)
335 /* Strip directories */
336 p
= strrchr (param_basename
, PATH_SEP
);
342 /* Protection against very long names */
343 shift
= strlen (p
) - (MC_MAXPATHLEN
- 16);
347 suffix
= g_malloc (MC_MAXPATHLEN
);
349 /* Protection against unusual characters */
351 while (*p
&& (*p
!= '#'))
353 if (strchr (".-_@", *p
) || isalnum ((unsigned char) *p
))
359 fd
= mc_mkstemps (pname
, prefix
, suffix
);
364 /* --------------------------------------------------------------------------------------------- */
365 /** Extract the hostname and username from the path
367 * Format of the path is [user@]hostname:port/remote-dir, e.g.:
369 * ftp://sunsite.unc.edu/pub/linux
370 * ftp://miguel@sphinx.nuclecu.unam.mx/c/nc
371 * ftp://tsx-11.mit.edu:8192/
372 * ftp://joe@foo.edu:11321/private
373 * ftp://joe:password@foo.se
375 * @param path is an input string to be parsed
376 * @param default_port is an input default port
377 * @param flags are parsing modifier flags (@see vfs_url_flags_t)
379 * @return g_malloc()ed url info.
380 * If the user is empty, e.g. ftp://@roxanne/private, and URL_USE_ANONYMOUS
381 * is not set, then the current login name is supplied.
382 * Return value is a g_malloc()ed structure with the pathname relative to the
387 vfs_url_split (const char *path
, int default_port
, vfs_url_flags_t flags
)
389 vfs_path_element_t
*path_element
;
393 char *dir
, *colon
, *inner_colon
, *at
, *rest
;
395 path_element
= g_new0 (vfs_path_element_t
, 1);
396 path_element
->port
= default_port
;
398 pcopy
= g_strdup (path
);
399 pend
= pcopy
+ strlen (pcopy
);
402 if ((flags
& URL_NOSLASH
) == 0)
404 /* locate path component */
405 while (*dir
!= PATH_SEP
&& *dir
!= '\0')
408 path_element
->path
= g_strdup (PATH_SEP_STR
);
411 path_element
->path
= g_strdup (dir
);
416 /* search for any possible user */
417 at
= strrchr (pcopy
, '@');
419 /* We have a username */
425 inner_colon
= strchr (pcopy
, ':');
426 if (inner_colon
!= NULL
)
430 path_element
->password
= g_strdup (inner_colon
);
434 path_element
->user
= g_strdup (pcopy
);
442 if ((flags
& URL_USE_ANONYMOUS
) == 0)
443 path_element
->user
= vfs_get_local_username ();
445 /* Check if the host comes with a port spec, if so, chop it */
447 colon
= strchr (rest
, ':');
450 colon
= strchr (++rest
, ']');
459 vfs_path_element_free (path_element
);
467 if (sscanf (colon
+ 1, "%d", &path_element
->port
) == 1)
469 if (path_element
->port
<= 0 || path_element
->port
>= 65536)
470 path_element
->port
= default_port
;
473 while (*(++colon
) != '\0')
478 path_element
->port
= 1;
481 path_element
->port
= 2;
487 path_element
->host
= g_strdup (rest
);
492 /* --------------------------------------------------------------------------------------------- */
495 vfs_split_text (char *p
)
500 memset (columns
, 0, sizeof (columns
));
502 for (numcols
= 0; *p
&& numcols
< MAXCOLS
; numcols
++)
504 while (*p
== ' ' || *p
== '\r' || *p
== '\n')
509 columns
[numcols
] = p
;
510 column_ptr
[numcols
] = p
- original
;
511 while (*p
&& *p
!= ' ' && *p
!= '\r' && *p
!= '\n')
517 /* --------------------------------------------------------------------------------------------- */
520 vfs_parse_filetype (const char *s
, size_t * ret_skipped
, mode_t
* ret_type
)
547 #ifdef S_IFDOOR /* Solaris door */
559 #ifdef S_IFNAM /* Special named files */
568 case 'm': /* Don't know what these are :-) */
582 /* --------------------------------------------------------------------------------------------- */
585 vfs_parse_fileperms (const char *s
, size_t * ret_skipped
, mode_t
* ret_perms
)
621 perms
|= S_IXUSR
| S_ISUID
;
658 break; /* found on Solaris */
660 perms
|= S_IXGRP
| S_ISGID
;
696 perms
|= S_IXOTH
| S_ISVTX
;
705 { /* ACLs on Solaris, HP-UX and others */
709 *ret_skipped
= p
- s
;
714 /* --------------------------------------------------------------------------------------------- */
717 vfs_parse_filemode (const char *s
, size_t * ret_skipped
, mode_t
* ret_mode
)
725 if (!vfs_parse_filetype (p
, &skipped
, &type
))
729 if (!vfs_parse_fileperms (p
, &skipped
, &perms
))
733 *ret_skipped
= p
- s
;
734 *ret_mode
= type
| perms
;
738 /* --------------------------------------------------------------------------------------------- */
741 vfs_parse_raw_filemode (const char *s
, size_t * ret_skipped
, mode_t
* ret_mode
)
744 mode_t remote_type
= 0, local_type
, perms
= 0;
749 while (*p
>= '0' && *p
<= '7')
759 while (*p
>= '0' && *p
<= '7')
762 remote_type
+= (*p
- '0');
770 $ perl -e 'use Fcntl ":mode";
771 my @modes = (S_IFDIR, S_IFBLK, S_IFCHR, S_IFLNK, S_IFREG);
772 foreach $t (@modes) { printf ("%o\n", $t); };'
773 TODO: S_IFDOOR, S_IFIFO, S_IFSOCK (if supported by os)
774 (see vfs_parse_filetype)
780 local_type
= S_IFCHR
;
783 local_type
= S_IFDIR
;
786 local_type
= S_IFBLK
;
789 local_type
= S_IFLNK
;
792 default: /* don't know what is it */
793 local_type
= S_IFREG
;
797 *ret_skipped
= p
- s
;
798 *ret_mode
= local_type
| perms
;
802 /* --------------------------------------------------------------------------------------------- */
803 /** This function parses from idx in the columns[] array */
806 vfs_parse_filedate (int idx
, time_t * t
)
812 int l10n
= 0; /* Locale's abbreviated month name */
814 struct tm
*local_time
;
816 /* Let's setup default time values */
817 current_time
= time (NULL
);
818 local_time
= localtime (¤t_time
);
819 tim
.tm_mday
= local_time
->tm_mday
;
820 tim
.tm_mon
= local_time
->tm_mon
;
821 tim
.tm_year
= local_time
->tm_year
;
826 tim
.tm_isdst
= -1; /* Let mktime() try to guess correct dst offset */
830 /* We eat weekday name in case of extfs */
831 if (is_week (p
, &tim
))
835 if (is_month (p
, &tim
))
837 /* And we expect, it followed by day number */
839 tim
.tm_mday
= (int) atol (columns
[idx
++]);
841 return 0; /* No day */
847 3 fields max or we'll see oddities with certain file names.
848 So both year and time is not allowed.
851 But in case of extfs we allow these date formats:
853 where Mon is Jan-Dec, DD, MM, YY two digit day, month, year,
854 YYYY four digit year, hh, mm, ss two digit hour, minute or second. */
856 /* Special case with MM-DD-YY or MM-DD-YYYY */
861 if (sscanf (p
, "%2d-%2d-%d", &d
[0], &d
[1], &d
[2]) == 3)
863 /* Months are zero based */
884 return 0; /* sscanf failed */
888 /* Locale's abbreviated month name followed by day number */
889 if (is_localized_month (p
) && (is_num (idx
++)))
892 return 0; /* unsupported format */
896 /* Here we expect to find time or year */
897 if (is_num (idx
) && (is_time (columns
[idx
], &tim
) || (got_year
= is_year (columns
[idx
], &tim
))))
900 return 0; /* Neither time nor date */
903 * If the date is less than 6 months in the past, it is shown without year
904 * other dates in the past or future are shown with year but without time
905 * This does not check for years before 1900 ... I don't know, how
906 * to represent them at all
908 if (!got_year
&& local_time
->tm_mon
< 6
909 && local_time
->tm_mon
< tim
.tm_mon
&& tim
.tm_mon
- local_time
->tm_mon
>= 6)
914 if (l10n
|| (*t
< 0))
919 /* --------------------------------------------------------------------------------------------- */
922 vfs_parse_ls_lga (const char *p
, struct stat
*s
, char **filename
, char **linkname
)
924 int idx
, idx2
, num_cols
;
928 const char *line
= p
;
931 if (strncmp (p
, "total", 5) == 0)
934 if (!vfs_parse_filetype (p
, &skipped
, &s
->st_mode
))
938 if (*p
== ' ') /* Notwell 4 */
942 if (strlen (p
) <= 8 || p
[8] != ']')
944 /* Should parse here the Notwell permissions :) */
945 if (S_ISDIR (s
->st_mode
))
946 s
->st_mode
|= (S_IRUSR
| S_IRGRP
| S_IROTH
| S_IWUSR
| S_IXUSR
| S_IXGRP
| S_IXOTH
);
948 s
->st_mode
|= (S_IRUSR
| S_IRGRP
| S_IROTH
| S_IWUSR
);
956 if (!vfs_parse_fileperms (p
, &lc_skipped
, &perms
))
962 p_copy
= g_strdup (p
);
963 num_cols
= vfs_split_text (p_copy
);
965 s
->st_nlink
= atol (columns
[0]);
966 if (s
->st_nlink
<= 0)
970 s
->st_uid
= vfs_finduid (columns
[1]);
972 s
->st_uid
= (uid_t
) atol (columns
[1]);
974 /* Mhm, the ls -lg did not produce a group field */
975 for (idx
= 3; idx
<= 5; idx
++)
976 if (is_month (columns
[idx
], NULL
) || is_week (columns
[idx
], NULL
)
977 || is_dos_date (columns
[idx
]) || is_localized_month (columns
[idx
]))
980 if (idx
== 6 || (idx
== 5 && !S_ISCHR (s
->st_mode
) && !S_ISBLK (s
->st_mode
)))
983 /* We don't have gid */
984 if (idx
== 3 || (idx
== 4 && (S_ISCHR (s
->st_mode
) || S_ISBLK (s
->st_mode
))))
988 /* We have gid field */
990 s
->st_gid
= (gid_t
) atol (columns
[2]);
992 s
->st_gid
= vfs_findgid (columns
[2]);
997 if (S_ISCHR (s
->st_mode
) || S_ISBLK (s
->st_mode
))
1001 /* Corner case: there is no whitespace(s) between maj & min */
1002 if (!is_num (idx2
) && idx2
== 2)
1004 if (!is_num (++idx2
) || sscanf (columns
[idx2
], " %d,%d", &maj
, &min
) != 2)
1009 if (!is_num (idx2
) || sscanf (columns
[idx2
], " %d,", &maj
) != 1)
1012 if (!is_num (++idx2
) || sscanf (columns
[idx2
], " %d", &min
) != 1)
1015 #ifdef HAVE_STRUCT_STAT_ST_RDEV
1016 s
->st_rdev
= makedev (maj
, min
);
1023 /* Common file size */
1028 s
->st_size
= (off_t
) atoll (columns
[idx2
]);
1030 s
->st_size
= (off_t
) atof (columns
[idx2
]);
1032 #ifdef HAVE_STRUCT_STAT_ST_RDEV
1037 idx
= vfs_parse_filedate (idx
, &s
->st_mtime
);
1040 /* Use resulting time value */
1041 s
->st_atime
= s
->st_ctime
= s
->st_mtime
;
1042 /* s->st_dev and s->st_ino must be initialized by vfs_s_new_inode () */
1043 #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
1044 s
->st_blksize
= 512;
1046 #ifdef HAVE_STRUCT_STAT_ST_BLOCKS
1047 s
->st_blocks
= (s
->st_size
+ 511) / 512;
1050 for (i
= idx
+ 1, idx2
= 0; i
< num_cols
; i
++)
1051 if (strcmp (columns
[i
], "->") == 0)
1057 if (((S_ISLNK (s
->st_mode
) || (num_cols
== idx
+ 3 && s
->st_nlink
> 1))) /* Maybe a hardlink? (in extfs) */
1063 *filename
= g_strndup (p
+ column_ptr
[idx
], column_ptr
[idx2
] - column_ptr
[idx
] - 1);
1067 t
= g_strdup (p
+ column_ptr
[idx2
+ 1]);
1073 /* Extract the filename from the string copy, not from the columns
1074 * this way we have a chance of entering hidden directories like ". ."
1079 * filename = g_strdup (columns [idx++]);
1082 t
= g_strdup (p
+ column_ptr
[idx
]);
1091 int p2
= strlen (t
);
1092 if ((--p2
> 0) && (t
[p2
] == '\r' || t
[p2
] == '\n'))
1094 if ((--p2
> 0) && (t
[p2
] == '\r' || t
[p2
] == '\n'))
1103 static int errorcount
= 0;
1105 if (++errorcount
< 5)
1107 message (D_ERROR
, _("Cannot parse:"), "%s", (p_copy
&& *p_copy
) ? p_copy
: line
);
1109 else if (errorcount
== 5)
1110 message (D_ERROR
, MSG_ERROR
, _("More parsing errors will be ignored."));
1117 /* --------------------------------------------------------------------------------------------- */
1120 vfs_die (const char *m
)
1122 message (D_ERROR
, _("Internal error:"), "%s", m
);
1123 exit (EXIT_FAILURE
);
1126 /* --------------------------------------------------------------------------------------------- */
1129 vfs_get_password (const char *msg
)
1131 return input_dialog (msg
, _("Password:"), MC_HISTORY_VFS_PASSWORD
, INPUT_PASSWORD
);
1134 /* --------------------------------------------------------------------------------------------- */