Release 2.1.3: Fix disk backup corruption, solid PQ encryption, block I/O, and format mismatches

- Corrected LZHP prediction encoding in disk backups to prevent data corruption
- Disabled spurious SOLID flag for per-block disk archives
- Shared write_enc_header() across all encryption paths to eliminate format mismatches
- Enabled solid compression with PQ encryption support
- Updated block device restore to use O_SYNC + fsync/sync
- Improved Termux/Android host detection for safer builds
- Made zupt_w8(), zupt_w16le(), zupt_w64le() non-static for shared use
This commit is contained in:
Cristian Cezar Moisés 2026-04-11 15:59:59 -03:00
commit 28b2744c67
39 changed files with 324 additions and 330 deletions

View file

@ -5,6 +5,32 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
---
## [2.1.3] — 2026-04-11
### Fixed — LZHP Prediction Encoding Missing in Disk Backup (data corruption)
- **Root cause:** `zupt_disk_backup()` LZHP compression path skipped the `zupt_predict_encode()` step. When byte prediction was active (`pred_active=1`), it stored the prediction table and wrote `cbuf[0] = 0x01`, but then compressed the **raw block data** instead of the prediction-encoded data. On restore, `decompress_block()` correctly applied `zupt_predict_decode()` to the decompressed output, producing corrupted data. Checksum mismatch on block 0 for any block with structured content (ext4 metadata, NTFS headers, partition tables).
- **Impact:** ALL disk backups using LZHP codec (default on CPUs without AVX2) on non-random data were silently corrupted. VaptVupt codec was unaffected (no prediction path). Random/incompressible data was unaffected (prediction benefit < threshold → `pred_active=0`).
- **Fix:** Added `zupt_predict_encode(rbuf, transformed, nread, pred)` before `zupt_lzh_compress()`, matching the correct path in `zupt_format.c` (lines 557563). Allocated temporary buffer for prediction-encoded data, freed after compression.
### Fixed — Spurious SOLID Flag on Disk Archives
- Disk backup no longer sets `ZUPT_FLAG_SOLID` in the archive header. Disk images are independent per-block archives, not solid streams. The SOLID flag caused `zupt_extract_archive()` to take the wrong code path if a disk archive was ever parsed by the extract function.
### Fixed — Shared Encryption Header (eliminates all format mismatches)
- **Extracted `write_enc_header()`** from `zupt_format.c` as a shared non-static function. ALL three encryption write paths — `zupt_compress_files()`, `zupt_compress_solid()`, and `zupt_disk_backup()` — now call the same function.
- **Solid compress now supports PQ encryption.**
- **`zupt_w8()`, `zupt_w16le()`, `zupt_w64le()`** made non-static and declared in `zupt.h`.
### Fixed — Block Device Restore I/O
- Restore uses POSIX raw I/O (`open()` + `write()` loop) with `O_SYNC` for block devices, `fsync()` + `sync()` before close.
### Fixed — Termux/Android Build
- Arch-safety guard uses `$(CC) -dumpmachine` for host detection. Falls back to `uname -m`.
### Tests
- **78 total:** 70 core + 8 disk (including LZHP+PQ+password on ext4 — the exact failing case). ASAN + UBSan clean.
---
## [2.1.2] — 2026-04-06
### Added — Full-Disk Backup/Restore
@ -273,6 +299,7 @@ All 4 `.jazz` files rewritten to fix compilation errors:
| Version | Key Change | Tests |
|---------|-----------|-------|
| **2.1.3** | Shared `write_enc_header()` eliminates all format mismatches, solid PQ support, block device O_SYNC. Disk restore rewritten — uses shared block I/O, fixes checksum mismatch with all encryption formats | 77 PASS |
| **2.1.2** | Full-disk backup/restore with sparse detection, all encryption modes, progress bar | 77 PASS |
| **2.1.1** | Termux/Android build fix, arch-safety guard, Keccak UB fix, no stale .o in tarballs | 70 PASS |
| **2.1.0** | VaptVupt 1.4.0: cross-block dictionary, context prefetch, faster adaptive window, integration API | 70 PASS |

View file

@ -118,22 +118,32 @@ ALL_OBJS = $(ZUPT_OBJS) $(VV_SIMD_OBJS) $(VV_PLAIN_OBJS)
# If pre-compiled .o files from a different architecture are present
# (e.g. x86_64 .o files in an aarch64 build), the linker will fail
# with "incompatible with <arch>". Detect and remove stale objects.
# This happens when tarballs accidentally include build artifacts.
# This happens when tarballs accidentally include build artifacts,
# or when the same source tree is shared between different machines.
#
# Detection: uses $(CC) -dumpmachine which works on ALL platforms
# including Termux (where /bin/sh does not exist).
# ═══════════════════════════════════════════════════════════════════
STALE_OBJS := $(wildcard src/*.o jasmin/*.o)
ifneq ($(STALE_OBJS),)
# Check if any existing .o is for the wrong architecture
FIRST_OBJ := $(firstword $(STALE_OBJS))
OBJ_ARCH := $(shell file $(FIRST_OBJ) 2>/dev/null | grep -oE 'x86.64|aarch64|ARM|PowerPC|S/390|RISC-V' | head -1)
HOST_ARCH := $(shell file /bin/sh 2>/dev/null | grep -oE 'x86.64|aarch64|ARM|PowerPC|S/390|RISC-V' | head -1)
ifneq ($(OBJ_ARCH),$(HOST_ARCH))
OBJ_ARCH := $(shell file $(FIRST_OBJ) 2>/dev/null | grep -oiE 'x86.64|aarch64|arm|powerpc|s390|riscv' | head -1)
HOST_TRIPLE := $(shell $(CC) -dumpmachine 2>/dev/null)
HOST_ARCH_CC := $(shell echo "$(HOST_TRIPLE)" | grep -oiE 'x86.64|aarch64|arm|powerpc|s390|riscv' | head -1)
# Fallback: try uname -m if CC -dumpmachine fails
ifeq ($(HOST_ARCH_CC),)
HOST_ARCH_CC := $(shell uname -m 2>/dev/null | grep -oiE 'x86.64|aarch64|arm|powerpc|s390|riscv' | head -1)
endif
ifneq ($(OBJ_ARCH),)
$(info [arch] Removing stale $(OBJ_ARCH) objects for $(HOST_ARCH) build)
ifneq ($(HOST_ARCH_CC),)
ifneq ($(OBJ_ARCH),$(HOST_ARCH_CC))
$(info [arch] Removing stale $(OBJ_ARCH) objects for $(HOST_ARCH_CC) build)
$(shell rm -f src/*.o jasmin/*.o)
endif
endif
endif
endif
# ═══════════════════════════════════════════════════════════════════
# BUILD RULES

View file

@ -4,7 +4,7 @@
![Build](https://img.shields.io/badge/build-passing-brightgreen)
![License](https://img.shields.io/badge/license-MIT-blue)
![Version](https://img.shields.io/badge/version-2.1.2-orange)
![Version](https://img.shields.io/badge/version-2.1.3-orange)
![Platform](https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20Windows-lightgrey)
![openSUSE](https://img.shields.io/badge/platform-openSUSE-73BA25?logo=opensuse&logoColor=white)
@ -372,11 +372,11 @@ All codecs are forward-compatible: archives created with any codec can be read b
| v1.1v1.4 | X25519 fix, NIST vectors, CPUID detection, Jasmin source files fixed |
| v1.5 | Jasmin CT assembly linked (MAC verify + ML-KEM select active) |
| v1.5.5 | Man page install, V=1 verbose, LDFLAGS/PIE, rpmlint, multi-arch Makefile |
| v2.0 | VaptVupt 1.1.0 codec with auto hardware detection, all 5 Jasmin functions wired, AVX SIGILL fix, copy_match/litlen overflow fixes, ACSL proofs, mlock, fuzzing, canaries, AES-NI 4-block pipeline, MT decompression, adaptive compression, multi-architecture support (6 arches), --lzhp flag |
| v2.1 | VaptVupt 1.4.0: cross-block dictionary carry, context decode prefetch, faster adaptive window trial (2.6× encode), integration API |
| v2.1.1 | Termux/Android build fix, arch-safety guard removes stale cross-arch .o, Keccak ROL64 UB fix, zero UBSan violations |
| **v2.1.2** | **Full-disk backup/restore (`zupt disk`), sparse detection, all encryption modes, per-block XXH64 verification, progress bar, 77 tests** |
| v2.1.3 | Wait for it. |
| v2.0 | VaptVupt 1.1.0 codec, auto hardware detection, all 5 Jasmin wired, AVX SIGILL fix, copy_match/litlen fixes, ACSL, mlock, fuzzing, canaries, AES-NI pipeline, MT decompress, multi-arch (6 arches), --lzhp flag |
| v2.1.0 | VaptVupt 1.4.0: cross-block dictionary carry, context decode prefetch, faster adaptive window (2.6× encode), integration API |
| v2.1.1 | Termux/Android build fix, arch-safety guard, Keccak ROL64 UB fix, zero UBSan violations |
| v2.1.2 | Full-disk backup/restore (`zupt disk`), sparse detection, all encryption modes, progress bar |
| **v2.1.3** | **Disk restore fix (POSIX raw I/O + O_SYNC for block devices, shared decompress_block), Termux build fix (CC -dumpmachine arch detection). LZHP prediction encoding fix (data corruption on structured data), shared write_enc_header, SOLID flag removed from disk, 78 tests** |
See [CHANGELOG.md](CHANGELOG.md) for detailed per-version changes.

View file

@ -21,7 +21,9 @@
| **v2.0** | **✅** | **VaptVupt 1.1.0 codec with auto hardware detection, all 5 Jasmin wired, AVX SIGILL fix, copy_match/litlen fixes, ACSL, mlock, fuzzing, canaries, AES-NI pipeline, MT decompress, multi-arch (6 arches)** |
| **v2.1** | **✅** | **VaptVupt 1.4.0: cross-block dictionary, context prefetch, faster adaptive window, integration API** |
| **v2.1.1** | **✅** | **Termux/Android build fix, arch-safety guard, Keccak UB fix, no stale .o in tarballs** |
| **v2.1.2** | **✅ Current** | **Full-disk backup/restore with sparse detection, all encryption modes, progress bar, 77 tests** |
| **v2.1.2** | **✅** | **Full-disk backup/restore with sparse detection, all encryption modes, progress bar, 77 tests** |
| **v2.1.3** | **✅** | **Disk restore rewritten — shared block I/O, fixes checksum mismatch on encrypted/PQ archives** |
| **v2.1.3** | **✅ Current** | **LZHP prediction encoding fix, shared write_enc_header, SOLID flag removed from disk, block device O_SYNC, 78 tests** |
## Planned

View file

@ -30,7 +30,7 @@
#define zupt_mkdir(p) mkdir(p, 0755)
#endif
#define ZUPT_VERSION_STRING "2.1.2"
#define ZUPT_VERSION_STRING "2.1.3"
#define ZUPT_FORMAT_MAJOR 1
#define ZUPT_FORMAT_MINOR 4
@ -347,4 +347,15 @@ zupt_error_t zupt_disk_backup(const char *output_path, const char *source_path,
zupt_error_t zupt_disk_restore(const char *archive_path, const char *target_path,
zupt_options_t *opts);
/* ─── Internal Block I/O (used by format + disk modules) ─── */
zupt_error_t read_block(FILE *f, zupt_block_t *b);
zupt_error_t read_enc_header(FILE *f, zupt_archive_header_t *hdr, zupt_options_t *opts);
zupt_error_t decompress_block(const zupt_block_t *b, const zupt_keyring_t *kr,
uint64_t block_seq, uint8_t **out, size_t *olen);
zupt_error_t write_enc_header(FILE *out, zupt_archive_header_t *hdr,
zupt_options_t *opts);
int zupt_w8(FILE *f, uint8_t v);
int zupt_w16le(FILE *f, uint16_t v);
int zupt_w64le(FILE *f, uint64_t v);
#endif

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,5 +1,5 @@
/*
* Zupt v2.1.2 Full-Disk Backup/Restore
* Zupt v2.1.3 Full-Disk Backup/Restore
* Copyright (c) 2026 Cristian Cezar Moisés MIT License
*
* Reads a raw block device or file, compresses in streaming chunks,
@ -224,7 +224,7 @@ zupt_error_t zupt_disk_backup(const char *output_path, const char *source_path,
hdr.magic[4] = ZUPT_MAGIC_4; hdr.magic[5] = ZUPT_MAGIC_5;
hdr.version_major = ZUPT_FORMAT_MAJOR;
hdr.version_minor = ZUPT_FORMAT_MINOR;
hdr.global_flags = ZUPT_FLAG_CKSUM_XXH64 | ZUPT_FLAG_SOLID | ZUPT_FLAG_DISK_IMAGE;
hdr.global_flags = ZUPT_FLAG_CKSUM_XXH64 | ZUPT_FLAG_DISK_IMAGE;
if (opts->encrypt) hdr.global_flags |= ZUPT_FLAG_ENCRYPTED;
if (opts->threads > 1) hdr.global_flags |= ZUPT_FLAG_MULTITHREADED;
hdr.creation_time = (uint64_t)time(NULL) * 1000000000ULL;
@ -234,67 +234,12 @@ zupt_error_t zupt_disk_backup(const char *output_path, const char *source_path,
if (fwrite(&hdr, sizeof(hdr), 1, out) != 1) write_err = 1;
/* ─── Encryption header ─── */
/* ─── Encryption header (uses same code as zupt compress) ─── */
if (opts->encrypt) {
hdr.encryption_header_off = (uint64_t)ftello(out);
if (opts->pq_mode) {
uint8_t enc_hdr_buf[1200];
size_t enc_hdr_len = 0;
fprintf(stderr, " Post-quantum key encapsulation (ML-KEM-768 + X25519)...\n");
if (zupt_hybrid_encrypt_init(&opts->keyring, opts->keyfile,
enc_hdr_buf, &enc_hdr_len) != 0) {
fprintf(stderr, "Error: PQ hybrid key encapsulation failed.\n");
zupt_error_t enc_err = write_enc_header(out, &hdr, opts);
if (enc_err != ZUPT_OK) {
fclose(src_f); fclose(out);
return ZUPT_ERR_AUTH_FAIL;
}
/* Write encryption block */
uint8_t bm0 = 0xBB, bm1 = 0x01, bt = ZUPT_BLOCK_ENC_HEADER;
fwrite(&bm0, 1, 1, out); fwrite(&bm1, 1, 1, out); fwrite(&bt, 1, 1, out);
uint8_t cs[2] = {0, 0}; fwrite(cs, 1, 2, out); fwrite(cs, 1, 2, out);
zupt_write_varint(out, enc_hdr_len);
zupt_write_varint(out, enc_hdr_len);
uint64_t ck = zupt_xxh64(enc_hdr_buf, enc_hdr_len, 0);
uint8_t ck8[8]; for (int i = 0; i < 8; i++) ck8[i] = (uint8_t)(ck >> (i*8));
fwrite(ck8, 1, 8, out);
fwrite(enc_hdr_buf, 1, enc_hdr_len, out);
hdr.global_flags |= ZUPT_FLAG_PQ_HYBRID;
fseeko(out, 0, SEEK_SET);
fwrite(&hdr, sizeof(hdr), 1, out);
fseeko(out, 0, SEEK_END);
fprintf(stderr, " Encryption: PQ Hybrid (ML-KEM-768 + X25519)\n\n");
} else {
uint8_t salt[ZUPT_SALT_SIZE], nonce[ZUPT_NONCE_SIZE];
zupt_random_bytes(salt, ZUPT_SALT_SIZE);
zupt_random_bytes(nonce, ZUPT_NONCE_SIZE);
fprintf(stderr, " Deriving encryption key (PBKDF2-SHA256, %d iterations)...\n",
ZUPT_KDF_ITERATIONS);
zupt_derive_keys(&opts->keyring, opts->password, salt, nonce, ZUPT_KDF_ITERATIONS);
uint8_t enc_hdr[53];
enc_hdr[0] = 0x01; /* ZUPT_ENC_PBKDF2 */
memcpy(enc_hdr + 1, salt, 32);
memcpy(enc_hdr + 33, nonce, 16);
uint32_t iter = ZUPT_KDF_ITERATIONS;
memcpy(enc_hdr + 49, &iter, 4);
uint8_t bm0 = 0xBB, bm1 = 0x01, bt = ZUPT_BLOCK_ENC_HEADER;
fwrite(&bm0, 1, 1, out); fwrite(&bm1, 1, 1, out); fwrite(&bt, 1, 1, out);
uint8_t cs[2] = {0, 0}; fwrite(cs, 1, 2, out); fwrite(cs, 1, 2, out);
zupt_write_varint(out, 53); zupt_write_varint(out, 53);
uint64_t ck = zupt_xxh64(enc_hdr, 53, 0);
uint8_t ck8[8]; for (int i = 0; i < 8; i++) ck8[i] = (uint8_t)(ck >> (i*8));
fwrite(ck8, 1, 8, out);
fwrite(enc_hdr, 1, 53, out);
fseeko(out, 0, SEEK_SET);
fwrite(&hdr, sizeof(hdr), 1, out);
fseeko(out, 0, SEEK_END);
fprintf(stderr, " Encryption: AES-256-CTR + HMAC-SHA256\n\n");
return enc_err;
}
}
@ -346,17 +291,32 @@ zupt_error_t zupt_disk_backup(const char *output_path, const char *source_path,
if (csz > 0 && (size_t)csz < nread)
comp_size = (size_t)csz;
} else if (codec == ZUPT_CODEC_ZUPT_LZHP) {
/* LZHP with prediction */
/* LZHP with prediction — must encode through prediction
* table before compressing, matching zupt_format.c */
float benefit = zupt_predict_benefit(rbuf, nread);
if (benefit > 0.02f && nread > 256) {
uint8_t pred[256];
zupt_predict_build(rbuf, nread, pred);
uint8_t *transformed = (uint8_t *)malloc(nread);
if (transformed) {
zupt_predict_encode(rbuf, transformed, nread, pred);
size_t plain = zupt_lzh_compress(transformed, nread,
cbuf + 257,
comp_cap - 257, opts->level);
free(transformed);
if (plain > 0 && 257 + plain < nread) {
cbuf[0] = 0x01;
memcpy(cbuf + 1, pred, 256);
size_t plain = zupt_lzh_compress(rbuf, nread, cbuf + 257,
comp_cap - 257, opts->level);
if (plain > 0 && 257 + plain < nread)
comp_size = 257 + plain;
} else {
/* Prediction didn't help — fall back to plain LZH */
cbuf[0] = 0x00;
plain = zupt_lzh_compress(rbuf, nread, cbuf + 1,
comp_cap - 1, opts->level);
if (plain > 0 && 1 + plain < nread)
comp_size = 1 + plain;
}
}
} else {
cbuf[0] = 0x00;
size_t plain = zupt_lzh_compress(rbuf, nread, cbuf + 1,
@ -535,25 +495,37 @@ zupt_error_t zupt_disk_backup(const char *output_path, const char *source_path,
/* ═══════════════════════════════════════════════════════════════════
* DISK RESTORE (extract archive device/file)
*
* Uses the standard zupt_extract_archive path but writes to a single
* file (the target device) instead of creating a directory tree.
* For disk images, the archive contains exactly one index entry.
* Rewritten for v2.1.3: uses the same read_block() / read_enc_header() /
* decompress_block() functions as zupt_extract_archive(). This eliminates
* the hand-rolled block parser that caused checksum mismatches due to
* encryption header format differences (52-byte vs 53-byte) and seek
* offset errors.
*
* Flow:
* 1. Read archive header validate magic + DISK_IMAGE flag
* 2. Read encryption header (if encrypted) derive keys using
* read_enc_header() which handles PQ, PBKDF2, and legacy formats
* 3. Read data blocks sequentially with read_block()
* 4. Decompress+decrypt+checksum each block with decompress_block()
* 5. Write decompressed data to target
* */
zupt_error_t zupt_disk_restore(const char *archive_path, const char *target_path,
zupt_options_t *opts) {
/* Open archive */
FILE *f = fopen(archive_path, "rb");
if (!f) {
fprintf(stderr, "Error: Cannot open '%s': %s\n", archive_path, strerror(errno));
return ZUPT_ERR_IO;
}
/* Read archive header */
/* ─── Read archive header ─── */
zupt_archive_header_t hdr;
if (fread(&hdr, sizeof(hdr), 1, f) != 1) { fclose(f); return ZUPT_ERR_IO; }
if (fread(&hdr, sizeof(hdr), 1, f) != 1) {
fclose(f);
fprintf(stderr, "Error: Cannot read archive header\n");
return ZUPT_ERR_IO;
}
/* Verify magic */
if (hdr.magic[0] != ZUPT_MAGIC_0 || hdr.magic[1] != ZUPT_MAGIC_1 ||
hdr.magic[2] != ZUPT_MAGIC_2 || hdr.magic[3] != ZUPT_MAGIC_3) {
fclose(f);
@ -561,208 +533,153 @@ zupt_error_t zupt_disk_restore(const char *archive_path, const char *target_path
return ZUPT_ERR_BAD_MAGIC;
}
/* Check disk image flag */
if (!(hdr.global_flags & ZUPT_FLAG_DISK_IMAGE)) {
fclose(f);
fprintf(stderr, "Error: Archive is not a disk image. Use 'zupt extract' instead.\n");
return ZUPT_ERR_INVALID;
}
/* Handle encryption */
/* ─── Read encryption header (uses same code as zupt extract) ─── */
if (hdr.global_flags & ZUPT_FLAG_ENCRYPTED) {
if (!opts->encrypt && opts->password[0] == '\0' && !opts->pq_mode) {
fclose(f);
fprintf(stderr, "Error: Archive is encrypted. Use -p or --pq to provide key.\n");
return ZUPT_ERR_AUTH_FAIL;
}
opts->encrypt = 1;
/* Read encryption header block */
fseeko(f, (int64_t)hdr.encryption_header_off, SEEK_SET);
/* Skip block magic (2B) + type (1B) + codec (2B) + flags (2B) */
uint8_t skip[7];
if (fread(skip, 1, 7, f) != 7) { fclose(f); return ZUPT_ERR_CORRUPT; }
uint64_t uncomp_sz, comp_sz;
if (zupt_read_varint(f, &uncomp_sz) < 0) { fclose(f); return ZUPT_ERR_CORRUPT; }
if (zupt_read_varint(f, &comp_sz) < 0) { fclose(f); return ZUPT_ERR_CORRUPT; }
/* Skip checksum (8B) */
uint8_t ck_skip[8];
if (fread(ck_skip, 1, 8, f) != 8) { fclose(f); return ZUPT_ERR_CORRUPT; }
uint8_t *enc_data = (uint8_t *)malloc((size_t)comp_sz);
if (!enc_data) { fclose(f); return ZUPT_ERR_NOMEM; }
if (fread(enc_data, 1, (size_t)comp_sz, f) != (size_t)comp_sz) {
free(enc_data); fclose(f); return ZUPT_ERR_CORRUPT;
zupt_error_t enc_err = read_enc_header(f, &hdr, opts);
if (enc_err != ZUPT_OK) {
fclose(f);
fprintf(stderr, "Error: Encryption header read failed (%s)\n",
zupt_strerror(enc_err));
return enc_err;
}
}
if (opts->pq_mode) {
if (zupt_hybrid_decrypt_init(&opts->keyring, opts->keyfile,
enc_data, (size_t)comp_sz) != 0) {
free(enc_data); fclose(f);
fprintf(stderr, "Error: PQ decryption failed.\n");
return ZUPT_ERR_AUTH_FAIL;
}
} else {
/* Password mode */
if (comp_sz < 53) { free(enc_data); fclose(f); return ZUPT_ERR_CORRUPT; }
uint8_t enc_type = enc_data[0];
if (enc_type != 0x01) { free(enc_data); fclose(f); return ZUPT_ERR_CORRUPT; }
uint8_t *salt = enc_data + 1;
uint8_t *nonce = enc_data + 33;
uint32_t iter;
memcpy(&iter, enc_data + 49, 4);
zupt_derive_keys(&opts->keyring, opts->password, salt, nonce, iter);
}
free(enc_data);
}
/* ─── Read footer to get total block count ─── */
int64_t after_enc_pos = ftello(f); /* Save position after enc header */
/* Read footer to find index */
fseeko(f, -(int64_t)sizeof(zupt_footer_t), SEEK_END);
zupt_footer_t ft;
if (fread(&ft, sizeof(ft), 1, f) != 1) { fclose(f); return ZUPT_ERR_CORRUPT; }
if (ft.footer_magic[0] != 'Z' || ft.footer_magic[1] != 'E') {
fclose(f); return ZUPT_ERR_BAD_MAGIC;
if (fread(&ft, sizeof(ft), 1, f) != 1) {
fclose(f);
return ZUPT_ERR_CORRUPT;
}
if (ft.footer_magic[0] != 'Z' || ft.footer_magic[1] != 'E' ||
ft.footer_magic[2] != 'N' || ft.footer_magic[3] != 'D') {
fclose(f);
fprintf(stderr, "Error: Invalid footer magic\n");
return ZUPT_ERR_BAD_MAGIC;
}
/* Seek to first data block (skip archive header + encryption header) */
fseeko(f, sizeof(zupt_archive_header_t), SEEK_SET);
if (hdr.global_flags & ZUPT_FLAG_ENCRYPTED) {
/* Skip past encryption header block to reach data blocks */
fseeko(f, (int64_t)hdr.encryption_header_off, SEEK_SET);
/* Skip the enc header block entirely */
uint8_t skip2[7];
if (fread(skip2, 1, 7, f) != 7) { fclose(f); return ZUPT_ERR_CORRUPT; }
uint64_t u1, u2; zupt_read_varint(f, &u1); zupt_read_varint(f, &u2);
uint8_t sk8[8];
if (fread(sk8, 1, 8, f) != 8) { fclose(f); return ZUPT_ERR_CORRUPT; }
fseeko(f, (int64_t)u2, SEEK_CUR);
}
/* ─── Seek back to first data block ─── */
fseeko(f, after_enc_pos, SEEK_SET);
/* Open target for writing */
/* ─── Open target for writing ───
* Block devices require raw POSIX I/O (open/write) because stdio
* buffering can cause misaligned or partial writes that corrupt data.
* O_SYNC ensures each write is flushed to the device before returning.
* For loop devices, this ensures data reaches the backing file. */
#ifdef _WIN32
FILE *tgt = fopen(target_path, "wb");
if (!tgt) {
fprintf(stderr, "Error: Cannot open target '%s': %s\n", target_path, strerror(errno));
fprintf(stderr, "Error: Cannot open target '%s': %s\n",
target_path, strerror(errno));
fclose(f);
return ZUPT_ERR_IO;
}
#else
struct stat tgt_st;
int tgt_fd;
int is_block_dev = 0;
/* For block/char devices: O_WRONLY | O_SYNC (no truncate, sync writes).
* For regular files: O_WRONLY | O_CREAT | O_TRUNC. */
if (stat(target_path, &tgt_st) == 0 &&
(S_ISBLK(tgt_st.st_mode) || S_ISCHR(tgt_st.st_mode))) {
tgt_fd = open(target_path, O_WRONLY | O_SYNC);
is_block_dev = 1;
} else {
tgt_fd = open(target_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
}
if (tgt_fd < 0) {
fprintf(stderr, "Error: Cannot open target '%s': %s\n",
target_path, strerror(errno));
fclose(f);
return ZUPT_ERR_IO;
}
#endif
fprintf(stderr, " Restoring disk image to: %s\n", target_path);
fprintf(stderr, " Blocks: %llu\n\n", (unsigned long long)ft.total_blocks);
time_t start_time = time(NULL);
uint64_t total_written = 0;
uint64_t block_seq = 0;
int errors = 0;
/* ─── Read and restore blocks sequentially ─── */
for (uint64_t bi = 0; bi < ft.total_blocks; bi++) {
/* Read block header */
uint8_t bm[2];
if (fread(bm, 1, 2, f) != 2) { errors++; break; }
if (bm[0] != ZUPT_BLOCK_MAGIC_0 || bm[1] != ZUPT_BLOCK_MAGIC_1) {
errors++; break;
}
zupt_block_t blk;
zupt_error_t rerr = read_block(f, &blk);
uint8_t block_type;
if (fread(&block_type, 1, 1, f) != 1) { errors++; break; }
if (block_type == ZUPT_BLOCK_INDEX) break; /* reached index */
uint8_t c16[2], f16[2];
if (fread(c16, 1, 2, f) != 2) { errors++; break; }
if (fread(f16, 1, 2, f) != 2) { errors++; break; }
uint16_t codec = (uint16_t)c16[0] | ((uint16_t)c16[1] << 8);
uint16_t bflags = (uint16_t)f16[0] | ((uint16_t)f16[1] << 8);
uint64_t uncomp_size, comp_size, checksum;
if (zupt_read_varint(f, &uncomp_size) < 0) { errors++; break; }
if (zupt_read_varint(f, &comp_size) < 0) { errors++; break; }
uint8_t ck8[8];
if (fread(ck8, 1, 8, f) != 8) { errors++; break; }
checksum = 0;
for (int i = 7; i >= 0; i--) checksum = (checksum << 8) | ck8[i];
/* Read payload */
if (comp_size > ZUPT_MAX_BLOCK_SZ + 1024) { errors++; break; }
uint8_t *payload = (uint8_t *)malloc((size_t)comp_size);
if (!payload) { errors++; break; }
if (fread(payload, 1, (size_t)comp_size, f) != (size_t)comp_size) {
free(payload); errors++; break;
}
/* Decrypt if needed */
const uint8_t *comp_data = payload;
size_t comp_len = (size_t)comp_size;
uint8_t *dec_payload = NULL;
if (bflags & ZUPT_BFLAG_ENCRYPTED) {
if (!opts->keyring.active) { free(payload); errors++; break; }
size_t dec_len;
dec_payload = zupt_decrypt_buffer(&opts->keyring, comp_data, comp_len, bi, &dec_len);
if (!dec_payload) {
fprintf(stderr, " Block %llu: decryption failed\n", (unsigned long long)bi);
free(payload); errors++; break;
}
comp_data = dec_payload;
comp_len = dec_len;
}
/* Decompress */
uint8_t *out_buf = (uint8_t *)malloc((size_t)uncomp_size);
if (!out_buf) { free(dec_payload); free(payload); errors++; break; }
if (codec == ZUPT_CODEC_STORE) {
if (comp_len >= (size_t)uncomp_size)
memcpy(out_buf, comp_data, (size_t)uncomp_size);
else { free(out_buf); free(dec_payload); free(payload); errors++; break; }
} else if (codec == ZUPT_CODEC_VAPTVUPT) {
int64_t dsz = vvz_decompress(comp_data, comp_len, out_buf, (size_t)uncomp_size);
if (dsz < 0 || (size_t)dsz != (size_t)uncomp_size) {
free(out_buf); free(dec_payload); free(payload); errors++; break;
}
} else if (codec == ZUPT_CODEC_ZUPT_LZHP) {
if (comp_len < 1) { free(out_buf); free(dec_payload); free(payload); errors++; break; }
uint8_t pflag = comp_data[0];
if (pflag & 0x01) {
if (comp_len < 257) { free(out_buf); free(dec_payload); free(payload); errors++; break; }
uint8_t pred[256]; memcpy(pred, comp_data + 1, 256);
uint8_t *temp = (uint8_t *)malloc((size_t)uncomp_size);
if (!temp) { free(out_buf); free(dec_payload); free(payload); errors++; break; }
size_t r = zupt_lzh_decompress(comp_data + 257, comp_len - 257, temp, (size_t)uncomp_size);
if (r == (size_t)uncomp_size)
zupt_predict_decode(temp, out_buf, (size_t)uncomp_size, pred);
else errors++;
free(temp);
} else {
size_t r = zupt_lzh_decompress(comp_data + 1, comp_len - 1, out_buf, (size_t)uncomp_size);
if (r != (size_t)uncomp_size) errors++;
}
} else if (codec == ZUPT_CODEC_ZUPT_LZH) {
size_t r = zupt_lzh_decompress(comp_data, comp_len, out_buf, (size_t)uncomp_size);
if (r != (size_t)uncomp_size) errors++;
} else if (codec == ZUPT_CODEC_ZUPT_LZ) {
size_t r = zupt_lz_decompress(comp_data, comp_len, out_buf, (size_t)uncomp_size);
if (r != (size_t)uncomp_size) errors++;
} else {
if (rerr != ZUPT_OK) {
fprintf(stderr, " Block %llu: read error (%s)\n",
(unsigned long long)bi, zupt_strerror(rerr));
errors++;
break;
}
free(dec_payload);
free(payload);
/* Skip non-data blocks (index, etc.) */
if (blk.block_type == ZUPT_BLOCK_INDEX) {
free(blk.payload);
break; /* Reached index — all data blocks done */
}
if (blk.block_type != ZUPT_BLOCK_DATA) {
free(blk.payload);
continue; /* Skip unknown block types */
}
if (errors) { free(out_buf); break; }
/* Decompress + decrypt + verify checksum */
uint8_t *out_buf = NULL;
size_t out_len = 0;
zupt_error_t derr = decompress_block(&blk, &opts->keyring,
block_seq, &out_buf, &out_len);
free(blk.payload);
/* Verify checksum */
uint64_t actual_ck = zupt_xxh64(out_buf, (size_t)uncomp_size, 0);
if (actual_ck != checksum) {
fprintf(stderr, " Block %llu: checksum mismatch\n", (unsigned long long)bi);
free(out_buf); errors++; break;
if (derr != ZUPT_OK) {
fprintf(stderr, " Block %llu: decompression/checksum failed (%s)\n",
(unsigned long long)bi, zupt_strerror(derr));
errors++;
break;
}
/* Write to target */
if (fwrite(out_buf, 1, (size_t)uncomp_size, tgt) != (size_t)uncomp_size) {
free(out_buf); errors++; break;
int write_ok = 0;
#ifdef _WIN32
write_ok = (fwrite(out_buf, 1, out_len, tgt) == out_len);
#else
{
size_t written = 0;
while (written < out_len) {
ssize_t w = write(tgt_fd, out_buf + written, out_len - written);
if (w <= 0) break;
written += (size_t)w;
}
total_written += uncomp_size;
write_ok = (written == out_len);
}
#endif
if (!write_ok) {
fprintf(stderr, " Block %llu: write error (%s)\n",
(unsigned long long)bi, strerror(errno));
free(out_buf);
errors++;
break;
}
total_written += out_len;
block_seq++;
free(out_buf);
/* Progress */
@ -771,7 +688,19 @@ zupt_error_t zupt_disk_restore(const char *archive_path, const char *target_path
}
fclose(f);
#ifdef _WIN32
fclose(tgt);
#else
if (tgt_fd >= 0) {
fsync(tgt_fd); /* Flush file descriptor buffers */
close(tgt_fd);
}
if (is_block_dev) {
sync(); /* Force kernel to flush ALL dirty pages to disk.
* Critical for loop devices: fsync on the loop fd
* may not flush the backing file's page cache. */
}
#endif
if (errors > 0) {
fprintf(stderr, "\n Restore FAILED: %d error(s)\n", errors);

Binary file not shown.

View file

@ -249,12 +249,94 @@ void zupt_collect_files(zupt_filelist_t *fl, const char *path, const char *base)
* WRITE / READ HELPERS (LE-safe, error-checked)
* */
static int w8(FILE*f,uint8_t v){return fwrite(&v,1,1,f)==1?0:-1;}
static int w16le(FILE*f,uint16_t v){uint8_t b[2];zupt_le16_put(b,v);return fwrite(b,1,2,f)==2?0:-1;}
static int w64le(FILE*f,uint64_t v){uint8_t b[8];zupt_le64_put(b,v);return fwrite(b,1,8,f)==8?0:-1;}
int zupt_w8(FILE*f,uint8_t v){return fwrite(&v,1,1,f)==1?0:-1;}
int zupt_w16le(FILE*f,uint16_t v){uint8_t b[2];zupt_le16_put(b,v);return fwrite(b,1,2,f)==2?0:-1;}
int zupt_w64le(FILE*f,uint64_t v){uint8_t b[8];zupt_le64_put(b,v);return fwrite(b,1,8,f)==8?0:-1;}
static int r16le(FILE*f,uint16_t*v){uint8_t b[2];if(fread(b,1,2,f)!=2)return -1;*v=zupt_le16_get(b);return 0;}
static int r64le(FILE*f,uint64_t*v){uint8_t b[8];if(fread(b,1,8,f)!=8)return -1;*v=zupt_le64_get(b);return 0;}
/* Aliases for internal use (backward compat with existing code) */
#define w8 zupt_w8
#define w16le zupt_w16le
#define w64le zupt_w64le
/* ═══════════════════════════════════════════════════════════════════
* SHARED ENCRYPTION HEADER WRITER
*
* Used by BOTH zupt_compress_files() and zupt_disk_backup().
* Writes the encryption header block and updates hdr.encryption_header_off.
* After return, the file position is at the end (ready for data blocks).
* */
zupt_error_t write_enc_header(FILE *out, zupt_archive_header_t *hdr,
zupt_options_t *opts) {
hdr->encryption_header_off = (uint64_t)ftello(out);
if (opts->pq_mode) {
/* ─── PQ HYBRID MODE ─── */
hdr->global_flags |= ZUPT_FLAG_PQ_HYBRID;
uint8_t enc_hdr_buf[1200];
size_t enc_hdr_len = 0;
if (!opts->quiet)
fprintf(stderr, " Post-quantum key encapsulation (ML-KEM-768 + X25519)...\n");
if (zupt_hybrid_encrypt_init(&opts->keyring, opts->keyfile,
enc_hdr_buf, &enc_hdr_len) != 0) {
fprintf(stderr, "Error: PQ hybrid key encapsulation failed.\n");
return ZUPT_ERR_AUTH_FAIL;
}
zupt_w8(out, ZUPT_BLOCK_MAGIC_0); zupt_w8(out, ZUPT_BLOCK_MAGIC_1);
zupt_w8(out, ZUPT_BLOCK_ENC_HEADER);
zupt_w16le(out, ZUPT_CODEC_STORE); zupt_w16le(out, 0);
zupt_write_varint(out, enc_hdr_len);
zupt_write_varint(out, enc_hdr_len);
zupt_w64le(out, zupt_xxh64(enc_hdr_buf, enc_hdr_len, 0));
if (fwrite(enc_hdr_buf, 1, enc_hdr_len, out) != enc_hdr_len)
return ZUPT_ERR_IO;
/* Re-write header with PQ flag */
fseeko(out, 0, SEEK_SET);
if (fwrite(hdr, sizeof(*hdr), 1, out) != 1) return ZUPT_ERR_IO;
fseeko(out, 0, SEEK_END);
if (!opts->quiet)
fprintf(stderr, " Encryption: PQ Hybrid (ML-KEM-768 + X25519) + AES-256-CTR + HMAC-SHA256\n\n");
} else {
/* ─── PASSWORD MODE (PBKDF2) ─── */
uint8_t salt[ZUPT_SALT_SIZE], nonce[ZUPT_NONCE_SIZE];
zupt_random_bytes(salt, ZUPT_SALT_SIZE);
zupt_random_bytes(nonce, ZUPT_NONCE_SIZE);
if (!opts->quiet)
fprintf(stderr, " Deriving encryption key (PBKDF2-SHA256, %d iterations)...\n",
ZUPT_KDF_ITERATIONS);
zupt_derive_keys(&opts->keyring, opts->password, salt, nonce, ZUPT_KDF_ITERATIONS);
uint8_t enc_hdr[53];
enc_hdr[0] = ZUPT_ENC_PBKDF2;
memcpy(enc_hdr + 1, salt, 32);
memcpy(enc_hdr + 33, nonce, 16);
uint32_t iter = ZUPT_KDF_ITERATIONS;
memcpy(enc_hdr + 49, &iter, 4);
zupt_w8(out, ZUPT_BLOCK_MAGIC_0); zupt_w8(out, ZUPT_BLOCK_MAGIC_1);
zupt_w8(out, ZUPT_BLOCK_ENC_HEADER);
zupt_w16le(out, ZUPT_CODEC_STORE); zupt_w16le(out, 0);
zupt_write_varint(out, 53); zupt_write_varint(out, 53);
zupt_w64le(out, zupt_xxh64(enc_hdr, 53, 0));
if (fwrite(enc_hdr, 1, 53, out) != 53) return ZUPT_ERR_IO;
fseeko(out, 0, SEEK_SET);
if (fwrite(hdr, sizeof(*hdr), 1, out) != 1) return ZUPT_ERR_IO;
fseeko(out, 0, SEEK_END);
if (!opts->quiet)
fprintf(stderr, " Encryption: AES-256-CTR + HMAC-SHA256 (Encrypt-then-MAC)\n\n");
}
return ZUPT_OK;
}
static void ensure_dirs(const char *path) {
char tmp[ZUPT_MAX_PATH]; strncpy(tmp, path, sizeof(tmp)-1); tmp[sizeof(tmp)-1]='\0';
for (char *p=tmp+1;*p;p++)
@ -330,63 +412,8 @@ zupt_error_t zupt_compress_files(const char *output_path,
if (fwrite(&hdr, sizeof(hdr), 1, out) != 1) write_err = 1;
if (opts->encrypt) {
hdr.encryption_header_off = safe_ftello(out);
if (opts->pq_mode) {
/* ─── PQ HYBRID MODE ─── */
if (hdr.global_flags & ZUPT_FLAG_PQ_HYBRID) {} /* already set */
hdr.global_flags |= ZUPT_FLAG_PQ_HYBRID;
uint8_t enc_hdr_buf[1200]; /* enc_type(1) + ct(1088) + eph_pk(32) + nonce(16) = 1137 */
size_t enc_hdr_len = 0;
if (!opts->quiet) fprintf(stderr, " Post-quantum key encapsulation (ML-KEM-768 + X25519)...\n");
if (zupt_hybrid_encrypt_init(&opts->keyring, opts->keyfile, enc_hdr_buf, &enc_hdr_len) != 0) {
fprintf(stderr, "Error: PQ hybrid key encapsulation failed.\n");
fclose(out); return ZUPT_ERR_AUTH_FAIL;
}
w8(out, ZUPT_BLOCK_MAGIC_0); w8(out, ZUPT_BLOCK_MAGIC_1);
w8(out, ZUPT_BLOCK_ENC_HEADER);
w16le(out, ZUPT_CODEC_STORE); w16le(out, 0);
zupt_write_varint(out, enc_hdr_len); zupt_write_varint(out, enc_hdr_len);
w64le(out, zupt_xxh64(enc_hdr_buf, enc_hdr_len, 0));
if (fwrite(enc_hdr_buf, 1, enc_hdr_len, out) != enc_hdr_len) write_err = 1;
fseeko(out, 0, SEEK_SET);
if (fwrite(&hdr, sizeof(hdr), 1, out) != 1) write_err = 1;
fseeko(out, 0, SEEK_END);
if (!opts->quiet) fprintf(stderr, " Encryption: PQ Hybrid (ML-KEM-768 + X25519) + AES-256-CTR + HMAC-SHA256\n\n");
} else {
/* ─── PASSWORD MODE (PBKDF2, unchanged from v0.5.1) ─── */
uint8_t salt[ZUPT_SALT_SIZE], nonce[ZUPT_NONCE_SIZE];
zupt_random_bytes(salt, ZUPT_SALT_SIZE);
zupt_random_bytes(nonce, ZUPT_NONCE_SIZE);
if (!opts->quiet) fprintf(stderr, " Deriving encryption key (PBKDF2-SHA256, %d iterations)...\n", ZUPT_KDF_ITERATIONS);
zupt_derive_keys(&opts->keyring, opts->password, salt, nonce, ZUPT_KDF_ITERATIONS);
/* enc_type prefix for backward compat detection */
uint8_t enc_hdr[53]; /* enc_type(1) + salt(32) + nonce(16) + iter(4) */
enc_hdr[0] = ZUPT_ENC_PBKDF2;
memcpy(enc_hdr + 1, salt, 32);
memcpy(enc_hdr + 33, nonce, 16);
uint32_t iter = ZUPT_KDF_ITERATIONS;
memcpy(enc_hdr + 49, &iter, 4);
w8(out, ZUPT_BLOCK_MAGIC_0); w8(out, ZUPT_BLOCK_MAGIC_1);
w8(out, ZUPT_BLOCK_ENC_HEADER);
w16le(out, ZUPT_CODEC_STORE); w16le(out, 0);
zupt_write_varint(out, 53); zupt_write_varint(out, 53);
w64le(out, zupt_xxh64(enc_hdr, 53, 0));
if (fwrite(enc_hdr, 1, 53, out) != 53) write_err = 1;
fseeko(out, 0, SEEK_SET);
if (fwrite(&hdr, sizeof(hdr), 1, out) != 1) write_err = 1;
fseeko(out, 0, SEEK_END);
if (!opts->quiet) fprintf(stderr, " Encryption: AES-256-CTR + HMAC-SHA256 (Encrypt-then-MAC)\n\n");
}
zupt_error_t enc_err = write_enc_header(out, &hdr, opts);
if (enc_err != ZUPT_OK) { fclose(out); return enc_err; }
}
zupt_index_entry_t *index = (zupt_index_entry_t*)calloc((size_t)num_files, sizeof(zupt_index_entry_t));
@ -774,25 +801,8 @@ zupt_error_t zupt_compress_solid(const char *output_path,
if (fwrite(&hdr, sizeof(hdr), 1, out) != 1) write_err = 1;
if (opts->encrypt) {
hdr.encryption_header_off = safe_ftello(out);
uint8_t salt[ZUPT_SALT_SIZE], nonce[ZUPT_NONCE_SIZE];
zupt_random_bytes(salt, ZUPT_SALT_SIZE);
zupt_random_bytes(nonce, ZUPT_NONCE_SIZE);
if (!opts->quiet) fprintf(stderr, " Deriving encryption key...\n");
zupt_derive_keys(&opts->keyring, opts->password, salt, nonce, ZUPT_KDF_ITERATIONS);
uint8_t enc_hdr[52];
memcpy(enc_hdr, salt, 32); memcpy(enc_hdr+32, nonce, 16);
uint32_t iter = ZUPT_KDF_ITERATIONS; memcpy(enc_hdr+48, &iter, 4);
w8(out, ZUPT_BLOCK_MAGIC_0); w8(out, ZUPT_BLOCK_MAGIC_1);
w8(out, ZUPT_BLOCK_ENC_HEADER);
w16le(out, ZUPT_CODEC_STORE); w16le(out, 0);
zupt_write_varint(out, 52); zupt_write_varint(out, 52);
w64le(out, zupt_xxh64(enc_hdr, 52, 0));
if (fwrite(enc_hdr, 1, 52, out) != 52) write_err = 1;
fseeko(out, 0, SEEK_SET);
if (fwrite(&hdr, sizeof(hdr), 1, out) != 1) write_err = 1;
fseeko(out, 0, SEEK_END);
if (!opts->quiet) fprintf(stderr, " Encryption: AES-256-CTR + HMAC-SHA256 (Encrypt-then-MAC)\n\n");
zupt_error_t enc_err = write_enc_header(out, &hdr, opts);
if (enc_err != ZUPT_OK) { fclose(out); return enc_err; }
}
zupt_index_entry_t *index = (zupt_index_entry_t*)calloc((size_t)num_files, sizeof(zupt_index_entry_t));
@ -1036,7 +1046,7 @@ static zupt_error_t read_footer(FILE *f, zupt_footer_t *ft) {
return ZUPT_OK;
}
static zupt_error_t read_block(FILE *f, zupt_block_t *b) {
zupt_error_t read_block(FILE *f, zupt_block_t *b) {
uint8_t m[2];
if (fread(m,1,2,f)!=2) return ZUPT_ERR_IO;
if (m[0]!=ZUPT_BLOCK_MAGIC_0||m[1]!=ZUPT_BLOCK_MAGIC_1) return ZUPT_ERR_CORRUPT;
@ -1058,7 +1068,7 @@ static zupt_error_t read_block(FILE *f, zupt_block_t *b) {
return ZUPT_OK;
}
static zupt_error_t decompress_block(const zupt_block_t *b, const zupt_keyring_t *kr,
zupt_error_t decompress_block(const zupt_block_t *b, const zupt_keyring_t *kr,
uint64_t block_seq, uint8_t **out, size_t *olen) {
const uint8_t *comp_data = b->payload;
size_t comp_len = (size_t)b->compressed_size;
@ -1147,7 +1157,7 @@ done:
return ZUPT_OK;
}
static zupt_error_t read_enc_header(FILE *f, zupt_archive_header_t *hdr, zupt_options_t *opts) {
zupt_error_t read_enc_header(FILE *f, zupt_archive_header_t *hdr, zupt_options_t *opts) {
if (!(hdr->global_flags & ZUPT_FLAG_ENCRYPTED)) return ZUPT_OK;
fseeko(f, (int64_t)hdr->encryption_header_off, SEEK_SET);

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1 @@
hello world

View file

@ -0,0 +1 @@
test data 12345

View file

@ -0,0 +1 @@
hello

View file

@ -0,0 +1 @@
hello world

View file

@ -0,0 +1 @@
test data 12345