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
mdb_col_comparer(MdbColumn ** a,MdbColumn ** b)22 static gint mdb_col_comparer(MdbColumn **a, MdbColumn **b)
23 {
24 if ((*a)->col_num > (*b)->col_num)
25 return 1;
26 else if ((*a)->col_num < (*b)->col_num)
27 return -1;
28 else
29 return 0;
30 }
31
mdb_alloc_tabledef(MdbCatalogEntry * entry)32 MdbTableDef *mdb_alloc_tabledef(MdbCatalogEntry *entry)
33 {
34 MdbTableDef *table = g_malloc0(sizeof(MdbTableDef));
35 table->entry=entry;
36 snprintf(table->name, sizeof(table->name), "%s", entry->object_name);
37
38 return table;
39 }
mdb_free_tabledef(MdbTableDef * table)40 void mdb_free_tabledef(MdbTableDef *table)
41 {
42 if (!table) return;
43 if (table->is_temp_table) {
44 guint i;
45 /* Temp table pages are being stored in memory */
46 for (i=0; i<table->temp_table_pages->len; i++)
47 g_free(g_ptr_array_index(table->temp_table_pages,i));
48 g_ptr_array_free(table->temp_table_pages, TRUE);
49 /* Temp tables use dummy entries */
50 g_free(table->entry);
51 }
52 mdb_free_columns(table->columns);
53 mdb_free_indices(table->indices);
54 g_free(table->usage_map);
55 g_free(table->free_usage_map);
56 g_free(table);
57 }
mdb_read_table(MdbCatalogEntry * entry)58 MdbTableDef *mdb_read_table(MdbCatalogEntry *entry)
59 {
60 MdbTableDef *table;
61 MdbHandle *mdb = entry->mdb;
62 MdbFormatConstants *fmt = mdb->fmt;
63 int row_start, pg_row;
64 void *buf, *pg_buf = mdb->pg_buf;
65 guint i;
66
67 if (!mdb_read_pg(mdb, entry->table_pg)) {
68 fprintf(stderr, "mdb_read_table: Unable to read page %lu\n", entry->table_pg);
69 return NULL;
70 }
71 if (mdb_get_byte(pg_buf, 0) != 0x02) {
72 fprintf(stderr, "mdb_read_table: Page %lu [size=%d] is not a valid table definition page (First byte = 0x%02X, expected 0x02)\n",
73 entry->table_pg, (int)fmt->pg_size, mdb_get_byte(pg_buf, 0));
74 return NULL;
75 }
76 table = mdb_alloc_tabledef(entry);
77
78 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 table->num_rows = mdb_get_int32(pg_buf, fmt->tab_num_rows_offset);
83 table->num_var_cols = mdb_get_int16(pg_buf, fmt->tab_num_cols_offset-2);
84 table->num_cols = mdb_get_int16(pg_buf, fmt->tab_num_cols_offset);
85 table->num_idxs = mdb_get_int32(pg_buf, fmt->tab_num_idxs_offset);
86 table->num_real_idxs = mdb_get_int32(pg_buf, fmt->tab_num_ridxs_offset);
87
88 /* grab a copy of the usage map */
89 pg_row = mdb_get_int32(pg_buf, fmt->tab_usage_map_offset);
90 if (mdb_find_pg_row(mdb, pg_row, &buf, &row_start, &(table->map_sz))) {
91 fprintf(stderr, "mdb_read_table: Unable to find page row %d\n", pg_row);
92 mdb_free_tabledef(table);
93 return NULL;
94 }
95 table->usage_map = g_memdup2((char*)buf + row_start, table->map_sz);
96 if (mdb_get_option(MDB_DEBUG_USAGE))
97 mdb_buffer_dump(buf, row_start, table->map_sz);
98 mdb_debug(MDB_DEBUG_USAGE,"usage map found on page %ld row %d start %d len %d",
99 pg_row >> 8, pg_row & 0xff, row_start, table->map_sz);
100
101 /* grab a copy of the free space page map */
102 pg_row = mdb_get_int32(pg_buf, fmt->tab_free_map_offset);
103 if (mdb_find_pg_row(mdb, pg_row, &buf, &row_start, &(table->freemap_sz))) {
104 fprintf(stderr, "mdb_read_table: Unable to find page row %d\n", pg_row);
105 mdb_free_tabledef(table);
106 return NULL;
107 }
108 table->free_usage_map = g_memdup2((char*)buf + row_start, table->freemap_sz);
109 mdb_debug(MDB_DEBUG_USAGE,"free map found on page %ld row %d start %d len %d\n",
110 pg_row >> 8, pg_row & 0xff, row_start, table->freemap_sz);
111
112 table->first_data_pg = mdb_get_int16(pg_buf, fmt->tab_first_dpg_offset);
113
114 if (entry->props)
115 for (i=0; i<entry->props->len; ++i) {
116 MdbProperties *props = g_ptr_array_index(entry->props, i);
117 if (!props->name)
118 table->props = props;
119 }
120
121 return table;
122 }
mdb_read_table_by_name(MdbHandle * mdb,gchar * table_name,int obj_type)123 MdbTableDef *mdb_read_table_by_name(MdbHandle *mdb, gchar *table_name, int obj_type)
124 {
125 unsigned int i;
126 MdbCatalogEntry *entry;
127
128 mdb_read_catalog(mdb, obj_type);
129
130 for (i=0; i<mdb->num_catalog; i++) {
131 entry = g_ptr_array_index(mdb->catalog, i);
132 if (!g_ascii_strcasecmp(entry->object_name, table_name))
133 return mdb_read_table(entry);
134 }
135
136 return NULL;
137 }
138
139
140 guint32
read_pg_if_32(MdbHandle * mdb,int * cur_pos)141 read_pg_if_32(MdbHandle *mdb, int *cur_pos)
142 {
143 char c[4];
144
145 read_pg_if_n(mdb, c, cur_pos, 4);
146 return mdb_get_int32(c, 0);
147 }
148 guint16
read_pg_if_16(MdbHandle * mdb,int * cur_pos)149 read_pg_if_16(MdbHandle *mdb, int *cur_pos)
150 {
151 char c[2];
152
153 read_pg_if_n(mdb, c, cur_pos, 2);
154 return mdb_get_int16(c, 0);
155 }
156 guint8
read_pg_if_8(MdbHandle * mdb,int * cur_pos)157 read_pg_if_8(MdbHandle *mdb, int *cur_pos)
158 {
159 guint8 c;
160
161 read_pg_if_n(mdb, &c, cur_pos, 1);
162 return c;
163 }
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 *
read_pg_if_n(MdbHandle * mdb,void * buf,int * cur_pos,size_t len)170 read_pg_if_n(MdbHandle *mdb, void *buf, int *cur_pos, size_t len)
171 {
172 char* _buf = buf;
173 char* _end = buf ? buf + len : NULL;
174
175 if (*cur_pos < 0)
176 return NULL;
177
178 /* Advance to page which contains the first byte */
179 while (*cur_pos >= mdb->fmt->pg_size) {
180 if (!mdb_read_pg(mdb, mdb_get_int32(mdb->pg_buf,4)))
181 return NULL;
182 *cur_pos -= (mdb->fmt->pg_size - 8);
183 }
184 /* Copy pages into buffer */
185 while (*cur_pos + len >= (size_t)mdb->fmt->pg_size) {
186 size_t piece_len = mdb->fmt->pg_size - *cur_pos;
187 if (_buf) {
188 if (_buf + piece_len > _end)
189 return NULL;
190 memcpy(_buf, mdb->pg_buf + *cur_pos, piece_len);
191 _buf += piece_len;
192 }
193 len -= piece_len;
194 if (!mdb_read_pg(mdb, mdb_get_int32(mdb->pg_buf,4)))
195 return NULL;
196 *cur_pos = 8;
197 }
198 /* Copy into buffer from final page */
199 if (len && _buf) {
200 if (_buf + len > _end)
201 return NULL;
202 memcpy(_buf, mdb->pg_buf + *cur_pos, len);
203 }
204 *cur_pos += len;
205 return _buf;
206 }
207
208
mdb_append_column(GPtrArray * columns,MdbColumn * in_col)209 void mdb_append_column(GPtrArray *columns, MdbColumn *in_col)
210 {
211 g_ptr_array_add(columns, g_memdup2(in_col,sizeof(MdbColumn)));
212 }
mdb_free_columns(GPtrArray * columns)213 void mdb_free_columns(GPtrArray *columns)
214 {
215 guint i, j;
216 MdbColumn *col;
217
218 if (!columns) return;
219 for (i=0; i<columns->len; i++) {
220 col = (MdbColumn *) g_ptr_array_index(columns, i);
221 if (col->sargs) {
222 for (j=0; j<col->sargs->len; j++) {
223 g_free( g_ptr_array_index(col->sargs, j));
224 }
225 g_ptr_array_free(col->sargs, TRUE);
226 }
227 g_free(col);
228 }
229 g_ptr_array_free(columns, TRUE);
230 }
mdb_read_columns(MdbTableDef * table)231 GPtrArray *mdb_read_columns(MdbTableDef *table)
232 {
233 MdbHandle *mdb = table->entry->mdb;
234 MdbFormatConstants *fmt = mdb->fmt;
235 MdbColumn *pcol;
236 unsigned char *col;
237 unsigned int i;
238 guint j;
239 int cur_pos;
240 size_t name_sz;
241 GPtrArray *allprops;
242
243 table->columns = g_ptr_array_new();
244
245 col = g_malloc(fmt->tab_col_entry_size);
246
247 cur_pos = fmt->tab_cols_start_offset +
248 (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 for (i=0;i<table->num_cols;i++) {
256 #ifdef MDB_DEBUG
257 /* printf("column %d\n", i);
258 mdb_buffer_dump(mdb->pg_buf, cur_pos, fmt->tab_col_entry_size); */
259 #endif
260 if (!read_pg_if_n(mdb, col, &cur_pos, fmt->tab_col_entry_size)) {
261 g_free(col);
262 mdb_free_columns(table->columns);
263 return table->columns = NULL;
264 }
265 pcol = g_malloc0(sizeof(MdbColumn));
266
267 pcol->table = table;
268
269 pcol->col_type = col[0];
270
271 // col_num_offset == 1 or 5
272 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 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 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 if (pcol->col_type == MDB_NUMERIC || pcol->col_type == MDB_MONEY ||
284 pcol->col_type == MDB_FLOAT || pcol->col_type == MDB_DOUBLE) {
285 pcol->col_scale = col[fmt->col_scale_offset];
286 pcol->col_prec = col[fmt->col_prec_offset];
287 }
288
289 // col_flags_offset == 13 or 15
290 pcol->is_fixed = col[fmt->col_flags_offset] & 0x01 ? 1 : 0;
291 pcol->is_long_auto = col[fmt->col_flags_offset] & 0x04 ? 1 : 0;
292 pcol->is_uuid_auto = col[fmt->col_flags_offset] & 0x40 ? 1 : 0;
293
294 // tab_col_offset_fixed == 14 or 21
295 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 if (pcol->col_type != MDB_BOOL) {
300 // col_size_offset == 16 or 23
301 pcol->col_size = mdb_get_int16(col, fmt->col_size_offset);
302 } else {
303 pcol->col_size=0;
304 }
305
306 g_ptr_array_add(table->columns, pcol);
307 }
308
309 g_free (col);
310
311 /*
312 ** column names - ordered the same as the column attributes table
313 */
314 for (i=0;i<table->num_cols;i++) {
315 char *tmp_buf;
316 pcol = g_ptr_array_index(table->columns, i);
317
318 if (IS_JET3(mdb))
319 name_sz = read_pg_if_8(mdb, &cur_pos);
320 else
321 name_sz = read_pg_if_16(mdb, &cur_pos);
322 tmp_buf = g_malloc(name_sz);
323 if (read_pg_if_n(mdb, tmp_buf, &cur_pos, name_sz))
324 mdb_unicode2ascii(mdb, tmp_buf, name_sz, pcol->name, sizeof(pcol->name));
325 g_free(tmp_buf);
326 }
327
328 /* Sort the columns by col_num */
329 g_ptr_array_sort(table->columns, (GCompareFunc)mdb_col_comparer);
330
331 allprops = table->entry->props;
332 if (allprops)
333 for (i=0;i<table->num_cols;i++) {
334 pcol = g_ptr_array_index(table->columns, i);
335 for (j=0; j<allprops->len; ++j) {
336 MdbProperties *props = g_ptr_array_index(allprops, j);
337 if (props->name && !strcmp(props->name, pcol->name)) {
338 pcol->props = props;
339 break;
340 }
341
342 }
343 }
344 table->index_start = cur_pos;
345 return table->columns;
346 }
347
mdb_table_dump(MdbCatalogEntry * entry)348 void mdb_table_dump(MdbCatalogEntry *entry)
349 {
350 MdbTableDef *table;
351 MdbColumn *col;
352 int coln;
353 MdbIndex *idx;
354 unsigned int i, bitn;
355 guint32 pgnum;
356
357 table = mdb_read_table(entry);
358 fprintf(stdout,"definition page = %lu\n",entry->table_pg);
359 fprintf(stdout,"number of datarows = %d\n",table->num_rows);
360 fprintf(stdout,"number of columns = %d\n",table->num_cols);
361 fprintf(stdout,"number of indices = %d\n",table->num_real_idxs);
362
363 if (table->props)
364 mdb_dump_props(table->props, stdout, 0);
365 mdb_read_columns(table);
366 mdb_read_indices(table);
367
368 for (i=0;i<table->num_cols;i++) {
369 col = g_ptr_array_index(table->columns,i);
370
371 fprintf(stdout,"column %d Name: %-20s Type: %s(%d)\n",
372 i, col->name,
373 mdb_get_colbacktype_string(col),
374 col->col_size);
375 if (col->props)
376 mdb_dump_props(col->props, stdout, 0);
377 }
378
379 for (i=0;i<table->num_idxs;i++) {
380 idx = g_ptr_array_index (table->indices, i);
381 mdb_index_dump(table, idx);
382 }
383 if (table->usage_map) {
384 printf("pages reserved by this object\n");
385 printf("usage map pg %" G_GUINT32_FORMAT "\n",
386 table->map_base_pg);
387 printf("free map pg %" G_GUINT32_FORMAT "\n",
388 table->freemap_base_pg);
389 pgnum = mdb_get_int32(table->usage_map,1);
390 /* the first 5 bytes of the usage map mean something */
391 coln = 0;
392 for (i=5;i<table->map_sz;i++) {
393 for (bitn=0;bitn<8;bitn++) {
394 if (table->usage_map[i] & 1 << bitn) {
395 coln++;
396 printf("%6" G_GUINT32_FORMAT, pgnum);
397 if (coln==10) {
398 printf("\n");
399 coln = 0;
400 } else {
401 printf(" ");
402 }
403 }
404 pgnum++;
405 }
406 }
407 printf("\n");
408 }
409 }
410
mdb_is_user_table(MdbCatalogEntry * entry)411 int mdb_is_user_table(MdbCatalogEntry *entry)
412 {
413 return ((entry->object_type == MDB_TABLE)
414 && !(entry->flags & 0x80000002)) ? 1 : 0;
415 }
mdb_is_system_table(MdbCatalogEntry * entry)416 int mdb_is_system_table(MdbCatalogEntry *entry)
417 {
418 return ((entry->object_type == MDB_TABLE)
419 && (entry->flags & 0x80000002)) ? 1 : 0;
420 }
421
422 const char *
mdb_table_get_prop(const MdbTableDef * table,const gchar * key)423 mdb_table_get_prop(const MdbTableDef *table, const gchar *key) {
424 if (!table->props)
425 return NULL;
426 return g_hash_table_lookup(table->props->hash, key);
427 }
428
429 const char *
mdb_col_get_prop(const MdbColumn * col,const gchar * key)430 mdb_col_get_prop(const MdbColumn *col, const gchar *key) {
431 if (!col->props)
432 return NULL;
433 return g_hash_table_lookup(col->props->hash, key);
434 }
435
mdb_col_is_shortdate(const MdbColumn * col)436 int mdb_col_is_shortdate(const MdbColumn *col) {
437 const char *format = mdb_col_get_prop(col, "Format");
438 return format && !strcmp(format, "Short Date");
439 }
440