@@ -339,9 +339,8 @@ struct zzip::context {
339
339
ZSTD_DCtx *dctx;
340
340
};
341
341
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 ) },
342
+ zzip::zzip ( std::shared_ptr<mmap_file> file, JsonObject footer )
343
+ : file_{ std::move ( file ) },
345
344
footer_{ std::move ( footer ) }
346
345
{}
347
346
@@ -358,6 +357,12 @@ std::optional<zzip> zzip::load( std::filesystem::path const &path,
358
357
{
359
358
std::shared_ptr<mmap_file> file = mmap_file::map_writeable_file ( path );
360
359
360
+ return load ( std::move ( file ), dictionary_path );
361
+ }
362
+ std::optional<zzip> zzip::load (
363
+ std::shared_ptr<mmap_file> file,
364
+ std::filesystem::path const &dictionary_path )
365
+ {
361
366
flexbuffers::Reference root;
362
367
std::shared_ptr<parsed_flexbuffer> flexbuffer;
363
368
JsonObject footer;
@@ -386,7 +391,7 @@ std::optional<zzip> zzip::load( std::filesystem::path const &path,
386
391
}
387
392
}
388
393
389
- std::optional<zzip> ret{ std::in_place, zzip{path, std::move ( file ), std::move ( footer )} };
394
+ std::optional<zzip> ret{ std::in_place, zzip{std::move ( file ), std::move ( footer )} };
390
395
zzip &zip = ret.value ();
391
396
392
397
ZSTD_CCtx *cctx;
@@ -471,7 +476,7 @@ bool zzip::add_file( std::filesystem::path const &zzip_relative_path, std::strin
471
476
472
477
473
478
bool zzip::copy_files ( std::vector<std::filesystem::path> const &zzip_relative_paths,
474
- zzip const &from )
479
+ zzip const &from, bool shrink_to_fit )
475
480
{
476
481
if ( zzip_relative_paths.empty () ) {
477
482
return true ;
@@ -505,7 +510,7 @@ bool zzip::copy_files( std::vector<std::filesystem::path> const &zzip_relative_p
505
510
entry_to_copy.offset = new_content_end;
506
511
new_content_end += entry_to_copy.len ;
507
512
}
508
- return update_footer ( original_footer, new_content_end, entries_to_copy );
513
+ return update_footer ( original_footer, new_content_end, entries_to_copy, shrink_to_fit );
509
514
}
510
515
511
516
@@ -602,11 +607,6 @@ size_t zzip::get_zzip_size() const
602
607
return file_->len ();
603
608
}
604
609
605
- std::filesystem::path const &zzip::get_path () const
606
- {
607
- return path_;
608
- }
609
-
610
610
std::vector<std::filesystem::path> zzip::get_entries () const
611
611
{
612
612
zzip_footer footer{ footer_ };
@@ -650,14 +650,23 @@ JsonObject zzip::copy_footer() const
650
650
}
651
651
}
652
652
653
+ namespace
654
+ {
655
+ size_t round_up_file_size ( size_t bytes )
656
+ {
657
+ // 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
+ return needed_pages * kAssumedPageSize ;
661
+ }
662
+ }
663
+
653
664
// Ensures the zzip file has room to write at least this many bytes.
654
665
// Returns the guaranteed size of the file, which is at least bytes large.
655
666
size_t zzip::ensure_capacity_for ( size_t bytes )
656
667
{
657
668
// 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 ;
669
+ size_t new_size = round_up_file_size ( bytes );
661
670
662
671
if ( file_->len () < new_size && !file_->resize_file ( new_size ) ) {
663
672
return 0 ;
@@ -730,7 +739,8 @@ size_t zzip::write_file_at( std::string_view filename, std::string_view content,
730
739
// original JsonObject and inserting the given new entries.
731
740
// If shrink_to_fit is true, will shrink the file as needed to eliminate padding bytes
732
741
// between the content and the footer.
733
- bool zzip::update_footer ( JsonObject const &original_footer, size_t content_end,
742
+ bool zzip::update_footer ( JsonObject const &original_footer,
743
+ size_t content_end,
734
744
const std::vector<compressed_entry> &entries, bool shrink_to_fit )
735
745
{
736
746
std::unordered_set<std::string> processed_files;
@@ -779,12 +789,12 @@ bool zzip::update_footer( JsonObject const &original_footer, size_t content_end,
779
789
auto buf = builder.GetBuffer ();
780
790
781
791
if ( shrink_to_fit ) {
782
- return false ;
783
792
if ( !file_->resize_file ( content_end + buf.size () ) ) {
793
+ return false ;
784
794
}
785
795
} else {
786
- size_t guaranteed_capacity = ensure_capacity_for ( content_end + buf.size () );
787
- if ( guaranteed_capacity == 0 ) {
796
+ size_t guaranteed_capacity = round_up_file_size ( content_end + buf.size () );
797
+ if ( guaranteed_capacity == 0 || !file_-> resize_file ( guaranteed_capacity ) ) {
788
798
return false ;
789
799
}
790
800
}
@@ -803,13 +813,13 @@ bool zzip::update_footer( JsonObject const &original_footer, size_t content_end,
803
813
std::shared_ptr<parsed_flexbuffer> new_flexbuffer = std::make_shared<zzip_flexbuffer>(
804
814
std::move ( new_storage )
805
815
);
806
- footer_ = JsonValue ( std::move ( new_flexbuffer ), new_root, nullptr , 0 );
807
816
817
+ footer_ = JsonValue ( std::move ( new_flexbuffer ), new_root, nullptr , 0 );
808
818
return true ;
809
819
}
810
820
811
821
// Scans the zzip to read what data we can validate and write a fresh footer.
812
- bool zzip::rewrite_footer ()
822
+ bool zzip::rewrite_footer ( bool shrink_to_fit )
813
823
{
814
824
const size_t zzip_len = file_->len ();
815
825
size_t scan_offset = 0 ;
@@ -910,7 +920,7 @@ bool zzip::rewrite_footer()
910
920
entries.emplace_back ( std::move ( entry ) );
911
921
}
912
922
913
- return update_footer ( JsonObject{}, scan_offset, entries, /* shrink_to_fit = */ true );
923
+ return update_footer ( JsonObject{}, scan_offset, entries, shrink_to_fit );
914
924
}
915
925
916
926
std::optional<zzip> zzip::create_from_folder ( std::filesystem::path const &path,
@@ -1031,12 +1041,17 @@ bool zzip::extract_to_folder( std::filesystem::path const &path,
1031
1041
return false ;
1032
1042
}
1033
1043
1034
- for ( const JsonMember &entry : zip->footer_ .get_object ( kEntriesKey ) ) {
1044
+ return zip->extract_to_folder ( folder );
1045
+ }
1046
+
1047
+ bool zzip::extract_to_folder ( std::filesystem::path const &folder )
1048
+ {
1049
+ for ( const JsonMember &entry : footer_.get_object ( kEntriesKey ) ) {
1035
1050
if ( entry.name ().empty () ) {
1036
1051
continue ;
1037
1052
}
1038
1053
std::filesystem::path filename = std::filesystem::u8path ( entry.name () );
1039
- size_t len = zip-> get_file_size ( filename );
1054
+ size_t len = get_file_size ( filename );
1040
1055
std::filesystem::path destination_file_path = folder / filename;
1041
1056
if ( !assure_dir_exist ( destination_file_path.parent_path () ) ) {
1042
1057
return false ;
@@ -1045,7 +1060,7 @@ bool zzip::extract_to_folder( std::filesystem::path const &path,
1045
1060
if ( !file || !file->resize_file ( len ) ) {
1046
1061
return false ;
1047
1062
}
1048
- if ( zip-> get_file_to ( filename, static_cast <std::byte *>( file->base () ), file->len () ) != len ) {
1063
+ if ( get_file_to ( filename, static_cast <std::byte *>( file->base () ), file->len () ) != len ) {
1049
1064
return false ;
1050
1065
}
1051
1066
}
@@ -1111,7 +1126,7 @@ bool zzip::delete_files( std::unordered_set<std::filesystem::path, std_fs_path_h
1111
1126
return !errored;
1112
1127
}
1113
1128
1114
- bool zzip::compact ( double bloat_factor )
1129
+ bool zzip::compact_to ( std::filesystem::path dest, double bloat_factor )
1115
1130
{
1116
1131
zzip_footer footer{ footer_ };
1117
1132
std::optional<zzip_meta> meta_opt = footer.get_meta ();
@@ -1135,69 +1150,26 @@ bool zzip::compact( double bloat_factor )
1135
1150
}
1136
1151
}
1137
1152
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 ) ) {
1153
+ std::unique_ptr<mmap_file> compacted_file = mmap_file::map_writeable_file ( dest );
1154
+ if ( !compacted_file ) {
1155
1155
return false ;
1156
1156
}
1157
1157
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
- }
1158
+ return compact_to ( std::move ( compacted_file ) );
1159
+ }
1185
1160
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 ) {
1161
+ bool zzip::compact_to ( std::shared_ptr<mmap_file> dest )
1162
+ {
1163
+ std::optional<zzip> new_zip = zzip::load ( dest );
1164
+ if ( !new_zip ) {
1192
1165
return false ;
1193
1166
}
1194
- reset_on_failure.cancel ();
1195
- return true ;
1167
+ return new_zip->copy_files ( get_entries (), *this , /* shrink_to_fit = */ true );
1196
1168
}
1197
1169
1198
1170
bool zzip::clear ()
1199
1171
{
1200
- return file_->resize_file ( 0 ) && rewrite_footer ();
1172
+ return file_->resize_file ( 0 ) && rewrite_footer ( /* shrink_to_fit = */ true );
1201
1173
}
1202
1174
1203
1175
// Can't directly increment void*, have to cast to char* first.
0 commit comments