/* * Copyright (c) 2000, 2001 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ /* * Standard includes. */ #include #include #include #include /* * Operating system includes. */ #include #include #include #include #include "direader.h" /* * Use the reentrant POSIX threads version of readdir()? */ #if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199506L #define USE_READDIR_R 1 #endif /* * Set the max length of the error-reporting string. There is no point * in this being longer than the width of a typical terminal window. * In composing error messages, I have assumed that this number is * at least 80, so you don't decrease it below this number. */ #define ERRLEN 200 /* * Objects of the following type are used to maintain the resources * needed to read directories. */ struct DirReader { DIR *dir; /* The directory stream (if open, NULL otherwise) */ struct dirent *file; /* The latest directory entry */ char errmsg[ERRLEN+1]; /* Error-report buffer */ #ifdef USE_READDIR_R struct dirent *buffer; /* A buffer used by the threaded version of readdir */ int buffer_dim; /* The allocated size of buffer[] */ #endif }; static int _dr_path_is_dir(const char *pathname); /*....................................................................... * Create a new DirReader object. * * Output: * return DirReader * The new object, or NULL on error. */ DirReader *_new_DirReader(void) { DirReader *dr; /* The object to be returned */ /* * Allocate the container. */ dr = (DirReader *) malloc(sizeof(DirReader)); if(!dr) { fprintf(stderr, "_new_DirReader: Insufficient memory.\n"); return NULL; }; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to _del_DirReader(). */ dr->dir = NULL; dr->file = NULL; dr->errmsg[0] = '\0'; #ifdef USE_READDIR_R dr->buffer = NULL; dr->buffer_dim = 0; #endif return dr; } /*....................................................................... * Delete a DirReader object. * * Input: * dr DirReader * The object to be deleted. * Output: * return DirReader * The deleted object (always NULL). */ DirReader *_del_DirReader(DirReader *dr) { if(dr) { _dr_close_dir(dr); #ifdef USE_READDIR_R free(dr->buffer); #endif free(dr); }; return NULL; } /*....................................................................... * Open a new directory. * * Input: * dr DirReader * The directory reader resource object. * path const char * The directory to be opened. * Input/Output: * errmsg char ** If an error occurs and errmsg isn't NULL, a * pointer to an error description will be assigned * to *errmsg. * Output: * return int 0 - OK. * 1 - Error (see *errmsg for a description). */ int _dr_open_dir(DirReader *dr, const char *path, char **errmsg) { DIR *dir = NULL; /* The directory stream */ /* * If a directory is already open, close it first. */ (void) _dr_close_dir(dr); /* * Is the path a directory? */ if(!_dr_path_is_dir(path)) { if(errmsg) { const char *fmt = "Can't open directory: %.*s\n"; sprintf(dr->errmsg, fmt, ERRLEN - strlen(fmt), path); *errmsg = dr->errmsg; }; return 1; }; /* * Attempt to open the directory. */ dir = opendir(path); if(!dir) { if(errmsg) { const char *fmt = "Can't open directory: %.*s\n"; sprintf(dr->errmsg, fmt, ERRLEN - strlen(fmt), path); *errmsg = dr->errmsg; }; return 1; }; /* * If using POSIX threads, allocate a buffer for readdir_r(). */ #ifdef USE_READDIR_R { size_t size; int name_max = pathconf(path, _PC_NAME_MAX); #ifdef NAME_MAX if(name_max < 0) name_max = NAME_MAX; #endif if(name_max < 0) { if(errmsg) { strcpy(dr->errmsg, "Unable to deduce readdir() buffer size."); *errmsg = dr->errmsg; }; closedir(dir); return 1; }; /* * How big a buffer do we need to allocate? */ size = sizeof(struct dirent) + name_max; /* * Extend the buffer? */ if(size > dr->buffer_dim || !dr->buffer) { struct dirent *buffer = (struct dirent *) (dr->buffer ? realloc(dr->buffer, size) : malloc(size)); if(!buffer) { if(errmsg) { strcpy(dr->errmsg, "Insufficient memory for readdir() buffer."); *errmsg = dr->errmsg; }; closedir(dir); return 1; }; dr->buffer = buffer; dr->buffer_dim = size; }; }; #endif /* * Record the successfully opened directory. */ dr->dir = dir; return 0; } /*....................................................................... * If the DirReader object is currently contains an open directory, * close it. * * Input: * dr DirReader * The directory reader resource object. */ void _dr_close_dir(DirReader *dr) { if(dr && dr->dir) { closedir(dr->dir); dr->dir = NULL; dr->file = NULL; dr->errmsg[0] = '\0'; }; } /*....................................................................... * Read the next file from the directory opened with _dr_open_dir(). * * Input: * dr DirReader * The directory reader resource object. * Output: * return char * The name of the new file, or NULL if we reached * the end of the directory. */ char *_dr_next_file(DirReader *dr) { /* * Are we currently reading a directory? */ if(dr->dir) { /* * Read the next directory entry. */ #ifdef USE_READDIR_R if(readdir_r(dr->dir, dr->buffer, &dr->file) == 0 && dr->file) return dr->file->d_name; #else dr->file = readdir(dr->dir); if(dr->file) return dr->file->d_name; #endif }; /* * When the end of a directory is reached, close it. */ _dr_close_dir(dr); return NULL; } /*....................................................................... * Return 1 if the specified pathname refers to a directory. * * Input: * pathname const char * The path to test. * Output: * return int 0 - Not a directory. * 1 - pathname[] refers to a directory. */ static int _dr_path_is_dir(const char *pathname) { struct stat statbuf; /* The file-statistics return buffer */ /* * Look up the file attributes. */ if(stat(pathname, &statbuf) < 0) return 0; /* * Is the file a directory? */ return S_ISDIR(statbuf.st_mode) != 0; }