/* pathdup.c - reproduces a path stripping /../ /./ and resolving symlinks Copyright (C) 1996-2000 Paul Sheer This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */ #include #include "global.h" #include "pipe-headers.h" #include #include "mad.h" struct comp { struct comp *prev; struct comp *next; char name[2]; }; static struct comp *comp_last (struct comp *p) { while (p->next) p = p->next; return p; } static struct comp *comp_first (struct comp *p) { while (p->prev) p = p->prev; return p; } static inline struct comp *comp_cat (struct comp *s, struct comp *t) { s = comp_last (s); t = comp_first (t); s->next = t; t->prev = s; return comp_first (s); } static inline struct comp *comp_insert (struct comp *p, struct comp *s) { struct comp *t; t = comp_last (s); s = comp_first (s); if (p->prev) p->prev->next = s; if (p->next) p->next->prev = t; t->next = p->next; s->prev = p->prev; memset (p, 0, sizeof (*p)); free (p); return t; } static inline struct comp *comp_replace (struct comp *p, struct comp *s) { struct comp *t, *prev, *r; t = comp_last (s); if (p->next) p->next->prev = t; t->next = p->next; for (r = p; r; r = prev) { prev = r->prev; memset (r, 0, sizeof (*r)); free (r); } return t; } static inline void comp_free (struct comp *p) { struct comp *next; p = comp_first (p); for (; p; p = next) { next = p->next; memset (p, 0, sizeof (*p)); free (p); } } #define COMP_DUMP(p) \ if (u == p) \ u = p->next; \ if (p->next) \ p->next->prev = p->prev; \ if (p->prev) \ p->prev->next = p->next; \ memset (p, 0, sizeof (*p)); \ free (p); /* dump .. . and nothings, but remember the place in the list of p */ static struct comp *comp_strip (struct comp *p) { struct comp *u, *next; u = comp_first (p); for (p = u; p; p = next) { next = p->next; if (!*p->name || !strcmp (p->name, ".")) { COMP_DUMP (p); } else if (!strcmp (p->name, "..")) { struct comp *t; if ((t = p->prev)) { COMP_DUMP (t); } COMP_DUMP (p); } } if (!u) { /* mustn't strip everything */ u = malloc (sizeof (struct comp)); memset (u, 0, sizeof (struct comp)); } return u; } /* split into a list along / */ #ifdef HAVE_MAD static char *mad_comp_combine (struct comp *s, char *file, int line) #define comp_combine(s) mad_comp_combine(s, __FILE__, __LINE__) #else static char *comp_combine (struct comp *s) #endif { int n; struct comp *t, *f; char *p, *r; f = comp_first (s); for (n = 0, t = f; t != s->next; t = t->next) n += strlen (t->name) + 1; #ifdef HAVE_MAD r = mad_alloc (n + 2, file, line); #else r = malloc (n + 2); #endif for (p = r, t = f; t != s->next; t = t->next) { *p++ = '/'; strcpy (p, t->name); p += strlen (p); } return r; } /* split into a list along / */ static struct comp *comp_tize (char *s) { struct comp *u, *p = 0; char *t; int done = 0; while (!done) { int l; t = (char *) strchr (s, '/'); if (!t) { t = s + strlen (s); done = 1; } l = (unsigned long) t - (unsigned long) s; u = malloc (sizeof (struct comp) + l); u->prev = p; u->next = 0; if (p) p->next = u; p = u; memcpy (u->name, s, l); u->name[l] = '\0'; s = t + 1; } return p; } static inline char *comp_readlink (struct comp *p) { char *s; int r; static char buf[2048]; s = comp_combine (p); r = readlink (s, buf, 2047); if (r == -1 && errno == EINVAL) { free (s); return ""; } if (r == -1) { free (s); return 0; } buf[r] = '\0'; free (s); return buf; } /* if there is an error, this just returns as far as it got */ static inline struct comp *resolve_symlink (struct comp *path) { int i; struct comp *t; path = comp_strip (comp_first (path)); path = comp_last (path); for (i = 0;; i++) { char *l; if (i >= 1000) break; l = comp_readlink (path); if (!l) break; if (l[0] == '/') { /* absolute symlink */ t = comp_tize (l); path = comp_replace (path, t); path = comp_strip (path); path = comp_last (path); continue; } else if (*l) { /* relative symlink */ t = comp_tize (l); path = comp_insert (path, t); path = comp_strip (path); path = comp_last (path); continue; } else if (path->prev) { /* not a symlink */ path = path->prev; continue; } break; } return path; } extern char *home_dir; #ifdef HAVE_MAD char *mad_pathdup (char *p, char *file, int line) #else char *pathdup (char *p) #endif { struct comp *s; s = comp_tize (p); if (!strcmp (comp_first (s)->name, "~")) { s = comp_replace (comp_first (s), comp_tize (home_dir)); } else if (*p != '/') { char *cwd; cwd = malloc (2048); #ifdef HAVE_GETCWD getcwd (cwd, 2047); #else getwd (cwd); #endif s = comp_cat (comp_tize (cwd), comp_tize (p)); free (cwd); } s = resolve_symlink (s); #ifdef HAVE_MAD p = mad_comp_combine (comp_last (s), file, line); #else p = comp_combine (comp_last (s)); #endif comp_free (s); return p; } #if 0 char *home_dir = "/root"; int main (int argc, char **argv) { printf ("%s\n", pathdup (argv[1])); return 0; } #endif