diff --git a/CMakeLists.txt b/CMakeLists.txt index bc60c7c..624f795 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,8 +12,8 @@ endif() add_definitions(-D_CRT_SECURE_NO_WARNINGS) -set (V8UNPACK_SOURCES src/main.cpp src/V8File.cpp src/utils.cpp src/placeholder216.cpp src/VersionFile.cpp) -set (V8UNPACK_HEADERS src/V8File.h src/version.h src/zconf.h src/zlib.h src/VersionFile.h) +set (V8UNPACK_SOURCES src/main.cpp src/V8File.cpp src/utils.cpp src/placeholder216.cpp src/VersionFile.cpp src/VersionsTable.cpp) +set (V8UNPACK_HEADERS src/V8File.h src/version.h src/zconf.h src/zlib.h src/VersionFile.h src/VersionsTable.h) if((MSVC) AND (NOT DISABLE_SINGLE_FILE)) diff --git a/README.md b/README.md index dc081f7..c0a3bc4 100755 --- a/README.md +++ b/README.md @@ -41,7 +41,8 @@ sudo dpkg -i v8unpack.deb ## Использование ``` - -U[NPACK] in_filename.cf out_dirname + -U[NPACK] in_filename.cf out_dirname [block_name] + -U[NPACK] in_filename.cf - block_name -U[NPACK] -L[IST] listfile -PA[CK] in_dirname out_filename.cf -PA[CK] -L[IST] listfile @@ -51,6 +52,18 @@ sudo dpkg -i v8unpack.deb -D[EFLATE] -L[IST] listfile -P[ARSE] in_filename out_dirname -P[ARSE] -L[IST] listfile + -DEL[ETE] in_filename [block_mask1 block_mask2 ...] + -DEL[ETE] -L[IST] listfile + -ADD [-PACK|-BUILD [-NOPACK]] [-N[AME] name] source|- out_filename + -ADD [-PACK|-BUILD [-NOPACK]] -LISTFILES|-LF listfile out_filename + -PUT [-PACK|-BUILD [-NOPACK]] [-N[AME] name] source|- out_filename + -PUT [-PACK|-BUILD [-NOPACK]] -LISTFILES|-LF listfile out_filename -B[UILD] [-N[OPACK]] in_dirname out_filename -B[UILD] [-N[OPACK]] -L[IST] listfile + -VERSIONSFILE|-VF -SHOW in_filename + -VERSIONSFILE|-VF -GET in_filename block_name + -VERSIONSFILE|-VF -SET in_filename block_name version + -VERSIONSFILE|-VF -SET -LIST|-LF listfile in_filename + -VERSIONSFILE|-VF -UPDATE in_filename block_name + -VERSIONSFILE|-VF -UPDATE -LIST|-LF listfile in_filename ``` diff --git a/package/deb/bash_completion.sh b/package/deb/bash_completion.sh index 72e0e81..de573b3 100644 --- a/package/deb/bash_completion.sh +++ b/package/deb/bash_completion.sh @@ -4,8 +4,8 @@ _v8unpack_complete() COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD]}" - opts="-unpack -pack -parse -build -inflate -deflate \ - -list -example -bat -version -listfiles" + opts="-unpack -pack -parse -build -inflate -deflate -delete -add -put \ + -list -example -bat -version -listfiles -versionsfile -vf" if [[ ${COMP_CWORD} == 1 ]] ; then COMPREPLY=( $(compgen -W "$opts" -- ${cur}) ) diff --git a/src/V8File.cpp b/src/V8File.cpp index 0318a33..2ac0207 100644 --- a/src/V8File.cpp +++ b/src/V8File.cpp @@ -19,6 +19,7 @@ at http://mozilla.org/MPL/2.0/. #include "VersionFile.h" #include #include +#include #include #include #include @@ -549,6 +550,9 @@ static int recursive_unpack(const string& directory, basic_istream& file, ElemsNum = i; break; } + if (pElemsAddrs[i].elem_header_addr == format::UNDEFINED_VALUE) { + continue; + } file.seekg(pElemsAddrs[i].elem_header_addr + format::BASE_OFFSET, ios_base::beg); @@ -593,6 +597,9 @@ static int list_files(boost::filesystem::ifstream &file) ElemsNum = i; break; } + if (pElemsAddrs[i].elem_header_addr == format::UNDEFINED_VALUE) { + continue; + } file.seekg(pElemsAddrs[i].elem_header_addr + format::BASE_OFFSET, ios_base::beg); @@ -632,9 +639,746 @@ int ListFiles(const string &filename) return list_files(file); } +static bool +MatchMask(const string &name, const string &mask) +{ + size_t n = 0; + size_t m = 0; + size_t star_n = string::npos; + size_t star_m = string::npos; + + while (n < name.size()) { + if (m < mask.size() && (mask[m] == '?' || mask[m] == name[n])) { + ++n; + ++m; + } else if (m < mask.size() && mask[m] == '*') { + star_n = n; + star_m = m; + ++m; + } else if (star_m != string::npos) { + ++star_n; + n = star_n; + m = star_m + 1; + } else { + return false; + } + } + + while (m < mask.size() && mask[m] == '*') { + ++m; + } + + return m == mask.size(); +} + +static bool +NameMatchesMasks(const string &name, const vector &masks) +{ + for (const auto &mask : masks) { + if (MatchMask(name, mask)) { + return true; + } + } + return false; +} + +template +static bool append_block_to_free_list( + stream_t &file, + addr_t block_addr, + addr_t &free_head) +{ + if (block_addr == format::UNDEFINED_VALUE) { + return true; + } + + auto current = block_addr; + typename format::block_header_t header; + + for (;;) { + file.clear(); + file.seekg(current + format::BASE_OFFSET, ios_base::beg); + file.read(reinterpret_cast(&header), header.Size()); + if (!file || !header.IsCorrect()) { + return false; + } + + auto next = header.next_page_addr(); + if (next == format::UNDEFINED_VALUE) { + break; + } + current = next; + } + + header.set_next_page_addr(free_head); + file.clear(); + file.seekp(current + format::BASE_OFFSET, ios_base::beg); + file.write(reinterpret_cast(&header), header.Size()); + if (!file) { + return false; + } + + free_head = block_addr; + return true; +} + +template +static bool write_into_block( + stream_t &file, + std::streamoff block_pos, + size_t offset_in_data, + const char *src, + size_t len) +{ + if (len == 0) { + return true; + } + + const size_t end = offset_in_data + len; + size_t data_cursor = 0; + std::streamoff page_pos = block_pos; + + for (;;) { + file.clear(); + file.seekg(page_pos, ios_base::beg); + typename format::block_header_t header; + file.read(reinterpret_cast(&header), header.Size()); + if (!file || !header.IsCorrect()) { + return false; + } + + const auto page_size = header.page_size(); + const auto next = header.next_page_addr(); + const std::streamoff page_data = page_pos + static_cast(header.Size()); + const size_t page_end = data_cursor + static_cast(page_size); + + if (offset_in_data < page_end && end > data_cursor) { + const size_t from = MAX(offset_in_data, data_cursor); + const size_t to = MIN(end, page_end); + const size_t chunk = to - from; + file.clear(); + file.seekp(page_data + static_cast(from - data_cursor), ios_base::beg); + file.write(src + (from - offset_in_data), chunk); + if (!file) { + return false; + } + } + + if (end <= page_end) { + return true; + } + if (next == format::UNDEFINED_VALUE) { + return false; + } + + data_cursor = page_end; + page_pos = static_cast(next + format::BASE_OFFSET); + } +} + +template +static int delete_blocks(boost::filesystem::fstream &file, const string &filename, const vector &masks) +{ + typename format::file_header_t FileHeader; + file.seekg(format::BASE_OFFSET, ios_base::beg); + file.read(reinterpret_cast(&FileHeader), FileHeader.Size()); + if (!file) { + return V8UNPACK_NOT_V8_FILE; + } + + const auto toc_pos = format::BASE_OFFSET + static_cast(format::file_header_t::Size()); + auto pElemsAddrs = ReadElementsAllocationTable(file); + auto ElemsNum = pElemsAddrs.size(); + + auto free_head = FileHeader.next_page_addr; + size_t removed = 0; + + for (uint32_t i = 0; i < ElemsNum; i++) { + if (pElemsAddrs[i].fffffff != format::UNDEFINED_VALUE) { + break; + } + if (pElemsAddrs[i].elem_header_addr == format::UNDEFINED_VALUE) { + continue; + } + + file.clear(); + file.seekg(pElemsAddrs[i].elem_header_addr + format::BASE_OFFSET, ios_base::beg); + + CV8Elem elem; + if (!SafeReadBlockData(file, elem.header)) { + return V8UNPACK_HEADER_ELEM_NOT_CORRECT; + } + + if (!NameMatchesMasks(elem.GetName(), masks)) { + continue; + } + + if (!append_block_to_free_list(file, pElemsAddrs[i].elem_header_addr, free_head)) { + return V8UNPACK_HEADER_ELEM_NOT_CORRECT; + } + if (!append_block_to_free_list(file, pElemsAddrs[i].elem_data_addr, free_head)) { + return V8UNPACK_HEADER_ELEM_NOT_CORRECT; + } + + pElemsAddrs[i].elem_header_addr = format::UNDEFINED_VALUE; + pElemsAddrs[i].elem_data_addr = format::UNDEFINED_VALUE; + + if (!write_into_block( + file, + toc_pos, + static_cast(i) * format::elem_addr_t::Size(), + reinterpret_cast(&pElemsAddrs[i]), + format::elem_addr_t::Size())) { + return V8UNPACK_ERROR; + } + + ++removed; + } + + if (removed != 0) { + FileHeader.next_page_addr = free_head; + file.clear(); + file.seekp(format::BASE_OFFSET, ios_base::beg); + file.write(reinterpret_cast(&FileHeader), FileHeader.Size()); + if (!file) { + return V8UNPACK_ERROR_CREATING_OUTPUT_FILE; + } + } + + cout << "Delete `" << filename << "`: ok" << endl << flush; + return V8UNPACK_OK; +} + +int DeleteBlocks(const string &filename, const vector &masks) +{ + boost::filesystem::fstream file(filename, ios_base::in | ios_base::out | ios_base::binary); + + if (!file) { + cerr << "Delete `" << filename << "`. Input file not found!" << endl; + return V8UNPACK_SOURCE_DOES_NOT_EXIST; + } + + if (IsV8File16ZeroBased(file)) { + return delete_blocks(file, filename, masks); + } + + if (!IsV8File(file)) { + cerr << "Delete `" << filename << "` is not V8 file!" << endl; + return V8UNPACK_NOT_V8_FILE; + } + + if (IsV8File16(file)) { + return delete_blocks(file, filename, masks); + } + + return delete_blocks(file, filename, masks); +} + +static string +elem_name_from_source(const string &source, const string &explicit_name) +{ + if (!explicit_name.empty()) { + return explicit_name; + } + return boost::filesystem::path(source).filename().string(); +} + +static int +read_all_bytes(istream &in, vector &data) +{ + data.assign(istreambuf_iterator(in), istreambuf_iterator()); + return V8UNPACK_OK; +} + +static int +read_file_bytes(const string &path, vector &data) +{ + boost::filesystem::ifstream in(path, ios_base::binary); + if (!in) { + cerr << "Add. Source not found: " << path << endl; + return V8UNPACK_SOURCE_DOES_NOT_EXIST; + } + return read_all_bytes(in, data); +} + +static int +prepare_add_payload(AddMode mode, const AddItem &item, vector &header, vector &data) +{ + const bool from_stdin = item.source == "-"; + const string name = elem_name_from_source(item.source, item.name); + if (name.empty()) { + return V8UNPACK_SHOW_USAGE; + } + + CV8Elem elem(name); + const bool deflate = (mode == AddMode::Build); + + if (from_stdin) { + read_all_bytes(cin, elem.data); + if (deflate) { + int ret = elem.Pack(true); + if (ret) { + return ret; + } + } + header = elem.header; + data = elem.data; + return V8UNPACK_OK; + } + + if (!boost::filesystem::exists(item.source)) { + cerr << "Add. Source not found: " << item.source << endl; + return V8UNPACK_SOURCE_DOES_NOT_EXIST; + } + + if (boost::filesystem::is_directory(item.source)) { + if (mode == AddMode::Pack) { + cerr << "Add. Directory source requires -BUILD" << endl; + return V8UNPACK_SHOW_USAGE; + } + elem.IsV8File = true; + elem.UnpackedData.LoadFileFromFolder(item.source); + int ret = elem.Pack(deflate); + if (ret) { + return ret; + } + header = elem.header; + data = elem.data; + return V8UNPACK_OK; + } + + int ret = read_file_bytes(item.source, elem.data); + if (ret != V8UNPACK_OK) { + return ret; + } + if (deflate) { + ret = elem.Pack(true); + if (ret) { + return ret; + } + } + header = elem.header; + data = elem.data; + return V8UNPACK_OK; +} + +template +static bool take_free_page( + stream_t &file, + addr_t &free_head, + size_t min_page_size, + addr_t &out_addr, + typename format::block_header_t &out_header) +{ + addr_t prev = format::UNDEFINED_VALUE; + addr_t curr = free_head; + + while (curr != format::UNDEFINED_VALUE) { + file.clear(); + file.seekg(curr + format::BASE_OFFSET, ios_base::beg); + typename format::block_header_t header; + file.read(reinterpret_cast(&header), header.Size()); + if (!file || !header.IsCorrect()) { + return false; + } + + const auto next = header.next_page_addr(); + if (header.page_size() >= min_page_size) { + if (prev == format::UNDEFINED_VALUE) { + free_head = next; + } else { + file.clear(); + file.seekg(prev + format::BASE_OFFSET, ios_base::beg); + typename format::block_header_t prev_header; + file.read(reinterpret_cast(&prev_header), prev_header.Size()); + if (!file || !prev_header.IsCorrect()) { + return false; + } + prev_header.set_next_page_addr(next); + file.clear(); + file.seekp(prev + format::BASE_OFFSET, ios_base::beg); + file.write(reinterpret_cast(&prev_header), prev_header.Size()); + if (!file) { + return false; + } + } + out_addr = curr; + out_header = header; + return true; + } + + prev = curr; + curr = next; + } + + return false; +} + +template +static bool allocate_block( + stream_t &file, + addr_t &free_head, + const char *data, + size_t data_size, + size_t preferred_page_size, + addr_t &out_addr) +{ + size_t page_size = preferred_page_size; + if (page_size < data_size) { + page_size = data_size; + } + + typename format::block_header_t reused; + if (take_free_page(file, free_head, data_size, out_addr, reused)) { + reused.set_data_size(data_size); + reused.set_next_page_addr(format::UNDEFINED_VALUE); + file.clear(); + file.seekp(out_addr + format::BASE_OFFSET, ios_base::beg); + file.write(reinterpret_cast(&reused), reused.Size()); + if (data_size != 0) { + file.write(data, data_size); + } + return static_cast(file); + } + + file.clear(); + file.seekp(0, ios_base::end); + const auto endpos = file.tellp(); + out_addr = static_cast(static_cast(endpos) - format::BASE_OFFSET); + + typename format::block_header_t header = format::block_header_t::create( + static_cast(data_size), + static_cast(page_size)); + file.write(reinterpret_cast(&header), header.Size()); + if (data_size != 0) { + file.write(data, data_size); + } + for (size_t i = page_size - data_size; i; --i) { + file << '\0'; + } + return static_cast(file); +} + +template +static bool toc_last_page( + stream_t &file, + std::streamoff toc_pos, + size_t &capacity, + std::streamoff &last_pos, + typename format::block_header_t &last_header) +{ + capacity = 0; + std::streamoff pos = toc_pos; + + for (;;) { + file.clear(); + file.seekg(pos, ios_base::beg); + typename format::block_header_t header; + file.read(reinterpret_cast(&header), header.Size()); + if (!file || !header.IsCorrect()) { + return false; + } + capacity += static_cast(header.page_size()); + last_pos = pos; + last_header = header; + if (header.next_page_addr() == format::UNDEFINED_VALUE) { + return true; + } + pos = static_cast(header.next_page_addr() + format::BASE_OFFSET); + } +} + +template +static bool ensure_toc_capacity( + stream_t &file, + std::streamoff toc_pos, + size_t needed, + addr_t &free_head) +{ + size_t capacity = 0; + std::streamoff last_pos = toc_pos; + typename format::block_header_t last_header; + + if (!toc_last_page(file, toc_pos, capacity, last_pos, last_header)) { + return false; + } + + while (capacity < needed) { + addr_t new_addr = 0; + if (!allocate_block( + file, + free_head, + nullptr, + 0, + format::DEFAULT_PAGE_SIZE, + new_addr)) { + return false; + } + + last_header.set_next_page_addr(new_addr); + file.clear(); + file.seekp(last_pos, ios_base::beg); + file.write(reinterpret_cast(&last_header), last_header.Size()); + if (!file) { + return false; + } + + capacity += format::DEFAULT_PAGE_SIZE; + last_pos = static_cast(new_addr + format::BASE_OFFSET); + file.clear(); + file.seekg(last_pos, ios_base::beg); + file.read(reinterpret_cast(&last_header), last_header.Size()); + if (!file || !last_header.IsCorrect()) { + return false; + } + } + + return true; +} + +template +static int find_unused_toc_index(const vector &toc) +{ + for (size_t i = 0; i < toc.size(); i++) { + if (toc[i].fffffff != format::UNDEFINED_VALUE) { + break; + } + if (toc[i].elem_header_addr == format::UNDEFINED_VALUE) { + return static_cast(i); + } + } + return -1; +} + +template +static int find_toc_index_by_name( + stream_t &file, + const vector &toc, + const string &name) +{ + for (size_t i = 0; i < toc.size(); i++) { + if (toc[i].fffffff != format::UNDEFINED_VALUE) { + break; + } + if (toc[i].elem_header_addr == format::UNDEFINED_VALUE) { + continue; + } + + file.clear(); + file.seekg(toc[i].elem_header_addr + format::BASE_OFFSET, ios_base::beg); + CV8Elem elem; + if (!SafeReadBlockData(file, elem.header)) { + return -2; + } + if (elem.GetName() == name) { + return static_cast(i); + } + } + return -1; +} + +template +static int add_items( + boost::filesystem::fstream &file, + const string &filename, + const vector &items, + AddMode mode, + bool replace) +{ + typename format::file_header_t FileHeader; + file.clear(); + file.seekg(format::BASE_OFFSET, ios_base::beg); + file.read(reinterpret_cast(&FileHeader), FileHeader.Size()); + if (!file) { + return V8UNPACK_NOT_V8_FILE; + } + + const auto toc_pos = format::BASE_OFFSET + static_cast(format::file_header_t::Size()); + auto toc = ReadElementsAllocationTable(file); + auto free_head = FileHeader.next_page_addr; + const char *op = replace ? "Put" : "Add"; + + for (const auto &item : items) { + const string name = elem_name_from_source(item.source, item.name); + int existing = find_toc_index_by_name(file, toc, name); + if (existing == -2) { + return V8UNPACK_HEADER_ELEM_NOT_CORRECT; + } + if (existing >= 0 && !replace) { + cerr << op << ". `" << name << "` already exists!" << endl; + return V8UNPACK_ELEM_ALREADY_EXISTS; + } + + vector header; + vector data; + int ret = prepare_add_payload(mode, item, header, data); + if (ret != V8UNPACK_OK) { + return ret; + } + + if (existing >= 0) { + if (!append_block_to_free_list(file, toc[existing].elem_header_addr, free_head)) { + return V8UNPACK_HEADER_ELEM_NOT_CORRECT; + } + if (!append_block_to_free_list(file, toc[existing].elem_data_addr, free_head)) { + return V8UNPACK_HEADER_ELEM_NOT_CORRECT; + } + } + + typename format::elem_addr_t entry; + entry.fffffff = format::UNDEFINED_VALUE; + if (!allocate_block( + file, + free_head, + header.data(), + header.size(), + header.size(), + entry.elem_header_addr)) { + return V8UNPACK_ERROR_CREATING_OUTPUT_FILE; + } + if (!allocate_block( + file, + free_head, + data.data(), + data.size(), + format::DEFAULT_PAGE_SIZE, + entry.elem_data_addr)) { + return V8UNPACK_ERROR_CREATING_OUTPUT_FILE; + } + + size_t index; + if (existing >= 0) { + index = static_cast(existing); + toc[index] = entry; + } else { + int slot = find_unused_toc_index(toc); + if (slot >= 0) { + index = static_cast(slot); + toc[index] = entry; + } else { + index = toc.size(); + const size_t new_size = (index + 1) * format::elem_addr_t::Size(); + if (!ensure_toc_capacity(file, toc_pos, new_size, free_head)) { + return V8UNPACK_ERROR; + } + + file.clear(); + file.seekg(toc_pos, ios_base::beg); + typename format::block_header_t toc_header; + file.read(reinterpret_cast(&toc_header), toc_header.Size()); + if (!file || !toc_header.IsCorrect()) { + return V8UNPACK_ERROR; + } + toc_header.set_data_size(static_cast(new_size)); + file.clear(); + file.seekp(toc_pos, ios_base::beg); + file.write(reinterpret_cast(&toc_header), toc_header.Size()); + if (!file) { + return V8UNPACK_ERROR; + } + + toc.push_back(entry); + } + } + + if (!write_into_block( + file, + toc_pos, + index * format::elem_addr_t::Size(), + reinterpret_cast(&entry), + format::elem_addr_t::Size())) { + return V8UNPACK_ERROR; + } + } + + FileHeader.next_page_addr = free_head; + file.clear(); + file.seekp(format::BASE_OFFSET, ios_base::beg); + file.write(reinterpret_cast(&FileHeader), FileHeader.Size()); + if (!file) { + return V8UNPACK_ERROR_CREATING_OUTPUT_FILE; + } + + cout << op << " `" << filename << "`: ok" << endl << flush; + return V8UNPACK_OK; +} + +int AddToContainer(const string &filename, const vector &items, AddMode mode, bool replace) +{ + if (items.empty()) { + return V8UNPACK_SHOW_USAGE; + } + + const char *op = replace ? "Put" : "Add"; + boost::filesystem::fstream file(filename, ios_base::in | ios_base::out | ios_base::binary); + if (!file) { + cerr << op << " `" << filename << "`. Input file not found!" << endl; + return V8UNPACK_SOURCE_DOES_NOT_EXIST; + } + + if (IsV8File16ZeroBased(file)) { + return add_items(file, filename, items, mode, replace); + } + + if (!IsV8File(file)) { + cerr << op << " `" << filename << "` is not V8 file!" << endl; + return V8UNPACK_NOT_V8_FILE; + } + + if (IsV8File16(file)) { + return add_items(file, filename, items, mode, replace); + } + + return add_items(file, filename, items, mode, replace); +} + +template +static int unpack_block_to_stream(basic_istream &file, basic_ostream &out, const string &block_name) +{ + file.seekg(format::BASE_OFFSET, ios_base::beg); + typename format::file_header_t FileHeader; + file.read(reinterpret_cast(&FileHeader), FileHeader.Size()); + + auto pElemsAddrs = ReadElementsAllocationTable(file); + auto ElemsNum = pElemsAddrs.size(); + + for (uint32_t i = 0; i < ElemsNum; i++) { + if (pElemsAddrs[i].fffffff != format::UNDEFINED_VALUE) { + break; + } + if (pElemsAddrs[i].elem_header_addr == format::UNDEFINED_VALUE) { + continue; + } + + file.seekg(pElemsAddrs[i].elem_header_addr + format::BASE_OFFSET, ios_base::beg); + + CV8Elem elem; + if (!SafeReadBlockData(file, elem.header)) { + return V8UNPACK_HEADER_ELEM_NOT_CORRECT; + } + + if (elem.GetName() != block_name) { + continue; + } + + if (pElemsAddrs[i].elem_data_addr != format::UNDEFINED_VALUE) { + file.seekg(pElemsAddrs[i].elem_data_addr + format::BASE_OFFSET, ios_base::beg); + ReadBlockData(file, out); + } + return V8UNPACK_OK; + } + + cerr << "Unpack. Block `" << block_name << "` not found!" << endl; + return V8UNPACK_ERROR; +} + template static int unpack_to_folder(boost::filesystem::ifstream &file, const string &dirname, const string &UnpackElemWithName, bool print_progress) { + if (dirname == "-" && !UnpackElemWithName.empty()) { + return unpack_block_to_stream(file, cout, UnpackElemWithName); + } + int ret = V8UNPACK_OK; boost::filesystem::path p_dir(dirname); @@ -669,6 +1413,9 @@ static int unpack_to_folder(boost::filesystem::ifstream &file, const string &dir ElemsNum = i; break; } + if (pElemsAddrs[i].elem_header_addr == format::UNDEFINED_VALUE) { + continue; + } file.seekg(pElemsAddrs[i].elem_header_addr + offset, ios_base::beg); diff --git a/src/V8File.h b/src/V8File.h index 6295ce8..1e8b65f 100644 --- a/src/V8File.h +++ b/src/V8File.h @@ -42,6 +42,10 @@ const int V8UNPACK_NOT_V8_FILE = V8UNPACK_ERROR - 1; const int V8UNPACK_HEADER_ELEM_NOT_CORRECT = V8UNPACK_ERROR - 2; const int V8UNPACK_SOURCE_DOES_NOT_EXIST = V8UNPACK_ERROR - 3; const int V8UNPACK_ERROR_CREATING_OUTPUT_FILE = V8UNPACK_ERROR - 4; +const int V8UNPACK_ELEM_ALREADY_EXISTS = V8UNPACK_ERROR - 5; +const int V8UNPACK_NOT_VERSIONS_FILE = V8UNPACK_ERROR - 6; +const int V8UNPACK_ELEM_NOT_FOUND = V8UNPACK_ERROR - 7; +const int V8UNPACK_INVALID_VERSION = V8UNPACK_ERROR - 8; const int V8UNPACK_INFLATE_ERROR = V8UNPACK_ERROR - 20; const int V8UNPACK_INFLATE_IN_FILE_NOT_FOUND = V8UNPACK_INFLATE_ERROR - 1; const int V8UNPACK_INFLATE_OUT_FILE_NOT_CREATED = V8UNPACK_INFLATE_ERROR - 2; @@ -350,6 +354,25 @@ int Parse( ); int ListFiles(const std::string &filename); +int DeleteBlocks(const std::string &filename, const std::vector &masks); + +enum class AddMode { + Pack = 0, + Build, + BuildNopack +}; + +struct AddItem { + std::string source; + std::string name; +}; + +int AddToContainer( + const std::string &filename, + const std::vector &items, + AddMode mode, + bool replace = false +); bool IsV8File(std::basic_istream &file); bool IsV8File16ZeroBased(std::basic_istream& file); bool IsV8File16(std::basic_istream& file); diff --git a/src/VersionsTable.cpp b/src/VersionsTable.cpp new file mode 100644 index 0000000..5d2ce77 --- /dev/null +++ b/src/VersionsTable.cpp @@ -0,0 +1,648 @@ +/*---------------------------------------------------------- +This Source Code Form is subject to the terms of the +Mozilla Public License, v.2.0. If a copy of the MPL +was not distributed with this file, You can obtain one +at http://mozilla.org/MPL/2.0/. +----------------------------------------------------------*/ + +#include "VersionsTable.h" +#include "V8File.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace v8unpack { + +using namespace std; + +enum class VersionsKind { + Uuid, + Base64 +}; + +struct VersionEntry { + string name; + string version; + size_t version_begin = 0; + size_t version_end = 0; +}; + +struct VersionsDocument { + string data; + VersionsKind kind = VersionsKind::Uuid; + vector entries; +}; + +static bool is_ws(char c) +{ + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; +} + +static size_t skip_ws(const string &s, size_t i) +{ + while (i < s.size() && is_ws(s[i])) { + i++; + } + return i; +} + +static size_t skip_bom(const string &s) +{ + if (s.size() >= 3 + && static_cast(s[0]) == 0xef + && static_cast(s[1]) == 0xbb + && static_cast(s[2]) == 0xbf) { + return 3; + } + return 0; +} + +static bool match_brace(const string &s, size_t open, size_t &close) +{ + if (open >= s.size() || s[open] != '{') { + return false; + } + + int depth = 0; + bool in_str = false; + for (size_t i = open; i < s.size(); i++) { + char c = s[i]; + if (in_str) { + if (c == '"') { + if (i + 1 < s.size() && s[i + 1] == '"') { + i++; + continue; + } + in_str = false; + } + continue; + } + if (c == '"') { + in_str = true; + continue; + } + if (c == '{') { + depth++; + } else if (c == '}') { + depth--; + if (depth == 0) { + close = i + 1; + return true; + } + } + } + return false; +} + +static bool read_quoted(const string &s, size_t &i, string &value) +{ + i = skip_ws(s, i); + if (i >= s.size() || s[i] != '"') { + return false; + } + i++; + value.clear(); + while (i < s.size()) { + if (s[i] == '"') { + if (i + 1 < s.size() && s[i + 1] == '"') { + value.push_back('"'); + i += 2; + continue; + } + i++; + return true; + } + value.push_back(s[i]); + i++; + } + return false; +} + +static bool read_unquoted(const string &s, size_t &i, string &value, size_t &begin, size_t &end) +{ + i = skip_ws(s, i); + begin = i; + while (i < s.size() && s[i] != ',' && s[i] != '}') { + i++; + } + end = i; + while (end > begin && is_ws(s[end - 1])) { + end--; + } + value.assign(s, begin, end - begin); + return !value.empty(); +} + +static bool expect_comma(const string &s, size_t &i) +{ + i = skip_ws(s, i); + if (i >= s.size() || s[i] != ',') { + return false; + } + i++; + return true; +} + +static int b64_value(char c) +{ + if (c >= 'A' && c <= 'Z') { + return c - 'A'; + } + if (c >= 'a' && c <= 'z') { + return c - 'a' + 26; + } + if (c >= '0' && c <= '9') { + return c - '0' + 52; + } + if (c == '+') { + return 62; + } + if (c == '/') { + return 63; + } + return -1; +} + +static bool decode_base64(const string &in, vector &out) +{ + out.clear(); + int val = 0; + int valb = -8; + bool saw_pad = false; + for (char c : in) { + if (c == '=') { + saw_pad = true; + continue; + } + if (is_ws(c)) { + continue; + } + if (saw_pad) { + return false; + } + int d = b64_value(c); + if (d < 0) { + return false; + } + val = (val << 6) + d; + valb += 6; + if (valb >= 0) { + out.push_back(static_cast((val >> valb) & 0xff)); + valb -= 8; + } + } + return true; +} + +static string encode_base64(const unsigned char *data, size_t n) +{ + static const char tbl[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + string out; + out.reserve(((n + 2) / 3) * 4); + size_t i = 0; + while (i + 3 <= n) { + unsigned int v = (static_cast(data[i]) << 16) + | (static_cast(data[i + 1]) << 8) + | static_cast(data[i + 2]); + out.push_back(tbl[(v >> 18) & 63]); + out.push_back(tbl[(v >> 12) & 63]); + out.push_back(tbl[(v >> 6) & 63]); + out.push_back(tbl[v & 63]); + i += 3; + } + if (i < n) { + unsigned int v = static_cast(data[i]) << 16; + if (i + 1 < n) { + v |= static_cast(data[i + 1]) << 8; + } + out.push_back(tbl[(v >> 18) & 63]); + out.push_back(tbl[(v >> 12) & 63]); + if (i + 1 < n) { + out.push_back(tbl[(v >> 6) & 63]); + } else { + out.push_back('='); + } + out.push_back('='); + } + return out; +} + +static bool is_hex(char c) +{ + return (c >= '0' && c <= '9') + || (c >= 'a' && c <= 'f') + || (c >= 'A' && c <= 'F'); +} + +static bool is_uuid(const string &value) +{ + static const int groups[] = {8, 4, 4, 4, 12}; + size_t i = 0; + for (int g = 0; g < 5; g++) { + if (g > 0) { + if (i >= value.size() || value[i] != '-') { + return false; + } + i++; + } + for (int n = 0; n < groups[g]; n++) { + if (i >= value.size() || !is_hex(value[i])) { + return false; + } + i++; + } + } + return i == value.size(); +} + +static bool is_version_b64(const string &value) +{ + if (value.size() != 28) { + return false; + } + vector decoded; + if (!decode_base64(value, decoded)) { + return false; + } + return decoded.size() == 20; +} + +static bool valid_version(VersionsKind kind, const string &value) +{ + return kind == VersionsKind::Uuid ? is_uuid(value) : is_version_b64(value); +} + +static void random_bytes(unsigned char *buf, size_t n) +{ + random_device rd; + mt19937_64 gen( + static_cast(rd()) + ^ static_cast( + chrono::high_resolution_clock::now().time_since_epoch().count()) + ); + uniform_int_distribution dist(0, 255); + for (size_t i = 0; i < n; i++) { + buf[i] = static_cast(dist(gen)); + } +} + +static string generate_uuid() +{ + unsigned char buf[16]; + random_bytes(buf, 16); + static const char hex[] = "0123456789abcdef"; + string out; + out.resize(36); + size_t pos = 0; + for (size_t i = 0; i < 16; i++) { + if (i == 4 || i == 6 || i == 8 || i == 10) { + out[pos++] = '-'; + } + out[pos++] = hex[buf[i] >> 4]; + out[pos++] = hex[buf[i] & 0x0f]; + } + return out; +} + +static string generate_b64() +{ + unsigned char buf[20]; + random_bytes(buf, 20); + return encode_base64(buf, 20); +} + +static bool parse_versions_list(const string &s, size_t begin, size_t end, VersionsDocument &doc) +{ + if (end <= begin + 1 || s[begin] != '{' || s[end - 1] != '}') { + return false; + } + + size_t i = begin + 1; + i = skip_ws(s, i); + if (i >= end || s[i] == '}') { + return false; + } + + string first; + size_t dummy_b = 0, dummy_e = 0; + if (!read_unquoted(s, i, first, dummy_b, dummy_e)) { + return false; + } + if (!expect_comma(s, i)) { + return false; + } + + i = skip_ws(s, i); + if (i >= end) { + return false; + } + + if (s[i] != '"') { + string count; + if (!read_unquoted(s, i, count, dummy_b, dummy_e)) { + return false; + } + if (!expect_comma(s, i)) { + return false; + } + doc.kind = VersionsKind::Uuid; + } else { + doc.kind = VersionsKind::Base64; + } + + while (true) { + i = skip_ws(s, i); + if (i >= end) { + return false; + } + if (s[i] == '}') { + break; + } + + VersionEntry entry; + if (!read_quoted(s, i, entry.name)) { + return false; + } + if (!expect_comma(s, i)) { + return false; + } + if (!read_unquoted(s, i, entry.version, entry.version_begin, entry.version_end)) { + return false; + } + doc.entries.push_back(entry); + + i = skip_ws(s, i); + if (i < end && s[i] == ',') { + i++; + continue; + } + if (i < end && s[i] == '}') { + break; + } + return false; + } + + return !doc.entries.empty(); +} + +static int load_document(const string &filename, VersionsDocument &doc) +{ + boost::filesystem::ifstream in(filename, ios_base::in | ios_base::binary); + if (!in) { + cerr << "VersionsFile. `" << filename << "`. Input file not found!" << endl; + return V8UNPACK_SOURCE_DOES_NOT_EXIST; + } + + in.seekg(0, ios_base::end); + auto size = in.tellg(); + if (size < 0) { + cerr << "VersionsFile. `" << filename << "` is not a versions file!" << endl; + return V8UNPACK_NOT_VERSIONS_FILE; + } + in.seekg(0, ios_base::beg); + doc.data.resize(static_cast(size)); + if (size > 0) { + in.read(&doc.data[0], size); + if (!in) { + cerr << "VersionsFile. `" << filename << "` is not a versions file!" << endl; + return V8UNPACK_NOT_VERSIONS_FILE; + } + } + + size_t i = skip_bom(doc.data); + vector> lists; + while (true) { + i = skip_ws(doc.data, i); + if (i >= doc.data.size()) { + break; + } + if (doc.data[i] == ',') { + i++; + continue; + } + if (doc.data[i] != '{') { + cerr << "VersionsFile. `" << filename << "` is not a versions file!" << endl; + return V8UNPACK_NOT_VERSIONS_FILE; + } + size_t close = 0; + if (!match_brace(doc.data, i, close)) { + cerr << "VersionsFile. `" << filename << "` is not a versions file!" << endl; + return V8UNPACK_NOT_VERSIONS_FILE; + } + lists.push_back(make_pair(i, close)); + i = close; + } + + size_t list_begin = 0; + size_t list_end = 0; + if (lists.size() == 1) { + list_begin = lists[0].first; + list_end = lists[0].second; + } else if (lists.size() == 3) { + list_begin = lists[2].first; + list_end = lists[2].second; + } else { + cerr << "VersionsFile. `" << filename << "` is not a versions file!" << endl; + return V8UNPACK_NOT_VERSIONS_FILE; + } + + if (!parse_versions_list(doc.data, list_begin, list_end, doc)) { + cerr << "VersionsFile. `" << filename << "` is not a versions file!" << endl; + return V8UNPACK_NOT_VERSIONS_FILE; + } + + if (lists.size() == 3) { + doc.kind = VersionsKind::Base64; + } + + return V8UNPACK_OK; +} + +static int find_entry(const VersionsDocument &doc, const string &block_name) +{ + for (size_t i = 0; i < doc.entries.size(); i++) { + if (doc.entries[i].name == block_name) { + return static_cast(i); + } + } + return -1; +} + +static int save_document(const string &filename, const VersionsDocument &doc) +{ + boost::filesystem::ofstream out(filename, ios_base::out | ios_base::binary | ios_base::trunc); + if (!out) { + cerr << "VersionsFile. `" << filename << "`. Cannot write file!" << endl; + return V8UNPACK_ERROR_CREATING_OUTPUT_FILE; + } + if (!doc.data.empty()) { + out.write(doc.data.data(), static_cast(doc.data.size())); + } + if (!out) { + cerr << "VersionsFile. `" << filename << "`. Cannot write file!" << endl; + return V8UNPACK_ERROR_CREATING_OUTPUT_FILE; + } + return V8UNPACK_OK; +} + +static void apply_version(VersionsDocument &doc, int index, const string &version) +{ + auto &entry = doc.entries[static_cast(index)]; + doc.data.replace(entry.version_begin, entry.version_end - entry.version_begin, version); + entry.version = version; + entry.version_end = entry.version_begin + version.size(); +} + +struct PendingVersion { + int index; + string version; +}; + +static void apply_versions(VersionsDocument &doc, vector &pending) +{ + sort(pending.begin(), pending.end(), + [&doc](const PendingVersion &a, const PendingVersion &b) { + return doc.entries[static_cast(a.index)].version_begin + > doc.entries[static_cast(b.index)].version_begin; + }); + for (const auto &item : pending) { + apply_version(doc, item.index, item.version); + } +} + +int VersionsShow(const string &filename) +{ + VersionsDocument doc; + int ret = load_document(filename, doc); + if (ret != V8UNPACK_OK) { + return ret; + } + + size_t width = 0; + for (const auto &entry : doc.entries) { + if (entry.name.size() > width) { + width = entry.name.size(); + } + } + + for (const auto &entry : doc.entries) { + cout << entry.name; + size_t pad = width > entry.name.size() ? width - entry.name.size() + 2 : 2; + cout << string(pad, ' ') << entry.version << '\n'; + } + cout << flush; + return V8UNPACK_OK; +} + +int VersionsGet(const string &filename, const string &block_name) +{ + VersionsDocument doc; + int ret = load_document(filename, doc); + if (ret != V8UNPACK_OK) { + return ret; + } + + int index = find_entry(doc, block_name); + if (index < 0) { + cerr << "VersionsFile. `" << block_name << "` not found!" << endl; + return V8UNPACK_ELEM_NOT_FOUND; + } + + cout << doc.entries[static_cast(index)].version << endl; + return V8UNPACK_OK; +} + +int VersionsSet(const string &filename, const vector> &items) +{ + if (items.empty()) { + return V8UNPACK_SHOW_USAGE; + } + + VersionsDocument doc; + int ret = load_document(filename, doc); + if (ret != V8UNPACK_OK) { + return ret; + } + + vector pending; + pending.reserve(items.size()); + for (const auto &item : items) { + if (!valid_version(doc.kind, item.second)) { + cerr << "VersionsFile. Invalid version!" << endl; + return V8UNPACK_INVALID_VERSION; + } + + int index = find_entry(doc, item.first); + if (index < 0) { + cerr << "VersionsFile. `" << item.first << "` not found!" << endl; + return V8UNPACK_ELEM_NOT_FOUND; + } + + pending.push_back({index, item.second}); + } + + apply_versions(doc, pending); + return save_document(filename, doc); +} + +int VersionsSet(const string &filename, const string &block_name, const string &version) +{ + return VersionsSet(filename, {make_pair(block_name, version)}); +} + +int VersionsUpdate(const string &filename, const vector &names) +{ + if (names.empty()) { + return V8UNPACK_SHOW_USAGE; + } + + VersionsDocument doc; + int ret = load_document(filename, doc); + if (ret != V8UNPACK_OK) { + return ret; + } + + vector pending; + pending.reserve(names.size()); + for (const auto &name : names) { + int index = find_entry(doc, name); + if (index < 0) { + cerr << "VersionsFile. `" << name << "` not found!" << endl; + return V8UNPACK_ELEM_NOT_FOUND; + } + + string version = doc.kind == VersionsKind::Uuid ? generate_uuid() : generate_b64(); + pending.push_back({index, version}); + } + + auto printed = pending; + apply_versions(doc, pending); + ret = save_document(filename, doc); + if (ret != V8UNPACK_OK) { + return ret; + } + + for (const auto &item : printed) { + if (names.size() == 1) { + cout << item.version << '\n'; + } else { + const auto &name = doc.entries[static_cast(item.index)].name; + cout << name << " " << item.version << '\n'; + } + } + cout << flush; + return V8UNPACK_OK; +} + +int VersionsUpdate(const string &filename, const string &block_name) +{ + return VersionsUpdate(filename, vector{block_name}); +} + +} diff --git a/src/VersionsTable.h b/src/VersionsTable.h new file mode 100644 index 0000000..b24381d --- /dev/null +++ b/src/VersionsTable.h @@ -0,0 +1,26 @@ +/*---------------------------------------------------------- +This Source Code Form is subject to the terms of the +Mozilla Public License, v.2.0. If a copy of the MPL +was not distributed with this file, You can obtain one +at http://mozilla.org/MPL/2.0/. +----------------------------------------------------------*/ + +#ifndef V8UNPACK_VERSIONSTABLE_H +#define V8UNPACK_VERSIONSTABLE_H + +#include +#include +#include + +namespace v8unpack { + +int VersionsShow(const std::string &filename); +int VersionsGet(const std::string &filename, const std::string &block_name); +int VersionsSet(const std::string &filename, const std::string &block_name, const std::string &version); +int VersionsSet(const std::string &filename, const std::vector> &items); +int VersionsUpdate(const std::string &filename, const std::string &block_name); +int VersionsUpdate(const std::string &filename, const std::vector &names); + +} + +#endif //V8UNPACK_VERSIONSTABLE_H diff --git a/src/main.cpp b/src/main.cpp index 721b9b3..cfc4974 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,10 +18,12 @@ at http://mozilla.org/MPL/2.0/. // #include "V8File.h" +#include "VersionsTable.h" #include "version.h" #include #include #include +#include #include using namespace std; @@ -42,6 +44,7 @@ int usage(vector &argv) cout << endl; cout << "V8UNPACK" << endl; cout << " -U[NPACK] in_filename.cf out_dirname [block_name]" << endl; + cout << " -U[NPACK] in_filename.cf - block_name" << endl; cout << " -U[NPACK] -L[IST] listfile" << endl; cout << " -PA[CK] in_dirname out_filename.cf" << endl; cout << " -PA[CK] -L[IST] listfile" << endl; @@ -51,11 +54,23 @@ int usage(vector &argv) cout << " -D[EFLATE] -L[IST] listfile" << endl; cout << " -P[ARSE] in_filename out_dirname [block_name1 block_name2 ...]" << endl; cout << " -P[ARSE] -L[IST] listfile" << endl; + cout << " -DEL[ETE] in_filename [block_mask1 block_mask2 ...]" << endl; + cout << " -DEL[ETE] -L[IST] listfile" << endl; + cout << " -ADD [-PACK|-BUILD [-NOPACK]] [-N[AME] name] source|- out_filename" << endl; + cout << " -ADD [-PACK|-BUILD [-NOPACK]] -LISTFILES|-LF listfile out_filename" << endl; + cout << " -PUT [-PACK|-BUILD [-NOPACK]] [-N[AME] name] source|- out_filename" << endl; + cout << " -PUT [-PACK|-BUILD [-NOPACK]] -LISTFILES|-LF listfile out_filename" << endl; cout << " -B[UILD] [-N[OPACK]] in_dirname out_filename" << endl; cout << " -B[UILD] [-N[OPACK]] -L[IST] listfile" << endl; cout << " -L[IST] listfile" << endl; cout << " -LISTFILES|-LF in_filename" << endl; + cout << " -VERSIONSFILE|-VF -SHOW in_filename" << endl; + cout << " -VERSIONSFILE|-VF -GET in_filename block_name" << endl; + cout << " -VERSIONSFILE|-VF -SET in_filename block_name version" << endl; + cout << " -VERSIONSFILE|-VF -SET -LIST|-LF listfile in_filename" << endl; + cout << " -VERSIONSFILE|-VF -UPDATE in_filename block_name" << endl; + cout << " -VERSIONSFILE|-VF -UPDATE -LIST|-LF listfile in_filename" << endl; cout << " -E[XAMPLE]" << endl; cout << " -BAT" << endl; @@ -84,6 +99,9 @@ int deflate(vector &argv) int unpack(vector &argv) { + if (argv[1] == "-" && argv[2].empty()) { + return V8UNPACK_SHOW_USAGE; + } int ret = UnpackToFolder(argv[0], argv[1], argv[2], true); return ret; } @@ -117,6 +135,295 @@ int list_files(vector &argv) return ret; } +int delete_blocks(vector &argv) +{ + if (argv.empty() || argv[0].empty()) { + return V8UNPACK_SHOW_USAGE; + } + + vector masks; + for (size_t i = 1; i < argv.size(); i++) { + if (!argv[i].empty()) { + masks.push_back(argv[i]); + } + } + + return DeleteBlocks(argv[0], masks); +} + +static string to_lower_copy(string value) +{ + transform(value.begin(), value.end(), value.begin(), ::tolower); + return value; +} + +int add_or_put(vector &argv, bool replace) +{ + AddMode mode = AddMode::Pack; + string elem_name; + string listfile; + vector positional; + + for (size_t i = 0; i < argv.size(); i++) { + if (argv[i].empty()) { + continue; + } + + string al = to_lower_copy(argv[i]); + if (al == "-") { + positional.push_back(argv[i]); + continue; + } + if (al[0] != '-') { + positional.push_back(argv[i]); + continue; + } + if (al == "-pack" || al == "-pa") { + mode = AddMode::Pack; + continue; + } + if (al == "-build" || al == "-b") { + mode = AddMode::Build; + continue; + } + if (al == "-nopack") { + mode = AddMode::BuildNopack; + continue; + } + if (al == "-name" || al == "-n") { + if (i + 1 >= argv.size() || argv[i + 1].empty()) { + return V8UNPACK_SHOW_USAGE; + } + elem_name = argv[++i]; + continue; + } + if (al == "-listfiles" || al == "-lf") { + if (i + 1 >= argv.size() || argv[i + 1].empty()) { + return V8UNPACK_SHOW_USAGE; + } + listfile = argv[++i]; + continue; + } + return V8UNPACK_SHOW_USAGE; + } + + if (!listfile.empty()) { + if (positional.empty() || positional[0].empty()) { + return V8UNPACK_SHOW_USAGE; + } + + boost::filesystem::ifstream in(listfile); + if (!in) { + cerr << "Add. List file not found: " << listfile << endl; + return V8UNPACK_SOURCE_DOES_NOT_EXIST; + } + + vector items; + string line; + while (getline(in, line)) { + if (!line.empty() && line.back() == '\r') { + line.pop_back(); + } + if (line.empty()) { + continue; + } + AddItem item; + item.source = line; + items.push_back(item); + } + + if (items.empty()) { + return V8UNPACK_SHOW_USAGE; + } + + return AddToContainer(positional[0], items, mode, replace); + } + + if (positional.size() < 2 || positional[0].empty() || positional[1].empty()) { + return V8UNPACK_SHOW_USAGE; + } + + if (positional[0] == "-" && elem_name.empty()) { + return V8UNPACK_SHOW_USAGE; + } + + AddItem item; + item.source = positional[0]; + item.name = elem_name; + return AddToContainer(positional[1], {item}, mode, replace); +} + +int add(vector &argv) +{ + return add_or_put(argv, false); +} + +int put(vector &argv) +{ + return add_or_put(argv, true); +} + +static bool is_vf_list_flag(const string &value) +{ + return value == "-lf" || value == "-listfiles" || value == "-list" || value == "-l"; +} + +static string trim_copy(string value) +{ + size_t begin = 0; + while (begin < value.size() && (value[begin] == ' ' || value[begin] == '\t')) { + begin++; + } + size_t end = value.size(); + while (end > begin && (value[end - 1] == ' ' || value[end - 1] == '\t' || value[end - 1] == '\r')) { + end--; + } + return value.substr(begin, end - begin); +} + +static bool split_name_version(const string &line, string &name, string &version) +{ + size_t i = 0; + while (i < line.size() && line[i] != ' ' && line[i] != '\t') { + i++; + } + if (i == 0 || i >= line.size()) { + return false; + } + name = line.substr(0, i); + version = trim_copy(line.substr(i)); + return !version.empty(); +} + +static int read_set_listfile(const string &listfile, vector> &items) +{ + boost::filesystem::ifstream in(listfile); + if (!in) { + cerr << "VersionsFile. List file not found: " << listfile << endl; + return V8UNPACK_SOURCE_DOES_NOT_EXIST; + } + + string line; + while (getline(in, line)) { + line = trim_copy(line); + if (line.empty()) { + continue; + } + string name; + string version; + if (!split_name_version(line, name, version)) { + cerr << "VersionsFile. Invalid list line: " << line << endl; + return V8UNPACK_ERROR; + } + items.push_back(make_pair(name, version)); + } + + if (items.empty()) { + return V8UNPACK_SHOW_USAGE; + } + return V8UNPACK_OK; +} + +static int read_update_listfile(const string &listfile, vector &names) +{ + boost::filesystem::ifstream in(listfile); + if (!in) { + cerr << "VersionsFile. List file not found: " << listfile << endl; + return V8UNPACK_SOURCE_DOES_NOT_EXIST; + } + + string line; + while (getline(in, line)) { + line = trim_copy(line); + if (line.empty()) { + continue; + } + names.push_back(line); + } + + if (names.empty()) { + return V8UNPACK_SHOW_USAGE; + } + return V8UNPACK_OK; +} + +int versionsfile(vector &argv) +{ + if (argv.empty() || argv[0].empty()) { + return V8UNPACK_SHOW_USAGE; + } + + string cmd = to_lower_copy(argv[0]); + if (cmd == "-show") { + if (argv.size() < 2 || argv[1].empty()) { + return V8UNPACK_SHOW_USAGE; + } + return VersionsShow(argv[1]); + } + if (cmd == "-get") { + if (argv.size() < 3 || argv[1].empty()) { + return V8UNPACK_SHOW_USAGE; + } + return VersionsGet(argv[1], argv[2]); + } + + if (cmd == "-set" || cmd == "-update") { + string listfile; + vector positional; + + for (size_t i = 1; i < argv.size(); i++) { + if (argv[i].empty()) { + continue; + } + string al = to_lower_copy(argv[i]); + if (is_vf_list_flag(al)) { + if (i + 1 >= argv.size() || argv[i + 1].empty()) { + return V8UNPACK_SHOW_USAGE; + } + listfile = argv[++i]; + continue; + } + if (al[0] == '-') { + return V8UNPACK_SHOW_USAGE; + } + positional.push_back(argv[i]); + } + + if (!listfile.empty()) { + if (positional.size() != 1 || positional[0].empty()) { + return V8UNPACK_SHOW_USAGE; + } + if (cmd == "-set") { + vector> items; + int ret = read_set_listfile(listfile, items); + if (ret != V8UNPACK_OK) { + return ret; + } + return VersionsSet(positional[0], items); + } + vector names; + int ret = read_update_listfile(listfile, names); + if (ret != V8UNPACK_OK) { + return ret; + } + return VersionsUpdate(positional[0], names); + } + + if (cmd == "-set") { + if (positional.size() < 3 || positional[0].empty() || positional[2].empty()) { + return V8UNPACK_SHOW_USAGE; + } + return VersionsSet(positional[0], positional[1], positional[2]); + } + if (positional.size() < 2 || positional[0].empty()) { + return V8UNPACK_SHOW_USAGE; + } + return VersionsUpdate(positional[0], positional[1]); + } + + return V8UNPACK_SHOW_USAGE; +} + int process_list(vector &argv) { if (argv.empty()) { @@ -237,6 +544,25 @@ handler_t get_run_mode(const vector &args, int &arg_base, bool &allow_li return parse; } + if (cur_mode == "-delete" || cur_mode == "-del") { + return delete_blocks; + } + + if (cur_mode == "-add") { + allow_listfile = false; + return add; + } + + if (cur_mode == "-put") { + allow_listfile = false; + return put; + } + + if (cur_mode == "-versionsfile" || cur_mode == "-vf") { + allow_listfile = false; + return versionsfile; + } + if (cur_mode == "-build" || cur_mode == "-b") { bool dont_pack = false; @@ -316,7 +642,7 @@ int main(int argc, char* argv[]) return 1; } - if (allow_listfile && arg_base <= argc) { + if (allow_listfile && arg_base < argc) { string a_list(argv[arg_base]); transform(a_list.begin(), a_list.end(), a_list.begin(), ::tolower); if (a_list == "-list" || a_list == "-l") { diff --git a/test/run.sh b/test/run.sh index 3720a97..7230423 100755 --- a/test/run.sh +++ b/test/run.sh @@ -112,7 +112,641 @@ rm -rf $OUTDIRNAME2 rm $TMPFILE2 rm $LISTFILE +echo 'delete block tests...' + +DEL_SRC='del-src' +DEL_CF='del-test.cf' +DEL_OUT='del-out' +FIX_FLAT="$SCRIPT_DIR/fixtures/f15-flat" +FIX_F16Z="$SCRIPT_DIR/fixtures/f16z-inner" +FIX_F16="$SCRIPT_DIR/fixtures/f16-zeropad-hex" + +rm -rf "$DEL_SRC" "$DEL_OUT" "$DEL_CF" del-test2.cf del-list.txt + +mkdir "$DEL_SRC" +printf 'one' > "$DEL_SRC/alpha" +printf 'two-two' > "$DEL_SRC/beta" +printf 'three-three-three' > "$DEL_SRC/gamma" +printf 't1' > "$DEL_SRC/test1" +printf 't2' > "$DEL_SRC/test2" +printf 'dot' > "$DEL_SRC/file.name" + +$UNPACK -build -nopack "$DEL_SRC" "$DEL_CF" >/dev/null +if [ $? -ne 0 ]; then + echo Failed + exit 1 +fi + +# точное имя, правка на месте (размер файла не меняется) +cp "$DEL_CF" del-exact.cf +before=$(wc -c < del-exact.cf | tr -d ' ') +$UNPACK -delete del-exact.cf beta >/dev/null +after=$(wc -c < del-exact.cf | tr -d ' ') +if [ "$before" != "$after" ]; then + echo Failed + exit 1 +fi +$UNPACK -listfiles del-exact.cf > del-names.txt +if grep -qx beta del-names.txt || ! grep -qx alpha del-names.txt; then + echo Failed + exit 1 +fi +$UNPACK -parse del-exact.cf "$DEL_OUT" >/dev/null +if [ ! -f "$DEL_OUT/alpha" ] || [ -e "$DEL_OUT/beta" ] || [ ! -f "$DEL_OUT/gamma" ]; then + echo Failed + exit 1 +fi +diff "$DEL_SRC/alpha" "$DEL_OUT/alpha" >$DIFFLOG 2>&1 +if [ $? -ne 0 ]; then + echo Failed + exit 1 +fi +rm -rf "$DEL_OUT" del-exact.cf del-names.txt + +# маска * +cp "$DEL_CF" del-star.cf +$UNPACK -del del-star.cf 'test*' >/dev/null +$UNPACK -parse del-star.cf "$DEL_OUT" >/dev/null +if [ -e "$DEL_OUT/test1" ] || [ -e "$DEL_OUT/test2" ] || [ ! -f "$DEL_OUT/alpha" ] || [ ! -f "$DEL_OUT/file.name" ]; then + echo Failed + exit 1 +fi +rm -rf "$DEL_OUT" del-star.cf + +# маска ? +cp "$DEL_CF" del-q.cf +$UNPACK -delete del-q.cf '????' >/dev/null +$UNPACK -parse del-q.cf "$DEL_OUT" >/dev/null +if [ -e "$DEL_OUT/beta" ] || [ ! -f "$DEL_OUT/alpha" ] || [ ! -f "$DEL_OUT/gamma" ]; then + echo Failed + exit 1 +fi +rm -rf "$DEL_OUT" del-q.cf + +# маска в середине и несколько масок +cp "$DEL_CF" del-multi.cf +$UNPACK -delete del-multi.cf 'a*a' 'file.*' >/dev/null +$UNPACK -parse del-multi.cf "$DEL_OUT" >/dev/null +if [ -e "$DEL_OUT/alpha" ] || [ -e "$DEL_OUT/file.name" ] || [ ! -f "$DEL_OUT/beta" ] || [ ! -f "$DEL_OUT/test1" ]; then + echo Failed + exit 1 +fi +rm -rf "$DEL_OUT" del-multi.cf + +# нет совпадений — все блоки на месте +cp "$DEL_CF" del-nomatch.cf +$UNPACK -delete del-nomatch.cf 'zzz*' >/dev/null +$UNPACK -parse del-nomatch.cf "$DEL_OUT" >/dev/null +diff -r "$DEL_SRC" "$DEL_OUT" >$DIFFLOG 2>&1 +if [ $? -ne 0 ]; then + echo Failed + exit 1 +fi +rm -rf "$DEL_OUT" del-nomatch.cf + +# удаление всех блоков +cp "$DEL_CF" del-all.cf +$UNPACK -delete del-all.cf '*' >/dev/null +$UNPACK -parse del-all.cf "$DEL_OUT" >/dev/null +if [ -e "$DEL_OUT/alpha" ] || [ -e "$DEL_OUT/beta" ] || [ -e "$DEL_OUT/gamma" ]; then + echo Failed + exit 1 +fi +rm -rf "$DEL_OUT" del-all.cf + +# list-файл +cp "$DEL_CF" del-list1.cf +cp "$DEL_CF" del-list2.cf +printf '%b\n%b' "del-list1.cf;alpha" "del-list2.cf;test?;file.*" > del-list.txt +$UNPACK -delete -list del-list.txt >/dev/null +$UNPACK -parse del-list1.cf del-list1-out >/dev/null +$UNPACK -parse del-list2.cf del-list2-out >/dev/null +if [ -e del-list1-out/alpha ] || [ ! -f del-list1-out/beta ]; then + echo Failed + exit 1 +fi +if [ -e del-list2-out/test1 ] || [ -e del-list2-out/test2 ] || [ -e del-list2-out/file.name ] || [ ! -f del-list2-out/alpha ]; then + echo Failed + exit 1 +fi +rm -rf del-list1-out del-list2-out del-list1.cf del-list2.cf del-list.txt + +# фикстура f15-paged-toc (запись TOC на границе страниц) +FIX_PAGED="$SCRIPT_DIR/fixtures/f15-paged-toc" +cp "$FIX_PAGED/in.cf" del-paged.cf +$UNPACK -delete del-paged.cf e2 >/dev/null +$UNPACK -parse del-paged.cf "$DEL_OUT" >/dev/null +if [ -e "$DEL_OUT/e2" ] || [ ! -f "$DEL_OUT/e0" ] || [ ! -f "$DEL_OUT/e1" ] || [ ! -f "$DEL_OUT/e3" ] || [ ! -f "$DEL_OUT/e4" ]; then + echo Failed + exit 1 +fi +diff "$FIX_PAGED/expected/e1" "$DEL_OUT/e1" >$DIFFLOG 2>&1 +if [ $? -ne 0 ]; then + echo Failed + exit 1 +fi +rm -rf "$DEL_OUT" del-paged.cf + +# фикстура f15 +cp "$FIX_FLAT/in.cf" del-fix15.cf +$UNPACK -delete del-fix15.cf 'a*' '?amma' >/dev/null +$UNPACK -parse del-fix15.cf "$DEL_OUT" >/dev/null +if [ -e "$DEL_OUT/alpha" ] || [ -e "$DEL_OUT/gamma" ] || [ ! -f "$DEL_OUT/beta" ]; then + echo Failed + exit 1 +fi +diff "$FIX_FLAT/expected/beta" "$DEL_OUT/beta" >$DIFFLOG 2>&1 +if [ $? -ne 0 ]; then + echo Failed + exit 1 +fi +rm -rf "$DEL_OUT" del-fix15.cf + +# фикстура f16z +cp "$FIX_F16Z/in.cf" del-fix16z.cf +$UNPACK -delete del-fix16z.cf info >/dev/null +$UNPACK -parse del-fix16z.cf "$DEL_OUT" >/dev/null +if [ -e "$DEL_OUT/info" ] || [ ! -f "$DEL_OUT/form" ]; then + echo Failed + exit 1 +fi +diff "$FIX_F16Z/expected/form" "$DEL_OUT/form" >$DIFFLOG 2>&1 +if [ $? -ne 0 ]; then + echo Failed + exit 1 +fi +rm -rf "$DEL_OUT" del-fix16z.cf + +# фикстура f16 (с placeholder) +cp "$FIX_F16/in.cf" del-fix16.cf +$UNPACK -delete del-fix16.cf 'z*' >/dev/null +$UNPACK -parse del-fix16.cf "$DEL_OUT" >/dev/null +if [ -e "$DEL_OUT/zeroed" ]; then + echo Failed + exit 1 +fi +rm -rf "$DEL_OUT" del-fix16.cf + +# ошибка: нет файла +$UNPACK -delete missing-file.cf alpha >/dev/null 2>&1 +if [ $? -eq 0 ]; then + echo Failed + exit 1 +fi + +echo Passed + +echo 'add block tests...' + +ADD_CF='add-test.cf' +ADD_OUT='add-out' +ADD_SRC='add-src' +rm -rf "$ADD_OUT" "$ADD_SRC" "$ADD_CF" add-extra.txt add-plain.txt add-list.txt add-dir add-stdin.cf + +cp "$DEL_CF" "$ADD_CF" + +# PACK по умолчанию, имя из файла +printf 'added-raw' > add-extra.txt +$UNPACK -add add-extra.txt "$ADD_CF" >/dev/null +$UNPACK -parse "$ADD_CF" "$ADD_OUT" >/dev/null +if [ ! -f "$ADD_OUT/add-extra.txt" ]; then + echo Failed + exit 1 +fi +diff add-extra.txt "$ADD_OUT/add-extra.txt" >$DIFFLOG 2>&1 +if [ $? -ne 0 ]; then + echo Failed + exit 1 +fi +rm -rf "$ADD_OUT" + +# повторное ADD с тем же именем — ошибка +$UNPACK -add add-extra.txt "$ADD_CF" >/dev/null 2>&1 +if [ $? -eq 0 ]; then + echo Failed + exit 1 +fi +$UNPACK -add -n alpha add-extra.txt "$ADD_CF" >/dev/null 2>&1 +if [ $? -eq 0 ]; then + echo Failed + exit 1 +fi + +# -NAME +printf 'named-body' > add-plain.txt +$UNPACK -add -n custom.name add-plain.txt "$ADD_CF" >/dev/null +$UNPACK -parse "$ADD_CF" "$ADD_OUT" >/dev/null +if [ ! -f "$ADD_OUT/custom.name" ]; then + echo Failed + exit 1 +fi +diff add-plain.txt "$ADD_OUT/custom.name" >$DIFFLOG 2>&1 +if [ $? -ne 0 ]; then + echo Failed + exit 1 +fi +rm -rf "$ADD_OUT" + +# -BUILD (deflate) +printf 'build-payload' > add-build.txt +$UNPACK -add -build add-build.txt "$ADD_CF" >/dev/null +$UNPACK -parse "$ADD_CF" "$ADD_OUT" >/dev/null +if [ ! -f "$ADD_OUT/add-build.txt" ]; then + echo Failed + exit 1 +fi +diff add-build.txt "$ADD_OUT/add-build.txt" >$DIFFLOG 2>&1 +if [ $? -ne 0 ]; then + echo Failed + exit 1 +fi +rm -rf "$ADD_OUT" + +# -BUILD -NOPACK для каталога +mkdir add-dir +printf 'child-a' > add-dir/child +$UNPACK -add -build -nopack add-dir "$ADD_CF" >/dev/null +$UNPACK -parse "$ADD_CF" "$ADD_OUT" >/dev/null +if [ ! -f "$ADD_OUT/add-dir/child" ]; then + echo Failed + exit 1 +fi +diff add-dir/child "$ADD_OUT/add-dir/child" >$DIFFLOG 2>&1 +if [ $? -ne 0 ]; then + echo Failed + exit 1 +fi +rm -rf "$ADD_OUT" + +# stdin + -NAME +cp "$DEL_CF" add-stdin.cf +printf 'from-stdin' | $UNPACK -add -name stdinfile - add-stdin.cf >/dev/null +$UNPACK -parse add-stdin.cf "$ADD_OUT" >/dev/null +if [ ! -f "$ADD_OUT/stdinfile" ]; then + echo Failed + exit 1 +fi +printf 'from-stdin' > add-stdin-expected.txt +diff add-stdin-expected.txt "$ADD_OUT/stdinfile" >$DIFFLOG 2>&1 +if [ $? -ne 0 ]; then + echo Failed + exit 1 +fi +rm -rf "$ADD_OUT" add-stdin.cf add-stdin-expected.txt + +# -LISTFILES +cp "$DEL_CF" add-lf.cf +printf 'one-list' > add-l1.txt +printf 'two-list' > add-l2.txt +printf '%s\n%s\n' add-l1.txt add-l2.txt > add-list.txt +$UNPACK -add -lf add-list.txt add-lf.cf >/dev/null +$UNPACK -parse add-lf.cf "$ADD_OUT" >/dev/null +if [ ! -f "$ADD_OUT/add-l1.txt" ] || [ ! -f "$ADD_OUT/add-l2.txt" ]; then + echo Failed + exit 1 +fi +diff add-l1.txt "$ADD_OUT/add-l1.txt" >$DIFFLOG 2>&1 +if [ $? -ne 0 ]; then + echo Failed + exit 1 +fi +rm -rf "$ADD_OUT" add-lf.cf add-l1.txt add-l2.txt add-list.txt + +# удаление + добавление в свободный слот, размер не растёт +cp "$DEL_CF" add-reuse.cf +printf 'zz' > add-reuse.txt +before=$(wc -c < add-reuse.cf | tr -d ' ') +$UNPACK -delete add-reuse.cf beta >/dev/null +$UNPACK -add -name zzzz add-reuse.txt add-reuse.cf >/dev/null +after=$(wc -c < add-reuse.cf | tr -d ' ') +if [ "$before" != "$after" ]; then + echo Failed + exit 1 +fi +$UNPACK -parse add-reuse.cf "$ADD_OUT" >/dev/null +if [ -e "$ADD_OUT/beta" ] || [ ! -f "$ADD_OUT/zzzz" ]; then + echo Failed + exit 1 +fi +diff add-reuse.txt "$ADD_OUT/zzzz" >$DIFFLOG 2>&1 +if [ $? -ne 0 ]; then + echo Failed + exit 1 +fi +rm -rf "$ADD_OUT" add-reuse.cf add-reuse.txt + +# фикстура f16z +cp "$FIX_F16Z/in.cf" add-fix16z.cf +printf 'new-form' > add-f16z.txt +$UNPACK -add -name extra add-f16z.txt add-fix16z.cf >/dev/null +$UNPACK -parse add-fix16z.cf "$ADD_OUT" >/dev/null +if [ ! -f "$ADD_OUT/extra" ] || [ ! -f "$ADD_OUT/info" ]; then + echo Failed + exit 1 +fi +diff add-f16z.txt "$ADD_OUT/extra" >$DIFFLOG 2>&1 +if [ $? -ne 0 ]; then + echo Failed + exit 1 +fi +rm -rf "$ADD_OUT" add-fix16z.cf add-f16z.txt + +# stdin без -NAME +printf 'x' | $UNPACK -add - "$ADD_CF" >/dev/null 2>&1 +if [ $? -eq 0 ]; then + echo Failed + exit 1 +fi + +echo Passed + +echo 'put block tests...' + +# PUT нового имени — как ADD +printf 'put-new' > put-new.txt +$UNPACK -put -n put-new put-new.txt "$ADD_CF" >/dev/null +$UNPACK -parse "$ADD_CF" "$ADD_OUT" >/dev/null +if [ ! -f "$ADD_OUT/put-new" ]; then + echo Failed + exit 1 +fi +diff put-new.txt "$ADD_OUT/put-new" >$DIFFLOG 2>&1 +if [ $? -ne 0 ]; then + echo Failed + exit 1 +fi +rm -rf "$ADD_OUT" + +# PUT существующего — замена содержимого +printf 'alpha-replaced' > put-alpha.txt +$UNPACK -put -n alpha put-alpha.txt "$ADD_CF" >/dev/null +if [ $? -ne 0 ]; then + echo Failed + exit 1 +fi +$UNPACK -parse "$ADD_CF" "$ADD_OUT" >/dev/null +if [ ! -f "$ADD_OUT/alpha" ]; then + echo Failed + exit 1 +fi +diff put-alpha.txt "$ADD_OUT/alpha" >$DIFFLOG 2>&1 +if [ $? -ne 0 ]; then + echo Failed + exit 1 +fi +printf 'one' > put-alpha-old.txt +diff put-alpha-old.txt "$ADD_OUT/alpha" >$DIFFLOG 2>&1 +if [ $? -eq 0 ]; then + echo Failed + exit 1 +fi +rm -rf "$ADD_OUT" put-new.txt put-alpha.txt put-alpha-old.txt + +echo Passed + +echo 'unpack stdout / pipe add tests...' + +FIX_FLAT="$SCRIPT_DIR/fixtures/f15-flat" +cp "$FIX_FLAT/in.cf" unpack-src.cf +$UNPACK -unpack unpack-src.cf - alpha > unpack-stdout.bin +if [ $? -ne 0 ]; then + echo Failed + exit 1 +fi +diff "$FIX_FLAT/expected/alpha" unpack-stdout.bin >$DIFFLOG 2>&1 +if [ $? -ne 0 ]; then + echo Failed + exit 1 +fi + +cp "$FIX_FLAT/in.cf" pipe-dest.cf +$UNPACK -unpack unpack-src.cf - beta | $UNPACK -add -name beta-copy - pipe-dest.cf >/dev/null +if [ $? -ne 0 ]; then + echo Failed + exit 1 +fi +$UNPACK -parse pipe-dest.cf pipe-out >/dev/null +if [ ! -f pipe-out/beta-copy ]; then + echo Failed + exit 1 +fi +diff "$FIX_FLAT/expected/beta" pipe-out/beta-copy >$DIFFLOG 2>&1 +if [ $? -ne 0 ]; then + echo Failed + exit 1 +fi + +$UNPACK -unpack unpack-src.cf - >/dev/null 2>&1 +if [ $? -eq 0 ]; then + echo Failed + exit 1 +fi + +$UNPACK -unpack unpack-src.cf - missing-block >/dev/null 2>&1 +if [ $? -eq 0 ]; then + echo Failed + exit 1 +fi + +echo Passed + +echo 'versions file tests...' + +VF_UUID='vf-uuid.txt' +VF_CI='vf-configinfo.txt' +printf '\xef\xbb\xbf{1,2,"",aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,"alpha",bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb}' > "$VF_UUID" +printf '%s' $'\xef\xbb\xbf{0,\r\n{216,0,\r\n{80317,0}\r\n}\r\n},\r\n{2,79165599-87f3-4ac0-a26f-bec87ea8f585,YZsp1SBerILCqmJ930K1cmU+5QPr64+4LgI0qUooM2SL6iy53LudBp+6DuIrnfwV},\r\n{2,"alpha",3zNTcmSVEx7JH9YSULpruqXpBC4=,"beta",mYC3tw2+8OkCY89Uyq+bcg+6WHE=}' > "$VF_CI" + +$UNPACK -vf -show "$VF_UUID" > vf-show.txt +if ! grep -q 'alpha' vf-show.txt || ! grep -q 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb' vf-show.txt; then + echo Failed + exit 1 +fi + +got=$($UNPACK -vf -get "$VF_UUID" alpha | tr -d '\r') +if [ "$got" != "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb" ]; then + echo Failed + exit 1 +fi + +$UNPACK -vf -set "$VF_UUID" alpha cccccccc-cccc-cccc-cccc-cccccccccccc >/dev/null +got=$($UNPACK -vf -get "$VF_UUID" alpha | tr -d '\r') +if [ "$got" != "cccccccc-cccc-cccc-cccc-cccccccccccc" ]; then + echo Failed + exit 1 +fi + +# пустое имя и исходная версия контейнера сохранились +got=$($UNPACK -vf -get "$VF_UUID" "" | tr -d '\r') +if [ "$got" != "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" ]; then + echo Failed + exit 1 +fi + +$UNPACK -vf -set "$VF_UUID" alpha not-a-uuid >/dev/null 2>&1 +if [ $? -eq 0 ]; then + echo Failed + exit 1 +fi +got=$($UNPACK -vf -get "$VF_UUID" alpha | tr -d '\r') +if [ "$got" != "cccccccc-cccc-cccc-cccc-cccccccccccc" ]; then + echo Failed + exit 1 +fi + +old=$got +new=$($UNPACK -vf -update "$VF_UUID" alpha | tr -d '\r') +if [ -z "$new" ] || [ "$new" = "$old" ]; then + echo Failed + exit 1 +fi +case "$new" in + [0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]) ;; + *) echo Failed; exit 1 ;; +esac +got=$($UNPACK -versionsfile -get "$VF_UUID" alpha | tr -d '\r') +if [ "$got" != "$new" ]; then + echo Failed + exit 1 +fi + +$UNPACK -vf -get "$VF_UUID" missing >/dev/null 2>&1 +if [ $? -eq 0 ]; then + echo Failed + exit 1 +fi + +got=$($UNPACK -vf -get "$VF_CI" alpha | tr -d '\r') +if [ "$got" != "3zNTcmSVEx7JH9YSULpruqXpBC4=" ]; then + echo Failed + exit 1 +fi + +cp "$VF_CI" vf-ci-before.txt +$UNPACK -vf -set "$VF_CI" beta AAAAAAAAAAAAAAAAAAAAAAAAAAA= >/dev/null +got=$($UNPACK -vf -get "$VF_CI" beta | tr -d '\r') +if [ "$got" != "AAAAAAAAAAAAAAAAAAAAAAAAAAA=" ]; then + echo Failed + exit 1 +fi +got=$($UNPACK -vf -get "$VF_CI" alpha | tr -d '\r') +if [ "$got" != "3zNTcmSVEx7JH9YSULpruqXpBC4=" ]; then + echo Failed + exit 1 +fi +# version/root не должны меняться +head -c 80 vf-ci-before.txt > vf-ci-head1.bin +head -c 80 "$VF_CI" > vf-ci-head2.bin +diff vf-ci-head1.bin vf-ci-head2.bin >$DIFFLOG 2>&1 +if [ $? -ne 0 ]; then + echo Failed + exit 1 +fi + +$UNPACK -vf -set "$VF_CI" beta not-base64 >/dev/null 2>&1 +if [ $? -eq 0 ]; then + echo Failed + exit 1 +fi +$UNPACK -vf -set "$VF_CI" beta YQ== >/dev/null 2>&1 +if [ $? -eq 0 ]; then + echo Failed + exit 1 +fi + +old=$($UNPACK -vf -get "$VF_CI" alpha | tr -d '\r') +new=$($UNPACK -vf -update "$VF_CI" alpha | tr -d '\r') +if [ -z "$new" ] || [ "$new" = "$old" ] || [ ${#new} -ne 28 ]; then + echo Failed + exit 1 +fi +got=$($UNPACK -vf -get "$VF_CI" alpha | tr -d '\r') +if [ "$got" != "$new" ]; then + echo Failed + exit 1 +fi + +$UNPACK -vf -show missing-vf.txt >/dev/null 2>&1 +if [ $? -eq 0 ]; then + echo Failed + exit 1 +fi + +# SET -LF: несколько строк, лишние пробелы и пустые строки +printf '\xef\xbb\xbf{1,3,"",aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,"alpha",bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb,"beta",eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee}' > vf-list-uuid.txt +printf '%s\n' \ + 'alpha 11111111-1111-1111-1111-111111111111' \ + '' \ + 'beta 22222222-2222-2222-2222-222222222222' \ + > vf-set-list.txt +$UNPACK -vf -set -lf vf-set-list.txt vf-list-uuid.txt >/dev/null +got=$($UNPACK -vf -get vf-list-uuid.txt alpha | tr -d '\r') +if [ "$got" != "11111111-1111-1111-1111-111111111111" ]; then + echo Failed + exit 1 +fi +got=$($UNPACK -vf -get vf-list-uuid.txt beta | tr -d '\r') +if [ "$got" != "22222222-2222-2222-2222-222222222222" ]; then + echo Failed + exit 1 +fi + +# ошибка в списке не меняет файл +printf '%s\n' \ + 'alpha 33333333-3333-3333-3333-333333333333' \ + 'missing 44444444-4444-4444-4444-444444444444' \ + > vf-set-bad.txt +$UNPACK -vf -set -list vf-set-bad.txt vf-list-uuid.txt >/dev/null 2>&1 +if [ $? -eq 0 ]; then + echo Failed + exit 1 +fi +got=$($UNPACK -vf -get vf-list-uuid.txt alpha | tr -d '\r') +if [ "$got" != "11111111-1111-1111-1111-111111111111" ]; then + echo Failed + exit 1 +fi + +# UPDATE -LF +printf '%s\n' alpha beta > vf-upd-list.txt +old_alpha=$($UNPACK -vf -get vf-list-uuid.txt alpha | tr -d '\r') +old_beta=$($UNPACK -vf -get vf-list-uuid.txt beta | tr -d '\r') +$UNPACK -vf -update -lf vf-upd-list.txt vf-list-uuid.txt > vf-upd-out.txt +new_alpha=$($UNPACK -vf -get vf-list-uuid.txt alpha | tr -d '\r') +new_beta=$($UNPACK -vf -get vf-list-uuid.txt beta | tr -d '\r') +if [ -z "$new_alpha" ] || [ "$new_alpha" = "$old_alpha" ] || [ "$new_beta" = "$old_beta" ]; then + echo Failed + exit 1 +fi +if ! grep -q "$new_alpha" vf-upd-out.txt || ! grep -q "$new_beta" vf-upd-out.txt; then + echo Failed + exit 1 +fi + +# SET -LF для configinfo +printf '%s\n' \ + 'alpha AAAAAAAAAAAAAAAAAAAAAAAAAAA=' \ + 'beta 3zNTcmSVEx7JH9YSULpruqXpBC4=' \ + > vf-set-ci.txt +$UNPACK -vf -set -lf vf-set-ci.txt "$VF_CI" >/dev/null +got=$($UNPACK -vf -get "$VF_CI" alpha | tr -d '\r') +if [ "$got" != "AAAAAAAAAAAAAAAAAAAAAAAAAAA=" ]; then + echo Failed + exit 1 +fi +got=$($UNPACK -vf -get "$VF_CI" beta | tr -d '\r') +if [ "$got" != "3zNTcmSVEx7JH9YSULpruqXpBC4=" ]; then + echo Failed + exit 1 +fi + +echo Passed + +rm -rf "$DEL_SRC" "$DEL_OUT" "$DEL_CF" "$ADD_OUT" "$ADD_SRC" "$ADD_CF" add-dir pipe-out +rm -f add-extra.txt add-plain.txt add-build.txt unpack-src.cf unpack-stdout.bin pipe-dest.cf \ + "$VF_UUID" "$VF_CI" vf-show.txt vf-ci-before.txt vf-ci-head1.bin vf-ci-head2.bin \ + vf-list-uuid.txt vf-set-list.txt vf-set-bad.txt vf-upd-list.txt vf-upd-out.txt vf-set-ci.txt $DIFFLOG + rm -rf $DIRNAME rm -rf $OUTDIRNAME -rm $TMPFILE -rm $DIFFLOG +rm -f $TMPFILE $DIFFLOG