// **********************************************************************
//
// Copyright (c) 1999
// Object Oriented Concepts, Inc.
// Billerica, MA, USA
//
// All Rights Reserved
//
// **********************************************************************

#include <OB/CORBA.h>
#include <OB/OCI_IIOP.h>
#include <OB/Narrow_impl.h>
#include <OB/Hashers.h>
#include <OB/Hashtable.h>
#include <OB/Util.h>

#include <OB/OBNaming_skel.h>

#include <Types.h>
#include <OBNaming_impl.h>

#include <stdio.h>

#include <NamingDatabase.h>

#include <sys/types.h>
#include <sys/stat.h>

#ifdef WIN32
#include <io.h>
#else
#include <unistd.h>
#endif

#include <fcntl.h>
#include <errno.h>

#include <stdlib.h>

#ifdef HAVE_FSTREAM
#   include <fstream>
#else
#   include <fstream.h>
#endif

// ----------------------------------------------------------------------
// NamingDatabase private member implementation
// ----------------------------------------------------------------------

//
// Dump the contents of a naming context. This is the set of bindings
// in the context.
//
void
NamingDatabase::dumpContext(CosNaming_NamingContext_ptr nc)
{
    assert(nc -> _is_local());
    
    //
    // Retrieve all bindings.
    //
    CosNaming_OBNamingContext_var obnc =
	CosNaming_OBNamingContext::_narrow(nc);

    CosNaming_OBNamingContext::ExtendedBindingList_var eb =
	obnc -> list_extended();

    for(CORBA_ULong i = 0 ; i < eb -> length() ; i++)
	bind(nc, eb[i].binding_name, eb[i].ncOrObj, eb[i].timestamp,
	     false);
}

void
NamingDatabase::writeAny(const CORBA_Any& any)
{
    //
    // Discover the count. We marshal our endianess, and the sequence of id's.
    //
    CORBA_ULong cnt = 0;
    OBMarshalCount(OBEndian, cnt);
    OBMarshalCount(any, cnt);

    //
    // Now marshal the actual data.
    //
    OBBuffer buf(cnt);
    CORBA_Octet* o = buf.data;
    
    OBMarshal(OBEndian, o);
    OBMarshal(any, o);

    CORBA_String_var s = OBOctetsToAscii(buf.data, cnt);

    assert(os_ != 0);

    bool err = false;
    err = (::fwrite(s.inout(), ::strlen(s), 1, os_) != 1);
    err = err || (::fputc('\n', os_) == EOF);
    if(!noflush_)
        err = err || (fflush(os_) == EOF);
    
    if(err)
    {
	OBMessageViewer::instance() -> error("Write failed!");
        throw CORBA_PERSIST_STORE();
    }
}

//
// Rename the file identified by `from' to the new name identified by
// `to'.
//
bool
NamingDatabase::doRename(const char* from, const char* to)
{
    bool result = (::rename(from, to) == 0);
    if(!result)
    {
	const char* err = ::strerror(errno);
	CORBA_String_var msg = CORBA_string_dup("Rename: ");
	msg += from;
	msg += " to ";
	msg += to;
	msg += " failed. Error: ";
	msg += err;
	OBMessageViewer::instance() -> error(msg);
        return false;
    }
    return true;
}

//
// Check to see if the file identified by `file' exists.
//
bool
NamingDatabase::exists(const char* file)
{
    struct stat sbuf;
    return (::stat(logFile_, &sbuf) == 0);
}

//
// Open the logfile.
//
void
NamingDatabase::openLog(bool create)
{
    //
    // Open the logFile_.
    //
    int mode = O_WRONLY|O_APPEND;
#ifndef WIN32
    mode |= O_SYNC;
#endif
    if(create)
        mode |= O_CREAT;
    int fd = ::open(logFile_, mode, 0666);
    if(fd < 0)
    {
	const char* err = strerror(errno);
	CORBA_String_var msg = CORBA_string_dup("Cannot open `");
	msg += logFile_;
	msg += "': ";
	msg += err;
	OBMessageViewer::instance() -> error(msg);
        throw CORBA_PERSIST_STORE();
    }
    os_ = ::fdopen(fd, "a");
}

// ----------------------------------------------------------------------
// NamingDatabase constructor and destructor
// ----------------------------------------------------------------------

NamingDatabase::NamingDatabase(CORBA_BOA_ptr boa, CORBA_ORB_ptr orb,
			       const char* log,
			       NamingContextSet* ncs,
			       bool noUpdates,
			       bool& start,
			       bool service)
    : orb_(CORBA_ORB::_duplicate(orb)),
      ncs_(ncs),
      os_(0),
      noUpdates_(noUpdates),
      initializing_(false),
      update_(false),
      noflush_(false)
{
    //
    // Determine host and port.
    //
    OCI_AccRegistry_var registry = boa -> get_acc_registry();
    OCI_AcceptorSeq_var acceptors = registry -> get_acceptors();

    CORBA_ULong i;
    for(i = 0 ; i < acceptors -> length() ; i++)
    {
	OCI_AcceptorInfo_var accInfo = acceptors[i] -> get_info();
	OCI_IIOP_AcceptorInfo_var iiopInfo =
	    OCI_IIOP_AcceptorInfo::_narrow(accInfo);
	if(!CORBA_is_nil(iiopInfo))
	{
	    host_ = CORBA_string_dup(iiopInfo -> host());
	    port_ = iiopInfo -> port();

	    break;
	}
    }

    //
    // Setup the filenames.
    //
    logFile_ = CORBA_string_dup(log);
    logFileNew_ = CORBA_string_dup(log);
    logFileNew_ += ".new";
    logFileBack_ = CORBA_string_dup(log);
    logFileBack_ += ".bak";

    OBMessageViewer* viewer = OBMessageViewer::instance();

    //
    // If we're running for the first time, then we need to verify
    // that the logfile is not already present.
    //
    if(start)
    {
        if(exists(logFile_))
        {
	    CORBA_String_var msg =
		CORBA_string_dup("Cannot specify --start option. ");
	    msg += "There is an existing log file: `";
	    msg += logFile_;
	    msg += "'.";
	    viewer -> error(msg);
            throw CORBA_PERSIST_STORE();
        }
        openLog(true);
        return;
    }

    //
    // If the logfile doesn't exist, and the backup file does then
    // this is probably indicative of a crash.
    //
    if(!exists(logFile_) && exists(logFileBack_))
    {
	CORBA_String_var msg =
	    CORBA_string_dup("NamingDatabase file is not present: ");
	msg += logFile_;
	msg += ". Using backup file: ";
	msg += logFileBack_;
	viewer -> warning(msg);
        if(!doRename(logFileBack_, logFile_))
        {
            viewer -> warning("Rename of backup file failed.");
            throw CORBA_PERSIST_STORE();
        }
    }

    //
    // Does the logfile exist?
    //
    if(!exists(logFile_))
    {
	//
	// If we're running as a service, then we create the logfile
	// now.
	//
	if(service)
	{
	    CORBA_String_var msg = CORBA_string_dup("Create database file: ");
	    msg += logFile_;
	    viewer -> trace(1, msg);
	    start = true;
	    openLog(true);
	    return;
	}
	CORBA_String_var msg =
	    CORBA_string_dup("NamingDatabase file is not present: `");
	msg += logFile_;
	msg += "'. Run with --start option if running for the first time.";
	viewer -> error(msg);
        throw CORBA_PERSIST_STORE();
    }

    //
    // Ok, let's get down to business. Read the contents of the
    // logfile.  This is a set of strings, each of which is an octet
    // encoding of a marshaled any.
    //
    ifstream in; // Must use open(name), not ifstream in(name) (VC++ bug)
    in.open(logFile_);
    if(in.fail())
    {
	CORBA_String_var msg = CORBA_string_dup("Cannot open store file: ");
	msg += logFile_;
        viewer -> error(msg);
        throw CORBA_PERSIST_STORE();
    }

    initializing_ = true;
    int rec = 0;
    CORBA_String_var cmd = CORBA_string_alloc(8192);

    try
    {
        try
        {
	    while(!in.eof())
	    {
		cmd[0] = '\0';
		//
		// Read the line in 8k chunks for efficiency.
		//
		do
		{
		    char str[8192];
		    str[0] = 0; // At least required for HP/UX
		    in.get(str, sizeof(str));
		    cmd += str;
		}
		while(!in.eof() && in.peek() != '\n');

		//
		// Skip the newline if necessary.
		//
		if(!in.eof())
		    in.get();

		//
		// If no data is available we're done.
		//
		if(::strlen(cmd.in()) == 0)
		    continue;

                //
                // Convert the octet character string to an octet sequence.
                //
                CORBA_Octet* const buf = OBAsciiToOctets(cmd.in());
                const CORBA_Octet* oct = buf;

                //
                // Unmarshal the endianness, and the any.
                //
                CORBA_Boolean endian;
                OBUnmarshal(endian, oct, false);
                CORBA_Any any;
                OBUnmarshal(any, oct, endian != OBEndian);
                
                delete[] buf;

                //
                // Next extract the instruction.
                //
		CosNaming_Database_Version* version;
		CosNaming_Database_Info* inf;
                CosNaming_Database_Create* create;
                CosNaming_Database_Bind* bind;
                CosNaming_Database_Destroy* destroy;
                CosNaming_Database_Unbind* unbind;

		if(any >>= version)
		{
		    // No action has to be taken here
		}
		else if(any >>= create)
                {
                    //
                    // Create a new naming context with the provided key.
                    //
                    (void) new CosNaming_OBNamingContext_impl(orb_, this, ncs_,
							      create -> key,
							      noUpdates_);
                }
                else if(any >>= bind)
                {
		    CosNaming_OBNamingContext_impl* nc =
			CosNaming_OBNamingContext_impl::_narrow_impl(
			    bind -> nc);
 	    
                    //
                    // Create a new binding.
                    //
                    if(bind -> type._d() == CosNaming_ncontext)
                    {
                        if(bind -> rebind)
                            nc -> rebind_context(bind -> name,
						 bind -> type.nc());
                        else
			{
			    CosNaming_OBNamingContext::NcOrObj ncOrObj;
			    ncOrObj.nc(bind -> type.nc());
			    nc -> _OB_bindWithTimestamp(bind -> name,
							ncOrObj,
							bind -> timestamp);
			}
                    }
                    else if(bind -> rebind)
                    {
                        nc -> rebind(bind -> name, bind -> type.obj());
                    }
                    else
                    {
			CosNaming_OBNamingContext::NcOrObj ncOrObj;
			ncOrObj.obj(bind -> type.obj());
			nc -> _OB_bindWithTimestamp(bind -> name,
						    ncOrObj,
						    bind -> timestamp);
		    }
                }
                else if(any >>= destroy)
                {
                    //
                    // Destroy a context.
                    //
                    destroy -> nc -> destroy();
                }
                else if(any >>= unbind)
                {
                    //
                    // Do an unbind.
                    //
                    unbind -> nc -> unbind(unbind -> name);
                }
		else if(any >>= inf)
		{
		    if(strcmp(host_, inf -> host) ||
		       (CORBA_Short)port_ != inf -> port)
		    {
			CORBA_String_var msg =
			    CORBA_string_dup("Wrong host or port: ");
			msg += host_;
			msg += ":";
			msg += (CORBA_UShort)port_;
			msg += " Expected host and port: ";
			msg += inf -> host;
			msg += ":";
			msg += (CORBA_UShort)inf -> port;
			viewer -> error(msg);
			throw CORBA_PERSIST_STORE();
		    }
		}
                else
                {
		    viewer -> error("Unknown any in database.");
                    throw CORBA_PERSIST_STORE();
                }
                rec++;
            }
        }
        catch(const CosNaming_NamingContext::NotFound& e)
        {
	    CORBA_String_var msg = CORBA_string_dup("Restore: NotFound: ");
            msg += "Reason: ";
            if(e.why == CosNaming_NamingContext::missing_node)
                msg += "missing_node";
            else if(e.why == CosNaming_NamingContext::not_context)
                msg += "not_context";
            else
                msg +=  "not_object";
            msg += " Rest of name: ";
            for(CORBA_ULong i = 0; i < e.rest_of_name.length(); ++i)
            {
                msg += '(';
		msg += e.rest_of_name[i].id;
		msg += ',';
		msg += e.rest_of_name[i].kind;
		msg += ") ";
            }
	    viewer -> error(msg);
            throw CORBA_PERSIST_STORE();
        }
        catch(const CosNaming_NamingContext::CannotProceed& e)
        {
	    CORBA_String_var msg =
		CORBA_string_dup("Restore failed: CannotProceed");
	    msg += " Rest of name: ";
            for(CORBA_ULong i = 0; i < e.rest_of_name.length(); ++i)
            {
                msg += '(';
		msg += e.rest_of_name[i].id;
		msg += ',';
		msg += e.rest_of_name[i].kind;
		msg += ") ";
            }
	    viewer -> error(msg);
            throw CORBA_PERSIST_STORE();
        }
        catch(const CosNaming_NamingContext::InvalidName&)
        {
            viewer -> error("Restore failed: InvalidName");
            throw CORBA_PERSIST_STORE();
        }
        catch(const CosNaming_NamingContext::AlreadyBound&)
        {
	    viewer -> error("Restore failed: AlreadyBound");
            throw CORBA_PERSIST_STORE();
        }
        catch(const CosNaming_NamingContext::NotEmpty&)
        {
            viewer -> error("Restore failed: NotEmpty");
            throw CORBA_PERSIST_STORE();
        }
    }
    catch(const CORBA_PERSIST_STORE&)
    {
	CORBA_String_var msg = CORBA_string_dup("Error processing record: ");
	msg += rec;
	viewer -> error(msg);
	in.close();
        throw;
    }

    in.close();
    initializing_ = false;

    //
    // Re-open the logfile for new journal entries.
    //
    openLog(false);
    update_ = true;
}

NamingDatabase::~NamingDatabase()
{
    if(os_ != 0)
        ::fclose(os_);
}

// ----------------------------------------------------------------------
// NamingDatabase public member implementation
// ----------------------------------------------------------------------

void
NamingDatabase::updateJournal()
{
    OB_SYNCHRONIZED

    if(!update_)
	return;

    //
    // Close old journal.
    //
    ::fclose(os_);
    os_ = 0;
    
    int fd = open(logFileNew_, O_WRONLY|O_CREAT|O_TRUNC, 0666);
    if(fd < 0)
    {
	const char* err = strerror(errno);
	CORBA_String_var msg = CORBA_string_dup("Cannot open `");
	msg += logFileNew_;
	msg += "': ";
	msg += err;
	OBMessageViewer::instance() -> error(msg);
        throw CORBA_PERSIST_STORE();;
    }

    //
    // Write 
    //
    os_ = fdopen(fd, "a");
    noflush_ = true;

    //
    // Write version, host and port
    //
    info();

    OBMessageViewer* viewer = OBMessageViewer::instance();

    //
    // We write the entire contents of our local naming tree to the
    // journal.  We do this in two steps. First we write the create
    // instructions.  Next we write the bindings. It's important that
    // this order is preserved else we can have a binding to a local
    // naming context *before* that context exists.
    //
    try
    {
        try
        {
#ifdef HAVE_JTC
	    JTCSynchronized sync(*ncs_);
#endif
	    NamingContextSet::Enumerator keys = ncs_ -> keys();

	    CORBA_ULong i;
	    for(i = 0; i < keys.length(); i++)
	    {
		CosNaming_NamingContext_ptr nc;
		ncs_ -> get(keys[i], nc);
		create(nc, 0);
	    }
    
	    for(i = 0; i < keys.length(); i++)
	    {
		CosNaming_NamingContext_ptr nc;
		ncs_ -> get(keys[i], nc);
		try
		{
		    dumpContext(nc);
		}
		catch(CORBA_OBJECT_NOT_EXIST&)
		{
		    //
		    // Ignore this. If this occurs then the context is
		    // in the middle of being destroyed and the
		    // destroy record will be added to the store once
		    // we've finished dumping the journal.
		    //
		}
	    }
        }
        catch(const CosNaming_NamingContext::CannotProceed& e)
        {
	    CORBA_String_var msg =
		CORBA_string_dup("Compact failed: CannotProceed");
	    msg += " Rest of name: ";
            for(CORBA_ULong i = 0; i < e.rest_of_name.length(); ++i)
            {
                msg += '(';
		msg += e.rest_of_name[i].id;
		msg += ',';
		msg += e.rest_of_name[i].kind;
		msg += ") ";
            }
	    viewer -> error(msg);
            throw CORBA_PERSIST_STORE();
        }
        catch(const CosNaming_NamingContext::InvalidName&)
        {
            viewer -> error("Compact failed: InvalidName");
            throw CORBA_PERSIST_STORE();
        }
        catch(const CosNaming_NamingContext::AlreadyBound&)
        {
	    viewer -> error("Compact failed: AlreadyBound");
            throw CORBA_PERSIST_STORE();
        }
	catch(const CORBA_UserException&)
	{
	    viewer -> error("Compact failed: UserException");
	    throw CORBA_PERSIST_STORE();
	}
	catch(const CORBA_SystemException& ex)
	{
	    CORBA_String_var msg =
		CORBA_string_dup("Compact failed: SystemException: ");
	    msg += OBExceptionToString(ex);
	    viewer -> error(msg);
	    throw CORBA_PERSIST_STORE();
	}
        if(ferror(os_) != 0)
        {
	    CORBA_String_var msg = CORBA_string_dup("Write to: ");
	    msg += logFileNew_;
	    msg += " failed.";
	    viewer -> error(msg);
            throw CORBA_PERSIST_STORE();
        }
    }
    catch(const CORBA_PERSIST_STORE&)
    {
	noflush_ = false;
        ::fclose(os_);
        os_ = 0;

	//
	// Re-open old log file.
	//
	openLog(false);

        throw;
    }

    ::fclose(os_);
    os_ = 0;

    noflush_ = false;

    //
    // Move the current to backup. If this fails, it not a big deal.
    //
    doRename(logFile_, logFileBack_);

    //
    // Move the new logfile to current. This failing IS a big deal!
    //
    if(!doRename(logFileNew_, logFile_))
        throw CORBA_PERSIST_STORE();

    ::unlink(logFileBack_);

    //
    // Open new.
    //
    openLog(false);
    update_ = false;
}

//
// Add a create instruction to the journal.
//
void
NamingDatabase::create(CosNaming_NamingContext_ptr nc, long timestamp)
{
    OB_SYNCHRONIZED

    if(initializing_)
        return;

    CosNaming_Database_Create create;
    CosNaming_OBNamingContext_impl* impl =
        CosNaming_OBNamingContext_impl::_narrow_impl(nc);
    create.key = CORBA_string_dup(impl -> _OB_getKey());
    create.timestamp = timestamp;
    CORBA_Any any;
    any <<= create;
    
    writeAny(any);

    update_ = true;
}

//
// Add a destroy instruction to the journal.
//
void
NamingDatabase::destroy(CosNaming_NamingContext_ptr nc)
{
    OB_SYNCHRONIZED

    if(initializing_)
        return;
    
    CosNaming_Database_Destroy destroy;
    destroy.nc = CosNaming_NamingContext::_duplicate(nc);

    CORBA_Any any;
    any <<= destroy;
    writeAny(any);

    update_ = true;
}

//
// Add a bind instruction to the journal.
//
void
NamingDatabase::bind(CosNaming_NamingContext_ptr nc,
                     const CosNaming_Name& name,
		     const CosNaming_OBNamingContext::NcOrObj& ncOrObj,
		     long timestamp,
                     bool rebind)
{
    OB_SYNCHRONIZED

    if(initializing_)
        return;
    
    CosNaming_Database_Bind bind;
    bind.rebind = rebind;
    bind.nc = CosNaming_NamingContext::_duplicate(nc);
    bind.name = name;
    bind.type = ncOrObj;
    bind.timestamp = timestamp;

    CORBA_Any any;
    any <<= bind;
    writeAny(any);

    update_ = true;
}

//
// Add an unbind instruction to the journal.
//
void
NamingDatabase::unbind(CosNaming_NamingContext_ptr nc,
		       const CosNaming_Name& name)
{
    OB_SYNCHRONIZED

    if(initializing_)
        return;
    
    CosNaming_Database_Unbind unbind;
    unbind.nc = CosNaming_NamingContext::_duplicate(nc);
    unbind.name = name;

    CORBA_Any any;
    any <<= unbind;
    writeAny(any);

    update_ = true;
}

//
// Add version, host and port to the journal.
//
void
NamingDatabase::info()
{
    OB_SYNCHRONIZED

    if(initializing_)
        return;

    //
    // Write version.
    //
    CosNaming_Database_Version version;
    version.ver = CORBA_string_dup(OBVersion);

    CORBA_Any any;
    any <<= version;
    writeAny(any);

    //
    // Write host and port.
    //
    CosNaming_Database_Info inf;
    inf.host = CORBA_string_dup(host_);
    inf.port = port_;

    any <<= inf;
    writeAny(any);

    update_ = true;
}
