/*
 *      SCCS:  @(#)sync.c	1.18 (98/08/28) 
 *
 *	UniSoft Ltd., London, England
 *
 * (C) Copyright 1992 X/Open Company Limited
 *
 * All rights reserved.  No part of this source code may be reproduced,
 * stored in a retrieval system, or transmitted, in any form or by any
 * means, electronic, mechanical, photocopying, recording or otherwise,
 * except as stated in the end-user licence agreement, without the prior
 * permission of the copyright owners.
 *
 * X/Open and the 'X' symbol are trademarks of X/Open Company Limited in
 * the UK and other countries.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

/************************************************************************

SCCS:   	@(#)sync.c	1.18 98/08/28 TETware release 3.3
NAME:		sync.c
PRODUCT:	TETware
AUTHOR:		Andrew Dingwall, UniSoft Ltd.
DATE CREATED:	April 1992

SYNOPSIS:
	#include "tet_api.h"

	int tet_remsync(long syncptno, int *syncnames, int nsysname,
		int waittime, int vote, struct tet_synmsg *msgp);

	int tet_sync(long syncptno, int *syncnames, int waittime);

	int tet_msync(long syncptno, int *syncnames, int waittime,
		struct tet_synmsg *msgp);

	void (*tet_syncerr)(struct tet_syncstat *statp, int nstat);

	void tet_syncreport(struct tet_syncstat *statp, int nstat);

DESCRIPTION:
	DTET API functions

	Tet_remsync() performs a user sync operation, returning 0 if
	successful or -1 on error.  Tet_sync() and tet_msync() provide
	a subset of the facilities of tet_remsync() and are included
	for backwards compatibility.

	Tet_syncreport() is the default sync-error-reporting function.
	Tet_syncerr is initially set to point to this function, but it
	may be changed to point to a user-supplied reporting function.

MODIFICATIONS:
	Denis McConalogue, UniSoft Limited, August 1993
	make syncptno parameter long rather than int
	changed dtet to tet2 in #include

	Andrew Dingwall, UniSoft Ltd., December 1993
	changed dapi.h to dtet2/tet_api.h

	Andrew Dingwall, UniSoft Ltd., October 1994
	added tet_msync() API function

	Geoff Clare, UniSoft Ltd., July-August 1996
	Changes for TETWare.

	Geoff Clare, UniSoft Ltd., Sept 1996
	Changes for TETWare-Lite.

	Andrew Dingwall, UniSoft Ltd., June 1997
	1) fixed the way in which sy_ptype is determined so that sync
	still works when there is no system 0
	2) in tet_syncreport(), print messages in an atomic operation if
	possible using tet_merror() - that way, when a sync fails which
	involves 3 or more systems, the lines from each system don't get
	mixed up in the journal

	Andrew Dingwall, UniSoft Ltd., July 1998
	Added support for shared API libraries.

	Aaron Plattner, April 2010
	Fixed warnings when compiled with GCC's -Wall option.

************************************************************************/

#ifndef TET_LITE /* -START-LITE-CUT- */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include "dtmac.h"
#include "dtmsg.h"
#include "dtthr.h"
#include "synreq.h"
#include "error.h"
#include "globals.h"
#include "servlib.h"
#include "dtetlib.h"
#include "tet_api.h"
#include "tet_jrnl.h"
#include "apilib.h"

#ifndef NOTRACE
#include "ltoa.h"
#endif

#define REPORT_YES	1
#define REPORT_NO	0

/* static function declarations */
static int tet_ms2 PROTOLIST((long, int *, int, int, int, struct tet_synmsg *,
	int));


TET_IMPORT int tet_remsync(syncptno, syncnames, nsyncname, waittime, vote, msgp)
long syncptno;
int *syncnames, nsyncname, waittime, vote;
struct tet_synmsg *msgp;
{
	if (!syncnames || nsyncname <= 0 ||
	    (vote != TET_SV_YES && vote != TET_SV_NO))
	{
		tet_errno = TET_ER_INVAL;
		return -1;
	}

	return tet_ms2(syncptno, syncnames, nsyncname, waittime, vote,
		msgp, REPORT_NO);
}

TET_IMPORT int tet_sync(syncptno, syncnames, waittime)
long syncptno;
int *syncnames, waittime;
{
	int nsyncname, defaultsync = 0;

	if (!syncnames) 
		syncnames = &defaultsync;

	/* count the entries in syncnames[] */
	for (nsyncname = 0; syncnames[nsyncname] != 0; nsyncname++)
		;

	/* include the 0 terminator so that system 0 participates */
	nsyncname++;

	return tet_ms2(syncptno, syncnames, nsyncname, waittime, TET_SV_YES,
		(struct tet_synmsg *) 0, REPORT_YES);
}

TET_IMPORT int tet_msync(syncptno, syncnames, waittime, msgp)
long syncptno;
int *syncnames, waittime;
struct tet_synmsg *msgp;
{
	int nsyncname, defaultsync = 0;

	if (!syncnames) 
		syncnames = &defaultsync;

	/* count the entries in syncnames[] */
	for (nsyncname = 0; syncnames[nsyncname] != 0; nsyncname++)
		;

	/* include the 0 terminator so that system 0 participates */
	nsyncname++;

	return tet_ms2(syncptno, syncnames, nsyncname, waittime, TET_SV_YES,
		msgp, REPORT_YES);
}

static int tet_ms2(syncptno, syncnames, nsys, waittime, vote, msgp, report_errs)
long syncptno;
int *syncnames, nsys, waittime, vote, report_errs;
struct tet_synmsg *msgp;
{
	register struct synreq *sp;
	register int *ip;
	register struct tet_syncstat *tsp;
	struct synmsg synmsg, *synmsgp;
	static struct synreq *synreq;
	static int reqlen;
	static struct tet_syncstat *synstat;
	static int statlen;

	API_LOCK;

	/* make sure that the synreq buffer is big enough */
	if (BUFCHK((char **) &synreq, &reqlen, (int) (nsys * sizeof *synreq)) < 0)
	{
		tet_errno = TET_ER_ERR;
		API_UNLOCK;
		return(-1);
	}

	/* copy the system names in to the synreq buffer */
	sp = synreq;
	for (ip = syncnames; ip < &syncnames[nsys]; ip++)
		if (*ip != tet_mysysid) {
			sp->sy_sysid = *ip;
			if (tet_snames && tet_Nsname > 0 &&
				*ip == *tet_snames)
					sp->sy_ptype = PT_MTCM;
			else
				sp->sy_ptype = PT_STCM;
			sp++;
		}

	/* recalculate nsys (will go down if tet_mysysid was in syncnames) */
	nsys = (sp - synreq);
	if (nsys == 0) {
		error(0, "syncnames does not include any other systems",
			(char *) 0);
		tet_errno = TET_ER_ERR;
		API_UNLOCK;
		return(-1);
	}

	/* set up the synmsg structure */
	if (msgp && (msgp->tsm_flags & (TET_SMSNDMSG | TET_SMRCVMSG))) {
		synmsg.sm_data = msgp->tsm_data;
		synmsg.sm_mdlen = msgp->tsm_dlen;
		if (msgp->tsm_flags & TET_SMSNDMSG) {
			synmsg.sm_flags = SM_SNDMSG;
			if ((synmsg.sm_dlen = msgp->tsm_dlen) > TET_SMMSGMAX) {
				synmsg.sm_dlen = TET_SMMSGMAX;
				synmsg.sm_flags |= SM_TRUNC;
			}
		}
		else {
			synmsg.sm_flags = SM_RCVMSG;
			synmsg.sm_dlen = 0;
		}
		synmsgp = &synmsg;
	}
	else
		synmsgp = (struct synmsg *) 0;

	/* perform the sync operation and handle the reply codes */
	if (tet_sdusync(tet_snid, tet_xrid, syncptno, vote, waittime, synreq, nsys, synmsgp) < 0) {
		if (report_errs) {
			char buf[100];
			switch (tet_sderrno) {
			case ER_DONE:
				sprintf(buf,
				    "sync event already happened, syncptno = %ld",
				    syncptno);
				tet_error(0, buf);
				break;
			case ER_INVAL:
				error(0, "invalid request parameter", (char *) 0);
				break;
			case ER_DUPS:
				error(0, "duplicates in syncnames list", (char *) 0);
				break;
			}
		}
		tet_errno = -tet_sderrno;
		API_UNLOCK;
		return(-1);
	}

	/* if this sytem voted NO, then other NO votes are not an error */
	if (vote == TET_SV_NO && tet_sderrno == ER_SYNCERR) {
		tet_sderrno = ER_OK;
		for (sp = synreq; sp < synreq + nsys; sp++)
			if (sp->sy_state != SS_SYNCYES &&
			    sp->sy_state != SS_SYNCNO) {
				tet_sderrno = ER_SYNCERR;
				break;
			}
	}

	/* do message processing */
	if (tet_sderrno == ER_OK || tet_sderrno == ER_SYNCERR) {
		if (msgp && (msgp->tsm_flags & (TET_SMSNDMSG | TET_SMRCVMSG))) {
			msgp->tsm_dlen = synmsg.sm_dlen;
			msgp->tsm_sysid = synmsg.sm_sysid;
			if (synmsg.sm_flags & SM_RCVMSG)
				msgp->tsm_flags = (msgp->tsm_flags & ~TET_SMSNDMSG) | TET_SMRCVMSG;
			if (synmsg.sm_flags & SM_DUP)
				msgp->tsm_flags |= TET_SMDUP;
			if (synmsg.sm_flags & SM_TRUNC)
				msgp->tsm_flags |= TET_SMTRUNC;
		}
	}

	/* return now if request succeeded */
	if (tet_sderrno == ER_OK) {
		API_UNLOCK;
		return(0);
	}

	/* make sure that the synstat buffer is big enough */
	if (BUFCHK((char **) &synstat, &statlen, (int) (nsys * sizeof *synstat)) < 0)
	{
		tet_errno = TET_ER_ERR;
		API_UNLOCK;
		return(-1);
	}

	/* here if the request failed in an "expected" way -
		pass detailed status info to reporting function */

	tet_errno = -tet_sderrno;

	if (tet_syncerr) {
		int sav_errno = tet_errno;

		tsp = synstat;
		for (sp = synreq; sp < synreq + nsys; sp++) {
			tsp->tsy_sysid = sp->sy_sysid;
			tsp->tsy_state = sp->sy_state;
			tsp++;
		}

		(*tet_syncerr)(syncptno, synstat, nsys);

		tet_errno = sav_errno; /* in case (*tet_syncerr)() changed it */
	}

	API_UNLOCK;
	return(-1);
}

TET_IMPORT void tet_syncreport(syncptno, statp, nsys)
long syncptno;
struct tet_syncstat *statp;
int nsys;
{
	struct tet_syncstat *tsp;
	char *p;
	char buf[512];
	static char nullstr[] = "";
	static char sstr[] = "s";
	char **lines, **lp;

	/*
	** allocate memory for the list of message line pointers 
	** if possible
	*/
	errno = 0;
	if ((lines = (char **) malloc((nsys + 1) * sizeof *lines)) == (char **) 0)
		error(errno,
			"can't allocate memory for sync report line pointers",
			(char *) 0);
	else
		TRACE2(tet_Tbuf, 6, "allocate sync report line pointers = %s",
			tet_i2x(lines));
	lp = lines;

	/*
	** generate a TCM info message detailing processes that caused the
	** sync to fail (as required by the spec)
	**
	** the message lines are stored in memory if possible, then
	** written out all at once;
	** if this is not possible they are written out one-at-a-time
	*/
	sprintf(buf, "sync operation failed, syncptno = %ld, ",
		syncptno);
	p = buf + strlen(buf);
	if (tet_errno == TET_ER_SYNCERR)
		sprintf(p, "%sother system%s did not sync or timed out",
			nsys > 1 ? "one or more of the " : nullstr,
			nsys == 1 ? nullstr : sstr);
	else if (tet_errno == TET_ER_TIMEDOUT)
		sprintf(p, "request timed out");
	else if (tet_errno >= 0 && tet_errno < tet_nerr)
		sprintf(p, "%s", tet_errlist[tet_errno]);
	else
		sprintf(p, "tet_errno = %d (unknown value)", tet_errno);
	if (lines)
		*lp++ = tet_strstore(buf);
	else
		tet_error(0, buf);

	/* generate the per-system diagnostics */
	for (tsp = statp; tsp < statp + nsys; tsp++) {
		sprintf(buf, "system = %2d, state = %s",
			tsp->tsy_sysid, tet_systate(tsp->tsy_state));
		if (lines)
			*lp++ = tet_strstore(buf);
		else
			tet_error(0, buf);
	}

	/* emit all the message lines at once if we stored them */
	if (lines)
		tet_merror(0, lines, nsys + 1);

	/* free storage allocated here */
	if (lines) {
		for (lp = lines; lp < lines + nsys + 1; lp++)
			if (*lp) {
				TRACE2(tet_Tbuf, 6,
					"free sync report line = %s",
					tet_i2x(*lp));
				free(*lp);
			}
		TRACE2(tet_Tbuf, 6, "free sync report line pointers = %s",
			tet_i2x(lines));
		free((char *) lines);
	}
}

TET_IMPORT void (*tet_syncerr)() = tet_syncreport;

#else /* -END-LITE-CUT- */

/* avoid "empty" file */
int tet_XSync_not_supported;

#endif /* -LITE-CUT-LINE- */

