/*  Copyright (C) 1993,1994 by the author(s).
 
 This software is published in the hope that it will be useful, but
 WITHOUT ANY WARRANTY for any part of this software to work correctly
 or as described in the manuals. See the ShapeTools Public License
 for details.

 Permission is granted to use, copy, modify, or distribute any part of
 this software but only under the conditions described in the ShapeTools 
 Public License. A copy of this license is supposed to have been given
 to you along with ShapeTools in a file named LICENSE. Among other
 things, this copyright notice and the Public License must be
 preserved on all copies.
 */
/*
 * ShapeTools Version Control System
 *
 * dovadm.c - several routines for "vadm" command
 *
 * by Andreas.Lampen@cs.tu-berlin.de
 *
 * $Header: dovadm.c[7.1] Thu Aug  4 16:18:16 1994 andy@cs.tu-berlin.de frozen $
 */

#include "atfs.h"
#include "sttk.h"
#include "atfstk.h"
#include "vadm.h"

extern int    forceFlag;
extern int    nomailFlag;
extern int    stdinFlag;
extern int    lockType, lockGen, lockRev;
extern char   *lockString;
extern time_t lockDate;


/*=======================
 *  doDelete
 *=======================*/

EXPORT int doDelete (asoName, aso)
     char   *asoName;
     Af_key *aso;
{
  Af_user *locker;

  int asoStatus = af_retnumattr (aso, AF_ATTSTATE);

  if (VATTR(aso).af_state != AF_DERIVED) {
    if (asoStatus > AF_SAVED) {
      sprintf (stMessage, "Cannot delete %s -- version status must be 'saved'.", asoName);
      stLog (stMessage, ST_LOG_ERROR);
      return (1);
    }

    if (atUserValid (locker = af_testlock (aso))) {
      if (atUserUid (locker) != geteuid ()) {
	/* locked by somebody else */
	sprintf (stMessage, "No permission to delete %s (locked by %s).", asoName, atUserName (locker));
	stLog (stMessage, ST_LOG_ERROR);
	return (1);
      }
    }
    else {
      /* the version is not locked. We must lock it to delete it. */
      if (af_lock (aso, af_afuser (geteuid ())) == NULL) {
	sprintf (stMessage, "Cannot lock %s for deletion -- not deleted.", asoName);
	stLog (stMessage, ST_LOG_ERROR);
	return (1);
      }
    }
  }

  if (!forceFlag && !stQuietFlag) {
    sprintf (stMessage, "Delete %s ?", asoName);
    if (!stAskConfirm (stMessage, "yes")) {
      sprintf (stMessage, "%s not deleted.", asoName);
      stLog (stMessage, ST_LOG_MSG);
      return (0);
    }
  }
  if (af_rm (aso) < 0) {
    sprintf (stMessage, "Cannot delete %s -- %s.", asoName, af_errmsg ("af_rm"));
    stLog (stMessage, ST_LOG_ERROR);
    return (1);
  }
  sprintf (stMessage, "%s deleted.", asoName);
  stLog (stMessage, ST_LOG_MSG);
  return (0);
}

/*=======================
 *  doLock
 *=======================*/

static Af_key lastLockAso = {NULL, 0};

EXPORT int doLock (path, asoName, aso, mode)
     char   *path;
     char   *asoName;
     Af_key *aso;
     int    mode;
{
  char    busyName[PATH_MAX], lockName[PATH_MAX], hostname[HOST_MAX+1];
  char    *opName;
  Af_key  lockAso, *tmpKey;
  Af_user *locker, *caller = af_afuser (geteuid());
  struct stat ibuf;
  FILE    *pip;

  /* get proper version to be locked */
  switch (lockType) {
  case AT_BIND_DATE:
    lockString = asctime (localtime (&lockDate));
    /* no break -- continue with next case */
  case AT_BIND_ALIAS:
    if ((tmpKey = atBindVersion (asoName, lockString))) {
      lockGen = af_retnumattr (tmpKey, AF_ATTGEN);
      af_dropkey (tmpKey);
    }
    else {
      sprintf (stMessage, "Symbolic name %s not known for %s -- skipped.",
	       lockString, asoName);
      stLog (stMessage, ST_LOG_ERROR);
      return (1);
    }
    break;
  case AT_BIND_VNUM:
    /* do nothing */
    break;
  case AT_BIND_DEFAULT:
    if ((lockGen = af_retnumattr (aso, AF_ATTGEN)) == AF_BUSYVERS)
      lockGen = AF_LASTVERS;
    break;
  }
  strcpy (busyName, af_retattr (aso, AF_ATTUNIXNAME));
  if (af_getkey (path, af_afname (busyName), af_aftype (busyName),
		 lockGen, AF_LASTVERS, &lockAso) < 0) {
    if (mode == VADM_UNLOCK)
      opName = "unlock";
    else
      opName = "lock";
    if (lockGen == AF_LASTVERS)
      sprintf (stMessage, "%s not %sed -- no versions found.", asoName, opName);
    else
      sprintf (stMessage, "Cannot %s %s -- generation %d does not exist.",
	       opName, asoName, lockGen);

    stLog (stMessage, ST_LOG_ERROR);
    return (1);
  }

  /* sort out multiple versions from the same history/generation */
  if ((lastLockAso.af_ldes == lockAso.af_ldes) && (lastLockAso.af_lpos == lockAso.af_lpos))
    return (0);
  af_dropkey (&lastLockAso);
  lastLockAso = lockAso;

  /* build lockName for output */
  if (path && *path) {
    strcpy (lockName, path);
    strcat (lockName, "/");
    strcat (lockName, af_retattr (&lockAso, AF_ATTBOUND));
  }
  else
    strcpy (lockName, af_retattr (&lockAso, AF_ATTBOUND));

  /* if not locked */
  if (!atUserValid (locker = af_testlock(&lockAso))) {
    if (mode == VADM_UNLOCK) {
      sprintf (stMessage, "%s was already unlocked.", lockName);
      stLog (stMessage, ST_LOG_MSG);
      return (0);
    }
    if (!stQuietFlag)
      atSetComment (&lockAso, AT_COMMENT_INTENT, NULL, AT_REUSE | AT_CONFIRM | (stdinFlag ? AT_FROMSTDIN : 0));
    if (stat (busyName, &ibuf) == 0) {
      stLog ("WARNING!", ST_LOG_MSG);
      stLog ("*** Locking of read-only copies is not recommended.", ST_LOG_MSG);
      stLog ("*** You may eclipse modifications made by other users.", ST_LOG_MSG);
      stLog ("*** Attribute references in the source text may also be lost.", ST_LOG_MSG);
      stLog ("*** The recommended procedure is to use \"retrv -lock ", ST_LOG_MSG | ST_LOG_NONL);
      stLog (busyName, ST_LOG_MSG | ST_LOG_NONL);
      stLog ("\".", ST_LOG_MSG);
      if (!forceFlag && stAskConfirm ("Continue locking ?", "no")) {
	return (0);
      }
    }
    if (!(af_lock (&lockAso, caller))) {
      sprintf (stMessage, "Cannot lock %s -- %s.", asoName, af_errmsg ("af_lock"));
      stLog (stMessage, ST_LOG_ERROR);
      return (1);
    }
    if (stat (busyName, &ibuf) == 0) {
      chmod (busyName, (int)(ibuf.st_mode | 0200));
      /*
       * Attach attributes of lockAso to soon-to-be busy version
       */
      if (af_retnumattr (aso, AF_ATTSTATE) == AF_BUSY) {
	Af_attrs attrbuf;
	char *reserveDate, *attrPtr;
	register int j;
	
	if (af_allattrs (&lockAso, &attrbuf) < 0) {
	  af_perror ("af_allattrs");
	  return (1);
	}
	j = 0;
	while ((attrPtr = attrbuf.af_udattrs[j++])) {
	  if (af_setattr (aso, AF_REPLACE, attrPtr) == -1)
	    af_setattr (aso, AF_ADD, attrPtr);
	}
	af_freeattrbuf (&attrbuf);
	af_setattr (aso, AF_REMOVE, AT_ATTALIAS);
	af_setattr (aso, AF_REMOVE, AT_ATTINTENT);
	if ((reserveDate = malloc ((unsigned) (strlen ("rtime") + 32)))) {
	  sprintf (reserveDate, "%s=%s", "rtime", af_retattr (aso, AF_ATTCTIME));
	  if ((af_setattr (aso, AF_REPLACE, reserveDate) == -1) &&
	      (af_setattr (aso, AF_ADD, reserveDate) == -1)) {
	    sprintf (stMessage, "Can't set reservation date for %s.", busyName);
	    stLog (stMessage, ST_LOG_WARNING);
	  }
	  free (reserveDate);
	}
	else {
	  sprintf (stMessage, "Can't set reservation date for %s (no memory).", busyName);
	  stLog (stMessage, ST_LOG_WARNING);
	}
      }
    }
    
    sprintf (stMessage, "%s locked.", lockName);
    stLog (stMessage, ST_LOG_MSG);
    return (0);
  }
  
  /* else there is a locker and it's me */
  if (atUserUid (locker) == geteuid()) {
    if (mode == VADM_UNLOCK) {
      /* clear write bits */
      if (stat (busyName, &ibuf) == 0) {
	chmod (busyName, (int)(ibuf.st_mode & ~0222));
      }
      af_unlock (&lockAso);
      sprintf (stMessage, "%s unlocked.", lockName);
      stLog (stMessage, ST_LOG_MSG);
      return (0);
    }
    /* else VADM_LOCK */
    sprintf (stMessage, "%s was already locked by us.", lockName);
    stLog (stMessage, ST_LOG_MSG);
    return (0);
  }

  /* else it is locked by someone else */
  if (mode == VADM_LOCK) {
    sprintf (stMessage, "%s is already locked by %s.", lockName, atUserName (locker));
    stLog (stMessage, ST_LOG_MSG);
    return (1);
  }
  
  if (atUserUid (af_retuserattr (aso, AF_ATTOWNER)) == geteuid()) { 
    /* we've got the power ... */
    sprintf (stMessage, "%s currently locked by %s. Break the lock ?",
	     lockName, atUserName (locker));
    if (stAskConfirm (stMessage, "yes")) {
      if (af_unlock (&lockAso) == NULL) {
	stLog (af_errmsg ("af_unlock"), ST_LOG_ERROR);
	return (1);
      }
      if (!nomailFlag) {
	sprintf (stMessage, "/bin/mail %s", atUserName(locker));
	if ((pip = popen (stMessage, "w")) == NULL) {
	  stLog ("Couldn't notify lockholder...", ST_LOG_WARNING);
	}
	else {
	  gethostname (hostname, HOST_MAX);
	  fprintf (pip, "Subject: Your lock on %s was broken by %s\n",
		   af_retattr (&lockAso, AF_ATTBOUNDPATH), atUserName (caller));
	  fprintf (pip, "This message was issued automatically by ");
	  fprintf (pip, "the ShapeTools version control system.\n");
	  fprintf (pip, "Your lock on %s was broken by %s.\n",
		   af_retattr (&lockAso, AF_ATTBOUNDPATH), atUserName (caller));
	  pclose (pip);
	}
      }
      sprintf (stMessage, "%s unlocked.", lockName);
      stLog (stMessage, ST_LOG_MSG);
      return (0);
    }
    else { /* we don't wanna break the lock */
      sprintf (stMessage, "%s remains locked by %s.", lockName, atUserName (locker));
      stLog (stMessage, ST_LOG_MSG);
      return (0);
    }
  }
  else { /* we cannot unlock the required version */
    sprintf (stMessage, "%s is locked by %s.", lockName, atUserName (locker));
    stLog (stMessage, ST_LOG_MSGERR);
    return (1);
  }
}

/*=======================
 *  doStatus
 *=======================*/

EXPORT int doStatus (asoName, aso, status)
     char   *asoName;
     Af_key *aso;
     int    status;
{
  int     oldStatus = af_retnumattr (aso, AF_ATTSTATE), newStatus;
  Af_user *locker;
  char    *actionName;

  if (oldStatus == AF_BUSY) {
    sprintf (stMessage, "Cannot change status of %s -- is a busy version.", asoName);
    stLog (stMessage, ST_LOG_ERROR);
    return (1);
  }

  if ((atUserUid (locker = af_retuserattr (aso, AF_ATTLOCKER)) != geteuid ()) &&
      atUserValid (locker)) {
    sprintf (stMessage, "Cannot change status of %s -- locked by %s.",
	     asoName, atUserName (locker));
    stLog (stMessage, ST_LOG_ERROR);
    return (1);
  }

  if (status == VADM_STATUS_PROMOTE) {
    if (oldStatus == AF_FROZEN) {
      sprintf (stMessage, "Status of %s is 'frozen' -- cannot promote.", asoName);
      stLog (stMessage, ST_LOG_ERROR);
      return (1);
    }
    newStatus = oldStatus+1;
    actionName = "promoted";
  }
  else if (status == VADM_STATUS_UNPROMOTE) {
    if (oldStatus == AF_SAVED) {
      sprintf (stMessage, "Status of %s is 'saved' -- cannot unpromote.", asoName);
      stLog (stMessage, ST_LOG_ERROR);
      return (1);
    }
    newStatus = oldStatus-1;
    actionName = "unpromoted";
  }
  else {
    if ((status - oldStatus) < 0) {
      sprintf (stMessage, "Status of %s already '%s' (better than desired status) -- not changed.", asoName, atWriteStatus (aso, TRUE));
      stLog (stMessage, ST_LOG_MSGERR); 
      return (1);
    }
    newStatus = status;
    actionName = "set";
  }

  /* lock aso if this is not yet done */
  if (!atUserValid (locker)) {
    if (af_lock (aso, af_afuser (geteuid())) == NULL) {
      sprintf (stMessage, "Cannot change status of %s -- cannot lock.", asoName);
      stLog (stMessage, ST_LOG_ERROR);
      return (1);
    }
  }

  if (af_sstate (aso, newStatus) < 0) {
    stLog (af_errmsg ("af_sstate"), ST_LOG_ERROR);
    return 1;
  }

  /* unlock ASO again if the lock was set only for the status change */
  if (!atUserValid (locker))
    af_unlock (aso);

  sprintf (stMessage, "Status of %s %s to '%s'.", asoName, actionName, atWriteStatus (aso, TRUE));
  stLog (stMessage, ST_LOG_MSG);
  return (0);
}

/*=======================
 *  doComment
 *=======================*/

EXPORT int doComment (path, asoName, aso, commentType)
     char   *path;
     char   *asoName;
     Af_key *aso;
     char   *commentType;
{
  Af_key  tmpAso;
  Af_user *locker;
  char    outName[PATH_MAX], busyName[PATH_MAX];

  strcpy (busyName, af_retattr (aso, AF_ATTUNIXNAME));
  /************ description **************/
  if (!strncmp (commentType, "description", strlen (commentType))) {
    /* description must be set to first version of all */
    if (af_getkey (path, af_afname (busyName), af_aftype (busyName),
		   AF_FIRSTVERS, AF_FIRSTVERS, &tmpAso) < 0) {
      sprintf (stMessage, "Cannot find first version of %s -- skipped.", asoName);
      stLog (stMessage, ST_LOG_ERROR);
      return (1);
    }
    /* build name for output */
    if (path && *path) {
      strcpy (outName, path);
      strcat (outName, "/");
      strcat (outName, af_retattr (&tmpAso, AF_ATTBOUND));
    }
    else
      strcpy (outName, af_retattr (&tmpAso, AF_ATTBOUND));

    if (!atSetComment (&tmpAso, AT_COMMENT_DESCR, NULL, AT_REUSE | (stdinFlag ? AT_FROMSTDIN : 0))) {
      sprintf (stMessage, "Cannot set description for %s -- %s.", outName, atErrMsg);
      stLog (stMessage, ST_LOG_ERROR);
      af_dropkey (&tmpAso);
      return (1);
    }
    af_dropkey (&tmpAso);
  }
  /************ intent **************/
  else if  (!strncmp (commentType, "intent", strlen (commentType))) {
    /* intent is set to last version of generation */
    if (af_getkey (path, af_afname (busyName), af_aftype (busyName),
		   af_retnumattr (aso, AF_ATTGEN), AF_LASTVERS, &tmpAso) < 0) {
      sprintf (stMessage, "Cannot find most recent version of %s -- skipped.", asoName);
      stLog (stMessage, ST_LOG_ERROR);
      return (1);
    }
    /* build name for output */
    if (path && *path) {
      strcpy (outName, path);
      strcat (outName, "/");
      strcat (outName, af_retattr (&tmpAso, AF_ATTBOUND));
    }
    else
      strcpy (outName, af_retattr (&tmpAso, AF_ATTBOUND));
    if (atUserValid (locker = af_testlock(&tmpAso))) {
      if (atUserUid (locker) == geteuid ()) {
	if (!atSetComment (&tmpAso, AT_COMMENT_INTENT, NULL, AT_REUSE | (stdinFlag ? AT_FROMSTDIN : 0))) {
	  sprintf (stMessage, "Cannot set intent for %s -- %s.", outName, atErrMsg);
	  stLog (stMessage, ST_LOG_ERROR);
	  af_dropkey (&tmpAso);
	  return (1);
	}
      }
      else {
	sprintf (stMessage, "Cannot set intent on %s -- locked by %s.", outName, atUserName (locker));
	stLog (stMessage, ST_LOG_ERROR);
	af_dropkey (&tmpAso);
	return (1);
      }
    }
    else {
      sprintf (stMessage, "You must have a lock on %s to set change intention.", outName);
      stLog (stMessage, ST_LOG_MSGERR);
      af_dropkey (&tmpAso);
      return (1);
    }
    af_dropkey (&tmpAso);
  }
  /************ note **************/
  else if  (!strncmp (commentType, "note", strlen (commentType))) {
    if (!atSetComment (aso, AT_COMMENT_LOG, NULL, AT_REUSE | (stdinFlag ? AT_FROMSTDIN : 0))) {
      sprintf (stMessage, "Cannot set note for %s -- %s.", asoName, atErrMsg);
      stLog (stMessage, ST_LOG_ERROR);
      return (1);
    }
  }
  /************ default **************/
  else {
    sprintf (stMessage, "usage: %s -set {note|description|intent} <name>...", stProgramName);
    stLog (stMessage, ST_LOG_MSGERR);
    return (1);
  }
  return (0);
}
