Subject: Re: bin/33866: make(1) does not :Q the newline character correctly
To: None <gnats-admin@netbsd.org, netbsd-bugs@netbsd.org, rillig@NetBSD.org>
From: Roland Illig <rillig@NetBSD.org>
List: netbsd-bugs
Date: 06/29/2006 18:35:02
The following reply was made to PR bin/33866; it has been noted by GNATS.

From: Roland Illig <rillig@NetBSD.org>
To: gnats-bugs@NetBSD.org
Cc: 
Subject: Re: bin/33866: make(1) does not :Q the newline character correctly
Date: Thu, 29 Jun 2006 20:34:39 +0200

 This is a multi-part message in MIME format.
 --------------020504090203020904010807
 Content-Type: text/plain; charset=us-ascii; format=flowed
 Content-Transfer-Encoding: 7bit
 
 Christos Zoulas wrote:
 > 
 >  On Jun 29,  3:15pm, rillig@NetBSD.org (rillig@NetBSD.org) wrote:
 >  -- Subject: bin/33866: make(1) does not :Q the newline character correctly
 >  
 >  | The make(1) man page says that the :Q operator escapes all shell
 >  | metacharacters properly. However, for the newline character this is
 >  | wrong. It is converted into <backslash><newline>, which does not result
 >  | in a single newline character when parsed by the shell.
 >  | 
 >  | >How-To-Repeat:
 >  | 
 >  | cd pkgsrc/regress/make-quoting && make REGRESS_TESTS=newline
 >  | 
 >  
 >  This has to be shell dependent. Have you tested it will different shells?
 
 No, I hadn't.
 
 For my second try, please see the appended patch. I have tested it with 
 sh, ksh and csh, and with a user-defined value of "nnn". They all work.
 
 Roland
 
 --------------020504090203020904010807
 Content-Type: text/plain;
  name="bmake-newline.patch"
 Content-Transfer-Encoding: 7bit
 Content-Disposition: inline;
  filename="bmake-newline.patch"
 
 ? .gdbinit
 ? make
 ? var.c.patch
 Index: arch.c
 ===================================================================
 RCS file: /cvsroot/src/usr.bin/make/arch.c,v
 retrieving revision 1.50
 diff -u -p -r1.50 arch.c
 --- arch.c	22 Apr 2006 18:38:38 -0000	1.50
 +++ arch.c	29 Jun 2006 18:30:27 -0000
 @@ -558,7 +558,7 @@ ArchStatMember(char *archive, char *memb
  	} else {
  	    /* Try truncated name */
  	    char copy[AR_MAX_NAME_LEN+1];
 -	    int len = strlen(member);
 +	    size_t len = strlen(member);
  
  	    if (len > AR_MAX_NAME_LEN) {
  		len = AR_MAX_NAME_LEN;
 @@ -851,7 +851,7 @@ ArchFindMember(char *archive, char *memb
      int		  size;       /* Size of archive member */
      char	  *cp;	      /* Useful character pointer */
      char	  magic[SARMAG];
 -    int		  len, tlen;
 +    size_t	  len, tlen;
  
      arch = fopen(archive, mode);
      if (arch == NULL) {
 Index: job.c
 ===================================================================
 RCS file: /cvsroot/src/usr.bin/make/job.c,v
 retrieving revision 1.111
 diff -u -p -r1.111 job.c
 --- job.c	31 Mar 2006 21:05:34 -0000	1.111
 +++ job.c	29 Jun 2006 18:30:28 -0000
 @@ -215,7 +215,7 @@ static Shell    shells[] = {
  {
      "csh",
      TRUE, "unset verbose", "set verbose", "unset verbose", 10,
 -    FALSE, "echo \"%s\"\n", "csh -c \"%s || exit 0\"\n", "", '#',
 +    FALSE, "echo \"%s\"\n", "csh -c \"%s || exit 0\"\n", "", "'\\\n'", '#',
      "v", "e",
  },
      /*
 @@ -225,7 +225,7 @@ static Shell    shells[] = {
  {
      "sh",
      FALSE, "", "", "", 0,
 -    FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", '#',
 +    FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#',
  #ifdef __NetBSD__
      "q",
  #else
 @@ -239,7 +239,7 @@ static Shell    shells[] = {
  {
      "ksh",
      TRUE, "set +v", "set -v", "set +v", 6,
 -    FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", '#',
 +    FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#',
      "v",
      "",
  },
 @@ -249,7 +249,7 @@ static Shell    shells[] = {
  {
      NULL,
      FALSE, NULL, NULL, NULL, 0,
 -    FALSE, NULL, NULL, NULL, 0,
 +    FALSE, NULL, NULL, NULL, NULL, 0,
      NULL, NULL,
  }
  };
 @@ -2187,6 +2187,17 @@ Shell_Init()
  }
  
  /*-
 + * Returns the string literal that is used in the current command shell
 + * to produce a newline character.
 + */
 +const char *
 +Shell_GetNewline(void)
 +{
 +
 +    return commandShell->newline;
 +}
 +
 +/*-
   *-----------------------------------------------------------------------
   * Job_Init --
   *	Initialize the process module
 @@ -2364,6 +2375,7 @@ JobMatchShell(const char *name)
   *	    echoFlag	    Flag to turn echoing on at the start
   *	    errFlag	    Flag to turn error checking on at the start
   *	    hasErrCtl	    True if shell has error checking control
 + *	    newline	    String literal to represent a newline char
   *	    check 	    Command to turn on error checking if hasErrCtl
   *	    	  	    is TRUE or template of command to echo a command
   *	    	  	    for which error checking is off if hasErrCtl is
 @@ -2422,6 +2434,8 @@ Job_ParseShell(char *line)
  		    char c = argv[0][10];
  		    newShell.hasErrCtl = !((c != 'Y') && (c != 'y') &&
  					   (c != 'T') && (c != 't'));
 +		} else if (strncmp(*argv, "newline=", 8) == 0) {
 +		    newShell.newline = &argv[0][8];
  		} else if (strncmp(*argv, "check=", 6) == 0) {
  		    newShell.errCheck = &argv[0][6];
  		} else if (strncmp(*argv, "ignore=", 7) == 0) {
 Index: job.h
 ===================================================================
 RCS file: /cvsroot/src/usr.bin/make/job.h,v
 retrieving revision 1.27
 diff -u -p -r1.27 job.h
 --- job.h	31 Mar 2006 21:05:34 -0000	1.27
 +++ job.h	29 Jun 2006 18:30:29 -0000
 @@ -250,6 +250,9 @@ typedef struct Shell {
      const char	 *errCheck;	/* string to turn error checking on */
      const char	 *ignErr;	/* string to turn off error checking */
      const char	 *errOut;	/* string to use for testing exit code */
 +    const char	 *newline;	/* string literal that results in a newline
 +				 * character when it appears outside of any
 +				 * 'quote' or "quote" characters */
      char   commentChar;		/* character used by shell for comment lines */
  
      /*
 @@ -268,6 +271,7 @@ extern int	maxJobs;	/* Max jobs we can r
  extern int	maxJobTokens;	/* Number of token for the job pipe */
  
  void Shell_Init(void);
 +const char *Shell_GetNewline(void);
  void Job_Touch(GNode *, Boolean);
  Boolean Job_CheckCommands(GNode *, void (*abortProc )(const char *, ...));
  void Job_CatchChildren(Boolean);
 Index: main.c
 ===================================================================
 RCS file: /cvsroot/src/usr.bin/make/main.c,v
 retrieving revision 1.126
 diff -u -p -r1.126 main.c
 --- main.c	19 May 2006 17:21:46 -0000	1.126
 +++ main.c	29 Jun 2006 18:30:30 -0000
 @@ -1220,7 +1220,7 @@ Check_Cwd_av(int ac, char **av, int copy
  
  	n = strlen(av[i]);
  	cp = &(av[i])[n - 1];
 -	if (strspn(av[i], "|&;") == n) {
 +	if (strspn(av[i], "|&;") == (size_t)n) {
  	    next_cmd = 1;
  	    continue;
  	} else if (*cp == ';' || *cp == '&' || *cp == '|' || *cp == ')') {
 Index: make.1
 ===================================================================
 RCS file: /cvsroot/src/usr.bin/make/make.1,v
 retrieving revision 1.126
 diff -u -p -r1.126 make.1
 --- make.1	17 Jun 2006 02:15:22 -0000	1.126
 +++ make.1	29 Jun 2006 18:30:30 -0000
 @@ -29,7 +29,7 @@
  .\"
  .\"	from: @(#)make.1	8.4 (Berkeley) 3/19/94
  .\"
 -.Dd March 18, 2006
 +.Dd June 29, 2006
  .Dt MAKE 1
  .Os
  .Sh NAME
 @@ -1574,13 +1574,16 @@ It is typically identical to
  The flag to pass the shell to enable error checking.
  .It Ar echoFlag
  The flag to pass the shell to enable command echoing.
 +.It Ar newline
 +The string literal to pass the shell that results in a single newline
 +character when used outside of any quoting characters.
  .El
  Example:
  .Bd -literal
  \&.SHELL: name=ksh path=/bin/ksh hasErrCtl=true \\
  	check="set -e" ignore="set +e" \\
  	echo="set -v" quiet="set +v" filter="set +v" \\
 -	echoFlag=v errFlag=e
 +	echoFlag=v errFlag=e newline="'\\n'"
  .Ed
  .It Ic .SILENT
  Apply the
 Index: suff.c
 ===================================================================
 RCS file: /cvsroot/src/usr.bin/make/suff.c,v
 retrieving revision 1.53
 diff -u -p -r1.53 suff.c
 --- suff.c	31 Mar 2006 21:58:08 -0000	1.53
 +++ suff.c	29 Jun 2006 18:30:31 -0000
 @@ -908,7 +908,7 @@ SuffScanTargets(ClientData targetp, Clie
  	return 1;
      }
  
 -    if (target->type == OP_TRANSFORM)
 +    if ((unsigned int)target->type == OP_TRANSFORM)
  	return 0;
  
      if ((ptr = strstr(target->name, gs->s->name)) == NULL ||
 Index: var.c
 ===================================================================
 RCS file: /cvsroot/src/usr.bin/make/var.c,v
 retrieving revision 1.110
 diff -u -p -r1.110 var.c
 --- var.c	19 May 2006 17:29:01 -0000	1.110
 +++ var.c	29 Jun 2006 18:30:32 -0000
 @@ -134,6 +134,7 @@ __RCSID("$NetBSD: var.c,v 1.110 2006/05/
  #include    "make.h"
  #include    "buf.h"
  #include    "dir.h"
 +#include    "job.h"
  
  /*
   * This is a harmless return value for Var_Parse that can be used by Var_Subst
 @@ -1835,12 +1836,19 @@ VarQuote(char *str)
      Buffer  	  buf;
      /* This should cover most shells :-( */
      static char meta[] = "\n \t'`\";&<>()|*?{}[]\\$!#^~";
 +    const char	*newline;
 +
 +    newline = Shell_GetNewline();
  
      buf = Buf_Init(MAKE_BSIZE);
      for (; *str; str++) {
 -	if (strchr(meta, *str) != NULL)
 -	    Buf_AddByte(buf, (Byte)'\\');
 -	Buf_AddByte(buf, (Byte)*str);
 +	if (*str == '\n' && newline != NULL) {
 +	    Buf_AddBytes(buf, strlen(newline), newline);
 +	} else {
 +	    if (strchr(meta, *str) != NULL)
 +		Buf_AddByte(buf, (Byte)'\\');
 +	    Buf_AddByte(buf, (Byte)*str);
 +	}
      }
      Buf_AddByte(buf, (Byte)'\0');
      str = (char *)Buf_GetAll(buf, NULL);
 @@ -2016,7 +2024,7 @@ ApplyModifiers(char *nstr, const char *t
  				      v, ctxt, err, &used, freePtr);
  		if (nstr == var_Error
  		    || (nstr == varNoError && err == 0)
 -		    || strlen(rval) != used) {
 +		    || strlen(rval) != (size_t) used) {
  		    if (freeIt)
  			free(freeIt);
  		    goto out;		/* error already reported */
 
 --------------020504090203020904010807
 Content-Type: text/plain;
  name="newline.mk"
 Content-Transfer-Encoding: 7bit
 Content-Disposition: inline;
  filename="newline.mk"
 
 TESTSTR=	a${.newline}b
 
 all:
 	echo ${TESTSTR:Q}""
 
 #.SHELL: name=sh
 #.SHELL: name=ksh
 #.SHELL: name=csh
 #.SHELL: path=/bin/sh newline="'\n'"
 
 --------------020504090203020904010807--