/* 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 <config.h>
#include "global.h"
#include "pipe-headers.h"
#include <my_string.h>
#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

