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