/* -*- mode: C; c-file-style: "bsd" -*- */
#include "errors.h"
#include "interfaces.h"
#include "server.h"
#include "types.h"
typedef struct _PORBitParam PORBitParam;
struct _PORBitParam {
CORBA_TypeCode type;
CORBA_ParameterMode mode;
};
/* Hackaround for breakage in some unstable versions of perl
*/
#if (PERL_VERSION == 5) && (PERL_SUBVERSION > 57) && defined (USE_THREADS)
# undef ERRSV
# define ERRSV (aTHX->errsv)
#else
# ifndef ERRSV
# define ERRSV GvSV(errgv)
# endif
#endif
#define INSTVARS_MAGIC 0x18981972
/* Magic (adopted from DBI) to attach InstVars invisibly to perlobj
*/
PORBitInstVars *
porbit_instvars_add (SV *perlobj)
{
SV *iv_sv = newSV (sizeof(PORBitInstVars));
PORBitInstVars *iv = (PORBitInstVars *)SvPVX(iv_sv);
SV *rv = newRV(iv_sv); /* just needed for sv_bless */
sv_bless (rv, gv_stashpv("CORBA::ORBit::InstVars", TRUE));
sv_free (rv);
iv->magic = INSTVARS_MAGIC;
iv->servant = NULL;
if (SvROK(perlobj))
perlobj = SvRV(perlobj);
sv_magic (perlobj, iv_sv, '~' , Nullch, 0);
SvREFCNT_dec (iv_sv); /* sv_magic() incremented it */
/* It looks from sv.c like this is now unecessary, but DBI does it
* and it shouldn't do any harm
*/
SvRMAGICAL_on (perlobj);
return iv;
}
PORBitInstVars *
porbit_instvars_get (SV *perlobj)
{
PORBitInstVars *iv = NULL;
if (SvROK(perlobj))
perlobj = SvRV(perlobj);
if (SvMAGICAL (perlobj)) {
MAGIC *mg = mg_find (perlobj, '~');
if (mg)
iv = (PORBitInstVars *)SvPVX(mg->mg_obj);
}
if (iv && (iv->magic == INSTVARS_MAGIC))
return iv;
else
return NULL;
}
void
porbit_instvars_destroy (PORBitInstVars *instvars)
{
CORBA_Environment ev;
CORBA_exception_init (&ev);
assert (instvars->magic == INSTVARS_MAGIC);
porbit_servant_destroy (instvars->servant, &ev);
if (ev._major != CORBA_NO_EXCEPTION) {
warn ("error while destroying servant");
}
/* We don't free instvars itself here, because we have stuck
* it inside an SV *
*/
}
/* Find a Perl object for this CORBA object.
*/
SV *
porbit_servant_to_sv (PortableServer_Servant servant)
{
if (servant) {
PORBitServant *porbit_servant = (PORBitServant *)servant;
return newRV_inc(porbit_servant->perlobj);
}
/* FIXME: memory leaks? */
return newSVsv(&PL_sv_undef);
}
static gchar *
porbit_get_repoid (SV *perlobj)
{
char *result;
int count;
dSP;
PUSHMARK(sp);
XPUSHs(perlobj);
PUTBACK;
count = perl_call_method("_porbit_repoid", G_SCALAR);
SPAGAIN;
if (count != 1) /* sanity check */
croak("object->_porbit_repoid didn't return 1 argument");
result = g_strdup (POPp);
PUTBACK;
return result;
}
static PortableServer_Servant
porbit_get_orbit_servant (SV *perlobj)
{
PortableServer_Servant result;
int count;
dSP;
PUSHMARK(sp);
XPUSHs(perlobj);
PUTBACK;
count = perl_call_method("_porbit_servant", G_SCALAR);
SPAGAIN;
if (count != 1) /* sanity check */
croak("object->_porbit_servant didn't return 1 argument");
result = (PortableServer_Servant) POPi;
PUTBACK;
return result;
}
PortableServer_Servant
porbit_sv_to_servant (SV *perlobj)
{
PORBitInstVars *iv;
if (!SvOK(perlobj))
return NULL;
iv = porbit_instvars_get (perlobj);
if (!iv && !sv_derived_from (perlobj, "PortableServer::ServantBase"))
croak ("Argument is not a PortableServer::ServantBase");
if (!iv) {
iv = porbit_instvars_add (perlobj);
iv->servant = porbit_get_orbit_servant (perlobj);
}
return iv->servant;
}
void
porbit_servant_ref (PortableServer_Servant servant)
{
PORBitServant *porbit_servant = (PORBitServant *)servant;
SvREFCNT_inc (porbit_servant->perlobj);
}
void
porbit_servant_unref (PortableServer_Servant servant)
{
PORBitServant *porbit_servant = (PORBitServant *)servant;
SvREFCNT_dec (porbit_servant->perlobj);
}
PortableServer_ObjectId *
porbit_sv_to_objectid (SV *sv)
{
STRLEN len;
char *str;
PortableServer_ObjectId *result;
str = SvPV(sv, len);
result = (PortableServer_ObjectId *)CORBA_sequence_octet__alloc();
result->_length = len + 1;
result->_buffer = CORBA_octet_allocbuf (result->_length);
result->_release = CORBA_TRUE;
memcpy (result->_buffer, str, len);
result->_buffer[len] = '\0';
return result;
}
SV *
porbit_objectid_to_sv (PortableServer_ObjectId *oid)
{
SV *sv;
char *strbuf;
sv = newSV(oid->_length);
SvCUR_set(sv, oid->_length-1);
SvPOK_on (sv);
strbuf = SvPVX(sv);
memcpy (strbuf, oid->_buffer, oid->_length - 1);
strbuf[oid->_length - 1] = '\0';
return sv;
}
/*********************
* Stub calling code *
*********************/
/* Utility function used for error reporting */
static char *
servant_classname (PORBitServant *servant)
{
return HvNAME(SvSTASH(servant->perlobj));
}
static SV *
porbit_call_method (PORBitServant *servant, const char *name, int return_items)
{
int return_count;
dSP;
GV *throwngv = gv_fetchpv("Error::THROWN", TRUE, SVt_PV);
save_scalar (throwngv); /* assume enclosing scope */
sv_setsv (GvSV(throwngv), &PL_sv_undef);
return_count = perl_call_method ((char *)name, G_EVAL |
((return_items == 0) ? G_VOID :
((return_items == 1) ? G_SCALAR : G_ARRAY)));
SPAGAIN;
if (SvOK(ERRSV) && (SvROK(ERRSV) || SvTRUE(ERRSV))) {
/* an error or exception occurred */
while (return_count--) /* empty stack */
(void)POPs;
PUTBACK;
if (SvOK(GvSV(throwngv))) { /* exception */
return newSVsv(GvSV(throwngv));
} else {
warn ("Error occured in implementation '%s::%s': %s",
servant_classname (servant), name, SvPV(ERRSV, PL_na));
return porbit_system_except("IDL:omg.org/CORBA/UNKNOWN:1.0",
0, CORBA_COMPLETED_MAYBE);
}
}
/* Even when we specify G_VOID we may still get a response if the user
didn't return with 'return;'! */
if (!return_items) {
if (return_count) {
while (return_count--)
(void)POPs;
PUTBACK;
}
} else if (return_count != return_items) {
warn("Implementation '%s::%s' should return %d items",
servant_classname (servant), name, return_items);
while (return_count--)
(void)POPs;
PUTBACK;
return porbit_system_except("IDL:omg.org/CORBA/MARSHAL:1.0",
0, CORBA_COMPLETED_YES);
}
return NULL;
}
static void
call_implementation (PORBitServant *servant,
GIOPRecvBuffer *recv_buffer,
CORBA_Environment *ev,
const char *name,
PORBitParam *params,
CORBA_unsigned_long nparams,
CORBA_ExcDescriptionSeq *exceptions)
{
dSP;
GIOPSendBuffer *send_buffer = NULL;
CORBA_unsigned_long i;
AV *inout_args = NULL;
SV *error_sv;
int exception_level = 0;
int stack_index;
int inout_index;
int return_items = 0;
ENTER;
SAVETMPS;
PUSHMARK(sp);
XPUSHs(sv_2mortal(newRV_inc(servant->perlobj)));
for (i=0; i < nparams; i++) {
if (params[i].mode == CORBA_PARAM_IN || params[i].mode == CORBA_PARAM_INOUT) {
SV *arg;
/* We need the PUTBACK/SPAGAIN here, since the call to
* porbit_get_sv might want the stack
*/
PUTBACK;
arg = porbit_get_sv (recv_buffer, params[i].type);
SPAGAIN;
if (!arg) {
error_sv = porbit_system_except("IDL:omg.org/CORBA/BAD_PARAM:1.0",
0, CORBA_COMPLETED_NO);
goto cleanup;
}
if (params[i].mode == CORBA_PARAM_INOUT) {
if (inout_args == NULL)
inout_args = newAV();
av_push(inout_args,arg);
XPUSHs(sv_2mortal(newRV_noinc(arg)));
return_items++;
} else {
XPUSHs(sv_2mortal(arg));
}
} else {
return_items++;
}
}
PUTBACK;
error_sv = porbit_call_method (servant, name, return_items);
if (error_sv)
goto cleanup;
/* The call succeeded -- decode the results */
SPAGAIN;
sp -= return_items;
PUTBACK;
if (!recv_buffer->message.u.request.response_expected) {
/* FIXME: free stack items */
goto cleanup;
}
send_buffer = giop_send_reply_buffer_use(GIOP_MESSAGE_BUFFER(recv_buffer)->connection,
NULL,
recv_buffer->message.u.request.request_id,
CORBA_NO_EXCEPTION);
if (!send_buffer) {
warn ("Lost connection to client while sending result of call to %s::%s", servant_classname (servant), name);
goto cleanup;
}
stack_index = 1;
inout_index = 0;
for (i = 0; i < nparams; i++) {
CORBA_boolean success;
switch (params[i].mode) {
case CORBA_PARAM_IN:
continue;
case CORBA_PARAM_OUT:
success = porbit_put_sv (send_buffer, params[i].type, *(sp+stack_index++));
break;
case CORBA_PARAM_INOUT:
success = porbit_put_sv (send_buffer, params[i].type, *av_fetch(inout_args, inout_index++, 0));
break;
}
if (!success) {
warn ("Error marshalling result of call to %s::%s", servant_classname (servant), name);
error_sv = porbit_system_except("IDL:omg.org/CORBA/MARSHAL:1.0",
0, CORBA_COMPLETED_YES);
goto cleanup;
}
}
giop_send_buffer_write (send_buffer);
cleanup:
if (inout_args) {
av_undef (inout_args);
inout_args = NULL;
}
if (send_buffer) {
giop_send_buffer_unuse (send_buffer);
send_buffer = NULL;
}
if (error_sv) {
SV *new_error;
CORBA_exception_type type;
exception_level++;
if (exception_level > 2) {
warn ("Panic: recursion marshalling error from %s::%s",
servant_classname (servant), name);
SvREFCNT_dec (error_sv);
goto out;
}
if (sv_derived_from(error_sv, "CORBA::UserException"))
type = CORBA_USER_EXCEPTION;
else if (sv_derived_from(error_sv, "CORBA::SystemException"))
type = CORBA_SYSTEM_EXCEPTION;
else {
warn ("Exception thrown from %s::%s must derive from CORBA::UserException or CORBA::SystemException", servant_classname (servant), name);
SvREFCNT_dec (error_sv);
error_sv = porbit_system_except("IDL:omg.org/CORBA/UNKNOWN:1.0",
0, CORBA_COMPLETED_MAYBE);
goto cleanup;
}
send_buffer = giop_send_reply_buffer_use(GIOP_MESSAGE_BUFFER(recv_buffer)->connection,
NULL,
recv_buffer->message.u.request.request_id,
type);
if (!send_buffer) {
warn ("Lost connection to client while sending exception from call to %s::%s.\n %s", servant_classname (servant), name, SvPV (error_sv, PL_na));
SvREFCNT_dec (error_sv);
goto out;
}
new_error = porbit_put_exception (send_buffer, NULL, error_sv, exceptions);
SvREFCNT_dec (error_sv);
if (new_error) {
error_sv = new_error;
goto cleanup;
}
giop_send_buffer_write (send_buffer);
giop_send_buffer_unuse (send_buffer);
}
out:
FREETMPS;
LEAVE;
}
static void
porbit_attr_set_skel (PORBitServant *servant,
GIOPRecvBuffer *recv_buffer,
CORBA_Environment *ev,
gpointer implementation)
{
PORBitParam param;
gchar *name;
CORBA_AttributeDescription *attr = implementation;
param.type = attr->type;
param.mode = CORBA_PARAM_IN;
name = g_strconcat ("_set_", attr->name, NULL);
call_implementation (servant, recv_buffer, ev, name, ¶m, 1, NULL);
g_free (name);
}
static void
porbit_attr_get_skel (PORBitServant *servant,
GIOPRecvBuffer *recv_buffer,
CORBA_Environment *ev,
gpointer implementation)
{
gchar *name;
PORBitParam param;
CORBA_AttributeDescription *attr = implementation;
param.type = attr->type;
param.mode = CORBA_PARAM_OUT;
name = g_strconcat ("_get_", attr->name, NULL);
call_implementation (servant, recv_buffer, ev, name, ¶m, 1, NULL);
g_free (name);
}
static void
porbit_operation_skel (PORBitServant *servant,
GIOPRecvBuffer *recv_buffer,
CORBA_Environment *ev,
gpointer implementation)
{
CORBA_OperationDescription *opr = implementation;
PORBitParam *params;
CORBA_unsigned_long nparams = opr->parameters._length;
CORBA_unsigned_long i,j;
if (opr->result->kind != CORBA_tk_void)
nparams++;
params = g_new (PORBitParam, nparams);
i = 0;
if (opr->result->kind != CORBA_tk_void) {
params[0].type = opr->result;
params[i].mode = CORBA_PARAM_OUT;
i++;
}
for (j=0; j<opr->parameters._length; j++) {
params[i].type = opr->parameters._buffer[j].type;
params[i].mode = opr->parameters._buffer[j].mode;
i++;
}
call_implementation (servant, recv_buffer, ev,
opr->name, params, nparams, &opr->exceptions);
g_free (params);
}
static CORBA_OperationDescription *
find_operation (CORBA_InterfaceDef_FullInterfaceDescription *d, const char *name)
{
CORBA_unsigned_long i;
for (i=0; i < d->operations._length; i++) {
if (strcmp (name, d->operations._buffer[i].name) == 0)
return &d->operations._buffer[i];
}
for (i = 0; i < d->base_interfaces._length; i++) {
PORBitIfaceInfo *info = porbit_find_interface_description(d->base_interfaces._buffer[i]);
if (info) {
CORBA_OperationDescription *res = find_operation(info->desc, name);
if (res)
return res;
}
}
return NULL;
}
CORBA_AttributeDescription *
find_attribute (CORBA_InterfaceDef_FullInterfaceDescription *d,
const char *name, bool set)
{
CORBA_unsigned_long i;
for (i=0; i < d->attributes._length; i++) {
if (!strcmp (name, d->attributes._buffer[i].name)) {
if (!set || d->attributes._buffer[i].mode != CORBA_ATTR_READONLY)
return &d->attributes._buffer[i];
}
}
for (i = 0; i < d->base_interfaces._length; i++) {
PORBitIfaceInfo *info = porbit_find_interface_description(d->base_interfaces._buffer[i]);
if (info) {
CORBA_AttributeDescription *res = find_attribute(info->desc, name, set);
if (res)
return res;
}
}
return NULL;
}
static ORBitSkeleton
porbit_get_skel (PORBitServant *servant,
GIOPRecvBuffer *recv_buffer,
gpointer *impl)
{
gchar *opname = recv_buffer->message.u.request.operation;
if (strncmp(opname, "_set_", 5) == 0) {
CORBA_AttributeDescription *attr_desc = find_attribute(servant->desc, opname+5, TRUE);
if (attr_desc) {
*impl = attr_desc;
return (ORBitSkeleton)porbit_attr_set_skel;
}
} else if (strncmp(opname, "_get_", 5) == 0) {
CORBA_AttributeDescription *attr_desc = find_attribute(servant->desc, opname+5, FALSE);
if (attr_desc) {
*impl = attr_desc;
return (ORBitSkeleton)porbit_attr_get_skel;
}
} else {
CORBA_OperationDescription *op_desc = find_operation(servant->desc, opname);
if (op_desc) {
*impl = op_desc;
return (ORBitSkeleton)porbit_operation_skel;
}
}
return (ORBitSkeleton)NULL;
}
PORBitServant *
porbit_servant_create (SV *perlobj, CORBA_Environment *ev)
{
char *repoid;
PORBitIfaceInfo *info;
PORBitServant *servant;
assert (SvROK(perlobj));
repoid = porbit_get_repoid(perlobj);
info = porbit_find_interface_description (repoid);
if (!info) {
info = porbit_load_contained (NULL, repoid, ev);
if (ev->_major != CORBA_NO_EXCEPTION) {
g_free (repoid);
return NULL;
}
}
g_free (repoid);
servant = g_new (PORBitServant, 1);
servant->_private = NULL;
servant->vepv = NULL;
servant->perlobj = SvRV(perlobj);
servant->desc = info->desc;
PortableServer_ServantBase__init((PortableServer_ServantBase *)servant, ev);
if (ev->_major != CORBA_NO_EXCEPTION)
goto exception;
if (!info->class_id) {
info->class_info.relay_call = (ORBit_impl_finder)porbit_get_skel;
info->class_info.class_name = info->desc->id;
info->class_info.init_local_objref = NULL;
info->class_id = ORBit_register_class(&info->class_info);
}
ORBIT_OBJECT_KEY(servant->_private)->class_info = &info->class_info;
exception:
if (ev->_major != CORBA_NO_EXCEPTION) {
g_free (servant);
servant = NULL;
}
return servant;
}
void
porbit_servant_destroy (PORBitServant *servant, CORBA_Environment *ev)
{
PortableServer_ServantBase__fini((PortableServer_Servant *)servant, ev);
g_free (servant);
}