/* FUSE-based test for mkstemp. Parallel collision statistics.
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
. */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* The number of subprocesses that call mkstemp. */
static pid_t processes[4];
/* Enough space to record the expected number of replies (62**3) for
each process. */
enum { results_allocated = array_length (processes) * 62 * 62 * 62 };
/* The thread will store the results there. */
static uint64_t *results;
/* Currently used part of the results array. */
static size_t results_used;
/* Fail with EEXIST (so that mkstemp tries again). Record observed
names for later statistical analysis. */
static void
fuse_thread (struct support_fuse *f, void *closure)
{
struct fuse_in_header *inh;
while ((inh = support_fuse_next (f)) != NULL)
{
if (support_fuse_handle_mountpoint (f)
|| (inh->nodeid == 1 && support_fuse_handle_directory (f)))
continue;
if (inh->opcode != FUSE_LOOKUP || results_used >= results_allocated)
{
support_fuse_reply_error (f, EIO);
continue;
}
char *name = support_fuse_cast (LOOKUP, inh);
TEST_COMPARE_BLOB (name, 3, "new", 3);
TEST_COMPARE (strlen (name), 9);
/* Extract 8 bytes of the name: 'w', the X replacements, and the
null terminator. Treat it as an uint64_t for easy sorting
below. Endianess does not matter because the relative order
of the entries is not important; sorting is only used to find
duplicates. */
TEST_VERIFY_EXIT (results_used < results_allocated);
memcpy (&results[results_used], name + 2, 8);
++results_used;
struct fuse_entry_out *out = support_fuse_prepare_entry (f, 2);
out->attr.mode = S_IFREG | 0600;
support_fuse_reply_prepared (f);
}
}
/* Used to sort the results array, to find duplicates. */
static int
results_sort (const void *a1, const void *b1)
{
const uint64_t *a = a1;
const uint64_t *b = b1;
if (*a < *b)
return -1;
if (*a == *b)
return 0;
return 1;
}
/* Number of occurrences of certain streak lengths. */
static size_t streak_lengths[6];
/* Called for every encountered streak. */
static inline void
report_streak (uint64_t current, size_t length)
{
if (length > 1)
{
printf ("info: name \"ne%.8s\" repeats: %zu\n",
(char *) ¤t, length);
TEST_VERIFY_EXIT (length < array_length (streak_lengths));
}
TEST_VERIFY_EXIT (length < array_length (streak_lengths));
++streak_lengths[length];
}
static int
do_test (void)
{
support_fuse_init ();
results = xmalloc (results_allocated * sizeof (*results));
struct shared
{
/* Used to synchronize the start of all subprocesses, to make it
more likely to expose concurrency-related bugs. */
pthread_barrier_t barrier1;
pthread_barrier_t barrier2;
/* Filled in after fork. */
char mountpoint[4096];
};
/* Used to synchronize the start of all subprocesses, to make it
more likely to expose concurrency-related bugs. */
struct shared *pshared = support_shared_allocate (sizeof (*pshared));
{
pthread_barrierattr_t attr;
xpthread_barrierattr_init (&attr);
xpthread_barrierattr_setpshared (&attr, PTHREAD_PROCESS_SHARED);
xpthread_barrierattr_destroy (&attr);
xpthread_barrier_init (&pshared->barrier1, &attr,
array_length (processes) + 1);
xpthread_barrier_init (&pshared->barrier2, &attr,
array_length (processes) + 1);
xpthread_barrierattr_destroy (&attr);
}
for (int i = 0; i < array_length (processes); ++i)
{
processes[i] = xfork ();
if (processes[i] == 0)
{
/* Wait for mountpoint initialization. */
xpthread_barrier_wait (&pshared->barrier1);
char *path = xasprintf ("%s/newXXXXXX", pshared->mountpoint);
/* Park this process until all processes have started. */
xpthread_barrier_wait (&pshared->barrier2);
errno = 0;
TEST_COMPARE (mkstemp (path), -1);
TEST_COMPARE (errno, EEXIST);
free (path);
_exit (0);
}
}
/* Do this after the forking, to minimize initialization inteference. */
struct support_fuse *f = support_fuse_mount (fuse_thread, NULL);
TEST_VERIFY (strlcpy (pshared->mountpoint, support_fuse_mountpoint (f),
sizeof (pshared->mountpoint))
< sizeof (pshared->mountpoint));
xpthread_barrier_wait (&pshared->barrier1);
puts ("info: performing mkstemp calls");
xpthread_barrier_wait (&pshared->barrier2);
for (int i = 0; i < array_length (processes); ++i)
{
int status;
xwaitpid (processes[i], &status, 0);
TEST_COMPARE (status, 0);
}
support_fuse_unmount (f);
xpthread_barrier_destroy (&pshared->barrier2);
xpthread_barrier_destroy (&pshared->barrier1);
printf ("info: checking results (count %zu)\n", results_used);
qsort (results, results_used, sizeof (*results), results_sort);
uint64_t current = -1;
size_t streak = 0;
for (size_t i = 0; i < results_used; ++i)
if (results[i] == current)
++streak;
else
{
report_streak (current, streak);
current = results[i];
streak = 1;
}
report_streak (current, streak);
puts ("info: repetition count distribution:");
for (int i = 1; i < array_length (streak_lengths); ++i)
printf (" length %d: %zu\n", i, streak_lengths[i]);
/* Some arbitrary threshold, hopefully unlikely enough. In over
260,000 runs of a simulation of this test, at most 26 pairs were
observed, and only one three-way collisions. */
if (streak_lengths[2] > 30)
FAIL ("unexpected repetition count 2: %zu", streak_lengths[2]);
if (streak_lengths[3] > 2)
FAIL ("unexpected repetition count 3: %zu", streak_lengths[3]);
for (int i = 4; i < array_length (streak_lengths); ++i)
if (streak_lengths[i] > 0)
FAIL ("too many repeats of count %d: %zu", i, streak_lengths[i]);
free (results);
return 0;
}
#include