Skip to content

Commit 63c9933

Browse files
committed
Remove path member from zzip so it can be tested in memory only, and write tests.
1 parent 415a453 commit 63c9933

File tree

8 files changed

+368
-109
lines changed

8 files changed

+368
-109
lines changed

src/debug_menu.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -484,8 +484,7 @@ void write_min_archive()
484484
{
485485
std::optional<zzip> min_mmr_temp_zzip = zzip::load( min_mmr_temp_zzip_path, mmr_dict );
486486
if( !min_mmr_temp_zzip ||
487-
!mmr_stack->copy_files_to( trimmed_mmr_entries, *min_mmr_temp_zzip ) ||
488-
!min_mmr_temp_zzip->compact( 0 ) ) {
487+
!mmr_stack->copy_files_to( trimmed_mmr_entries, *min_mmr_temp_zzip ) ) {
489488
popup( _( "Failed to create minimized archive" ) );
490489
return;
491490
}

src/game.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3500,7 +3500,8 @@ bool game::save_player_data()
35003500
".zzip.tmp" ).get_unrelative_path() );
35013501
saved_data = z->add_file( ( playerfile + SAVE_EXTENSION ).get_unrelative_path().filename(),
35023502
save.str() );
3503-
saved_data = saved_data && z->compact( 1.0 );
3503+
saved_data = saved_data && z->compact_to( ( playerfile + SAVE_EXTENSION +
3504+
".zzip" ).get_unrelative_path(), 0.0 );
35043505
} else {
35053506
saved_data = write_to_file( playerfile + SAVE_EXTENSION, [&]( std::ostream & fout ) {
35063507
serialize_json( fout );

src/mapbuffer.cpp

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,12 +254,12 @@ void mapbuffer::save_quad(
254254
bool file_exists = false;
255255

256256
std::optional<zzip> z;
257+
cata_path zzip_name = dirname;
258+
zzip_name += ".zzip";
257259
// The number of uniform submaps is so enormous that the filesystem overhead
258260
// for this step of just checking if the quad exists approaches 70% of the
259261
// total cost of saving the mapbuffer, in one test save I had.
260262
if( world_generator->active_world->has_compression_enabled() ) {
261-
cata_path zzip_name = dirname;
262-
zzip_name += ".zzip";
263263
z = zzip::load( zzip_name.get_unrelative_path(),
264264
( PATH_INFO::world_base_save_path() / "maps.dict" ).get_unrelative_path() );
265265
if( !z ) {
@@ -342,7 +342,6 @@ void mapbuffer::save_quad(
342342

343343
if( z ) {
344344
z->add_file( filename.get_relative_path().filename(), s );
345-
z->compact( 2.0 );
346345
} else {
347346
// Don't create the directory if it would be empty
348347
assure_dir_exist( dirname );
@@ -358,6 +357,13 @@ void mapbuffer::save_quad(
358357
std::filesystem::remove( filename.get_unrelative_path() );
359358
}
360359
}
360+
if( z ) {
361+
cata_path tmp_path = zzip_name + ".tmp";
362+
if( z->compact_to( tmp_path.get_unrelative_path(), 2.0 ) ) {
363+
z.reset();
364+
rename_file( tmp_path, zzip_name );
365+
}
366+
}
361367
}
362368

363369
// We're reading in way too many entities here to mess around with creating sub-objects and

src/overmap.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6758,7 +6758,11 @@ void overmap::save() const
67586758
throw std::runtime_error( string_format( "Failed to save omap %d.%d to %s", loc.x(),
67596759
loc.y(), zzip_path.get_unrelative_path().generic_u8string().c_str() ) );
67606760
}
6761-
z->compact( 2.0 );
6761+
cata_path tmp_path = zzip_path + ".tmp";
6762+
if( z->compact_to( tmp_path.get_unrelative_path(), 2.0 ) ) {
6763+
z.reset();
6764+
rename_file( tmp_path, zzip_path );
6765+
}
67626766
} else {
67636767
write_to_file( PATH_INFO::world_base_save_path() / overmapbuffer::terrain_filename( loc ), [&](
67646768
std::ostream & stream ) {

src/zzip.cpp

Lines changed: 50 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
#include <array>
55
#include <cstring>
66
#include <exception>
7-
#include <functional>
87
#include <iosfwd>
98
#include <memory>
109
#include <optional>
@@ -23,7 +22,6 @@
2322
#include <zstd/common/mem.h>
2423
#include <zstd/common/xxhash.h>
2524

26-
#include "cata_scope_helpers.h"
2725
#include "filesystem.h"
2826
#include "flexbuffer_cache.h"
2927
#include "flexbuffer_json.h"
@@ -339,9 +337,8 @@ struct zzip::context {
339337
ZSTD_DCtx *dctx;
340338
};
341339

342-
zzip::zzip( std::filesystem::path path, std::shared_ptr<mmap_file> file, JsonObject footer )
343-
: path_{ std::move( path ) },
344-
file_{ std::move( file ) },
340+
zzip::zzip( std::shared_ptr<mmap_file> file, JsonObject footer )
341+
: file_{ std::move( file ) },
345342
footer_{ std::move( footer ) }
346343
{}
347344

@@ -358,6 +355,12 @@ std::optional<zzip> zzip::load( std::filesystem::path const &path,
358355
{
359356
std::shared_ptr<mmap_file> file = mmap_file::map_writeable_file( path );
360357

358+
return load( std::move( file ), dictionary_path );
359+
}
360+
std::optional<zzip> zzip::load(
361+
std::shared_ptr<mmap_file> file,
362+
std::filesystem::path const &dictionary_path )
363+
{
361364
flexbuffers::Reference root;
362365
std::shared_ptr<parsed_flexbuffer> flexbuffer;
363366
JsonObject footer;
@@ -386,7 +389,7 @@ std::optional<zzip> zzip::load( std::filesystem::path const &path,
386389
}
387390
}
388391

389-
std::optional<zzip> ret{ std::in_place, zzip{path, std::move( file ), std::move( footer )} };
392+
std::optional<zzip> ret{ std::in_place, zzip{std::move( file ), std::move( footer )} };
390393
zzip &zip = ret.value();
391394

392395
ZSTD_CCtx *cctx;
@@ -471,7 +474,7 @@ bool zzip::add_file( std::filesystem::path const &zzip_relative_path, std::strin
471474

472475

473476
bool zzip::copy_files( std::vector<std::filesystem::path> const &zzip_relative_paths,
474-
zzip const &from )
477+
zzip const &from, bool shrink_to_fit )
475478
{
476479
if( zzip_relative_paths.empty() ) {
477480
return true;
@@ -505,7 +508,7 @@ bool zzip::copy_files( std::vector<std::filesystem::path> const &zzip_relative_p
505508
entry_to_copy.offset = new_content_end;
506509
new_content_end += entry_to_copy.len;
507510
}
508-
return update_footer( original_footer, new_content_end, entries_to_copy );
511+
return update_footer( original_footer, new_content_end, entries_to_copy, shrink_to_fit );
509512
}
510513

511514

@@ -602,11 +605,6 @@ size_t zzip::get_zzip_size() const
602605
return file_->len();
603606
}
604607

605-
std::filesystem::path const &zzip::get_path() const
606-
{
607-
return path_;
608-
}
609-
610608
std::vector<std::filesystem::path> zzip::get_entries() const
611609
{
612610
zzip_footer footer{ footer_ };
@@ -650,14 +648,23 @@ JsonObject zzip::copy_footer() const
650648
}
651649
}
652650

651+
namespace
652+
{
653+
size_t round_up_file_size( size_t bytes )
654+
{
655+
// Round up to a whole page. Drives nowadays are all 4k pages.
656+
size_t needed_pages = std::max( size_t{ 1 }, ( bytes + kAssumedPageSize - 1 ) / kAssumedPageSize );
657+
658+
return needed_pages * kAssumedPageSize;
659+
}
660+
}
661+
653662
// Ensures the zzip file has room to write at least this many bytes.
654663
// Returns the guaranteed size of the file, which is at least bytes large.
655664
size_t zzip::ensure_capacity_for( size_t bytes )
656665
{
657666
// Round up to a whole page. Drives nowadays are all 4k pages.
658-
size_t needed_pages = std::max( size_t{1}, ( bytes + kAssumedPageSize - 1 ) / kAssumedPageSize );
659-
660-
size_t new_size = needed_pages * kAssumedPageSize;
667+
size_t new_size = round_up_file_size( bytes );
661668

662669
if( file_->len() < new_size && !file_->resize_file( new_size ) ) {
663670
return 0;
@@ -730,7 +737,8 @@ size_t zzip::write_file_at( std::string_view filename, std::string_view content,
730737
// original JsonObject and inserting the given new entries.
731738
// If shrink_to_fit is true, will shrink the file as needed to eliminate padding bytes
732739
// between the content and the footer.
733-
bool zzip::update_footer( JsonObject const &original_footer, size_t content_end,
740+
bool zzip::update_footer( JsonObject const &original_footer,
741+
size_t content_end,
734742
const std::vector<compressed_entry> &entries, bool shrink_to_fit )
735743
{
736744
std::unordered_set<std::string> processed_files;
@@ -779,12 +787,12 @@ bool zzip::update_footer( JsonObject const &original_footer, size_t content_end,
779787
auto buf = builder.GetBuffer();
780788

781789
if( shrink_to_fit ) {
782-
return false;
783790
if( !file_->resize_file( content_end + buf.size() ) ) {
791+
return false;
784792
}
785793
} else {
786-
size_t guaranteed_capacity = ensure_capacity_for( content_end + buf.size() );
787-
if( guaranteed_capacity == 0 ) {
794+
size_t guaranteed_capacity = round_up_file_size( content_end + buf.size() );
795+
if( guaranteed_capacity == 0 || !file_->resize_file( guaranteed_capacity ) ) {
788796
return false;
789797
}
790798
}
@@ -803,13 +811,13 @@ bool zzip::update_footer( JsonObject const &original_footer, size_t content_end,
803811
std::shared_ptr<parsed_flexbuffer> new_flexbuffer = std::make_shared<zzip_flexbuffer>(
804812
std::move( new_storage )
805813
);
806-
footer_ = JsonValue( std::move( new_flexbuffer ), new_root, nullptr, 0 );
807814

815+
footer_ = JsonValue( std::move( new_flexbuffer ), new_root, nullptr, 0 );
808816
return true;
809817
}
810818

811819
// Scans the zzip to read what data we can validate and write a fresh footer.
812-
bool zzip::rewrite_footer()
820+
bool zzip::rewrite_footer( bool shrink_to_fit )
813821
{
814822
const size_t zzip_len = file_->len();
815823
size_t scan_offset = 0;
@@ -910,7 +918,7 @@ bool zzip::rewrite_footer()
910918
entries.emplace_back( std::move( entry ) );
911919
}
912920

913-
return update_footer( JsonObject{}, scan_offset, entries, /* shrink_to_fit = */ true );
921+
return update_footer( JsonObject{}, scan_offset, entries, shrink_to_fit );
914922
}
915923

916924
std::optional<zzip> zzip::create_from_folder( std::filesystem::path const &path,
@@ -1031,12 +1039,17 @@ bool zzip::extract_to_folder( std::filesystem::path const &path,
10311039
return false;
10321040
}
10331041

1034-
for( const JsonMember &entry : zip->footer_.get_object( kEntriesKey ) ) {
1042+
return zip->extract_to_folder( folder );
1043+
}
1044+
1045+
bool zzip::extract_to_folder( std::filesystem::path const &folder )
1046+
{
1047+
for( const JsonMember &entry : footer_.get_object( kEntriesKey ) ) {
10351048
if( entry.name().empty() ) {
10361049
continue;
10371050
}
10381051
std::filesystem::path filename = std::filesystem::u8path( entry.name() );
1039-
size_t len = zip->get_file_size( filename );
1052+
size_t len = get_file_size( filename );
10401053
std::filesystem::path destination_file_path = folder / filename;
10411054
if( !assure_dir_exist( destination_file_path.parent_path() ) ) {
10421055
return false;
@@ -1045,7 +1058,7 @@ bool zzip::extract_to_folder( std::filesystem::path const &path,
10451058
if( !file || !file->resize_file( len ) ) {
10461059
return false;
10471060
}
1048-
if( zip->get_file_to( filename, static_cast<std::byte *>( file->base() ), file->len() ) != len ) {
1061+
if( get_file_to( filename, static_cast<std::byte *>( file->base() ), file->len() ) != len ) {
10491062
return false;
10501063
}
10511064
}
@@ -1111,7 +1124,7 @@ bool zzip::delete_files( std::unordered_set<std::filesystem::path, std_fs_path_h
11111124
return !errored;
11121125
}
11131126

1114-
bool zzip::compact( double bloat_factor )
1127+
bool zzip::compact_to( std::filesystem::path dest, double bloat_factor )
11151128
{
11161129
zzip_footer footer{ footer_ };
11171130
std::optional<zzip_meta> meta_opt = footer.get_meta();
@@ -1135,69 +1148,26 @@ bool zzip::compact( double bloat_factor )
11351148
}
11361149
}
11371150

1138-
std::vector<compressed_entry> compressed_entries = footer.get_entries();
1139-
1140-
// If we were compacting in place, sorting left to right would ensure that we
1141-
// only move entries left that we need to move. Unchanged data would stay on the left.
1142-
// However that exposes us to data loss on power loss because zzip recovery
1143-
// relies on an intact sequence of zstd frames.
1144-
std::sort( compressed_entries.begin(), compressed_entries.end(), []( const compressed_entry & lhs,
1145-
const compressed_entry & rhs ) {
1146-
return lhs.offset < rhs.offset;
1147-
} );
1148-
1149-
std::filesystem::path tmp_path = path_;
1150-
tmp_path.replace_extension( ".zzip.tmp" ); // NOLINT(cata-u8-path)
1151-
1152-
std::shared_ptr<mmap_file> compacted_file = mmap_file::map_writeable_file( tmp_path );
1153-
if( !compacted_file ||
1154-
!compacted_file->resize_file( meta.total_content_size + kFixedSizeOverhead ) ) {
1151+
std::unique_ptr<mmap_file> compacted_file = mmap_file::map_writeable_file( dest );
1152+
if( !compacted_file ) {
11551153
return false;
11561154
}
11571155

1158-
size_t current_end = kFooterChecksumFrameSize;
1159-
for( compressed_entry &entry : compressed_entries ) {
1160-
memcpy( static_cast<char *>( compacted_file->base() ) + current_end,
1161-
file_base_plus( entry.offset ),
1162-
entry.len
1163-
);
1164-
entry.offset = current_end;
1165-
current_end += entry.len;
1166-
}
1167-
1168-
// We can swap the old mmap'd file with the new one
1169-
// and write a fresh footer directly into it.
1170-
JsonObject old_footer{ copy_footer() };
1171-
old_footer.allow_omitted_members();
1172-
1173-
file_ = compacted_file;
1174-
compacted_file = nullptr;
1175-
1176-
std::error_code ec;
1177-
on_out_of_scope reset_on_failure{ [&] {
1178-
file_ = mmap_file::map_writeable_file( path_ );
1179-
std::filesystem::remove( tmp_path, ec );
1180-
} };
1181-
1182-
if( !update_footer( old_footer, current_end, compressed_entries, /* shrink_to_fit = */ true ) ) {
1183-
return false;
1184-
}
1156+
return compact_to( std::move( compacted_file ) );
1157+
}
11851158

1186-
// Swap the tmp file over the real path. Give it a couple tries in case of filesystem races.
1187-
size_t attempts = 0;
1188-
do {
1189-
std::filesystem::rename( tmp_path, path_, ec );
1190-
} while( ec && ++attempts < 3 );
1191-
if( ec ) {
1159+
bool zzip::compact_to( std::shared_ptr<mmap_file> dest )
1160+
{
1161+
std::optional<zzip> new_zip = zzip::load( dest );
1162+
if( !new_zip ) {
11921163
return false;
11931164
}
1194-
reset_on_failure.cancel();
1195-
return true;
1165+
return new_zip->copy_files( get_entries(), *this, /* shrink_to_fit = */ true );
11961166
}
11971167

11981168
bool zzip::clear()
11991169
{
1200-
return file_->resize_file( 0 ) && rewrite_footer();
1170+
return file_->resize_file( 0 ) && rewrite_footer( /* shrink_to_fit = */ true );
12011171
}
12021172

12031173
// Can't directly increment void*, have to cast to char* first.

0 commit comments

Comments
 (0)