mirror of
				https://sourceware.org/git/glibc.git
				synced 2025-11-03 20:53:13 +03:00 
			
		
		
		
	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>
		
			
				
	
	
		
			234 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			234 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* 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>
 |