// -*- c-basic-offset: 4; related-file-name: "../include/click/driver.hh" -*-
/*
 * driver.cc -- support for packages
 * Eddie Kohler
 *
 * Copyright (c) 2001 Mazu Networks, Inc.
 * Copyright (c) 2003 International Computer Science Institute
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, subject to the conditions
 * listed in the Click LICENSE file. These conditions include: you must
 * preserve this copyright notice, and you cannot mention the copyright
 * holders in advertising related to the Software without their permission.
 * The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
 * notice is a summary of the Click LICENSE file; the license in that file is
 * legally binding.
 */

#include <click/config.h>
#include <click/pathvars.h>

#if CLICK_LINUXMODULE
# define WANT_MOD_USE_COUNT 1	/* glue.hh should not define MOD_USE_COUNTs */
#endif

#include <click/driver.hh>
#include <click/package.hh>
#include <click/hashmap.hh>
#include <click/error.hh>

#if !CLICK_LINUXMODULE && !CLICK_BSDMODULE
# include <click/userutils.hh>
# include <click/straccum.hh>
# include <unistd.h>
# include <errno.h>
# include <string.h>
# include <stdlib.h>
#endif

#if CLICK_TOOL
# include "lexert.hh"
# include "routert.hh"
# include <click/confparse.hh>
#else
# include <click/lexer.hh>
#endif

#if CLICK_USERLEVEL
# include <click/master.hh>
# include <click/notifier.hh>
# include <click/straccum.hh>
# include <click/nameinfo.hh>
# include <click/bighashmap_arena.hh>
#endif

#if HAVE_DYNAMIC_LINKING && !CLICK_LINUXMODULE && !CLICK_BSDMODULE
# define CLICK_PACKAGE_LOADED	1
#endif

// GENERIC PACKAGE SUPPORT

struct ClickProvision {
    CLICK_NAME(String) name;
#if CLICK_PACKAGE_LOADED
    bool loaded : 1;
#endif
    int provided;
};
static int nprovisions;
static int provisions_cap;
static ClickProvision *provisions;

static ClickProvision *
find_provision(const CLICK_NAME(String) &name, int add)
{
    ClickProvision *pf = 0;
    
    for (int i = 0; i < nprovisions; i++)
	if (provisions[i].name == name)
	    return &provisions[i];
	else if (add && provisions[i].provided == 0
#if CLICK_PACKAGE_LOADED
		 && !provisions[i].loaded
#endif
		 )
	    pf = &provisions[i];

    if (!add)
	return 0;

    // otherwise, create new ClickProvision
    if (!pf) {
	if (nprovisions >= provisions_cap) {
	    int n = (nprovisions ? nprovisions * 2 : 4);
	    ClickProvision *npf = new ClickProvision[n];
	    if (!npf)
		return 0;
	    for (int i = 0; i < nprovisions; i++)
		npf[i] = provisions[i];
	    provisions_cap = n;
	    delete[] provisions;
	    provisions = npf;
	}
	pf = &provisions[nprovisions++];
    }

    pf->name = name;
#if CLICK_PACKAGE_LOADED
    pf->loaded = false;
#endif
    pf->provided = 0;
    return pf;
}


extern "C" void
click_provide(const char *package)
{
    ClickProvision *p = find_provision(package, 1);
    if (p)
	p->provided++;
}

extern "C" void
click_unprovide(const char *package)
{
    ClickProvision *p = find_provision(package, 0);
    if (p && p->provided > 0)
	p->provided--;
}

extern "C" bool
click_has_provision(const char *package)
{
    ClickProvision *p = find_provision(package, 0);
    return (p && p->provided > 0);
}

extern "C" void
click_public_packages(CLICK_NAME(Vector)<CLICK_NAME(String)> &v)
{
    for (int i = 0; i < nprovisions; i++)
	if (provisions[i].provided > 0 && provisions[i].name && provisions[i].name[0] != '@')
	    v.push_back(provisions[i].name);
}

#if CLICK_LINUXMODULE || CLICK_BSDMODULE
extern "C" void
click_cleanup_packages()
{
    delete[] provisions;
    provisions = 0;
    nprovisions = provisions_cap = 0;
}
#endif


#if CLICK_USERLEVEL || (HAVE_DYNAMIC_LINKING && !CLICK_LINUXMODULE && !CLICK_BSDMODULE)
CLICK_DECLS

static int
archive_index(const Vector<ArchiveElement> *archive, const String &what)
{
    if (archive)
	for (int i = 0; i < archive->size(); i++)
	    if (archive->at(i).name == what)
		return i;
    return -1;
}

CLICK_ENDDECLS
#endif


#if CLICK_PACKAGE_LOADED
CLICK_DECLS

static String *click_buildtool_prog, *tmpdir;

static bool
check_tmpdir(const Vector<ArchiveElement> *archive, ErrorHandler *errh)
{
    // change to temporary directory
    if (!tmpdir)
	tmpdir = new String(click_mktmpdir(errh));
    if (!*tmpdir)
	return *tmpdir;

    // find compile program
    if (!click_buildtool_prog)
	click_buildtool_prog = new String(clickpath_find_file("click-buildtool", "bin", CLICK_BINDIR, errh));
    if (!*click_buildtool_prog)
	return *click_buildtool_prog;

    // store .hh files in temporary directory
    if (archive)
	for (int i = 0; i < archive->size(); i++)
	    if ((*archive)[i].name.substring(-3) == ".hh") {
		String filename = *tmpdir + (*archive)[i].name;
		FILE *f = fopen(filename.c_str(), "w");
		if (!f)
		    errh->warning("%s: %s", filename.c_str(), strerror(errno));
		else {
		    fwrite((*archive)[i].data.data(), 1, (*archive)[i].data.length(), f);
		    fclose(f);
		}
	    }

    return *tmpdir;
}

static String
compile_archive_file(String package, const Vector<ArchiveElement> *archive, int ai, ErrorHandler *errh)
{
    if (!check_tmpdir(archive, errh))
	return String();

#ifdef CLICK_TOOL
    String package_file = package + ".to";
    String target = "tool";
#else
    String package_file = package + ".uo";
    String target = "userlevel";
#endif
  
    ContextErrorHandler cerrh
	(errh, "While compiling package '" + package_file + "':");

    // write .cc file
    const ArchiveElement &ae = archive->at(ai);
    String filename = ae.name;
    int rightdot = filename.find_right('.');
    if (rightdot >= 0 && filename.substring(0, rightdot) == package)
	filename = package + "_" + filename.substring(rightdot);
    String filepath = *tmpdir + filename;
    FILE *f = fopen(filepath.c_str(), "w");
    if (!f) {
	cerrh.error("%s: %s", filepath.c_str(), strerror(errno));
	return String();
    }
    fwrite(ae.data.data(), 1, ae.data.length(), f);
    fclose(f);
  
    // run click-compile
    StringAccum compile_command;
    compile_command << *click_buildtool_prog << " makepackage -q -C "
		    << *tmpdir << " -t " << target << " "
		    << package << " " << filename << " 1>&2";
    errh->message("%s", compile_command.c_str());
    int compile_retval = system(compile_command.c_str());
    if (compile_retval == 127)
	cerrh.error("could not run '%s'", compile_command.c_str());
    else if (compile_retval < 0)
	cerrh.error("could not run '%s': %s", compile_command.c_str(), strerror(errno));
    else if (compile_retval != 0)
	cerrh.error("'%s' failed", compile_command.c_str());
    else
	return *tmpdir + package_file;

    return String();
}

void
clickdl_load_requirement(String name, const Vector<ArchiveElement> *archive, ErrorHandler *errh)
{
    ClickProvision *p = find_provision(name, 1);
    if (!p || p->loaded)
	return;

    ContextErrorHandler cerrh(errh, "While loading package '" + name + "':");

#ifdef CLICK_TOOL
    String suffix = ".to", cxx_suffix = ".t.cc";
#else
    String suffix = ".uo", cxx_suffix = ".u.cc";
#endif
    String package;
  
    // check archive
    int ai;
    if ((ai = archive_index(archive, name + suffix)) >= 0) {
	if (!check_tmpdir(archive, &cerrh))
	    return;
	package = *tmpdir + "/" + name + suffix;
	FILE *f = fopen(package.c_str(), "wb");
	if (!f) {
	    cerrh.error("cannot open '%s': %s", package.c_str(), strerror(errno));
	    package = String();
	} else {
	    const ArchiveElement &ae = archive->at(ai);
	    fwrite(ae.data.data(), 1, ae.data.length(), f);
	    fclose(f);
	}
    } else if ((ai = archive_index(archive, name + cxx_suffix)) >= 0)
	package = compile_archive_file(name, archive, ai, &cerrh);
    else if ((ai = archive_index(archive, name + ".cc")) >= 0)
	package = compile_archive_file(name, archive, ai, &cerrh);
    else {
	// search path
	package = clickpath_find_file(name + suffix, "lib", CLICK_LIBDIR);
	if (!package)
	    package = clickpath_find_file(name + ".o", "lib", CLICK_LIBDIR);
	if (!package)
	    cerrh.error("can't find required package '%s%s'\nin CLICKPATH or '%s'", name.c_str(), suffix.c_str(), CLICK_LIBDIR);
    }

    p->loaded = true;
    if (package)
	clickdl_load_package(package, &cerrh);
}

CLICK_ENDDECLS
#endif /* CLICK_PACKAGE_LOADED */


#ifdef CLICK_USERLEVEL
extern void click_export_elements();
extern void click_unexport_elements();

CLICK_DECLS
namespace {

class RequireLexerExtra : public LexerExtra { public:
    RequireLexerExtra(const Vector<ArchiveElement> *a) : _archive(a) { }
    void require(String, ErrorHandler *);
  private:
    const Vector<ArchiveElement> *_archive;
};

void
RequireLexerExtra::require(String name, ErrorHandler *errh)
{
# ifdef HAVE_DYNAMIC_LINKING
    if (!click_has_provision(name.c_str()))
	clickdl_load_requirement(name, _archive, errh);
# endif
    if (!click_has_provision(name.c_str()))
	errh->error("requirement '%s' not available", name.c_str());
}

}


static Lexer *click_lexer;

extern "C" int
click_add_element_type(const char *ename, Element *(*func)(uintptr_t), uintptr_t thunk)
{
    assert(ename);
    if (!click_lexer && !(click_lexer = new Lexer))
	return -99;
    else
	return click_lexer->add_element_type(ename, func, thunk);
}

extern "C" void
click_remove_element_type(int which)
{
    if (click_lexer)
	click_lexer->remove_element_type(which);
}


enum { GH_CLASSES, GH_PACKAGES };

static String
read_handler(Element *, void *thunk)
{
    Vector<String> v;
    switch (reinterpret_cast<intptr_t>(thunk)) {
      case GH_CLASSES:
	if (click_lexer)
	    click_lexer->element_type_names(v);
	break;
      case GH_PACKAGES:
	click_public_packages(v);
	break;
      default:
	return "<error>\n";
    }
    
    StringAccum sa;
    for (int i = 0; i < v.size(); i++)
	sa << v[i] << '\n';
    return sa.take_string();
}

void
click_static_initialize()
{
    String::static_initialize();
    NameInfo::static_initialize();
    cp_va_static_initialize();

    ErrorHandler::static_initialize(new FileErrorHandler(stderr, ""));

    Router::static_initialize();
    NotifierSignal::static_initialize();
    CLICK_DEFAULT_PROVIDES;

    Router::add_read_handler(0, "classes", read_handler, (void *)GH_CLASSES);
    Router::add_read_handler(0, "packages", read_handler, (void *)GH_PACKAGES);

    click_export_elements();
}

void
click_static_cleanup()
{
    delete click_lexer;
    click_lexer = 0;
    
    click_unexport_elements();
    
    Router::static_cleanup();
    ErrorHandler::static_cleanup();
    cp_va_static_cleanup();
    NameInfo::static_cleanup();
    HashMap_ArenaFactory::static_cleanup();

# ifdef HAVE_DYNAMIC_LINKING    
    delete tmpdir;
    delete click_buildtool_prog;
    tmpdir = click_buildtool_prog = 0;
# endif /* HAVE_DYNAMIC_LINKING */

    String::static_cleanup();
}

Router *
click_read_router(String filename, bool is_expr, ErrorHandler *errh, bool initialize, Master *master)
{
    if (!errh)
	errh = ErrorHandler::silent_handler();
    int before = errh->nerrors();

    // read file
    String config_str;
    if (is_expr) {
	config_str = filename;
	filename = "<expr>";
    } else {
	config_str = file_string(filename, errh);
	if (!filename || filename == "-")
	    filename = "<stdin>";
    }
    if (errh->nerrors() > before)
	return 0;

    // find config string in archive
    Vector<ArchiveElement> archive;
    if (config_str.length() != 0 && config_str[0] == '!') {
	separate_ar_string(config_str, archive, errh);
	int i = archive_index(&archive, "config");
	if (i >= 0)
	    config_str = archive[i].data;
	else {
	    errh->error("%s: archive has no 'config' section", filename.c_str());
	    return 0;
	}
    }

    // lex
    if (!click_lexer)
	click_lexer = new Lexer();
    RequireLexerExtra lextra(&archive);
    int cookie = click_lexer->begin_parse(config_str, filename, &lextra, errh);
    while (click_lexer->ystatement())
	/* do nothing */;
    Router *router = click_lexer->create_router(master ? master : new Master(1));
    click_lexer->end_parse(cookie);

    // initialize if requested
    if (initialize)
	if (errh->nerrors() > before || router->initialize(errh) < 0) {
	    delete router;
	    return 0;
	}
    
    return router;
}

CLICK_ENDDECLS
#endif /* CLICK_USERLEVEL */


#if CLICK_TOOL
CLICK_DECLS

void
click_static_initialize()
{
    String::static_initialize();
    cp_va_static_initialize();
    ErrorHandler::static_initialize(new FileErrorHandler(stderr, ""));
}

CLICK_ENDDECLS
#endif


syntax highlighted by Code2HTML, v. 0.9.1