|
| 1 | +#include "output.h" |
| 2 | +#include "filesystem.h" |
| 3 | +#include "filesystem_stream.h" |
| 4 | + |
| 5 | +#include <fmt/core.h> |
| 6 | +#include <string> |
| 7 | +#include <variant> |
| 8 | +#include <string_view> |
| 9 | +#include <iostream> |
| 10 | + |
| 11 | +#include "filesystem_tar.h" |
| 12 | + |
| 13 | +static std::string cstr_to_string_safe(const char *s, size_t n) { |
| 14 | + std::string target; |
| 15 | + target.resize(n); |
| 16 | + strncat(target.data(), s, n); |
| 17 | + if (auto nul = target.find('\0'); nul != std::string::npos) { |
| 18 | + target.resize(nul); |
| 19 | + target.shrink_to_fit(); |
| 20 | + } |
| 21 | + return target; |
| 22 | +} |
| 23 | + |
| 24 | +TarFilesystem::Entry::Entry() {} |
| 25 | + |
| 26 | +TarFilesystem::Entry::Entry(std::string dirname) : name(dirname), data(Children()) {} |
| 27 | + |
| 28 | +TarFilesystem::Entry::Entry(long offs, tar_entry_raw from, long *skip) { |
| 29 | + name = cstr_to_string_safe(from.data.ustar.path_prefix, sizeof(from.data.ustar.path_prefix)) + cstr_to_string_safe(from.data.filename, sizeof(from.data.filename)); |
| 30 | + |
| 31 | + std::string sizestr = cstr_to_string_safe(from.data.size, sizeof(from.data.size)); |
| 32 | + char *end; |
| 33 | + long filesize = strtol(sizestr.c_str(), &end, 8); |
| 34 | + |
| 35 | + auto magic = cstr_to_string_safe(from.data.ustar.magic, 5); |
| 36 | + if (magic != "ustar") { |
| 37 | + Output::Debug("TarFilesystem: invalid magic '{}' (offset {})", magic, offs); |
| 38 | + invalidate(); |
| 39 | + return; |
| 40 | + } |
| 41 | + |
| 42 | + switch (from.data.type) { |
| 43 | + case '0': |
| 44 | + case '\0': |
| 45 | + // BSD manpage tar(5): Numeric fields must be terminated with either space or NUL characters |
| 46 | + if (*end != 0 && *end != ' ') { |
| 47 | + Output::Debug("TarFilesystem: couldn't parse file size (file {}, offset {})", name, offs); |
| 48 | + invalidate(); |
| 49 | + return; |
| 50 | + } |
| 51 | + data = FileEntryInfo{offs, filesize}; |
| 52 | + if ((*skip = filesize) % 512 != 0) |
| 53 | + *skip += 512 - *skip % 512; |
| 54 | + break; |
| 55 | + case '5': |
| 56 | + if (filesize != 0 || (*end != 0 && *end != ' ')) { |
| 57 | + Output::Debug("TarFilesystem: invalid size for directory (file {}, offset {})", name, offs); |
| 58 | + invalidate(); |
| 59 | + return; |
| 60 | + } |
| 61 | + data = Children(); |
| 62 | + *skip = 0; |
| 63 | + break; |
| 64 | + default: |
| 65 | + Output::Debug("TarFilesystem: unknown format '{}' (file {})", from.data.type, name); |
| 66 | + invalidate(); |
| 67 | + } |
| 68 | +} |
| 69 | + |
| 70 | +bool TarFilesystem::Entry::valid() const { |
| 71 | + return !std::get_if<std::monostate>(&data); |
| 72 | +} |
| 73 | + |
| 74 | +void TarFilesystem::Entry::invalidate() { |
| 75 | + data = std::monostate(); |
| 76 | +} |
| 77 | + |
| 78 | +bool TarFilesystem::Entry::add(Entry &&entry) { |
| 79 | + assert(!std::get_if<std::monostate>(&entry.data)); |
| 80 | + |
| 81 | + auto slashloc = entry.name.rfind('/'); |
| 82 | + auto dirname = slashloc == std::string::npos ? "" : entry.name.substr(0, slashloc); |
| 83 | + entry.name = entry.name.substr(slashloc + 1); |
| 84 | + |
| 85 | + auto parent_entry = locate(dirname, true); |
| 86 | + if (!parent_entry) { |
| 87 | + Output::Debug("TarFilesystem: invalid path to archive entry"); |
| 88 | + return false; |
| 89 | + } |
| 90 | + |
| 91 | + if (entry.name.empty()) { |
| 92 | + // Is a directory (name becomes empty when ending on /) |
| 93 | + // "locate" already adds the directory |
| 94 | + return true; |
| 95 | + } |
| 96 | + |
| 97 | + std::get<Children>(parent_entry->data)[entry.name] = std::move(entry); |
| 98 | + return true; |
| 99 | +} |
| 100 | + |
| 101 | +TarFilesystem::Entry *TarFilesystem::Entry::locate(std::string path, bool auto_create_dirs) { |
| 102 | + if (path == "") |
| 103 | + return this; |
| 104 | + |
| 105 | + if (name != "") { |
| 106 | + assert(path.substr(0, path.find('/')) == name); |
| 107 | + path = path.substr(1 + path.find('/')); |
| 108 | + } |
| 109 | + |
| 110 | + auto *dirdata = std::get_if<Children>(&data); |
| 111 | + if (!dirdata) |
| 112 | + return nullptr; |
| 113 | + |
| 114 | + auto subdir = path.substr(0, path.find('/')); |
| 115 | + if (dirdata->find(subdir) == dirdata->end() && auto_create_dirs) |
| 116 | + (*dirdata)[subdir] = Entry(subdir); |
| 117 | + |
| 118 | + auto iter = dirdata->find(subdir); |
| 119 | + if (iter == dirdata->end()) |
| 120 | + return nullptr; |
| 121 | + return path.find('/') == std::string::npos ? &iter->second : iter->second.locate(std::move(path), auto_create_dirs); |
| 122 | +} |
| 123 | + |
| 124 | +const TarFilesystem::Entry *TarFilesystem::Entry::locate(std::string path) const { |
| 125 | + return const_cast<Entry *>(this)->locate(path, false); |
| 126 | +} |
| 127 | + |
| 128 | +TarFilesystem::TarFilesystem(std::string base_path, FilesystemView parent_fs) : Filesystem(base_path, parent_fs), stream(parent_fs.OpenInputStream(GetPath())) { |
| 129 | + if (!stream) { |
| 130 | + Output::Warning("Failed to open {}", base_path); |
| 131 | + rootdir.invalidate(); |
| 132 | + return; |
| 133 | + } |
| 134 | + |
| 135 | + tar_entry_raw data; |
| 136 | + int blank = 0, n = 0; |
| 137 | + static const char key[32] = { 0 }; |
| 138 | + while (stream.ReadIntoObj(data)) { |
| 139 | + if (memcmp(&data, key, sizeof(key)) == 0 && memcmp(&data, sizeof(key) + (const char *)&data, sizeof(data) - sizeof(key)) == 0) { |
| 140 | + if (++blank == 2) |
| 141 | + return; |
| 142 | + continue; |
| 143 | + } |
| 144 | + blank = 0; |
| 145 | + |
| 146 | + ++n; |
| 147 | + |
| 148 | + long skip; |
| 149 | + Entry entry { (long)stream.tellg(), data, &skip }; |
| 150 | + if (!entry.valid()) { |
| 151 | + rootdir.invalidate(); |
| 152 | + return; |
| 153 | + } |
| 154 | + if (skip) |
| 155 | + stream.seekg(skip, std::ios_base::cur); |
| 156 | + |
| 157 | + if (entry.name == "") |
| 158 | + continue; |
| 159 | + if (!rootdir.add(std::move(entry))) { |
| 160 | + rootdir.invalidate(); |
| 161 | + return; |
| 162 | + } |
| 163 | + } |
| 164 | + Output::Warning("TarFilesystem: tar file has no terminator"); |
| 165 | +} |
| 166 | + |
| 167 | +bool TarFilesystem::IsFile(std::string_view path) const { |
| 168 | + auto entry = rootdir.locate(std::string(path)); |
| 169 | + if (!entry) { |
| 170 | + return false; |
| 171 | + } |
| 172 | + return !!std::get_if<Entry::FileEntryInfo>(&entry->data); |
| 173 | +} |
| 174 | + |
| 175 | +bool TarFilesystem::IsDirectory(std::string_view path, bool follow_symlinks) const { |
| 176 | + (void)follow_symlinks; |
| 177 | + |
| 178 | + auto entry = rootdir.locate(std::string(path)); |
| 179 | + if (!entry) |
| 180 | + return false; |
| 181 | + return !!std::get_if<Entry::Children>(&entry->data); |
| 182 | +} |
| 183 | + |
| 184 | +bool TarFilesystem::Exists(std::string_view path) const { |
| 185 | + if (!rootdir.valid() && path == "") |
| 186 | + return false; // Filesystem::IsValid requires this behavior |
| 187 | + return !!rootdir.locate(std::string(path)); |
| 188 | +} |
| 189 | + |
| 190 | +int64_t TarFilesystem::GetFilesize(std::string_view path) const { |
| 191 | + auto entry = rootdir.locate(std::string(path)); |
| 192 | + if (!entry) |
| 193 | + return 0; |
| 194 | + auto info = std::get_if<Entry::FileEntryInfo>(&entry->data); |
| 195 | + if (!info) |
| 196 | + return 0; |
| 197 | + return info->size; |
| 198 | +} |
| 199 | + |
| 200 | +std::streambuf* TarFilesystem::CreateInputStreambuffer(std::string_view path, std::ios_base::openmode) const { |
| 201 | + std::vector<uint8_t> data; |
| 202 | + |
| 203 | + auto entry = rootdir.locate(std::string(path)); |
| 204 | + if (!entry) { |
| 205 | + return nullptr; |
| 206 | + } |
| 207 | + auto info = std::get_if<Entry::FileEntryInfo>(&entry->data); |
| 208 | + if (!info) { |
| 209 | + return nullptr; |
| 210 | + } |
| 211 | + data.resize(info->size); |
| 212 | + |
| 213 | + stream.seekg(info->offs, std::ios_base::beg); |
| 214 | + if (stream.read(reinterpret_cast<char*>(data.data()), info->size).gcount() != info->size) { |
| 215 | + Output::Warning("TarFilesystem: error reading archive"); |
| 216 | + return nullptr; |
| 217 | + } |
| 218 | + |
| 219 | + return new Filesystem_Stream::InputMemoryStreamBuf(std::move(data)); |
| 220 | +} |
| 221 | + |
| 222 | +bool TarFilesystem::GetDirectoryContent(std::string_view path, std::vector<DirectoryTree::Entry>& entries) const { |
| 223 | + auto entry = rootdir.locate(std::string(path)); |
| 224 | + if (!entry) { |
| 225 | + Output::Debug("TarFilesystem: no file exists at path {}", path); |
| 226 | + return false; |
| 227 | + } |
| 228 | + auto contents = std::get_if<Entry::Children>(&entry->data); |
| 229 | + if (!contents) { |
| 230 | + Output::Debug("TarFilesystem: {} is a file", path); |
| 231 | + return false; |
| 232 | + } |
| 233 | + for (const auto &pair : *contents) |
| 234 | + entries.emplace_back(std::string(pair.first), !std::get_if<Entry::FileEntryInfo>(&pair.second.data) ? DirectoryTree::FileType::Directory : DirectoryTree::FileType::Regular); |
| 235 | + return true; |
| 236 | +} |
| 237 | + |
| 238 | +std::string TarFilesystem::Describe() const { |
| 239 | + return fmt::format("[Tar] {}", GetPath()); |
| 240 | +} |
0 commit comments