Annotation of embedaddon/libpdel/http/servlet/http_servlet_file.c, revision 1.1.1.1
1.1 misho 1:
2: /*
3: * Copyright (c) 2001-2002 Packet Design, LLC.
4: * All rights reserved.
5: *
6: * Subject to the following obligations and disclaimer of warranty,
7: * use and redistribution of this software, in source or object code
8: * forms, with or without modifications are expressly permitted by
9: * Packet Design; provided, however, that:
10: *
11: * (i) Any and all reproductions of the source or object code
12: * must include the copyright notice above and the following
13: * disclaimer of warranties; and
14: * (ii) No rights are granted, in any manner or form, to use
15: * Packet Design trademarks, including the mark "PACKET DESIGN"
16: * on advertising, endorsements, or otherwise except as such
17: * appears in the above copyright notice or in the software.
18: *
19: * THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND
20: * TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO
21: * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING
22: * THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED
23: * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
24: * OR NON-INFRINGEMENT. PACKET DESIGN DOES NOT WARRANT, GUARANTEE,
25: * OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS
26: * OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY,
27: * RELIABILITY OR OTHERWISE. IN NO EVENT SHALL PACKET DESIGN BE
28: * LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE
29: * OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT,
30: * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL
31: * DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF
32: * USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY THEORY OF
33: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
35: * THE USE OF THIS SOFTWARE, EVEN IF PACKET DESIGN IS ADVISED OF
36: * THE POSSIBILITY OF SUCH DAMAGE.
37: *
38: * Author: Archie Cobbs <archie@freebsd.org>
39: */
40:
41: #include <sys/types.h>
42: #include <sys/param.h>
43: #include <sys/socket.h>
44: #include <sys/stat.h>
45: #include <sys/uio.h>
46: #ifdef __linux__
47: #include <sys/sendfile.h>
48: #endif
49:
50: #include <netinet/in.h>
51:
52: #include <stdlib.h>
53: #include <stdio.h>
54: #include <string.h>
55: #include <stdarg.h>
56: #include <syslog.h>
57: #include <fcntl.h>
58: #include <unistd.h>
59: #include <errno.h>
60: #include <assert.h>
61: #include <pthread.h>
62:
63: #include <openssl/ssl.h>
64:
65: #include "structs/structs.h"
66: #include "structs/type/array.h"
67:
68: #include "sys/alog.h"
69: #include "io/string_fp.h"
70: #include "tmpl/tmpl.h"
71: #include "util/ghash.h"
72: #include "util/typed_mem.h"
73:
74: #include "http/http_defs.h"
75: #include "http/http_server.h"
76: #include "http/http_servlet.h"
77: #include "http/servlet/tmpl.h"
78: #include "http/servlet/file.h"
79:
80: #define MEM_TYPE "http_servlet_file"
81: #define TMPL_SUFFIX ".tmpl"
82:
83: /* Cleanup state for http_servlet_file_serve() */
84: struct http_servlet_file_serve_state {
85: int fd;
86: };
87:
88: /* One cached template */
89: struct tmpl_cache {
90: char *path; /* file pathname */
91: struct http_servlet *servlet; /* tmpl servlet */
92: };
93:
94: /* Servlet state */
95: struct file_private {
96: struct http_servlet_file_info *info;
97: struct ghash *tmpls; /* cached tmpl servlets */
98: http_servlet_tmpl_free_t *freer; /* freer for tmpl arg */
99: };
100:
101: /* Directory -> file redirects */
102: static const char *http_servlet_file_dirindex[][2] = {
103: { "index.tmpl", "index" },
104: { "index.html", "index.html" },
105: { "index.htm", "index.htm" },
106: { NULL, NULL }
107: };
108:
109: /*
110: * Internal functions
111: */
112: static char *http_servlet_file_gen_filename(
113: struct http_servlet_file_info *finfo, const char *url,
114: const char *mtype);
115: static void http_servlet_file_tmpl(struct http_servlet *servlet,
116: const char *path, struct http_request *req,
117: struct http_response *resp);
118: static void http_servlet_file_serve_cleanup(void *arg);
119:
120: static http_servlet_run_t http_servlet_file_run;
121: static http_servlet_destroy_t http_servlet_file_destroy;
122:
123: static ghash_hash_t tmpl_cache_hash;
124: static ghash_equal_t tmpl_cache_equal;
125: static ghash_del_t tmpl_cache_del;
126:
127: /*
128: * Create a new file servlet.
129: */
130: struct http_servlet *
131: http_servlet_file_create(const struct http_servlet_file_info *info)
132: {
133: struct http_servlet *servlet = NULL;
134: struct file_private *priv = NULL;
135:
136: /* Create servlet */
137: if ((servlet = MALLOC(MEM_TYPE, sizeof(*servlet))) == NULL)
138: goto fail;
139: memset(servlet, 0, sizeof(*servlet));
140: servlet->run = http_servlet_file_run;
141: servlet->destroy = http_servlet_file_destroy;
142:
143: /* Initialize private info */
144: if ((priv = MALLOC(MEM_TYPE, sizeof(*priv))) == NULL)
145: goto fail;
146: memset(priv, 0, sizeof(*priv));
147:
148: /* Copy "info" */
149: if ((priv->info = MALLOC(MEM_TYPE, sizeof(*priv->info))) == NULL)
150: goto fail;
151: memset(priv->info, 0, sizeof(*priv->info));
152: if (info->docroot != NULL
153: && (priv->info->docroot = STRDUP(MEM_TYPE, info->docroot)) == NULL)
154: goto fail;
155: priv->info->allow_escape = info->allow_escape;
156: if (info->filename != NULL
157: && (priv->info->filename
158: = STRDUP(MEM_TYPE, info->filename)) == NULL)
159: goto fail;
160: if (info->prefix != NULL
161: && (priv->info->prefix = STRDUP(MEM_TYPE, info->prefix)) == NULL)
162: goto fail;
163: if (info->mime_type != NULL
164: && (priv->info->mime_type
165: = STRDUP(MEM_TYPE, info->mime_type)) == NULL)
166: goto fail;
167: if (info->mime_encoding != NULL
168: && (priv->info->mime_encoding
169: = STRDUP(MEM_TYPE, info->mime_encoding)) == NULL)
170: goto fail;
171: priv->info->logger = info->logger;
172: priv->info->hide = info->hide;
173: if (_http_servlet_tmpl_copy_tinfo(&priv->info->tinfo,
174: &info->tinfo) == -1)
175: goto fail;
176:
177: /* Only free the template argument once */
178: priv->freer = priv->info->tinfo.freer;
179: priv->info->tinfo.freer = NULL;
180:
181: /* Create template hash table */
182: if ((priv->tmpls = ghash_create(priv, 0, 200, MEM_TYPE,
183: tmpl_cache_hash, tmpl_cache_equal, NULL, tmpl_cache_del)) == NULL)
184: goto fail;
185: servlet->arg = priv;
186:
187: /* OK */
188: return (servlet);
189:
190: fail:
191: /* Clean up after failure */
192: if (priv != NULL) {
193: if (priv->info != NULL) {
194: FREE(MEM_TYPE, (char *)priv->info->filename);
195: FREE(MEM_TYPE, (char *)priv->info->docroot);
196: FREE(MEM_TYPE, (char *)priv->info->prefix);
197: FREE(MEM_TYPE, (char *)priv->info->mime_type);
198: FREE(MEM_TYPE, (char *)priv->info->mime_encoding);
199: _http_servlet_tmpl_free_tinfo(&priv->info->tinfo);
200: FREE(MEM_TYPE, priv->info);
201: }
202: ghash_destroy(&priv->tmpls);
203: FREE(MEM_TYPE, priv);
204: }
205: if (servlet != NULL)
206: FREE(MEM_TYPE, servlet);
207: return (NULL);
208: }
209:
210: /*
211: * Destroy a file servlet.
212: */
213: static void
214: http_servlet_file_destroy(struct http_servlet *servlet)
215: {
216: struct file_private *const priv = servlet->arg;
217:
218: /* Free template argument */
219: if (priv->freer != NULL)
220: (*priv->freer)(priv->info->tinfo.arg);
221:
222: /* Free private info */
223: FREE(MEM_TYPE, (char *)priv->info->filename);
224: FREE(MEM_TYPE, (char *)priv->info->docroot);
225: FREE(MEM_TYPE, (char *)priv->info->prefix);
226: FREE(MEM_TYPE, (char *)priv->info->mime_type);
227: FREE(MEM_TYPE, (char *)priv->info->mime_encoding);
228: _http_servlet_tmpl_free_tinfo(&priv->info->tinfo);
229: FREE(MEM_TYPE, priv->info);
230: ghash_destroy(&priv->tmpls);
231: FREE(MEM_TYPE, priv);
232:
233: /* Free servlet */
234: FREE(MEM_TYPE, servlet);
235: }
236:
237: /*
238: * Execute file servlet.
239: */
240: static int
241: http_servlet_file_run(struct http_servlet *servlet,
242: struct http_request *req, struct http_response *resp)
243: {
244: struct file_private *const priv = servlet->arg;
245: struct http_servlet_file_info *const info = priv->info;
246: const char *const urlpath = http_request_get_path(req);
247: char *path = NULL;
248: int got_tmpl = 0;
249: size_t len;
250:
251: /* Generate file name from URL; first try to find a template file */
252: if (info->tinfo.handler != NULL
253: && (len = strlen(urlpath)) < MAXPATHLEN - sizeof(TMPL_SUFFIX)) {
254: char tp[MAXPATHLEN];
255: struct stat sb;
256:
257: memcpy(tp, urlpath, len);
258: memcpy(tp + len, TMPL_SUFFIX, sizeof(TMPL_SUFFIX));
259: path = http_servlet_file_gen_filename(info, tp, TYPED_MEM_TEMP);
260: if (stat(path, &sb) == 0 && (sb.st_mode & S_IFMT) == S_IFREG)
261: got_tmpl = 1;
262: else {
263: FREE(TYPED_MEM_TEMP, path);
264: path = NULL;
265: }
266: }
267:
268: /* Generate file name from URL; now try a normal file */
269: if (!got_tmpl
270: && (path = http_servlet_file_gen_filename(info,
271: urlpath, TYPED_MEM_TEMP)) == NULL) {
272: http_response_send_errno_error(resp);
273: return (1);
274: }
275:
276: /* Handle templates */
277: if (got_tmpl) {
278: http_servlet_file_tmpl(servlet, path, req, resp);
279: goto done;
280: }
281:
282: /* Check whether to hide this file */
283: if (info->hide != NULL && (*info->hide)(info, req, resp, path)) {
284: FREE(TYPED_MEM_TEMP, path);
285: return (0); /* continue with next servlet */
286: }
287:
288: /* Use supplied MIME info, if any */
289: if (info->mime_type != NULL) {
290: http_response_set_header(resp, 0,
291: HTTP_HEADER_CONTENT_TYPE, "%s", info->mime_type);
292: if (info->mime_encoding != NULL) {
293: http_response_set_header(resp, 0,
294: HTTP_HEADER_CONTENT_ENCODING,
295: "%s", info->mime_encoding);
296: }
297: }
298:
299: /* Serve up file */
300: http_servlet_file_serve(path, info->logger, req, resp);
301:
302: done:
303: /* Done */
304: FREE(TYPED_MEM_TEMP, path);
305: return (1);
306: }
307:
308: #define MAX_ENCODINGS 10
309:
310: /*
311: * Serve up a file.
312: *
313: * This is a public function usable by other servlets.
314: */
315: void
316: http_servlet_file_serve(const char *path, http_logger_t *logger,
317: struct http_request *req, struct http_response *resp)
318: {
319: const char *hval;
320: struct stat sb;
321: char buf[1024];
322: FILE *output;
323: struct tm tm;
324: time_t when;
325: int sock;
326:
327: /* Stat file */
328: if (stat(path, &sb) == -1) {
329: fail_errno: http_response_send_errno_error(resp);
330: return;
331: }
332:
333: /* If file is a directory, redirect to default file if it exists */
334: if ((sb.st_mode & S_IFMT) == S_IFDIR) {
335: int i;
336:
337: for (i = 0; http_servlet_file_dirindex[i][0] != NULL; i++) {
338: const char *qs = http_request_get_query_string(req);
339: char *urlpath;
340: char *dfile;
341:
342: if (qs == NULL)
343: qs = "";
344: ASPRINTF(TYPED_MEM_TEMP, &dfile, "%s/%s", path,
345: http_servlet_file_dirindex[i][0]);
346: if (dfile == NULL)
347: goto fail_errno;
348: if (stat(dfile, &sb) == -1) {
349: FREE(TYPED_MEM_TEMP, dfile);
350: continue;
351: }
352: if ((urlpath = http_request_url_encode(TYPED_MEM_TEMP,
353: http_request_get_path(req))) == NULL) {
354: FREE(TYPED_MEM_TEMP, dfile);
355: goto fail_errno;
356: }
357: if (http_response_set_header(resp, 0,
358: HTTP_HEADER_LOCATION, "%s%s%s%s%s", urlpath,
359: "/" + (urlpath[strlen(urlpath) - 1] == '/'),
360: http_servlet_file_dirindex[i][1],
361: (*qs != '\0') ? "?" : "", qs) == -1) {
362: FREE(TYPED_MEM_TEMP, urlpath);
363: FREE(TYPED_MEM_TEMP, dfile);
364: goto fail_errno;
365: }
366: FREE(TYPED_MEM_TEMP, urlpath);
367: FREE(TYPED_MEM_TEMP, dfile);
368: http_response_send_error(resp,
369: HTTP_STATUS_FOUND, NULL);
370: return;
371: }
372: }
373:
374: /* File must be regular */
375: if ((sb.st_mode & S_IFMT) != S_IFREG) {
376: errno = ENOENT; /* hide non-regular files */
377: goto fail_errno;
378: }
379:
380: /* Set timestamp from stat(2) info */
381: strftime(buf, sizeof(buf), HTTP_TIME_FMT_RFC1123,
382: gmtime_r(&sb.st_mtime, &tm));
383: http_response_set_header(resp, 0, HTTP_HEADER_DATE, "%s", buf);
384:
385: /* Check for If-Modified-Since: header */
386: if ((hval = http_request_get_header(req,
387: HTTP_HEADER_IF_MODIFIED_SINCE)) != NULL) {
388: if ((when = http_request_parse_time(hval)) != (time_t)-1
389: && sb.st_mtime <= when) {
390: http_response_send_error(resp,
391: HTTP_STATUS_NOT_MODIFIED, NULL);
392: return;
393: }
394: }
395:
396: /* Set MIME type if not set already */
397: if (http_response_get_header(resp, HTTP_HEADER_CONTENT_TYPE) == NULL) {
398: const char *cencs[MAX_ENCODINGS];
399: const char *ctype;
400: int i;
401:
402: http_response_guess_mime(path, &ctype, cencs, MAX_ENCODINGS);
403: http_response_set_header(resp, 0,
404: HTTP_HEADER_CONTENT_TYPE, "%s", ctype);
405: for (i = 0; i < MAX_ENCODINGS && cencs[i] != NULL; i++) {
406: http_response_set_header(resp, i > 0,
407: HTTP_HEADER_CONTENT_ENCODING, "%s", cencs[i]);
408: }
409: }
410:
411: /* Set content length */
412: http_response_set_header(resp, 0,
413: HTTP_HEADER_CONTENT_LENGTH, "%lu", (u_long)sb.st_size);
414:
415: /* Get servlet output stream (unbuffered) */
416: if ((output = http_response_get_output(resp, 0)) == NULL) {
417: (*logger)(LOG_ERR, "can't get response output: %s",
418: strerror(errno));
419: return;
420: }
421:
422: /* Send file contents, using sendfile(2) if possible */
423: if ((sock = http_response_get_raw_socket(resp)) != -1) {
424: struct http_servlet_file_serve_state state;
425:
426: /* Open file */
427: if ((state.fd = open(path, O_RDONLY)) == -1)
428: goto fail_errno;
429:
430: /* Set cleanup hook in case thread is canceled */
431: pthread_cleanup_push(http_servlet_file_serve_cleanup, &state);
432:
433: /* Make sure headers are sent first */
434: http_response_send_headers(resp, 1);
435: fflush(output);
436:
437: /* Send file directly using sendfile(2) */
438: #ifndef __linux__
439: sendfile(state.fd, sock, 0, sb.st_size, NULL, NULL, 0);
440: #else
441: sendfile(sock, state.fd, NULL, sb.st_size);
442: #endif
443:
444: /* Close file */
445: pthread_cleanup_pop(1);
446: } else {
447: FILE *fp;
448: int ret;
449:
450: /* Open file */
451: if ((fp = fopen(path, "r")) == NULL)
452: goto fail_errno;
453:
454: /* Set cleanup hook in case thread is canceled */
455: pthread_cleanup_push((void (*)(void *))fclose, fp);
456:
457: /* Tranfer file contents */
458: while (1) {
459: if ((ret = fread(buf, 1, sizeof(buf), fp)) != 0) {
460: if (fwrite(buf, 1, ret, output) < ret)
461: break;
462: }
463: if (ret < sizeof(buf))
464: break;
465: }
466:
467: /* Close file */
468: pthread_cleanup_pop(1);
469: }
470: }
471:
472: /*
473: * Do a template file.
474: */
475: static void
476: http_servlet_file_tmpl(struct http_servlet *servlet, const char *path,
477: struct http_request *req, struct http_response *resp)
478: {
479: struct file_private *const priv = servlet->arg;
480: struct http_servlet_file_info *const info = priv->info;
481: struct http_servlet_tmpl_info ti;
482: char mimepath[MAXPATHLEN + 1];
483: struct tmpl_cache *t = NULL;
484: struct tmpl_cache key;
485: const char *s;
486:
487: /* See if template already cached */
488: key.path = (char *)path;
489: if ((t = ghash_get(priv->tmpls, &key)) != NULL)
490: goto found;
491:
492: /* Create new cached entry */
493: if ((t = MALLOC(MEM_TYPE, sizeof(*t))) == NULL)
494: goto fail;
495: memset(t, 0, sizeof(*t));
496: if ((t->path = STRDUP(MEM_TYPE, path)) == NULL)
497: goto fail;
498:
499: /* Set info required by the template servlet */
500: memset(&ti, 0, sizeof(ti));
501: ti.path = t->path;
502: ti.tinfo = info->tinfo;
503: ti.logger = info->logger;
504:
505: /* Figure out templates's output MIME type */
506: strlcat(mimepath, path, sizeof(mimepath));
507: mimepath[strlen(mimepath) - strlen(TMPL_SUFFIX)] = '\0';
508: if ((s = strrchr(mimepath, '.')) == NULL
509: || strchr(s, '/') != NULL) /* no suffix? assume html */
510: strlcat(mimepath, "x.html", sizeof(mimepath));
511: http_response_guess_mime(mimepath, &ti.mime_type, NULL, 0);
512:
513: /* Create template servlet */
514: if ((t->servlet = http_servlet_tmpl_create(&ti)) == NULL)
515: goto fail;
516:
517: /* Add it to hash table */
518: if (ghash_put(priv->tmpls, t) == -1) {
519: (*info->logger)(LOG_ERR,
520: "%s: %s", "ghash_put", strerror(errno));
521: fail: FREE(MEM_TYPE, t);
522: http_response_send_errno_error(resp);
523: return;
524: }
525:
526: found:
527: /* Invoke servlet */
528: (*t->servlet->run)(t->servlet, req, resp);
529: }
530:
531: static void
532: http_servlet_file_serve_cleanup(void *arg)
533: {
534: const struct http_servlet_file_serve_state *const state = arg;
535:
536: close(state->fd);
537: }
538:
539: static u_int32_t
540: tmpl_cache_hash(struct ghash *g, const void *item)
541: {
542: const struct tmpl_cache *const t = item;
543: u_int32_t hash;
544: const char *s;
545:
546: for (hash = 0, s = t->path; *s != '\0'; s++)
547: hash = (31 * hash) + (u_char)*s;
548: return (hash);
549: }
550:
551: static int
552: tmpl_cache_equal(struct ghash *g, const void *item1, const void *item2)
553: {
554: const struct tmpl_cache *const t1 = item1;
555: const struct tmpl_cache *const t2 = item2;
556:
557: return (strcmp(t1->path, t2->path) == 0);
558: }
559:
560: static void
561: tmpl_cache_del(struct ghash *g, void *item)
562: {
563: struct tmpl_cache *const t = item;
564:
565: http_server_destroy_servlet(&t->servlet);
566: FREE(MEM_TYPE, t->path);
567: FREE(MEM_TYPE, t);
568: }
569:
570: /*
571: * Compute a filename from supplied info and URL.
572: *
573: * Caller must free returned string, which is in a buffer of size MAXPATHLEN.
574: */
575: static char *
576: http_servlet_file_gen_filename(struct http_servlet_file_info *info,
577: const char *urlpath, const char *mtype)
578: {
579: char path[MAXPATHLEN];
580: char *rpath;
581: char *tok;
582: char *s;
583:
584: /* Sanity check */
585: assert(*urlpath == '/');
586:
587: /* Disallow all ".", "..", and empty components within urlpath */
588: strlcpy(path, urlpath, sizeof(path));
589: for (s = path + 1; (tok = strsep(&s, "/")) != NULL; ) {
590: if ((*tok == '\0' && s != NULL)
591: || strcmp(tok, ".") == 0 || strcmp(tok, "..") == 0) {
592: errno = ENOENT;
593: return (NULL);
594: }
595: }
596:
597: /* Prepend root directory, if any */
598: if (info->docroot != NULL) {
599: strlcpy(path, info->docroot, sizeof(path) - 1);
600: if (path[strlen(path) - 1] != '/')
601: strlcat(path, "/", sizeof(path));
602: } else
603: *path = '\0';
604:
605: /* Add fixed filename, if any */
606: if (info->filename != NULL) {
607: if (*info->filename == '/')
608: *path = '\0';
609: strlcat(path, info->filename, sizeof(path));
610: goto normalize;
611: }
612:
613: /* Strip URL prefix, if it matches */
614: if (info->prefix != NULL
615: && strncmp(urlpath, info->prefix, strlen(info->prefix)) == 0)
616: urlpath += strlen(info->prefix);
617:
618: /* Derive remainder of pathname from URL */
619: strlcat(path, urlpath + (*urlpath == '/'), sizeof(path));
620:
621: normalize:
622:
623: /* Normalize path */
624: if ((rpath = MALLOC(mtype, MAXPATHLEN)) == NULL)
625: return (NULL);
626: if (realpath(path, rpath) == NULL) {
627: FREE(mtype, rpath);
628: return (NULL);
629: }
630: rpath[MAXPATHLEN - 1] = '\0';
631:
632: /* Verify that file is within the document root directory hierarchy */
633: if (!info->allow_escape) {
634: const char *docroot;
635: char *dpath;
636: size_t rlen;
637: int within;
638:
639: /* Use current working directory if info->docroot is NULL */
640: if (info->docroot == NULL) {
641: getcwd(path, sizeof(path));
642: path[sizeof(path) - 1] = '\0';
643: docroot = path;
644: } else
645: docroot = info->docroot;
646:
647: /* Normalize docroot path */
648: if ((dpath = MALLOC(mtype, MAXPATHLEN)) == NULL) {
649: FREE(mtype, rpath);
650: return (NULL);
651: }
652: if (realpath(docroot, dpath) == NULL) {
653: FREE(mtype, dpath);
654: FREE(mtype, rpath);
655: return (NULL);
656: }
657: dpath[MAXPATHLEN - 1] = '\0';
658:
659: /* Verify that path is within the root */
660: rlen = strlen(dpath);
661: within = strncmp(rpath, dpath, rlen) == 0
662: && (rpath[rlen] == '\0' || rpath[rlen] == '/');
663: FREE(mtype, dpath);
664: if (!within) {
665: FREE(mtype, rpath);
666: errno = ENOENT;
667: return (NULL);
668: }
669: }
670:
671: /* Done */
672: return (rpath);
673: }
674:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>