Coverage Report

Created: 2026-01-17 08:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
124k
{
24
124k
  if ((*a)->col_num > (*b)->col_num)
25
35.9k
    return 1;
26
88.1k
  else if ((*a)->col_num < (*b)->col_num)
27
26.3k
    return -1;
28
61.7k
  else
29
61.7k
    return 0;
30
124k
}
31
32
MdbTableDef *mdb_alloc_tabledef(MdbCatalogEntry *entry)
33
6.09k
{
34
6.09k
  MdbTableDef *table = g_malloc0(sizeof(MdbTableDef));
35
6.09k
  table->entry=entry;
36
6.09k
  snprintf(table->name, sizeof(table->name), "%s", entry->object_name);
37
38
6.09k
  return table; 
39
6.09k
}
40
void mdb_free_tabledef(MdbTableDef *table)
41
6.09k
{
42
6.09k
  if (!table) return;
43
6.09k
  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.09k
  mdb_free_columns(table->columns);
53
6.09k
  mdb_free_indices(table->indices);
54
6.09k
  g_free(table->usage_map);
55
6.09k
  g_free(table->free_usage_map);
56
6.09k
  g_free(table);
57
6.09k
}
58
MdbTableDef *mdb_read_table(MdbCatalogEntry *entry)
59
12.1k
{
60
12.1k
  MdbTableDef *table;
61
12.1k
  MdbHandle *mdb = entry->mdb;
62
12.1k
  MdbFormatConstants *fmt = mdb->fmt;
63
12.1k
  int row_start, pg_row;
64
12.1k
  void *buf, *pg_buf = mdb->pg_buf;
65
12.1k
  guint i;
66
67
12.1k
  if (!mdb_read_pg(mdb, entry->table_pg)) {
68
889
        fprintf(stderr, "mdb_read_table: Unable to read page %lu\n", entry->table_pg);
69
889
        return NULL;
70
889
    }
71
11.2k
  if (mdb_get_byte(pg_buf, 0) != 0x02) {
72
5.17k
        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.17k
                entry->table_pg, (int)fmt->pg_size, mdb_get_byte(pg_buf, 0));
74
5.17k
    return NULL;
75
5.17k
    }
76
6.09k
  table = mdb_alloc_tabledef(entry);
77
78
6.09k
  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.09k
  table->num_rows = mdb_get_int32(pg_buf, fmt->tab_num_rows_offset);
83
6.09k
  table->num_var_cols = mdb_get_int16(pg_buf, fmt->tab_num_cols_offset-2);
84
6.09k
  table->num_cols = mdb_get_int16(pg_buf, fmt->tab_num_cols_offset);
85
6.09k
  table->num_idxs = mdb_get_int32(pg_buf, fmt->tab_num_idxs_offset);
86
6.09k
  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.09k
  pg_row = mdb_get_int32(pg_buf, fmt->tab_usage_map_offset);
90
6.09k
  if (mdb_find_pg_row(mdb, pg_row, &buf, &row_start, &(table->map_sz))) {
91
829
        fprintf(stderr, "mdb_read_table: Unable to find page row %d\n", pg_row);
92
829
    mdb_free_tabledef(table);
93
829
    return NULL;
94
829
  }
95
  /* First byte of usage_map is the map-type and must always be present */
96
5.26k
  if (table->map_sz < 1) {
97
305
    fprintf(stderr, "mdb_read_table: invalid map-size: %zu\n", table->map_sz);
98
305
    mdb_free_tabledef(table);
99
305
    return NULL;
100
305
  }
101
4.96k
  table->usage_map = g_memdup2((char*)buf + row_start, table->map_sz);
102
4.96k
  if (mdb_get_option(MDB_DEBUG_USAGE)) 
103
0
    mdb_buffer_dump(buf, row_start, table->map_sz);
104
4.96k
  mdb_debug(MDB_DEBUG_USAGE,"usage map found on page %ld row %d start %d len %d",
105
4.96k
    pg_row >> 8, pg_row & 0xff, row_start, table->map_sz);
106
107
  /* grab a copy of the free space page map */
108
4.96k
  pg_row = mdb_get_int32(pg_buf, fmt->tab_free_map_offset);
109
4.96k
  if (mdb_find_pg_row(mdb, pg_row, &buf, &row_start, &(table->freemap_sz))) {
110
406
        fprintf(stderr, "mdb_read_table: Unable to find page row %d\n", pg_row);
111
406
    mdb_free_tabledef(table);
112
406
    return NULL;
113
406
  }
114
4.55k
  table->free_usage_map = g_memdup2((char*)buf + row_start, table->freemap_sz);
115
4.55k
  mdb_debug(MDB_DEBUG_USAGE,"free map found on page %ld row %d start %d len %d\n",
116
4.55k
    pg_row >> 8, pg_row & 0xff, row_start, table->freemap_sz);
117
118
4.55k
  table->first_data_pg = mdb_get_int16(pg_buf, fmt->tab_first_dpg_offset);
119
120
4.55k
  if (entry->props)
121
1.58k
    for (i=0; i<entry->props->len; ++i) {
122
37
      MdbProperties *props = g_ptr_array_index(entry->props, i);
123
37
      if (!props->name)
124
0
        table->props = props;
125
37
    }
126
127
4.55k
  return table;
128
4.96k
}
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
16.0k
{
157
16.0k
  char c[2];
158
159
16.0k
  read_pg_if_n(mdb, c, cur_pos, 2);
160
16.0k
  return mdb_get_int16(c, 0);
161
16.0k
}
162
guint8
163
read_pg_if_8(MdbHandle *mdb, int *cur_pos)
164
22.7k
{
165
22.7k
  guint8 c;
166
167
22.7k
  read_pg_if_n(mdb, &c, cur_pos, 1);
168
22.7k
  return c;
169
22.7k
}
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
154k
{
178
154k
  char* _buf = buf;
179
154k
  char* _end = buf ? buf + len : NULL;
180
181
154k
  if (*cur_pos < 0)
182
952
    return NULL;
183
184
  /* Advance to page which contains the first byte */
185
154k
  while (*cur_pos >= mdb->fmt->pg_size) {
186
444
    if (!mdb_read_pg(mdb, mdb_get_int32(mdb->pg_buf,4)))
187
383
      return NULL;
188
61
    *cur_pos -= (mdb->fmt->pg_size - 8);
189
61
  }
190
  /* Copy pages into buffer */
191
215k
  while (*cur_pos + len >= (size_t)mdb->fmt->pg_size) {
192
62.1k
    size_t piece_len = mdb->fmt->pg_size - *cur_pos;
193
62.1k
    if (_buf) {
194
62.1k
      if (_buf + piece_len > _end)
195
0
        return NULL;
196
62.1k
      memcpy(_buf, mdb->pg_buf + *cur_pos, piece_len);
197
62.1k
      _buf += piece_len;
198
62.1k
    }
199
62.1k
    len -= piece_len;
200
62.1k
    if (!mdb_read_pg(mdb, mdb_get_int32(mdb->pg_buf,4)))
201
410
      return NULL;
202
61.7k
    *cur_pos = 8;
203
61.7k
  }
204
  /* Copy into buffer from final page */
205
153k
  if (len && _buf) {
206
145k
    if (_buf + len > _end)
207
0
      return NULL;
208
145k
    memcpy(_buf, mdb->pg_buf + *cur_pos, len);
209
145k
  }
210
153k
  *cur_pos += len;
211
153k
  return _buf;
212
153k
}
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.75k
{
221
7.75k
  guint i, j;
222
7.75k
  MdbColumn *col;
223
224
7.75k
  if (!columns) return;
225
80.1k
  for (i=0; i<columns->len; i++) {
226
75.5k
    col = (MdbColumn *) g_ptr_array_index(columns, i);
227
75.5k
    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
75.5k
    g_free(col);
234
75.5k
  }
235
4.55k
  g_ptr_array_free(columns, TRUE);
236
4.55k
}
237
GPtrArray *mdb_read_columns(MdbTableDef *table)
238
4.55k
{
239
4.55k
  MdbHandle *mdb = table->entry->mdb;
240
4.55k
  MdbFormatConstants *fmt = mdb->fmt;
241
4.55k
  MdbColumn *pcol;
242
4.55k
  unsigned char *col;
243
4.55k
  unsigned int i;
244
4.55k
  guint j;
245
4.55k
  int cur_pos;
246
4.55k
  size_t name_sz;
247
4.55k
  GPtrArray *allprops;
248
  
249
4.55k
  table->columns = g_ptr_array_new();
250
251
4.55k
  col = g_malloc(fmt->tab_col_entry_size);
252
253
4.55k
  cur_pos = fmt->tab_cols_start_offset + 
254
4.55k
    (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
80.1k
  for (i=0;i<table->num_cols;i++) {
262
77.2k
#ifdef MDB_DEBUG
263
  /* printf("column %d\n", i);
264
  mdb_buffer_dump(mdb->pg_buf, cur_pos, fmt->tab_col_entry_size); */
265
77.2k
#endif
266
77.2k
    if (!read_pg_if_n(mdb, col, &cur_pos, fmt->tab_col_entry_size)) {
267
1.66k
      g_free(col);
268
1.66k
      mdb_free_columns(table->columns);
269
1.66k
      return table->columns = NULL;
270
1.66k
    }
271
75.5k
    pcol = g_malloc0(sizeof(MdbColumn));
272
273
75.5k
    pcol->table = table;
274
275
75.5k
    pcol->col_type = col[0];
276
277
    // col_num_offset == 1 or 5
278
75.5k
    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
75.5k
    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
75.5k
    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
75.5k
    if (pcol->col_type == MDB_NUMERIC || pcol->col_type == MDB_MONEY ||
290
74.6k
        pcol->col_type == MDB_FLOAT || pcol->col_type == MDB_DOUBLE) {
291
2.95k
      pcol->col_scale = col[fmt->col_scale_offset];
292
2.95k
      pcol->col_prec = col[fmt->col_prec_offset];
293
2.95k
    }
294
295
    // col_flags_offset == 13 or 15
296
75.5k
    pcol->is_fixed = col[fmt->col_flags_offset] & 0x01 ? 1 : 0;
297
75.5k
    pcol->is_long_auto = col[fmt->col_flags_offset] & 0x04 ? 1 : 0;
298
75.5k
    pcol->is_uuid_auto = col[fmt->col_flags_offset] & 0x40 ? 1 : 0;
299
300
    // tab_col_offset_fixed == 14 or 21
301
75.5k
    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
75.5k
    if (pcol->col_type != MDB_BOOL) {
306
      // col_size_offset == 16 or 23
307
72.0k
      pcol->col_size = mdb_get_int16(col, fmt->col_size_offset);
308
72.0k
    } else {
309
3.53k
      pcol->col_size=0;
310
3.53k
    }
311
    
312
75.5k
    g_ptr_array_add(table->columns, pcol);
313
75.5k
  }
314
315
2.89k
  g_free (col);
316
317
  /* 
318
  ** column names - ordered the same as the column attributes table
319
  */
320
41.7k
  for (i=0;i<table->num_cols;i++) {
321
38.8k
    char *tmp_buf;
322
38.8k
    pcol = g_ptr_array_index(table->columns, i);
323
324
38.8k
    if (IS_JET3(mdb))
325
22.7k
      name_sz = read_pg_if_8(mdb, &cur_pos);
326
16.0k
    else
327
16.0k
      name_sz = read_pg_if_16(mdb, &cur_pos);
328
38.8k
    tmp_buf = g_malloc(name_sz);
329
38.8k
    if (read_pg_if_n(mdb, tmp_buf, &cur_pos, name_sz))
330
38.7k
      mdb_unicode2ascii(mdb, tmp_buf, name_sz, pcol->name, sizeof(pcol->name));
331
38.8k
    g_free(tmp_buf);
332
38.8k
  }
333
334
  /* Sort the columns by col_num */
335
2.89k
  g_ptr_array_sort(table->columns, (GCompareFunc)mdb_col_comparer);
336
337
2.89k
  allprops = table->entry->props;
338
2.89k
  if (allprops)
339
5.46k
    for (i=0;i<table->num_cols;i++) {
340
4.18k
      pcol = g_ptr_array_index(table->columns, i);
341
4.77k
      for (j=0; j<allprops->len; ++j) {
342
592
        MdbProperties *props = g_ptr_array_index(allprops, j);
343
592
        if (props->name && !strcmp(props->name, pcol->name)) {
344
0
          pcol->props = props;
345
0
          break;
346
0
        }
347
348
592
      }
349
4.18k
    }
350
2.89k
  table->index_start = cur_pos;
351
2.89k
  return table->columns;
352
4.55k
}
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.60k
mdb_col_get_prop(const MdbColumn *col, const gchar *key) {
440
2.60k
  if (!col->props)
441
2.60k
    return NULL;
442
0
  return g_hash_table_lookup(col->props->hash, key);
443
2.60k
}
444
445
2.60k
int mdb_col_is_shortdate(const MdbColumn *col) {
446
2.60k
    const char *format = mdb_col_get_prop(col, "Format");
447
2.60k
    return format && !strcmp(format, "Short Date");
448
2.60k
}