source: tags/ms_r16q2/ARBDB/ad_load.cxx

Last change on this file was 14582, checked in by westram, 8 years ago
  • remove now-unneeded parameter 'filename' from ARB_zfclose
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 55.8 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : ad_load.cxx                                       //
4//   Purpose   :                                                   //
5//                                                                 //
6//   Institute of Microbiology (Technical University Munich)       //
7//   http://www.arb-home.de/                                       //
8//                                                                 //
9// =============================================================== //
10
11#include <cctype>
12#include <ctime>
13#include <netinet/in.h>
14#include <sys/stat.h>
15
16#include <arbdbt.h>
17#include <arb_str.h>
18#include <arb_file.h>
19#include <arb_defs.h>
20#include <arb_progress.h>
21#include <arb_zfile.h>
22#include <BufferedFileReader.h>
23
24#include "gb_key.h"
25#include "gb_localdata.h"
26#include "gb_map.h"
27#include "gb_load.h"
28#include "ad_io_inline.h"
29
30static int gb_verbose_mode = 0;
31void GB_set_verbose() {
32    gb_verbose_mode = 1;
33}
34
35#define FILESIZE_GRANULARITY 1024 // progress will work with DB files up to 2Tb
36
37/* ----------------------------------------
38 * ASCII format
39 *
40 * Versions:
41 *
42 * V0  - 20.6.95
43 * V1  Full save
44 * V2  Differential save
45 * V3  May read from stdin. Skipped support for old ASCII format.
46 */
47
48static const char *getToken(char **line) {
49    char *token  = *line;
50    (*line)     += strcspn(*line, " \t");
51
52    if ((*line)[0]) {                               // sth follows behind token
53        (*line)[0]  = 0;                            // terminate token
54        (*line)++;
55        (*line)    += strspn(*line, " \t");         // goto next token
56    }
57
58    return token;
59}
60
61static GB_ERROR set_protection_level(GB_MAIN_TYPE *Main, GBDATA *gbd, const char *p) {
62    int      secr, secw, secd, lu;
63    GB_ERROR error = 0;
64
65    secr = secw = secd = 0;
66    lu   = 0;
67
68    if (p && p[0] == ':') {
69        long i;
70
71        secd = p[1]; A_TO_I(secd);
72        secw = p[2]; A_TO_I(secw);
73        secr = p[3]; A_TO_I(secr);
74
75        if (secd<0 || secd>7) error = GBS_global_string("Illegal protection level %i", secd);
76        else if (secw<0 || secw>7) error = GBS_global_string("Illegal protection level %i", secw);
77        else if (secr<0 || secr>7) error = GBS_global_string("Illegal protection level %i", secr);
78
79        lu = atoi(p+4);
80
81        for (i=Main->last_updated; i<=lu; ++i) {
82            gb_assert(i<ALLOWED_DATES);
83            Main->dates[i]     = strdup("unknown date");
84            Main->last_updated = lu+1;
85        }
86    }
87
88    if (!error) {
89        gbd->flags.security_delete = secd;
90        gbd->flags.security_write  = secw;
91        gbd->flags.security_read   = secr;
92        gbd->flags2.last_updated   = lu;
93    }
94
95    return error;
96}
97
98static GB_ERROR gb_parse_ascii_rek(LineReader& r, GBCONTAINER *gb_parent, const char *parent_name) {
99    // if parent_name == 0 -> we are parsing at root-level
100    GB_ERROR      error = 0;
101    int           done  = 0;
102    GB_MAIN_TYPE *Main  = GBCONTAINER_MAIN(gb_parent);
103
104    while (!error && !done) {
105        string LINE;
106        if (!r.getLine(LINE)) break;
107
108        char *line = (char*)(LINE.c_str()); // HACK: code below will modify the content of the string
109
110      rest :
111        line += strspn(line, " \t"); // goto first non-whitespace
112
113        if (line[0]) {          // not empty
114            if (line[0] == '/' && line[1] == '*') { // comment
115                char *eoc = strstr(line+2, "*/");
116                if (eoc) {
117                    line = eoc+2;
118                    goto rest;
119                }
120                error = "expected '*/'";
121            }
122            else { // real content
123                const char *name = getToken(&line);
124
125                if (name[0] == '%' && name[1] == ')' && !name[2]) { // close container
126                    if (!parent_name) {
127                        error = "Unexpected '%)' (not allowed outside container)";
128                    }
129                    else {
130                        if (line[0] == '/' && line[1] == '*') { // comment at container-end
131                            char *eoc  = strstr(line+2, "*/");
132                            if (!eoc) {
133                                error = "expected '*/'"; 
134                            }
135                            else {
136                                line += 2;
137                                *eoc  = 0;
138                                if (strcmp(line, parent_name) != 0) {
139                                    fprintf(stderr,
140                                            "Warning: comment at end of container ('%s') does not match name of container ('%s').\n"
141                                            "         (might be harmless if you've edited the file and did not care about these comments)\n",
142                                            line, parent_name); 
143                                }
144                                line = eoc+2;
145                            }
146                        }
147                        done = 1;
148                    }
149                }
150                else {
151                    const char *protection = NULL;
152                    if (line[0] == ':') {           // protection level
153                        protection = getToken(&line);
154                    }
155
156                    bool readAsString = true;
157                    if (line[0] == '%') {
158                        readAsString = false;
159
160                        const char *type = getToken(&line);
161
162                        if (type[1] == 0 || type[2] != 0) {
163                            error = GBS_global_string("Syntax error in type '%s' (expected %% and 1 more character)", type);
164                        }
165                        else {
166                            if (type[1] == '%' || type[1] == '$') {   // container
167                                if (line[0] == '(' && line[1] == '%') {
168                                    char        *cont_name = strdup(name);
169                                    GBCONTAINER *gbc       = gb_make_container(gb_parent, cont_name, -1, 0);
170
171                                    char *protection_copy = nulldup(protection);
172                                    bool  marked          = type[1] == '$';
173                                    error                 = gb_parse_ascii_rek(r, gbc, cont_name);
174                                    // CAUTION: most buffer variables are invalidated NOW!!!
175
176                                    if (!error) error = set_protection_level(Main, gbc, protection_copy);
177                                    if (marked) GB_write_flag(gbc, 1);
178
179                                    free(protection_copy);
180                                    free(cont_name);
181                                }
182                                else {
183                                    error = "Expected '(%' after '%%'";
184                                }
185                            }
186                            else {
187                                GB_TYPES gb_type = GB_NONE;
188
189                                switch (type[1]) {
190                                    case 'i': gb_type = GB_INT; break;
191                                    case 'l': gb_type = GB_LINK; break;
192                                    case 'y': gb_type = GB_BYTE; break;
193                                    case 'f': gb_type = GB_FLOAT; break;
194                                    case 'I': gb_type = GB_BITS; break;
195
196                                    case 'Y': gb_type = GB_BYTES; break;
197                                    case 'N': gb_type = GB_INTS; break;
198                                    case 'F': gb_type = GB_FLOATS; break;
199                                    case 's': gb_type = GB_STRING; readAsString = true; break; // special case (string starts with '%')
200
201                                    default:
202                                        error = GBS_global_string("Unknown type '%s'", type);
203                                        break;
204                                }
205
206                                if (!error && !readAsString) {
207                                    gb_assert(gb_type != GB_NONE);
208
209                                    GBENTRY *gb_new    = gb_make_entry(gb_parent, name, -1, 0, gb_type);
210                                    if (!gb_new) error = GB_await_error();
211                                    else {
212                                        switch (type[1]) {
213                                            case 'i': error = GB_write_int(gb_new, atoi(line)); break;
214                                            case 'l': error = GB_write_link(gb_new, line); break;
215                                            case 'y': error = GB_write_byte(gb_new, atoi(line)); break;
216                                            case 'f': error = GB_write_float(gb_new, GB_atof(line)); break;
217                                            case 'I': {
218                                                int len = strlen(line);
219                                                gb_assert(line[0] == '\"');
220                                                gb_assert(line[len-1] == '\"');
221                                                error   = GB_write_bits(gb_new, line+1, len-2, "-"); break;
222                                            }
223
224                                            case 'Y': if (gb_ascii_2_bin(line, gb_new)) error = "syntax error in byte-array"; break;
225                                            case 'N': if (gb_ascii_2_bin(line, gb_new)) error = "syntax error in int-array"; break;
226                                            case 'F': if (gb_ascii_2_bin(line, gb_new)) error = "syntax error in float-array"; break;
227
228                                            default: gb_assert(0);  // forgot a case ?
229                                        }
230
231                                        if (!error) error = set_protection_level(Main, gb_new, protection);
232                                    }
233                                }
234                            }
235                        }
236                    }
237                   
238                    if (readAsString) {
239                        if (line[0] != '\"') {
240                            error = GBS_global_string("Unexpected content '%s'", line);
241                        }
242                        else {                      // string entry
243                            char *string_start = line+1;
244                            char *end          = GBS_fconvert_string(string_start);
245
246                            if (!end) error = "Cannot convert string (contains zero char)";
247                            else {
248                                GBDATA *gb_string     = gb_make_entry(gb_parent, name, -1, 0, GB_STRING);
249                                if (!gb_string) error = GB_await_error();
250                                else {
251                                    error             = GB_write_string(gb_string, string_start);
252                                    if (!error) error = set_protection_level(Main, gb_string, protection);
253                                }
254                            }
255                        }
256                    }
257                }
258            }
259        }
260    }
261
262    return error;
263}
264
265static GB_ERROR gb_parse_ascii(LineReader& r, GBCONTAINER *gb_parent) {
266    GB_ERROR error = gb_parse_ascii_rek(r, gb_parent, 0);
267    if (error) {
268        error = GBS_global_string("%s in line %zu", error, r.getLineNumber()); // @@@ consider using r.lineError
269    }
270    return error;
271}
272
273struct BufferedPipeReader : public BufferedFileReader {
274    BufferedPipeReader(const string& pipename, FILE *in)
275        : BufferedFileReader(pipename, in)
276    {}
277    ~BufferedPipeReader() {
278        gb_assert(get_fp() == NULL); // you HAVETO call close() or dont_close() manually
279                                     // (to check the error and to close the pipe)
280    }
281
282    GB_ERROR close() {
283        FILE*&   pipe  = get_fp();
284        GB_ERROR error = ARB_zfclose(pipe);
285        pipe           = NULL;
286        return error;
287    }
288
289    void dont_close() {
290        FILE*& f = get_fp();
291        f        = NULL;
292    }
293};
294
295static GB_ERROR gb_read_ascii_beyond_header(FILE *in, const char *path, GBCONTAINER *gbc) {
296    // This loads an ASCII database (header line has already been read!)
297    GB_ERROR           error = 0;
298    BufferedPipeReader r(path, in);
299
300    GB_search(gbc, GB_SYSTEM_FOLDER, GB_CREATE_CONTAINER); // Switch to Version 3
301    error = gb_parse_ascii(r, gbc);
302    r.dont_close();                                        // done by caller
303
304    return error;
305}
306
307// --------------------------
308//      Read binary files
309
310static long gb_recover_corrupt_file(GBCONTAINER *gbc, FILE *in, GB_ERROR recovery_reason, bool loading_quick_save) {
311    // search pattern dx xx xx xx string 0
312    // returns 0 if recovery was able to resync
313    static FILE *old_in = 0;
314    static unsigned char *file = 0;
315    static long size = 0;
316    if (!GBCONTAINER_MAIN(gbc)->allow_corrupt_file_recovery) {
317        if (!recovery_reason) { recovery_reason = GB_await_error(); }
318        char       *reason         = strdup(recovery_reason);
319        const char *located_reason = GBS_global_string("%s (inside '%s')", reason, GB_get_db_path(gbc));
320
321        if (loading_quick_save) {
322            GB_export_error(located_reason);
323        }
324        else {
325            GB_export_errorf("%s\n"
326                             "(parts of your database might be recoverable using 'arb_repair yourDB.arb newName.arb')\n",
327                             located_reason);
328        }
329        free(reason);
330        return -1;
331    }
332
333    if (GB_is_fifo(in)) {
334        GB_export_error("Unable to recover from corrupt file (Reason: cannot recover from stream)\n"
335                        "Note: if the file is a compressed arb-file, uncompress it manually and retry.");
336        return -1;
337    }
338
339    long pos = ftell(in);
340    if (old_in != in) {
341        file   = (unsigned char *)GB_map_FILE(in, 0);
342        old_in = in;
343        size   = GB_size_of_FILE(in);
344    }
345    for (; pos<size-10; pos ++) {
346        if ((file[pos] & 0xf0) == (GB_STRING_SHRT<<4)) {
347            long s;
348            int c;
349            for (s = pos + 4; s<size && file[s]; s++) {
350                c = file[s];
351                if (! (isalnum(c) || isspace(c) || strchr("._;:,", c))) break;
352            }
353            if (s< size && s > pos+11 && !file[s]) {    // we found something
354                gb_local->search_system_folder = true;
355                return fseek(in, pos, 0);
356            }
357        }
358    }
359    return -1;          // no short string found
360}
361
362// ----------------------------------------
363// #define DEBUG_READ
364#if defined(DEBUG_READ)
365
366static void DEBUG_DUMP_INDENTED(long deep, const char *s) {
367    printf("%*s%s\n", (int)deep, "", s);
368}
369
370#else
371#define DEBUG_DUMP_INDENTED(d, s)
372#endif // DEBUG_READ
373// ----------------------------------------
374
375static long gb_read_bin_rek_V2(FILE *in, GBCONTAINER *gbc_dest, long nitems, long version, 
376                               long reversed, long deep, arb_progress& progress) {
377    GB_MAIN_TYPE *Main = GB_MAIN(gbc_dest);
378
379    DEBUG_DUMP_INDENTED(deep, GBS_global_string("Reading container with %li items", nitems));
380
381    progress.inc_to(ftell(in)/FILESIZE_GRANULARITY);
382    if (progress.aborted()) {
383        GB_export_error(progress.error_if_aborted());
384        return -1;
385    }
386
387    gb_create_header_array(gbc_dest, (int)nitems);
388    gb_header_list *header = GB_DATA_LIST_HEADER(gbc_dest->d);
389    if (deep == 0 && GBCONTAINER_MAIN(gbc_dest)->allow_corrupt_file_recovery) {
390        GB_warning("Now reading to end of file (trying to recover data from broken database)");
391        nitems = 10000000; // read forever at highest level
392    }
393
394    bool is_quicksave = version != 1;
395
396    for (long item = 0; item<nitems; item++) {
397        long type = getc(in);
398        DEBUG_DUMP_INDENTED(deep, GBS_global_string("Item #%li/%li type=%02lx (filepos=%08lx)", item+1, nitems, type, ftell(in)-1));
399        if (type == EOF) {
400            if (GBCONTAINER_MAIN(gbc_dest)->allow_corrupt_file_recovery) {
401                GB_export_error("End of file reached (no error)");
402            }
403            else {
404                GB_export_error("Unexpected end of file seen");
405            }
406            return -1;
407        }
408        if (!type) {
409            int func;
410            if (version == 1) { // master file
411                if (gb_recover_corrupt_file(gbc_dest, in, "Unknown DB type 0 (DB version=1)", is_quicksave)) return -1;
412                continue;
413            }
414            func = getc(in);
415            switch (func) {
416                case 1: {    //  delete entry
417                    int index = (int)gb_get_number(in);
418                    if (index >= gbc_dest->d.nheader) {
419                        gb_create_header_array(gbc_dest, index+1);
420                        header = GB_DATA_LIST_HEADER(gbc_dest->d);
421                    }
422
423                    GBDATA *gb2 = GB_HEADER_LIST_GBD(header[index]);
424                    if (gb2) {
425                        gb_delete_entry(gb2);
426                    }
427                    else {
428                        header[index].flags.set_change(GB_DELETED);
429                    }
430
431                    break;
432                }
433                default:
434                    if (gb_recover_corrupt_file(gbc_dest, in, GBS_global_string("Unknown func=%i", func), is_quicksave)) return -1;
435                    continue;
436            }
437            continue;
438        }
439
440        long    security = getc(in);
441        long    type2    = (type>>4)&0xf;
442        GBQUARK key      = (GBQUARK)gb_get_number(in);
443
444        if (key >= Main->keycnt || !quark2key(Main, key)) {
445            const char *reason = GBS_global_string("database entry with unknown field quark %i", key);
446            if (gb_recover_corrupt_file(gbc_dest, in, reason, is_quicksave)) return -1;
447            continue;
448        }
449
450        DEBUG_DUMP_INDENTED(deep, GBS_global_string("key='%s' type2=%li", quark2key(Main, key), type2));
451
452        GBENTRY     *gbe = NULL;
453        GBCONTAINER *gbc = NULL;
454        GBDATA      *gbd = NULL;
455
456        {
457            int index;
458            if (version == 2) {
459                index = (int)gb_get_number(in);
460                if (index >= gbc_dest->d.nheader) {
461                    gb_create_header_array(gbc_dest, index+1);
462                    header = GB_DATA_LIST_HEADER(gbc_dest->d);
463                }
464
465                if (index >= 0 && (gbd = GB_HEADER_LIST_GBD(header[index]))!=NULL) {
466                    if ((gbd->type() == GB_DB) != (type2 == GB_DB)) {
467                        GB_internal_error("Type changed, you may loose data");
468                        gb_delete_entry(gbd);
469                        SET_GB_HEADER_LIST_GBD(header[index], NULL);
470                    }
471                    else {
472                        if (type2 == GB_DB) {
473                            gbc = gbd->as_container();
474                        }
475                        else {
476                            gbe = gbd->as_entry();
477                            gbe->free_data();
478                        }
479                    }
480                }
481            }
482            else index = -1;
483
484            if (!gbd) {
485                if (type2 == (long)GB_DB) {
486                    gbd = gbc = gb_make_container(gbc_dest, NULL, index, key);
487                }
488                else {
489                    gbd = gbe = gb_make_entry(gbc_dest, NULL, index, key, (GB_TYPES)type2);
490                    gbe->index_check_out();
491                }
492            }
493        }
494
495        gb_assert(implicated(gbe, gbe == gbd));
496        gb_assert(implicated(gbc, gbc == gbd));
497        gb_assert(implicated(gbd, contradicted(gbe, gbc)));
498
499        if (version == 2) {
500            gbd->create_extended();
501            gbd->touch_creation_and_update(Main->clock);
502            header[gbd->index].flags.ever_changed = 1;
503        }
504        else {
505            Main->keys[key].nref_last_saved++;
506        }
507
508        gbd->flags.security_delete     = type >> 1;
509        gbd->flags.security_write      = ((type&1) << 2) + (security >> 6);
510        gbd->flags.security_read       = security >> 3;
511        gbd->flags.compressed_data     = security >> 2;
512        header[gbd->index].flags.flags = (int)((security >> 1) & 1);
513        gbd->flags.unused              = security >> 0;
514        gbd->flags2.last_updated       = getc(in);
515
516        switch (type2) {
517            case GB_INT:
518                {
519                    GB_UINT4 buffer;
520                    if (!fread((char*)&buffer, sizeof(GB_UINT4), 1, in)) {
521                        GB_export_error("File too short, seems truncated");
522                        return -1;
523                    }
524                    gbe->info.i = ntohl(buffer);
525                    break;
526                }
527
528            case GB_FLOAT:
529                if (!fread((char*)&gbe->info.i, sizeof(float), 1, in)) {
530                    GB_export_error("File too short, seems truncated");
531                    return -1;
532                }
533                break;
534            case GB_STRING_SHRT: {
535                long  i    = GB_give_buffer_size();
536                char *buff = GB_give_buffer(GBTUM_SHORT_STRING_SIZE+2);
537                char *p    = buff;
538
539                long size = 0;
540                while (1) {
541                    for (; size<i; size++) {
542                        if (!(*(p++) = getc(in))) goto shrtstring_fully_loaded;
543                    }
544                    i = i*3/2;
545                    buff = GB_increase_buffer(i);
546                    p = buff + size;
547                }
548                  shrtstring_fully_loaded :
549                gbe->insert_data(buff, size, size+1);
550                break;
551            }
552            case GB_STRING:
553            case GB_LINK:
554            case GB_BITS:
555            case GB_BYTES:
556            case GB_INTS:
557            case GB_FLOATS: {
558                long size    = gb_get_number(in);
559                long memsize = gb_get_number(in);
560
561                DEBUG_DUMP_INDENTED(deep, GBS_global_string("size=%li memsize=%li", size, memsize));
562
563                GBENTRY_memory storage(gbe, size, memsize);
564
565                long i = fread(storage, 1, (size_t)memsize, in);
566                if (i!=memsize) {
567                    gb_read_bin_error(in, gbe, "Unexpected EOF found");
568                    return -1;
569                }
570                break;
571            }
572            case GB_DB: {
573                long size = gb_get_number(in);
574                // gbc->d.size  is automatically incremented
575                if (gb_read_bin_rek_V2(in, gbc, size, version, reversed, deep+1, progress)) {
576                    if (!GBCONTAINER_MAIN(gbc_dest)->allow_corrupt_file_recovery) {
577                        return -1;
578                    }
579                }
580                break;
581            }
582            case GB_BYTE:
583                gbe->info.i = getc(in);
584                break;
585            default:
586                gb_read_bin_error(in, gbd, "Unknown type");
587                if (gb_recover_corrupt_file(gbc_dest, in, NULL, is_quicksave)) {
588                    return GBCONTAINER_MAIN(gbc_dest)->allow_corrupt_file_recovery
589                        ? 0                         // loading stopped
590                        : -1;
591                }
592
593                continue;
594        }
595    }
596    return 0;
597}
598
599static GBDATA *gb_search_system_folder_rek(GBDATA *gbd) {
600    GBDATA *gb2;
601    GBDATA *gb_result = 0;
602    for (gb2 = GB_child(gbd); gb2; gb2 = GB_nextChild(gb2)) {
603        int type = GB_read_type(gb2);
604        if (type != GB_DB) continue;
605        if (!strcmp(GB_SYSTEM_FOLDER, GB_read_key_pntr(gb2))) {
606            gb_result = gb2;
607            break;
608        }
609    }
610    return gb_result;
611}
612
613
614static void gb_search_system_folder(GBDATA *gb_main) {
615    /* Search a system folder within the database tree
616     * and copy it to main level
617     */
618    GBDATA   *gb_oldsystem;
619    GB_ERROR  error;
620    GBDATA   *gb_system = GB_entry(gb_main, GB_SYSTEM_FOLDER);
621    if (gb_system) return;
622
623    GB_warning("Searching system information");
624    gb_oldsystem = gb_search_system_folder_rek(gb_main);
625    if (!gb_oldsystem) {
626        GB_warning("!!!!! not found (bad)");
627        return;
628    }
629    gb_system = GB_search(gb_main, GB_SYSTEM_FOLDER, GB_CREATE_CONTAINER);
630    error = GB_copy(gb_system, gb_oldsystem);
631    if (!error) error = GB_delete(gb_oldsystem);
632    if (error) GB_warning(error);
633    GB_warning("***** found (good)");
634}
635
636inline bool read_keyword(const char *expected_keyword, FILE *in, GBCONTAINER *gbc) {
637    gb_assert(strlen(expected_keyword) == 4); // mandatory
638
639    long val         = gb_read_in_uint32(in, 0);
640    bool as_expected = strncmp((char*)&val, expected_keyword, 4) == 0;
641   
642    if (!as_expected) {
643        gb_read_bin_error(in, gbc, GBS_global_string("keyword '%s' not found", expected_keyword));
644    }
645    return as_expected;
646}
647
648static long gb_read_bin(FILE *in, GBCONTAINER *gbc, bool allowed_to_load_diff, arb_progress& progress) {
649    int   c = 1;
650    long  i;
651    long  error;
652    long  j, k;
653    long  version;
654    long  nodecnt;
655    long  first_free_key;
656    char *buffer, *p;
657
658    GB_MAIN_TYPE *Main = GBCONTAINER_MAIN(gbc);
659
660    while (c && c != EOF) {
661        c = getc(in);
662    }
663    if (c==EOF) {
664        gb_read_bin_error(in, gbc, "First zero not found");
665        return 1;
666    }
667
668    if (!read_keyword("vers", in, gbc)) return 1;
669
670    // detect byte order in ARB file
671    i = gb_read_in_uint32(in, 0);
672    bool reversed;
673    switch (i) {
674        case 0x01020304: reversed = false; break;
675        case 0x04030201: reversed = true; break;
676        default:
677            gb_read_bin_error(in, gbc, "keyword '^A^B^C^D' not found");
678            return 1;
679    }
680
681    version = gb_read_in_uint32(in, reversed);
682    if (version == 0) {
683        gb_read_bin_error(in, gbc, "ARB Database version 0 no longer supported (rev [9647])");
684        return 1;
685    }
686    if (version>2) {
687        gb_read_bin_error(in, gbc, "ARB Database version > '2'");
688        return 1;
689    }
690
691    if (version == 2 && !allowed_to_load_diff) {
692        GB_export_error("This is not a primary arb file, please select the master"
693                        " file xxx.arb");
694        return 1;
695    }
696
697    if (!read_keyword("keys", in, gbc)) return 1;
698
699    if (!Main->key_2_index_hash) Main->key_2_index_hash = GBS_create_hash(ALLOWED_KEYS, GB_MIND_CASE);
700
701    first_free_key = 0;
702    Main->free_all_keys();
703
704    buffer = GB_give_buffer(256);
705    while (1) {         // read keys
706        long nrefs = 0;
707        if (version) {
708            nrefs = gb_get_number(in);
709        }
710        p = buffer;
711        for (k=0; ; k++) {
712            c = getc(in);
713            if (!c) break;
714            if (c==EOF) {
715                gb_read_bin_error(in, gbc, "unexpected EOF while reading keys");
716                return 1;
717            }
718            *(p++) = c;
719        }
720        *p = 0;
721
722        if (k > GB_KEY_LEN_MAX) {
723            printf("Warning: Key '%s' exceeds maximum keylength (%i)\n"
724                   "         Please do NOT create such long keys!\n",
725                   buffer, GB_KEY_LEN_MAX);
726        }
727        if (p == buffer) break;
728
729        if (*buffer == 1) {                                                   // empty key
730            long index = gb_create_key(Main, 0, false);
731
732            Main->keys[index].key           = 0;
733            Main->keys[index].nref          = 0;
734            Main->keys[index].next_free_key = first_free_key;
735
736            first_free_key = index;
737        }
738        else {
739            long index = gb_create_key(Main, buffer, false);
740
741            Main->keys[index].nref = nrefs;
742        }
743    }
744
745    Main->first_free_key = first_free_key;
746
747    if (!read_keyword("time", in, gbc)) return 1;
748   
749    for (j=0; j<(ALLOWED_DATES-1); j++) {         // read times
750        p = buffer;
751        for (k=0; k<256; k++) {
752            c = getc(in);
753            if (!c) break;
754            if (c==EOF) {
755                gb_read_bin_error(in, gbc, "unexpected EOF while reading times");
756                return 1;
757            }
758            *(p++) = c;
759        }
760        *p = 0;
761        if (p == buffer) break;
762        freedup(Main->dates[j], buffer);
763    }
764    if (j>=(ALLOWED_DATES-1)) {
765        gb_read_bin_error(in, gbc, "too many date entries");
766        return 1;
767    }
768    Main->last_updated = (unsigned int)j;
769
770    if (!read_keyword("data", in, gbc)) return 1;
771
772    nodecnt = gb_read_in_uint32(in, reversed);
773    GB_give_buffer(256);
774
775    if (version==1) {                                     // teste auf map file falls version == 1
776        long          mode = GB_mode_of_link(Main->path); // old master
777        GB_CSTR       map_path;
778        unsigned long time_of_db;
779
780        if (S_ISLNK(mode)) {
781            char *path2 = GB_follow_unix_link(Main->path);
782            map_path    = gb_mapfile_name(path2);
783            time_of_db  = GB_time_of_file(path2);
784            free(path2);
785        }
786        else {
787            map_path = gb_mapfile_name(Main->path);
788            time_of_db  = GB_time_of_file(Main->path);
789        }
790
791        bool          mapped          = false;
792        GB_ERROR      map_fail_reason = NULL;
793        gb_map_header mheader;
794
795        switch (gb_is_valid_mapfile(map_path, &mheader, 0)) {
796            case -1: map_fail_reason = GBS_global_string("no FastLoad File '%s' found", map_path); break;
797            case  0: map_fail_reason = GB_await_error(); break;
798            case  1: {
799                unsigned long time_of_map = GB_time_of_file(map_path);
800
801                if (time_of_map != time_of_db) { // timestamp of mapfile differs
802                    unsigned long diff = time_of_map>time_of_db ? time_of_map-time_of_db : time_of_db-time_of_map;
803                    fprintf(stderr, "Warning: modification times of DB and fastload file differ (DB=%lu fastload=%lu diff=%lu)\n",
804                            time_of_db, time_of_map, diff);
805
806                    if (diff>5) {
807                        map_fail_reason = "modification times of DB and fastload file differ (too much)";
808                    }
809                    else {
810                        fprintf(stderr, "(accepting modification time difference of %lu seconds)\n", diff);
811                    }
812                }
813
814                if (!map_fail_reason) {
815                    if (gb_main_array[mheader.main_idx]==NULL) {
816                        GBCONTAINER *new_gbc = (GBCONTAINER*)gb_map_mapfile(map_path);
817
818                        if (new_gbc) {
819                            GBCONTAINER *father  = GB_FATHER(gbc);
820                            GB_MAIN_IDX  new_idx = mheader.main_idx;
821                            GB_MAIN_IDX  old_idx = father->main_idx;
822
823                            long gbc_index = gbc->index;
824
825                            GB_commit_transaction((GBDATA*)gbc);
826
827                            gb_assert(new_gbc->main_idx == new_idx);
828                            gb_assert((new_idx % GB_MAIN_ARRAY_SIZE) == new_idx);
829
830                            gb_main_array[new_idx] = Main;
831
832                            gbm_free_mem(Main->root_container, sizeof(GBCONTAINER), quark2gbmindex(Main, 0));
833
834                            Main->root_container = new_gbc;
835                            father->main_idx     = new_idx;
836
837                            SET_GBCONTAINER_ELEM(father, gbc_index, NULL); // unlink old main-entry
838
839                            gbc = new_gbc;
840                            SET_GB_FATHER(gbc, father);
841                       
842                            SET_GBCONTAINER_ELEM(father, gbc->index, (GBDATA*)gbc); // link new main-entry
843
844                            gb_main_array[old_idx]    = NULL;
845
846                            GB_begin_transaction((GBDATA*)gbc);
847                            mapped = true;
848                        }
849                    }
850                    else {
851                        map_fail_reason = GBS_global_string("FastLoad-File index conflict (%s, %i)", map_path, mheader.main_idx);
852                    }
853                }
854           
855                break;
856            }
857            default: gb_assert(0); break;
858        }
859
860        gb_assert(mapped || map_fail_reason);
861
862        if (mapped) {
863            Main->mapped = true;
864            return 0; // succeded loading mapfile -> no need to load normal DB file
865        }
866        GB_informationf("ARB: %s => loading entire DB", map_fail_reason);
867    }
868
869    // load binary DB file
870
871    switch (version) {
872        case 2: // quick save file
873            for (i=1; i < Main->keycnt; i++) {
874                if (Main->keys[i].key) {
875                    Main->keys[i].nref_last_saved = Main->keys[i].nref;
876                }
877            }
878
879            if (Main->clock<=0) Main->clock++;
880            // fall-through
881        case 1: // master arb file
882            error = gb_read_bin_rek_V2(in, gbc, nodecnt, version, reversed, 0, progress);
883            break;
884        default:
885            GB_internal_errorf("Sorry: This ARB Version does not support database format V%li", version);
886            error = 1;
887    }
888
889    if (gb_local->search_system_folder) {
890        gb_search_system_folder(gbc);
891    }
892
893    switch (version) {
894        case 2:
895        case 1:
896            for (i=1; i < Main->keycnt; i++) {
897                if (Main->keys[i].key) {
898                    Main->keys[i].nref = Main->keys[i].nref_last_saved;
899                }
900            }
901            break;
902        default:
903            break;
904    }
905
906    return error;
907}
908
909// ----------------------
910//      OPEN DATABASE
911
912static long gb_next_main_idx_for_mapfile = 0;
913
914void GB_set_next_main_idx(long idx) {
915    gb_next_main_idx_for_mapfile = idx;
916}
917
918GB_MAIN_IDX gb_make_main_idx(GB_MAIN_TYPE *Main) {
919    static int initialized = 0;
920    GB_MAIN_IDX idx;
921
922    if (!initialized) {
923        for (idx=0; idx<GB_MAIN_ARRAY_SIZE; idx++) gb_main_array[idx] = NULL;
924        initialized = 1;
925    }
926    if (gb_next_main_idx_for_mapfile<=0) {
927        while (1) {                                 // search for unused array index
928            idx = (short)GB_random(GB_MAIN_ARRAY_SIZE);
929            if (gb_main_array[idx]==NULL) break;
930        }
931    }
932    else {
933        idx = (short)gb_next_main_idx_for_mapfile;
934        gb_next_main_idx_for_mapfile = 0;
935    }
936
937    gb_assert((idx%GB_MAIN_ARRAY_SIZE) == idx);
938
939    gb_main_array[idx] = Main;
940    return idx;
941}
942
943void GB_MAIN_TYPE::release_main_idx() { 
944    if (dummy_father) {
945        GB_MAIN_IDX idx = dummy_father->main_idx;
946        gb_assert(gb_main_array[idx] == this);
947        gb_main_array[idx] = NULL;
948    }
949}
950
951GB_ERROR GB_MAIN_TYPE::login_remote(const char *db_path, const char *opent) {
952    GB_ERROR error = NULL;
953
954    i_am_server = false;
955    c_link = gbcmc_open(db_path);
956    if (!c_link) {
957        error = GBS_global_string("There is no ARBDB server '%s', please start one or add a filename", db_path);
958    }
959    else {
960        root_container->server_id = 0;
961
962        remote_hash = GBS_create_numhash(GB_REMOTE_HASH_SIZE);
963        error       = initial_client_transaction();
964
965        if (!error) {
966            root_container->flags2.folded_container = 1;
967
968            if (strchr(opent, 't')) error      = gb_unfold(root_container, 0, -2);  // tiny
969            else if (strchr(opent, 'm')) error = gb_unfold(root_container, 1, -2);  // medium (no sequence)
970            else if (strchr(opent, 'b')) error = gb_unfold(root_container, 2, -2);  // big (no tree)
971            else if (strchr(opent, 'h')) error = gb_unfold(root_container, -1, -2); // huge (all)
972            else error                         = gb_unfold(root_container, 0, -2);  // tiny
973        }
974    }
975    return error;
976}
977
978inline bool is_binary_db_id(int id) {
979    return (id == 0x56430176)
980        || (id == GBTUM_MAGIC_NUMBER)
981        || (id == GBTUM_MAGIC_REVERSED);
982}
983inline bool has_ascii_db_id(uint32_t bin_id, FILE *in) {
984    // assumes the first 4 bytes of the input have been read (into 'bin_id').
985    // will read enough bytes to check for valid ascii-header.
986    // returns true if file has an arb-ascii-database-header.
987    const int ASC_HEADER_SIZE = 15;
988    char      buffer[ASC_HEADER_SIZE+1];
989
990    size_t read_bytes = fread(buffer+4, 1, ASC_HEADER_SIZE-4, in); // 4 bytes have already been read as binary ID
991    if (read_bytes == (ASC_HEADER_SIZE-4)) {
992        buffer[ASC_HEADER_SIZE] = 0;
993
994        const char *ascii_header = "/*ARBDB ASCII*/";
995        uint32_t   *ui_buffer    = (uint32_t*)buffer;
996
997        *ui_buffer = bin_id; // insert these 4 bytes
998        if (strcmp(buffer, ascii_header) == 0) return true;
999
1000        *ui_buffer = reverse_byteorder(bin_id); // insert them reversed
1001        if (strcmp(buffer, ascii_header) == 0) return true;
1002    }
1003    return false;
1004}
1005
1006static GB_ERROR init_tmp_branch(GBDATA *gb_main) {
1007    GB_ERROR  error   = NULL;
1008    GBDATA   *gb_tmp  = GB_search(gb_main, "tmp", GB_CREATE_CONTAINER);
1009    if (gb_tmp) {
1010        error = GB_set_temporary(gb_tmp);
1011    }
1012    else {
1013        error = GB_await_error();
1014    }
1015    return error;
1016}
1017
1018static GBDATA *GB_login(const char *cpath, const char *opent, const char *user) {
1019    /*! open an ARB database
1020     *
1021     * @param cpath arb database. May be
1022     * - full path of database. (loads DB plus newest quicksave, if any quicksaves exist)
1023     * - full path of a quicksave file (loads DB with specific quicksave)
1024     * - ":" connects to a running ARB DB server (e.g. ARB_NT, arb_db_server)
1025     * @param opent contains one or more of the following characters:
1026     * - 'r' read
1027     * - 'w' write (w/o 'r' it overwrites existing database)
1028     * - 'c' create (if not found)
1029     * - 's'     read only ???
1030     * - 'D' looks for default in $ARBHOME/lib/arb_default if file is not found in $ARB_PROP
1031     *       (only works combined with mode 'c')
1032     * - memory usage:
1033     *   - 't' small memory usage
1034     *   - 'm' medium
1035     *   - 'b' big
1036     *   - 'h' huge
1037     * - 'R' allow corrupt file recovery + opening quicks with no master
1038     * - 'N' assume new database format (does not check whether to convert old->new compression)
1039     * @param user username
1040     *
1041     * @return DB root node (or NULL, in which case an error has been exported)
1042     *
1043     * @see GB_open() and GBT_open()
1044     */
1045    GBCONTAINER   *gbc;
1046    GB_MAIN_TYPE  *Main;
1047    gb_open_types  opentype;
1048    GB_CSTR        quickFile           = NULL;
1049    int            ignoreMissingMaster = 0;
1050    int            loadedQuickIndex    = -1;
1051    GB_ERROR       error               = 0;
1052    char          *path                = strdup(cpath?cpath:"");
1053    bool           dbCreated           = false;
1054
1055    gb_assert(strchr(opent, 'd') == NULL); // mode 'd' is deprecated. You have to use 'D' and store your defaults inside ARBHOME/lib/arb_default
1056
1057    opentype = (!opent || strchr(opent, 'w')) ? gb_open_all : gb_open_read_only_all;
1058
1059    if (strchr(path, ':')) {
1060        ; // remote access
1061    }
1062    else if (GBS_string_matches(path, "*.quick?", GB_MIND_CASE)) {
1063        char *ext = gb_findExtension(path);
1064        gb_assert(ext!=0);
1065        if (isdigit(ext[6]))
1066        {
1067            loadedQuickIndex = atoi(ext+6);
1068            strcpy(ext, ".arb");
1069            quickFile = gb_oldQuicksaveName(path, loadedQuickIndex);
1070            if (strchr(opent, 'R'))     ignoreMissingMaster = 1;
1071        }
1072    }
1073    else if (GBS_string_matches(path, "*.a??", GB_MIND_CASE)) {
1074
1075        char *extension = gb_findExtension(path);
1076
1077        if (isdigit(extension[2]) && isdigit(extension[3]))
1078        {
1079            loadedQuickIndex = atoi(extension+2);
1080            strcpy(extension, ".arb");
1081            quickFile = gb_quicksaveName(path, loadedQuickIndex);
1082            if (strchr(opent, 'R'))     ignoreMissingMaster = 1;
1083        }
1084        else {
1085            char *base = strdup(path);
1086            char *ext = gb_findExtension(base);
1087            {
1088                gb_scandir dir;
1089                ext[0] = 0;
1090                gb_scan_directory(base, &dir);
1091
1092                loadedQuickIndex = dir.highest_quick_index;
1093
1094                if (dir.highest_quick_index!=dir.newest_quick_index)
1095                {
1096                    GB_warning("The QuickSave-File with the highest index-number\n"
1097                               "is not the NEWEST of your QuickSave-Files.\n"
1098                               "If you didn't restore old QuickSave-File from a backup\n"
1099                               "please inform your system-administrator - \n"
1100                               "this may be a serious bug and you may loose your data.");
1101                }
1102
1103                switch (dir.type)
1104                {
1105                    case GB_SCAN_NO_QUICK:
1106                        break;
1107                    case GB_SCAN_NEW_QUICK:
1108                        quickFile = gb_quicksaveName(path, dir.highest_quick_index);
1109                        break;
1110                    case GB_SCAN_OLD_QUICK:
1111                        quickFile = gb_oldQuicksaveName(path, dir.newest_quick_index);
1112
1113                        break;
1114                }
1115            }
1116
1117            free(base);
1118        }
1119    }
1120
1121    if (gb_verbose_mode) {
1122        GB_informationf("ARB: Loading '%s'%s%s", path, quickFile ? " + Changes-File " : "", quickFile ? quickFile : "");
1123    }
1124
1125    error = GB_install_pid(1);
1126    if (error) {
1127        GB_export_error(error);
1128        return 0;
1129    }
1130
1131    GB_init_gb();
1132
1133    Main = new GB_MAIN_TYPE(path);
1134    Main->mark_as_server();
1135
1136    if (strchr(opent, 'R')) Main->allow_corrupt_file_recovery = 1;
1137
1138    ASSERT_RESULT(long, 0, gb_create_key(Main, "main", false));
1139
1140    Main->dummy_father            = gb_make_container(NULL, NULL, -1, 0); // create "main"
1141    Main->dummy_father->main_idx  = gb_make_main_idx(Main);
1142    Main->dummy_father->server_id = GBTUM_MAGIC_NUMBER;
1143    gbc                           = gb_make_container(Main->dummy_father, NULL, -1, 0); // create "main"
1144
1145    Main->root_container = gbc;
1146
1147    error = gbcm_login(gbc, user);
1148    if (!error) {
1149        Main->opentype       = opentype;
1150        Main->security_level = 7;
1151
1152        if (path && (strchr(opent, 'r'))) {
1153            if (strchr(path, ':')) {
1154                error = Main->login_remote(path, opent);
1155            }
1156            else {
1157                int read_from_stdin = strcmp(path, "-") == 0;
1158
1159                GB_ULONG time_of_main_file = 0; long i;
1160
1161                Main->mark_as_server();
1162                GB_begin_transaction(gbc);
1163                Main->clock      = 0; // start clock
1164
1165                FILE *input = read_from_stdin ? stdin : ARB_zfopen(path, "rb", ZFILE_AUTODETECT, error, false);
1166                if (!input && ignoreMissingMaster) {
1167                    error = NULL; // @@@ maybe need to inspect error message here
1168                    goto load_quick_save_file_only;
1169                }
1170
1171                if (!input) {
1172                    if (strchr(opent, 'c')) {
1173                        error = NULL; // from ARB_zfopen
1174                        GB_disable_quicksave(gbc, "Database Created");
1175
1176                        if (strchr(opent, 'D')) { // use default settings
1177                            GB_clear_error(); // with default-files gb_scan_directory (used above) has created an error, cause the path was a fake path
1178                       
1179                            gb_assert(!ARB_strBeginsWith(path, ".arb_prop/")); // do no longer pass path-prefix [deprecated!] 
1180                            char *found_path = GB_property_file(false, path);
1181
1182                            if (!found_path) {
1183                                fprintf(stderr, "file %s not found\n", path);
1184                                dbCreated = true;
1185                            }
1186                            else {
1187                                freeset(path, found_path);
1188                                // cppcheck-suppress deallocuse (false positive; path is reassigned to non-NULL above)
1189                                input = fopen(path, "rb");
1190                            }
1191                        }
1192                        else {
1193                            dbCreated = true;
1194                        }
1195
1196                        if (dbCreated) {
1197                            fprintf(stderr, "Created new database \"%s\".\n", path);
1198                        }
1199                    }
1200                    else {
1201                        gb_assert(error); // from ARB_zfopen
1202                        error = GBS_global_string("Database '%s' not found (%s)", path, error);
1203                        gbc   = 0;
1204                    }
1205                }
1206                if (input) {
1207                    if (strchr(opent, 'D')) { // we are loading properties -> be verboose
1208                        fprintf(stderr, "Using properties from '%s'\n", path);
1209                    }
1210                    time_of_main_file = GB_time_of_file(path);
1211
1212                    i = gb_read_in_uint32(input, 0);
1213
1214                    if (is_binary_db_id(i)) {
1215                        {
1216                            arb_progress progress("Loading database", GB_size_of_FILE(input)/FILESIZE_GRANULARITY);
1217                            i = gb_read_bin(input, gbc, false, progress);                  // read or map whole db
1218                            progress.done();
1219                        }
1220                        gbc   = Main->root_container;
1221                        error = ARB_zfclose(input);
1222
1223                        if (i) {
1224                            if (Main->allow_corrupt_file_recovery) {
1225                                GB_print_error();
1226                                GB_clear_error();
1227                            }
1228                            else {
1229                                gbc   = 0;
1230                                error = GBS_global_string("Failed to load database '%s'\n"
1231                                                          "Reason: %s",
1232                                                          path,
1233                                                          GB_await_error());
1234                            }
1235                        }
1236
1237                        if (gbc && quickFile) {
1238                            long     err;
1239                            GB_ERROR err_msg;
1240                          load_quick_save_file_only :
1241                            err     = 0;
1242                            err_msg = 0;
1243
1244                            input = fopen(quickFile, "rb");
1245
1246                            if (input) {
1247                                GB_ULONG time_of_quick_file = GB_time_of_file(quickFile);
1248                                if (time_of_main_file && time_of_quick_file < time_of_main_file) {
1249                                    const char *warning = GBS_global_string("Your main database file '%s' is newer than\n"
1250                                                                            "   the changes file '%s'\n"
1251                                                                            "   That is very strange and happens only if files where\n"
1252                                                                            "   moved/copied by hand\n"
1253                                                                            "   Your file '%s' may be an old relict,\n"
1254                                                                            "   if you ran into problems now,delete it",
1255                                                                            path, quickFile, quickFile);
1256                                    GB_warning(warning);
1257                                }
1258                                i = gb_read_in_uint32(input, 0);
1259                                if (is_binary_db_id(i)) {
1260                                    {
1261                                        arb_progress progress("Loading quicksave", GB_size_of_FILE(input)/FILESIZE_GRANULARITY);
1262                                        err = gb_read_bin(input, gbc, true, progress);
1263                                        progress.done();
1264                                    }
1265                                    fclose (input);
1266
1267                                    if (err) {
1268                                        err_msg = GBS_global_string("Loading failed (file corrupt?)\n"
1269                                                                    "[Fail-Reason: '%s']",
1270                                                                    GB_await_error());
1271                                    }
1272                                }
1273                                else {
1274                                    err_msg = "Wrong file format (not a quicksave file)";
1275                                    err     = 1;
1276                                }
1277                            }
1278                            else {
1279                                err_msg = "Can't open file";
1280                                err     = 1;
1281                            }
1282
1283                            if (err) {
1284                                error = GBS_global_string("I cannot load your quick file '%s'\n"
1285                                                          "Reason: %s\n"
1286                                                          "\n"
1287                                                          "Note: you MAY restore an older version by running arb with:\n"
1288                                                          "      arb <name of quicksave-file>",
1289                                                          quickFile, err_msg);
1290
1291                                if (!Main->allow_corrupt_file_recovery) {
1292                                    gbc = 0;
1293                                }
1294                                else {
1295                                    GB_export_error(error);
1296                                    GB_print_error();
1297                                    GB_clear_error();
1298                                    error = 0;
1299                                    GB_disable_quicksave(gbc, "Couldn't load last quicksave (your latest changes are NOT included)");
1300                                }
1301                            }
1302                        }
1303                        Main->qs.last_index = loadedQuickIndex; // determines which # will be saved next
1304                    }
1305                    else {
1306                        if (has_ascii_db_id(i, input)) {
1307                            error = gb_read_ascii_beyond_header(input, path, gbc);
1308                            if (input != stdin) {
1309                                GB_ERROR close_error = ARB_zfclose(input);
1310                                if (!error) error    = close_error;
1311                            }
1312                            GB_disable_quicksave(gbc,
1313                                                 "Sorry, I cannot save differences to ascii files\n"
1314                                                 "  Save whole database in binary mode first");
1315                        }
1316                        else {
1317                            error = "input file is not an arb database file";
1318                        }
1319
1320                    }
1321                }
1322            }
1323        }
1324        else {
1325            GB_disable_quicksave(gbc, "Database not part of this process");
1326            Main->mark_as_server();
1327            GB_begin_transaction(gbc);
1328        }
1329
1330        if (error) gbcm_logout(Main, user);
1331    }
1332
1333    gb_assert(error || gbc);
1334
1335    if (error) {
1336        gb_delete_dummy_father(Main->dummy_father);
1337        gbc = NULL;
1338        delete Main;
1339
1340        GB_export_error(error);
1341    }
1342    else {
1343        GB_commit_transaction(gbc);
1344        {
1345            GB_begin_transaction(gbc); // New Transaction, should be quicksaveable
1346            if (!strchr(opent, 'N')) {               // new format
1347                gb_convert_V2_to_V3(gbc); // Compression conversion
1348            }
1349            error = gb_load_key_data_and_dictionaries(Main);
1350            if (!error) error = gb_resort_system_folder_to_top(Main->root_container);
1351            if (!error) error = init_tmp_branch(gbc);
1352
1353            GB_commit_transaction(gbc);
1354
1355            // "handle" error
1356            if (error) GBK_terminatef("PANIC in GB_login: %s", error);
1357        }
1358        Main->security_level = 0;
1359        gbl_install_standard_commands(gbc);
1360
1361        if (Main->is_server()) {
1362            GBT_install_message_handler(gbc);
1363        }
1364        if (gb_verbose_mode && !dbCreated) GB_informationf("ARB: Loading '%s' done\n", path);
1365    }
1366    free(path);
1367    return gbc;
1368}
1369
1370GBDATA *GB_open(const char *path, const char *opent)
1371{
1372    const char *user;
1373    user = GB_getenvUSER();
1374    return GB_login(path, opent, user);
1375}
1376
1377GB_ERROR GBT_check_arb_file(const char *name) { // goes to header: __ATTR__USERESULT
1378    /*! Checks whether a file is an arb file (or ':')
1379     *
1380     * @result NULL if it is an arb file
1381     */
1382
1383    GB_ERROR error = NULL;
1384    if (!strchr(name, ':'))  { // don't check remote DB
1385        if (GB_is_regularfile(name)) {
1386            FILE *in = ARB_zfopen(name, "rb", ZFILE_AUTODETECT, error, true); // suppress stderr (to hide "broken pipe" errors from decompressors)
1387            if (!in) {
1388                gb_assert(error);
1389                error = GBS_global_string("Cannot read file '%s' (Reason: %s)", name, error);
1390            }
1391            else {
1392                uint32_t i = gb_read_in_uint32(in, 0);
1393
1394                if (!is_binary_db_id(i)) {
1395                    if (!has_ascii_db_id(i, in)) {
1396                        error = GBS_global_string("'%s' is not an arb file", name);
1397                    }
1398                }
1399                ARB_zfclose(in); // Note: error ignored here (will report broken pipe from decompressor)
1400            }
1401        }
1402        else {
1403            error = GBS_global_string("'%s' is no file", name);
1404        }
1405    }
1406    return error;
1407}
1408
1409
1410// --------------------------------------------------------------------------------
1411
1412#ifdef UNIT_TESTS
1413
1414#include <test_unit.h>
1415
1416void TEST_io_number() {
1417    struct data {
1418        long  val;
1419        short size_expd;
1420    };
1421
1422    data DATA[] = {
1423        { 0x0,      1 },
1424        { 0x1,      1 },
1425        { 0x7f,     1 },
1426                   
1427        { 0x80,     2 },
1428        { 0x81,     2 },
1429        { 0xff,     2 },
1430        { 0x100,    2 },
1431        { 0x1234,   2 },
1432        { 0x3fff,   2 },
1433                   
1434        { 0x4000,   3 },
1435        { 0x4001,   3 },
1436        { 0xffff,   3 },
1437        { 0x1fffff, 3 },
1438
1439        { 0x200000, 4 },
1440        { 0x7fffff, 4 },
1441        { 0x800002, 4 },
1442        { 0xffffff, 4 },
1443        { 0xfffffff, 4 },
1444
1445#if defined(ARB_64)
1446        // the following entries are negative on 32bit systems; see ad_io_inline.h@bit-hell
1447        { 0x10000000, 5 },
1448        { 0x7fffffff, 5 },
1449        { 0x80000000, 5 },
1450        { 0x80808080, 5 },
1451        { 0xffffffff, 5 },
1452#endif
1453    };
1454
1455    const char *numbers   = "numbers.test";
1456    long        writeSize = 0;
1457    long        readSize  = 0;
1458
1459    {
1460        FILE *out = fopen(numbers, "wb");
1461        TEST_REJECT_NULL(out);
1462
1463        long lastPos = 0;
1464        for (size_t i = 0; i<ARRAY_ELEMS(DATA); ++i) {
1465            data& d = DATA[i];
1466            TEST_ANNOTATE(GBS_global_string("val=0x%lx", d.val));
1467            gb_put_number(d.val, out);
1468
1469            long pos           = ftell(out);
1470            long bytes_written = pos-lastPos;
1471            TEST_EXPECT_EQUAL(bytes_written, d.size_expd);
1472
1473            writeSize += bytes_written;
1474
1475            lastPos = pos;
1476        }
1477        TEST_ANNOTATE(NULL);
1478
1479        fclose(out);
1480    }
1481
1482    {
1483        FILE *in = fopen(numbers, "rb");
1484        TEST_REJECT_NULL(in);
1485
1486        long lastPos = 0;
1487
1488        for (size_t i = 0; i<ARRAY_ELEMS(DATA); ++i) {
1489            data& d = DATA[i];
1490            TEST_ANNOTATE(GBS_global_string("val=0x%lx", d.val));
1491
1492            long val = gb_get_number(in);
1493            TEST_EXPECT_EQUAL(val, d.val);
1494
1495            long pos        = ftell(in);
1496            long bytes_read = pos-lastPos;
1497            TEST_EXPECT_EQUAL(bytes_read, d.size_expd);
1498
1499            readSize += bytes_read;
1500
1501            lastPos = pos;
1502        }
1503        TEST_ANNOTATE(NULL);
1504
1505        fclose(in);
1506    }
1507
1508    TEST_EXPECT_EQUAL(GB_size_of_file(numbers), writeSize);
1509    TEST_EXPECT_EQUAL(writeSize, readSize);
1510
1511    TEST_EXPECT_ZERO_OR_SHOW_ERRNO(GB_unlink(numbers));
1512}
1513
1514void TEST_GBDATA_size() {
1515    // protect GBDATA/GBENTRY/GBCONTAINER against unwanted changes
1516    GBENTRY fakeEntry;
1517
1518#if defined(ARB_64)
1519
1520    TEST_EXPECT_EQUAL(sizeof(GBDATA), 40);
1521    TEST_EXPECT_EQUAL(sizeof(fakeEntry.info), 24);  // @@@ SIZOFINTERN may be increased (to use unused space); that change would need new mapfile version
1522    TEST_EXPECT_EQUAL(sizeof(fakeEntry.cache_index), 4);
1523    // 40+24+4 = 68 (where/what are the missing 4 bytes?; they seem to be at the end of the struct; maybe padding-bytes)
1524
1525    TEST_EXPECT_EQUAL(sizeof(GBENTRY), 72);
1526    TEST_EXPECT_EQUAL(sizeof(GBCONTAINER), 104);
1527
1528#else // !defined(ARB_64)
1529
1530    TEST_EXPECT_EQUAL(sizeof(GBDATA), 24);
1531    TEST_EXPECT_EQUAL(sizeof(fakeEntry.info), 12);
1532    TEST_EXPECT_EQUAL(sizeof(fakeEntry.cache_index), 4);
1533    // 24+12+4 = 40 (here nothing is missing)
1534
1535    TEST_EXPECT_EQUAL(sizeof(GBENTRY), 40);
1536    TEST_EXPECT_EQUAL(sizeof(GBCONTAINER), 60);
1537
1538#endif
1539}
1540TEST_PUBLISH(TEST_GBDATA_size);
1541
1542#endif // UNIT_TESTS
Note: See TracBrowser for help on using the repository browser.