Back to home page

Enduro/X

 
 

    


0001 /**
0002  * @brief Tmsrv server - transaction monitor
0003  *   After that log transaction to hash & to disk for tracking the stuff...
0004  *   TODO: We should have similar control like "TP_COMMIT_CONTROL" -
0005  *   either return after stuff logged or after really commit completed.
0006  *   Error handling:
0007  *   - System errors we will track via ATMI interface error functions
0008  *   - XA errors will be tracked via XA error interface
0009  *
0010  * @file tmapi.c
0011  */
0012 /* -----------------------------------------------------------------------------
0013  * Enduro/X Middleware Platform for Distributed Transaction Processing
0014  * Copyright (C) 2009-2016, ATR Baltic, Ltd. All Rights Reserved.
0015  * Copyright (C) 2017-2023, Mavimax, Ltd. All Rights Reserved.
0016  * This software is released under one of the following licenses:
0017  * AGPL (with Java and Go exceptions) or Mavimax's license for commercial use.
0018  * See LICENSE file for full text.
0019  * -----------------------------------------------------------------------------
0020  * AGPL license:
0021  *
0022  * This program is free software; you can redistribute it and/or modify it under
0023  * the terms of the GNU Affero General Public License, version 3 as published
0024  * by the Free Software Foundation;
0025  *
0026  * This program is distributed in the hope that it will be useful, but WITHOUT ANY
0027  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
0028  * PARTICULAR PURPOSE. See the GNU Affero General Public License, version 3
0029  * for more details.
0030  *
0031  * You should have received a copy of the GNU Affero General Public License along 
0032  * with this program; if not, write to the Free Software Foundation, Inc.,
0033  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0034  *
0035  * -----------------------------------------------------------------------------
0036  * A commercial use license is available from Mavimax, Ltd
0037  * contact@mavimax.com
0038  * -----------------------------------------------------------------------------
0039  */
0040 #include <stdio.h>
0041 #include <stdlib.h>
0042 #include <string.h>
0043 #include <errno.h>
0044 #include <regex.h>
0045 #include <utlist.h>
0046 
0047 #include <ndebug.h>
0048 #include <atmi.h>
0049 #include <atmi_int.h>
0050 #include <typed_buf.h>
0051 #include <ndrstandard.h>
0052 #include <ubf.h>
0053 #include <Exfields.h>
0054 
0055 #include <exnet.h>
0056 #include <ndrxdcmn.h>
0057 
0058 #include "tmsrv.h"
0059 #include "../libatmisrv/srv_int.h"
0060 #include "tperror.h"
0061 #include <xa_cmn.h>
0062 #include <exbase64.h>
0063 /*---------------------------Externs------------------------------------*/
0064 /*---------------------------Macros-------------------------------------*/
0065 #define XID_RECOVER_BLOCK_SZ        500
0066 /*---------------------------Enums--------------------------------------*/
0067 /*---------------------------Typedefs-----------------------------------*/
0068 /*---------------------------Globals------------------------------------*/
0069 /*---------------------------Statics------------------------------------*/
0070 /*---------------------------Prototypes---------------------------------*/
0071 
0072 /******************************************************************************/
0073 /*                               TP API COMMANDS                              */
0074 /******************************************************************************/
0075 
0076 /**
0077  * TP API
0078  * TP entry for abort.
0079  * @param p_ub
0080  * @return 
0081  */
0082 expublic int tm_tpabort(UBFH *p_ub)
0083 {
0084     int ret = EXSUCCEED;
0085     atmi_xa_tx_info_t xai;
0086     atmi_xa_log_t *p_tl = NULL;
0087     int locke;
0088     
0089     NDRX_LOG(log_debug, "tm_tpabort() called");
0090     
0091     /* 1. get transaction from hash */
0092     if (EXSUCCEED!=atmi_xa_read_tx_info(p_ub, &xai, XA_TXINFO_NOBTID))
0093     {
0094         NDRX_LOG(log_error, "Failed to read transaction data");
0095         /* - will assume that completed OK!
0096         atmi_xa_set_error_msg(p_ub, TPESYSTEM, NDRX_XA_ERSN_INVPARAM, 
0097                 "Invalid transaction data (missing fields)");
0098         */
0099         atmi_xa_set_error_fmt(p_ub, TPEINVAL, NDRX_XA_ERSN_NOTX, 
0100                 "Transaction with xid [%s] not logged - probably was tout+abort", 
0101                 xai.tmxid);
0102         ret=EXSUCCEED;
0103         goto out;
0104     }
0105     
0106     /* read tx from hash */
0107     if (NULL==(p_tl = tms_log_get_entry(xai.tmxid, NDRX_LOCK_WAIT_TIME, &locke)))
0108     {
0109         if (locke)
0110         {
0111             NDRX_LOG(log_error, "Lock xid [%s] timed out", 
0112                     xai.tmxid);
0113             atmi_xa_set_error_fmt(p_ub, TPETIME, NDRX_XA_ERSN_LOCK, 
0114                     "Lock xid [%s] timed out", xai.tmxid);
0115         }
0116         else
0117         {
0118             NDRX_LOG(log_error, "Transaction with xid [%s] not logged", 
0119                     xai.tmxid);
0120             atmi_xa_set_error_fmt(p_ub, TPEPROTO, NDRX_XA_ERSN_NOTX, 
0121                     "Transaction with xid [%s] not logged", xai.tmxid);
0122         }
0123         EXFAIL_OUT(ret);
0124     }
0125     
0126     /* if processing in background (say time-out rollback, the commit shall not be
0127      * accepted)
0128      */
0129     if (p_tl->is_background || XA_TX_STAGE_ACTIVE!=p_tl->txstage)
0130     {
0131         userlog("Cannot abort xid [%s] is_background: (%d) stage: (%hd)", 
0132                 xai.tmxid, p_tl->is_background, p_tl->txstage);
0133         NDRX_LOG(log_error, "Cannot abort xid [%s] is_background: (%d) stage: (%hd)", 
0134                 xai.tmxid, p_tl->is_background, p_tl->txstage);
0135         
0136         if (p_tl->txstage >=XA_TX_STAGE_PREPARING)
0137         {            
0138             atmi_xa_set_error_fmt(p_ub, TPEPROTO, NDRX_XA_ERSN_INPROGRESS, 
0139                 "Cannot abort xid [%s] is_background: (%d) stage: (%hd) (>=preparing)", 
0140                 xai.tmxid, p_tl->is_background, p_tl->txstage);
0141         }
0142         else
0143         {
0144             /* assume completed heuristically (probably was time-out) */
0145             atmi_xa_set_error_fmt(p_ub, TPEHEURISTIC, NDRX_XA_ERSN_INPROGRESS, 
0146                 "xid [%s] is_background: (%d) stage: (%hd) (is aborting)", 
0147                 xai.tmxid, p_tl->is_background, p_tl->txstage);
0148         }
0149         
0150         tms_unlock_entry(p_tl);
0151         
0152         EXFAIL_OUT(ret);
0153     }
0154     
0155     /* Switch the state to aborting... */
0156     tms_log_stage(p_tl, XA_TX_STAGE_ABORTING, EXTRUE);
0157     
0158     /* Call internal version of abort */
0159     if (EXSUCCEED!=(ret=tm_drive(&xai, p_tl, XA_OP_ROLLBACK, EXFAIL, 0L)))
0160     {
0161         atmi_xa_set_error_fmt(p_ub, ret, NDRX_XA_ERSN_RMCOMMITFAIL, 
0162                 "Distributed transaction process did not finish completely");
0163         ret = EXSUCCEED;
0164     }
0165         
0166 out:
0167         
0168     return ret;
0169 }
0170 
0171 /**
0172  * TP API
0173  * Commit the transaction (master entry)
0174  * Transaction initiator ask to commit the global transaction.
0175  * @param p_ub
0176  * @return 
0177  */
0178 expublic int tm_tpcommit(UBFH *p_ub)
0179 {
0180     int ret = EXSUCCEED;
0181     atmi_xa_tx_info_t xai;
0182     atmi_xa_log_t *p_tl = NULL;
0183     int do_abort = EXFALSE;
0184     long tmflags;
0185     int locke;
0186     NDRX_LOG(log_debug, "tm_tpcommit() called");
0187     
0188     /* 1. get transaction from hash */
0189     if (EXSUCCEED!=atmi_xa_read_tx_info(p_ub, &xai, XA_TXINFO_NOBTID))
0190     {
0191         NDRX_LOG(log_error, "Failed to read transaction data");
0192         atmi_xa_set_error_msg(p_ub, TPESYSTEM, NDRX_XA_ERSN_INVPARAM, 
0193                 "Invalid transaction data (missing fields)");
0194         EXFAIL_OUT(ret);
0195     }
0196     
0197     /* tx.h support: */
0198     if (EXSUCCEED!=Bget(p_ub, TMTXFLAGS, 0, (char *)&tmflags, 0L))
0199     {
0200         NDRX_LOG(log_error, "Failed to get TMTXFLAGS: %s", Bstrerror(Berror));
0201         atmi_xa_set_error_msg(p_ub, TPESYSTEM, NDRX_XA_ERSN_INVPARAM, 
0202                 "Failed to read TMTXFLAGS");
0203         EXFAIL_OUT(ret);
0204     }
0205     
0206     /* read tx from hash */
0207     if (NULL==(p_tl = tms_log_get_entry(xai.tmxid, NDRX_LOCK_WAIT_TIME, &locke)))
0208     {
0209         if (locke)
0210         {
0211             NDRX_LOG(log_error, "Lock xid [%s] timed out", 
0212                     xai.tmxid);
0213             atmi_xa_set_error_fmt(p_ub, TPETIME, NDRX_XA_ERSN_LOCK, 
0214                     "Lock xid [%s] timed out", xai.tmxid);   
0215         }
0216         else
0217         {
0218             NDRX_LOG(log_error, "Transaction with xid [%s] not logged", 
0219                     xai.tmxid);
0220             atmi_xa_set_error_fmt(p_ub, TPEABORT, NDRX_XA_ERSN_NOTX, 
0221                     "Transaction with xid [%s] not logged - probably was tout+abort", 
0222                     xai.tmxid);
0223         }
0224         EXFAIL_OUT(ret);
0225     }
0226     
0227     /* if processing in background (say time-out rollback, the commit shall not be
0228      * accepted)
0229      */
0230     if (p_tl->is_background || XA_TX_STAGE_ACTIVE!=p_tl->txstage)
0231     {
0232         userlog("Cannot commit xid [%s] is_background: (%d) stage: (%hd)", 
0233                 xai.tmxid, p_tl->is_background, p_tl->txstage);
0234         NDRX_LOG(log_error, "Cannot commit xid [%s] is_background: (%d) stage: (%hd)", 
0235                 xai.tmxid, p_tl->is_background, p_tl->txstage);
0236         if (p_tl->txstage >=XA_TX_STAGE_PREPARING )
0237         {
0238             atmi_xa_set_error_fmt(p_ub, TPEPROTO, NDRX_XA_ERSN_INPROGRESS, 
0239                     "Cannot commit xid [%s] is_background: (%d) stage: (%hd) (>=is preparing)", 
0240                     xai.tmxid, p_tl->is_background, p_tl->txstage);
0241         }
0242         else
0243         {
0244             atmi_xa_set_error_fmt(p_ub, TPEABORT, NDRX_XA_ERSN_INPROGRESS, 
0245                     "Cannot commit xid [%s] is_background: (%d) stage: (%hd) (is aborting)", 
0246                     xai.tmxid, p_tl->is_background, p_tl->txstage);
0247         }
0248         
0249         tms_unlock_entry(p_tl);
0250         
0251         EXFAIL_OUT(ret);
0252     }
0253     
0254     /* Open log file 
0255      * - now we open the file at the start of the transaction.
0256     if (EXSUCCEED!=tms_open_logfile(p_tl, "w"))
0257     {
0258         NDRX_LOG(log_error, "Failed to open log file");
0259         atmi_xa_set_error_msg(p_ub, TPESYSTEM, NDRX_XA_ERSN_INVPARAM, 
0260                 "Failed to open log file");
0261         do_abort = EXTRUE;
0262         EXFAIL_OUT(ret);
0263     }
0264     */
0265     
0266     /* Log that we start commit... */
0267     if (EXSUCCEED!=tms_log_stage(p_tl, XA_TX_STAGE_PREPARING, EXFALSE))
0268     {
0269         NDRX_LOG(log_error, "Failed to log preparing stage");
0270         do_abort = EXTRUE;
0271         atmi_xa_set_error_fmt(p_ub, TPEABORT, NDRX_XA_ERSN_LOGFAIL, 
0272                     "Failed to log preparing stage");
0273         EXFAIL_OUT(ret);
0274     }
0275     
0276     /* Drive - it will auto-rollback if needed... */
0277     if (EXSUCCEED!=(ret=tm_drive(&xai, p_tl, XA_OP_COMMIT, EXFAIL, tmflags)))
0278     {
0279         atmi_xa_set_error_msg(p_ub, ret, NDRX_XA_ERSN_RMCOMMITFAIL, 
0280                 "Transaction did not complete fully");
0281         ret=EXFAIL;
0282         goto out;
0283     }
0284     
0285 out:
0286                 
0287     /* Rollback the work done! */
0288     if (do_abort)
0289     {
0290         NDRX_LOG(log_warn, "About to rollback transaction!");
0291         
0292         tms_log_stage(p_tl, XA_TX_STAGE_ABORTING, EXTRUE);
0293         
0294         /* Call internal version of abort */
0295         if (EXSUCCEED!=(ret=tm_drive(&xai, p_tl, XA_OP_ROLLBACK, EXFAIL, 0L)))
0296         {
0297             atmi_xa_override_error(p_ub, ret);
0298             ret=EXFAIL;
0299         }
0300         
0301     }
0302 
0303     return ret;
0304 }
0305 
0306 /**
0307  * TP API
0308  * Start new XA transaction
0309  * @param p_ub
0310  * @return 
0311  */
0312 expublic int tm_tpbegin(UBFH *p_ub)
0313 {
0314     int ret=EXSUCCEED;
0315     XID xid; /* handler for new XID */
0316     atmi_xa_tx_info_t xai;
0317     int do_rollback=EXFALSE;
0318     char xid_str[NDRX_XID_SERIAL_BUFSIZE];
0319     long txtout;
0320     long tmflags;
0321     long btid = EXFAIL;
0322     NDRX_LOG(log_debug, "tm_tpbegin() called");
0323     
0324     if (EXSUCCEED!=Bget(p_ub, TMTXFLAGS, 0, (char *)&tmflags, 0L))
0325     {
0326         NDRX_LOG(log_error, "Failed to read TMTXFLAGS!");
0327         EXFAIL_OUT(ret);   
0328     }
0329     
0330     atmi_xa_new_xid(&xid);
0331     
0332     xai.tmknownrms[0] = 0;
0333     
0334     /* we should start new transaction... (only if static...) */
0335     if (!(tmflags & TMFLAGS_DYNAMIC_REG))
0336     {
0337         /*
0338         if (EXSUCCEED!=(ret = atmi_xa_start_entry(&xid, 0, EXFALSE)))
0339         {
0340             NDRX_LOG(log_error, "Failed to start new transaction!");
0341             atmi_xa_set_error_fmt(p_ub, TPETRAN, NDRX_XA_ERSN_NONE, 
0342                     "Failed to start new transaction, "
0343                     "xa error: %d [%s]", ret, atmi_xa_geterrstr(ret));
0344             goto out;
0345         }
0346         */
0347         
0348         /* We know it only if it is not BTID resource */
0349         if (!(tmflags & TMFLAGS_TPNOSTARTXID))
0350         {
0351             xai.tmknownrms[0] = G_atmi_env.xa_rmid;
0352             xai.tmknownrms[1] = EXEOS;
0353         }
0354     }
0355 
0356     atmi_xa_serialize_xid(&xid, xid_str);
0357     
0358     /* load the XID into buffer */
0359     NDRX_STRCPY_SAFE(xai.tmxid, xid_str);
0360     xai.tmrmid = G_atmi_env.xa_rmid;
0361     xai.tmnodeid = G_tmsrv_cfg.vnodeid;
0362     xai.tmsrvid = G_server_conf.srv_id;
0363     
0364     /* Currently time-out is handled only locally by TM */
0365     if (EXSUCCEED!=Bget(p_ub, TMTXTOUT, 0, (char *)&txtout, 0L) || 0>=txtout)
0366     {
0367         txtout = G_tmsrv_cfg.dflt_timeout;
0368         NDRX_LOG(log_debug, "TX tout defaulted to: %ld", txtout);
0369     }
0370     else
0371     {
0372         NDRX_LOG(log_debug, "TX tout: %ld", txtout);
0373     }
0374     
0375     /* Only for static...
0376     if (!(tmflags & TMTXFLAGS_DYNAMIC_REG))
0377     {
0378         if (EXSUCCEED!=(ret = atmi_xa_end_entry(&xid, TMSUCCESS)))
0379         {
0380             NDRX_LOG(log_error, "Failed to end XA api!");
0381             atmi_xa_set_error_fmt(p_ub, TPETRAN, NDRX_XA_ERSN_NONE, 
0382                     "Failed to start end transaction, "
0383                     "xa error: %d [%s]", ret, atmi_xa_geterrstr(ret));
0384             goto out;
0385         }
0386     }
0387     */
0388     
0389     if (EXSUCCEED!=atmi_xa_load_tx_info(p_ub, &xai))
0390     {
0391         NDRX_LOG(log_error, "Failed to load tx info!");
0392         atmi_xa_set_error_fmt(p_ub, TPETRAN, NDRX_XA_ERSN_NONE, 
0393                     "Failed to return tx info!");
0394         do_rollback = EXTRUE;
0395         ret=EXFAIL;
0396         goto out;
0397     }
0398     
0399     /* Log into journal */
0400     if (EXSUCCEED!=tms_log_start(&xai, txtout, tmflags, &btid))
0401     {
0402         NDRX_LOG(log_error, "Failed to log the transaction!");
0403         atmi_xa_set_error_fmt(p_ub, TPETRAN, NDRX_XA_ERSN_LOGFAIL, 
0404                     "Failed to log the transaction!");
0405         do_rollback = EXTRUE;
0406         ret=EXFAIL;
0407         goto out;
0408     }
0409     
0410     /* TID to log */
0411     if (EXSUCCEED!=Bchg(p_ub, TMTXBTID, 0, (char *)&btid, 0L))
0412     {
0413         NDRX_LOG(log_error, "Failed to set TMTXBTID: %s", Bstrerror(Berror));
0414         atmi_xa_set_error_fmt(p_ub, TPESYSTEM, NDRX_XA_ERSN_UBFERR, 
0415                     "Failed to set TMTXBTID: %s", Bstrerror(Berror));
0416         
0417         /* How about closing log the file? */
0418         do_rollback = EXTRUE;
0419         ret=EXFAIL;
0420         goto out;
0421     }
0422     
0423 out:
0424     
0425     /* We should abort the transaction right now */
0426     if (do_rollback)
0427     {
0428         ret = tm_rollback_local(p_ub, &xai, btid);
0429     }
0430 
0431     return ret;
0432 }
0433 
0434 /******************************************************************************/
0435 /*                         TRANSACTION BRANCH COMMANDS                        */
0436 /******************************************************************************/
0437 
0438 /**
0439  * Register resource manager under given transaction
0440  * @param p_ub
0441  * @return 
0442  */
0443 expublic int tm_tmregister(UBFH *p_ub)
0444 {
0445     int ret = EXSUCCEED;
0446     short   callerrm;
0447     int is_already_logged = EXFALSE;
0448     atmi_xa_tx_info_t xai;
0449     long tmflags = 0;
0450     long btid=EXFAIL;
0451     
0452     
0453     /* TODO: Get flags! */
0454     
0455     if (EXSUCCEED!=Bget(p_ub, TMCALLERRM, 0, (char *)&callerrm, 0L))
0456     {
0457         atmi_xa_set_error_fmt(p_ub, TPESYSTEM, NDRX_XA_ERSN_INVPARAM, 
0458                     "Missing TMCALLERRM field: %s!", Bstrerror(Berror));
0459         EXFAIL_OUT(ret);
0460     }
0461     
0462     if (EXSUCCEED!=atmi_xa_read_tx_info(p_ub, &xai, XA_TXINFO_NOBTID))
0463     {
0464         atmi_xa_set_error_fmt(p_ub, TPESYSTEM, NDRX_XA_ERSN_INVPARAM, 
0465                     "Failed to read transaction info!");
0466         EXFAIL_OUT(ret);
0467     }
0468     
0469     /* read BTID (if have one in their side) */
0470     
0471     if (EXSUCCEED!=Bget(p_ub, TMTXBTID, 0, (char *)&btid, NULL) && Berror!=BNOTPRES)
0472     {
0473         NDRX_LOG(log_error, "Failed to get TMTXBTID: %s", Bstrerror(Berror));
0474         
0475         atmi_xa_set_error_fmt(p_ub, TPESYSTEM, NDRX_XA_ERSN_UBFERR, 
0476                      "Failed to get TMTXBTID: %s", Bstrerror(Berror));
0477         EXFAIL_OUT(ret);
0478     }
0479     
0480     if (EXSUCCEED!=Bget(p_ub, TMTXFLAGS, 0, (char *)&tmflags, 0L))
0481     {
0482         atmi_xa_set_error_fmt(p_ub, TPESYSTEM, NDRX_XA_ERSN_INVPARAM, 
0483                     "Missing TMTXFLAGS in buffer");
0484         EXFAIL_OUT(ret);
0485     }
0486     
0487     if (EXSUCCEED!=tms_log_addrm(&xai, callerrm, &is_already_logged, &btid, tmflags))
0488     {
0489         atmi_xa_set_error_fmt(p_ub, TPESYSTEM, NDRX_XA_ERSN_RMLOGFAIL, 
0490                     "Failed to log new RM!");
0491         EXFAIL_OUT(ret);
0492     }
0493     
0494     if (is_already_logged)
0495     {
0496         tmflags|=TMFLAGS_RMIDKNOWN;
0497     }
0498     
0499     /* return new TID */
0500     if (!Bpres(p_ub, TMTXBTID, 0) &&
0501             EXSUCCEED!=Bchg(p_ub, TMTXBTID, 0, (char *)&btid, 0L))
0502     {
0503         atmi_xa_set_error_fmt(p_ub, TPESYSTEM, NDRX_XA_ERSN_UBFERR, 
0504                     "Failed to set TMTXBTID!");
0505         EXFAIL_OUT(ret);
0506     }
0507     
0508     if (EXSUCCEED!=Bchg(p_ub, TMTXFLAGS, 0, (char *)&tmflags, 0L))
0509     {
0510         atmi_xa_set_error_fmt(p_ub, TPESYSTEM, NDRX_XA_ERSN_UBFERR, 
0511                     "Failed to set TMTXFLAGS!");
0512         EXFAIL_OUT(ret);
0513     }
0514     
0515 out:
0516     return ret; 
0517 }
0518 
0519 /**
0520  * Report transaction status of the branch
0521  * @param p_ub
0522  * @return 
0523  */
0524 expublic int tm_rmstatus(UBFH *p_ub)
0525 {
0526     int ret = EXSUCCEED;
0527     short   callerrm;
0528     int is_already_logged = EXFALSE;
0529     atmi_xa_tx_info_t xai;
0530     long tmflags = 0;
0531     long btid=EXFAIL;
0532     char rmstatus;
0533     
0534     
0535     /* TODO: Get flags! */
0536     
0537     if (EXSUCCEED!=Bget(p_ub, TMCALLERRM, 0, (char *)&callerrm, 0L))
0538     {
0539         atmi_xa_set_error_fmt(p_ub, TPESYSTEM, NDRX_XA_ERSN_INVPARAM, 
0540                     "Missing TMCALLERRM field: %s!", Bstrerror(Berror));
0541         EXFAIL_OUT(ret);
0542     }
0543     
0544     if (EXSUCCEED!=atmi_xa_read_tx_info(p_ub, &xai, XA_TXINFO_NOBTID))
0545     {
0546         atmi_xa_set_error_fmt(p_ub, TPESYSTEM, NDRX_XA_ERSN_INVPARAM, 
0547                     "Failed to read transaction info!");
0548         EXFAIL_OUT(ret);
0549     }
0550     
0551     /* read BTID (if have one in their side) */
0552     
0553     if (EXSUCCEED!=Bget(p_ub, TMTXBTID, 0, (char *)&btid, NULL))
0554     {
0555         atmi_xa_set_error_fmt(p_ub, TPESYSTEM, NDRX_XA_ERSN_INVPARAM, 
0556                     "Missing TMTXBTID!");
0557         EXFAIL_OUT(ret);
0558     }
0559     
0560     if (EXSUCCEED!=Bget(p_ub, TMTXRMSTATUS, 0, (char *)&rmstatus, 0L))
0561     {
0562         atmi_xa_set_error_fmt(p_ub, TPESYSTEM, NDRX_XA_ERSN_INVPARAM, 
0563                     "Missing TMTXRMSTATUS in buffer");
0564         EXFAIL_OUT(ret);
0565     }
0566 
0567     if (EXSUCCEED!=tms_log_chrmstat(&xai, callerrm, btid, rmstatus, p_ub))
0568     {
0569         atmi_xa_set_error_fmt(p_ub, TPESYSTEM, NDRX_XA_ERSN_RMLOGFAIL, 
0570                     "Failed to log new RM!");
0571         EXFAIL_OUT(ret);
0572     }
0573     
0574     if (is_already_logged)
0575     {
0576         tmflags|=TMFLAGS_RMIDKNOWN;
0577     }
0578     
0579     /* return new TID */
0580     if (!Bpres(p_ub, TMTXBTID, 0) &&
0581             EXSUCCEED!=Bchg(p_ub, TMTXBTID, 0, (char *)&btid, 0L))
0582     {
0583         atmi_xa_set_error_fmt(p_ub, TPESYSTEM, NDRX_XA_ERSN_UBFERR, 
0584                     "Failed to set TMTXBTID!");
0585         EXFAIL_OUT(ret);
0586     }
0587     
0588     if (EXSUCCEED!=Bchg(p_ub, TMTXFLAGS, 0, (char *)&tmflags, 0L))
0589     {
0590         atmi_xa_set_error_fmt(p_ub, TPESYSTEM, NDRX_XA_ERSN_UBFERR, 
0591                     "Failed to set TMTXFLAGS!");
0592         EXFAIL_OUT(ret);
0593     }
0594     
0595 out:
0596     return ret; 
0597 }
0598 
0599 
0600 /**
0601  * Do the internal prepare of transaction (request sent from other TM)
0602  * @param p_ub
0603  * @return 
0604  */
0605 expublic int tm_tmprepare(UBFH *p_ub)
0606 {
0607     int ret = EXSUCCEED;
0608     atmi_xa_tx_info_t xai;
0609     
0610     NDRX_LOG(log_debug, "tm_tmprepare called.");
0611     /* read xai from FB... */
0612     if (EXSUCCEED!=atmi_xa_read_tx_info(p_ub, &xai, 0))
0613     {
0614         atmi_xa_set_error_fmt(p_ub, TPESYSTEM, NDRX_XA_ERSN_INVPARAM, 
0615                     "Failed to read transaction info!");
0616         EXFAIL_OUT(ret);
0617     }
0618     
0619     if (EXSUCCEED!=(ret = tm_prepare_local(p_ub, &xai, xai.btid)))
0620     {
0621         EXFAIL_OUT(ret);
0622     }
0623     
0624 out:
0625     return ret;
0626 }
0627 
0628 /**
0629  * Do the internal commit of transaction (request sent from other TM)
0630  * @param p_ub
0631  * @return 
0632  */
0633 expublic int tm_tmcommit(UBFH *p_ub)
0634 {
0635     int ret = EXSUCCEED;
0636     atmi_xa_tx_info_t xai;
0637     
0638     NDRX_LOG(log_debug, "tm_tmcommit called.");
0639     /* read xai from FB... */
0640     if (EXSUCCEED!=atmi_xa_read_tx_info(p_ub, &xai, 0))
0641     {
0642         atmi_xa_set_error_fmt(p_ub, TPESYSTEM, NDRX_XA_ERSN_INVPARAM, 
0643                     "Failed to read transaction info!");
0644         EXFAIL_OUT(ret);
0645     }
0646     
0647     if (EXSUCCEED!=(ret = tm_commit_local(p_ub, &xai, xai.btid)))
0648     {
0649         EXFAIL_OUT(ret);
0650     }
0651     
0652 out:
0653     return ret;
0654 }
0655 
0656 /**
0657  * Local transaction branch abort command. Sent from Master TM
0658  * @param p_ub
0659  * @return 
0660  */
0661 expublic int tm_tmabort(UBFH *p_ub)
0662 {
0663     int ret = EXSUCCEED;
0664     atmi_xa_tx_info_t xai;
0665     
0666     NDRX_LOG(log_debug, "tm_tmabort called.");
0667     if (EXSUCCEED!=atmi_xa_read_tx_info(p_ub, &xai, 0))
0668     {
0669         atmi_xa_set_error_fmt(p_ub, TPESYSTEM, NDRX_XA_ERSN_INVPARAM, 
0670                     "Failed to read transaction info!");
0671         EXFAIL_OUT(ret);
0672     }
0673     
0674     /* read xai from FB... */
0675     if (EXSUCCEED!=(ret = tm_rollback_local(p_ub, &xai, xai.btid)))
0676     {
0677         EXFAIL_OUT(ret);
0678     }
0679     
0680 out:
0681     return ret;
0682 }
0683 
0684 /**
0685  * Forget entry
0686  * @param p_ub req buf
0687  * @return EXSUCCEED/EXFAIL
0688  */
0689 expublic int tm_tmforget(UBFH *p_ub)
0690 {
0691     int ret = EXSUCCEED;
0692     atmi_xa_tx_info_t xai;
0693     
0694     NDRX_LOG(log_debug, "tm_tmforget called.");
0695     if (EXSUCCEED!=atmi_xa_read_tx_info(p_ub, &xai, 0))
0696     {
0697         atmi_xa_set_error_fmt(p_ub, TPESYSTEM, NDRX_XA_ERSN_INVPARAM, 
0698                     "Failed to read transaction info!");
0699         EXFAIL_OUT(ret);
0700     }
0701     
0702     /* read xai from FB... */
0703     if (EXSUCCEED!=(ret = tm_forget_local(p_ub, &xai, xai.btid)))
0704     {
0705         EXFAIL_OUT(ret);
0706     }
0707     
0708 out:
0709     return ret;
0710 }
0711 
0712 /******************************************************************************/
0713 /*                         COMMAND LINE API                                   */
0714 /******************************************************************************/
0715 /**
0716  * Return list of transactions
0717  * @param p_ub
0718  * @param cd - call descriptor
0719  * @return 
0720  */
0721 expublic int tm_tpprinttrans(UBFH *p_ub, int cd)
0722 {
0723     int ret = EXSUCCEED;
0724     long revent;
0725     atmi_xa_log_list_t *tx_list;
0726     atmi_xa_log_list_t *el, *tmp;
0727     
0728     /* Get the lock! */
0729     tms_tx_hash_lock();
0730     
0731     tx_list = tms_copy_hash2list(COPY_MODE_FOREGROUND | COPY_MODE_BACKGROUND);
0732         
0733     LL_FOREACH_SAFE(tx_list,el,tmp)
0734     {
0735         
0736         /* Erase FB & setup the info there... */
0737         Bproj(p_ub, NULL); /* clear the FB! */
0738         if (EXSUCCEED!=tms_log_cpy_info_to_fb(p_ub, &(el->p_tl), EXTRUE))
0739         {
0740             EXFAIL_OUT(ret);
0741         }
0742         
0743         
0744         /* Bfprint(p_ub, stderr); */
0745         
0746         if (EXFAIL == tpsend(cd,
0747                             (char *)p_ub,
0748                             0L,
0749                             0,
0750                             &revent))
0751         {
0752             NDRX_LOG(log_error, "Send data failed [%s] %ld",
0753                                 tpstrerror(tperrno), revent);
0754             EXFAIL_OUT(ret);
0755         }
0756         else
0757         {
0758             NDRX_LOG(log_debug,"sent ok");
0759         }
0760         
0761         LL_DELETE(tx_list, el);
0762         NDRX_FREE(el);
0763     }
0764     
0765 out:
0766     /* TODO: might want to kill the list if FAIL!!!! */
0767     tms_tx_hash_unlock();
0768 
0769     return ret;
0770 }
0771 
0772 /**
0773  * Abort transaction.
0774  * @param p_ub
0775  * @param cd - call descriptor
0776  * @return 
0777  */
0778 expublic int tm_aborttrans(UBFH *p_ub)
0779 {
0780     int ret = EXSUCCEED;
0781     atmi_xa_log_t *p_tl;
0782     char tmxid[NDRX_XID_SERIAL_BUFSIZE+1];
0783     short tmrmid = EXFAIL;
0784     atmi_xa_tx_info_t xai;
0785     int locke;
0786     /* We should try to abort transaction
0787      Thus basically we need to lock the transaction on which we work on.
0788      Otherwise, we can conflict with background.
0789      */
0790     background_lock();
0791     
0792     if (EXSUCCEED!=Bget(p_ub, TMXID, 0, tmxid, 0L))
0793     {
0794         NDRX_LOG(log_error, "Failed to read TMXID: %s", 
0795                 Bstrerror(Berror));
0796         atmi_xa_set_error_msg(p_ub, TPESYSTEM, 0, "Protocol error, missing TMXID!");
0797         EXFAIL_OUT(ret);
0798     }
0799     
0800     /* optional */
0801     Bget(p_ub, TMTXRMID, 0, (char *)&tmrmid, 0L);
0802     
0803     /* Lookup for log. And then try to abort... */
0804     if (NULL==(p_tl = tms_log_get_entry(tmxid, NDRX_LOCK_WAIT_TIME, &locke)))
0805     {
0806         /* Generate error */
0807         if (locke)
0808         {
0809             atmi_xa_set_error_fmt(p_ub, TPETIME, 0, "Lock xid [%s] timed out", 
0810                     tmxid);
0811         }
0812         else
0813         {
0814             atmi_xa_set_error_fmt(p_ub, TPEMATCH, 0, "Transaction not found [%s]", 
0815                     tmxid);
0816         }
0817         EXFAIL_OUT(ret);
0818     }
0819     
0820     /* Try to abort stuff... */
0821     
0822     /* init xai for tl... */
0823     XA_TX_COPY((&xai), p_tl);
0824     
0825     NDRX_LOG(log_debug, "Got RMID: [%hd]", tmrmid);
0826     
0827     /* Switch transaction to aborting (if not already) */
0828     tms_log_stage(p_tl, XA_TX_STAGE_ABORTING, EXTRUE);
0829     
0830     if (EXSUCCEED!=(ret=tm_drive(&xai, p_tl, XA_OP_ROLLBACK, tmrmid, 0L)))
0831     {
0832         atmi_xa_set_error_fmt(p_ub, ret, NDRX_XA_ERSN_RMERR, 
0833                 "Failed to abort transaction");
0834         EXFAIL_OUT(ret);
0835     }
0836     
0837 out:
0838     background_unlock();
0839 
0840     return ret;
0841 }
0842 
0843 /**
0844  * Get transaction status
0845  * @param p_ub request buffer
0846  * @return 
0847  */
0848 expublic int tm_status(UBFH *p_ub)
0849 {
0850     int ret = EXSUCCEED;
0851     atmi_xa_log_t *p_tl = NULL;
0852     char tmxid[NDRX_XID_SERIAL_BUFSIZE+1];
0853     int locke;
0854     
0855     if (EXSUCCEED!=Bget(p_ub, TMXID, 0, tmxid, 0L))
0856     {
0857         NDRX_LOG(log_error, "Failed to read TMXID: %s", 
0858                 Bstrerror(Berror));
0859         atmi_xa_set_error_msg(p_ub, TPESYSTEM, 0, "Protocol error, missing TMXID!");
0860         EXFAIL_OUT(ret);
0861     }
0862     
0863     /* Lookup for log. And then try to abort...
0864      * in case if timed-out, we shall return different error.
0865      * maybe TPETIME.
0866      */
0867     if (NULL==(p_tl = tms_log_get_entry(tmxid, NDRX_LOCK_WAIT_TIME, &locke)))
0868     {
0869         /* Generate error */
0870         if (locke)
0871         {
0872             atmi_xa_set_error_fmt(p_ub, TPETIME, 0, "Lock xid [%s] timed out", 
0873                     tmxid);
0874         }
0875         /* detect concurrent run (for failover/singleton groups): */
0876         else if (EXFALSE!=(ret=tms_log_exists_file(tmxid)))
0877         {
0878             if (EXTRUE==ret)
0879             {
0880                 atmi_xa_set_error_fmt(p_ub, TPESYSTEM, 0, "Transaction [%s] exists on disk, "
0881                     "but not in memory - concurrent run?", tmxid);
0882             }
0883             else
0884             {
0885                 atmi_xa_set_error_fmt(p_ub, TPEOS, 0, "Transaction [%s] disk "
0886                     "log verification failure", tmxid);
0887             }
0888         }
0889         else
0890         {
0891             atmi_xa_set_error_fmt(p_ub, TPEMATCH, 0, "Transaction not found [%s]", 
0892                     tmxid);
0893         }
0894         EXFAIL_OUT(ret);
0895     }
0896     
0897     /* Return full status of the transaction... */
0898     if (EXSUCCEED!=tms_log_cpy_info_to_fb(p_ub, p_tl, EXFALSE))
0899     {
0900         EXFAIL_OUT(ret);
0901     }
0902     
0903 out:
0904     
0905     if (NULL!=p_tl)
0906     {
0907         tms_unlock_entry(p_tl);
0908     }
0909 
0910     return ret;
0911 }
0912 
0913 /**
0914  * Commit transaction.
0915  * @param p_ub
0916  * @param cd - call descriptor
0917  * @return 
0918  */
0919 expublic int tm_committrans(UBFH *p_ub)
0920 {
0921     int ret = EXSUCCEED;
0922     atmi_xa_log_t *p_tl;
0923     char tmxid[NDRX_XID_SERIAL_BUFSIZE+1];
0924     atmi_xa_tx_info_t xai;
0925     int locke;
0926     
0927     /* We should try to commit transaction
0928      Thus basically we need to lock the transaction on which we work on.
0929      Otherwise, we can conflict with background.
0930      */
0931     background_lock();
0932     
0933     if (EXSUCCEED!=Bget(p_ub, TMXID, 0, tmxid, 0L))
0934     {
0935         NDRX_LOG(log_error, "Failed to read TMXID: %s", 
0936                 Bstrerror(Berror));
0937         atmi_xa_set_error_msg(p_ub, TPESYSTEM, 0, "Protocol error, missing TMXID!");
0938         EXFAIL_OUT(ret);
0939     }
0940     
0941     /* Lookup for log. And then try to commit... */
0942     if (NULL==(p_tl = tms_log_get_entry(tmxid, NDRX_LOCK_WAIT_TIME, &locke)))
0943     {
0944         /* Generate error */
0945         if (locke)
0946         {
0947             atmi_xa_set_error_fmt(p_ub, TPETIME, 0, "Lock xid [%s] timed out", 
0948                     tmxid);
0949         }
0950         else
0951         {
0952             atmi_xa_set_error_fmt(p_ub, TPEMATCH, 0, "Transaction not found [%s]", 
0953                     tmxid);
0954         }
0955         EXFAIL_OUT(ret);
0956     }
0957     
0958     /* Try to commit stuff, only if stage is prepared...! */
0959     if (XA_TX_STAGE_COMMITTING!=p_tl->txstage)
0960     {
0961         atmi_xa_set_error_fmt(p_ub, TPEINVAL, 0, 
0962                 "Transaction not in PREPARED stage!");
0963         /* we shall unlock the transaction here! */
0964         tms_unlock_entry(p_tl);
0965         EXFAIL_OUT(ret);
0966     }
0967     
0968     /* init xai for tl... */
0969     XA_TX_COPY((&xai), p_tl);
0970     
0971     if (EXSUCCEED!=(ret=tm_drive(&xai, p_tl, XA_OP_COMMIT, EXFAIL, 0L)))
0972     {
0973         atmi_xa_set_error_fmt(p_ub, ret, NDRX_XA_ERSN_RMCOMMITFAIL, 
0974                 "Failed to commit transaction!");
0975         ret=EXFAIL;
0976         goto out;
0977     }
0978     
0979 out:
0980     background_unlock();
0981 
0982     return ret;
0983 }
0984 
0985 /**
0986  * Return list of in doubt local transactions.
0987  * This returns info directly from RM
0988  * @param p_ub
0989  * @param cd - call descriptor
0990  * @return 
0991  */
0992 expublic int tm_recoverlocal(UBFH *p_ub, int cd)
0993 {
0994     int ret = EXSUCCEED;
0995     long revent;
0996     XID arraxid[XID_RECOVER_BLOCK_SZ];
0997     long flags = TMSTARTRSCAN;
0998     int i;
0999     char tmp[1024];
1000     size_t out_len = sizeof(tmp);
1001     
1002     while ((ret = atmi_xa_recover_entry(arraxid, XID_RECOVER_BLOCK_SZ, G_atmi_env.xa_rmid, 
1003             flags)) > 0)
1004     {       
1005         /* reset first */
1006         if (TMNOFLAGS!=flags)
1007         {
1008             flags = TMNOFLAGS;
1009         }
1010         
1011         NDRX_LOG(log_debug, "Recovered txns %d flags: %ld", ret, flags);
1012         
1013         for (i=0; i<ret; i++)
1014         {
1015             /* generate xid as base64 string? */
1016             out_len = sizeof(tmp);
1017             if (NULL==ndrx_xa_base64_encode((unsigned char *)&arraxid[i], sizeof(arraxid[i]), 
1018                     &out_len, tmp))
1019             {
1020                 NDRX_LOG(log_error, "Base64 encode failed");
1021                 EXFAIL_OUT(ret);
1022             }
1023             /* tmp[out_len] = EXEOS; */
1024             
1025             NDRX_LOG(log_debug, "Recovered xid: [%s]", tmp);
1026             
1027             if (EXSUCCEED!=Bchg(p_ub, TMXID, 0, tmp, 0))
1028             {
1029                 NDRX_LOG(log_error, "Failed to set TMXID to [%s]", tmp);
1030                 EXFAIL_OUT(ret);
1031             }
1032             
1033             if (EXFAIL == tpsend(cd,
1034                                 (char *)p_ub,
1035                                 0L,
1036                                 0,
1037                                 &revent))
1038             {
1039                 NDRX_LOG(log_error, "Send data failed [%s] %ld",
1040                                     tpstrerror(tperrno), revent);
1041                 EXFAIL_OUT(ret);
1042             }
1043             else
1044             {
1045                 NDRX_LOG(log_debug,"sent ok");
1046             }
1047         }
1048         
1049         if (ret < XID_RECOVER_BLOCK_SZ)
1050         {
1051             /* this is EOF according to the spec */
1052             break;
1053         }
1054         
1055     }
1056     
1057 out:
1058 
1059     /* close the "cursor"  - no need we scan till end..
1060     atmi_xa_recover_entry(NULL, 0, G_atmi_env.xa_rmid, TMENDRSCAN);
1061      * */
1062 
1063     return ret;
1064 }
1065 
1066 /**
1067  * Perform local operation on single xid and send results back to server.
1068  * @param p_ub UBF buffer for connection
1069  * @param cd connection descriptor
1070  * @param cmd op code
1071  * @param xid ptr XID
1072  * @param flags TMTXFLAGS flags value
1073  * @return EXSUCCEED/EXFAIL
1074  */
1075 exprivate int tm_proclocal_single(UBFH *p_ub, int cd, char cmd, XID *xid, long flags)
1076 {
1077     int ret = EXSUCCEED;
1078     char tmp[1024];
1079     size_t out_len = sizeof(tmp);
1080     long revent;
1081         
1082     atmi_xa_unset_error(p_ub);
1083     ndrx_TPunset_error();
1084     
1085     switch (cmd)
1086     {
1087         case ATMI_XA_COMMITLOCAL:
1088             ret = atmi_xa_commit_entry(xid, TMNOFLAGS);
1089             break;
1090         case ATMI_XA_ABORTLOCAL:
1091             ret = atmi_xa_rollback_entry(xid, TMNOFLAGS);
1092             break;
1093         case ATMI_XA_FORGETLOCAL:
1094             ret = atmi_xa_forget_entry(xid, TMNOFLAGS);
1095             break;
1096         default:
1097             NDRX_LOG(log_error, "Invalid Opcode: %c", cmd);
1098             EXFAIL_OUT(ret);
1099             break;
1100     }
1101     
1102     /* In case if working on non-conv mode
1103      * return the failure
1104      * and load the results
1105      */
1106     
1107     /* load the result in UBF */
1108     ndrx_TPset_error_ubf(p_ub);
1109     
1110     
1111     if (flags & TMFLAGS_NOCON)
1112     {
1113         NDRX_LOG(log_debug, "No con call: %d", ret);
1114         goto out;
1115     }
1116     
1117     ret = EXSUCCEED;
1118     
1119     ndrx_xa_base64_encode((unsigned char *)xid, sizeof(*xid), 
1120                     &out_len, tmp);
1121     /* tmp[out_len] = EXEOS; */
1122             
1123     if (EXSUCCEED!=Bchg(p_ub, TMXID, 0, tmp, 0))
1124     {
1125         NDRX_LOG(log_error, "Failed to set TMXID to [%s]", tmp);
1126         EXFAIL_OUT(ret);
1127     }
1128 
1129     if (EXFAIL == tpsend(cd,
1130                         (char *)p_ub,
1131                         0L,
1132                         0,
1133                         &revent))
1134     {
1135         NDRX_LOG(log_error, "Send data failed [%s] %ld",
1136                             tpstrerror(tperrno), revent);
1137         EXFAIL_OUT(ret);
1138     }
1139     else
1140     {
1141         NDRX_LOG(log_debug,"sent ok");
1142     }
1143     
1144 out:
1145     return ret;
1146 }
1147 
1148 /**
1149  * Process manually the local transactions.
1150  * Return the list of processed + status
1151  * @param cmd ATMI_XA_COMMITLOCAL / ATMI_XA_ABORTLOCAL / ATMI_XA_FORGETLOCAL
1152  * @param p_ub
1153  * @param cd - call descriptor
1154  * @return 
1155  */
1156 expublic int tm_proclocal(char cmd, UBFH *p_ub, int cd)
1157 {
1158     int ret = EXSUCCEED;
1159     
1160     XID one;
1161     char onestr[sizeof(XID)*2];
1162     long flags = TMSTARTRSCAN;
1163     XID arraxid[XID_RECOVER_BLOCK_SZ];
1164     int i;
1165     size_t out_len = 0;
1166     BFLDLEN len;
1167     long tmtxflags=0;
1168     
1169     /* if there is single tran, then process it, if not, then loop over */
1170     
1171     if (Bpres(p_ub, TMTXFLAGS, 0) &&
1172             EXSUCCEED!=Bget(p_ub, TMTXFLAGS, 0, (char *)&tmtxflags, 0L))
1173     {
1174         NDRX_LOG(log_error, "Failed to get TMTXFLAGS: %s", Bstrerror(Berror));
1175         EXFAIL_OUT(ret);
1176     }
1177     
1178     if (Bpres(p_ub, TMXID, 0))
1179     {
1180         NDRX_LOG(log_debug, "XID present -> process single");
1181         len = sizeof(onestr);
1182         if (EXSUCCEED!=Bget(p_ub, TMXID, 0, onestr, &len))
1183         {
1184             NDRX_LOG(log_error, "Failed to get TMXID: %s", Bstrerror(Berror));
1185             EXFAIL_OUT(ret);
1186         }
1187         
1188         ndrx_xa_base64_decode((unsigned char *)onestr, strlen(onestr), 
1189                 &out_len, (char *)&one);
1190         
1191         if (EXSUCCEED!=tm_proclocal_single(p_ub, cd, cmd, &one, tmtxflags))
1192         {
1193             NDRX_DUMP(log_error, "Failed to process local xid", &one, sizeof(one));
1194             EXFAIL_OUT(ret);
1195         }
1196             
1197     } /* we process one by one otherwise we get improper one by one process */
1198     else while ((ret = atmi_xa_recover_entry(arraxid, XID_RECOVER_BLOCK_SZ, G_atmi_env.xa_rmid, 
1199             flags)) > 0)
1200     {       
1201         /* reset first */
1202         if (TMNOFLAGS!=flags)
1203         {
1204             flags = TMNOFLAGS;
1205         }
1206         
1207         NDRX_LOG(log_debug, "Recovered txns %d flags: %ld", ret, flags);
1208         /*
1209         atmi_xa_recover_entry(NULL, 0, G_atmi_env.xa_rmid, TMENDRSCAN);
1210         */
1211         
1212         for (i=0; i<ret; i++)
1213         {   
1214             if (EXSUCCEED!=tm_proclocal_single(p_ub, cd, cmd, &arraxid[i], tmtxflags))
1215             {
1216                 NDRX_DUMP(log_error, "Failed to process local xid", &arraxid[i], 
1217                         sizeof(arraxid[i]));
1218                 EXFAIL_OUT(ret);
1219             }
1220         }
1221         
1222         if (ret < XID_RECOVER_BLOCK_SZ)
1223         {
1224             /* this is EOF according to the spec */
1225             break;
1226         }
1227         
1228     }
1229     
1230 out:
1231 
1232     return ret;
1233 }
1234 
1235 
1236 
1237 
1238 
1239 
1240 /* vim: set ts=4 sw=4 et smartindent: */