#include "ent.hpp"
#include "io.hpp"
#include <fcntl.h>
#include <dirent.h>
#include <time.h>
#define REOPEN
static bool rmdir_r(int dfd, int fd, const char* name) {
DIR* dp = fdopendir(fd);
if (!dp) {
LOG_ERRNO("fdopendir(%d)", fd);
close(fd);
return false;
}
bool rv = true;
while (true) {
struct dirent* de = readdir(dp);
if (!de) {
break;
}
if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) {
continue;
}
if (de->d_type == DT_UNKNOWN) {
struct stat ss;
if (fstatat(fd, de->d_name, &ss, AT_SYMLINK_NOFOLLOW) == -1) {
LOG_ERRNO("fstatat(%s)", de->d_name);
rv = false;
break;
}
if (S_ISDIR(ss.st_mode)) {
de->d_type = DT_DIR;
}
}
if (de->d_type == DT_DIR) {
int fd_r = openat(fd, de->d_name, O_RDONLY|O_DIRECTORY|O_NOFOLLOW);
if (fd_r == -1) {
LOG_ERRNO("openat(%d, %s)", fd, de->d_name);
rv = false;
break;
}
if (!rmdir_r(fd, fd_r, de->d_name)) {
rv = false;
break;
}
} else {
if (unlinkat(fd, de->d_name, 0) == -1) {
LOG_ERRNO("unlinkat(%s)", de->d_name);
rv = false;
break;
}
}
}
if (rv) {
if (unlinkat(dfd, name, AT_REMOVEDIR) == -1) {
LOG_ERRNO("unlinkat(%s)", name);
rv = false;
}
}
closedir(dp); // closes fd
return rv;
}
Ent::Ent(int d, int f, const char* n, Attr* a): dfd(d), fd(f), fd_ro(false), attr(a), type(a->is_dir()? TYPE_DIR: TYPE_REG), name(strdup(n)) {
assert(dfd != -1);
assert(strlen(name));
assert(attr);
assert(attr->is_dir() || attr->is_reg());
#ifdef REOPEN
if (fd != -1) {
close(fd);
fd = -1;
}
#else
assert(fd != -1);
#endif
}
Ent::Ent(const char* n): dfd(-1), fd(-1), fd_ro(false), attr(NULL), type(TYPE_NONE), name(strdup(n)) {
assert(strlen(name));
}
Ent::~Ent() {
if (fd != -1) close(fd);
free((void*)name);
if (attr) delete attr;
}
bool Ent::reopen(bool recreate, bool ro) {
if (recreate && ro) return false;
if (fd != -1) {
if (recreate || (!ro && fd_ro)) {
close(fd);
fd = -1;
} else {
return true; // so r/o ones should be closed
}
}
switch (type) {
case TYPE_NONE:
return false;
case TYPE_DIR:
if (recreate) return false;
fd = openat(dfd, name, O_RDONLY|O_DIRECTORY|O_NOFOLLOW);
break;
case TYPE_REG:
if (recreate) {
fd = openat(dfd, name, O_RDWR|O_NOFOLLOW|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
assert(!ro);
} else {
fd = openat(dfd, name, (ro? O_RDONLY: O_RDWR)|O_NOFOLLOW);
}
fd_ro = ro;
break;
}
if (fd == -1) {
LOG_ERRNO("(re-)openat(%d, %s)", dfd, name);
return false;
}
Attr* new_attr = Attr::getInst(fd);
if (!new_attr) {
close(fd);
fd = -1;
return false;
}
assert(attr);
if (!recreate && (new_attr->ino() != attr->ino() || new_attr->cmp(attr) != Attr::SAME)) {
LOG("reopen apply mismatch for %s", name);
close(fd);
fd = -1;
return false;
}
delete attr;
attr = new_attr;
return true;
}
Ent* Ent::getInst(int dfd, int fd, const char* name, const struct stat* ss) {
Attr* a;
if (ss) {
a = Attr::getInst(*ss);
} else if (fd != -1) {
a = Attr::getInst(fd);
} else {
a = Attr::getInst(dfd, name);
}
if (!a) {
close(fd);
return NULL;
}
return new Ent(dfd, fd, name, a);
}
Ent* Ent::getInst(int dfd, const char* name, bool dir, const struct stat* ss) {
#ifdef REOPEN
return getInst(dfd, -1, name, ss);
#else
int fd = dir?
openat(dfd, name, O_RDONLY|O_DIRECTORY|O_NOFOLLOW):
#ifdef REOPEN
openat(dfd, name, O_RDONLY|O_NOFOLLOW);
#else
openat(dfd, name, O_RDWR|O_NOFOLLOW);
#endif
if (fd == -1) {
LOG_ERRNO("openat(%d, %s)", dfd, name);
return NULL;
}
return getInst(dfd, fd, name, ss);
#endif
}
Ent* Ent::createInst(int dfd, const char* name, bool dir) {
if (dir) {
if (mkdirat(dfd, name, S_IRUSR|S_IWUSR) == -1) {
LOG_ERRNO("mkdirat(%d, %s)", dfd, name);
return NULL;
}
Ent* rv = getInst(dfd, name, dir);
if (!rv) {
if (unlinkat(dfd, name, 0) == -1) {
LOG_ERRNO("unlinkat(%d, %s)", dfd, name);
}
}
return rv;
} else {
int fd = openat(dfd, name, O_RDWR|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
if (fd == -1) {
LOG_ERRNO("openat(%d, %s)", dfd, name);
return NULL;
}
return getInst(dfd, fd, name, NULL);
}
}
bool Ent::read_from(Ent* o, int bupdfd) {
if (type != TYPE_REG || o->type != TYPE_REG) {
return false;
}
if (bupdfd != -1 && !delInst(bupdfd)) {
return false;
}
if (!reopen(bupdfd != -1) || !o->reopen(false, true)) {
return false;
}
if (lseek(fd, 0, SEEK_SET) == -1 || lseek(o->fd, 0, SEEK_SET) == -1) {
LOG_ERRNO("lseek()");
return false;
}
if (ftruncate(fd, o->attr->size()) == -1) {
LOG_ERRNO("ftruncate(%d)", fd);
return false;
}
return write_buffered(o->attr->size(), o->fd, fd);
}
bool Ent::apply(const Ent* o) {
if (type == TYPE_NONE || type != o->type) {
return false;
}
if (!reopen()) {
return false;
}
if (!o->attr->apply_to(fd)) {
return false;
}
Attr* new_attr = Attr::getInst(fd); // can somehow take o?
if (!new_attr) {
return false;
}
if (new_attr->cmp(o->attr) != Attr::SAME) {
LOG("attr apply mismatch for %s", name);
}
if (attr) {
delete attr;
}
attr = new_attr;
return true;
}
bool Ent::delInst(int bupdfd) {
if (bupdfd != -1) {
if (type != TYPE_REG && type != TYPE_DIR) {
return false;
}
#if 0
if (renameat2(dfd, name, bupdfd, name, RENAME_NOREPLACE) == -1) { // needs kernel >= 3.15
LOG_ERRNO("renameat(%s)", name); // TODO: suffix in case of EEXIST
return false;
}
#else
struct stat ss;
errno = 0;
if (fstatat(bupdfd, name, &ss, AT_SYMLINK_NOFOLLOW) != -1 || errno != ENOENT) { // race
LOG_ERRNO("fstatat(%s)", name);
return false;
}
if (renameat(dfd, name, bupdfd, name) == -1) {
LOG_ERRNO("renameat(%s)", name);
return false;
}
#endif
if (fd != -1) {
close(fd);
fd = -1;
}
return true;
}
switch (type) {
case TYPE_NONE:
break;
case TYPE_DIR:
if (!reopen()) break;
if (rmdir_r(dfd, fd, name)) {
fd = -1;
return true;
}
fd = -1;
break;
case TYPE_REG:
if (unlinkat(dfd, name, 0) == -1) {
LOG_ERRNO("unlinkat(%d, %s)", dfd, name);
break;
}
if (fd != -1) {
close(fd);
fd = -1;
}
return true;
}
return false;
}
void Ent::print(const char* prefix, const char* suffix) const {
assert(type != TYPE_NONE);
char mt[32], ct[32];
ctime_r(&attr->mtime().tv_sec, mt);
ctime_r(&attr->ctime().tv_sec, ct);
*strchrnul(mt, '\n') = '\0';
*strchrnul(ct, '\n') = '\0';
printf("%s" COLOR(YELLOW, "%c") "%04o %05d/%05d %s %s " COLOR(YELLOW, "%s") " %ld%s", prefix?:"", attr->is_dir()? 'd':'-', attr->mode(), attr->uid(), attr->gid(), mt, ct, name, attr->size(), suffix?:"");
}
void Ent::print(const Ent* e, const char* prefix, const char* suffix) {
if (!e) {
printf("%s" COLOR(RED, "(none)") "%s", prefix?:"", suffix?:"");
} else if (e->type == TYPE_NONE) {
printf("%s" COLOR(RED, "%s") "%s", prefix?:"", e->name, suffix?:"");
} else {
e->print(prefix, suffix);
}
}