Index: cvs/CVSviaFTP.README
diff -c /dev/null cvs/CVSviaFTP.README:1.1
*** /dev/null	Tue Aug 26 00:02:58 1997
--- cvs/CVSviaFTP.README	Sun Aug 11 17:35:53 1996
***************
*** 0 ****
--- 1,188 ----
+ 	CVSviaFTP: Automagically update your remote FTP/WWW site
+ 	========================================================
+ 
+ IMPORTANT note: This README file is updated, but not very frequently.
+ For the newest version look at http://www.siber.com/cvs-via-ftp/index.html.
+ 
+ 
+ CVSviaFTP is a life-saver for Web site designers and administrators who
+ need to update their site remotely using FTP protocol only.
+ Here's my story.
+ 
+ 
+ Why I did it
+ ------------
+ 
+ I am maintaining a 700+ file multilevel WWW directory tree at
+ http://www.siber.com/ and I only have FTP access to the tree (my
+ provider took away shell accounts with access to Web sites from all
+ the customers citing security concerns).
+ 
+ Since I have a test site on my local computer and a production site on
+ the remote computer accessible only via FTP, I would have to manually
+ FTP over all the changes from the test site to the production site,
+ which is, to put it mildly, painful.
+ 
+ So I decided to modify revision control system named CVS to make it
+ update a mirror of the directory tree *via FTP protocol*.
+ 
+ The strong points of my designs are:
+ - Communication is minimal: only updated files are sent over FTP
+   and nothing else.
+ - New directories are created automatically at the remote site.
+   Unneeded directories are automatically removed.
+ - In addition to Update you can do a complete Checkout of the
+   local directory tree to the remote site.
+ 
+ 
+ What is CVS
+ -----------
+ 
+ First, you need to know what the CVS is.  I don't really have time to
+ explain it, so let's assume that you know it.
+ 
+ And if you don't know what it is, you still can get my distribution of CVS
+ and read papers and man pages on CVS that appear the distribution.
+ 
+ In short words, CVS is a revision control system that's built on top
+ of RCS.  The good things about it is that it works, it's free, and I
+ like it.  RCS is a standard low-level Revision Control System most
+ widely used on UNIX and everywhere else.
+ 
+ 
+ How to Install CVSviaFTP
+ ------------------------
+ 
+ First, download http://www.siber.com/cvs-via-ftp/cvs-1.8.1-via-ftp.tar.gz,
+ ungzip and untar it.
+ 
+ Then follow the instructions in the INSTALL file that's a part of
+ distribution.  CVS is installed just like any other GNU software
+ package.
+ 
+ 
+ How CVSviaFTP Works
+ -------------------
+ 
+ I modified CVS so that it can do "cvs checkout" and "cvs update"
+ at the remote site using FTP protocol.
+ 
+ To use the CVSviaFTP feature, download and install CVSviaFTP
+ (described above), and then type in your checked out directory:
+ 
+ cvs update   -F ftp_commands_file
+ 
+ 		OR
+ 
+ cvs checkout -F ftp_commands_file
+ 
+ 
+ After updating your local directory in the file ftp_commands_file you
+ will get a sequence of FTP commands that upgrades the FTP-accessible
+ mirror of this local directory.
+ 
+ That is, CVSviaFTP will not start an FTP session for you, it will just
+ write out a file that you can later feed to your favorite FTP client.
+ It was done on purpose: 
+ - Different people like different FTP clients;
+ - You need to prepend login commands and append 
+   quit or other finishing commands to the sequence produced by CVSviaFTP.
+ - For real sensitive sites you may want to manually check
+   FTP command sequence just to be sure everything's OK.
+ 
+ 
+ How to Integrate It
+ -------------------
+ 
+ This is an example of a C shell script that does 
+ the whole "local commit / remote update" thing:
+ 
+ #!/bin/csh -x
+ if ( "$1" != "" ) then
+   cd ~/html
+   cvs commit -m "$1"
+ endif
+ cd ~/html-dist/html
+ cvs update -F ftp.out -P -d
+ cat ~/etc/ftp-start $ftpfile ~/etc/ftp-finish >ftp-comms
+ ncftp -u gunky.junky.com <ftp-comms
+ 
+ 
+ In this script the test tree is kept at ~/html, and the tree that is
+ to be mirrored at the remote site is kept at ~/html-dist/html.
+ 
+ The script commits changes in the test tree to the repository, checks
+ out changes to ~/html-dist/html and writes FTP commands to ftp.out.
+ Then header and finisher lines are added to the ftp.out and FTP is
+ called that executes the update at the remote site.
+ 
+ 
+ Notes On Use
+ ------------
+ 
+ Read them if you want to know more:
+ 
+ CVS does not like symbolic links. CVSviaFTP does not like them
+ either.  It expects that "cd xxx/.." always gets to the
+ original directory.
+ 
+ You cannot do "cvs commit" via FTP, that is, repository
+ should be accessible to your machine in a 'regular' CVS way:
+ - Repository is mounted on local file system;
+ - Repository is on NFS-mounted file system;
+ - Repository is accessible via CVS client-server relationship.
+ 
+ My approach only works if initially the checked-out local directory
+ and the remote mirror directory trees are exactly the same, because
+ CVSviaFTP does not fstat remote files.
+ The reason it does not do it is that it slows things down
+ considerably (FTP protocol is slow), it is not portable (there is no
+ fstat command in FTP and format of ls's output is non-standard).
+ 
+ There is a bug in CVS: when you do "cvs update" with no arguments, 
+ CVS does "cd ." and after processing matches it with "cd .."
+ which gets you to the level above from where you started.
+ Since current directory is not kept upon exit, you don't notice it,
+ but my ftp_cd() function does. However it keeps quiet about it
+ most time. Still fixing this bug is a good idea.
+ 
+ Before doing actual FTPing, it would not hurt to check file with
+ FTP commands. I use CVSviaFTP to maintain my live site at 
+ http://www.siber.com, so please be assured that it is tested.
+ But taking a look at FTP command sequence before firing off FTP
+ would not hurt.
+ 
+ 
+ Legal Stuff
+ -----------
+ 
+ This software is distributed according to the GNU Public License,
+ that is for free (bezvozmezdno).
+ 
+ That is, you can use it for free, even to develop commercial products,
+ but if you modify it, the result is subject to the GNU Public License.
+ 
+ There is NO any explicit or implied warranty on this product, or any
+ promise thereof, that the product will work.  By downloading the
+ tarred-n-zipped distribution file you assume all the liability that
+ may arise from the use of this product.
+ 
+ 
+ Additional Services
+ -------------------
+ 
+ You can purchase a contract for maintenance of this product
+ from Siber Systems.
+ 
+ You are welcome to advertise at this page.
+ 
+ 
+ Final words
+ -----------
+ 
+ If you have questions/proposals, please contact
+ 
+ Vadim Maslov, vadik@siber.com
+ 
+ 
+ Last updated August 11, 1996.
Index: cvs/ChangeLog
diff -c cvs/ChangeLog:1.1.1.1 cvs/ChangeLog:1.2
*** cvs/ChangeLog:1.1.1.1	Tue Jul 30 12:07:36 1996
--- cvs/ChangeLog	Sun Aug 11 17:35:54 1996
***************
*** 1,3 ****
--- 1,10 ----
+ Sun Aug 11 20:35:21 1996  Vadim Maslov     <vadik@siber.com>
+ 
+ 	Added CVSviaFTP: do cvs update and cvs checkout 
+ 	to the remote site using FTP protocol.
+ 	Useful for maintaining remote WWW and FTP sites.
+ 	See details in CVSviaFTP-README.
+ 
  Sun May  5 17:38:21 1996  Benjamin J. Lee  <benjamin@cyclic.com>
  
  	Integrated changes submitted by Ian Taylor <ian@cygnus.com>
Index: cvs/src/checkout.c
diff -c cvs/src/checkout.c:1.1.1.1 cvs/src/checkout.c:1.2
*** cvs/src/checkout.c:1.1.1.1	Tue Jul 30 12:07:39 1996
--- cvs/src/checkout.c	Wed Aug  7 13:58:55 1996
***************
*** 75,80 ****
--- 75,81 ----
      "\t-D date\tCheck out revisions as of date.\n",
      "\t-d dir\tCheck out into dir instead of module name.\n",
      "\t-k kopt\tUse RCS kopt -k option on checkout.\n",
+     "\t-F file\tWrite out to file FTP commands to perform remote update/checkout\n",
      NULL
  };
  
***************
*** 122,128 ****
      else
      {
          m_type = CHECKOUT;
! 	valid_options = "ANnk:d:flRpQqcsr:D:j:P";
  	valid_usage = checkout_usage;
      }
  
--- 123,129 ----
      else
      {
          m_type = CHECKOUT;
! 	valid_options = "ANnk:d:flRpQqcsr:D:j:PF:";
  	valid_usage = checkout_usage;
      }
  
***************
*** 170,175 ****
--- 171,184 ----
  		break;
  	    case 'P':
  		checkout_prune_dirs = 1;
+ 	    case 'F':                   /* VM: FTP support */
+ 		ftp_comm = TRUE;
+ 		ftp_file = fopen(optarg, "w");
+ 		if(ftp_file==NULL) {
+ 		  error(1, 0, "Cannot open FTP command file '%s' for write\n",
+ 			optarg);
+ 		}
+ 		break;
  		break;
  	    case 'p':
  		pipeout = 1;
***************
*** 345,352 ****
--- 354,370 ----
  	char repository[PATH_MAX];
  
  	(void) CVS_MKDIR (where, 0777);
+ 	/* VM: FTP support */
+ 	if(ftp_comm) {
+ 	    ftp_cd(NULL);
+ 	    fprintf(ftp_file, "mkdir %s\n", where);
+ 	}
  	if (chdir (where) < 0)
  	    error (1, errno, "cannot chdir to %s", where);
+ 	/* VM: FTP support */
+ 	if(ftp_comm) {
+ 	    ftp_cd(where);
+ 	}
  	preload_update_dir = xstrdup (where);
  	where = (char *) NULL;
  	if (!isfile (CVSADM))
***************
*** 358,363 ****
--- 376,386 ----
  		mode_t omask;
  		omask = umask (cvsumask);
  		(void) CVS_MKDIR (repository, 0777);
+ 		/* VM: FTP support */
+ 		if(ftp_comm) {
+ 		  ftp_cd(NULL);
+ 		  fprintf(ftp_file, "mkdir %s\n", repository);
+ 		}
  		(void) umask (omask);
  	    }
  
***************
*** 398,403 ****
--- 421,430 ----
  
  	if (chdir (where) < 0)
  	    error (1, errno, "cannot chdir to %s", where);
+ 	/* VM: FTP support */
+ 	if(ftp_comm) {
+ 	    ftp_cd(where);
+ 	}
  
  	preload_update_dir = xstrdup (where);
  
***************
*** 720,725 ****
--- 747,756 ----
  	    preload_update_dir = oldupdate;
  	    return (1);
  	}
+ 	/* VM: FTP support */
+ 	if(ftp_comm) {
+ 	    ftp_cd(repository);
+ 	}
  	which = W_REPOS;
  	if (tag != NULL && !tag_validated)
  	{
***************
*** 852,862 ****
--- 883,902 ----
  	*slash = '\0';
  	*slash2 = '\0';
  	(void) CVS_MKDIR (cp, 0777);
+ 	/* VM: FTP support */
+ 	if(ftp_comm) {
+ 	    ftp_cd(NULL);
+ 	    fprintf(ftp_file, "mkdir %s\n", cp);
+ 	}
  	if (chdir (cp) < 0)
  	{
  	    error (0, errno, "cannot chdir to %s", cp);
  	    return (1);
  	}
+ 	/* VM: FTP support */
+ 	if(ftp_comm) {
+ 	    ftp_cd(cp);
+ 	}
  	if (!isfile (CVSADM) && strcmp (command_name, "export") != 0)
  	{
  	    (void) sprintf (repository, "%s/%s", prepath, path2);
***************
*** 880,889 ****
--- 920,938 ----
  	*slash2 = '/';
      }
      (void) CVS_MKDIR (cp, 0777);
+     /* VM: FTP support */
+     if(ftp_comm) {
+         ftp_cd(NULL);
+         fprintf(ftp_file, "mkdir %s\n", cp);
+     }
      if (chdir (cp) < 0)
      {
  	error (0, errno, "cannot chdir to %s", cp);
  	return (1);
+     }
+     /* VM: FTP support */
+     if(ftp_comm) {
+         ftp_cd(cp);
      }
      return (0);
  }
Index: cvs/src/cvs.h
diff -c cvs/src/cvs.h:1.1.1.1 cvs/src/cvs.h:1.3
*** cvs/src/cvs.h:1.1.1.1	Tue Jul 30 12:07:38 1996
--- cvs/src/cvs.h	Wed Aug  7 13:58:56 1996
***************
*** 407,413 ****
--- 407,418 ----
  extern int really_quiet, quiet;
  extern int use_editor;
  extern int cvswrite;
+ extern int ftp_comm;
+ 
+ /* VM: FTP support */
+ extern FILE *ftp_file;
  extern mode_t cvsumask;
+ void ftp_cd(char *dir);
  
  extern int trace;		/* Show all commands */
  extern int noexec;		/* Don't modify disk anywhere */
Index: cvs/src/main.c
diff -c cvs/src/main.c:1.1.1.1 cvs/src/main.c:1.3
*** cvs/src/main.c:1.1.1.1	Tue Jul 30 12:07:40 1996
--- cvs/src/main.c	Wed Aug  7 13:58:56 1996
***************
*** 80,85 ****
--- 80,89 ----
  int logoff = FALSE;
  mode_t cvsumask = UMASK_DFLT;
  
+ /* VM: FTP support */
+ int ftp_comm = FALSE;
+ FILE *ftp_file = NULL;
+ 
  char *CurDir;
  
  /*
Index: cvs/src/modules.c
diff -c cvs/src/modules.c:1.1.1.1 cvs/src/modules.c:1.2
*** cvs/src/modules.c:1.1.1.1	Tue Jul 30 12:07:41 1996
--- cvs/src/modules.c	Wed Aug  7 13:58:58 1996
***************
*** 350,355 ****
--- 350,359 ----
  		err++;
  		goto out;
  	    }
+ 	    /* VM: FTP support */
+ 	    if(ftp_comm) {
+ 	        ftp_cd(dir);
+ 	    }
  	    if (!isfile (CVSADM))
  	    {
  		char nullrepos[PATH_MAX];
Index: cvs/src/rcscmds.c
diff -c cvs/src/rcscmds.c:1.1.1.1 cvs/src/rcscmds.c:1.3
*** cvs/src/rcscmds.c:1.1.1.1	Tue Jul 30 12:07:41 1996
--- cvs/src/rcscmds.c	Wed Aug  7 13:58:58 1996
***************
*** 138,143 ****
--- 138,155 ----
      int flags;
      int noerr;
  {
+     /* VM: FTP support */
+     if(ftp_comm) {
+       static char save_dir_name[MAXPATHLEN] = {0};
+       char dir_name[MAXPATHLEN];
+       getcwd(dir_name, MAXPATHLEN);
+       if(strcmp(dir_name, save_dir_name)!=0) {
+ 	fprintf(ftp_file, "lcd %s\n", dir_name);
+ 	strcpy(save_dir_name, dir_name);
+       }
+       ftp_cd(NULL);
+       fprintf(ftp_file, "put %s\n", workfile);
+     }
      run_setup ("%s%s -x,v/ -q %s%s", Rcsbin, RCS_CO,
                 tag ? "-r" : "", tag ? tag : "");
      if (options != NULL && options[0] != '\0')
Index: cvs/src/recurse.c
diff -c cvs/src/recurse.c:1.1.1.1 cvs/src/recurse.c:1.3
*** cvs/src/recurse.c:1.1.1.1	Tue Jul 30 12:07:41 1996
--- cvs/src/recurse.c	Wed Aug  7 13:58:59 1996
***************
*** 571,576 ****
--- 571,580 ----
  	/* cd to the sub-directory */
  	if (chdir (dir) < 0)
  	    error (1, errno, "could not chdir to %s", dir);
+ 	/* VM: FTP support */
+ 	if(ftp_comm) {
+ 	    ftp_cd(dir);
+ 	}
  
  	/* honor the global SKIP_DIRS (a.k.a. local) */
  	if (flags == R_SKIP_DIRS)
***************
*** 684,689 ****
--- 688,697 ----
  	    exit (EXIT_FAILURE);
  	if (chdir (p->key) < 0)
  	    error (1, errno, "could not chdir to %s", p->key);
+ 	/* VM: FTP support */
+ 	if(ftp_comm) {
+ 	    ftp_cd(p->key);
+ 	}
  
  	save_update_dir = xstrdup (update_dir);
  
Index: cvs/src/update.c
diff -c cvs/src/update.c:1.1.1.1 cvs/src/update.c:1.3
*** cvs/src/update.c:1.1.1.1	Tue Jul 30 12:07:42 1996
--- cvs/src/update.c	Wed Aug  7 13:58:59 1996
***************
*** 100,105 ****
--- 100,106 ----
      "\t-j rev\tMerge in changes made between current revision and rev.\n",
      "\t-I ign\tMore files to ignore (! to reset).\n",
      "\t-W spec\tWrappers specification line.\n",
+     "\t-F file\tWrite out to file FTP commands to perform remote update/checkout\n",
      NULL
  };
  
***************
*** 123,129 ****
  
      /* parse the args */
      optind = 1;
!     while ((c = getopt (argc, argv, "ApPflRQqduk:r:D:j:I:W:")) != -1)
      {
  	switch (c)
  	{
--- 124,130 ----
  
      /* parse the args */
      optind = 1;
!     while ((c = getopt (argc, argv, "ApPflRQqduk:r:D:j:I:W:F:")) != -1)
      {
  	switch (c)
  	{
***************
*** 136,141 ****
--- 137,150 ----
  	    case 'W':
  		wrap_add (optarg, 0);
  		break;
+ 	    case 'F':                   /* VM: FTP support */
+ 		ftp_comm = TRUE;
+ 		ftp_file = fopen(optarg, "w");
+ 		if(ftp_file==NULL) {
+ 		  error(1, 0, "Cannot open FTP command file '%s' for write\n",
+ 			optarg);
+ 		}
+ 		break;
  	    case 'k':
  		if (options)
  		    free (options);
***************
*** 738,743 ****
--- 747,757 ----
  	{
  	    /* otherwise, create the dir and appropriate adm files */
  	    make_directory (dir);
+ 	    /* VM: FTP support */
+ 	    if(ftp_comm) {
+ 	        ftp_cd(NULL);
+ 		fprintf(ftp_file, "mkdir %s\n", dir);
+ 	    }
  	    Create_Admin (dir, update_dir, repository, tag, date);
  	}
      }
***************
*** 845,850 ****
--- 859,868 ----
      /* FIXME: chdir ("..") loses with symlinks.  */
      /* Prune empty dirs on the way out - if necessary */
      (void) chdir ("..");
+     /* VM: FTP support */
+     if(ftp_comm) {
+         ftp_cd("..");
+     }
      if (update_prune_dirs && isemptydir (dir))
      {
  	/* I'm not sure the existence_error is actually possible (except
***************
*** 852,857 ****
--- 870,880 ----
  	   this code used to ignore all errors, I'll play it safe.  */
  	if (unlink_file_dir (dir) < 0 && !existence_error (errno))
  	    error (0, errno, "cannot remove %s directory", dir);
+ 	/* VM: FTP support */
+ 	if(ftp_comm) {
+ 	    ftp_cd(NULL);
+ 	    fprintf(ftp_file, "rmdir %s\n", dir);
+ 	}
      }
  
      return (err);
***************
*** 899,904 ****
--- 922,932 ----
      history_write ('W', update_dir, "", file, repository);
      Scratch_Entry (entries, file);
      (void) unlink_file (file);
+     /* VM: FTP support */
+     if(ftp_comm) {
+         ftp_cd(NULL);
+         fprintf(ftp_file, "delete %s\n", file);
+     }
      return (0);
  }
  
***************
*** 1067,1072 ****
--- 1095,1105 ----
  			error (0, errno, "cannot remove %s/%s", update_dir,
  			       file);
  		}
+ 		/* VM: FTP support */
+ 		if(ftp_comm) {
+ 		    ftp_cd(NULL);
+ 		    fprintf(ftp_file, "delete %s\n", file);
+ 		}
  	    }
  	    else
  	      Register (entries, file,
***************
*** 1827,1830 ****
--- 1860,1958 ----
  joining ()
  {
      return (join_rev1 != NULL);
+ }
+ 
+ 
+ /*
+  * VM: CD in FTP tree.
+  * The goal is to turn sequence of CDs into one CD,
+  * thus saving communication time.
+  *
+  * Possible dir_name: aaa/bbb/ccc aaa . .. ../aaa/bb
+  * dir_name==NULL means: output accumulated CD.
+  *
+  * We interprete .. here, so don't use symbolic links.
+  * BTW, FTP does not like symbolic links either.
+  */
+ void ftp_cd(char *dir) {
+   static char save_dir[MAXPATHLEN] = {0};
+   static char curr_dir[MAXPATHLEN] = {0};
+   static char *pcdir = curr_dir;
+   static int updated = TRUE;
+   static int bad_dir = FALSE;
+ 
+   if(dir==NULL) {
+     /* Dump CD if it new curr_dir is different from the last time */
+     if(strcmp(curr_dir,save_dir) != 0) {
+       char tmp_dir[MAXPATHLEN];
+       int i;
+       char *p;
+ 
+       if(bad_dir) {
+ 	error(1, 0, "Attempt to cd to directory above the current directory\n");
+       }
+ 
+       /* Move down from save_dir to root */
+       fprintf(ftp_file, "cd ", tmp_dir);
+       for(p=save_dir; *p; p++) {
+ 	if(*p=='/') {
+ 	  fprintf(ftp_file, "../");
+ 	}
+       }
+ 
+       /* Move up to curr_dir */
+       strcpy(tmp_dir, curr_dir);
+       i = strlen(tmp_dir);
+       if(i>0) {
+ 	if(tmp_dir[--i]!='/') {
+ 	  error(1,0, "ftp_cd: bad directory name: %s\n", tmp_dir);
+ 	}
+ 	tmp_dir[i]=0;
+       }
+       fprintf(ftp_file, "%s\n", tmp_dir);
+       strcpy(save_dir, curr_dir);
+     }
+ 
+   } else {
+ 
+     /* Parse dir */
+     char *p, *q;
+     for(p=dir; *p; ) {
+       char name[MAXPATHLEN];
+       char *r = &name[0];
+       for(q=p; *q && *q!='/'; q++) {
+ 	*r++ = *q;
+       }
+       if(*q) {
+ 	p = q + 1;         /* next member name */
+       } else {
+ 	p = q;
+       }
+       *r = 0;
+ 
+       if(name[0]==0 || strcmp(name, ".") == 0) {
+ 	/* Do nothing */
+ 
+       } else if(strcmp(name, "..") == 0) {
+ 	/* Go up */
+ 	/*
+ 	 * CVS thinks that "cd ." should be matched by "cd ..".
+ 	 * So allow it do "cd .." to beyond the root, bit don't let it use it.
+ 	 */
+ 	if(pcdir<curr_dir+2 || pcdir[-1]!='/') {
+ 	  bad_dir = TRUE;
+ 	}
+ 	for(--pcdir; --pcdir>=curr_dir && *pcdir!='/'; ) ;
+ 	*++pcdir = 0;
+ 
+       } else {
+ 	/* Go down */
+ 	for(r=name; *r; r++) {
+ 	  *pcdir++ = *r;
+ 	}
+ 	*pcdir++ = '/';
+ 	*pcdir = 0;
+       }
+     }
+   }
  }
