0.3.0: converge relibc to upstream 0.6.0 + Red Bear patches

This commit is contained in:
2026-07-06 19:13:08 +03:00
parent 1a0edd8eeb
commit 4ef7e57571
1466 changed files with 75236 additions and 13644 deletions
+22
View File
@@ -0,0 +1,22 @@
#include <stdio.h>
#include <string.h>
#define DEVTTY "/dev/tty"
int main(void)
{
char *name = ctermid(NULL);
if(strcmp(name, DEVTTY) != 0) {
printf("ctermid name differs: expected %s, got: %s\n", DEVTTY, name);
return 1;
}
char name2[L_ctermid];
ctermid(name2);
if(strcmp(name, DEVTTY) != 0) {
printf("ctermid name2 differs: expected %s, got: %s\n", DEVTTY, name2);
return 1;
}
return 0;
}
+23
View File
@@ -0,0 +1,23 @@
#include <stdio.h>
#include <unistd.h>
#include "test_helpers.h"
int main(void) {
const char *msg = "Hello, %s";
int fd = dup(STDOUT_FILENO);
ERROR_IF(dup, fd, == -1);
int result = dprintf(fd, msg, "world");
ERROR_IF(dprintf, result, != sizeof("Hello, world") - 1);
UNEXP_IF(dprintf, result, < 0);
result = dprintf(fd, "\na\n");
UNEXP_IF(dprintf, result, < 0);
ERROR_IF(dprintf, result, != sizeof("\na\n") - 1);
close(fd);
return 0;
}
+9 -3
View File
@@ -10,9 +10,15 @@ int main(void) {
char buf[33] = { 0 };
for (int i = 1; i <= 32; ++i) {
if (fread(buf, 1, i, fp) < 0) {
perror("fread");
exit(EXIT_FAILURE);
size_t nread = fread(buf, 1, i, fp);
if (nread == 0) {
if (feof(fp)) {
fprintf(stderr, "early EOF\n");
return EXIT_FAILURE;
} else {
perror("fread");
return EXIT_FAILURE;
}
}
buf[i] = 0;
+2 -1
View File
@@ -1,4 +1,5 @@
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <wchar.h>
@@ -32,7 +33,7 @@ int main(void) {
&test_reopen_opens_file,
&test_reopen_resets_orientation,
};
for(int i=0; i<sizeof(tests)/sizeof(int(*)(void)); i++) {
for(size_t i = 0; i < sizeof(tests) / sizeof(int(*)(void)); i++) {
printf("%d\n", (*tests[i])());
}
}
+168
View File
@@ -0,0 +1,168 @@
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/getline.html
/// ssize_t getdelim(char **restrict lineptr, size_t *restrict n,
/// int delimiter, FILE *restrict stream);
/// ssize_t getline(char **restrict lineptr, size_t *restrict n,
/// FILE *restrict stream);
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include "test_helpers.h"
const char *INFILE_NAME = "stdio/getline.in";
const size_t INFILE_LINES = 7;
const size_t OVERREAD_LINES = 3;
void test_null_args();
void test_ferror();
void test_read_and_overread();
int main(void) {
setbuf(stdout, NULL);
// test if supplying NULL for either pointer correctly yields EINVAL
test_null_args();
// test reading stream with error flag set
test_ferror();
// "normal" case - read all `INFILE_LINES` lines of the test file and then overread by 3
test_read_and_overread();
}
void test_null_args() {
// Basics: NULL handling
// Test all combinations of NULL pointers since we fail on any one.
//
// I can't explicitly find that stream can't be NULL but nothing else
// makes sense, so let's return EINVAL
#define TEST_FOR_EINVAL(x) \
do \
{ \
size_t n = 0; \
char *lineptr = NULL; \
ssize_t status = 0; \
FILE *stream = NULL; \
\
stream = fopen(INFILE_NAME, "r"); \
ERROR_IF(fopen, stream, == NULL); \
ERROR_IF(fopen, ferror(stream), ); \
\
status = x; \
printf("%3zu: %s\n => ", ++counter, #x); \
CHECK_AND_PRINT_ERRNO(EINVAL); \
assert(status == -1 && errno == EINVAL); \
\
if (stream != NULL) \
fclose(stream); \
if (lineptr != NULL) \
free(lineptr); \
stream = NULL; \
errno = 0; \
(void)n; \
} while (0);
static size_t counter = 0;
TEST_FOR_EINVAL(getline(NULL, NULL, stream));
TEST_FOR_EINVAL(getline(NULL, &n, stream));
TEST_FOR_EINVAL(getline(&lineptr, NULL, stream));
// don't always use, as glibc doesn't tolerate stream being NULL or delim being out-of-range for char
#ifdef _RELIBC_STDIO_H
TEST_FOR_EINVAL(getline(NULL, NULL, NULL));
TEST_FOR_EINVAL(getline(&lineptr, NULL, NULL));
TEST_FOR_EINVAL(getline(NULL, &n, NULL));
TEST_FOR_EINVAL(getline(&lineptr, &n, NULL));
// test if using delim out of char range correctly causes EINVAL
// POSIX specifies UB, so we try to be more helpful
TEST_FOR_EINVAL(getdelim(&lineptr, &n, 25600, stream));
#endif
#undef TEST_FOR_EINVAL
printf("\n");
}
void test_ferror() {
// TODO test behavior on error flag set
}
void test_read_and_overread() {
// Basic use cases
// Read all INFILE_LINES lines of sample input, then overread OVERREAD_LINES times
size_t n = 0;
char *lineptr = NULL;
ssize_t status;
FILE *stream = NULL;
stream = fopen(INFILE_NAME, "r");
for (size_t i = 0; i < INFILE_LINES; ++i) {
/// "Upon successful completion, the getline() and getdelim() functions
/// shall return the number of bytes written into the buffer, including
/// the delimiter character if one was encountered before EOF, but
/// excluding the terminating NUL character."
printf("%3zu: ", i + 1);
status = getline(&lineptr, &n, stream);
/// "If the end-of-file indicator for the stream is set, or if no
/// characters were read and the stream is at end-of-file, the
/// end-of-file indicator for the stream shall be set and the function
/// shall return -1."
assert(!(status == -1 && feof(stream)));
/// "If an error occurs, the error indicator for the stream shall be
/// set, and the function shall return -1 and set errno to indicate the
/// error."
ERROR_IF(getline, status, == -1 && ferror(stream));
// This should only execute if the other error cases (with stream flags set) did not abort.
UNEXP_IF(getline, status, == -1);
// Print length and content to verify. Also test if the buffer is big
// enough and if strlen of input matches the return value.
// Although this doesn't HAVE to be true, as the input could contain
// NUL bytes, ours doesn't.
printf("status = %zi, strlen = %zu, feof = %s, ferror = %s\n |>%s",
status, strlen(lineptr), feof(stream) ? "1" : "0", ferror(stream) ? "1" : "0", lineptr);
// fflush(stdout);
assert(strlen(lineptr) == (size_t) status); // we can cast to size_t since we
assert(n >= (size_t) status + 1); // UNEXP_IF against status being -1
}
printf("\n");
// OVERREAD
for (size_t i = 0; i < OVERREAD_LINES; ++i) {
/// "If the end-of-file indicator for the stream is set, or if no
/// characters were read and the stream is at end-of-file, the
/// end-of-file indicator for the stream shall be set and the function
/// shall return -1."
status = getline(&lineptr, &n, stream);
printf("overread %zu, status = %zi, feof = %s, ferror = %s\n",
i + 1,
status,
feof(stream) ? "1" : "0", ferror(stream) ? "1" : "0");
if (i == 0) {
assert(status == -1);
assert(feof(stream));
assert(!ferror(stream));
}
printf("|~%s\n", lineptr);
}
// cleanup
fclose(stream);
free(lineptr);
}
+7
View File
@@ -0,0 +1,7 @@
Space: the final frontier.
These are the voyages of the starship Enterprise.
Its continuing mission:
- to explore strange new worlds;
- to seek out new life and new civilizations;
- to boldly go where no one has gone before!
+38 -21
View File
@@ -1,30 +1,47 @@
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "test_helpers.h"
int main(void) {
FILE *f = fopen("stdio/stdio.in", "r");
ERROR_IF(fopen, f, == NULL);
FILE *f;
flockfile(f);
void* thread_wontlock(void* arg) {
(void)arg;
// Commenting this out should cause a deadlock:
// flockfile(f);
int result = ftrylockfile(f);
UNEXP_IF(ftrylockfile, result, == 0);
if (!ftrylockfile(f)) {
puts("Mutex unlocked but it shouldn't be");
exit(EXIT_FAILURE);
}
funlockfile(f);
if (ftrylockfile(f)) {
puts("Mutex locked but it shouldn't be");
exit(EXIT_FAILURE);
}
if (!ftrylockfile(f)) {
puts("Mutex unlocked but it shouldn't be");
exit(EXIT_FAILURE);
}
funlockfile(f);
return NULL;
}
void* thread_willlock(void* arg) {
(void)arg;
int result = ftrylockfile(f);
UNEXP_IF(ftrylockfile, result, != 0);
return NULL;
}
int main(void) {
f = fopen("stdio/stdio.in", "r");
ERROR_IF(fopen, f, == NULL);
flockfile(f);
flockfile(f);
thread_willlock(NULL);
pthread_t thread;
for (int i = 1; i <= 3; i++) {
pthread_create(&thread, NULL, thread_wontlock, NULL);
pthread_join(thread, NULL);
funlockfile(f);
}
pthread_create(&thread, NULL, thread_willlock, NULL);
pthread_join(thread, NULL);
// TODO: glibc refuses to quit test without this
// but in relibc, it will result in EPERM
// funlockfile(f);
}
+3 -1
View File
@@ -7,9 +7,11 @@ int main(void) {
FILE *fp = popen("ls -1 example_dir", "r");
ERROR_IF(popen, fp, == NULL);
int lineno = 0;
char path[256] = { 0 };
while (fgets(path, 256, fp) != NULL) {
printf("%s", path);
lineno++;
printf("Line %d: %s", lineno, path);
}
int status = pclose(fp);
+15
View File
@@ -1,3 +1,4 @@
#include <errno.h>
#include <stdio.h>
#include <stdlib.h> // free()
#include <math.h> // INFINITY, NAN constants
@@ -64,6 +65,9 @@ int main(void) {
printf("%G\n", 0.00001);
printf("%E\n", 0.00001);
printf("%2$0*1$.*3$lf\n", 9, 1234.56, 2);
printf("%2$0*1$.*3$Lf\n", 9, 1234.56L, 2);
double nonfinites[] = {INFINITY, -INFINITY, NAN, -NAN};
char *float_formats[] = {"%e", "%E", "%f", "%F", "%g", "%G"};
puts("\nNon-finite float madness:");
@@ -90,4 +94,15 @@ int main(void) {
res = asprintf(&s, "test %s %d", "string", 2);
printf("printed: %s, value: %d\n", s, res);
free(s);
errno = EOWNERDEAD;
printf("\n%%m: %m\n");
puts("\nC23:");
printf("Binary %%b: %b\n", 4);
printf("Binary %%b alternate: %#b\n", 4);
printf("Binary %%B: %B\n", 4);
printf("Binary %%B alternate: %#B\n", 4);
return EXIT_SUCCESS;
}
+23
View File
@@ -0,0 +1,23 @@
#include <assert.h>
#include <stdio.h>
#include "test_helpers.h"
int main(void)
{
FILE* fp = tmpfile();
assert(fp != NULL);
flockfile(fp);
int c = 'c', r = 0;
r = putc_unlocked(c, fp);
ERROR_IF(putc_unlocked, r, == EOF);
r = fflush(fp);
ERROR_IF(fflush, r, == EOF);
funlockfile(fp);
// make sure unlock works
r = putc(c, fp);
ERROR_IF(putc, r, == EOF);
r = fflush(fp);
ERROR_IF(fflush, r, == EOF);
return 0;
}
+84 -1
View File
@@ -1,5 +1,7 @@
#include <stdio.h>
#include <assert.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
@@ -10,6 +12,84 @@ static char oldpath[] = "old-name.out";
static char newpath[] = "new-name.out";
static char str[] = "Hello, World!";
// Test that renaming a broken symbolic link works.
// Symlinks should not be resolved by rename.
// One of the problems that arises when links are resolved is that
// broken links can't be renamed.
//
// This test returns EXIT_FAILURE/EXIT_SUCCESS because it needs to clean
// up temporary files on failure. The test helpers call _exit.
int rename_broken_symlink(void) {
int result = EXIT_FAILURE;
char dir_template[] = "/tmp/sltest.XXXXXX";
size_t dlen = sizeof(dir_template) - 1;
char* temp_dir = mkdtemp(dir_template);
if (!temp_dir) {
perror("mkdtemp");
return EXIT_FAILURE;
}
// TODO: Almost all of the code below can be vastly simplified
// with openat/symlinkat later.
// Broken link to be created
const char link_name[] = "sym";
char link_path[PATH_MAX] = {0};
memcpy(link_path, temp_dir, dlen);
link_path[dlen] = '/';
memcpy(&link_path[dlen + 1], link_name, sizeof(link_name));
// Non-existing target
const char link_target[] = "target";
char target_path[PATH_MAX] = {0};
memcpy(target_path, temp_dir, dlen);
target_path[dlen] = '/';
memcpy(&target_path[dlen + 1], link_target, sizeof(link_target));
// New name of link (i.e. mv sym symrename)
const char link_rename[] = "symrename";
char rename_path[PATH_MAX] = {0};
memcpy(rename_path, temp_dir, dlen);
rename_path[dlen] = '/';
memcpy(&rename_path[dlen + 1], link_rename, sizeof(link_rename));
// Target most definitely does NOT exist.
// This is a sanity check that shouldn't fail as test uses temp dirs.
int target_fd = open(target_path, O_RDONLY);
if (target_fd != -1) {
fprintf(stderr,
"Target exists when it shouldn't: %s\n",
target_path
);
// Skip clean up on the very exceptional case that the
// randomized dir and file exists.
goto skip_cleanup;
}
// Create a broken symlink in a temp directory
if (symlink(target_path, link_path) < 0) {
perror("symlink");
goto cleanup;
}
// Rename the link; this should work even if target doesn't exist
if (rename(link_path, rename_path) < 0) {
perror("rename");
goto cleanup;
}
// TODO: Assert paths exist (needs openat)
result = EXIT_SUCCESS;
cleanup:
unlink(link_path);
unlink(rename_path);
rmdir(temp_dir);
skip_cleanup:
return result;
}
int main(void) {
char buf[14] = { 0 };
@@ -53,4 +133,7 @@ int main(void) {
puts("Comparison failed!");
exit(EXIT_FAILURE);
}
int broken_symlink_res = rename_broken_symlink();
assert(broken_symlink_res == EXIT_SUCCESS);
}
+225
View File
@@ -0,0 +1,225 @@
#include <fcntl.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static int temp_file(
const char dir[],
size_t dlen,
char buf[PATH_MAX],
const char name[],
size_t nlen,
bool create
) {
memcpy(buf, dir, dlen);
buf[dlen] = '/';
memcpy(&buf[dlen + 1], name, nlen);
if (create) {
int fd = open(buf, O_CREAT);
if (fd == -1) {
perror("open (file A)");
close(fd);
return -1;
}
close(fd);
}
return 0;
}
int main(void) {
int result = EXIT_FAILURE;
char dir_template[] = "/tmp/mvattest.XXXXXX";
size_t dlen = sizeof(dir_template) - 1;
if (!mkdtemp(dir_template)) {
perror("mkdtemp");
goto bye;
}
int dirfd = open(dir_template, O_DIRECTORY | O_PATH);
if (dirfd == -1) {
perror("open (temp directory)");
goto bye;
}
char dir_template2[] = "/tmp/mvattest2.XXXXXX";
size_t dlen2 = sizeof(dir_template2) - 1;
if (!mkdtemp(dir_template2)) {
perror("mkdtemp");
goto close_dir1;
}
int dirfd2 = open(dir_template2, O_DIRECTORY | O_PATH);
if (dirfd2 == -1) {
perror("open (temp directory)");
goto close_dir1;
}
// File A
const char file_a[] = "power";
char file_a_path[PATH_MAX] = {0};
if (temp_file(dir_template, dlen, file_a_path, file_a, sizeof(file_a), true) == -1) {
goto close_dir2;
}
// File B
const char file_b[] = "nyanko";
char file_b_path[PATH_MAX] = {0};
if (
temp_file(
dir_template,
dlen,
file_b_path,
file_b,
sizeof(file_b),
false
) == -1
) {
goto remove_ab;
}
// File A but in directory 2
char file_a_dir2[PATH_MAX] = {0};
if (
temp_file(
dir_template2,
dlen2,
file_a_dir2,
file_a,
sizeof(file_a),
false
) == -1
) {
goto remove_all;
}
// Move file A to file B normally (same dir)
if (renameat(dirfd, file_a, dirfd, file_b) == -1) {
perror("renameat (A -> B, basic)");
goto remove_all;
}
if (access(file_b_path, F_OK) == -1) {
perror("access (file B, basic)");
goto remove_all;
}
// Move file B to A (absolute path; same dir)
if (renameat(dirfd, file_b_path, dirfd, file_a) == -1) {
perror("renameat (B -> A, absolute path)");
goto remove_all;
}
if (access(file_a_path, F_OK) == -1) {
perror("access (file A, absolute path)");
goto remove_all;
}
// Move A to B (both absolute)
if (renameat(dirfd, file_a_path, dirfd, file_b_path) == -1) {
perror("renameat (A -> B, both absolute)");
goto remove_all;
}
if (access(file_b_path, F_OK) == -1) {
perror("access (file B, both absolute)");
goto remove_all;
}
// Move B to B
if (renameat(dirfd, file_b, dirfd, file_b) == -1) {
perror("renameat (B -> B)");
goto remove_all;
}
if (access(file_b_path, F_OK) == -1) {
perror("access (file B)");
goto remove_all;
}
// Move file B to A (AT_FDCWD)
char cwd[PATH_MAX] = {0};
if (!getcwd(cwd, PATH_MAX)) {
perror("getcwd");
goto remove_all;
}
if (chdir(dir_template) == -1) {
perror("chdir");
goto remove_all;
}
if (renameat(AT_FDCWD, file_b, dirfd, file_a) == -1) {
perror("renameat (B -> A, AT_FDCWD)");
goto remove_all;
}
if (access(file_a_path, F_OK) == -1) {
perror("access (file A, AT_FDCWD)");
goto remove_all;
}
// Reset, though it doesn't really matter.
if (chdir(cwd) == -1) {
perror("chdir");
goto remove_all;
}
// Move to different directory
if (renameat(dirfd, file_a, dirfd2, file_a) == -1) {
perror("renameat (A -> B, different dirs)");
goto remove_all;
}
if (access(file_a_dir2, F_OK) == -1) {
perror("access (file A in dir2)");
goto remove_all;
}
// Move non-existing file
if (renameat(dirfd, "aki", dirfd, "denji") == 0) {
// Wut?
fputs("renameat succeeded at moving a non-existing file\n", stderr);
goto remove_all;
}
// RENAME_NOREPLACE
// Create file B in dir 1 first because we moved it earlier.
int fd = open(file_b_path, O_CREAT);
if (fd == -1) {
perror("open (file B in dir 1)");
close(fd);
goto remove_all;
}
close(fd);
if (renameat2(dirfd, file_b, dirfd2, file_a, RENAME_NOREPLACE) == 0) {
fputs("renameat2 (B -> A, noreplace) succeeded\n", stderr);
goto remove_all;
}
if (access(file_a_dir2, F_OK) == -1) {
fputs("RENAME_NOREPLACE ate file A in dir 2\n", stderr);
goto remove_all;
}
if (access(file_b_path, F_OK) == -1) {
fputs("RENAME_NOREPLACE ate file B in dir 1\n", stderr);
goto remove_all;
}
// TODO: RENAME_EXCHANGE (Needs frename support in Redox)
// Notes for later:
// * Write a message to both files
// * Swap
// * Check if the files swapped correctly by strcmp messages
result = EXIT_SUCCESS;
remove_all:
remove(file_a_dir2);
remove_ab:
remove(file_a_path);
remove(file_b_path);
rmdir(dir_template);
rmdir(dir_template2);
close_dir2:
close(dirfd2);
close_dir1:
close(dirfd);
bye:
return result;
}
+3 -1
View File
@@ -33,7 +33,7 @@ void test(char* fmt_in, char* input, struct params *p, ...) {
int main(void) {
struct params p = { .c = 'a' };
test("%hhd %d", "12 345", &p, &p.sa, &p.ia);
test("%hd %d", "12 345", &p, &p.sa, &p.ia);
test("%x %i %i", "12 0x345 010", &p, &p.ia, &p.ib, &p.ic);
test("%f.%lf", "0.1.0.2", &p, &p.fa, &p.da);
test("%p", "0xABCDEF", &p, &p.ptr);
@@ -42,6 +42,8 @@ int main(void) {
test("%c%3c", "hello", &p, &p.c, &p.string1);
test("test: %2i%n", "test: 0xFF", &p, &p.ia, &p.ib);
test("hello world%%", "hello world%", &p);
test("hello %5ls %d", "hello world 42", &p, &p.string1, &p.ia);
test("bye %ls %d", "bye planet 84", &p, &p.string2, &p.ia);
test("h%1[ae]ll%1[^a] wor%1[^\n]%[d]", "hello world", &p, &p.string1, &p.string2, &p.string3, &p.string4);
test("h%1[ae]ll%1[^a] wor%1[^\n]%[d]", "halle worfdddddd", &p, &p.string1, &p.string2, &p.string3, &p.string4);
test("h%1[ae]ll%1[^a] wor%1[^\n]%[d]", "halle worfdddddd", &p, &p.string1, &p.string2, &p.string3, &p.string4);