/src/mdbtools/src/libmdb/table.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* MDB Tools - A library for reading MS Access database file |
2 | | * Copyright (C) 2000 Brian Bruns |
3 | | * |
4 | | * This library is free software; you can redistribute it and/or |
5 | | * modify it under the terms of the GNU Library General Public |
6 | | * License as published by the Free Software Foundation; either |
7 | | * version 2 of the License, or (at your option) any later version. |
8 | | * |
9 | | * This library is distributed in the hope that it will be useful, |
10 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | | * Library General Public License for more details. |
13 | | * |
14 | | * You should have received a copy of the GNU Library General Public |
15 | | * License along with this library; if not, write to the Free Software |
16 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
17 | | */ |
18 | | |
19 | | #include "mdbtools.h" |
20 | | #include "mdbprivate.h" |
21 | | |
22 | | static gint mdb_col_comparer(MdbColumn **a, MdbColumn **b) |
23 | 1.13k | { |
24 | 1.13k | if ((*a)->col_num > (*b)->col_num) |
25 | 490 | return 1; |
26 | 646 | else if ((*a)->col_num < (*b)->col_num) |
27 | 547 | return -1; |
28 | 99 | else |
29 | 99 | return 0; |
30 | 1.13k | } |
31 | | |
32 | | MdbTableDef *mdb_alloc_tabledef(MdbCatalogEntry *entry) |
33 | 280 | { |
34 | 280 | MdbTableDef *table = g_malloc0(sizeof(MdbTableDef)); |
35 | 280 | table->entry=entry; |
36 | 280 | snprintf(table->name, sizeof(table->name), "%s", entry->object_name); |
37 | | |
38 | 280 | return table; |
39 | 280 | } |
40 | | void mdb_free_tabledef(MdbTableDef *table) |
41 | 280 | { |
42 | 280 | if (!table) return; |
43 | 280 | if (table->is_temp_table) { |
44 | 0 | guint i; |
45 | | /* Temp table pages are being stored in memory */ |
46 | 0 | for (i=0; i<table->temp_table_pages->len; i++) |
47 | 0 | g_free(g_ptr_array_index(table->temp_table_pages,i)); |
48 | 0 | g_ptr_array_free(table->temp_table_pages, TRUE); |
49 | | /* Temp tables use dummy entries */ |
50 | 0 | g_free(table->entry); |
51 | 0 | } |
52 | 280 | mdb_free_columns(table->columns); |
53 | 280 | mdb_free_indices(table->indices); |
54 | 280 | g_free(table->usage_map); |
55 | 280 | g_free(table->free_usage_map); |
56 | 280 | g_free(table); |
57 | 280 | } |
58 | | MdbTableDef *mdb_read_table(MdbCatalogEntry *entry) |
59 | 1.67k | { |
60 | 1.67k | MdbTableDef *table; |
61 | 1.67k | MdbHandle *mdb = entry->mdb; |
62 | 1.67k | MdbFormatConstants *fmt = mdb->fmt; |
63 | 1.67k | int row_start, pg_row; |
64 | 1.67k | void *buf, *pg_buf = mdb->pg_buf; |
65 | 1.67k | guint i; |
66 | | |
67 | 1.67k | if (!mdb_read_pg(mdb, entry->table_pg)) { |
68 | 179 | fprintf(stderr, "mdb_read_table: Unable to read page %lu\n", entry->table_pg); |
69 | 179 | return NULL; |
70 | 179 | } |
71 | 1.50k | if (mdb_get_byte(pg_buf, 0) != 0x02) { |
72 | 1.22k | fprintf(stderr, "mdb_read_table: Page %lu [size=%d] is not a valid table definition page (First byte = 0x%02X, expected 0x02)\n", |
73 | 1.22k | entry->table_pg, (int)fmt->pg_size, mdb_get_byte(pg_buf, 0)); |
74 | 1.22k | return NULL; |
75 | 1.22k | } |
76 | 280 | table = mdb_alloc_tabledef(entry); |
77 | | |
78 | 280 | mdb_get_int16(pg_buf, 8); /* len */ |
79 | | |
80 | | /* Note that num_rows may be zero if the database was improperly closed. |
81 | | * See https://github.com/mdbtools/mdbtools/issues/120 for discussion. */ |
82 | 280 | table->num_rows = mdb_get_int32(pg_buf, fmt->tab_num_rows_offset); |
83 | 280 | table->num_var_cols = mdb_get_int16(pg_buf, fmt->tab_num_cols_offset-2); |
84 | 280 | table->num_cols = mdb_get_int16(pg_buf, fmt->tab_num_cols_offset); |
85 | 280 | table->num_idxs = mdb_get_int32(pg_buf, fmt->tab_num_idxs_offset); |
86 | 280 | table->num_real_idxs = mdb_get_int32(pg_buf, fmt->tab_num_ridxs_offset); |
87 | | |
88 | | /* grab a copy of the usage map */ |
89 | 280 | pg_row = mdb_get_int32(pg_buf, fmt->tab_usage_map_offset); |
90 | 280 | if (mdb_find_pg_row(mdb, pg_row, &buf, &row_start, &(table->map_sz))) { |
91 | 244 | fprintf(stderr, "mdb_read_table: Unable to find page row %d\n", pg_row); |
92 | 244 | mdb_free_tabledef(table); |
93 | 244 | return NULL; |
94 | 244 | } |
95 | 36 | table->usage_map = g_memdup2((char*)buf + row_start, table->map_sz); |
96 | 36 | if (mdb_get_option(MDB_DEBUG_USAGE)) |
97 | 0 | mdb_buffer_dump(buf, row_start, table->map_sz); |
98 | 36 | mdb_debug(MDB_DEBUG_USAGE,"usage map found on page %ld row %d start %d len %d", |
99 | 36 | pg_row >> 8, pg_row & 0xff, row_start, table->map_sz); |
100 | | |
101 | | /* grab a copy of the free space page map */ |
102 | 36 | pg_row = mdb_get_int32(pg_buf, fmt->tab_free_map_offset); |
103 | 36 | if (mdb_find_pg_row(mdb, pg_row, &buf, &row_start, &(table->freemap_sz))) { |
104 | 0 | fprintf(stderr, "mdb_read_table: Unable to find page row %d\n", pg_row); |
105 | 0 | mdb_free_tabledef(table); |
106 | 0 | return NULL; |
107 | 0 | } |
108 | 36 | table->free_usage_map = g_memdup2((char*)buf + row_start, table->freemap_sz); |
109 | 36 | mdb_debug(MDB_DEBUG_USAGE,"free map found on page %ld row %d start %d len %d\n", |
110 | 36 | pg_row >> 8, pg_row & 0xff, row_start, table->freemap_sz); |
111 | | |
112 | 36 | table->first_data_pg = mdb_get_int16(pg_buf, fmt->tab_first_dpg_offset); |
113 | | |
114 | 36 | if (entry->props) |
115 | 0 | for (i=0; i<entry->props->len; ++i) { |
116 | 0 | MdbProperties *props = g_ptr_array_index(entry->props, i); |
117 | 0 | if (!props->name) |
118 | 0 | table->props = props; |
119 | 0 | } |
120 | | |
121 | 36 | return table; |
122 | 36 | } |
123 | | MdbTableDef *mdb_read_table_by_name(MdbHandle *mdb, gchar *table_name, int obj_type) |
124 | 0 | { |
125 | 0 | unsigned int i; |
126 | 0 | MdbCatalogEntry *entry; |
127 | |
|
128 | 0 | mdb_read_catalog(mdb, obj_type); |
129 | |
|
130 | 0 | for (i=0; i<mdb->num_catalog; i++) { |
131 | 0 | entry = g_ptr_array_index(mdb->catalog, i); |
132 | 0 | if (!g_ascii_strcasecmp(entry->object_name, table_name)) |
133 | 0 | return mdb_read_table(entry); |
134 | 0 | } |
135 | | |
136 | 0 | return NULL; |
137 | 0 | } |
138 | | |
139 | | |
140 | | guint32 |
141 | | read_pg_if_32(MdbHandle *mdb, int *cur_pos) |
142 | 0 | { |
143 | 0 | char c[4]; |
144 | |
|
145 | 0 | read_pg_if_n(mdb, c, cur_pos, 4); |
146 | 0 | return mdb_get_int32(c, 0); |
147 | 0 | } |
148 | | guint16 |
149 | | read_pg_if_16(MdbHandle *mdb, int *cur_pos) |
150 | 0 | { |
151 | 0 | char c[2]; |
152 | |
|
153 | 0 | read_pg_if_n(mdb, c, cur_pos, 2); |
154 | 0 | return mdb_get_int16(c, 0); |
155 | 0 | } |
156 | | guint8 |
157 | | read_pg_if_8(MdbHandle *mdb, int *cur_pos) |
158 | 404 | { |
159 | 404 | guint8 c; |
160 | | |
161 | 404 | read_pg_if_n(mdb, &c, cur_pos, 1); |
162 | 404 | return c; |
163 | 404 | } |
164 | | /* |
165 | | * Read data into a buffer, advancing pages and setting the |
166 | | * page cursor as needed. In the case that buf in NULL, pages |
167 | | * are still advanced and the page cursor is still updated. |
168 | | */ |
169 | | void * |
170 | | read_pg_if_n(MdbHandle *mdb, void *buf, int *cur_pos, size_t len) |
171 | 1.21k | { |
172 | 1.21k | char* _buf = buf; |
173 | 1.21k | char* _end = buf ? buf + len : NULL; |
174 | | |
175 | 1.21k | if (*cur_pos < 0) |
176 | 0 | return NULL; |
177 | | |
178 | | /* Advance to page which contains the first byte */ |
179 | 1.22k | while (*cur_pos >= mdb->fmt->pg_size) { |
180 | 14 | if (!mdb_read_pg(mdb, mdb_get_int32(mdb->pg_buf,4))) |
181 | 7 | return NULL; |
182 | 7 | *cur_pos -= (mdb->fmt->pg_size - 8); |
183 | 7 | } |
184 | | /* Copy pages into buffer */ |
185 | 1.21k | while (*cur_pos + len >= (size_t)mdb->fmt->pg_size) { |
186 | 0 | size_t piece_len = mdb->fmt->pg_size - *cur_pos; |
187 | 0 | if (_buf) { |
188 | 0 | if (_buf + piece_len > _end) |
189 | 0 | return NULL; |
190 | 0 | memcpy(_buf, mdb->pg_buf + *cur_pos, piece_len); |
191 | 0 | _buf += piece_len; |
192 | 0 | } |
193 | 0 | len -= piece_len; |
194 | 0 | if (!mdb_read_pg(mdb, mdb_get_int32(mdb->pg_buf,4))) |
195 | 0 | return NULL; |
196 | 0 | *cur_pos = 8; |
197 | 0 | } |
198 | | /* Copy into buffer from final page */ |
199 | 1.21k | if (len && _buf) { |
200 | 1.17k | if (_buf + len > _end) |
201 | 0 | return NULL; |
202 | 1.17k | memcpy(_buf, mdb->pg_buf + *cur_pos, len); |
203 | 1.17k | } |
204 | 1.21k | *cur_pos += len; |
205 | 1.21k | return _buf; |
206 | 1.21k | } |
207 | | |
208 | | |
209 | | void mdb_append_column(GPtrArray *columns, MdbColumn *in_col) |
210 | 0 | { |
211 | 0 | g_ptr_array_add(columns, g_memdup2(in_col,sizeof(MdbColumn))); |
212 | 0 | } |
213 | | void mdb_free_columns(GPtrArray *columns) |
214 | 287 | { |
215 | 287 | guint i, j; |
216 | 287 | MdbColumn *col; |
217 | | |
218 | 287 | if (!columns) return; |
219 | 440 | for (i=0; i<columns->len; i++) { |
220 | 404 | col = (MdbColumn *) g_ptr_array_index(columns, i); |
221 | 404 | if (col->sargs) { |
222 | 0 | for (j=0; j<col->sargs->len; j++) { |
223 | 0 | g_free( g_ptr_array_index(col->sargs, j)); |
224 | 0 | } |
225 | 0 | g_ptr_array_free(col->sargs, TRUE); |
226 | 0 | } |
227 | 404 | g_free(col); |
228 | 404 | } |
229 | 36 | g_ptr_array_free(columns, TRUE); |
230 | 36 | } |
231 | | GPtrArray *mdb_read_columns(MdbTableDef *table) |
232 | 36 | { |
233 | 36 | MdbHandle *mdb = table->entry->mdb; |
234 | 36 | MdbFormatConstants *fmt = mdb->fmt; |
235 | 36 | MdbColumn *pcol; |
236 | 36 | unsigned char *col; |
237 | 36 | unsigned int i; |
238 | 36 | guint j; |
239 | 36 | int cur_pos; |
240 | 36 | size_t name_sz; |
241 | 36 | GPtrArray *allprops; |
242 | | |
243 | 36 | table->columns = g_ptr_array_new(); |
244 | | |
245 | 36 | col = g_malloc(fmt->tab_col_entry_size); |
246 | | |
247 | 36 | cur_pos = fmt->tab_cols_start_offset + |
248 | 36 | (table->num_real_idxs * fmt->tab_ridx_entry_size); |
249 | | |
250 | | /* new code based on patch submitted by Tim Nelson 2000.09.27 */ |
251 | | |
252 | | /* |
253 | | ** column attributes |
254 | | */ |
255 | 440 | for (i=0;i<table->num_cols;i++) { |
256 | 411 | #ifdef MDB_DEBUG |
257 | | /* printf("column %d\n", i); |
258 | | mdb_buffer_dump(mdb->pg_buf, cur_pos, fmt->tab_col_entry_size); */ |
259 | 411 | #endif |
260 | 411 | if (!read_pg_if_n(mdb, col, &cur_pos, fmt->tab_col_entry_size)) { |
261 | 7 | g_free(col); |
262 | 7 | mdb_free_columns(table->columns); |
263 | 7 | return table->columns = NULL; |
264 | 7 | } |
265 | 404 | pcol = g_malloc0(sizeof(MdbColumn)); |
266 | | |
267 | 404 | pcol->table = table; |
268 | | |
269 | 404 | pcol->col_type = col[0]; |
270 | | |
271 | | // col_num_offset == 1 or 5 |
272 | 404 | pcol->col_num = col[fmt->col_num_offset]; |
273 | | |
274 | | //fprintf(stdout,"----- column %d -----\n",pcol->col_num); |
275 | | // col_var == 3 or 7 |
276 | 404 | pcol->var_col_num = mdb_get_int16(col, fmt->tab_col_offset_var); |
277 | | //fprintf(stdout,"var column pos %d\n",pcol->var_col_num); |
278 | | |
279 | | // col_var == 5 or 9 |
280 | 404 | pcol->row_col_num = mdb_get_int16(col, fmt->tab_row_col_num_offset); |
281 | | //fprintf(stdout,"row column num %d\n",pcol->row_col_num); |
282 | | |
283 | 404 | if (pcol->col_type == MDB_NUMERIC || pcol->col_type == MDB_MONEY || |
284 | 404 | pcol->col_type == MDB_FLOAT || pcol->col_type == MDB_DOUBLE) { |
285 | 37 | pcol->col_scale = col[fmt->col_scale_offset]; |
286 | 37 | pcol->col_prec = col[fmt->col_prec_offset]; |
287 | 37 | } |
288 | | |
289 | | // col_flags_offset == 13 or 15 |
290 | 404 | pcol->is_fixed = col[fmt->col_flags_offset] & 0x01 ? 1 : 0; |
291 | 404 | pcol->is_long_auto = col[fmt->col_flags_offset] & 0x04 ? 1 : 0; |
292 | 404 | pcol->is_uuid_auto = col[fmt->col_flags_offset] & 0x40 ? 1 : 0; |
293 | | |
294 | | // tab_col_offset_fixed == 14 or 21 |
295 | 404 | pcol->fixed_offset = mdb_get_int16(col, fmt->tab_col_offset_fixed); |
296 | | //fprintf(stdout,"fixed column offset %d\n",pcol->fixed_offset); |
297 | | //fprintf(stdout,"col type %s\n",pcol->is_fixed ? "fixed" : "variable"); |
298 | | |
299 | 404 | if (pcol->col_type != MDB_BOOL) { |
300 | | // col_size_offset == 16 or 23 |
301 | 348 | pcol->col_size = mdb_get_int16(col, fmt->col_size_offset); |
302 | 348 | } else { |
303 | 56 | pcol->col_size=0; |
304 | 56 | } |
305 | | |
306 | 404 | g_ptr_array_add(table->columns, pcol); |
307 | 404 | } |
308 | | |
309 | 29 | g_free (col); |
310 | | |
311 | | /* |
312 | | ** column names - ordered the same as the column attributes table |
313 | | */ |
314 | 433 | for (i=0;i<table->num_cols;i++) { |
315 | 404 | char *tmp_buf; |
316 | 404 | pcol = g_ptr_array_index(table->columns, i); |
317 | | |
318 | 404 | if (IS_JET3(mdb)) |
319 | 404 | name_sz = read_pg_if_8(mdb, &cur_pos); |
320 | 0 | else |
321 | 0 | name_sz = read_pg_if_16(mdb, &cur_pos); |
322 | 404 | tmp_buf = g_malloc(name_sz); |
323 | 404 | if (read_pg_if_n(mdb, tmp_buf, &cur_pos, name_sz)) |
324 | 404 | mdb_unicode2ascii(mdb, tmp_buf, name_sz, pcol->name, sizeof(pcol->name)); |
325 | 404 | g_free(tmp_buf); |
326 | 404 | } |
327 | | |
328 | | /* Sort the columns by col_num */ |
329 | 29 | g_ptr_array_sort(table->columns, (GCompareFunc)mdb_col_comparer); |
330 | | |
331 | 29 | allprops = table->entry->props; |
332 | 29 | if (allprops) |
333 | 0 | for (i=0;i<table->num_cols;i++) { |
334 | 0 | pcol = g_ptr_array_index(table->columns, i); |
335 | 0 | for (j=0; j<allprops->len; ++j) { |
336 | 0 | MdbProperties *props = g_ptr_array_index(allprops, j); |
337 | 0 | if (props->name && !strcmp(props->name, pcol->name)) { |
338 | 0 | pcol->props = props; |
339 | 0 | break; |
340 | 0 | } |
341 | |
|
342 | 0 | } |
343 | 0 | } |
344 | 29 | table->index_start = cur_pos; |
345 | 29 | return table->columns; |
346 | 36 | } |
347 | | |
348 | | void mdb_table_dump(MdbCatalogEntry *entry) |
349 | 0 | { |
350 | 0 | MdbTableDef *table; |
351 | 0 | MdbColumn *col; |
352 | 0 | int coln; |
353 | 0 | MdbIndex *idx; |
354 | 0 | unsigned int i, bitn; |
355 | 0 | guint32 pgnum; |
356 | |
|
357 | 0 | table = mdb_read_table(entry); |
358 | 0 | fprintf(stdout,"definition page = %lu\n",entry->table_pg); |
359 | 0 | fprintf(stdout,"number of datarows = %d\n",table->num_rows); |
360 | 0 | fprintf(stdout,"number of columns = %d\n",table->num_cols); |
361 | 0 | fprintf(stdout,"number of indices = %d\n",table->num_real_idxs); |
362 | |
|
363 | 0 | if (table->props) |
364 | 0 | mdb_dump_props(table->props, stdout, 0); |
365 | 0 | mdb_read_columns(table); |
366 | 0 | mdb_read_indices(table); |
367 | |
|
368 | 0 | for (i=0;i<table->num_cols;i++) { |
369 | 0 | col = g_ptr_array_index(table->columns,i); |
370 | | |
371 | 0 | fprintf(stdout,"column %d Name: %-20s Type: %s(%d)\n", |
372 | 0 | i, col->name, |
373 | 0 | mdb_get_colbacktype_string(col), |
374 | 0 | col->col_size); |
375 | 0 | if (col->props) |
376 | 0 | mdb_dump_props(col->props, stdout, 0); |
377 | 0 | } |
378 | |
|
379 | 0 | for (i=0;i<table->num_idxs;i++) { |
380 | 0 | idx = g_ptr_array_index (table->indices, i); |
381 | 0 | mdb_index_dump(table, idx); |
382 | 0 | } |
383 | 0 | if (table->usage_map) { |
384 | 0 | printf("pages reserved by this object\n"); |
385 | 0 | printf("usage map pg %" G_GUINT32_FORMAT "\n", |
386 | 0 | table->map_base_pg); |
387 | 0 | printf("free map pg %" G_GUINT32_FORMAT "\n", |
388 | 0 | table->freemap_base_pg); |
389 | 0 | pgnum = mdb_get_int32(table->usage_map,1); |
390 | | /* the first 5 bytes of the usage map mean something */ |
391 | 0 | coln = 0; |
392 | 0 | for (i=5;i<table->map_sz;i++) { |
393 | 0 | for (bitn=0;bitn<8;bitn++) { |
394 | 0 | if (table->usage_map[i] & 1 << bitn) { |
395 | 0 | coln++; |
396 | 0 | printf("%6" G_GUINT32_FORMAT, pgnum); |
397 | 0 | if (coln==10) { |
398 | 0 | printf("\n"); |
399 | 0 | coln = 0; |
400 | 0 | } else { |
401 | 0 | printf(" "); |
402 | 0 | } |
403 | 0 | } |
404 | 0 | pgnum++; |
405 | 0 | } |
406 | 0 | } |
407 | 0 | printf("\n"); |
408 | 0 | } |
409 | 0 | } |
410 | | |
411 | | int mdb_is_user_table(MdbCatalogEntry *entry) |
412 | 0 | { |
413 | 0 | return ((entry->object_type == MDB_TABLE) |
414 | 0 | && !(entry->flags & 0x80000002)) ? 1 : 0; |
415 | 0 | } |
416 | | int mdb_is_system_table(MdbCatalogEntry *entry) |
417 | 0 | { |
418 | 0 | return ((entry->object_type == MDB_TABLE) |
419 | 0 | && (entry->flags & 0x80000002)) ? 1 : 0; |
420 | 0 | } |
421 | | |
422 | | const char * |
423 | 0 | mdb_table_get_prop(const MdbTableDef *table, const gchar *key) { |
424 | 0 | if (!table->props) |
425 | 0 | return NULL; |
426 | 0 | return g_hash_table_lookup(table->props->hash, key); |
427 | 0 | } |
428 | | |
429 | | const char * |
430 | 42 | mdb_col_get_prop(const MdbColumn *col, const gchar *key) { |
431 | 42 | if (!col->props) |
432 | 42 | return NULL; |
433 | 0 | return g_hash_table_lookup(col->props->hash, key); |
434 | 42 | } |
435 | | |
436 | 42 | int mdb_col_is_shortdate(const MdbColumn *col) { |
437 | 42 | const char *format = mdb_col_get_prop(col, "Format"); |
438 | 42 | return format && !strcmp(format, "Short Date"); |
439 | 42 | } |