#if !defined(lint) && !defined(SABER)
static char rcsid[] = "$Id: rootlinks.c,v 1.3 1992/05/14 18:43:38 epeisach Exp $";
#endif

#include "mkserv.h"

struct root_link {
	struct root_link *next;
	char *current;
	char *pointing;
};

static void free_link_tree(struct root_link *p);
static int recurse_link_fix(char *, struct stat *, char *, char *);

/* 
 *	fix_root_links(char *serverdir, char *rvdroot)
 *
 *	Restores all symlinks indirecting through the serverdir
 *	back to the srvd. This is necessary if we delete a service and 
 *	remove the servrdir copy, the rootdir copy will be broken.
 *
 *	We build up a structure of changes to be made in order to ensure
 *	the directory tree does not change will scanning the directories.
 *
 *	Similar sh code: 
 *		for i in `find / -xdev -type l -ls | \
 *		/bin/sed -n -e "s:^.*${SERVERDIR}::p"`; do
 *		/bin/rm -f $i; /bin/ln -s ${RVDROOT}$i $i
 *	done
 */

int fix_root_links(char * serverdir, char *rvdroot)
{
	struct root_link *p, *op;
	int ret, rvdrootlen=strlen(rvdroot), serverdirlen=strlen(serverdir);
	char buf[MAXPATHLEN+1];

	p = NULL;

	ret = walk_tree("/", recurse_link_fix, serverdir, (char *) &p, 0);
	if(ret) {
		if(p) free_link_tree(p);
		return ret;
	}
	
	if(os_usr_not_on_root()) {
	  ret = walk_tree("/usr", recurse_link_fix, serverdir, (char *) &p, 1);
	  if(ret) {
	    if(p) free_link_tree(p);
	    return ret;
	  }
	}
	
	op = p;
	while(p != NULL) {
		char *c;

		/* Need to determine changes necessary... */
		strcpy(buf,p->pointing);
		
		if((c = find_in_string(serverdir, buf)) == NULL) {
			fprintf(stderr, "Inconsistancy in fix_root_links");
			return 1;
		}

		/* buf has what we are pointing at.... */
		/* c points to begining of change - lets chanegs */
		strcpy(c, rvdroot);

		/* Now copy the remainder */

		strcpy(c + rvdrootlen, p->pointing + (c-buf)+ serverdirlen);
#if DEBUG
		printf("%s -> %s\n", p->current, p->pointing); 
		printf("rm %s\n", p->current);
		printf("ln -s %s %s\n", buf, p->current);
#endif
		if(unlink(p->current)) {
			sprintf(buf, "In removing %s", p->current);
			perror(buf);
			free_link_tree(op);
			return 1;
		}

		if(symlink(buf, p->current)) {
			char buf1[2*MAXPATHLEN+20];
			sprintf(buf1, "In symlinking %s -> %s", p->current, buf);
			perror(buf1);
			free_link_tree(op);
			return 1;
		}

		p = p->next;
	}

	free_link_tree(op);
	return 0;
}

static void 
free_link_tree(struct root_link *p)
{
	struct root_link *op;
	
	while(p) {
		free(p->current);
		free(p->pointing);
		op = p;
		p = p->next;
		free(op);
	}
}


static 
recurse_link_fix(char *name, struct stat *sbuf, char *server, char *opt)
{
	char buf[MAXPATHLEN+1];
	int cc;
	struct root_link **p = (struct root_link **) opt;
	struct root_link *rl;

	/* Only interested in links */
	if((sbuf->st_mode & S_IFMT) != S_IFLNK) return 0;

	cc = readlink(name, buf, MAXPATHLEN);
	if(cc < 0) {
		perror("In adjusting links");
		return 1;
	}
	buf[cc]='\0';
	/* printf("%s -> %s\n", name, buf);  */
	if(find_in_string(server, buf) != NULL) {
		rl = (struct root_link *) malloc(sizeof(struct root_link));
		if (rl == NULL) {
			fprintf(stderr, "Out of memory in recurse_link\n");
			return 1;
		}
		rl->current = strdup(name);
		if(rl->current == NULL) {
			fprintf(stderr, "Out of memory in recurse_link\n");
			return 1;
		}
		rl->pointing = strdup(buf);
		if(rl->pointing == NULL) {
			fprintf(stderr, "Out of memory in recurse_link\n");
			return 1;
		}
		rl->next = *p;
		*p = rl;
	}	
	return 0;
}


/*
 *	walk_tree(char *directory, int (*funcptr)(char *, stat *, char *), char *opt)
 *	Walks a tree, without crossing devices or following symlinks
 *	
 *	directory must not be a symlink
 *
 *	For each entry, call (*funcptr)(name, opt)
 *	If (*funcptr) returns non-zero, return from walk_tree with 
 *		value
 *
 *	Return value:
 *		(*funcptr) if error
 *		0 no error
 *		-1 if cannot open directory, or not a directory specified
 */
int walk_tree(char *directory,
	      int (*funcptr) _AP((char *, struct stat *, char *, char *)),
	      char *opt1, char *opt2, int norecurse)
{
	register DIR   *dirp;
	my_dir	       *dp;
	struct stat	sbuf;
	int 		ret, i;
	char 	       *pathname, *p;
	dev_t		dev;

	/* If can't stat, directory, then return with error */
	if(lstat(directory, &sbuf) < 0) 
		return -1;

	/* If not a directory, then problem... Handles the symlink case */
	if((sbuf.st_mode & S_IFMT) != S_IFDIR)
		return -1;
	
	/* Open directory; if we fail, return */
	dirp = opendir(directory);
	if(dirp == NULL) 
		return -1;

	/* Save device name */
	dev = sbuf.st_dev;

	/* Call function and if application indicates, close and return */
	ret = (*funcptr)(directory, &sbuf, opt1, opt2);
	if (ret) {
		closedir(dirp);
		return ret;
	}

	/* Save full path of name if continuing */
	i=strlen(directory);
	if((pathname = malloc(i + 2 + MAXNAMLEN)) == NULL) {
		closedir(dirp);
		return -1;
	}
	(void) strcpy(pathname, directory);

	/* Append / */
	p = &pathname[i];
	if (i && p[-1] != '/') 
		*p++ = '/';

	/* Ok - lets read and recurse */
	while ((dp = readdir(dirp)) != NULL) {
		/* Skip . and .. */
		if(strcmp(dp->d_name, ".") && strcmp(dp->d_name, "..")) {
			(void) strcpy(p, dp->d_name);
			if(lstat(pathname, &sbuf) < 0) 
				continue;
			if(((sbuf.st_mode & S_IFMT) == S_IFDIR) && 
			   (sbuf.st_dev == dev) && (norecurse == 0)) {
				ret = walk_tree(pathname, funcptr,
						opt1, opt2, norecurse);
				if (ret) {
					free(pathname);
					closedir(dirp);
					return ret;
				}
			} else {
				ret = (*funcptr)(pathname, &sbuf, opt1, opt2);
				if (ret) {
					free(pathname);
					closedir(dirp);
					return ret;
				}
			}
		}

	} /* while */

	free(pathname);
	closedir(dirp);
	return 0;
}

