Subject: bin/4231: ftp(1) doesn't create directories
To: None <gnats-bugs@gnats.netbsd.org>
From: Jaromir Dolecek <dolecek@ics.muni.cz>
List: netbsd-bugs
Date: 10/07/1997 00:53:47
>Number:         4231
>Category:       bin
>Synopsis:       ftp(1) doesn't create directories
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    bin-bug-people (Utility Bug People)
>State:          open
>Class:          change-request
>Submitter-Id:   net
>Arrival-Date:   Mon Oct  6 16:05:01 1997
>Last-Modified:
>Originator:     Jaromir Dolecek
>Organization:
	ICS MU Brno, Czech Republic
>Release:        1.2G
>Environment:
	
System: NetBSD beleg.ics.muni.cz 1.2G NetBSD 1.2G
Architecture: i386

>Description:
	Commands ``*put'' and ``*get'' doesn't handle case, where
	target file cannot be writtent due to not-existent
	directory, even throught the director(y|ies) can be created
	by sequence of mkdir(2) or MKD commands.
>How-To-Repeat:
	ftp> get pub/csacek/README
	local: pub/csacek/README remote: pub/csacek/README
	ftp: local: pub/csacek/README: No such file or directory
	ftp>
>Fix:
	"Un*x machines" referred to in following text stands
		for a few I've tried - it's NetBSD, SunOS, Sun Solaris.

	The code assumes:
		root exists on every machine and needn't to be created
		ftp server's response to STO? command has code
			553 or 550, if the file cannot be put
			due to not-existent directory (Un*x machines
			use 553, NT 4.0 box 550)
		even through MKD command hasn't succeeded, it's possible
			it can succeed for sub-directory (is case
			on special-configured NT boxes at least)
		path separators on local machine are '/'
		path separators used for remote files are '/' or
			'\\' ('/' checked first, no translation done
			in case of backslash)

	Other changes introduced by a patch:
		ftp.c: sendrequest(), recvrequest(): 
			on one place the code was plain copied, just
			because of passing other parameters to command(); now
			the code exists just once and only the parameters
			are changed
		ftp.c: getreply(): new global variable getreply_silent
			was added; if it's not 0, reply from ftp server
			is not copied to output

	The code is tested just for ``*put'' and ``*get'', but it
	actually should work for anything using recvrequest() or
	sendrequest().

	The test was not very very hard; I've just tried a few typical
	cases and all seemed to work. I've touched the code only
	where it was really necessary, so I don't expect it would break
	anything.

	IMHO it's nice feature; the last one I need and miss in
	our superior ftp client. In adittion, it's very robust;
	at least its implementation is better than equivalent ncftp(1)'s
	function.

	The patch follows. It's done against ftp as of about a week ago.

diff -c ../ftp_old/cmds.c ./cmds.c
*** ../ftp_old/cmds.c	Sun Sep 14 15:39:35 1997
--- ./cmds.c	Mon Oct  6 22:41:32 1997
***************
*** 669,674 ****
--- 669,675 ----
  	printf("Hash mark printing: %s; Mark count: %d; Progress bar: %s.\n",
  	    onoff(hash), mark, onoff(progress));
  	printf("Use of PORT cmds: %s.\n", onoff(sendport));
+ 	printf("Automatic directory creating: %s.\n", onoff(makedirs));
  #ifndef SMALL
  	printf("Command line editing: %s.\n", onoff(editing));
  #endif /* !SMALL */
***************
*** 1603,1608 ****
--- 1604,1617 ----
  {
  
  	code = togglevar(argc, argv, &crflag, "Carriage Return stripping");
+ }
+ 
+ void
+ setmakedirs(argc, argv)
+ 	int argc;
+ 	char *argv[];
+ {
+ 	code = togglevar(argc, argv, &makedirs, "Automatic directory making");
  }
  
  void
diff -c ../ftp_old/cmdtab.c ./cmdtab.c
*** ../ftp_old/cmdtab.c	Sun Sep 14 15:39:35 1997
--- ./cmdtab.c	Mon Oct  6 21:20:38 1997
***************
*** 76,81 ****
--- 76,82 ----
  char	lpwdhelp[] =	"print local working directory";
  char	lshelp[] =	"list contents of remote directory";
  char	macdefhelp[] =  "define a macro";
+ char	makedirshelp[]= "toggle automatic creating of directories";
  char	mdeletehelp[] =	"delete multiple files";
  char	mdirhelp[] =	"list contents of multiple remote directories";
  char	mgethelp[] =	"get multiple files";
***************
*** 169,174 ****
--- 170,176 ----
  	{ "lpwd",	lpwdhelp,	0, 0, 0, CMPL0		lpwd },
  	{ "ls",		lshelp,		1, 1, 1, CMPL(rl)	ls },
  	{ "macdef",	macdefhelp,	0, 0, 0, CMPL0		macdef },
+ 	{ "makedirs",	makedirshelp,	0, 0, 1, CMPL0		setmakedirs },
  	{ "mdelete",	mdeletehelp,	1, 1, 1, CMPL(R)	mdelete },
  	{ "mdir",	mdirhelp,	1, 1, 1, CMPL(R)	mls },
  	{ "mget",	mgethelp,	1, 1, 1, CMPL(R)	mget },
diff -c ../ftp_old/extern.h ./extern.h
*** ../ftp_old/extern.h	Thu Sep 11 12:14:12 1997
--- ./extern.h	Thu Sep 11 12:27:38 1997
***************
*** 135,140 ****
--- 135,141 ----
  void	setftmode __P((int, char **));
  void	setglob __P((int, char **));
  void	sethash __P((int, char **));
+ void	setmakedirs __P((int, char **));
  void	setnmap __P((int, char **));
  void	setntrans __P((int, char **));
  void	setpassive __P((int, char **));
diff -c ../ftp_old/ftp.1 ./ftp.1
*** ../ftp_old/ftp.1	Thu Sep 11 12:14:12 1997
--- ./ftp.1	Mon Oct  6 22:40:32 1997
***************
*** 431,436 ****
--- 431,439 ----
  on the second pass it is replaced by the second argument, and so on.
  A `\e' followed by any character is replaced by that character.
  Use the `\e' to prevent special treatment of the `$'.
+ .It Ic makedirs
+ Automatically create directories, if they don't exist. In interactive mode,
+ user will be prompted for confirmation first.
  .It Ic mdelete Op Ar remote-files
  Delete the
  .Ar remote-files
diff -c ../ftp_old/ftp.c ./ftp.c
*** ../ftp_old/ftp.c	Sun Sep 14 15:39:35 1997
--- ./ftp.c	Tue Oct  7 00:16:56 1997
***************
*** 263,268 ****
--- 263,270 ----
  }
  
  char reply_string[BUFSIZ];		/* first line of previous reply */
+ int  getreply_silent=0;			/* true if getreply() shouldn't */
+ 					/* copy reply to output */
  
  int
  getreply(expecteof)
***************
*** 321,327 ****
  				if (proxflag &&
  				   (dig == 1 || (dig == 5 && verbose == 0)))
  					printf("%s:", hostname);
! 				(void)putchar(c);
  			}
  			if (dig < 4 && isdigit(c))
  				code = code * 10 + (c - '0');
--- 323,331 ----
  				if (proxflag &&
  				   (dig == 1 || (dig == 5 && verbose == 0)))
  					printf("%s:", hostname);
! 
! 				if (!getreply_silent)
! 					(void)putchar(c);
  			}
  			if (dig < 4 && isdigit(c))
  				code = code * 10 + (c - '0');
***************
*** 348,355 ****
  				*cp++ = c;
  		}
  		if (verbose > 0 || (verbose > -1 && n == '5')) {
! 			(void)putchar(c);
! 			(void)fflush (stdout);
  		}
  		if (line == 0) {
  			size_t len = cp - current_line;
--- 352,361 ----
  				*cp++ = c;
  		}
  		if (verbose > 0 || (verbose > -1 && n == '5')) {
! 			if (!getreply_silent) {
! 				(void)putchar(c);
! 				(void)fflush (stdout);
! 			}
  		}
  		if (line == 0) {
  			size_t len = cp - current_line;
***************
*** 544,571 ****
  		restart_point = 0;
  		lmode = "r+w";
  	}
! 	if (remote) {
! 		if (command("%s %s", cmd, remote) != PRELIM) {
! 			(void)signal(SIGINT, oldintr);
! 			(void)signal(SIGINFO, oldinti);
! 			progress = oprogress;
! 			if (oldintp)
! 				(void)signal(SIGPIPE, oldintp);
! 			if (closefunc != NULL)
! 				(*closefunc)(fin);
! 			return;
  		}
! 	} else
! 		if (command("%s", cmd) != PRELIM) {
! 			(void)signal(SIGINT, oldintr);
! 			(void)signal(SIGINFO, oldinti);
! 			progress = oprogress;
! 			if (oldintp)
! 				(void)signal(SIGPIPE, oldintp);
! 			if (closefunc != NULL)
! 				(*closefunc)(fin);
! 			return;
  		}
  	dout = dataconn(lmode);
  	if (dout == NULL)
  		goto abort;
--- 550,617 ----
  		restart_point = 0;
  		lmode = "r+w";
  	}
! 	if (command(remote ? "%s %s" : "%s", cmd, remote) != PRELIM) {
! 		int abort=1;
! 
! 		if (remote && makedirs && (code == 553 || code == 550)
! 			&& strncmp(cmd, "STO", 3) == 0)
! 		{
! 			/* catch both STOR and STOU */
! 			/* work only when remote server returned	*/
! 			/* File Not Found or Requested action not taken */
! 		    char *dir, *temp, pathsep='/';
! 		    int len;
! 
! 		    /* copy directory part of cmd parameter */
! 		    temp = strrchr(remote, '/');
! 		    if (temp == NULL)
! 			temp = strchr(remote, '\\'), pathsep = '\\'; 
! 			/* some legacy machines use backslash as separator */
! 
! 		    if (temp) {
! 			len = temp-remote+1;
! 			dir = (char *) alloca(len+1);
! 			strncpy(dir, remote, len);
! 			dir[len] = 0;
! 		    }
! 		   
! 		    if (temp && confirm("Create remote directory", dir) )
! 		    {
! 			char *argv[2]; /* for makedir() */
! 
! 			argv[0] = (char *) "";
! 			argv[1] = dir;
! 			temp = dir;
! 			while((temp = strchr(temp, pathsep))) {
! 			   if (temp == dir)
! 				continue; /* root needn't to be created */
! 			   *temp = '\0';
! 				
! 			   getreply_silent=1;
! 			   makedir(2, argv);
! 			   getreply_silent=0;
! 			   *temp = pathsep;
! 			   temp++; /* shift behind the slash */
! 			}
! 			if (temp == NULL && abort) {
! 				if (command(remote ? "%s %s" : "%s", cmd,
! 						remote) == PRELIM)
! 					abort = 0;
! 			}
! 		    }
  		}
! 
! 		if (abort) {
! 		    (void)signal(SIGINT, oldintr);
! 		    (void)signal(SIGINFO, oldinti);
! 		    progress = oprogress;
! 		    if (oldintp)
! 			(void)signal(SIGPIPE, oldintp);
! 		    if (closefunc != NULL)
! 			(*closefunc)(fin);
! 		    return;
  		}
+ 	}
  	dout = dataconn(lmode);
  	if (dout == NULL)
  		goto abort;
***************
*** 754,781 ****
  	oldinti = signal(SIGINFO, psummary);
  	if (strcmp(local, "-") && *local != '|') {
  		if (access(local, 2) < 0) {
! 			char *dir = strrchr(local, '/');
  
! 			if (errno != ENOENT && errno != EACCES) {
  				warn("local: %s", local);
  				(void)signal(SIGINT, oldintr);
  				(void)signal(SIGINFO, oldinti);
  				code = -1;
  				return;
  			}
! 			if (dir != NULL)
! 				*dir = 0;
! 			d = access(dir == local ? "/" : dir ? local : ".", 2);
! 			if (dir != NULL)
! 				*dir = '/';
! 			if (d < 0) {
  				warn("local: %s", local);
  				(void)signal(SIGINT, oldintr);
  				(void)signal(SIGINFO, oldinti);
  				code = -1;
  				return;
! 			}
! 			if (!runique && errno == EACCES &&
  			    chmod(local, 0600) < 0) {
  				warn("local: %s", local);
  				(void)signal(SIGINT, oldintr);
--- 800,859 ----
  	oldinti = signal(SIGINFO, psummary);
  	if (strcmp(local, "-") && *local != '|') {
  		if (access(local, 2) < 0) {
! 			char *dir, *temp;
! 			int len, first_errno=errno;
  
! 			if (first_errno != ENOENT && first_errno != EACCES) {
  				warn("local: %s", local);
  				(void)signal(SIGINT, oldintr);
  				(void)signal(SIGINFO, oldinti);
  				code = -1;
  				return;
  			}
! 
! 			temp = strrchr(local, '/');
! 			/* have to crerate copy of ``local'', as we need */
! 			/* to write to it and ``local'' is const	 */
! 			/* it's only directory part of ``local'' what is */
! 			/* needed, so copy just this */
! 			len = temp ? temp-local+1 : 1;
! 			dir = (char *) alloca(len+1);
! 			strncpy(dir, temp ? local : ".", len);
! 			dir[len] = '\0';
! 
! 			if (access(dir, 2) < 0) {
! 			    if (!makedirs || !confirm("Create local dir",dir))
! 			    {
  				warn("local: %s", local);
  				(void)signal(SIGINT, oldintr);
  				(void)signal(SIGINFO, oldinti);
  				code = -1;
  				return;
! 			    }
! 			    else {
! 				temp = dir;
! 				/* create all subdirectories too */
! 				while((temp = strchr(temp, '/'))) {
! 				    if (temp == dir)
! 					continue;
! 					/* don't need to create root ;-) */
! 				    *temp = 0;
! 				    if (access(dir, 2) < 0
! 					&& mkdir(dir,0755) < 0)
! 				    {
! 					warn("local: %s: %s", local, dir);
! 					(void)signal(SIGINT, oldintr);
! 					(void)signal(SIGINFO, oldinti);
! 					code = -1;
! 					return;
! 				    }
! 				    *temp = '/';
! 				    temp++; /* shift behind the slash */
! 				} /* while cycle */
! 			    } /* else (!makedirs ...) */
! 			} /* if (access(dir, 2)<0) */
! 
! 			if (!runique && first_errno == EACCES &&
  			    chmod(local, 0600) < 0) {
  				warn("local: %s", local);
  				(void)signal(SIGINT, oldintr);
***************
*** 817,834 ****
  	if (is_retr && restart_point &&
  	    command("REST %ld", (long) restart_point) != CONTINUE)
  		return;
! 	if (remote) {
! 		if (command("%s %s", cmd, remote) != PRELIM) {
! 			(void)signal(SIGINT, oldintr);
! 			(void)signal(SIGINFO, oldinti);
! 			return;
! 		}
! 	} else {
! 		if (command("%s", cmd) != PRELIM) {
! 			(void)signal(SIGINT, oldintr);
! 			(void)signal(SIGINFO, oldinti);
! 			return;
! 		}
  	}
  	din = dataconn("r");
  	if (din == NULL)
--- 895,904 ----
  	if (is_retr && restart_point &&
  	    command("REST %ld", (long) restart_point) != CONTINUE)
  		return;
! 	if (command(remote ? "%s %s" : "%s", cmd, remote) != PRELIM) {
! 		(void)signal(SIGINT, oldintr);
! 		(void)signal(SIGINFO, oldinti);
! 		return;
  	}
  	din = dataconn("r");
  	if (din == NULL)
***************
*** 1247,1252 ****
--- 1317,1323 ----
  		int sunqe;
  		int runqe;
  		int mcse;
+ 		int mkdirs;
  		int ntflg;
  		char nti[17];
  		char nto[17];
***************
*** 1299,1304 ****
--- 1370,1377 ----
  	runique = op->runqe;
  	ip->mcse = mcase;
  	mcase = op->mcse;
+ 	ip->mkdirs = makedirs;
+ 	makedirs = op->mkdirs;
  	ip->ntflg = ntflag;
  	ntflag = op->ntflg;
  	(void)strncpy(ip->nti, ntin, sizeof(ip->nti) - 1);
diff -c ../ftp_old/ftp_var.h ./ftp_var.h
*** ../ftp_old/ftp_var.h	Thu Sep 11 12:14:12 1997
--- ./ftp_var.h	Tue Oct  7 00:06:01 1997
***************
*** 80,85 ****
--- 80,86 ----
  int	sunique;		/* store files on server with unique name */
  int	runique;		/* store local files with unique name */
  int	mcase;			/* map upper to lower case for mget names */
+ int	makedirs;		/* automatically create directories */
  int	ntflag;			/* use ntin ntout tables for name translation */
  int	mapflag;		/* use mapin mapout templates on file names */
  int	preserve;		/* preserve modification time on files */
>Audit-Trail:
>Unformatted: