Coverage Report

Created: 2026-08-13 07:54

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