feat(stdio): getline/getdelim/stream locking
This commit is contained in:
@@ -0,0 +1,611 @@
|
||||
/*
|
||||
* vlibc — getline/getdelim, stream locking, memory streams test (todo 18).
|
||||
*
|
||||
* Exercises the todo-18 additions end to end:
|
||||
*
|
||||
* 1. getline on a 1 MiB line returns 1000001 (delimiter included) with a
|
||||
* NUL terminator, growing a NULL/zero-capacity buffer from scratch;
|
||||
* the next call at end of file returns -1. Reuse keeps the grown
|
||||
* buffer.
|
||||
* 2. getdelim with ',' over "a,b,c\n" yields "a,", "b,", then the
|
||||
* partial final field "c\n" at end of file, then -1.
|
||||
* 3. Embedded NUL bytes survive: getline on "ab\0cd\n" returns 6 with
|
||||
* the NUL at line[2] intact (length, not strlen).
|
||||
* 4. A small preallocated buffer (malloc(4)) grows to fit a long line.
|
||||
* 5. flockfile recursion: two nested flockfile calls, ftrylockfile
|
||||
* failing (nonzero) under the recursion and succeeding (0) when the
|
||||
* stream is free again.
|
||||
* 6. getc_unlocked/putc_unlocked under a held flockfile move real data;
|
||||
* getchar_unlocked/putchar_unlocked and puts work over freopen'd
|
||||
* stdin/stdout.
|
||||
* 7. fmemopen over a fixed buffer: write, rewind, read back
|
||||
* byte-identical; seeking past the size reads EOF (no error);
|
||||
* fmemopen(NULL, ...) grows and is freed by fclose (leak-checked);
|
||||
* "a" starts at the current data length.
|
||||
* 8. open_memstream: after fflush/fclose *sizeloc is the length and the
|
||||
* buffer is NUL-terminated; *ptr tracks the (possibly moved) buffer.
|
||||
* 9. fclose over many fmemopen/open_memstream cycles leaks nothing.
|
||||
*
|
||||
* Failure mode (-f): getline on a stream whose underlying descriptor was
|
||||
* closed underneath it returns -1 (the read hits EBADF); the return value
|
||||
* is asserted, never errno. -f exits via a raw SYS_exit_group before any
|
||||
* host-libc cleanup runs (the tests/syscall_test.c discipline).
|
||||
*
|
||||
* errno is never READ here; the default mode keeps every library errno
|
||||
* write out of the exercised paths. All diagnostics go through raw
|
||||
* SYS_write and the only headers are vlibc's own.
|
||||
*
|
||||
* Not part of the library proper; compiled manually for this todo.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../src/internal/syscall.h"
|
||||
|
||||
/* The allocator's heap-walk consistency probe (hidden; linked in via
|
||||
* malloc.c). Returns the live block count, or (size_t)-1 on disagreement. */
|
||||
extern size_t
|
||||
__vlibc_malloc_check(void); // NOLINT(bugprone-reserved-identifier)
|
||||
|
||||
static int failures;
|
||||
|
||||
/* Write a NUL-terminated string to fd via the raw syscall layer. */
|
||||
static void
|
||||
say(int fd, const char *s)
|
||||
{
|
||||
long n = 0;
|
||||
|
||||
while (s[n] != '\0')
|
||||
{
|
||||
n++;
|
||||
}
|
||||
__syscall3(SYS_write, fd, (long)s, n);
|
||||
}
|
||||
|
||||
/* Write v in decimal to fd. */
|
||||
static void
|
||||
say_dec(int fd, unsigned long v) // NOLINT(bugprone-easily-swappable-parameters)
|
||||
{
|
||||
char buf[24];
|
||||
int i = (int)sizeof(buf);
|
||||
|
||||
buf[--i] = '\0';
|
||||
do
|
||||
{
|
||||
buf[--i] = (char)('0' + (v % 10));
|
||||
v /= 10;
|
||||
} while (v != 0);
|
||||
__syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i));
|
||||
}
|
||||
|
||||
static void
|
||||
check(int cond, const char *what)
|
||||
{
|
||||
if (cond)
|
||||
{
|
||||
say(1, "PASS: ");
|
||||
say(1, what);
|
||||
say(1, "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
say(2, "FAIL: ");
|
||||
say(2, what);
|
||||
say(2, "\n");
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Byte-compare two buffers. noipa keeps -O2 from folding the check. */
|
||||
// NOLINTBEGIN(clang-analyzer-unix.Stream)
|
||||
static __attribute__((noipa)) int
|
||||
mem_eq(const unsigned char *a, const unsigned char *b, unsigned long n)
|
||||
{
|
||||
unsigned long i;
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
if (a[i] != b[i])
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Byte-compare two NUL-terminated strings. */
|
||||
static __attribute__((noipa)) int
|
||||
str_eq(const char *a, const char *b)
|
||||
{
|
||||
while (*a == *b && *a != '\0')
|
||||
{
|
||||
a++;
|
||||
b++;
|
||||
}
|
||||
return *a == *b;
|
||||
}
|
||||
|
||||
/* Read a whole small file through raw syscalls into buf; returns size. */
|
||||
static long
|
||||
raw_read_all(const char *path, char *buf, unsigned long cap)
|
||||
{
|
||||
long fd = __syscall4(SYS_openat, -100, (long)path, 0x0, 0);
|
||||
long got = -1;
|
||||
long total = 0;
|
||||
|
||||
if (fd < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
while (total < (long)cap)
|
||||
{
|
||||
got = __syscall3(SYS_read, fd, (long)(buf + total), (long)(cap - (unsigned long)total));
|
||||
if (got <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
total += got;
|
||||
}
|
||||
__syscall1(SYS_close, fd);
|
||||
return got < 0 ? -1 : total;
|
||||
}
|
||||
|
||||
/* Write path bytes to a fresh file, then reopen it for reading. */
|
||||
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
|
||||
static FILE *
|
||||
write_file(const char *path, const char *data, unsigned long n)
|
||||
{
|
||||
FILE *f = fopen(path, "w");
|
||||
|
||||
if (f == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
if (fwrite(data, 1, n, f) != n)
|
||||
{
|
||||
(void)fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
if (fclose(f) != 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
f = fopen(path, "r");
|
||||
if (f == NULL)
|
||||
{
|
||||
say(2, "FAIL: write_file reopen\n");
|
||||
failures++;
|
||||
}
|
||||
return f;
|
||||
}
|
||||
// NOLINTEND(bugprone-easily-swappable-parameters)
|
||||
|
||||
static char onemeg[1000001];
|
||||
|
||||
/* 1/3/4: getline length, growth, EOF, embedded NUL. */
|
||||
static void
|
||||
line_scenario(void)
|
||||
{
|
||||
const char bigpath[] = "/tmp/vlibc-test-t18-bigline.txt";
|
||||
const char nulpath[] = "/tmp/vlibc-test-t18-nul.txt";
|
||||
const char longpath[] = "/tmp/vlibc-test-t18-long.txt";
|
||||
const char longline[] = "this line is way longer than sixteen bytes\n";
|
||||
FILE *f;
|
||||
char *line;
|
||||
size_t n;
|
||||
ssize_t got;
|
||||
unsigned long i;
|
||||
|
||||
for (i = 0; i < 1000000; i++)
|
||||
{
|
||||
onemeg[i] = 'x';
|
||||
}
|
||||
onemeg[1000000] = '\n';
|
||||
f = fopen(bigpath, "w");
|
||||
check(f != NULL, "bigline fopen w succeeds");
|
||||
if (f == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
check(fwrite(onemeg, 1, sizeof(onemeg), f) == sizeof(onemeg), "bigline fwrite 1 MiB + newline");
|
||||
check(fclose(f) == 0, "bigline fclose writer");
|
||||
|
||||
f = fopen(bigpath, "r");
|
||||
check(f != NULL, "bigline fopen r succeeds");
|
||||
line = NULL;
|
||||
n = 0;
|
||||
got = getline(&line, &n, f);
|
||||
check(got == 1000001, "getline on the 1 MiB line returns 1000001");
|
||||
check(line != NULL && n >= 1000002, "getline grew the buffer past 1 MiB + NUL");
|
||||
if (line != NULL)
|
||||
{
|
||||
int allx = 1;
|
||||
|
||||
for (i = 0; i < 1000000; i++)
|
||||
{
|
||||
if (line[i] != 'x')
|
||||
{
|
||||
allx = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
check(allx != 0, "the 1 MiB line content is intact");
|
||||
check(line[1000000] == '\n', "the newline is kept at line[1000000]");
|
||||
check(line[1000001] == '\0', "the line is NUL-terminated");
|
||||
}
|
||||
got = getline(&line, &n, f);
|
||||
check(got == -1, "getline at end of file returns -1");
|
||||
free(line);
|
||||
check(fclose(f) == 0, "bigline fclose reader");
|
||||
check(remove(bigpath) == 0, "bigline remove");
|
||||
|
||||
/* Embedded NUL bytes are data, not terminators. */
|
||||
f = write_file(nulpath, "ab\0cd\n", 6);
|
||||
if (f != NULL)
|
||||
{
|
||||
line = NULL;
|
||||
n = 0;
|
||||
got = getline(&line, &n, f);
|
||||
check(got == 6, "getline over \"ab\\0cd\\n\" returns 6 bytes");
|
||||
check(line != NULL && line[0] == 'a' && line[1] == 'b' && line[2] == '\0' &&
|
||||
line[3] == 'c' && line[4] == 'd' && line[5] == '\n' && line[6] == '\0',
|
||||
"embedded NUL survives and the line is still NUL-terminated");
|
||||
free(line);
|
||||
check(fclose(f) == 0, "nul fclose");
|
||||
check(remove(nulpath) == 0, "nul remove");
|
||||
}
|
||||
|
||||
/* A small preallocated buffer grows past its initial capacity. */
|
||||
f = write_file(longpath, longline, sizeof(longline) - 1);
|
||||
if (f != NULL)
|
||||
{
|
||||
line = malloc(4);
|
||||
check(line != NULL, "small-buffer malloc");
|
||||
n = 4;
|
||||
got = getline(&line, &n, f);
|
||||
check(got == (ssize_t)(sizeof(longline) - 1),
|
||||
"getline with a 4-byte buffer reads the whole long line");
|
||||
check(n > sizeof(longline) - 1, "the capacity grew past the line length");
|
||||
check(line != NULL && str_eq(line, longline), "grown small buffer holds the line");
|
||||
free(line);
|
||||
check(fclose(f) == 0, "long fclose");
|
||||
check(remove(longpath) == 0, "long remove");
|
||||
}
|
||||
}
|
||||
|
||||
/* 2: getdelim with a custom delimiter. */
|
||||
static void
|
||||
delim_scenario(void)
|
||||
{
|
||||
const char path[] = "/tmp/vlibc-test-t18-delim.txt";
|
||||
FILE *f;
|
||||
char *line;
|
||||
size_t n;
|
||||
ssize_t got;
|
||||
|
||||
f = write_file(path, "a,b,c\n", 6);
|
||||
check(f != NULL, "delim file setup");
|
||||
if (f == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
line = NULL;
|
||||
n = 0;
|
||||
got = getdelim(&line, &n, ',', f);
|
||||
check(got == 2 && line != NULL && str_eq(line, "a,"), "getdelim first field is \"a,\"");
|
||||
got = getdelim(&line, &n, ',', f);
|
||||
check(got == 2 && line != NULL && str_eq(line, "b,"), "getdelim second field is \"b,\"");
|
||||
got = getdelim(&line, &n, ',', f);
|
||||
check(got == 2 && line != NULL && str_eq(line, "c\n"),
|
||||
"getdelim returns the partial final field at end of file");
|
||||
got = getdelim(&line, &n, ',', f);
|
||||
check(got == -1, "getdelim at end of file returns -1");
|
||||
free(line);
|
||||
check(fclose(f) == 0, "delim fclose");
|
||||
check(remove(path) == 0, "delim remove");
|
||||
}
|
||||
|
||||
/* 5/6: stream locking and the four _unlocked forms. */
|
||||
static void
|
||||
lock_scenario(void)
|
||||
{
|
||||
const char rpath[] = "/tmp/vlibc-test-t18-lockr.txt";
|
||||
const char wpath[] = "/tmp/vlibc-test-t18-lockw.txt";
|
||||
char got[8];
|
||||
FILE *f;
|
||||
int r0;
|
||||
int r1;
|
||||
|
||||
f = fopen(rpath, "w+");
|
||||
check(f != NULL, "lock fopen w+ succeeds");
|
||||
if (f == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
r0 = ftrylockfile(f);
|
||||
check(r0 == 0, "ftrylockfile on a free stream returns 0");
|
||||
flockfile(f); /* recursion depth 2 */
|
||||
flockfile(f); /* recursion depth 3 */
|
||||
r1 = ftrylockfile(f);
|
||||
check(r1 != 0, "ftrylockfile under the recursion returns nonzero");
|
||||
funlockfile(f);
|
||||
funlockfile(f);
|
||||
funlockfile(f);
|
||||
r0 = ftrylockfile(f);
|
||||
check(r0 == 0, "ftrylockfile succeeds again after all unlocks");
|
||||
funlockfile(f);
|
||||
check(fclose(f) == 0, "lock fclose");
|
||||
|
||||
f = write_file(rpath, "MNO", 3);
|
||||
check(f != NULL, "unlocked-read setup");
|
||||
if (f != NULL)
|
||||
{
|
||||
flockfile(f);
|
||||
check(getc_unlocked(f) == 'M', "getc_unlocked reads 'M' under the lock");
|
||||
check(getc_unlocked(f) == 'N', "getc_unlocked reads 'N' under the lock");
|
||||
funlockfile(f);
|
||||
check(fgetc(f) == 'O', "the locked fgetc still reads 'O' after unlock");
|
||||
check(fclose(f) == 0, "unlocked-read fclose");
|
||||
check(remove(rpath) == 0, "unlocked-read remove");
|
||||
}
|
||||
|
||||
f = fopen(wpath, "w");
|
||||
check(f != NULL, "unlocked-write setup");
|
||||
if (f != NULL)
|
||||
{
|
||||
flockfile(f);
|
||||
check(putc_unlocked('Q', f) == 'Q', "putc_unlocked writes 'Q' under the lock");
|
||||
check(putc_unlocked('R', f) == 'R', "putc_unlocked writes 'R' under the lock");
|
||||
funlockfile(f);
|
||||
check(fclose(f) == 0, "unlocked-write fclose");
|
||||
check(raw_read_all(wpath, got, sizeof(got)) == 2 && got[0] == 'Q' && got[1] == 'R',
|
||||
"putc_unlocked data reached the file");
|
||||
check(remove(wpath) == 0, "unlocked-write remove");
|
||||
}
|
||||
}
|
||||
|
||||
/* 7: fmemopen over a fixed buffer, past-size EOF, NULL-buffer growth, 'a'. */
|
||||
static void
|
||||
fmemopen_scenario(void)
|
||||
{
|
||||
char buf[64];
|
||||
char out[64];
|
||||
FILE *f;
|
||||
size_t r;
|
||||
int c;
|
||||
|
||||
f = fmemopen(buf, sizeof(buf), "w+");
|
||||
check(f != NULL, "fmemopen w+ succeeds");
|
||||
if (f == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
check(fputs("hello fmemopen", f) >= 0, "fputs into the fixed buffer");
|
||||
check(ftello(f) == 14, "ftello == 14 after the write");
|
||||
check(fseek(f, 0, SEEK_SET) == 0, "rewind via fseek(0, SEEK_SET)");
|
||||
r = fread(out, 1, sizeof(out), f);
|
||||
check(r == 14 &&
|
||||
mem_eq((const unsigned char *)out, (const unsigned char *)"hello fmemopen", 14),
|
||||
"fmemopen write/rewind/read is byte-identical");
|
||||
c = fgetc(f);
|
||||
check(c == EOF && feof(f), "fmemopen read past the data returns EOF");
|
||||
check(fclose(f) == 0, "fmemopen w+ fclose");
|
||||
|
||||
/* Seek beyond the buffer size, then read: EOF, not an error. */
|
||||
f = fmemopen(buf, sizeof(buf), "w+");
|
||||
if (f != NULL)
|
||||
{
|
||||
check(fputs("xyz", f) >= 0, "fmemopen setup writes xyz");
|
||||
check(fseek(f, 100, SEEK_SET) == 0, "fseek past the size succeeds");
|
||||
c = fgetc(f);
|
||||
check(c == EOF && feof(f) && ferror(f) == 0, "reading past the size is EOF with no error");
|
||||
check(ftello(f) == 100, "ftello stays at the seek target");
|
||||
check(fclose(f) == 0, "fmemopen past-size fclose");
|
||||
}
|
||||
|
||||
/* 'a' positions and appends at the current data length. */
|
||||
{
|
||||
char abuf[64] = "hello";
|
||||
FILE *g = fmemopen(abuf, sizeof(abuf), "a");
|
||||
|
||||
check(g != NULL, "fmemopen a succeeds");
|
||||
if (g != NULL)
|
||||
{
|
||||
check(ftello(g) == 5, "fmemopen a starts at the current length");
|
||||
check(fputs("XY", g) >= 0, "fmemopen a appends");
|
||||
check(fclose(g) == 0, "fmemopen a fclose flushes");
|
||||
check(mem_eq((const unsigned char *)abuf, (const unsigned char *)"helloXY", 7),
|
||||
"the appended data landed in the caller buffer");
|
||||
check(abuf[7] == '\0', "string-mode fmemopen NUL-terminates at the length");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* open_memstream: publish on flush, grow, NUL-terminate, free on close. */
|
||||
static void
|
||||
memstream_scenario(void)
|
||||
{
|
||||
char *p = NULL;
|
||||
size_t z = 0;
|
||||
FILE *f;
|
||||
|
||||
f = open_memstream(&p, &z);
|
||||
check(f != NULL && p != NULL && z == 0, "open_memstream returns a stream and an empty buffer");
|
||||
if (f == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
check(fputs("hello", f) >= 0, "open_memstream write 1");
|
||||
check(fflush(f) == 0, "open_memstream fflush 1");
|
||||
check(z == 5, "*sizeloc is the length after fflush");
|
||||
check(p != NULL && p[5] == '\0' &&
|
||||
mem_eq((const unsigned char *)p, (const unsigned char *)"hello", 5),
|
||||
"the flushed buffer is NUL-terminated at the length");
|
||||
check(fputs(" world", f) >= 0, "open_memstream write 2");
|
||||
check(fflush(f) == 0, "open_memstream fflush 2");
|
||||
check(z == 11 && p != NULL && p[11] == '\0',
|
||||
"the grown length and NUL are published after the second fflush");
|
||||
check(fclose(f) == 0, "open_memstream fclose");
|
||||
check(z == 11 && p != NULL && p[11] == '\0', "fclose keeps the NUL-terminated result");
|
||||
check(mem_eq((const unsigned char *)p, (const unsigned char *)"hello world", 11),
|
||||
"the open_memstream content is correct");
|
||||
free(p);
|
||||
}
|
||||
|
||||
/* 9: no leaks across repeated memory-stream open/close cycles. */
|
||||
static void
|
||||
memstream_leak_scenario(void)
|
||||
{
|
||||
const char *data = "0123456789abcdef0123456789abcdef0123456789abcdef";
|
||||
size_t before;
|
||||
size_t after;
|
||||
int i;
|
||||
|
||||
before = __vlibc_malloc_check();
|
||||
check(before != (size_t)-1, "allocator heap walk is consistent before the cycles");
|
||||
if (before == (size_t)-1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (i = 0; i < 100; i++)
|
||||
{
|
||||
FILE *g;
|
||||
char *p = NULL;
|
||||
size_t z = 0;
|
||||
|
||||
g = fmemopen(NULL, 8, "w");
|
||||
if (g == NULL || fputs(data, g) == EOF || fclose(g) != 0)
|
||||
{
|
||||
say(2, "FAIL: fmemopen cycle failed\n");
|
||||
failures++;
|
||||
return;
|
||||
}
|
||||
g = open_memstream(&p, &z);
|
||||
if (g == NULL || fputs(data, g) == EOF || fclose(g) != 0)
|
||||
{
|
||||
say(2, "FAIL: open_memstream cycle failed\n");
|
||||
failures++;
|
||||
return;
|
||||
}
|
||||
free(p);
|
||||
}
|
||||
after = __vlibc_malloc_check();
|
||||
check(after == before,
|
||||
"fclose releases memory-stream FILEs and owned buffers (no leak over 100 cycles)");
|
||||
}
|
||||
|
||||
/* puts, putchar_unlocked, getchar_unlocked over redirected std streams. */
|
||||
static void
|
||||
stream_redirect_scenario(void)
|
||||
{
|
||||
const char outpath[] = "/tmp/vlibc-test-t18-stdout.txt";
|
||||
const char inpath[] = "/tmp/vlibc-test-t18-stdin.txt";
|
||||
char buf[32];
|
||||
long saved_out;
|
||||
long saved_in;
|
||||
long got;
|
||||
int r1;
|
||||
int r2;
|
||||
int r3;
|
||||
int r4;
|
||||
int c;
|
||||
|
||||
/* stdout: no check() output on fd 1 while it names the file. */
|
||||
saved_out = __syscall1(SYS_dup, 1);
|
||||
check(saved_out >= 0, "dup(1) saves the stdout descriptor");
|
||||
r1 = (freopen(outpath, "w", stdout) == stdout);
|
||||
r2 = (puts("AA") >= 0);
|
||||
r3 = (putchar_unlocked('B') == 'B');
|
||||
r4 = (fflush(stdout) == 0);
|
||||
__syscall2(SYS_dup2, saved_out, 1);
|
||||
__syscall1(SYS_close, saved_out);
|
||||
check(r1, "freopen stdout to a file");
|
||||
check(r2, "puts writes through stdout");
|
||||
check(r3, "putchar_unlocked writes through stdout");
|
||||
check(r4, "fflush(stdout) after the freopen");
|
||||
got = raw_read_all(outpath, buf, sizeof(buf));
|
||||
check(got == 4 && buf[0] == 'A' && buf[1] == 'A' && buf[2] == '\n' && buf[3] == 'B',
|
||||
"puts + putchar_unlocked content reached the file as \"AA\\nB\"");
|
||||
check(remove(outpath) == 0, "remove the stdout file");
|
||||
|
||||
/* stdin: seed a file, rebind stdin, read a char, restore. */
|
||||
{
|
||||
FILE *seed = write_file(inpath, "K", 1);
|
||||
|
||||
check(seed != NULL, "stdin seed file written");
|
||||
if (seed != NULL)
|
||||
{
|
||||
(void)fclose(seed);
|
||||
}
|
||||
saved_in = __syscall1(SYS_dup, 0);
|
||||
check(saved_in >= 0, "dup(0) saves the stdin descriptor");
|
||||
r1 = (freopen(inpath, "r", stdin) == stdin);
|
||||
c = getchar_unlocked();
|
||||
r2 = (c == 'K');
|
||||
__syscall2(SYS_dup2, saved_in, 0);
|
||||
__syscall1(SYS_close, saved_in);
|
||||
check(r1, "freopen stdin from the seed file");
|
||||
check(r2, "getchar_unlocked reads the seed character");
|
||||
check(remove(inpath) == 0, "remove the stdin file");
|
||||
}
|
||||
}
|
||||
|
||||
/* Failure scenarios (-f). */
|
||||
static int
|
||||
failure_scenarios(void)
|
||||
{
|
||||
const char path[] = "/tmp/vlibc-test-t18-fail.txt";
|
||||
FILE *f;
|
||||
char *line = NULL;
|
||||
size_t n = 0;
|
||||
ssize_t got;
|
||||
|
||||
f = fopen(path, "w+");
|
||||
if (f == NULL)
|
||||
{
|
||||
say(2, "FAIL: -f setup fopen failed\n");
|
||||
failures++;
|
||||
return 1;
|
||||
}
|
||||
check(fputs("data-data-data", f) >= 0, "-f setup fputs");
|
||||
check(fflush(f) == 0, "-f setup fflush");
|
||||
(void)__syscall1(SYS_close, fileno(f)); /* close the fd underneath */
|
||||
got = getline(&line, &n, f);
|
||||
check(got == -1, "getline on a closed-underneath stream returns -1");
|
||||
free(line);
|
||||
(void)fclose(f);
|
||||
check(remove(path) == 0, "-f remove");
|
||||
return failures > 0 ? 1 : 0;
|
||||
}
|
||||
// NOLINTEND(clang-analyzer-unix.Stream)
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
|
||||
{
|
||||
int rc = failure_scenarios();
|
||||
|
||||
__syscall1(SYS_exit_group, rc);
|
||||
return rc; /* not reached */
|
||||
}
|
||||
|
||||
line_scenario();
|
||||
delim_scenario();
|
||||
lock_scenario();
|
||||
fmemopen_scenario();
|
||||
memstream_scenario();
|
||||
memstream_leak_scenario();
|
||||
stream_redirect_scenario();
|
||||
|
||||
if (failures > 0)
|
||||
{
|
||||
say(2, "FAILED (");
|
||||
say_dec(2, (unsigned long)failures);
|
||||
say(2, " check(s))\n");
|
||||
return 1;
|
||||
}
|
||||
say(1, "all getline/locking/memstream tests passed\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user