/*
 * $Header: /datum01/src/continuity/src/dl.c,v 1.41.2.6 2002/09/11 18:02:28
 * aleigh Exp $
 */

/*
 * Copyright (c) 1994, 2004 Alex Leigh, All Rights Reserved
 * 
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF ALEX LEIGH The copyright
 * notice above does not evidence any actual or intended publication of such
 * source code.
 * 
 * Disclosure, copying, distribution, or transmission of of this code without
 * express permission is prohibited.
 */

/*
 * MTS: The category list is MTS write unsafe.
 */

#include "csys.h"
#include "mecha.h"

struct dlSlib {
    char *fn;            // Filename without extension; ie ../lib/site
    void *handle;        // Handle to the library load
    char *name;          // Short name of the module. 
    xmlTconfig *profile; // XML profile (if provided)
    struct dlSlib *next;
};
typedef struct dlSlib dlTlib;

struct dlSmap {
    char symbol[64];
    void *addr;
    lstTset *opts;
    struct dlSmap *next;
};

struct dlSentry {
    lstTset *opts;
    int (*addr)(void *, lstTset *);
    struct dlSentry *next;
};

struct dlScategory {
    char *name;
    unsigned int id;
    struct dlSentry *entry;
    struct dlScategory *next;
};

#include "continuity.h"

static void dlFcreate_config(void);
static void dlFmap_append(dlTmap ** mapset, dlTmap * entry);
static void dlFentry_append(dlTcategory * category, dlTentry * entry);
static dlTcategory *dlFcategory_getbyid(unsigned int catid);
static dlTcategory *dlFcategory_get(const char *category);
static dlTcategory *dlFcategory_add(const char *category);
static void dlFresolve_categories(void);
static int dlFsatisfy_conditions(dlTlib *lib);

#include <dlfcn.h>

#ifndef LINUX
/* #include <link.h> */
#endif

unsigned int dlGcategory_seq = 0;
dlTcategory *dlGcategories = NULL;
dlTlib *dlGlibs = NULL;
dlTmap *dlGmap = NULL;

int dlFop_show_dl_loads(netTconn *conn, int argc, char **argv) {
  netFconn_writef(conn,"\r\nDL Loads\r\n--------\r\n");
  if(dlGlibs==NULL) {
    netFconn_writef(conn,"No loads.\r\n");
    return STATUS_OK;
  }

  for(dlTlib *p=dlGlibs;p!=NULL;p=p->next) {
    netFconn_writef(conn,"%s\r\n", p->fn);
  }

  return STATUS_OK;
}

int dlFop_show_dl_symbols(netTconn *conn, int argc, char **argv) {
  netFconn_writef(conn,"\r\nSymbols\r\n-------\r\n");

  if(dlGmap==NULL) {
    netFconn_writef(conn,"No dynamic symbols.\r\n");
    return STATUS_OK;
  }

  for(dlTmap *p=dlGmap;p!=NULL;p=p->next) {
    netFconn_writef(conn,"0x%06x %s()\r\n", p->addr, p->symbol);
  }
  return STATUS_OK;
}

/*
 * Append a new function reference to the map. This is reverse order out of
 * legacy from mk2 and should probably be changed to just FILO the list,
 * since the order is no longer relavant in mk3.
 */
static void dlFmap_append(dlTmap ** mapset, dlTmap * entry)
{
    dlTmap *p;

    if (*mapset == NULL) {
        entry->next = NULL;
        *mapset = entry;
        return;
    }
    p = (dlTmap *) * mapset;

    while (1) {
        if (p->next == NULL) {
            entry->next = NULL;
            p->next = entry;
            return;
        } else {
            p = p->next;
        }
    }
}

/*
 * FIFO an entry into the categories list. The order is important here since
 * this is the order they are going to get executed in the pipeline.
 */
static void dlFentry_append(dlTcategory * category, dlTentry * entry)
{
    dlTentry *p;

    if (category->entry == NULL) {
        entry->next = NULL;
        category->entry = entry;
        return;
    }
    p = (dlTentry *) category->entry;

    while (1) {
        if (p->next == NULL) {
            entry->next = NULL;
            p->next = entry;
            return;
        } else {
            p = p->next;
        }
    }
}

/*
 * Return the category structure for the given cateogy name. Returns NULL if
 * the category did not exist.
 */
static dlTcategory *dlFcategory_getbyid(unsigned int catid)
{
    dlTcategory *cat;

    cat = dlGcategories;
    while (cat != NULL) {
        if (catid == cat->id) {
            return cat;
        }
        cat = cat->next;
    }
    return NULL;
}

/*
 * Return the category structure for the given cateogy name. Returns NULL if
 * the category did not exist.
 */
static dlTcategory *dlFcategory_get(const char *category)
{
    dlTcategory *cat;

    cat = dlGcategories;
    while (cat != NULL) {
        if (strcmp(cat->name, category) == 0) {
            return cat;
        }
        cat = cat->next;
    }
    return NULL;
}

/*
 * Return the catid for the given category name. Returns -1 if the name was
 * not found.
 */
int dlFpipeline_getid(const char *pipeline)
{
    dlTcategory *cat;

    cat = dlGcategories;
    while (cat != NULL) {
        if (strcmp(cat->name, pipeline) == 0) {
            return cat->id;
        }
        cat = cat->next;
    }
    return -1;
}

/*
 * o/~ What we see, and what we seem, are but a dream, within a dream o/~
 */

static dlTcategory *dlFcategory_add(const char *category)
{
    dlTcategory *cat;

    cat = (dlTcategory *) malloc(sizeof(dlTcategory));
    cat->name = strFcopy(category);

    /*
     * Side-effect
     */
    cat->id = dlGcategory_seq++;
    cat->entry = NULL;

    cat->next = dlGcategories;
    dlGcategories = cat;

    return cat;
}

int dlFload_config(void)
{
    dlTcategory *category;
    const xmlTtag *atag;
    xmlTiterator *ait;

    dlFcreate_config();

    atag = conFget_config_basetag();
    assert(atag != NULL);
    if (atag == NULL)
        return -1;

    atag = xmlFfind_first_tag_children(atag, "continuity_config");
    assert(atag != NULL);

    if (atag == NULL)
        return -1;

    atag = xmlFfind_first_tag_children(atag, "modules");
    assert(atag != NULL);

    if (atag == NULL)
        return -1;

    ait = xmlFiterator_init(atag);
    assert(ait != NULL);

    while ((atag = xmlFiterator_next_name(ait, "module")) != NULL) {
        const char *value = xmlFtag_get_value_str(atag);
        dlFload_library(value);
    }

    xmlFiterator_free(ait);

    /* Run the init class */

    category = dlFcategory_get("init");

    dlFresolve_categories();
    
    dlFpipeline_exec(NULL, dlFpipeline_getid("__init"), NULL, NULL);
    dlFpipeline_exec(NULL, dlFpipeline_getid("init"), NULL, NULL);
    
    return STATUS_OK;
}

/*
 * This function is used to execute one of the dynamic categories. By the
 * time this function is called, all of the addresses in the category should
 * have been mapped.
 */
int dlFpipeline_exec(void *payload, int catid, const char *limit_name,
                     const char *limit_value)
{
    dlTcategory *cat;
    dlTentry *entry;
    int (*func) (void *, lstTset *);
    register int ret;
    const char *limit;
    register int done = 1;
    int restart_count = 0;

    cat = dlFcategory_getbyid(catid);

    if (cat == NULL) {
        return STATUS_ERROR;
    }
    
    if (cat->entry == NULL) {
        return STATUS_OK;
    }

    while (1) {
        if (limit_name != NULL && limit_value != NULL) {
            for (entry = cat->entry; entry != NULL; entry = entry->next) {
                limit = lstFset_get(entry->opts, limit_name);
                if (limit != NULL) {
                    if (strcmp(limit_value, limit) != 0)
                        continue;
                }
                func = (int (*)(void *, lstTset *)) entry->addr;

                ret = (func) (payload, entry->opts);
                if (ret == STATUS_EXIT)
                    return ret;

                if (ret == STATUS_RESTART) {
                    done = 0;
                    break;
                }
            }
        } else {
            for (entry = cat->entry; entry != NULL; entry = entry->next) {
                func = (int (*)(void *, lstTset *)) entry->addr;
                ret = (func) (payload, entry->opts);
                if (ret == STATUS_EXIT)
                    return ret;
                if (ret == STATUS_RESTART) {
                    done = 0;
                    break;
                }
            }
        }

        if (done == 0) {
            restart_count++;
            if (restart_count > 32)
                return STATUS_ERROR;
            done = 1;
            continue;
        }
        break;
    }
    return STATUS_OK;
}

static void dlFresolve_categories(void)
{
    dlTcategory *c;
    dlTentry *e;
    const char *sym;

    for (c = dlGcategories; c != NULL; c = c->next) {
        for (e = c->entry; e != NULL; e = e->next) {
            sym = lstFset_get(e->opts, "fn");
            if (sym == NULL)
                continue;
            
            if (strcmp(sym, "ipv4_dispatcher") == 0) {
                e->addr = ipv4_dispatcher;
            } else {
                if(strcmp(sym,"set_userid")==0) {
                    e->addr=set_userid;
                } else {
                    if(strcmp(sym,"ipv4_openport")==0) {
                        e->addr=ipv4_openport;
                    } else {
                        e->addr = (cont_handler)dlFget_addr(sym);
                        
                        if (e->addr == NULL) {
                            logFmsg(2,
                                    "Failed to map function %s from loaded libraries.",
                                    sym);
                            exit(5);
                        }
                    }
                }
            }
        }
    }
}

static void dlFcreate_config(void)
{
    const xmlTtag *atag, *btag, *ctag;
    xmlTiterator *ait, *bit, *cit;
    dlTentry *entry;
    dlTcategory *cat;

        /*
         * Create the special __init pipeline that will hold
         * the automatic init handlers.
         */
    dlFcategory_add("__init");
    
    atag = conFget_config_basetag();
    if (atag == NULL)
        return;

    atag = xmlFfind_first_tag_children(atag, "continuity_config");
    if (atag == NULL)
        return;

    atag = xmlFfind_first_tag_children(atag, "pipelines");
    if (atag == NULL)
        return;

    ait = xmlFiterator_init(atag);
    assert(ait != NULL);

    while ((atag = xmlFiterator_next_name(ait, "pipeline")) != NULL) {
        const char *name = xmlFtag_get_attrib_value(atag, "name");
        assert(name != NULL);
        cat = dlFcategory_get(name);
        if (cat == NULL)
            cat = dlFcategory_add(name);

        bit = xmlFiterate_children(atag);
        if (bit != NULL) {
            while ((btag = xmlFiterator_next_name(bit, "handler")) != NULL) {
                const char *func = xmlFtag_get_attrib_value(btag, "func");
                assert(func != NULL);

                entry = (dlTentry *) malloc(sizeof(dlTentry));
                bzero(entry, sizeof(dlTentry));
                entry->opts = lstFset_init();
                lstFset_add(entry->opts, "fn", func);

                cit = xmlFiterate_children(btag);
                if (cit != NULL) {
                    while ((ctag =
                            xmlFiterator_next_name(cit, "arg")) != NULL) {
                        const char *arg_name =
                            xmlFtag_get_attrib_value(ctag, "name");
                        const char *arg_value = xmlFtag_get_value_str(ctag);
                        if (arg_name != NULL && arg_value != NULL) {
                            lstFset_add(entry->opts, arg_name, arg_value);
                        }
                    }
                    xmlFiterator_free(cit);
                }
                dlFentry_append(cat, entry);
            }
            xmlFiterator_free(bit);
        }
    }
    xmlFiterator_free(ait);
}

/* Check to see if the conditions of the XML profile
 * are met and that this module should be loaded. The specific
 * requirements that are checked are that the modules listed
 * in the <requires> tag have already been loaded into the
 * server. */
static int dlFsatisfy_conditions(dlTlib *lib) 
{
    xmlTtag *tag, *req;
    xmlTiterator *it;
    
        /* 
         * If there is no XML profile associated with the module
         * there's nothing we can do but hope. 
         */
    
    if(lib->profile==NULL) {
        return -1;
    }
    
    tag=xmlFbase_tag(lib->profile);
    
        /* If we have a profile but it's empty that's the same 
           as not having any requirements, so we pass this
           config */
    
    if(tag==NULL) {
        return 0;
    }

    tag=xmlFtag_get_children(tag);

        /* Ditto */
    if(tag==NULL) {
        return 0;
    }

    req=xmlFfind_first_tag_children(tag,"requires");

        /* Ditto */
    if(req==NULL) {
        return 0;
    }

    it=xmlFiterator_init(req);

    assert(it!=NULL);

    while((tag=xmlFiterator_next_name(it,"require"))!=NULL) {
        char *required=xmlFtag_get_value_str(tag);
        int found=0;

        if(required==NULL) continue;

//        logFmsg(3,"Module requires %s; searching...", xmlFtag_get_value_str(tag));
        
        for(dlTlib *lp=dlGlibs;lp!=NULL;lp=lp->next) {
                /* Not all modules have profiles, so not
                 * all modules have names. */
            if(lp->name==NULL) continue;

            if(strcmp(lp->name,required)==0) {
                found=1;
                break;
            }
        }
        
        if(found==0) {
            logFmsg(2,"Unable to load module: requires %s", required);
            return -1;
        }
    }

    xmlFiterator_free(it);

    return 0;
}

/* Load the requested library. This is called directly in reponse
 * to <module> tags stored in the XML configuration.
 */
int dlFload_library(const char *shlib)
{
    void *handle;
    dlTlib *lib;
    char xmlpath[1024];
    char sopath[1024];
    dlTcategory *cat = dlFcategory_get("__init");

    assert(shlib != NULL);
    assert(cat!=NULL);
    
    snprintf(xmlpath,1024,"%s.xml", shlib);
    snprintf(sopath,1024,"%s.so",shlib);

    lib = (dlTlib *) malloc(sizeof(dlTlib));

    memFclear (lib, sizeof (dlTlib));
    
    if(fleFexist(xmlpath)==0) {
        logFmsg(0,"XML profile %s not found.", xmlpath);
    } else {
        char *xmlfile;
        size_t xmllen;
//        logFmsg(3,"XML profile found for %s", shlib);

        xmlfile=fleFload_file(xmlpath,&xmllen);
        if(xmlfile==NULL) {
            logFmsg(1,"Unable to load XML profile. %s", xmlpath);
            exit(5);
        }
        lib->profile=xmlFparse(xmlfile);
        if(lib->profile==NULL) {
            logFmsg(1,"Unable to parse XML profile. %s", xmlpath);
        } else {
            xmlTtag *base, *tag;

            base=xmlFbase_tag(lib->profile);
            if(base!=NULL) {
                tag=xmlFfind_first_child(base,"module_name");
                if(tag!=NULL) {
                    const char *n = xmlFtag_get_value_str(tag);
                    if(n!=NULL) {
                        lib->name=n;
//                        logFmsg(3,"Module was named %s", n);
                    }
                }
                
                tag=xmlFfind_first_child(base,"init_func");
                if(tag!=NULL) {
                    const char *f = xmlFtag_get_value_str(tag);

                    if(f!=NULL) {
                        dlTentry *e;

                        e=(dlTentry *)malloc(sizeof(dlTentry));
                        memFclear(e,sizeof(dlTentry));
                        e->opts=lstFset_init();
                        lstFset_add(e->opts,"fn",f);
                        dlFentry_append(cat,e);
                    }
                }
            }
            
//            logFmsg(3,"XML profile loaded.");
            if(dlFsatisfy_conditions(lib)!=0) {
                logFmsg(2,"Could not load module %s. Failing.", shlib);
                exit(5);
            }
        }
    }
    
#ifndef OPENBSD
    handle = dlopen(sopath, RTLD_LAZY | RTLD_GLOBAL);
#else
    handle = dlopen(sopath, RTLD_LAZY);
#endif

    if (handle == NULL) {
        logFmsg(3, "Unable to dlopen() shlib %s: %s",
                sopath, dlerror());
        exit(1);
    } else {
        logFmsg(0, "Loaded %s", sopath);
    }


    lib->fn = strFcopy(shlib);
    lib->handle = handle;

    lib->next = dlGlibs;
    dlGlibs = lib;

    return STATUS_OK;
}

void *dlFget_addr(const char *name)
{
    void *sym;
    dlTmap *map;
    dlTlib *l;

#if defined(SOLARIS)||defined(DARWIN)
    void *ghandle;
#endif

#if defined(SOLARIS)||defined(DARWIN)
#ifdef SOLARIS
    sym = dlsym(RTLD_DEFAULT, name);
#else                           /* darwin */
    ghandle = dlopen(NULL, 0);
    sym = dlsym(ghandle, name);
#endif
#else
    sym = dlsym(NULL, name);
#endif

    if (sym == NULL) {
        char buf[64];

        snprintf(buf, sizeof(buf), "_%s", name);

#if defined(SOLARIS)||defined(DARWIN)
#ifdef SOLARIS
        sym = dlsym(RTLD_DEFAULT, buf);

#else                           /* darwin */
        sym = dlsym(ghandle, buf);
#endif
#else
        sym = dlsym(NULL, buf);
#endif
    }
   /**** Try the loaded libraries ****/

    for (l = dlGlibs; l != NULL; l = l->next) {
        sym = dlsym(l->handle, name);

        /*
         * Also required for OPENBSD but we dropped support for that anyways
         */
#ifdef DARWIN
        if (sym == NULL) {
            char buf[64];

            snprintf(buf, sizeof(buf), "_%s", name);
            sym = dlsym(l->handle, buf);
        }
#endif
        if (sym != NULL)
            break;
    }

    if (sym == NULL) {
        logFmsg(1, "Failed to map symbol %s.", name);
        return NULL;
    }
    /*
     * We've found the symbol but it wasn't in the map cache, so we're going
     * to add it.
     */

    map = (dlTmap *) malloc(sizeof(dlTmap));
    map->addr = sym;
    strcpy(map->symbol, name);
    map->opts = NULL;

    dlFmap_append(&dlGmap, map);

    return sym;
}
