mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
Derek Atkins' RPC backend patch.
* src/engine/rpc/*: Gnucash RPC Subsystem (Backend and Server). Still considered experimental. Note that the RPC protocol may change over time causing client/server incompatibilities between various versions of client and server. * configure.in: Add option to --enable-rpc. Hook in RPC Subsystem * src/engine/Account.c: Fix for initial balance on stock/etc. accounts. In particular, don't set to zero if there are no splits. * src/engine/Backend.h: New RPC errors * src/engine/Query.{c,h}: Add function to obtain the internal Query Predicate functions (for use in rebuilding Queries from the RPC subsystem) * src/engine/Transaction.c: Be sure to call the backend in xaccTransRollbackEdit() * src/engine/gnc-book.{c,h}: Added gnc_run_rpc_server() function. Added hooks to call RPC Backend * src/guile/gnc.gwp: added gnc_rpc_server() and new RPC Backend errors * src/scm/command-line.scm: added --rpc-server optio * src/engine/Makefile.am: Added RPC subdir git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@3801 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
parent
afda83308b
commit
9f379e7fb7
31
ChangeLog
31
ChangeLog
@ -1,3 +1,34 @@
|
||||
2001-03-19 Derek Atkins <warlord@MIT.EDU>
|
||||
|
||||
* src/engine/rpc/*: Gnucash RPC Subsystem (Backend and Server).
|
||||
Still considered experimental. Note that the RPC protocol may
|
||||
change over time causing client/server incompatibilities between
|
||||
various versions of client and server.
|
||||
|
||||
* configure.in: Add option to --enable-rpc. Hook in RPC Subsystem
|
||||
|
||||
* src/engine/Account.c: Fix for initial balance on
|
||||
stock/etc. accounts. In particular, don't set to zero if there
|
||||
are no splits.
|
||||
|
||||
* src/engine/Backend.h: New RPC errors
|
||||
|
||||
* src/engine/Query.{c,h}: Add function to obtain the internal
|
||||
Query Predicate functions (for use in rebuilding Queries from
|
||||
the RPC subsystem)
|
||||
|
||||
* src/engine/Transaction.c: Be sure to call the backend in
|
||||
xaccTransRollbackEdit()
|
||||
|
||||
* src/engine/gnc-book.{c,h}: Added gnc_run_rpc_server() function.
|
||||
Added hooks to call RPC Backend
|
||||
|
||||
* src/guile/gnc.gwp: added gnc_rpc_server() and new RPC Backend errors
|
||||
|
||||
* src/scm/command-line.scm: added --rpc-server optio
|
||||
|
||||
* src/engine/Makefile.am: Added RPC subdir
|
||||
|
||||
2001-03-19 Dave Peticolas <dave@krondo.com>
|
||||
|
||||
* src/gnome/gnc-dir.h.in: move to src/gnome. fix for make dist
|
||||
|
10
configure.in
10
configure.in
@ -172,6 +172,15 @@ AC_ARG_ENABLE( sql,
|
||||
AC_SUBST(SQL_DIR)
|
||||
|
||||
|
||||
### --------------------------------------------------------------------------
|
||||
### RPC
|
||||
AC_ARG_ENABLE( rpc,
|
||||
[ --enable-rpc compile with rpc support],
|
||||
RPC_DIR=rpc)
|
||||
|
||||
AC_SUBST(RPC_DIR)
|
||||
|
||||
|
||||
### --------------------------------------------------------------------------
|
||||
### i18n
|
||||
AC_ARG_WITH( locale-dir,
|
||||
@ -545,6 +554,7 @@ AC_OUTPUT(
|
||||
src/doc/design/Makefile
|
||||
src/engine/Makefile
|
||||
src/engine/sql/Makefile
|
||||
src/engine/rpc/Makefile
|
||||
src/experimental/Makefile
|
||||
src/experimental/cbb/Makefile
|
||||
src/experimental/cbb/cbb-engine/Makefile
|
||||
|
@ -823,9 +823,9 @@ xaccAccountRecomputeBalance (Account * acc)
|
||||
price_xfer(last_split, share_reconciled_balance);
|
||||
}
|
||||
else {
|
||||
acc -> balance = gnc_numeric_zero();
|
||||
acc -> cleared_balance = gnc_numeric_zero();
|
||||
acc -> reconciled_balance = gnc_numeric_zero();
|
||||
acc -> balance = dbalance;
|
||||
acc -> cleared_balance = dcleared_balance;
|
||||
acc -> reconciled_balance = dreconciled_balance;
|
||||
}
|
||||
} else {
|
||||
acc -> share_balance = dbalance;
|
||||
|
@ -51,6 +51,19 @@ typedef enum {
|
||||
ERR_SQL_CORRUPT_DB, /* data in db is corrupt */
|
||||
ERR_SQL_MISSING_DATA, /* database doesn't contain expected data */
|
||||
|
||||
/* RPC errors */
|
||||
ERR_RPC_BAD_URL, /* Can't parse url */
|
||||
ERR_RPC_HOST_UNK, /* Host unknown */
|
||||
ERR_RPC_CANT_CONNECT, /* bad hostname/port/dbname/etc. */
|
||||
ERR_RPC_CANT_BIND, /* can't bind to address */
|
||||
ERR_RPC_CANT_ACCEPT, /* can't accept connection */
|
||||
ERR_RPC_NO_CONNECTION, /* no connection to server */
|
||||
ERR_RPC_CONNECTION_LOST, /* Lost connection to server */
|
||||
ERR_RPC_BAD_VERSION, /* RPC Version Mismatch */
|
||||
ERR_RPC_SERVER_STATE, /* Invalid/bad server state */
|
||||
ERR_RPC_FAILED, /* Operation failed */
|
||||
ERR_RPC_NOT_ADDED, /* object not added */
|
||||
|
||||
} GNCBackendError;
|
||||
/* NOTE: if you modify GNCBackendError, please update src/scm/gnc.gwp */
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
# Engine Makefile.am file.
|
||||
|
||||
# don't build sql subdir by default, unless you want hurt
|
||||
SUBDIRS = ${SQL_DIR}
|
||||
# don't build sql or RPC subdirs by default, unless you want hurt
|
||||
SUBDIRS = ${SQL_DIR} ${RPC_DIR}
|
||||
|
||||
# but put it in the dist
|
||||
DIST_SUBDIRS = sql
|
||||
# but put them in the dist
|
||||
DIST_SUBDIRS = sql rpc
|
||||
|
||||
# Build a libtool library, libhello.la for installation in libdir.
|
||||
lib_LTLIBRARIES = libgncengine.la
|
||||
|
@ -1201,6 +1201,57 @@ xaccQueryGetTransactions (Query * q, query_run_t runtype) {
|
||||
}
|
||||
|
||||
|
||||
Predicate
|
||||
xaccQueryGetPredicate (pr_type_t term_type)
|
||||
{
|
||||
Predicate p = NULL;
|
||||
|
||||
/* the predicates are only known in the local
|
||||
* address space, which is why we have to set them
|
||||
* from the abstract type here.
|
||||
*/
|
||||
switch (term_type)
|
||||
{
|
||||
case PR_ACCOUNT:
|
||||
p = & xaccAccountMatchPredicate;
|
||||
break;
|
||||
case PR_ACTION:
|
||||
p = & xaccActionMatchPredicate;
|
||||
break;
|
||||
case PR_AMOUNT:
|
||||
p = & xaccAmountMatchPredicate;
|
||||
break;
|
||||
case PR_BALANCE:
|
||||
p = & xaccBalanceMatchPredicate;
|
||||
break;
|
||||
case PR_CLEARED:
|
||||
p = & xaccClearedMatchPredicate;
|
||||
break;
|
||||
case PR_DATE:
|
||||
p = & xaccDateMatchPredicate;
|
||||
break;
|
||||
case PR_DESC:
|
||||
p = & xaccDescriptionMatchPredicate;
|
||||
break;
|
||||
case PR_MEMO:
|
||||
p = & xaccMemoMatchPredicate;
|
||||
break;
|
||||
case PR_NUM:
|
||||
p = & xaccNumberMatchPredicate;
|
||||
break;
|
||||
case PR_PRICE:
|
||||
p = & xaccSharePriceMatchPredicate;
|
||||
break;
|
||||
case PR_SHRS:
|
||||
p = & xaccSharesMatchPredicate;
|
||||
break;
|
||||
case PR_MISC:
|
||||
PERR ("misc term must not appear");
|
||||
break;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/********************************************************************
|
||||
* xaccQueryAddPredicate
|
||||
* Add a predicate an existing query.
|
||||
@ -1216,50 +1267,7 @@ xaccQueryAddPredicate (Query * q,
|
||||
Query * qr;
|
||||
|
||||
qt->data = *pred;
|
||||
|
||||
/* the predicates are only known in the local
|
||||
* address space, which is why we have to set them
|
||||
* from the abstract type here.
|
||||
*/
|
||||
switch (pred->base.term_type)
|
||||
{
|
||||
case PR_ACCOUNT:
|
||||
qt->p = & xaccAccountMatchPredicate;
|
||||
break;
|
||||
case PR_ACTION:
|
||||
qt->p = & xaccActionMatchPredicate;
|
||||
break;
|
||||
case PR_AMOUNT:
|
||||
qt->p = & xaccAmountMatchPredicate;
|
||||
break;
|
||||
case PR_BALANCE:
|
||||
qt->p = & xaccBalanceMatchPredicate;
|
||||
break;
|
||||
case PR_CLEARED:
|
||||
qt->p = & xaccClearedMatchPredicate;
|
||||
break;
|
||||
case PR_DATE:
|
||||
qt->p = & xaccDateMatchPredicate;
|
||||
break;
|
||||
case PR_DESC:
|
||||
qt->p = & xaccDescriptionMatchPredicate;
|
||||
break;
|
||||
case PR_MEMO:
|
||||
qt->p = & xaccMemoMatchPredicate;
|
||||
break;
|
||||
case PR_NUM:
|
||||
qt->p = & xaccNumberMatchPredicate;
|
||||
break;
|
||||
case PR_PRICE:
|
||||
qt->p = & xaccSharePriceMatchPredicate;
|
||||
break;
|
||||
case PR_SHRS:
|
||||
qt->p = & xaccSharesMatchPredicate;
|
||||
break;
|
||||
case PR_MISC:
|
||||
PERR ("misc term must not appear");
|
||||
break;
|
||||
}
|
||||
qt->p = xaccQueryGetPredicate (qt->data.base.term_type);
|
||||
|
||||
xaccInitQuery(qs, qt);
|
||||
xaccQuerySetGroup(qs, q->acct_group);
|
||||
|
@ -306,4 +306,7 @@ time_t xaccQueryGetEarliestDateFound(Query * q);
|
||||
time_t xaccQueryGetLatestDateFound(Query * q);
|
||||
|
||||
|
||||
/* This is useful for network systems */
|
||||
Predicate xaccQueryGetPredicate (pr_type_t term_type);
|
||||
|
||||
#endif
|
||||
|
@ -1476,6 +1476,7 @@ xaccTransCommitEdit (Transaction *trans)
|
||||
void
|
||||
xaccTransRollbackEdit (Transaction *trans)
|
||||
{
|
||||
Backend *be;
|
||||
Transaction *orig;
|
||||
int force_it=0, mismatch=0;
|
||||
int i;
|
||||
@ -1639,6 +1640,17 @@ xaccTransRollbackEdit (Transaction *trans)
|
||||
}
|
||||
}
|
||||
|
||||
be = xaccTransactionGetBackend (trans);
|
||||
if (be && be->trans_rollback_edit)
|
||||
{
|
||||
int rc = 0;
|
||||
rc = (be->trans_rollback_edit) (be, trans);
|
||||
|
||||
if (rc) {
|
||||
PERR ("Rollback Failed. Ouch!");
|
||||
}
|
||||
}
|
||||
|
||||
xaccTransWriteLog (trans, 'R');
|
||||
|
||||
xaccFreeTransaction (trans->orig);
|
||||
|
@ -495,7 +495,8 @@ gnc_book_begin (GNCBook *book, const char * book_id,
|
||||
/* -------------------------------------------------- */
|
||||
if ((!strncmp(book_id, "http://", 7)) ||
|
||||
(!strncmp(book_id, "https://", 8)) ||
|
||||
(!strncmp(book_id, "postgres://", 11)))
|
||||
(!strncmp(book_id, "postgres://", 11)) ||
|
||||
(!strncmp(book_id, "rpc://", 6)))
|
||||
{
|
||||
char *p, *filefrag;
|
||||
|
||||
@ -570,6 +571,45 @@ gnc_book_begin (GNCBook *book, const char * book_id,
|
||||
}
|
||||
|
||||
book->backend = (*pg_new) ();
|
||||
} else
|
||||
if (!strncmp(book_id, "rpc://", 6))
|
||||
{
|
||||
char * dll_err;
|
||||
void * dll_handle;
|
||||
Backend * (*rpc_new)(void);
|
||||
|
||||
/* open and resolve all symbols now (we don't want mystery
|
||||
* failure later) */
|
||||
dll_handle = dlopen ("libgnc_rpc.so", RTLD_NOW);
|
||||
if (! dll_handle)
|
||||
{
|
||||
dll_err = dlerror();
|
||||
PWARN (" can't load library: %s\n", dll_err);
|
||||
g_free(book->fullpath);
|
||||
book->fullpath = NULL;
|
||||
g_free(book->book_id);
|
||||
book->book_id = NULL;
|
||||
gnc_book_push_error (book, ERR_BACKEND_NO_BACKEND);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* For the rpc backend, do the equivalent of
|
||||
* the statically loaded
|
||||
* book->backend = pgendNew (); */
|
||||
rpc_new = dlsym (dll_handle, "rpcendNew");
|
||||
dll_err = dlerror();
|
||||
if (dll_err)
|
||||
{
|
||||
PWARN (" can't find symbol: %s\n", dll_err);
|
||||
g_free(book->fullpath);
|
||||
book->fullpath = NULL;
|
||||
g_free(book->book_id);
|
||||
book->book_id = NULL;
|
||||
gnc_book_push_error (book, ERR_BACKEND_NO_BACKEND);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
book->backend = (*rpc_new) ();
|
||||
}
|
||||
|
||||
/* if there's a begin method, call that. */
|
||||
@ -651,7 +691,8 @@ gnc_book_load (GNCBook *book)
|
||||
}
|
||||
else if ((strncmp(book->book_id, "http://", 7) == 0) ||
|
||||
(strncmp(book->book_id, "https://", 8) == 0) ||
|
||||
(strncmp(book->book_id, "postgres://", 11) == 0))
|
||||
(strncmp(book->book_id, "postgres://", 11) == 0) ||
|
||||
(strncmp(book->book_id, "rpc://", 6)) == 0)
|
||||
{
|
||||
/* This code should be sufficient to initialize *any* backend,
|
||||
* whether http, postgres, or anything else that might come along.
|
||||
@ -1058,7 +1099,8 @@ xaccResolveURL (const char * pathfrag)
|
||||
|
||||
if (!strncmp (pathfrag, "http://", 7) ||
|
||||
!strncmp (pathfrag, "https://", 8) ||
|
||||
!strncmp (pathfrag, "postgres://", 11) )
|
||||
!strncmp (pathfrag, "postgres://", 11) ||
|
||||
!strncmp (pathfrag, "rpc://", 6))
|
||||
{
|
||||
return g_strdup(pathfrag);
|
||||
}
|
||||
@ -1069,3 +1111,36 @@ xaccResolveURL (const char * pathfrag)
|
||||
|
||||
return (xaccResolveFilePath (pathfrag));
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
gnc_run_rpc_server (void)
|
||||
{
|
||||
char * dll_err;
|
||||
void * dll_handle;
|
||||
int (*rpc_run)(short);
|
||||
int ret;
|
||||
|
||||
/* open and resolve all symbols now (we don't want mystery
|
||||
* failure later) */
|
||||
dll_handle = dlopen ("libgnc_rpc.so", RTLD_NOW);
|
||||
if (! dll_handle)
|
||||
{
|
||||
dll_err = dlerror();
|
||||
PWARN (" can't load library: %s\n", dll_err);
|
||||
return;
|
||||
}
|
||||
|
||||
rpc_run = dlsym (dll_handle, "rpc_server_run");
|
||||
dll_err = dlerror();
|
||||
if (dll_err)
|
||||
{
|
||||
dll_err = dlerror();
|
||||
PWARN (" can't find symbol: %s\n", dll_err);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = (*rpc_run)(0);
|
||||
|
||||
/* XXX How do we force an exit? */
|
||||
}
|
||||
|
@ -191,4 +191,7 @@ gboolean gnc_book_process_events (GNCBook *book);
|
||||
char * xaccResolveFilePath (const char * filefrag);
|
||||
char * xaccResolveURL (const char * pathfrag);
|
||||
|
||||
/* Run the RPC Server */
|
||||
void gnc_run_rpc_server (void);
|
||||
|
||||
#endif /* __GNC_BOOK_H__ */
|
||||
|
4
src/engine/rpc/.cvsignore
Normal file
4
src/engine/rpc/.cvsignore
Normal file
@ -0,0 +1,4 @@
|
||||
gncRpc.h
|
||||
gncRpc_clnt.c
|
||||
gncRpc_xdr.c
|
||||
gncRpc_svc.c
|
75
src/engine/rpc/Makefile.am
Normal file
75
src/engine/rpc/Makefile.am
Normal file
@ -0,0 +1,75 @@
|
||||
# RPC library Makefile.am file.
|
||||
# Created By: Derek Atkins <warlord@MIT.EDU>
|
||||
|
||||
# The GNC RPC Backend
|
||||
lib_LTLIBRARIES = libgnc_rpc.la
|
||||
|
||||
libgnc_rpc_la_SOURCES = \
|
||||
RpcBackend.c \
|
||||
RpcServer.c \
|
||||
RpcSock.c \
|
||||
RpcUtils.c \
|
||||
gncRpc_xdr.c \
|
||||
gncRpc_clnt.c \
|
||||
gncRpc_svc.c \
|
||||
gncRpc_server.c \
|
||||
clnt_thrd.c \
|
||||
svc_thrd.c \
|
||||
xprt_thrd.c
|
||||
|
||||
libgnc_rpc_la_LDFLAGS = -version-info 0:0:0
|
||||
|
||||
noinst_HEADERS = \
|
||||
RpcBackend.h \
|
||||
RpcServer.h \
|
||||
RpcServerP.h \
|
||||
RpcSock.h \
|
||||
RpcUtils.h \
|
||||
gncRpc.h \
|
||||
clnt_thrd.h \
|
||||
svc_thrd.h \
|
||||
xprt_thrd.h
|
||||
|
||||
RPCGEN_FILES = \
|
||||
gncRpc.h \
|
||||
gncRpc_xdr.c \
|
||||
gncRpc_clnt.c \
|
||||
gncRpc_svc.c
|
||||
|
||||
EXTRA_DIST = \
|
||||
.cvsignore \
|
||||
README \
|
||||
$(RPCGEN_SRCS)
|
||||
|
||||
INCLUDES = -I.. -I/usr/lib/glib/include
|
||||
|
||||
LDFLAGS = -lpthread
|
||||
|
||||
RPCGEN=rpcgen -M
|
||||
|
||||
RPCGEN_SRCS = gncAccount.x gncCommodity.x gncKVP.x gncGUID.x \
|
||||
gncPrice.x gncQuery.x gncRpc.x gncSplit.x gncTxn.x
|
||||
|
||||
gncRpc.h: $(RPCGEN_SRCS)
|
||||
$(RM) $@
|
||||
$(RPCGEN) -h -o $@ gncRpc.x
|
||||
|
||||
gncRpc_xdr.c: $(RPCGEN_SRCS)
|
||||
$(RM) $@
|
||||
$(RPCGEN) -c -o $@ gncRpc.x
|
||||
|
||||
gncRpc_clnt.c: $(RPCGEN_SRCS)
|
||||
$(RM) $@
|
||||
$(RPCGEN) -l -o $@ gncRpc.x
|
||||
|
||||
gncRpc_svc.c: $(RPCGEN_SRCS)
|
||||
$(RM) $@
|
||||
$(RPCGEN) -m -o $@ gncRpc.x
|
||||
|
||||
#gncRpc_server_stubs.c: $(RPCGEN_SRCS)
|
||||
# $(RPCGEN) -Ss -o $@ gncRpc.x
|
||||
|
||||
rpcgen: $(RPCGEN_FILES)
|
||||
|
||||
# Make sure the system knows to run rpcgen first
|
||||
$(libgnc_rpc_la_SOURCES): gncRpc.h
|
67
src/engine/rpc/README
Normal file
67
src/engine/rpc/README
Normal file
@ -0,0 +1,67 @@
|
||||
|
||||
This directory contains code for RPC support. The RPC Backend is a
|
||||
part of the client and connects to an RPC Server sitting elsewhere.
|
||||
The RPC support in Gnucash is considered experimental, and you should
|
||||
use it only at your own risk. In addition, the RPC Client and Server
|
||||
must match, meaning that they must be using the same version of the
|
||||
RPC protocol. The client does attempt to check the version of the
|
||||
server and will complain if it does not match.
|
||||
|
||||
Version 1, 2001-03-17
|
||||
|
||||
GnuCash Build Instructions
|
||||
--------------------------
|
||||
Build the same as usual, but you must specify the flag '--enable-rpc'
|
||||
to the configure (or autogen.sh), and then run 'make' as usual.
|
||||
|
||||
How to use GnuCash RPC
|
||||
----------------------
|
||||
GnuCash RPC has two parts, an RPC Server and an RPC Client. First,
|
||||
choose your server machine and run the GnuCash RPC Server:
|
||||
|
||||
gnucash --rpc-server
|
||||
|
||||
Next, start the RPC Client on your workstation. You activate the RPC
|
||||
Client by supplying a URL of the form rpc://hostname[:port]/<URL> to
|
||||
your client. This will connect to the RPC Server on the specified
|
||||
host and open the Gnucash file <URL> on the server. The <URL> must be
|
||||
a complete Gnucash URL.
|
||||
|
||||
Example RPC URLs include:
|
||||
|
||||
rpc://localhost//path/to/gnucash-file.xac
|
||||
rpc://serverhost:12345/file:/path/to/file.xac
|
||||
rpc://host/postgres:/...
|
||||
|
||||
Things to do:
|
||||
-------------
|
||||
|
||||
- Implement Security (encryption) on the RPC connection
|
||||
|
||||
- Better version checking in the RPC Protocol (allow client/server to
|
||||
try to find a 'matching' protocol version?) This might require
|
||||
more integration of the version number into RpcUtils
|
||||
|
||||
- username/password or other authentication methods
|
||||
|
||||
- better server integration with e.g. postgres (allow RPC Server to
|
||||
authenticate to postgres on behalf of client, instead of in-band
|
||||
authentication in the postgres URL)
|
||||
|
||||
- better error messages
|
||||
|
||||
- Support for pricedb
|
||||
|
||||
- cope with new accounts/transactions in begin_edit()
|
||||
|
||||
- grab the book_error more often for the result/error returns in the server
|
||||
|
||||
- better handling when the server connection is lost
|
||||
provide a way to reconnect?
|
||||
|
||||
- better way to kill the server (currently can only 'kill' it or ^C it)
|
||||
|
||||
|
||||
|
||||
|
||||
Derek Atkins <warlord@MIT.EDU>
|
919
src/engine/rpc/RpcBackend.c
Normal file
919
src/engine/rpc/RpcBackend.c
Normal file
@ -0,0 +1,919 @@
|
||||
/*
|
||||
* FILE:
|
||||
* RpcBackend.c
|
||||
*
|
||||
* FUNCTION:
|
||||
* Implements the callbacks for the RPC (client) backend.
|
||||
*
|
||||
* HISTORY:
|
||||
* Created By: Derek Atkins <warlord@MIT.EDU>
|
||||
* Copyright (c) 2001, Derek Atkins
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <rpc/xprt_thrd.h>
|
||||
|
||||
#include <glib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "AccountP.h"
|
||||
#include "Backend.h"
|
||||
#include "Group.h"
|
||||
#include "GroupP.h"
|
||||
#include "gnc-book.h"
|
||||
#include "gnc-commodity.h"
|
||||
#include "gnc-engine.h"
|
||||
#include "gnc-engine-util.h"
|
||||
#include "gnc-event.h"
|
||||
#include "guid.h"
|
||||
#include "TransactionP.h"
|
||||
|
||||
#include "gncRpc.h"
|
||||
#include "RpcBackend.h"
|
||||
#include "RpcSock.h"
|
||||
#include "RpcUtils.h"
|
||||
|
||||
#define RPCEND_MAGIC 0x49c3a61c
|
||||
#define VERIFY_BE(be,ret) { \
|
||||
if ((be) == NULL) \
|
||||
return (ret); \
|
||||
assert (be->magic == RPCEND_MAGIC); \
|
||||
}
|
||||
#define VERIFY_BEV(be) { \
|
||||
if ((be) == NULL) \
|
||||
return; \
|
||||
assert (be->magic == RPCEND_MAGIC); \
|
||||
}
|
||||
|
||||
struct _rpcend {
|
||||
Backend be; /* Backend */
|
||||
gint32 magic; /* Magic number */
|
||||
|
||||
char * hostname; /* RPC Server */
|
||||
char * portNum; /* Server Port Number */
|
||||
char * dbName; /* Foreign Database Name */
|
||||
|
||||
GNCBook * book; /* My GNC Book, for insertions */
|
||||
|
||||
TXPRT * xprt; /* RPC Transport */
|
||||
CLIENT * client; /* RPC Client */
|
||||
RPCSock * sock; /* Socket */
|
||||
|
||||
pthread_mutex_t eventlock; /* Mutex around events members */
|
||||
gboolean events; /* Are there events? */
|
||||
|
||||
/* For saving the hooks */
|
||||
int nest_count; /* counter for nested disables */
|
||||
Backend snr;
|
||||
};
|
||||
|
||||
static void rpcendInit (RPCBackend *);
|
||||
|
||||
static short module = MOD_BACKEND;
|
||||
|
||||
/*******************************************************************/
|
||||
/* Internal helper functions */
|
||||
|
||||
static void rpcendEnable (RPCBackend *be)
|
||||
{
|
||||
if (be->nest_count <= 0)
|
||||
PERR ("too many nested disables");
|
||||
|
||||
be->nest_count --;
|
||||
PINFO("nest count=%d", be->nest_count);
|
||||
if (be->nest_count) return;
|
||||
|
||||
/* restore hooks */
|
||||
be->be.account_begin_edit = be->snr.account_begin_edit;
|
||||
be->be.account_commit_edit = be->snr.account_commit_edit;
|
||||
be->be.trans_begin_edit = be->snr.trans_begin_edit;
|
||||
be->be.trans_commit_edit = be->snr.trans_commit_edit;
|
||||
be->be.trans_rollback_edit = be->snr.trans_rollback_edit;
|
||||
be->be.run_query = be->snr.run_query;
|
||||
be->be.sync = be->snr.sync;
|
||||
}
|
||||
|
||||
static void rpcendDisable (RPCBackend *be)
|
||||
{
|
||||
if (be->nest_count < 0)
|
||||
PERR ("too many nested enables");
|
||||
|
||||
be->nest_count ++;
|
||||
PINFO("nest count=%d", be->nest_count);
|
||||
if (be->nest_count > 1) return;
|
||||
|
||||
/* save hooks */
|
||||
be->snr.account_begin_edit = be->be.account_begin_edit;
|
||||
be->snr.account_commit_edit = be->be.account_commit_edit;
|
||||
be->snr.trans_begin_edit = be->be.trans_begin_edit;
|
||||
be->snr.trans_commit_edit = be->be.trans_commit_edit;
|
||||
be->snr.trans_rollback_edit = be->be.trans_rollback_edit;
|
||||
be->snr.run_query = be->be.run_query;
|
||||
be->snr.sync = be->be.sync;
|
||||
|
||||
/* And turn off future calls */
|
||||
be->be.account_begin_edit = NULL;
|
||||
be->be.account_commit_edit = NULL;
|
||||
be->be.trans_begin_edit = NULL;
|
||||
be->be.trans_commit_edit = NULL;
|
||||
be->be.trans_rollback_edit = NULL;
|
||||
be->be.run_query = NULL;
|
||||
be->be.sync = NULL;
|
||||
}
|
||||
|
||||
static void myClose (void *arg)
|
||||
{
|
||||
RPCBackend *be = (RPCBackend *)arg;
|
||||
|
||||
if (be->client)
|
||||
CLNT_DESTROY(be->client);
|
||||
be->client = NULL;
|
||||
|
||||
if (be->xprt)
|
||||
TXPRT_DESTROY (be->xprt);
|
||||
be->xprt = NULL;
|
||||
|
||||
be->sock = NULL;
|
||||
|
||||
/* Turn off all the callbacks (should we do this manually?) */
|
||||
rpcendDisable (be);
|
||||
|
||||
/* Should we set an event, here? */
|
||||
|
||||
/* Don't worry about setting the error here -- it will get set if
|
||||
* the backend is called again
|
||||
*/
|
||||
}
|
||||
|
||||
static void rpcendLogin (RPCBackend *be)
|
||||
{
|
||||
unsigned short portNum = 0;
|
||||
RPCSock *sock = NULL;
|
||||
TXPRT *xprt = NULL;
|
||||
CLIENT *client = NULL;
|
||||
int err;
|
||||
|
||||
ENTER ("be=%p, host=\"%s\", port=\"%s\"", be, be->hostname, be->portNum);
|
||||
|
||||
/* Figure out the port number */
|
||||
if (be->portNum)
|
||||
portNum = (unsigned short) atoi (be->portNum);
|
||||
|
||||
if (!portNum)
|
||||
portNum = RPCEND_PORT;
|
||||
|
||||
/* Convert portnum to net byte order */
|
||||
portNum = htons (portNum);
|
||||
|
||||
/* Connect to the RPC Server */
|
||||
if ((err = RpcConnect (be->hostname, portNum, &sock)) != 0) {
|
||||
xaccBackendSetError (&be->be, err);
|
||||
LEAVE ("connect");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Build the RPC Transport */
|
||||
if ((err = RpcTransport (sock, myClose, (void *)be, &xprt)) != 0) {
|
||||
RpcClose (sock);
|
||||
xaccBackendSetError (&be->be, err);
|
||||
LEAVE ("transport");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get the RPC Client */
|
||||
client = TXPRT_NEW_CLIENT (xprt, GNCRPC_PROG, GNCRPC_VERS);
|
||||
if (client == NULL) {
|
||||
RpcClose (sock);
|
||||
xaccBackendSetError (&be->be, ERR_BACKEND_ALLOC);
|
||||
LEAVE ("client");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Verify the RPC Server Version */
|
||||
err = 0;
|
||||
gncrpc_version_1 (NULL, &err, client);
|
||||
if (err != GNCRPC_PROTOCOL_VERSION) {
|
||||
CLNT_DESTROY (client);
|
||||
TXPRT_DESTROY (xprt);
|
||||
RpcClose (sock);
|
||||
xaccBackendSetError (&be->be, ERR_RPC_BAD_VERSION);
|
||||
LEAVE ("rpc version");
|
||||
return;
|
||||
}
|
||||
|
||||
/* "Login" to the server */
|
||||
|
||||
|
||||
/* Save the rpc information */
|
||||
be->xprt = xprt;
|
||||
be->sock = sock;
|
||||
be->client = client;
|
||||
|
||||
LEAVE ("ok");
|
||||
return;
|
||||
}
|
||||
|
||||
static void rpcend_add_gncaccount (RPCBackend *be, gncAccount *acct,
|
||||
AccountGroup *topgrp)
|
||||
{
|
||||
gnc_commodity_table *ct = gnc_book_get_commodity_table (be->book);
|
||||
rpcend_do_add_acct (topgrp, acct, ct);
|
||||
return;
|
||||
}
|
||||
|
||||
static void rpcend_add_gncacctlist (RPCBackend *be,
|
||||
AccountGroup *topgrp,
|
||||
gnc_acctlist *acctlist)
|
||||
{
|
||||
for (; acctlist != NULL; acctlist = acctlist->next) {
|
||||
rpcend_add_gncaccount (be, acctlist->acct, topgrp);
|
||||
}
|
||||
}
|
||||
|
||||
/* Return an int depicting whether this txn would be added to the engine:
|
||||
* -1 == transaction is new(er) and would be added
|
||||
* 0 == transaction version numbers match
|
||||
* 1 == transaction is older than current engine cache
|
||||
*/
|
||||
static int rpcend_would_add_txn (gnc_vers_list *txn)
|
||||
{
|
||||
Transaction *trans;
|
||||
GUID *guid;
|
||||
int version;
|
||||
|
||||
if (!txn)
|
||||
return 0;
|
||||
|
||||
guid = (GUID *)(txn->guid);
|
||||
trans = xaccTransLookup (guid);
|
||||
if (!trans)
|
||||
return -1;
|
||||
|
||||
version = xaccTransGetVersion (trans);
|
||||
if (version == txn->vers)
|
||||
return 0;
|
||||
else if (version > txn->vers)
|
||||
return 1;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Return an int depicting whether this txn was added to the engine:
|
||||
* -1 == transaction is new(er) and was added
|
||||
* 0 == transaction version numbers match; no changes
|
||||
* 1 == transaction is older than current engine cache; no changes
|
||||
*/
|
||||
static int rpcend_add_transaction (RPCBackend *be, gncTransaction *txn)
|
||||
{
|
||||
int cache_is_newer;
|
||||
gnc_commodity_table *ct;
|
||||
|
||||
if (!be || !txn) return 0;
|
||||
|
||||
/* disable callbacks and GUI events */
|
||||
gnc_engine_suspend_events ();
|
||||
rpcendDisable (be);
|
||||
|
||||
ct = gnc_book_get_commodity_table (be->book);
|
||||
cache_is_newer = rpcend_do_add_txn (txn, ct);
|
||||
|
||||
/* re-enable events to the backend and GUI */
|
||||
rpcendEnable (be);
|
||||
gnc_engine_resume_events ();
|
||||
|
||||
return cache_is_newer;
|
||||
}
|
||||
|
||||
static int rpcend_add_gnctransaction_list (RPCBackend *be, gnc_txnlist *tl)
|
||||
{
|
||||
for (; tl != NULL; tl = tl->next) {
|
||||
rpcend_add_transaction (be, tl->txn);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rpcend_add_gnccommoditylist (RPCBackend *be, gnc_commoditylist *cl)
|
||||
{
|
||||
gnc_commodity_table *ct = gnc_book_get_commodity_table (be->book);
|
||||
|
||||
rpcend_load_commoditylist (ct, cl);
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************/
|
||||
/* Below this line are the 'exported interface' functions */
|
||||
|
||||
/*
|
||||
* book_load will only load the commodity table and account tree
|
||||
*/
|
||||
static AccountGroup * rpcend_book_load (Backend *bend)
|
||||
{
|
||||
RPCBackend *be = (RPCBackend *)bend;
|
||||
AccountGroup *ag = NULL;
|
||||
gncrpc_ptr backend;
|
||||
gncrpc_book_load_ret ret;
|
||||
|
||||
VERIFY_BE (be, NULL);
|
||||
|
||||
ENTER ("be=%p", be);
|
||||
|
||||
/* XXX: Handle the case where the connection was closed! */
|
||||
|
||||
memset (backend, 0, sizeof(backend));
|
||||
memset (&ret, 0, sizeof (ret));
|
||||
memcpy (backend, (char *)&be, sizeof(be));
|
||||
gncrpc_book_load_1 (backend, &ret, be->client);
|
||||
|
||||
if (ret.error != 0) {
|
||||
xaccBackendSetError (&be->be, ret.error);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* suspend events */
|
||||
gnc_engine_suspend_events ();
|
||||
rpcendDisable (be);
|
||||
|
||||
/* Build the Commodity Table */
|
||||
rpcend_add_gnccommoditylist (be, ret.commodities);
|
||||
|
||||
/* Parse the AccountGroup */
|
||||
ag = gnc_book_get_group (be->book);
|
||||
if (!ag)
|
||||
ag = xaccMallocAccountGroup ();
|
||||
rpcend_add_gncacctlist (be, ag, ret.acctlist);
|
||||
|
||||
/* Mark the newly read group as saved, since the act of putting
|
||||
* it together will have caused it to be marked up as not-saved.
|
||||
*/
|
||||
xaccGroupMarkSaved (ag);
|
||||
|
||||
rpcendEnable (be);
|
||||
gnc_engine_resume_events ();
|
||||
|
||||
/* Free the RPC results */
|
||||
CLNT_FREERES (be->client, (xdrproc_t)xdr_gncrpc_book_load_ret, (caddr_t)&ret);
|
||||
|
||||
LEAVE ("be=%p, ag=%p", be, ag);
|
||||
|
||||
return ag;
|
||||
}
|
||||
|
||||
static void rpcend_book_end (Backend *bend)
|
||||
{
|
||||
RPCBackend *be = (RPCBackend *)bend;
|
||||
gncrpc_ptr backend;
|
||||
int res = 0;
|
||||
VERIFY_BEV (be);
|
||||
|
||||
ENTER ("be=%p", be);
|
||||
|
||||
/* XXX: Handle the case where the connection was closed */
|
||||
if (be->client) {
|
||||
memset (backend, 0, sizeof(backend));
|
||||
memcpy (backend, (char *)&be, sizeof(be));
|
||||
gncrpc_book_end_1 (backend, &res, be->client);
|
||||
if (res != 0)
|
||||
xaccBackendSetError (&be->be, res);
|
||||
}
|
||||
|
||||
rpcendDisable (be);
|
||||
/* Disconnect and cleanup XXX */
|
||||
|
||||
/* Now close the socket */
|
||||
if (be->sock)
|
||||
RpcClose (be->sock);
|
||||
|
||||
LEAVE ("be=%p", be);
|
||||
}
|
||||
|
||||
static int rpcend_account_begin_edit (Backend *bend, Account *acct)
|
||||
{
|
||||
RPCBackend *be = (RPCBackend *)bend;
|
||||
gncrpc_backend_guid args;
|
||||
int ret = 0;
|
||||
VERIFY_BE (be, -1);
|
||||
|
||||
ENTER ("be=%p, acc=%p", be, acct);
|
||||
|
||||
memset (&args, 0, sizeof (args));
|
||||
memcpy (args.backend, (char *)&be, sizeof(be));
|
||||
memcpy (args.guid, acct->guid.data, sizeof (args.guid));
|
||||
|
||||
gncrpc_account_begin_edit_1 (&args, &ret, be->client);
|
||||
LEAVE ("be=%p, acc=%p (%s)", be, acct, acct ? acct->accountName : "");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rpcend_account_rollback_edit (Backend *bend, Account *acct)
|
||||
{
|
||||
RPCBackend *be = (RPCBackend *)bend;
|
||||
gncrpc_backend_guid args;
|
||||
int ret = 0;
|
||||
VERIFY_BE (be, -1);
|
||||
|
||||
ENTER ("be=%p, acc=%p (%s)", be, acct, acct ? acct->accountName : "");
|
||||
|
||||
if (acct == NULL)
|
||||
return ERR_BACKEND_MISC;
|
||||
|
||||
memset (&args, 0, sizeof (args));
|
||||
memcpy (args.backend, (char *)&be, sizeof(be));
|
||||
memcpy (args.guid, acct->guid.data, sizeof (args.guid));
|
||||
|
||||
gncrpc_account_rollback_edit_1 (&args, &ret, be->client);
|
||||
|
||||
LEAVE ("be=%p, acc=%p, ret=%d", be, acct, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rpcend_account_commit_edit (Backend *bend, Account *acct)
|
||||
{
|
||||
RPCBackend *be = (RPCBackend *)bend;
|
||||
gncrpc_commit_acct_args args;
|
||||
int ret = 0;
|
||||
AccountGroup *parent;
|
||||
VERIFY_BE (be, -1);
|
||||
|
||||
ENTER ("be=%p, acc=%p (%s)", be, acct, acct ? acct->accountName : "");
|
||||
|
||||
if (acct == NULL)
|
||||
return ERR_BACKEND_MISC;
|
||||
|
||||
parent = xaccAccountGetParent(acct);
|
||||
|
||||
/* First, see if we need to do anything here */
|
||||
if (acct->core_dirty == FALSE) {
|
||||
if (parent) parent->saved = 1;
|
||||
PINFO ("core_dirty == FALSE");
|
||||
return (rpcend_account_rollback_edit (bend, acct));
|
||||
} else {
|
||||
acct->version++;
|
||||
}
|
||||
|
||||
memset (&args, 0, sizeof (args));
|
||||
memcpy (args.backend, (char *)&be, sizeof(be));
|
||||
|
||||
/* Copy the Account information */
|
||||
rpcend_build_gncacct (&(args.acct), acct);
|
||||
#ifdef GNCACCT_COMMODITY
|
||||
args.commodity = (gncCommodity *) xaccAccountGetCommodity (acct);
|
||||
#else
|
||||
args.currency = (gncCommodity *) xaccAccountGetCurrency (acct);
|
||||
args.security = (gncCommodity *) xaccAccountGetSecurity (acct);
|
||||
#endif
|
||||
|
||||
gncrpc_account_commit_edit_1 (&args, &ret, be->client);
|
||||
|
||||
/* Free up allocated data (in particular the kvp_frame */
|
||||
rpcend_free_gnckvp (args.acct.kvp_data);
|
||||
|
||||
/* Set the save flag in the parent, even if the RPC failed */
|
||||
if (parent) parent->saved = 1;
|
||||
|
||||
if (ret != 0) {
|
||||
/* Failed to write; revert our copy */
|
||||
acct->version--;
|
||||
}
|
||||
|
||||
LEAVE ("be=%p, acc=%p, ret=%d", be, acct, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rpcend_trans_begin_edit (Backend *bend, Transaction *txn)
|
||||
{
|
||||
RPCBackend *be = (RPCBackend *)bend;
|
||||
gncrpc_backend_guid args;
|
||||
int ret = 0;
|
||||
VERIFY_BE (be, -1);
|
||||
|
||||
ENTER ("be=%p, txn=%p", be, txn);
|
||||
|
||||
memset (&args, 0, sizeof (args));
|
||||
memcpy (args.backend, (char *)&be, sizeof(be));
|
||||
memcpy (args.guid, txn->guid.data, sizeof (args.guid));
|
||||
|
||||
gncrpc_txn_begin_edit_1 (&args, &ret, be->client);
|
||||
LEAVE ("be=%p, txn=%p", be, txn);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rpcend_trans_commit_edit (Backend *bend, Transaction *new,
|
||||
Transaction *orig)
|
||||
{
|
||||
RPCBackend *be = (RPCBackend *)bend;
|
||||
gncrpc_commit_txn_args args;
|
||||
int ret = 0;
|
||||
VERIFY_BE (be, -1);
|
||||
ENTER ("be=%p, new=%p, vers=%d", be, new, new->version);
|
||||
|
||||
new->version++;
|
||||
|
||||
memset (&args, 0, sizeof (args));
|
||||
memcpy (args.backend, (char *)&be, sizeof(be));
|
||||
rpcend_build_gnctxn (&args.new, new);
|
||||
|
||||
gncrpc_txn_commit_edit_1 (&args, &ret, be->client);
|
||||
|
||||
rpcend_free_gnctxn (&args.new, FALSE);
|
||||
|
||||
if (ret != 0) {
|
||||
/* Failed to write; backoff our version number */
|
||||
new->version--;
|
||||
}
|
||||
|
||||
LEAVE ("be=%p, new=%p, ret=%d, txn_vers=%d", be, new, ret, new->version);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rpcend_trans_rollback_edit (Backend *bend, Transaction *txn)
|
||||
{
|
||||
RPCBackend *be = (RPCBackend *)bend;
|
||||
gncrpc_backend_guid args;
|
||||
int ret = 0;
|
||||
VERIFY_BE (be, -1);
|
||||
ENTER ("be=%p, txn=%p", be, txn);
|
||||
memset (&args, 0, sizeof (args));
|
||||
memcpy (args.backend, (char *)&be, sizeof(be));
|
||||
memcpy (args.guid, txn->guid.data, sizeof (args.guid));
|
||||
|
||||
gncrpc_txn_rollback_edit_1 (&args, &ret, be->client);
|
||||
|
||||
LEAVE ("be=%p, txn=%p, ret=%d", be, txn, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void rpcend_run_query (Backend *bend, Query *q)
|
||||
{
|
||||
RPCBackend *be = (RPCBackend *)bend;
|
||||
gncrpc_query_args args;
|
||||
gncrpc_query_ret ret;
|
||||
gncrpc_get_txns_args txns_args;
|
||||
gncrpc_get_txns_ret txns_ret;
|
||||
|
||||
VERIFY_BEV (be);
|
||||
if (!q) return;
|
||||
|
||||
ENTER ("be=%p, q=%p", be, q);
|
||||
memset (&args, 0, sizeof (args));
|
||||
memset (&ret, 0, sizeof (ret));
|
||||
memcpy (args.backend, (char *)&be, sizeof(be));
|
||||
|
||||
/* xaccQueryPrint (q);*/
|
||||
|
||||
/* Save off some non-transmitted information from the Query, and
|
||||
* restore it after we run the query
|
||||
*/
|
||||
{
|
||||
AccountGroup *actg;
|
||||
gncQuery gq;
|
||||
|
||||
rpcend_build_gncquery (&gq, q);
|
||||
args.query = &gq;
|
||||
|
||||
actg = (AccountGroup *)gq.acct_group;
|
||||
gq.acct_group = NULL;
|
||||
gq.split_list = NULL;
|
||||
gq.xtn_list = NULL;
|
||||
|
||||
if (actg && actg->parent)
|
||||
args.group_parent_guid = (gncGUID *) &(actg->parent->guid);
|
||||
|
||||
PINFO ("Calling 'run_query'");
|
||||
gncrpc_run_query_1 (&args, &ret, be->client);
|
||||
|
||||
rpcend_free_gncquery (&gq);
|
||||
}
|
||||
|
||||
if (ret.error != 0) {
|
||||
xaccBackendSetError (&be->be, ret.error);
|
||||
LEAVE ("be=%p, q=%p, query_1", be, q);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Foreach transaction, figure out if it's in the engine or not. If
|
||||
* it's not in the engine, or if the version implies an updated
|
||||
* version, then add it to the list of txns to download. Then
|
||||
* download all the 'updated' txns and add them to the engine
|
||||
*/
|
||||
|
||||
{
|
||||
gnc_vers_list *old = NULL, **endold = &old;
|
||||
gnc_vers_list *new = NULL, **endnew = &new;
|
||||
gnc_vers_list *verslist;
|
||||
|
||||
/* Figure out which transactions we need to add */
|
||||
for (verslist = ret.txnlist; verslist != NULL; verslist = verslist->next) {
|
||||
if (rpcend_would_add_txn (verslist) < 0) {
|
||||
*endnew = verslist;
|
||||
endnew = &(verslist->next);
|
||||
} else {
|
||||
*endold = verslist;
|
||||
endold = &(verslist->next);
|
||||
}
|
||||
}
|
||||
|
||||
*endnew = NULL;
|
||||
|
||||
memset (&txns_ret, 0, sizeof (txns_ret));
|
||||
if (new) {
|
||||
/* Now grab the updated/new transactions */
|
||||
memcpy (txns_args.backend, (char *)&be, sizeof(be));
|
||||
txns_args.guids = new;
|
||||
PINFO ("Calling 'get_txns'");
|
||||
gncrpc_get_txns_1 (&txns_args, &txns_ret, be->client);
|
||||
|
||||
/* And now reconnect the new and old txnlist and put it back in
|
||||
* the results so that freeres will destroy it all
|
||||
*/
|
||||
}
|
||||
*endold = new;
|
||||
ret.txnlist = old;
|
||||
}
|
||||
|
||||
/* Free the results from the first RPC query */
|
||||
CLNT_FREERES (be->client, (xdrproc_t)xdr_gncrpc_query_ret, (caddr_t)&ret);
|
||||
|
||||
/* Make sure this RPC was ok */
|
||||
if (txns_ret.error != 0) {
|
||||
xaccBackendSetError (&be->be, txns_ret.error);
|
||||
LEAVE ("be=%p, q=%p, get_txns", be, q);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Suspend events */
|
||||
gnc_engine_suspend_events();
|
||||
rpcendDisable(be);
|
||||
|
||||
/* Now, add all the transactions to the engine */
|
||||
rpcend_add_gnctransaction_list (be, txns_ret.txnlist);
|
||||
|
||||
/* Resume events */
|
||||
rpcendEnable(be);
|
||||
gnc_engine_resume_events();
|
||||
|
||||
/* And free the results */
|
||||
CLNT_FREERES (be->client, (xdrproc_t)xdr_gncrpc_get_txns_ret, (caddr_t)&txns_ret);
|
||||
LEAVE ("be=%p, q=%p", be, q);
|
||||
}
|
||||
|
||||
static void rpcend_sync (Backend *bend, AccountGroup *acctgrp)
|
||||
{
|
||||
RPCBackend *be = (RPCBackend *)bend;
|
||||
gncrpc_sync1_args args1;
|
||||
gncrpc_sync1_ret ret1;
|
||||
gncrpc_sync2_args args2;
|
||||
int ret2 = -1;
|
||||
gnc_commodity_table *ct = gnc_book_get_commodity_table (be->book);
|
||||
VERIFY_BEV (be);
|
||||
ENTER ("be=%p, ag=%p", be, acctgrp);
|
||||
|
||||
memset (&args1, 0, sizeof (args1));
|
||||
memset (&ret1, 0, sizeof (ret1));
|
||||
memcpy (args1.backend, (char *)&be, sizeof(be));
|
||||
args1.commodities = rpcend_build_gnccommoditylist (ct, FALSE);
|
||||
args1.acctlist = rpcend_build_gncacct_verslist (acctgrp, FALSE);
|
||||
args1.txnlist = rpcend_build_gnctxn_verslist (acctgrp, FALSE);
|
||||
|
||||
/* Call RPC */
|
||||
gncrpc_sync1_1 (&args1, &ret1, be->client);
|
||||
|
||||
/* Free the arguments of request1 */
|
||||
rpcend_free_gnccommoditylist (args1.commodities, FALSE);
|
||||
rpcend_free_verslist (args1.txnlist, FALSE);
|
||||
rpcend_free_verslist (args1.acctlist, FALSE);
|
||||
|
||||
if (ret1.error != 0) {
|
||||
xaccBackendSetError (&be->be, ret1.error);
|
||||
CLNT_FREERES (be->client, (xdrproc_t)xdr_gncrpc_sync1_ret, (caddr_t)&ret1);
|
||||
LEAVE ("be=%p, ag=%p, sync1", be, acctgrp);
|
||||
return;
|
||||
}
|
||||
|
||||
/* XXX: Do we need to update any version numbers here? */
|
||||
|
||||
/*
|
||||
* Now, add the accounts and transactions that the server sent us,
|
||||
* and send the server the accounts and transactions that the server
|
||||
* requested
|
||||
*/
|
||||
|
||||
/* Suspend events */
|
||||
gnc_engine_suspend_events();
|
||||
rpcendDisable(be);
|
||||
|
||||
/* Add all the commoditities, accounts, and transactions to the engine */
|
||||
rpcend_add_gnccommoditylist (be, ret1.commodities);
|
||||
rpcend_add_gncacctlist (be, acctgrp, ret1.acctlist);
|
||||
rpcend_add_gnctransaction_list (be, ret1.txnlist);
|
||||
|
||||
/* Then build the response to send back to the server */
|
||||
memset (&args2, 0, sizeof (args2));
|
||||
memcpy (args2.backend, (char *)&be, sizeof(be));
|
||||
args2.acctlist = rpcend_build_gncacctlist_list (acctgrp,
|
||||
ret1.send_acctlist);
|
||||
args2.txnlist = rpcend_build_gnctxnlist_list (acctgrp,
|
||||
ret1.send_txnlist);
|
||||
|
||||
/* Free the first results */
|
||||
CLNT_FREERES (be->client, (xdrproc_t)xdr_gncrpc_sync1_ret, (caddr_t)&ret1);
|
||||
|
||||
/* set everything as saved */
|
||||
xaccGroupMarkSaved (acctgrp);
|
||||
|
||||
/* Resume events */
|
||||
rpcendEnable(be);
|
||||
gnc_engine_resume_events();
|
||||
|
||||
/* Now send this back to the server */
|
||||
gncrpc_sync2_1 (&args2, &ret2, be->client);
|
||||
|
||||
/* Free the arguments to request 2 */
|
||||
rpcend_free_gnctxnlist (args2.txnlist);
|
||||
rpcend_free_gncacctlist (args2.acctlist);
|
||||
|
||||
/* And return */
|
||||
xaccBackendSetError (&be->be, ret2);
|
||||
LEAVE ("be=%p, ag=%p", be, acctgrp);
|
||||
return;
|
||||
}
|
||||
|
||||
static gboolean rpcend_events_pending (Backend *bend)
|
||||
{
|
||||
RPCBackend *be = (RPCBackend *)bend;
|
||||
VERIFY_BE (be, FALSE);
|
||||
|
||||
/* The events flag can only be cleared from within this thread of
|
||||
* control. So, the worst thing that can happen here is that we
|
||||
* have a false-negative.
|
||||
*/
|
||||
return be->events;
|
||||
}
|
||||
|
||||
static gboolean rpcend_process_events (Backend *bend)
|
||||
{
|
||||
RPCBackend *be = (RPCBackend *)bend;
|
||||
gboolean changed = FALSE;
|
||||
VERIFY_BE (be, FALSE);
|
||||
|
||||
pthread_mutex_lock (&(be->eventlock));
|
||||
if (!be->events) {
|
||||
pthread_mutex_unlock (&(be->eventlock));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Yep, there are events. Copy off the event information, then
|
||||
* unlock the Backend event handler. Then go off and process the
|
||||
* events. Note that one of the events could be "disconnected from
|
||||
* server", so check for that first!
|
||||
*/
|
||||
|
||||
/* Process any waiting events */
|
||||
/* XXX */
|
||||
return changed;
|
||||
}
|
||||
|
||||
static void rpcend_book_begin (GNCBook *book, const char *book_id,
|
||||
gboolean ignore_lock, gboolean create)
|
||||
{
|
||||
RPCBackend *be;
|
||||
char *url, *start, *end, *rest;
|
||||
|
||||
if (!book) return;
|
||||
be = (RPCBackend *) xaccGNCBookGetBackend (book);
|
||||
VERIFY_BEV (be);
|
||||
|
||||
ENTER("be=%p, id=%s, ignore=%s, create=%s", be, book_id,
|
||||
(ignore_lock == TRUE ? "true" : "false"),
|
||||
(create == TRUE ? "true" : "false"));
|
||||
|
||||
/* close any dangling sessions from before and then reinitialize */
|
||||
rpcend_book_end ((Backend *)be);
|
||||
rpcendInit (be);
|
||||
|
||||
/* Remember my book */
|
||||
be->book = book;
|
||||
|
||||
/* Parse the book_id for the hostname and db name.
|
||||
* The expected URL format is:
|
||||
* rpc://host[:port]/db_name
|
||||
*/
|
||||
if (strncmp (book_id, "rpc://", 6)) {
|
||||
xaccBackendSetError (&be->be, ERR_RPC_BAD_URL);
|
||||
LEAVE ("Not an RPC URL?");
|
||||
return;
|
||||
}
|
||||
url = g_strdup(book_id);
|
||||
start = url + 6;
|
||||
rest = strchr (start, '/');
|
||||
if (!rest || *rest == '\0') {
|
||||
xaccBackendSetError (&be->be, ERR_RPC_BAD_URL);
|
||||
g_free (url);
|
||||
LEAVE ("cannot find a path after host[:port]");
|
||||
return;
|
||||
}
|
||||
*rest = '\0';
|
||||
end = strchr (start, ':');
|
||||
if (end) {
|
||||
/* end would have the port number */
|
||||
*end = '\0';
|
||||
be->portNum = g_strdup (end+1);
|
||||
}
|
||||
be->hostname = g_strdup (start);
|
||||
start = rest+1;
|
||||
if (*start == '\0') {
|
||||
xaccBackendSetError (&be->be, ERR_RPC_BAD_URL);
|
||||
g_free (url);
|
||||
LEAVE ("tailing slash but no path after host[:port]");
|
||||
return;
|
||||
}
|
||||
|
||||
/* dbname is the last thing before any url-encoded data */
|
||||
be->dbName = g_strdup (start);
|
||||
|
||||
/* I would parse url-encoded data here, but I don't have any */
|
||||
/* XXX */
|
||||
|
||||
/* make the connection */
|
||||
rpcendLogin (be);
|
||||
|
||||
/* Free the URL */
|
||||
g_free (url);
|
||||
|
||||
if (be->xprt == NULL)
|
||||
return;
|
||||
|
||||
/* Call the remote procedure to open the book */
|
||||
{
|
||||
gncrpc_book_begin_args args;
|
||||
int res = 0;
|
||||
|
||||
memset (&args, 0, sizeof (args));
|
||||
memcpy (args.book, (char *)&book, sizeof (book));
|
||||
memcpy (args.backend, (char *)&be, sizeof (be));
|
||||
args.book_id = be->dbName;
|
||||
args.ignore_lock = ignore_lock;
|
||||
args.create = create;
|
||||
|
||||
gncrpc_book_begin_1 (&args, &res, be->client);
|
||||
|
||||
if (res != 0) {
|
||||
RpcClose (be->sock);
|
||||
xaccBackendSetError (&be->be, res);
|
||||
LEAVE ("begin");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Setup callbacks */
|
||||
rpcendEnable (be);
|
||||
be->be.book_end = rpcend_book_end;
|
||||
be->be.book_load = rpcend_book_load;
|
||||
be->be.account_begin_edit = rpcend_account_begin_edit;
|
||||
be->be.account_commit_edit = rpcend_account_commit_edit;
|
||||
be->be.trans_begin_edit = rpcend_trans_begin_edit;
|
||||
be->be.trans_commit_edit = rpcend_trans_commit_edit;
|
||||
be->be.trans_rollback_edit = rpcend_trans_rollback_edit;
|
||||
be->be.run_query = rpcend_run_query;
|
||||
be->be.sync = rpcend_sync;
|
||||
be->be.events_pending = rpcend_events_pending;
|
||||
be->be.process_events = rpcend_process_events;
|
||||
|
||||
LEAVE("be=%p, id=%s", be, book_id);
|
||||
}
|
||||
|
||||
static void
|
||||
rpcendInit (RPCBackend *be)
|
||||
{
|
||||
memset (be, 0, sizeof (*be));
|
||||
|
||||
/* The only callback that should work is Begin */
|
||||
be->be.book_begin = rpcend_book_begin;
|
||||
|
||||
rpcendDisable (be);
|
||||
be->be.last_err = ERR_BACKEND_NO_ERR;
|
||||
|
||||
/* RPC specific data */
|
||||
be->magic = RPCEND_MAGIC;
|
||||
|
||||
/* Initialize the event mutex */
|
||||
pthread_mutex_init (&(be->eventlock), NULL);
|
||||
}
|
||||
|
||||
Backend *
|
||||
rpcendNew (void)
|
||||
{
|
||||
RPCBackend *be;
|
||||
be = (RPCBackend *) g_malloc (sizeof (*be));
|
||||
rpcendInit (be);
|
||||
|
||||
return (Backend *) be;
|
||||
}
|
||||
|
25
src/engine/rpc/RpcBackend.h
Normal file
25
src/engine/rpc/RpcBackend.h
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* FILE:
|
||||
* RpcBackend.h
|
||||
*
|
||||
* FUNCTION:
|
||||
* Implements the callbacks for the RPC (client) backend.
|
||||
*
|
||||
* HISTORY:
|
||||
* Created By: Derek Atkins <warlord@MIT.EDU>
|
||||
* Copyright (c) 2001, Derek Atkins
|
||||
*/
|
||||
|
||||
#ifndef __RPC_BACKEND_H
|
||||
#define __RPC_BACKEND_H
|
||||
|
||||
#include "BackendP.h"
|
||||
|
||||
typedef struct _rpcend RPCBackend;
|
||||
|
||||
/*
|
||||
* rpcendNew() creates a new RPC Backend
|
||||
*/
|
||||
Backend * rpcendNew (void);
|
||||
|
||||
#endif /* __RPC_BACKEND_H */
|
116
src/engine/rpc/RpcServer.c
Normal file
116
src/engine/rpc/RpcServer.c
Normal file
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* FILE:
|
||||
* RpcServer.c
|
||||
*
|
||||
* FUNCTION:
|
||||
* Implements the Gnucash RPC server.
|
||||
*
|
||||
* HISTORY:
|
||||
* Created By: Derek Atkins <warlord@MIT.EDU>
|
||||
* Copyright (c) 2001, Derek Atkins
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <rpc/xprt_thrd.h>
|
||||
|
||||
#include "gnc-engine-util.h"
|
||||
|
||||
#include "RpcServer.h"
|
||||
#include "RpcServerP.h"
|
||||
#include "RpcSock.h"
|
||||
#include "gncRpc.h"
|
||||
|
||||
extern void gncrpc_prog_1(struct svc_req *, register SVCXPRT *);
|
||||
|
||||
static short module = MOD_BACKEND;
|
||||
|
||||
static void myClose (void *arg)
|
||||
{
|
||||
GncRpcSvc *cl = (GncRpcSvc *)arg;
|
||||
|
||||
if (!cl)
|
||||
return;
|
||||
|
||||
TXPRT_DESTROY (cl->xprt);
|
||||
if (cl->clnt)
|
||||
CLNT_DESTROY (cl->clnt);
|
||||
if (cl->book)
|
||||
gnc_book_destroy (cl->book);
|
||||
|
||||
PINFO ("Client Disconnected: %p", cl);
|
||||
|
||||
/* Remove myself from the Client List */
|
||||
cl->clist->clist = g_list_remove (cl->clist->clist, cl);
|
||||
|
||||
g_free (cl);
|
||||
}
|
||||
|
||||
int rpc_server_run (unsigned short port)
|
||||
{
|
||||
RPCSock *master;
|
||||
int ret;
|
||||
int run = 1;
|
||||
GncRPCClist clist;
|
||||
|
||||
ENTER ("port=%d", port);
|
||||
|
||||
if (!port)
|
||||
port = RPCEND_PORT;
|
||||
|
||||
if ((ret = RpcCreateListener (htons(port), &master)) != 0) {
|
||||
LEAVE ("listener failed");
|
||||
return ret;
|
||||
}
|
||||
|
||||
fprintf (stderr, "RPC Server Running...\n");
|
||||
|
||||
while (run) {
|
||||
GncRpcSvc *new = g_malloc (sizeof (*new));
|
||||
if (!new) {
|
||||
LEAVE ("g_malloc failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset (new, 0, sizeof (*new));
|
||||
new->book = gnc_book_new ();
|
||||
if (!new->book) {
|
||||
g_free (new);
|
||||
LEAVE ("gnc_book_new() failed");
|
||||
return -2;
|
||||
}
|
||||
|
||||
new->clist = &clist;
|
||||
|
||||
/* Setup to listen */
|
||||
RpcListen (master, 3);
|
||||
|
||||
/* Grab the next client */
|
||||
if ((ret = RpcAccept (master, &(new->sock))) != 0) {
|
||||
run = 0;
|
||||
LEAVE ("Accept failed (%d)", ret);
|
||||
break;
|
||||
}
|
||||
|
||||
/* XXX Authenticate? */
|
||||
|
||||
/* Build the RPC Transport */
|
||||
if ((ret = RpcTransport (new->sock, myClose, (void *)new, &(new->xprt)))
|
||||
!= 0) {
|
||||
RpcClose (new->sock);
|
||||
run = 0;
|
||||
LEAVE ("Transport failed (%d)", ret);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Add this client to the clist */
|
||||
clist.clist = g_list_prepend (clist.clist, new);
|
||||
|
||||
/* Register the service */
|
||||
TXPRT_REG_CALLOUT (new->xprt, GNCRPC_PROG, GNCRPC_VERS, gncrpc_prog_1);
|
||||
PINFO ("New Client connected: %p", new);
|
||||
}
|
||||
return ret;
|
||||
}
|
20
src/engine/rpc/RpcServer.h
Normal file
20
src/engine/rpc/RpcServer.h
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* FILE:
|
||||
* RpcServer.h
|
||||
*
|
||||
* FUNCTION:
|
||||
* Implements the Gnucash RPC server.
|
||||
*
|
||||
* HISTORY:
|
||||
* Created By: Derek Atkins <warlord@MIT.EDU>
|
||||
* Copyright (c) 2001, Derek Atkins
|
||||
*/
|
||||
|
||||
#ifndef __RPC_SERVER_H
|
||||
#define __RPC_SERVER_H
|
||||
|
||||
typedef struct _gncrpc_svc GncRpcSvc;
|
||||
|
||||
int rpc_server_run (unsigned short port);
|
||||
|
||||
#endif /* __RPC_SERVER_H */
|
37
src/engine/rpc/RpcServerP.h
Normal file
37
src/engine/rpc/RpcServerP.h
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* FILE:
|
||||
* RpcServerP.h
|
||||
*
|
||||
* FUNCTION:
|
||||
* Gnucash RPC Server Private Header
|
||||
*
|
||||
* HISTORY:
|
||||
* Created By: Derek Atkins <warlord@MIT.EDU>
|
||||
* Copyright (c) 2001, Derek Atkins
|
||||
*/
|
||||
|
||||
#ifndef __RPC_SERVERP_H
|
||||
#define __RPC_SERVERP_H
|
||||
|
||||
#include <rpc/xprt_thrd.h>
|
||||
|
||||
#include "RpcServer.h"
|
||||
#include "RpcSock.h"
|
||||
#include "gncRpc.h"
|
||||
#include "gnc-book.h"
|
||||
|
||||
typedef struct _gncrpc_clist {
|
||||
GList *clist;
|
||||
} GncRPCClist;
|
||||
|
||||
struct _gncrpc_svc {
|
||||
TXPRT * xprt; /* Transport */
|
||||
RPCSock * sock; /* Socket */
|
||||
|
||||
GNCBook * book; /* My Client's Book */
|
||||
CLIENT * clnt; /* Client's Callback Object */
|
||||
|
||||
GncRPCClist * clist; /* Client List */
|
||||
};
|
||||
|
||||
#endif /* __RPC_SERVERP_H */
|
252
src/engine/rpc/RpcSock.c
Normal file
252
src/engine/rpc/RpcSock.c
Normal file
@ -0,0 +1,252 @@
|
||||
/*
|
||||
* FILE:
|
||||
* RpcSock.c
|
||||
*
|
||||
* FUNCTION:
|
||||
* Implements the RPC Socket connection
|
||||
*
|
||||
* HISTORY:
|
||||
* Created By: Derek Atkins <warlord@MIT.EDU>
|
||||
* Copyright (c) 2001, Derek Atkins
|
||||
*/
|
||||
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
#include <rpc/xprt_thrd.h>
|
||||
#include "RpcBackend.h"
|
||||
#include "RpcSock.h"
|
||||
|
||||
struct _rpcend_sock {
|
||||
int sock; /* socket */
|
||||
TXPRT * xprt; /* Transport */
|
||||
void * cb_arg; /* Callback Argument */
|
||||
void (*close)(void *cb); /* Close callback */
|
||||
gboolean listener; /* Is this a master listener? */
|
||||
struct sockaddr_in peer; /* Peer Address */
|
||||
};
|
||||
|
||||
|
||||
static int
|
||||
myread (caddr_t sockp, char *buf, int len)
|
||||
{
|
||||
RPCSock *sock = (RPCSock *)sockp;
|
||||
|
||||
if (len == 0)
|
||||
return 0;
|
||||
|
||||
switch (len = read (sock->sock, buf, len))
|
||||
{
|
||||
case 0:
|
||||
/* premature eof */
|
||||
/* ECONNRESET, RPC_CANTRECV */
|
||||
len = -1; /* it's really an error */
|
||||
break;
|
||||
|
||||
case -1:
|
||||
/* RPC_CANTRECV */
|
||||
break;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static int
|
||||
mywrite (caddr_t sockp, char *buf, int len)
|
||||
{
|
||||
RPCSock *sock = (RPCSock *)sockp;
|
||||
int i, cnt;
|
||||
|
||||
for (cnt = len; cnt > 0; cnt -= i, buf += i)
|
||||
{
|
||||
if ((i = write (sock->sock, buf, cnt)) == -1)
|
||||
{
|
||||
/* RPC_CANTSEND */
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static int
|
||||
myselect (caddr_t sockp)
|
||||
{
|
||||
RPCSock *sock = (RPCSock *)sockp;
|
||||
struct timeval timeout;
|
||||
fd_set fds;
|
||||
|
||||
timeout.tv_sec = 2;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(sock->sock, &fds);
|
||||
|
||||
return select (sock->sock+1, &fds, 0, 0, &timeout);
|
||||
}
|
||||
|
||||
static void
|
||||
myclose (caddr_t sockp)
|
||||
{
|
||||
RPCSock *sock = (RPCSock *)sockp;
|
||||
|
||||
/* Close callback */
|
||||
if (sock->close)
|
||||
(*sock->close)(sock->cb_arg);
|
||||
|
||||
close (sock->sock);
|
||||
g_free (sock);
|
||||
}
|
||||
|
||||
int RpcConnect (char *hostname, unsigned short port, RPCSock **sock)
|
||||
{
|
||||
RPCSock *new;
|
||||
int s;
|
||||
struct hostent *hp;
|
||||
struct sockaddr_in sin;
|
||||
|
||||
if (hostname == NULL || *hostname == '\0' || port == 0)
|
||||
return ERR_RPC_BAD_URL;
|
||||
|
||||
if (sock == NULL)
|
||||
return ERR_BACKEND_MISC;
|
||||
|
||||
if ((hp = gethostbyname (hostname)) == NULL)
|
||||
return ERR_RPC_HOST_UNK;
|
||||
|
||||
sin.sin_family = hp->h_addrtype;
|
||||
sin.sin_port = port;
|
||||
memcpy (&sin.sin_addr, hp->h_addr, MIN(sizeof(sin.sin_addr),hp->h_length));
|
||||
|
||||
if ((s = socket (sin.sin_family, SOCK_STREAM, 0)) < 0)
|
||||
return ERR_BACKEND_ALLOC;
|
||||
|
||||
if (connect (s, &sin, sizeof(sin)) != 0)
|
||||
return ERR_RPC_CANT_CONNECT;
|
||||
|
||||
new = g_malloc (sizeof (*new));
|
||||
if (new == NULL) {
|
||||
close (s);
|
||||
return ERR_BACKEND_ALLOC;
|
||||
}
|
||||
|
||||
memset (new, 0, sizeof (*new));
|
||||
new->sock = s;
|
||||
*sock = new;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RpcClose (RPCSock *sock)
|
||||
{
|
||||
if (sock == NULL)
|
||||
return ERR_BACKEND_MISC;
|
||||
|
||||
myclose ((caddr_t) sock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RpcTransport (RPCSock *sock, void (*myClose)(void *arg), void *arg,
|
||||
TXPRT **xprt)
|
||||
{
|
||||
TXPRT *x;
|
||||
|
||||
if (sock == NULL || xprt == NULL || myClose == NULL || sock->xprt != NULL)
|
||||
return ERR_BACKEND_MISC;
|
||||
|
||||
x = xprt_thrd_create ((caddr_t)sock, myread, mywrite, myclose, myselect,
|
||||
0, 0);
|
||||
if (x == NULL)
|
||||
return ERR_BACKEND_ALLOC;
|
||||
|
||||
sock->close = myClose;
|
||||
sock->cb_arg = arg;
|
||||
sock->xprt = *xprt = x;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RpcCreateListener (unsigned short port, RPCSock **sock)
|
||||
{
|
||||
int s;
|
||||
int on = 1;
|
||||
struct sockaddr_in sin;
|
||||
RPCSock *new;
|
||||
|
||||
if (!sock)
|
||||
return ERR_BACKEND_MISC;
|
||||
|
||||
/* Create socket */
|
||||
if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0)
|
||||
return ERR_BACKEND_ALLOC;
|
||||
|
||||
/* Set socket options */
|
||||
if (setsockopt (s, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on))
|
||||
< 0) {
|
||||
close (s);
|
||||
return ERR_BACKEND_MISC;
|
||||
}
|
||||
|
||||
/* Bind to the address */
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_port = port;
|
||||
sin.sin_addr.s_addr = INADDR_ANY;
|
||||
if (bind (s, &sin, sizeof (sin)) < 0) {
|
||||
close (s);
|
||||
return ERR_RPC_CANT_BIND;
|
||||
}
|
||||
|
||||
/* Build RPCSock */
|
||||
new = g_malloc (sizeof (*new));
|
||||
if (new == NULL) {
|
||||
close (s);
|
||||
return ERR_BACKEND_ALLOC;
|
||||
}
|
||||
|
||||
memset (new, 0, sizeof (*new));
|
||||
new->sock = s;
|
||||
new->listener = TRUE;
|
||||
*sock = new;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RpcListen (RPCSock *sock, int val)
|
||||
{
|
||||
if (!sock)
|
||||
return -1;
|
||||
|
||||
if (!sock->listener)
|
||||
return -2;
|
||||
|
||||
return (listen (sock->sock, val));
|
||||
}
|
||||
|
||||
int RpcAccept (RPCSock *master, RPCSock **client)
|
||||
{
|
||||
int s;
|
||||
socklen_t len;
|
||||
RPCSock *new;
|
||||
|
||||
if (!master || !client)
|
||||
return ERR_BACKEND_MISC;
|
||||
|
||||
if (!master->listener)
|
||||
return ERR_BACKEND_MISC;
|
||||
|
||||
new = g_malloc (sizeof (*new));
|
||||
if (!new)
|
||||
return ERR_BACKEND_ALLOC;
|
||||
|
||||
memset (new, 0, sizeof (*new));
|
||||
len = sizeof (new->peer);
|
||||
if ((new->sock = accept (master->sock, &(new->peer), &len)) < 0) {
|
||||
g_free (new);
|
||||
return ERR_RPC_CANT_ACCEPT;
|
||||
}
|
||||
|
||||
*client = new;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void * RpcGetData (RPCSock *sock)
|
||||
{
|
||||
if (!sock)
|
||||
return NULL;
|
||||
|
||||
return (sock->cb_arg);
|
||||
}
|
34
src/engine/rpc/RpcSock.h
Normal file
34
src/engine/rpc/RpcSock.h
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* FILE:
|
||||
* RpcSock.h
|
||||
*
|
||||
* FUNCTION:
|
||||
* Implements the RPC Socket connection
|
||||
*
|
||||
* HISTORY:
|
||||
* Created By: Derek Atkins <warlord@MIT.EDU>
|
||||
* Copyright (c) 2001, Derek Atkins
|
||||
*/
|
||||
|
||||
#ifndef __RPC_SOCK_H
|
||||
#define __RPC_SOCK_H
|
||||
|
||||
#include <rpc/xprt_thrd.h>
|
||||
#include "RpcBackend.h"
|
||||
|
||||
#define RPCEND_PORT 11207
|
||||
|
||||
typedef struct _rpcend_sock RPCSock;
|
||||
|
||||
int RpcConnect (char *hostname, unsigned short port, RPCSock **sock);
|
||||
int RpcClose (RPCSock *sock);
|
||||
|
||||
int RpcCreateListener (unsigned short port, RPCSock **sock);
|
||||
int RpcListen (RPCSock *sock, int val);
|
||||
int RpcAccept (RPCSock *master, RPCSock **client);
|
||||
|
||||
int RpcTransport (RPCSock *sock, void (*myClose)(void *arg), void *cb_arg,
|
||||
TXPRT **xprt);
|
||||
void * RpcGetData (RPCSock *sock);
|
||||
|
||||
#endif /* __RPC_SOCK_H */
|
1270
src/engine/rpc/RpcUtils.c
Normal file
1270
src/engine/rpc/RpcUtils.c
Normal file
File diff suppressed because it is too large
Load Diff
71
src/engine/rpc/RpcUtils.h
Normal file
71
src/engine/rpc/RpcUtils.h
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* FILE:
|
||||
* RpcUtils.h
|
||||
*
|
||||
* FUNCTION:
|
||||
* Implements some utility functions for the RPC (client) backend.
|
||||
*
|
||||
* HISTORY:
|
||||
* Created By: Derek Atkins <warlord@MIT.EDU>
|
||||
* Copyright (c) 2001, Derek Atkins
|
||||
*/
|
||||
|
||||
#ifndef __RPC_UTILS_H
|
||||
#define __RPC_UTILS_H
|
||||
|
||||
#include "gncRpc.h"
|
||||
#include "Account.h"
|
||||
#include "RpcBackend.h"
|
||||
|
||||
/*
|
||||
* This number should be increased every time the RPC protocol
|
||||
* changes. This means every time any of the .x file change, this
|
||||
* number should be increased.
|
||||
*/
|
||||
#define GNCRPC_PROTOCOL_VERSION 1
|
||||
|
||||
gnc_kvp_frame *rpcend_build_gnckvp (kvp_frame *frame);
|
||||
void rpcend_free_gnckvp (gnc_kvp_frame *frame);
|
||||
kvp_frame * rpcend_parse_gnckvp (gnc_kvp_frame *data);
|
||||
|
||||
void rpcend_build_gnctxn (gncTransaction *gnctxn, Transaction *txn);
|
||||
void rpcend_free_gnctxn (gncTransaction *gnctxn, gboolean freetxn);
|
||||
|
||||
void rpcend_build_gncacct (gncAccount *gncacct, Account *acc);
|
||||
void rpcend_free_gncacct (gncAccount *acc, gboolean freeacct);
|
||||
|
||||
gnc_txnlist * rpcend_build_gnctxnlist_list (AccountGroup *ag,
|
||||
gnc_vers_list *txnlist);
|
||||
void rpcend_free_gnctxnlist (gnc_txnlist *txnlist);
|
||||
|
||||
gnc_acctlist * rpcend_build_gncacctlist_list (AccountGroup *ag,
|
||||
gnc_vers_list *acctlist);
|
||||
gnc_acctlist * rpcend_build_gncacctlist (AccountGroup *ag);
|
||||
void rpcend_free_gncacctlist (gnc_acctlist *acctlist);
|
||||
|
||||
gnc_vers_list * rpcend_build_gncacct_verslist (AccountGroup *ag,
|
||||
gboolean copyguid);
|
||||
gnc_vers_list * rpcend_build_gnctxn_verslist (AccountGroup *ag,
|
||||
gboolean copyguid);
|
||||
gnc_vers_list * rpcend_build_gncverslist_txn (GList *txnlist,
|
||||
gboolean copyguid);
|
||||
void rpcend_free_verslist (gnc_vers_list *vlist, gboolean freeguid);
|
||||
|
||||
gnc_commoditylist * rpcend_build_gnccommoditylist (gnc_commodity_table *ct,
|
||||
gboolean copycom);
|
||||
void rpcend_free_gnccommoditylist (gnc_commoditylist *clist, gboolean freecom);
|
||||
|
||||
void rpcend_load_gnccommodity (gnc_commodity_table *ct, gncCommodity *com);
|
||||
void rpcend_load_commoditylist (gnc_commodity_table *ct,
|
||||
gnc_commoditylist *clist);
|
||||
|
||||
int rpcend_do_add_acct (AccountGroup *ag, gncAccount * acct,
|
||||
gnc_commodity_table *ct);
|
||||
int rpcend_do_add_txn (gncTransaction * txn, gnc_commodity_table *ct);
|
||||
|
||||
void rpcend_build_gncquery (gncQuery *gq, Query *q);
|
||||
void rpcend_parse_gncquery (gncQuery *gq, Query *q);
|
||||
void rpcend_free_gncquery (gncQuery *gq);
|
||||
void rpcend_free_query (Query *q);
|
||||
|
||||
#endif /* __RPC_UTILS_H */
|
418
src/engine/rpc/clnt_thrd.c
Normal file
418
src/engine/rpc/clnt_thrd.c
Normal file
@ -0,0 +1,418 @@
|
||||
/* @(#)clnt_tcp.c 2.2 88/08/01 4.0 RPCSRC */
|
||||
/*
|
||||
* Sun RPC is a product of Sun Microsystems, Inc. and is provided for
|
||||
* unrestricted use provided that this legend is included on all tape
|
||||
* media and as a part of the software program in whole or part. Users
|
||||
* may copy or modify Sun RPC without charge, but are not authorized
|
||||
* to license or distribute it to anyone else except as part of a product or
|
||||
* program developed by the user.
|
||||
*
|
||||
* SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
|
||||
* WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
|
||||
*
|
||||
* Sun RPC is provided with no support and without any obligation on the
|
||||
* part of Sun Microsystems, Inc. to assist in its use, correction,
|
||||
* modification or enhancement.
|
||||
*
|
||||
* SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
|
||||
* INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC
|
||||
* OR ANY PART THEREOF.
|
||||
*
|
||||
* In no event will Sun Microsystems, Inc. be liable for any lost revenue
|
||||
* or profits or other special, indirect and consequential damages, even if
|
||||
* Sun has been advised of the possibility of such damages.
|
||||
*
|
||||
* Sun Microsystems, Inc.
|
||||
* 2550 Garcia Avenue
|
||||
* Mountain View, California 94043
|
||||
*/
|
||||
#if !defined(lint) && defined(SCCSIDS)
|
||||
static char sccsid[] = "@(#)clnt_tcp.c 1.37 87/10/05 Copyr 1984 Sun Micro";
|
||||
#endif
|
||||
|
||||
/*
|
||||
* clnt_thrd.c, Implements a Threaded, TCP/IP based, client side RPC.
|
||||
*
|
||||
* Copyright (C) 1984, Sun Microsystems, Inc.
|
||||
*
|
||||
* TCP based RPC supports 'batched calls'.
|
||||
* A sequence of calls may be batched-up in a send buffer. The rpc call
|
||||
* return immediately to the client even though the call was not necessarily
|
||||
* sent. The batching occurs if the results' xdr routine is NULL (0) AND
|
||||
* the rpc timeout value is zero (see clnt.h, rpc).
|
||||
*
|
||||
* Clients should NOT casually batch calls that in fact return results; that is,
|
||||
* the server side should be aware that a call is batched and not produce any
|
||||
* return message. Batched calls that produce many result messages can
|
||||
* deadlock (netlock) the client and the server....
|
||||
*
|
||||
* Now go hang yourself.
|
||||
*/
|
||||
|
||||
#include <netdb.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <rpc/rpc.h>
|
||||
#include <sys/poll.h>
|
||||
#include <sys/socket.h>
|
||||
#include <rpc/pmap_clnt.h>
|
||||
|
||||
#include "xprt_thrd.h"
|
||||
#include "clnt_thrd.h"
|
||||
|
||||
#define MCALL_MSG_SIZE 24
|
||||
|
||||
struct ct_data
|
||||
{
|
||||
bool_t ct_waitset; /* wait set by clnt_control? */
|
||||
struct timeval ct_wait;
|
||||
struct rpc_err ct_error;
|
||||
char ct_mcall[MCALL_MSG_SIZE]; /* marshalled callmsg header */
|
||||
u_int ct_mpos; /* length of marshalled header */
|
||||
u_long ct_prog; /* Program number */
|
||||
u_long ct_vers; /* Version Number */
|
||||
TXPRT_WAIT *ct_repl; /* A reply wait object */
|
||||
TXPRT *ct_xprt; /* Transport Object */
|
||||
};
|
||||
|
||||
static enum clnt_stat clntthrd_call (CLIENT *, u_long, xdrproc_t, caddr_t,
|
||||
xdrproc_t, caddr_t, struct timeval);
|
||||
static void clntthrd_abort (void);
|
||||
static void clntthrd_geterr (CLIENT *, struct rpc_err *);
|
||||
static bool_t clntthrd_freeres (CLIENT *, xdrproc_t, caddr_t);
|
||||
static bool_t clntthrd_control (CLIENT *, int, char *);
|
||||
static void clntthrd_destroy (CLIENT *);
|
||||
|
||||
static struct clnt_ops thrd_ops =
|
||||
{
|
||||
clntthrd_call,
|
||||
clntthrd_abort,
|
||||
clntthrd_geterr,
|
||||
clntthrd_freeres,
|
||||
clntthrd_destroy,
|
||||
clntthrd_control
|
||||
};
|
||||
|
||||
/*
|
||||
* Create a client handle for a threaded tcp/ip connection.
|
||||
* transport is the Threaded Transport we wish to attach this client to.
|
||||
* NB: It is the client's responsibility to open and close the transport
|
||||
* NB: The rpch->cl_auth is set null authentication. Caller may wish to set
|
||||
* this to something more useful.
|
||||
*/
|
||||
CLIENT *
|
||||
clntthrd_create (TXPRT *transport, u_long prog, u_long vers)
|
||||
{
|
||||
CLIENT *h = NULL;
|
||||
struct ct_data *ct = NULL;
|
||||
struct rpc_msg call_msg;
|
||||
struct timeval now;
|
||||
XDR xdr;
|
||||
|
||||
if (transport == NULL)
|
||||
return (CLIENT *)NULL;
|
||||
|
||||
h = (CLIENT *) malloc (sizeof (*h));
|
||||
if (h == NULL)
|
||||
{
|
||||
(void) fprintf (stderr, "clntthrd_create: out of memory\n");
|
||||
goto fooy;
|
||||
}
|
||||
ct = (struct ct_data *) malloc (sizeof (*ct));
|
||||
if (ct == NULL)
|
||||
{
|
||||
(void) fprintf (stderr, "clntthrd_create: out of memory\n");
|
||||
goto fooy;
|
||||
}
|
||||
|
||||
memset (ct, 0, sizeof (*ct));
|
||||
memset (h, 0, sizeof (*h));
|
||||
|
||||
/*
|
||||
* Set up private data structure
|
||||
*/
|
||||
ct->ct_repl = xprt_thrd_new_wait ();
|
||||
if (ct->ct_repl == NULL)
|
||||
{
|
||||
(void) fprintf (stderr, "clntthrd_create: out of memory\n");
|
||||
goto fooy;
|
||||
}
|
||||
|
||||
ct->ct_wait.tv_usec = 0;
|
||||
ct->ct_waitset = FALSE;
|
||||
ct->ct_xprt = transport;
|
||||
|
||||
/*
|
||||
* Initialize call message
|
||||
*/
|
||||
gettimeofday (&now, NULL);
|
||||
srand48 (now.tv_sec ^ now.tv_usec);
|
||||
call_msg.rm_xid = lrand48();
|
||||
call_msg.rm_direction = CALL;
|
||||
call_msg.rm_call.cb_rpcvers = RPC_MSG_VERSION;
|
||||
ct->ct_prog = call_msg.rm_call.cb_prog = prog;
|
||||
ct->ct_vers = call_msg.rm_call.cb_vers = vers;
|
||||
|
||||
/*
|
||||
* pre-serialize the static part of the call msg and stash it away
|
||||
*/
|
||||
xdrmem_create (&xdr, ct->ct_mcall, MCALL_MSG_SIZE, XDR_ENCODE);
|
||||
if (!xdr_callhdr (&xdr, &call_msg))
|
||||
{
|
||||
goto fooy;
|
||||
}
|
||||
ct->ct_mpos = XDR_GETPOS (&xdr);
|
||||
XDR_DESTROY (&xdr);
|
||||
|
||||
/*
|
||||
* Build the CLIENT structure
|
||||
*/
|
||||
h->cl_auth = authnone_create ();
|
||||
h->cl_ops = &thrd_ops;
|
||||
h->cl_private = (caddr_t) ct;
|
||||
return h;
|
||||
|
||||
fooy:
|
||||
/*
|
||||
* Something goofed, free stuff and barf
|
||||
*/
|
||||
if (ct != NULL) {
|
||||
if (ct->ct_repl != NULL)
|
||||
xprt_destroy_wait (ct->ct_repl);
|
||||
free ((caddr_t) ct);
|
||||
}
|
||||
if (h != NULL) free ((caddr_t) h);
|
||||
return ((CLIENT *) NULL);
|
||||
}
|
||||
|
||||
static enum clnt_stat
|
||||
clntthrd_call (h, proc, xdr_args, args_ptr, xdr_results, results_ptr, timeout)
|
||||
CLIENT *h;
|
||||
u_long proc;
|
||||
xdrproc_t xdr_args;
|
||||
caddr_t args_ptr;
|
||||
xdrproc_t xdr_results;
|
||||
caddr_t results_ptr;
|
||||
struct timeval timeout;
|
||||
{
|
||||
struct ct_data *ct = (struct ct_data *) h->cl_private;
|
||||
XDR *xdrs;
|
||||
struct rpc_msg reply_msg;
|
||||
u_long x_id;
|
||||
u_int32_t *msg_x_id = (u_int32_t *) (ct->ct_mcall); /* yuk */
|
||||
bool_t shipnow;
|
||||
int refreshes = 2;
|
||||
|
||||
if (!ct->ct_waitset)
|
||||
{
|
||||
ct->ct_wait = timeout;
|
||||
}
|
||||
|
||||
shipnow =
|
||||
(xdr_results == (xdrproc_t) 0 && timeout.tv_sec == 0
|
||||
&& timeout.tv_usec == 0) ? FALSE : TRUE;
|
||||
|
||||
/* Obtain and lock XDR object */
|
||||
xdrs = TXPRT_GET_XDR (ct->ct_xprt);
|
||||
|
||||
call_again:
|
||||
xdrs->x_op = XDR_ENCODE;
|
||||
ct->ct_error.re_status = RPC_SUCCESS;
|
||||
x_id = ntohl (--(*msg_x_id));
|
||||
if ((!XDR_PUTBYTES (xdrs, ct->ct_mcall, ct->ct_mpos)) ||
|
||||
(!XDR_PUTLONG (xdrs, (long *) &proc)) ||
|
||||
(!AUTH_MARSHALL (h->cl_auth, xdrs)) ||
|
||||
(!(*xdr_args) (xdrs, args_ptr)))
|
||||
{
|
||||
if (ct->ct_error.re_status == RPC_SUCCESS)
|
||||
ct->ct_error.re_status = RPC_CANTENCODEARGS;
|
||||
(void) xdrrec_endofrecord (xdrs, TRUE);
|
||||
TXPRT_REL_XDR (ct->ct_xprt);
|
||||
return (ct->ct_error.re_status);
|
||||
}
|
||||
if (!xdrrec_endofrecord (xdrs, shipnow)) {
|
||||
TXPRT_REL_XDR (ct->ct_xprt);
|
||||
return ct->ct_error.re_status = RPC_CANTSEND;
|
||||
}
|
||||
if (!shipnow) {
|
||||
TXPRT_REL_XDR (ct->ct_xprt);
|
||||
return RPC_SUCCESS;
|
||||
}
|
||||
/*
|
||||
* Hack to provide rpc-based message passing
|
||||
*/
|
||||
if (timeout.tv_sec == 0 && timeout.tv_usec == 0)
|
||||
{
|
||||
TXPRT_REL_XDR (ct->ct_xprt);
|
||||
return ct->ct_error.re_status = RPC_TIMEDOUT;
|
||||
}
|
||||
|
||||
/* Setup reply-state for Transport callback */
|
||||
ct->ct_repl->tw_x_id = x_id;
|
||||
reply_msg.acpted_rply.ar_verf = _null_auth;
|
||||
reply_msg.acpted_rply.ar_results.where = NULL;
|
||||
reply_msg.acpted_rply.ar_results.proc = (xdrproc_t)xdr_void;
|
||||
|
||||
/* Now register the callback and wait for our reply message */
|
||||
TXPRT_WAIT_REPLY (ct->ct_xprt, &reply_msg, &xdrs, ct->ct_repl, timeout);
|
||||
|
||||
/*
|
||||
* Ok, we're back. That means that our reply has come in. We expect
|
||||
* that reply_msg and xdrs have been filled in for us, and the XDR
|
||||
* Object is locked. That means we don't have to look for the response
|
||||
* on our own, nor lock XDR on our own, so....
|
||||
*/
|
||||
|
||||
/*
|
||||
* process header
|
||||
*/
|
||||
_seterr_reply (&reply_msg, &(ct->ct_error));
|
||||
if (ct->ct_error.re_status == RPC_SUCCESS)
|
||||
{
|
||||
if (!AUTH_VALIDATE (h->cl_auth, &reply_msg.acpted_rply.ar_verf))
|
||||
{
|
||||
ct->ct_error.re_status = RPC_AUTHERROR;
|
||||
ct->ct_error.re_why = AUTH_INVALIDRESP;
|
||||
}
|
||||
else if (!(*xdr_results) (xdrs, results_ptr))
|
||||
{
|
||||
if (ct->ct_error.re_status == RPC_SUCCESS)
|
||||
ct->ct_error.re_status = RPC_CANTDECODERES;
|
||||
}
|
||||
/* free verifier ... */
|
||||
if (reply_msg.acpted_rply.ar_verf.oa_base != NULL)
|
||||
{
|
||||
xdrs->x_op = XDR_FREE;
|
||||
(void) xdr_opaque_auth (xdrs, &(reply_msg.acpted_rply.ar_verf));
|
||||
}
|
||||
} /* end successful completion */
|
||||
else
|
||||
{
|
||||
/* maybe our credentials need to be refreshed ... */
|
||||
if (refreshes-- && AUTH_REFRESH (h->cl_auth))
|
||||
/* We still have the XDR lock */
|
||||
goto call_again;
|
||||
} /* end of unsuccessful completion */
|
||||
TXPRT_REL_XDR (ct->ct_xprt);
|
||||
return ct->ct_error.re_status;
|
||||
}
|
||||
|
||||
static void
|
||||
clntthrd_geterr (h, errp)
|
||||
CLIENT *h;
|
||||
struct rpc_err *errp;
|
||||
{
|
||||
struct ct_data *ct =
|
||||
(struct ct_data *) h->cl_private;
|
||||
|
||||
*errp = ct->ct_error;
|
||||
}
|
||||
|
||||
static bool_t
|
||||
clntthrd_freeres (cl, xdr_res, res_ptr)
|
||||
CLIENT *cl;
|
||||
xdrproc_t xdr_res;
|
||||
caddr_t res_ptr;
|
||||
{
|
||||
bool_t res;
|
||||
struct ct_data *ct = (struct ct_data *) cl->cl_private;
|
||||
XDR *xdrs = TXPRT_GET_XDR (ct->ct_xprt);
|
||||
xdrs->x_op = XDR_FREE;
|
||||
res = (*xdr_res) (xdrs, res_ptr);
|
||||
TXPRT_REL_XDR (ct->ct_xprt);
|
||||
return res;
|
||||
}
|
||||
|
||||
static void
|
||||
clntthrd_abort ()
|
||||
{
|
||||
}
|
||||
|
||||
static bool_t
|
||||
clntthrd_control (CLIENT *cl, int request, char *info)
|
||||
{
|
||||
struct ct_data *ct = (struct ct_data *) cl->cl_private;
|
||||
|
||||
|
||||
switch (request)
|
||||
{
|
||||
case CLSET_TIMEOUT:
|
||||
ct->ct_wait = *(struct timeval *) info;
|
||||
ct->ct_waitset = TRUE;
|
||||
break;
|
||||
case CLGET_TIMEOUT:
|
||||
*(struct timeval *) info = ct->ct_wait;
|
||||
break;
|
||||
case CLGET_XID:
|
||||
/*
|
||||
* use the knowledge that xid is the
|
||||
* first element in the call structure *.
|
||||
* This will get the xid of the PREVIOUS call
|
||||
*/
|
||||
*(u_long *)info = ntohl (*(u_long *)ct->ct_mcall);
|
||||
break;
|
||||
case CLSET_XID:
|
||||
/* This will set the xid of the NEXT call */
|
||||
*(u_long *)ct->ct_mcall = htonl (*(u_long *)info - 1);
|
||||
/* decrement by 1 as clntthrd_call() increments once */
|
||||
case CLGET_VERS:
|
||||
/*
|
||||
* This RELIES on the information that, in the call body,
|
||||
* the version number field is the fifth field from the
|
||||
* begining of the RPC header. MUST be changed if the
|
||||
* call_struct is changed
|
||||
*/
|
||||
*(u_long *)info = ntohl (*(u_long *)(ct->ct_mcall +
|
||||
4 * BYTES_PER_XDR_UNIT));
|
||||
break;
|
||||
case CLSET_VERS:
|
||||
*(u_long *)(ct->ct_mcall + 4 * BYTES_PER_XDR_UNIT)
|
||||
= htonl (*(u_long *)info);
|
||||
break;
|
||||
case CLGET_PROG:
|
||||
/*
|
||||
* This RELIES on the information that, in the call body,
|
||||
* the program number field is the field from the
|
||||
* begining of the RPC header. MUST be changed if the
|
||||
* call_struct is changed
|
||||
*/
|
||||
*(u_long *)info = ntohl(*(u_long *)(ct->ct_mcall +
|
||||
3 * BYTES_PER_XDR_UNIT));
|
||||
break;
|
||||
case CLSET_PROG:
|
||||
*(u_long *)(ct->ct_mcall + 3 * BYTES_PER_XDR_UNIT)
|
||||
= htonl(*(u_long *)info);
|
||||
break;
|
||||
|
||||
/* We don't support these, yet */
|
||||
case CLSET_FD_CLOSE:
|
||||
case CLSET_FD_NCLOSE:
|
||||
case CLGET_SERVER_ADDR:
|
||||
case CLGET_FD:
|
||||
return FALSE;
|
||||
|
||||
/* The following are only possible with TI-RPC */
|
||||
case CLGET_RETRY_TIMEOUT:
|
||||
case CLSET_RETRY_TIMEOUT:
|
||||
case CLGET_SVC_ADDR:
|
||||
case CLSET_SVC_ADDR:
|
||||
case CLSET_PUSH_TIMOD:
|
||||
case CLSET_POP_TIMOD:
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
clntthrd_destroy (CLIENT *h)
|
||||
{
|
||||
struct ct_data *ct = (struct ct_data *) h->cl_private;
|
||||
|
||||
xprt_destroy_wait (ct->ct_repl);
|
||||
free ((caddr_t) ct);
|
||||
free ((caddr_t) h);
|
||||
}
|
16
src/engine/rpc/clnt_thrd.h
Normal file
16
src/engine/rpc/clnt_thrd.h
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* clnt_thrd.h -- Threaded, Multiplexed, RPC/TCP Client
|
||||
*
|
||||
* Written By: Derek Atkins <warlord@MIT.EDU>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef RPC_CLNT_THRD_H
|
||||
#define RPC_CLNT_THRD_H
|
||||
|
||||
#include <rpc/clnt.h>
|
||||
#include "xprt_thrd.h"
|
||||
|
||||
CLIENT *clntthrd_create (TXPRT *transport, u_long prog, u_long vers);
|
||||
|
||||
#endif /* RPC_CLNT_THRD_H */
|
62
src/engine/rpc/gncAccount.x
Normal file
62
src/engine/rpc/gncAccount.x
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* FILE:
|
||||
* gncAccount.x
|
||||
*
|
||||
* FUNCTION:
|
||||
* The RPC definition for a Gnucash Account
|
||||
*
|
||||
* HISTORY:
|
||||
* Created By: Derek Atkins <warlord@MIT.EDU>
|
||||
* Copyright (c) 2001, Derek Atkins
|
||||
*/
|
||||
|
||||
#ifndef __GNC_ACCOUNT_X
|
||||
#define __GNC_ACCOUNT_X
|
||||
|
||||
#include "gncKVP.x"
|
||||
#include "gncGUID.x"
|
||||
#include "gncCommodity.x"
|
||||
|
||||
#ifdef RPC_HDR
|
||||
%#include "Account.h"
|
||||
#endif
|
||||
|
||||
struct gncAccount {
|
||||
gncGUID guid;
|
||||
string name<>;
|
||||
string code<>;
|
||||
string desc<>;
|
||||
gnc_kvp_frame * kvp_data;
|
||||
enum_t type; /* GNCAccountType */
|
||||
|
||||
/* This really sucks -- why do I need to send both a currency and a
|
||||
* security? Well, the engine seems to want it that way. Ouch.
|
||||
*/
|
||||
|
||||
#ifdef GNCACCT_COMMODITY
|
||||
gncCommodityPtr commodity;
|
||||
#else
|
||||
gncCommodityPtr currency;
|
||||
gncCommodityPtr security;
|
||||
#endif
|
||||
|
||||
gncGUID * parent;
|
||||
int vers;
|
||||
|
||||
gncNumeric balance;
|
||||
gncNumeric cleared_balance;
|
||||
gncNumeric reconciled_balance;
|
||||
gncNumeric share_balance;
|
||||
gncNumeric share_cleared_balance;
|
||||
gncNumeric share_reconciled_balance;
|
||||
|
||||
bool core_dirty;
|
||||
bool do_free;
|
||||
};
|
||||
|
||||
struct gnc_acctlist {
|
||||
gncAccount * acct;
|
||||
gnc_acctlist * next;
|
||||
};
|
||||
|
||||
#endif /* __GNC_ACCOUNT_X */
|
43
src/engine/rpc/gncCommodity.x
Normal file
43
src/engine/rpc/gncCommodity.x
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* FILE:
|
||||
* gncCommodity.x
|
||||
*
|
||||
* FUNCTION:
|
||||
* The RPC definition for a Gnucash Commodity
|
||||
*
|
||||
* HISTORY:
|
||||
* Created By: Derek Atkins <warlord@MIT.EDU>
|
||||
* Copyright (c) 2001, Derek Atkins
|
||||
*/
|
||||
|
||||
#ifndef __GNC_COMMODITY_X
|
||||
#define __GNC_COMMODITY_X
|
||||
|
||||
/* This is an actual commodity entry; unique_name is generated */
|
||||
struct gncCommodity {
|
||||
string fullname<>;
|
||||
string namespace<>;
|
||||
string mnemonic<>;
|
||||
string printname<>;
|
||||
string exchange_code<>;
|
||||
int fraction;
|
||||
};
|
||||
|
||||
/* List of commodities */
|
||||
struct gnc_commoditylist {
|
||||
gncCommodity * commodity;
|
||||
gnc_commoditylist * next;
|
||||
};
|
||||
|
||||
/* This is sufficient information to find a commodity in the commodity
|
||||
* table using gnc_commodity_table_lookup(), although while it does
|
||||
* save on network transfer space, it makes it harder on the sender
|
||||
* to actually package up an account. So let's keep it here but not
|
||||
* use it.
|
||||
*/
|
||||
struct gncCommodityPtr {
|
||||
string namespace<>;
|
||||
string mnemonic<>;
|
||||
};
|
||||
|
||||
#endif /* __GNC_COMMODITY_X */
|
34
src/engine/rpc/gncGUID.x
Normal file
34
src/engine/rpc/gncGUID.x
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* FILE:
|
||||
* gncQuery.x
|
||||
*
|
||||
* FUNCTION:
|
||||
* The RPC definition for a Gnucash Query
|
||||
*
|
||||
* HISTORY:
|
||||
* Created By: Derek Atkins <warlord@MIT.EDU>
|
||||
* Copyright (c) 2001, Derek Atkins
|
||||
*/
|
||||
|
||||
#ifndef __GNC_GUID_X
|
||||
#define __GNC_GUID_X
|
||||
|
||||
typedef opaque gncGUID[16];
|
||||
|
||||
struct gnc_guidlist {
|
||||
gncGUID * guid;
|
||||
gnc_guidlist * next;
|
||||
};
|
||||
|
||||
struct gnc_vers_list {
|
||||
gncGUID * guid;
|
||||
int vers;
|
||||
gnc_vers_list * next;
|
||||
};
|
||||
|
||||
struct gncTimespec {
|
||||
int64_t tv_sec;
|
||||
int tv_nsec;
|
||||
};
|
||||
|
||||
#endif /* __GNC_GUID_X */
|
61
src/engine/rpc/gncKVP.x
Normal file
61
src/engine/rpc/gncKVP.x
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* FILE:
|
||||
* gncKVP.x
|
||||
*
|
||||
* FUNCTION:
|
||||
* The RPC definition for a Gnucash kvp_frame
|
||||
*
|
||||
* HISTORY:
|
||||
* Created By: Derek Atkins <warlord@MIT.EDU>
|
||||
* Copyright (c) 2001, Derek Atkins
|
||||
*/
|
||||
|
||||
#ifndef __GNC_KVP_X
|
||||
#define __GNC_KVP_X
|
||||
|
||||
#include "gncGUID.x"
|
||||
|
||||
#ifdef RPC_HDR
|
||||
%#include "kvp_frame.h"
|
||||
#endif
|
||||
|
||||
struct gncNumeric {
|
||||
int64_t num;
|
||||
int64_t denom;
|
||||
};
|
||||
|
||||
struct gnc_kvp_valuelist {
|
||||
struct gnc_kvp_value * val;
|
||||
gnc_kvp_valuelist * next;
|
||||
};
|
||||
|
||||
union gnc_kvp_value switch (enum_t type) { /* kvp_value_t */
|
||||
case KVP_TYPE_GINT64:
|
||||
int64_t int64;
|
||||
case KVP_TYPE_DOUBLE:
|
||||
double dbl;
|
||||
case KVP_TYPE_NUMERIC:
|
||||
gncNumeric numeric;
|
||||
case KVP_TYPE_STRING:
|
||||
string str<>;
|
||||
case KVP_TYPE_GUID:
|
||||
gncGUID guid;
|
||||
case KVP_TYPE_BINARY:
|
||||
opaque binary<>;
|
||||
case KVP_TYPE_GLIST:
|
||||
gnc_kvp_valuelist * glist;
|
||||
case KVP_TYPE_FRAME:
|
||||
struct gnc_kvp_frame * frame;
|
||||
};
|
||||
|
||||
struct gnc_kvp {
|
||||
string key<>;
|
||||
gnc_kvp_value * value;
|
||||
};
|
||||
|
||||
struct gnc_kvp_frame {
|
||||
gnc_kvp * data;
|
||||
gnc_kvp_frame * next;
|
||||
};
|
||||
|
||||
#endif /* __GNC_KVP_X */
|
18
src/engine/rpc/gncPrice.x
Normal file
18
src/engine/rpc/gncPrice.x
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* FILE:
|
||||
* gncPrice.x
|
||||
*
|
||||
* FUNCTION:
|
||||
* The RPC protocol definition for Gnucash PriceDB
|
||||
*
|
||||
* HISTORY:
|
||||
* Created By: Derek Atkins <warlord@MIT.EDU>
|
||||
* Copyright (c) 2001, Derek Atkins
|
||||
*/
|
||||
|
||||
#ifndef __GNC_PRICE_X
|
||||
#define __GNC_PRICE_X
|
||||
|
||||
|
||||
|
||||
#endif /* __GNC_PRICE_X */
|
90
src/engine/rpc/gncQuery.corba
Normal file
90
src/engine/rpc/gncQuery.corba
Normal file
@ -0,0 +1,90 @@
|
||||
module Query {
|
||||
enum SortBy { STANDARD, DATE, DATEENTERED, DATERECONCILED, NUM,
|
||||
AMOUNT, MEMO, DESC, RECONCILE, NONE };
|
||||
enum QueryOp { AND, OR, NAND, NOR, XOR };
|
||||
enum pdtypet { DATE, AMOUNT, ACCOUNT, STRING, CLEARED, MISC };
|
||||
enum acctmatcht { ACCT_MATCH_ALL, ACCT_MATCH_ANY, ACCT_MATCH_NONE }
|
||||
enum amtmatcht { AMT_MATCH_ATLEAST, AMT_MATCH_ATMOST,
|
||||
AMT_MATCH_EXACTLY } ;
|
||||
|
||||
enum amtmatchsgnt { AMT_SGN_MATCH_EITHER, AMT_SGN_MATCH_CREDIT,
|
||||
AMT_SGN_MATCH_DEBIT } ;
|
||||
|
||||
enum clearing { CLEARED_NO, CLEARED_CLEARED, CLEARED_RECONCILED,
|
||||
CLEARED_FROZEN };
|
||||
|
||||
enum strmatching { STRING_MATCH_CASE, STRING_MATCH_REGEXP,
|
||||
STRING_MATCH_INSENSITIVE };
|
||||
|
||||
struct DatePredicateData {
|
||||
long usestart;
|
||||
Timespec start;
|
||||
long useend;
|
||||
Timespec end;
|
||||
}
|
||||
struct AmountPredicateData {
|
||||
amtmatcht how;
|
||||
amtmatchsgnt amt_sgn;
|
||||
double amount;
|
||||
};
|
||||
struct AccountPredicateData {
|
||||
acctmatcht how;
|
||||
Accounts::AccountList accounts;
|
||||
// Note that this references a type from
|
||||
// module "Accounts"
|
||||
}
|
||||
struct StringPredicateData {
|
||||
strmatching behavior; /* what kind of search? */
|
||||
string matchstring;
|
||||
// regex_t compiled; // Compilation isn't part of the interface
|
||||
} StringPredicateData;
|
||||
struct MiscPredicateData {
|
||||
long how; /* Shouldn't this use one of the enums? */
|
||||
long data; /* What is this? */
|
||||
};
|
||||
struct ClearedPredicateData {
|
||||
long how;
|
||||
};
|
||||
|
||||
union PredicateData switch (pdtypet) {
|
||||
case DATE:
|
||||
DatePredicateData date;
|
||||
case AMOUNT:
|
||||
AmountPredicateData amount;
|
||||
case ACCOUNT:
|
||||
AccountPredicateData acct;
|
||||
case STRING:
|
||||
StringPredicateData str;
|
||||
case CLEARED:
|
||||
ClearedPredicateData cleared;
|
||||
case MISC:
|
||||
MiscPredicateData misc;
|
||||
}
|
||||
|
||||
struct QueryTerm {
|
||||
Predicate p; /* What is this type supposed to be? */
|
||||
PredicateData data;
|
||||
long sense;
|
||||
};
|
||||
|
||||
/* Something is wrong with the following; I am somehow not correctly
|
||||
understanding what the Predicate type is... I'll punt and
|
||||
pretend that a Query consists of a binary tree of QueryTerm
|
||||
elements... */
|
||||
|
||||
struct Query {
|
||||
QueryTerm qt;
|
||||
QueryOp combinethus;
|
||||
Query subquery;
|
||||
};
|
||||
|
||||
// Locally, there would be the whole host of "Query Construction"
|
||||
// functions to build up a complex query.
|
||||
|
||||
void InvokeQuery (in query q,
|
||||
out Transactions::TransactionList txns);
|
||||
|
||||
/* Note that this references, from the "Transactions" module, the
|
||||
"TransactionList" typedef, which would reflect a sequence of
|
||||
transactions... */
|
||||
};
|
134
src/engine/rpc/gncQuery.x
Normal file
134
src/engine/rpc/gncQuery.x
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* FILE:
|
||||
* gncQuery.x
|
||||
*
|
||||
* FUNCTION:
|
||||
* The RPC definition for a Gnucash Query
|
||||
*
|
||||
* HISTORY:
|
||||
* Created By: Derek Atkins <warlord@MIT.EDU>
|
||||
* Copyright (c) 2001, Derek Atkins
|
||||
*/
|
||||
|
||||
#ifndef __GNC_QUERY_X
|
||||
#define __GNC_QUERY_X
|
||||
|
||||
#include "gncAccount.x"
|
||||
#include "gncGUID.x"
|
||||
|
||||
#ifdef RPC_HDR
|
||||
%#include "Query.h"
|
||||
#endif
|
||||
|
||||
struct gncDatePredicateData {
|
||||
enum_t term_type; /* pr_type_t */
|
||||
int sense;
|
||||
int usestart;
|
||||
gncTimespec start;
|
||||
int useend;
|
||||
gncTimespec end;
|
||||
};
|
||||
|
||||
struct gncAmountPredicateData {
|
||||
enum_t term_type; /* pr_type_t */
|
||||
int sense;
|
||||
enum_t how; /* amt_match_t */
|
||||
enum_t amt_sgn; /* amt_match_sgn_t */
|
||||
double amount;
|
||||
};
|
||||
|
||||
struct gncAccountPredicateData {
|
||||
enum_t term_type; /* pr_type_t */
|
||||
int sense;
|
||||
enum_t how; /* acct_match_t */
|
||||
/* Leave out Accts; we can recreate on the server side */
|
||||
gnc_guidlist * acct_guids;
|
||||
};
|
||||
|
||||
struct gncStringPredicateData {
|
||||
enum_t term_type; /* pr_type_t */
|
||||
int sense;
|
||||
int case_sens;
|
||||
int use_regexp;
|
||||
string matchstring<>;
|
||||
/* XXX: How do I transfer a regex_t? */
|
||||
};
|
||||
|
||||
struct gncClearedPredicateData {
|
||||
enum_t term_type; /* pr_type_t */
|
||||
int sense;
|
||||
enum_t how; /* cleared_match_t */
|
||||
};
|
||||
|
||||
struct gncBalancePredicateData {
|
||||
enum_t term_type; /* pr_type_t */
|
||||
int sense;
|
||||
enum_t how; /* balance_match_t */
|
||||
};
|
||||
|
||||
struct gncMiscPredicateData {
|
||||
enum_t term_type; /* pr_type_t */
|
||||
int sense;
|
||||
int how;
|
||||
int data;
|
||||
};
|
||||
|
||||
union gncPredicateData switch (enum_t type) { /* pd_type_t */
|
||||
case PD_DATE:
|
||||
gncDatePredicateData date;
|
||||
case PD_AMOUNT:
|
||||
gncAmountPredicateData amount;
|
||||
case PD_ACCOUNT:
|
||||
gncAccountPredicateData acct;
|
||||
case PD_STRING:
|
||||
gncStringPredicateData str;
|
||||
case PD_CLEARED:
|
||||
gncClearedPredicateData cleared;
|
||||
case PD_BALANCE:
|
||||
gncBalancePredicateData balance;
|
||||
case PD_MISC:
|
||||
gncMiscPredicateData misc;
|
||||
};
|
||||
|
||||
struct gncQueryTerm {
|
||||
gncPredicateData data;
|
||||
int * p; /* This 'predicate' is just for compatibility
|
||||
* with Query.h. We should really check if
|
||||
* it's non-NULL, because we cannot easily
|
||||
* transfer a function-pointer!
|
||||
*/
|
||||
};
|
||||
|
||||
/* A list of QueryTerms */
|
||||
struct gncQTList {
|
||||
gncQueryTerm * qt;
|
||||
gncQTList * next;
|
||||
};
|
||||
|
||||
/* A list of QueryTerm Lists */
|
||||
struct gncQTOrlist {
|
||||
gncQTList * andlist;
|
||||
gncQTOrlist * next;
|
||||
};
|
||||
|
||||
/* See Query.c for the description */
|
||||
struct gncQuery {
|
||||
gncQTOrlist * terms;
|
||||
enum_t primary_sort; /* sort_type_t */
|
||||
enum_t secondary_sort; /* sort_type_t */
|
||||
enum_t tertiary_sort; /* sort_type_t */
|
||||
bool primary_increasing;
|
||||
bool secondary_increasing;
|
||||
bool tertiary_increasing;
|
||||
int max_splits;
|
||||
|
||||
/* Results cache */
|
||||
int changed;
|
||||
enum_t last_run_type; /* query_run_t */
|
||||
int * acct_group; /* AccountGroup * */
|
||||
|
||||
int * split_list; /* GList * */
|
||||
int * xtn_list; /* GList * */
|
||||
};
|
||||
|
||||
#endif /* __GNC_QUERY_X */
|
140
src/engine/rpc/gncRpc.x
Normal file
140
src/engine/rpc/gncRpc.x
Normal file
@ -0,0 +1,140 @@
|
||||
/*
|
||||
* FILE:
|
||||
* gncRpc.x
|
||||
*
|
||||
* FUNCTION:
|
||||
* The RPC protocol definition for Gnucash
|
||||
*
|
||||
* HISTORY:
|
||||
* Created By: Derek Atkins <warlord@MIT.EDU>
|
||||
* Copyright (c) 2001, Derek Atkins
|
||||
*/
|
||||
|
||||
#ifdef RPC_XDR
|
||||
%#ifndef xdr_enum_t
|
||||
%#define xdr_enum_t xdr_enum
|
||||
%#endif
|
||||
#endif
|
||||
|
||||
#include "gncQuery.x"
|
||||
#include "gncAccount.x"
|
||||
#include "gncTxn.x"
|
||||
|
||||
typedef opaque gncrpc_ptr[8];
|
||||
|
||||
/***************************************************************/
|
||||
|
||||
struct gncrpc_book_begin_args {
|
||||
gncrpc_ptr book;
|
||||
gncrpc_ptr backend;
|
||||
string book_id<>;
|
||||
bool ignore_lock;
|
||||
bool create;
|
||||
};
|
||||
|
||||
struct gncrpc_book_load_ret {
|
||||
int error;
|
||||
gnc_commoditylist * commodities; /* List of commodities */
|
||||
gnc_acctlist * acctlist; /* Full Account tree */
|
||||
};
|
||||
|
||||
struct gncrpc_backend_guid {
|
||||
gncrpc_ptr backend;
|
||||
gncGUID guid;
|
||||
};
|
||||
|
||||
struct gncrpc_backend_txn {
|
||||
gncrpc_ptr backend;
|
||||
gncTransaction txn;
|
||||
};
|
||||
|
||||
struct gncrpc_commit_acct_args {
|
||||
gncrpc_ptr backend;
|
||||
gncAccount acct;
|
||||
#ifdef GNCACCT_COMMODITY
|
||||
gncCommodity * commodity;
|
||||
#else
|
||||
gncCommodity * currency;
|
||||
gncCommodity * security;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct gncrpc_commit_txn_args {
|
||||
gncrpc_ptr backend;
|
||||
gncTransaction new;
|
||||
};
|
||||
|
||||
struct gncrpc_sync1_args {
|
||||
gncrpc_ptr backend;
|
||||
gnc_commoditylist * commodities; /* all commodities */
|
||||
gnc_vers_list * acctlist; /* list of all account guid+versions */
|
||||
gnc_vers_list * txnlist; /* list of all txn guid+versions */
|
||||
};
|
||||
|
||||
struct gncrpc_sync1_ret {
|
||||
int error;
|
||||
gnc_vers_list * send_acctlist; /* accts to send to server */
|
||||
gnc_vers_list * send_txnlist; /* txns to send to server */
|
||||
gnc_acctlist * acctlist; /* new or changed accounts from server */
|
||||
gnc_txnlist * txnlist; /* new or changed txns from server */
|
||||
gnc_commoditylist * commodities; /* new commodities from server */
|
||||
};
|
||||
|
||||
struct gncrpc_sync2_args {
|
||||
gncrpc_ptr backend;
|
||||
gnc_acctlist * acctlist; /* new or changed accounts in client */
|
||||
gnc_txnlist * txnlist; /* new or changed txns in client */
|
||||
};
|
||||
|
||||
struct gncrpc_query_args {
|
||||
gncrpc_ptr backend;
|
||||
gncGUID * group_parent_guid;
|
||||
gncQuery * query;
|
||||
};
|
||||
|
||||
struct gncrpc_query_ret {
|
||||
int error;
|
||||
gnc_vers_list * txnlist;
|
||||
};
|
||||
|
||||
struct gncrpc_get_txns_args {
|
||||
gncrpc_ptr backend;
|
||||
gnc_vers_list * guids;
|
||||
};
|
||||
|
||||
struct gncrpc_get_txns_ret {
|
||||
int error;
|
||||
gnc_txnlist * txnlist;
|
||||
};
|
||||
|
||||
/***************************************************************/
|
||||
|
||||
program GNCRPC_PROG {
|
||||
version GNCRPC_VERS {
|
||||
/*
|
||||
* int gncrpc_version ()
|
||||
* send the min/max version and get back a suggested version number
|
||||
*/
|
||||
int gncrpc_version () = 0;
|
||||
|
||||
/* Backend functions */
|
||||
int gncrpc_book_begin (gncrpc_book_begin_args args) = 1;
|
||||
gncrpc_book_load_ret gncrpc_book_load (gncrpc_ptr backend) = 2;
|
||||
int gncrpc_book_end (gncrpc_ptr backend) = 3;
|
||||
|
||||
int gncrpc_account_begin_edit (gncrpc_backend_guid) = 4;
|
||||
int gncrpc_account_commit_edit (gncrpc_commit_acct_args) = 5;
|
||||
int gncrpc_account_rollback_edit (gncrpc_backend_guid) = 6;
|
||||
int gncrpc_txn_begin_edit (gncrpc_backend_guid) = 7;
|
||||
int gncrpc_txn_commit_edit (gncrpc_commit_txn_args) = 8;
|
||||
int gncrpc_txn_rollback_edit (gncrpc_backend_guid) = 9;
|
||||
|
||||
gncrpc_query_ret gncrpc_run_query (gncrpc_query_args) = 10;
|
||||
gncrpc_sync1_ret gncrpc_sync1 (gncrpc_sync1_args) = 11;
|
||||
int gncrpc_sync2 (gncrpc_sync2_args) = 12;
|
||||
|
||||
/* Helper functions */
|
||||
gncrpc_get_txns_ret gncrpc_get_txns (gncrpc_get_txns_args) = 13;
|
||||
|
||||
} = 1;
|
||||
} = 729284;
|
568
src/engine/rpc/gncRpc_server.c
Normal file
568
src/engine/rpc/gncRpc_server.c
Normal file
@ -0,0 +1,568 @@
|
||||
/*
|
||||
* FILE:
|
||||
* gncRpc_server.c
|
||||
*
|
||||
* FUNCTION:
|
||||
* Implements the Gnucash RPC server stubs, as generated by rpcgen.
|
||||
* Note: The function prototypes here must match those created by
|
||||
* rpcgen -- if any function prototypes in gncRpc.x are changed, this
|
||||
* file must change as well.
|
||||
*
|
||||
* HISTORY:
|
||||
* Created By: Derek Atkins <warlord@MIT.EDU>
|
||||
* Copyright (c) 2001, Derek Atkins
|
||||
*/
|
||||
|
||||
#include "gncRpc.h"
|
||||
#include "RpcServerP.h"
|
||||
#include "RpcUtils.h"
|
||||
#include "TransactionP.h"
|
||||
#include "AccountP.h"
|
||||
#include "gnc-engine-util.h"
|
||||
|
||||
static short module = MOD_BACKEND;
|
||||
|
||||
static bool_t gncrpc_get_state (struct svc_req *req, TXPRT **xprt,
|
||||
GncRpcSvc **cl)
|
||||
{
|
||||
TXPRT *x;
|
||||
bool_t retval;
|
||||
|
||||
retval = svc_getargs (req->rq_xprt, (xdrproc_t) xprt_thrd_getargs_hook,
|
||||
(caddr_t) &x);
|
||||
if (!retval)
|
||||
return !retval;
|
||||
|
||||
if (xprt)
|
||||
*xprt = x;
|
||||
if (cl) {
|
||||
RPCSock *s = (RPCSock *) TXPRT_GETSOCK (x);
|
||||
*cl = (GncRpcSvc *) RpcGetData (s);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_version_1_svc(void *argp, int *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval = TRUE;
|
||||
|
||||
*result = GNCRPC_PROTOCOL_VERSION;
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_book_begin_1_svc(gncrpc_book_begin_args *argp, int *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval = TRUE;
|
||||
GncRpcSvc *cl;
|
||||
int res = ERR_RPC_SERVER_STATE;
|
||||
|
||||
ENTER ("id=\"%s\"", argp->book_id);
|
||||
|
||||
do {
|
||||
gboolean ret;
|
||||
|
||||
retval = gncrpc_get_state (rqstp, NULL, &cl);
|
||||
if (!retval)
|
||||
break;
|
||||
|
||||
ret = gnc_book_begin (cl->book, argp->book_id, argp->ignore_lock,
|
||||
argp->create);
|
||||
PINFO ("ret == %s", (ret == TRUE ? "true" : "false"));
|
||||
res = gnc_book_pop_error (cl->book);
|
||||
} while (0);
|
||||
|
||||
*result = res;
|
||||
LEAVE ("done %d", res);
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_book_load_1_svc(char *argp, gncrpc_book_load_ret *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval = TRUE;
|
||||
GncRpcSvc *cl;
|
||||
gboolean ret;
|
||||
|
||||
ENTER ("ok");
|
||||
memset (result, 0, sizeof (*result));
|
||||
|
||||
retval = gncrpc_get_state (rqstp, NULL, &cl);
|
||||
if (!retval) {
|
||||
result->error = ERR_RPC_SERVER_STATE;
|
||||
LEAVE ("bad state");
|
||||
return retval;
|
||||
}
|
||||
|
||||
ret = gnc_book_load (cl->book);
|
||||
if (!ret) {
|
||||
result->error = gnc_book_pop_error (cl->book);
|
||||
} else {
|
||||
result->commodities =
|
||||
rpcend_build_gnccommoditylist (gnc_book_get_commodity_table
|
||||
(cl->book),
|
||||
TRUE);
|
||||
result->acctlist =
|
||||
rpcend_build_gncacctlist (gnc_book_get_group (cl->book));
|
||||
}
|
||||
|
||||
LEAVE ("res = %d", result->error);
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_book_end_1_svc(char *argp, int *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval = TRUE;
|
||||
GncRpcSvc *cl;
|
||||
|
||||
ENTER ("ok");
|
||||
retval = gncrpc_get_state (rqstp, NULL, &cl);
|
||||
if (!retval) {
|
||||
*result = ERR_RPC_SERVER_STATE;
|
||||
LEAVE ("bad state");
|
||||
return retval;
|
||||
}
|
||||
gnc_book_end (cl->book);
|
||||
*result = gnc_book_pop_error (cl->book);
|
||||
|
||||
LEAVE ("res = %d", *result);
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_account_begin_edit_1_svc(gncrpc_backend_guid *argp, int *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval = TRUE;
|
||||
Account *acc;
|
||||
GUID *guid = (GUID *) &(argp->guid);
|
||||
|
||||
ENTER ("ok");
|
||||
acc = xaccAccountLookup (guid);
|
||||
if (!acc) {
|
||||
*result = -1; /* XXX Deal with new accounts */
|
||||
LEAVE ("no acc");
|
||||
return retval;
|
||||
}
|
||||
|
||||
xaccAccountBeginEdit (acc);
|
||||
*result = 0; /* XXX book_error */
|
||||
|
||||
LEAVE ("acct=%p (%s)", acc, acc ? acc->accountName : "");
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_account_commit_edit_1_svc(gncrpc_commit_acct_args *argp, int *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval = TRUE;
|
||||
Account *acc;
|
||||
AccountGroup *ag;
|
||||
gnc_commodity_table *ct;
|
||||
GUID *guid = (GUID *) &(argp->acct.guid);
|
||||
GncRpcSvc *cl;
|
||||
int added;
|
||||
|
||||
ENTER ("vers=%d", argp->acct.vers);
|
||||
retval = gncrpc_get_state (rqstp, NULL, &cl);
|
||||
if (!retval) {
|
||||
*result = ERR_RPC_SERVER_STATE;
|
||||
LEAVE ("bad state");
|
||||
return retval;
|
||||
}
|
||||
|
||||
ag = gnc_book_get_group (cl->book);
|
||||
ct = gnc_book_get_commodity_table (cl->book);
|
||||
|
||||
/* Load the commodity, in case it changed */
|
||||
#ifdef GNCACCT_COMMODITY
|
||||
rpcend_load_gnccommodity (ct, argp->commodity);
|
||||
#else
|
||||
rpcend_load_gnccommodity (ct, argp->currency);
|
||||
rpcend_load_gnccommodity (ct, argp->security);
|
||||
#endif
|
||||
|
||||
/* Now add the account */
|
||||
added = rpcend_do_add_acct (ag, &(argp->acct), ct);
|
||||
|
||||
acc = xaccAccountLookup (guid);
|
||||
if (!acc) {
|
||||
*result = gnc_book_pop_error (cl->book);
|
||||
if (*result == 0)
|
||||
*result = ERR_RPC_FAILED;
|
||||
LEAVE ("no acc");
|
||||
return retval;
|
||||
}
|
||||
|
||||
PINFO ("commiting acct..");
|
||||
xaccAccountCommitEdit (acc);
|
||||
|
||||
LEAVE ("vers=%d, added=%d (%s)", acc->version, added,
|
||||
acc ? acc->accountName : "");
|
||||
*result = added < 0 ? 0 : ERR_RPC_NOT_ADDED; /* XXX book error? */
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_account_rollback_edit_1_svc(gncrpc_backend_guid *argp, int *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval = TRUE;
|
||||
Account *acc;
|
||||
GUID *guid = (GUID *) &(argp->guid);
|
||||
GncRpcSvc *cl;
|
||||
|
||||
ENTER ("ok");
|
||||
retval = gncrpc_get_state (rqstp, NULL, &cl);
|
||||
if (!retval) {
|
||||
*result = ERR_RPC_SERVER_STATE;
|
||||
LEAVE ("bad state");
|
||||
return retval;
|
||||
}
|
||||
|
||||
acc = xaccAccountLookup (guid);
|
||||
if (!acc) {
|
||||
*result = ERR_RPC_FAILED;
|
||||
LEAVE ("no acc");
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* XXX There is no account rollback. Just commit it without
|
||||
* any changes */
|
||||
xaccAccountCommitEdit (acc);
|
||||
*result = gnc_book_pop_error (cl->book);
|
||||
|
||||
LEAVE ("acc=%p (%s)", acc, acc ? acc->accountName : "");
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_txn_begin_edit_1_svc(gncrpc_backend_guid *argp, int *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval = TRUE;
|
||||
Transaction *txn;
|
||||
GUID *guid = (GUID *) &(argp->guid);
|
||||
|
||||
ENTER ("ok");
|
||||
txn = xaccTransLookup (guid);
|
||||
if (!txn) {
|
||||
*result = -1; /* XXX: deal with 'new' transaction */
|
||||
LEAVE ("no txn");
|
||||
return retval;
|
||||
}
|
||||
|
||||
xaccTransBeginEdit (txn);
|
||||
*result = 0; /* XXX: book error */
|
||||
|
||||
LEAVE ("ok");
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_txn_commit_edit_1_svc(gncrpc_commit_txn_args *argp, int *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval = TRUE;
|
||||
Transaction *txn;
|
||||
gnc_commodity_table *ct;
|
||||
GUID *guid = (GUID *) &(argp->new.guid);
|
||||
GncRpcSvc *cl;
|
||||
int added;
|
||||
|
||||
ENTER ("vers=%d", argp->new.vers);
|
||||
retval = gncrpc_get_state (rqstp, NULL, &cl);
|
||||
if (!retval) {
|
||||
*result = ERR_RPC_SERVER_STATE;
|
||||
LEAVE ("bad state");
|
||||
return retval;
|
||||
}
|
||||
|
||||
ct = gnc_book_get_commodity_table (cl->book);
|
||||
added = rpcend_do_add_txn (&(argp->new), ct);
|
||||
|
||||
txn = xaccTransLookup (guid);
|
||||
if (!txn) {
|
||||
*result = ERR_RPC_FAILED;
|
||||
LEAVE ("no txn!");
|
||||
return retval;
|
||||
}
|
||||
PINFO ("Committing txn..");
|
||||
xaccTransCommitEdit (txn);
|
||||
LEAVE ("ok, added=%d, vers=%d", added, txn->version);
|
||||
*result = added < 0 ? 0 : ERR_RPC_NOT_ADDED; /* XXX book error? */
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_txn_rollback_edit_1_svc(gncrpc_backend_guid *argp, int *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval = TRUE;
|
||||
Transaction *txn;
|
||||
GUID *guid = (GUID *) &(argp->guid);
|
||||
|
||||
ENTER ("ok");
|
||||
txn = xaccTransLookup (guid);
|
||||
if (!txn) {
|
||||
*result = ERR_RPC_FAILED;
|
||||
LEAVE ("no txn");
|
||||
return retval;
|
||||
}
|
||||
|
||||
xaccTransRollbackEdit (txn);
|
||||
*result = 0; /* XXX book error? */
|
||||
|
||||
LEAVE ("ok");
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_run_query_1_svc(gncrpc_query_args *argp, gncrpc_query_ret *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval = TRUE;
|
||||
gncQuery thisq;
|
||||
Query *q = (Query *)&thisq;
|
||||
GList *txnlist;
|
||||
GncRpcSvc *cl;
|
||||
AccountGroup *ag = NULL;
|
||||
|
||||
ENTER ("parent=%p, q=%p", argp->group_parent_guid, argp->query);
|
||||
|
||||
memset (result, 0, sizeof (*result));
|
||||
|
||||
/* Find state */
|
||||
retval = gncrpc_get_state (rqstp, NULL, &cl);
|
||||
if (!retval) {
|
||||
result->error = ERR_RPC_SERVER_STATE;
|
||||
LEAVE ("Bad state");
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* Figure out the query account group */
|
||||
if (argp->group_parent_guid) {
|
||||
Account *acc = xaccAccountLookup ((GUID *)argp->group_parent_guid);
|
||||
if (acc)
|
||||
ag = xaccAccountGetChildren (acc);
|
||||
}
|
||||
if (!ag)
|
||||
ag = gnc_book_get_group (cl->book);
|
||||
|
||||
/* Setup query */
|
||||
argp->query->acct_group = (int *)ag;
|
||||
rpcend_parse_gncquery (argp->query, q);
|
||||
argp->query->acct_group = NULL;
|
||||
|
||||
/* Run Query */
|
||||
txnlist = xaccQueryGetTransactions (q, QUERY_MATCH_ANY);
|
||||
|
||||
/* Setup return value */
|
||||
result->txnlist = rpcend_build_gncverslist_txn (txnlist, TRUE);
|
||||
|
||||
/* Free the txnlist */
|
||||
g_list_free (txnlist);
|
||||
|
||||
/* Reset argument/query structure */
|
||||
g_list_free ((GList *)(((gncQuery *)q)->split_list));
|
||||
rpcend_free_query (q);
|
||||
|
||||
LEAVE ("query done");
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_sync1_1_svc(gncrpc_sync1_args *argp, gncrpc_sync1_ret *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval = TRUE;
|
||||
GncRpcSvc *cl;
|
||||
AccountGroup *ag = NULL;
|
||||
gnc_commodity_table *ct;
|
||||
gnc_vers_list *this, *next, *new, **endnew, *old, **endold;
|
||||
gnc_acctlist *acctlist = NULL;
|
||||
gnc_txnlist *txnlist = NULL;
|
||||
|
||||
ENTER ("ok");
|
||||
|
||||
memset (result, 0, sizeof (*result));
|
||||
retval = gncrpc_get_state (rqstp, NULL, &cl);
|
||||
if (!retval) {
|
||||
result->error = ERR_RPC_SERVER_STATE;
|
||||
LEAVE ("bad state");
|
||||
return retval;
|
||||
}
|
||||
|
||||
ct = gnc_book_get_commodity_table (cl->book);
|
||||
ag = gnc_book_get_group (cl->book);
|
||||
rpcend_load_commoditylist (ct, argp->commodities);
|
||||
|
||||
/* Just send back the whole commodity list. It wont be too
|
||||
* big, right???? Otherwise, I can implement an N^2 algorithm
|
||||
* to figure out which commodities we need to send back...
|
||||
*/
|
||||
result->commodities = rpcend_build_gnccommoditylist (ct, TRUE);
|
||||
|
||||
/* Walk the acctlist and txnlist; for each one, if the client
|
||||
* is newer, ask them to send it; if the server is newer, grab
|
||||
* the new version
|
||||
*/
|
||||
new = NULL;
|
||||
endnew = &new;
|
||||
old = NULL;
|
||||
endold = &old;
|
||||
|
||||
for (this = argp->acctlist; this; this = next) {
|
||||
GUID *guid = (GUID *)(this->guid);
|
||||
Account *acc = xaccAccountLookup (guid);
|
||||
|
||||
next = this->next;
|
||||
|
||||
if (!acc || acc->version < this->vers) {
|
||||
/* Tell the client to send this account */
|
||||
*endnew = this;
|
||||
endnew = &(this->next);
|
||||
} else {
|
||||
/* Maybe send this account? */
|
||||
if (acc->version < this->vers) {
|
||||
gnc_acctlist *newaccl = malloc (sizeof (*newaccl));
|
||||
gncAccount *newacc = malloc (sizeof (*newacc));
|
||||
rpcend_build_gncacct (newacc, acc);
|
||||
newaccl->acct = newacc;
|
||||
newaccl->next = acctlist;
|
||||
acctlist = newaccl;
|
||||
}
|
||||
*endold = this;
|
||||
endold = &(this->next);
|
||||
}
|
||||
} /* for */
|
||||
|
||||
/* XXX Should we figure out if there are new accounts and send
|
||||
* them?
|
||||
*/
|
||||
|
||||
/* Be sure to NULL-terminate these lists! */
|
||||
*endold = NULL;
|
||||
*endnew = NULL;
|
||||
|
||||
/* Reset the args and set the reply */
|
||||
result->send_acctlist = new;
|
||||
result->acctlist = acctlist;
|
||||
argp->acctlist = old;
|
||||
|
||||
/* Do a similar thing for transactions */
|
||||
new = NULL;
|
||||
endnew = &new;
|
||||
old = NULL;
|
||||
endold = &old;
|
||||
|
||||
for (this = argp->txnlist; this; this = next) {
|
||||
GUID *guid = (GUID *)(this->guid);
|
||||
Transaction *txn = xaccTransLookup (guid);
|
||||
|
||||
next = this->next;
|
||||
|
||||
if (!txn || txn->version < this->vers) {
|
||||
/* Tell the client to send this account */
|
||||
*endnew = this;
|
||||
endnew = &(this->next);
|
||||
} else {
|
||||
/* Maybe send this txn? */
|
||||
if (txn->version < this->vers) {
|
||||
gnc_txnlist *newtxnl = malloc (sizeof (*newtxnl));
|
||||
gncTransaction *newtxn = malloc (sizeof (*newtxn));
|
||||
rpcend_build_gnctxn (newtxn, txn);
|
||||
newtxnl->txn = newtxn;
|
||||
newtxnl->next = txnlist;
|
||||
txnlist = newtxnl;
|
||||
}
|
||||
*endold = this;
|
||||
endold = &(this->next);
|
||||
}
|
||||
} /* for */
|
||||
|
||||
/* Be sure to NULL-terminate these lists! */
|
||||
*endold = NULL;
|
||||
*endnew = NULL;
|
||||
|
||||
/* Reset the args and set the reply */
|
||||
result->send_txnlist = new;
|
||||
result->txnlist = txnlist;
|
||||
argp->txnlist = old;
|
||||
LEAVE ("ok");
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_sync2_1_svc(gncrpc_sync2_args *argp, int *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval = TRUE;
|
||||
GncRpcSvc *cl;
|
||||
AccountGroup *ag = NULL;
|
||||
gnc_acctlist *acctlist = NULL;
|
||||
gnc_txnlist *txnlist = NULL;
|
||||
gnc_commodity_table *ct;
|
||||
|
||||
ENTER ("ok");
|
||||
|
||||
*result = 0;
|
||||
retval = gncrpc_get_state (rqstp, NULL, &cl);
|
||||
if (!retval) {
|
||||
*result = ERR_RPC_SERVER_STATE;
|
||||
LEAVE ("bad state");
|
||||
return retval;
|
||||
}
|
||||
|
||||
ag = gnc_book_get_group (cl->book);
|
||||
ct = gnc_book_get_commodity_table (cl->book);
|
||||
|
||||
for (acctlist = argp->acctlist; acctlist; acctlist = acctlist->next) {
|
||||
rpcend_do_add_acct (ag, acctlist->acct, ct);
|
||||
}
|
||||
|
||||
for (txnlist = argp->txnlist; txnlist; txnlist = txnlist->next) {
|
||||
rpcend_do_add_txn (txnlist->txn, ct);
|
||||
}
|
||||
|
||||
gnc_book_save (cl->book);
|
||||
*result = gnc_book_pop_error (cl->book);
|
||||
/* XXX: clear results if error! */
|
||||
|
||||
LEAVE ("ok");
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_get_txns_1_svc(gncrpc_get_txns_args *argp, gncrpc_get_txns_ret *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval = TRUE;
|
||||
GncRpcSvc *cl;
|
||||
AccountGroup *ag;
|
||||
|
||||
ENTER ("get txns");
|
||||
|
||||
memset (result, 0, sizeof (*result));
|
||||
retval = gncrpc_get_state (rqstp, NULL, &cl);
|
||||
if (!retval) {
|
||||
result->error = ERR_RPC_SERVER_STATE;
|
||||
LEAVE ("Bad state");
|
||||
return retval;
|
||||
}
|
||||
|
||||
ag = gnc_book_get_group (cl->book);
|
||||
result->txnlist = rpcend_build_gnctxnlist_list (ag, argp->guids);
|
||||
|
||||
LEAVE ("ok");
|
||||
return retval;
|
||||
}
|
||||
|
||||
int
|
||||
gncrpc_prog_1_freeresult (SVCXPRT *transp, xdrproc_t xdr_result, caddr_t result)
|
||||
{
|
||||
xdr_free (xdr_result, result);
|
||||
|
||||
/*
|
||||
* Insert additional freeing code here, if needed
|
||||
*/
|
||||
|
||||
return 1;
|
||||
}
|
175
src/engine/rpc/gncRpc_server_stubs.c
Normal file
175
src/engine/rpc/gncRpc_server_stubs.c
Normal file
@ -0,0 +1,175 @@
|
||||
/*
|
||||
* This is sample code generated by rpcgen.
|
||||
* These are only templates and you can use them
|
||||
* as a guideline for developing your own functions.
|
||||
*/
|
||||
|
||||
#include "gncRpc.h"
|
||||
|
||||
bool_t
|
||||
gncrpc_version_1_svc(void *argp, int *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval;
|
||||
|
||||
/*
|
||||
* insert server code here
|
||||
*/
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_book_begin_1_svc(gncrpc_book_begin_args *argp, int *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval;
|
||||
|
||||
/*
|
||||
* insert server code here
|
||||
*/
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_book_load_1_svc(char *argp, gncrpc_book_load_ret *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval;
|
||||
|
||||
/*
|
||||
* insert server code here
|
||||
*/
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_book_end_1_svc(char *argp, int *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval;
|
||||
|
||||
/*
|
||||
* insert server code here
|
||||
*/
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_account_begin_edit_1_svc(gncrpc_backend_guid *argp, int *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval;
|
||||
|
||||
/*
|
||||
* insert server code here
|
||||
*/
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_account_commit_edit_1_svc(gncrpc_commit_acct_args *argp, int *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval;
|
||||
|
||||
/*
|
||||
* insert server code here
|
||||
*/
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_txn_begin_edit_1_svc(gncrpc_backend_guid *argp, int *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval;
|
||||
|
||||
/*
|
||||
* insert server code here
|
||||
*/
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_txn_commit_edit_1_svc(gncrpc_commit_txn_args *argp, int *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval;
|
||||
|
||||
/*
|
||||
* insert server code here
|
||||
*/
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_txn_rollback_edit_1_svc(gncrpc_backend_guid *argp, int *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval;
|
||||
|
||||
/*
|
||||
* insert server code here
|
||||
*/
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_run_query_1_svc(gncrpc_query_args *argp, gncrpc_query_ret *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval;
|
||||
|
||||
/*
|
||||
* insert server code here
|
||||
*/
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_sync1_1_svc(gncrpc_sync1_args *argp, gncrpc_sync1_ret *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval;
|
||||
|
||||
/*
|
||||
* insert server code here
|
||||
*/
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_sync2_1_svc(gncrpc_sync2_args *argp, int *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval;
|
||||
|
||||
/*
|
||||
* insert server code here
|
||||
*/
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool_t
|
||||
gncrpc_get_txns_1_svc(gncrpc_get_txns_args *argp, gncrpc_get_txns_ret *result, struct svc_req *rqstp)
|
||||
{
|
||||
bool_t retval;
|
||||
|
||||
/*
|
||||
* insert server code here
|
||||
*/
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
int
|
||||
gncrpc_prog_1_freeresult (SVCXPRT *transp, xdrproc_t xdr_result, caddr_t result)
|
||||
{
|
||||
xdr_free (xdr_result, result);
|
||||
|
||||
/*
|
||||
* Insert additional freeing code here, if needed
|
||||
*/
|
||||
|
||||
return 1;
|
||||
}
|
41
src/engine/rpc/gncSplit.x
Normal file
41
src/engine/rpc/gncSplit.x
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* FILE:
|
||||
* gncSplit.x
|
||||
*
|
||||
* FUNCTION:
|
||||
* The RPC protocol definition for Gnucash Splits
|
||||
*
|
||||
* HISTORY:
|
||||
* Created By: Derek Atkins <warlord@MIT.EDU>
|
||||
* Copyright (c) 2001, Derek Atkins
|
||||
*/
|
||||
|
||||
#ifndef __GNC_SPLIT_X
|
||||
#define __GNC_SPLIT_X
|
||||
|
||||
#include "gncGUID.x"
|
||||
#include "gncKVP.x"
|
||||
|
||||
struct gncSplit {
|
||||
gncGUID guid;
|
||||
|
||||
/* Different from Gnucash Split */
|
||||
|
||||
gncGUID acct_guid; /* Account GUID for this split */
|
||||
gncGUID txn_guid; /* Parent transaction GUID */
|
||||
|
||||
string memo<>;
|
||||
string action<>;
|
||||
gnc_kvp_frame * kvp_data;
|
||||
char reconciled;
|
||||
gncTimespec date_reconciled;
|
||||
gncNumeric value;
|
||||
gncNumeric damount;
|
||||
};
|
||||
|
||||
struct gnc_splitlist {
|
||||
gncSplit * split;
|
||||
gnc_splitlist * next;
|
||||
};
|
||||
|
||||
#endif /* __GNC_SPLIT_X */
|
40
src/engine/rpc/gncTxn.x
Normal file
40
src/engine/rpc/gncTxn.x
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* FILE:
|
||||
* gncTxn.x
|
||||
*
|
||||
* FUNCTION:
|
||||
* The RPC protocol definition for Gnucash Transactions
|
||||
*
|
||||
* HISTORY:
|
||||
* Created By: Derek Atkins <warlord@MIT.EDU>
|
||||
* Copyright (c) 2001, Derek Atkins
|
||||
*/
|
||||
|
||||
#ifndef __GNC_TXN_X
|
||||
#define __GNC_TXN_X
|
||||
|
||||
#include "gncCommodity.x"
|
||||
#include "gncGUID.x"
|
||||
#include "gncKVP.x"
|
||||
#include "gncSplit.x"
|
||||
|
||||
struct gncTransaction {
|
||||
gncGUID guid;
|
||||
gncTimespec date_entered;
|
||||
gncTimespec date_posted;
|
||||
string num<>;
|
||||
string desc<>;
|
||||
gnc_kvp_frame* kvp_data;
|
||||
gncCommodityPtr common_currency;
|
||||
int vers;
|
||||
/* Different from here */
|
||||
gnc_splitlist * splits;
|
||||
bool do_free;
|
||||
};
|
||||
|
||||
struct gnc_txnlist {
|
||||
gncTransaction * txn;
|
||||
gnc_txnlist * next;
|
||||
};
|
||||
|
||||
#endif /* __GNC_TXN_X */
|
219
src/engine/rpc/svc_thrd.c
Normal file
219
src/engine/rpc/svc_thrd.c
Normal file
@ -0,0 +1,219 @@
|
||||
/* @(#)svc_tcp.c 2.2 88/08/01 4.0 RPCSRC */
|
||||
/*
|
||||
* Sun RPC is a product of Sun Microsystems, Inc. and is provided for
|
||||
* unrestricted use provided that this legend is included on all tape
|
||||
* media and as a part of the software program in whole or part. Users
|
||||
* may copy or modify Sun RPC without charge, but are not authorized
|
||||
* to license or distribute it to anyone else except as part of a product or
|
||||
* program developed by the user.
|
||||
*
|
||||
* SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
|
||||
* WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
|
||||
*
|
||||
* Sun RPC is provided with no support and without any obligation on the
|
||||
* part of Sun Microsystems, Inc. to assist in its use, correction,
|
||||
* modification or enhancement.
|
||||
*
|
||||
* SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
|
||||
* INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC
|
||||
* OR ANY PART THEREOF.
|
||||
*
|
||||
* In no event will Sun Microsystems, Inc. be liable for any lost revenue
|
||||
* or profits or other special, indirect and consequential damages, even if
|
||||
* Sun has been advised of the possibility of such damages.
|
||||
*
|
||||
* Sun Microsystems, Inc.
|
||||
* 2550 Garcia Avenue
|
||||
* Mountain View, California 94043
|
||||
*/
|
||||
#if !defined(lint) && defined(SCCSIDS)
|
||||
static char sccsid[] = "@(#)svc_tcp.c 1.21 87/08/11 Copyr 1984 Sun Micro";
|
||||
#endif
|
||||
|
||||
/*
|
||||
* svc_thrd.c, Server side for Threaded TCP/IP based RPC.
|
||||
*
|
||||
* Copyright (C) 1984, Sun Microsystems, Inc.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <rpc/rpc.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/poll.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "xprt_thrd.h"
|
||||
#include "svc_thrd.h"
|
||||
|
||||
#ifdef DEBUG_RPC
|
||||
#define dfprintf(a) fprintf a
|
||||
#else
|
||||
#define dfprintf(a)
|
||||
#endif
|
||||
|
||||
#ifdef USE_IN_LIBIO
|
||||
# include <libio/iolibio.h>
|
||||
# define fputs(s, f) _IO_fputs (s, f)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Ops vector for TCP/IP based rpc service handle
|
||||
*/
|
||||
static bool_t svcthrd_recv (SVCXPRT *, struct rpc_msg *);
|
||||
static enum xprt_stat svcthrd_stat (SVCXPRT *);
|
||||
static bool_t svcthrd_getargs (SVCXPRT *, xdrproc_t, caddr_t);
|
||||
static bool_t svcthrd_reply (SVCXPRT *, struct rpc_msg *);
|
||||
static bool_t svcthrd_freeargs (SVCXPRT *, xdrproc_t, caddr_t);
|
||||
static void svcthrd_destroy (SVCXPRT *);
|
||||
|
||||
static const struct xp_ops svcthrd_op =
|
||||
{
|
||||
svcthrd_recv,
|
||||
svcthrd_stat,
|
||||
svcthrd_getargs,
|
||||
svcthrd_reply,
|
||||
svcthrd_freeargs,
|
||||
svcthrd_destroy
|
||||
};
|
||||
|
||||
struct tcp_conn
|
||||
{ /* kept in xprt->xp_p1 */
|
||||
u_long x_id;
|
||||
TXPRT *xprt;
|
||||
char verf_body[MAX_AUTH_BYTES];
|
||||
};
|
||||
|
||||
/*
|
||||
* Usage:
|
||||
* xprt = svcthrd_create(transport);
|
||||
*
|
||||
* Creates and returns a (rpc) tcp based transporter.
|
||||
* This routine returns a NULL if a problem occurred.
|
||||
*
|
||||
* NOTE: This assumes that a lock is held on the XDR Object whenever
|
||||
* the methods herein are called. A word to the wise.... (The tranport
|
||||
* object will hold this lock when it calls the dispatch routine for a call)
|
||||
*/
|
||||
SVCXPRT *
|
||||
svcthrd_create (TXPRT *transport)
|
||||
{
|
||||
SVCXPRT *xprt;
|
||||
struct tcp_conn *cd;
|
||||
|
||||
xprt = (SVCXPRT *) malloc (sizeof (SVCXPRT));
|
||||
if (xprt == (SVCXPRT *) NULL)
|
||||
{
|
||||
(void) fputs ("svcthrd_create: out of memory\n", stderr);
|
||||
goto done;
|
||||
}
|
||||
cd = (struct tcp_conn *) malloc (sizeof (struct tcp_conn));
|
||||
if (cd == (struct tcp_conn *) NULL)
|
||||
{
|
||||
(void) fputs ("svcthrd_create: out of memory\n", stderr);
|
||||
free ((char *) xprt);
|
||||
xprt = (SVCXPRT *) NULL;
|
||||
goto done;
|
||||
}
|
||||
memset (xprt, 0, sizeof (*xprt));
|
||||
memset (cd, 0, sizeof (*cd));
|
||||
|
||||
cd->xprt = transport;
|
||||
xprt->xp_p2 = NULL;
|
||||
xprt->xp_p1 = (caddr_t) cd;
|
||||
xprt->xp_verf.oa_base = cd->verf_body;
|
||||
xprt->xp_addrlen = 0;
|
||||
xprt->xp_ops = &svcthrd_op; /* truly deals with calls */
|
||||
xprt->xp_port = 0; /* this is a connection, not a rendezvouser */
|
||||
xprt->xp_sock = -1;
|
||||
done:
|
||||
return xprt;
|
||||
}
|
||||
|
||||
static enum xprt_stat
|
||||
svcthrd_stat (SVCXPRT *xprt)
|
||||
{
|
||||
return XPRT_IDLE;
|
||||
}
|
||||
|
||||
static bool_t
|
||||
svcthrd_recv (SVCXPRT *xprt, struct rpc_msg *msg)
|
||||
{
|
||||
struct tcp_conn *cd = (struct tcp_conn *) (xprt->xp_p1);
|
||||
|
||||
dfprintf ((stderr, "svcthrd_recv: set xid: %lx\n", msg->rm_xid));
|
||||
cd->x_id = msg->rm_xid;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* NB: This method assumes that readlock is held, and it will release
|
||||
* the locks on exit.
|
||||
*
|
||||
* This implies that the "caller" must already have the lock, and
|
||||
* must only call this function once. NOTE that calling with
|
||||
* xprt_thrd_getargs_hook will NOT touch the XDR object.
|
||||
*/
|
||||
static bool_t
|
||||
svcthrd_getargs (SVCXPRT *xprt, xdrproc_t xdr_args, caddr_t args_ptr)
|
||||
{
|
||||
struct tcp_conn *cd = (struct tcp_conn *) (xprt->xp_p1);
|
||||
XDR *xdr;
|
||||
bool_t res;
|
||||
|
||||
if (xdr_args == (xdrproc_t) xprt_thrd_getargs_hook) {
|
||||
if (args_ptr != NULL)
|
||||
*((TXPRT **) args_ptr) = cd->xprt;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
xdr = TXPRT_GET_XDR (cd->xprt);
|
||||
xdr->x_op = XDR_DECODE;
|
||||
res = ((*xdr_args) (xdr, args_ptr));
|
||||
TXPRT_REL_XDR (cd->xprt);
|
||||
return res;
|
||||
}
|
||||
|
||||
static bool_t
|
||||
svcthrd_freeargs (SVCXPRT *xprt, xdrproc_t xdr_args, caddr_t args_ptr)
|
||||
{
|
||||
struct tcp_conn *cd = (struct tcp_conn *) (xprt->xp_p1);
|
||||
XDR *xdrs = TXPRT_GET_XDR (cd->xprt);
|
||||
bool_t res;
|
||||
|
||||
xdrs->x_op = XDR_FREE;
|
||||
res = ((*xdr_args) (xdrs, args_ptr));
|
||||
TXPRT_REL_XDR (cd->xprt);
|
||||
return res;
|
||||
}
|
||||
|
||||
static bool_t
|
||||
svcthrd_reply (SVCXPRT *xprt, struct rpc_msg *msg)
|
||||
{
|
||||
struct tcp_conn *cd = (struct tcp_conn *) (xprt->xp_p1);
|
||||
XDR *xdrs = TXPRT_GET_XDR (cd->xprt);
|
||||
bool_t stat;
|
||||
|
||||
dfprintf ((stderr, "svcthrd_reply: xid=%lx (msg says %lx)\n", cd->x_id,
|
||||
msg->rm_xid));
|
||||
|
||||
xdrs->x_op = XDR_ENCODE;
|
||||
msg->rm_xid = cd->x_id;
|
||||
stat = xdr_replymsg (xdrs, msg);
|
||||
(void) xdrrec_endofrecord (xdrs, TRUE);
|
||||
TXPRT_REL_XDR (cd->xprt);
|
||||
return stat;
|
||||
}
|
||||
|
||||
static void
|
||||
svcthrd_destroy (SVCXPRT *xprt)
|
||||
{
|
||||
struct tcp_conn *cd = (struct tcp_conn *) xprt->xp_p1;
|
||||
|
||||
free ((caddr_t) cd);
|
||||
free ((caddr_t) xprt);
|
||||
}
|
16
src/engine/rpc/svc_thrd.h
Normal file
16
src/engine/rpc/svc_thrd.h
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* svc_thrd.h -- Threaded, Multiplexed, RPC/TCP Service
|
||||
*
|
||||
* Written By: Derek Atkins <warlord@MIT.EDU>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef RPC_SVC_THRD_H
|
||||
#define RPC_SVC_THRD_H
|
||||
|
||||
#include <rpc/svc.h>
|
||||
#include "xprt_thrd.h"
|
||||
|
||||
extern SVCXPRT *svcthrd_create (TXPRT *transport);
|
||||
|
||||
#endif /* RPC_SVC_THRD_H */
|
869
src/engine/rpc/xprt_thrd.c
Normal file
869
src/engine/rpc/xprt_thrd.c
Normal file
@ -0,0 +1,869 @@
|
||||
/*
|
||||
* Threaded TCP Transport scheme for RPC; allow a client and a service
|
||||
* to share a single (TCP) stream. A single reader/writer thread
|
||||
* exists to handle to I/O. Writers obtain a write-lock before
|
||||
* writing to the stream, and 'readers' setup a callback with the I/O
|
||||
* thread which wakes them up when a particular request (or reply) is
|
||||
* received.
|
||||
*
|
||||
* Created by: Derek Atkins <warlord@MIT.EDU>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <rpc/rpc.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/poll.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
#include "xprt_thrd.h"
|
||||
#include "clnt_thrd.h"
|
||||
#include "svc_thrd.h"
|
||||
|
||||
#ifdef DEBUG_RPC
|
||||
#define dfprintf(a) fprintf a
|
||||
#else
|
||||
#define dfprintf(a)
|
||||
#endif
|
||||
|
||||
#define RQCRED_SIZE 400 /* this size is excessive */
|
||||
|
||||
/* Internal Service Request */
|
||||
struct my_svc_req {
|
||||
char cred[2 * MAX_AUTH_BYTES + RQCRED_SIZE];
|
||||
struct svc_req r;
|
||||
struct rpc_msg m;
|
||||
void (*dispatch) (struct svc_req *r, SVCXPRT *x);
|
||||
TXPRT * xprt;
|
||||
};
|
||||
|
||||
/* Transport Wait Object private storage */
|
||||
struct tw_priv {
|
||||
pthread_cond_t cond;
|
||||
struct rpc_msg *repl;
|
||||
TXPRT_WAIT *next;
|
||||
};
|
||||
|
||||
/* Dispatch service */
|
||||
struct xt_svc {
|
||||
u_long prog;
|
||||
u_long vers;
|
||||
void (*dispatch) (struct svc_req *r, SVCXPRT *x);
|
||||
struct xt_svc *next;
|
||||
};
|
||||
|
||||
/*
|
||||
* A stream object private storage
|
||||
*/
|
||||
struct xt_priv {
|
||||
pthread_mutex_t xdrlock;
|
||||
pthread_mutex_t waitlock;
|
||||
pthread_mutex_t readlock;
|
||||
bool_t readlock_is_locked;
|
||||
pthread_t readlock_by;
|
||||
int svccnt;
|
||||
pthread_mutex_t svccntlock;
|
||||
pthread_attr_t pattr;
|
||||
int sock;
|
||||
struct rpc_err err;
|
||||
XDR xdr;
|
||||
SVCXPRT *svc;
|
||||
struct xt_svc *callout;
|
||||
TXPRT_WAIT *waitlist;
|
||||
|
||||
/* For select loop */
|
||||
pthread_t select_thread;
|
||||
bool_t stop;
|
||||
caddr_t selectarg;
|
||||
int (*select)(caddr_t arg);
|
||||
void (*close)(caddr_t arg);
|
||||
|
||||
};
|
||||
|
||||
static int readtcp (caddr_t, char *, int);
|
||||
static int writetcp (caddr_t, char *, int);
|
||||
static void closetcp (caddr_t);
|
||||
static int selecttcp (caddr_t);
|
||||
static void xprt_thrd_run (TXPRT *xprt);
|
||||
|
||||
static SVCXPRT *xprt_get_svcxprt (TXPRT *xprt);
|
||||
|
||||
static struct xt_svc *svc_find (struct xt_priv *xtp, u_long prog, u_long vers,
|
||||
bool_t *found, u_long *low, u_long *high);
|
||||
|
||||
/* threadsafe Waitlist handling */
|
||||
static TXPRT_WAIT *find_wait (struct xt_priv *xtp, u_long x_id);
|
||||
static void add_wait (struct xt_priv *xtp, TXPRT_WAIT *wait);
|
||||
static void rem_wait (struct xt_priv *xtp, TXPRT_WAIT *wait);
|
||||
|
||||
/* We need to re-implement some of the low-level RPC functions */
|
||||
static bool_t read_reply (XDR *xdrs, struct rpc_msg *rmsg);
|
||||
static bool_t read_call (XDR *xdrs, struct rpc_msg *cmsg);
|
||||
|
||||
/* Ops vector for Threaded Transport */
|
||||
static CLIENT * xprt_thrd_new_client (TXPRT *xprt, u_long proc, u_long vers);
|
||||
static XDR * xprt_thrd_get_xdr (TXPRT *xprt);
|
||||
static void xprt_thrd_rel_xdr (TXPRT *xprt);
|
||||
static void xprt_thrd_wait_rep (TXPRT *xprt, struct rpc_msg *reply_msg,
|
||||
XDR **xdrs, TXPRT_WAIT *wait,
|
||||
struct timeval timeout);
|
||||
static bool_t xprt_thrd_reg_call (TXPRT *xprt, u_long prog, u_long vers,
|
||||
void (*dispatch) (struct svc_req *,
|
||||
SVCXPRT *));
|
||||
static caddr_t xprt_thrd_getsock (TXPRT *xprt);
|
||||
static void xprt_thrd_destroy (TXPRT *xprt);
|
||||
static void xprt_thrd_readlock (TXPRT *xprt);
|
||||
static bool_t xprt_thrd_doread (TXPRT *xprt);
|
||||
|
||||
static const struct txprt_ops xprtthrd_ops =
|
||||
{
|
||||
xprt_thrd_new_client,
|
||||
xprt_thrd_reg_call,
|
||||
xprt_thrd_getsock,
|
||||
xprt_thrd_destroy,
|
||||
xprt_thrd_readlock,
|
||||
xprt_thrd_doread,
|
||||
xprt_thrd_get_xdr,
|
||||
xprt_thrd_rel_xdr,
|
||||
xprt_thrd_wait_rep
|
||||
};
|
||||
|
||||
/*
|
||||
* Create a baseline threaded transport
|
||||
*/
|
||||
TXPRT *
|
||||
xprt_thrd_create (caddr_t sock,
|
||||
int (*read)(caddr_t, char *, int),
|
||||
int (*write)(caddr_t, char *, int),
|
||||
void (*close)(caddr_t),
|
||||
int (*select)(caddr_t),
|
||||
u_int sendsz, u_int recvsz)
|
||||
{
|
||||
TXPRT *xprt;
|
||||
struct xt_priv *xtp;
|
||||
|
||||
if ((read == NULL && write != NULL) ||
|
||||
(read != NULL && write == NULL))
|
||||
return NULL;
|
||||
|
||||
/* If read/write are supplied, must supply select and close */
|
||||
if (read != NULL &&
|
||||
(select == NULL || close == NULL))
|
||||
return NULL;
|
||||
|
||||
xprt = (TXPRT *) malloc (sizeof (*xprt));
|
||||
if (xprt == NULL) {
|
||||
fprintf (stderr, ("xprt_thrd_create: out of memory\n"));
|
||||
goto done;
|
||||
}
|
||||
xtp = (struct xt_priv *) malloc (sizeof (*xtp));
|
||||
if (xtp == NULL) {
|
||||
fprintf (stderr, ("xprt_thrd__create: out of memory\n"));
|
||||
free ((char *) xprt);
|
||||
xprt = (TXPRT *) NULL;
|
||||
goto done;
|
||||
}
|
||||
memset (xprt, 0, sizeof (*xprt));
|
||||
memset (xtp, 0, sizeof (*xtp));
|
||||
|
||||
/* Initialized Private context */
|
||||
pthread_mutex_init (&(xtp->xdrlock), NULL);
|
||||
pthread_mutex_init (&(xtp->waitlock), NULL);
|
||||
pthread_mutex_init (&(xtp->readlock), NULL);
|
||||
pthread_mutex_init (&(xtp->svccntlock), NULL);
|
||||
pthread_attr_init (&(xtp->pattr));
|
||||
pthread_attr_setdetachstate (&(xtp->pattr), PTHREAD_CREATE_DETACHED);
|
||||
|
||||
if (read == NULL) {
|
||||
xtp->sock = (int) sock;
|
||||
read = readtcp;
|
||||
write = writetcp;
|
||||
close = closetcp;
|
||||
select = selecttcp;
|
||||
sock = (caddr_t) xtp;
|
||||
}
|
||||
xdrrec_create (&(xtp->xdr), sendsz, recvsz, sock, read, write);
|
||||
xtp->close = close;
|
||||
xtp->select = select;
|
||||
xtp->selectarg = sock;
|
||||
|
||||
/* Setup Transport Object */
|
||||
xprt->txp_private = (caddr_t) xtp;
|
||||
xprt->txp_ops = &xprtthrd_ops;
|
||||
|
||||
/* Start it running */
|
||||
xprt_thrd_run (xprt);
|
||||
|
||||
done:
|
||||
return xprt;
|
||||
}
|
||||
|
||||
static CLIENT *
|
||||
xprt_thrd_new_client (TXPRT *xprt, u_long proc, u_long vers)
|
||||
{
|
||||
return clntthrd_create (xprt, proc, vers);
|
||||
}
|
||||
|
||||
static caddr_t
|
||||
xprt_thrd_getsock (TXPRT *xprt)
|
||||
{
|
||||
struct xt_priv *xtp = (struct xt_priv *)xprt->txp_private;
|
||||
return (xtp->selectarg);
|
||||
}
|
||||
|
||||
static XDR *
|
||||
xprt_thrd_get_xdr (TXPRT *xprt)
|
||||
{
|
||||
struct xt_priv *xtp = (struct xt_priv *)xprt->txp_private;
|
||||
pthread_mutex_lock (&(xtp->xdrlock));
|
||||
return &(xtp->xdr);
|
||||
}
|
||||
|
||||
static void
|
||||
xprt_thrd_readlock (TXPRT *xprt)
|
||||
{
|
||||
struct xt_priv *xtp = (struct xt_priv *)xprt->txp_private;
|
||||
pthread_mutex_lock (&(xtp->readlock));
|
||||
xtp->readlock_is_locked = TRUE;
|
||||
xtp->readlock_by = pthread_self ();
|
||||
}
|
||||
|
||||
static void
|
||||
unlock_readlock (TXPRT *xprt)
|
||||
{
|
||||
struct xt_priv *xtp = (struct xt_priv *)xprt->txp_private;
|
||||
xtp->readlock_is_locked = FALSE;
|
||||
pthread_mutex_unlock (&(xtp->readlock));
|
||||
}
|
||||
|
||||
static void
|
||||
xprt_thrd_unlock_xdr (struct xt_priv *xtp)
|
||||
{
|
||||
pthread_mutex_unlock (&(xtp->xdrlock));
|
||||
}
|
||||
|
||||
static void
|
||||
xprt_thrd_rel_xdr (TXPRT *xprt)
|
||||
{
|
||||
struct xt_priv *xtp = (struct xt_priv *)xprt->txp_private;
|
||||
|
||||
/* Release the read lock if we're decoding */
|
||||
if (xtp->xdr.x_op == XDR_DECODE && xtp->readlock_is_locked) {
|
||||
unlock_readlock (xprt);
|
||||
}
|
||||
|
||||
/* Then release the XDR lock */
|
||||
xprt_thrd_unlock_xdr (xtp);
|
||||
}
|
||||
|
||||
static void
|
||||
xprt_thrd_wait_rep (TXPRT *xprt, struct rpc_msg *reply_msg,
|
||||
XDR **xdrs, TXPRT_WAIT *wait, struct timeval timeout)
|
||||
{
|
||||
struct xt_priv *xtp = (struct xt_priv *)xprt->txp_private;
|
||||
struct tw_priv *twp = (struct tw_priv *)wait->tw_priv;
|
||||
|
||||
/* Add wait to waitlist */
|
||||
twp->repl = reply_msg;
|
||||
add_wait (xtp, wait);
|
||||
|
||||
dfprintf ((stderr, "xprt_thrd_wait_rep: waiting for xid %lx\n", wait->tw_x_id));
|
||||
|
||||
/* wait for reply */
|
||||
pthread_cond_wait (&twp->cond, &xtp->xdrlock);
|
||||
/* Ok, we're awake, and we should have xdrlock held now */
|
||||
|
||||
/* Remove the wait from the waitlist */
|
||||
rem_wait (xtp, wait);
|
||||
|
||||
/* And fill in the rest.. */
|
||||
*xdrs = &(xtp->xdr);
|
||||
(*xdrs)->x_op = XDR_DECODE;
|
||||
return;
|
||||
}
|
||||
|
||||
static bool_t
|
||||
xprt_thrd_reg_call (TXPRT *xprt, u_long prog, u_long vers,
|
||||
void (*dispatch) (struct svc_req *,
|
||||
SVCXPRT *))
|
||||
{
|
||||
struct xt_priv *xtp = (struct xt_priv *)xprt->txp_private;
|
||||
struct xt_svc *new;
|
||||
|
||||
if ((new = svc_find (xtp, prog, vers, NULL, NULL, NULL)) != NULL) {
|
||||
/* re-registering???? Can't do that. */
|
||||
return FALSE;
|
||||
}
|
||||
new = (struct xt_svc *) malloc (sizeof (*new));
|
||||
if (new == NULL)
|
||||
return FALSE;
|
||||
new->prog = prog;
|
||||
new->vers = vers;
|
||||
new->dispatch = dispatch;
|
||||
new->next = xtp->callout;
|
||||
xtp->callout = new;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
xprt_thrd_destroy (TXPRT *xprt)
|
||||
{
|
||||
struct xt_priv *xtp = (struct xt_priv *)xprt->txp_private;
|
||||
struct xt_svc *t, *s;
|
||||
TXPRT_WAIT *wait, *tempwait;
|
||||
|
||||
/*
|
||||
* First, wait for the select thread to exit. If we're in the
|
||||
* select thread (which can happen if the close() callback destroys
|
||||
* the transport), then just continue -- we know we'll exit when
|
||||
* control is returned.
|
||||
*/
|
||||
xtp->stop = TRUE;
|
||||
if (xtp->select)
|
||||
pthread_join (xtp->select_thread, NULL);
|
||||
|
||||
/* Clear out registered callouts */
|
||||
for (s = xtp->callout; s != NULL; s = t) {
|
||||
t = s->next;
|
||||
free (s);
|
||||
}
|
||||
|
||||
/* Wakeup waiting threads */
|
||||
if (xtp->waitlist != NULL) {
|
||||
/* Obtain the locks */
|
||||
xprt_thrd_readlock (xprt);
|
||||
xprt_thrd_get_xdr (xprt);
|
||||
|
||||
while ((wait = xtp->waitlist) != NULL) {
|
||||
struct tw_priv *twp = (struct tw_priv *)wait->tw_priv;
|
||||
XDR *xdr;
|
||||
pthread_cond_signal (&twp->cond);
|
||||
xprt_thrd_unlock_xdr (xtp);
|
||||
|
||||
/* We don't have to do anything here; we'll block waiting for readlock */
|
||||
xprt_thrd_readlock (xprt);
|
||||
xprt_thrd_get_xdr (xprt);
|
||||
}
|
||||
/*
|
||||
* At this point we should have the locks held and all threads
|
||||
* should be dead. So let's clear the locks and go home.
|
||||
*/
|
||||
xtp->xdr.x_op = XDR_DECODE;
|
||||
xprt_thrd_rel_xdr (xprt);
|
||||
}
|
||||
|
||||
/* Make sure all service calls have returned */
|
||||
while (xtp->svccnt > 0);
|
||||
|
||||
/* Then clear my memory */
|
||||
pthread_mutex_destroy (&(xtp->xdrlock));
|
||||
pthread_mutex_destroy (&(xtp->waitlock));
|
||||
pthread_mutex_destroy (&(xtp->readlock));
|
||||
pthread_mutex_destroy (&(xtp->svccntlock));
|
||||
pthread_attr_destroy (&(xtp->pattr));
|
||||
|
||||
XDR_DESTROY (&(xtp->xdr));
|
||||
if (xtp->svc) svc_destroy(xtp->svc);
|
||||
free (xtp);
|
||||
free (xprt);
|
||||
}
|
||||
|
||||
static void
|
||||
xprt_thrd_destroy_wait (TXPRT_WAIT *wait)
|
||||
{
|
||||
struct tw_priv *twp = (struct tw_priv *)wait->tw_priv;
|
||||
|
||||
pthread_cond_destroy (&twp->cond);
|
||||
free (twp);
|
||||
free (wait);
|
||||
}
|
||||
|
||||
TXPRT_WAIT *
|
||||
xprt_thrd_new_wait (void)
|
||||
{
|
||||
TXPRT_WAIT *wait;
|
||||
struct tw_priv *twp;
|
||||
|
||||
wait = (TXPRT_WAIT *)malloc (sizeof (*wait));
|
||||
if (wait != NULL) {
|
||||
twp = (struct tw_priv *) malloc (sizeof (*twp));
|
||||
if (twp == NULL) {
|
||||
free (wait);
|
||||
return NULL;
|
||||
}
|
||||
memset (wait, 0, sizeof (*wait));
|
||||
pthread_cond_init (&twp->cond, NULL);
|
||||
twp->next = NULL;
|
||||
wait->tw_priv = (caddr_t) twp;
|
||||
wait->tw_destroy = xprt_thrd_destroy_wait;
|
||||
}
|
||||
return wait;
|
||||
}
|
||||
|
||||
static SVCXPRT *
|
||||
xprt_get_svcxprt (TXPRT *xprt)
|
||||
{
|
||||
struct xt_priv *xtp = (struct xt_priv *)xprt->txp_private;
|
||||
if (xtp->svc == NULL) {
|
||||
xtp->svc = svcthrd_create (xprt);
|
||||
}
|
||||
return xtp->svc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Interface between xdr serializer and tcp connection.
|
||||
* Behaves like the system calls, read & write, but keeps some error state
|
||||
* around for the rpc level.
|
||||
*/
|
||||
static int
|
||||
readtcp (char *ptr, char *buf, int len)
|
||||
{
|
||||
struct xt_priv *xt = (struct xt_priv *)ptr;
|
||||
struct pollfd fd;
|
||||
int milliseconds = 30000; /* XXX */
|
||||
/* (ct->ct_wait.tv_sec * 1000) + (ct->ct_wait.tv_usec / 1000); */
|
||||
|
||||
if (len == 0)
|
||||
return 0;
|
||||
|
||||
fd.fd = xt->sock;
|
||||
fd.events = POLLIN;
|
||||
while (TRUE)
|
||||
{
|
||||
switch (poll(&fd, 1, milliseconds))
|
||||
{
|
||||
case 0:
|
||||
xt->err.re_status = RPC_TIMEDOUT;
|
||||
return -1;
|
||||
|
||||
case -1:
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
xt->err.re_status = RPC_CANTRECV;
|
||||
xt->err.re_errno = errno;
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
switch (len = read (xt->sock, buf, len))
|
||||
{
|
||||
|
||||
case 0:
|
||||
/* premature eof */
|
||||
xt->err.re_errno = ECONNRESET;
|
||||
xt->err.re_status = RPC_CANTRECV;
|
||||
len = -1; /* it's really an error */
|
||||
break;
|
||||
|
||||
case -1:
|
||||
xt->err.re_errno = errno;
|
||||
xt->err.re_status = RPC_CANTRECV;
|
||||
break;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static int
|
||||
writetcp (caddr_t ptr, char *buf, int len)
|
||||
{
|
||||
int i, cnt;
|
||||
struct xt_priv *xt = (struct xt_priv*)ptr;
|
||||
|
||||
for (cnt = len; cnt > 0; cnt -= i, buf += i)
|
||||
{
|
||||
if ((i = write (xt->sock, buf, cnt)) == -1)
|
||||
{
|
||||
xt->err.re_errno = errno;
|
||||
xt->err.re_status = RPC_CANTSEND;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static void
|
||||
closetcp (caddr_t sock)
|
||||
{
|
||||
/* Do we close or not? */
|
||||
}
|
||||
|
||||
static int
|
||||
selecttcp (caddr_t sockp)
|
||||
{
|
||||
TXPRT *xprt = (TXPRT *)sockp;
|
||||
struct xt_priv *xtp = (struct xt_priv *)xprt->txp_private;
|
||||
int sock = xtp->sock;
|
||||
struct timeval timeout;
|
||||
fd_set fds;
|
||||
|
||||
timeout.tv_sec = 10;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(sock, &fds);
|
||||
|
||||
return select (sock+1, &fds, 0, 0, &timeout);
|
||||
}
|
||||
|
||||
static struct xt_svc *
|
||||
svc_find (struct xt_priv *xtp, u_long prog, u_long vers,
|
||||
bool_t *found, u_long *low, u_long *high)
|
||||
{
|
||||
struct xt_svc *s = xtp->callout;
|
||||
while (s != NULL) {
|
||||
if (s->prog == prog) {
|
||||
if (s->vers == vers) {
|
||||
goto done;
|
||||
}
|
||||
if (found) *found = TRUE;
|
||||
if (low && high) {
|
||||
if (s->vers < *low)
|
||||
*low = s->vers;
|
||||
if (s->vers > *high)
|
||||
*high = s->vers;
|
||||
}
|
||||
} /* s->prog != prog */
|
||||
s = s->next;
|
||||
}
|
||||
done:
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
static TXPRT_WAIT *
|
||||
find_wait (struct xt_priv *xtp, u_long x_id)
|
||||
{
|
||||
TXPRT_WAIT *wait;
|
||||
struct tw_priv *twp;
|
||||
|
||||
pthread_mutex_lock (&xtp->waitlock);
|
||||
wait = xtp->waitlist;
|
||||
while (wait != NULL) {
|
||||
if (wait->tw_x_id == x_id)
|
||||
break;
|
||||
twp = (struct tw_priv *)wait->tw_priv;
|
||||
wait = twp->next;
|
||||
}
|
||||
pthread_mutex_unlock (&xtp->waitlock);
|
||||
return wait;
|
||||
}
|
||||
|
||||
static void
|
||||
add_wait (struct xt_priv *xtp, TXPRT_WAIT *wait)
|
||||
{
|
||||
struct tw_priv *twp = (struct tw_priv *)wait->tw_priv;
|
||||
|
||||
pthread_mutex_lock (&xtp->waitlock);
|
||||
twp->next = xtp->waitlist;
|
||||
xtp->waitlist = wait;
|
||||
pthread_mutex_unlock (&xtp->waitlock);
|
||||
}
|
||||
|
||||
static void
|
||||
rem_wait (struct xt_priv *xtp, TXPRT_WAIT *wait)
|
||||
{
|
||||
TXPRT_WAIT **oldwait;
|
||||
|
||||
pthread_mutex_lock (&xtp->waitlock);
|
||||
oldwait = &(xtp->waitlist);
|
||||
while (*oldwait != NULL) {
|
||||
struct tw_priv *twp = (struct tw_priv *)(*oldwait)->tw_priv;
|
||||
if (*oldwait == wait) {
|
||||
*oldwait = twp->next;
|
||||
break;
|
||||
}
|
||||
oldwait = &(twp->next);
|
||||
}
|
||||
pthread_mutex_unlock (&xtp->waitlock);
|
||||
return;
|
||||
}
|
||||
|
||||
bool_t
|
||||
xprt_thrd_getargs_hook (XDR *x, void *v)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static bool_t
|
||||
read_call (XDR *xdrs, struct rpc_msg *cmsg)
|
||||
{
|
||||
if (
|
||||
xdr_u_long (xdrs, &(cmsg->rm_call.cb_rpcvers)) &&
|
||||
(cmsg->rm_call.cb_rpcvers == RPC_MSG_VERSION) &&
|
||||
xdr_u_long (xdrs, &(cmsg->rm_call.cb_prog)) &&
|
||||
xdr_u_long (xdrs, &(cmsg->rm_call.cb_vers)) &&
|
||||
xdr_u_long (xdrs, &(cmsg->rm_call.cb_proc)) &&
|
||||
xdr_opaque_auth (xdrs, &(cmsg->rm_call.cb_cred)))
|
||||
return xdr_opaque_auth (xdrs, &(cmsg->rm_call.cb_verf));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* declarations from the SunRPC library */
|
||||
extern bool_t xdr_accepted_reply (XDR*, void*);
|
||||
extern bool_t xdr_rejected_reply (XDR*, void*);
|
||||
|
||||
static const struct xdr_discrim reply_dscrm[3] =
|
||||
{
|
||||
{(int) MSG_ACCEPTED, (xdrproc_t) xdr_accepted_reply},
|
||||
{(int) MSG_DENIED, (xdrproc_t) xdr_rejected_reply},
|
||||
{__dontcare__, NULL_xdrproc_t}};
|
||||
|
||||
static bool_t
|
||||
read_reply (XDR *xdrs, struct rpc_msg *rmsg)
|
||||
{
|
||||
return xdr_union (xdrs, (enum_t *) & (rmsg->rm_reply.rp_stat),
|
||||
(caddr_t) & (rmsg->rm_reply.ru), reply_dscrm,
|
||||
NULL_xdrproc_t);
|
||||
}
|
||||
|
||||
static void
|
||||
svc_up (struct xt_priv *xtp)
|
||||
{
|
||||
pthread_mutex_lock (&(xtp->svccntlock));
|
||||
xtp->svccnt++;
|
||||
pthread_mutex_unlock (&(xtp->svccntlock));
|
||||
}
|
||||
|
||||
static void
|
||||
svc_down (struct xt_priv *xtp)
|
||||
{
|
||||
pthread_mutex_lock (&(xtp->svccntlock));
|
||||
xtp->svccnt--;
|
||||
pthread_mutex_unlock (&(xtp->svccntlock));
|
||||
}
|
||||
|
||||
static void *
|
||||
do_callsvc (void *rqst)
|
||||
{
|
||||
struct my_svc_req *mr = (struct my_svc_req *) rqst;
|
||||
struct xt_priv *xtp = (struct xt_priv *)mr->xprt->txp_private;
|
||||
|
||||
/* Let the transport know we exist */
|
||||
svc_up (xtp);
|
||||
|
||||
/* Usurp the readlock */
|
||||
xtp->readlock_by = pthread_self ();
|
||||
|
||||
dfprintf ((stderr, "do_callsvc: xid=%lx\n", mr->m.rm_xid));
|
||||
|
||||
/* Call the dispatch */
|
||||
(*(mr->dispatch))(&(mr->r), mr->r.rq_xprt);
|
||||
|
||||
dfprintf ((stderr, "do_callsvc return: xid=%lx\n", mr->m.rm_xid));
|
||||
|
||||
/* Now figure out if we need to unlock the readlock */
|
||||
if (xtp->readlock_is_locked && pthread_equal (xtp->readlock_by,
|
||||
pthread_self ()))
|
||||
unlock_readlock (mr->xprt);
|
||||
|
||||
/* Clean up and exit */
|
||||
free (mr);
|
||||
svc_down (xtp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* The Magic reader. When this function is called we KNOW we have
|
||||
* input but we don't know to whom the message is intended. It may be
|
||||
* either a request or a response; if it is a request, we need to call
|
||||
* out to the service. If it is a response, we need to wakeup the
|
||||
* calling thread.
|
||||
*
|
||||
* NB: Assumes readlock is already held.
|
||||
* NB: Assumes xdrlock is NOT held.
|
||||
* Will release lock(s) as necessary on return
|
||||
*/
|
||||
static bool_t
|
||||
xprt_thrd_doread (TXPRT *xprt)
|
||||
{
|
||||
struct xt_priv *xtp = (struct xt_priv *)xprt->txp_private;
|
||||
struct rpc_msg msg;
|
||||
XDR *xdr = xprt_thrd_get_xdr (xprt); /* Grab and Lock XDR! */
|
||||
|
||||
xdr->x_op = XDR_DECODE;
|
||||
|
||||
/* First, we need to make sure we're at the beginning of the record */
|
||||
if (!xdrrec_skiprecord (xdr)) {
|
||||
xprt_thrd_rel_xdr (xprt);
|
||||
dfprintf ((stderr, "xprt_thrd_doread: skiprecord failed\n"));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (xdr_u_long (xdr, &(msg.rm_xid)) &&
|
||||
xdr_enum (xdr, (enum_t *) &(msg.rm_direction))) {
|
||||
/* Ok, we've got our x_id and direction... */
|
||||
|
||||
dfprintf ((stderr, "xprt_thrd_doread: %s for %lx\n",
|
||||
(msg.rm_direction == CALL ? "call" :
|
||||
(msg.rm_direction == REPLY ? "reply" : "(unk)")),
|
||||
msg.rm_xid));
|
||||
|
||||
if (msg.rm_direction == CALL) {
|
||||
struct my_svc_req *mr = (struct my_svc_req *) malloc (sizeof (*mr));
|
||||
if (mr == NULL) {
|
||||
xprt_thrd_rel_xdr (xprt);
|
||||
fprintf (stderr, "xprt_thrd_doread: malloc failed\n");
|
||||
return FALSE;
|
||||
}
|
||||
memset (mr, 0, sizeof (*mr));
|
||||
mr->xprt = xprt;
|
||||
mr->m.rm_xid = msg.rm_xid;
|
||||
mr->m.rm_direction = msg.rm_direction;
|
||||
mr->m.rm_call.cb_cred.oa_base = mr->cred;
|
||||
mr->m.rm_call.cb_verf.oa_base = &(mr->cred[MAX_AUTH_BYTES]);
|
||||
mr->r.rq_clntcred = &(mr->cred[2 * MAX_AUTH_BYTES]);
|
||||
|
||||
if (read_call (xdr, &(mr->m))) {
|
||||
struct xt_svc *s;
|
||||
enum auth_stat why;
|
||||
bool_t found = FALSE;
|
||||
u_long low = 0, high = 0;
|
||||
|
||||
mr->r.rq_prog = mr->m.rm_call.cb_prog;
|
||||
mr->r.rq_vers = mr->m.rm_call.cb_vers;
|
||||
mr->r.rq_proc = mr->m.rm_call.cb_proc;
|
||||
mr->r.rq_cred = mr->m.rm_call.cb_cred;
|
||||
mr->r.rq_xprt = xprt_get_svcxprt (xprt);
|
||||
|
||||
/* Setup for reply */
|
||||
SVC_RECV (mr->r.rq_xprt, &(mr->m));
|
||||
|
||||
if ((why = _authenticate (&(mr->r), &(mr->m))) != AUTH_OK)
|
||||
{
|
||||
xprt_thrd_rel_xdr (xprt);
|
||||
svcerr_auth (mr->r.rq_xprt, why);
|
||||
goto call_done;
|
||||
}
|
||||
/* now match message with a registered service */
|
||||
s = svc_find (xtp, mr->r.rq_prog, mr->r.rq_vers, &found, &low, &high);
|
||||
if (s != NULL) {
|
||||
pthread_t thr;
|
||||
mr->dispatch = s->dispatch;
|
||||
xprt_thrd_unlock_xdr (xtp);
|
||||
dfprintf ((stderr, "xprt_thrd_doread: calling proc for xid %lx\n",
|
||||
msg.rm_xid));
|
||||
pthread_create (&thr, &(xtp->pattr), do_callsvc, mr);
|
||||
mr = NULL;
|
||||
goto call_done;
|
||||
} else {
|
||||
/* Or unlock XDR for to respond in error */
|
||||
xprt_thrd_rel_xdr (xprt);
|
||||
}
|
||||
/*
|
||||
* if we got here, the program or version
|
||||
* is not served ...
|
||||
*/
|
||||
/* First we clear the locks */
|
||||
|
||||
if (found)
|
||||
svcerr_progvers (mr->r.rq_xprt, low, high);
|
||||
else
|
||||
svcerr_noprog (mr->r.rq_xprt);
|
||||
/* Fall through to ... */
|
||||
|
||||
call_done:
|
||||
/* If we're here, then we had a "successful" call */
|
||||
if (mr != NULL)
|
||||
free (mr);
|
||||
|
||||
return TRUE;
|
||||
} /* !read_call() */
|
||||
|
||||
if (mr != NULL)
|
||||
free (mr);
|
||||
|
||||
dfprintf ((stderr, "xprt_thrd_doread: read_call failed, %lx\n",
|
||||
msg.rm_xid));
|
||||
} else if (msg.rm_direction == REPLY) {
|
||||
/* Find the requestor thread.. */
|
||||
TXPRT_WAIT *wait = find_wait(xtp, msg.rm_xid);
|
||||
if (wait != NULL) {
|
||||
struct tw_priv *twp = (struct tw_priv *) wait->tw_priv;
|
||||
twp->repl->rm_xid = msg.rm_xid;
|
||||
twp->repl->rm_direction = msg.rm_direction;
|
||||
if (read_reply (xdr, twp->repl)) {
|
||||
pthread_cond_signal (&twp->cond);
|
||||
/* Now unlock the lock to let the other thread pick it up.
|
||||
Note, keep the readlock locked! */
|
||||
xprt_thrd_unlock_xdr (xtp);
|
||||
return TRUE;
|
||||
}
|
||||
/* If we got here, then the reply didn't read correctly */
|
||||
dfprintf ((stderr, "Reply didn't read correctly\n"));
|
||||
}
|
||||
/* If we got here, then either the reply didn't read or there is
|
||||
no matching xid */
|
||||
dfprintf ((stderr, "bad reply or no matching xid, %lx\n", msg.rm_xid));
|
||||
}
|
||||
/*
|
||||
* If we got here, either the direction is bogus or the message
|
||||
* didn't parse correctly
|
||||
*/
|
||||
dfprintf ((stderr, "bad message or bad direction\n"));
|
||||
}
|
||||
/* If we got here, then we've got a bad message */
|
||||
xprt_thrd_rel_xdr (xprt);
|
||||
dfprintf ((stderr, "xprt_thrd_doread: bad message\n"));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void *
|
||||
do_select_loop (void *xprt)
|
||||
{
|
||||
struct xt_priv *xtp = (struct xt_priv *)((TXPRT *)xprt)->txp_private;
|
||||
int (*select)(caddr_t) = xtp->select;
|
||||
caddr_t arg = xtp->selectarg;
|
||||
|
||||
/* Get the readlock */
|
||||
xprt_thrd_readlock (xprt);
|
||||
|
||||
while (1) {
|
||||
if (xtp->stop) {
|
||||
unlock_readlock (xprt);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* now see if there is anything to read */
|
||||
switch ((*select)(arg)) {
|
||||
case -1:
|
||||
/* error */
|
||||
if (errno == EINTR)
|
||||
break;
|
||||
perror ("xprt_thrd: xprt_do_select_loop: select failed");
|
||||
unlock_readlock (xprt);
|
||||
return NULL;
|
||||
|
||||
case 0:
|
||||
/* timeout */
|
||||
continue;
|
||||
|
||||
default:
|
||||
/* Go read the data */
|
||||
if (!xprt_thrd_doread (xprt)) {
|
||||
/* We Died */
|
||||
xtp->stop = 1;
|
||||
if (xtp->close)
|
||||
xtp->close (xtp->selectarg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Then wait until the reading is done */
|
||||
xprt_thrd_readlock (xprt);
|
||||
|
||||
} /* select */
|
||||
} /* while */
|
||||
}
|
||||
|
||||
static void
|
||||
xprt_thrd_run (TXPRT *xprt)
|
||||
{
|
||||
struct xt_priv *xtp = (struct xt_priv *)xprt->txp_private;
|
||||
|
||||
xtp->stop = FALSE;
|
||||
|
||||
/* start thread */
|
||||
pthread_create (&(xtp->select_thread), NULL, do_select_loop, xprt);
|
||||
}
|
191
src/engine/rpc/xprt_thrd.h
Normal file
191
src/engine/rpc/xprt_thrd.h
Normal file
@ -0,0 +1,191 @@
|
||||
/*
|
||||
* xprt_thrd.h
|
||||
*
|
||||
* Threaded TCP Transport scheme for RPC; allow a client and a service
|
||||
* to share a single (TCP) stream. A single reader/writer thread
|
||||
* exists to handle to I/O. Writers obtain a write-lock before
|
||||
* writing to the stream, and 'readers' setup a callback with the I/O
|
||||
* thread which wakes them up when a particular request (or reply) is
|
||||
* received.
|
||||
*
|
||||
* Created by: Derek Atkins <warlord@MIT.EDU>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef RPC_XPRT_THRD_H
|
||||
#define RPC_XPRT_THRD_H
|
||||
|
||||
#include <rpc/types.h>
|
||||
#include <rpc/xdr.h>
|
||||
#include <rpc/clnt.h>
|
||||
#include <rpc/svc.h>
|
||||
#include <rpc/rpc_msg.h>
|
||||
|
||||
typedef struct TXPRT_WAIT TXPRT_WAIT;
|
||||
|
||||
typedef struct TXPRT TXPRT;
|
||||
struct TXPRT {
|
||||
const struct txprt_ops {
|
||||
/*
|
||||
* PUBLIC interfaces
|
||||
*/
|
||||
|
||||
CLIENT * (*txp_new_clnt) (TXPRT *xprt, u_long prog, u_long vers);
|
||||
/* build and return a new CLIENT object on this transport */
|
||||
|
||||
bool_t (*txp_reg_call) (TXPRT *xprt, u_long prog, u_long vers,
|
||||
void (*dispatch) (struct svc_req *,
|
||||
SVCXPRT *));
|
||||
/* register a callout to a service/dispatch */
|
||||
|
||||
caddr_t (*txp_get_sock) (TXPRT *xprt);
|
||||
/* Return the socket pointer for a transport */
|
||||
|
||||
void (*txp_destroy) (TXPRT *xprt);
|
||||
/* destroy a threaded transport (does NOT destroy CLIENTs nor does
|
||||
it call close() */
|
||||
|
||||
/*
|
||||
* Semi-Private interfaces
|
||||
* (for use in writing your own select loop)
|
||||
*/
|
||||
|
||||
void (*txp_readlock) (TXPRT *xptr);
|
||||
/* Obtain the read lock on the underlying transport */
|
||||
|
||||
bool_t (*txp_doread) (TXPRT *xptr);
|
||||
/* When the read-lock is held and data exists on this transport, read it */
|
||||
|
||||
/*
|
||||
* PRIVATE interfaces
|
||||
*/
|
||||
|
||||
XDR * (*txp_get_xdr) (TXPRT *xprt);
|
||||
/* get and lock the XDR object (blocks until lock can be obtained) */
|
||||
void (*txp_rel_xdr) (TXPRT *xprt);
|
||||
/* release/unlock the XDR object */
|
||||
|
||||
void (*txp_wait_rep) (TXPRT *xprt, struct rpc_msg *reply_msg,
|
||||
XDR **xdrs, TXPRT_WAIT *wait,
|
||||
struct timeval timeout);
|
||||
/* wait for a reply to the call, return with reply_msg filled
|
||||
* and xdrs filled and locked */
|
||||
} *txp_ops;
|
||||
caddr_t txp_private; /* Private Stuff */
|
||||
};
|
||||
|
||||
/*
|
||||
* operations on a Threaded Transport handle:
|
||||
*
|
||||
* TXPRT *xprt
|
||||
* u_long prog, vers
|
||||
* void (*)(struct svc_req *, SVCSPRT *) disp
|
||||
*/
|
||||
|
||||
/*
|
||||
* PUBLIC Interfaces
|
||||
*/
|
||||
|
||||
#define TXPRT_NEW_CLIENT(xprt,prog,vers) \
|
||||
(*(xprt)->txp_ops->txp_new_clnt)((xprt),(prog),(vers))
|
||||
#define txprt_new_client(xprt,prog,vers) \
|
||||
(*(xprt)->txp_ops->txp_new_clnt)((xprt),(prog),(vers))
|
||||
|
||||
#define TXPRT_REG_CALLOUT(xprt,prog,vers,disp) \
|
||||
(*(xprt)->txp_ops->txp_reg_call)((xprt),(prog),(vers),(disp))
|
||||
#define txprt_reg_callout(xprt,prog,vers,disp) \
|
||||
(*(xprt)->txp_ops->txp_reg_call)((xprt),(prog),(vers),(disp))
|
||||
|
||||
#define TXPRT_DESTROY(xprt) (*(xprt)->txp_ops->txp_destroy)(xprt)
|
||||
#define txprt_destroy(xprt) (*(xprt)->txp_ops->txp_destroy)(xprt)
|
||||
|
||||
#define TXPRT_GETSOCK(xprt) (*(xprt)->txp_ops->txp_get_sock)(xprt)
|
||||
#define txprt_getsock(xprt) (*(xprt)->txp_ops->txp_get_sock)(xprt)
|
||||
|
||||
/*
|
||||
* Semi-Private interfaces
|
||||
* (for use in writing your own select loop)
|
||||
*
|
||||
* NOTE: you must lock readlock and then call doread with the lock held;
|
||||
* doread will unlock the readlock when it is done.
|
||||
*/
|
||||
|
||||
#define TXPRT_READLOCK(xprt) (*(xprt)->txp_ops->txp_readlock)(xprt)
|
||||
#define txprt_readlock(xprt) (*(xprt)->txp_ops->txp_readlock)(xprt)
|
||||
|
||||
#define TXPRT_DOREAD(xprt) (*(xprt)->txp_ops->txp_doread)(xprt)
|
||||
#define txprt_doread(xprt) (*(xprt)->txp_ops->txp_doread)(xprt)
|
||||
|
||||
/*
|
||||
* Transport Creation/Destruction
|
||||
*/
|
||||
|
||||
extern TXPRT * xprt_thrd_create (caddr_t sock,
|
||||
int (*read)(caddr_t, char *, int),
|
||||
int (*write)(caddr_t, char *, int),
|
||||
void (*close)(caddr_t),
|
||||
int (*select)(caddr_t),
|
||||
u_int sendsz, u_int recvsz);
|
||||
|
||||
/*
|
||||
* This is an xdrproc_t function that, when passed into a threaded
|
||||
* SVCXPRT will obtain the TXPRT * from the service. It should be
|
||||
* used by service routines if they want to obtain the TXPRT * to
|
||||
* e.g. build a CLIENT for a callback from a request. It should be
|
||||
* used like this:
|
||||
*
|
||||
* SVCXPRT *svc
|
||||
* TXPRT *xprt
|
||||
* bool_t res
|
||||
*
|
||||
* res = svc_getargs(svc, (xdrproc_t) xprt_thrd_getargs_hook,
|
||||
* (caddr_t) &xprt);
|
||||
*
|
||||
* If res == TRUE, then xprt is a valid pointer. If res == FALSE,
|
||||
* then svc is not a threaded transport service (and xprt is invalid)
|
||||
*/
|
||||
extern bool_t xprt_thrd_getargs_hook (XDR *, void *);
|
||||
|
||||
|
||||
/*
|
||||
* PRIVATE Interfaces
|
||||
*/
|
||||
|
||||
struct TXPRT_WAIT {
|
||||
u_long tw_x_id;
|
||||
void (*tw_destroy)(TXPRT_WAIT *);
|
||||
caddr_t tw_priv;
|
||||
};
|
||||
|
||||
/*
|
||||
* operations on a Threaded Transport handle:
|
||||
*
|
||||
* TXPRT *xprt
|
||||
* XDR **xdrp
|
||||
* struct rpc_msg *rmsg
|
||||
* TXPRT_WAIT *wait (with tw_x_id filled in with the request ID)
|
||||
*/
|
||||
|
||||
/* Returns and locks XDR */
|
||||
#define TXPRT_GET_XDR(xprt) (*(xprt)->txp_ops->txp_get_xdr)(xprt)
|
||||
#define txprt_get_xdr(xprt) (*(xprt)->txp_ops->txp_get_xdr)(xprt)
|
||||
|
||||
/* Unlocks XDR (and potentially the ReadLock if x_op == XDR_DECODE */
|
||||
#define TXPRT_REL_XDR(xprt) (*(xprt)->txp_ops->txp_rel_xdr)(xprt)
|
||||
#define txprt_rel_xdr(xprt) (*(xprt)->txp_ops->txp_rel_xdr)(xprt)
|
||||
|
||||
/* Wait for a reply -- blocks until we get a response for wait->tw_x_id */
|
||||
#define TXPRT_WAIT_REPLY(xprt,rmsg,xdrp,wait,time) \
|
||||
(*(xprt)->txp_ops->txp_wait_rep)((xprt),(rmsg),(xdrp),(wait),(time))
|
||||
#define txprt_wait_reply(xprt,rmsg,xdrp,wait,time) \
|
||||
(*(xprt)->txp_ops->txp_wait_rep)((xprt),(rmsg),(xdrp),(wait),(time))
|
||||
|
||||
|
||||
/* create a wait object (for use in the client) */
|
||||
extern TXPRT_WAIT * xprt_thrd_new_wait (void);
|
||||
|
||||
/* Destroy a wait object */
|
||||
#define XPRT_DESTROY_WAIT(wait) (*(wait)->tw_destroy)(wait)
|
||||
#define xprt_destroy_wait(wait) (*(wait)->tw_destroy)(wait)
|
||||
|
||||
#endif /* RPC_XPRT_THRD_H */
|
@ -211,7 +211,14 @@
|
||||
'boolean
|
||||
gnc:load-system-config-if-needed
|
||||
#f
|
||||
(N_ "Load the system configuation"))))
|
||||
(N_ "Load the system configuation"))
|
||||
|
||||
(list "rpc-server"
|
||||
'boolean
|
||||
(lambda (val)
|
||||
(if val (gnc:run-rpc-server)))
|
||||
#f
|
||||
(N_ "Run the RPC Server"))))
|
||||
|
||||
(define (gnc:cmd-line-get-boolean-arg args)
|
||||
;; --arg means #t
|
||||
|
Loading…
Reference in New Issue
Block a user