#pragma once #include #include #include #include #include #include #include #include #include namespace nall::Beat::Archive { struct Node { static auto create(string name, string location) -> shared_pointer; static auto createPath(string name) -> shared_pointer; static auto createFile(string name, array_view memory) -> shared_pointer; explicit operator bool() const { return (bool)name; } auto isPath() const -> bool { return name.endsWith("/"); } auto isFile() const -> bool { return !name.endsWith("/"); } auto isCompressed() const -> bool { return (bool)compression.type; } auto metadata(bool indented = true) const -> string; auto compressLZSA() -> bool; auto unserialize(array_view container, Markup::Node metadata) -> bool; auto decompress() -> bool; auto getTimestamp(string) const -> uint64_t; auto getPermissions() const -> uint; auto getOwner() const -> string; auto getGroup() const -> string; //files and paths string name; bool timestamps = false; struct Timestamp { string created; string modified; string accessed; } timestamp; bool permissions = false; struct Permission { struct Owner { string name; bool readable = false; bool writable = false; bool executable = false; } owner; struct Group { string name; bool readable = false; bool writable = false; bool executable = false; } group; struct Other { bool readable = false; bool writable = false; bool executable = false; } other; } permission; //files only vector memory; uint64_t offset = 0; struct Compression { string type; uint size = 0; //decompressed size; memory.size() == compressed size } compression; }; auto Node::create(string name, string location) -> shared_pointer { if(!inode::exists(location)) return {}; shared_pointer node = new Node; node->name = name; node->timestamps = true; node->timestamp.created = chrono::utc::datetime(inode::timestamp(location, inode::time::create)); node->timestamp.modified = chrono::utc::datetime(inode::timestamp(location, inode::time::modify)); node->timestamp.accessed = chrono::utc::datetime(inode::timestamp(location, inode::time::access)); uint mode = inode::mode(location); node->permissions = true; node->permission.owner.name = inode::owner(location); node->permission.group.name = inode::group(location); node->permission.owner.readable = mode & 0400; node->permission.owner.writable = mode & 0200; node->permission.owner.executable = mode & 0100; node->permission.group.readable = mode & 0040; node->permission.group.writable = mode & 0020; node->permission.group.executable = mode & 0010; node->permission.other.readable = mode & 0004; node->permission.other.writable = mode & 0002; node->permission.other.executable = mode & 0001; if(file::exists(location)) { node->memory = file::read(location); } return node; } auto Node::createPath(string name) -> shared_pointer { if(!name) return {}; shared_pointer node = new Node; node->name = name; return node; } auto Node::createFile(string name, array_view memory) -> shared_pointer { if(!name) return {}; shared_pointer node = new Node; node->name = name; node->memory.resize(memory.size()); memory::copy(node->memory.data(), memory.data(), memory.size()); return node; } auto Node::metadata(bool indented) const -> string { string metadata; if(!name) return metadata; string indent; if(indented) { indent.append(" "); auto bytes = string{name}.trimRight("/"); for(auto& byte : bytes) { if(byte == '/') indent.append(" "); } } if(isPath()) { metadata.append(indent, "path: ", name, "\n"); } if(isFile()) { metadata.append(indent, "file: ", name, "\n"); } if(timestamps) { metadata.append(indent, " timestamp\n"); if(timestamp.created != timestamp.modified) metadata.append(indent, " created: ", timestamp.created, "\n"); metadata.append(indent, " modified: ", timestamp.modified, "\n"); if(timestamp.accessed != timestamp.modified) metadata.append(indent, " accessed: ", timestamp.accessed, "\n"); } if(permissions) { metadata.append(indent, " permission\n"); metadata.append(indent, " owner: ", permission.owner.name, "\n"); if(permission.owner.readable) metadata.append(indent, " readable\n"); if(permission.owner.writable) metadata.append(indent, " writable\n"); if(permission.owner.executable) metadata.append(indent, " executable\n"); metadata.append(indent, " group: ", permission.group.name, "\n"); if(permission.group.readable) metadata.append(indent, " readable\n"); if(permission.group.writable) metadata.append(indent, " writable\n"); if(permission.group.executable) metadata.append(indent, " executable\n"); metadata.append(indent, " other\n"); if(permission.other.readable) metadata.append(indent, " readable\n"); if(permission.other.writable) metadata.append(indent, " writable\n"); if(permission.other.executable) metadata.append(indent, " executable\n"); } if(isFile()) { metadata.append(indent, " offset: ", offset, "\n"); if(!isCompressed()) { metadata.append(indent, " size: ", memory.size(), "\n"); } else { metadata.append(indent, " size: ", compression.size, "\n"); metadata.append(indent, " compression: ", compression.type, "\n"); metadata.append(indent, " size: ", memory.size(), "\n"); } } return metadata; } auto Node::unserialize(array_view container, Markup::Node metadata) -> bool { *this = {}; if(!metadata.text()) return false; name = metadata.text(); if(auto node = metadata["timestamp"]) { timestamps = true; if(auto created = node["created" ]) timestamp.created = created.text(); if(auto modified = node["modified"]) timestamp.modified = modified.text(); if(auto accessed = node["accessed"]) timestamp.accessed = accessed.text(); } if(auto node = metadata["permission"]) { permissions = true; if(auto owner = node["owner"]) { permission.owner.name = owner.text(); permission.owner.readable = (bool)owner["readable"]; permission.owner.writable = (bool)owner["writable"]; permission.owner.executable = (bool)owner["executable"]; } if(auto group = node["group"]) { permission.group.name = group.text(); permission.group.readable = (bool)group["readable"]; permission.group.writable = (bool)group["writable"]; permission.group.executable = (bool)group["executable"]; } if(auto other = node["other"]) { permission.other.readable = (bool)other["readable"]; permission.other.writable = (bool)other["writable"]; permission.other.executable = (bool)other["executable"]; } } if(isPath()) return true; uint offset = metadata["offset"].natural(); uint size = metadata["size"].natural(); if(metadata["compression"]) { size = metadata["compression/size"].natural(); compression.type = metadata["compression"].text(); } if(offset + size >= container.size()) return false; memory.reallocate(size); nall::memory::copy(memory.data(), container.view(offset, size), size); return true; } auto Node::compressLZSA() -> bool { if(!memory) return true; //don't compress empty files if(isCompressed()) return true; //don't recompress files auto compressedMemory = Encode::LZSA(memory); if(compressedMemory.size() >= memory.size()) return true; //can't compress smaller than original size compression.type = "lzsa"; compression.size = memory.size(); memory = move(compressedMemory); return true; } auto Node::decompress() -> bool { if(!isCompressed()) return true; if(compression.type == "lzsa") { compression = {}; memory = Decode::LZSA(memory); return (bool)memory; } return false; } auto Node::getTimestamp(string type) const -> uint64_t { if(!timestamps) return time(nullptr); string value = chrono::utc::datetime(); if(type == "created" ) value = timestamp.created; if(type == "modified") value = timestamp.modified; if(type == "accessed") value = timestamp.accessed; #if !defined(PLATFORM_WINDOWS) struct tm timeInfo{}; if(strptime(value, "%Y-%m-%d %H:%M:%S", &timeInfo) != nullptr) { //todo: not thread safe ... auto tz = getenv("TZ"); setenv("TZ", "", 1); timeInfo.tm_isdst = -1; auto result = mktime(&timeInfo); if(tz) setenv("TZ", tz, 1); else unsetenv("TZ"); if(result != -1) return result; } #endif return time(nullptr); } auto Node::getPermissions() const -> uint { if(!permissions) return 0755; uint mode = 0; if(permission.owner.readable ) mode |= 0400; if(permission.owner.writable ) mode |= 0200; if(permission.owner.executable) mode |= 0100; if(permission.group.readable ) mode |= 0040; if(permission.group.writable ) mode |= 0020; if(permission.group.executable) mode |= 0010; if(permission.other.readable ) mode |= 0004; if(permission.other.writable ) mode |= 0002; if(permission.other.executable) mode |= 0001; return mode; } auto Node::getOwner() const -> string { if(!permissions || !permission.owner.name) { #if !defined(PLATFORM_WINDOWS) struct passwd* pwd = getpwuid(getuid()); assert(pwd); return pwd->pw_name; #endif } return permission.owner.name; } auto Node::getGroup() const -> string { if(!permissions || !permission.group.name) { #if !defined(PLATFORM_WINDOWS) struct group* grp = getgrgid(getgid()); assert(grp); return grp->gr_name; #endif } return permission.group.name; } }