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 */
70struct 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
81static 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 */
89DirReader *_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 */
129DirReader *_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 */
156int _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 */
244void _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 */
263char *_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 */
297static 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