1 /*        $NetBSD: scan_dir.c,v 1.2 2017/02/14 01:16:49 christos Exp $          */
2 
3 /*++
4 /* NAME
5 /*        scan_dir 3
6 /* SUMMARY
7 /*        directory scanning
8 /* SYNOPSIS
9 /*        #include <scan_dir.h>
10 /*
11 /*        SCAN_DIR *scan_dir_open(path)
12 /*        const char *path;
13 /*
14 /*        char      *scan_dir_next(scan)
15 /*        SCAN_DIR *scan;
16 /*
17 /*        char      *scan_dir_path(scan)
18 /*        SCAN_DIR *scan;
19 /*
20 /*        void      scan_push(scan, entry)
21 /*        SCAN_DIR *scan;
22 /*        const char *entry;
23 /*
24 /*        SCAN_DIR *scan_pop(scan)
25 /*        SCAN_DIR *scan;
26 /*
27 /*        SCAN_DIR *scan_dir_close(scan)
28 /*        SCAN_DIR *scan;
29 /* DESCRIPTION
30 /*        These functions scan directories for names. The "." and
31 /*        ".." names are skipped. Essentially, this is <dirent>
32 /*        extended with error handling and with knowledge of the
33 /*        name of the directory being scanned.
34 /*
35 /*        scan_dir_open() opens the named directory and
36 /*        returns a handle for subsequent use.
37 /*
38 /*        scan_dir_close() terminates the directory scan, cleans up
39 /*        and returns a null pointer.
40 /*
41 /*        scan_dir_next() returns the next requested object in the specified
42 /*        directory. It skips the "." and ".." entries.
43 /*
44 /*        scan_dir_path() returns the name of the directory being scanned.
45 /*
46 /*        scan_dir_push() causes the specified directory scan to enter the
47 /*        named subdirectory.
48 /*
49 /*        scan_dir_pop() leaves the directory being scanned and returns
50 /*        to the previous one. The result is the argument, null if no
51 /*        previous directory information is available.
52 /* DIAGNOSTICS
53 /*        All errors are fatal.
54 /* LICENSE
55 /* .ad
56 /* .fi
57 /*        The Secure Mailer license must be distributed with this software.
58 /* AUTHOR(S)
59 /*        Wietse Venema
60 /*        IBM T.J. Watson Research
61 /*        P.O. Box 704
62 /*        Yorktown Heights, NY 10598, USA
63 /*
64 /*        Wietse Venema
65 /*        Google, Inc.
66 /*        111 8th Avenue
67 /*        New York, NY 10011, USA
68 /*--*/
69 
70 /* System library. */
71 
72 #include <sys_defs.h>
73 #ifdef HAVE_DIRENT_H
74 #include <dirent.h>
75 #else
76 #define dirent direct
77 #ifdef HAVE_SYS_NDIR_H
78 #include <sys/ndir.h>
79 #endif
80 #ifdef HAVE_SYS_DIR_H
81 #include <sys/dir.h>
82 #endif
83 #ifdef HAVE_NDIR_H
84 #include <ndir.h>
85 #endif
86 #endif
87 #include <string.h>
88 #include <errno.h>
89 
90 /* Utility library. */
91 
92 #include "msg.h"
93 #include "mymalloc.h"
94 #include "stringops.h"
95 #include "vstring.h"
96 #include "scan_dir.h"
97 
98  /*
99   * The interface is based on an opaque structure, so we don't have to expose
100   * the user to the guts. Subdirectory info sits in front of parent directory
101   * info: a simple last-in, first-out list.
102   */
103 typedef struct SCAN_INFO SCAN_INFO;
104 
105 struct SCAN_INFO {
106     char   *path;                       /* directory name */
107     DIR    *dir;                        /* directory structure */
108     SCAN_INFO *parent;                            /* linkage */
109 };
110 struct SCAN_DIR {
111     SCAN_INFO *current;                           /* current scan */
112 };
113 
114 #define SCAN_DIR_PATH(scan)   (scan->current->path)
115 #define STR(x)                          vstring_str(x)
116 
117 /* scan_dir_path - return the path of the directory being read.  */
118 
scan_dir_path(SCAN_DIR * scan)119 char   *scan_dir_path(SCAN_DIR *scan)
120 {
121     return (SCAN_DIR_PATH(scan));
122 }
123 
124 /* scan_dir_push - enter directory */
125 
scan_dir_push(SCAN_DIR * scan,const char * path)126 void    scan_dir_push(SCAN_DIR *scan, const char *path)
127 {
128     const char *myname = "scan_dir_push";
129     SCAN_INFO *info;
130 
131     info = (SCAN_INFO *) mymalloc(sizeof(*info));
132     if (scan->current)
133           info->path = concatenate(SCAN_DIR_PATH(scan), "/", path, (char *) 0);
134     else
135           info->path = mystrdup(path);
136     if ((info->dir = opendir(info->path)) == 0)
137           msg_fatal("%s: open directory %s: %m", myname, info->path);
138     if (msg_verbose > 1)
139           msg_info("%s: open %s", myname, info->path);
140     info->parent = scan->current;
141     scan->current = info;
142 }
143 
144 /* scan_dir_pop - leave directory */
145 
scan_dir_pop(SCAN_DIR * scan)146 SCAN_DIR *scan_dir_pop(SCAN_DIR *scan)
147 {
148     const char *myname = "scan_dir_pop";
149     SCAN_INFO *info = scan->current;
150     SCAN_INFO *parent;
151 
152     if (info == 0)
153           return (0);
154     parent = info->parent;
155     if (closedir(info->dir))
156           msg_fatal("%s: close directory %s: %m", myname, info->path);
157     if (msg_verbose > 1)
158           msg_info("%s: close %s", myname, info->path);
159     myfree(info->path);
160     myfree((void *) info);
161     scan->current = parent;
162     return (parent ? scan : 0);
163 }
164 
165 /* scan_dir_open - start directory scan */
166 
scan_dir_open(const char * path)167 SCAN_DIR *scan_dir_open(const char *path)
168 {
169     SCAN_DIR *scan;
170 
171     scan = (SCAN_DIR *) mymalloc(sizeof(*scan));
172     scan->current = 0;
173     scan_dir_push(scan, path);
174     return (scan);
175 }
176 
177 /* scan_dir_next - find next entry */
178 
scan_dir_next(SCAN_DIR * scan)179 char   *scan_dir_next(SCAN_DIR *scan)
180 {
181     const char *myname = "scan_dir_next";
182     SCAN_INFO *info = scan->current;
183     struct dirent *dp;
184 
185 #define STREQ(x,y)  (strcmp((x),(y)) == 0)
186 
187     if (info) {
188 
189           /*
190            * Fix 20150421: readdir() does not reset errno after reaching the
191            * end-of-directory. This dates back all the way to the initial
192            * implementation of 19970309.
193            */
194           errno = 0;
195           while ((dp = readdir(info->dir)) != 0) {
196               if (STREQ(dp->d_name, ".") || STREQ(dp->d_name, "..")) {
197                     if (msg_verbose > 1)
198                         msg_info("%s: skip %s", myname, dp->d_name);
199                     continue;
200               } else {
201                     if (msg_verbose > 1)
202                         msg_info("%s: found %s", myname, dp->d_name);
203                     return (dp->d_name);
204               }
205           }
206     }
207     return (0);
208 }
209 
210 /* scan_dir_close - terminate directory scan */
211 
scan_dir_close(SCAN_DIR * scan)212 SCAN_DIR *scan_dir_close(SCAN_DIR *scan)
213 {
214     while (scan->current)
215           scan_dir_pop(scan);
216     myfree((void *) scan);
217     return (0);
218 }
219