// last modified or checked 12 August 2002

#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <ctype.h>
#include <dirent.h>
#include <stdlib.h>
#include <string.h>
#include "SLinkedList.h"
#include <pthread.h>


#ifndef _PATH

#define _PATH

#ifndef _REENTRANT 
	#define _REEENTRANT
#endif

pthread_mutex_t read_muteX = PTHREAD_MUTEX_INITIALIZER;


// warning .. don't compile with 'g++ -ansi' option


const int MAX_PATH_SIZE=255;


struct file_stat{

	file_stat(){
		fname=0;
	}

	~file_stat(){
		
		if (fname!=0) delete[] fname;
	}

	file_stat(const file_stat& other){ // copy constructor
		int size =0;
		if (other.fname!=0){
			size = strlen(other.fname);
			fname = new char[size+1];
			strcpy(fname, other.fname);
		}
		fstats = other.fstats;
	}

	file_stat& operator=(const file_stat& other){
		if (this !=&other){

			if (fname!=0) delete[] fname;
			int size =0;
			if (other.fname!=0){
				size = strlen(other.fname);
				fname = new char[size+1];
				strcpy(fname, other.fname);
			}
			fstats = other.fstats;
		}// if not self
		
		return *this;
	}

	void set_fname(const char* nfname){

		if (fname!=0) delete[] fname;
		int len = strlen(nfname);
		fname = new char[len+1];
		strcpy(fname, nfname);
	}

	struct stat fstats;
	char* fname;
};



class Path{

	public:

		Path(){
			current_path=0;
		}

	
		Path(const Path& other){	// copy constructor
			if (other.current_path!=0){
				int len = strlen(other.current_path);
				current_path = new char[len+1];
				strcpy(current_path, other.current_path);
			}
			else current_path=0;
		}


		Path& operator=(const Path& other){// assignment from other Path
			if (this !=&other){
				if (other.current_path!=0){
					int len = strlen(other.current_path);
					if (current_path !=0) delete[] current_path;
					current_path = new char[len+1];
					strcpy(current_path, other.current_path);
				}
				else current_path=0;
			}
			
			return *this;
		}


		Path(const char* path){  // accept initial path (Absolute only)
			current_path=0;			
			int err = changePath(path);
			if (err) current_path=0;
		}

		sute::SLinkedList<int, file_stat> getContents();

		int validateLeadingPath(const char* path, file_stat* fstat);
		unsigned int getAccess(file_stat &fstat); // returns octal r4,w2,x1 access code applicable to application
		int changePath(const char* path);  // absolute OR relative change path.
		char* getPath(char* path, int size);	// returns current_path
		int get_file_stat(const char* path, file_stat* fstat);	// returns zero and absolute path & stats
																// or -1 in case of error

		static const int PERMISSION_ERROR=-2;
		static const int NO_SUCH_FILE_OR_DIRECTORY=-3;
		static const int NOT_DIRECTORY = -4;
		static const int ERROR=-1;


	private:	

		int realPath(const char* path, char* resolved);	// gives tested and resolved directory path
		char* resolvePath(const char* path, char* resolved);// resolves filenames
		int getStats(const char* rel_path, struct stat *tstat);// returns the struct stat for a file
																// absolute or relative

	// member Variables

		char* current_path;
};


int Path::realPath(const char* path, char* resolved){	// returns resolved Absolute path (directories only)

	file_stat fstat;
	int code=0;

	int err = this->get_file_stat(path, &fstat); // returns absolute path & stats

	if (err) return NO_SUCH_FILE_OR_DIRECTORY; // only universal way of checking is there

	// check it IS a directory
	if (!(S_ISDIR(fstat.fstats.st_mode))) return NOT_DIRECTORY;
	else code = this->getAccess(fstat); 

	if ((code|1)!=code) return PERMISSION_ERROR; // don't have execute on directory

	strcpy(resolved, fstat.fname);

	return 0;
}


								// ensure (file/directory) path is absolute 
char* Path::resolvePath(const char* path, char* resolved){	// AND IF on unix then, test if exists.
								// (testing won't work on cygwin)			
	char res[MAX_PATH_SIZE*2];
	char apath[MAX_PATH_SIZE*2];
	char tpath[MAX_PATH_SIZE+1];
	memset(tpath, 0, (MAX_PATH_SIZE+1));
	memset(apath, 0, (MAX_PATH_SIZE*2));
	memset(res, 0, (MAX_PATH_SIZE*2));
	char* real=0;
	
	errno=0;  // set to zero to catch newer errors not old
	strncpy(tpath, path, MAX_PATH_SIZE-1);  // copy path to non-const
	int plen = strlen(tpath);  
	if (isspace(tpath[plen-1])) tpath[plen-1]=0; // discard endlines etc..

	if (tpath[0]=='/'){  // might be absolute (can't be relative)
		real = realpath(tpath, res);
	}
	else if (current_path!=0){  // might be relative (must not be relative first time)
		strcpy(apath, current_path);
		int len = strlen(apath);
		if (apath[len-1]!='/') {	
			apath[len]='/';apath[len+1]=0;
		}
		strncat(apath, tpath, MAX_PATH_SIZE-1);
		real = realpath(apath, res);
	}

	if (real==0) return 0; // error bad path , check errno
	else{
		strcpy(resolved, real);
		return resolved;
	}
}


// returns -1 on error, zero otherwise
int Path::get_file_stat(const char* path, file_stat* fstat){ // returns absolute path & stats

	char* real =0;
	char* temp = new char[MAX_PATH_SIZE+1];
	memset(temp, 0, (MAX_PATH_SIZE+1));

	if ((real = resolvePath(path, temp))==0) return -1;
	int rlen = strlen(real);
	if (fstat->fname !=0) delete[] (fstat->fname);// just incase old file_stat in use
	fstat->fname = new char[rlen+1];
	strcpy(fstat->fname, real);	// copy the absolute path to the stat
	return getStats(real, &(fstat->fstats));
}


// returns possible filenames and paths .. ie real paths with allowable filenames including stats of 
// leading path... user should check permissions cause this is not looked at here
int Path::validateLeadingPath(const char* path, file_stat* fstat){

	int err = this->get_file_stat(path, fstat); 
	if (!err) return err;	// if it's already a real path just return it
	if (current_path==0) return -1;

	char filename[1025]={0};
	char lpath[2050]={0};
	char* middle=0;

	int len = strlen(path);
	int index = len-1;
	while (index>=0 && path[index]!='/') // separate from new filename and possible existing path
		index--;

	if (index>=0){
		middle = strrchr(path, '/'); // already got index, now we have pointer
		strcpy(filename, ++middle); // now we have the filename by itself
		strncpy(lpath, path, index+1);
		lpath[index+1]=0; // end string with null (strncpy doesn't do this)
	}
	else {		// no slash, just use current path add it in bobs your uncle
		this->get_file_stat(current_path, fstat);
		strcpy(lpath, current_path);
		len = strlen(lpath);
		if (lpath[len-1]!='/')  strcat(lpath, "/");  
		strcat(lpath, path);
		fstat->set_fname(lpath);
		return 0;
	}

	err = this->get_file_stat(lpath, fstat); // get stats of leading path

	if (err) return err; // if no such leading path, return error code
	else if (!S_ISDIR(fstat->fstats.st_mode)) return NOT_DIRECTORY;

	strcpy(lpath, fstat->fname);
	len = strlen(lpath);
	if (lpath[len-1]!='/') strcat(lpath, "/");
	strcat(lpath, filename);	// add the relative filename to the end of it
	fstat->set_fname(lpath);
	return 0;
}



int Path::changePath(const char* path){  // accept relative path or abs (if start with '/')

	char npath[MAX_PATH_SIZE];
	int err = this->realPath(path, npath);

	if (err) return err;
	else{
		int l = strlen(npath);
		if (current_path!=0) delete[] current_path;
		current_path = new char[l+1];
		strcpy(current_path, npath);
		return 0;
	}
}


char* Path::getPath(char* path, int size){	// return stored current_path

	memset((void*)path, 0, sizeof(char)*size);

	if (current_path==0) return 0;
	else	strncpy(path, current_path, size);
	
	return path;
}


sute::SLinkedList<int, file_stat> Path::getContents(){

	sute::SLinkedList<int, file_stat> list; 
	if (current_path==0){
		return list;
	}
	dirent* dent =0;
	struct stat tstat;
	file_stat fstat;
	int count=0;
	errno=0;

	pthread_mutex_lock(&read_muteX);  // protect the read process and internal dirent

		DIR* dirp = opendir(current_path);
		if (dirp==0){
			return list;
		}

		char* temp=new char[MAX_PATH_SIZE+1];  // must not be const
		memset(temp, 0, (MAX_PATH_SIZE+1));
		
		while ((dent = readdir(dirp))!=0){
			strcpy(temp, dent->d_name);
			getStats(temp, &tstat);
			fstat.fname = temp;
			fstat.fstats = tstat;
			list.add(count, fstat);
			count++;
		}
	
		closedir(dirp);

	pthread_mutex_unlock(&read_muteX);  // finished with internal dirent

	fstat.fname=0; // take care of deletion personally
	delete[] temp;	

	return list;
}


								// converts relative to absolute so it
int Path::getStats(const char* path, struct stat *tstat){	// can get file information in any directory
								// only returns the stat struct via arg 2
	char* tpfile = new char[MAX_PATH_SIZE+1];		
	memset(tpfile, 0, (MAX_PATH_SIZE+1));

	if (strlen(path)<1) return -1;	// non-zero means error
	
	if (path[0]!='/'){	// if relative
		strcpy(tpfile, current_path);
		int len = strlen(tpfile);
	
		if (tpfile[len-1]!='/'){	
			tpfile[len]='/';tpfile[len+1]=0;
		}
	}

	strncat(tpfile, path, MAX_PATH_SIZE-1);  // do this in either case 
	int err = lstat(tpfile, tstat);

	delete[] tpfile;
	return err;
}


unsigned int Path::getAccess(file_stat &fstat){

	int group = getgid();
	int user = getuid();
	unsigned int code =0;

	if (user == fstat.fstats.st_uid){

		code = (((S_IRUSR &fstat.fstats.st_mode)!=0)*4)|
				(((S_IWUSR&fstat.fstats.st_mode)!=0)*2)|
				((S_IXUSR&fstat.fstats.st_mode)!=0);
	}
	else if (group == fstat.fstats.st_gid){

		code = (((S_IRGRP &fstat.fstats.st_mode)!=0)*4)|
				(((S_IWGRP&fstat.fstats.st_mode)!=0)*2)|
				((S_IXGRP&fstat.fstats.st_mode)!=0);
	}
	else{	// other

		code = (((S_IROTH &fstat.fstats.st_mode)!=0)*4)|
				(((S_IWOTH&fstat.fstats.st_mode)!=0)*2)|
				((S_IXOTH&fstat.fstats.st_mode)!=0);
	}

	return code;
}


#endif


