1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements.  See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License.  You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "apr_arch_file_io.h"
18#include "apr_strings.h"
19#include "apr_portable.h"
20#if APR_HAVE_SYS_SYSLIMITS_H
21#include <sys/syslimits.h>
22#endif
23#if APR_HAVE_LIMITS_H
24#include <limits.h>
25#endif
26
27#ifdef __DARWIN_APR_BUILDING_32_BIT_INODE
28__private_extern__ apr_status_t dir_cleanup(void *thedir);
29#else /* !__DARWIN_APR_BUILDING_32_BIT_INODE */
30__private_extern__ apr_status_t dir_cleanup(void *thedir)
31{
32    apr_dir_t *dir = thedir;
33    if (closedir(dir->dirstruct) == 0) {
34        return APR_SUCCESS;
35    }
36    else {
37        return errno;
38    }
39}
40
41#define PATH_SEPARATOR '/'
42
43/* Remove trailing separators that don't affect the meaning of PATH. */
44static const char *path_canonicalize (const char *path, apr_pool_t *pool)
45{
46    /* At some point this could eliminate redundant components.  For
47     * now, it just makes sure there is no trailing slash. */
48    apr_size_t len = strlen (path);
49    apr_size_t orig_len = len;
50
51    while ((len > 0) && (path[len - 1] == PATH_SEPARATOR))
52        len--;
53
54    if (len != orig_len)
55        return apr_pstrndup (pool, path, len);
56    else
57        return path;
58}
59
60/* Remove one component off the end of PATH. */
61static char *path_remove_last_component (const char *path, apr_pool_t *pool)
62{
63    const char *newpath = path_canonicalize (path, pool);
64    int i;
65
66    for (i = (strlen(newpath) - 1); i >= 0; i--) {
67        if (path[i] == PATH_SEPARATOR)
68            break;
69    }
70
71    return apr_pstrndup (pool, path, (i < 0) ? 0 : i);
72}
73#endif /* !__DARWIN_APR_BUILDING_32_BIT_INODE */
74
75apr_status_t apr_dir_open(apr_dir_t **new, const char *dirname,
76                          apr_pool_t *pool)
77{
78    /* On some platforms (e.g., Linux+GNU libc), d_name[] in struct
79     * dirent is declared with enough storage for the name.  On other
80     * platforms (e.g., Solaris 8 for Intel), d_name is declared as a
81     * one-byte array.  Note: gcc evaluates this at compile time.
82     */
83    apr_size_t dirent_size =
84        sizeof(*(*new)->entry) +
85        (sizeof((*new)->entry->d_name) > 1 ? 0 : 255);
86    DIR *dir = opendir(dirname);
87
88    if (!dir) {
89        return errno;
90    }
91
92    (*new) = (apr_dir_t *)apr_palloc(pool, sizeof(apr_dir_t));
93
94    (*new)->pool = pool;
95    (*new)->dirname = apr_pstrdup(pool, dirname);
96    (*new)->dirstruct = dir;
97    (*new)->entry = apr_pcalloc(pool, dirent_size);
98
99    apr_pool_cleanup_register((*new)->pool, *new, dir_cleanup,
100                              apr_pool_cleanup_null);
101    return APR_SUCCESS;
102}
103
104#ifndef __DARWIN_APR_BUILDING_32_BIT_INODE
105apr_status_t apr_dir_close(apr_dir_t *thedir)
106{
107    return apr_pool_cleanup_run(thedir->pool, thedir, dir_cleanup);
108}
109#endif /* !__DARWIN_APR_BUILDING_32_BIT_INODE */
110
111#ifdef DIRENT_TYPE
112#ifdef __DARWIN_APR_BUILDING_32_BIT_INODE
113__private_extern__ apr_filetype_e filetype_from_dirent_type(int type);
114#else /* !__DARWIN_APR_BUILDING_32_BIT_INODE */
115__private_extern__ apr_filetype_e filetype_from_dirent_type(int type)
116{
117    switch (type) {
118    case DT_REG:
119        return APR_REG;
120    case DT_DIR:
121        return APR_DIR;
122    case DT_LNK:
123        return APR_LNK;
124    case DT_CHR:
125        return APR_CHR;
126    case DT_BLK:
127        return APR_BLK;
128#if defined(DT_FIFO)
129    case DT_FIFO:
130        return APR_PIPE;
131#endif
132#if !defined(BEOS) && defined(DT_SOCK)
133    case DT_SOCK:
134        return APR_SOCK;
135#endif
136    default:
137        return APR_UNKFILE;
138    }
139}
140#endif /* !__DARWIN_APR_BUILDING_32_BIT_INODE */
141#endif
142
143apr_status_t apr_dir_read(apr_finfo_t *finfo, apr_int32_t wanted,
144                          apr_dir_t *thedir)
145{
146    apr_status_t ret = 0;
147#ifdef DIRENT_TYPE
148    apr_filetype_e type;
149#endif
150#if APR_HAS_THREADS && defined(_POSIX_THREAD_SAFE_FUNCTIONS) \
151                    && !defined(READDIR_IS_THREAD_SAFE)
152#ifdef APR_USE_READDIR64_R
153    struct dirent64 *retent;
154
155    /* If LFS is enabled and readdir64_r is available, readdir64_r is
156     * used in preference to readdir_r.  This allows directories to be
157     * read which contain a (64-bit) inode number which doesn't fit
158     * into the 32-bit apr_ino_t, iff the caller doesn't actually care
159     * about the inode number (i.e. wanted & APR_FINFO_INODE == 0).
160     * (such inodes may be seen in some wonky NFS environments)
161     *
162     * Similarly, if the d_off field cannot be reprented in a 32-bit
163     * offset, the libc readdir_r() would barf; using readdir64_r
164     * bypasses that case entirely since APR does not care about
165     * d_off. */
166
167    ret = readdir64_r(thedir->dirstruct, thedir->entry, &retent);
168#else
169
170    struct dirent *retent;
171
172    ret = readdir_r(thedir->dirstruct, thedir->entry, &retent);
173#endif
174
175    /* POSIX treats "end of directory" as a non-error case, so ret
176     * will be zero and retent will be set to NULL in that case. */
177    if (!ret && retent == NULL) {
178        ret = APR_ENOENT;
179    }
180
181    /* Solaris is a bit strange, if there are no more entries in the
182     * directory, it returns EINVAL.  Since this is against POSIX, we
183     * hack around the problem here.  EINVAL is possible from other
184     * readdir implementations, but only if the result buffer is too small.
185     * since we control the size of that buffer, we should never have
186     * that problem.
187     */
188    if (ret == EINVAL) {
189        ret = APR_ENOENT;
190    }
191#else
192    /* We're about to call a non-thread-safe readdir() that may
193       possibly set `errno', and the logic below actually cares about
194       errno after the call.  Therefore we need to clear errno first. */
195    errno = 0;
196    thedir->entry = readdir(thedir->dirstruct);
197    if (thedir->entry == NULL) {
198        /* If NULL was returned, this can NEVER be a success. Can it?! */
199        if (errno == APR_SUCCESS) {
200            ret = APR_ENOENT;
201        }
202        else
203            ret = errno;
204    }
205#endif
206
207    /* No valid bit flag to test here - do we want one? */
208    finfo->fname = NULL;
209
210    if (ret) {
211        finfo->valid = 0;
212        return ret;
213    }
214
215#ifdef DIRENT_TYPE
216    type = filetype_from_dirent_type(thedir->entry->DIRENT_TYPE);
217    if (type != APR_UNKFILE) {
218        wanted &= ~APR_FINFO_TYPE;
219    }
220#endif
221#ifdef DIRENT_INODE
222    if (thedir->entry->DIRENT_INODE && thedir->entry->DIRENT_INODE != -1) {
223#ifdef APR_USE_READDIR64_R
224        /* If readdir64_r is used, check for the overflow case of trying
225         * to fit a 64-bit integer into a 32-bit integer. */
226        if (sizeof(apr_ino_t) >= sizeof(retent->DIRENT_INODE)
227            || (apr_ino_t)retent->DIRENT_INODE == retent->DIRENT_INODE) {
228            wanted &= ~APR_FINFO_INODE;
229        } else {
230            /* Prevent the fallback code below from filling in the
231             * inode if the stat call fails. */
232            retent->DIRENT_INODE = 0;
233        }
234#else
235        wanted &= ~APR_FINFO_INODE;
236#endif /* APR_USE_READDIR64_R */
237    }
238#endif /* DIRENT_INODE */
239
240    wanted &= ~APR_FINFO_NAME;
241
242    if (wanted)
243    {
244        char fspec[APR_PATH_MAX];
245        char *end;
246
247        end = apr_cpystrn(fspec, thedir->dirname, sizeof fspec);
248
249        if (end > fspec && end[-1] != '/' && (end < fspec + APR_PATH_MAX))
250            *end++ = '/';
251
252        apr_cpystrn(end, thedir->entry->d_name,
253                    sizeof fspec - (end - fspec));
254
255        ret = apr_stat(finfo, fspec, APR_FINFO_LINK | wanted, thedir->pool);
256        /* We passed a stack name that will disappear */
257        finfo->fname = NULL;
258    }
259
260    if (wanted && (ret == APR_SUCCESS || ret == APR_INCOMPLETE)) {
261        wanted &= ~finfo->valid;
262    }
263    else {
264        /* We don't bail because we fail to stat, when we are only -required-
265         * to readdir... but the result will be APR_INCOMPLETE
266         */
267        finfo->pool = thedir->pool;
268        finfo->valid = 0;
269#ifdef DIRENT_TYPE
270        if (type != APR_UNKFILE) {
271            finfo->filetype = type;
272            finfo->valid |= APR_FINFO_TYPE;
273        }
274#endif
275#ifdef DIRENT_INODE
276        if (thedir->entry->DIRENT_INODE && thedir->entry->DIRENT_INODE != -1) {
277            finfo->inode = thedir->entry->DIRENT_INODE;
278            finfo->valid |= APR_FINFO_INODE;
279        }
280#endif
281    }
282
283    finfo->name = apr_pstrdup(thedir->pool, thedir->entry->d_name);
284    finfo->valid |= APR_FINFO_NAME;
285
286    if (wanted)
287        return APR_INCOMPLETE;
288
289    return APR_SUCCESS;
290}
291
292apr_status_t apr_dir_rewind(apr_dir_t *thedir)
293{
294    rewinddir(thedir->dirstruct);
295    return APR_SUCCESS;
296}
297
298#ifndef __DARWIN_APR_BUILDING_32_BIT_INODE
299apr_status_t apr_dir_make(const char *path, apr_fileperms_t perm,
300                          apr_pool_t *pool)
301{
302    mode_t mode = apr_unix_perms2mode(perm);
303
304    if (mkdir(path, mode) == 0) {
305        return APR_SUCCESS;
306    }
307    else {
308        return errno;
309    }
310}
311
312apr_status_t apr_dir_make_recursive(const char *path, apr_fileperms_t perm,
313                                           apr_pool_t *pool)
314{
315    apr_status_t apr_err = 0;
316
317    apr_err = apr_dir_make (path, perm, pool); /* Try to make PATH right out */
318
319    if (apr_err == ENOENT) { /* Missing an intermediate dir */
320        char *dir;
321
322        dir = path_remove_last_component(path, pool);
323        /* If there is no path left, give up. */
324        if (dir[0] == '\0') {
325            return apr_err;
326        }
327
328        apr_err = apr_dir_make_recursive(dir, perm, pool);
329
330        if (!apr_err)
331            apr_err = apr_dir_make (path, perm, pool);
332    }
333
334    /*
335     * It's OK if PATH exists. Timing issues can lead to the second
336     * apr_dir_make being called on existing dir, therefore this check
337     * has to come last.
338     */
339    if (APR_STATUS_IS_EEXIST(apr_err))
340        return APR_SUCCESS;
341
342    return apr_err;
343}
344
345apr_status_t apr_dir_remove(const char *path, apr_pool_t *pool)
346{
347    if (rmdir(path) == 0) {
348        return APR_SUCCESS;
349    }
350    else {
351        return errno;
352    }
353}
354
355apr_status_t apr_os_dir_get(apr_os_dir_t **thedir, apr_dir_t *dir)
356{
357    if (dir == NULL) {
358        return APR_ENODIR;
359    }
360    *thedir = dir->dirstruct;
361    return APR_SUCCESS;
362}
363
364apr_status_t apr_os_dir_put(apr_dir_t **dir, apr_os_dir_t *thedir,
365                          apr_pool_t *pool)
366{
367    if ((*dir) == NULL) {
368        (*dir) = (apr_dir_t *)apr_pcalloc(pool, sizeof(apr_dir_t));
369        (*dir)->pool = pool;
370    }
371    (*dir)->dirstruct = thedir;
372    return APR_SUCCESS;
373}
374
375#endif /* !__DARWIN_APR_BUILDING_32_BIT_INODE */
376