1 /*
2  * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd.
3  *
4  * All rights reserved.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a
7  * copy of this software and associated documentation files (the
8  * "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, and/or sell copies of the Software, and to permit persons
11  * to whom the Software is furnished to do so, provided that the above
12  * copyright notice(s) and this permission notice appear in all copies of
13  * the Software and that both the above copyright notice(s) and this
14  * permission notice appear in supporting documentation.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
19  * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
20  * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
21  * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
22  * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
23  * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
24  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25  *
26  * Except as contained in this notice, the name of a copyright holder
27  * shall not be used in advertising or otherwise to promote the sale, use
28  * or other dealings in this Software without prior written authorization
29  * of the copyright holder.
30  */
31 
32 /*
33  * If file-system access is to be excluded, this module has no function,
34  * so all of its code should be excluded.
35  */
36 #ifndef WITHOUT_FILE_SYSTEM
37 
38 /*
39  * Standard includes.
40  */
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <errno.h>
45 
46 /*
47  * Operating system includes.
48  */
49 #include <unistd.h>
50 #include <sys/types.h>
51 #include <sys/stat.h>
52 #include <dirent.h>
53 
54 #include "direader.h"
55 #include "errmsg.h"
56 
57 /*
58  * Use the reentrant POSIX threads version of readdir()?
59  */
60 #if defined(PREFER_REENTRANT) && defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199506L
61 #define USE_READDIR_R 1
62 #endif
63 
64 /*
65  * Objects of the following type are used to maintain the resources
66  * needed to read directories.
67  */
68 struct DirReader {
69   ErrMsg *err;             /* The error reporting buffer */
70   DIR *dir;                /* The directory stream (if open, NULL otherwise) */
71   struct dirent *file;     /* The latest directory entry */
72 #ifdef USE_READDIR_R
73   struct dirent *buffer;   /* A buffer used by the threaded version of */
74                            /*  readdir() */
75   int buffer_dim;          /* The allocated size of buffer[] */
76 #endif
77 };
78 
79 static int _dr_path_is_dir(const char *pathname);
80 
81 /*.......................................................................
82  * Create a new DirReader object.
83  *
84  * Output:
85  *  return  DirReader *  The new object, or NULL on error.
86  */
_new_DirReader(void)87 DirReader *_new_DirReader(void)
88 {
89   DirReader *dr;  /* The object to be returned */
90 /*
91  * Allocate the container.
92  */
93   dr = (DirReader *) malloc(sizeof(DirReader));
94   if(!dr) {
95     errno = ENOMEM;
96     return NULL;
97   };
98 /*
99  * Before attempting any operation that might fail, initialize the
100  * container at least up to the point at which it can safely be passed
101  * to _del_DirReader().
102  */
103   dr->err = NULL;
104   dr->dir = NULL;
105   dr->file = NULL;
106 #ifdef USE_READDIR_R
107   dr->buffer = NULL;
108   dr->buffer_dim = 0;
109 #endif
110 /*
111  * Allocate a place to record error messages.
112  */
113   dr->err = _new_ErrMsg();
114   if(!dr->err)
115     return _del_DirReader(dr);
116   return dr;
117 }
118 
119 /*.......................................................................
120  * Delete a DirReader object.
121  *
122  * Input:
123  *  dr     DirReader *  The object to be deleted.
124  * Output:
125  *  return DirReader *  The deleted object (always NULL).
126  */
_del_DirReader(DirReader * dr)127 DirReader *_del_DirReader(DirReader *dr)
128 {
129   if(dr) {
130     _dr_close_dir(dr);
131 #ifdef USE_READDIR_R
132     free(dr->buffer);
133 #endif
134     dr->err = _del_ErrMsg(dr->err);
135     free(dr);
136   };
137   return NULL;
138 }
139 
140 /*.......................................................................
141  * Open a new directory.
142  *
143  * Input:
144  *  dr      DirReader *   The directory reader resource object.
145  *  path   const char *   The directory to be opened.
146  * Input/Output:
147  *  errmsg       char **  If an error occurs and errmsg isn't NULL, a
148  *                        pointer to an error description will be assigned
149  *                        to *errmsg.
150  * Output:
151  *  return        int     0 - OK.
152  *                        1 - Error (see *errmsg for a description).
153  */
_dr_open_dir(DirReader * dr,const char * path,char ** errmsg)154 int _dr_open_dir(DirReader *dr, const char *path, char **errmsg)
155 {
156   DIR *dir = NULL;   /* The directory stream */
157 /*
158  * If a directory is already open, close it first.
159  */
160   (void) _dr_close_dir(dr);
161 /*
162  * Is the path a directory?
163  */
164   if(!_dr_path_is_dir(path)) {
165     if(errmsg) {
166       _err_record_msg(dr->err, "Can't open directory: ", path, END_ERR_MSG);
167       *errmsg = _err_get_msg(dr->err);
168     };
169     return 1;
170   };
171 /*
172  * Attempt to open the directory.
173  */
174   dir = opendir(path);
175   if(!dir) {
176     if(errmsg) {
177       _err_record_msg(dr->err, "Can't open directory: ", path, END_ERR_MSG);
178       *errmsg = _err_get_msg(dr->err);
179     };
180     return 1;
181   };
182 /*
183  * If using POSIX threads, allocate a buffer for readdir_r().
184  */
185 #ifdef USE_READDIR_R
186   {
187     size_t size;
188     int name_max = pathconf(path, _PC_NAME_MAX);
189 #ifdef NAME_MAX
190     if(name_max < 0)
191       name_max = NAME_MAX;
192 #endif
193     if(name_max < 0) {
194       if(errmsg) {
195 	_err_record_msg(dr->err, "Unable to deduce readdir() buffer size.",
196 			END_ERR_MSG);
197 	*errmsg = _err_get_msg(dr->err);
198       };
199       closedir(dir);
200       return 1;
201     };
202 /*
203  * How big a buffer do we need to allocate?
204  */
205     size = sizeof(struct dirent) + name_max;
206 /*
207  * Extend the buffer?
208  */
209     if(size > dr->buffer_dim || !dr->buffer) {
210       struct dirent *buffer = (struct dirent *) (dr->buffer ?
211 						 realloc(dr->buffer, size) :
212 						 malloc(size));
213       if(!buffer) {
214 	if(errmsg) {
215 	  _err_record_msg(dr->err, "Insufficient memory for readdir() buffer.",
216 			  END_ERR_MSG);
217 	  *errmsg = _err_get_msg(dr->err);
218 	};
219 	closedir(dir);
220 	errno = ENOMEM;
221 	return 1;
222       };
223       dr->buffer = buffer;
224       dr->buffer_dim = size;
225     };
226   };
227 #endif
228 /*
229  * Record the successfully opened directory.
230  */
231   dr->dir = dir;
232   return 0;
233 }
234 
235 /*.......................................................................
236  * If the DirReader object is currently contains an open directory,
237  * close it.
238  *
239  * Input:
240  *  dr    DirReader *   The directory reader resource object.
241  */
_dr_close_dir(DirReader * dr)242 void _dr_close_dir(DirReader *dr)
243 {
244   if(dr && dr->dir) {
245     closedir(dr->dir);
246     dr->dir = NULL;
247     dr->file = NULL;
248     _err_clear_msg(dr->err);
249   };
250 }
251 
252 /*.......................................................................
253  * Read the next file from the directory opened with _dr_open_dir().
254  *
255  * Input:
256  *  dr    DirReader *  The directory reader resource object.
257  * Output:
258  *  return     char *  The name of the new file, or NULL if we reached
259  *                     the end of the directory.
260  */
_dr_next_file(DirReader * dr)261 char *_dr_next_file(DirReader *dr)
262 {
263 /*
264  * Are we currently reading a directory?
265  */
266   if(dr->dir) {
267 /*
268  * Read the next directory entry.
269  */
270 #ifdef USE_READDIR_R
271     if(readdir_r(dr->dir, dr->buffer, &dr->file) == 0 && dr->file)
272       return dr->file->d_name;
273 #else
274     dr->file = readdir(dr->dir);
275     if(dr->file)
276       return dr->file->d_name;
277 #endif
278   };
279 /*
280  * When the end of a directory is reached, close it.
281  */
282   _dr_close_dir(dr);
283   return NULL;
284 }
285 
286 /*.......................................................................
287  * Return 1 if the specified pathname refers to a directory.
288  *
289  * Input:
290  *  pathname  const char *  The path to test.
291  * Output:
292  *  return           int    0 - Not a directory.
293  *                          1 - pathname[] refers to a directory.
294  */
_dr_path_is_dir(const char * pathname)295 static int _dr_path_is_dir(const char *pathname)
296 {
297   struct stat statbuf;    /* The file-statistics return buffer */
298 /*
299  * Look up the file attributes.
300  */
301   if(stat(pathname, &statbuf) < 0)
302     return 0;
303 /*
304  * Is the file a directory?
305  */
306   return S_ISDIR(statbuf.st_mode) != 0;
307 }
308 
309 #endif  /* ifndef WITHOUT_FILE_SYSTEM */
310