/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2020 Robert Mustacchi */ /* * Test memory based streams: opem_memstream(3C), open_wmemstream(3C), and * fmemopen(3C). */ #include #include #include #include #include #include #include #include #include #include typedef boolean_t (*memstream_test_f)(void); static char *fmemopen_str1 = "The Road goes ever on and on\n" "Down from the door where it began.\n"; const wchar_t *wstream_str = L"いつか終わる夢"; /* * smatch doesn't support wide-character constants (wchar_t foo = L'xxx'), so * instead use a string which it'll happily accept. */ const wchar_t *wstr_const = L"光"; const char * _umem_debug_init(void) { return ("default,verbose"); } const char * _umem_logging_init(void) { return ("fail,contents"); } static boolean_t fmemopen_badopen(void *buf, size_t size, const char *mode, int err) { FILE *f = fmemopen(buf, size, mode); if (f != NULL) { warnx("fmemopen() succeeded erroneously"); (void) fclose(f); return (B_FALSE); } if (errno != err) { warnx("fmemopen() open failed with wrong errno, " "found %d (%s), expected %d (%s)", errno, strerror(errno), err, strerror(err)); return (B_FALSE); } return (B_TRUE); } static boolean_t fmemopen_badmode(void) { return (fmemopen_badopen(fmemopen_str1, strlen(fmemopen_str1), "foobar", EINVAL)); } static boolean_t fmemopen_zerobuf1(void) { return (fmemopen_badopen(fmemopen_str1, 0, "w", EINVAL)); } static boolean_t fmemopen_zerobuf2(void) { return (fmemopen_badopen(NULL, 0, "w+", EINVAL)); } static boolean_t fmemopen_nullbuf1(void) { return (fmemopen_badopen(NULL, 10, "r", EINVAL)); } static boolean_t fmemopen_nullbuf2(void) { return (fmemopen_badopen(NULL, 10, "w", EINVAL)); } static boolean_t fmemopen_nullbuf3(void) { return (fmemopen_badopen(NULL, 10, "a", EINVAL)); } static boolean_t fmemopen_nullbuf4(void) { return (fmemopen_badopen(NULL, 10, "ax", EINVAL)); } static boolean_t fmemopen_sizemax(void) { return (fmemopen_badopen(NULL, SIZE_MAX, "w+", ENOMEM)); } static boolean_t fmemopen_cantalloc(void) { boolean_t ret; umem_setmtbf(1); ret = fmemopen_badopen(NULL, 10, "w+", ENOMEM); umem_setmtbf(0); return (ret); } static boolean_t open_memstream_badopen(char **bufp, size_t *sizep, int err) { FILE *f = open_memstream(bufp, sizep); if (f != NULL) { warnx("open_memstream() succeeded erroneously"); (void) fclose(f); return (B_FALSE); } if (errno != err) { warnx("open_memstream() open failed with wrong errno, " "found %d (%s), expected %d (%s)", errno, strerror(errno), err, strerror(err)); return (B_FALSE); } return (B_TRUE); } static boolean_t open_memstream_badbuf(void) { size_t s, check; boolean_t ret; arc4random_buf(&s, sizeof (s)); check = s; ret = open_memstream_badopen(NULL, &s, EINVAL); if (check != s) { warnx("open_memstream() open erroneously wrote to size " "pointer"); return (B_FALSE); } return (ret); } static boolean_t open_memstream_badsize(void) { char *c; return (open_memstream_badopen(&c, NULL, EINVAL)); } static boolean_t open_memstream_allnull(void) { return (open_memstream_badopen(NULL, NULL, EINVAL)); } static boolean_t open_memstream_cantalloc(void) { boolean_t ret; char *c; size_t len; umem_setmtbf(1); ret = open_memstream_badopen(&c, &len, EAGAIN); umem_setmtbf(0); return (ret); } static boolean_t open_wmemstream_badopen(wchar_t **bufp, size_t *sizep, int err) { FILE *f = open_wmemstream(bufp, sizep); if (f != NULL) { warnx("open_wmemstream() succeeded erroneously"); (void) fclose(f); return (B_FALSE); } if (errno != err) { warnx("open_wmemstream() open failed with wrong errno, " "found %d (%s), expected %d (%s)", errno, strerror(errno), err, strerror(err)); return (B_FALSE); } return (B_TRUE); } static boolean_t open_wmemstream_badbuf(void) { size_t s, check; boolean_t ret; arc4random_buf(&s, sizeof (s)); check = s; ret = open_wmemstream_badopen(NULL, &s, EINVAL); if (check != s) { warnx("open_wmemstream() open erroneously wrote to size " "pointer"); return (B_FALSE); } return (ret); } static boolean_t open_wmemstream_badsize(void) { wchar_t *c; return (open_wmemstream_badopen(&c, NULL, EINVAL)); } static boolean_t open_wmemstream_allnull(void) { return (open_wmemstream_badopen(NULL, NULL, EINVAL)); } static boolean_t open_wmemstream_cantalloc(void) { boolean_t ret; wchar_t *c; size_t len; umem_setmtbf(1); ret = open_wmemstream_badopen(&c, &len, EAGAIN); umem_setmtbf(0); return (ret); } static boolean_t fmemopen_fill_putc(FILE *f, size_t len, boolean_t buffer) { boolean_t ret = B_TRUE; size_t i; for (i = 0; i < BUFSIZ * 2; i++) { if (fputc('a', f) != 'a') { break; } } if (buffer) { if (i < len) { warnx("write mismatch, had %zu bytes, wrote %zu", len, i); ret = B_FALSE; } if (fflush(f) == 0) { warnx("somehow flushed overly full stream, expected " "failure"); ret = B_FALSE; } } else if (i != len) { warnx("write mismatch, had %zu bytes, wrote %zu", len, i); ret = B_FALSE; } if (feof(f) != 0) { warn("EOF mistakenly set on write"); ret = B_FALSE; } if (ferror(f) == 0) { warn("feof not set on write past the end"); ret = B_FALSE; } if (fclose(f) != 0) { warn("failed to close memory stream"); return (B_FALSE); } return (ret); } static boolean_t fmemopen_fill_fwrite(FILE *f, size_t len, boolean_t buffer) { boolean_t ret = B_TRUE; size_t i; char buf[BUFSIZ]; (void) memset(buf, 'a', sizeof (buf)); i = fwrite(buf, sizeof (buf), 1, f); if (buffer) { if (i != 1) { warnx("write mismatch, expected 1 entry, found %zu", i); ret = B_FALSE; } if (fflush(f) == 0) { warnx("somehow flushed overly full stream, expected " "failure"); ret = B_FALSE; } } else if (i != 0 && i != len) { warnx("write mismatch, had %zu bytes, wrote %zu", len, i); ret = B_FALSE; } if (feof(f) != 0) { warn("EOF mistakenly set on write"); ret = B_FALSE; } if (ferror(f) == 0) { warn("feof not set on write past the end"); ret = B_FALSE; } if (fclose(f) != 0) { warn("failed to close memory stream"); return (B_FALSE); } return (ret); } static boolean_t fmemopen_fill_alt_fwrite(FILE *f, size_t len, boolean_t buffer) { boolean_t ret = B_TRUE; size_t i; char buf[BUFSIZ]; (void) memset(buf, 'a', sizeof (buf)); i = fwrite(buf, 1, sizeof (buf), f); if (buffer) { if (i < len) { warnx("write mismatch, had %zu bytes, wrote %zu", len, i); ret = B_FALSE; } if (fflush(f) == 0) { warnx("somehow flushed overly full stream, expected " "failure"); ret = B_FALSE; } } else if (i != len) { warnx("write mismatch, had %zu bytes, wrote %zu", len, i); ret = B_FALSE; } if (feof(f) != 0) { warn("EOF mistakenly set on write"); ret = B_FALSE; } if (ferror(f) == 0) { warn("feof not set on write past the end"); ret = B_FALSE; } if (fclose(f) != 0) { warn("failed to close memory stream"); return (B_FALSE); } return (ret); } static boolean_t fmemopen_fill_fputs(FILE *f, size_t len, boolean_t buffer) { boolean_t ret = B_TRUE; size_t i; char buf[17]; (void) memset(buf, 'a', sizeof (buf)); buf[16] = '\0'; for (i = 0; i < BUFSIZ * 2; i += 16) { if (fputs(buf, f) != 16) { break; } } /* * We don't check flushing in the puts case because fputs seems to clear * the buffer as a side effect. */ if (buffer) { if (i < len) { warnx("write mismatch, had %zu bytes, wrote %zu", len, i); ret = B_FALSE; } } else if (i != len) { warnx("write mismatch, had %zu bytes, wrote %zu", len, i); ret = B_FALSE; } if (feof(f) != 0) { warn("EOF mistakenly set on write"); ret = B_FALSE; } if (ferror(f) == 0) { warn("feof not set on write past the end"); ret = B_FALSE; } if (fclose(f) != 0) { warn("failed to close memory stream"); return (B_FALSE); } return (ret); } static boolean_t fmemopen_fill_default(void) { FILE *f; f = fmemopen(NULL, 128, "w+"); if (f == NULL) { warn("failed to open fmemopen stream"); return (B_FALSE); } return (fmemopen_fill_putc(f, 128, B_TRUE)); } static boolean_t fmemopen_fill_lbuf(void) { FILE *f; f = fmemopen(NULL, 128, "w+"); if (f == NULL) { warn("failed to open fmemopen stream"); return (B_FALSE); } if (setvbuf(f, NULL, _IOLBF, BUFSIZ) != 0) { warn("failed to set buffer to line-buffered mode"); } return (fmemopen_fill_putc(f, 128, B_TRUE)); } static boolean_t fmemopen_fill_nobuf(void) { FILE *f; f = fmemopen(NULL, 128, "w+"); if (f == NULL) { warn("failed to open fmemopen stream"); return (B_FALSE); } if (setvbuf(f, NULL, _IONBF, 0) != 0) { warn("failed to set buffer to non-buffered mode"); } return (fmemopen_fill_putc(f, 128, B_FALSE)); } static boolean_t fmemopen_fwrite_default(void) { FILE *f; f = fmemopen(NULL, 128, "w+"); if (f == NULL) { warn("failed to open fmemopen stream"); return (B_FALSE); } return (fmemopen_fill_fwrite(f, 128, B_TRUE)); } static boolean_t fmemopen_fwrite_lbuf(void) { FILE *f; f = fmemopen(NULL, 128, "w+"); if (f == NULL) { warn("failed to open fmemopen stream"); return (B_FALSE); } if (setvbuf(f, NULL, _IOLBF, BUFSIZ) != 0) { warn("failed to set buffer to line-buffered mode"); } return (fmemopen_fill_fwrite(f, 128, B_TRUE)); } static boolean_t fmemopen_fwrite_nobuf(void) { FILE *f; f = fmemopen(NULL, 128, "w+"); if (f == NULL) { warn("failed to open fmemopen stream"); return (B_FALSE); } if (setvbuf(f, NULL, _IONBF, 0) != 0) { warn("failed to set buffer to non-buffered mode"); } return (fmemopen_fill_fwrite(f, 128, B_FALSE)); } static boolean_t fmemopen_alt_fwrite_default(void) { FILE *f; f = fmemopen(NULL, 128, "w+"); if (f == NULL) { warn("failed to open fmemopen stream"); return (B_FALSE); } return (fmemopen_fill_alt_fwrite(f, 128, B_TRUE)); } static boolean_t fmemopen_alt_fwrite_lbuf(void) { FILE *f; f = fmemopen(NULL, 128, "w+"); if (f == NULL) { warn("failed to open fmemopen stream"); return (B_FALSE); } if (setvbuf(f, NULL, _IOLBF, BUFSIZ) != 0) { warn("failed to set buffer to line-buffered mode"); } return (fmemopen_fill_alt_fwrite(f, 128, B_TRUE)); } static boolean_t fmemopen_alt_fwrite_nobuf(void) { FILE *f; f = fmemopen(NULL, 128, "w+"); if (f == NULL) { warn("failed to open fmemopen stream"); return (B_FALSE); } if (setvbuf(f, NULL, _IONBF, 0) != 0) { warn("failed to set buffer to non-buffered mode"); } return (fmemopen_fill_alt_fwrite(f, 128, B_FALSE)); } static boolean_t fmemopen_fputs_default(void) { FILE *f; f = fmemopen(NULL, 128, "w+"); if (f == NULL) { warn("failed to open fmemopen stream"); return (B_FALSE); } return (fmemopen_fill_fputs(f, 128, B_TRUE)); } static boolean_t fmemopen_fputs_lbuf(void) { FILE *f; f = fmemopen(NULL, 128, "w+"); if (f == NULL) { warn("failed to open fmemopen stream"); return (B_FALSE); } if (setvbuf(f, NULL, _IOLBF, BUFSIZ) != 0) { warn("failed to set buffer to line-buffered mode"); } return (fmemopen_fill_fputs(f, 128, B_TRUE)); } static boolean_t fmemopen_fputs_nobuf(void) { FILE *f; f = fmemopen(NULL, 128, "w+"); if (f == NULL) { warn("failed to open fmemopen stream"); return (B_FALSE); } if (setvbuf(f, NULL, _IONBF, 0) != 0) { warn("failed to set buffer to non-buffered mode"); } return (fmemopen_fill_fputs(f, 128, B_FALSE)); } static boolean_t memstream_check_seek(FILE *f, size_t len, int whence) { off_t o; long l; boolean_t ret = B_TRUE; if (fseeko(f, 0, whence) != 0) { warn("failed to seek, whence: %d", whence); return (B_FALSE); } if ((o = ftello(f)) == -1) { warn("failed to get offset from ftello"); ret = B_FALSE; } else if (o < 0 || (size_t)o != len) { warnx("found bad stream position: expected %zu, found: %zu", len, (size_t)o); ret = B_FALSE; } if ((l = ftell(f)) == -1) { warn("failed to get offset from ftell"); ret = B_FALSE; } else if (l < 0 || (size_t)l != len) { warnx("found bad stream position: expected %zu, found: %zu", len, (size_t)l); ret = B_FALSE; } return (ret); } static boolean_t fmemopen_defseek_r(void) { FILE *f; size_t len = strlen(fmemopen_str1); boolean_t ret, ret2; f = fmemopen(fmemopen_str1, len, "r"); if (f == NULL) { warn("failed to open fmemopen stream"); return (B_FALSE); } ret = memstream_check_seek(f, 0, SEEK_CUR); ret2 = memstream_check_seek(f, len, SEEK_END); (void) fclose(f); return (ret && ret2); } static boolean_t fmemopen_defseek_rp(void) { FILE *f; size_t len = strlen(fmemopen_str1); boolean_t ret, ret2; f = fmemopen(fmemopen_str1, len, "r+"); if (f == NULL) { warn("failed to open fmemopen stream"); return (B_FALSE); } ret = memstream_check_seek(f, 0, SEEK_CUR); ret2 = memstream_check_seek(f, len, SEEK_END); (void) fclose(f); return (ret && ret2); } static boolean_t fmemopen_defseek_w(void) { FILE *f; size_t len = strlen(fmemopen_str1); boolean_t ret, ret2; char *str; if ((str = strdup(fmemopen_str1)) == NULL) { warn("failed to duplicate string"); return (B_FALSE); } f = fmemopen(str, len, "w"); if (f == NULL) { warn("failed to open fmemopen stream"); free(str); return (B_FALSE); } ret = memstream_check_seek(f, 0, SEEK_CUR); ret2 = memstream_check_seek(f, 0, SEEK_END); (void) fclose(f); free(str); return (ret && ret2); } static boolean_t fmemopen_defseek_wp(void) { FILE *f; size_t len = strlen(fmemopen_str1); boolean_t ret, ret2; char *str; if ((str = strdup(fmemopen_str1)) == NULL) { warn("failed to duplicate string"); return (B_FALSE); } f = fmemopen(str, len, "w+"); if (f == NULL) { warn("failed to open fmemopen stream"); free(str); return (B_FALSE); } ret = memstream_check_seek(f, 0, SEEK_CUR); ret2 = memstream_check_seek(f, 0, SEEK_END); (void) fclose(f); free(str); return (ret && ret2); } static boolean_t fmemopen_defseek_a(void) { FILE *f; size_t len = strlen(fmemopen_str1); boolean_t ret, ret2; char *str; if ((str = strdup(fmemopen_str1)) == NULL) { warn("failed to duplicate string"); return (B_FALSE); } f = fmemopen(str, len, "a"); if (f == NULL) { warn("failed to open fmemopen stream"); free(str); return (B_FALSE); } ret = memstream_check_seek(f, len, SEEK_CUR); ret2 = memstream_check_seek(f, len, SEEK_END); (void) fclose(f); free(str); return (ret && ret2); } static boolean_t fmemopen_defseek_ap(void) { FILE *f; size_t len = strlen(fmemopen_str1); boolean_t ret, ret2; char *str; if ((str = strdup(fmemopen_str1)) == NULL) { warn("failed to duplicate string"); return (B_FALSE); } f = fmemopen(str, len, "a+"); if (f == NULL) { warn("failed to open fmemopen stream"); free(str); return (B_FALSE); } ret = memstream_check_seek(f, len, SEEK_CUR); ret2 = memstream_check_seek(f, len, SEEK_END); (void) fclose(f); free(str); return (ret && ret2); } static boolean_t fmemopen_defseek_a_nbyte(void) { FILE *f; size_t len = strlen(fmemopen_str1); boolean_t ret, ret2; char *str; if ((str = strdup(fmemopen_str1)) == NULL) { warn("failed to duplicate string"); return (B_FALSE); } str[8] = '\0'; f = fmemopen(str, len, "a"); if (f == NULL) { warn("failed to open fmemopen stream"); free(str); return (B_FALSE); } ret = memstream_check_seek(f, 8, SEEK_CUR); ret2 = memstream_check_seek(f, 8, SEEK_END); (void) fclose(f); free(str); return (ret && ret2); } static boolean_t fmemopen_defseek_ap_nbyte(void) { FILE *f; size_t len = strlen(fmemopen_str1); boolean_t ret, ret2; char *str; if ((str = strdup(fmemopen_str1)) == NULL) { warn("failed to duplicate string"); return (B_FALSE); } str[12] = '\0'; f = fmemopen(str, len, "a+"); if (f == NULL) { warn("failed to open fmemopen stream"); free(str); return (B_FALSE); } ret = memstream_check_seek(f, 12, SEEK_CUR); ret2 = memstream_check_seek(f, 12, SEEK_END); (void) fclose(f); free(str); return (ret && ret2); } static boolean_t fmemopen_defseek_ap_null(void) { FILE *f; size_t len = strlen(fmemopen_str1); boolean_t ret, ret2; f = fmemopen(NULL, len, "a+"); if (f == NULL) { warn("failed to open fmemopen stream"); return (B_FALSE); } ret = memstream_check_seek(f, 0, SEEK_CUR); ret2 = memstream_check_seek(f, 0, SEEK_END); (void) fclose(f); return (ret && ret2); } static boolean_t fmemopen_read_eof_fgetc(void) { FILE *f; size_t len = strlen(fmemopen_str1); boolean_t ret = B_TRUE, ret2, ret3; f = fmemopen(fmemopen_str1, len, "r"); if (f == NULL) { warn("failed to open fmemopen stream"); return (B_FALSE); } while (fgetc(f) != EOF) { continue; } if (feof(f) == 0) { warnx("stream not at end of EOF"); ret = B_FALSE; } ret2 = memstream_check_seek(f, len, SEEK_CUR); ret3 = memstream_check_seek(f, len, SEEK_END); (void) fclose(f); return (ret && ret2 && ret3); } static boolean_t fmemopen_read_eof_fgets(void) { FILE *f; size_t len = strlen(fmemopen_str1); boolean_t ret = B_TRUE, ret2, ret3; char buf[BUFSIZ]; f = fmemopen(fmemopen_str1, len, "r"); if (f == NULL) { warn("failed to open fmemopen stream"); return (B_FALSE); } while (fgets(buf, sizeof (buf), f) != NULL) { continue; } if (feof(f) == 0) { warnx("stream not at end of EOF"); ret = B_FALSE; } ret2 = memstream_check_seek(f, len, SEEK_CUR); ret3 = memstream_check_seek(f, len, SEEK_END); (void) fclose(f); return (ret && ret2 && ret3); } static boolean_t fmemopen_read_eof_fread(void) { FILE *f; size_t len = strlen(fmemopen_str1); boolean_t ret = B_TRUE, ret2, ret3; char buf[BUFSIZ]; f = fmemopen(fmemopen_str1, len, "r"); if (f == NULL) { warn("failed to open fmemopen stream"); return (B_FALSE); } while (fread(buf, sizeof (buf), 1, f) != 0) { continue; } if (feof(f) == 0) { warnx("stream not at end of EOF"); ret = B_FALSE; } ret2 = memstream_check_seek(f, len, SEEK_CUR); ret3 = memstream_check_seek(f, len, SEEK_END); (void) fclose(f); return (ret && ret2 && ret3); } static boolean_t fmemopen_read_eof_fread2(void) { FILE *f; size_t len = strlen(fmemopen_str1); boolean_t ret = B_TRUE, ret2, ret3; char buf[BUFSIZ]; f = fmemopen(fmemopen_str1, len, "r"); if (f == NULL) { warn("failed to open fmemopen stream"); return (B_FALSE); } while (fread(buf, 1, sizeof (buf), f) != 0) { continue; } if (feof(f) == 0) { warnx("stream not at end of EOF"); ret = B_FALSE; } ret2 = memstream_check_seek(f, len, SEEK_CUR); ret3 = memstream_check_seek(f, len, SEEK_END); (void) fclose(f); return (ret && ret2 && ret3); } static boolean_t fmemopen_bad_seeks(void) { FILE *f; boolean_t ret = B_TRUE; size_t len = strlen(fmemopen_str1); uint_t i; struct { int ret; int whence; long off; long newpos; } seeks[] = { { 0, SEEK_CUR, 0, 0 }, { -1, SEEK_CUR, -1, 0 }, { -1, SEEK_SET, -5, 0 }, { -1, SEEK_END, -128, 0 }, { -1, SEEK_END, 1, 0 }, { -1, SEEK_SET, 128, 0 }, { 0, SEEK_SET, 16, 16 }, { -1, SEEK_CUR, -20, 16 }, { 0, SEEK_CUR, -16, 0 }, }; f = fmemopen(fmemopen_str1, len, "r"); if (f == NULL) { warn("failed to open fmemopen stream"); return (B_FALSE); } for (i = 0; i < ARRAY_SIZE(seeks); i++) { int r; r = fseek(f, seeks[i].off, seeks[i].whence); if (r != seeks[i].ret) { warnx("found bad return value for seek %d/%ld, " "expected %d, found %d", seeks[i].whence, seeks[i].off, seeks[i].ret, r); ret = B_FALSE; } ret &= memstream_check_seek(f, seeks[i].newpos, SEEK_CUR); } (void) fclose(f); return (ret); } static boolean_t fmemopen_open_trunc(void) { char buf[16]; FILE *f; boolean_t ret = B_TRUE; (void) memset(buf, 'a', sizeof (buf)); f = fmemopen(buf, sizeof (buf), "w+"); if (f == NULL) { warn("failed to create fmemopen stream"); return (B_FALSE); } if (buf[0] != '\0') { warnx("w+ mode didn't truncate the buffer"); ret = B_FALSE; } (void) fclose(f); return (ret); } static boolean_t fmemopen_write_nul(void) { char buf[BUFSIZ]; FILE *f; boolean_t ret = B_TRUE; size_t npos = sizeof (buf) - 32; (void) memset(buf, 'a', sizeof (buf)); f = fmemopen(buf, sizeof (buf), "w"); if (f == NULL) { warn("failed to create fmemopen stream"); return (B_FALSE); } if (fputc('b', f) != 'b') { warn("failed to write 'b' character to stream"); ret = B_FALSE; } if (fflush(f) != 0) { warn("failed to flush stream"); ret = B_FALSE; } if (buf[0] != 'b' || buf[1] != '\0') { warn("stream didn't properly write character and nul"); ret = B_FALSE; } if (fseek(f, sizeof (buf) - 32, SEEK_SET)) { warn("failed to seek stream"); ret = B_FALSE; } if (fflush(f) != 0) { warn("failed to flush stream"); ret = B_FALSE; } if (buf[npos] != 'a' || buf[npos - 1] != 'a' || buf[npos + 1] != 'a') { warnx("seeking incorrectly inserted a nul"); ret = B_FALSE; } (void) fclose(f); if (buf[npos] != 'a' || buf[npos - 1] != 'a' || buf[npos + 1] != 'a') { warnx("seeking incorrectly inserted a nul"); ret = B_FALSE; } return (ret); } static boolean_t fmemopen_append_nul(void) { char buf[32], buf2[32]; FILE *f; boolean_t ret = B_TRUE; (void) memset(buf, 'a', sizeof (buf)); buf[8] = '\0'; f = fmemopen(buf, sizeof (buf), "a"); if (f == NULL) { warn("failed to create fmemopen stream"); return (B_FALSE); } if (fputc('b', f) != 'b') { warn("failed to write 'b' character to stream"); ret = B_FALSE; } if (fflush(f) != 0) { warn("failed to flush stream"); ret = B_FALSE; } if (buf[8] != 'b' || buf[9] != '\0') { warn("stream didn't properly write character and nul"); ret = B_FALSE; } /* * Append mode shouldn't insert a NUL if we write the entire buffer. */ (void) memset(buf2, 'b', sizeof (buf2)); if (fwrite(buf2, sizeof (buf2) - ftell(f), 1, f) != 1) { warn("failed to write buf2"); ret = B_FALSE; } if (fflush(f) != 0) { warn("failed to flush stream"); ret = B_FALSE; } if (buf[sizeof (buf) - 1] != 'b') { warnx("found invalid character: %x", buf[sizeof (buf) - 1]); ret = B_FALSE; } (void) fclose(f); if (buf[sizeof (buf) - 1] != 'b') { warnx("found invalid character: %x", buf[sizeof (buf) - 1]); ret = B_FALSE; } return (ret); } static boolean_t fmemopen_read_nul(void) { char buf[32]; FILE *f; (void) memset(buf, '\0', sizeof (buf)); f = fmemopen(buf, sizeof (buf), "r+"); if (f == NULL) { warn("failed to create fmemopen stream"); return (B_FALSE); } if (fgetc(f) != '\0') { warnx("failed to read nul character"); return (B_FALSE); } (void) fclose(f); return (B_TRUE); } static boolean_t open_memstream_def_seek(void) { char *c; size_t s; FILE *f; boolean_t ret, ret2; if ((f = open_memstream(&c, &s)) == NULL) { warn("failed to call open_memstream()"); return (B_FALSE); } ret = memstream_check_seek(f, 0, SEEK_CUR); ret2 = memstream_check_seek(f, 0, SEEK_END); (void) fclose(f); free(c); return (ret && ret2); } static boolean_t open_wmemstream_def_seek(void) { wchar_t *c; size_t s; FILE *f; boolean_t ret, ret2; if ((f = open_wmemstream(&c, &s)) == NULL) { warn("failed to call open_wmemstream()"); return (B_FALSE); } ret = memstream_check_seek(f, 0, SEEK_CUR); ret2 = memstream_check_seek(f, 0, SEEK_END); (void) fclose(f); free(c); return (ret && ret2); } static boolean_t open_memstream_no_read(void) { char *c; size_t s; FILE *f; boolean_t ret = B_TRUE; if ((f = open_memstream(&c, &s)) == NULL) { warn("failed to call open_memstream()"); return (B_FALSE); } if (fgetc(f) != EOF) { warnx("read succeeded when it should have failed"); ret = B_FALSE; } if (errno != EBADF) { warnx("found wrong errno, expected %d, found %d", EBADF, errno); ret = B_FALSE; } (void) fclose(f); free(c); return (ret); } static boolean_t open_wmemstream_no_read(void) { wchar_t *c; size_t s; FILE *f; boolean_t ret = B_TRUE; if ((f = open_wmemstream(&c, &s)) == NULL) { warn("failed to call open_wmemstream()"); return (B_FALSE); } if (fgetc(f) != EOF) { warnx("read succeeded when it should have failed"); ret = B_FALSE; } if (errno != EBADF) { warnx("found wrong errno, expected %d, found %d", EBADF, errno); ret = B_FALSE; } (void) fclose(f); free(c); return (ret); } static boolean_t open_memstream_bad_flush(void) { char *c; size_t s; FILE *f; boolean_t ret = B_TRUE; if ((f = open_memstream(&c, &s)) == NULL) { warn("failed to call open_memstream()"); return (B_FALSE); } /* Force the buffer to exist */ if (fputc('a', f) != 'a') { warn("failed to write character to buffer"); ret = B_FALSE; } if (fseek(f, BUFSIZ * 2 + 1, SEEK_END) != 0) { warn("Failed to seek beyond buffer size"); ret = B_FALSE; } umem_setmtbf(1); if (fputc('a', f) != 'a') { warn("failed to write character to buffer"); ret = B_FALSE; } if (fflush(f) != EOF) { warnx("fflush succeeded when it should have failed"); } if (errno != EAGAIN) { warnx("bad errno, found %d, expected %d", errno, EAGAIN); } umem_setmtbf(0); (void) fclose(f); free(c); return (ret); } static boolean_t open_wmemstream_bad_flush(void) { wchar_t *c; size_t s; FILE *f; boolean_t ret = B_TRUE; if ((f = open_wmemstream(&c, &s)) == NULL) { warn("failed to call open_wmemstream()"); return (B_FALSE); } /* Force the buffer to exist */ if (fputwc('a', f) != 'a') { warn("failed to write character to buffer"); ret = B_FALSE; } if (fseek(f, BUFSIZ * 2 + 1, SEEK_END) != 0) { warn("Failed to seek beyond buffer size"); ret = B_FALSE; } umem_setmtbf(1); if (fputc('a', f) != 'a') { warn("failed to write character to buffer"); ret = B_FALSE; } if (fflush(f) != EOF) { warnx("fflush succeeded when it should have failed"); } if (errno != EAGAIN) { warnx("bad errno, found %d, expected %d", errno, EAGAIN); } umem_setmtbf(0); (void) fclose(f); free(c); return (ret); } static boolean_t memstream_bad_seek(void) { FILE *f, *fw; boolean_t ret = B_TRUE; uint_t i; char *c; wchar_t *w; size_t s1, s2; struct { int ret; int whence; long off; long newpos; } seeks[] = { { 0, SEEK_CUR, 0, 0 }, { -1, SEEK_CUR, -1, 0 }, { -1, SEEK_SET, -5, 0 }, { -1, SEEK_END, -5, 0 }, { 0, SEEK_SET, 16, 16 }, { -1, SEEK_CUR, -20, 16 }, { 0, SEEK_CUR, -16, 0 }, }; f = open_memstream(&c, &s1); fw = open_wmemstream(&w, &s2); if (f == NULL || fw == NULL) { warnx("failed to create memory streams"); return (B_FALSE); } for (i = 0; i < ARRAY_SIZE(seeks); i++) { int r; r = fseek(f, seeks[i].off, seeks[i].whence); if (r != seeks[i].ret) { warnx("found bad return value for seek %d/%ld, " "expected %d, found %d", seeks[i].whence, seeks[i].off, seeks[i].ret, r); ret = B_FALSE; } ret &= memstream_check_seek(f, seeks[i].newpos, SEEK_CUR); r = fseek(fw, seeks[i].off, seeks[i].whence); if (r != seeks[i].ret) { warnx("found bad return value for seek %d/%ld, " "expected %d, found %d", seeks[i].whence, seeks[i].off, seeks[i].ret, r); ret = B_FALSE; } ret &= memstream_check_seek(fw, seeks[i].newpos, SEEK_CUR); } (void) fclose(f); (void) fclose(fw); free(c); free(w); return (ret); } static boolean_t open_memstream_append_nul(void) { char *c; size_t s; FILE *f; boolean_t ret = B_TRUE; if ((f = open_memstream(&c, &s)) == NULL) { warn("failed to call open_memstream()"); return (B_FALSE); } if (fputc('a', f) != 'a') { warn("failed to write 'a' to stream"); ret = B_FALSE; } if (fflush(f) != 0) { warn("failed to flush stream"); ret = B_FALSE; } if (c[s] != '\0') { warnx("missing nul character, found %x", c[s]); ret = B_FALSE; } if (fseek(f, arc4random_uniform(2 * BUFSIZ), SEEK_SET) != 0) { warn("failed to seek"); ret = B_FALSE; } if (fflush(f) != 0) { warn("failed to flush stream"); ret = B_FALSE; } if (c[s] != '\0') { warnx("missing nul character, found %x", c[s]); ret = B_FALSE; } (void) fclose(f); free(c); return (ret); } static boolean_t open_wmemstream_append_nul(void) { wchar_t *c; size_t s; FILE *f; boolean_t ret = B_TRUE; if ((f = open_wmemstream(&c, &s)) == NULL) { warn("failed to call open_wmemstream()"); return (B_FALSE); } if (fputwc('a', f) != 'a') { warn("failed to write 'a' to stream"); ret = B_FALSE; } if (fflush(f) != 0) { warn("failed to flush stream"); ret = B_FALSE; } if (c[s] != L'\0') { warnx("missing nul character, found %" _PRIxWC, c[s]); ret = B_FALSE; } if (fseek(f, arc4random_uniform(2 * BUFSIZ), SEEK_SET) != 0) { warn("failed to seek"); ret = B_FALSE; } if (fflush(f) != 0) { warn("failed to flush stream"); ret = B_FALSE; } if (c[s] != L'\0') { warnx("missing nul character, found %" _PRIxWC, c[s]); ret = B_FALSE; } (void) fclose(f); free(c); return (ret); } static boolean_t open_wmemstream_embed_nuls(void) { const char str[] = { 'H', 'e', 'l', 'l', 'o', '\0', 'w', 'o', 'r', 'd' }; const wchar_t wstr[] = { L'H', L'e', L'l', L'l', L'o', L'\0', L'w', L'o', L'r', L'd' }; wchar_t *c; size_t s; FILE *f; boolean_t ret = B_TRUE; if ((f = open_wmemstream(&c, &s)) == NULL) { warn("failed to call open_wmemstream()"); return (B_FALSE); } if (fwrite(str, sizeof (char), ARRAY_SIZE(str), f) == 0) { warn("failed to write data buffer"); ret = B_FALSE; } if (fflush(f) != 0) { warn("failed to flush data buffer"); ret = B_FALSE; } if (ARRAY_SIZE(wstr) != s) { warnx("size mismatch, wrote %zu chars, found %zu chars", ARRAY_SIZE(wstr), s); ret = B_FALSE; } if (bcmp(wstr, c, sizeof (wstr)) != 0) { warnx("data not written in expected format"); ret = B_FALSE; } (void) fclose(f); free(c); return (ret); } static boolean_t open_wmemstream_wide_write(void) { size_t slen = wcslen(wstream_str); wchar_t *c; size_t s; FILE *f; boolean_t ret = B_TRUE; if ((f = open_wmemstream(&c, &s)) == NULL) { warn("failed to call open_wmemstream()"); return (B_FALSE); } if (fputws(wstream_str, f) == -1) { warn("failed to write string"); ret = B_FALSE; } if (fflush(f) != 0) { warn("failed to flush stream"); ret = B_FALSE; } if (s != slen) { warnx("size mismatch, expected %zu chars, but found %zu", slen, s); ret = B_FALSE; } if (wcscmp(wstream_str, c) != 0) { warnx("basic write doesn't match!"); ret = B_FALSE; } ret &= memstream_check_seek(f, slen, SEEK_CUR); ret &= memstream_check_seek(f, slen, SEEK_END); (void) fclose(f); free(c); return (ret); } /* * Make sure that if we seek somewhere and flush that it doesn't cause us to * grow. */ static boolean_t open_wmemstream_seek_grow(void) { size_t slen = wcslen(wstream_str); wchar_t *c; size_t s; FILE *f; boolean_t ret = B_TRUE; if ((f = open_wmemstream(&c, &s)) == NULL) { warn("failed to call open_wmemstream()"); return (B_FALSE); } if (fflush(f) != 0) { warn("failed to flush stream"); ret = B_FALSE; } if (s != 0) { warn("bad initial size"); ret = B_FALSE; } ret &= memstream_check_seek(f, 0, SEEK_CUR); ret &= memstream_check_seek(f, 0, SEEK_END); if (fseek(f, 2048, SEEK_SET) != 0) { warn("failed to seek"); } ret &= memstream_check_seek(f, 2048, SEEK_CUR); if (fflush(f) != 0) { warn("failed to flush stream"); ret = B_FALSE; } if (s != 0) { warnx("bad size after seek"); ret = B_FALSE; } if (fputws(wstream_str, f) == -1) { warn("failed to write string"); ret = B_FALSE; } if (fflush(f) != 0) { warn("failed to flush stream"); ret = B_FALSE; } if (s != slen + 2048) { warnx("size is off after seek and write, found %zu", s); ret = B_FALSE; } ret &= memstream_check_seek(f, s, SEEK_CUR); ret &= memstream_check_seek(f, s, SEEK_END); (void) fclose(f); free(c); return (ret); } static boolean_t open_wmemstream_byte_writes(void) { wchar_t *c; size_t s, len, i; FILE *f; boolean_t ret = B_TRUE; if ((f = open_wmemstream(&c, &s)) == NULL) { warn("failed to call open_wmemstream()"); return (B_FALSE); } /* * Use non-buffered mode so that way we can make sure to detect mbs * state errors right away. */ if (setvbuf(f, NULL, _IONBF, 0) != 0) { warnx("failed to set to non-buffered mode"); ret = B_FALSE; } len = wcslen(wstream_str); for (i = 0; i < len; i++) { char buf[MB_CUR_MAX + 1]; int mblen, curmb; mblen = wctomb(buf, wstream_str[i]); if (mblen == -1) { warn("failed to convert wc %zu", i); ret = B_FALSE; continue; } for (curmb = 0; curmb < mblen; curmb++) { if (fputc(buf[curmb], f) == EOF) { warn("failed to write byte %d of wc %zu", curmb, i); ret = B_FALSE; } } } if (fflush(f) != 0) { warn("failed to flush stream"); ret = B_FALSE; } if (s != len) { warnx("found wrong number of wide characters, expected %zu, " "found %zu", len + 1, s); ret = B_FALSE; } if (wcscmp(c, wstream_str) != 0) { warnx("the wide character strings don't compare equally"); ret = B_FALSE; } (void) fclose(f); free(c); return (ret); } static boolean_t open_wmemstream_bad_seq(void) { wchar_t *c, test = wstr_const[0]; size_t s; FILE *f; char buf[MB_CUR_MAX + 1]; boolean_t ret = B_TRUE; if (wctomb(buf, test) == -1) { warn("failed to convert 光 to multi-byte sequence"); return (B_FALSE); } if ((f = open_wmemstream(&c, &s)) == NULL) { warn("failed to call open_wmemstream()"); return (B_FALSE); } /* * Make sure to use a non-buffered mode so that way writes immediately * get sent to the underlying stream. */ if (setvbuf(f, NULL, _IONBF, 0) != 0) { warnx("failed to set to non-buffered mode"); ret = B_FALSE; } if (fputc(buf[0], f) == EOF) { warn("failed to write 0x%x to buffer", buf[0]); ret = B_FALSE; } if (fputc(buf[0], f) != EOF) { warnx("successfully wrote 0x%x to buffer, but should have " "failed", buf[0]); ret = B_FALSE; } if (errno != EIO) { warnx("found wrong errno, expected EIO, but found 0x%x", errno); ret = B_FALSE; } (void) fclose(f); free(c); return (ret); } static boolean_t open_wmemstream_bad_seq_fflush(void) { wchar_t *c, test = wstr_const[0]; size_t s; FILE *f; char buf[MB_CUR_MAX + 1]; boolean_t ret = B_TRUE; if (wctomb(buf, test) == -1) { warn("failed to convert 光 to multi-byte sequence"); return (B_FALSE); } if ((f = open_wmemstream(&c, &s)) == NULL) { warn("failed to call open_wmemstream()"); return (B_FALSE); } if (fputc(buf[0], f) == EOF) { warn("failed to write 0x%x to buffer", buf[0]); ret = B_FALSE; } if (fputc(buf[0], f) == EOF) { warn("failed to write bad byte 0x%x to buffer", buf[0]); ret = B_FALSE; } if (fflush(f) == 0) { warn("fflush succeeded, expected failure"); ret = B_FALSE; } if (errno != EIO) { warn("found wrong errno, expected EIO, but found 0x%x", errno); ret = B_FALSE; } (void) fclose(f); free(c); return (ret); } /* * When writing individual bytes out, we need to make sure that we don't * incorrectly count buffered data as offsets like we do for other byte oriented * consumers of the ftell family. */ static boolean_t open_wmemstream_ftell(void) { wchar_t *c, test = wstr_const[0]; size_t s, i, wclen; FILE *f; char buf[MB_CUR_MAX + 1]; boolean_t ret = B_TRUE; long loc; if ((wclen = wctomb(buf, test)) == -1) { warn("failed to convert 光 to multi-byte sequence"); return (B_FALSE); } if ((f = open_wmemstream(&c, &s)) == NULL) { warn("failed to call open_wmemstream()"); return (B_FALSE); } if ((loc = ftell(f)) != 0) { warnx("stream at bad loc after start, found %ld, expected 0", loc); ret = B_FALSE; } if (fputwc(test, f) == WEOF) { warn("failed to write wide character to stream"); ret = B_FALSE; } if ((loc = ftell(f)) != 1) { warnx("stream at bad loc after writing a single wide char, " "found %ld, expected 1", loc); ret = B_FALSE; } for (i = 0; i < wclen - 1; i++) { if (fputc(buf[i], f) == EOF) { warn("failed to write mb char 0x%x", buf[i]); ret = B_FALSE; } if ((loc = ftell(f)) != 1) { warnx("stream at bad loc after putting partial mb seq, " "found %ld, expected 1", loc); ret = B_FALSE; } } /* * Only after we advance the final char should it be two. */ if (fputc(buf[i], f) == EOF) { warn("failed to write mb char 0x%x", buf[i]); ret = B_FALSE; } if ((loc = ftell(f)) != 2) { warnx("stream at bad loc after writing a mb seq, " "found %ld, expected 2", loc); ret = B_FALSE; } if (fflush(f) != 0) { warn("failed to flush stream"); ret = B_FALSE; } if (s != 2) { warnx("size of stream is wrong, found %zu, expected 2", s); ret = B_FALSE; } if (s != loc) { warnx("size of buffer, %zu does not match pre-fflush " "ftell loc: %ld", s, loc); ret = B_FALSE; } loc = ftell(f); if (s != loc) { warnx("size of buffer, %zu does not match post-fflush " "ftell loc: %ld", s, loc); ret = B_FALSE; } (void) fclose(f); free(c); return (ret); } typedef struct memstream_test { memstream_test_f mt_func; const char *mt_test; } memstream_test_t; static const memstream_test_t memstream_tests[] = { { fmemopen_badmode, "fmemopen: bad mode argument" }, { fmemopen_zerobuf1, "fmemopen: bad buffer size, valid buf" }, { fmemopen_zerobuf2, "fmemopen: bad buffer size, NULL buf" }, { fmemopen_nullbuf1, "fmemopen: invalid NULL buf, mode: r" }, { fmemopen_nullbuf2, "fmemopen: invalid NULL buf, mode: w" }, { fmemopen_nullbuf3, "fmemopen: invalid NULL buf, mode: a" }, { fmemopen_nullbuf4, "fmemopen: invalid NULL buf, mode: ax" }, { fmemopen_sizemax, "fmemopen: bad open ask for SIZE_MAX bytes" }, { fmemopen_cantalloc, "fmemopen: simulate malloc failure at open" }, { open_memstream_badbuf, "open_memstream: bad buf" }, { open_memstream_badsize, "open_memstream: bad size" }, { open_memstream_allnull, "open_memstream: bad buf and size" }, { open_memstream_cantalloc, "open_memstream: simulate malloc failure " "at " "open" }, { open_wmemstream_badbuf, "open_wmemstream: bad buf" }, { open_wmemstream_badsize, "open_wmemstream: bad size" }, { open_wmemstream_allnull, "open_wmemstream: bad buf and size" }, { open_wmemstream_cantalloc, "open_wmemstream: simulate malloc " "failure at open" }, { fmemopen_fill_default, "fmemopen: write beyond end of buffer: putc " "(buf smaller than BUFSIZ)" }, { fmemopen_fill_lbuf, "fmemopen: write beyond end of buffer: putc " "(line buffering)" }, { fmemopen_fill_nobuf, "fmemopen: write beyond end of buffer: putc " "(no stdio buffering)" }, { fmemopen_fwrite_default, "fmemopen: write beyond end of buffer: " "fwrite (buf smaller than BUFSIZ)" }, { fmemopen_fwrite_lbuf, "fmemopen: write beyond end of buffer: fwrite " "(line buffering)" }, { fmemopen_fwrite_nobuf, "fmemopen: write beyond end of buffer: fwrite " "(no stdio buffering)" }, { fmemopen_alt_fwrite_default, "fmemopen: write beyond end of buffer: " "fwrite 2 (buf smaller than BUFSIZ)" }, { fmemopen_alt_fwrite_lbuf, "fmemopen: write beyond end of buffer: " "fwrite 2 (line buffering)" }, { fmemopen_alt_fwrite_nobuf, "fmemopen: write beyond end of buffer: " "fwrite 2 (no stdio buffering)" }, { fmemopen_fputs_default, "fmemopen: write beyond end of buffer: fputs " "(buf smaller than BUFSIZ)" }, { fmemopen_fputs_lbuf, "fmemopen: write beyond end of buffer: fputs " "(line buffering)" }, { fmemopen_fputs_nobuf, "fmemopen: write beyond end of buffer: fputs " "(no stdio buffering)" }, { fmemopen_defseek_r, "fmemopen: default position and log. size, " "mode: r"}, { fmemopen_defseek_rp, "fmemopen: default position and log. size, " "mode: r+"}, { fmemopen_defseek_w, "fmemopen: default position and log. size, " "mode: w"}, { fmemopen_defseek_wp, "fmemopen: default position and log. size, " "mode: w+"}, { fmemopen_defseek_a, "fmemopen: default position and log. size, " "mode: a"}, { fmemopen_defseek_ap, "fmemopen: default position and log. size, " "mode: a+"}, { fmemopen_defseek_a_nbyte, "fmemopen: default position and log. size, " "mode: a, nul byte"}, { fmemopen_defseek_ap_nbyte, "fmemopen: default position and log. " "size, mode: a+, nul byte"}, { fmemopen_defseek_ap_null, "fmemopen: default position and log. size, " "mode: a+, NULL buf"}, { fmemopen_read_eof_fgetc, "fmemopen: read until EOF with fgetc" }, { fmemopen_read_eof_fgets, "fmemopen: read until EOF with fgets" }, { fmemopen_read_eof_fread, "fmemopen: read until EOF with fread" }, { fmemopen_read_eof_fread2, "fmemopen: read until EOF with fread 2" }, { fmemopen_bad_seeks, "fmemopen: invalid seeks" }, { fmemopen_open_trunc, "fmemopen: w+ mode truncates buffer" }, { fmemopen_write_nul, "fmemopen: NULs properly inserted (w)" }, { fmemopen_append_nul, "fmemopen: NULs properly inserted (a)" }, { fmemopen_read_nul, "fmemopen: read NUL character normally" }, { open_memstream_def_seek, "open_memstream: default position and " "logical size" }, { open_wmemstream_def_seek, "wopen_memstream: default position and " "logical size" }, { open_memstream_no_read, "open_memstream: read doesn't work" }, { open_wmemstream_no_read, "open_wmemstream: read doesn't work" }, { open_memstream_bad_flush, "open_memstream: flush failure due to " "induced memory failure" }, { open_wmemstream_bad_flush, "open_wmemstream: flush failure due to " "induced memory failure" }, { memstream_bad_seek, "open_[w]memstream: bad seeks" }, { open_memstream_append_nul, "open_memstream: appends NULs" }, { open_wmemstream_append_nul, "open_wmemstream: appends NULs" }, { open_wmemstream_embed_nuls, "open_wmemstream: handles embedded " "NULs" }, { open_wmemstream_wide_write, "open_wmemstream: write wide chars" }, { open_wmemstream_seek_grow, "open_wmemstream: seeking doesn't grow" }, { open_wmemstream_byte_writes, "open_wmemstream: Write mb sequences" }, { open_wmemstream_bad_seq, "open_wmemstream: detect bad utf-8 " "sequence" }, { open_wmemstream_bad_seq_fflush, "open_wmemstream: detect bad utf-8 " "sequence 2 (fflush)" }, { open_wmemstream_ftell, "open_wmemstream: ftell buffering behavior" } }; int main(void) { uint_t i; uint_t passes = 0; uint_t ntests = ARRAY_SIZE(memstream_tests); /* * Set a UTF-8 locale to make sure to exercise open_wmemstream()'s * mbstate logic in a more interesting way than ASCII. */ (void) setlocale(LC_ALL, "en_US.UTF-8"); for (i = 0; i < ntests; i++) { boolean_t r; r = memstream_tests[i].mt_func(); (void) fprintf(stderr, "TEST %s: %s\n", r ? "PASSED" : "FAILED", memstream_tests[i].mt_test); if (r) { passes++; } } (void) printf("%d/%d test%s passed\n", passes, ntests, passes > 1 ? "s" : ""); return (passes == ntests ? EXIT_SUCCESS : EXIT_FAILURE); }