/* 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