Last active
December 12, 2024 01:33
-
-
Save jweinst1/efaa9a5b0d716edd15779bbac4ff1b5f to your computer and use it in GitHub Desktop.
dir iterator with C++ and direct
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 <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