1
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <time.h>
5 #include <iconv.h>
6
7 #include "../readstat.h"
8 #include "../readstat_writer.h"
9 #include "readstat_sas.h"
10 #include "readstat_sas_rle.h"
11
12 typedef struct sas7bcat_block_s {
13 size_t len;
14 char data[1]; // Flexible array; use [1] for C++-98 compatibility
15 } sas7bcat_block_t;
16
sas7bcat_block_for_label_set(readstat_label_set_t * r_label_set)17 static sas7bcat_block_t *sas7bcat_block_for_label_set(readstat_label_set_t *r_label_set) {
18 size_t len = 0;
19 size_t name_len = strlen(r_label_set->name);
20 int j;
21 char name[32];
22
23 len += 106;
24
25 if (name_len > 8) {
26 len += 32; // long name
27 if (name_len > 32) {
28 name_len = 32;
29 }
30 }
31
32 memcpy(&name[0], r_label_set->name, name_len);
33
34 for (j=0; j<r_label_set->value_labels_count; j++) {
35 readstat_value_label_t *value_label = readstat_get_value_label(r_label_set, j);
36 len += 30; // Value: 14-byte header + 16-byte padded value
37
38 len += 8 + 2 + value_label->label_len + 1;
39 }
40
41 sas7bcat_block_t *block = calloc(1, sizeof(sas7bcat_block_t) + len);
42 block->len = len;
43
44 off_t begin = 106;
45 int32_t count = r_label_set->value_labels_count;
46 memcpy(&block->data[38], &count, sizeof(int32_t));
47 memcpy(&block->data[42], &count, sizeof(int32_t));
48 if (name_len > 8) {
49 block->data[2] = (char)0x80;
50 memcpy(&block->data[8], name, 8);
51
52 memset(&block->data[106], ' ', 32);
53 memcpy(&block->data[106], name, name_len);
54
55 begin += 32;
56 } else {
57 memset(&block->data[8], ' ', 8);
58 memcpy(&block->data[8], name, name_len);
59 }
60
61 char *lbp1 = &block->data[begin];
62 char *lbp2 = &block->data[begin+r_label_set->value_labels_count*30];
63
64 for (j=0; j<r_label_set->value_labels_count; j++) {
65 readstat_value_label_t *value_label = readstat_get_value_label(r_label_set, j);
66 lbp1[2] = 24; // size - 6
67 int32_t index = j;
68 memcpy(&lbp1[10], &index, sizeof(int32_t));
69 if (r_label_set->type == READSTAT_TYPE_STRING) {
70 size_t string_len = value_label->string_key_len;
71 if (string_len > 16)
72 string_len = 16;
73 memset(&lbp1[14], ' ', 16);
74 memcpy(&lbp1[14], value_label->string_key, string_len);
75 } else {
76 uint64_t big_endian_value;
77 double double_value = -1.0 * value_label->double_key;
78 memcpy(&big_endian_value, &double_value, sizeof(double));
79 if (machine_is_little_endian()) {
80 big_endian_value = byteswap8(big_endian_value);
81 }
82 memcpy(&lbp1[22], &big_endian_value, sizeof(uint64_t));
83 }
84
85 int16_t label_len = value_label->label_len;
86 memcpy(&lbp2[8], &label_len, sizeof(int16_t));
87 memcpy(&lbp2[10], value_label->label, label_len);
88
89 lbp1 += 30;
90 lbp2 += 8 + 2 + value_label->label_len + 1;
91 }
92
93 return block;
94 }
95
sas7bcat_emit_header(readstat_writer_t * writer,sas_header_info_t * hinfo)96 static readstat_error_t sas7bcat_emit_header(readstat_writer_t *writer, sas_header_info_t *hinfo) {
97 sas_header_start_t header_start = {
98 .a2 = hinfo->u64 ? SAS_ALIGNMENT_OFFSET_4 : SAS_ALIGNMENT_OFFSET_0,
99 .a1 = SAS_ALIGNMENT_OFFSET_0,
100 .endian = machine_is_little_endian() ? SAS_ENDIAN_LITTLE : SAS_ENDIAN_BIG,
101 .file_format = SAS_FILE_FORMAT_UNIX,
102 .encoding = 20, /* UTF-8 */
103 .file_type = "SAS FILE",
104 .file_info = "CATALOG "
105 };
106
107 memcpy(&header_start.magic, sas7bcat_magic_number, sizeof(header_start.magic));
108
109 return sas_write_header(writer, hinfo, header_start);
110 }
111
sas7bcat_begin_data(void * writer_ctx)112 static readstat_error_t sas7bcat_begin_data(void *writer_ctx) {
113 readstat_writer_t *writer = (readstat_writer_t *)writer_ctx;
114 readstat_error_t retval = READSTAT_OK;
115
116 int i;
117 sas_header_info_t *hinfo = sas_header_info_init(writer, 0);
118 sas7bcat_block_t **blocks = malloc(writer->label_sets_count * sizeof(sas7bcat_block_t));
119 char *page = malloc(hinfo->page_size);
120
121 for (i=0; i<writer->label_sets_count; i++) {
122 blocks[i] = sas7bcat_block_for_label_set(writer->label_sets[i]);
123 }
124
125 hinfo->page_count = 4;
126
127 // Header
128 retval = sas7bcat_emit_header(writer, hinfo);
129 if (retval != READSTAT_OK)
130 goto cleanup;
131
132 // Page 0
133 retval = readstat_write_zeros(writer, hinfo->page_size);
134 if (retval != READSTAT_OK)
135 goto cleanup;
136
137 memset(page, '\0', hinfo->page_size);
138
139 // Page 1
140 char *xlsr = &page[856];
141 int16_t block_idx, block_off;
142 block_idx = 4;
143 block_off = 16;
144 for (i=0; i<writer->label_sets_count; i++) {
145 if (xlsr + 212 > page + hinfo->page_size)
146 break;
147
148 memcpy(&xlsr[0], "XLSR", 4);
149
150 memcpy(&xlsr[4], &block_idx, sizeof(int16_t));
151 memcpy(&xlsr[8], &block_off, sizeof(int16_t));
152
153 xlsr[50] = 'O';
154
155 block_off += blocks[i]->len;
156
157 xlsr += 212;
158 }
159
160 retval = readstat_write_bytes(writer, page, hinfo->page_size);
161 if (retval != READSTAT_OK)
162 goto cleanup;
163
164 // Page 2
165 retval = readstat_write_zeros(writer, hinfo->page_size);
166 if (retval != READSTAT_OK)
167 goto cleanup;
168
169 // Page 3
170 memset(page, '\0', hinfo->page_size);
171
172 char block_header[16];
173 block_off = 16;
174 for (i=0; i<writer->label_sets_count; i++) {
175 if (block_off + sizeof(block_header) + blocks[i]->len > hinfo->page_size)
176 break;
177
178 memset(block_header, '\0', sizeof(block_header));
179
180 int32_t next_page = 0;
181 int16_t next_off = 0;
182 int16_t block_len = blocks[i]->len;
183 memcpy(&block_header[0], &next_page, sizeof(int32_t));
184 memcpy(&block_header[4], &next_off, sizeof(int16_t));
185 memcpy(&block_header[6], &block_len, sizeof(int16_t));
186
187 memcpy(&page[block_off], block_header, sizeof(block_header));
188 block_off += sizeof(block_header);
189
190 memcpy(&page[block_off], blocks[i]->data, blocks[i]->len);
191 block_off += blocks[i]->len;
192 }
193
194 retval = readstat_write_bytes(writer, page, hinfo->page_size);
195 if (retval != READSTAT_OK)
196 goto cleanup;
197
198 cleanup:
199 for (i=0; i<writer->label_sets_count; i++) {
200 free(blocks[i]);
201 }
202 free(blocks);
203 free(hinfo);
204 free(page);
205
206 return retval;
207 }
208
readstat_begin_writing_sas7bcat(readstat_writer_t * writer,void * user_ctx)209 readstat_error_t readstat_begin_writing_sas7bcat(readstat_writer_t *writer, void *user_ctx) {
210
211 if (writer->version == 0)
212 writer->version = SAS_DEFAULT_FILE_VERSION;
213
214 writer->callbacks.begin_data = &sas7bcat_begin_data;
215
216 return readstat_begin_writing_file(writer, user_ctx, 0);
217 }
218