1/* savedirinfo.c -- Save the list of files in a directory, with additional information.
2
3   Copyright 1990, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005 Free
4   Software Foundation, Inc.
5
6   This program is free software: you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation, either version 3 of the License, or
9   (at your option) any later version.
10
11   This program is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with this program.  If not, see <http://www.gnu.org/licenses/>.
18*/
19/* Written by James Youngman, <jay@gnu.org>. */
20/* Derived from savedir.c, written by David MacKenzie <djm@gnu.org>. */
21
22#if HAVE_CONFIG_H
23# include <config.h>
24#endif
25
26#if HAVE_SYS_STAT_H
27# include <sys/stat.h>
28#endif
29
30#if HAVE_SYS_TYPES_H
31# include <sys/types.h>
32#endif
33
34/* The presence of unistd.h is assumed by gnulib these days, so we
35 * might as well assume it too.
36 */
37#include <unistd.h>
38
39#include <errno.h>
40
41#if HAVE_DIRENT_H
42# include <dirent.h>
43#else
44# define dirent direct
45# if HAVE_SYS_NDIR_H
46#  include <sys/ndir.h>
47# endif
48# if HAVE_SYS_DIR_H
49#  include <sys/dir.h>
50# endif
51# if HAVE_NDIR_H
52#  include <ndir.h>
53# endif
54#endif
55
56#ifdef CLOSEDIR_VOID
57/* Fake a return value. */
58# define CLOSEDIR(d) (closedir (d), 0)
59#else
60# define CLOSEDIR(d) closedir (d)
61#endif
62
63#include <stddef.h>
64#include <stdlib.h>
65#include <string.h>
66
67#include "xalloc.h"
68#include "extendbuf.h"
69#include "savedirinfo.h"
70
71/* In order to use struct dirent.d_type, it has to be enabled on the
72 * configure command line, and we have to have a d_type member in
73 * 'struct dirent'.
74 */
75#if !defined(USE_STRUCT_DIRENT_D_TYPE)
76/* Not enabled, hence pretend it is absent. */
77#undef HAVE_STRUCT_DIRENT_D_TYPE
78#endif
79#if !defined(HAVE_STRUCT_DIRENT_D_TYPE)
80/* Not present, so cannot use it. */
81#undef USE_STRUCT_DIRENT_D_TYPE
82#endif
83
84
85#if defined(HAVE_STRUCT_DIRENT_D_TYPE) && defined(USE_STRUCT_DIRENT_D_TYPE)
86/* Convert the value of struct dirent.d_type into a value for
87 * struct stat.st_mode (at least the file type bits), or zero
88 * if the type is DT_UNKNOWN or is a value we don't know about.
89 */
90static mode_t
91type_to_mode(unsigned type)
92{
93  switch (type)
94    {
95#ifdef DT_FIFO
96    case DT_FIFO: return S_IFIFO;
97#endif
98#ifdef DT_CHR
99    case DT_CHR:  return S_IFCHR;
100#endif
101#ifdef DT_DIR
102    case DT_DIR:  return S_IFDIR;
103#endif
104#ifdef DT_BLK
105    case DT_BLK:  return S_IFBLK;
106#endif
107#ifdef DT_REG
108    case DT_REG:  return S_IFREG;
109#endif
110#ifdef DT_LNK
111    case DT_LNK:  return S_IFLNK;
112#endif
113#ifdef DT_SOCK
114    case DT_SOCK: return S_IFSOCK;
115#endif
116    default:
117      return 0;			/* Unknown. */
118    }
119}
120
121#endif
122
123struct new_savedir_direntry_internal
124{
125  int    flags;			/* from SaveDirDataFlags */
126  mode_t type_info;
127  size_t buffer_offset;
128};
129
130
131
132static int
133savedir_cmp(const void *p1, const void *p2)
134{
135  const struct savedir_direntry *de1, *de2;
136  de1 = p1;
137  de2 = p2;
138  return strcmp(de1->name, de2->name); /* POSIX order, not locale order. */
139}
140
141
142static struct savedir_direntry*
143convertentries(const struct savedir_dirinfo *info,
144	       struct new_savedir_direntry_internal *internal)
145{
146  char *p = info->buffer;
147  struct savedir_direntry *result;
148  int n =info->size;
149  int i;
150
151
152  result = xmalloc(sizeof(*result) * info->size);
153
154  for (i=0; i<n; ++i)
155    {
156      result[i].flags = internal[i].flags;
157      result[i].type_info = internal[i].type_info;
158      result[i].name = &p[internal[i].buffer_offset];
159    }
160  return result;
161}
162
163
164struct savedir_dirinfo *
165xsavedir(const char *dir, int flags)
166{
167  DIR *dirp;
168  struct dirent *dp;
169  struct savedir_dirinfo *result = NULL;
170  struct new_savedir_direntry_internal *internal;
171
172  size_t namebuf_allocated = 0u, namebuf_used = 0u;
173  size_t entrybuf_allocated = 0u;
174  int save_errno;
175
176  dirp = opendir (dir);
177  if (dirp == NULL)
178    return NULL;
179
180  errno = 0;
181  result = xmalloc(sizeof(*result));
182  result->buffer = NULL;
183  result->size = 0u;
184  result->entries = NULL;
185  internal = NULL;
186
187  while ((dp = readdir (dirp)) != NULL)
188    {
189      /* Skip "", ".", and "..".  "" is returned by at least one buggy
190         implementation: Solaris 2.4 readdir on NFS file systems.  */
191      char const *entry = dp->d_name;
192      if (entry[entry[0] != '.' ? 0 : entry[1] != '.' ? 1 : 2] != '\0')
193	{
194	  /* Remember the name. */
195	  size_t entry_size = strlen (entry) + 1;
196	  result->buffer = extendbuf(result->buffer, namebuf_used+entry_size, &namebuf_allocated);
197	  memcpy ((result->buffer) + namebuf_used, entry, entry_size);
198
199	  /* Remember the other stuff. */
200	  internal = extendbuf(internal, (1+result->size)*sizeof(*internal), &entrybuf_allocated);
201	  internal[result->size].flags = 0;
202
203#if defined(HAVE_STRUCT_DIRENT_D_TYPE) && defined(USE_STRUCT_DIRENT_D_TYPE)
204	  internal[result->size].type_info = type_to_mode(dp->d_type);
205	  if (dp->d_type != DT_UNKNOWN)
206	    internal[result->size].flags |= SavedirHaveFileType;
207#else
208	  internal[result->size].type_info = 0;
209#endif
210	  internal[result->size].buffer_offset = namebuf_used;
211
212	  /* Prepare for the next iteration */
213	  ++(result->size);
214	  namebuf_used += entry_size;
215	}
216    }
217
218  result->buffer = extendbuf(result->buffer, namebuf_used+1, &namebuf_allocated);
219  result->buffer[namebuf_used] = '\0';
220
221  /* convert the result to its externally-usable form. */
222  result->entries = convertentries(result, internal);
223  free(internal);
224  internal = NULL;
225
226
227  if (flags & SavedirSort)
228    {
229      qsort(result->entries,
230	    result->size, sizeof(*result->entries),
231	    savedir_cmp);
232    }
233
234
235  save_errno = errno;
236  if (CLOSEDIR (dirp) != 0)
237    save_errno = errno;
238  if (save_errno != 0)
239    {
240      free (result->buffer);
241      free (result);
242      errno = save_errno;
243      return NULL;
244    }
245
246  return result;
247}
248
249void free_dirinfo(struct savedir_dirinfo *p)
250{
251  free(p->entries);
252  p->entries = NULL;
253  free(p->buffer);
254  p->buffer = NULL;
255  free(p);
256}
257
258
259
260static char *
261new_savedirinfo (const char *dir, struct savedir_extrainfo **extra)
262{
263  struct savedir_dirinfo *p = xsavedir(dir, SavedirSort);
264  char *buf, *s;
265  size_t bufbytes = 0;
266  int i;
267
268  if (p)
269    {
270      struct savedir_extrainfo *pex = xmalloc(p->size * sizeof(*extra));
271      for (i=0; i<p->size; ++i)
272	{
273	  bufbytes += strlen(p->entries[i].name);
274	  ++bufbytes;		/* the \0 */
275
276	  pex[i].type_info = p->entries[i].type_info;
277	}
278
279      s = buf = xmalloc(bufbytes+1);
280      for (i=0; i<p->size; ++i)
281	{
282	  size_t len = strlen(p->entries[i].name);
283	  memcpy(s, p->entries[i].name, len);
284	  s += len;
285	  *s = 0;		/* Place a NUL */
286	  ++s;			/* Skip the NUL. */
287	}
288      *s = 0;			/* final (doubled) terminating NUL */
289
290      if (extra)
291	*extra = pex;
292      else
293	free (pex);
294      return buf;
295    }
296  else
297    {
298      return NULL;
299    }
300}
301
302
303#if 0
304/* Return a freshly allocated string containing the filenames
305   in directory DIR, separated by '\0' characters;
306   the end is marked by two '\0' characters in a row.
307   Return NULL (setting errno) if DIR cannot be opened, read, or closed.  */
308
309static char *
310old_savedirinfo (const char *dir, struct savedir_extrainfo **extra)
311{
312  DIR *dirp;
313  struct dirent *dp;
314  char *name_space;
315  size_t namebuf_allocated = 0u, namebuf_used = 0u;
316#if defined(HAVE_STRUCT_DIRENT_D_TYPE) && defined(USE_STRUCT_DIRENT_D_TYPE)
317  size_t extra_allocated = 0u, extra_used = 0u;
318  struct savedir_extrainfo *info = NULL;
319#endif
320  int save_errno;
321
322  if (extra)
323    *extra = NULL;
324
325  dirp = opendir (dir);
326  if (dirp == NULL)
327    return NULL;
328
329  errno = 0;
330  name_space = NULL;
331  while ((dp = readdir (dirp)) != NULL)
332    {
333      /* Skip "", ".", and "..".  "" is returned by at least one buggy
334         implementation: Solaris 2.4 readdir on NFS file systems.  */
335      char const *entry = dp->d_name;
336      if (entry[entry[0] != '.' ? 0 : entry[1] != '.' ? 1 : 2] != '\0')
337	{
338	  /* Remember the name. */
339	  size_t entry_size = strlen (entry) + 1;
340	  name_space = extendbuf(name_space, namebuf_used+entry_size, &namebuf_allocated);
341	  memcpy (name_space + namebuf_used, entry, entry_size);
342	  namebuf_used += entry_size;
343
344
345#if defined(HAVE_STRUCT_DIRENT_D_TYPE) && defined(USE_STRUCT_DIRENT_D_TYPE)
346	  /* Remember the type. */
347	  if (extra)
348	    {
349	      info = extendbuf(info,
350			       (extra_used+1) * sizeof(struct savedir_dirinfo),
351			       &extra_allocated);
352	      info[extra_used].type_info = type_to_mode(dp->d_type);
353	      ++extra_used;
354	    }
355#endif
356	}
357    }
358
359  name_space = extendbuf(name_space, namebuf_used+1, &namebuf_allocated);
360  name_space[namebuf_used] = '\0';
361
362  save_errno = errno;
363  if (CLOSEDIR (dirp) != 0)
364    save_errno = errno;
365  if (save_errno != 0)
366    {
367      free (name_space);
368      errno = save_errno;
369      return NULL;
370    }
371
372#if defined(HAVE_STRUCT_DIRENT_D_TYPE) && defined(USE_STRUCT_DIRENT_D_TYPE)
373  if (extra && info)
374    *extra = info;
375#endif
376
377  return name_space;
378}
379#endif
380
381
382char *
383savedirinfo (const char *dir, struct savedir_extrainfo **extra)
384{
385  return new_savedirinfo(dir, extra);
386}
387