Created
June 3, 2021 21:44
-
-
Save sgielen/ade2b98681840d50e70324bdfff8a171 to your computer and use it in GitHub Desktop.
macfuse testcase
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <dirent.h> | |
#include <errno.h> | |
#include <fcntl.h> | |
#include <fuse.h> | |
#include <stdio.h> | |
#include <string.h> | |
#include <algorithm> | |
#include <chrono> | |
#include <iostream> | |
#include <random> | |
#include <string> | |
#include <thread> | |
#include <vector> | |
/** | |
Reproduction scenario for missing files / double file entries when testing against macfuse. | |
Run me with e.g. `./main -f test`. | |
I can reproduce with macfuse 4.1.2 on macOS Big Sur 11.2.3. | |
Interestingly, the `recurse` threads don't trigger the problem, but they do cause load | |
that seems necessary for reproduction. | |
I can reproduce by running an `ls -la` in a loop; it should always come back with 28 lines | |
(25 entries + 3 lines at the top), but it does not: | |
$ while :; do ls -la test/Tsttst | wc -l; sleep 1; done | |
28 | |
[...six lines "28"...] | |
28 | |
23 | |
28 | |
[...seventeen lines "28"....] | |
28 | |
27 | |
28 | |
*/ | |
std::default_random_engine rng; | |
static int | |
test_getattr(const char *path, struct stat *stbuf); | |
static int | |
test_open(const char *path, struct fuse_file_info *fi); | |
static int | |
test_read(const char *path, char *buf, size_t size, off_t offset, | |
struct fuse_file_info *fi); | |
static int | |
test_readdir(const char *path, void *buf, fuse_fill_dir_t filler, | |
off_t offset, struct fuse_file_info *fi); | |
static struct fuse_operations fs_ops = { | |
.getattr = test_getattr, | |
.open = test_open, | |
.read = test_read, | |
.readdir = test_readdir, | |
}; | |
static void | |
recurse(); | |
std::string mountpoint; | |
int | |
main(int argc, char **argv) | |
{ | |
mountpoint = argv[argc - 1]; | |
printf("running - assuming mountpoint is %s\n", mountpoint.c_str()); | |
std::vector<std::thread> threads; | |
for (int i = 0; i < 2; ++i) { | |
threads.emplace_back(recurse); | |
} | |
return fuse_main(argc, argv, &fs_ops, NULL); | |
} | |
static void | |
recurse_dir(std::string path) | |
{ | |
std::string fullpath = mountpoint + "/" + path; | |
DIR *d = opendir(fullpath.c_str()); | |
if (d == NULL) { | |
fprintf(stderr, "recurse failed to open directory %s: %s", fullpath.c_str(), strerror(errno)); | |
return; | |
} | |
struct dirent *de; | |
while ((de = readdir(d)) != NULL) { | |
fullpath = mountpoint + "/" + path + "/" + de->d_name; | |
struct stat buf; | |
if (stat(fullpath.c_str(), &buf) != 0) { | |
fprintf(stderr, "recurse failed to stat %s: %s", fullpath.c_str(), strerror(errno)); | |
return; | |
} | |
if ((buf.st_mode & S_IFDIR) != 0) { | |
recurse_dir(path + "/" + de->d_name); | |
} | |
} | |
closedir(d); | |
} | |
static void | |
recurse() | |
{ | |
// wait for mount to succeed | |
std::this_thread::sleep_for(std::chrono::seconds(5)); | |
printf("recurse starting."); | |
while(1){ | |
recurse_dir(""); | |
std::this_thread::sleep_for(std::chrono::milliseconds(10)); | |
} | |
} | |
// ======================= | |
static int | |
vfs_readdir(std::string path, std::vector<std::string> &children); | |
static int | |
test_getattr(const char *path, struct stat *stbuf) | |
{ | |
if (path[0] == 0 || strcmp(path, "/") == 0) { | |
stbuf->st_mode = 0555 | S_IFDIR; | |
return 0; | |
} | |
const char *slash = strrchr(path, '/'); | |
std::string dn, fn; | |
if (slash == path || slash == NULL) { | |
dn = ""; | |
fn = std::string(path + 1); | |
} else { | |
dn = std::string(path, slash - path); | |
fn = std::string(slash + 1); | |
} | |
//auto rand_sleep_ms = (200.l * rng()) / rng.max(); | |
//std::this_thread::sleep_for(std::chrono::milliseconds((int)rand_sleep_ms)); | |
std::vector<std::string> children; | |
int res = vfs_readdir(dn, children); | |
if (res != 0) { | |
fprintf(stderr, "Invalid vfs_readdir {%s}, returning %d\n", path, res); | |
return res; | |
} | |
for (auto child : children) { | |
if (child == fn) { | |
stbuf->st_mode = 0755 | S_IFDIR; | |
stbuf->st_size = 0; | |
stbuf->st_mtimespec.tv_sec = 0; | |
stbuf->st_mtimespec.tv_nsec = 0; | |
return 0; | |
} | |
} | |
return -ENOENT; | |
} | |
static int | |
test_open(const char*, struct fuse_file_info*) | |
{ | |
return 0; | |
} | |
static int | |
test_read(const char*, char *buf, size_t size, off_t offset, | |
struct fuse_file_info*) | |
{ | |
for (size_t i = 0; i < size; ++i) { | |
buf[offset + size] = 0; | |
} | |
return size; | |
} | |
static int | |
test_readdir(const char *path, void *buf, fuse_fill_dir_t filler, | |
off_t offset, struct fuse_file_info*) | |
{ | |
if (offset != 0) { | |
fprintf(stderr, "offset in readdir was not 0, but %lld\n", offset); | |
exit(1); | |
} | |
std::vector<std::string> children; | |
int res = vfs_readdir(std::string(path), children); | |
if (res != 0) { | |
fprintf(stderr, "Invalid vfs_readdir {%s}, returning %d\n", path, res); | |
return res; | |
} | |
auto rand_sleep_ms = (200.l * rng()) / rng.max(); | |
std::this_thread::sleep_for(std::chrono::milliseconds((int)rand_sleep_ms)); | |
std::shuffle(std::begin(children), std::end(children), rng); | |
for (auto const &child : children) { | |
if (filler(buf, child.c_str(), NULL, 0) != 0) { | |
fprintf(stderr, "filler failed for {%s}\n", path); | |
exit(1); | |
} | |
} | |
return 0; | |
} | |
// ======================= | |
static int | |
vfs_readdir(std::string path, std::vector<std::string> &children) { | |
if (path.length() > 0 && path[0] == '/') { | |
path = path.substr(1); | |
} | |
children.clear(); | |
if (path == "") { | |
children.push_back("Tsttst"); | |
} else if (path == "Tsttst") { | |
children.push_back("A AAA AAAAAA AAA"); | |
children.push_back("BBBBBBBBB"); | |
children.push_back("CCCCCCCCC.0000.CCCCC.CCCC.CC3-CCC"); | |
children.push_back("DDDDDDDDD DDDDD"); | |
children.push_back("EEEEE EEEE"); | |
children.push_back("FFFF.FFFF.FFFF.FFFF.0000.000F.FFFFF.FFFF-FFFFF"); | |
children.push_back("GGGGGG"); | |
children.push_back("HHHHHHH (0000)"); | |
children.push_back("IIIIIII IIIII (0000)"); | |
children.push_back("JJJJJ JJJJJ"); | |
children.push_back("KKKKKK KKKKKKKK.KKKKKKKKK"); | |
children.push_back("LLLLLLLLL (0000)"); | |
children.push_back("MMMM MMMMMM MMMM MMMMM"); | |
children.push_back("NNN NNNN NNNNNNN"); | |
children.push_back("OOOOOOO OO OOO OOOOOOOOO - OO OOOOOOOO OOOOO (0000) [0000o]"); | |
children.push_back("PP"); | |
children.push_back("QQQ QQQQQQ: QQ QQQQQQQQQQ QQQQQQQ"); | |
children.push_back("RRR RRRR RRRRRRRRR"); | |
children.push_back("SSS SSSSSS (0000)"); | |
children.push_back("TTT TTTTT (0000)"); | |
children.push_back("UUU UUUUU UUUUUUUU"); | |
children.push_back("VVVVV VVVVVVVVVV VVVVVVV VVVVVV VVVVVVVV"); | |
children.push_back("WWWWW WWWWWW (0000)"); | |
children.push_back("XXXX XXXXX"); | |
children.push_back("YYYYYY"); | |
} else if (path == "Tsttst/A AAA AAAAAA AAA") { | |
// no entries | |
} else if (path == "Tsttst/BBBBBBBBB") { | |
// no entries | |
} else if (path == "Tsttst/CCCCCCCCC.0000.CCCCC.CCCC.CC3-CCC") { | |
// no entries | |
} else if (path == "Tsttst/DDDDDDDDD DDDDD") { | |
children.push_back("Subdirectr"); | |
} else if (path == "Tsttst/DDDDDDDDD DDDDD/Subdirectr") { | |
// no entries | |
} else if (path == "Tsttst/EEEEE EEEE") { | |
children.push_back("aaaaaa aaaaaa (0000)"); | |
children.push_back("bbbbb bbbb bbb bbbbbbb bbb (0000) [0000b]"); | |
} else if (path == "Tsttst/EEEEE EEEE/aaaaaa aaaaaa (0000)") { | |
// no entries | |
} else if (path == "Tsttst/EEEEE EEEE/bbbbb bbbb bbb bbbbbbb bbb (0000) [0000b]") { | |
// no entries | |
} else if (path == "Tsttst/FFFF.FFFF.FFFF.FFFF.0000.000F.FFFFF.FFFF-FFFFF") { | |
children.push_back("Subd"); | |
} else if (path == "Tsttst/FFFF.FFFF.FFFF.FFFF.0000.000F.FFFFF.FFFF-FFFFF/Subd") { | |
// no entries | |
} else if (path == "Tsttst/GGGGGG") { | |
children.push_back("Subd"); | |
} else if (path == "Tsttst/GGGGGG/Subd") { | |
// no entries | |
} else if (path == "Tsttst/HHHHHHH (0000)") { | |
// no entries | |
} else if (path == "Tsttst/IIIIIII IIIII (0000)") { | |
// no entries | |
} else if (path == "Tsttst/JJJJJ JJJJJ") { | |
children.push_back("jj1"); | |
children.push_back("jj2"); | |
children.push_back("Subs"); | |
} else if (path == "Tsttst/JJJJJ JJJJJ/jj1") { | |
// no entries | |
} else if (path == "Tsttst/JJJJJ JJJJJ/jj2") { | |
// no entries | |
} else if (path == "Tsttst/JJJJJ JJJJJ/Subs") { | |
// no entries | |
} else if (path == "Tsttst/KKKKKK KKKKKKKK.KKKKKKKKK") { | |
children.push_back("ccccccccccc.ccccccccc"); | |
children.push_back("eeeeeee.eeeeeeeee"); | |
children.push_back("gggggggggg.ggggggggg"); | |
children.push_back("tttttt.ttttttttt"); | |
children.push_back("rrrrrrrrrrr.rrrrrrrrr"); | |
} else if (path == "Tsttst/KKKKKK KKKKKKKK.KKKKKKKKK/ccccccccccc.ccccccccc") { | |
// no entries | |
} else if (path == "Tsttst/KKKKKK KKKKKKKK.KKKKKKKKK/eeeeeee.eeeeeeeee") { | |
// no entries | |
} else if (path == "Tsttst/KKKKKK KKKKKKKK.KKKKKKKKK/gggggggggg.ggggggggg") { | |
// no entries | |
} else if (path == "Tsttst/KKKKKK KKKKKKKK.KKKKKKKKK/tttttt.ttttttttt") { | |
// no entries | |
} else if (path == "Tsttst/KKKKKK KKKKKKKK.KKKKKKKKK/rrrrrrrrrrr.rrrrrrrrr") { | |
// no entries | |
} else if (path == "Tsttst/LLLLLLLLL (0000)") { | |
// no entries | |
} else if (path == "Tsttst/MMMM MMMMMM MMMM MMMMM") { | |
// no entries | |
} else if (path == "Tsttst/NNN NNNN NNNNNNN") { | |
children.push_back("NNN NNNN NNNNNNN"); | |
} else if (path == "Tsttst/NNN NNNN NNNNNNN/NNN NNNN NNNNNNN") { | |
children.push_back("annnn_TS"); | |
children.push_back("vnnnn_TS"); | |
} else if (path == "Tsttst/NNN NNNN NNNNNNN/NNN NNNN NNNNNNN/annnn_TS") { | |
// no entries | |
} else if (path == "Tsttst/NNN NNNN NNNNNNN/NNN NNNN NNNNNNN/vnnnn_TS") { | |
// no entries | |
} else if (path == "Tsttst/OOOOOOO OO OOO OOOOOOOOO - OO OOOOOOOO OOOOO (0000) [0000o]") { | |
// no entries | |
} else if (path == "Tsttst/PP") { | |
// no entries | |
} else if (path == "Tsttst/QQQ QQQQQQ: QQ QQQQQQQQQQ QQQQQQQ") { | |
// no entries | |
} else if (path == "Tsttst/RRR RRRR RRRRRRRRR") { | |
children.push_back("Subd"); | |
} else if (path == "Tsttst/RRR RRRR RRRRRRRRR/Subd") { | |
// no entries | |
} else if (path == "Tsttst/SSS SSSSSS (0000)") { | |
// no entries | |
} else if (path == "Tsttst/TTT TTTTT (0000)") { | |
// no entries | |
} else if (path == "Tsttst/UUU UUUUU UUUUUUUU") { | |
// no entries | |
} else if (path == "Tsttst/VVVVV VVVVVVVVVV VVVVVVV VVVVVV VVVVVVVV") { | |
// no entries | |
} else if (path == "Tsttst/WWWWW WWWWWW (0000)") { | |
// no entries | |
} else if (path == "Tsttst/XXXX XXXXX") { | |
// no entries | |
} else if (path == "Tsttst/YYYYYY") { | |
children.push_back("subd"); | |
} else if (path == "Tsttst/YYYYYY/subd") { | |
// no entries | |
} else { | |
return -ENOENT; | |
} | |
return 0; | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
main: main.cpp | |
g++ -DFUSE_USE_VERSION=28 -D_FILE_OFFSET_BITS=64 -I/usr/local/include/osxfuse/fuse -lfuse main.cpp -o main -std=c++11 -Wall -Wextra |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment