new my scripts/mm_mkdep, dependences work now
[platform/upstream/busybox.git] / scripts / bb_mkdep.c
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
42 typedef 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
53 void bb_error_d(const char *s, ...) __attribute__ ((noreturn, format (printf, 1, 2)));
54 char * bb_asprint(const char *format, ...) __attribute__ ((format (printf, 1, 2)));
55
56 /* stolen from libbb as is */
57 typedef struct llist_s {
58         char *data;
59         struct llist_s *link;
60 } llist_t;
61 llist_t *llist_add_to(llist_t *old_head, char *new_item);
62 void *xrealloc(void *p, size_t size);
63 void *xmalloc(size_t size);
64 char *bb_xstrdup(const char *s);
65 char *bb_simplify_path(const char *path);
66
67 /* for lexical analyzier */
68 static bb_key_t *key_top;
69
70 static void parse_inc(const char *include, const char *fname);
71 static void parse_conf_opt(char *opt, const char *val, size_t rsz);
72
73 #define CHECK_ONLY  0
74 #define MAKE_NEW    1
75 static 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 */
107 static 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
394 static void show_usage(void) __attribute__ ((noreturn));
395 static 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
401 static const char *kp;
402 static llist_t *Iop;
403 static bb_key_t *Ifound;
404 static int noiwarning;
405 static llist_t *configs;
406
407 static 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
426 static 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
439 static 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
475 static 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
564 static 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
593 static llist_t *files;
594
595 static 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
653 static 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
685 int 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
742 void 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
754 void *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
763 void *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
770 char *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
785 llist_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
796 char *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
804 char *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 }