/*
** Copyright 2000-2004 University of Illinois Board of Trustees
** Copyright 2000-2004 Mark D. Roth
** All rights reserved.
**
** dircache.c - FTP directory caching code
**
** Mark D. Roth <roth@feep.net>
*/
#include <internal.h>
#include <time.h>
#include <errno.h>
#include <sys/param.h>
#ifdef STDC_HEADERS
# include <string.h>
# include <stdlib.h>
#endif
/* useful constants */
#define DIRCACHE_HASH_SIZE 256 /* size of directory hash */
#define DIRCACHE_FILEHASH_SIZE 128 /* size of directory contents hashes
(7-bit ASCII filenames) */
/******************************************************************************
***** dir cache entry code
******************************************************************************/
/* dircache entry structure */
struct dcent
{
char dc_name[MAXPATHLEN]; /* name of entry */
time_t dc_timestamp; /* time added to cache */
enum dcent_types dc_type; /* type of entry */
union
{
fget_hash_t *dc_files_h; /* directory contents */
file_info_t *dc_fip; /* individual file listing */
} dc_data;
};
typedef struct dcent dcent_t;
/*
** _dcent_hashfunc() - hash function for directory cache entries
*/
static int
_dcent_hashfunc(char *key, int num_buckets)
{
register unsigned result = 0;
register int i;
if (key == NULL)
return 0;
for (i = 0; *key != '\0' && i < 32; i++)
result = result * 33U + *key++;
return (result % num_buckets);
}
/*
** _dcent_free() - free a directory cache entry
*/
static void
_dcent_free(dcent_t *dcp)
{
if (dcp->dc_type == DCENT_DIR)
{
if (dcp->dc_data.dc_files_h != NULL)
fget_hash_free(dcp->dc_data.dc_files_h, free);
}
else
{
if (dcp->dc_data.dc_fip != NULL)
free(dcp->dc_data.dc_fip);
}
free(dcp);
}
/*
** _dcent_new() - allocate a new directory cache entry
*/
static int
_dcent_new(dcent_t **dcpp, char *name, int type, void *data)
{
/* allocate new cache entry */
*dcpp = (dcent_t *)calloc(1, sizeof(dcent_t));
if (*dcpp == NULL)
return -1;
/* set timestamp */
(*dcpp)->dc_timestamp = time(NULL);
/* set fields with caller-supplied data */
strlcpy((*dcpp)->dc_name, name, sizeof((*dcpp)->dc_name));
(*dcpp)->dc_type = type;
if (type == DCENT_DIR)
(*dcpp)->dc_data.dc_files_h = (fget_hash_t *)data;
else
(*dcpp)->dc_data.dc_fip = (file_info_t *)data;
return 0;
}
/*
** _dcent_find_file() - find an entry in a files hash
** Returns:
** 1 success
** 0 failure
*/
static int
_dcent_find_file(fget_hash_t *file_h, char *file, file_info_t **fipp)
{
fget_hashptr_t hp;
#ifdef DEBUG
printf("==> _dcent_find_file(file_h=0x%lx, file=\"%s\", "
"fipp=0x%lx (0x%lx))\n", file_h, file, fipp, *fipp);
#endif
if (file_h == NULL)
return 0;
fget_hashptr_reset(&hp);
if (fget_hash_getkey(file_h, &hp, file, NULL) == 0)
return 0;
*fipp = (file_info_t *)fget_hashptr_data(&hp);
#ifdef DEBUG
printf("<== _dcent_find_file(): returning \"%s\"\n",
(*fipp)->fi_filename);
#endif
return 1;
}
/******************************************************************************
***** dir cache code
******************************************************************************/
/*
** _dc_expire_dir() - remove a given directory from the dir cache
*/
static void
_dc_expire_dir(FTP *ftp, dcent_t *dcp, fget_listptr_t *lpp)
{
fget_hashptr_t hp;
#ifdef DEBUG
printf("==> _dc_expire_dir(ftp=0x%lx (%s), dcp=0x%lx (%s), "
"lpp=0x%lx)\n",
ftp, fget_netio_get_host(ftp->ftp_control),
dcp, dcp->dc_name, lpp);
#endif
fget_hashptr_reset(&hp);
if (fget_hash_getkey(ftp->ftp_dc_h, &hp, dcp, NULL) == 0)
/* can't happen */
return;
fget_hash_del(ftp->ftp_dc_h, &hp);
fget_list_del(ftp->ftp_dc_age_l, lpp);
#ifdef DEBUG
printf(" _dc_expire_dir(): ftp->ftp_dc_size = %lu - %u\n",
ftp->ftp_dc_size, fget_hash_nents(dcp->dc_data.dc_files_h));
#endif
if (dcp->dc_type == DCENT_DIR)
ftp->ftp_dc_size -= fget_hash_nents(dcp->dc_data.dc_files_h);
else
ftp->ftp_dc_size--; /* DCENT_FILE */
_dcent_free(dcp);
}
/*
** _dc_expire() - keep dircache within specified size and time
*/
static void
_dc_expire(FTP *ftp)
{
fget_listptr_t lp;
dcent_t *dcp;
time_t age;
#ifdef DEBUG
printf("==> _dc_expire(ftp=0x%lx (%s))\n",
ftp, fget_netio_get_host(ftp->ftp_control));
printf(" ftp->ftp_cache_maxsize = %lu\n",
(unsigned long)ftp->ftp_cache_maxsize);
printf(" ftp->ftp_cache_expire = %ld\n",
(long)ftp->ftp_cache_expire);
#endif
fget_listptr_reset(&lp);
if (ftp->ftp_cache_maxsize != -1
&& fget_list_next(ftp->ftp_dc_age_l, &lp) != 0)
{
/*
** NOTE: we don't need to call fget_list_next() each
** time, since fget_list_del() gets called in
** _dc_expire_dir() and leaves the listptr pointing
** to the next node
*/
while (fget_hash_nents(ftp->ftp_dc_h) > 0)
{
dcp = (dcent_t *)fget_listptr_data(&lp);
#ifdef DEBUG
printf(" _dc_expire(): EXP 1: "
"dcp=0x%lx (%s), cache size=%ld\n",
dcp, dcp->dc_name, (long)ftp->ftp_dc_size);
#endif
if (ftp->ftp_dc_size
<= (unsigned long)ftp->ftp_cache_maxsize)
break;
_dc_expire_dir(ftp, dcp, &lp);
}
}
fget_listptr_reset(&lp);
if (ftp->ftp_cache_expire != (time_t)-1
&& fget_list_next(ftp->ftp_dc_age_l, &lp) != 0)
{
/*
** (see note about fget_list_next() above)
*/
while (fget_hash_nents(ftp->ftp_dc_h) > 0)
{
dcp = (dcent_t *)fget_listptr_data(&lp);
age = time(NULL) - dcp->dc_timestamp;
#ifdef DEBUG
printf(" _dc_expire(): EXP 2: "
"dcp=0x%lx (%s), age=%ld\n",
dcp, dcp->dc_name, (long)age);
#endif
if (age <= ftp->ftp_cache_expire)
break;
_dc_expire_dir(ftp, dcp, &lp);
}
}
}
/*
** _dc_insert() - insert a new entry into the directory cache
*/
static void
_dc_insert(FTP *ftp, dcent_t *dcp)
{
/* update cache size */
if (dcp->dc_type == DCENT_DIR)
ftp->ftp_dc_size += fget_hash_nents(dcp->dc_data.dc_files_h);
else
ftp->ftp_dc_size++; /* DCENT_FILE */
/* add to cache */
fget_hash_add(ftp->ftp_dc_h, dcp);
fget_list_add(ftp->ftp_dc_age_l, dcp);
}
/*
** _dc_get() - find an entry in the directory hash
** Returns:
** 1 success
** 0 failure
*/
static int
_dc_get(fget_hash_t *dc_h, char *name, dcent_t **dcpp,
enum dcent_types preferred_type)
{
fget_hashptr_t hp;
int i;
#ifdef DEBUG
printf("==> _dc_get(dc_h=0x%lx, name=\"%s\", "
"dcpp=0x%lx (0x%lx))\n", dc_h, name, dcpp, *dcpp);
#endif
if (dc_h == NULL)
{
#ifdef DEBUG
printf("<== _dc_get(): failed\n");
#endif
return 0;
}
*dcpp = NULL;
fget_hashptr_reset(&hp);
while (fget_hash_getkey(dc_h, &hp, name, NULL) == 1)
{
*dcpp = (dcent_t *)fget_hashptr_data(&hp);
/* this is the preferred type, so stop here */
if ((*dcpp)->dc_type == preferred_type)
break;
/*
** if we found something that is not the preferred type,
** save it in *dcpp, but keep looking
*/
}
if (*dcpp == NULL)
{
#ifdef DEBUG
printf("<== _dc_get(): failed\n");
#endif
return 0;
}
#ifdef DEBUG
printf("<== _dc_get(): returning \"%s\" (%spreferred type)\n",
(*dcpp)->dc_name,
((*dcpp)->dc_type == preferred_type
? ""
: "NOT "));
#endif
return 1;
}
/******************************************************************************
***** FTP list interface
******************************************************************************/
/*
** _dc_query_server() - add directory info to the dir cache
*/
static int
_dc_query_server(FTP *ftp, char *dir, dcent_t **dcpp,
enum dcent_types preferred_type)
{
fget_hash_t *files_h = NULL;
fget_hashptr_t hp;
file_info_t *fip;
char path[MAXPATHLEN];
#ifdef DEBUG
printf("==> _dc_query_server(ftp=0x%lx (%s), dir=\"%s\", "
"dcpp=0x%lx (0x%lx), preferred_type=%d)\n",
ftp, fget_netio_get_host(ftp->ftp_control),
dir, dcpp, *dcpp, preferred_type);
#endif
files_h = fget_hash_new(DIRCACHE_FILEHASH_SIZE, NULL);
if (files_h == NULL)
return -1;
if (_ftp_list(ftp, dir, files_h, preferred_type, 0) == -1)
{
#ifdef DEBUG
printf("<== _dc_query_server(): _ftp_list() failed "
"(errno = %d)\n", errno);
#endif
fget_hash_free(files_h, free);
return -1;
}
#ifdef DEBUG
printf(" _dc_query_server(): files_h = 0x%lx\n", files_h);
#endif
/*
** check if the server returned a full directory listing,
** or if it just listed a single file
*/
fget_hashptr_reset(&hp);
fget_hash_next(files_h, &hp);
fip = (file_info_t *)fget_hashptr_data(&hp);
if (fget_hash_nents(files_h) == 1
&& fip != NULL
&& strcmp(fip->fi_filename, dir) == 0)
{
/*
** server listed a single file
*/
/* free hash */
fget_hash_free(files_h, NULL);
/* fix filename field to include only the basename */
strlcpy(fip->fi_filename, basename(fip->fi_filename),
sizeof(fip->fi_filename));
/* create new entry */
if (_dcent_new(dcpp, dir, DCENT_FILE, fip) == -1)
return -1;
}
else
{
/*
** it's a full directory listing
*/
/* create new entry */
if (_dcent_new(dcpp, dir, DCENT_DIR, files_h) == -1)
return -1;
}
/* insert new entry */
_dc_insert(ftp, *dcpp);
#ifdef DEBUG
printf("<== _dc_query_server(): *dcpp=0x%lx\n", *dcpp);
#endif
return 0;
}
/******************************************************************************
***** external interface to this object
******************************************************************************/
/*
** _ftp_dircache_init() - initialize directory cache
*/
void
_ftp_dircache_init(FTP *ftp)
{
ftp->ftp_dc_h = fget_hash_new(DIRCACHE_HASH_SIZE,
(fget_hashfunc_t)_dcent_hashfunc);
ftp->ftp_dc_age_l = fget_list_new(LIST_QUEUE, NULL);
}
/*
** _ftp_dircache_free() - free the entire directory cache
*/
void
_ftp_dircache_free(FTP *ftp)
{
if (ftp->ftp_dc_h != NULL)
fget_hash_free(ftp->ftp_dc_h, (fget_freefunc_t)_dcent_free);
ftp->ftp_dc_h = NULL;
if (ftp->ftp_dc_age_l != NULL)
fget_list_free(ftp->ftp_dc_age_l, NULL);
ftp->ftp_dc_age_l = NULL;
}
/*
** _ftp_dircache_find_file() - get file info from directory cache
** Returns:
** 0 success
** -1 (and sets errno) failure
**
** Side effects:
** sets *fipp to point to the info for the requested file
**
** Notes:
** caller MUST NOT MODIFY the data "returned" in *fipp
*/
int
_ftp_dircache_find_file(FTP *ftp, char *pathname, unsigned short flags,
file_info_t **fipp)
{
dcent_t *parent_dcp = NULL;
dcent_t *dcp = NULL;
unsigned long level;
char buf[MAXPATHLEN];
char path[MAXPATHLEN];
char path_dirname[MAXPATHLEN];
char path_basename[MAXPATHLEN];
file_info_t *fip;
#ifdef DEBUG
printf("==> _ftp_dircache_find_file(ftp=0x%lx (%s), pathname=\"%s\", "
"flags=%d, fipp=0x%lx)\n",
ftp, fget_netio_get_host(ftp->ftp_control),
pathname, flags, fipp);
#endif
/* run cache expiration */
_dc_expire(ftp);
if (strcmp(pathname, "/") == 0)
strlcpy(path, pathname, sizeof(path));
else
{
/*
** allow last component of pathname to escape canonification
** (this allows us to return data for "." and "..")
*/
_ftp_abspath(ftp, dirname(pathname), buf, sizeof(buf));
snprintf(path, sizeof(path), "%s/%s", buf, basename(pathname));
}
for (level = 0;
level < MAXSYMLINKS;
level++)
{
#ifdef DEBUG
printf(" _ftp_dircache_find_file(): TOP OF LOOP: "
"level=%lu, path=\"%s\"\n",
level, path);
#endif
/* reset at beginning of loop */
parent_dcp = NULL;
dcp = NULL;
/* save dirname and basename */
strlcpy(path_dirname, dirname(path), sizeof(path_dirname));
strlcpy(path_basename, basename(path), sizeof(path_basename));
/*
** Step 1: check cache for parent directory
**
** Note: we skip this step for the root directory,
** since root doesn't really have a parent
*/
if (strcmp(path, "/") != 0
&& _dc_get(ftp->ftp_dc_h, path_dirname, &parent_dcp,
DCENT_DIR) == 1)
{
switch (parent_dcp->dc_type)
{
case DCENT_FILE:
fip = parent_dcp->dc_data.dc_fip;
/*
** if it's a symlink, it presumably
** points to the real directory, but
** it doesn't really help us.
** so, we treat it as a cache miss.
*/
if (S_ISLNK(fip->fi_stat.fs_mode))
{
parent_dcp = NULL;
break;
}
/*
** if the parent is a DCENT_FILE but not
** a symlink, then fail
**
** FIXME: is this too much of an assumption?
*/
#ifdef DEBUG
printf("<== _ftp_dircache_find_file(): "
"FAILED: ENOTDIR in step 1\n");
#endif
errno = ENOTDIR;
return -1;
case DCENT_DIR:
if (_dcent_find_file(parent_dcp->dc_data.dc_files_h,
path_basename, fipp) == 1)
goto gotcha;
break;
default:
/*
** can't happen
** treat this as if nothing was found
*/
parent_dcp = NULL;
break;
}
/*
** if we get here, the parent must be
** a DCENT_DIR that does not contain an entry
** for path_basename
**
** in this case, we just fall through and don't
** worry about it, since we don't want to make
** too many assumptions about what the server
** may be hiding
*/
}
/*
** if we get here, the parent was either not
** found in the cache, or it is a DCENT_DIR
** with no path_basename entry
*/
/*
** Step 2: check cache for entry with the full path
*/
if (_dc_get(ftp->ftp_dc_h, path, &dcp, DCENT_FILE) == 1)
{
switch (dcp->dc_type)
{
case DCENT_FILE:
/* if it's a file, we've got our man */
*fipp = dcp->dc_data.dc_fip;
goto gotcha;
case DCENT_DIR:
/*
** try checking whether the directory
** contains a "." entry
*/
if (_dcent_find_file(dcp->dc_data.dc_files_h,
".", fipp) == 1)
goto gotcha;
break;
default:
/*
** can't happen
** treat this as if nothing was found
*/
dcp = NULL;
break;
}
/*
** if we get here, the full path must be
** a DCENT_DIR with no "." entry
*/
}
/*
** if we get here, the full path was either not
** found in the cache, or it is a DCENT_DIR
** with no "." entry
*/
/*
** Step 3: get parent directory from server
** (if not already cached)
**
** Note: we skip this step for the root directory,
** since root doesn't really have a parent
*/
if (parent_dcp == NULL
&& strcmp(path, "/") != 0)
{
#ifdef DEBUG
printf(" _ftp_dircache_find_file(): "
"getting parent dir from server\n");
#endif
if (_dc_query_server(ftp, path_dirname,
&parent_dcp, DCENT_DIR) == -1)
{
#ifdef DEBUG
printf("<== _ftp_dircache_find_file(): "
"FAILED: no data from server\n");
#endif
return -1;
}
switch (parent_dcp->dc_type)
{
case DCENT_FILE:
/*
** FIXME: should we check for symlinks here?
** are there any cases where the server would
** return a DCENT_FILE when we ask for a
** DCENT_DIR? (see _ftp_list() code)
*/
#if 0
fip = parent_dcp->dc_data.dc_fip;
if (S_ISLNK(fip->fi_stat.fs_mode))
{
}
#endif
/*
** if the parent is a DCENT_FILE but not
** a symlink, then fail
**
** FIXME: is this too much of an assumption?
*/
#ifdef DEBUG
printf("<== _ftp_dircache_find_file(): "
"FAILED: ENOTDIR in step 3\n");
#endif
errno = ENOTDIR;
return -1;
case DCENT_DIR:
/*
** check for child's entry in parent directory
*/
if (_dcent_find_file(parent_dcp->dc_data.dc_files_h,
path_basename, fipp) == 1)
goto gotcha;
break;
default:
/*
** can't happen
*/
break;
}
/*
** if we get here, the parent must be
** a DCENT_DIR with no path_basename entry
*/
}
/*
** Step 4: get full path from server
** (if not already cached)
*/
if (dcp == NULL)
{
#ifdef DEBUG
printf(" _ftp_dircache_find_file(): "
"getting full path from server\n");
#endif
if (_dc_query_server(ftp, path, &dcp,
DCENT_FILE) == -1)
{
#ifdef DEBUG
printf("<== _ftp_dircache_find_file(): FAILED: "
"no data from server\n");
#endif
return -1;
}
switch (dcp->dc_type)
{
case DCENT_FILE:
/* if it's a file, we've got our man */
*fipp = dcp->dc_data.dc_fip;
goto gotcha;
case DCENT_DIR:
/* try checking the "." entry */
if (_dcent_find_file(dcp->dc_data.dc_files_h,
".", fipp) == 1)
goto gotcha;
break;
default:
/*
** can't happen
*/
break;
}
/*
** if we get here, the full path must be
** a DCENT_DIR with no "." entry
*/
}
/*
** if we get through to here, then the path is a
** DCENT_DIR with no "." entry, and we've exhausted
** our options for getting info on the directory itself.
**
** one of two things is happening:
**
** (1) the full path is a DCENT_DIR with no "." entry
** and the parent is a DCENT_DIR with no
** path_basename entry.
**
** this "can't happen", but I wouldn't put it past
** some servers I've encountered...
**
** (2) the path requested is "/" (i.e., no parent
** directory to check), and it is a DCENT_DIR
** with no "." entry.
**
** this is ugly, but it's somewhat common on
** servers that don't grok ls(1) options
** as arguments to a LIST request.
**
** in either case, we know that the requested path does
** actually exist, so we punt by making up some info.
*/
*fipp = (file_info_t *)calloc(1, sizeof(file_info_t));
if (*fipp == NULL)
{
#ifdef DEBUG
printf("<== _ftp_dircache_find_file(): "
"FAILED while creating dummy directory "
"stub entry: calloc(): %s\n",
strerror(errno));
#endif
return -1;
}
/*
** all fields were initialized to 0 by calloc(),
** so just set the ones we care about
*/
strlcpy((*fipp)->fi_filename, path_basename,
sizeof((*fipp)->fi_filename));
(*fipp)->fi_stat.fs_mode = S_IFDIR|0555;
strlcpy((*fipp)->fi_stat.fs_username, "-1",
sizeof((*fipp)->fi_stat.fs_username));
strlcpy((*fipp)->fi_stat.fs_groupname, "-1",
sizeof((*fipp)->fi_stat.fs_groupname));
/* create new entry */
if (_dcent_new(&dcp, path, DCENT_FILE, *fipp) == -1)
{
free(*fipp);
*fipp = NULL;
#ifdef DEBUG
printf("<== _ftp_dircache_find_file(): "
"FAILED while creating dummy directory "
"stub entry: _dcent_new(): %s\n",
strerror(errno));
#endif
return -1;
}
/* insert new entry */
_dc_insert(ftp, dcp);
#ifdef DEBUG
printf("<== _ftp_dircache_find_file(): "
"returning dummy directory stub entry\n");
#endif
return 0;
gotcha:
/*
** now fipp points to the right thing, regardless of
** whether it came from the cache or whether we
** just got it from the server
*/
/*
** if it's not a link, or if we're not following links,
** then break out of the loop
*/
if (! S_ISLNK((*fipp)->fi_stat.fs_mode)
|| BIT_ISSET(flags, DC_NOFOLLOWLINKS))
break;
/*
** otherwise, follow symlink and try again
*/
if ((*fipp)->fi_linkto[0] != '/')
snprintf(buf, sizeof(buf), "%s/%s",
path_dirname, (*fipp)->fi_linkto);
else
strlcpy(buf, (*fipp)->fi_linkto, sizeof(buf));
fget_cleanpath(buf, path, sizeof(path));
#ifdef DEBUG
printf(" _ftp_dircache_find_file(): "
"following symlink; new path is: \"%s\"\n",
path);
#endif
}
if (level >= MAXSYMLINKS)
{
#ifdef DEBUG
printf("<== _ftp_dircache_find_file(): FAILED: ELOOP\n");
#endif
errno = ELOOP;
return -1;
}
#ifdef DEBUG
printf("<== _ftp_dircache_find_file(): success\n");
#endif
return 0;
}
/*
** _ftp_dircache_find_dir() - get contents of a directory
** Returns:
** 0 success
** -1 (and sets errno) failure
**
** Side effects:
** sets *dir_hp to point to the contents of the directory
**
** Notes:
** caller MUST NOT MODIFY the data "returned" in *dir_hp
*/
int
_ftp_dircache_find_dir(FTP *ftp, char *pathname, fget_hash_t **dir_hp)
{
dcent_t *parent_dcp = NULL;
dcent_t *dcp = NULL;
char buf[MAXPATHLEN];
char path[MAXPATHLEN];
char path_dirname[MAXPATHLEN];
char path_basename[MAXPATHLEN];
file_info_t *fip = NULL;
#ifdef DEBUG
printf("==> _ftp_dircache_find_dir(ftp=0x%lx (%s), pathname=\"%s\", "
"dir_hp=0x%lx)\n",
ftp, fget_netio_get_host(ftp->ftp_control), pathname, dir_hp);
#endif
/* run cache expiration */
_dc_expire(ftp);
_ftp_abspath(ftp, pathname, path, sizeof(path));
/* save dirname and basename */
strlcpy(path_dirname, dirname(path), sizeof(path_dirname));
strlcpy(path_basename, basename(path), sizeof(path_basename));
/*
** Step 1: check cache for full path
*/
if (_dc_get(ftp->ftp_dc_h, path, &dcp, DCENT_DIR) == 1)
{
switch (dcp->dc_type)
{
case DCENT_DIR:
/* if it's a directory, we've got our man */
goto gotcha;
case DCENT_FILE:
fip = dcp->dc_data.dc_fip;
/*
** if it's a directory stub or
** a symlink (presumably to a directory),
** we might still be able to get the
** directory contents
*/
if (S_ISDIR(fip->fi_stat.fs_mode)
|| S_ISLNK(fip->fi_stat.fs_mode))
break;
/*
** otherwise, fail with ENOTDIR
*/
#ifdef DEBUG
printf("<== _ftp_dircache_find_dir(): "
"FAILED: ENOTDIR in step 1\n");
#endif
errno = ENOTDIR;
return -1;
default:
/*
** can't happen
*/
break;
}
/*
** if we get here, then the full path is DCENT_FILE
** entry for a directory or a symlink
*/
}
/*
** if we get here, then the full path is either not
** cached or is a DCENT_FILE entry for
** a directory or a symlink
*/
/*
** Step 2: check cache for parent directory
**
** Note: we skip this step for the root directory,
** since root doesn't really have a parent
*/
if (strcmp(path, "/") == 0
&& _dc_get(ftp->ftp_dc_h, path_dirname, &parent_dcp,
DCENT_DIR) == 1)
{
switch (parent_dcp->dc_type)
{
case DCENT_DIR:
/*
** if it's a directory, search for
** path_basename
*/
if (_dcent_find_file(parent_dcp->dc_data.dc_files_h,
path_basename, &fip) == 1)
{
if (S_ISDIR(fip->fi_stat.fs_mode)
|| S_ISLNK(fip->fi_stat.fs_mode))
break;
#ifdef DEBUG
printf("<== _ftp_dircache_find_dir(): "
"FAILED: parent directory lists "
"path as a non-directory: "
"ENOTDIR in step 2\n");
#endif
errno = ENOTDIR;
return -1;
}
break;
case DCENT_FILE:
fip = dcp->dc_data.dc_fip;
/*
** if it's a directory stub or
** a symlink (presumably to a directory),
** we might still be able to get the
** directory contents
*/
if (S_ISDIR(fip->fi_stat.fs_mode)
|| S_ISLNK(fip->fi_stat.fs_mode))
break;
/*
** otherwise, fail with ENOTDIR
*/
#ifdef DEBUG
printf("<== _ftp_dircache_find_dir(): "
"FAILED: parent directory found "
"as a DCENT_FILE: ENOTDIR in step 2\n");
#endif
errno = ENOTDIR;
return -1;
default:
/*
** can't happen
*/
break;
}
/*
** if we get here, then the full path is DCENT_FILE
** entry for a directory or a symlink
*/
}
/*
** Step 3: get listing from server
*/
if (_dc_query_server(ftp, path, &dcp, DCENT_DIR) == -1)
{
#ifdef DEBUG
printf("<== _ftp_dircache_find_dir(): FAILED: "
"no data from server\n");
#endif
return -1;
}
switch (dcp->dc_type)
{
case DCENT_DIR:
/* if it's a directory, we've got our man */
goto gotcha;
case DCENT_FILE:
/* if it's a file, fail */
#ifdef DEBUG
printf("<== _ftp_dircache_find_dir(): "
"FAILED: ENOTDIR in step 3\n");
#endif
errno = ENOTDIR;
return -1;
default:
/*
** can't happen
*/
#ifdef DEBUG
printf("<== _ftp_dircache_find_dir(): "
"FAILED: ENOSYS in step 3\n");
#endif
errno = ENOSYS;
return -1;
}
gotcha:
*dir_hp = dcp->dc_data.dc_files_h;
#ifdef DEBUG
printf("<== _ftp_dircache_find_dir(): success\n");
#endif
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1