nfs-ganesha 1.4
|
00001 /* 00002 * vim:expandtab:shiftwidth=8:tabstop=8: 00003 * 00004 * Copyright CEA/DAM/DIF (2008) 00005 * contributeur : Philippe DENIEL philippe.deniel@cea.fr 00006 * Thomas LEIBOVICI thomas.leibovici@cea.fr 00007 * 00008 * 00009 * This program is free software; you can redistribute it and/or 00010 * modify it under the terms of the GNU Lesser General Public 00011 * License as published by the Free Software Foundation; either 00012 * version 3 of the License, or (at your option) any later version. 00013 * 00014 * This program is distributed in the hope that it will be useful, 00015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00017 * Lesser General Public License for more details. 00018 * 00019 * You should have received a copy of the GNU Lesser General Public 00020 * License along with this library; if not, write to the Free Software 00021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00022 * 00023 * --------------------------------------- 00024 */ 00025 00038 #ifdef HAVE_CONFIG_H 00039 #include "config.h" 00040 #endif 00041 00042 #ifdef _SOLARIS 00043 #include "solaris_port.h" 00044 #define NAME_MAX 255 00045 #include <sys/statvfs.h> /* For statfs */ 00046 #endif /* _SOLARIS */ 00047 00048 #include "fsal.h" 00049 #include "LRU_List.h" 00050 #include "log.h" 00051 #include "HashData.h" 00052 #include "HashTable.h" 00053 #include "cache_inode.h" 00054 #include "cache_content.h" 00055 #include "nfs_exports.h" 00056 00057 #include <unistd.h> 00058 #include <sys/types.h> 00059 #include <sys/param.h> 00060 #include <time.h> 00061 #include <pthread.h> 00062 #include <string.h> 00063 #include <libgen.h> 00064 00065 #ifdef _LINUX 00066 #include <sys/vfs.h> /* For statfs */ 00067 #endif 00068 00069 #ifdef _APPLE 00070 #include <sys/param.h> /* For Statfs */ 00071 #include <sys/mount.h> 00072 #endif 00073 00074 unsigned int cache_content_dir_errno; 00075 00076 /* HashFileID4 : creates a 16bits hash of the 64bits fileid4 buffer. 00077 * 00078 * @param fileid4 [IN] 64bits fileid to be hashed. 00079 */ 00080 short HashFileID4(u_int64_t fileid4) 00081 { 00082 int i; 00083 short hash_val = 0; 00084 00085 for(i = 0; i <= 56; i += 8) 00086 { 00087 #define ALPHABET_LEN 16 00088 #define PRIME_16BITS 65521 00089 00090 hash_val = (hash_val * ALPHABET_LEN + ((fileid4 >> i) & 0xFF)) % PRIME_16BITS; 00091 } 00092 00093 return hash_val; 00094 } 00095 00111 cache_content_status_t cache_content_create_name(char *path, 00112 cache_content_nametype_t type, 00113 fsal_op_context_t * pcontext, 00114 cache_entry_t * pentry_inode, 00115 cache_content_client_t * pclient) 00116 { 00117 fsal_status_t fsal_status; 00118 u_int64_t fileid4; /* Don't want to include nfs_prot.h at this level */ 00119 fsal_handle_t *pfsal_handle = NULL; 00120 struct fsal_handle_desc fh_desc; 00121 char entrydir[MAXPATHLEN]; 00122 int i, nb_char; 00123 short hash_val; 00124 00125 pfsal_handle = &pentry_inode->handle; 00126 00127 fh_desc.start = (caddr_t)&fileid4; 00128 fh_desc.len = sizeof(fileid4); 00129 /* Get the digest for the handle, for computing an entry name */ 00130 fsal_status = FSAL_DigestHandle(FSAL_GET_EXP_CTX(pcontext), 00131 FSAL_DIGEST_FILEID4, pfsal_handle, &fh_desc); 00132 00133 if(FSAL_IS_ERROR(fsal_status)) 00134 return CACHE_CONTENT_FSAL_ERROR; 00135 00136 /* computes a 16bits hash of the 64bits fileid4 buffer */ 00137 hash_val = HashFileID4(fileid4); 00138 00139 /* for limiting the number of entries into each datacache directory 00140 * we create 256 subdirectories on 2 levels, depending on the entry's fileid. 00141 */ 00142 nb_char = snprintf(entrydir, MAXPATHLEN, "%s/export_id=%d", pclient->cache_dir, 0); 00143 00144 for(i = 0; i <= 8; i += 8) 00145 { 00146 /* concatenation of hashval */ 00147 nb_char += snprintf((char *)(entrydir + nb_char), MAXPATHLEN - nb_char, 00148 "/%02hhX", (char)((hash_val >> i) & 0xFF)); 00149 00150 /* creating the directory if necessary */ 00151 if((mkdir(entrydir, 0750) != 0) && (errno != EEXIST)) 00152 { 00153 return CACHE_CONTENT_LOCAL_CACHE_ERROR; 00154 } 00155 } 00156 00157 /* Create files for caching the entry: index file */ 00158 switch (type) 00159 { 00160 case CACHE_CONTENT_DATA_FILE: 00161 snprintf(path, MAXPATHLEN, "%s/node=%llx.data", entrydir, 00162 (unsigned long long)fileid4); 00163 break; 00164 00165 case CACHE_CONTENT_INDEX_FILE: 00166 snprintf(path, MAXPATHLEN, "%s/node=%llx.index", entrydir, 00167 (unsigned long long)fileid4); 00168 break; 00169 00170 case CACHE_CONTENT_DIR: 00171 snprintf(path, MAXPATHLEN, "%s/export_id=%d", pclient->cache_dir, 0); 00172 break; 00173 00174 default: 00175 return CACHE_CONTENT_INVALID_ARGUMENT; 00176 } 00177 00178 return CACHE_CONTENT_SUCCESS; 00179 } /* cache_content_create_name */ 00180 00192 int cache_content_get_export_id(char *dirname) 00193 { 00194 int exportid; 00195 00196 if(strncmp(dirname, "export_id=", strlen("export_id="))) 00197 return -1; 00198 00199 if(sscanf(dirname, "export_id=%d", &exportid) == 0) 00200 return -1; 00201 else 00202 return exportid; 00203 } /* cache_content_get_export_id */ 00204 00216 u_int64_t cache_content_get_inum(char *filename) 00217 { 00218 unsigned long long inum; 00219 char buff[MAXNAMLEN]; 00220 char *bname = NULL; 00221 00222 /* splits the dirent->d_name into path and filename */ 00223 strncpy(buff, filename, MAXNAMLEN); 00224 bname = basename(buff); 00225 00226 if(strncmp(bname, "node=", strlen("node="))) 00227 return 0; 00228 00229 if(strlen(bname) < 5) 00230 return 0; 00231 00232 if(strncmp(bname + strlen(bname) - 5, "index", NAME_MAX)) 00233 return 0; 00234 00235 if(sscanf(bname, "node=%llx.index", &inum) == 0) 00236 return 0; 00237 else 00238 return (u_int64_t) inum; 00239 } /* cache_content_get_inum */ 00240 00254 int cache_content_get_datapath(char *basepath, u_int64_t inum, char *datapath) 00255 { 00256 short hash_val; 00257 00258 hash_val = HashFileID4(inum); 00259 00260 snprintf(datapath, MAXPATHLEN, "%s/%02hhX/%02hhX/node=%llx.data", basepath, 00261 (char)((hash_val) & 0xFF), 00262 (char)((hash_val >> 8) & 0xFF), (unsigned long long)inum); 00263 00264 LogFullDebug(COMPONENT_CACHE_CONTENT, "cache_content_get_datapath : datapath ----> %s", 00265 datapath); 00266 00267 /* it is ok, we now return 0 */ 00268 return 0; 00269 } /* cache_content_get_datapath */ 00270 00284 off_t cache_content_recover_size(char *basepath, u_int64_t inum) 00285 { 00286 char path[MAXPATHLEN]; 00287 struct stat buffstat; 00288 00289 cache_content_get_datapath(basepath, inum, path); 00290 00291 if(stat(path, &buffstat) != 0) 00292 { 00293 LogCrit(COMPONENT_CACHE_CONTENT, 00294 "Failure in cache_content_recover_size while stat on local cache: path=%s errno = %u", 00295 path, errno); 00296 00297 return -1; 00298 } 00299 00300 LogFullDebug(COMPONENT_CACHE_CONTENT, "path ----> %s %"PRIu64, path, buffstat.st_size); 00301 00302 /* Stat is ok, we now return the size */ 00303 return buffstat.st_size; 00304 } /* cache_content_recover_size */ 00305 00317 off_t cache_content_get_cached_size(cache_content_entry_t * pentry) 00318 { 00319 struct stat buffstat; 00320 00321 if(stat(pentry->local_fs_entry.cache_path_data, &buffstat) != 0) 00322 { 00323 LogCrit(COMPONENT_CACHE_CONTENT, 00324 "Failure in cache_content_get_cached_size while stat on local cache: path=%s errno = %u", 00325 pentry->local_fs_entry.cache_path_index, errno); 00326 00327 return -1; 00328 } 00329 00330 /* Stat is ok, we now return the size */ 00331 00332 return buffstat.st_size; 00333 00334 } /* cache_content_get_cached_size */ 00335 00347 cache_inode_status_t cache_content_error_convert(cache_content_status_t status) 00348 { 00349 cache_inode_status_t converted_status; 00350 00351 switch (status) 00352 { 00353 case CACHE_CONTENT_SUCCESS: 00354 converted_status = CACHE_INODE_SUCCESS; 00355 break; 00356 00357 case CACHE_CONTENT_INVALID_ARGUMENT: 00358 converted_status = CACHE_INODE_INVALID_ARGUMENT; 00359 break; 00360 00361 case CACHE_CONTENT_BAD_CACHE_INODE_ENTRY: 00362 converted_status = CACHE_INODE_INVALID_ARGUMENT; 00363 break; 00364 00365 case CACHE_CONTENT_ENTRY_EXISTS: 00366 converted_status = CACHE_INODE_ENTRY_EXISTS; 00367 break; 00368 00369 case CACHE_CONTENT_FSAL_ERROR: 00370 converted_status = CACHE_INODE_FSAL_ERROR; 00371 break; 00372 00373 case CACHE_CONTENT_LOCAL_CACHE_ERROR: 00374 converted_status = CACHE_INODE_CACHE_CONTENT_ERROR; 00375 break; 00376 00377 case CACHE_CONTENT_MALLOC_ERROR: 00378 converted_status = CACHE_INODE_MALLOC_ERROR; 00379 break; 00380 00381 case CACHE_CONTENT_LRU_ERROR: 00382 converted_status = CACHE_INODE_LRU_ERROR; 00383 break; 00384 00385 default: 00386 converted_status = CACHE_INODE_INVALID_ARGUMENT; 00387 break; 00388 } 00389 00390 return converted_status; 00391 } /* cache_content_error_convert */ 00392 00405 size_t cache_content_fsal_size_convert(fsal_size_t size, cache_content_status_t * pstatus) 00406 { 00407 size_t taille; 00408 00409 *pstatus = CACHE_CONTENT_SUCCESS; 00410 taille = (size_t) size; 00411 00412 return taille; 00413 } /* cache_content_fsal_size_convert */ 00414 00427 cache_content_status_t cache_content_prepare_directories(exportlist_t * pexportlist, 00428 char *cache_dir, 00429 cache_content_status_t * pstatus) 00430 { 00431 exportlist_t *pexport = NULL; 00432 char cache_sub_dir[MAXPATHLEN]; 00433 00434 /* Does the cache root directory exist ? */ 00435 if(access(cache_dir, F_OK) == -1) 00436 { 00437 /* Create the cache root directory */ 00438 if(mkdir(cache_dir, 0750) == -1) 00439 return CACHE_CONTENT_LOCAL_CACHE_ERROR; 00440 } 00441 00442 /* Create the sub directories if needed */ 00443 for(pexport = pexportlist; pexport != NULL; pexport = pexport->next) 00444 { 00445 /* Create a directory only if the export entry is to be datya cached */ 00446 if(pexport->options & EXPORT_OPTION_USE_DATACACHE) 00447 { 00448 snprintf(cache_sub_dir, MAXPATHLEN, "%s/export_id=%d", cache_dir, 0); 00449 00450 if(access(cache_sub_dir, F_OK) == -1) 00451 { 00452 /* Create the cache directory */ 00453 if(mkdir(cache_sub_dir, 0750) == -1) 00454 return CACHE_CONTENT_LOCAL_CACHE_ERROR; 00455 } 00456 } 00457 } 00458 00459 /* If this point is reached, everything went ok */ 00460 return CACHE_CONTENT_SUCCESS; 00461 } /* cache_content_prepare_directories */ 00462 00482 cache_content_status_t cache_content_check_threshold(char *datacache_path, 00483 unsigned int threshold_min, 00484 unsigned int threshold_max, 00485 int *p_bool_overflow, 00486 unsigned long *p_blocks_to_lwm) 00487 { 00488 char fspath[MAXPATHLEN]; 00489 #ifdef _SOLARIS 00490 struct statvfs info_fs; 00491 #else 00492 struct statfs info_fs; 00493 #endif 00494 unsigned long total_user_blocs, dispo_hw __attribute__((unused)), dispo_lw; 00495 double tx_used, hw, lw; 00496 00497 /* defensive checks */ 00498 00499 if(!datacache_path || !p_bool_overflow || !p_blocks_to_lwm 00500 || (threshold_min > threshold_max) || (threshold_max > 100)) 00501 return CACHE_CONTENT_INVALID_ARGUMENT; 00502 00503 /* cross mountpoint */ 00504 00505 snprintf(fspath, MAXPATHLEN, "%s/.", datacache_path); 00506 00507 /* retieve FS info */ 00508 00509 if(statfs(fspath, &info_fs) != 0) 00510 { 00511 LogCrit(COMPONENT_CACHE_CONTENT, "Error getting local filesystem info: path=%s errno=%u", fspath, 00512 errno); 00513 return CACHE_CONTENT_LOCAL_CACHE_ERROR; 00514 } 00515 00516 /* Compute thresholds and total block count. 00517 * Those formulas are based on the df's code: 00518 * used = f_blocks - available_to_root 00519 * = f_blocks - f_bfree 00520 * total = used + available 00521 * = f_blocks - f_bfree + f_bavail 00522 */ 00523 hw = (double)threshold_max; /* cast to double */ 00524 lw = (double)threshold_min; /* cast to double */ 00525 00526 total_user_blocs = (info_fs.f_blocks + info_fs.f_bavail - info_fs.f_bfree); 00527 dispo_hw = (unsigned long)(((100.0 - hw) * total_user_blocs) / 100.0); 00528 dispo_lw = (unsigned long)(((100.0 - lw) * total_user_blocs) / 100.0); 00529 00530 tx_used = 100.0 * ((double)info_fs.f_blocks - (double)info_fs.f_bfree) / 00531 ((double)info_fs.f_blocks + (double)info_fs.f_bavail - (double)info_fs.f_bfree); 00532 00533 LogEvent(COMPONENT_CACHE_CONTENT, 00534 "Datacache: %s: %.2f%% used, low_wm = %.2f%%, high_wm = %.2f%%", 00535 datacache_path, tx_used, lw, hw); 00536 00537 /* threshold test */ 00538 00539 /* if the threshold is under high watermark, nothing to do */ 00540 00541 if(tx_used < hw) 00542 { 00543 *p_bool_overflow = FALSE; 00544 *p_blocks_to_lwm = 0; 00545 LogEvent(COMPONENT_CACHE_CONTENT, "Datacache: no purge needed"); 00546 } 00547 else 00548 { 00549 *p_bool_overflow = TRUE; 00550 *p_blocks_to_lwm = dispo_lw - info_fs.f_bavail; 00551 LogEvent(COMPONENT_CACHE_CONTENT, 00552 "Datacache: need to purge %lu blocks for reaching low WM", 00553 *p_blocks_to_lwm); 00554 } 00555 00556 return CACHE_CONTENT_SUCCESS; 00557 00558 } 00559 00569 int cache_content_local_cache_opendir(char *cache_dir, 00570 cache_content_dirinfo_t * pdirectory) 00571 { 00572 pdirectory->level0_dir = NULL; 00573 pdirectory->level1_dir = NULL; 00574 pdirectory->level2_dir = NULL; 00575 pdirectory->level1_cnt = 0; 00576 pdirectory->cookie0 = NULL; 00577 pdirectory->cookie1 = NULL; 00578 pdirectory->cookie2 = NULL; 00579 strcpy(pdirectory->level0_path, ""); 00580 strcpy(pdirectory->level1_name, ""); 00581 strcpy(pdirectory->level2_name, ""); 00582 00583 /* opens the top level directory */ 00584 if((pdirectory->level0_dir = opendir(cache_dir)) == NULL) 00585 { 00586 cache_content_dir_errno = errno; 00587 return FALSE; 00588 } 00589 else 00590 { 00591 cache_content_dir_errno = 0; 00592 strncpy(pdirectory->level0_path, cache_dir, MAXPATHLEN); 00593 } 00594 00595 pdirectory->level1_cnt = 0; 00596 return TRUE; 00597 } /* cache_content_local_cache_opendir */ 00598 00613 cache_content_status_t cache_content_test_cached(cache_entry_t * pentry_inode, 00614 cache_content_client_t * pclient, 00615 fsal_op_context_t * pcontext, 00616 cache_content_status_t * pstatus) 00617 { 00618 char cache_path_index[MAXPATHLEN]; 00619 00620 if(pstatus == NULL) 00621 return CACHE_CONTENT_INVALID_ARGUMENT; 00622 00623 if(pentry_inode == NULL || pclient == NULL || pcontext == NULL) 00624 { 00625 *pstatus = CACHE_CONTENT_INVALID_ARGUMENT; 00626 return *pstatus; 00627 } 00628 00629 /* Build the cache index path */ 00630 if((*pstatus = cache_content_create_name(cache_path_index, 00631 CACHE_CONTENT_INDEX_FILE, 00632 pcontext, 00633 pentry_inode, 00634 pclient)) != CACHE_CONTENT_SUCCESS) 00635 { 00636 return *pstatus; 00637 } 00638 00639 /* Check if the file exists */ 00640 if(access(cache_path_index, F_OK) == 0) 00641 { 00642 /* File is accessible and exists */ 00643 *pstatus = CACHE_CONTENT_SUCCESS; 00644 return CACHE_CONTENT_SUCCESS; 00645 } 00646 00647 /* No access */ 00648 *pstatus = CACHE_CONTENT_NOT_FOUND; 00649 return CACHE_CONTENT_NOT_FOUND; 00650 00651 } /* cache_content_test_cached */ 00652 00664 int cache_content_local_cache_dir_iter(cache_content_dirinfo_t * directory, 00665 struct dirent *pdir_entry, 00666 unsigned int index, unsigned int mod) 00667 { 00668 int rc_readdir = 0; 00669 00670 /* sanity check */ 00671 if(directory == NULL || pdir_entry == NULL) 00672 { 00673 cache_content_dir_errno = EFAULT; 00674 return FALSE; 00675 } 00676 00677 do 00678 { 00679 00680 errno = 0; 00681 00682 /* if the lowest level directory is not opened, 00683 * proceed a readdir, on the topper level directory, 00684 * and so on. 00685 */ 00686 if(directory->level2_dir != NULL) 00687 { 00688 rc_readdir = 00689 readdir_r(directory->level2_dir, pdir_entry, &(directory->cookie2)); 00690 00691 if(rc_readdir == 0 && directory->cookie2 != NULL) 00692 { 00693 char d_name_save[MAXNAMLEN]; 00694 00695 /* go to the next loop if the entry is . or .. */ 00696 if(!strcmp(".", pdir_entry->d_name) || !strcmp("..", pdir_entry->d_name)) 00697 continue; 00698 00699 LogFullDebug(COMPONENT_CACHE_CONTENT,"iterator --> %s/%s/%s/%s", directory->level0_path, 00700 directory->level1_name, directory->level2_name, pdir_entry->d_name); 00701 00702 /* the d_name must actually be the relative path from 00703 * the cache directory path, so that a file can be 00704 * accessed using <rootpath>/<d_name> path. 00705 */ 00706 strncpy(d_name_save, pdir_entry->d_name, MAXNAMLEN); 00707 snprintf(pdir_entry->d_name, MAXNAMLEN, "%s/%s/%s", 00708 directory->level1_name, directory->level2_name, d_name_save); 00709 00710 return TRUE; 00711 } 00712 else 00713 { 00714 /* test if it is an error or an end of dir */ 00715 if(errno != 0) 00716 { 00717 cache_content_dir_errno = errno; 00718 return TRUE; 00719 } 00720 else 00721 { 00722 /* the lowest level entry dir is finished, 00723 * must proceed a readdir on the topper level 00724 */ 00725 closedir(directory->level2_dir); 00726 directory->level2_dir = NULL; 00727 /* go to next loop */ 00728 } 00729 } 00730 } 00731 /* continue directory at level 1 */ 00732 else if(directory->level1_dir != NULL) 00733 { 00734 if(mod <= 1) 00735 { 00736 /* list all dirs */ 00737 rc_readdir = 00738 readdir_r(directory->level1_dir, pdir_entry, &(directory->cookie1)); 00739 directory->level1_cnt += 1; 00740 } 00741 else 00742 { 00743 rc_readdir = 00744 readdir_r(directory->level1_dir, pdir_entry, &(directory->cookie1)); 00745 directory->level1_cnt++; 00746 00747 LogFullDebug(COMPONENT_CACHE_CONTENT, 00748 "---> directory->level1_cnt=%u mod=%u index=%u modulocalcule=%u name=%s", 00749 directory->level1_cnt, mod, index, directory->level1_cnt % mod, 00750 pdir_entry->d_name); 00751 00752 /* skip entry if cnt % mod == index */ 00753 if((directory->level1_cnt % mod != index)) 00754 continue; 00755 } 00756 00757 if(rc_readdir == 0 && directory->cookie1 != NULL) 00758 { 00759 char dirpath[MAXPATHLEN]; 00760 00761 /* go to the next loop if this is the . or .. entry */ 00762 if(!strcmp(".", pdir_entry->d_name) || !strcmp("..", pdir_entry->d_name)) 00763 continue; 00764 00765 strncpy(directory->level2_name, pdir_entry->d_name, MAXNAMLEN); 00766 00767 /* must now open the entry as the level2 directory */ 00768 snprintf(dirpath, MAXPATHLEN, "%s/%s/%s", 00769 directory->level0_path, 00770 directory->level1_name, directory->level2_name); 00771 00772 if((directory->level2_dir = opendir(dirpath)) == NULL) 00773 { 00774 cache_content_dir_errno = errno; 00775 return FALSE; 00776 } 00777 } 00778 else 00779 { 00780 /* test if it is an error or an end of dir */ 00781 if(errno != 0) 00782 { 00783 cache_content_dir_errno = errno; 00784 return TRUE; 00785 } 00786 else 00787 { 00788 /* the lowest level entry dir is finished, 00789 * must proceed a readdir on the topper level 00790 */ 00791 closedir(directory->level1_dir); 00792 directory->level1_dir = NULL; 00793 } 00794 } 00795 } 00796 /* continue directory at level 0 */ 00797 else if(directory->level0_dir != NULL) 00798 { 00799 00800 rc_readdir = 00801 readdir_r(directory->level0_dir, pdir_entry, &(directory->cookie0)); 00802 00803 if(rc_readdir == 0 && directory->cookie0 != NULL) 00804 { 00805 char dirpath[MAXPATHLEN]; 00806 00807 /* go to the next loop if this is the . or .. entry */ 00808 if(!strcmp(".", pdir_entry->d_name) || !strcmp("..", pdir_entry->d_name)) 00809 continue; 00810 00811 strncpy(directory->level1_name, pdir_entry->d_name, MAXNAMLEN); 00812 00813 /* must now open the entry as the level1 directory */ 00814 snprintf(dirpath, MAXPATHLEN, "%s/%s", 00815 directory->level0_path, directory->level1_name); 00816 00817 directory->level1_cnt = 0; 00818 00819 if((directory->level1_dir = opendir(dirpath)) == NULL) 00820 { 00821 cache_content_dir_errno = errno; 00822 return TRUE; 00823 } 00824 } 00825 else 00826 { 00827 /* test if it is an error or an end of dir */ 00828 if(errno != 0) 00829 { 00830 cache_content_dir_errno = errno; 00831 return TRUE; 00832 } 00833 else 00834 { 00835 /* we are at the end of the level directory 00836 * return End of Dir 00837 */ 00838 cache_content_dir_errno = 0; 00839 return FALSE; 00840 } 00841 } 00842 } 00843 else 00844 { 00845 /* invalid base directory descriptor */ 00846 cache_content_dir_errno = EINVAL; 00847 return TRUE; 00848 } 00849 00850 } 00851 while(1); 00852 00853 cache_content_dir_errno = -1; 00854 /* should never happen */ 00855 return TRUE; 00856 00857 } /* cache_content_local_cache_dir_iter */ 00858 00868 void cache_content_local_cache_closedir(cache_content_dirinfo_t * directory) 00869 { 00870 if(directory != NULL) 00871 { 00872 if(directory->level2_dir != NULL) 00873 { 00874 closedir(directory->level2_dir); 00875 directory->level2_dir = NULL; 00876 } 00877 00878 if(directory->level1_dir != NULL) 00879 { 00880 closedir(directory->level1_dir); 00881 directory->level1_dir = NULL; 00882 } 00883 00884 if(directory->level0_dir != NULL) 00885 { 00886 closedir(directory->level0_dir); 00887 directory->level0_dir = NULL; 00888 } 00889 } 00890 } /* cache_content_local_cache_closedir */