/* ** 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 */ #include #include #include #include #ifdef STDC_HEADERS # include # include #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; }