From patchwork Fri Aug 9 19:05:04 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: DJ Delorie X-Patchwork-Id: 95605 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 36775385DDC2 for ; Fri, 9 Aug 2024 19:05:39 +0000 (GMT) X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id 671B7385B532 for ; Fri, 9 Aug 2024 19:05:13 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 671B7385B532 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 671B7385B532 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1723230315; cv=none; b=XnNrx6hzbxJXsbdduj6J6yQ/dXEpHw1a4XIct1uae9N1You1ePPqU9PExRoLbyVxEJWCv7io0ExWQCeQoVWY0afbXKkUoXAs3mnXxaLjpIaXk1AwCSw15uZSUV8TWkWQnZTH2AM9M728qxfAlZ0oj6eJ/cgJVJHo1HbjSIxqKoE= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1723230315; c=relaxed/simple; bh=6lDGoNiRJCai335/vyXMTGoEeqFFI6tGUYUsd4Q+Hs4=; h=DKIM-Signature:Date:Message-Id:From:To:Subject; b=aAHB4odiLIjC9N4z6rNHTmQOFQsDpxY+aqrSzWW5Rs7hvpG5ZYUSajPcWZI8BfVPCj/tr5Hx4zgkOAixz0DX8D1n5F530o4bToG6ICYUu3Vl2XFbPafYH3QjaKC7eU3OZpEJcsAv42gpaSvaTHxsXdsysxYuPHEG/0q6n7V0hPs= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1723230313; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:content-type:content-type; bh=iwgq22TjXgEDY976pkthuHhqAbj9S0rcRqA8b/jxNtA=; b=QF/csmYk/TVM92VPokS93wayfuDO1oGVKt+JHA9KJ2fU7dU7hrf7wO6yCMegBi+SbCWiiV clwTu/0Op6umsk5UDcmqC1wa7xW1pN4SMzG3xcCGrHN6NlHXQXgkbczCFYtcbzbrWfmnKg BFx9VUC9SiuXHVFBrf8pVYdrq/HDOL8= Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-515-SZdluc9DNsuX-KXbF6CADA-1; Fri, 09 Aug 2024 15:05:11 -0400 X-MC-Unique: SZdluc9DNsuX-KXbF6CADA-1 Received: from mx-prod-int-02.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-02.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.15]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 6E01A1944CF2 for ; Fri, 9 Aug 2024 19:05:10 +0000 (UTC) Received: from greed.delorie.com (unknown [10.22.10.97]) by mx-prod-int-02.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id F194C1956089 for ; Fri, 9 Aug 2024 19:05:09 +0000 (UTC) Received: from greed.delorie.com.redhat.com (localhost [127.0.0.1]) by greed.delorie.com (8.16.1/8.16.1) with ESMTP id 479J54lj2030112 for ; Fri, 9 Aug 2024 15:05:04 -0400 Date: Fri, 09 Aug 2024 15:05:04 -0400 Message-Id: From: DJ Delorie To: libc-alpha@sourceware.org Subject: [patch v1] fgets: more tests X-Scanned-By: MIMEDefang 3.0 on 10.30.177.15 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-10.2 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_ASCII_DIVIDERS, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org make test: PASS: stdio-common/tst-fgets2 *.out looks like: testing base operation...PASS testing zero size file...PASS testing zero size buffer...PASS testing NULL buffer...PASS testing embedded NUL...PASS testing writable stream...PASS testing closed fd stream...PASS I left in two more tests that check for valid FILE* parameters, but those checks are conditional in the libc sources. I made the tests conditional the same way, although I don't see a way of enabling these either in glibc or here. The tests would otherwise just always segfault. - - - - - Add more tests for unusual situations fgets() might see: * zero size file * zero sized buffer * NULL buffer * NUL data * writable stream * closed stream diff --git a/stdio-common/Makefile b/stdio-common/Makefile index e4f0146d2c..2c54badf3b 100644 --- a/stdio-common/Makefile +++ b/stdio-common/Makefile @@ -209,6 +209,7 @@ tests := \ tst-fdopen \ tst-ferror \ tst-fgets \ + tst-fgets2 \ tst-fileno \ tst-fmemopen \ tst-fmemopen2 \ diff --git a/stdio-common/tst-fgets2.c b/stdio-common/tst-fgets2.c new file mode 100644 index 0000000000..7a76b181c4 --- /dev/null +++ b/stdio-common/tst-fgets2.c @@ -0,0 +1,354 @@ +/* Test for additional fgets error handling. + Copyright (C) 2024 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#define _GNU_SOURCE 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/*------------------------------------------------------------*/ +/* Implementation of our FILE stream backend. */ + +static int bytes_read; +static int bytes_written; +static int cookie_valid = 0; +struct Cookie { + const char *buffer; + int bufptr; + int bufsz; +}; + +#define VALIDATE_COOKIE() if (! cookie_valid) { \ + FAIL ("call to %s after file closed\n", __FUNCTION__); \ + return -1; \ + } + +static ssize_t +io_read (void *vcookie, char *buf, size_t size) +{ + struct Cookie *cookie = (struct Cookie *) vcookie; + + VALIDATE_COOKIE (); + + if (size > cookie->bufsz - cookie->bufptr) + size = cookie->bufsz - cookie->bufptr; + + memcpy (buf, cookie->buffer + cookie->bufptr, size); + cookie->bufptr += size; + bytes_read += size; + return size; +} + +static ssize_t +io_write (void *vcookie, const char *buf, size_t size) +{ + VALIDATE_COOKIE (); + + bytes_written += size; + return -1; +} + +static int +io_seek (void *vcookie, off64_t *position, int whence) +{ + VALIDATE_COOKIE (); + return -1; +} + +static int +io_clean (void *vcookie) +{ + struct Cookie *cookie = (struct Cookie *) vcookie; + + VALIDATE_COOKIE (); + + cookie->buffer = NULL; + cookie->bufsz = 0; + cookie->bufptr = 0; + + cookie_valid = 0; + free (cookie); + return 0; +} + +cookie_io_functions_t io_funcs = { + .read = io_read, + .write = io_write, + .seek = io_seek, + .close = io_clean +}; + +FILE * +io_open (const char *buffer, int buflen, const char *mode, void **vcookie) +{ + FILE *f; + struct Cookie *cookie; + + cookie = (struct Cookie *) xcalloc (1, sizeof (struct Cookie)); + *vcookie = cookie; + cookie_valid = 1; + + cookie->buffer = buffer; + cookie->bufsz = buflen; + bytes_read = 0; + bytes_written = 0; + + f = fopencookie (cookie, mode, io_funcs); + if (f == NULL) + { + perror ("fopencookie"); + exit (1); + } + + return f; +} + +/*------------------------------------------------------------*/ +/* Convenience functions. */ + +static char * +hex (const char *b, int s) +{ + static int bi = 0; + static char buf[3][100]; + static char digit[] = "0123456789ABCDEF"; + char *bp; + if (s > 30) + exit (1); + bi = (bi+1)%3; + bp = buf[bi]; + while (s > 0) + { + if (isgraph (*b)) + *bp ++ = *b; + else if (*b == '\n') + { + *bp ++ = '\\'; + *bp ++ = 'n'; + } + else if (*b == '\0') + { + *bp ++ = '\\'; + *bp ++ = '0'; + } + else + { + *bp ++ = 'x'; + *bp ++ = digit[(*b >> 4) & 0x0f]; + *bp ++ = digit[*b & 0x0f]; + } + b ++; + s --; + } + *bp = 0; + return buf[bi]; +} + +#define my_open(s,l,m) io_open (s, l, m, (void *) &cookie) + +/*------------------------------------------------------------*/ +/* The test cases. */ + +static int +do_test (void) +{ + FILE *f; + struct Cookie *cookie; + char buf [100]; + char *str; + + printf ("testing base operation..."); + f = my_open ("hello\n", 6, "r"); + memset (buf, 0x11, sizeof (buf)); + str = fgets (buf, 100, f); + if (str == NULL) + { + FAIL ("FAIL (returned NULL)\n"); + } + else if (memcmp (str, "hello\n\0", 7) != 0) + { + FAIL ("FAIL (returned %s instead of %s)\n", hex (str, 7), hex ("hello\n\0", 7)); + } + else if (bytes_read != 6) + { + FAIL ("FAIL (%d bytes read instead of %d)\n", bytes_read, 6); + } + else + printf ("PASS\n"); + fclose (f); + + printf ("testing zero size file..."); + f = my_open ("hello\n", 0, "r"); + memset (buf, 0x11, sizeof (buf)); + str = fgets (buf, 100, f); + if (str != NULL) + { + FAIL ("FAIL (returned %s instead of NULL)\n", hex (str, strlen (str))); + } + else if (bytes_read != 0) + { + printf ("FAIL (%d bytes read instead of %d)\n", bytes_read, 0); + } + else + printf ("PASS\n"); + fclose (f); + + printf ("testing zero size buffer..."); + f = my_open ("hello\n", 6, "r"); + memset (buf, 0x11, sizeof (buf)); + str = fgets (buf, 0, f); + if (str != NULL) + { + FAIL ("FAIL (returned %s instead of NULL)\n", hex (str, strlen (str))); + } + else if (bytes_read != 0) + { + printf ("FAIL (%d bytes read instead of %d)\n", bytes_read, 0); + } + else + printf ("PASS\n"); + fclose (f); + + printf ("testing NULL buffer..."); + f = my_open ("hello\n", 0, "r"); + memset (buf, 0x11, sizeof (buf)); + + DIAG_PUSH_NEEDS_COMMENT; + /* We're intentionally passing an invalid size here. */ + DIAG_IGNORE_NEEDS_COMMENT (7, "-Wnonnull"); + str = fgets (NULL, 100, f); + DIAG_POP_NEEDS_COMMENT; + + if (str != NULL) + { + FAIL ("FAIL (returned %s instead of NULL)\n", hex (str, strlen (str))); + } + else if (bytes_read != 0) + { + FAIL ("FAIL (%d bytes read instead of %d)\n", bytes_read, 0); + } + else + printf ("PASS\n"); + fclose (f); + + printf ("testing embedded NUL..."); + f = my_open ("hel\0lo\n", 7, "r"); + memset (buf, 0x11, sizeof (buf)); + str = fgets (buf, 100, f); + if (str == NULL) + { + FAIL ("FAIL (returned NULL)\n"); + } + else if (memcmp (str, "hel\0lo\n\0", 8) != 0) + { + FAIL ("FAIL (returned %s instead of %s)\n", hex (str, 8), hex ("hel\0lo\n\0", 8)); + } + else if (bytes_read != 7) + { + FAIL ("FAIL (%d bytes read instead of %d)\n", bytes_read, 7); + } + else + printf ("PASS\n"); + fclose (f); + + printf ("testing writable stream..."); + f = my_open ("hel\0lo\n", 7, "w"); + memset (buf, 0x11, sizeof (buf)); + str = fgets (buf, 100, f); + if (str != NULL) + { + FAIL ("FAIL (returned %s instead of NULL)\n", hex (str, strlen (str))); + } + else if (bytes_read != 0) + { + FAIL ("FAIL (%d bytes read instead of %d)\n", bytes_read, 0); + } + else + printf ("PASS\n"); + fclose (f); + + printf ("testing closed fd stream..."); + int fd = open ("/dev/null", O_RDONLY); + f = fdopen (fd, "r"); + close (fd); + memset (buf, 0x11, sizeof (buf)); + str = fgets (buf, 100, f); + if (str != NULL) + { + FAIL ("FAIL (returned %s instead of NULL)\n", hex (str, strlen (str))); + } + else if (bytes_read != 0) + { + FAIL ("FAIL (%d bytes read instead of %d)\n", bytes_read, 0); + } + else + printf ("PASS\n"); + fclose (f); + +#ifdef IO_DEBUG + /* These tests only pass if glibc is built with -DIO_DEBUG. */ + + printf ("testing NULL descriptor..."); + memset (buf, 0x11, sizeof (buf)); + str = fgets (buf, 100, NULL); + if (str != NULL) + { + FAIL ("FAIL (returned %s instead of NULL)\n", hex (str, strlen (str))); + } + else if (bytes_read != 0) + { + FAIL ("FAIL (%d bytes read instead of %d)\n", bytes_read, 0); + } + else + printf ("PASS\n"); + + printf ("testing closed descriptor..."); + f = my_open ("hello\n", 7, "r"); + fclose (f); + memset (buf, 0x11, sizeof (buf)); + str = fgets (buf, 100, f); + if (str != NULL) + { + FAIL ("FAIL (returned %s instead of NULL)\n", hex (str, strlen (str))); + } + else if (bytes_read != 0) + { + FAIL ("FAIL (%d bytes read instead of %d)\n", bytes_read, 0); + } + else + printf ("PASS\n"); +#endif + + return 0; +} + +#include