Skip to content

Instantly share code, notes, and snippets.

@jweinst1
Last active December 12, 2024 01:33
Show Gist options
  • Save jweinst1/efaa9a5b0d716edd15779bbac4ff1b5f to your computer and use it in GitHub Desktop.
Save jweinst1/efaa9a5b0d716edd15779bbac4ff1b5f to your computer and use it in GitHub Desktop.
dir iterator with C++ and direct
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h>
#include <limits.h>
#include <assert.h>
#include <sys/event.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <string>
#include <vector>
#include <unordered_map>
int is_directory(const char *path) {
struct stat statbuf;
if (stat(path, &statbuf) != 0)
return 0;
return S_ISDIR(statbuf.st_mode);
}
int is_regular_file(const char *path) {
struct stat statbuf;
if (stat(path, &statbuf) != 0)
return 0;
return S_ISREG(statbuf.st_mode);
}
int is_dot_or_dot_dot(const char* name) {
return name[0] == '.' && (name[1] == '.' || name[1] == '\0');
}
class DirIterator {
public:
DirIterator(const char* dpath);
~DirIterator();
bool getNext(std::string& dirMem);
private:
struct dirent * _entry = nullptr;
struct dirent * _result = nullptr;
DIR* _dirp = nullptr;
std::string _path;
};
DirIterator::DirIterator(const char* dpath) {
if (!is_directory(dpath)) {
abort();
}
_path = dpath;
_dirp = opendir(_path.c_str());
if (_dirp == nullptr) {
abort();
}
_entry = (struct dirent*)malloc(offsetof(struct dirent, d_name) + NAME_MAX + 1);
}
bool DirIterator::getNext(std::string& dirMem) {
bool foundMem = false;
while (!foundMem) {
bool result = readdir_r(_dirp, _entry, &_result) == 0 && _result != NULL;
if (!result) {
return false;
}
if (is_dot_or_dot_dot(_entry->d_name))
continue;
break;
}
dirMem.assign(_path);
dirMem.append("/");
dirMem.append(_entry->d_name);
return true;
}
DirIterator::~DirIterator() {
free(_entry);
closedir(_dirp);
}
static void printDirectory(const char* dpath) {
DirIterator iter(dpath);
std::string it;
while (iter.getNext(it)) {
printf("%s\n", it.c_str());
}
}
enum class FileType {
Undetermined,
File,
Directory
};
static FileType determineType(const std::string& npath) {
struct stat statbuf;
if (stat(npath.c_str(), &statbuf) != 0)
return FileType::Undetermined;
if(S_ISDIR(statbuf.st_mode)) {
return FileType::Directory;
} else if (S_ISREG(statbuf.st_mode)) {
return FileType::File;
}
printf("File %s is not determined!\n", npath.c_str());
return FileType::Undetermined;
}
class MonitorNode {
public:
MonitorNode(const std::string& npath, MonitorNode* parent = nullptr): _path(npath), _parent(parent) {
_fd = open(_path.c_str(), O_EVTONLY);
if (!valid())
return;
}
virtual ~MonitorNode() {
}
virtual const std::unordered_map<std::string, MonitorNode*>* getChildren() const {
return nullptr;
}
virtual void checkForNewChildren(std::vector<MonitorNode*>& newNodes) {}
virtual void removeChild(const std::string& cpath) {}
bool addToQueue(int kqfd) const {
struct kevent change;
EV_SET(&change, _fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_RENAME | NOTE_LINK | NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB, 0, (void*)this);
if (kevent(kqfd, &change, 1, NULL, 0, NULL) == -1) {
perror("kevent");
printf("errno is %d\n", errno);
return false;
}
return true;
}
bool removeFromQueue(int kqfd) const {
struct kevent change;
EV_SET(&change, _fd, EVFILT_VNODE, EV_DELETE, 0, 0, (void*)this);
if (kevent(kqfd, &change, 1, NULL, 0, NULL) == -1) {
perror("kevent");
return false;
}
return true;
}
bool valid() const { return _fd != -1;}
const std::string& getPath() const { return _path; }
int getFd() const { return _fd; }
MonitorNode* getParent() const { return _parent; }
protected:
std::string _path;
int _fd = -1;
MonitorNode* _parent = nullptr;
};
class FileMonitorNode : public MonitorNode {
public:
FileMonitorNode(const std::string& npath, MonitorNode* parent = nullptr): MonitorNode(npath, parent) {
}
const std::unordered_map<std::string, MonitorNode*>* getChildren() const override {
return nullptr;
}
void checkForNewChildren(std::vector<MonitorNode*>& newNodes) override {}
void removeChild(const std::string& cpath) override {}
};
class DirMonitorNode : public MonitorNode {
public:
DirMonitorNode(const std::string& npath, MonitorNode* parent = nullptr): MonitorNode(npath, parent) {
populateChildren();
}
const std::unordered_map<std::string, MonitorNode*>* getChildren() const override {
return &_children;
}
void removeChild(const std::string& cpath) override {
printf("Erasing Path %s\n", cpath.c_str());
_children.erase(cpath);
}
void populateChildren() {
DirIterator iter(_path.c_str());
std::string it;
while (iter.getNext(it)) {
FileType ftype = determineType(it);
if (ftype == FileType::Directory) {
auto* node = new DirMonitorNode(it, this);
if (!node->valid()) {
printf("Invalid node %s\n", it.c_str());
delete node;
continue;
}
_children.insert({it, node});
} else if (ftype == FileType::File) {
auto node = new FileMonitorNode(it, this);
if (!node->valid()) {
printf("Invalid node %s\n", it.c_str());
delete node;
continue;
}
_children.insert({it, node});
}
}
}
void checkForNewChildren(std::vector<MonitorNode*>& newNodes) override {
DirIterator iter(_path.c_str());
std::string it;
while (iter.getNext(it)) {
const auto& result = _children.find(it);
if (result != _children.end()) {
continue;
}
FileType ftype = determineType(it);
if (ftype == FileType::Directory) {
auto* node = new DirMonitorNode(it, this);
if (!node->valid()) {
printf("Invalid node %s\n", it.c_str());
delete node;
continue;
}
_children.insert({it, node});
newNodes.push_back(node);
} else if (ftype == FileType::File) {
auto node = new FileMonitorNode(it, this);
if (!node->valid()) {
printf("Invalid node %s\n", it.c_str());
delete node;
continue;
}
_children.insert({it, node});
newNodes.push_back(node);
}
}
}
private:
std::unordered_map<std::string, MonitorNode*> _children;
friend class MonitorPoint;
};
enum class MonitorEventType {
Undetermined,
Write,
Append,
Link,
Rename,
Delete,
Attrib
};
struct MonitorEvent {
MonitorEventType type = MonitorEventType::Undetermined;
MonitorNode* node = nullptr;
void toString(std::string& s) const {
if (type == MonitorEventType::Undetermined) {
s.append("Undetermined ");
} else if (type == MonitorEventType::Write) {
s.append("Write ");
} else if (type == MonitorEventType::Append) {
s.append("Append ");
} else if (type == MonitorEventType::Link) {
s.append("Link ");
} else if (type == MonitorEventType::Rename) {
s.append("Rename ");
} else if (type == MonitorEventType::Delete) {
s.append("Delete ");
} else if (type == MonitorEventType::Attrib) {
s.append("Attrib ");
}
s.append(node->getPath());
}
};
class MonitorPoint {
public:
MonitorPoint(): _kqfd(kqueue()) {
}
bool addNode(const MonitorNode* node) {
if (!node->valid() || !valid())
return false;
if (!node->addToQueue(_kqfd)) {
printf("Failed add to queue %s\n", node->getPath().c_str());
return false;
}
const std::unordered_map<std::string, MonitorNode*>* nptr = node->getChildren();
if (nptr != nullptr) {
for (const auto& [key, val]: *nptr) {
if(!addNode(val)) {
printf("Failed to add node %s\n", key.c_str());
}
}
}
return true;
}
bool removeNode(const MonitorNode* node) {
if (!node->valid() || !valid())
return false;
if (!node->removeFromQueue(_kqfd)) {
printf("Failed remove from queue %s\n", node->getPath().c_str());
return false;
}
const std::unordered_map<std::string, MonitorNode*>* nptr = node->getChildren();
if (nptr != nullptr) {
for (const auto& [key, val]: *nptr) {
if(!removeNode(val)) {
printf("Failed to add node %s\n", key.c_str());
}
}
}
return true;
}
int getEvents(std::vector<MonitorEvent>& eventResults) {
struct kevent events[256];
std::vector<MonitorNode*> nodeBank;
int event_count = kevent(_kqfd, NULL, 0, events, 256, NULL);
if (event_count == -1) {
perror("kevent");
return 0;
}
for (int i = 0; i < event_count; ++i) {
MonitorEvent mn;
MonitorNode* curNode = (MonitorNode*)events[i].udata;
mn.node = curNode;
if (events[i].fflags & NOTE_WRITE) {
mn.type = MonitorEventType::Write;
curNode->checkForNewChildren(nodeBank);
if (nodeBank.size() > 0) {
for (const auto* node : nodeBank) {
addNode(node);
}
nodeBank.clear();
}
}
else if (events[i].fflags & NOTE_EXTEND) {
mn.type = MonitorEventType::Append;
}
else if (events[i].fflags & NOTE_DELETE) {
mn.type = MonitorEventType::Delete;
auto* parent = curNode->getParent();
if (parent != nullptr)parent->removeChild(curNode->getPath());
removeNode(curNode);
}
else if (events[i].fflags & NOTE_LINK) {
mn.type = MonitorEventType::Link;
}
else if (events[i].fflags & NOTE_ATTRIB) {
mn.type = MonitorEventType::Attrib;
}
else if (events[i].fflags & NOTE_RENAME) {
// todo handle dup error rename
mn.type = MonitorEventType::Rename;
}
else {
mn.type = MonitorEventType::Undetermined;
}
eventResults.push_back(mn);
}
return event_count;
}
bool valid() const { return _kqfd != -1;}
private:
int _kqfd = -1;
};
int main(int argc, char const *argv[])
{
const std::string inputString = argv[1];
printDirectory(inputString.c_str());
DirMonitorNode foo(inputString);
MonitorPoint mp;
mp.addNode(&foo);
std::vector<MonitorEvent> events;
while (true) {
int result = mp.getEvents(events);
for (const auto& event: events) {
std::string s;
event.toString(s);
printf("EVENT: %s\n", s.c_str());
}
events.clear();
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment