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--