Coverage Report

Created: 2025-12-11 08:33

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
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
}