#include "dir.hpp"
#include <fcntl.h>
#include <dirent.h>
#include <algorithm>
#include <fnmatch.h>
static const char* ignore[] = {
NULL
};
bool matches(const char** list, const char* fn) {
for (const char** p=list; *p; ++p) {
if (fnmatch(*p, fn, FNM_NOESCAPE) == 0) return true;
}
return false;
}
bool DirEntries::before(const Ent* a, const Ent* b) {
if (a->type != b->type) {
if (a->type == Ent::TYPE_DIR || b->type == Ent::TYPE_DIR) return b->type == Ent::TYPE_DIR; // files first
}
return strcmp(a->name, b->name) < 0; // utf8 'compatible'? TODO: ignore case?
}
DirEntries* DirEntries::getInst(int dfd) {
int fd = dup(dfd);
if (fd == -1) {
LOG_ERRNO("dup(%d)", dfd);
return NULL;
}
DIR* dp = fdopendir(fd);
if (!dp) {
LOG_ERRNO("fdopendir(%d)", fd);
close(fd);
return NULL;
}
DirEntries* rv = new DirEntries();
//rewinddir(dp);
while (true) {
struct dirent* de = readdir(dp);
if (!de) {
break;
}
if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) {
continue;
}
if (matches(ignore, de->d_name)) {
//LOG("skipping '%s'", de->d_name);
(void)rv->entries.push_back(new Ent(de->d_name));
continue;
}
struct stat ss;
bool have_ss = false;
if (de->d_type == DT_UNKNOWN) {
if (fstatat(dfd, de->d_name, &ss, AT_SYMLINK_NOFOLLOW) == -1) {
LOG_ERRNO("fstatat(%s)", de->d_name);
} else {
have_ss = true;
}
if (S_ISDIR(ss.st_mode)) {
de->d_type = DT_DIR;
} else if (S_ISREG(ss.st_mode)) {
de->d_type = DT_REG;
}
}
Ent* e = NULL;
if (de->d_type == DT_DIR) {
e = Ent::getInst(dfd, de->d_name, true, have_ss? &ss: NULL);
} else if (de->d_type == DT_REG) {
e = Ent::getInst(dfd, de->d_name, false, have_ss? &ss: NULL);
} else {
//LOG("skipping irregular file '%s'", de->d_name); // really want to skip? maybe we do not want to hide them to detect conflicts
}
if (!e) {
e = new Ent(de->d_name);
}
#if 0
(void)rv->entries.push_back(e);
}
std::sort(rv->entries.begin(), rv->entries.end(), &after); // s.t. we can pop from back
#else
std::vector<Ent*>::iterator eit = rv->entries.begin();
for (; eit != rv->entries.end(); ++eit) {
if (after(e, *eit)) break; // reverse ordering, can pop lowest ('a') from back
}
(void)rv->entries.insert(eit, e); // inserts before
}
#endif
closedir(dp); // A successful call to closedir() also closes the underlying file descriptor associated with dirp (that's also why we dup'ed)
return rv;
}
DirEntries::~DirEntries() {
for (std::vector<Ent*>::iterator it = entries.begin(); it != entries.end(); it++) {
delete *it;
}
}
Ent* DirEntries::pop() {
if (!entries.size()) {
return NULL;
}
Ent* rv = entries.back();
entries.pop_back();
return rv;
}
void DirEntries::unpop(Ent* e) {
assert(!entries.size() || before(e, entries.back()));
entries.push_back(e);
}
Dir::Dir(int f, DirEntries* e, Attr* a): fd(f), attr(a), entries(e) {
assert(fd != -1);
assert(entries);
assert(attr);
}
Dir::~Dir() {
close(fd);
delete entries;
delete attr;
}
Dir* Dir::getInst(const char* dn, Dir* parent) {
int fd = parent? openat(parent->fd, dn, O_DIRECTORY|O_NOFOLLOW|O_RDONLY): open(dn, O_DIRECTORY|O_NOFOLLOW|O_RDONLY);
if (fd == -1) {
LOG_ERRNO("open(%s, DIR)", dn);
return NULL;
}
Attr* attr = Attr::getInst(fd);
if (!attr) {
close(fd);
return NULL;
}
DirEntries* entries = DirEntries::getInst(fd);
if (!entries) {
delete attr;
close(fd);
return NULL;
}
return new Dir(fd, entries, attr);
}
DirStack::DirStack(const char* dn) {
(void)push(dn);
}
DirStack::~DirStack() {
while (st.size()) {
pop();
}
}
Dir* DirStack::push(const char* dn) {
Dir* rv = Dir::getInst(dn, get());
if (rv) {
st.push(rv);
}
return rv;
}
Dir* DirStack::get() {
return st.size()? st.top(): NULL;
}
void DirStack::pop() {
assert(st.size());
delete st.top();
st.pop();
}