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 #pragma ident	"%Z%%M%	%I%	%E% SMI"
33 
34 /*
35  * If file-system access is to be excluded, this module has no function,
36  * so all of its code should be excluded.
37  */
38 #ifndef WITHOUT_FILE_SYSTEM
39 
40 /*
41  * Standard includes.
42  */
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <errno.h>
47 
48 /*
49  * Operating system includes.
50  */
51 #include <unistd.h>
52 #include <sys/types.h>
53 #include <sys/stat.h>
54 #include <dirent.h>
55 
56 #include "direader.h"
57 #include "errmsg.h"
58 
59 /*
60  * Use the reentrant POSIX threads version of readdir()?
61  */
62 #if defined(PREFER_REENTRANT) && defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199506L
63 #define USE_READDIR_R 1
64 #endif
65 
66 /*
67  * Objects of the following type are used to maintain the resources
68  * needed to read directories.
69  */
70 struct DirReader {
71   ErrMsg *err;             /* The error reporting buffer */
72   DIR *dir;                /* The directory stream (if open, NULL otherwise) */
73   struct dirent *file;     /* The latest directory entry */
74 #ifdef USE_READDIR_R
75   struct dirent *buffer;   /* A buffer used by the threaded version of */
76                            /*  readdir() */
77   int buffer_dim;          /* The allocated size of buffer[] */
78 #endif
79 };
80 
81 static int _dr_path_is_dir(const char *pathname);
82 
83 /*.......................................................................
84  * Create a new DirReader object.
85  *
86  * Output:
87  *  return  DirReader *  The new object, or NULL on error.
88  */
89 DirReader *_new_DirReader(void)
90 {
91   DirReader *dr;  /* The object to be returned */
92 /*
93  * Allocate the container.
94  */
95   dr = (DirReader *) malloc(sizeof(DirReader));
96   if(!dr) {
97     errno = ENOMEM;
98     return NULL;
99   };
100 /*
101  * Before attempting any operation that might fail, initialize the
102  * container at least up to the point at which it can safely be passed
103  * to _del_DirReader().
104  */
105   dr->err = NULL;
106   dr->dir = NULL;
107   dr->file = NULL;
108 #ifdef USE_READDIR_R
109   dr->buffer = NULL;
110   dr->buffer_dim = 0;
111 #endif
112 /*
113  * Allocate a place to record error messages.
114  */
115   dr->err = _new_ErrMsg();
116   if(!dr->err)
117     return _del_DirReader(dr);
118   return dr;
119 }
120 
121 /*.......................................................................
122  * Delete a DirReader object.
123  *
124  * Input:
125  *  dr     DirReader *  The object to be deleted.
126  * Output:
127  *  return DirReader *  The deleted object (always NULL).
128  */
129 DirReader *_del_DirReader(DirReader *dr)
130 {
131   if(dr) {
132     _dr_close_dir(dr);
133 #ifdef USE_READDIR_R
134     free(dr->buffer);
135 #endif
136     dr->err = _del_ErrMsg(dr->err);
137     free(dr);
138   };
139   return NULL;
140 }
141 
142 /*.......................................................................
143  * Open a new directory.
144  *
145  * Input:
146  *  dr      DirReader *   The directory reader resource object.
147  *  path   const char *   The directory to be opened.
148  * Input/Output:
149  *  errmsg       char **  If an error occurs and errmsg isn't NULL, a
150  *                        pointer to an error description will be assigned
151  *                        to *errmsg.
152  * Output:
153  *  return        int     0 - OK.
154  *                        1 - Error (see *errmsg for a description).
155  */
156 int _dr_open_dir(DirReader *dr, const char *path, char **errmsg)
157 {
158   DIR *dir = NULL;   /* The directory stream */
159 /*
160  * If a directory is already open, close it first.
161  */
162   (void) _dr_close_dir(dr);
163 /*
164  * Is the path a directory?
165  */
166   if(!_dr_path_is_dir(path)) {
167     if(errmsg) {
168       _err_record_msg(dr->err, "Can't open directory: ", path, END_ERR_MSG);
169       *errmsg = _err_get_msg(dr->err);
170     };
171     return 1;
172   };
173 /*
174  * Attempt to open the directory.
175  */
176   dir = opendir(path);
177   if(!dir) {
178     if(errmsg) {
179       _err_record_msg(dr->err, "Can't open directory: ", path, END_ERR_MSG);
180       *errmsg = _err_get_msg(dr->err);
181     };
182     return 1;
183   };
184 /*
185  * If using POSIX threads, allocate a buffer for readdir_r().
186  */
187 #ifdef USE_READDIR_R
188   {
189     size_t size;
190     int name_max = pathconf(path, _PC_NAME_MAX);
191 #ifdef NAME_MAX
192     if(name_max < 0)
193       name_max = NAME_MAX;
194 #endif
195     if(name_max < 0) {
196       if(errmsg) {
197 	_err_record_msg(dr->err, "Unable to deduce readdir() buffer size.",
198 			END_ERR_MSG);
199 	*errmsg = _err_get_msg(dr->err);
200       };
201       closedir(dir);
202       return 1;
203     };
204 /*
205  * How big a buffer do we need to allocate?
206  */
207     size = sizeof(struct dirent) + name_max;
208 /*
209  * Extend the buffer?
210  */
211     if(size > dr->buffer_dim || !dr->buffer) {
212       struct dirent *buffer = (struct dirent *) (dr->buffer ?
213 						 realloc(dr->buffer, size) :
214 						 malloc(size));
215       if(!buffer) {
216 	if(errmsg) {
217 	  _err_record_msg(dr->err, "Insufficient memory for readdir() buffer.",
218 			  END_ERR_MSG);
219 	  *errmsg = _err_get_msg(dr->err);
220 	};
221 	closedir(dir);
222 	errno = ENOMEM;
223 	return 1;
224       };
225       dr->buffer = buffer;
226       dr->buffer_dim = size;
227     };
228   };
229 #endif
230 /*
231  * Record the successfully opened directory.
232  */
233   dr->dir = dir;
234   return 0;
235 }
236 
237 /*.......................................................................
238  * If the DirReader object is currently contains an open directory,
239  * close it.
240  *
241  * Input:
242  *  dr    DirReader *   The directory reader resource object.
243  */
244 void _dr_close_dir(DirReader *dr)
245 {
246   if(dr && dr->dir) {
247     closedir(dr->dir);
248     dr->dir = NULL;
249     dr->file = NULL;
250     _err_clear_msg(dr->err);
251   };
252 }
253 
254 /*.......................................................................
255  * Read the next file from the directory opened with _dr_open_dir().
256  *
257  * Input:
258  *  dr    DirReader *  The directory reader resource object.
259  * Output:
260  *  return     char *  The name of the new file, or NULL if we reached
261  *                     the end of the directory.
262  */
263 char *_dr_next_file(DirReader *dr)
264 {
265 /*
266  * Are we currently reading a directory?
267  */
268   if(dr->dir) {
269 /*
270  * Read the next directory entry.
271  */
272 #ifdef USE_READDIR_R
273     if(readdir_r(dr->dir, dr->buffer, &dr->file) == 0 && dr->file)
274       return dr->file->d_name;
275 #else
276     dr->file = readdir(dr->dir);
277     if(dr->file)
278       return dr->file->d_name;
279 #endif
280   };
281 /*
282  * When the end of a directory is reached, close it.
283  */
284   _dr_close_dir(dr);
285   return NULL;
286 }
287 
288 /*.......................................................................
289  * Return 1 if the specified pathname refers to a directory.
290  *
291  * Input:
292  *  pathname  const char *  The path to test.
293  * Output:
294  *  return           int    0 - Not a directory.
295  *                          1 - pathname[] refers to a directory.
296  */
297 static int _dr_path_is_dir(const char *pathname)
298 {
299   struct stat statbuf;    /* The file-statistics return buffer */
300 /*
301  * Look up the file attributes.
302  */
303   if(stat(pathname, &statbuf) < 0)
304     return 0;
305 /*
306  * Is the file a directory?
307  */
308   return S_ISDIR(statbuf.st_mode) != 0;
309 }
310 
311 #endif  /* ifndef WITHOUT_FILE_SYSTEM */
312