4
4
#include < array>
5
5
#include < cstring>
6
6
#include < exception>
7
- #include < functional>
8
7
#include < iosfwd>
9
8
#include < memory>
10
9
#include < optional>
23
22
#include < zstd/common/mem.h>
24
23
#include < zstd/common/xxhash.h>
25
24
26
- #include " cata_scope_helpers.h"
27
25
#include " filesystem.h"
28
26
#include " flexbuffer_cache.h"
29
27
#include " flexbuffer_json.h"
@@ -339,9 +337,8 @@ struct zzip::context {
339
337
ZSTD_DCtx *dctx;
340
338
};
341
339
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 ) },
345
342
footer_{ std::move ( footer ) }
346
343
{}
347
344
@@ -358,6 +355,12 @@ std::optional<zzip> zzip::load( std::filesystem::path const &path,
358
355
{
359
356
std::shared_ptr<mmap_file> file = mmap_file::map_writeable_file ( path );
360
357
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
+ {
361
364
flexbuffers::Reference root;
362
365
std::shared_ptr<parsed_flexbuffer> flexbuffer;
363
366
JsonObject footer;
@@ -386,7 +389,7 @@ std::optional<zzip> zzip::load( std::filesystem::path const &path,
386
389
}
387
390
}
388
391
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 )} };
390
393
zzip &zip = ret.value ();
391
394
392
395
ZSTD_CCtx *cctx;
@@ -471,7 +474,7 @@ bool zzip::add_file( std::filesystem::path const &zzip_relative_path, std::strin
471
474
472
475
473
476
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 )
475
478
{
476
479
if ( zzip_relative_paths.empty () ) {
477
480
return true ;
@@ -505,7 +508,7 @@ bool zzip::copy_files( std::vector<std::filesystem::path> const &zzip_relative_p
505
508
entry_to_copy.offset = new_content_end;
506
509
new_content_end += entry_to_copy.len ;
507
510
}
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 );
509
512
}
510
513
511
514
@@ -602,11 +605,6 @@ size_t zzip::get_zzip_size() const
602
605
return file_->len ();
603
606
}
604
607
605
- std::filesystem::path const &zzip::get_path () const
606
- {
607
- return path_;
608
- }
609
-
610
608
std::vector<std::filesystem::path> zzip::get_entries () const
611
609
{
612
610
zzip_footer footer{ footer_ };
@@ -650,14 +648,23 @@ JsonObject zzip::copy_footer() const
650
648
}
651
649
}
652
650
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
+
653
662
// Ensures the zzip file has room to write at least this many bytes.
654
663
// Returns the guaranteed size of the file, which is at least bytes large.
655
664
size_t zzip::ensure_capacity_for ( size_t bytes )
656
665
{
657
666
// 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 );
661
668
662
669
if ( file_->len () < new_size && !file_->resize_file ( new_size ) ) {
663
670
return 0 ;
@@ -730,7 +737,8 @@ size_t zzip::write_file_at( std::string_view filename, std::string_view content,
730
737
// original JsonObject and inserting the given new entries.
731
738
// If shrink_to_fit is true, will shrink the file as needed to eliminate padding bytes
732
739
// 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,
734
742
const std::vector<compressed_entry> &entries, bool shrink_to_fit )
735
743
{
736
744
std::unordered_set<std::string> processed_files;
@@ -779,12 +787,12 @@ bool zzip::update_footer( JsonObject const &original_footer, size_t content_end,
779
787
auto buf = builder.GetBuffer ();
780
788
781
789
if ( shrink_to_fit ) {
782
- return false ;
783
790
if ( !file_->resize_file ( content_end + buf.size () ) ) {
791
+ return false ;
784
792
}
785
793
} 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 ) ) {
788
796
return false ;
789
797
}
790
798
}
@@ -803,13 +811,13 @@ bool zzip::update_footer( JsonObject const &original_footer, size_t content_end,
803
811
std::shared_ptr<parsed_flexbuffer> new_flexbuffer = std::make_shared<zzip_flexbuffer>(
804
812
std::move ( new_storage )
805
813
);
806
- footer_ = JsonValue ( std::move ( new_flexbuffer ), new_root, nullptr , 0 );
807
814
815
+ footer_ = JsonValue ( std::move ( new_flexbuffer ), new_root, nullptr , 0 );
808
816
return true ;
809
817
}
810
818
811
819
// 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 )
813
821
{
814
822
const size_t zzip_len = file_->len ();
815
823
size_t scan_offset = 0 ;
@@ -910,7 +918,7 @@ bool zzip::rewrite_footer()
910
918
entries.emplace_back ( std::move ( entry ) );
911
919
}
912
920
913
- return update_footer ( JsonObject{}, scan_offset, entries, /* shrink_to_fit = */ true );
921
+ return update_footer ( JsonObject{}, scan_offset, entries, shrink_to_fit );
914
922
}
915
923
916
924
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,
1031
1039
return false ;
1032
1040
}
1033
1041
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 ) ) {
1035
1048
if ( entry.name ().empty () ) {
1036
1049
continue ;
1037
1050
}
1038
1051
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 );
1040
1053
std::filesystem::path destination_file_path = folder / filename;
1041
1054
if ( !assure_dir_exist ( destination_file_path.parent_path () ) ) {
1042
1055
return false ;
@@ -1045,7 +1058,7 @@ bool zzip::extract_to_folder( std::filesystem::path const &path,
1045
1058
if ( !file || !file->resize_file ( len ) ) {
1046
1059
return false ;
1047
1060
}
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 ) {
1049
1062
return false ;
1050
1063
}
1051
1064
}
@@ -1111,7 +1124,7 @@ bool zzip::delete_files( std::unordered_set<std::filesystem::path, std_fs_path_h
1111
1124
return !errored;
1112
1125
}
1113
1126
1114
- bool zzip::compact ( double bloat_factor )
1127
+ bool zzip::compact_to ( std::filesystem::path dest, double bloat_factor )
1115
1128
{
1116
1129
zzip_footer footer{ footer_ };
1117
1130
std::optional<zzip_meta> meta_opt = footer.get_meta ();
@@ -1135,69 +1148,26 @@ bool zzip::compact( double bloat_factor )
1135
1148
}
1136
1149
}
1137
1150
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 ) {
1155
1153
return false ;
1156
1154
}
1157
1155
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
+ }
1185
1158
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 ) {
1192
1163
return false ;
1193
1164
}
1194
- reset_on_failure.cancel ();
1195
- return true ;
1165
+ return new_zip->copy_files ( get_entries (), *this , /* shrink_to_fit = */ true );
1196
1166
}
1197
1167
1198
1168
bool zzip::clear ()
1199
1169
{
1200
- return file_->resize_file ( 0 ) && rewrite_footer ();
1170
+ return file_->resize_file ( 0 ) && rewrite_footer ( /* shrink_to_fit = */ true );
1201
1171
}
1202
1172
1203
1173
// Can't directly increment void*, have to cast to char* first.
0 commit comments