// ---------------- 
// rvi - Revision Control System Interface <mtsegaye-fm@rucus.ru.ac.za>
//       v1.0.2 25/09/2000
//
// NB. Correct the program paths below for your particular system.
//
// Freeware. Use/modify as you wish.
//
// ----------------

#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#define RCSDIR "RCS/\0"
#define RCSEXT ",v\0"

#define UNLOCK "/usr/bin/rcs -u -q \0" 
#define CHECK_OUT "/usr/bin/co -l -q \0"
#define CHECK_OUT_R "/usr/bin/co -q \0"
#define CHECK_IN  "/usr/bin/ci -u -q \0"
 
#define EDITOR "/usr/bin/vi\0"  // guy :)

#define MAX_BUFFER_SZ 255

#define REEDIT 2
#define ABORT  4
#define DG_YES 6
#define DG_NO  8
#define DG_OTHER 0

//#define DEBUG

//protos
void handle_error(int);
void do_filecheck(char*);
int do_filechange_check(char*,struct stat *);
char * exec_cmd(char**,int,int flag=0);
int dialog();

int main(int argc,char ** argv)
{ printf("Revision Control System Interface (rvi) <mtsegaye-fm@rucus.ru.ac.za>\n");
  if (argc<2)
    { printf("Usage : rvi <filename> [editor]\n");
      exit(0);
    }
  
  // check that
  // 1) the file isn't already locked out
  // 2) check/create /RCS exists, if it doesn't create it w/user's consent 
  do_filecheck(argv[1]);
 
  // save the uid and gid of the file being opened
  struct stat fileinfo;
  stat(argv[1] , &fileinfo);
  
  #ifdef DEBUG
   printf("UID: %d EUID:%d GID:%d EGID:%d\n",
    fileinfo.st_uid,getenv("UID"),fileinfo.st_gid,getenv("GID"));
  #endif

  // get editor  
  char * editor;
  if (argc==3)  
     editor = argv[2]; // from the cmd line
  else if(argc<3) 
     editor = getenv("EDITOR");  // or the environment 
    
  if (!editor) editor = EDITOR;

  // multiple editing
  
  // go ahead and check the file out (co -l filename
    { char * params[4];
      params[0] = CHECK_OUT;
      params[1] = argv[1];
      exec_cmd(params,2);
      // save modification time
      struct stat tmp;
      stat(argv[1],&tmp);
      fileinfo.st_mtime = tmp.st_mtime; 
    } 

   // multiple editing 
   do {
       // spawn editor
       char * params[3];
       params[0] = editor;
       params[1] = " \0";
       params[2] = argv[1]; 
       exec_cmd(params,3);
    
       // ask user if he/she wants to keep the changes he made
     } while (do_filechange_check(argv[1],&fileinfo)==REEDIT);
     

  // check in file into RCS and check out a read_only copy  
  char * params[2];
  params[0] = CHECK_IN;
  params[1] = argv[1];
  exec_cmd(params,2);
  
  //restore the uid_gid of the file 
  chown(argv[1], fileinfo.st_uid, fileinfo.st_gid);
     
  return 0;   
} 


// function to check for existance of filename, proper user permissions and
// locked out files
void do_filecheck(char * filename)
{ 
  // extract path to file
  char * rcspath; 
  int fname_p; int str_ln=strlen(filename)-1;
  for(fname_p=str_ln;fname_p>0 && filename[fname_p]!='/';fname_p--);
  if(fname_p<1 && filename[0]=='/')
    { rcspath ="/\0"; fname_p++;}
  else if(fname_p<1) rcspath = "./\0";
  else 
  { rcspath = new char[fname_p+1];
    strncpy(rcspath,filename,++fname_p);
  }  

  struct stat *filecheck = new struct stat;
  memset(filecheck,-1,sizeof(struct stat));
  stat(rcspath,filecheck);
   // check that user has write access to current dir
   if(!((filecheck->st_mode - S_IFDIR | S_IWUSR)==filecheck->st_mode - S_IFDIR))
      { printf("You don't have write access to %s : Bailing out\n",rcspath);
        exit(0);
      }
   
   // check if RCS exits
   int rcsdir_exists=0, rcsfile_exists=0;
   filecheck->st_size = -1;
   memset(filecheck,-1,sizeof(struct stat));
   char * FULLRCSPATH;
   { char * params[2];
     params[0] = rcspath;
     params[1] = RCSDIR;
     FULLRCSPATH = exec_cmd(params,2,1);
   }
   stat(FULLRCSPATH,filecheck);
   if(S_ISDIR(filecheck->st_mode)) 
      { rcsdir_exists=1; 
        if (rcsdir_exists )
          { // check RCS/filename,v exists
            char *param[3];
            param[0] = FULLRCSPATH;
            param[1] = filename+fname_p; 
            param[2] = RCSEXT;
            //concat RCSDIR + filename + RCSEXT 
            memset(filecheck,-1,sizeof(struct stat));
            char * fname = exec_cmd(param,3,1);
            stat(fname,filecheck);
            if (S_ISREG(filecheck->st_mode)) rcsfile_exists=1;
          }         
      }
   
  // check if the file exists
  memset(filecheck,-1,sizeof(struct stat));
  stat(filename,filecheck);
  int file_exists=0;
  if (S_ISREG(filecheck->st_mode))  file_exists =1; 
  if( !file_exists && !rcsfile_exists ) // specified file doesn't exist
    {  printf("The file \"%s\" doesn't exist \n",filename);
       printf("Should I create a new file and check it into rcs [N] ");   
       if (dialog() != DG_YES) exit(0);    
       #ifndef DEBUG
       FILE * f = fopen(filename,"a"); 
       if (!f)
           { printf("Failed to create %s \n ERROR: ",filename);
             handle_error(-1);
           }
       else fclose(f); 
       if (!rcsdir_exists) mkdir(FULLRCSPATH,S_IRWXU | S_IRWXG | S_IRWXO);
       #endif
       // check file in first time
       char * params[2];
       params[0] = CHECK_IN;
       params[1] = filename;
       exec_cmd(params,2); 
    }
  else
  if( !rcsfile_exists && file_exists)
  {  printf("%s isn't being handled by RCS, check it into RCS for the first time[N] ",filename);
     if (dialog() != DG_YES) exit(0); 
     #ifndef DEBUG
     if (!rcsdir_exists) mkdir(FULLRCSPATH,S_IRWXU | S_IRWXG | S_IRWXO);
     #endif
     // check file in first time
      char * params[2];
      params[0] = CHECK_IN;
      params[1] = filename;
      exec_cmd(params,2);
  } 
  else if(file_exists & rcsfile_exists)
  // check if file has been checked out aready/Writable version exists
   { memset(filecheck,-1,sizeof(struct stat));
     stat(filename,filecheck);
     if ((filecheck->st_mode - S_IFREG | S_IWUSR)  == filecheck->st_mode -S_IFREG  ||
         (filecheck->st_mode - S_IFREG | S_IWGRP) == filecheck->st_mode - S_IFREG  ||
         (filecheck->st_mode - S_IFREG | S_IWOTH) == filecheck->st_mode - S_IFREG )
        { printf("%s has already been checked out \n",filename);
          printf("Writable version exists : Bailing out\n");
          exit(0);
        }
   }
  delete filecheck;
}

// function to check in size & time of a file
int do_filechange_check(char * fname,struct stat * original)
{ struct stat finfo;
  stat(fname,&finfo);
  if(finfo.st_mtime != original->st_mtime)
   { printf("You made changes to %s, (Accept \\ Re-edit \\ Discard) [A] ", fname);
     int key = dialog();
     if (key == ABORT)  
       {  // unlock file and exit  
           { char * params[2];
             params[0] = UNLOCK;
             params[1] = fname;
             exec_cmd(params,2);
           }
           remove(fname);
           // restore file (read-only)
           { char * params[2];
             params[0] = CHECK_OUT_R;
             params[1] = fname;
             exec_cmd(params,2);
           } 
           exit(0); 
       }
     else return key;
   }
  return DG_OTHER; 
}


// function to execute a command || copy list into a linear array
// command[0] == command
// command[1..n] == args
char * exec_cmd(char ** params,int len,int flag)
{ char * command = new char [MAX_BUFFER_SZ];
  int buf_ptr=0;
  for(int i=0;i<len;i++)
   { strncpy(command+buf_ptr,params[i],MAX_BUFFER_SZ-buf_ptr);
     buf_ptr += strlen(params[i]);
   } 
  #ifndef DEBUG
    if (flag) return command; 
    handle_error(system(command));
  #else
    printf("%s\n",command);
    if (flag) return command; 
  #endif
  delete [] command;
  return (char*) NULL; 
}

// um.. just in case
void handle_error(int err)
{ switch(err)
   {  case 127 : 
          printf("Error : execve call for /bin/sh failed\n"); 
          exit(1);
      case -1  : 
          printf("Error %s\n",sys_errlist[errno]);
          exit(1);
      default  : break;
   } 
}

// handles simple y/n dialog w/user
int dialog()
{  char response[10];
   fgets(response,10,stdin);
   response[0] = toupper(response[0]);
   switch(response[0])
     { case 'D' : return ABORT;
       case 'R' : return REEDIT;
       case 'Y' : return DG_YES;
       case 'N' : return DG_NO;
     } 
   return DG_OTHER;
}



syntax highlighted by Code2HTML, v. 0.9.1