Skip to content

Instantly share code, notes, and snippets.

@sgielen
Created June 3, 2021 21:44
Show Gist options
  • Save sgielen/ade2b98681840d50e70324bdfff8a171 to your computer and use it in GitHub Desktop.
Save sgielen/ade2b98681840d50e70324bdfff8a171 to your computer and use it in GitHub Desktop.
macfuse testcase
#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;
}
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