[SV 58497] Ensure $(file <) newline removal succeeds

Keep a count of bytes read rather than comparing pointers since the
variable_buffer might get reallocated.

Bug and patch by Ken Tossell <ken@tossell.net>
Regression tests by Dmitry Goncharov <dgoncharov@users.sf.net>
Tweaked by Paul Smith <psmith@gnu.org>

* src/function.c (func_file): Use bytes read rather than a pointer.
* tests/scripts/functions/file: Provide various tests for reading
empty files, files with/without newlines, and large files.
This commit is contained in:
Paul Smith 2021-03-15 03:28:11 -04:00
parent 510e5ce801
commit c5319e75f5
3 changed files with 83 additions and 7 deletions

View file

@ -2303,7 +2303,7 @@ func_file (char *o, char **argv, const char *funcname UNUSED)
}
else if (fn[0] == '<')
{
char *preo = o;
size_t n = 0;
FILE *fp;
++fn;
@ -2327,8 +2327,10 @@ func_file (char *o, char **argv, const char *funcname UNUSED)
char buf[1024];
size_t l = fread (buf, 1, sizeof (buf), fp);
if (l > 0)
o = variable_buffer_output (o, buf, l);
{
o = variable_buffer_output (o, buf, l);
n += l;
}
if (ferror (fp))
if (errno != EINTR)
OSS (fatal, reading_file, _("read: %s: %s"), fn, strerror (errno));
@ -2339,9 +2341,8 @@ func_file (char *o, char **argv, const char *funcname UNUSED)
OSS (fatal, reading_file, _("close: %s: %s"), fn, strerror (errno));
/* Remove trailing newline. */
if (o > preo && o[-1] == '\n')
if (--o > preo && o[-1] == '\r')
--o;
if (n && o[-1] == '\n')
o -= 1 + (n > 1 && o[-2] == '\r');
}
else
OS (fatal, *expanding_var, _("file: invalid file operation: %s"), fn);

View file

@ -118,6 +118,76 @@ x:;@echo '$(X1)'; echo '$(A)'; echo '$(B)'
unlink('file.out');
# Read an empty file.
touch("file.out");
run_make_test(q!# empty file
X1 := x$(file <file.out)y
x:;@echo '$(X1)'
!,
'', "xy\n");
unlink('file.out');
# Read a file whose full contents is a newline.
create_file('file.out', "\n");
run_make_test(q!# <nl>
X1 := x$(file <file.out)y
x:;@echo '$(X1)'
!,
'', "xy\n");
unlink('file.out');
# Read a file which does not end with a newline.
create_file('file.out', "hello");
# echo prints a trailig newline, because run_make_test appends a newline.
run_make_test(q!# hello
X1 := x$(file <file.out)y
x:;@echo $(X1)
!,
'', "xhelloy\n");
unlink('file.out');
# Read a file which ends with a newline.
create_file('file.out', "hello\n");
# echo prints a trailig newline, because run_make_test appends a newline.
run_make_test(q!# hello<nl>
X1 := x$(file <file.out)y
x:;@echo '$(X1)'
!,
'', "xhelloy\n");
unlink('file.out');
# Read a file which ends with multiple newlines.
create_file('file.out', "hello\n\n");
run_make_test(q!# hello<nl><nl>
X1 := x$(file <file.out)y
export X1
x:;@echo "$$X1"
!,
'', "xhello\ny\n");
unlink('file.out');
# Read a file whose contents exceed 200 bytes.
# 200 is the initial size of variable_buffer.
# File bigger than 200 bytes causes a realloc.
# The size of the file in this test not only exceeds 200 bytes, it exceeds 65k.
my $s = "hello world, hello world, hello world, hello world, hello world";
my $answer = $s x 2000;
create_file('file.out', $answer);
run_make_test(q!# <hugestring>
X1 := x$(file <file.out)y
x:;@echo '$(X1)'
!,
'', "x${answer}y\n");
unlink('file.out');
# Reading from non-existent file
run_make_test(q!
X1 := $(file <file.out)

View file

@ -8,7 +8,8 @@
# Each step consists of an operator and argument.
#
# It supports the following operators:
# out <word> : echo <word> to stdout
# out <word> : echo <word> to stdout with a newline
# raw <word> : echo <word> to stdout without adding anything
# file <word> : echo <word> to stdout AND create the file <word>
# dir <word> : echo <word> to stdout AND create the directory <word>
# rm <word> : echo <word> to stdout AND delete the file/directory <word>
@ -34,6 +35,10 @@ sub op {
print "$nm\n";
return 1;
}
if ($op eq 'raw') {
print "$nm";
return 1;
}
# Show the output before creating the file
if ($op eq 'file') {