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. */
25 #include <mhl/types.h>
26 #include <mhl/memory.h>
27 #include <mhl/string.h>
29 #include "../src/global.h"
30 #include "../src/tty.h" /* enable/disable interrupt key */
31 #include "../src/wtools.h" /* message() */
32 #include "../src/main.h" /* print_vfs_message */
35 #include "../src/unixcompat.h"
36 #include "../src/history.h"
38 /* Extract the hostname and username from the path */
39 /* path is in the form: [user@]hostname:port/remote-dir, e.g.:
41 * ftp://sunsite.unc.edu/pub/linux
42 * ftp://miguel@sphinx.nuclecu.unam.mx/c/nc
43 * ftp://tsx-11.mit.edu:8192/
44 * ftp://joe@foo.edu:11321/private
45 * ftp://joe:password@foo.se
47 * Returns g_malloc()ed host, user and pass they are present.
48 * If the user is empty, e.g. ftp://@roxanne/private, and URL_ALLOW_ANON
49 * is not set, then the current login name is supplied.
51 * Return value is a g_malloc()ed string with the pathname relative to the
57 vfs_split_url (const char *path
, char **host
, char **user
, int *port
,
58 char **pass
, int default_port
, int flags
)
60 struct passwd
*passwd_info
;
61 char *dir
, *colon
, *inner_colon
, *at
, *rest
;
63 char * const pcopy
= mhl_str_dup (path
);
64 const char *pend
= pcopy
+ strlen (pcopy
);
73 if (!(flags
& URL_NOSLASH
)) {
74 /* locate path component */
75 while (*dir
!= PATH_SEP
&& *dir
)
78 retval
= mhl_str_dup (dir
);
81 retval
= mhl_str_dup (PATH_SEP_STR
);
84 /* search for any possible user */
85 at
= strrchr (pcopy
, '@');
87 /* We have a username */
90 inner_colon
= strchr (pcopy
, ':');
95 *pass
= mhl_str_dup (inner_colon
);
98 *user
= mhl_str_dup (pcopy
);
107 if (!*user
&& !(flags
& URL_ALLOW_ANON
)) {
108 passwd_info
= getpwuid (geteuid ());
109 if (passwd_info
&& passwd_info
->pw_name
)
110 *user
= mhl_str_dup (passwd_info
->pw_name
);
112 /* This is very unlikely to happen */
113 *user
= mhl_str_dup ("anonymous");
118 /* Check if the host comes with a port spec, if so, chop it */
119 colon
= strchr (rest
, ':');
122 if (sscanf (colon
+ 1, "%d", port
) == 1) {
123 if (*port
<= 0 || *port
>= 65536)
124 *port
= default_port
;
139 *host
= mhl_str_dup (rest
);
144 #endif /* USE_NETCODE */
147 * Look up a user or group name from a uid/gid, maintaining a cache.
148 * FIXME, for now it's a one-entry cache.
149 * FIXME2, the "-993" is to reduce the chance of a hit on the first lookup.
150 * This file should be modified for non-unix systems to do something
161 #define myuid ( my_uid < 0? (my_uid = getuid()): my_uid )
162 #define mygid ( my_gid < 0? (my_gid = getgid()): my_gid )
165 vfs_finduid (const char *uname
)
167 static int saveuid
= -993;
168 static char saveuname
[TUNMLEN
];
169 static int my_uid
= -993;
173 if (uname
[0] != saveuname
[0] /* Quick test w/o proc call */
174 ||0 != strncmp (uname
, saveuname
, TUNMLEN
)) {
175 g_strlcpy (saveuname
, uname
, TUNMLEN
);
176 pw
= getpwnam (uname
);
178 saveuid
= pw
->pw_uid
;
187 vfs_findgid (const char *gname
)
189 static int savegid
= -993;
190 static char savegname
[TGNMLEN
];
191 static int my_gid
= -993;
195 if (gname
[0] != savegname
[0] /* Quick test w/o proc call */
196 ||0 != strncmp (gname
, savegname
, TUNMLEN
)) {
197 g_strlcpy (savegname
, gname
, TUNMLEN
);
198 gr
= getgrnam (gname
);
200 savegid
= gr
->gr_gid
;
209 * Create a temporary file with a name resembling the original.
210 * This is needed e.g. for local copies requested by extfs.
211 * Some extfs scripts may look at the extension.
212 * We also protect stupid scripts agains dangerous names.
215 vfs_mkstemps (char **pname
, const char *prefix
, const char *basename
)
222 /* Strip directories */
223 p
= strrchr (basename
, PATH_SEP
);
229 /* Protection against very long names */
230 shift
= strlen (p
) - (MC_MAXPATHLEN
- 16);
234 suffix
= g_malloc (MC_MAXPATHLEN
);
236 /* Protection against unusual characters */
238 while (*p
&& (*p
!= '#')) {
239 if (strchr (".-_@", *p
) || isalnum ((unsigned char) *p
))
245 fd
= mc_mkstemps (pname
, prefix
, suffix
);
250 /* Parsing code is used by ftpfs, fish and extfs */
253 static char *columns
[MAXCOLS
]; /* Points to the string in column n */
254 static int column_ptr
[MAXCOLS
]; /* Index from 0 to the starting positions of the columns */
257 vfs_split_text (char *p
)
262 memset (columns
, 0, sizeof (columns
));
264 for (numcols
= 0; *p
&& numcols
< MAXCOLS
; numcols
++) {
265 while (*p
== ' ' || *p
== '\r' || *p
== '\n') {
269 columns
[numcols
] = p
;
270 column_ptr
[numcols
] = p
- original
;
271 while (*p
&& *p
!= ' ' && *p
!= '\r' && *p
!= '\n')
280 char *column
= columns
[idx
];
282 if (!column
|| column
[0] < '0' || column
[0] > '9')
288 /* Return 1 for MM-DD-YY and MM-DD-YYYY */
290 is_dos_date (const char *str
)
298 if (len
!= 8 && len
!= 10)
301 if (str
[2] != str
[5])
304 if (!strchr ("\\-/", (int) str
[2]))
311 is_week (const char *str
, struct tm
*tim
)
313 static const char *week
= "SunMonTueWedThuFriSat";
319 if ((pos
= strstr (week
, str
)) != NULL
) {
321 tim
->tm_wday
= (pos
- week
) / 3;
328 is_month (const char *str
, struct tm
*tim
)
330 static const char *month
= "JanFebMarAprMayJunJulAugSepOctNovDec";
336 if ((pos
= strstr (month
, str
)) != NULL
) {
338 tim
->tm_mon
= (pos
- month
) / 3;
345 * Check for possible locale's abbreviated month name (Jan..Dec).
346 * Any 3 bytes long string without digit, control and punctuation characters.
347 * isalpha() is locale specific, so it cannot be used if current
348 * locale is "C" and ftp server use Cyrillic.
349 * NB: It is assumed there are no whitespaces in month.
352 is_localized_month (const char *month
)
359 while ((i
< 3) && *month
&& !isdigit ((unsigned char) *month
)
360 && !iscntrl ((unsigned char) *month
)
361 && !ispunct ((unsigned char) *month
)) {
365 return ((i
== 3) && (*month
== 0));
369 is_time (const char *str
, struct tm
*tim
)
376 if ((p
= strchr (str
, ':')) && (p2
= strrchr (str
, ':'))) {
379 (str
, "%2d:%2d:%2d", &tim
->tm_hour
, &tim
->tm_min
,
383 if (sscanf (str
, "%2d:%2d", &tim
->tm_hour
, &tim
->tm_min
) != 2)
393 is_year (char *str
, struct tm
*tim
)
400 if (strchr (str
, ':'))
403 if (strlen (str
) != 4)
406 if (sscanf (str
, "%ld", &year
) != 1)
409 if (year
< 1900 || year
> 3000)
412 tim
->tm_year
= (int) (year
- 1900);
418 vfs_parse_filetype (const char *s
, size_t *ret_skipped
, mode_t
*ret_type
)
423 case 'd': type
= S_IFDIR
; break;
424 case 'b': type
= S_IFBLK
; break;
425 case 'c': type
= S_IFCHR
; break;
426 case 'l': type
= S_IFLNK
; break;
428 case 's': type
= S_IFSOCK
; break;
430 case 's': type
= S_IFIFO
; break;
432 #ifdef S_IFDOOR /* Solaris door */
433 case 'D': type
= S_IFDOOR
; break;
435 case 'D': type
= S_IFIFO
; break;
437 case 'p': type
= S_IFIFO
; break;
438 #ifdef S_IFNAM /* Special named files */
439 case 'n': type
= S_IFNAM
; break;
441 case 'n': type
= S_IFREG
; break;
443 case 'm': /* Don't know what these are :-) */
445 case '?': type
= S_IFREG
; break;
446 default: return FALSE
;
455 vfs_parse_fileperms (const char *s
, size_t *ret_skipped
, mode_t
*ret_perms
)
465 case 'r': perms
|= S_IRUSR
; break;
466 default: return FALSE
;
470 case 'w': perms
|= S_IWUSR
; break;
471 default: return FALSE
;
475 case 'S': perms
|= S_ISUID
; break;
476 case 's': perms
|= S_IXUSR
| S_ISUID
; break;
477 case 'x': perms
|= S_IXUSR
; break;
478 default: return FALSE
;
482 case 'r': perms
|= S_IRGRP
; break;
483 default: return FALSE
;
487 case 'w': perms
|= S_IWGRP
; break;
488 default: return FALSE
;
492 case 'S': perms
|= S_ISGID
; break;
493 case 'l': perms
|= S_ISGID
; break; /* found on Solaris */
494 case 's': perms
|= S_IXGRP
| S_ISGID
; break;
495 case 'x': perms
|= S_IXGRP
; break;
496 default: return FALSE
;
500 case 'r': perms
|= S_IROTH
; break;
501 default: return FALSE
;
505 case 'w': perms
|= S_IWOTH
; break;
506 default: return FALSE
;
510 case 'T': perms
|= S_ISVTX
; break;
511 case 't': perms
|= S_IXOTH
| S_ISVTX
; break;
512 case 'x': perms
|= S_IXOTH
; break;
513 default: return FALSE
;
515 if (*p
== '+') { /* ACLs on Solaris, HP-UX and others */
519 *ret_skipped
= p
- s
;
525 vfs_parse_filemode (const char *s
, size_t *ret_skipped
,
534 if (!vfs_parse_filetype (p
, &skipped
, &type
))
538 if (!vfs_parse_fileperms (p
, &skipped
, &perms
))
542 *ret_skipped
= p
- s
;
543 *ret_mode
= type
| perms
;
548 vfs_parse_raw_filemode (const char *s
, size_t *ret_skipped
,
552 mode_t remote_type
= 0, local_type
, perms
= 0;
557 while(*p
>= '0' && *p
<= '7')
567 while(*p
>= '0' && *p
<= '7')
570 remote_type
+= (*p
- '0');
578 $ perl -e 'use Fcntl ":mode";
579 my @modes = (S_IFDIR, S_IFBLK, S_IFCHR, S_IFLNK, S_IFREG);
580 foreach $t (@modes) { printf ("%o\n", $t); };'
581 TODO: S_IFDOOR, S_IFIFO, S_IFSOCK (if supported by os)
582 (see vfs_parse_filetype)
588 local_type
= S_IFCHR
; break;
590 local_type
= S_IFDIR
; break;
592 local_type
= S_IFBLK
; break;
594 local_type
= S_IFLNK
; break;
596 default:// don't know what is it
597 local_type
= S_IFREG
; break;
600 *ret_skipped
= p
- s
;
601 *ret_mode
= local_type
| perms
;
605 /* This function parses from idx in the columns[] array */
607 vfs_parse_filedate (int idx
, time_t *t
)
613 int l10n
= 0; /* Locale's abbreviated month name */
615 struct tm
*local_time
;
617 /* Let's setup default time values */
618 current_time
= time (NULL
);
619 local_time
= localtime (¤t_time
);
620 tim
.tm_mday
= local_time
->tm_mday
;
621 tim
.tm_mon
= local_time
->tm_mon
;
622 tim
.tm_year
= local_time
->tm_year
;
627 tim
.tm_isdst
= -1; /* Let mktime() try to guess correct dst offset */
631 /* We eat weekday name in case of extfs */
632 if (is_week (p
, &tim
))
636 if (is_month (p
, &tim
)) {
637 /* And we expect, it followed by day number */
639 tim
.tm_mday
= (int) atol (columns
[idx
++]);
641 return 0; /* No day */
645 3 fields max or we'll see oddities with certain file names.
646 So both year and time is not allowed.
649 But in case of extfs we allow these date formats:
651 where Mon is Jan-Dec, DD, MM, YY two digit day, month, year,
652 YYYY four digit year, hh, mm, ss two digit hour, minute or second. */
654 /* Special case with MM-DD-YY or MM-DD-YYYY */
655 if (is_dos_date (p
)) {
658 if (sscanf (p
, "%2d-%2d-%d", &d
[0], &d
[1], &d
[2]) == 3) {
659 /* Months are zero based */
676 return 0; /* sscanf failed */
678 /* Locale's abbreviated month name followed by day number */
679 if (is_localized_month (p
) && (is_num (idx
++)))
682 return 0; /* unsupported format */
686 /* Here we expect to find time or year */
687 if (is_num (idx
) && (is_time (columns
[idx
], &tim
)
688 || (got_year
= is_year (columns
[idx
], &tim
))))
691 return 0; /* Neither time nor date */
694 * If the date is less than 6 months in the past, it is shown without year
695 * other dates in the past or future are shown with year but without time
696 * This does not check for years before 1900 ... I don't know, how
697 * to represent them at all
699 if (!got_year
&& local_time
->tm_mon
< 6
700 && local_time
->tm_mon
< tim
.tm_mon
701 && tim
.tm_mon
- local_time
->tm_mon
>= 6)
705 if (l10n
|| (*t
= mktime (&tim
)) < 0)
711 vfs_parse_ls_lga (const char *p
, struct stat
*s
, char **filename
,
714 int idx
, idx2
, num_cols
;
718 const char *line
= p
;
721 if (strncmp (p
, "total", 5) == 0)
724 if (!vfs_parse_filetype (p
, &skipped
, &s
->st_mode
))
728 if (*p
== ' ') /* Notwell 4 */
731 if (strlen (p
) <= 8 || p
[8] != ']')
733 /* Should parse here the Notwell permissions :) */
734 if (S_ISDIR (s
->st_mode
))
736 (S_IRUSR
| S_IRGRP
| S_IROTH
| S_IWUSR
| S_IXUSR
| S_IXGRP
739 s
->st_mode
|= (S_IRUSR
| S_IRGRP
| S_IROTH
| S_IWUSR
);
745 if (!vfs_parse_fileperms (p
, &skipped
, &perms
))
751 p_copy
= mhl_str_dup (p
);
752 num_cols
= vfs_split_text (p_copy
);
754 s
->st_nlink
= atol (columns
[0]);
755 if (s
->st_nlink
<= 0)
759 s
->st_uid
= vfs_finduid (columns
[1]);
761 s
->st_uid
= (uid_t
) atol (columns
[1]);
763 /* Mhm, the ls -lg did not produce a group field */
764 for (idx
= 3; idx
<= 5; idx
++)
765 if (is_month (columns
[idx
], NULL
) || is_week (columns
[idx
], NULL
)
766 || is_dos_date (columns
[idx
])
767 || is_localized_month (columns
[idx
]))
771 || (idx
== 5 && !S_ISCHR (s
->st_mode
) && !S_ISBLK (s
->st_mode
)))
774 /* We don't have gid */
776 || (idx
== 4 && (S_ISCHR (s
->st_mode
) || S_ISBLK (s
->st_mode
))))
779 /* We have gid field */
781 s
->st_gid
= (gid_t
) atol (columns
[2]);
783 s
->st_gid
= vfs_findgid (columns
[2]);
788 if (S_ISCHR (s
->st_mode
) || S_ISBLK (s
->st_mode
)) {
791 /* Corner case: there is no whitespace(s) between maj & min */
792 if (!is_num (idx2
) && idx2
== 2) {
793 if (!is_num (++idx2
) || sscanf (columns
[idx2
], " %d,%d", &min
, &min
) != 2)
796 if (!is_num (idx2
) || sscanf (columns
[idx2
], " %d,", &maj
) != 1)
799 if (!is_num (++idx2
) || sscanf (columns
[idx2
], " %d", &min
) != 1)
802 #ifdef HAVE_STRUCT_STAT_ST_RDEV
803 s
->st_rdev
= makedev (maj
, min
);
808 /* Common file size */
813 s
->st_size
= (off_t
) atoll (columns
[idx2
]);
815 s
->st_size
= (off_t
) atof (columns
[idx2
]);
817 #ifdef HAVE_STRUCT_STAT_ST_RDEV
822 idx
= vfs_parse_filedate (idx
, &s
->st_mtime
);
825 /* Use resulting time value */
826 s
->st_atime
= s
->st_ctime
= s
->st_mtime
;
827 /* s->st_dev and s->st_ino must be initialized by vfs_s_new_inode () */
828 #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
831 #ifdef HAVE_STRUCT_STAT_ST_BLOCKS
832 s
->st_blocks
= (s
->st_size
+ 511) / 512;
835 for (i
= idx
+ 1, idx2
= 0; i
< num_cols
; i
++)
836 if (strcmp (columns
[i
], "->") == 0) {
841 if (((S_ISLNK (s
->st_mode
) || (num_cols
== idx
+ 3 && s
->st_nlink
> 1))) /* Maybe a hardlink? (in extfs) */
846 g_strndup (p
+ column_ptr
[idx
],
847 column_ptr
[idx2
] - column_ptr
[idx
] - 1);
850 t
= mhl_str_dup (p
+ column_ptr
[idx2
+ 1]);
854 /* Extract the filename from the string copy, not from the columns
855 * this way we have a chance of entering hidden directories like ". ."
859 * filename = mhl_str_dup (columns [idx++]);
862 t
= mhl_str_dup (p
+ column_ptr
[idx
]);
871 if ((--p
> 0) && (t
[p
] == '\r' || t
[p
] == '\n'))
873 if ((--p
> 0) && (t
[p
] == '\r' || t
[p
] == '\n'))
882 static int errorcount
= 0;
884 if (++errorcount
< 5) {
885 message (D_ERROR
, _("Cannot parse:"), "%s",
886 (p_copy
&& *p_copy
) ? p_copy
: line
);
887 } else if (errorcount
== 5)
888 message (D_ERROR
, MSG_ERROR
,
889 _("More parsing errors will be ignored."));
897 vfs_die (const char *m
)
899 message (D_ERROR
, _("Internal error:"), "%s", m
);
904 vfs_get_password (const char *msg
)
906 return input_dialog (msg
, _("Password:"), MC_HISTORY_VFS_PASSWORD
, INPUT_PASSWORD
);