aboutsummaryrefslogtreecommitdiff
path: root/scripts/bb_mkdep.c
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/bb_mkdep.c')
-rw-r--r--scripts/bb_mkdep.c855
1 files changed, 855 insertions, 0 deletions
diff --git a/scripts/bb_mkdep.c b/scripts/bb_mkdep.c
new file mode 100644
index 000000000..408397332
--- /dev/null
+++ b/scripts/bb_mkdep.c
@@ -0,0 +1,855 @@
1/*
2 * Another dependences for Makefile mashine generator
3 *
4 * Copyright (C) 2005 by Vladimir Oleynik <dzo@simtreas.ru>
5 *
6 * This programm do
7 * 1) find #define KEY VALUE or #undef KEY from include/config.h
8 * 2) save include/config/key*.h if changed after previous usage
9 * 3) recursive scan from "./" *.[ch] files, but skip scan include/config/...
10 * 4) find #include "*.h" and KEYs using, if not as #define and #undef
11 * 5) generate depend to stdout
12 * path/file.o: include/config/key*.h found_include_*.h
13 * path/inc.h: include/config/key*.h found_included_include_*.h
14 * This programm do not generate dependences for #include <...>
15 *
16 * Options:
17 * -I local_include_path (include`s paths, default: LOCAL_INCLUDE_PATH)
18 * -d (don`t generate depend)
19 * -w (show warning if include files not found)
20 * -k include/config (default: INCLUDE_CONFIG_PATH)
21 * -c include/config.h (configs, default: INCLUDE_CONFIG_KEYS_PATH)
22*/
23
24#define LOCAL_INCLUDE_PATH "include"
25#define INCLUDE_CONFIG_PATH LOCAL_INCLUDE_PATH"/config"
26#define INCLUDE_CONFIG_KEYS_PATH LOCAL_INCLUDE_PATH"/config.h"
27
28#define _GNU_SOURCE
29#include <sys/types.h>
30#include <sys/stat.h>
31#include <sys/mman.h>
32#include <getopt.h>
33#include <dirent.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <stdarg.h>
38#include <unistd.h>
39#include <errno.h>
40#include <fcntl.h>
41
42typedef struct BB_KEYS {
43 char *keyname;
44 const char *value;
45 char *stored_path;
46 int checked;
47 struct BB_KEYS *next;
48} bb_key_t;
49
50
51/* partial and simplify libbb routine */
52
53void bb_error_d(const char *s, ...) __attribute__ ((noreturn, format (printf, 1, 2)));
54char * bb_asprint(const char *format, ...) __attribute__ ((format (printf, 1, 2)));
55
56/* stolen from libbb as is */
57typedef struct llist_s {
58 char *data;
59 struct llist_s *link;
60} llist_t;
61llist_t *llist_add_to(llist_t *old_head, char *new_item);
62void *xrealloc(void *p, size_t size);
63void *xmalloc(size_t size);
64char *bb_xstrdup(const char *s);
65char *bb_simplify_path(const char *path);
66
67/* for lexical analyzier */
68static bb_key_t *key_top;
69
70static void parse_inc(const char *include, const char *fname);
71static void parse_conf_opt(char *opt, const char *val, size_t rsz);
72
73#define CHECK_ONLY 0
74#define MAKE_NEW 1
75static bb_key_t *find_already(bb_key_t *k, const char *nk, int flg_save_new);
76
77#define yy_error_d(s) bb_error_d("%s:%d hmm, %s", fname, line, s)
78
79/* state */
80#define S 0 /* start state */
81#define STR '"' /* string */
82#define CHR '\'' /* char */
83#define REM '*' /* block comment */
84#define POUND '#' /* # */
85#define I 'i' /* #include preprocessor`s directive */
86#define D 'd' /* #define preprocessor`s directive */
87#define U 'u' /* #undef preprocessor`s directive */
88#define LI 'I' /* #include "... */
89#define DK 'K' /* #define KEY... (config mode) */
90#define DV 'V' /* #define KEY "... or #define KEY '... */
91#define NLC 'n' /* \+\n */
92#define ANY '?' /* skip unparsed . */
93
94/* [A-Z_a-z] */
95#define ID(c) ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
96/* [A-Z_a-z0-9] */
97#define ISALNUM(c) (ID(c) || (c >= '0' && c <= '9'))
98
99#define getc1() do { c = (optr >= oend) ? EOF : *optr++; } while(0)
100#define ungetc1() optr--
101
102#define put_id(c) do { if(id_len == mema_id) \
103 id = xrealloc(id, mema_id += 16); \
104 id[id_len++] = c; } while(0)
105
106/* stupid C lexical analizator */
107static void c_lex(const char *fname, int flg_config_include)
108{
109 int c = EOF; /* stupid initialize */
110 int prev_state = EOF;
111 int called;
112 int state;
113 int line;
114 static size_t mema_id;
115 char *id = xmalloc(mema_id=128); /* fist allocate */
116 size_t id_len = 0; /* stupid initialize */
117 char *val = NULL;
118 unsigned char *optr, *oend;
119 unsigned char *start = NULL; /* stupid initialize */
120
121 int fd;
122 char *map;
123 int mapsize;
124 {
125 /* stolen from mkdep by Linus Torvalds */
126 int pagesizem1 = getpagesize() - 1;
127 struct stat st;
128
129 fd = open(fname, O_RDONLY);
130 if(fd < 0) {
131 perror(fname);
132 return;
133 }
134 fstat(fd, &st);
135 if (st.st_size == 0)
136 bb_error_d("%s is empty", fname);
137 mapsize = st.st_size;
138 mapsize = (mapsize+pagesizem1) & ~pagesizem1;
139 map = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0);
140 if ((long) map == -1)
141 bb_error_d("%s: mmap: %m", fname);
142
143 /* hereinafter is my */
144 optr = (unsigned char *)map;
145 oend = optr + st.st_size;
146 }
147
148 line = 1;
149 called = state = S;
150
151 for(;;) {
152 if(state == LI || state == DV) {
153 /* store "include.h" or config mode #define KEY "|'..."|' */
154 put_id(0);
155 if(state == LI) {
156 parse_inc(id, fname);
157 } else {
158 /*
159 if(val[0] == '\0')
160 yy_error_d("expected value");
161 */
162 parse_conf_opt(id, val, (optr - start));
163 }
164 state = S;
165 }
166 if(prev_state != state) {
167 prev_state = state;
168 getc1();
169 }
170
171 /* [ \t]+ eat first space */
172 while(c == ' ' || c == '\t')
173 getc1();
174
175 if(c == '\\') {
176 getc1();
177 if(c == '\n') {
178 /* \\\n eat continued */
179 line++;
180 prev_state = NLC;
181 continue;
182 }
183 ungetc1();
184 c = '\\';
185 }
186
187 if(state == S) {
188 while(c <= ' ' && c != EOF) {
189 /* <S>[\000- ]+ */
190 if(c == '\n')
191 line++;
192 getc1();
193 }
194 if(c == EOF) {
195 /* <S><<EOF>> */
196 munmap(map, mapsize);
197 close(fd);
198 return;
199 }
200 if(c == '/') {
201 /* <S>/ */
202 getc1();
203 if(c == '/') {
204 /* <S>"//"[^\n]* */
205 do getc1(); while(c != '\n' && c != EOF);
206 } else if(c == '*') {
207 /* <S>[/][*] */
208 called = S;
209 state = REM;
210 }
211 /* eat <S>/ */
212 } else if(c == '#') {
213 /* <S>\"|\'|# */
214 start = optr - 1;
215 state = c;
216 } else if(c == STR || c == CHR) {
217 /* <S>\"|\'|# */
218 val = NULL;
219 called = S;
220 state = c;
221 } else if(ISALNUM(c)) {
222 /* <S>[A-Z_a-z0-9] */
223 id_len = 0;
224 do {
225 /* <S>[A-Z_a-z0-9]+ */
226 put_id(c);
227 getc1();
228 } while(ISALNUM(c));
229 put_id(0);
230 find_already(key_top, id, CHECK_ONLY);
231 } else {
232 /* <S>. */
233 prev_state = ANY;
234 }
235 continue;
236 }
237 if(state == REM) {
238 for(;;) {
239 /* <REM>[^*]+ */
240 while(c != '*') {
241 if(c == '\n') {
242 /* <REM>\n */
243 if(called != S)
244 yy_error_d("unexpected newline");
245 line++;
246 } else if(c == EOF)
247 yy_error_d("unexpected EOF");
248 getc1();
249 }
250 /* <REM>[*] */
251 getc1();
252 if(c == '/') {
253 /* <REM>[*][/] */
254 state = called;
255 break;
256 }
257 }
258 continue;
259 }
260 if(state == STR || state == CHR) {
261 for(;;) {
262 /* <STR,CHR>\n|<<EOF>> */
263 if(c == '\n' || c == EOF)
264 yy_error_d("unterminating");
265 if(c == '\\') {
266 /* <STR,CHR>\\ */
267 getc1();
268 if(c != '\\' && c != '\n' && c != state) {
269 /* another usage \ in str or char */
270 if(c == EOF)
271 yy_error_d("unexpected EOF");
272 if(val)
273 put_id(c);
274 continue;
275 }
276 /* <STR,CHR>\\[\\\n] or <STR>\\\" or <CHR>\\\' */
277 /* eat 2 char */
278 if(c == '\n')
279 line++;
280 else if(val)
281 put_id(c);
282 } else if(c == state) {
283 /* <STR>\" or <CHR>\' */
284 if(called == DV)
285 put_id(c);
286 state = called;
287 break;
288 } else if(val)
289 put_id(c);
290 /* <STR,CHR>. */
291 getc1();
292 }
293 continue;
294 }
295
296 /* begin preprocessor states */
297 if(c == EOF)
298 yy_error_d("unexpected EOF");
299 if(c == '/') {
300 /* <#.*>/ */
301 getc1();
302 if(c == '/')
303 yy_error_d("detect // in preprocessor line");
304 if(c == '*') {
305 /* <#.*>[/][*] */
306 called = state;
307 state = REM;
308 continue;
309 }
310 /* hmm, #.*[/] */
311 yy_error_d("strange preprocessor line");
312 }
313 if(state == '#') {
314 static const char * const preproc[] = {
315 "define", "undef", "include", ""
316 };
317 const char * const *str_type;
318
319 id_len = 0;
320 while(ISALNUM(c)) {
321 put_id(c);
322 getc1();
323 }
324 put_id(0);
325 for(str_type = preproc; (state = **str_type); str_type++) {
326 if(*id == state && strcmp(id, *str_type) == 0)
327 break;
328 }
329 /* to S if another #directive */
330 ungetc1();
331 id_len = 0; /* common for save */
332 continue;
333 }
334 if(state == I) {
335 if(c == STR) {
336 /* <I>\" */
337 val = id;
338 state = STR;
339 called = LI;
340 continue;
341 }
342 /* another (may be wrong) #include ... */
343 ungetc1();
344 state = S;
345 continue;
346 }
347 if(state == D || state == U) {
348 while(ISALNUM(c)) {
349 if(flg_config_include) {
350 /* save KEY from #"define"|"undef" ... */
351 put_id(c);
352 }
353 getc1();
354 }
355 if(!flg_config_include) {
356 state = S;
357 } else {
358 if(!id_len)
359 yy_error_d("expected identificator");
360 put_id(0);
361 if(state == U) {
362 parse_conf_opt(id, NULL, (optr - start));
363 state = S;
364 } else {
365 /* D -> DK */
366 state = DK;
367 }
368 }
369 ungetc1();
370 continue;
371 }
372 if(state == DK) {
373 /* #define (config mode) */
374 val = id + id_len;
375 if(c == STR || c == CHR) {
376 /* define KEY "... or define KEY '... */
377 put_id(c);
378 called = DV;
379 state = c;
380 continue;
381 }
382 while(ISALNUM(c)) {
383 put_id(c);
384 getc1();
385 }
386 ungetc1();
387 state = DV;
388 continue;
389 }
390 }
391}
392
393
394static void show_usage(void) __attribute__ ((noreturn));
395static void show_usage(void)
396{
397 bb_error_d("Usage: [-I local_include_path] [-dw] "
398 "[-k path_for_store_keys] [-s skip_file]");
399}
400
401static const char *kp;
402static llist_t *Iop;
403static bb_key_t *Ifound;
404static int noiwarning;
405static llist_t *configs;
406
407static bb_key_t *find_already(bb_key_t *k, const char *nk, int flg_save_new)
408{
409 bb_key_t *cur;
410
411 for(cur = k; cur; cur = cur->next) {
412 if(strcmp(cur->keyname, nk) == 0) {
413 cur->checked = 1;
414 return NULL;
415 }
416 }
417 if(flg_save_new == CHECK_ONLY)
418 return NULL;
419 cur = xmalloc(sizeof(bb_key_t));
420 cur->keyname = bb_xstrdup(nk);
421 cur->checked = 1;
422 cur->next = k;
423 return cur;
424}
425
426static int store_include_fullpath(char *p_i, bb_key_t *li)
427{
428 struct stat st;
429 int ok = 0;
430
431 if(stat(p_i, &st) == 0) {
432 li->stored_path = bb_simplify_path(p_i);
433 ok = 1;
434 }
435 free(p_i);
436 return ok;
437}
438
439static void parse_inc(const char *include, const char *fname)
440{
441 bb_key_t *li;
442 char *p_i;
443 llist_t *lo;
444
445 if((li = find_already(Ifound, include, MAKE_NEW)) == NULL)
446 return;
447 Ifound = li;
448 if(include[0] != '/') {
449 /* relative */
450 int w;
451 const char *p;
452
453 p_i = strrchr(fname, '/');
454 if(p_i == NULL) {
455 p = ".";
456 w = 1;
457 } else {
458 w = (p_i-fname);
459 p = fname;
460 }
461 p_i = bb_asprint("%.*s/%s", w, p, include);
462 if(store_include_fullpath(p_i, li))
463 return;
464 }
465 for(lo = Iop; lo; lo = lo->link) {
466 p_i = bb_asprint("%s/%s", lo->data, include);
467 if(store_include_fullpath(p_i, li))
468 return;
469 }
470 li->stored_path = NULL;
471 if(noiwarning)
472 fprintf(stderr, "%s: Warning: #include \"%s\" not found in specified paths\n", fname, include);
473}
474
475static void parse_conf_opt(char *opt, const char *val, size_t recordsz)
476{
477 bb_key_t *cur = find_already(key_top, opt, MAKE_NEW);
478
479 if(cur != NULL) {
480 /* new key, check old key if present after previous usage */
481 char *s, *p;
482 struct stat st;
483 int fd;
484 int cmp_ok = 0;
485 static char *record_buf;
486 static char *r_cmp;
487 static size_t r_sz;
488
489 recordsz += 2; /* \n\0 */
490 if(recordsz > r_sz) {
491 record_buf = xrealloc(record_buf, r_sz=recordsz);
492 r_cmp = xrealloc(r_cmp, recordsz);
493 }
494 s = record_buf;
495 if(val)
496 sprintf(s, "#define %s%s%s\n", opt, (*val ? " " : ""), val);
497 else
498 sprintf(s, "#undef %s\n", opt);
499 /* may be short count " " */
500 recordsz = strlen(s);
501 /* key converting [A-Z] -> [a-z] */
502 for(p = opt; *p; p++) {
503 if(*p >= 'A' && *p <= 'Z')
504 *p = *p - 'A' + 'a';
505 if(*p == '_')
506 *p = '/';
507 }
508 p = bb_asprint("%s/%s.h", kp, opt);
509 cur->stored_path = opt = p;
510 while(*++p) {
511 /* Auto-create directories. */
512 if (*p == '/') {
513 *p = '\0';
514 if (stat(opt, &st) != 0 && mkdir(opt, 0755) != 0)
515 bb_error_d("mkdir(%s): %m", opt);
516 *p = '/';
517 }
518 }
519 if(stat(opt, &st) == 0) {
520 /* found */
521 if(st.st_size == recordsz) {
522 fd = open(opt, O_RDONLY);
523 if(fd < 0 || read(fd, r_cmp, recordsz) != recordsz)
524 bb_error_d("%s: %m", opt);
525 close(fd);
526 cmp_ok = memcmp(s, r_cmp, recordsz) == 0;
527 }
528 }
529 if(!cmp_ok) {
530 fd = open(opt, O_WRONLY|O_CREAT|O_TRUNC, 0644);
531 if(fd < 0 || write(fd, s, recordsz) != recordsz)
532 bb_error_d("%s: %m", opt);
533 close(fd);
534 }
535 /* store only */
536 cur->checked = 0;
537 if(val) {
538 if(*val == '\0') {
539 cur->value = "";
540 } else {
541 cur->value = bb_xstrdup(val);
542 }
543 } else {
544 cur->value = NULL;
545 }
546 key_top = cur;
547 } else {
548 /* present already */
549 for(cur = key_top; cur; cur = cur->next) {
550 if(strcmp(cur->keyname, opt) == 0) {
551 cur->checked = 0;
552 if(cur->value == NULL && val == NULL)
553 return;
554 if((cur->value == NULL && val != NULL) ||
555 (cur->value != NULL && val == NULL) ||
556 strcmp(cur->value, val))
557 fprintf(stderr, "Warning: redefined %s\n", opt);
558 return;
559 }
560 }
561 }
562}
563
564static int show_dep(int first, bb_key_t *k, const char *a)
565{
566 bb_key_t *cur;
567
568 for(cur = k; cur; cur = cur->next) {
569 if(cur->checked && cur->stored_path) {
570 if(first) {
571 const char *ext;
572
573 if(*a == '.' && a[1] == '/')
574 a += 2;
575 ext = strrchr(a, '.');
576 if(ext && ext[1] == 'c' && ext[2] == '\0') {
577 /* *.c -> *.o */
578 printf("\n%.*s.o:", (ext - a), a);
579 } else {
580 printf("\n%s:", a);
581 }
582 first = 0;
583 } else {
584 printf(" \\\n ");
585 }
586 printf(" %s", cur->stored_path);
587 }
588 cur->checked = 0;
589 }
590 return first;
591}
592
593static llist_t *files;
594
595static llist_t *filter_chd(const char *fe, const char *p, llist_t *pdirs)
596{
597 const char *e;
598 struct stat st;
599 char *fp;
600 char *afp;
601 llist_t *cfl;
602
603 if (*fe == '.')
604 return NULL;
605 fp = bb_asprint("%s/%s", p, fe);
606 if(stat(fp, &st)) {
607 fprintf(stderr, "Warning: stat(%s): %m", fp);
608 free(fp);
609 return NULL;
610 }
611 afp = bb_simplify_path(fp);
612 if(S_ISDIR(st.st_mode)) {
613 if(strcmp(kp, afp) == 0) {
614 /* is autogenerated to kp/key* by previous usage */
615 free(afp);
616 free(fp);
617 /* drop scan kp/ directory */
618 return NULL;
619 }
620 free(afp);
621 return llist_add_to(pdirs, fp);
622 }
623 if(!S_ISREG(st.st_mode)) {
624 /* hmm, is device! */
625 free(afp);
626 free(fp);
627 return NULL;
628 }
629 e = strrchr(fe, '.');
630 if(e == NULL || !((e[1]=='c' || e[1]=='h') && e[2]=='\0')) {
631 /* direntry is not directory or *.[ch] */
632 free(afp);
633 free(fp);
634 return NULL;
635 }
636 for(cfl = configs; cfl; cfl = cfl->link) {
637 if(cfl->data && strcmp(cfl->data, afp) == 0) {
638 /* parse configs.h */
639 free(afp);
640 c_lex(fp, 1);
641 free(fp);
642 free(cfl->data);
643 cfl->data = NULL;
644 return NULL;
645 }
646 }
647 free(fp);
648 /* direntry is *.[ch] regular file */
649 files = llist_add_to(files, afp);
650 return NULL;
651}
652
653static void scan_dir_find_ch_files(char *p)
654{
655 llist_t *dirs;
656 llist_t *d_add;
657 llist_t *d;
658 struct dirent *de;
659 DIR *dir;
660
661 dirs = llist_add_to(NULL, p);
662 /* emulate recursive */
663 while(dirs) {
664 d_add = NULL;
665 while(dirs) {
666 dir = opendir(dirs->data);
667 if (dir == NULL)
668 fprintf(stderr, "Warning: opendir(%s): %m", dirs->data);
669 while ((de = readdir(dir)) != NULL) {
670 d = filter_chd(de->d_name, dirs->data, d_add);
671 if(d)
672 d_add = d;
673 }
674 closedir(dir);
675 if(dirs->data != p)
676 free(dirs->data);
677 d = dirs;
678 dirs = dirs->link;
679 free(d);
680 }
681 dirs = d_add;
682 }
683}
684
685int main(int argc, char **argv)
686{
687 int generate_dep = 1;
688 char *s;
689 int i;
690 llist_t *fl;
691
692 while ((i = getopt(argc, argv, "I:c:dk:w")) > 0) {
693 switch(i) {
694 case 'I':
695 Iop = llist_add_to(Iop, optarg);
696 break;
697 case 'c':
698 s = bb_simplify_path(optarg);
699 configs = llist_add_to(configs, s);
700 break;
701 case 'd':
702 generate_dep = 0;
703 break;
704 case 'k':
705 if(kp)
706 bb_error_d("Hmm, why multiple -k?");
707 kp = bb_simplify_path(optarg);
708 break;
709 case 'w':
710 noiwarning = 1;
711 break;
712 default:
713 show_usage();
714 }
715 }
716 if(argc > optind)
717 show_usage();
718
719 /* defaults */
720 if(kp == NULL)
721 kp = bb_simplify_path(INCLUDE_CONFIG_PATH);
722 if(Iop == NULL)
723 Iop = llist_add_to(Iop, LOCAL_INCLUDE_PATH);
724 if(configs == NULL) {
725 s = bb_simplify_path(INCLUDE_CONFIG_KEYS_PATH);
726 configs = llist_add_to(configs, s);
727 }
728 scan_dir_find_ch_files(".");
729
730 for(fl = files; fl; fl = fl->link) {
731 c_lex(fl->data, 0);
732 if(generate_dep) {
733 i = show_dep(1, Ifound, fl->data);
734 i = show_dep(i, key_top, fl->data);
735 if(i == 0)
736 putchar('\n');
737 }
738 }
739 return 0;
740}
741
742void bb_error_d(const char *s, ...)
743{
744 va_list p;
745
746 va_start(p, s);
747 vfprintf(stderr, s, p);
748 va_end(p);
749 putc('\n', stderr);
750 exit(1);
751}
752
753
754void *xmalloc(size_t size)
755{
756 void *p = malloc(size);
757
758 if(p == NULL)
759 bb_error_d("memory exhausted");
760 return p;
761}
762
763void *xrealloc(void *p, size_t size) {
764 p = realloc(p, size);
765 if(p == NULL)
766 bb_error_d("memory exhausted");
767 return p;
768}
769
770char *bb_asprint(const char *format, ...)
771{
772 va_list p;
773 int r;
774 char *out;
775
776 va_start(p, format);
777 r = vasprintf(&out, format, p);
778 va_end(p);
779
780 if (r < 0)
781 bb_error_d("bb_asprint: %m");
782 return out;
783}
784
785llist_t *llist_add_to(llist_t *old_head, char *new_item)
786{
787 llist_t *new_head;
788
789 new_head = xmalloc(sizeof(llist_t));
790 new_head->data = new_item;
791 new_head->link = old_head;
792
793 return(new_head);
794}
795
796char *bb_xstrdup(const char *s)
797{
798 char *r = strdup(s);
799 if(r == NULL)
800 bb_error_d("memory exhausted");
801 return r;
802}
803
804char *bb_simplify_path(const char *path)
805{
806 char *s, *start, *p;
807
808 if (path[0] == '/')
809 start = bb_xstrdup(path);
810 else {
811 static char *pwd;
812
813 if(pwd == NULL) {
814 /* is not libbb, but this program have not chdir() */
815 unsigned path_max = 512;
816 char *cwd = xmalloc (path_max);
817#define PATH_INCR 32
818 while (getcwd (cwd, path_max) == NULL) {
819 if(errno != ERANGE)
820 bb_error_d("getcwd: %m");
821 path_max += PATH_INCR;
822 cwd = xrealloc (cwd, path_max);
823 }
824 pwd = cwd;
825 }
826 start = bb_asprint("%s/%s", pwd, path);
827 }
828 p = s = start;
829
830 do {
831 if (*p == '/') {
832 if (*s == '/') { /* skip duplicate (or initial) slash */
833 continue;
834 } else if (*s == '.') {
835 if (s[1] == '/' || s[1] == 0) { /* remove extra '.' */
836 continue;
837 } else if ((s[1] == '.') && (s[2] == '/' || s[2] == 0)) {
838 ++s;
839 if (p > start) {
840 while (*--p != '/'); /* omit previous dir */
841 }
842 continue;
843 }
844 }
845 }
846 *++p = *s;
847 } while (*++s);
848
849 if ((p == start) || (*p != '/')) { /* not a trailing slash */
850 ++p; /* so keep last character */
851 }
852 *p = 0;
853
854 return start;
855}