#include <stdarg.h>

#include "reclinker.h"

#ifndef GNU 
char *
gnu_getcwd ()
{
	size_t size = 100;
	void * MALLOC (size_t);

	while (1) {
		char *buffer = (char *) MALLOC (size);
		if (getcwd (buffer, size) == buffer)
			return buffer;
		free (buffer);
		if (errno != ERANGE)
			return 0;
		size *= 2;
	}
}
#endif

/* Erases "reduce_with" from "from" if it is an initial segment */
char *
strreduce(char *from, const char *reduce_with)
{
	while(*reduce_with == *from && *reduce_with != '\0') {
		reduce_with++;
		from++;
	}
	if(*reduce_with == '\0')
		return strdup(from);
	return NULL;
}

int
strsubtest(char *from, const char *reduce_with)
{
	while(*reduce_with == *from && *reduce_with != '\0') {
		reduce_with++;
		from++;
	}
	if(*reduce_with == '\0')
		return 1;
	return 0;
}


void *
xmalloc (size_t size)
{
	register void *value = (void*)malloc(size);
	if (value == 0)
		error ("virtual memory exhausted","malloc",NULL);
	return value;
}

void *
xrealloc (char* buf,size_t size)
{
	register void *value = (void*)realloc(buf,size);
	if (value == 0)
		error ("virtual memory exhausted","realloc",NULL);
	return value;
}

void
error(char *msg,...)
{
	va_list vl;
	char *s;

	va_start(vl,msg);
	while ((s = va_arg(vl, char *)))
		fprintf(stderr,s);
	va_end(vl);

	fprintf(stderr,": ");
	perror(msg);
	exit(FATAL);
}

void
chowner()
{
	if(uid == -1  && gid == -1)
		return;

	if (lchown(where->str,uid,gid) == 0) {
		  /*lchown was not mentioned in glibc docs, but it does exist */
		if (verbosity >= 2) { 
			printf("%s%s: setting ",whereorep,where->strmid);
			if (usrname != NULL)  
				printf("uid to %s ", usrname);
			if (usrname != NULL && grpname != NULL) 
				printf( "and ");
			if (grpname != NULL) 
				printf("gid to %s ", grpname);
			printf("\n");      
		}	
	} else { 
		program_retval = SMALLPROB;
		fprintf(stderr,"%s%s: setting ",whereorep,where->strmid);
		if (usrname != NULL)  
			fprintf(stderr, "uid to %s ", usrname);
		if (usrname != NULL && grpname != NULL) 
			fprintf(stderr, "and ");
		if (grpname != NULL) 
			fprintf(stderr, "gid to %s ", grpname);
		fprintf(stderr,"failed\n");      
	}	
}
     
/* How to proceed a directory (it's put to a separate function as both 
 * main() and linker() uses this)
 */

int
createdir()
{
	int retval = mkdir(where->str, mode);
	int errno_saved = errno;
		
	if(retval == 0)	{  
		if(verbosity >= 1) {
			printf("%s%s/",whereorep,where->strmid);
			if (verbosity >= 2) 
				printf(": dir created with mode %o",
			               (int)(mode & ~mask));
	  	  	putchar('\n');	
		}	

		chowner();
			
	} else if (errno == EEXIST 
#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \
    defined(__DragonFly__)
	           /* work around weird BSD errno upon mkdir("/") */
                   || errno == EISDIR
#endif
 	          ) {
		struct stat to_createstat;
		stat(where->str, &to_createstat);
		if ((to_createstat.st_mode & S_IFMT) == S_IFDIR) {
			if(verbosity >= 2) {
				printf("%s%s/: dir already exists\n",whereorep,where->strmid);
			} 
		} else {
		/*	uerror("the dir cannot be created");  */
			errno = errno_saved;
			return -2;
		}
	} else {
	/*	uerror("the dir cannot be created"); */
		errno = errno_saved;
		return -2;
	}
	/* Do we really have to be so strict and raise an error? */
 	return retval;
}  

void
defaults()
{
	program_retval = SUCCESS;
	verbosity=1;
	force=0;
	rel=0;
	/* mode = S_IRWXAUGS; */
	onlydir=0;
	deletemode=0;
	firstmodeused=0;
	othermodeused=0;
	uid = -1;
	gid = -1;
	Uid = -1;
	Gid = -1;
}




void
usage(FILE* desc)
{
	fprintf(desc,
"Recursive linking/deleting utility, version %s. Usage:\n\
\n\
%s [-l|-d|-t] [options] <from> <where> [<file>...]\n",VERSION,me);
}

void
showhelp()
{
	printf(
"\n\
The program has a linker, a deleter and a test mode. By default it's in\n\
linker mode. It gets into deleter mode if it's invoked as %s, and into\n\
test mode if it's invoked as %s. Linker/deleter/test mode may be\n\
forced with the -l/-d/-t switches.\n\
\n\
In linker mode it mirrors the directory tree under <from> to under <where>\n\
and symlinks files there. The symlinks point to the basename of the file\n\
processed, prefixed with <prefix>. If <prefix> is not given, it defaults\n\
to the absolute pathname of <from> (ie., the symlink points to the\n\
correct absolute filename).\n\
\n\
In deleter mode it deletes non-broken symlinks (empty directories) of\n\
the form <where>/foo, where <from>/foo is an existing file (directory).\n\
(A dir is also considered empty if it gets empty during the deletion\n\
procedure.)\n\
\n\
In test mode it tests whether symlinks/dirs under <where> corresponding\n\
to files/dirs under <from> exist, and whether are they created properly.\n\
\n\
If extra arguments are given, then they will be treated as files named\n\
relatively from <from>, and they will be proceeded individually \n\
(no mass action is taken). If \"-\" is among these extra arguments,\n\
then individual file names are also read from stdin; if \"0-\" is among\n\
them, individual file names are also read from stdin, being separated\n\
by  '\\0'.\n\
\n\
Options common for all modes:\n\
\n\
  -h\t\tprint this message and exit\n\
  -U <user>\tskip file under <from> if it's not owned by <user>\n\
  -G <group>\tskip file under <from> if it doesn't belong to <group>\n\
  -v/-q\t\tincrease/decrease verbosity level\n\
  \t\t(currently 0, 1, 2 are in use, default is 1)\n\
\n\
Options in linker mode are:\n\
\n\
  -r\t\tproduce relative symlinks\n\
  -f\t\toverwrite existing files\n\
  -m <mode>\tcreate new directories with mode <mode> ( &'d with umask!)\n\
  -o\t\tforce <mode> given by -m for all processed directories \n\
  \t\t(if -m is not used, the mode of the actually processed\n\
  \t\tdir is forced)\n\
  -u <user>\tnewly created dirs/symlinks shall be owned by <user>\n\
  -g <group>\tnewly created dirs/symlinks shall belong to <group>\n\
  -D\t\tonly directories are proceeded \n\
  -p <prefix>\tprepend <prefix> to link targets \n\
  \n\
Options in deleter mode are:\n\
\n\
  -f\t\tdelete corresponding file even it's not a symlink\n\
  -m <mode>\tdeletes <where>/foo only if <from>/foo is of mode <mode>\n\
  -o\t\tonly broken symlinks are deleted\n\
  -u <user>\tdeletes <where>/foo only if it belongs to <user>\n\
  -g <group>\tdeletes <where>/foo only if it belongs to <group>\n\
  -D\t\tdon't delete directories \n\
 \n\
Options in test mode are:\n\
\n\
  -r\t\ttest symlinks as if they were created using -r in linker mode\n\
  -m <mode>\tskip foo if <from>/foo isn't of mode <mode>\n\
  -u <user>\tskip foo if <where>/foo is not owned by <user>\n\
  -g <group>\tskip foo if <where>/foo doesn't belong to <group>\n\
  -p <prefix>\ttest links assuming they were created using \"-p <prefix>\" \n\
\n\
Return values: 0 - success; 1 - fatal error; 2 - some act failed but\n\
just kept on doing; 3 - arguments imply nothing happens (eg., bad\n\
options used)\n\
",DELETE_INVOKE,TEST_INVOKE);
}



gid_t
parsegid(char* grpnam)
{
	struct group* grp;
	char *p = NULL;
	gid_t _gid_;

	_gid_ = strtoul(grpnam, &p, 10);  /* Is it numeric? */
	if (grpnam == p) {
/* #ifdef __UCLIBC__ 
 that was an oooold uClibc, now I dare to forget about this issue
*/
#if 0
/* It's a damn stupid bug in uClibc (I met with it in 0.9.21): if <group> 
resides on the 0x*d-th row of /etc/group, then calling twice 
getgrnam(<group>) with no intermediate usage of getgrnam results in a segfault
*/
		getgrnam("root");
#endif
		grp = getgrnam(grpnam);
	}
	else
		grp = getgrgid(_gid_);

	if (grp == NULL)
		return -1;
	return grp->gr_gid;  
}

	
uid_t
parseuid(char* usrnam) /* based on Busybox code */
{
	struct passwd* usr;
	char *p = NULL;
	uid_t _uid_; 

	_uid_ = strtoul(usrnam, &p, 10);  /* Is it numeric? */
	if (usrnam == p)
		usr = getpwnam(usrnam);
	else
		usr = getpwuid(_uid_);

	if (usr == NULL)
		return -1;
	return usr->pw_uid;  
}


mode_t
parsemode(char* modestring)
{
	char* modestring_orig = modestring;
	int i=0;

	while (*modestring != '\0') {
		if((i++ > 3) || (*modestring < '0') || (*modestring++ > '7'))
			goto badmode;
	}

	return strtol(optarg,NULL,8);
	
badmode:
	fprintf(stderr,"%s: invalid mode value\n", modestring_orig);
	exit(BADOPT);
}



void
cleanup_aux(int isnew)
{
	if(isnew == 0) {
		int rmsuc = rmdir(where->str);
		if(rmsuc == 0) {
			if(verbosity >=2)
				printf("%s removed\n",whereorig);
		} else {	
			error("cleanup failed",whereorig,NULL);    
		}
	}
}

char *
my_realpath(char *str)
{
	char *aux = (char *)MALLOC(PATH_MAX+1);
	char *aux2 = (char *)realpath(str,aux); /* why do get I warnings if I omit the 
					           (char *) constraint ??? */
	char *aux3;

	if (aux2 == NULL) {
		free(aux);
		return NULL;
	}
	aux3 = strdup(aux2);
	free(aux);

	return aux3;
}

char *
my_readlink (const char *filename)
/* based on sample code of the glibc manual */
{
	int size = STARTLENGTH;
	
	while (1) {
		char *buffer = (char *) MALLOC (size);
		int nchars = readlink (filename, buffer, size);
		if (nchars < 0)
			return NULL;
		if (nchars < size) {
			*(buffer + nchars)  = '\0';
			return buffer;
		}
		free (buffer);
		size *= 2;
	}
}

/* Now comes stuff for deciding whether a symlink points to file. */

char *
soft_realpath(const char *filename)
/* the same as my_realpath, it just gives a symlinkless for broken
 * symlinks as well (symlinkess, except for the symlink proceeded on 
 */
{
	char *fname, *bname, *dname, *rdname, *finalname;
	
	fname = strdup(filename);

	if ((bname = (char *)basename(fname)) == NULL)
		return NULL;
	if ((dname = (char *)dirname(fname)) == NULL)
		return NULL;
	if ((rdname = (char *)my_realpath(dname)) == NULL)
		return NULL;	
	/* free(dname); */
	
	finalname = (char *)MALLOC(strlen(rdname) + strlen(bname) + 2);

	strcpy(finalname,rdname);
	free(rdname);

	strcat(finalname,"/");
	strcat(finalname,bname);
	/* free(bname); */

	return strdup(finalname);
}

				
char *
canon_readlink(const char *filename)
/* gives the soft_realpath of the target of a symlink */
/* freeing should also happen if a we return NULL due to a failed function call,
 * ehh...
 */
{
	char *pretarget, *target, *rtarget;

	if ((pretarget = (char *)my_readlink(filename)) == NULL)
		return NULL;
	if ((*pretarget) == '/') {
		target = pretarget;
	} else {		
		char *rname, *dname;

		if ((rname = (char *)soft_realpath(filename)) == NULL)
			return NULL;
		if ((dname = (char *)dirname(rname)) == NULL)
			return NULL;
		/* free(rname); */
	
		target = (char *)MALLOC(strlen(dname) + strlen(pretarget) + 2);
		strcpy(target,dname);
		/* free(dname);	*/
	
		strcat(target,"/");
		strcat(target,pretarget);
		free(pretarget);
	}

	if ((rtarget = (char *)soft_realpath(target)) == NULL)
		return NULL;
	free(target);
	
	return strdup(rtarget);
}
	
int rec_pointto(char *linkname, char *filename)
{
	char *currpos, *newpos, *rname;
	int i;

	if((currpos = (char *)soft_realpath(linkname)) == NULL ||
	   (rname = (char *)soft_realpath(filename)) == NULL)
		return 0;

	for (i=0; i < MY_MAXSYMLINKS; i++) {
		if (strcmp(currpos,rname) == 0)
			return 1;
		if ((newpos = (char *)canon_readlink(currpos)) == NULL)
			return 0;
		free(currpos);
		currpos = newpos;
	}
	
	errno = ELOOP;
	return 0;
}	

/*
 * Read at most len bytes from file into buf, until we hit EOF or sepchar or 0.
 * Upon hitting EOF or sepchar without error, buf is 0 terminated, and the
 * length of the resulting string (excluding the final zero) is returned.
 *
 * Else -- ie., we got an error, we read 0 when sepchar is not zero,
 * we read len bytes without sepchar or zero -- -1 is returned in
 * order to signal an error.
 */
int
get_line_from_file(FILE *file, char *buf, size_t len, char sepchar)
{
	int i = 0;
	char c;	

	if (len == 0)
		return 0;

	for (; i < len; i++) {
		c = fgetc(file);
		if (c == EOF || c == sepchar) {
			if (ferror(file))
				return -1;
			buf[i] = '\0';
			return i;
		}
		if (c == '\0')
			return -1;
		buf[i] = c;
	}

	return -1;
}


syntax highlighted by Code2HTML, v. 0.9.1