#include "reclinker.h"

int (*preselfcall)();
void (*postselfcall)();

void (*wantmydose)(struct pathnode *root);

struct myarray *prefix;
char *prefixorig = NULL;
char *whereabs;
char *fromabs;

int forceproperprefix = 0;

#define checkUG(stru_stat)											\
	if ((Uid != -1 && Uid != stru_stat.st_uid) ||								\
	   (Gid != -1 && Gid != stru_stat.st_gid))								\
	{													\
		if (verbosity >= 2)										\
			printf("%s%s skipped because of non-matching uid/gid\n",fromorep,where->strmid);	\
		return 0;											\
	}

int
removelink()
{
	struct stat whatstat;
	struct stat anotherstat;
	void removedir();
	
	if (lstat(what, &whatstat) != 0)	
		error("can't stat",fromorep,where->strmid,NULL);

	checkUG(whatstat);
	
	if ((whatstat.st_mode & S_IFMT) == S_IFDIR) {
		if (stat(where->str, &anotherstat) == 0) {	
			if ((anotherstat.st_mode & S_IFMT) == S_IFDIR) 
				return 1;
			else {
				if (verbosity >= 2) {
					printf("%s%s is not a dir...\n",whereorep,where->strmid);
				}
				removedir();
				return 0;
			}
		} else {
			if (verbosity >= 2) {
				printf("%s%s/: can't stat: %s\n",whereorep,where->strmid,strerror(errno));
			}
			return 0; /* We don't descend into "what" in vain */
		}
	}

	if (firstmodeused && mode != (whatstat.st_mode & S_IRWXAUGS)) {
		if (verbosity >= 2)
			printf("%s%s kept because of non-matching mode\n",whereorep,where->strmid);
		return 0;
	}	

	

	if (lstat(where->str, &whatstat) != 0)	{
		if (verbosity >= 2)
			printf("%s%s: can't stat: %s\n",whereorep,where->strmid,strerror(errno));
		return 0;
	}
	
	if ((uid != -1 && uid != whatstat.st_uid) ||
	   (gid != -1 && gid != whatstat.st_gid)) {
		if (verbosity >= 1)
			printf("%s%s kept because of non-matching uid/gid\n",whereorep,where->strmid);
		return 0;
	}


	if (stat(where->str, &anotherstat) != 0)
		/* where->str is a broken symlink */
		goto removal;

	
	if (force) {
	/* what's not a symlink is tried to be removed */
		if ((whatstat.st_mode & S_IFMT) != S_IFLNK)
			goto removal;
	}

	if (! othermodeused)  {	/* othermodeused used to be named onlybroken, 
			     	 * it's just parsimony in option usage
			     	 */  
	/* where->str is removed iff it's a symlink */
		if ((whatstat.st_mode & S_IFMT) == S_IFLNK)
			goto removal;
	}
		
	return 0;

removal:
	if (remove(where->str) == 0) {
		if (verbosity >= 1)
			printf("%s%s removed\n",whereorep,where->strmid);
	} else {
		fprintf(stderr,"%s%s: ", whereorep,where->strmid);
		perror("remove failed");
		program_retval = SMALLPROB;
	}
	
	return 0;
}	
		
			
void
removedir()
{
	struct stat whatstat;
	
	if (onlydir)
		return;

	if (lstat(where->str, &whatstat) != 0) {
		if (verbosity >= 2)
			printf("%s%s: can't stat: %s\n",whereorep,where->strmid,strerror(errno));
		return;
	}

	if ((uid != -1 && uid != whatstat.st_uid) ||
	   (gid != -1 && gid != whatstat.st_gid)) {
		return;
	}	

	if (remove(where->str) == 0) {
		if (verbosity >= 1)
			printf("%s%s/ removed\n",whereorep,where->strmid);
	} else if (verbosity >= 2) {
		printf("%s%s/: remove failed: %s\n", whereorep,where->strmid,strerror(errno));
	}
}	


#define dirsuck() {						\
	fprintf(stderr,"%s%s/: ", whereorep,where->strmid);	\
  	perror("dir cannot be created");			\
	program_retval = SMALLPROB;				\
}

/* The implementation of proceeding a file (if it's a dir, the
 * counterpart is created on the other side, else it's symlinked to the
 * other side)
 */

int
linker()
{

	struct stat whatstat;
	/* char *to_linkto; */     
	int cdrv = 0;
	
	if (lstat(what, &whatstat) != 0)	
		error("can't stat",fromorep,where->strmid,NULL);		
	
	checkUG(whatstat);
	
	if (! firstmodeused)
		mode = (whatstat.st_mode & S_IRWXAUGS); 
	
	if ((whatstat.st_mode & S_IFMT) == S_IFDIR) {
		cdrv = createdir();
		if (cdrv == -2) {
			if (!force) {
				dirsuck();
				return 0;
			}
		}
		else
			return 1;
	}	

	if (force) {
		if (remove(where->str) != 0 && errno != ENOENT) {
			/* Maybe this block should be replaced with the invocation of a more
			 * general error fuction than the actual one ?? 
			 */
			program_retval = SMALLPROB;
			fprintf(stderr,"%s%s: ",whereorep,where->strmid);
			perror("forcing creation failed");
		}
		if (cdrv == -2) {
		 	if (createdir() == -2) {
				dirsuck();
				return 0;
			}		
			else
				return 1;
		}
	}

	if (onlydir) return 0; 

	if (symlink(prefix->str,where->str) == 0) {
		if (verbosity >= 1) {
			printf("%s%s -> %s",whereorep,where->strmid,prefix->str);
			if (verbosity >= 2)
				printf(": symlink created");
			putchar('\n');
		}

		chowner();
	} else {              
		program_retval = SMALLPROB;
		fprintf(stderr,"%s%s: ",whereorep,where->strmid);
		perror("the symlink cannot be created");
	}
	
	return 0;
}

void
modeforcer()
{
	if (! othermodeused)
		return; 

	if (chmod(where->str,mode_forced) == 0) {
		if (verbosity >= 2) 
 			printf("%s%s/: mode set to %o\n", whereorep,
		               where->strmid, (int)mode_forced);
	} else {	
		program_retval = SMALLPROB;
 		fprintf(stderr,"%s%s/: ",whereorep,where->strmid); 
 		perror("setting mode failed");
	}
}
	
int
test()
{
        struct stat whatstat;
        struct stat wherestat;
	int retval;
	char *typeind;
	int stat_retval;
	
	/* First we stat the actually processed entity, what.
	   It's *very* strange if this fails, so then we go error */
	if (lstat(what, &whatstat) != 0)	
		error("can't stat",fromorep,where->strmid,NULL);
	
	checkUG(whatstat);
		
	if ((whatstat.st_mode & S_IFMT) == S_IFDIR) {
	  /* Here we'll stat where->str; 
	     if what was a dir, then we need it resolved
	     otherwise as is */
		retval = 1;
		typeind = "/";
		stat_retval = stat(where->str,&wherestat);	
	} else {
		retval = 0;
		typeind = "";
		stat_retval = lstat(where->str,&wherestat);	
	}
	
	if (stat_retval != 0) {
		printf("%s%s%s: can't stat: %s\n",whereorep,where->strmid,typeind,strerror(errno));
		program_retval = SMALLPROB;
		return 0; /* We don't descend into what in vain */	
	} else if ((firstmodeused && mode != (whatstat.st_mode & S_IRWXAUGS)) ||
		   (uid != -1 && uid != wherestat.st_uid) ||
		   (gid != -1 && gid != wherestat.st_gid)) {
		if (retval == 0) {
			printf("%s%s%s: permissions, owner or group is not as it's required\n",whereorep,where->strmid,typeind);
			program_retval = SMALLPROB;
		}
		return retval;
	}

	if (retval) {
		/* "what" is a dir */
		if ((wherestat.st_mode & S_IFMT) == S_IFDIR) {
			if (verbosity >= 2)
				printf("%s%s/ is OK\n",whereorep,where->strmid);
		} else {
			printf("%s%s/ exists but it's not a dir\n",whereorep,where->strmid);
			program_retval = SMALLPROB;
			retval = 0; /* don't descend into "what" in vain */
		}
	}
	else if ((wherestat.st_mode & S_IFMT) != S_IFLNK) {
		program_retval = SMALLPROB;
		printf("%s%s exists but it's not a symlink\n",whereorep,where->strmid);
		return retval;
	} else if (! rec_pointto(where->str,what)) {
		program_retval = SMALLPROB;
		printf("%s%s is an existing symlink but it's not resolved in %s%s\n",whereorep,where->strmid,fromorep,where->strmid);
		return retval;
	} else {	
		char *l, *f;
		int v;
		l = canon_readlink(where->str);
		f = soft_realpath(what);
		v = strcmp(l,f);
		free(l);
		free(f);
		if (v != 0) {
			program_retval = SMALLPROB;
			printf("%s%s is a symlink resolved in %s%s, but it's pointing there only indirectly\n",whereorep,where->strmid,fromorep,where->strmid);
			return retval;
		} else {
			f = my_readlink(where->str);
			v = strcmp(f,prefix->str);
			free(f);
			if (v != 0) {
				program_retval = SMALLPROB;
				printf("%s%s is a symlink pointing to where it should but the value is not named as it should be\n",whereorep,where->strmid);
			} else if (verbosity >= 2) {	
				printf("%s%s is OK\n",whereorep,where->strmid);
			}
		}
	}
	return retval;
}
					
			

void
noop()
{
}

/* The following two functions can serve as "wantmydose" */
void
fetchdir(struct pathnode *root)
{
	DIR *dir;
	struct dirent *entry;
	struct pathnode *pn;
	dir = opendir(".");
	pn = root;
	while ((entry = readdir(dir))) {
		addnode(pn,entry->d_name);
	}
	if (closedir(dir) != 0)                                                  
		error((char *)GETCWD(NULL,0),"unable to close dir");
}

void
nextinpath(struct pathnode *root)
{
	if (indivfile->next != NULL) {
		indivfile = indivfile->next;
		addnode(root,indivfile->name);
	}
}


#define backabit(arr,n)		\
{				\
	arr->strend -= n;	\
	*(arr->strend) = '\0';	\
}

#define forthabit(arr,n) arr->str += n

struct pathstate {
	struct myarray *prefix;
	struct pathnode *root;
	struct pathnode *pn;
	int postgrowth;
};


/*
 * A recursive function to walk throgh all the source dir tree and
 * calling linker on the certain files
 */
void
reclinker()
{
	/*
	 * State information which is kept around over recursive calls
	 * is quarantined in pstate
	 */
	struct pathstate pstate;
	memset(&pstate, 0, sizeof(pstate));

	initpath(pstate.root);
	wantmydose(pstate.root);
	pstate.pn = pstate.root;

	while ((pstate.pn = pstate.pn->next)) {
		int prune = 0;
		char *aux = NULL, *realwhere = NULL;

		what = pstate.pn->name;
		if (strcmp(what,".") == 0 || strcmp(what,"..") == 0)
			continue;

		pstate.postgrowth = strlen(what) + 1;
		appendasadir(where,what);
		if (deletemode != 1)
			appendasadir(prefix,what);

		/*
		 * We don't descend into directories which reside inside
		 * the mirrored tree
		 */
		realwhere = my_realpath(where->str);
		if (realwhere)
			aux = strreduce(realwhere, fromabs);
		if (aux != NULL && (*aux == '\0' || *aux == '/')) {
			struct stat astat;

			stat(realwhere, &astat);
			if ((astat.st_mode & S_IFMT) == S_IFDIR) {
				prune = 1;
				if (verbosity >= 2)
					printf("pruning %s%s as it's contained in original copy\n", whereorep, where->strmid);
			}
		}
		free(aux);

		if (! prune && preselfcall()) {
		/* preselfcall is a function pointer, may point to
		 * linker, removelink or test */
			if (chdir(what) == 0) {
				if (rel) {
						struct stat whatstat;
						lstat(where->str,&whatstat);
						if (forceproperprefix || (whatstat.st_mode & S_IFMT) == S_IFLNK) {
							char *abspref, *pathbetween;

							abspref = (char *)MALLOC(strlen(whereabs) + strlen(prefixorig) + strlen(where->strmid) + 2);
							strcpy(abspref,whereabs);
							strcat(abspref,"/");
							strcat(abspref,prefixorig);
							strcat(abspref,where->strmid);
							if (!forceproperprefix)
								dupemyarray(prefix, pstate.prefix);

							resetmyarray(prefix);
							if (! realwhere)
								/*
								 *  where->str might be ceated in preselfcall()
								 *  in which case realwhere needs to be recomputed
								 */
								realwhere = my_realpath(where->str);
							/* XXX this assertion doesn't cares about races with the real world */
							assert(realwhere);
							pathbetween = str_relpath(realwhere,abspref);
							appendtomyarray(prefix,pathbetween);

							free(abspref);
							free(pathbetween);
						}
						else
							prependtomyarray(prefix,"../");
				}

				free(realwhere);
				reclinker();

				if (chdir("..") == 0)	{
					if (rel) {
						if (pstate.prefix == NULL) {
							forthabit(prefix,3);
						} else {
							freemyarray(prefix);
							prefix = pstate.prefix;
						}
					}
					/* If we want to set the mode of
					 * created dirs arbitrarily, we have to
					 * do it here, after recursive calls of
 					 * reclinker() are over, thus avioding
					 * potential read/write conflicts of
					 * children reclinker()'s
					 */
					postselfcall();
					/* It's a function pointer which may
					 * point to modeforcer, removedir or
					 * noop
					 */
				} else
					error("can't go back to parent dir",what,NULL);
					/*Unlikely error*/
			} else {
				free(realwhere);
				program_retval = SMALLPROB;
				fprintf(stderr,"%s%s: ",fromorep,where->strmid);
				perror("unable to enter dir");
			}
		} else
			free(realwhere);
		backabit(where, pstate.postgrowth);
		if (deletemode != 1)
			backabit(prefix, pstate.postgrowth);
	}
	freepath(pstate.root);
}




int
main(int argc, char **argv)
{
	int o;
	char *aux;
	int auxlength;
	void act_as_reclinker(int argc, char** argv);
	void act_as_recdeleter(int argc, char** argv);	
	void act_as_reclinktester(int argc, char** argv);
	int len;		

	me = (char*)basename(argv[0]);

	defaults();	

	if (strcmp(me,DELETE_INVOKE) == 0) deletemode=1;
	if (strcmp(me,TEST_INVOKE) == 0) deletemode=2;
	
	while ((o = getopt (argc, argv, "hvqdltrfDm:ou:g:U:G:p:")) != -1) {
		switch (o) {
		case 'h':
			usage(stdout);
			showhelp();
			exit(BADOPT);
		case 'v':
			verbosity += 1;
			break;
		case 'q':
			verbosity -= 1;
			break;
		case 'r': 
			rel=1;
			break;
		case 'd':
			deletemode=1;
			break;
		case 'l':
			deletemode=0;
			break;
		case 't':
			deletemode=2;
			break;		
		case 'f':
			force=1;
			break;
		case 'D':
			onlydir=1;
			break;
		case 'm':
			mode = parsemode(optarg);
			firstmodeused = 1;
			break;
		case 'o':
			othermodeused = 1;
			break;
		case 'u':
			usrname = optarg;
			if ((uid = parseuid(optarg)) == -1) {
				fprintf(stderr, "%s: no such user\n", usrname);
				exit(FATAL);
			}
			break;
		case 'g':
			grpname = optarg;
			if ((gid = parsegid(optarg)) == -1) {
				fprintf(stderr, "%s: no such group\n", grpname);
				exit(FATAL);
			}
			break;
		case 'U':
			Usrname = optarg;
			if ((Uid = parseuid(optarg)) == -1) {
				fprintf(stderr, "%s: no such user\n", Usrname);
				exit(FATAL);
			}
			break;
		case 'G':
			Grpname = optarg;
			if ((Gid = parsegid(optarg)) == -1) {
				fprintf(stderr, "%s: no such group\n", Grpname);
				exit(FATAL);
			}
			break;
		case 'p':
			prefixorig = optarg;
			break;	
		case '?':
			badopt;
		}	 	
	}
	
	if (argc < optind + 2) {
		badopt;
	}

	aux = (char*) GETCWD(NULL,0);
	auxlength = strlen(aux) + 1;
	aux = (char *)REALLOC(aux,auxlength);
	strcat(aux,"/");
	
#define makerepstr(wg,wp) {			\
	len = strlen(wg);			\
	len--;					\
	if (*(wg + len) == '/') {		\
		wp = (char *)malloc(len + 1);	\
		memcpy(wp,wg,len);		\
		*(wp + len) = '\0';		\
	} else {				\
		wp = wg;			\
	}					\
}

	whereorig = argv[optind+1];
	makerepstr(whereorig,whereorep);
	initmyarray(where);
	if (*whereorig == '/') {
		appendtomyarray(where,normalize(whereorig));
	} else {
		aux = (char *)REALLOC(aux,auxlength + strlen(whereorig) + 1);
		appendtomyarray(where,normalize(strcat(aux,whereorig)));	
	}
	where->strmid = where->strend;

	fromorig = argv[optind];
	makerepstr(fromorig,fromorep);
	if (*fromorig == '/') {
		from = normalize(fromorig);
	} else {
		aux = (char *)REALLOC(aux,auxlength + strlen(fromorig) + 1);
		*(aux + auxlength) = '\0'; 
		from = normalize(strcat(aux,fromorig));	
	}
	free(aux);
	fromabs = my_realpath(from);

	switch (deletemode) {
	case 0:
		act_as_reclinker(argc,argv);
		break;
	case 1: 
		act_as_recdeleter(argc,argv);
		break;
	case 2:
		act_as_reclinktester(argc,argv);
		break;
	default:
		fprintf(stderr,"%d: no such mode defined\n",deletemode);
		exit(BADOPT);
	}	
	
	if (argc >= optind + 3) {
		int i;
		wantmydose = nextinpath;
		for (i=optind+2;i < argc;i++) {
			if (chdir(from) != 0) {
				fprintf(stderr,"%s/: ",from);
				perror("can't enter source directory");
				exit(FATAL);
			}
			if (strcmp(argv[i],"-") != 0 &&
			   strcmp(argv[i],"0-") != 0) {
				indivfile = strtopath(argv[i]);
				reclinker();
				postselfcall();
			} else {			
				char line[PATH_MAX], sepchar;
				int res;
				unsigned lno;

				switch((*argv[i])) {
				case '-':
					sepchar = '\n';
					break;
				case '0':
					sepchar = '\0';
					break;
				default:
					fprintf(stderr, "stdin parsing mode sigill is weird\n");
					abort();
				}	
				for (lno = 1; lno++; ) {
					res = get_line_from_file(stdin, line, sizeof(line), sepchar);
					if (res <= 0) {
						if (res == 0)
							break;
						fprintf(stderr, "invalid input line %u\n", lno);
						exit(FATAL);
					}
					indivfile = strtopath(line);
					reclinker();
					postselfcall();
				}
			}
		}
	} else {
		wantmydose = fetchdir; 	
		reclinker();
		postselfcall();
	}

	exit(program_retval);
}
	
#define initprefix {							\
	if (prefixorig == NULL) {					\
		if (rel) {						\
			prefixorig = str_relpath(whereabs,from);	\
		} else							\
			prefixorig = from;				\
	}	 							\
	initmyarray(prefix);						\
	appendtomyarray(prefix,prefixorig);				\
	if (!strsubtest(prefix->str,"../"))				\
		forceproperprefix = 1;					\
}

void
act_as_reclinker(int argc, char** argv)
{
	char* currdir; 
	char* aux = NULL;
	int isnew;

	if (firstmodeused) {
		mode_forced = mode;
		mode = (mode| S_IWUSR | S_IXUSR);
		
		if (mode != mode_forced && (! othermodeused)) {
			fprintf(stderr,
"Prescribed mode given by -m is supported only for modes including\n"
"write/execute permissions for user -- if you *really* want another mode,\n"
"use -o as well\n");
			exit(BADOPT);
		}
	} else { struct stat fromstat;
		if (stat(from,&fromstat) != 0) {
			error("can't stat",fromorig,NULL);
		}
		mode = fromstat.st_mode & S_IRWXAUGS;
	}

	/* The createdir() fucntion uses the value of mask (for reoporting only) */
	mask = umask (0);
	umask (mask);	

	isnew = createdir();
	if (isnew == -2) {
		/* Bah. I couldn't help putting here this block which is taken
		 * from linker() with small modifications. Ugly solution,
		 * anyway. 
		 */
		if (force) {
			if (remove(where->str) != 0 && errno != ENOENT) {
				/* Maybe this block should be replaced with the invocation of a more
				 * general error fuction than the actual one ?? 
				 */
				program_retval = SMALLPROB;
				fprintf(stderr,"%s%s: ",whereorep,where->strmid);
				perror("forcing creation failed");
			} else
				isnew = createdir();
			if (isnew != -2)
				goto goon;
		}
		fprintf(stderr, "creating target directory failed\n");
		exit(FATAL);
	}
goon:
	if (chdir(from) != 0) {
		fprintf(stderr,"%s: ",fromorig);
		perror("can't enter source directory");
		cleanup_aux(isnew);
		exit(FATAL);
	}

	/* The following block is for avoiding infite loops and overwriting 
	 * files in source dir with symlinks
	 */
	whereabs = my_realpath(where->str);
	currdir = (char*) GETCWD(NULL,0);
	if (whereabs)
		aux = strreduce(whereabs, currdir);
	if (aux != NULL && (*aux == '\0' || *aux == '/')) { 
		/* Eh, and if from is subdir of where, ain't that a problem?? */
		fprintf(stderr,"Target dir cannot be subdir of source dir\n");
		/* Problem: "whereorig" is created regardless the above anomaly shows
		 * up; quick'n'dirty solution: if it's newly created, we remove it
		 * immediately
		 */      
		cleanup_aux(isnew);
		exit(BADOPT);
	}
	free(currdir);
	free(aux);
 
	initprefix;
	
	preselfcall = linker;
	postselfcall = modeforcer;
}


void
act_as_recdeleter(int argc, char** argv)
{
	if (chdir(from) != 0) {
		fprintf(stderr,"%s/: ",from);
		perror("can't enter source directory");
		exit(FATAL);
	}

	/* what = from; */
	
	preselfcall = removelink;
	postselfcall = removedir;

	what = my_realpath(from);
	if (preselfcall() == 0)
		exit(SUCCESS);
	free(what);

	rel=0; /*recdeleter makes no use of -r*/
}

void
act_as_reclinktester(int argc, char** argv)
{
	/* struct stat wherestat; */

	if (chdir(from) != 0) {
		fprintf(stderr,"%s/: ",from);
		perror("can't enter source directory");
		exit(FATAL);
	}

	whereabs = my_realpath(where->str);
 
	initprefix;
	
	preselfcall = test;
	postselfcall = noop;

	what = my_realpath(from);
	if (preselfcall() == 0)
		exit(SUCCESS);
	free(what);
}


syntax highlighted by Code2HTML, v. 0.9.1