/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "install.h"
#include "install-ds.h"
#include <prerror.h>
#include <prlock.h>
#include <prio.h>
#include <prmem.h>
#include <prprf.h>
#include <prsystem.h>
#include <prproces.h>

#ifdef XP_UNIX
/* for chmod */
#include <sys/types.h>
#include <sys/stat.h>
#endif

/*extern "C" {*/
#include <jar.h>
/*}*/

extern /*"C"*/
int Pk11Install_AddNewModule(char* moduleName, char* dllPath,
                              unsigned long defaultMechanismFlags,
                              unsigned long cipherEnableFlags);
extern /*"C"*/
short Pk11Install_UserVerifyJar(JAR *jar, PRFileDesc *out,
	PRBool query);
extern /*"C"*/
const char* mySECU_ErrorString(PRErrorCode errnum);
extern 
int Pk11Install_yyparse();

#define INSTALL_METAINFO_TAG "Pkcs11_install_script"
#define SCRIPT_TEMP_FILE "pkcs11inst.tmp"
#define ROOT_MARKER "%root%"
#define TEMP_MARKER "%temp%"
#define PRINTF_ROOT_MARKER "%%root%%"
#define TEMPORARY_DIRECTORY_NAME "pk11inst.dir"
#define JAR_BASE_END (JAR_BASE+100)

static PRLock* errorHandlerLock=NULL;
static Pk11Install_ErrorHandler errorHandler=NULL;
static char* PR_Strdup(const char* str);
static int rm_dash_r (char *path);
static int make_dirs(char *path, int file_perms);
static int dir_perms(int perms);

static Pk11Install_Error DoInstall(JAR *jar, const char *installDir,
	const char* tempDir, Pk11Install_Platform *platform, 
	PRFileDesc *feedback, PRBool noverify);

static char *errorString[]= {
	"Operation was successful", /* PK11_INSTALL_NO_ERROR */
	"Directory \"%s\" does not exist", /* PK11_INSTALL_DIR_DOESNT_EXIST */
	"File \"%s\" does not exist", /* PK11_INSTALL_FILE_DOESNT_EXIST */
	"File \"%s\" is not readable", /* PK11_INSTALL_FILE_NOT_READABLE */
	"%s",		/* PK11_INSTALL_ERROR_STRING */
	"Error in JAR file %s: %s",  /* PK11_INSTALL_JAR_ERROR */
	"No Pkcs11_install_script specified in JAR metainfo file",
				/* PK11_INSTALL_NO_INSTALLER_SCRIPT */
	"Could not delete temporary file \"%s\"",
				/*PK11_INSTALL_DELETE_TEMP_FILE */
	"Could not open temporary file \"%s\"", /*PK11_INSTALL_OPEN_SCRIPT_FILE*/
	"%s: %s", /* PK11_INSTALL_SCRIPT_PARSE */
	"Error in script: %s",
	"Unable to obtain system platform information",
	"Installer script has no information about the current platform (%s)",
	"Relative directory \"%s\" does not contain "PRINTF_ROOT_MARKER,
	"Module File \"%s\" not found",
	"Error occurred installing module \"%s\" into database",
	"Error extracting \"%s\" from JAR file: %s",
	"Directory \"%s\" is not writeable",
	"Could not create directory \"%s\"",
	"Could not remove directory \"%s\"",
	"Unable to execute \"%s\"",
	"Unable to wait for process \"%s\"",
	"\"%s\" returned error code %d",
	"User aborted operation",
	"Unspecified error"
};

enum {
	INSTALLED_FILE_MSG=0,
	INSTALLED_MODULE_MSG,
	INSTALLER_SCRIPT_NAME,
	MY_PLATFORM_IS,
	USING_PLATFORM,
	PARSED_INSTALL_SCRIPT,
	EXEC_FILE_MSG,
	EXEC_SUCCESS,
	INSTALLATION_COMPLETE_MSG,
	USER_ABORT
};

static char *msgStrings[] = {
	"Installed file %s to %s\n",
	"Installed module \"%s\" into module database\n",
	"Using installer script \"%s\"\n",
	"Current platform is %s\n",
	"Using installation parameters for platform %s\n",
	"Successfully parsed installation script\n",
	"Executing \"%s\"...\n",
	"\"%s\" executed successfully\n",
	"\nInstallation completed successfully\n",
	"\nAborting...\n"
};

/**************************************************************************
 * S t r i n g N o d e
 */
typedef struct StringNode_str {
    char *str;
    struct StringNode_str* next;
} StringNode;

StringNode* StringNode_new()
{
	StringNode* new_this;
	new_this = (StringNode*)PR_Malloc(sizeof(StringNode));
	PORT_Assert(new_this != NULL);
	new_this->str = NULL;
	new_this->next = NULL;
	return new_this;
}

void StringNode_delete(StringNode* s) 
{
	if(s->str) {
		PR_Free(s->str);
		s->str=NULL;
	}
}

/*************************************************************************
 * S t r i n g L i s t
 */
typedef struct StringList_str {
  StringNode* head;
	StringNode* tail;
} StringList;

void StringList_new(StringList* list)
{
  list->head=NULL;
  list->tail=NULL;
}

void StringList_delete(StringList* list)
{
	StringNode *tmp;
	while(list->head) {
		tmp = list->head;
		list->head = list->head->next;
		StringNode_delete(tmp);
	}
}

void
StringList_Append(StringList* list, char* str)
{
	if(!str) {
		return;
	}

	if(!list->tail) {
		/* This is the first element */
	  list->head = list->tail = StringNode_new();
	} else {
		list->tail->next = StringNode_new();
		list->tail = list->tail->next;
	}

	list->tail->str = PR_Strdup(str);
	list->tail->next = NULL;	/* just to be sure */
}

/**************************************************************************
 *
 * P k 1 1 I n s t a l l _ S e t E r r o r H a n d l e r
 *
 * Sets the error handler to be used by the library.  Returns the current
 * error handler function.
 */
Pk11Install_ErrorHandler
Pk11Install_SetErrorHandler(Pk11Install_ErrorHandler handler)
{
	Pk11Install_ErrorHandler old;

	if(!errorHandlerLock) {
		errorHandlerLock = PR_NewLock();
	}

	PR_Lock(errorHandlerLock);

	old = errorHandler;
	errorHandler = handler;

	PR_Unlock(errorHandlerLock);

	return old;
}

/**************************************************************************
 *
 * P k 1 1 I n s t a l l _ I n i t
 *
 * Does initialization that otherwise would be done on the fly.  Only
 * needs to be called by multithreaded apps, before they make any calls
 * to this library.
 */
void
Pk11Install_Init()
{
	if(!errorHandlerLock) {
		errorHandlerLock = PR_NewLock();
	}
}

/**************************************************************************
 *
 * P k 1 1 I n s t a l l _ R e l e a s e
 *
 * Releases static data structures used by the library.  Don't use the
 * library after calling this, unless you call Pk11Install_Init()
 * first.  This function doesn't have to be called at all unless you're
 * really anal about freeing memory before your program exits.
 */
void
Pk11Install_Release()
{
	if(errorHandlerLock) {
		PR_Free(errorHandlerLock);
		errorHandlerLock = NULL;
	}
}

/*************************************************************************
 *
 * e r r o r
 *
 * Takes an error code and its arguments, creates the error string,
 * and sends the string to the handler function if it exists.
 */

#ifdef OSF1
/* stdarg has already been pulled in from NSPR */
#undef va_start
#undef va_end
#undef va_arg
#include <varargs.h>
#else
#include <stdarg.h>
#endif

#ifdef OSF1
static void
error(long va_alist, ...)
#else
static void
error(Pk11Install_Error errcode, ...)
#endif
{

	va_list ap;
	char *errstr;
	Pk11Install_ErrorHandler handler;

	if(!errorHandlerLock) {
		errorHandlerLock = PR_NewLock();
	}

	PR_Lock(errorHandlerLock);

	handler = errorHandler;

	PR_Unlock(errorHandlerLock);

	if(handler) {
#ifdef OSF1
		va_start(ap);
		errstr = PR_vsmprintf(errorString[va_arg(ap, Pk11Install_Error)], ap);
#else
		va_start(ap, errcode);
		errstr = PR_vsmprintf(errorString[errcode], ap);
#endif
		handler(errstr);
		PR_smprintf_free(errstr);
		va_end(ap);
	}
}

/*************************************************************************
 *
 * j a r _ c a l l b a c k
 */
static int
jar_callback(int status, JAR *foo, const char *bar, char *pathname,
	char *errortext) {
	char *string;

	string = PR_smprintf("JAR error %d: %s in file %s\n", status, errortext,
		pathname);
	error(PK11_INSTALL_ERROR_STRING, string);
	PR_smprintf_free(string);
	return 0;
}
	
/*************************************************************************
 *
 * P k 1 1 I n s t a l l _ D o I n s t a l l
 *
 * jarFile is the path of a JAR in the PKCS #11 module JAR format.
 * installDir is the directory relative to which files will be
 *   installed.
 */
Pk11Install_Error
Pk11Install_DoInstall(char *jarFile, const char *installDir,
	const char *tempDir, PRFileDesc *feedback, short force, PRBool noverify)
{
	JAR *jar;
	char *installer;
	unsigned long installer_len;
	int status;
	Pk11Install_Error ret;
	PRBool made_temp_file;
	Pk11Install_Info installInfo;
	Pk11Install_Platform *platform;
	char* errMsg;
	char sysname[SYS_INFO_BUFFER_LENGTH], release[SYS_INFO_BUFFER_LENGTH],
       arch[SYS_INFO_BUFFER_LENGTH];
	char *myPlatform;

	jar=NULL;
	ret = PK11_INSTALL_UNSPECIFIED;
	made_temp_file=PR_FALSE;
	errMsg=NULL;
	Pk11Install_Info_init(&installInfo);

	/*
	printf("Inside DoInstall, jarFile=%s, installDir=%s, tempDir=%s\n",
		jarFile, installDir, tempDir);
	*/

	/*
	 * Check out jarFile and installDir for validity
	 */
	if( PR_Access(installDir, PR_ACCESS_EXISTS) != PR_SUCCESS ) {
		error(PK11_INSTALL_DIR_DOESNT_EXIST, installDir);
		return PK11_INSTALL_DIR_DOESNT_EXIST;
	}
	if(!tempDir) {
		tempDir = ".";
	}
	if( PR_Access(tempDir, PR_ACCESS_EXISTS) != PR_SUCCESS ) {
		error(PK11_INSTALL_DIR_DOESNT_EXIST, tempDir);
		return PK11_INSTALL_DIR_DOESNT_EXIST;
	}
	if( PR_Access(tempDir, PR_ACCESS_WRITE_OK) != PR_SUCCESS ) {
		error(PK11_INSTALL_DIR_NOT_WRITEABLE, tempDir);
		return PK11_INSTALL_DIR_NOT_WRITEABLE;
	}
	if( (PR_Access(jarFile, PR_ACCESS_EXISTS) != PR_SUCCESS) ) {
		error(PK11_INSTALL_FILE_DOESNT_EXIST, jarFile);
		return PK11_INSTALL_FILE_DOESNT_EXIST;
	}
	if( PR_Access(jarFile, PR_ACCESS_READ_OK) != PR_SUCCESS ) {
		error(PK11_INSTALL_FILE_NOT_READABLE, jarFile);
		return PK11_INSTALL_FILE_NOT_READABLE;
	}

	/*
	 * Extract the JAR file
	 */
	jar = JAR_new();
	JAR_set_callback(JAR_CB_SIGNAL, jar, jar_callback);

	if(noverify) {
		status = JAR_pass_archive_unverified(jar, jarArchGuess, jarFile, "url");
	} else {
		status = JAR_pass_archive(jar, jarArchGuess, jarFile, "url");
	}
	if( (status < 0) || (jar->valid < 0) ) {
		if (status >= JAR_BASE && status <= JAR_BASE_END) {
			error(PK11_INSTALL_JAR_ERROR, jarFile, JAR_get_error(status));
		} else {
			error(PK11_INSTALL_JAR_ERROR, jarFile,
			  mySECU_ErrorString(PORT_GetError()));
		}
		ret=PK11_INSTALL_JAR_ERROR;
		goto loser;
	}
	/*printf("passed the archive\n");*/

	/*
	 * Show the user security information, allow them to abort or continue
	 */
	if( Pk11Install_UserVerifyJar(jar, PR_STDOUT,
		force?PR_FALSE:PR_TRUE) && !force) {
		if(feedback) {
			PR_fprintf(feedback, msgStrings[USER_ABORT]);
		}
		ret=PK11_INSTALL_USER_ABORT;
		goto loser;
	}

	/*
	 * Get the name of the installation file
	 */
	if( JAR_get_metainfo(jar, NULL, INSTALL_METAINFO_TAG, (void**)&installer,
		(unsigned long*)&installer_len) ) {
		error(PK11_INSTALL_NO_INSTALLER_SCRIPT);
		ret=PK11_INSTALL_NO_INSTALLER_SCRIPT;
		goto loser;
	}
	if(feedback) {
		PR_fprintf(feedback, msgStrings[INSTALLER_SCRIPT_NAME], installer);
	}

	/*
	 * Extract the installation file
	 */
	if( PR_Access(SCRIPT_TEMP_FILE, PR_ACCESS_EXISTS) == PR_SUCCESS) {
		if( PR_Delete(SCRIPT_TEMP_FILE) != PR_SUCCESS) {
			error(PK11_INSTALL_DELETE_TEMP_FILE, SCRIPT_TEMP_FILE);
			ret=PK11_INSTALL_DELETE_TEMP_FILE;
			goto loser;
		}
	}
	if(noverify) {
		status = JAR_extract(jar, installer, SCRIPT_TEMP_FILE);
	} else {
		status = JAR_verified_extract(jar, installer, SCRIPT_TEMP_FILE);
	}
	if(status) {
		if (status >= JAR_BASE && status <= JAR_BASE_END) {
			error(PK11_INSTALL_JAR_EXTRACT, installer, JAR_get_error(status));
		} else {
			error(PK11_INSTALL_JAR_EXTRACT, installer,
			  mySECU_ErrorString(PORT_GetError()));
		}
		ret = PK11_INSTALL_JAR_EXTRACT;
		goto loser;
	} else {
		made_temp_file = PR_TRUE;
	}

	/*
	 * Parse the installation file into a syntax tree
	 */
	Pk11Install_FD = PR_Open(SCRIPT_TEMP_FILE, PR_RDONLY, 0);
	if(!Pk11Install_FD) {
		error(PK11_INSTALL_OPEN_SCRIPT_FILE, SCRIPT_TEMP_FILE);
		ret=PK11_INSTALL_OPEN_SCRIPT_FILE;
		goto loser;
	}
	if(Pk11Install_yyparse()) {
		error(PK11_INSTALL_SCRIPT_PARSE, installer,
			Pk11Install_yyerrstr ? Pk11Install_yyerrstr : "");
		ret=PK11_INSTALL_SCRIPT_PARSE;
		goto loser;
	}

#if 0
	/* for debugging */
	Pk11Install_valueList->Print(0);
#endif

	/*
	 * From the syntax tree, build a semantic structure
	 */
	errMsg = Pk11Install_Info_Generate(&installInfo,Pk11Install_valueList);
	if(errMsg) {
		error(PK11_INSTALL_SEMANTIC, errMsg);
		ret=PK11_INSTALL_SEMANTIC;
		goto loser;
	}
#if 0
	installInfo.Print(0);
#endif

	if(feedback) {
		PR_fprintf(feedback, msgStrings[PARSED_INSTALL_SCRIPT]);
	}

	/*
	 * Figure out which platform to use
	 */
	{
		sysname[0] = release[0] = arch[0] = '\0';

		if( (PR_GetSystemInfo(PR_SI_SYSNAME, sysname, SYS_INFO_BUFFER_LENGTH)
				!= PR_SUCCESS) ||
		    (PR_GetSystemInfo(PR_SI_RELEASE, release, SYS_INFO_BUFFER_LENGTH)
				!= PR_SUCCESS) ||
		    (PR_GetSystemInfo(PR_SI_ARCHITECTURE, arch, SYS_INFO_BUFFER_LENGTH)
				!= PR_SUCCESS) ) {
			error(PK11_INSTALL_SYSINFO);
			ret=PK11_INSTALL_SYSINFO;
			goto loser;
		}
		myPlatform = PR_smprintf("%s:%s:%s", sysname, release, arch);
		platform = Pk11Install_Info_GetBestPlatform(&installInfo,myPlatform);
		if(!platform) {
			error(PK11_INSTALL_NO_PLATFORM, myPlatform);
			PR_smprintf_free(myPlatform);
			ret=PK11_INSTALL_NO_PLATFORM;
			goto loser;
		}
		if(feedback) {
			PR_fprintf(feedback, msgStrings[MY_PLATFORM_IS], myPlatform);
			PR_fprintf(feedback, msgStrings[USING_PLATFORM],
                    Pk11Install_PlatformName_GetString(&platform->name));
		}
		PR_smprintf_free(myPlatform);
	}

	/* Run the install for that platform */
	ret = DoInstall(jar, installDir, tempDir, platform, feedback, noverify);
	if(ret) {
		goto loser;
	}

	ret = PK11_INSTALL_SUCCESS;
loser:
	if(Pk11Install_valueList) {
		Pk11Install_ValueList_delete(Pk11Install_valueList);
		PR_Free(Pk11Install_valueList);
		Pk11Install_valueList = NULL;
	}
	if(jar) {
		JAR_destroy(jar);
	}
	if(made_temp_file) {
		PR_Delete(SCRIPT_TEMP_FILE);
	}
	if(errMsg) {
		PR_smprintf_free(errMsg);
	}
	return ret;
}

/*
/////////////////////////////////////////////////////////////////////////
// actually run the installation, copying files to and fro
*/
static Pk11Install_Error
DoInstall(JAR *jar, const char *installDir, const char *tempDir,
	Pk11Install_Platform *platform, PRFileDesc *feedback, PRBool noverify)
{
	Pk11Install_File *file;
	Pk11Install_Error ret;
	char *reldir;
	char *dest;
	char *modDest;
	char *cp;
	int i;
	int status;
	char *tempname, *temp;
	StringList executables;
	StringNode *execNode;
	PRProcessAttr *attr;
	PRProcess *proc;
	char *argv[2];
	char *envp[1];
	int errcode;

	ret=PK11_INSTALL_UNSPECIFIED;
	reldir=NULL;
	dest=NULL;
	modDest=NULL;
	tempname=NULL;

	StringList_new(&executables);
	/*
	// Create Temporary directory
	*/
	tempname = PR_smprintf("%s/%s", tempDir, TEMPORARY_DIRECTORY_NAME);
	if( PR_Access(tempname, PR_ACCESS_EXISTS)==PR_SUCCESS ) {
		/* Left over from previous run?  Delete it. */
		rm_dash_r(tempname);
	}
	if(PR_MkDir(tempname, 0700) != PR_SUCCESS) {
		error(PK11_INSTALL_CREATE_DIR, tempname);
		ret = PK11_INSTALL_CREATE_DIR;
		goto loser;
	}

	/*
	// Install all the files
	*/
	for(i=0; i < platform->numFiles; i++) {
		file = &platform->files[i];

		if(file->relativePath) {
			PRBool foundMarker = PR_FALSE;
			reldir = PR_Strdup(file->relativePath);

			/* Replace all the markers with the directories for which they stand */
			while(1) {
				if( (cp=PL_strcasestr(reldir, ROOT_MARKER)) ) {
					/* Has a %root% marker  */
					*cp = '\0';
					temp = PR_smprintf("%s%s%s", reldir, installDir,
						cp+strlen(ROOT_MARKER));
					PR_Free(reldir);
					reldir = temp;
					foundMarker = PR_TRUE;
				} else if( (cp = PL_strcasestr(reldir, TEMP_MARKER)) ) {
					/* Has a %temp% marker */
					*cp = '\0';
					temp = PR_smprintf("%s%s%s", reldir, tempname, 
						cp+strlen(TEMP_MARKER));
					PR_Free(reldir);
					reldir = temp;
					foundMarker = PR_TRUE;
				} else {
					break;
				}
			}
			if(!foundMarker) {
				/* Has no markers...this isn't really a relative directory */
				error(PK11_INSTALL_BOGUS_REL_DIR, file->relativePath);
				ret = PK11_INSTALL_BOGUS_REL_DIR;
				goto loser;
			}
			dest = reldir;
			reldir = NULL;
		} else if(file->absolutePath) {
			dest = PR_Strdup(file->absolutePath);
		}

		/* Remember if this is the module file, we'll need to add it later */
		if(i == platform->modFile) {
			modDest = PR_Strdup(dest);
		}

		/* Remember is this is an executable, we'll need to run it later */
		if(file->executable) {
			StringList_Append(&executables,dest);
			/*executables.Append(dest);*/
		}

		/* Make sure the directory we are targetting exists */
		if( make_dirs(dest, file->permissions) ) {
			ret=PK11_INSTALL_CREATE_DIR;
			goto loser;
		}

		/* Actually extract the file onto the filesystem */
		if(noverify) {
			status = JAR_extract(jar, (char*)file->jarPath, dest);
		} else {
			status = JAR_verified_extract(jar, (char*)file->jarPath, dest);
		}
		if(status) {
			if (status >= JAR_BASE && status <= JAR_BASE_END) {
				error(PK11_INSTALL_JAR_EXTRACT, file->jarPath,
                  JAR_get_error(status));
			} else {
				error(PK11_INSTALL_JAR_EXTRACT, file->jarPath,
				  mySECU_ErrorString(PORT_GetError()));
			}
			ret=PK11_INSTALL_JAR_EXTRACT;
			goto loser;
		}
		if(feedback) {
			PR_fprintf(feedback, msgStrings[INSTALLED_FILE_MSG],
				file->jarPath, dest);
		}

		/* no NSPR command to change permissions? */
#ifdef XP_UNIX
		chmod(dest, file->permissions);
#endif

		/* Memory clean-up tasks */
		if(reldir) {
			PR_Free(reldir);
			reldir = NULL;
		}
		if(dest) {
			PR_Free(dest);
			dest = NULL;
		}
	}
	/* Make sure we found the module file */
	if(!modDest) {
		/* Internal problem here, since every platform is supposed to have
		   a module file */
		error(PK11_INSTALL_NO_MOD_FILE, platform->moduleName);
		ret=PK11_INSTALL_NO_MOD_FILE;
		goto loser;
	}

	/*
	// Execute any executable files
	*/
	{
		argv[1] = NULL;
		envp[0] = NULL;
		for(execNode = executables.head; execNode; execNode = execNode->next) {
			attr = PR_NewProcessAttr();
			argv[0] = PR_Strdup(execNode->str);

			/* Announce our intentions */
			if(feedback) {
				PR_fprintf(feedback, msgStrings[EXEC_FILE_MSG], execNode->str);
			}

			/* start the process */
			if( !(proc=PR_CreateProcess(execNode->str, argv, envp, attr)) ) {
				PR_Free(argv[0]);
				PR_DestroyProcessAttr(attr);
				error(PK11_INSTALL_EXEC_FILE, execNode->str);
				ret=PK11_INSTALL_EXEC_FILE;
				goto loser;
			}

			/* wait for it to finish */
			if( PR_WaitProcess(proc, &errcode) != PR_SUCCESS) {
				PR_Free(argv[0]);
				PR_DestroyProcessAttr(attr);
				error(PK11_INSTALL_WAIT_PROCESS, execNode->str);
				ret=PK11_INSTALL_WAIT_PROCESS;
				goto loser;
			}

			/* What happened? */
			if(errcode) {
				/* process returned an error */
				error(PK11_INSTALL_PROC_ERROR, execNode->str, errcode);
			} else if(feedback) {
				/* process ran successfully */
				PR_fprintf(feedback, msgStrings[EXEC_SUCCESS], execNode->str);
			}

			PR_Free(argv[0]);
			PR_DestroyProcessAttr(attr);
		}
	}

	/*
	// Add the module
	*/
	status = Pk11Install_AddNewModule((char*)platform->moduleName,
		(char*)modDest, platform->mechFlags, platform->cipherFlags );

	if(status != SECSuccess) {
		error(PK11_INSTALL_ADD_MODULE, platform->moduleName);
		ret=PK11_INSTALL_ADD_MODULE;
		goto loser;
	}
	if(feedback) {
		PR_fprintf(feedback, msgStrings[INSTALLED_MODULE_MSG],
			platform->moduleName);
	}

	if(feedback) {
		PR_fprintf(feedback, msgStrings[INSTALLATION_COMPLETE_MSG]);
	}

	ret = PK11_INSTALL_SUCCESS;

loser:
	if(reldir) {
		PR_Free(reldir);
	}
	if(dest) {
		PR_Free(dest);
	}
	if(modDest) {
		PR_Free(modDest);
	}
	if(tempname) {
		PRFileInfo info;
		if(PR_GetFileInfo(tempname, &info) == PR_SUCCESS) {
			if(info.type == PR_FILE_DIRECTORY) {
				/* Recursively remove temporary directory */
				if(rm_dash_r(tempname)) {
					error(PK11_INSTALL_REMOVE_DIR,
						tempname);
					ret=PK11_INSTALL_REMOVE_DIR;
				}
					
			}
		}
		PR_Free(tempname);
	}
	StringList_delete(&executables);
	return ret;
}

/*
//////////////////////////////////////////////////////////////////////////
*/
static char*
PR_Strdup(const char* str)
{
	char *tmp = (char*) PR_Malloc(strlen(str)+1);
	strcpy(tmp, str);
	return tmp;
}

/*
 *  r m _ d a s h _ r
 *
 *  Remove a file, or a directory recursively.
 *
 */
static int
rm_dash_r (char *path)
{
    PRDir   *dir;
    PRDirEntry *entry;
    PRFileInfo fileinfo;
    char filename[240];

    if(PR_GetFileInfo(path, &fileinfo) != PR_SUCCESS) {
        /*fprintf(stderr, "Error: Unable to access %s\n", filename);*/
        return -1;
    }
    if(fileinfo.type == PR_FILE_DIRECTORY) {

        dir = PR_OpenDir(path);
        if(!dir) {
            return -1;
        }

        /* Recursively delete all entries in the directory */
        while((entry = PR_ReadDir(dir, PR_SKIP_BOTH)) != NULL) {
            sprintf(filename, "%s/%s", path, entry->name);
            if(rm_dash_r(filename)) {
                PR_CloseDir(dir);
                return -1;
            }
        }

        if(PR_CloseDir(dir) != PR_SUCCESS) {
            return -1;
        }

        /* Delete the directory itself */
        if(PR_RmDir(path) != PR_SUCCESS) {
            return -1;
        }
    } else {
        if(PR_Delete(path) != PR_SUCCESS) {
            return -1;
        }
    }
    return 0;
}

/***************************************************************************
 *
 * m a k e _ d i r s
 *
 * Ensure that the directory portion of the path exists.  This may require
 * making the directory, and its parent, and its parent's parent, etc.
 */
static int
make_dirs(char *path, int file_perms)
{
	char *Path;
	char *start;
	char *sep;
	int ret = 0;
	PRFileInfo info;

	if(!path) {
		return 0;
	}

	Path = PR_Strdup(path);
	start = strpbrk(Path, "/\\");
	if(!start) {
		return 0;
	}
	start++; /* start right after first slash */

	/* Each time through the loop add one more directory. */
	while( (sep=strpbrk(start, "/\\")) ) {
		*sep = '\0';

		if( PR_GetFileInfo(Path, &info) != PR_SUCCESS) {
			/* No such dir, we have to create it */
			if( PR_MkDir(Path, dir_perms(file_perms)) != PR_SUCCESS) {
				error(PK11_INSTALL_CREATE_DIR, Path);
				ret = PK11_INSTALL_CREATE_DIR;
				goto loser;
			}
		} else {
			/* something exists by this name, make sure it's a directory */
			if( info.type != PR_FILE_DIRECTORY ) {
				error(PK11_INSTALL_CREATE_DIR, Path);
				ret = PK11_INSTALL_CREATE_DIR;
				goto loser;
			}
		}

		/* If this is the lowest directory level, make sure it is writeable */
		if(!strpbrk(sep+1, "/\\")) {
			if( PR_Access(Path, PR_ACCESS_WRITE_OK)!=PR_SUCCESS) {
				error(PK11_INSTALL_DIR_NOT_WRITEABLE, Path);
				ret = PK11_INSTALL_DIR_NOT_WRITEABLE;
				goto loser;
			}
		}

		start = sep+1; /* start after the next slash */
		*sep = '/';
	}

loser:
	PR_Free(Path);
	return ret;
}

/*************************************************************************
 * d i r _ p e r m s
 * 
 * Guesses the desired permissions on a directory based on the permissions
 * of a file that will be stored in it. Give read, write, and
 * execute to the owner (so we can create the file), read and 
 * execute to anyone who has read permissions on the file, and write
 * to anyone who has write permissions on the file.
 */
static int
dir_perms(int perms)
{
	int ret = 0;

	/* owner */
	ret |= 0700;

	/* group */
	if(perms & 0040) {
		/* read on the file -> read and execute on the directory */
		ret |= 0050;
	}
	if(perms & 0020) {
		/* write on the file -> write on the directory */
		ret |= 0020;
	}

	/* others */
	if(perms & 0004) {
		/* read on the file -> read and execute on the directory */
		ret |= 0005;
	}
	if(perms & 0002) {
		/* write on the file -> write on the directory */
		ret |= 0002;
	}

	return ret;
}
