From dabcb5e16f2fc294cb5656e6cc918db505bdad08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20P=C3=B6ttker?= <25299532+hpoettker@users.noreply.github.com> Date: Wed, 28 Oct 2026 18:55:02 +0100 Subject: [PATCH] Fix COMP page trailer layout for SAS7BDAT files Row-compressed files contain an extra page after the metadata (page type 0x9000). The signed 16-bit field at phs-2 of this page holds the offset to the page trailer. The writer had three problems here: - It placed the trailer at the end of the physical page. For page sizes of 32 KiB and above the field overflowed (e.g. 0xFFBE for 64 KiB pages). SAS then rejected the file with "Page header information invalid". - The trailer offsets only matched SAS for 32-bit files. - The field value only matched SAS for 64-bit files with pages below 32 KiB. The physical page size is unchanged. Beyond the trailer, the page is zero-filled. --- src/sas/readstat_sas7bdat_write.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/sas/readstat_sas7bdat_write.c b/src/sas/readstat_sas7bdat_write.c index 943dc373..d2891fa9 100644 --- a/src/sas/readstat_sas7bdat_write.c +++ b/src/sas/readstat_sas7bdat_write.c @@ -1028,6 +1028,10 @@ static readstat_error_t sas7bdat_write_comp_page(readstat_writer_t *writer, sas7 int64_t rows_on_first_page = 0; int64_t i; + /* SAS lays out the COMP page within at most 0x7FFF bytes, as the field + * at phs-2 is a signed 16-bit value, and zero-fills the rest. */ + size_t region = ctx->page_size < 0x7FFF ? ctx->page_size : 0x7FFF; + for (i=first_page->first; ifirst+first_page->count; i++) { if (ctx->subheaders[i]->is_row_data) rows_on_first_page++; @@ -1039,10 +1043,10 @@ static readstat_error_t sas7bdat_write_comp_page(readstat_writer_t *writer, sas7 w2(&buffer[ctx->phs-8], SAS_PAGE_TYPE_COMP); w2(&buffer[ctx->phs-6], ctx->total_pages); w2(&buffer[ctx->phs-4], 1); - w2(&buffer[ctx->phs-2], ctx->page_size - ctx->phs - ctx->sps - 2); - buffer[ctx->page_size-7] = rows_on_first_page & 0xFF; - buffer[ctx->page_size-6] = 1; - w2(&buffer[ctx->page_size-2], first_page->count); + w2(&buffer[ctx->phs-2], region - ctx->phs - ctx->sps - (10 - ctx->int_len)); + buffer[region - ctx->int_len - 3] = rows_on_first_page & 0xFF; + buffer[region - ctx->int_len - 2] = 1; + w2(&buffer[region-2], first_page->count); return readstat_write_bytes(writer, buffer, ctx->page_size); }