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