/*
 *                         OpenSplice DDS
 *
 *   This software and documentation are Copyright 2006 to TO_YEAR PrismTech
 *   Limited, its affiliated companies and licensors. All rights reserved.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */
/* format:
 *   file name: /<temporary directory>/spddskey_*, mode: 600
 *   line 1: <shm name>\0
 *   line 2: <shm base mapping address> [hex, printed in chars]
 *   line 3: <shm size>                 [hex, printed in chars]
 *   line 4: <shm version>              [?]
 *   line 5: <pid of creator>
 */



#include <sys/types.h>
#include <dirent.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <assert.h>

#include "a_key.h"
#include "a_mem.h"
#include "a_tre.h"




#define A_KEY_DUPESALLOWED           0
#define A_KEY_MAX_SHM_NAME_LENGTH  255
#define A_KEY_MAX_VERSION_LENGTH    30
#define A_KEY_MAX_FILE_LINE_LENGTH 256




/* File's Context
 * The real data structure (in this case: a tree) remains
 * hidden to the outside
 */
struct a_keyContext_s {
    a_treTree tree;
};





/* Data Structure for holding the information of one key file
 */
typedef struct a_keyKeyFile {
    char      *fname;       // full file name of key file (typically /tmp/spddskey_...)
    char      *shm_name;    // key file's line# 1: Shared Memory Name (or Domain Name)
    c_address  address;     //            line# 2: Shm Start Address
    c_long     size;        //            line# 3: Shm Size
    char      *version;     //            line# 4: Version (something)
    int        pid;         //            line# 5: Creator PID
} *a_keyKeyFile;







/* Compares the shm_name values of two entries. (uses strcmp)
 * Returns -1 if entry1 <  entry 2
 *          0           ==
 *          1            >
 *        -99 if entry1 or entry2 is NULL
 * This function is needed by a_tre.c, passed on to with a pointer
 * to function type.
 */
static int
a_keyCompareEntries(
    void *entry1Ptr,
    void *entry2Ptr)
{
    int result;
    a_keyKeyFile key1 = (a_keyKeyFile)entry1Ptr;
    a_keyKeyFile key2 = (a_keyKeyFile)entry2Ptr;
    if (key1 && key2) {
        result = strcmp(key2->shm_name, key1->shm_name);
        result = (result < 0) ? -1 : (0 < result) ? 1 : 0;    // force -1, 0 or 1
    } else {
        result = -99;
    }
    return result;
}



/* Creates a new KeyFile entry, specified by its members.
 * Remember to free the instance after use!
 */
static a_keyKeyFile
a_keyNewKeyFile(
    char      *fname,
    char      *shm_name,
    c_address  address,
    c_long     size,
    char      *version,
    int        pid)
{
    a_keyKeyFile newKeyFile = a_memAlloc(sizeof(struct a_keyKeyFile));
    newKeyFile->fname    = a_memStrdup(fname);
    newKeyFile->shm_name = a_memStrdup(shm_name);
    newKeyFile->address  = address;
    newKeyFile->size     = size;
    newKeyFile->version  = a_memStrdup(version);
    newKeyFile->pid      = pid;
    return newKeyFile;
}




/* Destroys (frees) an entry.
 */
static void
a_keyDestroyKeyFilesCallback(
    a_keyKeyFile *keyFile)
{
    if (*keyFile) {
        if ((*keyFile)->fname) {
            a_memFree((*keyFile)->fname);
        }
        if ((*keyFile)->shm_name) {
            a_memFree((*keyFile)->shm_name);
        }
        if ((*keyFile)->version) {
            a_memFree((*keyFile)->version);
        }
        a_memFree(*keyFile);
    }
}




/* Destroys all entries in a list and afterwards the list itself
 */
static int
a_keyDestroyKeyFiles(
    a_keyContext context)
{
    int result;
    if (context) {
        result = a_treDestroyTree(context->tree, (a_treFreeAction)a_keyDestroyKeyFilesCallback);
        context->tree = NULL;
    } else {
        result = 0;
    }
    return result;
}




/* Inserts an entry into the data structure
 */
static int
a_keyInsertKeyFile(
    a_keyContext context,
    a_keyKeyFile keyFile)
{
    return a_treInsertNode(context->tree, (void *)keyFile, (a_treSortAction)a_keyCompareEntries, A_KEY_DUPESALLOWED);
}



/* Finds an entry in the list, specified by its shm name and
 * returns a pointer to that entry, or NULL if not found.
 */
static a_keyKeyFile
a_keyFindKeyFile(
    a_keyContext context,
    char *shm_name)
{
    a_keyKeyFile result;
    a_treSortAction compareAction = a_keyCompareEntries;
    a_keyKeyFile tmpKeyFile = a_keyNewKeyFile("search entry", shm_name, 0, 0, "", 0);
    result = (a_keyKeyFile)a_treFindValue(context->tree, tmpKeyFile, compareAction);
    a_keyDestroyKeyFilesCallback(&tmpKeyFile);
    return result;
}



/* Opens and parses a file with filename <fname> (full file name path!)
 * and stores the contents in the list (internally).
 * Precondition:
 *   - the file exists and is user-readable
 *   - the file is of format:
 *       line# 1: <Shared Memory Name>       (string)
 *       line# 2: <shm start address>        (string in hex notation)
 *       line# 3: <shm size>                 (string in hex notation)
 *       line# 4: <version>                  (string)
 *       line# 5: <creator pid>              (string in decimal notation)
 */
static int
a_keyReadKeyFile(
    a_keyContext context,
    char *fname)
{
    int result = 0;
    const int max_line_length = A_KEY_MAX_FILE_LINE_LENGTH;
    char line[max_line_length + 1];
    FILE *fp;

    char        *shm_name;
    unsigned int hexAddress;
    c_address    address;
    unsigned int hexSize;
    c_long       size;
    char        *version;
    int          pid;
    char        *res;

    fp = fopen(fname, "r");
    if (fp) {
        if ((res = fgets(line, max_line_length, fp))) {
            shm_name = a_memStrdup(line);
        }

        if (res && (res = fgets(line, max_line_length, fp))) {
            sscanf(line, "%x", &hexAddress);
            address = (c_address)hexAddress;
        }

        if (res && (res = fgets(line, max_line_length, fp))) {
            sscanf(line, "%x", &hexSize);
            size = (c_long)hexSize;
        }

        if (res && (res = fgets(line, max_line_length, fp))) {
            version = a_memStrdup(line);
        }

        if (res && (res = fgets(line, max_line_length, fp))) {
            sscanf(line, "%d", &pid);
        }

        fclose(fp);

        if (res) {
            result = a_keyInsertKeyFile(context, a_keyNewKeyFile(fname, shm_name, address, size, version, pid));
        }

        a_memFree(shm_name);
        a_memFree(version);
    }
    return result;
}




/* Scans in <dir> for all files with file name starting
 * with <mask> *and* can be accessed (i.e. readable) by
 * current user.
 * Note: function "access" (unistd.h) is used. A similar
 *       function in os_stdlib.h (os_access) is prefered,
 *       but is seems that an enum type for arg2
 *       (permissions) is not exported and therefore
 *       unusable.
 *       Also, the file seperator is hard coded to "/".
 *       Better would be to use os_fileSep (os_stdlib.h).
 */
static int
a_keyScanFiles(
    a_keyContext context,
    const char *dir,
    const char *mask)
{
    int result = 1;
    DIR *dirp = opendir(dir);
    struct dirent *dirEntry;
    while (dirp && result) {
        dirEntry = readdir(dirp);
        if (dirEntry) {
            if (strncmp(dirEntry->d_name, mask, strlen(mask)) == 0) {
                char *fullFname = a_memAlloc(strlen(dir) + 1 + strlen(dirEntry->d_name) + 1);
                strcpy(fullFname, dir);
                strcat(fullFname, "/");
                strcat(fullFname, dirEntry->d_name);
                if (access(fullFname, R_OK) == 0) {
                    result = a_keyReadKeyFile(context, fullFname);
                }
                a_memFree(fullFname);
            }
        } else {
            closedir(dirp);
            dirp = NULL;
        }
    }
    return result;
}




/* Initialise the context
 * A list with contents of all key files will be
 * created and stored (internally).
 */
a_keyContext
a_keyInit(
    const char *dir,
    const char *mask)
{
    a_keyContext context = a_memAlloc(sizeof(struct a_keyContext_s));
    context->tree = a_treCreateTree();
    a_keyScanFiles(context, dir, mask);
    return context;
}




/* Deinitialise the context
 * Remember to call this function before program termination
 */
void
a_keyDeInit(
    a_keyContext context)
{
    a_keyDestroyKeyFiles(context);
    a_memFree(context);
}



/* Searches for a (already recorded) key file with the specified
 * shm name and returns its start address, or 0 if not found.
 */
c_address
a_keyGetStartAddress(
    a_keyContext context,
    char *shm_name)
{
    a_keyKeyFile keyFile = a_keyFindKeyFile(context, shm_name);
    return keyFile ? keyFile->address : 0;
}



/* Searches for an (already recorded) key file with the specified
 * shm name and returns its size, or 0 if not found.
 */
c_long
a_keyGetSize(
    a_keyContext context,
    char *shm_name)
{
    a_keyKeyFile keyFile = a_keyFindKeyFile(context, shm_name);
    return keyFile ? keyFile->size : 0;
}



/* Searches for an (already recorded) key file with the specified
 * shm name and returns a pointer to its version string, or NULL
 * if not found.
 */
char *
a_keyGetVersion(
    a_keyContext context,
    char *shm_name)
{
    a_keyKeyFile keyFile = a_keyFindKeyFile(context, shm_name);
    return keyFile ? keyFile->version : NULL;
}




/* Searches for an (already recorded) key file with the specified
 * shm name and returns the pid of the creator, or 0 if not found.
 */
int
a_keyGetPid(
    a_keyContext context,
    char *shm_name)
{
    a_keyKeyFile keyFile = a_keyFindKeyFile(context, shm_name);
    return keyFile ? keyFile->pid : 0;
}



//END a_key.c
