/* 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.
 */

#include <stdarg.h>

static void
error(PRErrorCode errcode, ...)
{

    va_list ap;
    char *errstr;
    Pk11Install_ErrorHandler handler;

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

    PR_Lock(errorHandlerLock);

    handler = errorHandler;

    PR_Unlock(errorHandlerLock);

    if (handler) {
        va_start(ap, errcode);
        errstr = PR_vsmprintf(errorString[errcode], ap);
        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);
        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 *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;
    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++) {
        char *dest;
        file = &platform->files[i];

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

            if (!reldir) {
                error(PK11_INSTALL_UNSPECIFIED);
                goto loser;
            }

            /* 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;
                PR_Free(reldir);
                goto loser;
            }
            dest = reldir;
        } else if (file->absolutePath) {
            dest = PR_Strdup(file->absolutePath);
        } else {
            error(PK11_INSTALL_UNSPECIFIED);
            goto loser;
        }

        /* 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
        (void)chmod(dest, file->permissions);
#endif

        PR_Free(dest);
    }
    /* 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 (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) {
            snprintf(filename, sizeof(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;
}
