feat(stdio): FILE buffering core and stream I/O
This commit is contained in:
+296
@@ -0,0 +1,296 @@
|
|||||||
|
#ifndef VLIBC_STDIO_H
|
||||||
|
#define VLIBC_STDIO_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — <stdio.h>.
|
||||||
|
*
|
||||||
|
* Buffered stream I/O (todo 15). The FILE object is opaque to consumers;
|
||||||
|
* the layout lives in src/stdio/stdio_impl.h. stdin/stdout/stderr are
|
||||||
|
* pre-wired streams over fds 0/1/2 and are initialized lazily on first
|
||||||
|
* use. On Linux there is no text/binary distinction, so the 'b' mode
|
||||||
|
* character is accepted and ignored.
|
||||||
|
*
|
||||||
|
* Buffering modes for setvbuf: _IOFBF (fully buffered), _IOLBF (line
|
||||||
|
* buffered: flush on newline), _IONBF (unbuffered). A stream opened on a
|
||||||
|
* terminal defaults to line buffering for stdout and full buffering for
|
||||||
|
* stdin; stderr is always unbuffered.
|
||||||
|
*
|
||||||
|
* getc/putc/getchar/putchar are declared as functions (address-taking and
|
||||||
|
* #undef work) and additionally defined as macros over fgetc/fputc; the
|
||||||
|
* macro arguments are evaluated exactly once.
|
||||||
|
*
|
||||||
|
* Level 2 (muslmimic) adds tmpnam/ctermid/setbuffer/setlinebuf and the
|
||||||
|
* fopen64 name alias (identical ABI on LP64).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <vlibc/features.h>
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The opaque stream object. The underlying struct tag is vlibc_FILE (see
|
||||||
|
* src/stdio/stdio_impl.h); consumers only ever use FILE *.
|
||||||
|
*/
|
||||||
|
typedef struct vlibc_FILE FILE;
|
||||||
|
|
||||||
|
/* The three pre-wired standard streams (fds 0, 1, 2). */
|
||||||
|
extern FILE *stdin;
|
||||||
|
extern FILE *stdout;
|
||||||
|
extern FILE *stderr;
|
||||||
|
|
||||||
|
/* End of file indicator for character functions. */
|
||||||
|
#define EOF (-1)
|
||||||
|
|
||||||
|
/* Minimum number of simultaneously open files. */
|
||||||
|
#define FOPEN_MAX 16
|
||||||
|
|
||||||
|
/* Default buffer size for setvbuf/setbuf. */
|
||||||
|
#define BUFSIZ 8192
|
||||||
|
|
||||||
|
/* Maximum length of a path argument for stdio functions. */
|
||||||
|
#define FILENAME_MAX 4096
|
||||||
|
|
||||||
|
/* Minimum number of distinct tmpnam-generated names. */
|
||||||
|
#define TMP_MAX 238328
|
||||||
|
|
||||||
|
/* Buffer sizes for tmpnam and ctermid results. */
|
||||||
|
#define L_tmpnam 20
|
||||||
|
#define L_ctermid 9
|
||||||
|
|
||||||
|
/* Seek positions for fseek/fseeko. */
|
||||||
|
#define SEEK_SET 0
|
||||||
|
#define SEEK_CUR 1
|
||||||
|
#define SEEK_END 2
|
||||||
|
|
||||||
|
/* setvbuf modes. */
|
||||||
|
#define _IOFBF 0 // NOLINT(bugprone-reserved-identifier)
|
||||||
|
#define _IOLBF 1 // NOLINT(bugprone-reserved-identifier)
|
||||||
|
#define _IONBF 2 // NOLINT(bugprone-reserved-identifier)
|
||||||
|
|
||||||
|
/* Opaque file position type for fgetpos/fsetpos. */
|
||||||
|
typedef off_t fpos_t;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Open the file at path with the given mode. The mode is 'r', 'w', or 'a',
|
||||||
|
* optionally followed by '+' (update: read and write), 'b' (ignored on
|
||||||
|
* Linux), and/or 'x' (exclusive create, C11). Returns NULL with errno set
|
||||||
|
* on failure.
|
||||||
|
*/
|
||||||
|
FILE *
|
||||||
|
fopen(const char *restrict path, const char *restrict mode);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wrap an existing descriptor in a stream. The requested mode must be
|
||||||
|
* compatible with the descriptor's access mode (basic check via fcntl).
|
||||||
|
* The descriptor is not duplicated; fclose closes it.
|
||||||
|
*/
|
||||||
|
FILE *
|
||||||
|
fdopen(int fd, const char *mode);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Rebind stream to path. The old descriptor is flushed and closed first.
|
||||||
|
* With path == NULL only the mode changes and the descriptor stays open.
|
||||||
|
* Returns NULL with errno set on failure.
|
||||||
|
*/
|
||||||
|
FILE *
|
||||||
|
freopen(const char *restrict path, const char *restrict mode, FILE *restrict stream);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Flush pending output, close the descriptor, and release the stream.
|
||||||
|
* Returns EOF if flushing or closing failed.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fclose(FILE *stream);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Flush pending output of stream. fflush(NULL) flushes all open streams
|
||||||
|
* with pending output. On a stream with no pending writes (read mode) the
|
||||||
|
* unread buffered data is discarded and the position rewound; this never
|
||||||
|
* corrupts the stream. Returns EOF on error.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fflush(FILE *stream);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the buffer of stream to buf. buf == NULL selects unbuffered I/O;
|
||||||
|
* otherwise the buffer is used with full buffering and BUFSIZ size. Must
|
||||||
|
* be called before the first operation on the stream.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
setbuf(FILE *restrict stream, char *restrict buf);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the buffering mode of stream: _IOFBF, _IOLBF, or _IONBF. With
|
||||||
|
* buf != NULL the caller supplies size bytes of storage; with buf == NULL
|
||||||
|
* the buffer is allocated lazily on first use (size is then ignored,
|
||||||
|
* except that _IONBF needs no buffer). Returns 0, or -1 with errno EINVAL
|
||||||
|
* for an invalid mode or a non-NULL buf with size 0 (except _IONBF).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
setvbuf(FILE *restrict stream, char *restrict buf, int mode, size_t size);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read up to size * nmemb bytes in items of size bytes each. Returns the
|
||||||
|
* number of complete items read; fewer than nmemb means end of file or an
|
||||||
|
* error (distinguishable via feof/ferror). size or nmemb zero returns 0
|
||||||
|
* without touching the stream.
|
||||||
|
*/
|
||||||
|
size_t
|
||||||
|
fread(void *restrict ptr, size_t size, size_t nmemb, FILE *restrict stream);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write size * nmemb bytes in items of size bytes each. Returns the number
|
||||||
|
* of complete items written; fewer than nmemb means an error (see
|
||||||
|
* ferror).
|
||||||
|
*/
|
||||||
|
size_t
|
||||||
|
fwrite(const void *restrict ptr, size_t size, size_t nmemb, FILE *restrict stream);
|
||||||
|
|
||||||
|
/* Read the next character as unsigned char, or EOF. */
|
||||||
|
int
|
||||||
|
fgetc(FILE *stream);
|
||||||
|
|
||||||
|
/* Write c as unsigned char; returns it, or EOF on error. */
|
||||||
|
int
|
||||||
|
fputc(int c, FILE *stream);
|
||||||
|
|
||||||
|
/* Same as fgetc/fputc; also provided as macros (arguments evaluated once). */
|
||||||
|
int
|
||||||
|
getc(FILE *stream);
|
||||||
|
|
||||||
|
int
|
||||||
|
putc(int c, FILE *stream);
|
||||||
|
|
||||||
|
int
|
||||||
|
getchar(void);
|
||||||
|
|
||||||
|
int
|
||||||
|
putchar(int c);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read at most n-1 characters into s, stopping after (and keeping) a
|
||||||
|
* newline, then NUL-terminate. Returns s, or NULL if no character was read
|
||||||
|
* (end of file or error).
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
fgets(char *restrict s, int n, FILE *restrict stream);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write the NUL-terminated string s to stream (no trailing newline is
|
||||||
|
* added). Returns a non-negative value, or EOF on error.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fputs(const char *restrict s, FILE *restrict stream);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Push c back onto the input stream; the next read returns it again. One
|
||||||
|
* byte of pushback is guaranteed. Returns c, or EOF on error (also for
|
||||||
|
* ungetc(EOF)). A successful seek discards the pushed-back character.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
ungetc(int c, FILE *stream);
|
||||||
|
|
||||||
|
/* Position the stream (see SEEK_SET/SEEK_CUR/SEEK_END); clears feof. */
|
||||||
|
int
|
||||||
|
fseek(FILE *stream, long offset, int whence);
|
||||||
|
|
||||||
|
int
|
||||||
|
fseeko(FILE *stream, off_t offset, int whence);
|
||||||
|
|
||||||
|
/* Current stream position, -1 with errno set on error. */
|
||||||
|
long
|
||||||
|
ftell(FILE *stream);
|
||||||
|
|
||||||
|
__attribute__((pure)) off_t
|
||||||
|
ftello(FILE *stream);
|
||||||
|
|
||||||
|
/* Rewind to the start and clear feof/ferror (equivalent to
|
||||||
|
* fseeko(stream, 0, SEEK_SET) + clearerr). */
|
||||||
|
void
|
||||||
|
rewind(FILE *stream);
|
||||||
|
|
||||||
|
/* Get/set the opaque position via fpos_t. */
|
||||||
|
int
|
||||||
|
fgetpos(FILE *restrict stream, fpos_t *restrict pos);
|
||||||
|
|
||||||
|
int
|
||||||
|
fsetpos(FILE *stream, const fpos_t *pos);
|
||||||
|
|
||||||
|
/* End-of-file and error indicators. */
|
||||||
|
__attribute__((pure)) int
|
||||||
|
feof(FILE *stream);
|
||||||
|
|
||||||
|
__attribute__((pure)) int
|
||||||
|
ferror(FILE *stream);
|
||||||
|
|
||||||
|
void
|
||||||
|
clearerr(FILE *stream);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove the file at path (a directory is removed like rmdir). Returns 0,
|
||||||
|
* or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
remove(const char *path);
|
||||||
|
|
||||||
|
/* Rename oldpath to newpath. Returns 0, or -1 with errno set. */
|
||||||
|
int
|
||||||
|
rename(const char *oldpath, const char *newpath);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create an anonymous temporary file ("w+b"): the file is created in /tmp
|
||||||
|
* and unlinked immediately, so it disappears on close. Returns the stream,
|
||||||
|
* or NULL with errno set.
|
||||||
|
*/
|
||||||
|
FILE *
|
||||||
|
tmpfile(void);
|
||||||
|
|
||||||
|
/* The descriptor underlying the stream. */
|
||||||
|
__attribute__((pure)) int
|
||||||
|
fileno(FILE *stream);
|
||||||
|
|
||||||
|
/* getc/putc/getchar/putchar as macros over fgetc/fputc (see above). */
|
||||||
|
#define getc(stream) fgetc(stream)
|
||||||
|
#define putc(c, stream) fputc((c), (stream))
|
||||||
|
#define getchar() fgetc(stdin)
|
||||||
|
#define putchar(c) fputc((c), stdout)
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Generate a name for a temporary file ("/tmp/vlibcXXXXXX" form; the file
|
||||||
|
* is NOT created). With s == NULL a static buffer is used. Not thread-safe,
|
||||||
|
* obsolescent.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
tmpnam(char *s);
|
||||||
|
|
||||||
|
/* Controlling terminal path: copies "/dev/tty" into s (or a static
|
||||||
|
* buffer when s == NULL) and returns it. */
|
||||||
|
char *
|
||||||
|
ctermid(char *s);
|
||||||
|
|
||||||
|
/* BSD: like setvbuf with _IOFBF/_IONBF and the given size. */
|
||||||
|
void
|
||||||
|
setbuffer(FILE *stream, char *buf, size_t size);
|
||||||
|
|
||||||
|
/* BSD: select line buffering (setvbuf with _IOLBF and NULL buffer). */
|
||||||
|
void
|
||||||
|
setlinebuf(FILE *stream);
|
||||||
|
|
||||||
|
/* glibc LFS name alias: identical to fopen on LP64. */
|
||||||
|
FILE *
|
||||||
|
fopen64(const char *restrict path, const char *restrict mode);
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* VLIBC_STDIO_H */
|
||||||
+1168
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,94 @@
|
|||||||
|
#ifndef VLIBC_STDIO_STDIO_IMPL_H
|
||||||
|
#define VLIBC_STDIO_STDIO_IMPL_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — internal FILE layout and stream helpers (todo 15).
|
||||||
|
*
|
||||||
|
* struct vlibc_FILE is the object behind the opaque FILE of <stdio.h>.
|
||||||
|
* Position invariant (authoritative):
|
||||||
|
*
|
||||||
|
* - Read mode: `pos` is the file offset of buf[0]. The logical position
|
||||||
|
* (offset of the byte at rpos) is pos + (rpos - buf), and the kernel
|
||||||
|
* position is pos + (rstop - buf); with an empty buffer
|
||||||
|
* (rpos == rstop) the kernel sits exactly at the logical position.
|
||||||
|
* - Write mode: `pos` is the file offset of buf[0]; the next written byte
|
||||||
|
* lands at pos + (wpos - buf). With an empty buffer the kernel sits at
|
||||||
|
* pos. A flush advances pos by the number of bytes written.
|
||||||
|
*
|
||||||
|
* Mode switching: reading while write data is pending flushes it first;
|
||||||
|
* writing while unread buffered data is pending seeks the kernel back to
|
||||||
|
* the logical position (discarding the unread data). F_PUSHED marks the
|
||||||
|
* single guaranteed ungetc pushback byte in `ungot`; a successful seek
|
||||||
|
* discards it.
|
||||||
|
*
|
||||||
|
* The helper prototypes below are shared with the formatted-I/O todos
|
||||||
|
* (16: vfprintf, 17: vfscanf) so they operate on the same buffer state.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "../internal/libc.h"
|
||||||
|
|
||||||
|
struct vlibc_FILE
|
||||||
|
{
|
||||||
|
unsigned char *buf; /* buffer base: library- or user-supplied */
|
||||||
|
unsigned char *rpos; /* read mode: next byte to deliver */
|
||||||
|
unsigned char *rstop; /* read mode: end of valid buffered data */
|
||||||
|
unsigned char *wpos; /* write mode: next byte to store */
|
||||||
|
unsigned char *wstop; /* write mode: end of the buffer */
|
||||||
|
int fd; /* underlying descriptor; -1 when closed */
|
||||||
|
int flags; /* F_* bits below */
|
||||||
|
size_t buf_size; /* usable buffer size; 0 = not yet sized */
|
||||||
|
off_t pos; /* position per the invariant above */
|
||||||
|
struct vlibc_FILE *next; /* registry link for fflush(NULL) */
|
||||||
|
unsigned char ungot; /* pushed-back byte when F_PUSHED */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Stream flags. */
|
||||||
|
#define F_READ 0x001 /* reads are enabled by the open mode */
|
||||||
|
#define F_WRITE 0x002 /* writes are enabled by the open mode */
|
||||||
|
#define F_EOF 0x004 /* end of file has been seen */
|
||||||
|
#define F_ERR 0x008 /* an I/O error occurred */
|
||||||
|
#define F_LINEBUF 0x010 /* line buffered: flush on newline */
|
||||||
|
#define F_UNBUF 0x020 /* unbuffered: 1-byte buffer */
|
||||||
|
#define F_APPEND 0x040 /* append mode: writes land at end of file */
|
||||||
|
#define F_OWNBUF 0x080 /* buf was allocated by the library */
|
||||||
|
#define F_HEAP 0x100 /* the FILE struct itself is heap-allocated */
|
||||||
|
#define F_PUSHED 0x200 /* ungot holds a pushed-back byte */
|
||||||
|
|
||||||
|
/* Flush pending write data; returns 0, or -1 with F_ERR set. */
|
||||||
|
hidden int
|
||||||
|
stdio_flush(FILE *f);
|
||||||
|
|
||||||
|
/* Refill the read buffer from the descriptor (read mode only). */
|
||||||
|
hidden void
|
||||||
|
stdio_refill(FILE *f);
|
||||||
|
|
||||||
|
/* Discard unread buffered data, seeking the kernel back to pos. */
|
||||||
|
hidden int
|
||||||
|
stdio_discard_read(FILE *f);
|
||||||
|
|
||||||
|
/* Allocate the buffer lazily (std streams, setvbuf with NULL). */
|
||||||
|
hidden void
|
||||||
|
stdio_init_if_needed(FILE *f);
|
||||||
|
|
||||||
|
/* Allocate and register a FILE over an open descriptor. */
|
||||||
|
hidden FILE *
|
||||||
|
stdio_alloc_file(int fd, int m);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Parse a fopen-style mode string. On success stores the F_READ/F_WRITE
|
||||||
|
* capability bits in *m, the openat flags in *oflags, and returns 1;
|
||||||
|
* returns 0 for an invalid mode.
|
||||||
|
*/
|
||||||
|
hidden int
|
||||||
|
stdio_parse_mode(const char *mode, int *m, int *oflags);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Raw lseek with errno translation. Separate from syscall_ret() because
|
||||||
|
* syscall_ret narrows results to int and would truncate large offsets.
|
||||||
|
*/
|
||||||
|
hidden off_t
|
||||||
|
stdio_lseek(int fd, off_t off, int whence);
|
||||||
|
|
||||||
|
#endif /* VLIBC_STDIO_STDIO_IMPL_H */
|
||||||
@@ -0,0 +1,718 @@
|
|||||||
|
/*
|
||||||
|
* vlibc — buffered stdio test (todo 15).
|
||||||
|
*
|
||||||
|
* Exercises the stdio FILE core end to end:
|
||||||
|
*
|
||||||
|
* 1. Round trip: fopen "w+b", fwrite a 1 MiB deterministic binary
|
||||||
|
* pattern, fflush, ftello == size, fseek(0, SEEK_SET), fread the
|
||||||
|
* pattern back, byte-identical. Then read past the end: fgetc ==
|
||||||
|
* EOF, feof set, ferror clear.
|
||||||
|
* 2. Text lines: fputs lines, fgets line boundaries (newline kept,
|
||||||
|
* NUL-terminated), the n-1 clamp, NULL + feof at end of file.
|
||||||
|
* 3. Seek: fseeko SEEK_SET/CUR/END positions, ftell/ftello after
|
||||||
|
* fgetc, fgetpos/fsetpos round trip, rewind.
|
||||||
|
* 4. ungetc: pushback of a read character yields the same character;
|
||||||
|
* ungetc(EOF) == EOF; a second pushback fails; fseek discards the
|
||||||
|
* pushed-back byte.
|
||||||
|
* 5. fflush mid-stream then continue; fflush(NULL) flushes an open
|
||||||
|
* file stream and a freopen'd stdout.
|
||||||
|
* 6. setvbuf modes: _IONBF (NULL buffer), _IOFBF (user buffer),
|
||||||
|
* _IOLBF flushes on newline without an explicit fflush; setbuf
|
||||||
|
* with NULL selects unbuffered.
|
||||||
|
* 7. fdopen over a raw descriptor; fileno returns it; fclose closes
|
||||||
|
* it (probed with raw fcntl).
|
||||||
|
* 8. freopen: re-open a different path on the same FILE; NULL path
|
||||||
|
* changes the mode only.
|
||||||
|
* 9. remove/rename lifecycle; tmpfile write+rewind+read.
|
||||||
|
* 10. Level 2: tmpnam/ctermid/setbuffer/setlinebuf/fopen64.
|
||||||
|
*
|
||||||
|
* Failure mode (-f): fseek past EOF then fread returns 0 with feof set
|
||||||
|
* and no crash; fopen of a nonexistent path returns NULL; fdopen(-1)
|
||||||
|
* returns NULL; fputc on a read-only stream returns EOF with ferror;
|
||||||
|
* remove of a nonexistent path returns -1. The -f mode makes the
|
||||||
|
* library write errno in several places and then leaves through a raw
|
||||||
|
* SYS_exit_group before any host-libc cleanup runs (the
|
||||||
|
* tests/syscall_test.c discipline).
|
||||||
|
*
|
||||||
|
* The default mode keeps the stream error paths free of library errno
|
||||||
|
* writes, except one setvbuf EINVAL case which is bracketed with a
|
||||||
|
* save/restore of the host TCB slot 1 (the tests/test_env.c pattern).
|
||||||
|
*
|
||||||
|
* errno is never READ here. All diagnostics go through raw SYS_write;
|
||||||
|
* the only header included is vlibc's own <stdio.h> (host headers would
|
||||||
|
* pull GCC internals shadowed by -Iinclude).
|
||||||
|
*
|
||||||
|
* Not part of the library proper; compiled manually for this todo (the
|
||||||
|
* tests/ + make check wiring is owned by a later todo).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "../src/internal/syscall.h"
|
||||||
|
|
||||||
|
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.
|
||||||
|
* From here through the scenarios the analyzer is waived: the stream
|
||||||
|
* checker cannot model vlibc's FILE semantics (deliberate reads at EOF,
|
||||||
|
* reads after a failed probe, buffers written by our own fread), and its
|
||||||
|
* raw-syscall modeling flags byte comparisons as reading garbage. */
|
||||||
|
// NOLINTBEGIN(clang-analyzer-unix.Stream, clang-analyzer-core.UndefinedBinaryOperatorResult,
|
||||||
|
// clang-analyzer-unix.StdCLibraryFunctions)
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (a == NULL || b == NULL)
|
||||||
|
{
|
||||||
|
return a == b;
|
||||||
|
}
|
||||||
|
while (*a == *b && *a != '\0')
|
||||||
|
{
|
||||||
|
a++;
|
||||||
|
b++;
|
||||||
|
}
|
||||||
|
return *a == *b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* True when s starts with the given prefix (used by the L2 scenario). */
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
static __attribute__((noipa)) int
|
||||||
|
prefix_eq(const char *s, const char *prefix)
|
||||||
|
{
|
||||||
|
while (*prefix != '\0')
|
||||||
|
{
|
||||||
|
if (*s != *prefix)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
s++;
|
||||||
|
prefix++;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Save/restore the host libc's TCB slot 1 (%fs:0+8), see the banner. */
|
||||||
|
static unsigned long
|
||||||
|
tcb_slot1_save(void)
|
||||||
|
{
|
||||||
|
return *(unsigned long *)((char *)__builtin_thread_pointer() + 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
tcb_slot1_restore(unsigned long v)
|
||||||
|
{
|
||||||
|
*(unsigned long *)((char *)__builtin_thread_pointer() + 8) = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Deterministic pattern generator (64-bit LCG, like the library's). */
|
||||||
|
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
|
||||||
|
static void
|
||||||
|
fill_pattern(unsigned char *p, unsigned long n, unsigned long long seed)
|
||||||
|
{
|
||||||
|
unsigned long i;
|
||||||
|
unsigned long long s = seed;
|
||||||
|
|
||||||
|
for (i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
s = (s * 6364136223846793005ULL) + 1ULL;
|
||||||
|
p[i] = (unsigned char)(s >> 33);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// NOLINTEND(bugprone-easily-swappable-parameters)
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned char pattern[1 << 20];
|
||||||
|
static unsigned char back[1 << 20];
|
||||||
|
|
||||||
|
/* 1. Binary round trip. */
|
||||||
|
static void
|
||||||
|
roundtrip_scenario(void)
|
||||||
|
{
|
||||||
|
const char path[] = "/tmp/vlibc-test-stdio-roundtrip.bin";
|
||||||
|
FILE *f;
|
||||||
|
unsigned long w;
|
||||||
|
unsigned long r;
|
||||||
|
|
||||||
|
f = fopen(path, "w+b");
|
||||||
|
check(f != NULL, "fopen w+b succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fill_pattern(pattern, sizeof(pattern), 42);
|
||||||
|
w = (unsigned long)fwrite(pattern, 1, sizeof(pattern), f);
|
||||||
|
check(w == sizeof(pattern), "fwrite writes the full 1 MiB");
|
||||||
|
check(fflush(f) == 0, "fflush after fwrite returns 0");
|
||||||
|
check(ftello(f) == (off_t)sizeof(pattern), "ftello after the write == size");
|
||||||
|
check(fseek(f, 0, SEEK_SET) == 0, "fseek(0, SEEK_SET) returns 0");
|
||||||
|
r = (unsigned long)fread(back, 1, sizeof(back), f);
|
||||||
|
check(r == sizeof(back), "fread reads the full 1 MiB");
|
||||||
|
check(mem_eq(pattern, back, sizeof(pattern)), "1 MiB round trip is byte-identical");
|
||||||
|
check(feof(f) == 0, "feof is clear after reading exactly to the end");
|
||||||
|
check(fgetc(f) == EOF, "fgetc past the end returns EOF");
|
||||||
|
check(feof(f) != 0, "feof is set after reading past the end");
|
||||||
|
check(ferror(f) == 0, "ferror stays clear at EOF");
|
||||||
|
clearerr(f);
|
||||||
|
check(feof(f) == 0, "clearerr clears feof");
|
||||||
|
check(fclose(f) == 0, "fclose returns 0");
|
||||||
|
check(remove(path) == 0, "remove deletes the round-trip file");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. Text lines. */
|
||||||
|
static void
|
||||||
|
text_scenario(void)
|
||||||
|
{
|
||||||
|
const char path[] = "/tmp/vlibc-test-stdio-text.txt";
|
||||||
|
FILE *f;
|
||||||
|
char line[128];
|
||||||
|
char small[4];
|
||||||
|
|
||||||
|
f = fopen(path, "w");
|
||||||
|
check(f != NULL, "text fopen w succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(fputs("alpha\n", f) >= 0, "fputs line 1 returns non-negative");
|
||||||
|
check(fputs("beta gamma\n", f) >= 0, "fputs line 2 returns non-negative");
|
||||||
|
check(fputs("delta-no-newline", f) >= 0, "fputs final line without newline");
|
||||||
|
check(fclose(f) == 0, "fclose text writer returns 0");
|
||||||
|
|
||||||
|
f = fopen(path, "r");
|
||||||
|
check(f != NULL, "text fopen r succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(fgets(line, (int)sizeof(line), f) == line, "fgets line 1 returns s");
|
||||||
|
check(str_eq(line, "alpha\n"), "fgets line 1 == \"alpha\\n\"");
|
||||||
|
check(fgets(line, (int)sizeof(line), f) == line, "fgets line 2 returns s");
|
||||||
|
check(str_eq(line, "beta gamma\n"), "fgets line 2 == \"beta gamma\\n\"");
|
||||||
|
check(fgets(line, (int)sizeof(line), f) == line, "fgets final line returns s");
|
||||||
|
check(str_eq(line, "delta-no-newline"), "fgets final line has no newline");
|
||||||
|
check(fgets(line, (int)sizeof(line), f) == NULL, "fgets at EOF returns NULL");
|
||||||
|
check(feof(f) != 0, "feof is set after the NULL fgets");
|
||||||
|
check(fclose(f) == 0, "fclose text reader returns 0");
|
||||||
|
|
||||||
|
f = fopen(path, "r");
|
||||||
|
check(fgets(small, (int)sizeof(small), f) == small, "fgets with n=4 returns s");
|
||||||
|
check(small[0] == 'a' && small[1] == 'l' && small[2] == 'p' && small[3] == '\0',
|
||||||
|
"fgets clamps to n-1 chars and NUL-terminates");
|
||||||
|
check(fclose(f) == 0, "fclose after the clamp check returns 0");
|
||||||
|
check(remove(path) == 0, "remove deletes the text file");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. Seek and position queries. */
|
||||||
|
static void
|
||||||
|
seek_scenario(void)
|
||||||
|
{
|
||||||
|
const char path[] = "/tmp/vlibc-test-stdio-seek.bin";
|
||||||
|
FILE *f;
|
||||||
|
fpos_t saved;
|
||||||
|
int c;
|
||||||
|
|
||||||
|
f = fopen(path, "w+");
|
||||||
|
check(f != NULL, "seek fopen w+ succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(fputs("0123456789", f) >= 0, "seek setup writes digits");
|
||||||
|
check(fseeko(f, 3, SEEK_SET) == 0, "fseeko(3, SEEK_SET) returns 0");
|
||||||
|
c = fgetc(f);
|
||||||
|
check(c == '3', "fgetc after SEEK_SET 3 == '3'");
|
||||||
|
check(ftello(f) == 4, "ftello after reading one char == 4");
|
||||||
|
check(fseek(f, 2, SEEK_CUR) == 0, "fseek(2, SEEK_CUR) returns 0");
|
||||||
|
c = fgetc(f);
|
||||||
|
check(c == '6', "fgetc after SEEK_CUR 2 == '6'");
|
||||||
|
check(ftell(f) == 7, "ftell after the SEEK_CUR read == 7");
|
||||||
|
check(fseek(f, -2, SEEK_END) == 0, "fseek(-2, SEEK_END) returns 0");
|
||||||
|
c = fgetc(f);
|
||||||
|
check(c == '8', "fgetc after SEEK_END -2 == '8'");
|
||||||
|
check(fgetpos(f, &saved) == 0, "fgetpos returns 0");
|
||||||
|
check((off_t)saved == 9, "fgetpos saved position == 9");
|
||||||
|
check(fseek(f, 0, SEEK_SET) == 0, "fseek(0, SEEK_SET) returns 0");
|
||||||
|
c = fgetc(f);
|
||||||
|
check(c == '0', "fgetc at the start == '0'");
|
||||||
|
check(fsetpos(f, &saved) == 0, "fsetpos returns 0");
|
||||||
|
c = fgetc(f);
|
||||||
|
check(c == '9', "fgetc after fsetpos == '9'");
|
||||||
|
rewind(f); // NOLINT: rewind has no error return; its effect is asserted below
|
||||||
|
check(ftello(f) == 0, "rewind positions at 0");
|
||||||
|
c = fgetc(f);
|
||||||
|
check(c == '0', "fgetc after rewind == '0'");
|
||||||
|
check(fclose(f) == 0, "fclose seek stream returns 0");
|
||||||
|
check(remove(path) == 0, "remove deletes the seek file");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. ungetc pushback. */
|
||||||
|
static void
|
||||||
|
ungetc_scenario(void)
|
||||||
|
{
|
||||||
|
const char path[] = "/tmp/vlibc-test-stdio-ungetc.txt";
|
||||||
|
FILE *f;
|
||||||
|
int c;
|
||||||
|
|
||||||
|
f = fopen(path, "w+");
|
||||||
|
check(f != NULL, "ungetc fopen w+ succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(fputs("hello", f) >= 0, "ungetc setup writes \"hello\"");
|
||||||
|
check(fseek(f, 0, SEEK_SET) == 0, "ungetc setup seeks back");
|
||||||
|
c = fgetc(f);
|
||||||
|
check(c == 'h', "fgetc reads 'h'");
|
||||||
|
check(ungetc(c, f) == 'h', "ungetc('h') returns 'h'");
|
||||||
|
c = fgetc(f);
|
||||||
|
check(c == 'h', "fgetc after ungetc reads 'h' again");
|
||||||
|
check(ungetc(EOF, f) == EOF, "ungetc(EOF) returns EOF");
|
||||||
|
check(ungetc('x', f) == 'x', "ungetc('x') returns 'x'");
|
||||||
|
check(ungetc('y', f) == EOF, "second pushback without a read fails");
|
||||||
|
c = fgetc(f);
|
||||||
|
check(c == 'x', "the first pushed-back byte is still readable");
|
||||||
|
check(ungetc('z', f) == 'z', "ungetc('z') returns 'z'");
|
||||||
|
check(fseek(f, 0, SEEK_SET) == 0, "fseek after ungetc returns 0");
|
||||||
|
c = fgetc(f);
|
||||||
|
check(c == 'h', "fseek discards the pushed-back byte");
|
||||||
|
check(fclose(f) == 0, "fclose ungetc stream returns 0");
|
||||||
|
check(remove(path) == 0, "remove deletes the ungetc file");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 5. fflush mid-stream and fflush(NULL). */
|
||||||
|
static void
|
||||||
|
flush_scenario(void)
|
||||||
|
{
|
||||||
|
const char path[] = "/tmp/vlibc-test-stdio-flush.txt";
|
||||||
|
const char outpath[] = "/tmp/vlibc-test-stdio-stdout.txt";
|
||||||
|
char buf[64];
|
||||||
|
FILE *f;
|
||||||
|
long got;
|
||||||
|
long saved_fd;
|
||||||
|
int r1;
|
||||||
|
int r2;
|
||||||
|
int r3;
|
||||||
|
int r4;
|
||||||
|
|
||||||
|
f = fopen(path, "w");
|
||||||
|
check(f != NULL, "flush fopen w succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(fwrite("abc", 1, 3, f) == 3, "fwrite the first half");
|
||||||
|
check(fflush(f) == 0, "fflush mid-stream returns 0");
|
||||||
|
check(fwrite("def", 1, 3, f) == 3, "fwrite the second half");
|
||||||
|
|
||||||
|
saved_fd = __syscall1(SYS_dup, 1);
|
||||||
|
check(saved_fd >= 0, "dup(1) saves the stdout descriptor");
|
||||||
|
/* No check() output while fd 1 is the outpath file: the PASS lines
|
||||||
|
* would pollute the very file content the checks assert below. */
|
||||||
|
r1 = (freopen(outpath, "w", stdout) == stdout);
|
||||||
|
r2 = (fputs("STDOUT-MARKER", stdout) >= 0);
|
||||||
|
r3 = (fflush(NULL) == 0);
|
||||||
|
r4 = (fclose(f) == 0);
|
||||||
|
__syscall2(SYS_dup2, saved_fd, 1);
|
||||||
|
__syscall1(SYS_close, saved_fd);
|
||||||
|
check(r1, "freopen stdout to a file");
|
||||||
|
check(r2, "fputs to the freopen'd stdout");
|
||||||
|
check(r3, "fflush(NULL) returns 0");
|
||||||
|
check(r4, "fclose after fflush(NULL) returns 0");
|
||||||
|
|
||||||
|
got = raw_read_all(path, buf, sizeof(buf));
|
||||||
|
check(got == 6 && mem_eq((const unsigned char *)buf, (const unsigned char *)"abcdef", 6),
|
||||||
|
"fflush(NULL) left the whole file content");
|
||||||
|
got = raw_read_all(outpath, buf, sizeof(buf));
|
||||||
|
check(got == 13 &&
|
||||||
|
mem_eq((const unsigned char *)buf, (const unsigned char *)"STDOUT-MARKER", 13),
|
||||||
|
"fflush(NULL) flushed the freopen'd stdout");
|
||||||
|
|
||||||
|
check(remove(path) == 0, "remove deletes the flush file");
|
||||||
|
check(remove(outpath) == 0, "remove deletes the stdout file");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 6. Buffering modes. */
|
||||||
|
static void
|
||||||
|
setvbuf_scenario(void)
|
||||||
|
{
|
||||||
|
const char path1[] = "/tmp/vlibc-test-stdio-setvbuf1.txt";
|
||||||
|
const char path2[] = "/tmp/vlibc-test-stdio-setvbuf2.txt";
|
||||||
|
const char path3[] = "/tmp/vlibc-test-stdio-setvbuf3.txt";
|
||||||
|
char buf[64];
|
||||||
|
static char userbuf[512];
|
||||||
|
FILE *f;
|
||||||
|
unsigned long saved;
|
||||||
|
long got;
|
||||||
|
|
||||||
|
f = fopen(path1, "w");
|
||||||
|
check(f != NULL, "setvbuf fopen w succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(setvbuf(f, NULL, _IONBF, 0) == 0, "setvbuf _IONBF NULL returns 0");
|
||||||
|
check(fputs("AB", f) >= 0, "fputs through the unbuffered stream");
|
||||||
|
got = raw_read_all(path1, buf, sizeof(buf));
|
||||||
|
check(got == 2 && buf[0] == 'A' && buf[1] == 'B',
|
||||||
|
"_IONBF data reaches the file without fflush");
|
||||||
|
check(setvbuf(f, NULL, _IOFBF, BUFSIZ) == 0, "setvbuf _IOFBF NULL returns 0");
|
||||||
|
check(fputs("CD", f) >= 0, "fputs through the fully buffered stream");
|
||||||
|
check(fflush(f) == 0, "fflush the fully buffered stream");
|
||||||
|
check(setvbuf(f, userbuf, _IOFBF, sizeof(userbuf)) == 0,
|
||||||
|
"setvbuf with a user buffer returns 0");
|
||||||
|
check(fputs("EF", f) >= 0, "fputs through the user buffer");
|
||||||
|
check(fflush(f) == 0, "fflush the user buffer");
|
||||||
|
got = raw_read_all(path1, buf, sizeof(buf));
|
||||||
|
check(got == 6 && mem_eq((const unsigned char *)buf, (const unsigned char *)"ABCDEF", 6),
|
||||||
|
"all three modes wrote sequential content");
|
||||||
|
check(fclose(f) == 0, "fclose setvbuf stream returns 0");
|
||||||
|
check(remove(path1) == 0, "remove deletes the setvbuf file");
|
||||||
|
|
||||||
|
f = fopen(path2, "w");
|
||||||
|
check(setvbuf(f, NULL, _IOLBF, 0) == 0, "setvbuf _IOLBF NULL returns 0");
|
||||||
|
check(fputs("line-one\n", f) >= 0, "fputs a newline-terminated line");
|
||||||
|
got = raw_read_all(path2, buf, sizeof(buf));
|
||||||
|
check(got == 9 && mem_eq((const unsigned char *)buf, (const unsigned char *)"line-one\n", 9),
|
||||||
|
"_IOLBF flushes on the newline without fflush");
|
||||||
|
check(fputs("line-two\n", f) >= 0, "fputs a second line");
|
||||||
|
check(fclose(f) == 0, "fclose line-buffered stream returns 0");
|
||||||
|
got = raw_read_all(path2, buf, sizeof(buf));
|
||||||
|
check(got == 18, "fclose flushed the second line");
|
||||||
|
check(remove(path2) == 0, "remove deletes the line-buffered file");
|
||||||
|
|
||||||
|
f = fopen(path3, "w");
|
||||||
|
setbuf(f, NULL); // NOLINT: setbuf has no error return; its effect is asserted below
|
||||||
|
check(fputs("SB", f) >= 0, "fputs through setbuf(NULL) stream");
|
||||||
|
got = raw_read_all(path3, buf, sizeof(buf));
|
||||||
|
check(got == 2 && buf[0] == 'S' && buf[1] == 'B',
|
||||||
|
"setbuf(NULL) data reaches the file without fflush");
|
||||||
|
check(fclose(f) == 0, "fclose setbuf stream returns 0");
|
||||||
|
check(remove(path3) == 0, "remove deletes the setbuf file");
|
||||||
|
|
||||||
|
saved = tcb_slot1_save();
|
||||||
|
check(setvbuf(f, NULL, 99, 0) == -1, "setvbuf with an invalid mode returns -1");
|
||||||
|
tcb_slot1_restore(saved);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 7. fdopen and fileno. */
|
||||||
|
static void
|
||||||
|
fdopen_scenario(void)
|
||||||
|
{
|
||||||
|
const char path[] = "/tmp/vlibc-test-stdio-fdopen.txt";
|
||||||
|
char line[64];
|
||||||
|
FILE *g;
|
||||||
|
long fd;
|
||||||
|
long rc;
|
||||||
|
|
||||||
|
fd = __syscall4(SYS_openat, -100, (long)path, (long)(0x2 | 0x40 | 0x200 /* RDWR|CREAT|TRUNC */),
|
||||||
|
0666);
|
||||||
|
check(fd >= 0, "raw openat for fdopen succeeds");
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
g = fdopen((int)fd, "w+");
|
||||||
|
check(g != NULL, "fdopen wraps the descriptor");
|
||||||
|
if (g == NULL)
|
||||||
|
{
|
||||||
|
__syscall1(SYS_close, fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(fileno(g) == (int)fd, "fileno returns the wrapped descriptor");
|
||||||
|
check(fputs("via-fdopen", g) >= 0, "fputs through the fdopen'd stream");
|
||||||
|
rewind(g); // NOLINT: rewind has no error return; its effect is asserted below
|
||||||
|
check(fgets(line, (int)sizeof(line), g) == line, "fgets after rewind returns s");
|
||||||
|
check(str_eq(line, "via-fdopen"), "fgets reads back the fdopen'd content");
|
||||||
|
check(fclose(g) == 0, "fclose fdopen stream returns 0");
|
||||||
|
rc = __syscall3(SYS_fcntl, fd, 3 /* F_GETFL */, 0);
|
||||||
|
check(rc < 0, "fclose closed the underlying descriptor");
|
||||||
|
check(remove(path) == 0, "remove deletes the fdopen file");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 8. freopen. */
|
||||||
|
static void
|
||||||
|
freopen_scenario(void)
|
||||||
|
{
|
||||||
|
const char path1[] = "/tmp/vlibc-test-stdio-freopen1.txt";
|
||||||
|
const char path2[] = "/tmp/vlibc-test-stdio-freopen2.txt";
|
||||||
|
char line[64];
|
||||||
|
FILE *f;
|
||||||
|
|
||||||
|
f = fopen(path1, "w");
|
||||||
|
check(f != NULL, "freopen setup fopen w succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(fputs("one", f) >= 0, "freopen setup writes the first file");
|
||||||
|
check(fclose(f) == 0, "fclose the first file");
|
||||||
|
f = fopen(path1, "r");
|
||||||
|
check(f != NULL, "freopen setup fopen r succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(freopen(NULL, "r", f) == f, "freopen with NULL path changes the mode only");
|
||||||
|
check(fgets(line, (int)sizeof(line), f) == line, "fgets on the mode-changed stream");
|
||||||
|
check(str_eq(line, "one"), "the mode change kept the descriptor");
|
||||||
|
check(freopen(path2, "w", f) == f, "freopen rebinds to the second path");
|
||||||
|
check(fputs("two", f) >= 0, "fputs through the rebound stream");
|
||||||
|
check(fclose(f) == 0, "fclose the rebound stream");
|
||||||
|
check(fopen(path2, "r") != NULL, "the second path exists");
|
||||||
|
check(remove(path1) == 0, "remove deletes the first freopen file");
|
||||||
|
check(remove(path2) == 0, "remove deletes the second freopen file");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 9. remove/rename and tmpfile. */
|
||||||
|
static void
|
||||||
|
name_scenario(void)
|
||||||
|
{
|
||||||
|
const char path1[] = "/tmp/vlibc-test-stdio-name1.txt";
|
||||||
|
const char path2[] = "/tmp/vlibc-test-stdio-name2.txt";
|
||||||
|
char line[64];
|
||||||
|
FILE *f;
|
||||||
|
FILE *t;
|
||||||
|
|
||||||
|
f = fopen(path1, "w");
|
||||||
|
check(f != NULL, "name fopen w succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(fclose(f) == 0, "fclose the name file");
|
||||||
|
check(rename(path1, path2) == 0, "rename returns 0");
|
||||||
|
check(fopen(path1, "r") == NULL, "the old name is gone after rename");
|
||||||
|
check(fopen(path2, "r") != NULL, "the new name exists after rename");
|
||||||
|
check(remove(path2) == 0, "remove returns 0");
|
||||||
|
check(fopen(path2, "r") == NULL, "the removed file is gone");
|
||||||
|
|
||||||
|
t = tmpfile();
|
||||||
|
check(t != NULL, "tmpfile returns a stream");
|
||||||
|
if (t != NULL)
|
||||||
|
{
|
||||||
|
check(fputs("temp-data", t) >= 0, "fputs through the tmpfile stream");
|
||||||
|
rewind(t); // NOLINT: rewind has no error return; its effect is asserted below
|
||||||
|
check(fgets(line, (int)sizeof(line), t) == line, "fgets after rewind returns s");
|
||||||
|
check(str_eq(line, "temp-data"), "tmpfile content round-trips");
|
||||||
|
check(fclose(t) == 0, "fclose tmpfile returns 0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/* 10. Level 2 additions. */
|
||||||
|
static void
|
||||||
|
level2_scenario(void)
|
||||||
|
{
|
||||||
|
const char path[] = "/tmp/vlibc-test-stdio-l2.txt";
|
||||||
|
char tname[L_tmpnam];
|
||||||
|
char buf[64];
|
||||||
|
char *p;
|
||||||
|
FILE *f;
|
||||||
|
static char userbuf[128];
|
||||||
|
long got;
|
||||||
|
|
||||||
|
p = tmpnam(tname);
|
||||||
|
check(p == tname, "tmpnam returns its argument");
|
||||||
|
check(prefix_eq(tname, "/tmp/"), "tmpnam produces a /tmp name");
|
||||||
|
check(tmpnam(NULL) != NULL, "tmpnam with NULL uses a static buffer");
|
||||||
|
p = ctermid(NULL);
|
||||||
|
check(p != NULL && str_eq(p, "/dev/tty"), "ctermid returns \"/dev/tty\"");
|
||||||
|
|
||||||
|
f = fopen64(path, "w");
|
||||||
|
check(f != NULL, "fopen64 opens a stream");
|
||||||
|
if (f != NULL)
|
||||||
|
{
|
||||||
|
check(fputs("l2", f) >= 0, "fputs through the fopen64 stream");
|
||||||
|
check(fclose(f) == 0, "fclose the fopen64 stream");
|
||||||
|
got = raw_read_all(path, buf, sizeof(buf));
|
||||||
|
check(got == 2 && buf[0] == 'l' && buf[1] == '2', "fopen64 wrote the content");
|
||||||
|
check(remove(path) == 0, "remove deletes the fopen64 file");
|
||||||
|
}
|
||||||
|
|
||||||
|
f = fopen(path, "w");
|
||||||
|
setbuffer(f, userbuf, sizeof(userbuf));
|
||||||
|
check(fputs("setbuffer", f) >= 0, "fputs through the setbuffer stream");
|
||||||
|
check(fflush(f) == 0, "fflush the setbuffer stream");
|
||||||
|
setlinebuf(f);
|
||||||
|
check(fputs("line\n", f) >= 0, "fputs a line through the setlinebuf stream");
|
||||||
|
got = raw_read_all(path, buf, sizeof(buf));
|
||||||
|
check(got == 14 &&
|
||||||
|
mem_eq((const unsigned char *)buf, (const unsigned char *)"setbufferline\n", 14),
|
||||||
|
"setlinebuf flushed on the newline");
|
||||||
|
check(fclose(f) == 0, "fclose the setlinebuf stream");
|
||||||
|
check(remove(path) == 0, "remove deletes the setlinebuf file");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
/* Failure scenarios (-f). */
|
||||||
|
static int
|
||||||
|
failure_scenarios(void)
|
||||||
|
{
|
||||||
|
const char path[] = "/tmp/vlibc-test-stdio-fail.bin";
|
||||||
|
unsigned char small[8];
|
||||||
|
FILE *f;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
f = fopen(path, "w+b");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: -f setup fopen failed\n");
|
||||||
|
failures++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
check(fwrite("abc", 1, 3, f) == 3, "-f setup fwrite");
|
||||||
|
check(fseek(f, 100, SEEK_SET) == 0, "fseek past EOF succeeds");
|
||||||
|
check(ftello(f) == 100, "ftello after fseek past EOF == 100");
|
||||||
|
rc = (int)fread(small, 1, sizeof(small), f);
|
||||||
|
check(rc == 0, "fread past EOF returns 0 items");
|
||||||
|
check(feof(f) != 0, "feof is set after the past-EOF read");
|
||||||
|
check(ferror(f) == 0, "ferror stays clear: it was EOF, not an error");
|
||||||
|
check(fclose(f) == 0, "fclose the -f stream");
|
||||||
|
check(remove(path) == 0, "remove the -f file");
|
||||||
|
}
|
||||||
|
check(fopen("/tmp/vlibc-no-such-file-000", "r") == NULL,
|
||||||
|
"fopen of a nonexistent path returns NULL");
|
||||||
|
check(fdopen(-1, "r") == NULL, "fdopen(-1) returns NULL");
|
||||||
|
f = fopen(path, "r");
|
||||||
|
if (f != NULL)
|
||||||
|
{
|
||||||
|
check(fputc('x', f) == EOF, "fputc on a read-only stream returns EOF");
|
||||||
|
check(ferror(f) != 0, "ferror is set after the invalid fputc");
|
||||||
|
check(fclose(f) == 0, "fclose the read-only stream");
|
||||||
|
}
|
||||||
|
check(remove("/tmp/vlibc-no-such-file-000") == -1, "remove of a missing path returns -1");
|
||||||
|
return failures > 0 ? 1 : 0;
|
||||||
|
}
|
||||||
|
// NOLINTEND(clang-analyzer-unix.Stream, clang-analyzer-core.UndefinedBinaryOperatorResult,
|
||||||
|
// clang-analyzer-unix.StdCLibraryFunctions)
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* The failure scenarios make the library write errno several
|
||||||
|
* times; leave via the raw syscall so the host cleanup never
|
||||||
|
* runs after those writes (see the banner).
|
||||||
|
*/
|
||||||
|
int rc = failure_scenarios();
|
||||||
|
|
||||||
|
__syscall1(SYS_exit_group, rc);
|
||||||
|
return rc; /* not reached */
|
||||||
|
}
|
||||||
|
|
||||||
|
roundtrip_scenario();
|
||||||
|
text_scenario();
|
||||||
|
seek_scenario();
|
||||||
|
ungetc_scenario();
|
||||||
|
flush_scenario();
|
||||||
|
setvbuf_scenario();
|
||||||
|
fdopen_scenario();
|
||||||
|
freopen_scenario();
|
||||||
|
name_scenario();
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
level2_scenario();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (failures > 0)
|
||||||
|
{
|
||||||
|
say(2, "FAILED (");
|
||||||
|
say_dec(2, (unsigned long)failures);
|
||||||
|
say(2, " check(s))\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
say(1, "all stdio tests passed\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user