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