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 "fsio.h"
19#include "nks/dirio.h"
20#include "apr_file_io.h"
21#include "apr_general.h"
22#include "apr_strings.h"
23#include "apr_errno.h"
24#include "apr_hash.h"
25#include "apr_thread_rwlock.h"
26
27#ifdef HAVE_UTIME_H
28#include <utime.h>
29#endif
30
31#define APR_HAS_PSA
32
33static apr_filetype_e filetype_from_mode(mode_t mode)
34{
35    apr_filetype_e type = APR_NOFILE;
36
37    if (S_ISREG(mode))
38        type = APR_REG;
39    else if (S_ISDIR(mode))
40        type = APR_DIR;
41    else if (S_ISCHR(mode))
42        type = APR_CHR;
43    else if (S_ISBLK(mode))
44        type = APR_BLK;
45    else if (S_ISFIFO(mode))
46        type = APR_PIPE;
47    else if (S_ISLNK(mode))
48        type = APR_LNK;
49    else if (S_ISSOCK(mode))
50        type = APR_SOCK;
51    else
52        type = APR_UNKFILE;
53    return type;
54}
55
56static void fill_out_finfo(apr_finfo_t *finfo, struct stat *info,
57                           apr_int32_t wanted)
58{
59    finfo->valid = APR_FINFO_MIN | APR_FINFO_IDENT | APR_FINFO_NLINK
60                    | APR_FINFO_OWNER | APR_FINFO_PROT;
61
62    finfo->protection = apr_unix_mode2perms(info->st_mode);
63    finfo->filetype = filetype_from_mode(info->st_mode);
64    finfo->user = info->st_uid;
65    finfo->group = info->st_gid;
66    finfo->size = info->st_size;
67    finfo->inode = info->st_ino;
68    finfo->device = info->st_dev;
69    finfo->nlink = info->st_nlink;
70
71    apr_time_ansi_put(&finfo->atime, info->st_atime.tv_sec);
72    apr_time_ansi_put(&finfo->mtime, info->st_mtime.tv_sec);
73    apr_time_ansi_put(&finfo->ctime, info->st_ctime.tv_sec);
74
75#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
76#ifdef DEV_BSIZE
77    finfo->csize = (apr_off_t)info->st_blocks * (apr_off_t)DEV_BSIZE;
78#else
79    finfo->csize = (apr_off_t)info->st_blocks * (apr_off_t)512;
80#endif
81    finfo->valid |= APR_FINFO_CSIZE;
82#endif
83}
84
85apr_status_t apr_file_info_get_locked(apr_finfo_t *finfo, apr_int32_t wanted,
86                                      apr_file_t *thefile)
87{
88    struct_stat info;
89
90    if (thefile->buffered) {
91        apr_status_t rv = apr_file_flush_locked(thefile);
92        if (rv != APR_SUCCESS)
93            return rv;
94    }
95
96    if (fstat(thefile->filedes, &info) == 0) {
97        finfo->pool = thefile->pool;
98        finfo->fname = thefile->fname;
99        fill_out_finfo(finfo, &info, wanted);
100        return (wanted & ~finfo->valid) ? APR_INCOMPLETE : APR_SUCCESS;
101    }
102    else {
103        return errno;
104    }
105}
106
107APR_DECLARE(apr_status_t) apr_file_info_get(apr_finfo_t *finfo,
108                                            apr_int32_t wanted,
109                                            apr_file_t *thefile)
110{
111    struct stat info;
112
113    if (thefile->buffered) {
114        /* XXX: flush here is not mutex protected */
115        apr_status_t rv = apr_file_flush(thefile);
116        if (rv != APR_SUCCESS)
117            return rv;
118    }
119
120    if (fstat(thefile->filedes, &info) == 0) {
121        finfo->pool = thefile->pool;
122        finfo->fname = thefile->fname;
123        fill_out_finfo(finfo, &info, wanted);
124        return (wanted & ~finfo->valid) ? APR_INCOMPLETE : APR_SUCCESS;
125    }
126    else {
127        return errno;
128    }
129}
130
131APR_DECLARE(apr_status_t) apr_file_perms_set(const char *fname,
132                                             apr_fileperms_t perms)
133{
134    mode_t mode = apr_unix_perms2mode(perms);
135
136    if (chmod(fname, mode) == -1)
137        return errno;
138    return APR_SUCCESS;
139}
140
141APR_DECLARE(apr_status_t) apr_file_attrs_set(const char *fname,
142                                             apr_fileattrs_t attributes,
143                                             apr_fileattrs_t attr_mask,
144                                             apr_pool_t *pool)
145{
146    apr_status_t status;
147    apr_finfo_t finfo;
148
149    /* Don't do anything if we can't handle the requested attributes */
150    if (!(attr_mask & (APR_FILE_ATTR_READONLY
151                       | APR_FILE_ATTR_EXECUTABLE)))
152        return APR_SUCCESS;
153
154    status = apr_stat(&finfo, fname, APR_FINFO_PROT, pool);
155    if (status)
156        return status;
157
158    /* ### TODO: should added bits be umask'd? */
159    if (attr_mask & APR_FILE_ATTR_READONLY)
160    {
161        if (attributes & APR_FILE_ATTR_READONLY)
162        {
163            finfo.protection &= ~APR_UWRITE;
164            finfo.protection &= ~APR_GWRITE;
165            finfo.protection &= ~APR_WWRITE;
166        }
167        else
168        {
169            /* ### umask this! */
170            finfo.protection |= APR_UWRITE;
171            finfo.protection |= APR_GWRITE;
172            finfo.protection |= APR_WWRITE;
173        }
174    }
175
176    if (attr_mask & APR_FILE_ATTR_EXECUTABLE)
177    {
178        if (attributes & APR_FILE_ATTR_EXECUTABLE)
179        {
180            /* ### umask this! */
181            finfo.protection |= APR_UEXECUTE;
182            finfo.protection |= APR_GEXECUTE;
183            finfo.protection |= APR_WEXECUTE;
184        }
185        else
186        {
187            finfo.protection &= ~APR_UEXECUTE;
188            finfo.protection &= ~APR_GEXECUTE;
189            finfo.protection &= ~APR_WEXECUTE;
190        }
191    }
192
193    return apr_file_perms_set(fname, finfo.protection);
194}
195
196#ifndef APR_HAS_PSA
197static apr_status_t stat_cache_cleanup(void *data)
198{
199    apr_pool_t *p = (apr_pool_t *)getGlobalPool();
200    apr_hash_index_t *hi;
201    apr_hash_t *statCache = (apr_hash_t*)data;
202	char *key;
203    apr_ssize_t keylen;
204    NXPathCtx_t pathctx;
205
206    for (hi = apr_hash_first(p, statCache); hi; hi = apr_hash_next(hi)) {
207        apr_hash_this(hi, (const void**)&key, &keylen, (void**)&pathctx);
208
209        if (pathctx) {
210            NXFreePathContext(pathctx);
211        }
212    }
213
214    return APR_SUCCESS;
215}
216
217int cstat (NXPathCtx_t ctx, char *path, struct stat *buf, unsigned long requestmap, apr_pool_t *p)
218{
219    apr_pool_t *gPool = (apr_pool_t *)getGlobalPool();
220    apr_hash_t *statCache = NULL;
221    apr_thread_rwlock_t *rwlock = NULL;
222
223    NXPathCtx_t pathctx = 0;
224    char *ptr = NULL, *tr;
225    int len = 0, x;
226    char *ppath;
227    char *pinfo;
228
229    if (ctx == 1) {
230
231        /* If there isn't a global pool then just stat the file
232           and return */
233        if (!gPool) {
234            char poolname[50];
235
236            if (apr_pool_create(&gPool, NULL) != APR_SUCCESS) {
237                return getstat(ctx, path, buf, requestmap);
238            }
239
240            setGlobalPool(gPool);
241            apr_pool_tag(gPool, apr_pstrdup(gPool, "cstat_mem_pool"));
242
243            statCache = apr_hash_make(gPool);
244            apr_pool_userdata_set ((void*)statCache, "STAT_CACHE", stat_cache_cleanup, gPool);
245
246            apr_thread_rwlock_create(&rwlock, gPool);
247            apr_pool_userdata_set ((void*)rwlock, "STAT_CACHE_LOCK", apr_pool_cleanup_null, gPool);
248        }
249        else {
250            apr_pool_userdata_get((void**)&statCache, "STAT_CACHE", gPool);
251            apr_pool_userdata_get((void**)&rwlock, "STAT_CACHE_LOCK", gPool);
252        }
253
254        if (!gPool || !statCache || !rwlock) {
255            return getstat(ctx, path, buf, requestmap);
256        }
257
258        for (x = 0,tr = path;*tr != '\0';tr++,x++) {
259            if (*tr == '\\' || *tr == '/') {
260                ptr = tr;
261                len = x;
262            }
263            if (*tr == ':') {
264                ptr = "\\";
265                len = x;
266            }
267        }
268
269        if (ptr) {
270            ppath = apr_pstrndup (p, path, len);
271            strlwr(ppath);
272            if (ptr[1] != '\0') {
273                ptr++;
274            }
275            /* If the path ended in a trailing slash then our result path
276               will be a single slash. To avoid stat'ing the root with a
277               slash, we need to make sure we stat the current directory
278               with a dot */
279            if (((*ptr == '/') || (*ptr == '\\')) && (*(ptr+1) == '\0')) {
280                pinfo = apr_pstrdup (p, ".");
281            }
282            else {
283                pinfo = apr_pstrdup (p, ptr);
284            }
285        }
286
287        /* If we have a statCache then try to pull the information
288           from the cache.  Otherwise just stat the file and return.*/
289        if (statCache) {
290            apr_thread_rwlock_rdlock(rwlock);
291            pathctx = (NXPathCtx_t) apr_hash_get(statCache, ppath, APR_HASH_KEY_STRING);
292            apr_thread_rwlock_unlock(rwlock);
293            if (pathctx) {
294                return getstat(pathctx, pinfo, buf, requestmap);
295            }
296            else {
297                int err;
298
299                err = NXCreatePathContext(0, ppath, 0, NULL, &pathctx);
300                if (!err) {
301                    apr_thread_rwlock_wrlock(rwlock);
302                    apr_hash_set(statCache, apr_pstrdup(gPool,ppath) , APR_HASH_KEY_STRING, (void*)pathctx);
303                    apr_thread_rwlock_unlock(rwlock);
304                    return getstat(pathctx, pinfo, buf, requestmap);
305                }
306            }
307        }
308    }
309    return getstat(ctx, path, buf, requestmap);
310}
311#endif
312
313APR_DECLARE(apr_status_t) apr_stat(apr_finfo_t *finfo,
314                                   const char *fname,
315                                   apr_int32_t wanted, apr_pool_t *pool)
316{
317    struct stat info;
318    int srv;
319    NXPathCtx_t pathCtx = 0;
320
321    getcwdpath(NULL, &pathCtx, CTX_ACTUAL_CWD);
322#ifdef APR_HAS_PSA
323	srv = getstat(pathCtx, (char*)fname, &info, ST_STAT_BITS|ST_NAME_BIT);
324#else
325    srv = cstat(pathCtx, (char*)fname, &info, ST_STAT_BITS|ST_NAME_BIT, pool);
326#endif
327    errno = srv;
328
329    if (srv == 0) {
330        finfo->pool = pool;
331        finfo->fname = fname;
332        fill_out_finfo(finfo, &info, wanted);
333        if (wanted & APR_FINFO_LINK)
334            wanted &= ~APR_FINFO_LINK;
335        if (wanted & APR_FINFO_NAME) {
336            finfo->name = apr_pstrdup(pool, info.st_name);
337            finfo->valid |= APR_FINFO_NAME;
338        }
339        return (wanted & ~finfo->valid) ? APR_INCOMPLETE : APR_SUCCESS;
340    }
341    else {
342#if !defined(ENOENT) || !defined(ENOTDIR)
343#error ENOENT || ENOTDIR not defined; please see the
344#error comments at this line in the source for a workaround.
345        /*
346         * If ENOENT || ENOTDIR is not defined in one of the your OS's
347         * include files, APR cannot report a good reason why the stat()
348         * of the file failed; there are cases where it can fail even though
349         * the file exists.  This opens holes in Apache, for example, because
350         * it becomes possible for someone to get a directory listing of a
351         * directory even though there is an index (eg. index.html) file in
352         * it.  If you do not have a problem with this, delete the above
353         * #error lines and start the compile again.  If you need to do this,
354         * please submit a bug report to http://www.apache.org/bug_report.html
355         * letting us know that you needed to do this.  Please be sure to
356         * include the operating system you are using.
357         */
358        /* WARNING: All errors will be handled as not found
359         */
360#if !defined(ENOENT)
361        return APR_ENOENT;
362#else
363        /* WARNING: All errors but not found will be handled as not directory
364         */
365        if (errno != ENOENT)
366            return APR_ENOENT;
367        else
368            return errno;
369#endif
370#else /* All was defined well, report the usual: */
371        return errno;
372#endif
373    }
374}
375
376APR_DECLARE(apr_status_t) apr_file_mtime_set(const char *fname,
377                                              apr_time_t mtime,
378                                              apr_pool_t *pool)
379{
380    apr_status_t status;
381    apr_finfo_t finfo;
382
383    status = apr_stat(&finfo, fname, APR_FINFO_ATIME, pool);
384    if (status) {
385        return status;
386    }
387
388#ifdef HAVE_UTIMES
389    {
390      struct timeval tvp[2];
391
392      tvp[0].tv_sec = apr_time_sec(finfo.atime);
393      tvp[0].tv_usec = apr_time_usec(finfo.atime);
394      tvp[1].tv_sec = apr_time_sec(mtime);
395      tvp[1].tv_usec = apr_time_usec(mtime);
396
397      if (utimes(fname, tvp) == -1) {
398        return errno;
399      }
400    }
401#elif defined(HAVE_UTIME)
402    {
403      struct utimbuf buf;
404
405      buf.actime = (time_t) (finfo.atime / APR_USEC_PER_SEC);
406      buf.modtime = (time_t) (mtime / APR_USEC_PER_SEC);
407
408      if (utime(fname, &buf) == -1) {
409        return errno;
410      }
411    }
412#else
413    return APR_ENOTIMPL;
414#endif
415
416    return APR_SUCCESS;
417}
418