/* 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
 *
 *	doretrv.c - "retrv" command
 *
 *      by Axel.Mahler@cs.tu-berlin.de
 *	and Andreas.Lampen@cs.tu-berlin.de
 *
 *	$Header: doretrv.c[6.2] Sun Jan 23 21:55:33 1994 axel@cs.tu-berlin.de frozen $
 */

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

extern int copyFlag;
extern char *destPath;
extern int fixFlag;
extern int forceFlag;
extern char *intent;
extern int lockFlag;
extern int stdinFlag;
extern int expandFlag;
extern int vcatFlag;
extern Af_key *newLock;

extern int af_errno;

/* permission status for retrieveAFile */
#define DOIT	   01
#define DENIED	   02
#define RECREATE   04
#define UNCHANGED 010

/*=========================
 *  vcat
 *=========================*/

EXPORT void showAFile (path, aso)
     char *path;
     Af_key *aso;
{
  FILE *versionFile;
  int nbytes;
  char *contentsBuf;

  /* an empty argument indicates, that "vcat" was called
   * withour arguments or with "-" as argument -> read from stdin
   */
  if (!aso) {
#   define IOBUFLEN 2048
    char iobuf[IOBUFLEN];
    int cr, cw;

    stLog ("stdin", ST_LOG_MSGERR);
    while ((cr = fread (iobuf, sizeof (char), IOBUFLEN, stdin))) {
      cw = fwrite (iobuf, sizeof (char), cr, stdout);
      fflush (stdout);
      if (cw != cr)
	stLog ("Oops! Write error on stdout", ST_LOG_ERROR);
    }
    clearerr (stdin);
    return;
  }

  /* ToDo: show file */
  if (!(versionFile = af_open (aso, "r"))) {
    stLog (af_errmsg ("af_open"), ST_LOG_ERROR);
    stAbortThis (TRUE);
  }
  if (path[0])
    sprintf (stMessage, "%s/%s", path, af_retattr (aso, AF_ATTBOUND));
  else
    sprintf (stMessage, "%s", af_retattr (aso, AF_ATTBOUND));
  stLog (stMessage, ST_LOG_MSGERR);

  if ((contentsBuf = malloc ((unsigned) af_retnumattr (aso, AF_ATTSIZE)+1)) == NULL) {
    stLog ("Out of memory.", ST_LOG_ERROR);
    stAbortThis (TRUE);
  }
  nbytes = fread (contentsBuf, sizeof (char), af_retnumattr (aso, AF_ATTSIZE), versionFile);
  contentsBuf[nbytes] = '\0';
  af_close (versionFile);
  if (expandFlag)
    atExpandAttrs (aso, contentsBuf, nbytes, stdout, 0, AT_EXPAND_FILE);
  else
    fwrite(contentsBuf, sizeof (char), nbytes, stdout);
  fflush (stdout);
  free (contentsBuf);
}

/*=========================
 *  retrv
 *=========================*/

EXPORT void retrieveAFile (path, aso)
     char *path;
     Af_key *aso;
{
  char *busyLocation, destName[PATH_MAX], tmpName[PATH_MAX], *contentsBuf;
  char *reserveDate, *attrPtr;
  unsigned int  permit = 0;
  int  nbytes, i;
  FILE *newFile, *versionFile, *tmpFile;
  Af_key busyAso, lastAso;
  Af_attrs asoAttrBuf;
  Af_user *locker;
  struct stat iBuf;
  struct utimbuf oldDate;

  busyLocation = destPath ? destPath : (path[0] ? path : ".");
  if (destPath || path[0])
    sprintf (destName, "%s/%s", busyLocation, af_retattr (aso, AF_ATTUNIXNAME));
  else
    strcpy (destName, af_retattr (aso, AF_ATTUNIXNAME));
  af_allattrs (aso, &asoAttrBuf);

  if (!lockFlag) {
    /* This creates a plain UNIX file from the specified ASO
     * The created copy is - in general - an object without
     * history. If, however, the copy happens to go into the 
     * history-directory (the one containing the archive) it will
     * 'automatically' be considered the busy-version.
     * If - in this case - a copy replaces a formerly locked busy-version,
     * the lock will be released.
     */
    if ((newFile = fopen (destName, "r")) == NULL) {
      /* take this as test for presence */
      if (access (busyLocation, W_OK) == 0) { /* may we create ? */
	permit |= DOIT; /* No scruples if no busyvers current */
      }
      else {
	sprintf (stMessage, "write permission for directory %s denied.", busyLocation);
	stLog (stMessage, ST_LOG_ERROR);
	permit |= DENIED;
      }
    }
    else { /* file exists */
      /* check if it is already restored */
      if (!copyFlag && (fstat (fileno(newFile), &iBuf) != -1)) {
	if ((iBuf.st_size == af_retnumattr (aso, AF_ATTSIZE)) &&
	    (iBuf.st_mtime == af_rettimeattr (aso, AF_ATTMTIME)) &&
	    (iBuf.st_mode == (af_retnumattr (aso, AF_ATTMODE) & ~0222))) {
	  if (stQuietFlag)
	    permit |= UNCHANGED;
	  else if (forceFlag)
	    /* do nothing */;
	  else {
	    sprintf (stMessage, "%s and %s are identical, retrieve anyway ?",
		     af_retattr (aso, AF_ATTBOUND), destName);
	    if (stAskConfirm (stMessage, "no"))
	      permit |= UNCHANGED;
	  }
	}
      }
      fclose (newFile);
      if (permit & UNCHANGED) {
	sprintf (stMessage, "%s not retrieved", af_retattr (aso, AF_ATTBOUND));
	stLog (stMessage, ST_LOG_MSGERR);
	return;
      }
      if (access (destName, W_OK) < 0) {
	if (access (busyLocation, W_OK) == 0) {
	  if (stQuietFlag)
	    permit |= forceFlag ? (RECREATE | DOIT) : DENIED;
	  else if (forceFlag)
	    permit |= RECREATE | DOIT;
	  else {
	    sprintf (stMessage, "%s write-protected, re-create it ?", destName);
	    if (stAskConfirm (stMessage, "no"))
	      permit |= DENIED;
	    else
	      permit |= (RECREATE | DOIT);
	  }
	}
	else { 
	  sprintf (stMessage, "no write permission for %s", destName);
	  stLog (stMessage, ST_LOG_ERROR);
	  permit |= DENIED;
	}
      }
      else { /* write access on destfile */
	if (strcmp (busyLocation, ".")) {
	  if (stQuietFlag)
	    permit |= forceFlag ? DOIT : 0;
	  else if (forceFlag)
	    permit |= DOIT;
	  else {
	    sprintf (stMessage, "%s exists and is writable. Overwrite it ?", destName);
	    if (!stAskConfirm (stMessage, "no"))
	      permit |= DOIT;
	  }
	}
	else { /* current dir! - test for lock */
	  /* this test looks only for a lock in the last generation. Locks on other
	     generations will not be recognized. However, this is not serious as
	     these will go into the else part  where "exists and is writable..."
	     is asked. */
	  if (af_getkey (path, asoAttrBuf.af_name, asoAttrBuf.af_type, AF_LASTVERS, AF_LASTVERS, &lastAso) == -1) {
	    /* No version -- this is impossible here */
	    sprintf (stMessage, "%s", af_errmsg (af_retattr (aso, AF_ATTUNIXNAME)));
	    stLog (stMessage, ST_LOG_ERROR);
	    stAbortThis (TRUE);
	  }
	  else {
	    if (atUserUid (af_retuserattr (&lastAso, AF_ATTLOCKER)) == geteuid ()) {
	      if (stQuietFlag)
		permit |= forceFlag ? DOIT : 0;
	      if (forceFlag)
		permit |= DOIT;
	      else {
		sprintf (stMessage, "Give up lock on %s and overwrite it ?", destName);
		if (!stAskConfirm (stMessage, "no")) {
		  permit |= DOIT;
		  atUnlock (&lastAso);
		}
		else {
		  permit |= DENIED;
		}
	      }
	    }
	    else {
	      if (stQuietFlag)
		permit |= forceFlag ? DOIT : 0;
	      else if (forceFlag)
		permit |= DOIT;
	      else {
		sprintf (stMessage, "%s exists and is writable. Overwrite it ?", destName);
		if (!stAskConfirm (stMessage, "no"))
		  permit |= DOIT;
	      }
	    }
	    af_dropkey (&lastAso);
	  }
	}
      }
    }
    if (permit & DOIT) {
      if ((versionFile = af_open (aso, "r")) == NULL) {
	stLog (af_errmsg ("af_open"), ST_LOG_ERROR);
	stAbortThis (TRUE);
      }
      strcpy (tmpName, stTmpFile (busyLocation));
      if ((tmpFile = fopen (tmpName, "w")) == NULL) {
	sprintf (stMessage, "cannot create temporary file %s for writing.", tmpName);
	stLog (stMessage, ST_LOG_ERROR);
	stAbortThis (TRUE);
      }
      if (path[0])
	sprintf (stMessage, "%s/%s -> %s", path, af_retattr (aso, AF_ATTBOUND), destName);
      else
	sprintf (stMessage, "%s -> %s", af_retattr (aso, AF_ATTBOUND), destName);
      stLog (stMessage, ST_LOG_MSGERR);
      if ((contentsBuf = malloc ((unsigned) asoAttrBuf.af_size+1)) == NULL) {
	stLog ("Out of memory", ST_LOG_ERROR);
	stAbortThis(TRUE);
      }
      nbytes = fread (contentsBuf, sizeof (char), asoAttrBuf.af_size, versionFile);
      contentsBuf[nbytes] = '\0';
      af_close (versionFile);
      if (expandFlag)
	atExpandAttrs (aso, contentsBuf, nbytes, tmpFile, 0, AT_EXPAND_FILE);
      else
	fwrite(contentsBuf, sizeof (char), nbytes, tmpFile);
      free (contentsBuf);
      fclose (tmpFile);

      oldDate.actime = asoAttrBuf.af_atime;
      oldDate.modtime = asoAttrBuf.af_mtime;
      utime (tmpName, &oldDate);
      unlink (destName);
      if (link (tmpName, destName) < 0) {
	sprintf (stMessage, "cannot link %s to %s.", tmpName, destName);
	stLog (stMessage, ST_LOG_ERROR);
	stAbortThis (TRUE);
      }
      chmod (destName, asoAttrBuf.af_mode & ~0222);
      stUnRegisterFile (tmpName);
      unlink (tmpName);
    }
    else {
      sprintf (stMessage, "%s not retrieved", af_retattr (aso, AF_ATTBOUND));
      stLog (stMessage, ST_LOG_MSGERR);
      stThisTransaction.tr_rc += 1;
    }
    return;
  }

  /* else lockFlag is set */
  /*
   *  Before a version is retrieved, set-busy, and locked, the
   *  following preconditions must be fulfilled:
   *  - the retrieve must go to the directory containing the 
   *    archive directory. -> current directory
   *  - the retrieved version must not be locked by anybody but
   *    the calling user.
   *  - the current directory must grant write access to the 
   *    calling user.
   *  - if some busy-version would be overwritten by the retrieve,
   *    the user is asked if she wants that
   */
  /*
   *  The following checks are based on the permission information
   *  stored in the archive files. It is unclear how
   *  to properly handle vanilla filesystem related inquiries.
   */
  if (af_getkey (path,  asoAttrBuf.af_name, asoAttrBuf.af_type,
		 fixFlag ? asoAttrBuf.af_gen : AF_LASTVERS, AF_LASTVERS, &lastAso) == -1) {
    sprintf (stMessage, "%s", af_errmsg (af_retattr (aso, AF_ATTBOUND)));
    stLog (stMessage, ST_LOG_ERROR);
    stAbortThis (TRUE);
  }

  /* there is a version */
  if ((atUserUid (locker = af_retuserattr (&lastAso, AF_ATTLOCKER)) == geteuid ()) ||
      !atUserValid (locker)) {
    if (access (destName, W_OK) == 0) {
      if (stQuietFlag)
	permit |= forceFlag ? DOIT : DENIED;
      else if (forceFlag)
	permit |= DOIT;
      else {
	sprintf (stMessage, "Writable %s exists, overwrite it ?", destName);
	permit |= (stAskConfirm (stMessage, "no")) ? DENIED : DOIT;
      }
    }
    else if (access (busyLocation, W_OK) == 0) {
      if (access (destName, F_OK) == 0) {
	if (stQuietFlag)
	  permit |= forceFlag ? DOIT : DENIED;
	else if (forceFlag)
	  permit |= DOIT;
	else {
	  sprintf (stMessage, "Write access on %s denied. Overwrite it anyway ?", destName);
	  permit |= (stAskConfirm (stMessage, "no")) ? DENIED : DOIT;
	}
      }
      else
	permit |= DOIT;
    }
    else { /* no write access on current dir */
      sprintf (stMessage, "Can't create in %s", busyLocation);
      stLog (stMessage, ST_LOG_ERROR);
      stAbortThis (TRUE);
    }
    if (!atUserValid (locker)) {
      if (!af_lock (&lastAso, af_afuser (geteuid()))) {
	sprintf (stMessage, "%s", af_errmsg (af_retattr (aso, AF_ATTBOUND)));
	stLog (stMessage, ST_LOG_ERROR);
	stAbortThis (TRUE);
      }
      newLock = &lastAso;
    }
    if (af_commit () < 0) {
      stLog ("Cannot commit lock!", ST_LOG_ERROR);
    }
    af_transaction ();
  }
  else { /* busy version locked by someone else */
    permit |= DENIED;
    sprintf (stMessage, "%s already locked by %s.", destName, atUserName (locker));
    stLog (stMessage, ST_LOG_MSGERR);
    sprintf (stMessage, "%s not restored", af_retattr (aso, AF_ATTBOUND));
    stLog (stMessage, ST_LOG_MSGERR);
    stAbortThis (FALSE);
  }

  /* now all the checks are done. set retrieved version busy and 
   * create it in busyLocation.
   */
  if ((permit & DOIT) && (!(permit & DENIED))) {
	     
    /*
     * Try to get a description of intended changes.
     */
    if (intent || !(stQuietFlag || forceFlag) || stdinFlag) {
      atSetComment (&lastAso, AT_COMMENT_INTENT, intent, AT_REUSE | AT_CONFIRM | (stdinFlag ? AT_FROMSTDIN : 0));
    }
    /*
     * Create the file, containing the retrieved version.
     * "busyAso" is set up to be the AtFS reference to the file.
     */
    
    /* setbusy sets just the attributes. data must be moved manually */
    if ((versionFile = af_open (aso, "r")) == NULL) {
      stLog (af_errmsg ("af_open"), ST_LOG_ERROR);
      stAbortThis (TRUE);
    } 
    strcpy (tmpName, stTmpFile (busyLocation));
    if ((tmpFile = fopen (tmpName, "w")) == NULL) {
      sprintf (stMessage, "cannot create temporary file %s for writing.", tmpName);
      stLog (stMessage, ST_LOG_ERROR);
      stAbortThis (TRUE);
    }
    if (path[0])
      sprintf (stMessage, "%s/%s -> %s", path, af_retattr (aso, AF_ATTBOUND), destName);
    else
      sprintf (stMessage, "%s -> %s", af_retattr (aso, AF_ATTBOUND), destName);
    stLog (stMessage, ST_LOG_MSGERR);
    if ((contentsBuf = malloc ((unsigned) asoAttrBuf.af_size+1)) == NULL) {
      stLog ("Out of memory", ST_LOG_ERROR);
      stAbortThis(TRUE);
    }
    nbytes = fread (contentsBuf, sizeof (char), asoAttrBuf.af_size, versionFile);
    contentsBuf[nbytes] = '\0';
    af_close (versionFile);
    if (fwrite(contentsBuf, sizeof (char), nbytes, tmpFile) != nbytes) {
      stLog ("couldn't write busy file.", ST_LOG_ERROR);
      stAbortThis (TRUE);
    }
    free (contentsBuf);
    fclose (tmpFile);
    
    oldDate.actime = asoAttrBuf.af_atime;
    oldDate.modtime = asoAttrBuf.af_mtime;
    utime (tmpName, &oldDate);
    unlink (destName);
    if (link (tmpName, destName) < 0) {
      sprintf (stMessage, "cannot link %s to %s.", tmpName, destName);
      stLog (stMessage, ST_LOG_ERROR);
      stAbortThis (TRUE);
    }
    chmod (destName, asoAttrBuf.af_mode);
    stThisTransaction.tr_done = TRUE;
    stUnRegisterFile (tmpName);
    unlink (tmpName);
    
    if (af_getkey (path, asoAttrBuf.af_name, asoAttrBuf.af_type,
		   AF_BUSYVERS, AF_BUSYVERS, &busyAso) == -1) {
      stLog (af_errmsg ("af_getkey"), ST_LOG_ERROR);
      stAbortThis (TRUE);
    }
    /*
     * "busyAso" points to the key of a newly created file, which 
     * has been retrieved from the version archive in case no 
     * file was present.
     */
    
    /*
     * Register the busyversion appropriately: set busy if
     * needed, attach (most of) the user-defined attributes
     * of the original version, attach intent-description.
     */
    
    if (!fixFlag) {
      Af_key previous_busy;
      
      if (af_setbusy (&busyAso, aso, &previous_busy) == -1) {
	stLog (af_errmsg ("af_getkey"), ST_LOG_ERROR);
	stAbortThis (TRUE);
      }
      af_dropkey (&previous_busy);
    }
    
    i = 0;
    while ((attrPtr = asoAttrBuf.af_udattrs[i++])) {
      if (af_setattr (&busyAso, AF_REPLACE, attrPtr) == -1)
	af_setattr (&busyAso, AF_ADD, attrPtr);
    }
    af_setattr (&busyAso, AF_REMOVE, AT_ATTALIAS);
    if ((reserveDate = malloc ((unsigned) (strlen ("rtime") + 32)))) {
      sprintf (reserveDate, "%s=%s", "rtime", af_retattr (&busyAso, AF_ATTCTIME));
      if ((af_setattr (&busyAso, AF_REPLACE, reserveDate) == -1) &&
	  (af_setattr (&busyAso, AF_ADD, reserveDate) == -1)) {
	sprintf (stMessage, "Can't set reservation date for %s.", af_retattr (aso, AF_ATTUNIXNAME));
	stLog (stMessage, ST_LOG_WARNING);
      }
      free (reserveDate);
    }
    else {
      sprintf (stMessage, "Can't set reservation date for %s (no memory).", af_retattr (aso, AF_ATTUNIXNAME));
      stLog (stMessage, ST_LOG_WARNING);
    }
    af_dropkey (&busyAso);
  }
  else { /* denied or not doit */
    sprintf (stMessage, "%s not restored", stThisTransaction.tr_fname);
    stLog (stMessage, ST_LOG_MSGERR);
    stThisTransaction.tr_rc += 1;
  }
  af_dropkey (&lastAso);
  af_freeattrbuf (&asoAttrBuf);
}
