/* FUSE-based test for mkstemp.
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
/* Set to true in do_test to cause the first FUSE_CREATE attempt to fail. */
static _Atomic bool simulate_creat_race;
/* Basic tests with eventually successful creation. */
static void
fuse_thread_basic (struct support_fuse *f, void *closure)
{
char *previous_name = NULL;
int state = 0;
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;
switch (inh->opcode)
{
case FUSE_LOOKUP:
/* File does not exist initially. */
TEST_COMPARE (inh->nodeid, 1);
if (simulate_creat_race)
{
if (state < 3)
++state;
else
FAIL ("invalid state: %d", state);
}
else
{
TEST_COMPARE (state, 0);
state = 3;
}
support_fuse_reply_error (f, ENOENT);
break;
case FUSE_CREATE:
{
TEST_COMPARE (inh->nodeid, 1);
char *name;
struct fuse_create_in *p
= support_fuse_cast_name (CREATE, inh, &name);
/* Name follows after struct fuse_create_in. */
TEST_COMPARE (p->flags & O_ACCMODE, O_RDWR);
TEST_VERIFY (p->flags & O_EXCL);
TEST_VERIFY (p->flags & O_CREAT);
TEST_COMPARE (p->mode & 07777, 0600);
TEST_VERIFY (S_ISREG (p->mode));
TEST_COMPARE_BLOB (name, 3, "new", 3);
if (state != 3 && simulate_creat_race)
{
++state;
support_fuse_reply_error (f, EEXIST);
}
else
{
if (previous_name != NULL)
/* This test has a very small probability of failure
due to a harmless collision (one in 62**6 tests). */
TEST_VERIFY (strcmp (name, previous_name) != 0);
TEST_COMPARE (state, 3);
++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);
}
free (previous_name);
previous_name = xstrdup (name);
}
break;
case FUSE_FLUSH:
case FUSE_RELEASE:
TEST_COMPARE (state, 4);
TEST_COMPARE (inh->nodeid, 2);
support_fuse_reply_empty (f);
break;
default:
support_fuse_reply_error (f, EIO);
}
}
free (previous_name);
}
/* Reply that all files exist. */
static void
fuse_thread_eexist (struct support_fuse *f, void *closure)
{
uint64_t counter = 0;
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;
switch (inh->opcode)
{
case FUSE_LOOKUP:
++counter;
TEST_COMPARE (inh->nodeid, 1);
char *name = support_fuse_cast (LOOKUP, inh);
TEST_COMPARE_BLOB (name, 3, "new", 3);
TEST_COMPARE (strlen (name), 9);
for (int i = 3; i <= 8; ++i)
{
/* The glibc implementation uses letters and digits only. */
char ch = name[i];
TEST_VERIFY (('0' <= ch && ch <= '9')
|| ('a' <= ch && ch <= 'z')
|| ('A' <= ch && ch <= 'Z'));
}
struct fuse_entry_out out =
{
.nodeid = 2,
.attr = {
.mode = S_IFREG | 0600,
.ino = 2,
},
};
support_fuse_reply (f, &out, sizeof (out));
break;
default:
support_fuse_reply_error (f, EIO);
}
}
/* Verify that mkstemp has retried a lot. The current
implementation tries 62 * 62 * 62 times until it goves up. */
TEST_VERIFY (counter >= 200000);
}
static int
do_test (void)
{
support_fuse_init ();
for (int do_simulate_creat_race = 0; do_simulate_creat_race < 2;
++do_simulate_creat_race)
{
simulate_creat_race = do_simulate_creat_race;
printf ("info: testing with simulate_creat_race == %d\n",
(int) simulate_creat_race);
struct support_fuse *f = support_fuse_mount (fuse_thread_basic, NULL);
char *path = xasprintf ("%s/newXXXXXX", support_fuse_mountpoint (f));
int fd = mkstemp (path);
TEST_VERIFY (fd > 2);
xclose (fd);
free (path);
support_fuse_unmount (f);
}
puts ("info: testing EEXIST failure case for mkstemp");
{
struct support_fuse *f = support_fuse_mount (fuse_thread_eexist, NULL);
char *path = xasprintf ("%s/newXXXXXX", support_fuse_mountpoint (f));
errno = 0;
TEST_COMPARE (mkstemp (path), -1);
TEST_COMPARE (errno, EEXIST);
free (path);
support_fuse_unmount (f);
}
return 0;
}
#include