1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 1998,2008 Oracle.  All rights reserved.
5 *
6 * $Id: xa.c,v 12.21 2008/01/08 20:59:00 bostic Exp $
7 */
8
9#include "db_config.h"
10
11#include "db_int.h"
12#include "dbinc/txn.h"
13
14static int __db_xa_close __P((char *, int, long));
15static int __db_xa_commit __P((XID *, int, long));
16static int __db_xa_complete __P((int *, int *, int, long));
17static int __db_xa_end __P((XID *, int, long));
18static int __db_xa_forget __P((XID *, int, long));
19static int __db_xa_open __P((char *, int, long));
20static int __db_xa_prepare __P((XID *, int, long));
21static int __db_xa_recover __P((XID *, long, int, long));
22static int __db_xa_rollback __P((XID *, int, long));
23static int __db_xa_start __P((XID *, int, long));
24static int __xa_txn_continue __P((ENV *, DB_TXN *, TXN_DETAIL *));
25static int __xa_put_txn __P((ENV *, DB_TXN *));
26static int __xa_txn_get_prepared
27	       __P((ENV *, XID *, DB_PREPLIST *, long, long *, u_int32_t));
28
29/*
30 * Possible flag values:
31 *	Dynamic registration	0 => no dynamic registration
32 *				TMREGISTER => dynamic registration
33 *	Asynchronous operation	0 => no support for asynchrony
34 *				TMUSEASYNC => async support
35 *	Migration support	0 => migration of transactions across
36 *				     threads is possible
37 *				TMNOMIGRATE => no migration across threads
38 */
39const struct xa_switch_t db_xa_switch = {
40	 "Berkeley DB",		/* name[RMNAMESZ] */
41	 TMNOMIGRATE,		/* flags */
42	 0,			/* version */
43	 __db_xa_open,		/* xa_open_entry */
44	 __db_xa_close,		/* xa_close_entry */
45	 __db_xa_start,		/* xa_start_entry */
46	 __db_xa_end,		/* xa_end_entry */
47	 __db_xa_rollback,	/* xa_rollback_entry */
48	 __db_xa_prepare,	/* xa_prepare_entry */
49	 __db_xa_commit,	/* xa_commit_entry */
50	 __db_xa_recover,	/* xa_recover_entry */
51	 __db_xa_forget,	/* xa_forget_entry */
52	 __db_xa_complete	/* xa_complete_entry */
53};
54
55/*
56 * If you want your XA server to be multi-threaded, then you must (at least)
57 * edit this file and change:
58 *	#undef  XA_MULTI_THREAD
59 * to:
60 *	#define XA_MULTI_THREAD 1
61 */
62#undef XA_MULTI_THREAD
63
64/*
65 * __xa_get_txn --
66 *	Return a pointer to the current transaction structure for the
67 * designated environment.  If do_init is non-zero and we don't find a
68 * structure for the current thread, then create a new structure for it.
69 *
70 * PUBLIC: int __xa_get_txn __P((ENV *, DB_TXN **, int));
71 */
72int
73__xa_get_txn(env, txnp, do_init)
74	ENV *env;
75	DB_TXN **txnp;
76	int do_init;
77{
78#ifdef XA_MULTI_THREAD
79	DB_TXN *t;
80	DB_TXNMGR *mgr;
81	TXN_DETAIL *td;
82	db_threadid_t tid;
83	pid_t pid;
84#endif
85	int ret;
86
87	ret = 0;
88
89#ifdef XA_MULTI_THREAD
90	env->thread_id(env, &pid, &tid);
91	*txnp = NULL;
92
93	DB_ASSERT(env, env->tx_handle != NULL);
94	mgr = env->tx_handle;
95
96	/*
97	 * We need to protect the xa_txn linked list, but the environment does
98	 * not have a mutex.  Since we are in an XA transaction environment,
99	 * we know there is a transaction structure, we can use its mutex.
100	 */
101	MUTEX_LOCK(env, mgr->mutex);
102	TAILQ_FOREACH(t, &env->xa_txn, xalinks) {
103		td = t->td;
104		if (td->pid != pid)
105			continue;
106#ifdef HAVE_SIMPLE_THREAD_TYPE
107		if (t->tid == tid) {
108			*txnp = t;
109			break;
110		}
111#else
112		if (memcmp(&t->tid, &tid, sizeof(tid)) == 0) {
113			*txnp = t;
114			break;
115		}
116#endif
117	}
118	MUTEX_UNLOCK(env, mgr->mutex);
119
120	if (*txnp == NULL) {
121		if (!do_init)
122			ret = EINVAL;
123		else if ((ret =
124		    __os_malloc(env, sizeof(DB_TXN), txnp)) == 0) {
125			(*txnp)->tid = tid;
126			MUTEX_LOCK(env, mgr->mutex);
127			TAILQ_INSERT_HEAD(&env->xa_txn, *txnp, xalinks);
128			MUTEX_UNLOCK(env, mgr->mutex);
129		}
130	}
131#else
132	COMPQUIET(do_init, 0);
133
134	*txnp = TAILQ_FIRST(&env->xa_txn);
135	if (*txnp == NULL &&
136	    (ret = __os_calloc(env, 1, sizeof(DB_TXN), txnp)) == 0) {
137		(*txnp)->txnid = TXN_INVALID;
138		TAILQ_INSERT_HEAD(&env->xa_txn, *txnp, xalinks);
139	}
140#endif
141
142	return (ret);
143}
144
145static int
146__xa_put_txn(env, txnp)
147	ENV *env;
148	DB_TXN *txnp;
149{
150#ifdef XA_MULTI_THREAD
151	DB_TXNMGR *mgr;
152	mgr = env->tx_handle;
153
154	MUTEX_LOCK(env, mgr->mutex);
155	TAILQ_REMOVE(&env->xa_txn, txnp, xalinks);
156	MUTEX_UNLOCK(env, mgr->mutex);
157	__os_free(env, txnp);
158#else
159	COMPQUIET(env, NULL);
160	txnp->txnid = TXN_INVALID;
161#endif
162	return (0);
163}
164
165/*
166 * __xa_txn_continue --
167 *	This call wraps the __txn_continue function with the environment
168 * wrapper, so we can properly failchk environments running XA.
169 */
170static int
171__xa_txn_continue(env, txn, td)
172	ENV *env;
173	DB_TXN *txn;
174	TXN_DETAIL *td;
175{
176	DB_THREAD_INFO *ip;
177	int ret;
178
179	ENV_ENTER(env, ip);
180	ret = __txn_continue(env, txn, td);
181	ENV_LEAVE(env, ip);
182
183	return (ret);
184}
185
186/*
187 * __xa_txn_get_prepared --
188 *	Wrap the internal call to __txn_get_prepared so that we can call
189 * it from XA. XA routines are not considered to be running "inside" the
190 * library, so when they make calls into the library, we need to use interface
191 * routines that support replication and failchk.  Since __txn_get_prepared
192 * is internal, there is no user API to call, so we use this wrapper routine
193 * instead.
194 */
195static int
196__xa_txn_get_prepared(env, xids, txns, count, retp, flags)
197	ENV *env;
198	XID *xids;
199	DB_PREPLIST *txns;
200	long count;		/* This is long for XA compatibility. */
201	long *retp;
202	u_int32_t flags;
203{
204	DB_THREAD_INFO *ip;
205	int ret;
206
207	ENV_ENTER(env, ip);
208	REPLICATION_WRAP(env,
209	    (__txn_get_prepared(env, xids, txns, count, retp, flags)), 0, ret);
210	ENV_LEAVE(env, ip);
211	return (ret);
212}
213
214#ifdef XA_MULTI_THREAD
215#define	XA_FLAGS \
216	(DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | \
217	DB_INIT_TXN | DB_THREAD)
218#else
219#define	XA_FLAGS \
220	(DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | \
221	DB_INIT_TXN)
222#endif
223
224/*
225 * __db_xa_open --
226 *	The open call in the XA protocol.  The rmid field is an id number
227 * that the TM assigned us and will pass us on every xa call.  We need to
228 * map that rmid number into a env structure that we create during
229 * initialization.  Since this id number is thread specific, we do not
230 * need to store it in shared memory.  The file xa_map.c implements all
231 * such xa->db mappings.
232 *	The xa_info field is instance specific information.  We require
233 * that the value of DB_HOME be passed in xa_info.  Since xa_info is the
234 * only thing that we get to pass to db_env_create, any config information
235 * will have to be done via a config file instead of via the db_env_create
236 * call.
237 */
238static int
239__db_xa_open(xa_info, rmid, arg_flags)
240	char *xa_info;
241	int rmid;
242	long arg_flags;
243{
244	DB_ENV *dbenv;
245	ENV *env;
246	u_long flags;
247
248	flags = (u_long)arg_flags;	/* Conversion for bit operations. */
249
250	if (LF_ISSET(TMASYNC))
251		return (XAER_ASYNC);
252	if (flags != TMNOFLAGS)
253		return (XAER_INVAL);
254
255	/* Verify if we already have this environment open. */
256	if (__db_rmid_to_env(rmid, &env) == 0)
257		return (XA_OK);
258
259	/* Open a new environment. */
260	if (db_env_create(&dbenv, 0) != 0)
261		return (XAER_RMERR);
262	env = dbenv->env;
263	if (dbenv->open(dbenv, xa_info, XA_FLAGS, 0) != 0)
264		goto err;
265
266	/* Create the mapping. */
267	if (__db_map_rmid(rmid, env) != 0)
268		goto err;
269
270	/* Allocate space for the current transaction. */
271	TAILQ_INIT(&env->xa_txn);
272
273	return (XA_OK);
274
275err:	(void)dbenv->close(dbenv, 0);
276
277	return (XAER_RMERR);
278}
279
280/*
281 * __db_xa_close --
282 *	The close call of the XA protocol.  The only trickiness here
283 * is that if there are any active transactions, we must fail.  It is
284 * *not* an error to call close on an environment that has already been
285 * closed (I am interpreting that to mean it's OK to call close on an
286 * environment that has never been opened).
287 */
288static int
289__db_xa_close(xa_info, rmid, arg_flags)
290	char *xa_info;
291	int rmid;
292	long arg_flags;
293{
294	DB_TXN *t;
295	ENV *env;
296	int ret, t_ret;
297	u_long flags;
298
299	COMPQUIET(xa_info, NULL);
300
301	flags = (u_long)arg_flags;	/* Conversion for bit operations. */
302
303	if (LF_ISSET(TMASYNC))
304		return (XAER_ASYNC);
305	if (flags != TMNOFLAGS)
306		return (XAER_INVAL);
307
308	/* If the environment is closed, then we're done. */
309	if (__db_rmid_to_env(rmid, &env) != 0)
310		return (XA_OK);
311
312	/* Check if there are any pending transactions. */
313	if ((t = TAILQ_FIRST(&env->xa_txn)) != NULL &&
314	    t->txnid != TXN_INVALID)
315		return (XAER_PROTO);
316
317	/* Destroy the mapping. */
318	ret = __db_unmap_rmid(rmid);
319
320	/* Discard space held for the current transaction. */
321	while ((t = TAILQ_FIRST(&env->xa_txn)) != NULL) {
322		TAILQ_REMOVE(&env->xa_txn, t, xalinks);
323		__os_free(env, t);
324	}
325
326	/* Close the environment. */
327	if ((t_ret = env->dbenv->close(env->dbenv, 0)) != 0 && ret == 0)
328		ret = t_ret;
329
330	return (ret == 0 ? XA_OK : XAER_RMERR);
331}
332
333/*
334 * __db_xa_start --
335 *	Begin a transaction for the current resource manager.
336 */
337static int
338__db_xa_start(xid, rmid, arg_flags)
339	XID *xid;
340	int rmid;
341	long arg_flags;
342{
343	DB_TXN *txnp;
344	ENV *env;
345	TXN_DETAIL *td;
346	roff_t off;
347	u_long flags;
348	int is_known;
349
350	flags = (u_long)arg_flags;	/* Conversion for bit operations. */
351
352#define	OK_FLAGS	(TMJOIN | TMRESUME | TMNOWAIT | TMASYNC | TMNOFLAGS)
353	if (LF_ISSET(~OK_FLAGS))
354		return (XAER_INVAL);
355
356	if (LF_ISSET(TMJOIN) && LF_ISSET(TMRESUME))
357		return (XAER_INVAL);
358
359	if (LF_ISSET(TMASYNC))
360		return (XAER_ASYNC);
361
362	if (__db_rmid_to_env(rmid, &env) != 0)
363		return (XAER_PROTO);
364
365	is_known = __db_xid_to_txn(env, xid, &off) == 0;
366
367	if (is_known && !LF_ISSET(TMRESUME) && !LF_ISSET(TMJOIN))
368		return (XAER_DUPID);
369
370	if (!is_known && LF_ISSET(TMRESUME | TMJOIN))
371		return (XAER_NOTA);
372
373	/*
374	 * This can't block, so we can ignore TMNOWAIT.
375	 *
376	 * Other error conditions: RMERR, RMFAIL, OUTSIDE, PROTO, RB*
377	 */
378	if (is_known) {
379		td = R_ADDR(&env->tx_handle->reginfo, off);
380		if (td->xa_status == TXN_XA_SUSPENDED &&
381		    !LF_ISSET(TMRESUME | TMJOIN))
382			return (XAER_PROTO);
383		if (td->xa_status == TXN_XA_DEADLOCKED)
384			return (XA_RBDEADLOCK);
385		if (td->xa_status == TXN_XA_ABORTED)
386			return (XA_RBOTHER);
387
388		/* Now, fill in the global transaction structure. */
389		if (__xa_get_txn(env, &txnp, 1) != 0)
390			return (XAER_RMERR);
391		if (__xa_txn_continue(env, txnp, td) != 0)
392			return (XAER_RMERR);
393		td->xa_status = TXN_XA_STARTED;
394	} else {
395		if (__xa_get_txn(env, &txnp, 1) != 0)
396			return (XAER_RMERR);
397		if (__txn_xa_begin(env, txnp))
398			return (XAER_RMERR);
399		(void)__db_map_xid(env, xid, txnp->td);
400		td = txnp->td;
401		td->xa_status = TXN_XA_STARTED;
402	}
403	return (XA_OK);
404}
405
406/*
407 * __db_xa_end --
408 *	Disassociate the current transaction from the current process.
409 */
410static int
411__db_xa_end(xid, rmid, flags)
412	XID *xid;
413	int rmid;
414	long flags;
415{
416	DB_TXN *txn;
417	ENV *env;
418	TXN_DETAIL *td;
419	roff_t off;
420
421	if (flags != TMNOFLAGS && !LF_ISSET(TMSUSPEND | TMSUCCESS | TMFAIL))
422		return (XAER_INVAL);
423
424	if (__db_rmid_to_env(rmid, &env) != 0)
425		return (XAER_PROTO);
426
427	if (__db_xid_to_txn(env, xid, &off) != 0)
428		return (XAER_NOTA);
429
430	if (__xa_get_txn(env, &txn, 0) != 0)
431		return (XAER_RMERR);
432
433	td = R_ADDR(&env->tx_handle->reginfo, off);
434	if (td != txn->td)
435		return (XAER_PROTO);
436
437	if (td->xa_status == TXN_XA_DEADLOCKED)
438		return (XA_RBDEADLOCK);
439
440	if (td->status == TXN_ABORTED)
441		return (XA_RBOTHER);
442
443	if (td->xa_status != TXN_XA_STARTED)
444		return (XAER_PROTO);
445
446	/*
447	 * If we ever support XA migration, we cannot keep SUSPEND/END
448	 * status in the shared region; it would have to be process local.
449	 */
450	if (LF_ISSET(TMSUSPEND))
451		td->xa_status = TXN_XA_SUSPENDED;
452	else
453		td->xa_status = TXN_XA_ENDED;
454
455	/*
456	 * XXX
457	 * This can fail in XA_MULTI_THREAD mode.
458	 */
459	(void)__xa_put_txn(env, txn);
460	return (XA_OK);
461}
462
463/*
464 * __db_xa_prepare --
465 *	Sync the log to disk so we can guarantee recoverability.
466 */
467static int
468__db_xa_prepare(xid, rmid, arg_flags)
469	XID *xid;
470	int rmid;
471	long arg_flags;
472{
473	DB_TXN *txnp;
474	ENV *env;
475	TXN_DETAIL *td;
476	roff_t off;
477	u_long flags;
478
479	flags = (u_long)arg_flags;	/* Conversion for bit operations. */
480
481	if (LF_ISSET(TMASYNC))
482		return (XAER_ASYNC);
483	if (flags != TMNOFLAGS)
484		return (XAER_INVAL);
485
486	/*
487	 * We need to know if we've ever called prepare on this.
488	 * As part of the prepare, we set the xa_status field to
489	 * reflect that fact that prepare has been called, and if
490	 * it's ever called again, it's an error.
491	 */
492	if (__db_rmid_to_env(rmid, &env) != 0)
493		return (XAER_PROTO);
494
495	if (__db_xid_to_txn(env, xid, &off) != 0)
496		return (XAER_NOTA);
497	td = R_ADDR(&env->tx_handle->reginfo, off);
498	if (td->xa_status == TXN_XA_DEADLOCKED)
499		return (XA_RBDEADLOCK);
500
501	if (td->xa_status != TXN_XA_ENDED && td->xa_status != TXN_XA_SUSPENDED)
502		return (XAER_PROTO);
503
504	/* Now, fill in the global transaction structure. */
505	if (__xa_get_txn(env, &txnp, 0) != 0)
506		return (XAER_PROTO);
507	if (__xa_txn_continue(env, txnp, td) != 0)
508		return (XAER_RMERR);
509
510	if (txnp->prepare(txnp, (u_int8_t *)xid->data) != 0)
511		return (XAER_RMERR);
512
513	td->xa_status = TXN_XA_PREPARED;
514
515	/*
516	 * XXX
517	 * This can fail in XA_MULTI_THREAD mode.
518	 */
519	(void)__xa_put_txn(env, txnp);
520	return (XA_OK);
521}
522
523/*
524 * __db_xa_commit --
525 *	Commit the transaction
526 */
527static int
528__db_xa_commit(xid, rmid, arg_flags)
529	XID *xid;
530	int rmid;
531	long arg_flags;
532{
533	DB_TXN *txnp;
534	ENV *env;
535	TXN_DETAIL *td;
536	roff_t off;
537	u_long flags;
538
539	flags = (u_long)arg_flags;	/* Conversion for bit operations. */
540
541	if (LF_ISSET(TMASYNC))
542		return (XAER_ASYNC);
543#undef	OK_FLAGS
544#define	OK_FLAGS	(TMNOFLAGS | TMNOWAIT | TMONEPHASE)
545	if (LF_ISSET(~OK_FLAGS))
546		return (XAER_INVAL);
547
548	/*
549	 * We need to know if we've ever called prepare on this.
550	 * We can verify this by examining the xa_status field.
551	 */
552	if (__db_rmid_to_env(rmid, &env) != 0)
553		return (XAER_PROTO);
554
555	if (__db_xid_to_txn(env, xid, &off) != 0)
556		return (XAER_NOTA);
557
558	td = R_ADDR(&env->tx_handle->reginfo, off);
559	if (td->xa_status == TXN_XA_DEADLOCKED)
560		return (XA_RBDEADLOCK);
561
562	if (td->xa_status == TXN_XA_ABORTED)
563		return (XA_RBOTHER);
564
565	if (LF_ISSET(TMONEPHASE) &&
566	    td->xa_status != TXN_XA_ENDED && td->xa_status != TXN_XA_SUSPENDED)
567		return (XAER_PROTO);
568
569	if (!LF_ISSET(TMONEPHASE) && td->xa_status != TXN_XA_PREPARED)
570		return (XAER_PROTO);
571
572	/* Now, fill in the global transaction structure. */
573	if (__xa_get_txn(env, &txnp, 0) != 0)
574		return (XAER_RMERR);
575	if (__xa_txn_continue(env, txnp, td) != 0)
576		return (XAER_RMERR);
577
578	if (txnp->commit(txnp, 0) != 0)
579		return (XAER_RMERR);
580
581	/*
582	 * XXX
583	 * This can fail in XA_MULTI_THREAD mode.
584	 */
585	(void)__xa_put_txn(env, txnp);
586	return (XA_OK);
587}
588
589/*
590 * __db_xa_recover --
591 *	Returns a list of prepared and heuristically completed transactions.
592 *
593 * The return value is the number of xids placed into the xid array (less
594 * than or equal to the count parameter).  The flags are going to indicate
595 * whether we are starting a scan or continuing one.
596 */
597static int
598__db_xa_recover(xids, count, rmid, flags)
599	XID *xids;
600	long count, flags;
601	int rmid;
602{
603	ENV *env;
604	u_int32_t newflags;
605	long rval;
606
607	/* If the environment is closed, then we're done. */
608	if (__db_rmid_to_env(rmid, &env) != 0)
609		return (XAER_PROTO);
610
611	if (LF_ISSET(TMSTARTRSCAN))
612		newflags = DB_FIRST;
613	else if (LF_ISSET(TMENDRSCAN))
614		newflags = DB_LAST;
615	else
616		newflags = DB_NEXT;
617
618	rval = 0;
619	if (__xa_txn_get_prepared(env,
620	    xids, NULL, count, &rval, newflags) != 0)
621		return (XAER_RMERR);
622	else
623		return (rval);
624}
625
626/*
627 * __db_xa_rollback
628 *	Abort an XA transaction.
629 */
630static int
631__db_xa_rollback(xid, rmid, arg_flags)
632	XID *xid;
633	int rmid;
634	long arg_flags;
635{
636	DB_TXN *txnp;
637	ENV *env;
638	TXN_DETAIL *td;
639	roff_t off;
640	u_long flags;
641
642	flags = (u_long)arg_flags;	/* Conversion for bit operations. */
643
644	if (LF_ISSET(TMASYNC))
645		return (XAER_ASYNC);
646	if (flags != TMNOFLAGS)
647		return (XAER_INVAL);
648
649	if (__db_rmid_to_env(rmid, &env) != 0)
650		return (XAER_PROTO);
651
652	if (__db_xid_to_txn(env, xid, &off) != 0)
653		return (XAER_NOTA);
654
655	td = R_ADDR(&env->tx_handle->reginfo, off);
656	if (td->xa_status == TXN_XA_DEADLOCKED)
657		return (XA_RBDEADLOCK);
658
659	if (td->xa_status == TXN_XA_ABORTED)
660		return (XA_RBOTHER);
661
662	if (td->xa_status != TXN_XA_ENDED &&
663	    td->xa_status != TXN_XA_SUSPENDED &&
664	    td->xa_status != TXN_XA_PREPARED)
665		return (XAER_PROTO);
666
667	/* Now, fill in the global transaction structure. */
668	if (__xa_get_txn(env, &txnp, 0) != 0)
669		return (XAER_RMERR);
670	if (__xa_txn_continue(env, txnp, td) != 0)
671		return (XAER_RMERR);
672	if (txnp->abort(txnp) != 0)
673		return (XAER_RMERR);
674
675	/*
676	 * XXX
677	 * This can fail in XA_MULTI_THREAD mode.
678	 */
679	(void)__xa_put_txn(env, txnp);
680	return (XA_OK);
681}
682
683/*
684 * __db_xa_forget --
685 *	Forget about an XID for a transaction that was heuristically
686 * completed.  Since we do not heuristically complete anything, I
687 * don't think we have to do anything here, but we should make sure
688 * that we reclaim the slots in the txnid table.
689 */
690static int
691__db_xa_forget(xid, rmid, arg_flags)
692	XID *xid;
693	int rmid;
694	long arg_flags;
695{
696	ENV *env;
697	roff_t off;
698	u_long flags;
699
700	flags = (u_long)arg_flags;	/* Conversion for bit operations. */
701
702	if (LF_ISSET(TMASYNC))
703		return (XAER_ASYNC);
704	if (flags != TMNOFLAGS)
705		return (XAER_INVAL);
706
707	if (__db_rmid_to_env(rmid, &env) != 0)
708		return (XAER_PROTO);
709
710	/*
711	 * If mapping is gone, then we're done.
712	 */
713	if (__db_xid_to_txn(env, xid, &off) != 0)
714		return (XA_OK);
715
716	__db_unmap_xid(env, xid, off);
717
718	/* No fatal value that would require an XAER_RMFAIL. */
719	return (XA_OK);
720}
721
722/*
723 * __db_xa_complete --
724 *	Used to wait for asynchronous operations to complete.  Since we're
725 *	not doing asynch, this is an invalid operation.
726 */
727static int
728__db_xa_complete(handle, retval, rmid, flags)
729	int *handle, *retval, rmid;
730	long flags;
731{
732	COMPQUIET(handle, NULL);
733	COMPQUIET(retval, NULL);
734	COMPQUIET(rmid, 0);
735	COMPQUIET(flags, 0);
736
737	return (XAER_INVAL);
738}
739