Coverage Report

Created: 2025-10-10 07:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mdbtools/src/libmdb/file.c
Line
Count
Source
1
/* MDB Tools - A library for reading MS Access database files
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 <inttypes.h>
20
#include <stddef.h>
21
#include "mdbtools.h"
22
#include "mdbprivate.h"
23
24
MdbFormatConstants MdbJet4Constants = {
25
  .pg_size = 4096,
26
    .row_count_offset = 0x0c,
27
    .tab_num_rows_offset = 16,
28
    .tab_num_cols_offset = 45,
29
    .tab_num_idxs_offset = 47,
30
    .tab_num_ridxs_offset = 51,
31
    .tab_usage_map_offset = 55,
32
    .tab_first_dpg_offset = 56,
33
    .tab_cols_start_offset = 63,
34
    .tab_ridx_entry_size = 12,
35
    .col_scale_offset = 11,
36
    .col_prec_offset = 12,
37
    .col_flags_offset = 15,
38
    .col_size_offset = 23,
39
    .col_num_offset = 5,
40
    .tab_col_entry_size = 25,
41
    .tab_free_map_offset = 59,
42
    .tab_col_offset_var = 7,
43
    .tab_col_offset_fixed = 21,
44
    .tab_row_col_num_offset = 9
45
};
46
MdbFormatConstants MdbJet3Constants = {
47
  .pg_size = 2048,
48
    .row_count_offset = 0x08,
49
    .tab_num_rows_offset = 12,
50
    .tab_num_cols_offset = 25,
51
    .tab_num_idxs_offset = 27,
52
    .tab_num_ridxs_offset = 31,
53
    .tab_usage_map_offset = 35,
54
    .tab_first_dpg_offset = 36,
55
    .tab_cols_start_offset = 43,
56
    .tab_ridx_entry_size = 8,
57
    .col_scale_offset = 9,
58
    .col_prec_offset = 10,
59
    .col_flags_offset = 13,
60
    .col_size_offset = 16,
61
    .col_num_offset = 1,
62
    .tab_col_entry_size = 18,
63
    .tab_free_map_offset = 39,
64
    .tab_col_offset_var = 3,
65
    .tab_col_offset_fixed = 14,
66
    .tab_row_col_num_offset = 5
67
};
68
69
static ssize_t _mdb_read_pg(MdbHandle *mdb, void *pg_buf, unsigned long pg);
70
71
/**
72
 * mdb_find_file:
73
 * @filename: path to MDB (database) file
74
 *
75
 * Finds and returns the absolute path to an MDB file.  Function will first try
76
 * to fstat file as passed, then search through the $MDBPATH if not found.
77
 *
78
 * Return value: gchar pointer to absolute path. Caller is responsible for
79
 * freeing.
80
 **/
81
82
static char *mdb_find_file(const char *file_name)
83
0
{
84
0
  struct stat status;
85
0
  gchar *mdbpath, **dir, *tmpfname;
86
0
  unsigned int i = 0;
87
88
  /* try the provided file name first */
89
0
  if (!stat(file_name, &status)) {
90
0
    char *result;
91
0
    result = g_strdup(file_name);
92
0
    if (!result)
93
0
      fprintf(stderr, "Can't alloc filename\n");
94
0
    return result;
95
0
  }
96
  
97
  /* Now pull apart $MDBPATH and try those */
98
0
  mdbpath = (gchar *) getenv("MDBPATH");
99
  /* no path, can't find file */
100
0
  if (!mdbpath || !strlen(mdbpath)) return NULL;
101
102
0
  dir = g_strsplit(mdbpath, ":", 0); 
103
0
  while (dir[i]) {
104
0
    if (!strlen(dir[i])) continue;
105
0
    tmpfname = g_strconcat(dir[i++], "/", file_name, NULL);
106
0
    if (!stat(tmpfname, &status)) {
107
0
      g_strfreev(dir);
108
0
      return tmpfname;
109
0
    }
110
0
    g_free(tmpfname);
111
0
  }
112
0
  g_strfreev(dir);
113
0
  return NULL;
114
0
}
115
116
/**
117
 * mdb_handle_from_stream:
118
 * @stream An open file stream
119
 * @flags MDB_NOFLAGS for read-only, MDB_WRITABLE for read/write
120
 *
121
 * Allocates, initializes, and return an MDB handle from a file stream pointing
122
 * to an MDB file.
123
 *
124
 * Return value: The handle on success, NULL on failure
125
 */
126
53
static MdbHandle *mdb_handle_from_stream(FILE *stream, MdbFileFlags flags) {
127
53
  MdbHandle *mdb = g_malloc0(sizeof(MdbHandle));
128
53
  mdb_set_default_backend(mdb, "access");
129
53
    mdb_set_date_fmt(mdb, "%x %X");
130
53
    mdb_set_shortdate_fmt(mdb, "%x");
131
53
    mdb_set_bind_size(mdb, MDB_BIND_SIZE);
132
53
    mdb_set_boolean_fmt_numbers(mdb);
133
53
    mdb_set_repid_fmt(mdb, MDB_BRACES_4_2_2_8);
134
53
#ifdef HAVE_ICONV
135
53
  mdb->iconv_in = (iconv_t)-1;
136
53
  mdb->iconv_out = (iconv_t)-1;
137
53
#endif
138
  /* need something to bootstrap with, reassign after page 0 is read */
139
53
  mdb->fmt = &MdbJet3Constants;
140
53
  mdb->f = g_malloc0(sizeof(MdbFile));
141
53
  mdb->f->refs = 1;
142
53
  mdb->f->stream = stream;
143
53
  if (flags & MDB_WRITABLE) {
144
0
    mdb->f->writable = TRUE;
145
0
    }
146
147
53
  if (!mdb_read_pg(mdb, 0)) {
148
    // fprintf(stderr,"Couldn't read first page.\n");
149
0
    mdb_close(mdb);
150
0
    return NULL;
151
0
  }
152
53
  if (mdb->pg_buf[0] != 0) {
153
1
    mdb_close(mdb);
154
1
    return NULL; 
155
1
  }
156
52
  mdb->f->jet_version = mdb_get_byte(mdb->pg_buf, 0x14);
157
52
  switch(mdb->f->jet_version) {
158
48
  case MDB_VER_JET3:
159
48
    mdb->fmt = &MdbJet3Constants;
160
48
    break;
161
0
  case MDB_VER_JET4:
162
4
  case MDB_VER_ACCDB_2007:
163
4
  case MDB_VER_ACCDB_2010:
164
4
  case MDB_VER_ACCDB_2013:
165
4
  case MDB_VER_ACCDB_2016:
166
4
  case MDB_VER_ACCDB_2019:
167
4
    mdb->fmt = &MdbJet4Constants;
168
4
    break;
169
0
  default:
170
0
    fprintf(stderr,"Unknown Jet version: %x\n", mdb->f->jet_version);
171
0
    mdb_close(mdb);
172
0
    return NULL; 
173
52
  }
174
175
52
  unsigned char tmp_key[4] = { 0xC7, 0xDA, 0x39, 0x6B };
176
52
  mdbi_rc4(tmp_key, sizeof(tmp_key),
177
52
    mdb->pg_buf + 0x18,
178
52
    mdb->f->jet_version == MDB_VER_JET3 ? 126 : 128
179
52
  );
180
181
52
  if (mdb->f->jet_version == MDB_VER_JET3) {
182
48
    mdb->f->lang_id = mdb_get_int16(mdb->pg_buf, 0x3a);
183
48
  } else {
184
4
    mdb->f->lang_id = mdb_get_int16(mdb->pg_buf, 0x6e);
185
4
  }
186
52
  mdb->f->code_page = mdb_get_int16(mdb->pg_buf, 0x3c);
187
52
  mdb->f->db_key = mdb_get_int32(mdb->pg_buf, 0x3e);
188
52
    if (mdb->f->jet_version == MDB_VER_JET3) {
189
        /* JET4 needs additional masking with the DB creation date, currently unsupported */
190
        /* Bug - JET3 supports 20 byte passwords, this is currently just 14 bytes */
191
48
        memcpy(mdb->f->db_passwd, mdb->pg_buf + 0x42, sizeof(mdb->f->db_passwd));
192
48
    }
193
194
52
  mdb_iconv_init(mdb);
195
196
52
  return mdb;
197
52
}
198
199
/**
200
 * mdb_open_buffer:
201
 * @buffer A memory buffer containing an MDB file
202
 * @len Length of the buffer
203
 *
204
 * Opens an MDB file in memory and returns an MdbHandle to it.
205
 *
206
 * Return value: point to MdbHandle structure.
207
 */
208
53
MdbHandle *mdb_open_buffer(void *buffer, size_t len, MdbFileFlags flags) {
209
53
    FILE *file = NULL;
210
53
#ifdef HAVE_FMEMOPEN
211
53
    file = fmemopen(buffer, len, (flags & MDB_WRITABLE) ? "r+" : "r");
212
#else
213
    fprintf(stderr, "mdb_open_buffer requires a platform with support for fmemopen(3)\n");
214
#endif
215
53
    if (file == NULL) {
216
0
        fprintf(stderr, "Couldn't open memory buffer\n");
217
0
        return NULL;
218
0
    }
219
53
    return mdb_handle_from_stream(file, flags);
220
53
}
221
222
/**
223
 * mdb_open:
224
 * @filename: path to MDB (database) file
225
 * @flags: MDB_NOFLAGS for read-only, MDB_WRITABLE for read/write
226
 *
227
 * Opens an MDB file and returns an MdbHandle to it.  MDB File may be relative
228
 * to the current directory, a full path to the file, or relative to a 
229
 * component of $MDBPATH.
230
 *
231
 * Return value: pointer to MdbHandle structure.
232
 **/
233
MdbHandle *mdb_open(const char *filename, MdbFileFlags flags)
234
0
{
235
0
    FILE *file;
236
237
0
  char *filepath = mdb_find_file(filename);
238
0
  if (!filepath) {
239
0
    fprintf(stderr, "File not found\n");
240
0
    return NULL; 
241
0
  }
242
#ifdef _WIN32
243
    char *mode = (flags & MDB_WRITABLE) ? "rb+" : "rb";
244
#else
245
0
    char *mode = (flags & MDB_WRITABLE) ? "r+" : "r";
246
0
#endif
247
248
0
    if ((file = fopen(filepath, mode)) == NULL) {
249
0
    fprintf(stderr,"Couldn't open file %s\n",filepath);
250
0
    g_free(filepath);
251
0
    return NULL;
252
0
    }
253
254
0
    g_free(filepath);
255
256
0
    return mdb_handle_from_stream(file, flags);
257
0
}
258
259
/**
260
 * mdb_close:
261
 * @mdb: Handle to open MDB database file
262
 *
263
 * Dereferences MDB file, closes if reference count is 0, and destroys handle.
264
 *
265
 **/
266
void 
267
mdb_close(MdbHandle *mdb)
268
53
{
269
53
  if (!mdb) return; 
270
53
  mdb_free_catalog(mdb);
271
53
  g_free(mdb->stats);
272
53
  g_free(mdb->backend_name);
273
274
53
  if (mdb->f) {
275
53
    if (mdb->f->refs > 1) {
276
0
      mdb->f->refs--;
277
53
    } else {
278
53
      if (mdb->f->stream) fclose(mdb->f->stream);
279
53
      g_free(mdb->f);
280
53
    }
281
53
  }
282
283
53
  mdb_iconv_close(mdb);
284
53
    mdb_remove_backends(mdb);
285
286
53
  g_free(mdb);
287
53
}
288
/**
289
 * mdb_clone_handle:
290
 * @mdb: Handle to open MDB database file
291
 *
292
 * Clones an existing database handle.  Cloned handle shares the file descriptor
293
 * but has its own page buffer, page position, and similar internal variables.
294
 *
295
 * Return value: new handle to the database.
296
 */
297
MdbHandle *mdb_clone_handle(MdbHandle *mdb)
298
0
{
299
0
  MdbHandle *newmdb;
300
0
  MdbCatalogEntry *entry, *data;
301
0
  unsigned int i;
302
303
0
  newmdb = (MdbHandle *) g_memdup2(mdb, sizeof(MdbHandle));
304
305
0
  memset(&newmdb->catalog, 0, sizeof(MdbHandle) - offsetof(MdbHandle, catalog));
306
307
0
  newmdb->catalog = g_ptr_array_new();
308
0
  for (i=0;i<mdb->num_catalog;i++) {
309
0
    entry = g_ptr_array_index(mdb->catalog,i);
310
0
    data = g_memdup2(entry,sizeof(MdbCatalogEntry));
311
0
    data->mdb = newmdb;
312
0
    data->props = NULL;
313
0
    g_ptr_array_add(newmdb->catalog, data);
314
0
  }
315
316
0
  mdb_iconv_init(newmdb);
317
0
  mdb_set_default_backend(newmdb, mdb->backend_name);
318
319
  // formats for the source handle may have been changed from
320
  // the backend's default formats, so we need to explicitly copy them here
321
0
  mdb_set_date_fmt(newmdb, mdb->date_fmt);
322
0
  mdb_set_shortdate_fmt(newmdb, mdb->shortdate_fmt);
323
0
  mdb_set_repid_fmt(newmdb, mdb->repid_fmt);
324
325
0
  if (mdb->f) {
326
0
    mdb->f->refs++;
327
0
  }
328
329
0
  return newmdb;
330
0
}
331
332
/* 
333
** mdb_read a wrapper for read that bails if anything is wrong 
334
*/
335
ssize_t mdb_read_pg(MdbHandle *mdb, unsigned long pg)
336
61.5k
{
337
61.5k
  ssize_t len;
338
339
61.5k
  if (pg && mdb->cur_pg == pg) return mdb->fmt->pg_size;
340
341
60.9k
  len = _mdb_read_pg(mdb, mdb->pg_buf, pg);
342
  //fprintf(stderr, "read page %ld type %02x\n", pg, mdb->pg_buf[0]);
343
60.9k
  mdb->cur_pg = pg;
344
  /* kan - reset the cur_pos on a new page read */
345
60.9k
  mdb->cur_pos = 0; /* kan */
346
60.9k
  return len;
347
61.5k
}
348
ssize_t mdb_read_alt_pg(MdbHandle *mdb, unsigned long pg)
349
1.33M
{
350
1.33M
  return _mdb_read_pg(mdb, mdb->alt_pg_buf, pg);
351
1.33M
}
352
static ssize_t _mdb_read_pg(MdbHandle *mdb, void *pg_buf, unsigned long pg)
353
1.39M
{
354
1.39M
  ssize_t len;
355
1.39M
  off_t offset = pg * mdb->fmt->pg_size;
356
357
1.39M
    if (fseeko(mdb->f->stream, 0, SEEK_END) == -1) {
358
0
        fprintf(stderr, "Unable to seek to end of file\n");
359
0
        return 0;
360
0
    }
361
1.39M
    if (ftello(mdb->f->stream) < offset) { 
362
2.03k
        fprintf(stderr,"offset %" PRIu64 " is beyond EOF\n",(uint64_t)offset);
363
2.03k
        return 0;
364
2.03k
    }
365
1.39M
  if (mdb->stats && mdb->stats->collect) 
366
0
    mdb->stats->pg_reads++;
367
368
1.39M
  if (fseeko(mdb->f->stream, offset, SEEK_SET) == -1) {
369
0
        fprintf(stderr, "Unable to seek to page %lu\n", pg);
370
0
        return 0;
371
0
    }
372
1.39M
  len = fread(pg_buf, 1, mdb->fmt->pg_size, mdb->f->stream);
373
1.39M
  if (ferror(mdb->f->stream)) {
374
0
    perror("read");
375
0
    return 0;
376
0
  }
377
1.39M
    memset(pg_buf + len, 0, mdb->fmt->pg_size - len);
378
  /*
379
   * unencrypt the page if necessary.
380
   * it might make sense to cache the unencrypted data blocks?
381
   */
382
1.39M
  if (pg != 0 && mdb->f->db_key != 0)
383
1
  {
384
1
    uint32_t tmp_key_i = mdb->f->db_key ^ pg;
385
1
    unsigned char tmp_key[4] = {
386
1
      tmp_key_i & 0xFF, (tmp_key_i >> 8) & 0xFF,
387
1
      (tmp_key_i >> 16) & 0xFF, (tmp_key_i >> 24) & 0xFF };
388
1
    mdbi_rc4(tmp_key, sizeof(tmp_key), pg_buf, mdb->fmt->pg_size);
389
1
  }
390
391
1.39M
  return mdb->fmt->pg_size;
392
1.39M
}
393
void mdb_swap_pgbuf(MdbHandle *mdb)
394
2.66M
{
395
2.66M
char tmpbuf[MDB_PGSIZE];
396
397
2.66M
  memcpy(tmpbuf,mdb->pg_buf, MDB_PGSIZE);
398
2.66M
  memcpy(mdb->pg_buf,mdb->alt_pg_buf, MDB_PGSIZE);
399
2.66M
  memcpy(mdb->alt_pg_buf,tmpbuf,MDB_PGSIZE);
400
2.66M
}
401
402
403
unsigned char mdb_get_byte(void *buf, int offset)
404
64.1k
{
405
64.1k
  return ((unsigned char *)(buf))[offset];
406
64.1k
}
407
unsigned char mdb_pg_get_byte(MdbHandle *mdb, int offset)
408
0
{
409
0
  if (offset < 0 || offset+1 > mdb->fmt->pg_size) return -1;
410
0
  mdb->cur_pos++;
411
0
  return mdb->pg_buf[offset];
412
0
}
413
414
int mdb_get_int16(void *buf, int offset)
415
4.81M
{
416
4.81M
    unsigned char *u8_buf = (unsigned char *)buf + offset;
417
4.81M
    return ((uint32_t)u8_buf[0] << 0) + ((uint32_t)u8_buf[1] << 8);
418
4.81M
}
419
int mdb_pg_get_int16(MdbHandle *mdb, int offset)
420
0
{
421
0
  if (offset < 0 || offset+2 > mdb->fmt->pg_size) return -1;
422
0
  mdb->cur_pos+=2;
423
0
  return mdb_get_int16(mdb->pg_buf, offset);
424
0
}
425
426
long mdb_get_int32_msb(void *buf, int offset)
427
0
{
428
0
    unsigned char *u8_buf = (unsigned char *)buf + offset;
429
0
    return
430
0
        ((uint32_t)u8_buf[0] << 24) +
431
0
        ((uint32_t)u8_buf[1] << 16) +
432
0
        ((uint32_t)u8_buf[2] << 8) +
433
0
        ((uint32_t)u8_buf[3] << 0);
434
0
}
435
long mdb_get_int32(void *buf, int offset)
436
1.34M
{
437
1.34M
    unsigned char *u8_buf = (unsigned char *)buf + offset;
438
1.34M
    return
439
1.34M
        ((uint32_t)u8_buf[0] << 0) +
440
1.34M
        ((uint32_t)u8_buf[1] << 8) +
441
1.34M
        ((uint32_t)u8_buf[2] << 16) +
442
1.34M
        ((uint32_t)u8_buf[3] << 24);
443
1.34M
}
444
long mdb_pg_get_int32(MdbHandle *mdb, int offset)
445
0
{
446
0
  if (offset <0 || offset+4 > mdb->fmt->pg_size) return -1;
447
0
  mdb->cur_pos+=4;
448
0
  return mdb_get_int32(mdb->pg_buf, offset);
449
0
}
450
451
float mdb_get_single(void *buf, int offset)
452
143
{
453
143
  union {uint32_t g; float f;} f;
454
143
    unsigned char *u8_buf = (unsigned char *)buf + offset;
455
143
    f.g = ((uint32_t)u8_buf[0] << 0) +
456
143
        ((uint32_t)u8_buf[1] << 8) +
457
143
        ((uint32_t)u8_buf[2] << 16) +
458
143
        ((uint32_t)u8_buf[3] << 24);
459
143
  return f.f;
460
143
}
461
float mdb_pg_get_single(MdbHandle *mdb, int offset)
462
0
{
463
0
       if (offset <0 || offset+4 > mdb->fmt->pg_size) return -1;
464
0
       mdb->cur_pos+=4;
465
0
       return mdb_get_single(mdb->pg_buf, offset);
466
0
}
467
468
double mdb_get_double(void *buf, int offset)
469
3.04k
{
470
3.04k
  union {uint64_t g; double d;} d;
471
3.04k
    unsigned char *u8_buf = (unsigned char *)buf + offset;
472
3.04k
    d.g = ((uint64_t)u8_buf[0] << 0) +
473
3.04k
           ((uint64_t)u8_buf[1] << 8) +
474
3.04k
           ((uint64_t)u8_buf[2] << 16) +
475
3.04k
           ((uint64_t)u8_buf[3] << 24) +
476
3.04k
           ((uint64_t)u8_buf[4] << 32) +
477
3.04k
           ((uint64_t)u8_buf[5] << 40) +
478
3.04k
           ((uint64_t)u8_buf[6] << 48) +
479
3.04k
           ((uint64_t)u8_buf[7] << 56);
480
3.04k
  return d.d;
481
3.04k
}
482
483
double mdb_pg_get_double(MdbHandle *mdb, int offset)
484
0
{
485
0
  if (offset <0 || offset+8 > mdb->fmt->pg_size) return -1;
486
0
  mdb->cur_pos+=8;
487
0
  return mdb_get_double(mdb->pg_buf, offset);
488
0
}
489
490
int 
491
mdb_set_pos(MdbHandle *mdb, int pos)
492
0
{
493
0
  if (pos<0 || pos >= mdb->fmt->pg_size) return 0;
494
495
0
  mdb->cur_pos=pos;
496
0
  return pos;
497
0
}
498
int mdb_get_pos(MdbHandle *mdb)
499
0
{
500
0
  return mdb->cur_pos;
501
0
}