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