diff options
author | Tulio Magno Quites Machado Filho <tuliom@redhat.com> | 2025-01-28 15:37:44 -0300 |
---|---|---|
committer | Tulio Magno Quites Machado Filho <tuliom@redhat.com> | 2025-01-28 15:37:44 -0300 |
commit | 1515f74fd81035a79861cd9fa12053fa9450ec65 (patch) | |
tree | 5363b32f80600a3b2710c32673435dd69c3f1d3c /stdio-common | |
parent | 596a61cf6b51ce2d58b8ca4e1d1f4fdfe1440dbc (diff) | |
download | glibc-1515f74fd81035a79861cd9fa12053fa9450ec65.zip glibc-1515f74fd81035a79861cd9fa12053fa9450ec65.tar.gz glibc-1515f74fd81035a79861cd9fa12053fa9450ec65.tar.bz2 |
libio: Add a new fwrite test that evaluates partial writes
Test if the file-position is correctly updated when fwrite tries to
flush its internal cache but is not able to completely write all items.
Reviewed-by: DJ Delorie <dj@redhat.com>
Diffstat (limited to 'stdio-common')
-rw-r--r-- | stdio-common/Makefile | 1 | ||||
-rw-r--r-- | stdio-common/tst-fwrite-pos.c | 233 |
2 files changed, 234 insertions, 0 deletions
diff --git a/stdio-common/Makefile b/stdio-common/Makefile index 0b9f85e..1e7bca6 100644 --- a/stdio-common/Makefile +++ b/stdio-common/Makefile @@ -264,6 +264,7 @@ tests := \ tst-fwrite-memstrm \ tst-fwrite-overflow \ tst-fwrite-pipe \ + tst-fwrite-pos \ tst-fwrite-ro \ tst-getline \ tst-getline-enomem \ diff --git a/stdio-common/tst-fwrite-pos.c b/stdio-common/tst-fwrite-pos.c new file mode 100644 index 0000000..3923490 --- /dev/null +++ b/stdio-common/tst-fwrite-pos.c @@ -0,0 +1,233 @@ +/* Test if fwrite returns consistent values on partial writes. + Copyright (C) 2025 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 + <https://www.gnu.org/licenses/>. */ + +#include <errno.h> +/* stdio.h provides BUFSIZ, which is the size of fwrite's internal buffer. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <support/check.h> +#include <support/fuse.h> +#include <support/support.h> +#include <support/xstdio.h> +#include <support/temp_file.h> + +/* Length of the buffer in bytes. */ +#define INBUF_SIZE (BUFSIZ) + +/* Amount of bytes written to fwrite's internal cache that trigger a + flush. */ +#define CACHE_THRESHOLD (BUFSIZ / 2) + +#define ITERATIONS 1000 + +/* Maximum number of bytes written during a partial write. */ +#define PARTIAL_BYTES 4 + +#define EXPECT_EVENT(opcode, state, expected_state) \ + { \ + if (state != expected_state) \ + { \ + char *s = support_fuse_opcode (opcode); \ + FAIL ("unexpected event %s at state %d", s, state); \ + free (s); \ + } \ + } + +/* The goal of this test is to check that file position of a file stream is + correctly updated when write () returns a partial write. + The file system simulates pseudorandom partial writes while the test is + running. + Meanwhile the main thread calls fwrite () with a large object first and + small objects later. The usage of a large enough object ensures that + fwrite's internal cache is full enough, without triggering a write to file. + Subsequent calls to fwrite are guaranteed to trigger a write to file. */ + +static void +fuse_thread (struct support_fuse *f, void *closure) +{ + struct fuse_in_header *inh; + int state = 0; + while ((inh = support_fuse_next (f)) != NULL) + { + { + char *opcode = support_fuse_opcode (inh->opcode); + printf ("info: (T) event %s(%llu) len=%u nodeid=%llu\n", + opcode, (unsigned long long int) inh->unique, inh->len, + (unsigned long long int) inh->nodeid); + free (opcode); + } + + /* Handle mountpoint and basic directory operation for the root (1). */ + if (support_fuse_handle_mountpoint (f) + || (inh->nodeid == 1 && support_fuse_handle_directory (f))) + continue; + + switch (inh->opcode) + { + case FUSE_LOOKUP: + EXPECT_EVENT (inh->nodeid, state, 0); + state++; + support_fuse_reply_error (f, ENOENT); + break; + case FUSE_CREATE: + EXPECT_EVENT (inh->nodeid, state, 1); + state++; + struct fuse_entry_out *entry; + struct fuse_open_out *open; + support_fuse_prepare_create (f, 2, &entry, &open); + entry->attr.mode = S_IFREG | 0600; + support_fuse_reply_prepared (f); + break; + case FUSE_GETXATTR: + /* We don't need to support extended attributes in this test. */ + support_fuse_reply_error (f, ENOSYS); + break; + case FUSE_GETATTR: + /* Happens after open. */ + if (inh->nodeid == 2) + { + struct fuse_attr_out *out = support_fuse_prepare_attr (f); + out->attr.mode = S_IFREG | 0600; + out->attr.size = 0; + support_fuse_reply_prepared (f); + } + else + support_fuse_reply_error (f, ENOENT); + break; + case FUSE_WRITE: + if (inh->nodeid == 2) + { + struct fuse_write_out out; + if (state > 1 && state < ITERATIONS + 2) + { + /* The 2nd and subsequent calls to fwrite () trigger a + flush of fwrite's internal cache. Simulate a partial + write of up to PARTIAL_BYTES bytes. */ + out.padding = 0; + out.size = 1 + rand () % PARTIAL_BYTES, + state++; + support_fuse_reply (f, &out, sizeof (out)); + } + else if (state >= ITERATIONS + 2) + { + /* This request is expected to come from fflush (). Copy + all the data successfully. This may be executed more + than once. */ + struct fuse_write_in *p = support_fuse_cast (WRITE, inh); + out.padding = 0; + out.size = p->size, + state++; + support_fuse_reply (f, &out, sizeof (out)); + } + else + support_fuse_reply_error (f, EIO); + } + else + support_fuse_reply_error (f, EIO); + break; + case FUSE_FLUSH: + case FUSE_RELEASE: + TEST_COMPARE (inh->nodeid, 2); + support_fuse_reply_empty (f); + break; + default: + FAIL ("unexpected event %s", support_fuse_opcode (inh->opcode)); + support_fuse_reply_error (f, EIO); + } + } +} + +static int +do_test (void) +{ + char *in; + int i; + size_t written; + + _Static_assert (CACHE_THRESHOLD <= INBUF_SIZE, + "the input buffer must be larger than the cache threshold"); + /* Avoid filling up fwrite's cache. */ + _Static_assert (CACHE_THRESHOLD - 1 + PARTIAL_BYTES * ITERATIONS <= BUFSIZ, + "fwrite's cache must fit all data written"); + + support_fuse_init (); + struct support_fuse *fs = support_fuse_mount (fuse_thread, NULL); + + /* Create and open a temporary file in the fuse mount point. */ + char *fname = xasprintf ("%s/%sXXXXXX", support_fuse_mountpoint (fs), + "tst-fwrite-fuse"); + int fd = mkstemp (fname); + TEST_VERIFY_EXIT (fd != -1); + FILE *f = fdopen (fd, "w"); + TEST_VERIFY_EXIT (f != NULL); + + /* Allocate an input array that will be written to the temporary file. */ + in = xmalloc (INBUF_SIZE); + for (i = 0; i < INBUF_SIZE; i++) + in[i] = i % 0xff; + + /* Ensure the file position indicator is at the beginning of the stream. */ + TEST_COMPARE (ftell (f), 0); + + /* Try to fill as most data to the cache of the file stream as possible + with a single large object. + All data is expected to be written to the cache. + No errors are expected from this. */ + TEST_COMPARE (fwrite (in, CACHE_THRESHOLD - 1, 1, f), 1); + TEST_COMPARE (ferror (f), 0); + written = CACHE_THRESHOLD - 1; + + /* Ensure the file position indicator advanced correctly. */ + TEST_COMPARE (ftell (f), written); + + for (i = 0; i < ITERATIONS; i++) + { + /* Write an extra object of size PARTIAL_BYTES that triggers a write to + disk. Our FS will write at most PARTIAL_BYTES bytes to the file + instead of all the data. By writting PARTIAL_BYTES, we guarantee + the amount of data in the cache will never decrease below + CACHE_THRESHOLD. + No errors are expected. */ + TEST_COMPARE (fwrite (in, PARTIAL_BYTES, 1, f), 1); + TEST_COMPARE (ferror (f), 0); + written += PARTIAL_BYTES; + + /* Ensure the file position indicator advanced correctly. */ + TEST_COMPARE (ftell (f), written); + } + + /* Flush the rest of the data. */ + TEST_COMPARE (fflush (f), 0); + TEST_COMPARE (ferror (f), 0); + + /* Ensure the file position indicator was not modified. */ + TEST_COMPARE (ftell (f), written); + + /* In case an unexpected error happened, clear it before exiting. */ + if (ferror (f)) + clearerr (f); + + xfclose (f); + free (fname); + free (in); + support_fuse_unmount (fs); + return 0; +} + +#include <support/test-driver.c> |