[svn] Removed ftpparse dependencies. New parser for VMS listings. MacOS

NetPresenz FTP sevber is now supported.
This commit is contained in:
janp 2001-02-12 23:44:59 -08:00
parent ccfe785e9e
commit 004b339d2e
4 changed files with 323 additions and 189 deletions

View File

@ -60,33 +60,33 @@ ftp_response (struct rbuf *rbuf, char **line)
do
{
for (i = 0; 1; i++)
{
int res;
if (i > bufsize - 1)
*line = (char *)xrealloc (*line, (bufsize <<= 1));
res = RBUF_READCHAR (rbuf, *line + i);
/* RES is number of bytes read. */
if (res == 1)
{
if ((*line)[i] == '\n')
{
(*line)[i] = '\0';
/* Get rid of \r. */
if (i > 0 && (*line)[i - 1] == '\r')
(*line)[i - 1] = '\0';
break;
}
}
else
return FTPRERR;
}
{
int res;
if (i > bufsize - 1)
*line = (char *)xrealloc (*line, (bufsize <<= 1));
res = RBUF_READCHAR (rbuf, *line + i);
/* RES is number of bytes read. */
if (res == 1)
{
if ((*line)[i] == '\n')
{
(*line)[i] = '\0';
/* Get rid of \r. */
if (i > 0 && (*line)[i - 1] == '\r')
(*line)[i - 1] = '\0';
break;
}
}
else
return FTPRERR;
}
if (opt.server_response)
logprintf (LOG_ALWAYS, "%s\n", *line);
logprintf (LOG_ALWAYS, "%s\n", *line);
else
DEBUGP (("%s\n", *line));
DEBUGP (("%s\n", *line));
}
while (!(i >= 3 && ISDIGIT (**line) && ISDIGIT ((*line)[1]) &&
ISDIGIT ((*line)[2]) && (*line)[3] == ' '));
ISDIGIT ((*line)[2]) && (*line)[3] == ' '));
strncpy (ftp_last_respline, *line, sizeof (ftp_last_respline));
ftp_last_respline[sizeof (ftp_last_respline) - 1] = '\0';
return FTPOK;
@ -99,16 +99,16 @@ static char *
ftp_request (const char *command, const char *value)
{
char *res = (char *)xmalloc (strlen (command)
+ (value ? (1 + strlen (value)) : 0)
+ 2 + 1);
+ (value ? (1 + strlen (value)) : 0)
+ 2 + 1);
sprintf (res, "%s%s%s\r\n", command, value ? " " : "", value ? value : "");
if (opt.server_response)
{
/* Hack: don't print out password. */
if (strncmp (res, "PASS", 4) != 0)
logprintf (LOG_ALWAYS, "--> %s\n", res);
logprintf (LOG_ALWAYS, "--> %s\n", res);
else
logputs (LOG_ALWAYS, "--> PASS Turtle Power!\n");
logputs (LOG_ALWAYS, "--> PASS Turtle Power!\n");
}
else
DEBUGP (("\n--> %s\n", res));
@ -179,31 +179,31 @@ ftp_login (struct rbuf *rbuf, const char *acc, const char *pass)
for (i = 0; i < ARRAY_SIZE (skey_head); i++)
{
if (strncasecmp (skey_head[i], respline, strlen (skey_head[i])) == 0)
break;
if (strncasecmp (skey_head[i], respline, strlen (skey_head[i])) == 0)
break;
}
if (i < ARRAY_SIZE (skey_head))
{
const char *cp;
int skey_sequence = 0;
const char *cp;
int skey_sequence = 0;
for (cp = respline + strlen (skey_head[i]);
'0' <= *cp && *cp <= '9';
cp++)
{
skey_sequence = skey_sequence * 10 + *cp - '0';
}
if (*cp == ' ')
cp++;
else
{
bad:
xfree (respline);
return FTPLOGREFUSED;
}
if ((cp = calculate_skey_response (skey_sequence, cp, pass)) == 0)
goto bad;
pass = cp;
for (cp = respline + strlen (skey_head[i]);
'0' <= *cp && *cp <= '9';
cp++)
{
skey_sequence = skey_sequence * 10 + *cp - '0';
}
if (*cp == ' ')
cp++;
else
{
bad:
xfree (respline);
return FTPLOGREFUSED;
}
if ((cp = calculate_skey_response (skey_sequence, cp, pass)) == 0)
goto bad;
pass = cp;
}
}
#endif /* USE_OPIE */
@ -258,8 +258,8 @@ ftp_port (struct rbuf *rbuf)
/* Construct the argument of PORT (of the form a,b,c,d,e,f). */
bytes = (char *)alloca (6 * 4 + 1);
sprintf (bytes, "%d,%d,%d,%d,%d,%d", in_addr[0], in_addr[1],
in_addr[2], in_addr[3], (unsigned) (port & 0xff00) >> 8,
port & 0xff);
in_addr[2], in_addr[3], (unsigned) (port & 0xff00) >> 8,
port & 0xff);
/* Send PORT request. */
request = ftp_request ("PORT", bytes);
nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
@ -326,15 +326,15 @@ ftp_pasv (struct rbuf *rbuf, unsigned char *addr)
{
addr[i] = 0;
for (; ISDIGIT (*s); s++)
addr[i] = (*s - '0') + 10 * addr[i];
addr[i] = (*s - '0') + 10 * addr[i];
if (*s == ',')
s++;
s++;
else if (i < 5)
{
/* When on the last number, anything can be a terminator. */
xfree (respline);
return FTPINVPASV;
}
{
/* When on the last number, anything can be a terminator. */
xfree (respline);
return FTPINVPASV;
}
}
xfree (respline);
return FTPOK;
@ -578,7 +578,10 @@ ftp_syst (struct rbuf *rbuf, enum stype *server_type)
if (!strcasecmp (request, "WINDOWS_NT"))
*server_type = ST_WINNT;
else
*server_type = ST_OTHER;
if (!strcasecmp (request, "MACOS"))
*server_type = ST_MACOS;
else
*server_type = ST_OTHER;
xfree (respline);
/* All OK. */

View File

@ -1,5 +1,6 @@
/* Parsing FTP `ls' output.
Copyright (C) 1995, 1996, 1997, 2000 Free Software Foundation, Inc.
Copyright (C) 1995, 1996, 1997, 2000, 2001 Free Software Foundation,
Inc.
This file is part of Wget.
@ -38,15 +39,6 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
#include "ftp.h"
#include "url.h"
/* Undef this if FTPPARSE is not available. In that case, Wget will
still work with Unix FTP servers, which covers most cases. */
#define HAVE_FTPPARSE
#ifdef HAVE_FTPPARSE
#include "ftpparse.h"
#endif
/* Converts symbolic permissions to number-style ones, e.g. string
rwxr-xr-x to 755. For now, it knows nothing of
setuid/setgid/sticky. ACLs are ignored. */
@ -67,6 +59,23 @@ symperms (const char *s)
}
/* Cleans a line of text so that it can be consistently parsed. Destroys
<CR> and <LF> in case that thay occur at the end of the line and
replaces all <TAB> character with <SPACE>. Returns the length of the
modified line. */
static int
clean_line(char *line)
{
int len = strlen (line);
if (!len) return 0;
if (line[len - 1] == '\n')
line[--len] = '\0';
if (line[len - 1] == '\r')
line[--len] = '\0';
for ( ; *line ; line++ ) if (*line == '\t') *line = ' ';
return len;
}
/* Convert the Un*x-ish style directory listing stored in FILE to a
linked list of fileinfo (system-independent) entries. The contents
of FILE are considered to be produced by the standard Unix `ls -la'
@ -103,14 +112,7 @@ ftp_parse_unix_ls (const char *file, int ignore_perms)
/* Line loop to end of file: */
while ((line = read_whole_line (fp)))
{
DEBUGP (("%s\n", line));
len = strlen (line);
/* Destroy <CR><LF> if present. */
if (len && line[len - 1] == '\n')
line[--len] = '\0';
if (len && line[len - 1] == '\r')
line[--len] = '\0';
len = clean_line (line);
/* Skip if total... */
if (!strncasecmp (line, "total", 5))
{
@ -426,13 +428,7 @@ ftp_parse_winnt_ls (const char *file)
/* Line loop to end of file: */
while ((line = read_whole_line (fp)))
{
DEBUGP (("%s\n", line));
len = strlen (line);
/* Destroy <CR><LF> if present. */
if (len && line[len - 1] == '\n')
line[--len] = '\0';
if (len && line[len - 1] == '\r')
line[--len] = '\0';
len = clean_line (line);
/* Extracting name is a bit of black magic and we have to do it
before `strtok' inserted extra \0 characters in the line
@ -529,20 +525,45 @@ ftp_parse_winnt_ls (const char *file)
return dir;
}
/* Converts VMS symbolic permissions to number-style ones, e.g. string
RWED,RWE,RE to 755. "D" (delete) is taken to be equal to "W"
(write). Inspired by a patch of Stoyan Lekov <lekov@eda.bg>. */
static int
vmsperms (const char *s)
{
int perms = 0;
#ifdef HAVE_FTPPARSE
do
{
switch (*s) {
case ',': perms <<= 3; break;
case 'R': perms |= 4; break;
case 'W': perms |= 2; break;
case 'D': perms |= 2; break;
case 'E': perms |= 1; break;
default: DEBUGP(("wrong VMS permissons!\n"));
}
}
while (*++s);
return perms;
}
/* This is a "glue function" that connects the ftpparse interface to
the interface Wget expects. ftpparse is used to parse listings
from servers other than Unix, like those running VMS or NT. */
static struct fileinfo *
ftp_parse_nonunix_ls (const char *file)
ftp_parse_vms_ls (const char *file)
{
FILE *fp;
int len;
/* #### A third copy of more-or-less the same array ? */
static const char *months[] = {
"JAN", "FEB", "MAR", "APR", "MAY", "JUN",
"JUL", "AUG", "SEP", "OCT", "NOV", "DEC"
};
int i;
int year, month, day; /* for time analysis */
int hour, min, sec;
struct tm timestruct;
char *line; /* tokenizer */
char *line, *tok, *p; /* tokenizer */
struct fileinfo *dir, *l, cur; /* list creation */
fp = fopen (file, "rb");
@ -553,56 +574,161 @@ ftp_parse_nonunix_ls (const char *file)
}
dir = l = NULL;
/* Empty line */
read_whole_line (fp);
/* "Directory PUB$DEVICE[PUB]" */
read_whole_line (fp);
/* Empty line */
read_whole_line (fp);
/* Line loop to end of file: */
while ((line = read_whole_line (fp)))
{
struct ftpparse fp;
i = clean_line (line);
if (!i) break;
DEBUGP (("%s\n", line));
len = strlen (line);
/* Destroy <CR><LF> if present. */
if (len && line[len - 1] == '\n')
line[--len] = '\0';
if (len && line[len - 1] == '\r')
line[--len] = '\0';
/* First column: Name. A bit of black magic again. The name my be
either ABCD.EXT or ABCD.EXT;NUM and it might be on a separate
line. Therefore we will first try to get the complete name
until the first space character; if it fails, we assume that the name
occupies the whole line. After that we search for the version
separator ";", we remove it and check the extension of the file;
extension .DIR denotes directory. */
if (ftpparse(&fp, line, len))
tok = strtok(line, " ");
if (tok == NULL) tok = line;
DEBUGP(("file name: '%s'\n", tok));
for (p = tok ; *p && *p != ';' ; p++);
if (*p == ';') *p = '\0';
p = tok + strlen(tok) - 4;
if (!strcmp(p, ".DIR")) *p = '\0';
cur.name = xstrdup(tok);
DEBUGP(("Name: '%s'\n", cur.name));
/* If the name ends on .DIR or .DIR;#, it's a directory. We also set
the file size to zero as the listing does tell us only the size in
filesystem blocks - for an integrity check (when mirroring, for
example) we would need the size in bytes. */
if (! *p)
{
cur.size = fp.size;
cur.name = (char *)xmalloc (fp.namelen + 1);
memcpy (cur.name, fp.name, fp.namelen);
cur.name[fp.namelen] = '\0';
DEBUGP (("%s\n", cur.name));
/* No links on non-UNIX systems */
cur.linkto = NULL;
/* ftpparse won't tell us correct permisions. So lets just invent
something. */
if (fp.flagtrycwd)
{
cur.type = FT_DIRECTORY;
cur.perms = 0755;
}
else
{
cur.type = FT_PLAINFILE;
cur.perms = 0644;
}
if (!dir)
{
l = dir = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
memcpy (l, &cur, sizeof (cur));
l->prev = l->next = NULL;
}
else
{
cur.prev = l;
l->next = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
l = l->next;
memcpy (l, &cur, sizeof (cur));
l->next = NULL;
}
l->tstamp = fp.mtime;
cur.type = FT_DIRECTORY;
cur.size = 0;
DEBUGP(("Directory\n"));
}
else
{
cur.type = FT_PLAINFILE;
DEBUGP(("File\n"));
}
cur.size = 0;
/* Second column, if exists, or the first column of the next line
contain file size in blocks. We will skip it. */
tok = strtok(NULL, " ");
if (tok == NULL)
{
DEBUGP(("Getting additional line\n"));
xfree (line);
line = read_whole_line (fp);
if (!line)
{
DEBUGP(("empty line read, leaving listing parser\n"));
break;
}
i = clean_line (line);
if (!i)
{
DEBUGP(("confusing VMS listing item, leaving listing parser\n"));
break;
}
tok = strtok(line, " ");
}
DEBUGP(("second token: '%s'\n", tok));
/* Third/Second column: Date DD-MMM-YYYY. */
tok = strtok(NULL, "-");
DEBUGP(("day: '%s'\n",tok));
day = atoi(tok);
tok = strtok(NULL, "-");
if (!tok)
{
/* If the server produces garbage like
'EA95_0PS.GZ;1 No privilege for attempted operation'
the first strtok(NULL, "-") will return everything until the end
of the line and only the next strtok() call will return NULL. */
DEBUGP(("nonsense in VMS listing, skipping this line\n"));
break;
}
for (i=0; i<12; i++) if (!strcmp(tok,months[i])) break;
/* Uknown months are mapped to January */
month = (i%12)+1;
tok = strtok(NULL, " ");
year = atoi(tok)-1900;
DEBUGP(("date parsed\n"));
/* Fourth/Third column: Time hh:mm:ss */
tok = strtok(NULL, ":");
hour = atoi(tok);
tok = strtok(NULL, ":");
min = atoi(tok);
tok = strtok(NULL, " ");
sec = atoi(tok);
DEBUGP(("YYYY/MM/DD HH:MM:SS - %d/%02d/%02d %02d:%02d:%02d\n",
year+1900, month, day, hour, min, sec));
/* Build the time-stamp (copy & paste from above) */
timestruct.tm_sec = sec;
timestruct.tm_min = min;
timestruct.tm_hour = hour;
timestruct.tm_mday = day;
timestruct.tm_mon = month;
timestruct.tm_year = year;
timestruct.tm_wday = 0;
timestruct.tm_yday = 0;
timestruct.tm_isdst = -1;
cur.tstamp = mktime (&timestruct); /* store the time-stamp */
DEBUGP(("Timestamp: %ld\n", cur.tstamp));
/* Skip the fifth column */
tok = strtok(NULL, " ");
/* Sixth column: Permissions */
tok = strtok(NULL, ","); /* Skip the VMS-specific SYSTEM permissons */
tok = strtok(NULL, ")");
if (tok == NULL)
{
DEBUGP(("confusing VMS permissions, skipping line\n"));
continue;
}
/* Permissons have the format "RWED,RWED,RE" */
cur.perms = vmsperms(tok);
DEBUGP(("permissions: %s -> 0%o\n", tok, cur.perms));
cur.linkto = NULL;
/* And put everything into the linked list */
if (!dir)
{
l = dir = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
memcpy (l, &cur, sizeof (cur));
l->prev = l->next = NULL;
}
else
{
cur.prev = l;
l->next = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
l = l->next;
memcpy (l, &cur, sizeof (cur));
l->next = NULL;
}
xfree (line);
}
@ -610,14 +736,13 @@ ftp_parse_nonunix_ls (const char *file)
fclose (fp);
return dir;
}
#endif
/* This function switches between the correct parsing routine
depending on the SYSTEM_TYPE. If system type is ST_UNIX, we use
our home-grown ftp_parse_unix_ls; otherwise, we use our interface
to ftpparse, also known as ftp_parse_nonunix_ls. The system type
should be based on the result of the "SYST" response of the FTP
server. */
/* This function switches between the correct parsing routine depending on
the SYSTEM_TYPE. The system type should be based on the result of the
"SYST" response of the FTP server. According to this repsonse we will
use on of the three different listing parsers that cover the most of FTP
servers used nowadays. */
struct fileinfo *
ftp_parse_ls (const char *file, const enum stype system_type)
@ -636,23 +761,24 @@ ftp_parse_ls (const char *file, const enum stype system_type)
{
logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
return NULL;
}
}
c = fgetc(fp);
fclose(fp);
/* If the first character of the file is '0'-'9', it's WINNT
format. */
if (c >= '0' && c <='9')
return ftp_parse_winnt_ls (file);
else
return ftp_parse_unix_ls (file, TRUE);
else
return ftp_parse_unix_ls (file, TRUE);
}
case ST_VMS:
return ftp_parse_vms_ls (file);
case ST_MACOS:
return ftp_parse_unix_ls (file, TRUE);
default:
#ifdef HAVE_FTPPARSE
return ftp_parse_nonunix_ls (file);
#else
/* #### Maybe log some warning here? */
return ftp_parse_unix_ls (file);
#endif
logprintf (LOG_NOTQUIET, _("\
Usupported listing type, trying Unix listing parser.\n"));
return ftp_parse_unix_ls (file, FALSE);
}
}

View File

@ -62,12 +62,6 @@ extern int h_errno;
extern char ftp_last_respline[];
/* #### Global variables?? These two should be members of struct
ccon! */
static enum stype host_type=ST_UNIX;
static char *pwd;
/* Look for regexp "( *[0-9]+ *byte" (literal parenthesis) anywhere in
the string S, and return the number converted to long, if found, 0
otherwise. */
@ -114,7 +108,7 @@ ftp_expected_bytes (const char *s)
connection to the server. It always closes the data connection,
and closes the control connection in case of error. */
static uerr_t
getftp (const struct urlinfo *u, long *len, long restval, ccon *con)
getftp (struct urlinfo *u, long *len, long restval, ccon *con)
{
int csock, dtsock, res;
uerr_t err;
@ -254,7 +248,7 @@ Error in server response, closing control connection.\n"));
/* Third: Get the system type */
if (!opt.server_response)
logprintf (LOG_VERBOSE, "==> SYST ... ");
err = ftp_syst (&con->rbuf, &host_type);
err = ftp_syst (&con->rbuf, &con->rs);
/* FTPRERR */
switch (err)
{
@ -285,7 +279,7 @@ Error in server response, closing control connection.\n"));
if (!opt.server_response)
logprintf (LOG_VERBOSE, "==> PWD ... ");
err = ftp_pwd(&con->rbuf, &pwd);
err = ftp_pwd(&con->rbuf, &con->id);
/* FTPRERR */
switch (err)
{
@ -361,10 +355,10 @@ Error in server response, closing control connection.\n"));
DEBUGP (("changing working directory\n"));
if (*(u->dir) == '/')
{
int pwd_len = strlen (pwd);
int pwd_len = strlen (con->id);
char *result = (char *)alloca (strlen (u->dir) + pwd_len + 10);
*result = '\0';
switch (host_type)
switch (con->rs)
{
case ST_VMS:
{
@ -373,7 +367,7 @@ Error in server response, closing control connection.\n"));
for (tmpp = tmp_dir; *tmpp; tmpp++)
if (*tmpp=='/')
*tmpp = '.';
strcpy (result, pwd);
strcpy (result, con->id);
/* pwd ends with ']', we have to get rid of it */
result[pwd_len - 1]= '\0';
strcat (result, tmp_dir);
@ -382,16 +376,14 @@ Error in server response, closing control connection.\n"));
break;
case ST_UNIX:
case ST_WINNT:
case ST_MACOS:
/* pwd_len == 1 means pwd = "/", but u->dir begins with '/'
already */
if (pwd_len > 1)
strcpy (result, pwd);
strcpy (result, con->id);
strcat (result, u->dir);
/* These look like debugging messages to me. */
#if 0
logprintf (LOG_VERBOSE, "\npwd=\"%s\"", pwd);
logprintf (LOG_VERBOSE, "\nu->dir=\"%s\"", u->dir);
#endif
DEBUGP(("\npwd=\"%s\"", con->id));
DEBUGP(("\nu->dir=\"%s\"", u->dir));
break;
default:
abort ();
@ -1177,7 +1169,7 @@ ftp_get_listing (struct urlinfo *u, ccon *con)
err = ftp_loop_internal (u, NULL, con);
u->local = olocal;
if (err == RETROK)
f = ftp_parse_ls (list_filename, host_type);
f = ftp_parse_ls (list_filename, con->rs);
else
f = NULL;
if (opt.remove_listing)
@ -1259,7 +1251,7 @@ ftp_retrieve_list (struct urlinfo *u, struct fileinfo *f, ccon *con)
dlthis = 1;
if (opt.timestamping && f->type == FT_PLAINFILE)
{
{
struct stat st;
/* If conversion of HTML files retrieved via FTP is ever implemented,
we'll need to stat() <file>.orig here when -K has been specified.
@ -1268,30 +1260,38 @@ ftp_retrieve_list (struct urlinfo *u, struct fileinfo *f, ccon *con)
.orig suffix. */
if (!stat (u->local, &st))
{
int eq_size;
int cor_val;
/* Else, get it from the file. */
local_size = st.st_size;
tml = st.st_mtime;
if (local_size == f->size && tml >= f->tstamp)
/* Compare file sizes only for servers that tell us correct
values. Assumme sizes being equal for servers that lie
about file size. */
cor_val = (con->rs == ST_UNIX || con->rs == ST_WINNT);
eq_size = cor_val ? (local_size == f->size) : 1 ;
if (f->tstamp <= tml && eq_size)
{
logprintf (LOG_VERBOSE, _("\
Server file no newer than local file `%s' -- not retrieving.\n\n"), u->local);
/* Remote file is older, file sizes can be compared and
are both equal. */
logprintf (LOG_VERBOSE, _("\
Remote file no newer than local file `%s' -- not retrieving.\n"), u->local);
dlthis = 0;
}
else if (local_size != f->size)
{
if (host_type == ST_VMS)
{
logprintf (LOG_VERBOSE, _("\
Cannot compare sizes, remote system is VMS.\n"));
dlthis = 0;
}
else
{
logprintf (LOG_VERBOSE, _("\
The sizes do not match (local %ld) -- retrieving.\n"), local_size);
}
}
}
else if (eq_size)
{
/* Remote file is newer or sizes cannot be matched */
logprintf (LOG_VERBOSE, _("\
Remote file is newer than local file `%s' -- retrieving.\n\n"),
u->local);
}
else
{
/* Sizes do not match */
logprintf (LOG_VERBOSE, _("\
The sizes do not match (local %ld) -- retrieving.\n\n"), local_size);
}
}
} /* opt.timestamping && f->type == FT_PLAINFILE */
switch (f->type)
{
@ -1571,6 +1571,8 @@ ftp_loop (struct urlinfo *u, int *dt)
rbuf_uninitialize (&con.rbuf);
con.st = ON_YOUR_OWN;
con.rs = ST_UNIX;
con.id = NULL;
res = RETROK; /* in case it's not used */
/* If the file name is empty, the user probably wants a directory
@ -1634,8 +1636,8 @@ ftp_loop (struct urlinfo *u, int *dt)
/* If a connection was left, quench it. */
if (rbuf_initialized_p (&con.rbuf))
CLOSE (RBUF_FD (&con.rbuf));
FREE_MAYBE (pwd);
pwd = NULL;
FREE_MAYBE (con.id);
con.id = NULL;
return res;
}

View File

@ -27,6 +27,7 @@ enum stype
ST_UNIX,
ST_VMS,
ST_WINNT,
ST_MACOS,
ST_OTHER
};
@ -98,6 +99,8 @@ typedef struct
int cmd; /* command code */
struct rbuf rbuf; /* control connection buffer */
long dltime; /* time of the download */
enum stype rs; /* remote system reported by ftp server */
char *id; /* initial directory */
} ccon;
struct fileinfo *ftp_parse_ls PARAMS ((const char *, enum stype));