tigerbeetle 0.0.34 → 0.0.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/ext/tb_client/extconf.rb +13 -13
- data/ext/tb_client/tigerbeetle/LICENSE +177 -0
- data/ext/tb_client/tigerbeetle/build.zig +2327 -0
- data/ext/tb_client/tigerbeetle/src/aof.zig +1000 -0
- data/ext/tb_client/tigerbeetle/src/build_multiversion.zig +808 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp/protocol.zig +1283 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp/spec.zig +1704 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp/types.zig +341 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp.zig +1450 -0
- data/ext/tb_client/tigerbeetle/src/cdc/runner.zig +1659 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/samples/main.c +406 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/context.zig +1084 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/echo_client.zig +286 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/packet.zig +158 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal.zig +229 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal_fuzz.zig +110 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.h +386 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.zig +34 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_exports.zig +281 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header.zig +312 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header_test.zig +138 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/test.zig +466 -0
- data/ext/tb_client/tigerbeetle/src/clients/docs_samples.zig +157 -0
- data/ext/tb_client/tigerbeetle/src/clients/docs_types.zig +90 -0
- data/ext/tb_client/tigerbeetle/src/clients/dotnet/ci.zig +203 -0
- data/ext/tb_client/tigerbeetle/src/clients/dotnet/docs.zig +79 -0
- data/ext/tb_client/tigerbeetle/src/clients/dotnet/dotnet_bindings.zig +542 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/ci.zig +109 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/docs.zig +86 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/go_bindings.zig +370 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/pkg/native/tb_client.h +386 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/ci.zig +167 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/docs.zig +126 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/java_bindings.zig +996 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/client.zig +748 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/jni.zig +3238 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_tests.zig +1718 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_thread_cleaner.zig +190 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/ci.zig +104 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/docs.zig +75 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/node.zig +522 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/node_bindings.zig +267 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/src/c.zig +3 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/src/translate.zig +379 -0
- data/ext/tb_client/tigerbeetle/src/clients/python/ci.zig +131 -0
- data/ext/tb_client/tigerbeetle/src/clients/python/docs.zig +63 -0
- data/ext/tb_client/tigerbeetle/src/clients/python/python_bindings.zig +588 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/assets/tb_client.h +386 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/ci.zig +73 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/docs.zig +106 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/rust_bindings.zig +305 -0
- data/ext/tb_client/tigerbeetle/src/config.zig +296 -0
- data/ext/tb_client/tigerbeetle/src/constants.zig +790 -0
- data/ext/tb_client/tigerbeetle/src/copyhound.zig +202 -0
- data/ext/tb_client/tigerbeetle/src/counting_allocator.zig +72 -0
- data/ext/tb_client/tigerbeetle/src/direction.zig +11 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/build.zig +158 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/content.zig +156 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/docs.zig +252 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/file_checker.zig +313 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/html.zig +87 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/page_writer.zig +63 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/redirects.zig +47 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/search_index_writer.zig +28 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/service_worker_writer.zig +61 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/single_page_writer.zig +169 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/website.zig +46 -0
- data/ext/tb_client/tigerbeetle/src/ewah.zig +445 -0
- data/ext/tb_client/tigerbeetle/src/ewah_benchmark.zig +128 -0
- data/ext/tb_client/tigerbeetle/src/ewah_fuzz.zig +171 -0
- data/ext/tb_client/tigerbeetle/src/fuzz_tests.zig +179 -0
- data/ext/tb_client/tigerbeetle/src/integration_tests.zig +662 -0
- data/ext/tb_client/tigerbeetle/src/io/common.zig +155 -0
- data/ext/tb_client/tigerbeetle/src/io/darwin.zig +1093 -0
- data/ext/tb_client/tigerbeetle/src/io/linux.zig +1880 -0
- data/ext/tb_client/tigerbeetle/src/io/test.zig +1005 -0
- data/ext/tb_client/tigerbeetle/src/io/windows.zig +1598 -0
- data/ext/tb_client/tigerbeetle/src/io.zig +34 -0
- data/ext/tb_client/tigerbeetle/src/iops.zig +134 -0
- data/ext/tb_client/tigerbeetle/src/list.zig +236 -0
- data/ext/tb_client/tigerbeetle/src/lsm/binary_search.zig +848 -0
- data/ext/tb_client/tigerbeetle/src/lsm/binary_search_benchmark.zig +179 -0
- data/ext/tb_client/tigerbeetle/src/lsm/cache_map.zig +424 -0
- data/ext/tb_client/tigerbeetle/src/lsm/cache_map_fuzz.zig +420 -0
- data/ext/tb_client/tigerbeetle/src/lsm/compaction.zig +2117 -0
- data/ext/tb_client/tigerbeetle/src/lsm/composite_key.zig +182 -0
- data/ext/tb_client/tigerbeetle/src/lsm/forest.zig +1119 -0
- data/ext/tb_client/tigerbeetle/src/lsm/forest_fuzz.zig +1102 -0
- data/ext/tb_client/tigerbeetle/src/lsm/forest_table_iterator.zig +200 -0
- data/ext/tb_client/tigerbeetle/src/lsm/groove.zig +1495 -0
- data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge.zig +739 -0
- data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge_benchmark.zig +166 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest.zig +754 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_level.zig +1294 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_level_fuzz.zig +510 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_log.zig +1263 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_log_fuzz.zig +628 -0
- data/ext/tb_client/tigerbeetle/src/lsm/node_pool.zig +247 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_buffer.zig +116 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_builder.zig +543 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_fuzz.zig +938 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_lookup.zig +293 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_merge.zig +362 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_range.zig +99 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_state.zig +17 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_tree.zig +1036 -0
- data/ext/tb_client/tigerbeetle/src/lsm/schema.zig +617 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scratch_memory.zig +84 -0
- data/ext/tb_client/tigerbeetle/src/lsm/segmented_array.zig +1500 -0
- data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_benchmark.zig +149 -0
- data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_fuzz.zig +7 -0
- data/ext/tb_client/tigerbeetle/src/lsm/set_associative_cache.zig +865 -0
- data/ext/tb_client/tigerbeetle/src/lsm/table.zig +607 -0
- data/ext/tb_client/tigerbeetle/src/lsm/table_memory.zig +843 -0
- data/ext/tb_client/tigerbeetle/src/lsm/table_value_iterator.zig +105 -0
- data/ext/tb_client/tigerbeetle/src/lsm/timestamp_range.zig +40 -0
- data/ext/tb_client/tigerbeetle/src/lsm/tree.zig +630 -0
- data/ext/tb_client/tigerbeetle/src/lsm/tree_fuzz.zig +933 -0
- data/ext/tb_client/tigerbeetle/src/lsm/zig_zag_merge.zig +557 -0
- data/ext/tb_client/tigerbeetle/src/message_buffer.zig +469 -0
- data/ext/tb_client/tigerbeetle/src/message_bus.zig +1214 -0
- data/ext/tb_client/tigerbeetle/src/message_bus_fuzz.zig +936 -0
- data/ext/tb_client/tigerbeetle/src/message_pool.zig +343 -0
- data/ext/tb_client/tigerbeetle/src/multiversion.zig +2195 -0
- data/ext/tb_client/tigerbeetle/src/queue.zig +390 -0
- data/ext/tb_client/tigerbeetle/src/repl/completion.zig +201 -0
- data/ext/tb_client/tigerbeetle/src/repl/parser.zig +1356 -0
- data/ext/tb_client/tigerbeetle/src/repl/terminal.zig +496 -0
- data/ext/tb_client/tigerbeetle/src/repl.zig +1034 -0
- data/ext/tb_client/tigerbeetle/src/scripts/amqp.zig +973 -0
- data/ext/tb_client/tigerbeetle/src/scripts/cfo.zig +1866 -0
- data/ext/tb_client/tigerbeetle/src/scripts/changelog.zig +304 -0
- data/ext/tb_client/tigerbeetle/src/scripts/ci.zig +227 -0
- data/ext/tb_client/tigerbeetle/src/scripts/client_readmes.zig +658 -0
- data/ext/tb_client/tigerbeetle/src/scripts/devhub.zig +466 -0
- data/ext/tb_client/tigerbeetle/src/scripts/release.zig +1058 -0
- data/ext/tb_client/tigerbeetle/src/scripts.zig +105 -0
- data/ext/tb_client/tigerbeetle/src/shell.zig +1195 -0
- data/ext/tb_client/tigerbeetle/src/stack.zig +260 -0
- data/ext/tb_client/tigerbeetle/src/state_machine/auditor.zig +911 -0
- data/ext/tb_client/tigerbeetle/src/state_machine/workload.zig +2079 -0
- data/ext/tb_client/tigerbeetle/src/state_machine.zig +4872 -0
- data/ext/tb_client/tigerbeetle/src/state_machine_fuzz.zig +288 -0
- data/ext/tb_client/tigerbeetle/src/state_machine_tests.zig +3128 -0
- data/ext/tb_client/tigerbeetle/src/static_allocator.zig +82 -0
- data/ext/tb_client/tigerbeetle/src/stdx/bit_set.zig +157 -0
- data/ext/tb_client/tigerbeetle/src/stdx/bounded_array.zig +292 -0
- data/ext/tb_client/tigerbeetle/src/stdx/debug.zig +65 -0
- data/ext/tb_client/tigerbeetle/src/stdx/flags.zig +1414 -0
- data/ext/tb_client/tigerbeetle/src/stdx/mlock.zig +92 -0
- data/ext/tb_client/tigerbeetle/src/stdx/prng.zig +677 -0
- data/ext/tb_client/tigerbeetle/src/stdx/radix.zig +336 -0
- data/ext/tb_client/tigerbeetle/src/stdx/ring_buffer.zig +511 -0
- data/ext/tb_client/tigerbeetle/src/stdx/sort_test.zig +112 -0
- data/ext/tb_client/tigerbeetle/src/stdx/stdx.zig +1160 -0
- data/ext/tb_client/tigerbeetle/src/stdx/testing/low_level_hash_vectors.zig +142 -0
- data/ext/tb_client/tigerbeetle/src/stdx/testing/snaptest.zig +361 -0
- data/ext/tb_client/tigerbeetle/src/stdx/time_units.zig +275 -0
- data/ext/tb_client/tigerbeetle/src/stdx/unshare.zig +295 -0
- data/ext/tb_client/tigerbeetle/src/stdx/vendored/aegis.zig +436 -0
- data/ext/tb_client/tigerbeetle/src/stdx/windows.zig +48 -0
- data/ext/tb_client/tigerbeetle/src/stdx/zipfian.zig +402 -0
- data/ext/tb_client/tigerbeetle/src/storage.zig +489 -0
- data/ext/tb_client/tigerbeetle/src/storage_fuzz.zig +180 -0
- data/ext/tb_client/tigerbeetle/src/testing/bench.zig +146 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/grid_checker.zig +53 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/journal_checker.zig +61 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/manifest_checker.zig +76 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/message_bus.zig +110 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/network.zig +412 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/state_checker.zig +331 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/storage_checker.zig +458 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster.zig +1198 -0
- data/ext/tb_client/tigerbeetle/src/testing/exhaustigen.zig +128 -0
- data/ext/tb_client/tigerbeetle/src/testing/fixtures.zig +181 -0
- data/ext/tb_client/tigerbeetle/src/testing/fuzz.zig +144 -0
- data/ext/tb_client/tigerbeetle/src/testing/id.zig +97 -0
- data/ext/tb_client/tigerbeetle/src/testing/io.zig +317 -0
- data/ext/tb_client/tigerbeetle/src/testing/marks.zig +126 -0
- data/ext/tb_client/tigerbeetle/src/testing/packet_simulator.zig +533 -0
- data/ext/tb_client/tigerbeetle/src/testing/reply_sequence.zig +154 -0
- data/ext/tb_client/tigerbeetle/src/testing/state_machine.zig +389 -0
- data/ext/tb_client/tigerbeetle/src/testing/storage.zig +1247 -0
- data/ext/tb_client/tigerbeetle/src/testing/table.zig +249 -0
- data/ext/tb_client/tigerbeetle/src/testing/time.zig +98 -0
- data/ext/tb_client/tigerbeetle/src/testing/tmp_tigerbeetle.zig +212 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/constants.zig +26 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/faulty_network.zig +580 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/java_driver/ci.zig +39 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/logged_process.zig +214 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/rust_driver/ci.zig +34 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/supervisor.zig +766 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/workload.zig +543 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/zig_driver.zig +181 -0
- data/ext/tb_client/tigerbeetle/src/tidy.zig +1448 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_driver.zig +227 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_load.zig +1069 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/cli.zig +1422 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect.zig +1658 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect_integrity.zig +518 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/libtb_client.zig +36 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/main.zig +646 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle.zig +958 -0
- data/ext/tb_client/tigerbeetle/src/time.zig +236 -0
- data/ext/tb_client/tigerbeetle/src/trace/event.zig +745 -0
- data/ext/tb_client/tigerbeetle/src/trace/statsd.zig +462 -0
- data/ext/tb_client/tigerbeetle/src/trace.zig +556 -0
- data/ext/tb_client/tigerbeetle/src/unit_tests.zig +321 -0
- data/ext/tb_client/tigerbeetle/src/vopr.zig +1785 -0
- data/ext/tb_client/tigerbeetle/src/vortex.zig +101 -0
- data/ext/tb_client/tigerbeetle/src/vsr/checkpoint_trailer.zig +473 -0
- data/ext/tb_client/tigerbeetle/src/vsr/checksum.zig +208 -0
- data/ext/tb_client/tigerbeetle/src/vsr/checksum_benchmark.zig +43 -0
- data/ext/tb_client/tigerbeetle/src/vsr/client.zig +768 -0
- data/ext/tb_client/tigerbeetle/src/vsr/client_replies.zig +532 -0
- data/ext/tb_client/tigerbeetle/src/vsr/client_sessions.zig +338 -0
- data/ext/tb_client/tigerbeetle/src/vsr/clock.zig +1019 -0
- data/ext/tb_client/tigerbeetle/src/vsr/fault_detector.zig +279 -0
- data/ext/tb_client/tigerbeetle/src/vsr/free_set.zig +1381 -0
- data/ext/tb_client/tigerbeetle/src/vsr/free_set_fuzz.zig +315 -0
- data/ext/tb_client/tigerbeetle/src/vsr/grid.zig +1460 -0
- data/ext/tb_client/tigerbeetle/src/vsr/grid_blocks_missing.zig +757 -0
- data/ext/tb_client/tigerbeetle/src/vsr/grid_scrubber.zig +797 -0
- data/ext/tb_client/tigerbeetle/src/vsr/journal.zig +2586 -0
- data/ext/tb_client/tigerbeetle/src/vsr/marzullo.zig +308 -0
- data/ext/tb_client/tigerbeetle/src/vsr/message_header.zig +1777 -0
- data/ext/tb_client/tigerbeetle/src/vsr/multi_batch.zig +715 -0
- data/ext/tb_client/tigerbeetle/src/vsr/multi_batch_fuzz.zig +185 -0
- data/ext/tb_client/tigerbeetle/src/vsr/repair_budget.zig +333 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica.zig +12355 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica_format.zig +416 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica_reformat.zig +165 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica_test.zig +2910 -0
- data/ext/tb_client/tigerbeetle/src/vsr/routing.zig +1075 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock.zig +1603 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock_fuzz.zig +484 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums.zig +405 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +355 -0
- data/ext/tb_client/tigerbeetle/src/vsr/sync.zig +29 -0
- data/ext/tb_client/tigerbeetle/src/vsr.zig +1727 -0
- data/lib/tb_client/shared_lib.rb +12 -5
- data/lib/tigerbeetle/client.rb +1 -1
- data/lib/tigerbeetle/platforms.rb +9 -0
- data/lib/tigerbeetle/version.rb +2 -2
- data/tigerbeetle.gemspec +22 -5
- metadata +242 -3
- data/ext/tb_client/pkg.tar.gz +0 -0
|
@@ -0,0 +1,2195 @@
|
|
|
1
|
+
const builtin = @import("builtin");
|
|
2
|
+
const std = @import("std");
|
|
3
|
+
const stdx = @import("stdx");
|
|
4
|
+
const assert = std.debug.assert;
|
|
5
|
+
const maybe = stdx.maybe;
|
|
6
|
+
const os = std.os;
|
|
7
|
+
const posix = std.posix;
|
|
8
|
+
const constants = @import("constants.zig");
|
|
9
|
+
const IO = @import("io.zig").IO;
|
|
10
|
+
const Timeout = @import("./vsr.zig").Timeout;
|
|
11
|
+
|
|
12
|
+
const elf = std.elf;
|
|
13
|
+
|
|
14
|
+
// Re-export to make release code easier.
|
|
15
|
+
pub const checksum = @import("vsr/checksum.zig");
|
|
16
|
+
pub const multiversion_binary_size_max = constants.multiversion_binary_size_max;
|
|
17
|
+
pub const multiversion_binary_platform_size_max = constants.multiversion_binary_platform_size_max;
|
|
18
|
+
|
|
19
|
+
// Multiversion interface backed by three different implementations:
|
|
20
|
+
// - MultiversionOS implements the real dynamic re-execution logic,
|
|
21
|
+
// - single_version is used when multiversion is disabled (release list of count one),
|
|
22
|
+
// - VOPR simulation.
|
|
23
|
+
pub const Multiversion = struct {
|
|
24
|
+
context: *anyopaque,
|
|
25
|
+
vtable: *const VTable,
|
|
26
|
+
|
|
27
|
+
pub const VTable = struct {
|
|
28
|
+
releases_bundled: *const fn (context: *anyopaque) ReleaseList,
|
|
29
|
+
release_execute: *const fn (context: *anyopaque, release: Release) void,
|
|
30
|
+
tick: *const fn (context: *anyopaque) void,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/// A list of all versions of code that are available in the current binary.
|
|
34
|
+
/// Includes the current version, newer versions, and older versions.
|
|
35
|
+
/// Ordered from lowest/oldest to highest/newest.
|
|
36
|
+
/// Can be updated by multiversioning while running.
|
|
37
|
+
pub fn releases_bundled(multiversion: Multiversion) ReleaseList {
|
|
38
|
+
return multiversion.vtable.releases_bundled(multiversion.context);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// Replace the currently-running replica with the given release.
|
|
42
|
+
///
|
|
43
|
+
/// If called with a `release` that is *not* in `releases_bundled`, the replica should shut
|
|
44
|
+
/// down with a helpful error message to warn the operator that they must upgrade.
|
|
45
|
+
// NB: this is void for VOPR, but the actual implementation is noreturn.
|
|
46
|
+
pub fn release_execute(multiversion: Multiversion, release: Release) void {
|
|
47
|
+
multiversion.vtable.release_execute(multiversion.context, release);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
pub fn tick(multiversion: Multiversion) void {
|
|
51
|
+
multiversion.vtable.tick(multiversion.context);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Multiversion implementation that only has one release available.
|
|
55
|
+
pub fn single_release(comptime release: Release) Multiversion {
|
|
56
|
+
const vtable = struct {
|
|
57
|
+
fn releases_bundled(_: *anyopaque) ReleaseList {
|
|
58
|
+
var result: ReleaseList = .empty;
|
|
59
|
+
result.push(release);
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
fn release_execute(_: *anyopaque, release_next: Release) void {
|
|
63
|
+
assert(release_next.value != release.value);
|
|
64
|
+
@panic("multiversion unsupported");
|
|
65
|
+
}
|
|
66
|
+
fn tick(_: *anyopaque) void {}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return .{
|
|
70
|
+
.context = @constCast(&{}),
|
|
71
|
+
.vtable = &.{
|
|
72
|
+
.releases_bundled = vtable.releases_bundled,
|
|
73
|
+
.release_execute = vtable.release_execute,
|
|
74
|
+
.tick = vtable.tick,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/// In order to embed multiversion headers and bodies inside a universal binary, we repurpose some
|
|
81
|
+
/// old CPU Type IDs.
|
|
82
|
+
/// These are valid (in the MachO spec) but ancient (macOS has never run on anything other than
|
|
83
|
+
/// x86_64 / arm64) platforms. They were chosen so that it wouldn't be a random value, but also
|
|
84
|
+
/// wouldn't be something that could be realistically encountered.
|
|
85
|
+
pub const section_to_macho_cpu = enum(c_int) {
|
|
86
|
+
tb_mvb_aarch64 = 0x00000001, // VAX
|
|
87
|
+
tb_mvh_aarch64 = 0x00000002, // ROMP
|
|
88
|
+
tb_mvb_x86_64 = 0x00000004, // NS32032
|
|
89
|
+
tb_mvh_x86_64 = 0x00000005, // NS32332
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const log = std.log.scoped(.multiversioning);
|
|
93
|
+
|
|
94
|
+
/// Creates a virtual file backed by memory.
|
|
95
|
+
fn open_memory_file(name: [*:0]const u8) posix.fd_t {
|
|
96
|
+
const mfd_cloexec = 0x0001;
|
|
97
|
+
|
|
98
|
+
return @intCast(os.linux.memfd_create(name, mfd_cloexec));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// TODO(zig): std doesn't have execveat.
|
|
102
|
+
// Once that's available, this can be removed.
|
|
103
|
+
fn execveat(
|
|
104
|
+
dirfd: i32,
|
|
105
|
+
path: [*:0]const u8,
|
|
106
|
+
argv: [*:null]const ?[*:0]const u8,
|
|
107
|
+
envp: [*:null]const ?[*:0]const u8,
|
|
108
|
+
flags: i32,
|
|
109
|
+
) usize {
|
|
110
|
+
return os.linux.syscall5(
|
|
111
|
+
.execveat,
|
|
112
|
+
@as(usize, @bitCast(@as(isize, dirfd))),
|
|
113
|
+
@intFromPtr(path),
|
|
114
|
+
@intFromPtr(argv),
|
|
115
|
+
@intFromPtr(envp),
|
|
116
|
+
@as(usize, @bitCast(@as(isize, flags))),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/// A ReleaseList is ordered from lowest-to-highest.
|
|
121
|
+
pub const ReleaseList = struct {
|
|
122
|
+
buffer: [constants.vsr_releases_max]Release,
|
|
123
|
+
count: u16,
|
|
124
|
+
|
|
125
|
+
pub const empty: ReleaseList = .{
|
|
126
|
+
.buffer = undefined,
|
|
127
|
+
.count = 0,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
pub fn push(release_list: *ReleaseList, release: Release) void {
|
|
131
|
+
assert(release_list.count < constants.vsr_releases_max);
|
|
132
|
+
assert(release.value > 0);
|
|
133
|
+
if (release_list.count > 0) {
|
|
134
|
+
assert(release_list.last().value < release.value);
|
|
135
|
+
}
|
|
136
|
+
release_list.buffer[release_list.count] = release;
|
|
137
|
+
release_list.count += 1;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
pub fn slice(release_list: *const ReleaseList) []const Release {
|
|
141
|
+
return release_list.buffer[0..release_list.count];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
pub fn verify(release_list: *const ReleaseList) void {
|
|
145
|
+
assert(release_list.count > 0);
|
|
146
|
+
for (release_list.slice()) |a| assert(a.value > 0);
|
|
147
|
+
for (
|
|
148
|
+
release_list.slice()[0 .. release_list.count - 1],
|
|
149
|
+
release_list.slice()[1..],
|
|
150
|
+
) |a, b| {
|
|
151
|
+
assert(a.value < b.value); // Sorted and unique.
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
pub fn contains(release_list: *const ReleaseList, release: Release) bool {
|
|
156
|
+
for (release_list.slice()) |r| {
|
|
157
|
+
if (r.value == release.value) return true;
|
|
158
|
+
}
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
pub fn first(release_list: *const ReleaseList) Release {
|
|
163
|
+
assert(release_list.count > 0);
|
|
164
|
+
return release_list.slice()[0];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
pub fn last(release_list: *const ReleaseList) Release {
|
|
168
|
+
assert(release_list.count > 0);
|
|
169
|
+
return release_list.slice()[release_list.count - 1];
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
pub const Release = extern struct {
|
|
174
|
+
value: u32,
|
|
175
|
+
|
|
176
|
+
comptime {
|
|
177
|
+
assert(@sizeOf(Release) == 4);
|
|
178
|
+
assert(@sizeOf(Release) == @sizeOf(ReleaseTriple));
|
|
179
|
+
assert(stdx.no_padding(Release));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
pub const zero = Release.from(.{ .major = 0, .minor = 0, .patch = 0 });
|
|
183
|
+
// Minimum is used for all development builds, to distinguish them from production deployments.
|
|
184
|
+
pub const minimum = Release.from(.{ .major = 0, .minor = 0, .patch = 1 });
|
|
185
|
+
|
|
186
|
+
/// The 65535.x.x releases are reserved for cluster=0.
|
|
187
|
+
/// This way, when testing multiversion binaries (either manually or with the integration tests'
|
|
188
|
+
/// or Vortex's build) it isn't possible to use the test's multiversion build to upgrade a
|
|
189
|
+
/// production cluster to non-production code.
|
|
190
|
+
pub const development_major: u16 = std.math.maxInt(u16);
|
|
191
|
+
|
|
192
|
+
pub fn from(release_triple: ReleaseTriple) Release {
|
|
193
|
+
return std.mem.bytesAsValue(Release, std.mem.asBytes(&release_triple)).*;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
pub fn parse(string: []const u8) !Release {
|
|
197
|
+
return Release.from(try ReleaseTriple.parse(string));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
pub fn triple(release: *const Release) ReleaseTriple {
|
|
201
|
+
return std.mem.bytesAsValue(ReleaseTriple, std.mem.asBytes(release)).*;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/// Test-only release binary will only start when cluster=0.
|
|
205
|
+
/// This ensures that test/development multiversion binaries can be tested by upgrading from
|
|
206
|
+
/// actual production binaries, without risking accidentally upgrading a production cluster to a
|
|
207
|
+
/// test/development binary.
|
|
208
|
+
pub fn testing(release: *const Release) bool {
|
|
209
|
+
return release.triple().major == std.math.maxInt(u16);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
pub fn format(
|
|
213
|
+
release: Release,
|
|
214
|
+
comptime fmt: []const u8,
|
|
215
|
+
options: std.fmt.FormatOptions,
|
|
216
|
+
writer: anytype,
|
|
217
|
+
) !void {
|
|
218
|
+
_ = fmt;
|
|
219
|
+
_ = options;
|
|
220
|
+
const release_triple = release.triple();
|
|
221
|
+
return writer.print("{}.{}.{}", .{
|
|
222
|
+
release_triple.major,
|
|
223
|
+
release_triple.minor,
|
|
224
|
+
release_triple.patch,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
pub fn max(a: Release, b: Release) Release {
|
|
229
|
+
if (a.value > b.value) {
|
|
230
|
+
return a;
|
|
231
|
+
} else {
|
|
232
|
+
return b;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
pub fn less_than(_: void, a: Release, b: Release) bool {
|
|
237
|
+
return switch (std.math.order(a.value, b.value)) {
|
|
238
|
+
.lt => true,
|
|
239
|
+
.eq => false,
|
|
240
|
+
.gt => false,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
pub const ReleaseTriple = extern struct {
|
|
246
|
+
patch: u8,
|
|
247
|
+
minor: u8,
|
|
248
|
+
major: u16,
|
|
249
|
+
|
|
250
|
+
comptime {
|
|
251
|
+
assert(@sizeOf(ReleaseTriple) == 4);
|
|
252
|
+
assert(stdx.no_padding(ReleaseTriple));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
pub fn parse(string: []const u8) error{InvalidRelease}!ReleaseTriple {
|
|
256
|
+
var parts = std.mem.splitScalar(u8, string, '.');
|
|
257
|
+
const major = parts.first();
|
|
258
|
+
const minor = parts.next() orelse return error.InvalidRelease;
|
|
259
|
+
const patch = parts.next() orelse return error.InvalidRelease;
|
|
260
|
+
if (parts.next() != null) return error.InvalidRelease;
|
|
261
|
+
return .{
|
|
262
|
+
.major = std.fmt.parseUnsigned(u16, major, 10) catch return error.InvalidRelease,
|
|
263
|
+
.minor = std.fmt.parseUnsigned(u8, minor, 10) catch return error.InvalidRelease,
|
|
264
|
+
.patch = std.fmt.parseUnsigned(u8, patch, 10) catch return error.InvalidRelease,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
test "ReleaseTriple.parse" {
|
|
270
|
+
const tests = [_]struct {
|
|
271
|
+
string: []const u8,
|
|
272
|
+
result: error{InvalidRelease}!ReleaseTriple,
|
|
273
|
+
}{
|
|
274
|
+
// Valid:
|
|
275
|
+
.{ .string = "0.0.1", .result = .{ .major = 0, .minor = 0, .patch = 1 } },
|
|
276
|
+
.{ .string = "0.1.0", .result = .{ .major = 0, .minor = 1, .patch = 0 } },
|
|
277
|
+
.{ .string = "1.0.0", .result = .{ .major = 1, .minor = 0, .patch = 0 } },
|
|
278
|
+
|
|
279
|
+
// Invalid characters:
|
|
280
|
+
.{ .string = "v0.0.1", .result = error.InvalidRelease },
|
|
281
|
+
.{ .string = "0.0.1v", .result = error.InvalidRelease },
|
|
282
|
+
// Invalid separators:
|
|
283
|
+
.{ .string = "0.0.0.1", .result = error.InvalidRelease },
|
|
284
|
+
.{ .string = "0..0.1", .result = error.InvalidRelease },
|
|
285
|
+
// Overflow (and near-overflow):
|
|
286
|
+
.{ .string = "0.0.255", .result = .{ .major = 0, .minor = 0, .patch = 255 } },
|
|
287
|
+
.{ .string = "0.0.256", .result = error.InvalidRelease },
|
|
288
|
+
.{ .string = "0.255.0", .result = .{ .major = 0, .minor = 255, .patch = 0 } },
|
|
289
|
+
.{ .string = "0.256.0", .result = error.InvalidRelease },
|
|
290
|
+
.{ .string = "65535.0.0", .result = .{ .major = 65535, .minor = 0, .patch = 0 } },
|
|
291
|
+
.{ .string = "65536.0.0", .result = error.InvalidRelease },
|
|
292
|
+
};
|
|
293
|
+
for (tests) |t| {
|
|
294
|
+
try std.testing.expectEqualDeep(ReleaseTriple.parse(t.string), t.result);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
pub const MultiversionHeader = extern struct {
|
|
299
|
+
pub const Flags = packed struct {
|
|
300
|
+
/// Normally release upgrades are allowed to skip to the latest. If a corresponding release
|
|
301
|
+
/// is set to true here, it must be visited on the way to the newest release.
|
|
302
|
+
visit: bool,
|
|
303
|
+
|
|
304
|
+
/// If this binary has debug info attached.
|
|
305
|
+
debug: bool,
|
|
306
|
+
|
|
307
|
+
padding: u6 = 0,
|
|
308
|
+
|
|
309
|
+
comptime {
|
|
310
|
+
assert(@sizeOf(Flags) == 1);
|
|
311
|
+
assert(@bitSizeOf(Flags) == @sizeOf(Flags) * 8);
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// When slicing into the binary:
|
|
316
|
+
// checksum(section[past_offset..past_offset+past_size]) == past_checksum.
|
|
317
|
+
// This is then validated when the binary is written to a memfd or similar.
|
|
318
|
+
// TODO: Might be nicer as an AoS? It's control plane state.
|
|
319
|
+
pub const PastReleases = extern struct {
|
|
320
|
+
/// The maximum number of past releases is one less, because the current release is
|
|
321
|
+
/// stored outside PastReleases.
|
|
322
|
+
const past_releases_max = constants.vsr_releases_max - 1;
|
|
323
|
+
|
|
324
|
+
count: u32 = 0,
|
|
325
|
+
|
|
326
|
+
releases: [past_releases_max]u32 = @splat(0),
|
|
327
|
+
checksums: [past_releases_max]u128 = @splat(0),
|
|
328
|
+
|
|
329
|
+
/// Offsets are relative to the start of the body (`.tb_mvb`) offset.
|
|
330
|
+
offsets: [past_releases_max]u32 = @splat(0),
|
|
331
|
+
|
|
332
|
+
sizes: [past_releases_max]u32 = @splat(0),
|
|
333
|
+
|
|
334
|
+
flags: [past_releases_max]Flags = @splat(.{ .visit = false, .debug = false }),
|
|
335
|
+
flags_padding: [1]u8 = @splat(0),
|
|
336
|
+
|
|
337
|
+
// Extra metadata. Not used by any current upgrade processes directly, but useful to know:
|
|
338
|
+
git_commits: [past_releases_max][20]u8 = @splat(@splat(0)),
|
|
339
|
+
release_client_mins: [past_releases_max]u32 = @splat(0),
|
|
340
|
+
|
|
341
|
+
pub fn add(self: *PastReleases, next: struct {
|
|
342
|
+
release: u32,
|
|
343
|
+
checksum: u128,
|
|
344
|
+
size: u32,
|
|
345
|
+
flags: Flags,
|
|
346
|
+
git_commit: [20]u8,
|
|
347
|
+
release_client_min: u32,
|
|
348
|
+
}) void {
|
|
349
|
+
assert(next.release != 0);
|
|
350
|
+
assert(next.checksum != 0);
|
|
351
|
+
assert(next.size != 0);
|
|
352
|
+
assert(next.release_client_min != 0);
|
|
353
|
+
|
|
354
|
+
assert(self.count < past_releases_max);
|
|
355
|
+
const index = self.count;
|
|
356
|
+
|
|
357
|
+
if (index > 0) {
|
|
358
|
+
assert(self.releases[index - 1] < next.release);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const offset = if (index > 0)
|
|
362
|
+
self.offsets[index - 1] + self.sizes[index - 1]
|
|
363
|
+
else
|
|
364
|
+
0;
|
|
365
|
+
|
|
366
|
+
self.releases[index] = next.release;
|
|
367
|
+
self.checksums[index] = next.checksum;
|
|
368
|
+
self.offsets[index] = offset;
|
|
369
|
+
self.sizes[index] = next.size;
|
|
370
|
+
self.flags[index] = next.flags;
|
|
371
|
+
self.git_commits[index] = next.git_commit;
|
|
372
|
+
self.release_client_mins[index] = next.release_client_min;
|
|
373
|
+
self.count += 1;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
pub fn verify(self: *const PastReleases) !void {
|
|
377
|
+
if (self.count > past_releases_max) return error.InvalidPastReleases;
|
|
378
|
+
if (self.count == 0) return error.InvalidPastReleases;
|
|
379
|
+
|
|
380
|
+
if (!stdx.zeroed(std.mem.sliceAsBytes(self.releases[self.count..])) or
|
|
381
|
+
!stdx.zeroed(std.mem.sliceAsBytes(self.checksums[self.count..])) or
|
|
382
|
+
!stdx.zeroed(std.mem.sliceAsBytes(self.offsets[self.count..])) or
|
|
383
|
+
!stdx.zeroed(std.mem.sliceAsBytes(self.sizes[self.count..])) or
|
|
384
|
+
!stdx.zeroed(std.mem.sliceAsBytes(self.flags[self.count..])) or
|
|
385
|
+
!stdx.zeroed(&self.flags_padding) or
|
|
386
|
+
!stdx.zeroed(std.mem.sliceAsBytes(self.git_commits[self.count..])) or
|
|
387
|
+
!stdx.zeroed(std.mem.sliceAsBytes(self.release_client_mins[self.count..])))
|
|
388
|
+
{
|
|
389
|
+
return error.InvalidPastReleases;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const releases = self.releases[0..self.count];
|
|
393
|
+
const offsets = self.offsets[0..self.count];
|
|
394
|
+
const sizes = self.sizes[0..self.count];
|
|
395
|
+
const flags = self.flags[0..self.count];
|
|
396
|
+
const git_commits = self.git_commits[0..self.count];
|
|
397
|
+
const release_client_mins = self.release_client_mins[0..self.count];
|
|
398
|
+
|
|
399
|
+
for (releases) |v| if (v == 0) return error.InvalidPastReleases;
|
|
400
|
+
if (!std.sort.isSorted(u32, releases, {}, std.sort.asc(u32))) {
|
|
401
|
+
return error.InvalidPastReleases;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (offsets[0] != 0) return error.InvalidPastReleases;
|
|
405
|
+
for (offsets[1..], 1..) |offset, i| {
|
|
406
|
+
const calculated_offset = blk: {
|
|
407
|
+
var calculated_offset: u32 = 0;
|
|
408
|
+
for (sizes[0..i]) |size| {
|
|
409
|
+
calculated_offset += size;
|
|
410
|
+
}
|
|
411
|
+
break :blk calculated_offset;
|
|
412
|
+
};
|
|
413
|
+
if (offset == 0) return error.InvalidPastReleases;
|
|
414
|
+
if (offset != calculated_offset) return error.InvalidPastReleases;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
for (sizes) |s| if (s == 0) return error.InvalidPastReleases;
|
|
418
|
+
for (flags) |f| if (f.padding != 0) return error.InvalidPastReleases;
|
|
419
|
+
for (git_commits) |g| if (stdx.zeroed(&g)) return error.InvalidPastReleases;
|
|
420
|
+
|
|
421
|
+
for (release_client_mins) |v| if (v == 0) return error.InvalidPastReleases;
|
|
422
|
+
if (!std.sort.isSorted(u32, release_client_mins, {}, std.sort.asc(u32))) {
|
|
423
|
+
return error.InvalidPastReleases;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/// Used by the build process to verify that the inner checksums are correct. Skipped during
|
|
428
|
+
/// runtime, as the outer checksum includes them all. This same method can't be implemented
|
|
429
|
+
/// for current_release, as that would require `objcopy` at runtime to split the pieces out.
|
|
430
|
+
pub fn verify_checksums(self: *const PastReleases, body: []const u8) !void {
|
|
431
|
+
for (
|
|
432
|
+
self.checksums[0..self.count],
|
|
433
|
+
self.offsets[0..self.count],
|
|
434
|
+
self.sizes[0..self.count],
|
|
435
|
+
) |checksum_expected, offset, size| {
|
|
436
|
+
const checksum_calculated = checksum.checksum(body[offset..][0..size]);
|
|
437
|
+
if (checksum_calculated != checksum_expected) {
|
|
438
|
+
return error.PastReleaseChecksumMismatch;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
/// Covers MultiversionHeader[@sizeOf(u128)..].
|
|
445
|
+
checksum_header: u128 = undefined,
|
|
446
|
+
|
|
447
|
+
/// The AEGIS128L checksum of the binary, if the header (`.tb_mvh`) section were zeroed out.
|
|
448
|
+
/// Used to validate that the binary itself is not corrupt. Putting this in requires a bit
|
|
449
|
+
/// of trickery:
|
|
450
|
+
/// * inject a zero `.tb_mvh` section of the correct size,
|
|
451
|
+
/// * compute the hash,
|
|
452
|
+
/// * update the section with the correct data.
|
|
453
|
+
/// Used to ensure we don't try and exec into a corrupt binary.
|
|
454
|
+
checksum_binary_without_header: u128 = 0,
|
|
455
|
+
|
|
456
|
+
/// The AEGIS128L checksum of the direct output of `zig build`, for the current_release, before
|
|
457
|
+
/// any objcopy or build magic has been performed.
|
|
458
|
+
/// Used when extracting the latest binary from a now-past release during the build process.
|
|
459
|
+
/// Instead of having to rebuild from source, objcopy is used to remove the multiversion
|
|
460
|
+
/// sections, which is then compared to this checksum to ensure the output is identical.
|
|
461
|
+
current_checksum: u128,
|
|
462
|
+
|
|
463
|
+
/// Track the schema of the header. It's possible to completely change the schema - past this
|
|
464
|
+
/// point - while maintaining an upgrade path by having a transitional release:
|
|
465
|
+
/// * 0.15.4 uses schema version 1,
|
|
466
|
+
/// * 0.15.5 uses schema version 1/2,
|
|
467
|
+
/// * 0.15.6 uses schema version 2.
|
|
468
|
+
///
|
|
469
|
+
/// Then, it's possible to have 2 multiversion releases, one with {0.15.4, 0.15.5} and one with
|
|
470
|
+
/// {0.15.5, 0.15.6}, that allow an upgrade path with 2 steps.
|
|
471
|
+
schema_version: u32 = 1,
|
|
472
|
+
vsr_releases_max: u32 = constants.vsr_releases_max,
|
|
473
|
+
|
|
474
|
+
/// The current release is executed differently to past releases embedded in the body, so store
|
|
475
|
+
/// it separately. See exec_current vs exec_release.
|
|
476
|
+
current_release: u32,
|
|
477
|
+
|
|
478
|
+
current_flags: Flags,
|
|
479
|
+
current_flags_padding: [3]u8 = @splat(0),
|
|
480
|
+
|
|
481
|
+
past: PastReleases = .{},
|
|
482
|
+
past_padding: [16]u8 = @splat(0),
|
|
483
|
+
|
|
484
|
+
current_git_commit: [20]u8,
|
|
485
|
+
current_release_client_min: u32,
|
|
486
|
+
|
|
487
|
+
/// Reserved space for future use. This is special: unlike the rest of the *_padding fields,
|
|
488
|
+
/// which are required to be zeroed, this is not. This allows adding whole new fields in a
|
|
489
|
+
/// backwards compatible way, while preventing the temptation of changing the meaning of
|
|
490
|
+
/// existing fields without bumping the schema version entirely.
|
|
491
|
+
reserved: [4744]u8 = @splat(0),
|
|
492
|
+
|
|
493
|
+
/// Parses an instance from a slice of bytes and validates its checksum. Returns a copy.
|
|
494
|
+
pub fn init_from_bytes(bytes: *const [@sizeOf(MultiversionHeader)]u8) !MultiversionHeader {
|
|
495
|
+
const self = std.mem.bytesAsValue(MultiversionHeader, bytes).*;
|
|
496
|
+
try self.verify();
|
|
497
|
+
|
|
498
|
+
return self;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
pub fn verify(self: *const MultiversionHeader) !void {
|
|
502
|
+
const checksum_calculated = self.calculate_header_checksum();
|
|
503
|
+
|
|
504
|
+
if (checksum_calculated != self.checksum_header) return error.ChecksumMismatch;
|
|
505
|
+
if (self.schema_version != 1) return error.InvalidSchemaVersion;
|
|
506
|
+
if (self.vsr_releases_max != constants.vsr_releases_max) return error.InvalidVSRReleaseMax;
|
|
507
|
+
if (self.current_flags.padding != 0) return error.InvalidCurrentFlags;
|
|
508
|
+
if (!stdx.zeroed(&self.current_flags_padding)) return error.InvalidCurrentFlags;
|
|
509
|
+
if (!self.current_flags.visit) return error.InvalidCurrentFlags;
|
|
510
|
+
if (self.current_release == 0) return error.InvalidCurrentRelease;
|
|
511
|
+
|
|
512
|
+
// current_git_commit and current_release_client_min were added after 0.15.4.
|
|
513
|
+
if (self.current_release > (try Release.parse("0.15.4")).value) {
|
|
514
|
+
if (stdx.zeroed(&self.current_git_commit)) return error.InvalidCurrentRelease;
|
|
515
|
+
if (self.current_release_client_min == 0) return error.InvalidCurrentRelease;
|
|
516
|
+
} else {
|
|
517
|
+
if (!stdx.zeroed(&self.current_git_commit)) return error.InvalidCurrentRelease;
|
|
518
|
+
if (self.current_release_client_min != 0) return error.InvalidCurrentRelease;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
stdx.maybe(stdx.zeroed(&self.reserved));
|
|
522
|
+
|
|
523
|
+
try self.past.verify();
|
|
524
|
+
if (!stdx.zeroed(&self.past_padding)) return error.InvalidPastPadding;
|
|
525
|
+
|
|
526
|
+
const past_release_newest = self.past.releases[self.past.count - 1];
|
|
527
|
+
if (past_release_newest >= self.current_release) return error.PastReleaseNewerThanCurrent;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
pub fn calculate_header_checksum(self: *const MultiversionHeader) u128 {
|
|
531
|
+
// The checksum for the rest of the file must have been set by this point.
|
|
532
|
+
assert(self.checksum_binary_without_header != 0);
|
|
533
|
+
|
|
534
|
+
comptime assert(std.meta.fieldIndex(MultiversionHeader, "checksum_header") == 0);
|
|
535
|
+
|
|
536
|
+
const checksum_size = @sizeOf(@TypeOf(self.checksum_header));
|
|
537
|
+
comptime assert(checksum_size == @sizeOf(u128));
|
|
538
|
+
|
|
539
|
+
return checksum.checksum(std.mem.asBytes(self)[@sizeOf(u128)..]);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/// Given a release, return all the releases:
|
|
543
|
+
/// * Older than the specified from_release,
|
|
544
|
+
/// * Newer than the current from_release, up to and including a newer one with the `visits`
|
|
545
|
+
/// flag set.
|
|
546
|
+
pub fn advertisable(self: *const MultiversionHeader, from_release: Release) ReleaseList {
|
|
547
|
+
var release_list: ReleaseList = .empty;
|
|
548
|
+
|
|
549
|
+
for (0..self.past.count) |i| {
|
|
550
|
+
release_list.push(Release{ .value = self.past.releases[i] });
|
|
551
|
+
if (from_release.value < self.past.releases[i]) {
|
|
552
|
+
if (self.past.flags[i].visit) {
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
} else {
|
|
557
|
+
release_list.push(Release{ .value = self.current_release });
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// These asserts should be impossible to reach barring a bug; they're checked in verify()
|
|
561
|
+
// so there shouldn't be a way for a corrupt / malformed binary to get this far.
|
|
562
|
+
release_list.verify();
|
|
563
|
+
|
|
564
|
+
return release_list;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
comptime {
|
|
568
|
+
// Changing these will affect the structure stored on disk, which has implications for past
|
|
569
|
+
// clients trying to read!
|
|
570
|
+
assert(constants.vsr_releases_max == 64);
|
|
571
|
+
assert(PastReleases.past_releases_max == 63);
|
|
572
|
+
assert(@sizeOf(MultiversionHeader) == 8192);
|
|
573
|
+
assert(@offsetOf(MultiversionHeader, "checksum_header") == 0);
|
|
574
|
+
assert(@offsetOf(MultiversionHeader, "schema_version") == 48);
|
|
575
|
+
assert(stdx.no_padding(PastReleases));
|
|
576
|
+
assert(stdx.no_padding(MultiversionHeader));
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
test "MultiversionHeader.advertisable" {
|
|
581
|
+
const tests: []const struct {
|
|
582
|
+
releases: []const struct { u32, MultiversionHeader.Flags },
|
|
583
|
+
current: u32,
|
|
584
|
+
from: u32,
|
|
585
|
+
expected: []const u32,
|
|
586
|
+
} = &.{
|
|
587
|
+
.{ .releases = &.{
|
|
588
|
+
.{ 1, .{ .visit = false, .debug = false } },
|
|
589
|
+
.{ 2, .{ .visit = false, .debug = false } },
|
|
590
|
+
.{ 3, .{ .visit = false, .debug = false } },
|
|
591
|
+
}, .current = 4, .from = 2, .expected = &.{ 1, 2, 3, 4 } },
|
|
592
|
+
.{ .releases = &.{
|
|
593
|
+
.{ 1, .{ .visit = false, .debug = false } },
|
|
594
|
+
.{ 2, .{ .visit = false, .debug = false } },
|
|
595
|
+
.{ 3, .{ .visit = true, .debug = false } },
|
|
596
|
+
}, .current = 4, .from = 2, .expected = &.{ 1, 2, 3 } },
|
|
597
|
+
.{ .releases = &.{
|
|
598
|
+
.{ 1, .{ .visit = false, .debug = false } },
|
|
599
|
+
.{ 2, .{ .visit = false, .debug = false } },
|
|
600
|
+
.{ 3, .{ .visit = true, .debug = false } },
|
|
601
|
+
.{ 4, .{ .visit = false, .debug = false } },
|
|
602
|
+
}, .current = 5, .from = 2, .expected = &.{ 1, 2, 3 } },
|
|
603
|
+
.{ .releases = &.{
|
|
604
|
+
.{ 1, .{ .visit = true, .debug = false } },
|
|
605
|
+
.{ 2, .{ .visit = false, .debug = false } },
|
|
606
|
+
.{ 3, .{ .visit = true, .debug = false } },
|
|
607
|
+
.{ 4, .{ .visit = true, .debug = false } },
|
|
608
|
+
}, .current = 5, .from = 5, .expected = &.{ 1, 2, 3, 4, 5 } },
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
for (tests) |t| {
|
|
612
|
+
var past_releases: MultiversionHeader.PastReleases = .{};
|
|
613
|
+
for (t.releases) |release_flags| {
|
|
614
|
+
past_releases.add(.{
|
|
615
|
+
.release = release_flags[0],
|
|
616
|
+
.checksum = 1,
|
|
617
|
+
.size = 1,
|
|
618
|
+
.flags = release_flags[1],
|
|
619
|
+
.git_commit = "00000000000000000000".*,
|
|
620
|
+
.release_client_min = 1,
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
try past_releases.verify();
|
|
624
|
+
|
|
625
|
+
var header = MultiversionHeader{
|
|
626
|
+
.past = past_releases,
|
|
627
|
+
.current_release = t.current,
|
|
628
|
+
.current_checksum = 0,
|
|
629
|
+
.current_flags = .{ .visit = true, .debug = false },
|
|
630
|
+
.checksum_binary_without_header = 1,
|
|
631
|
+
.current_git_commit = @splat(0),
|
|
632
|
+
.current_release_client_min = 0,
|
|
633
|
+
};
|
|
634
|
+
header.checksum_header = header.calculate_header_checksum();
|
|
635
|
+
|
|
636
|
+
try header.verify();
|
|
637
|
+
|
|
638
|
+
const advertisable = header.advertisable(Release{ .value = t.from });
|
|
639
|
+
var expected: ReleaseList = .empty;
|
|
640
|
+
for (t.expected) |release| {
|
|
641
|
+
expected.push(Release{ .value = release });
|
|
642
|
+
}
|
|
643
|
+
try std.testing.expectEqualSlices(
|
|
644
|
+
Release,
|
|
645
|
+
expected.slice(),
|
|
646
|
+
advertisable.slice(),
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const multiversion_uuid = "tigerbeetle-multiversion-1768a738-ef69-4605-8b5c-c6e63580e345";
|
|
652
|
+
|
|
653
|
+
pub const MultiversionOS = struct {
|
|
654
|
+
const ArgsEnvp = if (builtin.target.os.tag == .windows) struct {
|
|
655
|
+
target_path_w: [:0]const u16,
|
|
656
|
+
exe_path_w: [:0]const u16,
|
|
657
|
+
} else struct {
|
|
658
|
+
// Coerces to [*:null]const ?[*:0]const u8 but lets us keep information to free the memory
|
|
659
|
+
// later.
|
|
660
|
+
args: [:null]?[*:0]const u8,
|
|
661
|
+
envp: [*:null]const ?[*:0]const u8,
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
const ExePathFormat = enum { elf, pe, macho, detect };
|
|
665
|
+
|
|
666
|
+
io: *IO,
|
|
667
|
+
|
|
668
|
+
exe_path: [:0]const u8,
|
|
669
|
+
exe_path_format: ExePathFormat,
|
|
670
|
+
args_envp: ArgsEnvp,
|
|
671
|
+
|
|
672
|
+
source_buffer: []align(8) u8,
|
|
673
|
+
source_fd: ?posix.fd_t = null,
|
|
674
|
+
source_offset: ?u64 = null,
|
|
675
|
+
|
|
676
|
+
target_fd: posix.fd_t,
|
|
677
|
+
target_path: [:0]const u8,
|
|
678
|
+
target_body_offset: ?u32 = null,
|
|
679
|
+
target_body_size: ?u32 = null,
|
|
680
|
+
target_header: ?MultiversionHeader = null,
|
|
681
|
+
/// This list only contains the advertisable releases, which are a subset of the actual
|
|
682
|
+
/// releases included in the multiversion binary. See MultiversionHeader.advertisable().
|
|
683
|
+
releases_bundled: ReleaseList = .empty,
|
|
684
|
+
|
|
685
|
+
completion: IO.Completion = undefined,
|
|
686
|
+
|
|
687
|
+
timeout: Timeout,
|
|
688
|
+
timeout_statx: os.linux.Statx = undefined,
|
|
689
|
+
timeout_statx_previous: union(enum) { none, previous: os.linux.Statx, err } = .none,
|
|
690
|
+
|
|
691
|
+
stage: union(enum) {
|
|
692
|
+
init,
|
|
693
|
+
|
|
694
|
+
source_stat,
|
|
695
|
+
source_open,
|
|
696
|
+
source_read,
|
|
697
|
+
|
|
698
|
+
target_update,
|
|
699
|
+
|
|
700
|
+
ready,
|
|
701
|
+
err: anyerror,
|
|
702
|
+
} = .init,
|
|
703
|
+
|
|
704
|
+
pub fn init(
|
|
705
|
+
allocator: std.mem.Allocator,
|
|
706
|
+
io: *IO,
|
|
707
|
+
exe_path: [:0]const u8,
|
|
708
|
+
exe_path_format: enum { detect, native },
|
|
709
|
+
) !MultiversionOS {
|
|
710
|
+
assert(std.fs.path.isAbsolute(exe_path));
|
|
711
|
+
|
|
712
|
+
const multiversion_binary_size_max_by_format = switch (exe_path_format) {
|
|
713
|
+
.detect => constants.multiversion_binary_size_max,
|
|
714
|
+
.native => constants.multiversion_binary_platform_size_max(.{
|
|
715
|
+
.macos = builtin.target.os.tag == .macos,
|
|
716
|
+
.debug = builtin.mode != .ReleaseSafe,
|
|
717
|
+
}),
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
// To keep the invariant that whatever has been advertised can be executed, while allowing
|
|
721
|
+
// new binaries to be put in place, double buffering is used:
|
|
722
|
+
// * source_buffer is where the in-progress data lives,
|
|
723
|
+
// * target_fd is where the advertised data lives.
|
|
724
|
+
// This does impact memory usage.
|
|
725
|
+
const source_buffer = try allocator.alignedAlloc(
|
|
726
|
+
u8,
|
|
727
|
+
8,
|
|
728
|
+
multiversion_binary_size_max_by_format,
|
|
729
|
+
);
|
|
730
|
+
errdefer allocator.free(source_buffer);
|
|
731
|
+
|
|
732
|
+
const nonce = stdx.unique_u128();
|
|
733
|
+
|
|
734
|
+
const target_path: [:0]const u8 = switch (builtin.target.os.tag) {
|
|
735
|
+
.linux => try allocator.dupeZ(u8, multiversion_uuid),
|
|
736
|
+
.macos, .windows => blk: {
|
|
737
|
+
const suffix = if (builtin.target.os.tag == .windows) ".exe" else "";
|
|
738
|
+
const temporary_directory = try system_temporary_directory(allocator);
|
|
739
|
+
defer allocator.free(temporary_directory);
|
|
740
|
+
|
|
741
|
+
const filename = try std.fmt.allocPrint(allocator, "{s}-{}" ++ suffix, .{
|
|
742
|
+
multiversion_uuid,
|
|
743
|
+
nonce,
|
|
744
|
+
});
|
|
745
|
+
defer allocator.free(filename);
|
|
746
|
+
|
|
747
|
+
break :blk try std.fs.path.joinZ(allocator, &.{ temporary_directory, filename });
|
|
748
|
+
},
|
|
749
|
+
else => @panic("unsupported platform"),
|
|
750
|
+
};
|
|
751
|
+
errdefer allocator.free(target_path);
|
|
752
|
+
|
|
753
|
+
// Only Linux has a nice API for executing from an in-memory file. For macOS and Windows,
|
|
754
|
+
// a standard named temporary file will be used instead.
|
|
755
|
+
const target_fd: posix.fd_t = switch (builtin.target.os.tag) {
|
|
756
|
+
.linux => blk: {
|
|
757
|
+
const fd = open_memory_file(target_path);
|
|
758
|
+
errdefer posix.close(fd);
|
|
759
|
+
|
|
760
|
+
try posix.ftruncate(fd, multiversion_binary_size_max_by_format);
|
|
761
|
+
|
|
762
|
+
break :blk fd;
|
|
763
|
+
},
|
|
764
|
+
|
|
765
|
+
.macos, .windows => blk: {
|
|
766
|
+
const mode = if (builtin.target.os.tag == .macos) 0o777 else 0;
|
|
767
|
+
const file = std.fs.createFileAbsolute(
|
|
768
|
+
target_path,
|
|
769
|
+
.{ .read = true, .truncate = true, .mode = mode },
|
|
770
|
+
) catch |e| std.debug.panic(
|
|
771
|
+
"error in target_fd open: {}",
|
|
772
|
+
.{e},
|
|
773
|
+
);
|
|
774
|
+
try file.setEndPos(multiversion_binary_size_max);
|
|
775
|
+
|
|
776
|
+
break :blk file.handle;
|
|
777
|
+
},
|
|
778
|
+
|
|
779
|
+
else => @panic("unsupported platform"),
|
|
780
|
+
};
|
|
781
|
+
errdefer posix.close(target_fd);
|
|
782
|
+
|
|
783
|
+
const args_envp: ArgsEnvp = switch (builtin.target.os.tag) {
|
|
784
|
+
.linux, .macos => blk: {
|
|
785
|
+
// We can pass through our env as-is to exec. We have to manipulate the types
|
|
786
|
+
// here somewhat: they're cast in start.zig and we can't access `argc_argv_ptr`
|
|
787
|
+
// directly. process.zig does the same trick in execve().
|
|
788
|
+
//
|
|
789
|
+
// For args, modify them so that argv[0] is exe_path. This allows our memfd executed
|
|
790
|
+
// binary to find its way back to the real file on disk.
|
|
791
|
+
const args = try allocator.allocSentinel(?[*:0]const u8, os.argv.len, null);
|
|
792
|
+
errdefer allocator.free(args);
|
|
793
|
+
|
|
794
|
+
args[0] = try allocator.dupeZ(u8, exe_path);
|
|
795
|
+
errdefer allocator.free(args[0]);
|
|
796
|
+
|
|
797
|
+
for (1..os.argv.len) |i| args[i] = os.argv[i];
|
|
798
|
+
|
|
799
|
+
break :blk .{
|
|
800
|
+
.args = args,
|
|
801
|
+
.envp = @as([*:null]const ?[*:0]const u8, @ptrCast(os.environ.ptr)),
|
|
802
|
+
};
|
|
803
|
+
},
|
|
804
|
+
|
|
805
|
+
.windows => .{
|
|
806
|
+
.target_path_w = try std.unicode.wtf8ToWtf16LeAllocZ(allocator, target_path),
|
|
807
|
+
.exe_path_w = try std.unicode.wtf8ToWtf16LeAllocZ(allocator, exe_path),
|
|
808
|
+
},
|
|
809
|
+
|
|
810
|
+
else => comptime unreachable,
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
return .{
|
|
814
|
+
.io = io,
|
|
815
|
+
|
|
816
|
+
.exe_path = exe_path,
|
|
817
|
+
.exe_path_format = switch (exe_path_format) {
|
|
818
|
+
.native => switch (builtin.target.os.tag) {
|
|
819
|
+
.linux => .elf,
|
|
820
|
+
.windows => .pe,
|
|
821
|
+
.macos => .macho,
|
|
822
|
+
else => comptime unreachable,
|
|
823
|
+
},
|
|
824
|
+
.detect => .detect,
|
|
825
|
+
},
|
|
826
|
+
|
|
827
|
+
.args_envp = args_envp,
|
|
828
|
+
|
|
829
|
+
.source_buffer = source_buffer,
|
|
830
|
+
|
|
831
|
+
.target_fd = target_fd,
|
|
832
|
+
.target_path = target_path,
|
|
833
|
+
|
|
834
|
+
.timeout = Timeout{
|
|
835
|
+
.name = "multiversioning_timeout",
|
|
836
|
+
.id = 0, // id for logging is set by timeout_enable after opening the superblock.
|
|
837
|
+
.after = constants.multiversion_poll_interval_ms / constants.tick_ms,
|
|
838
|
+
},
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
pub fn deinit(self: *MultiversionOS, allocator: std.mem.Allocator) void {
|
|
843
|
+
posix.close(self.target_fd);
|
|
844
|
+
self.target_fd = IO.INVALID_FILE;
|
|
845
|
+
allocator.free(self.target_path);
|
|
846
|
+
|
|
847
|
+
allocator.free(self.source_buffer);
|
|
848
|
+
|
|
849
|
+
if (builtin.target.os.tag == .windows) {
|
|
850
|
+
allocator.free(self.args_envp.target_path_w);
|
|
851
|
+
allocator.free(self.args_envp.exe_path_w);
|
|
852
|
+
} else {
|
|
853
|
+
allocator.free(std.mem.span(self.args_envp.args[0].?));
|
|
854
|
+
allocator.free(self.args_envp.args);
|
|
855
|
+
}
|
|
856
|
+
self.* = undefined;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
pub fn multiversion(self: *MultiversionOS) Multiversion {
|
|
860
|
+
return .{ .context = self, .vtable = &.{
|
|
861
|
+
.releases_bundled = vtable_releases_bundled,
|
|
862
|
+
.release_execute = vtable_release_execute,
|
|
863
|
+
.tick = vtable_tick,
|
|
864
|
+
} };
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
fn vtable_releases_bundled(context: *anyopaque) ReleaseList {
|
|
868
|
+
const self: *const MultiversionOS = @ptrCast(@alignCast(context));
|
|
869
|
+
return self.releases_bundled;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
fn vtable_release_execute(context: *anyopaque, release: Release) void {
|
|
873
|
+
const self: *MultiversionOS = @ptrCast(@alignCast(context));
|
|
874
|
+
self.replica_release_execute(release);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
fn vtable_tick(context: *anyopaque) void {
|
|
878
|
+
const self: *MultiversionOS = @ptrCast(@alignCast(context));
|
|
879
|
+
self.tick();
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
pub fn open_sync(self: *MultiversionOS) !void {
|
|
883
|
+
assert(self.stage == .init);
|
|
884
|
+
assert(!self.timeout.ticking);
|
|
885
|
+
|
|
886
|
+
if (comptime builtin.target.os.tag == .linux) {
|
|
887
|
+
self.binary_statx();
|
|
888
|
+
} else {
|
|
889
|
+
self.binary_open();
|
|
890
|
+
}
|
|
891
|
+
assert(self.stage != .init);
|
|
892
|
+
|
|
893
|
+
while (self.stage != .ready and self.stage != .err) {
|
|
894
|
+
self.io.run() catch |e| {
|
|
895
|
+
assert(self.stage != .ready);
|
|
896
|
+
self.stage = .{ .err = e };
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
if (self.stage == .err) {
|
|
901
|
+
// If there's been an error starting up multiversioning, don't disable it, but
|
|
902
|
+
// advertise only the current version in memory.
|
|
903
|
+
self.releases_bundled = .empty;
|
|
904
|
+
self.releases_bundled.push(constants.config.process.release);
|
|
905
|
+
|
|
906
|
+
return self.stage.err;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
assert(self.stage == .ready);
|
|
910
|
+
assert(self.target_header != null);
|
|
911
|
+
assert(self.releases_bundled.count >= 1);
|
|
912
|
+
|
|
913
|
+
if (comptime builtin.target.os.tag == .linux) {
|
|
914
|
+
assert(self.timeout_statx_previous != .none);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
fn tick(self: *MultiversionOS) void {
|
|
919
|
+
self.timeout.tick();
|
|
920
|
+
if (self.timeout.fired()) self.on_timeout();
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
pub fn timeout_start(self: *MultiversionOS, replica_index: u8) void {
|
|
924
|
+
assert(!self.timeout.ticking);
|
|
925
|
+
if (builtin.target.os.tag != .linux) {
|
|
926
|
+
// Checking for new binaries on disk after the replica has been opened is only
|
|
927
|
+
// supported on Linux.
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
assert(self.timeout.id == 0);
|
|
931
|
+
self.timeout.id = replica_index;
|
|
932
|
+
self.timeout.start();
|
|
933
|
+
log.debug("enabled automatic on-disk version detection.", .{});
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
fn on_timeout(self: *MultiversionOS) void {
|
|
937
|
+
self.timeout.reset();
|
|
938
|
+
|
|
939
|
+
assert(builtin.target.os.tag == .linux);
|
|
940
|
+
if (comptime builtin.target.os.tag != .linux) return; // Prevent codegen.
|
|
941
|
+
|
|
942
|
+
switch (self.stage) {
|
|
943
|
+
.source_stat,
|
|
944
|
+
.source_open,
|
|
945
|
+
.source_read,
|
|
946
|
+
.target_update,
|
|
947
|
+
=> return, // Previous check still in progress
|
|
948
|
+
|
|
949
|
+
.init, .ready, .err => {},
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
self.stage = .init;
|
|
953
|
+
self.binary_statx();
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
fn binary_statx(self: *MultiversionOS) void {
|
|
957
|
+
assert(self.stage == .init);
|
|
958
|
+
|
|
959
|
+
self.stage = .source_stat;
|
|
960
|
+
self.io.statx(
|
|
961
|
+
*MultiversionOS,
|
|
962
|
+
self,
|
|
963
|
+
binary_statx_callback,
|
|
964
|
+
&self.completion,
|
|
965
|
+
posix.AT.FDCWD,
|
|
966
|
+
self.exe_path,
|
|
967
|
+
0,
|
|
968
|
+
os.linux.STATX_BASIC_STATS,
|
|
969
|
+
&self.timeout_statx,
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
fn binary_statx_callback(self: *MultiversionOS, _: *IO.Completion, result: anyerror!void) void {
|
|
974
|
+
assert(self.stage == .source_stat);
|
|
975
|
+
|
|
976
|
+
_ = result catch |e| {
|
|
977
|
+
self.timeout_statx_previous = .err;
|
|
978
|
+
|
|
979
|
+
return self.handle_error(e);
|
|
980
|
+
};
|
|
981
|
+
|
|
982
|
+
if (self.timeout_statx.mode & os.linux.S.IXUSR == 0) {
|
|
983
|
+
return self.handle_error(error.BinaryNotMarkedExecutable);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// Zero the atime, so we can compare the rest of the struct directly.
|
|
987
|
+
self.timeout_statx.atime = std.mem.zeroes(os.linux.statx_timestamp);
|
|
988
|
+
|
|
989
|
+
if (self.timeout_statx_previous == .previous and
|
|
990
|
+
stdx.equal_bytes(
|
|
991
|
+
os.linux.Statx,
|
|
992
|
+
&self.timeout_statx_previous.previous,
|
|
993
|
+
&self.timeout_statx,
|
|
994
|
+
))
|
|
995
|
+
{
|
|
996
|
+
self.stage = .init;
|
|
997
|
+
} else {
|
|
998
|
+
if (self.timeout_statx_previous != .none) {
|
|
999
|
+
log.info("binary change detected: {s}", .{self.exe_path});
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
self.stage = .init;
|
|
1003
|
+
self.binary_open();
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
self.timeout_statx_previous = .{ .previous = self.timeout_statx };
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
fn binary_open(self: *MultiversionOS) void {
|
|
1010
|
+
assert(self.stage == .init);
|
|
1011
|
+
assert(self.source_offset == null);
|
|
1012
|
+
|
|
1013
|
+
self.stage = .source_open;
|
|
1014
|
+
self.source_offset = 0;
|
|
1015
|
+
|
|
1016
|
+
switch (builtin.os.tag) {
|
|
1017
|
+
.linux => self.io.openat(
|
|
1018
|
+
*MultiversionOS,
|
|
1019
|
+
self,
|
|
1020
|
+
binary_open_callback,
|
|
1021
|
+
&self.completion,
|
|
1022
|
+
IO.INVALID_FILE,
|
|
1023
|
+
self.exe_path,
|
|
1024
|
+
.{ .ACCMODE = .RDONLY },
|
|
1025
|
+
0,
|
|
1026
|
+
),
|
|
1027
|
+
.macos, .windows => {
|
|
1028
|
+
const file = std.fs.openFileAbsolute(self.exe_path, .{}) catch |e|
|
|
1029
|
+
std.debug.panic("error in binary_open: {}", .{e});
|
|
1030
|
+
self.binary_open_callback(&self.completion, file.handle);
|
|
1031
|
+
},
|
|
1032
|
+
else => @panic("unsupported platform"),
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
fn binary_open_callback(
|
|
1037
|
+
self: *MultiversionOS,
|
|
1038
|
+
_: *IO.Completion,
|
|
1039
|
+
result: IO.OpenatError!posix.fd_t,
|
|
1040
|
+
) void {
|
|
1041
|
+
assert(self.stage == .source_open);
|
|
1042
|
+
assert(self.source_fd == null);
|
|
1043
|
+
assert(self.source_offset.? == 0);
|
|
1044
|
+
|
|
1045
|
+
const fd = result catch |e| {
|
|
1046
|
+
self.source_offset = null;
|
|
1047
|
+
return self.handle_error(e);
|
|
1048
|
+
};
|
|
1049
|
+
|
|
1050
|
+
self.stage = .source_read;
|
|
1051
|
+
self.source_fd = fd;
|
|
1052
|
+
self.binary_read();
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
fn binary_read(self: *MultiversionOS) void {
|
|
1056
|
+
assert(self.stage == .source_read);
|
|
1057
|
+
assert(self.source_fd != null);
|
|
1058
|
+
assert(self.source_offset != null);
|
|
1059
|
+
assert(self.source_offset.? < self.source_buffer.len);
|
|
1060
|
+
|
|
1061
|
+
self.io.read(
|
|
1062
|
+
*MultiversionOS,
|
|
1063
|
+
self,
|
|
1064
|
+
binary_read_callback,
|
|
1065
|
+
&self.completion,
|
|
1066
|
+
self.source_fd.?,
|
|
1067
|
+
self.source_buffer[self.source_offset.?..],
|
|
1068
|
+
self.source_offset.?,
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
fn binary_read_callback(
|
|
1073
|
+
self: *MultiversionOS,
|
|
1074
|
+
_: *IO.Completion,
|
|
1075
|
+
result: IO.ReadError!usize,
|
|
1076
|
+
) void {
|
|
1077
|
+
assert(self.stage == .source_read);
|
|
1078
|
+
assert(self.source_fd != null);
|
|
1079
|
+
assert(self.source_offset != null);
|
|
1080
|
+
assert(self.source_offset.? < self.source_buffer.len);
|
|
1081
|
+
|
|
1082
|
+
defer {
|
|
1083
|
+
if (self.stage != .source_read) {
|
|
1084
|
+
assert(self.stage == .err or self.stage == .ready);
|
|
1085
|
+
assert(self.source_fd != null);
|
|
1086
|
+
assert(self.source_offset != null);
|
|
1087
|
+
|
|
1088
|
+
posix.close(self.source_fd.?);
|
|
1089
|
+
self.source_offset = null;
|
|
1090
|
+
self.source_fd = null;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
const bytes_read = result catch |e| return self.handle_error(e);
|
|
1095
|
+
self.source_offset.? += bytes_read;
|
|
1096
|
+
assert(self.source_offset.? <= self.source_buffer.len);
|
|
1097
|
+
// This could be a truncated file, but it'll get rejected when we verify the checksum.
|
|
1098
|
+
maybe(self.source_offset.? == self.source_buffer.len);
|
|
1099
|
+
|
|
1100
|
+
if (bytes_read == 0) {
|
|
1101
|
+
const source_buffer = self.source_buffer[0..self.source_offset.?];
|
|
1102
|
+
|
|
1103
|
+
self.stage = .target_update;
|
|
1104
|
+
self.target_update(source_buffer) catch |e| return self.handle_error(e);
|
|
1105
|
+
assert(self.stage == .ready);
|
|
1106
|
+
} else {
|
|
1107
|
+
self.binary_read();
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
fn target_update(self: *MultiversionOS, source_buffer: []align(8) u8) !void {
|
|
1112
|
+
assert(self.stage == .target_update);
|
|
1113
|
+
const offsets = switch (self.exe_path_format) {
|
|
1114
|
+
.elf => try parse_elf(source_buffer),
|
|
1115
|
+
.pe => try parse_pe(source_buffer),
|
|
1116
|
+
.macho => try parse_macho(source_buffer),
|
|
1117
|
+
.detect => parse_elf(source_buffer) catch parse_pe(source_buffer) catch
|
|
1118
|
+
parse_macho(source_buffer) catch return error.NoValidPlatformDetected,
|
|
1119
|
+
};
|
|
1120
|
+
|
|
1121
|
+
const active = offsets.active() orelse return error.NoValidPlatformDetected;
|
|
1122
|
+
|
|
1123
|
+
if (active.header_offset + @sizeOf(MultiversionHeader) > source_buffer.len) {
|
|
1124
|
+
return error.FileTooSmall;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// `init_from_bytes` validates the header checksum internally.
|
|
1128
|
+
const source_buffer_header =
|
|
1129
|
+
source_buffer[active.header_offset..][0..@sizeOf(MultiversionHeader)];
|
|
1130
|
+
const header = try MultiversionHeader.init_from_bytes(source_buffer_header);
|
|
1131
|
+
var header_inactive_platform: ?MultiversionHeader = null;
|
|
1132
|
+
|
|
1133
|
+
// MachO's checksum_binary_without_header works slightly differently since there are
|
|
1134
|
+
// actually two headers, once for x86_64 and one for aarch64. It zeros them both.
|
|
1135
|
+
if (offsets.inactive()) |inactive| {
|
|
1136
|
+
assert(offsets.format == .macho);
|
|
1137
|
+
|
|
1138
|
+
const source_buffer_header_inactive_platform =
|
|
1139
|
+
source_buffer[inactive.header_offset..][0..@sizeOf(MultiversionHeader)];
|
|
1140
|
+
header_inactive_platform = try MultiversionHeader.init_from_bytes(
|
|
1141
|
+
source_buffer_header_inactive_platform,
|
|
1142
|
+
);
|
|
1143
|
+
@memset(source_buffer_header_inactive_platform, 0);
|
|
1144
|
+
if (header.checksum_binary_without_header !=
|
|
1145
|
+
header_inactive_platform.?.checksum_binary_without_header)
|
|
1146
|
+
{
|
|
1147
|
+
return error.HeadersDiffer;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// Zero the header section in memory, to compute the hash, before copying it back.
|
|
1152
|
+
@memset(source_buffer_header, 0);
|
|
1153
|
+
const source_buffer_checksum = checksum.checksum(source_buffer);
|
|
1154
|
+
if (source_buffer_checksum != header.checksum_binary_without_header) {
|
|
1155
|
+
return error.ChecksumMismatch;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// Restore the header(s).
|
|
1159
|
+
stdx.copy_disjoint(
|
|
1160
|
+
.exact,
|
|
1161
|
+
u8,
|
|
1162
|
+
source_buffer_header,
|
|
1163
|
+
std.mem.asBytes(&header),
|
|
1164
|
+
);
|
|
1165
|
+
|
|
1166
|
+
if (offsets.inactive()) |inactive| {
|
|
1167
|
+
assert(offsets.format == .macho);
|
|
1168
|
+
const source_buffer_header_inactive_platform =
|
|
1169
|
+
source_buffer[inactive.header_offset..][0..@sizeOf(MultiversionHeader)];
|
|
1170
|
+
|
|
1171
|
+
stdx.copy_disjoint(
|
|
1172
|
+
.exact,
|
|
1173
|
+
u8,
|
|
1174
|
+
source_buffer_header_inactive_platform,
|
|
1175
|
+
std.mem.asBytes(&header_inactive_platform.?),
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
// Potentially update the releases_bundled list, if all our checks pass:
|
|
1180
|
+
// 1. The release on disk includes the release we're running.
|
|
1181
|
+
// 2. The existing releases_bundled, of any versions newer than current, is a subset
|
|
1182
|
+
// of the new advertisable releases.
|
|
1183
|
+
const advertisable = header.advertisable(constants.config.process.release);
|
|
1184
|
+
if (!advertisable.contains(constants.config.process.release)) {
|
|
1185
|
+
return error.RunningVersionNotIncluded;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
for (self.releases_bundled.slice()) |existing_release| {
|
|
1189
|
+
// It doesn't matter if older releases don't overlap.
|
|
1190
|
+
if (existing_release.value < constants.config.process.release.value) continue;
|
|
1191
|
+
|
|
1192
|
+
if (!advertisable.contains(existing_release)) return error.NotSuperset;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// Log out the releases bundled; both old and new. Only if this was a change detection run
|
|
1196
|
+
// and not from startup.
|
|
1197
|
+
if (self.timeout_statx_previous != .none) {
|
|
1198
|
+
log.info("releases_bundled old: {any}", .{self.releases_bundled.slice()});
|
|
1199
|
+
}
|
|
1200
|
+
defer if (self.timeout_statx_previous != .none) {
|
|
1201
|
+
log.info("releases_bundled new: {any}", .{self.releases_bundled.slice()});
|
|
1202
|
+
};
|
|
1203
|
+
|
|
1204
|
+
// The below flip needs to happen atomically:
|
|
1205
|
+
// * update the releases_bundled to be what's in the source,
|
|
1206
|
+
// * update the target_fd to have the same contents as the source.
|
|
1207
|
+
//
|
|
1208
|
+
// Since target_fd points to a memfd on Linux, this is functionally a memcpy. On other
|
|
1209
|
+
// platforms, it's blocking IO - which is acceptable for development.
|
|
1210
|
+
self.releases_bundled = advertisable;
|
|
1211
|
+
|
|
1212
|
+
// While these look like blocking IO operations, on a memfd they're memory manipulation.
|
|
1213
|
+
// TODO: Would panic'ing be a better option? On Linux, these should never fail. On other
|
|
1214
|
+
// platforms where target_fd might be backed by a file, they could...
|
|
1215
|
+
errdefer log.warn("target binary update failed - " ++
|
|
1216
|
+
"this replica might fail to automatically restart!", .{});
|
|
1217
|
+
|
|
1218
|
+
const target_file = std.fs.File{ .handle = self.target_fd };
|
|
1219
|
+
try target_file.pwriteAll(source_buffer, 0);
|
|
1220
|
+
|
|
1221
|
+
self.target_header = header;
|
|
1222
|
+
self.target_body_offset = active.body_offset;
|
|
1223
|
+
self.target_body_size = active.body_size;
|
|
1224
|
+
|
|
1225
|
+
self.stage = .ready;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
fn handle_error(self: *MultiversionOS, result: anyerror) void {
|
|
1229
|
+
assert(self.stage != .init);
|
|
1230
|
+
|
|
1231
|
+
log.err("binary does not contain valid multiversion data: {}", .{result});
|
|
1232
|
+
|
|
1233
|
+
self.stage = .{ .err = result };
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
fn replica_release_execute(self: *MultiversionOS, release: Release) noreturn {
|
|
1237
|
+
assert(release.value != constants.config.process.release.value);
|
|
1238
|
+
assert(release.value != Release.zero.value);
|
|
1239
|
+
assert(release.value != Release.minimum.value);
|
|
1240
|
+
|
|
1241
|
+
if (!self.releases_bundled.contains(release)) {
|
|
1242
|
+
log.err("release_execute: release {} is not available;" ++
|
|
1243
|
+
" upgrade (or downgrade) the binary", .{
|
|
1244
|
+
release,
|
|
1245
|
+
});
|
|
1246
|
+
@panic("release not available");
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// We have two paths here, depending on if we're upgrading or downgrading. If we're
|
|
1250
|
+
// downgrading the invariant is that this code is running _before_ we've finished opening,
|
|
1251
|
+
// that is, release_transition is called in open().
|
|
1252
|
+
if (release.value < constants.config.process.release.value) {
|
|
1253
|
+
self.exec_release(
|
|
1254
|
+
release,
|
|
1255
|
+
) catch |err| {
|
|
1256
|
+
std.debug.panic("failed to execute previous release: {}", .{err});
|
|
1257
|
+
};
|
|
1258
|
+
} else {
|
|
1259
|
+
// For the upgrade case, re-run the latest binary in place. If we need something older
|
|
1260
|
+
// than the latest, that'll be handled when the case above is hit when re-execing:
|
|
1261
|
+
// (current version v1) -> (latest version v4) -> (desired version v2)
|
|
1262
|
+
self.exec_current(release) catch |err| {
|
|
1263
|
+
std.debug.panic("failed to execute latest release: {}", .{err});
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
comptime unreachable;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
fn exec_current(self: *MultiversionOS, release_target: Release) !noreturn {
|
|
1271
|
+
// target_fd is only modified in target_update() which happens synchronously.
|
|
1272
|
+
assert(self.stage != .target_update);
|
|
1273
|
+
|
|
1274
|
+
// Ensure that target_update() has been called at least once, and thus target_fd is
|
|
1275
|
+
// populated by checking that target_header has been set.
|
|
1276
|
+
assert(self.target_header != null);
|
|
1277
|
+
|
|
1278
|
+
// `release_target` is only used as a sanity check, and doesn't control the exec path here.
|
|
1279
|
+
// There are two possible cases:
|
|
1280
|
+
// * release_target == target_header.current_release:
|
|
1281
|
+
// The latest release will be executed, and it won't do any more re-execs from there
|
|
1282
|
+
// onwards (that we know about). Happens when jumping to the latest release.
|
|
1283
|
+
// * release_target in target_header.past.releases:
|
|
1284
|
+
// The latest release will be executed, but after starting up it will use exec_release()
|
|
1285
|
+
// to execute a past version. Happens when stopping at an intermediate release with
|
|
1286
|
+
// visit == true.
|
|
1287
|
+
const release_target_current = release_target.value == self.target_header.?.current_release;
|
|
1288
|
+
const release_target_past = std.mem.indexOfScalar(
|
|
1289
|
+
u32,
|
|
1290
|
+
self.target_header.?.past.releases[0..self.target_header.?.past.count],
|
|
1291
|
+
release_target.value,
|
|
1292
|
+
) != null;
|
|
1293
|
+
|
|
1294
|
+
assert(release_target_current != release_target_past);
|
|
1295
|
+
|
|
1296
|
+
// The trailing newline is intentional - it provides visual separation in the logs when
|
|
1297
|
+
// exec'ing new versions.
|
|
1298
|
+
if (release_target_current) {
|
|
1299
|
+
log.info("executing current release {} via {s}...\n", .{
|
|
1300
|
+
release_target,
|
|
1301
|
+
self.exe_path,
|
|
1302
|
+
});
|
|
1303
|
+
} else if (release_target_past) {
|
|
1304
|
+
log.info("executing current release {} (target: {}) via {s}...\n", .{
|
|
1305
|
+
Release{ .value = self.target_header.?.current_release },
|
|
1306
|
+
release_target,
|
|
1307
|
+
self.exe_path,
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
try self.exec_target_fd();
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
/// exec_release is called before a replica is fully open, but just after it has transitioned to
|
|
1314
|
+
/// static. Therefore, standard `os.read` blocking syscalls are available.
|
|
1315
|
+
/// (in any case, using blocking IO on a memfd on Linux is safe.)
|
|
1316
|
+
fn exec_release(self: *MultiversionOS, release_target: Release) !noreturn {
|
|
1317
|
+
// exec_release uses self.source_buffer, but this may be the target of an async read by
|
|
1318
|
+
// the kernel (from binary_open_callback). Assert that timeouts are not running, and
|
|
1319
|
+
// multiversioning is ready to ensure this can't be the case.
|
|
1320
|
+
assert(!self.timeout.ticking);
|
|
1321
|
+
assert(self.stage == .ready);
|
|
1322
|
+
|
|
1323
|
+
const header = &self.target_header.?;
|
|
1324
|
+
|
|
1325
|
+
if (header.current_release == constants.config.process.release.value) {
|
|
1326
|
+
// Normally if we are downgrading, it means that we are running the newest release
|
|
1327
|
+
// in the list of bundled releases.
|
|
1328
|
+
assert(constants.config.process.release.value == self.releases_bundled.last().value);
|
|
1329
|
+
} else {
|
|
1330
|
+
// Scenario:
|
|
1331
|
+
// 1. Replica starts on release A.
|
|
1332
|
+
// 2. Replica detects that its binary has been replaced by B.
|
|
1333
|
+
// It reads the binary of B into a memfd.
|
|
1334
|
+
// 3. Replica decides to upgrade to B, so it exec()'s the memfd.
|
|
1335
|
+
// 4. (Swap B's binary on disk with C.)
|
|
1336
|
+
// 5. Replica starts up, running B's binary.
|
|
1337
|
+
// 6. During open, replica reads the binary's header from disk.
|
|
1338
|
+
// But that's C's binary/header, so B is unexpectedly not the latest release in it.
|
|
1339
|
+
log.warn("binary changed unexpectedly (expected={} found={})", .{
|
|
1340
|
+
constants.config.process.release,
|
|
1341
|
+
Release{ .value = header.current_release },
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1344
|
+
assert(constants.config.process.release.value != self.releases_bundled.last().value);
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
// It should never happen that index is null: the caller must (and does, in the case of
|
|
1348
|
+
// replica_release_execute) ensure that exec_release is only called if the release
|
|
1349
|
+
// is available.
|
|
1350
|
+
const index = std.mem.indexOfScalar(
|
|
1351
|
+
u32,
|
|
1352
|
+
header.past.releases[0..header.past.count],
|
|
1353
|
+
release_target.value,
|
|
1354
|
+
).?;
|
|
1355
|
+
|
|
1356
|
+
const binary_offset = header.past.offsets[index];
|
|
1357
|
+
const binary_size = header.past.sizes[index];
|
|
1358
|
+
const binary_checksum = header.past.checksums[index];
|
|
1359
|
+
|
|
1360
|
+
const target_file = std.fs.File{ .handle = self.target_fd };
|
|
1361
|
+
|
|
1362
|
+
// Our target release is physically embedded in the binary. Shuffle the bytes
|
|
1363
|
+
// around, so that it's at the start, then truncate the descriptor so there's nothing
|
|
1364
|
+
// trailing.
|
|
1365
|
+
const bytes_read = try target_file.preadAll(
|
|
1366
|
+
self.source_buffer[0..binary_size],
|
|
1367
|
+
self.target_body_offset.? + binary_offset,
|
|
1368
|
+
);
|
|
1369
|
+
assert(bytes_read == binary_size);
|
|
1370
|
+
|
|
1371
|
+
try target_file.pwriteAll(self.source_buffer[0..binary_size], 0);
|
|
1372
|
+
|
|
1373
|
+
// Zero the remaining bytes in the file.
|
|
1374
|
+
try posix.ftruncate(self.target_fd, binary_size);
|
|
1375
|
+
|
|
1376
|
+
// Ensure the checksum matches the header. This could have been done above, but
|
|
1377
|
+
// do it in a separate step to make sure.
|
|
1378
|
+
const written_checksum = blk: {
|
|
1379
|
+
const bytes_read_for_checksum = try target_file.preadAll(
|
|
1380
|
+
self.source_buffer[0..binary_size],
|
|
1381
|
+
0,
|
|
1382
|
+
);
|
|
1383
|
+
|
|
1384
|
+
assert(bytes_read_for_checksum == binary_size);
|
|
1385
|
+
break :blk checksum.checksum(self.source_buffer[0..binary_size]);
|
|
1386
|
+
};
|
|
1387
|
+
assert(written_checksum == binary_checksum);
|
|
1388
|
+
|
|
1389
|
+
// The trailing newline is intentional - it provides visual separation in the logs when
|
|
1390
|
+
// exec'ing new versions.
|
|
1391
|
+
log.info("executing internal release {} via {s}...\n", .{
|
|
1392
|
+
release_target,
|
|
1393
|
+
self.exe_path,
|
|
1394
|
+
});
|
|
1395
|
+
try self.exec_target_fd();
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
fn exec_target_fd(self: *MultiversionOS) !noreturn {
|
|
1399
|
+
switch (builtin.os.tag) {
|
|
1400
|
+
.linux => {
|
|
1401
|
+
if (execveat(
|
|
1402
|
+
self.target_fd,
|
|
1403
|
+
"",
|
|
1404
|
+
self.args_envp.args,
|
|
1405
|
+
self.args_envp.envp,
|
|
1406
|
+
posix.AT.EMPTY_PATH,
|
|
1407
|
+
) == 0) {
|
|
1408
|
+
unreachable;
|
|
1409
|
+
} else {
|
|
1410
|
+
return error.ExecveatFailed;
|
|
1411
|
+
}
|
|
1412
|
+
},
|
|
1413
|
+
.macos => {
|
|
1414
|
+
std.posix.execveZ(self.target_path, self.args_envp.args, self.args_envp.envp) catch
|
|
1415
|
+
return error.ExecveZFailed;
|
|
1416
|
+
|
|
1417
|
+
unreachable;
|
|
1418
|
+
},
|
|
1419
|
+
.windows => {
|
|
1420
|
+
// "The Unicode version of this function, CreateProcessW, can modify the contents of
|
|
1421
|
+
// this string. Therefore, this parameter cannot be a pointer to read-only memory
|
|
1422
|
+
// (such as a const variable or a literal string). If this parameter is a constant
|
|
1423
|
+
// string, the function may cause an access violation."
|
|
1424
|
+
//
|
|
1425
|
+
// That said, with how CreateProcessW is called, this should _never_ happen, since
|
|
1426
|
+
// its both provided a full lpApplicationName, and because GetCommandLineW actually
|
|
1427
|
+
// points to a copy of memory from the PEB.
|
|
1428
|
+
const cmd_line_w = stdx.windows.GetCommandLineW();
|
|
1429
|
+
|
|
1430
|
+
var lp_startup_info = std.mem.zeroes(std.os.windows.STARTUPINFOW);
|
|
1431
|
+
lp_startup_info.cb = @sizeOf(std.os.windows.STARTUPINFOW);
|
|
1432
|
+
|
|
1433
|
+
var lp_process_information: std.os.windows.PROCESS_INFORMATION = undefined;
|
|
1434
|
+
|
|
1435
|
+
// Close the handle before trying to execute.
|
|
1436
|
+
posix.close(self.target_fd);
|
|
1437
|
+
|
|
1438
|
+
const pipe_name: [*:0]const u16 =
|
|
1439
|
+
std.unicode.utf8ToUtf16LeStringLiteral("\\\\.\\pipe\\") ++ random_wstr();
|
|
1440
|
+
|
|
1441
|
+
// Use pipe to send our HANDLE to the child, see `wait_for_parent_to_exit`.
|
|
1442
|
+
const pipe = std.os.windows.kernel32.CreateNamedPipeW(
|
|
1443
|
+
pipe_name,
|
|
1444
|
+
std.os.windows.PIPE_ACCESS_OUTBOUND |
|
|
1445
|
+
0x00080000, // FILE_FLAG_FIRST_PIPE_INSTANCE,
|
|
1446
|
+
std.os.windows.PIPE_TYPE_BYTE | std.os.windows.PIPE_WAIT,
|
|
1447
|
+
1, // nMaxInstances
|
|
1448
|
+
0, // nOutBufferSize
|
|
1449
|
+
0, // nInBufferSize
|
|
1450
|
+
0, // nDefaultTimeOut
|
|
1451
|
+
null, // lpSecurityAttributes
|
|
1452
|
+
);
|
|
1453
|
+
if (pipe == std.os.windows.INVALID_HANDLE_VALUE) {
|
|
1454
|
+
log.err("CreateNamedPipeW: {}", .{std.os.windows.GetLastError()});
|
|
1455
|
+
return error.CreateNamedPipeWFailed;
|
|
1456
|
+
}
|
|
1457
|
+
assert(pipe != std.os.windows.INVALID_HANDLE_VALUE);
|
|
1458
|
+
errdefer std.os.windows.CloseHandle(pipe);
|
|
1459
|
+
|
|
1460
|
+
// Pass the name of the pipe and the path to the original executable
|
|
1461
|
+
// via the inherited environment.
|
|
1462
|
+
assert(
|
|
1463
|
+
std.os.windows.kernel32.SetEnvironmentVariableW(
|
|
1464
|
+
comptime std.unicode.utf8ToUtf16LeStringLiteral(TB_MULTIVERSION_PIPE),
|
|
1465
|
+
pipe_name,
|
|
1466
|
+
) != 0,
|
|
1467
|
+
);
|
|
1468
|
+
assert(
|
|
1469
|
+
std.os.windows.kernel32.SetEnvironmentVariableW(
|
|
1470
|
+
comptime std.unicode.utf8ToUtf16LeStringLiteral(TB_MULTIVERSION_EXE),
|
|
1471
|
+
self.args_envp.exe_path_w,
|
|
1472
|
+
) != 0,
|
|
1473
|
+
);
|
|
1474
|
+
|
|
1475
|
+
// If bInheritHandles is FALSE, and dwFlags inside STARTUPINFOW doesn't have
|
|
1476
|
+
// STARTF_USESTDHANDLES set, the stdin/stdout/stderr handles of the parent will
|
|
1477
|
+
// be passed through to the child.
|
|
1478
|
+
std.os.windows.CreateProcessW(
|
|
1479
|
+
self.args_envp.target_path_w,
|
|
1480
|
+
cmd_line_w,
|
|
1481
|
+
null,
|
|
1482
|
+
null,
|
|
1483
|
+
std.os.windows.FALSE,
|
|
1484
|
+
std.os.windows.CREATE_UNICODE_ENVIRONMENT,
|
|
1485
|
+
null,
|
|
1486
|
+
null,
|
|
1487
|
+
&lp_startup_info,
|
|
1488
|
+
&lp_process_information,
|
|
1489
|
+
) catch return error.CreateProcessWFailed;
|
|
1490
|
+
const child: std.os.windows.HANDLE = lp_process_information.hProcess;
|
|
1491
|
+
|
|
1492
|
+
if (stdx.windows.ConnectNamedPipe(pipe, null) == std.os.windows.FALSE and
|
|
1493
|
+
std.os.windows.GetLastError() != .PIPE_CONNECTED)
|
|
1494
|
+
{
|
|
1495
|
+
log.err("ConnectNamedPipe: {}", .{std.os.windows.GetLastError()});
|
|
1496
|
+
return error.ConnectNamedPipeFailed;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
var me: std.os.windows.HANDLE = undefined;
|
|
1500
|
+
if (std.os.windows.kernel32.DuplicateHandle(
|
|
1501
|
+
std.os.windows.GetCurrentProcess(),
|
|
1502
|
+
std.os.windows.GetCurrentProcess(),
|
|
1503
|
+
child,
|
|
1504
|
+
&me,
|
|
1505
|
+
0,
|
|
1506
|
+
std.os.windows.FALSE,
|
|
1507
|
+
std.os.windows.DUPLICATE_SAME_ACCESS,
|
|
1508
|
+
) != std.os.windows.TRUE) {
|
|
1509
|
+
log.err("DuplicateHandle: {}", .{std.os.windows.GetLastError()});
|
|
1510
|
+
return error.DuplicateHandleFailed;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
const write_size = try std.os.windows.WriteFile(pipe, std.mem.asBytes(&me), null);
|
|
1514
|
+
assert(write_size == @sizeOf(@TypeOf(me)));
|
|
1515
|
+
|
|
1516
|
+
std.process.exit(0);
|
|
1517
|
+
},
|
|
1518
|
+
else => @panic("unsupported platform"),
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1522
|
+
|
|
1523
|
+
pub fn self_exe_path(allocator: std.mem.Allocator) ![:0]const u8 {
|
|
1524
|
+
var buf: [std.fs.max_path_bytes]u8 = undefined;
|
|
1525
|
+
|
|
1526
|
+
if (builtin.target.os.tag == .windows) {
|
|
1527
|
+
// Special case: Wine doesn't support selfExePath.
|
|
1528
|
+
const ntdll = os.windows.kernel32.GetModuleHandleW(
|
|
1529
|
+
std.unicode.utf8ToUtf16LeStringLiteral("ntdll.dll"),
|
|
1530
|
+
).?;
|
|
1531
|
+
const wine_get_version = os.windows.kernel32.GetProcAddress(ntdll, "wine_get_version");
|
|
1532
|
+
|
|
1533
|
+
if (wine_get_version != null) {
|
|
1534
|
+
log.warn("wine doesn't support std.fs.selfExePath", .{});
|
|
1535
|
+
return allocator.dupeZ(u8, "");
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
const native_self_exe_path = try std.fs.selfExePath(&buf);
|
|
1540
|
+
|
|
1541
|
+
if (builtin.target.os.tag == .linux and
|
|
1542
|
+
std.mem.eql(u8, native_self_exe_path, "/memfd:" ++ multiversion_uuid ++ " (deleted)"))
|
|
1543
|
+
{
|
|
1544
|
+
comptime assert(builtin.target.os.tag == .linux);
|
|
1545
|
+
// Technically, "/memfd:tigerbeetle-multiversion-... (deleted)" is a valid path at which you
|
|
1546
|
+
// could place your binary - please don't!
|
|
1547
|
+
assert(std.fs.cwd().statFile(native_self_exe_path) catch null == null);
|
|
1548
|
+
|
|
1549
|
+
// Running from a memfd already; the real path is argv[0].
|
|
1550
|
+
const path = try allocator.dupeZ(u8, std.mem.span(os.argv[0]));
|
|
1551
|
+
assert(std.fs.path.isAbsolute(path));
|
|
1552
|
+
|
|
1553
|
+
return path;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
if (builtin.target.os.tag == .macos and
|
|
1557
|
+
std.mem.indexOf(u8, native_self_exe_path, multiversion_uuid) != null)
|
|
1558
|
+
{
|
|
1559
|
+
comptime assert(builtin.target.os.tag == .macos);
|
|
1560
|
+
// Similarly to the Linux case, assume that UUID-containing name means an upgrade, though
|
|
1561
|
+
// it's not possible to assert this.
|
|
1562
|
+
|
|
1563
|
+
// Running from a temp path already; the real path is argv[0].
|
|
1564
|
+
const path = try allocator.dupeZ(u8, std.mem.span(os.argv[0]));
|
|
1565
|
+
assert(std.fs.path.isAbsolute(path));
|
|
1566
|
+
|
|
1567
|
+
return path;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
if (builtin.target.os.tag == .windows and
|
|
1571
|
+
std.mem.indexOf(u8, native_self_exe_path, multiversion_uuid) != null)
|
|
1572
|
+
{
|
|
1573
|
+
comptime assert(builtin.target.os.tag == .windows);
|
|
1574
|
+
// Similarly to the Linux case, assume that UUID-containing name means an upgrade, though
|
|
1575
|
+
// it's not possible to assert this.
|
|
1576
|
+
|
|
1577
|
+
// Windows make it error-prone to set argv[0], so the path is passed via env.
|
|
1578
|
+
const path = try std.process.getEnvVarOwned(allocator, TB_MULTIVERSION_EXE);
|
|
1579
|
+
defer allocator.free(path);
|
|
1580
|
+
|
|
1581
|
+
assert(std.fs.path.isAbsolute(path));
|
|
1582
|
+
|
|
1583
|
+
return try allocator.dupeZ(u8, path);
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
// Not running from a memfd or temp path. `native_self_exe_path` is the real path.
|
|
1587
|
+
return try allocator.dupeZ(u8, native_self_exe_path);
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
pub fn random_wstr() [32]u16 {
|
|
1591
|
+
var result: [32]u16 = @splat(std.unicode.utf8ToUtf16LeStringLiteral("0")[0]);
|
|
1592
|
+
|
|
1593
|
+
var buffer_utf8: [31]u8 = undefined;
|
|
1594
|
+
const name_utf8 = stdx.array_print(31, &buffer_utf8, "{d}", .{std.crypto.random.int(u64)});
|
|
1595
|
+
var fba = std.heap.FixedBufferAllocator.init(std.mem.asBytes(&result));
|
|
1596
|
+
_ = std.unicode.utf8ToUtf16LeAllocZ(fba.allocator(), name_utf8) catch |err| switch (err) {
|
|
1597
|
+
error.InvalidUtf8, error.OutOfMemory => unreachable,
|
|
1598
|
+
};
|
|
1599
|
+
return result;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
// During upgrades, tigerbeetle dynamically re-executes itself at a different version.
|
|
1603
|
+
// There can be only one tigerbeetle instance running at a time, due to any of:
|
|
1604
|
+
// - data file lock,
|
|
1605
|
+
// - incoming port,
|
|
1606
|
+
// - available RAM.
|
|
1607
|
+
// On POSIX, execve semantics of replacing the parent process gives us this for free.
|
|
1608
|
+
// On Windows, parent and child cooperate, with the child blocking until the parent exit.
|
|
1609
|
+
// Specifically:
|
|
1610
|
+
// 1. Parent creates a named pipe with a random name.
|
|
1611
|
+
// 2. Parent passes the name of this pipe to the child via inherited env variable.
|
|
1612
|
+
// 3. Parent duplicates its own handle into the child process.
|
|
1613
|
+
// 4. Parent passes the value of this handle to the child via anonymous pipe
|
|
1614
|
+
// (We don't want to pass parent handle _directly_ to avoid handle inheritance)
|
|
1615
|
+
// 5. Child gets the name of the pipe trough env.
|
|
1616
|
+
// 6. Child receives the parent's handle through the pipe.
|
|
1617
|
+
// 7. Child waits for parent to exit.
|
|
1618
|
+
const TB_MULTIVERSION_PIPE = "TB_MULTIVERSION_PIPE";
|
|
1619
|
+
const TB_MULTIVERSION_EXE = "TB_MULTIVERSION_EXE";
|
|
1620
|
+
pub fn wait_for_parent_to_exit() !void {
|
|
1621
|
+
comptime assert(builtin.os.tag == .windows);
|
|
1622
|
+
|
|
1623
|
+
var pipe_name_buffer: [64]u16 = undefined;
|
|
1624
|
+
const count = std.os.windows.kernel32.GetEnvironmentVariableW(
|
|
1625
|
+
comptime std.unicode.utf8ToUtf16LeStringLiteral(TB_MULTIVERSION_PIPE),
|
|
1626
|
+
&pipe_name_buffer,
|
|
1627
|
+
pipe_name_buffer.len,
|
|
1628
|
+
);
|
|
1629
|
+
if (count == 0) return;
|
|
1630
|
+
assert(pipe_name_buffer[count] == 0);
|
|
1631
|
+
|
|
1632
|
+
const pipe = std.os.windows.kernel32.CreateFileW(
|
|
1633
|
+
@ptrCast(&pipe_name_buffer),
|
|
1634
|
+
std.os.windows.GENERIC_READ,
|
|
1635
|
+
0,
|
|
1636
|
+
null,
|
|
1637
|
+
std.os.windows.OPEN_EXISTING,
|
|
1638
|
+
0,
|
|
1639
|
+
null,
|
|
1640
|
+
);
|
|
1641
|
+
if (pipe == std.os.windows.INVALID_HANDLE_VALUE) {
|
|
1642
|
+
log.err("CreateFileW: {}", .{std.os.windows.GetLastError()});
|
|
1643
|
+
return error.CreateFileWFailed;
|
|
1644
|
+
}
|
|
1645
|
+
assert(pipe != std.os.windows.INVALID_HANDLE_VALUE);
|
|
1646
|
+
defer std.os.windows.CloseHandle(pipe);
|
|
1647
|
+
|
|
1648
|
+
var parent: std.os.windows.HANDLE = undefined;
|
|
1649
|
+
const read_size = try std.os.windows.ReadFile(pipe, std.mem.asBytes(&parent), null);
|
|
1650
|
+
assert(read_size == @sizeOf(@TypeOf(parent)));
|
|
1651
|
+
defer std.os.windows.CloseHandle(parent);
|
|
1652
|
+
|
|
1653
|
+
try std.os.windows.WaitForSingleObject(parent, std.os.windows.INFINITE);
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
const HeaderBodyOffsets = struct {
|
|
1657
|
+
const Offsets = struct {
|
|
1658
|
+
header_offset: u32,
|
|
1659
|
+
body_offset: u32,
|
|
1660
|
+
body_size: u32,
|
|
1661
|
+
};
|
|
1662
|
+
|
|
1663
|
+
format: enum { elf, pe, macho },
|
|
1664
|
+
aarch64: ?Offsets,
|
|
1665
|
+
x86_64: ?Offsets,
|
|
1666
|
+
|
|
1667
|
+
fn active(header_body_offsets: HeaderBodyOffsets) ?Offsets {
|
|
1668
|
+
return switch (builtin.target.cpu.arch) {
|
|
1669
|
+
.x86_64 => header_body_offsets.x86_64,
|
|
1670
|
+
.aarch64 => header_body_offsets.aarch64,
|
|
1671
|
+
else => comptime unreachable,
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
fn inactive(header_body_offsets: HeaderBodyOffsets) ?Offsets {
|
|
1676
|
+
return switch (builtin.target.cpu.arch) {
|
|
1677
|
+
.x86_64 => header_body_offsets.aarch64,
|
|
1678
|
+
.aarch64 => header_body_offsets.x86_64,
|
|
1679
|
+
else => comptime unreachable,
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
};
|
|
1683
|
+
|
|
1684
|
+
/// Parse an untrusted, unverified, and potentially corrupt ELF file. This parsing happens before
|
|
1685
|
+
/// any checksums are verified, and so needs to deal with any ELF metadata being corrupt, while
|
|
1686
|
+
/// not panicking and returning errors.
|
|
1687
|
+
///
|
|
1688
|
+
/// Anything that would normally assert should return an error instead - especially implicit things
|
|
1689
|
+
/// like bounds checking on slices.
|
|
1690
|
+
pub fn parse_elf(buffer: []align(@alignOf(elf.Elf64_Ehdr)) const u8) !HeaderBodyOffsets {
|
|
1691
|
+
if (@sizeOf(elf.Elf64_Ehdr) > buffer.len) return error.InvalidELF;
|
|
1692
|
+
const elf_header = try elf.Header.parse(buffer[0..@sizeOf(elf.Elf64_Ehdr)]);
|
|
1693
|
+
|
|
1694
|
+
// TigerBeetle only supports little endian on 64 bit platforms.
|
|
1695
|
+
if (elf_header.endian != .little) return error.WrongEndian;
|
|
1696
|
+
if (!elf_header.is_64) return error.Not64bit;
|
|
1697
|
+
|
|
1698
|
+
// Map to some non-abbreviated names to make understanding ELF a little bit easier. Later on,
|
|
1699
|
+
// when sh_* names are used, they refer to `section header ...`.
|
|
1700
|
+
const elf_section_headers_offset = elf_header.shoff;
|
|
1701
|
+
const elf_section_headers_count = elf_header.shnum;
|
|
1702
|
+
const string_table_section_header_index = elf_header.shstrndx;
|
|
1703
|
+
|
|
1704
|
+
// Only support "simple" ELF string tables.
|
|
1705
|
+
if (string_table_section_header_index >= elf.SHN_LORESERVE) return error.LongStringTable;
|
|
1706
|
+
if (string_table_section_header_index == elf.SHN_UNDEF) return error.LongStringTable;
|
|
1707
|
+
|
|
1708
|
+
// We iterate over elf_section_headers_count, so add a sanity check on the number of sections
|
|
1709
|
+
// in the file. It is a u16, so it is bounded relatively low already, but we expect on the
|
|
1710
|
+
// order of maybe ~30 with debug symbols.
|
|
1711
|
+
if (elf_section_headers_count > 128) return error.TooManySections;
|
|
1712
|
+
if (elf_section_headers_count < 2) return error.TooFewSections;
|
|
1713
|
+
|
|
1714
|
+
// First, read the string table section.
|
|
1715
|
+
const string_table_elf_section_header_offset: u64 = elf_section_headers_offset +
|
|
1716
|
+
@as(u64, @sizeOf(elf.Elf64_Shdr)) * string_table_section_header_index;
|
|
1717
|
+
if (string_table_elf_section_header_offset + @sizeOf(elf.Elf64_Shdr) > buffer.len) {
|
|
1718
|
+
return error.InvalidELF;
|
|
1719
|
+
}
|
|
1720
|
+
const string_table_elf_section_header = std.mem.bytesAsValue(
|
|
1721
|
+
elf.Elf64_Shdr,
|
|
1722
|
+
buffer[string_table_elf_section_header_offset..][0..@sizeOf(elf.Elf64_Shdr)],
|
|
1723
|
+
);
|
|
1724
|
+
|
|
1725
|
+
if (string_table_elf_section_header.sh_type != elf.SHT_STRTAB) return error.InvalidStringTable;
|
|
1726
|
+
if (string_table_elf_section_header.sh_size <= 0) return error.InvalidStringTable;
|
|
1727
|
+
if (string_table_elf_section_header.sh_size >= buffer.len) return error.InvalidStringTable;
|
|
1728
|
+
|
|
1729
|
+
const string_table_offset = string_table_elf_section_header.sh_offset;
|
|
1730
|
+
|
|
1731
|
+
if (@as(u65, string_table_offset) + string_table_elf_section_header.sh_size >
|
|
1732
|
+
std.math.maxInt(usize))
|
|
1733
|
+
{
|
|
1734
|
+
return error.InvalidStringTable;
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
if (string_table_offset + string_table_elf_section_header.sh_size > buffer.len) {
|
|
1738
|
+
return error.InvalidStringTable;
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
if (buffer[string_table_offset + string_table_elf_section_header.sh_size - 1] != 0) {
|
|
1742
|
+
return error.InvalidStringTable;
|
|
1743
|
+
}
|
|
1744
|
+
const string_table =
|
|
1745
|
+
buffer[string_table_offset..][0 .. string_table_elf_section_header.sh_size - 1 :0];
|
|
1746
|
+
|
|
1747
|
+
// Next, go through each ELF section to find the ones we're looking for:
|
|
1748
|
+
var header_offset: ?u32 = null;
|
|
1749
|
+
var body_offset: ?u32 = null;
|
|
1750
|
+
var body_size: ?u32 = null;
|
|
1751
|
+
for (0..elf_section_headers_count) |i| {
|
|
1752
|
+
const offset: u64 = elf_section_headers_offset + @as(u64, @sizeOf(elf.Elf64_Shdr)) * i;
|
|
1753
|
+
if (offset + @sizeOf(elf.Elf64_Shdr) > buffer.len) return error.InvalidSectionOffset;
|
|
1754
|
+
|
|
1755
|
+
const elf_section_header = std.mem.bytesAsValue(
|
|
1756
|
+
elf.Elf64_Shdr,
|
|
1757
|
+
buffer[offset..][0..@sizeOf(elf.Elf64_Shdr)],
|
|
1758
|
+
);
|
|
1759
|
+
|
|
1760
|
+
if (elf_section_header.sh_name > string_table.len) return error.InvalidStringTableOffset;
|
|
1761
|
+
|
|
1762
|
+
// This will always match _something_, since above we check that the last item in the
|
|
1763
|
+
// string table is a null terminator.
|
|
1764
|
+
const name = std.mem.sliceTo(
|
|
1765
|
+
@as([*:0]const u8, string_table[elf_section_header.sh_name.. :0]),
|
|
1766
|
+
0,
|
|
1767
|
+
);
|
|
1768
|
+
|
|
1769
|
+
if (std.mem.eql(u8, name, ".tb_mvb")) {
|
|
1770
|
+
// The body must be the second-last section in the file.
|
|
1771
|
+
if (body_offset != null) return error.MultipleMultiversionBody;
|
|
1772
|
+
if (i != elf_section_headers_count - 2) return error.InvalidMultiversionBodyLocation;
|
|
1773
|
+
if (elf_section_header.sh_offset > std.math.maxInt(@TypeOf(body_offset.?))) {
|
|
1774
|
+
return error.InvalidMultiversionBodyOffset;
|
|
1775
|
+
}
|
|
1776
|
+
if (elf_section_header.sh_size > std.math.maxInt(@TypeOf(body_size.?))) {
|
|
1777
|
+
return error.InvalidMultiversionBodySize;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
assert(body_size == null);
|
|
1781
|
+
|
|
1782
|
+
body_offset = @intCast(elf_section_header.sh_offset);
|
|
1783
|
+
body_size = @intCast(elf_section_header.sh_size);
|
|
1784
|
+
} else if (std.mem.eql(u8, name, ".tb_mvh")) {
|
|
1785
|
+
// The header must be the last section in the file. (It's _logically_ a header.)
|
|
1786
|
+
if (header_offset != null) return error.MultipleMultiversionHeader;
|
|
1787
|
+
if (elf_section_header.sh_size != @sizeOf(MultiversionHeader)) {
|
|
1788
|
+
return error.InvalidMultiversionHeaderSize;
|
|
1789
|
+
}
|
|
1790
|
+
if (i != elf_section_headers_count - 1) return error.InvalidMultiversionHeaderLocation;
|
|
1791
|
+
if (elf_section_header.sh_offset > std.math.maxInt(@TypeOf(header_offset.?))) {
|
|
1792
|
+
return error.InvalidMultiversionHeaderOffset;
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
header_offset = @intCast(elf_section_header.sh_offset);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
if (header_offset == null or body_offset == null) {
|
|
1800
|
+
return error.MultiversionHeaderOrBodyNotFound;
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
if (body_offset.? + body_size.? > header_offset.?) {
|
|
1804
|
+
return error.MultiversionBodyOverlapsHeader;
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
const offsets: HeaderBodyOffsets.Offsets = .{
|
|
1808
|
+
.header_offset = header_offset.?,
|
|
1809
|
+
.body_offset = body_offset.?,
|
|
1810
|
+
.body_size = body_size.?,
|
|
1811
|
+
};
|
|
1812
|
+
|
|
1813
|
+
return switch (elf_header.machine) {
|
|
1814
|
+
.AARCH64 => .{ .format = .elf, .aarch64 = offsets, .x86_64 = null },
|
|
1815
|
+
.X86_64 => .{ .format = .elf, .aarch64 = null, .x86_64 = offsets },
|
|
1816
|
+
else => error.UnknownArchitecture,
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
pub fn parse_macho(buffer: []const u8) !HeaderBodyOffsets {
|
|
1821
|
+
if (@sizeOf(std.macho.fat_header) > buffer.len) return error.InvalidMacho;
|
|
1822
|
+
const fat_header = std.mem.bytesAsValue(
|
|
1823
|
+
std.macho.fat_header,
|
|
1824
|
+
buffer[0..@sizeOf(std.macho.fat_header)],
|
|
1825
|
+
);
|
|
1826
|
+
if (fat_header.magic != std.macho.FAT_CIGAM) return error.InvalidMachoMagic;
|
|
1827
|
+
if (@byteSwap(fat_header.nfat_arch) != 6) return error.InvalidMachoArches;
|
|
1828
|
+
|
|
1829
|
+
var header_offset_aarch64: ?u32 = null;
|
|
1830
|
+
var header_offset_x86_64: ?u32 = null;
|
|
1831
|
+
var body_offset_aarch64: ?u32 = null;
|
|
1832
|
+
var body_offset_x86_64: ?u32 = null;
|
|
1833
|
+
var body_size_aarch64: ?u32 = null;
|
|
1834
|
+
var body_size_x86_64: ?u32 = null;
|
|
1835
|
+
for (0..6) |i| {
|
|
1836
|
+
const offset = @sizeOf(std.macho.fat_header) + @sizeOf(std.macho.fat_arch) * i;
|
|
1837
|
+
if (offset + @sizeOf(std.macho.fat_arch) > buffer.len) return error.InvalidMacho;
|
|
1838
|
+
const fat_arch = std.mem.bytesAsValue(
|
|
1839
|
+
std.macho.fat_arch,
|
|
1840
|
+
buffer[offset..][0..@sizeOf(std.macho.fat_arch)],
|
|
1841
|
+
);
|
|
1842
|
+
const fat_arch_cpu_type = @byteSwap(fat_arch.cputype);
|
|
1843
|
+
|
|
1844
|
+
switch (fat_arch_cpu_type) {
|
|
1845
|
+
@intFromEnum(section_to_macho_cpu.tb_mvb_aarch64) => {
|
|
1846
|
+
assert(body_offset_aarch64 == null and body_size_aarch64 == null);
|
|
1847
|
+
body_offset_aarch64 = @byteSwap(fat_arch.offset);
|
|
1848
|
+
body_size_aarch64 = @byteSwap(fat_arch.size);
|
|
1849
|
+
},
|
|
1850
|
+
@intFromEnum(section_to_macho_cpu.tb_mvh_aarch64) => {
|
|
1851
|
+
assert(header_offset_aarch64 == null);
|
|
1852
|
+
header_offset_aarch64 = @byteSwap(fat_arch.offset);
|
|
1853
|
+
},
|
|
1854
|
+
@intFromEnum(section_to_macho_cpu.tb_mvb_x86_64) => {
|
|
1855
|
+
assert(body_offset_x86_64 == null and body_size_x86_64 == null);
|
|
1856
|
+
body_offset_x86_64 = @byteSwap(fat_arch.offset);
|
|
1857
|
+
body_size_x86_64 = @byteSwap(fat_arch.size);
|
|
1858
|
+
},
|
|
1859
|
+
@intFromEnum(section_to_macho_cpu.tb_mvh_x86_64) => {
|
|
1860
|
+
assert(header_offset_x86_64 == null);
|
|
1861
|
+
header_offset_x86_64 = @byteSwap(fat_arch.offset);
|
|
1862
|
+
},
|
|
1863
|
+
else => {},
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
if (header_offset_aarch64 == null or body_offset_aarch64 == null) {
|
|
1868
|
+
return error.MultiversionHeaderOrBodyNotFound;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
if (header_offset_x86_64 == null or body_offset_x86_64 == null) {
|
|
1872
|
+
return error.MultiversionHeaderOrBodyNotFound;
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
if (body_offset_aarch64.? + body_size_aarch64.? > header_offset_aarch64.?) {
|
|
1876
|
+
return error.MultiversionBodyOverlapsHeader;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
if (body_offset_x86_64.? + body_size_x86_64.? > header_offset_x86_64.?) {
|
|
1880
|
+
return error.MultiversionBodyOverlapsHeader;
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
return .{
|
|
1884
|
+
.format = .macho,
|
|
1885
|
+
.aarch64 = .{
|
|
1886
|
+
.header_offset = header_offset_aarch64.?,
|
|
1887
|
+
.body_offset = body_offset_aarch64.?,
|
|
1888
|
+
.body_size = body_size_aarch64.?,
|
|
1889
|
+
},
|
|
1890
|
+
.x86_64 = .{
|
|
1891
|
+
.header_offset = header_offset_x86_64.?,
|
|
1892
|
+
.body_offset = body_offset_x86_64.?,
|
|
1893
|
+
.body_size = body_size_x86_64.?,
|
|
1894
|
+
},
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
pub fn parse_pe(buffer: []const u8) !HeaderBodyOffsets {
|
|
1899
|
+
const coff = try std.coff.Coff.init(buffer, false);
|
|
1900
|
+
|
|
1901
|
+
if (!coff.is_image) return error.InvalidPE;
|
|
1902
|
+
|
|
1903
|
+
const header_section = coff.getSectionByName(".tb_mvh");
|
|
1904
|
+
const body_section = coff.getSectionByName(".tb_mvb");
|
|
1905
|
+
|
|
1906
|
+
if (header_section == null) return error.MultiversionHeaderOrBodyNotFound;
|
|
1907
|
+
if (body_section == null) return error.MultiversionHeaderOrBodyNotFound;
|
|
1908
|
+
|
|
1909
|
+
const header_offset = header_section.?.pointer_to_raw_data;
|
|
1910
|
+
const body_offset = body_section.?.pointer_to_raw_data;
|
|
1911
|
+
const body_size = body_section.?.size_of_raw_data;
|
|
1912
|
+
|
|
1913
|
+
if (body_offset + body_size > header_offset) {
|
|
1914
|
+
return error.MultiversionBodyOverlapsHeader;
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
const offsets: HeaderBodyOffsets.Offsets = .{
|
|
1918
|
+
.header_offset = header_offset,
|
|
1919
|
+
.body_offset = body_offset,
|
|
1920
|
+
.body_size = body_size,
|
|
1921
|
+
};
|
|
1922
|
+
|
|
1923
|
+
return switch (coff.getCoffHeader().machine) {
|
|
1924
|
+
.ARM64 => .{ .format = .pe, .aarch64 = offsets, .x86_64 = null },
|
|
1925
|
+
.X64 => .{ .format = .pe, .aarch64 = null, .x86_64 = offsets },
|
|
1926
|
+
else => error.UnknownArchitecture,
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
fn expect_any_error(actual_error_union: anytype) !void {
|
|
1931
|
+
if (actual_error_union) |_| return error.TestUnexpectedError else |_| {}
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
const test_elf_name_length_max = 10;
|
|
1935
|
+
|
|
1936
|
+
fn test_elf_build_header(buffer: []align(8) u8) !*elf.Elf64_Ehdr {
|
|
1937
|
+
try expect_any_error(parse_elf(buffer));
|
|
1938
|
+
|
|
1939
|
+
const elf_header: *elf.Elf64_Ehdr = std.mem.bytesAsValue(
|
|
1940
|
+
elf.Elf64_Ehdr,
|
|
1941
|
+
buffer[0..@sizeOf(elf.Elf64_Ehdr)],
|
|
1942
|
+
);
|
|
1943
|
+
|
|
1944
|
+
stdx.copy_disjoint(.exact, u8, elf_header.e_ident[0..4], elf.MAGIC);
|
|
1945
|
+
try expect_any_error(parse_elf(buffer));
|
|
1946
|
+
|
|
1947
|
+
elf_header.e_ident[elf.EI_VERSION] = 1;
|
|
1948
|
+
try expect_any_error(parse_elf(buffer));
|
|
1949
|
+
elf_header.e_ident[elf.EI_DATA] = elf.ELFDATA2LSB;
|
|
1950
|
+
try expect_any_error(parse_elf(buffer));
|
|
1951
|
+
elf_header.e_ident[elf.EI_CLASS] = elf.ELFCLASS64;
|
|
1952
|
+
try expect_any_error(parse_elf(buffer));
|
|
1953
|
+
|
|
1954
|
+
elf_header.e_machine = elf.EM.X86_64;
|
|
1955
|
+
try expect_any_error(parse_elf(buffer));
|
|
1956
|
+
elf_header.e_shnum = 4;
|
|
1957
|
+
try expect_any_error(parse_elf(buffer));
|
|
1958
|
+
elf_header.e_shoff = 8192;
|
|
1959
|
+
try expect_any_error(parse_elf(buffer));
|
|
1960
|
+
elf_header.e_shstrndx = 1;
|
|
1961
|
+
try expect_any_error(parse_elf(buffer));
|
|
1962
|
+
|
|
1963
|
+
return elf_header;
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
fn test_elf_build_string_table(buffer: []align(8) u8, elf_header: *elf.Elf64_Ehdr) ![]u8 {
|
|
1967
|
+
const string_table_elf_section_header_offset: u64 = elf_header.e_shoff +
|
|
1968
|
+
@as(u64, @sizeOf(elf.Elf64_Shdr)) * elf_header.e_shstrndx;
|
|
1969
|
+
|
|
1970
|
+
const string_table_elf_section_header = std.mem.bytesAsValue(
|
|
1971
|
+
elf.Elf64_Shdr,
|
|
1972
|
+
buffer[string_table_elf_section_header_offset..][0..@sizeOf(elf.Elf64_Shdr)],
|
|
1973
|
+
);
|
|
1974
|
+
|
|
1975
|
+
string_table_elf_section_header.sh_type = elf.SHT_STRTAB;
|
|
1976
|
+
try expect_any_error(parse_elf(buffer));
|
|
1977
|
+
|
|
1978
|
+
string_table_elf_section_header.sh_size = test_elf_name_length_max * elf_header.e_shnum;
|
|
1979
|
+
try expect_any_error(parse_elf(buffer));
|
|
1980
|
+
|
|
1981
|
+
string_table_elf_section_header.sh_offset = 300;
|
|
1982
|
+
try expect_any_error(parse_elf(buffer));
|
|
1983
|
+
|
|
1984
|
+
string_table_elf_section_header.sh_name = @intCast(string_table_elf_section_header.sh_size - 1);
|
|
1985
|
+
|
|
1986
|
+
const string_table_size = string_table_elf_section_header.sh_size;
|
|
1987
|
+
const string_table = buffer[string_table_elf_section_header.sh_offset..][0..string_table_size];
|
|
1988
|
+
string_table[string_table_elf_section_header.sh_size - 1] = 0;
|
|
1989
|
+
try expect_any_error(parse_elf(buffer));
|
|
1990
|
+
|
|
1991
|
+
return string_table;
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
fn test_elf_build_section(
|
|
1995
|
+
buffer: []align(8) u8,
|
|
1996
|
+
string_table: []u8,
|
|
1997
|
+
elf_header: *elf.Elf64_Ehdr,
|
|
1998
|
+
index: u32,
|
|
1999
|
+
name: []const u8,
|
|
2000
|
+
) !*align(1) elf.Elf64_Shdr {
|
|
2001
|
+
assert(name.len < test_elf_name_length_max);
|
|
2002
|
+
assert(index < elf_header.e_shnum);
|
|
2003
|
+
|
|
2004
|
+
const offset: u64 = elf_header.e_shoff + @as(u64, @sizeOf(elf.Elf64_Shdr)) * index;
|
|
2005
|
+
const elf_section_header = std.mem.bytesAsValue(
|
|
2006
|
+
elf.Elf64_Shdr,
|
|
2007
|
+
buffer[offset..][0..@sizeOf(elf.Elf64_Shdr)],
|
|
2008
|
+
);
|
|
2009
|
+
elf_section_header.sh_name = test_elf_name_length_max * index;
|
|
2010
|
+
try expect_any_error(parse_elf(buffer));
|
|
2011
|
+
|
|
2012
|
+
stdx.copy_disjoint(.inexact, u8, string_table[elf_section_header.sh_name..], name);
|
|
2013
|
+
try expect_any_error(parse_elf(buffer));
|
|
2014
|
+
string_table[elf_section_header.sh_name..][name.len] = 0;
|
|
2015
|
+
try expect_any_error(parse_elf(buffer));
|
|
2016
|
+
elf_section_header.sh_offset = 8192 * index;
|
|
2017
|
+
try expect_any_error(parse_elf(buffer));
|
|
2018
|
+
|
|
2019
|
+
return elf_section_header;
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
// Not quite a fuzzer, but build up an ELF, checking that there's an error after each step, with a
|
|
2023
|
+
// full range of values is the undefined intermediate bits.
|
|
2024
|
+
test parse_elf {
|
|
2025
|
+
var buffer: [32768]u8 align(8) = undefined;
|
|
2026
|
+
for (0..256) |i| {
|
|
2027
|
+
@memset(&buffer, @as(u8, @intCast(i)));
|
|
2028
|
+
|
|
2029
|
+
const elf_header = try test_elf_build_header(&buffer);
|
|
2030
|
+
const string_table = try test_elf_build_string_table(&buffer, elf_header);
|
|
2031
|
+
|
|
2032
|
+
// The string table can't be 0, and the .tb_mvb and .tb_mvh sections need to be the second
|
|
2033
|
+
// last and last sections in the file respectively. Pad the 0 index with a no-op section.
|
|
2034
|
+
_ = try test_elf_build_section(&buffer, string_table, elf_header, 0, ".tb_nop");
|
|
2035
|
+
|
|
2036
|
+
const section_mvb = try test_elf_build_section(
|
|
2037
|
+
&buffer,
|
|
2038
|
+
string_table,
|
|
2039
|
+
elf_header,
|
|
2040
|
+
2,
|
|
2041
|
+
".tb_mvb",
|
|
2042
|
+
);
|
|
2043
|
+
// So it overlaps on purpose, to check the MultiversionBodyOverlapsHeader assert.
|
|
2044
|
+
section_mvb.sh_size = 16384;
|
|
2045
|
+
|
|
2046
|
+
const section_mvh = try test_elf_build_section(
|
|
2047
|
+
&buffer,
|
|
2048
|
+
string_table,
|
|
2049
|
+
elf_header,
|
|
2050
|
+
3,
|
|
2051
|
+
".tb_mvh",
|
|
2052
|
+
);
|
|
2053
|
+
section_mvh.sh_size = 8192; // @sizeOf(MultiversionHeader), but hardcoded.
|
|
2054
|
+
|
|
2055
|
+
try std.testing.expectError(error.MultiversionBodyOverlapsHeader, parse_elf(&buffer));
|
|
2056
|
+
|
|
2057
|
+
section_mvb.sh_size = 8192;
|
|
2058
|
+
const parsed = try parse_elf(&buffer);
|
|
2059
|
+
|
|
2060
|
+
assert(parsed.x86_64.?.body_offset == 16384);
|
|
2061
|
+
assert(parsed.x86_64.?.header_offset == 24576);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
pub fn print_information(
|
|
2066
|
+
gpa: std.mem.Allocator,
|
|
2067
|
+
exe_path: []const u8,
|
|
2068
|
+
output: std.io.AnyWriter,
|
|
2069
|
+
) !void {
|
|
2070
|
+
var io = try IO.init(32, 0);
|
|
2071
|
+
defer io.deinit();
|
|
2072
|
+
|
|
2073
|
+
const absolute_exe_path = try std.fs.cwd().realpathAlloc(gpa, exe_path);
|
|
2074
|
+
defer gpa.free(absolute_exe_path);
|
|
2075
|
+
|
|
2076
|
+
const absolute_exe_path_z = try gpa.dupeZ(u8, absolute_exe_path);
|
|
2077
|
+
defer gpa.free(absolute_exe_path_z);
|
|
2078
|
+
|
|
2079
|
+
var multiversion = try MultiversionOS.init(
|
|
2080
|
+
gpa,
|
|
2081
|
+
&io,
|
|
2082
|
+
absolute_exe_path_z,
|
|
2083
|
+
.detect,
|
|
2084
|
+
);
|
|
2085
|
+
defer multiversion.deinit(gpa);
|
|
2086
|
+
|
|
2087
|
+
multiversion.open_sync() catch |err| {
|
|
2088
|
+
try output.print("multiversioning not enabled: {}\n", .{err});
|
|
2089
|
+
return err;
|
|
2090
|
+
};
|
|
2091
|
+
|
|
2092
|
+
assert(multiversion.stage == .ready);
|
|
2093
|
+
|
|
2094
|
+
try output.print("multiversioning.exe_path={s}\n", .{exe_path});
|
|
2095
|
+
try output.print("multiversioning.absolute_exe_path={s}\n", .{absolute_exe_path});
|
|
2096
|
+
|
|
2097
|
+
const header = multiversion.target_header.?;
|
|
2098
|
+
|
|
2099
|
+
// `source_buffer` contains the same data as `target_file` - this code doesn't update anything
|
|
2100
|
+
// after the initial open_sync().
|
|
2101
|
+
const target_body_size = multiversion.target_body_size.?; // Line length limits.
|
|
2102
|
+
try header.past.verify_checksums(
|
|
2103
|
+
multiversion.source_buffer[multiversion.target_body_offset.?..][0..target_body_size],
|
|
2104
|
+
);
|
|
2105
|
+
|
|
2106
|
+
try output.print(
|
|
2107
|
+
"multiversioning.releases_bundled={any}\n",
|
|
2108
|
+
.{multiversion.releases_bundled.slice()},
|
|
2109
|
+
);
|
|
2110
|
+
|
|
2111
|
+
inline for (
|
|
2112
|
+
comptime std.enums.values(std.meta.FieldEnum(MultiversionHeader)),
|
|
2113
|
+
) |field| {
|
|
2114
|
+
const field_name = @tagName(field);
|
|
2115
|
+
switch (field) {
|
|
2116
|
+
.past, .current_flags_padding, .past_padding, .reserved => continue,
|
|
2117
|
+
.current_git_commit => {
|
|
2118
|
+
try output.print("multiversioning.header.{s}={s}\n", .{
|
|
2119
|
+
field_name,
|
|
2120
|
+
std.fmt.fmtSliceHexLower(&header.current_git_commit),
|
|
2121
|
+
});
|
|
2122
|
+
},
|
|
2123
|
+
.current_release, .current_release_client_min => {
|
|
2124
|
+
try output.print("multiversioning.header.{s}={any}\n", .{
|
|
2125
|
+
field_name,
|
|
2126
|
+
Release{ .value = @field(header, field_name) },
|
|
2127
|
+
});
|
|
2128
|
+
},
|
|
2129
|
+
.checksum_header,
|
|
2130
|
+
.checksum_binary_without_header,
|
|
2131
|
+
.current_checksum,
|
|
2132
|
+
.schema_version,
|
|
2133
|
+
.vsr_releases_max,
|
|
2134
|
+
.current_flags,
|
|
2135
|
+
=> {
|
|
2136
|
+
try output.print("multiversioning.header.{s}={any}\n", .{
|
|
2137
|
+
field_name,
|
|
2138
|
+
@field(header, field_name),
|
|
2139
|
+
});
|
|
2140
|
+
},
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
try output.print("multiversioning.header.past.count={}\n", .{header.past.count});
|
|
2145
|
+
inline for (
|
|
2146
|
+
comptime std.enums.values(std.meta.FieldEnum(MultiversionHeader.PastReleases)),
|
|
2147
|
+
) |field| {
|
|
2148
|
+
const field_name = @tagName(field);
|
|
2149
|
+
switch (field) {
|
|
2150
|
+
.count, .flags_padding => {},
|
|
2151
|
+
.releases, .release_client_mins => {
|
|
2152
|
+
comptime assert(@sizeOf(Release) ==
|
|
2153
|
+
@sizeOf(@TypeOf(@field(header.past, field_name)[0])));
|
|
2154
|
+
const release_list: []const Release =
|
|
2155
|
+
@ptrCast(@field(header.past, field_name)[0..header.past.count]);
|
|
2156
|
+
|
|
2157
|
+
try output.print("multiversioning.header.past.{s}={any}\n", .{
|
|
2158
|
+
field_name,
|
|
2159
|
+
release_list,
|
|
2160
|
+
});
|
|
2161
|
+
},
|
|
2162
|
+
.git_commits => {
|
|
2163
|
+
for (@field(header.past, field_name)[0..header.past.count], 0..) |*git_commit, i| {
|
|
2164
|
+
try output.print("multiversioning.header.past.{s}.{}={}\n", .{
|
|
2165
|
+
field_name,
|
|
2166
|
+
Release{ .value = header.past.releases[i] },
|
|
2167
|
+
std.fmt.fmtSliceHexLower(git_commit),
|
|
2168
|
+
});
|
|
2169
|
+
}
|
|
2170
|
+
},
|
|
2171
|
+
.checksums, .offsets, .sizes, .flags => {
|
|
2172
|
+
try output.print("multiversioning.header.past.{s}={any}\n", .{
|
|
2173
|
+
field_name,
|
|
2174
|
+
@field(header.past, field_name)[0..header.past.count],
|
|
2175
|
+
});
|
|
2176
|
+
},
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
/// This is not exhaustive, but should be good enough for 99.95% of the modern systems we support.
|
|
2182
|
+
/// Caller owns returned memory.
|
|
2183
|
+
fn system_temporary_directory(allocator: std.mem.Allocator) ![]const u8 {
|
|
2184
|
+
switch (builtin.os.tag) {
|
|
2185
|
+
.linux, .macos => {
|
|
2186
|
+
return std.process.getEnvVarOwned(allocator, "TMPDIR") catch allocator.dupe(u8, "/tmp");
|
|
2187
|
+
},
|
|
2188
|
+
.windows => {
|
|
2189
|
+
return std.process.getEnvVarOwned(allocator, "TMP") catch
|
|
2190
|
+
std.process.getEnvVarOwned(allocator, "TEMP") catch
|
|
2191
|
+
allocator.dupe(u8, "C:\\Windows\\Temp");
|
|
2192
|
+
},
|
|
2193
|
+
else => @panic("unsupported platform"),
|
|
2194
|
+
}
|
|
2195
|
+
}
|