diff options
Diffstat (limited to 'misc/tst-mkstemp-fuse.c')
-rw-r--r-- | misc/tst-mkstemp-fuse.c | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/misc/tst-mkstemp-fuse.c b/misc/tst-mkstemp-fuse.c new file mode 100644 index 0000000000..5ac6a6872a --- /dev/null +++ b/misc/tst-mkstemp-fuse.c @@ -0,0 +1,197 @@ +/* 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 + <https://www.gnu.org/licenses/>. */ + +#include <stdlib.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <support/check.h> +#include <support/fuse.h> +#include <support/support.h> +#include <support/xunistd.h> + +/* 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 <support/test-driver.c> |