/*
 *  This file is part of DVS, a simple distributed versioning system
 *  See http://www.cs.colorado.edu/~carzanig/dvs/
 *
 *  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
 *
 *  Copyright (C) 1998-2002 University of Colorado
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
 *  USA, or send email to carzanig@cs.colorado.edu.
 *
 *
 *  $Id: dvs.c,v 1.16 2002/05/01 17:32:15 carzanig Exp $
 */
#include <dvsconf.h>

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <pwd.h>
#include <stdlib.h>
#include <errno.h>
#ifdef STDARG
#include <stdarg.h>
#else
#include <varargs.h>
#endif
#include <sys/types.h>
#include <time.h>
#include <strings.h>
#include <dirent.h>
#include <sys/stat.h>

#include <nc.h>
#include <proxy.h>

#include "paths.h"
#include "misclib.h"
#include "dvsmsg.h"
#include "ws_list.h"
#include "dirstack.h"

#define BUFLEN 8192
#define USERNAMELEN 256

#define LOCK     1
#define NO_LOCK  0

#define OVERRIDEWS  1
#define KEEPWS      0

#define MAXTIMELEN  16

/*
 *  functions prototypes
 */
void print_usage(char *name);
int print_locks(const char *pathname, int verbose);
int print_new_versions(const char *pathname, int verbose);
int set_standard_attributes(const char *pathname);
int print_log(const char *pathname, int maxcycles);
int lockonly(char *pathname);
int lockpath(const char *pathname, int last);
int unlockpath(const char *pathname, int last, int force);
int link_artifact(const char *pathname, int last);
int unlink_artifact(const char *pathname);
int check_out_wrapper(const char *pathname, int lock, int last, int here);
int check_out(const char *pathname, int lock, int last);
int dvs_close(const char *pathname);
int set_log_message(const char *pathname);
int check_in_recursive(const char *pathname);
int check_in_new(const char *pathname);
int check_in_collection(const char *pathname);
int check_in_old(const char *pathname);
int check_in(const char *pathname, int lock);
int execute_command(int argc, char *argv[]);
int main(int argc, char *argv[]);


static const char *RCSLock = "RCSLock";
static const char *RCSDate = "RCSDate";
static const char *RCSAuthor = "RCSAuthor";
static const char *RCSLog = "RCSLog";

/*
 *  Global variables
 */
static char Tmpbuf[BUFLEN];
static char *Username;
static char *LogMsg;
static int Force = 0; 
static int Recursive = 0;
static int ReplaceVersion = 0;

void print_usage(char *name)
{
    fprintf(stderr,"DVS v. " DVSVERSION "\n\
Copyright (c) 1997-2002 Software Engineering Research Laboratory\n\
Department of Computer Science, University of Colorado at Boulder.\n\
\n\
Usage: %s <command> [options...]\n\
commands and options:\n\
  co          [-R] [-f] [-l] [-last] path [path ...]\n\
  ci          [-R] [-l] [-m message] path [path ...]\n\
  link        [-last] path [path ...]\n\
  unlink      [-f] path [path ...]\n\
  lock        [-last] path [path ...]\n\
  unlock      [-last] [-f] path [path ...]\n\
  list        [-v] [-o <filter>] [path ...]\n\
              <filter>=[cwbo]+ (collection workspace both(w+c) other)\n\
  close       [-f] path [path...]\n\
  log         [-n <num>] path [path ...]\n\
  setlog      [-m message] path [path ...]\n\
  printlocks  [-R] [-v] path [path...]\n\
  whatsnew    [-R] [-v] path [path...]\n\
  sync        [-R] [-f] path [path...]\n\
\nTo specify your NUCM server, set NUCMHOST and NUCMPORT appropriately.\n\
The default values are localhost and 1234 respectively.\n\
\nYou can also tell DVS to redirect its requests through a proxy by setting\n\
DVS_PROXY_MAP to point to a proxy map file.\n\
", name);
  
    exit(-1);
};

/*
 *  This function parses the argument list (argc,argv) looking for
 *  the parameters defined by `format' and setting the following 
 *  parameters (...) accordingly.
 *  format contains a sequence of pairs of characters, e.g.,
 *  "-f" indicates that the next parameter is interpreted as an int* 
 *  that is set to 1 if a flag `-f' is found in (argc,argv) or otherwise 
 *  to 0.
 *  "*f" indicates that the next parameter is interpreted as a char**.
 *  if a flag `-f' is found, the parameter is set to point to the argv 
 *  following `-f' otherwise it is set to NULL.
 *  For every parameter found, this function removes the corresponding 
 *  parameters, i.e, it packs the (argc,argv) array.
 *  The final value of argc is returned. -1 is returned in case of error, 
 *  i.e., if a "*f" flag is matched, but there are no more parameters.
 */
int parse_options(int *argc, char *argv[], char *format, ...)
{
#define MAXOPTIONLEN 64
    va_list  args;
    int *ip;
    char **cp;
    int i;
    char fmtchr;
    char buf[MAXOPTIONLEN];
    char *aux;

#ifdef STDARG
    va_start(args, format);
#else
    va_start(args);
#endif

    while(*format != '\0') {
	fmtchr = *format++;
	aux = buf;
	while (*format != ' ' && *format != '\0')
	    *aux++ = *format++;
	if (*format == ' ') format++;
	*aux = '\0';
	if (*buf == '\0') return *argc;
	switch(fmtchr) {
	case '-':
	    ip = va_arg(args, int*);                             /* flag param */
	    *ip = 0;
	    for (i=0; i< *argc; i++)
		if (argv[i][0] == '-')
		    if (strcmp(&(argv[i][1]), buf)==0) {
			*ip = 1;
			for((*argc)--;i < *argc; i++)                  /* pack argv */
			    argv[i] = argv[i+1];
			break; /* this break is not strictly necessary (i > *argc) */
		    }
	    break;
	case '*':                                             /* string param */
	    cp = va_arg(args, char**);
	    *cp = NULL;
	    for (i=0; i < *argc; i++)
		if (argv[i][0] == '-')
		    if (strcmp(&(argv[i][1]), buf)==0) {
			if (i+1 >= *argc) return -1;
			*cp = argv[i+1];
			for(*argc -= 2; i < *argc; i++)               /* pack argv */
			    argv[i] = argv[i+2];
			break; /* this break is not strictly necessary (i > *argc) */
		    }
	    break;
	}
    }
    return *argc;
}

/*
 *  this lists every artifact in pathname 
 *  (it assumes that pathname is a collection/directory)
 */
int list_generic(const char *pathname, int verbose, const char *format)
{
    struct stat pathstat;
    t_memberlist *files = NULL;
    t_memberlist *aux = NULL;
    dvs_ws_list wlist = NULL;
    char filename[MAXPATHLEN];
    char path[MAXPATHLEN];
  
    copy_normalize_path(path,pathname);
    please_wait();
    if (!RESOLVE(path,nc_list(path,Empty, &files))) {
	fprintf(stderr, E_List, path, NUCMERROR);
	return -1;
    }
    wipe_please_wait();

    while(files != NULL) {
	aux = files->next;
	ws_list_add_c(&wlist, files->name, files->version);
	free(files);
	files = aux;
    }

    pathfilename_r(path,filename);
    if (stat(filename, &pathstat)<0) {
	if (errno != ENOENT) {
	    fprintf(stderr, E_Stat, filename, strerror(errno));
	    return -1;
	}
    }
    else if (S_ISDIR(pathstat.st_mode)) {
	DIR *d;
	struct dirent *de;
	char currentversion[MAX_VERSION_LENGTH];
    
	if ((d=opendir(filename))==NULL) {
	    fprintf(stderr, E_ReadDir, path, strerror(errno));
	    return -1;
	}
	if (pushdir(path)<0) return -1;
	while((de = readdir(d)) != NULL) {
	    if (not_dot_or_dotdot(de->d_name)) {
		if (nc_version(de->d_name, Empty, currentversion)<0) {
		    if (NdbErrno == NdbNotOpen) {
			ws_list_add_v(&wlist, de->d_name);
		    }
		    else {
			fprintf(stderr, E_Version, de->d_name, NUCMERROR);
			closedir(d);
			popdir();
			return -1;
		    }
		}
		else {
		    ws_list_add_d(&wlist, de->d_name, currentversion);
		}
	    }
	}
	closedir(d);
	if (popdir()<0) return -1;
    }
    ws_list_print(wlist, verbose, format);
    ws_list_free(wlist);
    return 0;
}

int print_locks(const char *pathname, int verbose)
{
    char wholocks[USERNAMELEN];
    int i, maxver;
    char *version;
    char versionnum[MAX_VERSION_LENGTH];
    char strippedpath[MAXPATHLEN];
    int result = 0;
    int locked = 0;
    int atype;
    int isfirst;

    isfirst = 1;
    please_wait();
    copy_normalize_path(strippedpath,pathname);
    if (!RESOLVE(strippedpath,nc_lastversion(strippedpath, Empty, versionnum)))
	{    
	    fprintf(stderr, E_GetLatest, strippedpath, NUCMERROR);
	    return -1;
	}
    /*
     *  copy pathname on strippedpath, stripping off the version, adding 
     *  the version separatorn and leaving `version' to point to the next char 
     */ 
    strip_version_x(strippedpath);
    for(version = strippedpath; *version != '\0'; version++);
    *version++ = VERSION_SEPARATOR;
    maxver = atoi(versionnum);
  
    for(i = maxver; i >= 1; i--) {
	sprintf(version,"%d", i);
	isfirst = 1;
	please_wait();
	if (nc_getattributevalue(strippedpath, Empty,RCSLock, wholocks) == 0) {
	    if (isfirst) {
		wipe_please_wait();
		isfirst = 0;
	    }
	    if (!locked) {
		*(--version) = '\0';
		printf("%s\n", strippedpath);
		*version++ = VERSION_SEPARATOR;
		locked = 1;
	    }
	    if (verbose) {
		printf("-- version %d", i);
		if (i == maxver) printf(" (latest)");
		printf(" locked by %s\n", wholocks);
	    }
	    else {
		i = 0;
	    }
	}
	else if (NdbErrno != NdbNonExist) {
	    fprintf(stderr, E_GetLock, strippedpath, NUCMERROR);
	    result = -1;     
	}
    }

    *(--version) = '\0';

    if ((atype = nc_gettype(strippedpath, Empty))<0) {
	fprintf(stderr, E_Type, strippedpath, NUCMERROR);
	return -1;
    }
    if (isfirst) {
	wipe_please_wait();
	isfirst = 0;
    }
    if (atype == COLLECTION) {                 /* if pathname is a collection */
	t_memberlist *files;                     /* checks recursively into it  */
	t_memberlist *aux;
    
	if (nc_list(strippedpath,Empty, &files)<0) {
	    fprintf(stderr, E_List, strippedpath, NUCMERROR);
	    return -1;
	}

	*version++ = FS_SEPARATOR;
	while(files != NULL) {
	    sprintf(version, "%s", files->name);
	    if (print_locks(strippedpath, verbose)<0) result = -1;
	    aux = files->next;
	    free(files);
	    files = aux;
	}
    }

    return result;
};

int print_new_versions(const char *pathname, int verbose)
{
    char currentversion[MAX_VERSION_LENGTH];
    char lastversion[MAX_VERSION_LENGTH];
    int atype;
    char strippedpath[MAXPATHLEN];
    int isfirst;
    strip_version_r(pathname, strippedpath);

    if (nc_version(strippedpath, Empty, currentversion)<0) {
	fprintf(stderr, E_Version, 
		pathname, NUCMERROR);
	return -1;
    }
    please_wait();
    isfirst = 1;
    if (nc_lastversion(strippedpath, Empty,lastversion)<0) {
	fprintf(stderr, E_GetLatest, pathname, NUCMERROR);
	return -1;
    }
    if (strcmp(lastversion, currentversion)!=0) {
	if (isfirst) {
	    wipe_please_wait();
	    isfirst = 0;
	}
	if (verbose) {
	    printf("%s v.%s --> v.%s\n", strippedpath, 
		   currentversion, lastversion);
	}
	else {
	    printf("%s\n", strippedpath);
	}
    }
    wipe_please_wait();
    if (Recursive) {
	if ((atype = nc_gettype(pathname, Cwd))<0) {
	    fprintf(stderr, E_Type, pathname, NUCMERROR);
	    return -1;
	}
	if (atype == COLLECTION) {
	    t_memberlist *files;
	    t_memberlist *aux;
      
	    if (nc_list(pathname, Empty, &files)<0) {
		fprintf(stderr, E_List, pathname, NUCMERROR);
		return -1;
	    }
	    while(files != NULL) {
		char newpathname[MAXPATHLEN];
		sprintf(newpathname, "%s%c%s", pathname, FS_SEPARATOR, files->name);
		print_new_versions(newpathname, verbose);
		aux = files->next;
		free(files);
		files = aux;
	    }
	}
    }

    return 0;
};

int sync_new_versions(const char *pathname, int force)
{
    char currentversion[MAX_VERSION_LENGTH];
    char lastversion[MAX_VERSION_LENGTH];
    int atype;
    char versionpath[MAXPATHLEN];
    char pathdir[MAXPATHLEN];
    char strippedpath[MAXPATHLEN];
    int result = 0;

    if (!local_path(pathname)) {
	printf(E_NotLocal, pathname);
	return -1;
    }
    
    copy_dirname_filename(pathdir, strippedpath, pathname);
    strip_version_x(strippedpath);
    if (pushdir(pathdir)<0) return -1;
    if (nc_version(strippedpath, Empty, currentversion)<0) {
	if (NdbErrno == NdbUnknown) {
	    *currentversion = '\0'; /* this forces dvs to check-out the file */
	}                           /* when the file hasn't been open        */
	else {
	    fprintf(stderr, E_Version, pathname, NUCMERROR);
	    result = -1;
	    goto return_statement;
	}
    }
    please_wait();
    if (nc_lastversion(strippedpath, Empty,lastversion)<0) {
	fprintf(stderr, E_GetLatest, pathname, NUCMERROR);
	result = -1;
	goto return_statement;
    }
    if (strcmp(lastversion, currentversion)!=0) {
	wipe_please_wait();
	set_version_x(strippedpath, lastversion);
	result = check_out(strippedpath, NO_LOCK, 0);
	goto return_statement;
    } else {
	set_version_r(strippedpath, versionpath, lastversion);
	if (strcmp(strippedpath, Cwd)!=0) {
	    open_artifact(versionpath,force);
	}
	wipe_please_wait();
	if (Recursive) {
	    if ((atype = nc_gettype(strippedpath, Cwd))<0) {
		fprintf(stderr, E_Type, strippedpath, NUCMERROR);
		result = -1;
		goto return_statement;
	    }
	    if (atype == COLLECTION) {
		t_memberlist *files;
		t_memberlist *aux;
	
		if (nc_list(strippedpath, Empty, &files)<0) {
		    fprintf(stderr, E_List, strippedpath, NUCMERROR);
		    result = -1;
		    goto return_statement;
		}
		if (pushdir(strippedpath)<0) return -1;
		while(files != NULL) {
		    if (sync_new_versions(files->name, Force) < 0)
			result = -1;
		    aux = files->next;
		    free(files);
		    files = aux;
		}
		if (popdir()<0) return -1;
	    }
	}
    }
 return_statement:
    if (popdir()<0) return -1;
    return result;
};


int set_standard_attributes(const char *pathname)
{ 
    int result = 0;
    char timestr[MAXTIMELEN];
    time_t tp;

    if (nc_testandsetattribute(pathname,Empty,RCSAuthor,Username)<0) {
	fprintf(stderr, E_SetAuthor, pathname, NUCMERROR);
	result = -1;
    }
    if (time(&tp)<0) {
	fprintf(stderr, E_GetTime, strerror(errno));
    }
    else {
#ifdef OSF1
	sprintf(timestr, "%d", tp);
#else
	sprintf(timestr, "%ld", tp);
#endif
	if (nc_testandsetattribute(pathname,Empty,RCSDate,timestr)<0) {
	    fprintf(stderr, E_SetDate, pathname, NUCMERROR);
	    result = -1;
	}
    }

    return result;
}

/*
 * prints out a log of the version history of pathname
 * the log includes author, date and possibly lock information
 */
int print_log(const char *pathname, int maxcycles) 
{
    char versionnum[MAX_VERSION_LENGTH];
    char wholocks[USERNAMELEN];
    char timestr[MAXTIMELEN];
    char whocreated[USERNAMELEN];

    int i, maxver, minver;
    char *version;
    static char realpath[MAXPATHLEN];

    int result = 0;

    char resolvedpath[MAXPATHLEN];
    copy_normalize_path(resolvedpath, pathname);
    if (!RESOLVE(resolvedpath, nc_lastversion(resolvedpath, Empty, versionnum)))
	{
	    fprintf(stderr, E_GetLatest, resolvedpath, NUCMERROR);
	    return -1;
	}
    version = strip_version_x(resolvedpath);
    while (*version != '\0') version++;
    *version = '\0';
    printf("-- %s\n", resolvedpath); 
    *version++ = VERSION_SEPARATOR;
    maxver = atoi(versionnum);
    minver = (maxcycles) ? maxver - maxcycles : 0;
    if (minver < 0) minver = 0;

    for(i = maxver; i > minver; i--) {
	sprintf(version,"%d", i);
	printf(" - version %d ", i); fflush(stdout);

	printf("\n   ");
	if (nc_getattributevalue(resolvedpath,Empty, RCSDate, timestr) == 0) {
	    time_t timevalue;
	    timevalue = atol(timestr);
	    printf("date: %s; ", strtok(ctime(&timevalue),"\n")); fflush(stdout);
	}
	else if (NdbErrno != NdbNonExist) {
	    fprintf(stderr,E_GetDate, realpath, NUCMERROR);
	    result = -1;
	}
	if (nc_getattributevalue(resolvedpath,Empty, RCSAuthor, whocreated) == 0) {
	    printf("author: %s; ", whocreated); fflush(stdout);
	}
	else if (NdbErrno != NdbNonExist) {
	    fprintf(stderr, E_GetAuthor, realpath, NUCMERROR);
	    result = -1;
	}
	if (nc_getattributevalue(resolvedpath, Empty,RCSLock, wholocks) == 0) {
	    printf("\n   locked by %s", wholocks); fflush(stdout);
	}
	else if (NdbErrno != NdbNonExist) {
	    fprintf(stderr, E_GetLock, realpath, NUCMERROR);
	    result = -1;     
	}
	if (nc_getattributevalue(resolvedpath, Empty, RCSLog, Tmpbuf) == 0) {
	    if (*Tmpbuf == '\0') {
		printf("\n*** empty log message ***");
	    }
	    else {
		printf("\n%s",Tmpbuf);
	    }
	}
	else if (NdbErrno != NdbNonExist) {
	    fprintf(stderr, E_GetLog, resolvedpath, NUCMERROR);
	    result = -1;
	}
	printf("\n");
    }

    return result;
}

static int do_set_lock(const char *path)
{
    char wholocks[USERNAMELEN];

    if (nc_testandsetattribute(path,Empty,RCSLock, Username)==0)
	return 0;

    if (NdbErrno == NdbExist) 
	if (nc_getattributevalue(path,Empty,RCSLock, wholocks)==0) {
	    if (strcmp(wholocks, Username)==0) {
		return 0;	/* Username already has the lock   */
	    }
	    else {		/* someone else has the lock */
		fprintf(stderr, E_AlreadyLocked, path, wholocks);
		return -1;
	    }
	}
    fprintf(stderr, E_GetLock, path, NUCMERROR);
    return -1;     
}

int lockonly(char *path)
{
    char lastversion[MAX_VERSION_LENGTH];
    char currentversion[MAX_VERSION_LENGTH];

    /*
     * first I check whether the requested version is indeed the
     * latest version.
     */
    strncpy(currentversion, version_position(path), MAX_VERSION_LENGTH);
    if (*currentversion == '\0') 
	if (nc_version(path, Empty, currentversion) < 0) 
	    if (NdbErrno != NdbUnknown) {
		fprintf(stderr, E_Version, path, NUCMERROR);
		return -1;
	    }

    if (!RESOLVE(path,nc_lastversion(path, Empty, lastversion))) {    
	fprintf(stderr, E_GetLatest, path, NUCMERROR);
	return -1;
    }
    if (*currentversion != '\0' && strcmp(lastversion, currentversion) != 0) 
	if (!ask_question(0, M_NotLatest, currentversion, path, lastversion))
	    return -1;

    return do_set_lock(path);
}

int lockpath(const char *pathname, int last) 
{
    char path[MAXPATHLEN];

    copy_normalize_path(path,pathname);
    please_wait();
    if (last)
	if (resolve_last(path)==NULL)
	    return -1;
    if (lockonly(path)<0) return -1;
    if (initiate_change(path)<0) return -1;
    printf("Artifact `%s' has been locked.\n", path);
  
    return 0;
}

int unlockpath(const char *pathname, int last, int force) 
{
    char wholocks[USERNAMELEN];
    char path[MAXPATHLEN];
  
    copy_normalize_path(path,pathname);

    if (strcmp(strip_version(path), Cwd)==0) {
	fprintf(stderr, E_NotCurrDir);
	return -1;
    }

    please_wait();
    if (last)
	if (resolve_last(path)==NULL) 
	    return -1;
  
    if (!RESOLVE(path,nc_getattributevalue(path,Empty,RCSLock, wholocks))) {
	if (NdbErrno != NdbNonExist) {
	    fprintf(stderr, E_GetLock, path, NUCMERROR);
	    return -1;
	} 
	else { 
	    if (abort_change(path, force)<0) {
		return -1;
	    } else goto return_ok;
	}
    } else if (strcmp(wholocks, Username)!=0) {
	fprintf(stderr, E_AlreadyLocked, path, wholocks);
	return -1;
    } else if (abort_change(path, force)<0) {
	return -1;
    } else if (nc_removeattribute(path , Empty, RCSLock)<0) {
	fprintf(stderr, E_RemoveLock, path, NUCMERROR);
	fprintf(stderr, "Warning: the workspace may be in an inconsistent state.\n");
	return -1;
    }

 return_ok:

    printf("Artifact `%s' has been unlocked.\n", path);
    return 0;
}

int link_artifact(const char *pathname, int last)
{
    char filename[MAXPATHLEN];
    char wholocks[USERNAMELEN];
  
    please_wait();
    if (nc_getattributevalue(Cwd,Empty,RCSLock, wholocks)<0) {
	if (NdbErrno != NdbNonExist) {
	    fprintf(stderr, E_GetLock, Cwd, NUCMERROR);
	    return -1;
	}
	else {
	    fprintf(stderr, E_LockDir);
	    return -1;
	}
    }
    else if (strcmp(wholocks, Username)!=0) {
	fprintf(stderr, E_LockDir);
	fprintf(stderr, E_NoteLockDir, wholocks);
	return -1;
    }
    if (check_out(pathname,NO_LOCK, last)<0)
	return -1;
  
    pathfilename_r(pathname,filename);
    
    /* here I assume that the current collection is `initiated' */
    if (nc_add(filename,Empty,Cwd,Empty)<0) {
	fprintf(stderr,E_AddCurrent, filename, NUCMERROR);
	return -1;
    }

    printf("Artifact `%s' has been linked to the current collection.\n", 
	   pathname);
    return 0;
}

int unlink_artifact(const char *pathname)
{
    char filename[MAXPATHLEN];
    char wholocks[USERNAMELEN];

    pathfilename_r(pathname,filename);
  
    please_wait();
    if (nc_getattributevalue(Cwd,Empty,RCSLock, wholocks)<0) {
	if (NdbErrno != NdbNonExist) {
	    fprintf(stderr, E_GetLock, filename, NUCMERROR);
	    return -1;
	}
	else {
	    fprintf(stderr, E_LockDir);
	    return -1;
	}
    } 
    else if (strcmp(wholocks, Username)!=0) {
	fprintf(stderr, E_LockDir);
	fprintf(stderr, E_NoteLockDir, wholocks);
	return -1;
    }
    if (!Force)
	if (check_non_dvs(filename))
	    if (!ask_question(0, M_CheckNonDvs, filename))
		return -1;
  
    if (nc_remove(filename, Empty, Force)<0) {
	fprintf(stderr,E_Remove, filename, NUCMERROR);
	return -1;
    } else {
	printf("Artifact `%s' has been removed from the current collection.\n", 
	       filename);
	return 0;
    }
}

int check_out_wrapper(const char *pathname, int lock, int last, int here)
{
    char path[MAXPATHLEN];
    char pathdir[MAXPATHLEN];
    struct stat pathstat;
    int result = 0;
    *pathdir = '\0';

    copy_normalize_path(path,pathname);
    if(local_path(path) && !here) {
	copy_dirname_filename(pathdir, path, path);
	if (stat(pathdir, &pathstat)<0) {
	    copy_normalize_path(path,pathname);
	    *pathdir = '\0';
	}
    }
    if (pushdir(pathdir)<0) return -1;
    result = check_out(path, lock, last);
    if (popdir()<0) return -1;
    return result;
}

/*
 *  this function checks out the artifact referred by pathname 
 *  optionally locking it, according to lock.
 *  This function uses the default (last) version when this is not 
 *  specified.
 *  If path is a local target that does not exist, the function 
 *  automatically refers to the default server
 *  When checking out collections, this function checks out all its 
 *  components. Even if the collection is checked-out with lock, all
 *  its components are not locked
 */
int check_out(const char *pathname, int lock, int last) 
{
    int atype;
    char path[MAXPATHLEN];

    copy_normalize_path(path,pathname);
    please_wait();
    if (last)
	if (resolve_last(path)==NULL) 
	    return -1;

    if (lock) 
	if (lockonly(path)<0)
	    return -1;
    
    if (open_artifact(path, Force)<0)
	return -1;

    if (lock) 
	if (initiate_change(path)<0) 
	    return -1;

    printf("Artifact `%s' has been checked out%s", path, lock ? " and locked.\n" : ".\n");

    if (Recursive) {
	strip_version_x(pathfilename_x(path));
	if ((atype = nc_gettype(path, Cwd))<0) {
	    fprintf(stderr, E_Type, path, NUCMERROR);
	    return -1;
	}
	if (atype == COLLECTION) {
	    t_memberlist *files;
	    t_memberlist *aux;
      
	    if (nc_list(path,Empty, &files)<0) {
		fprintf(stderr, E_List, path, NUCMERROR);
		return -1;
	    }
	    if (pushdir(path)< 0) return -1;
	    printf(M_Recursing, path);
	    while(files != NULL) {
		char newpathname[MAXPATHLEN];
		sprintf(newpathname, "%s:%s", files->name, files->version);
		check_out(newpathname, NO_LOCK, last);
		aux = files->next;
		free(files);
		files = aux;
	    }
	    if (popdir()<0) return -1;
	}
    }
    return 0;
}

int dvs_close(const char *pathname)
{
    char filename[MAXPATHLEN];
    char pathdir[MAXPATHLEN];
    int result = 0;
    *pathdir = '\0';

    copy_normalize_path(filename,pathname);
    copy_dirname_filename(pathdir, filename, filename);
    if (!local_path(filename) || pushdir(pathdir)<0) {
	printf(E_NotLocal, filename);
	return -1;
    }
  
    if (!Force) 
	if (check_non_dvs(filename)) {
	    printf("Warning: this operation will remove some files that have not been checked in.\n");
	    if (!ask_question(0, "do you want to continue?", filename))
		return -1;
	}
    result = patched_nc_close(filename, Empty, Force);
    if(popdir()<0) return -1;
    return result;
}

int set_log_message(const char *pathname)
{
    char *curs;
    int tmpspace;
    char *msg;

    if (nc_removeattribute(pathname, Empty, RCSLog)<0) 
	if (NdbErrno != NdbNonExist)
	    {
		fprintf(stderr, E_ClearLog, pathname, NUCMERROR);
		return -1;
	    }
    if (LogMsg != NULL) {
	msg = LogMsg;
    }
    else {
	msg = Tmpbuf;
	printf("Input log message for artifact `%s'\nmax %d characters, terminated with single '.' or end of file:\n", pathname, BUFLEN);
	*Tmpbuf = '\0';
	curs = Tmpbuf; 
	tmpspace = BUFLEN;
	while(1) {
	    printf(">> ");
	    if (fgets(curs, tmpspace, stdin) == NULL) {
		printf("\n");
		break;
	    } else if (curs[0] == '.' && curs[1] == '\n') {
		break;
	    }
	    while(*curs++ != '\n') tmpspace--;
	}
	*(--curs) = '\0';
    }
    if (nc_testandsetattribute(pathname, Empty, RCSLog, msg)<0) {
	fprintf(stderr, E_SetLog, pathname, NUCMERROR);
	return -1;
    }
    return 0;
}

int check_in_recursive(const char *pathname)
{
    struct stat pathstat;
    int result=0;

    if (stat(pathname, &pathstat)<0) {
	fprintf(stderr, E_Stat, pathname, strerror(errno));
	return -1;
    }
    if (S_ISDIR(pathstat.st_mode)) {
	DIR *d;
	struct dirent *de;

	if ((d=opendir(pathname))==NULL) {
	    fprintf(stderr, E_ReadDir, pathname, strerror(errno));
	    return -1;
	}
	if (initiate_change(pathname)<0) {
	    closedir(d);
	    fprintf(stderr, E_Initiate, pathname, NUCMERROR);
	    return -1;
	}
	printf(M_Recursing, pathname);
	if (pushdir(pathname)<0) {
	    closedir(d);
	    return -1;
	}
	while((de = readdir(d)) != NULL) {
	    if (not_dot_or_dotdot(de->d_name)) {
		if (add_new_artifact(de->d_name)<0) {
		    fprintf(stderr,E_Add, de->d_name, pathname, NUCMERROR);
		    result = -1;
		}
		if (set_standard_attributes(de->d_name)<0)
		    result = -1;
		if (set_log_message(de->d_name)<0)
		    result = -1;
		printf(M_Initial, de->d_name);
		check_in_recursive(de->d_name);
	    }
	}
	closedir(d);
	if (popdir()<0) return -1;
	if (commit_change_replace(pathname)<0) {
	    fprintf(stderr, E_CheckIn,
		    pathname, NUCMERROR);
	    return -1;
	}
    }
    return result;
}

int check_in_new(const char *pathname)
{
    char wholocks[USERNAMELEN];
    int result = 0;

    if (nc_getattributevalue(Cwd,Empty,RCSLock, wholocks)<0) {
	if (NdbErrno != NdbNonExist) {
	    fprintf(stderr, E_GetLock, Cwd, NUCMERROR);
	    return -1;
	}
	else {
	    fprintf(stderr, E_LockDir);
	    return -1;
	}
    } 
    else if (strcmp(wholocks, Username)!=0) {
	fprintf(stderr, E_LockDir);
	fprintf(stderr, E_NoteLockDir, 
		wholocks);
	return -1;
    }
    /* here I assume that the current collection is `initiated' */
    if (add_new_artifact(pathname)<0)
	return -1;
  
    if (Recursive) 
	if (check_in_recursive(pathname)<0) 
	    result = -1;
    if (set_standard_attributes(pathname)<0)
	result = -1;
    if (set_log_message(pathname)<0)
	result = -1;
    printf(M_Initial, pathname);

    return result;
}

int check_in_collection(const char *pathname) 
{
    /*
     * this assumes that pathname is already locked
     */
    t_memberlist *files;
    t_memberlist *aux;

    if (nc_list(pathname,Empty, &files)<0) {
	fprintf(stderr, E_List, pathname, NUCMERROR);
	return -1;
    }
    if (files != NULL) { 
	if (pushdir(pathfilename(pathname))<0) return -1;
	while(files != NULL) {
	    char newversion[MAX_VERSION_LENGTH];
	    if (nc_version(files->name, Empty, newversion)<0) {
		fprintf(stderr, E_GetCurrent, files->name, NUCMERROR); 
		fprintf(stderr, "Assuming it hasn't changed.\n");
	    }
	    else {
		if (strcmp(newversion,files->version)!=0) {
		    if (nc_replaceversion(files->name,Cwd,newversion,1)<0) {
			fprintf(stderr, E_Replace, files->name, NUCMERROR);
		    }
		    else {
			printf("Replaced version %s with version %s for artifact `%s'.\n", 
			       files->version, newversion, files->name);
		    }
		}
	    }
	    aux = files->next;
	    free(files);
	    files = aux;
	}
	return popdir();
    }
  
    return 0;             /* here I have to handle errors better */
}

int check_in_old(const char *pathname) 
{
    char wholocks[USERNAMELEN];
    char version[MAX_VERSION_LENGTH];
    char newversion[MAX_VERSION_LENGTH];
    char oldpath[MAXPATHLEN];
    int atype;
    int result = 0;

    if (nc_getattributevalue(pathname,Empty,RCSLock, wholocks)<0) {
	if (NdbErrno != NdbNonExist) {
	    fprintf(stderr, E_GetLock, pathname, NUCMERROR);
	    return -1;
	}
	else {
	    fprintf(stderr,E_NotLocked, pathname);
	    return -1;
	}
    } 
    else if (strcmp(wholocks, Username)!=0) {
	fprintf(stderr, E_AlreadyLocked, pathname, wholocks);
	return -1;
    }
    if (nc_version(pathname,Empty,version)<0) {
	fprintf(stderr, E_GetCurrent, pathname, NUCMERROR);
	return -1;
    }
    sprintf(oldpath,"%s:%s", pathname, version);
    /* 
     * if I'm checking in a collection, I have to update all the
     * versions of the components of that collection
     */
    if ((atype = nc_gettype(pathname,Cwd))<0) {
	fprintf(stderr, E_Type, pathname);
	result = -1;    
    }                 /* here I should clean-up ... */
    else if (atype == COLLECTION) {
	printf("Updating members of collection `%s'.\n",  pathname);
	if (check_in_collection(pathname)<0) {
	    fprintf(stderr, E_CompleteCheckIn, pathname);
	    result = -1;
	}                /* here I should clean-up ... */
    }
    /* here I assume that the current collection is `initiated' */
    if (ReplaceVersion) {
	if (commit_change_replace(pathname)<0) {
	    fprintf(stderr, E_Commit, pathname, NUCMERROR);
	    result = -1;
	}
	strcpy(newversion, version);
    } else {
	if (commit_change(pathname, newversion)<0) return -1;
	/* here I set all the attributes for the new version */
    }
    if (set_standard_attributes(pathname)<0)
	result = -1;
  
    set_log_message(pathname);
    if (nc_removeattribute(oldpath,Cwd,RCSLock)<0) {
	fprintf(stderr, E_RemoveLock, oldpath, NUCMERROR);
	return -1;
    }
    printf("Checked in new version %s for artifact `%s'.\n", 
	   newversion, pathname);

    return result;
}

int check_in(const char *path, int lock)
{
    char pathname[MAXPATHLEN];
    char pathdir[MAXPATHLEN];
    int result = 0;
    struct stat pathstat;
    
    if (stat(path, &pathstat)<0) {
	if (errno != ENOENT) {
	    fprintf(stderr, E_Stat, path, strerror(errno));
	    return -1;
	}
    }
    copy_dirname_filename(pathdir, pathname, path);
    if (pushdir(pathdir)<0) return -1;
    
    /*
     * this is a hack that I don't like.
     * see the notes on the NUCM data model
     */
    please_wait();
    if (strcmp(strip_version(pathname), Cwd)==0) {
	result = check_in_old(pathname);
    } else if (is_in_current_collection(pathname) == 0) {
	result = check_in_new(pathname);
    } else {
	result = check_in_old(pathname);
    }
    if (lock) {
	if (do_set_lock(pathname)==0) {
	    if (initiate_change(pathname)<0) 
		result = -1;
	} else {
	    result = -1;
	}
    }

    if (popdir() < 0) { result = -1; }

    return result;
}

int execute_command(int argc, char *argv[])
{
    char *cmd;
    char *progname;
    int i, result;

    progname = argv[0];

    cmd = argv[1];

    argc -= 2;
    argv += 2;

    result = 0;

    if (strcmp(cmd,"co")==0) {           /* CHECK-OUT */
	int lock = 0;
	int last = 0;
	int here = 0;

	if (parse_options(&argc, argv, "-R -f -l -last -here", 
			  &Recursive, &Force, &lock, &last, &here) < 1) {
	    print_usage(progname);
	} else {
	    for (i=0; i<argc; i++) 
		if(check_out_wrapper(argv[i], lock, last, here)<0)
		    if(result == 0) result = -1;
	    return result;
	}
    } else if(strcmp(cmd,"lock")==0) {
	int last = 0;
	if (parse_options(&argc, argv, "-last", &last) < 1) {
	    print_usage(progname);
	} else {
	    for (i=0; i<argc; i++) 
		if(lockpath(argv[i], last)<0)
		    if(result == 0) result = -1;
	    return result;
	}     
    } else if(strcmp(cmd,"unlock")==0) {
	int last = 0;
	int force = 0;

	if (parse_options(&argc, argv, "-last -f", &last, &force) < 1) {
	    print_usage(progname);
	}
	else {
	    for (i=0; i<argc; i++) 
		if(unlockpath(argv[i], last, force)<0)
		    if(result == 0) result = -1;
	    return result;
	}     
    }
    else if(strcmp(cmd,"ci")==0) {
	int lock = 0;
	if (parse_options(&argc, argv, "-R *m -l", 
			  &Recursive, &LogMsg, &lock) < 1) {
	    print_usage(progname);
	}
	else {
	    for (i=0; i<argc; i++) 
		if(check_in(argv[i], lock)<0)
		    if(result == 0) result = -1;
	    return result;
	}     
    }
    else if(strcmp(cmd,"link")==0) {
	int last;
	if (parse_options(&argc, argv, "-last", &last) < 1) {
	    print_usage(progname);
	}
	else {
	    for (i=0; i<argc; i++) 
		if(link_artifact(argv[i], last)<0)
		    if(result == 0) result = -1;
	    return result;
	}     
    }
    else if(strcmp(cmd,"unlink")==0) {
	if (parse_options(&argc, argv, "-f", &Force) < 1) {
	    print_usage(progname);
	}
	else {
	    for (i=0; i<argc; i++) 
		if(unlink_artifact(argv[i])<0)
		    if(result == 0) result = -1;
	    return result;
	}     
    }
    else if(strcmp(cmd,"close")==0) {
	if (parse_options(&argc, argv, "-f", &Force) < 1) {
	    print_usage(progname);
	}
	else {
	    for (i=0;i<argc; i++) 
		if(dvs_close(argv[i])<0)
		    if(result == 0) result = -1;
	    return result;
	}     
    }
    else if(strcmp(cmd,"list")==0) {
	int verbose = 0;
	const char *format;
	if (parse_options(&argc, argv, "-v *o", &verbose, &format) < 0) {
	    print_usage(progname);
	}
	if (argc == 0) {
	    return list_generic(Cwd, verbose, format);
	} else {
	    for (i=0; i<argc; i++) 
		if(list_generic(argv[i], verbose, format)<0)
		    if(result==0) result = -1;
      
	    return result;
	}
    }
    else if(strcmp(cmd,"log")==0) {
	const char *maxcycles;
	if (parse_options(&argc, argv, "*n", &maxcycles) < 1) {
	    print_usage(progname);
	}
	if (argc < 1) {
	    print_usage(progname);
	    return -1;
	}
	if (maxcycles == NULL) maxcycles = "0";
	for (i=0; i<argc; i++) 
	    if(print_log(argv[i], atoi(maxcycles))<0)
		if(result==0) result = -1;
    
	return result;
    }
    else if(strcmp(cmd,"setlog")==0) {
	if (parse_options(&argc, argv,"*m",&LogMsg) < 1) {
	    print_usage(progname);
	}
	for (i=0; i<argc; i++) 
	    if(set_log_message(argv[i])<0)
		if(result==0) result = -1;
    
	return result;
    }
    else if(strcmp(cmd,"printlocks")==0) {
	int verbose = 0;

	if (parse_options(&argc, argv, "-R -v", &Recursive, &verbose) < 0) {
	    print_usage(progname);
	}
	if (argc == 0) {
	    Recursive = 1;
	    return print_locks(Cwd, verbose);
	} else {
	    for (i=0; i<argc; i++) 
		if(print_locks(argv[i], verbose)<0)
		    if(result==0) result = -1;
	}
	return result;
    }
    else if(strcmp(cmd,"whatsnew")==0) {
	int verbose = 0;

	if (parse_options(&argc, argv, "-R -v", &Recursive, &verbose) < 0) {
	    print_usage(progname);
	}
	if (argc == 0) {
	    Recursive = 1;
	    return print_new_versions(Cwd, verbose);
	} else {
	    for (i=0; i<argc; i++) 
		if(print_new_versions(argv[i], verbose)<0)
		    if(result==0) result = -1;
	}
	return result;
    }
    else if(strcmp(cmd,"sync")==0) {
	if (parse_options(&argc, argv, "-R -f", &Recursive, &Force) < 1) {
	    print_usage(progname);
	}
	for (i=0; i<argc; i++) 
	    if(sync_new_versions(argv[i], Force)<0)
		if(result==0) result = -1;
    
	return result;
    }
    else {
	print_usage(progname);
    }
    return -1;
}

int main(int argc, char *argv[])
{
    char *progname;
    char *host, *port;
    char *proxylist;

    progname = argv[0];

    if (argc < 2) {
	print_usage(progname);
    }

    if ((host = (char *)getenv("NUCMHOST"))==0) {
	strcpy(nucmserver.host, "localhost");
    }
    else {
	strcpy(nucmserver.host, host);
    }
    if ((port = (char *)getenv("NUCMPORT"))==0) {
	port = "1234";
    }
    strcpy(nucmserver.port, port);
    nc_setmyserver(&nucmserver);

    if ((Username = getenv("USERADDRESS"))==NULL)
#ifdef WIN32
	{
	    fprintf(stderr,"You must set the USERADDRESS variable to specify your identity.\n");
	    return 1;
	}
#else
    Username = getpwuid(getuid())->pw_name;
#endif
    
    if ((proxylist = (char *)getenv("DVS_PROXY_MAP"))!=NULL) {
	proxy_add_list(proxylist);
	proxy_on();
    }
    
    return execute_command(argc, argv);
}
