source: branches/alilink/ARBDB/ad_save_load.cxx

Last change on this file was 18126, checked in by westram, 5 years ago
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 71.4 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : ad_save_load.cxx                                  //
4//   Purpose   :                                                   //
5//                                                                 //
6//   Institute of Microbiology (Technical University Munich)       //
7//   http://www.arb-home.de/                                       //
8//                                                                 //
9// =============================================================== //
10
11#include <unistd.h>
12#include <sys/stat.h>
13#include <netinet/in.h>
14
15#include <arb_file.h>
16#include <arb_diff.h>
17#include <arb_zfile.h>
18#include <arb_defs.h>
19#include <arb_strbuf.h>
20
21#include "gb_key.h"
22#include "gb_map.h"
23#include "gb_load.h"
24#include "ad_io_inline.h"
25#include <arb_misc.h>
26
27GB_MAIN_TYPE *gb_main_array[GB_MAIN_ARRAY_SIZE];
28
29char *gb_findExtension(char *path) {
30    char *punkt = strrchr(path, '.');
31    if (punkt) {
32        char *slash = strchr(punkt, '/');
33        if (slash) punkt = NULp; //  slash after '.' -> no extension
34    }
35    return punkt;
36}
37
38
39/* CAUTION!!!
40 *
41 * The following functions (gb_quicksaveName, gb_oldQuicksaveName, gb_mapfile_name, gb_overwriteName)
42 * use static buffers for the created filenames.
43 *
44 * So you have to make sure, to use only one instance of every of these
45 * functions or to dup the string before using the second instance
46 */
47
48inline char *STATIC_BUFFER(SmartCharPtr& strvar, int minlen) {
49    gb_assert(minlen > 0);
50    if (strvar.isNull() || (strlen(&*strvar) < (size_t)(minlen-1))) {
51        strvar = ARB_calloc<char>(minlen);
52    }
53    return &*strvar;
54}
55
56GB_CSTR gb_oldQuicksaveName(GB_CSTR path, int nr) {
57    static SmartCharPtr Qname;
58
59    size_t  len   = strlen(path);
60    char   *qname = STATIC_BUFFER(Qname, len+15);
61    strcpy(qname, path);
62
63    char *ext     = gb_findExtension(qname);
64    if (!ext) ext = qname + len;
65
66    if (nr==-1) sprintf(ext, ".arb.quick?");
67    else    sprintf(ext, ".arb.quick%i", nr);
68
69    return qname;
70}
71
72GB_CSTR gb_quicksaveName(GB_CSTR path, int nr) {
73    static SmartCharPtr Qname;
74
75    char *qname = STATIC_BUFFER(Qname, strlen(path)+4);
76    strcpy(qname, path);
77
78    char *ext     = gb_findExtension(qname);
79    if (!ext) ext = qname + strlen(qname);
80
81    if (nr==-1) sprintf(ext, ".a??");
82    else    sprintf(ext, ".a%02i", nr);
83
84    return qname;
85}
86
87GB_CSTR gb_mapfile_name(GB_CSTR path) {
88    static SmartCharPtr Mapname;
89
90    char *mapname = STATIC_BUFFER(Mapname, strlen(path)+4+1);
91    strcpy(mapname, path);
92
93    char *ext     = gb_findExtension(mapname);
94    if (!ext) ext = mapname + strlen(mapname);
95
96    strcpy(ext, ".ARM");
97
98    return mapname;
99}
100
101GB_CSTR GB_mapfile(GBDATA *gb_main) {
102    GB_MAIN_TYPE *Main = GB_MAIN(gb_main);
103    return gb_mapfile_name(Main->path);
104}
105
106static GB_CSTR gb_overwriteName(GB_CSTR path) {
107    static SmartCharPtr Oname;
108
109    int   len   = strlen(path);
110    char *oname = STATIC_BUFFER(Oname, len+2);
111
112    strcpy(oname, path);
113    strcpy(oname+len, "~");                         // append ~
114
115    return oname;
116}
117
118static GB_CSTR gb_reffile_name(GB_CSTR path) {
119    static SmartCharPtr Refname;
120
121    size_t  len     = strlen(path);
122    char   *refname = STATIC_BUFFER(Refname, len+4+1);
123    memcpy(refname, path, len+1);
124
125    const char *ext        = gb_findExtension(refname);
126    size_t      ext_offset = ext ? (size_t)(ext-refname) : len;
127
128    strcpy(refname+ext_offset, ".ARF");
129
130    return refname;
131}
132
133static char *gb_full_path(const char *path) {
134    char *res = NULp;
135
136    if (path[0] == '/') res = ARB_strdup(path);
137    else {
138        const char *cwd = GB_getcwd();
139
140        if (path[0] == 0) res = ARB_strdup(cwd);
141        else res              = GBS_global_string_copy("%s/%s", cwd, path);
142    }
143    return res;
144}
145
146static GB_ERROR gb_delete_reference(const char *master) {
147    GB_ERROR    error      = NULp;
148    char       *fullmaster = gb_full_path(master);
149    const char *fullref    = gb_reffile_name(fullmaster);
150
151    GB_unlink_or_warn(fullref, &error);
152
153    free(fullmaster);
154    return error;
155}
156
157static GB_ERROR gb_create_reference(const char *master) {
158    char       *fullmaster = gb_full_path(master);
159    const char *fullref    = gb_reffile_name(fullmaster);
160    GB_ERROR    error      = NULp;
161    FILE       *out        = fopen(fullref, "w");
162
163    if (out) {
164        fprintf(out, "***** The following files may be a link to %s ********\n", fullmaster);
165        fclose(out);
166        error = GB_failedTo_error("create reference file", NULp, GB_set_mode_of_file(fullref, 00666));
167    }
168    else {
169        error = GBS_global_string("Cannot create reference file '%s'\n"
170                                  "Your database was saved, but you should check "
171                                  "write permissions in the destination directory!", fullref);
172    }
173
174    free(fullmaster);
175    return error;
176}
177
178static GB_ERROR gb_add_reference(const char *master, const char *changes) {
179    char       *fullmaster  = gb_full_path(master);
180    char       *fullchanges = gb_full_path(changes);
181    const char *fullref     = gb_reffile_name(fullmaster);
182    GB_ERROR    error       = NULp;
183    FILE       *out         = fopen(fullref, "a");
184
185    if (out) {
186        fprintf(out, "%s\n", fullchanges);
187        fclose(out);
188        error = GB_failedTo_error("append to reference files", NULp, GB_set_mode_of_file(fullref, 00666));
189    }
190    else {
191        error = GBS_global_string("Cannot add your file '%s'\n"
192                                  "to the list of references of '%s'.\n"
193                                  "Please ask the owner of that file not to delete it\n"
194                                  "or save the entire database (that's recommended!)",
195                                  fullchanges, fullref);
196    }
197
198    free(fullchanges);
199    free(fullmaster);
200
201    return error;
202}
203
204static GB_ERROR gb_remove_quick_saved(GB_MAIN_TYPE *Main, const char *path) {
205    int      i;
206    GB_ERROR error = NULp;
207
208    for (i=0; i<GB_MAX_QUICK_SAVE_INDEX && !error; i++) GB_unlink_or_warn(gb_quicksaveName(path, i), &error);
209    for (i=0; i<10 && !error; i++) GB_unlink_or_warn(gb_oldQuicksaveName(path, i), &error);
210    if (Main) Main->qs.last_index = -1;
211
212    RETURN_ERROR(error);
213}
214
215static GB_ERROR gb_remove_all_but_main(GB_MAIN_TYPE *Main, const char *path) {
216    GB_ERROR error = gb_remove_quick_saved(Main, path);
217    if (!error) GB_unlink_or_warn(gb_mapfile_name(path), &error); // delete old mapfile
218
219    RETURN_ERROR(error);
220}
221
222static GB_ERROR deleteSuperfluousQuicksaves(GB_MAIN_TYPE *Main) {
223    int       cnt   = 0;
224    int       i;
225    char     *path  = Main->path;
226    GB_ERROR  error = NULp;
227
228    for (i=0; i <= GB_MAX_QUICK_SAVE_INDEX; i++) {
229        GB_CSTR qsave = gb_quicksaveName(path, i);
230        if (GB_is_regularfile(qsave)) cnt++;
231    }
232
233    for (i=0; cnt>GB_MAX_QUICK_SAVES && i <= GB_MAX_QUICK_SAVE_INDEX && !error; i++) {
234        GB_CSTR qsave = gb_quicksaveName(path, i);
235        if (GB_is_regularfile(qsave)) {
236            if (GB_unlink(qsave)<0) error = GB_await_error();
237            else cnt--;
238        }
239    }
240
241    return error;
242}
243
244static GB_ERROR renameQuicksaves(GB_MAIN_TYPE *Main) {
245    /* After hundred quicksaves rename the quicksave-files to a00...a09
246     * to keep MS-DOS-compatibility (8.3)
247     * Note: This has to be called just before saving.
248     */
249
250    GB_ERROR error = deleteSuperfluousQuicksaves(Main);
251    if (!error) {
252        const char *path = Main->path;
253        int         i;
254        int         j;
255
256        for (i=0, j=0; i <= GB_MAX_QUICK_SAVE_INDEX; i++) {
257            GB_CSTR qsave = gb_quicksaveName(path, i);
258
259            if (GB_is_regularfile(qsave)) {
260                if (i!=j) {                         // otherwise the filename is correct
261                    char    *qdup = ARB_strdup(qsave);
262                    GB_CSTR  qnew = gb_quicksaveName(path, j);
263
264                    if (error) GB_warning(error);
265                    error = GB_move_file(qdup, qnew);
266                    free(qdup);
267                }
268
269                j++;
270            }
271        }
272
273        gb_assert(j <= GB_MAX_QUICK_SAVES);
274        Main->qs.last_index = j-1;
275    }
276
277    return error;
278}
279
280// ------------------------
281//      Ascii to Binary
282
283long gb_ascii_2_bin(const char *source, GBENTRY *gbe) {
284    const char *s = source;
285    char        c = *(s++);
286
287    A_TO_I(c);
288    gbe->flags.compressed_data = c;
289
290    long size;
291    if (*s == ':') {
292        size = 0;
293        s++;
294    }
295    else {
296        long i, k;
297        for (i = 0, k = 8; k && (c = *(s++)); k--) {
298            A_TO_I(c);
299            i = (i<<4)+c;
300        }
301        size = i;
302    }
303    source = s;
304
305    long len = 0;
306    while ((c = *(s++))) {
307        if ((c == '.') || (c=='-')) {
308            len++;
309            continue;
310        }
311        if ((c == ':') || (c=='=')) {
312            len += 2;
313            continue;
314        }
315        if (!(c = *(s++))) {
316            return 1;
317        };
318        len++;
319    }
320
321    GBENTRY_memory storage(gbe, size, len);
322
323    char *d = storage;
324    s       = source;
325
326    while ((c = *(s++))) {
327        if (c == '.') {
328            *(d++)=0;
329            continue;
330        }
331        if (c == ':') {
332            *(d++)=0;
333            *(d++)=0;
334            continue;
335        }
336        if (c == '-') {
337            *(d++) = 0xff;
338            continue;
339        }
340        if (c == '=') {
341            *(d++) = 0xff;
342            *(d++) = 0xff;
343            continue;
344        }
345        A_TO_I(c);
346        long i = c << 4;
347        c = *(s++);
348        A_TO_I(c);
349        *(d++) = (char)(i + c);
350    }
351
352    return 0;
353}
354// ------------------------
355//      Binary to Ascii
356
357#define GB_PUT(c, out) do { if (c>=10) c+='A'-10; else c += '0'; *(out++) = (char)c; } while (0)
358
359static GB_BUFFER gb_bin_2_ascii(GBENTRY *gbe) {
360    signed char   *s, *out, c, mo;
361    unsigned long  i;
362    int            j;
363    char          *buffer;
364    int            k;
365
366    const char *source     = gbe->data();
367    long        len        = gbe->memsize();
368    long        xtended    = gbe->size();
369    int         compressed = gbe->flags.compressed_data;
370
371    buffer = GB_give_buffer(len * 2 + 10);
372    out = (signed char *)buffer;
373    s = (signed char *)source, mo = -1;
374
375    GB_PUT(compressed, out);
376    if (!xtended) {
377        *(out++) = ':';
378    }
379    else {
380        for (i = 0xf0000000, j=28; j>=0; j-=4, i=i>>4) {
381            k = (int)((xtended & i)>>j);
382            GB_PUT(k, out);
383        }
384    }
385    for (i = len; i; i--) {
386        if (!(c = *(s++))) {
387            if ((i > 1) && !*s) {
388                *(out++) = ':';
389                s ++;
390                i--;
391                continue;
392            }
393            *(out++) = '.';
394            continue;
395        }
396        if (c == mo) {
397            if ((i > 1) && (*s == -1)) {
398                *(out++) = '=';
399                s ++;
400                i--;
401                continue;
402            }
403            *(out++) = '-';
404            continue;
405        }
406        j = ((unsigned char) c) >> 4;
407        GB_PUT(j, out);
408        j = c & 15;
409        GB_PUT(j, out);
410    }
411    *(out++) = 0;
412    return buffer;
413}
414
415// -------------------------
416//      Write Ascii File
417
418#define GB_PUT_OUT(c, out) do { if (c>=10) c+='A'-10; else c += '0'; putc(c, out); } while (0)
419
420static bool gb_write_childs(FILE *out, GBCONTAINER *gbc, GBCONTAINER*& gb_writeFrom, GBCONTAINER *gb_writeTill, int indent);
421
422static bool gb_write_one_child(FILE *out, GBDATA *gb, GBCONTAINER*& gb_writeFrom, GBCONTAINER *gb_writeTill, int indent) {
423    /*! parameters same as for gb_write_childs()
424     * The differences between both functions are:
425     * - gb_write_one_child can be called for entries and writes container tags if called with a container
426     * - gb_write_childs can only be called with containers and does NOT write enclosing container tags
427     */
428    {
429        const char *key = GB_KEY(gb);
430        if (!strcmp(key, GB_SYSTEM_FOLDER)) return true;   // do not save system folder
431
432        if (!gb_writeFrom) {
433            for (int i=indent; i--;) putc('\t', out);
434            fprintf(out, "%s\t", key);
435            const int klen = strlen(key);
436            if (klen < 16) {
437                putc('\t', out);
438                if (klen < 8) putc('\t', out);
439            }
440        }
441    }
442
443    if (!gb_writeFrom) {
444        if (gb->flags.security_delete ||
445            gb->flags.security_write ||
446            gb->flags.security_read ||
447            gb->flags2.last_updated)
448        {
449            putc(':', out);
450            char c;
451            c= gb->flags.security_delete; GB_PUT_OUT(c, out);
452            c= gb->flags.security_write;  GB_PUT_OUT(c, out);
453            c= gb->flags.security_read;   GB_PUT_OUT(c, out);
454            fprintf(out, "%u\t", gb->flags2.last_updated);
455        }
456        else {
457            putc('\t', out);
458        }
459    }
460
461    if (gb->is_container()) {
462        if (!gb_writeFrom) {
463            fprintf(out, "%%%c (%%\n", GB_read_flag(gb) ? '$' : '%');
464            bool closeTags = gb_write_childs(out, gb->as_container(), gb_writeFrom, gb_writeTill, indent+1);
465            if (!closeTags) return false;
466        }
467        if (gb_writeFrom && gb_writeFrom == gb->as_container()) gb_writeFrom = NULp;
468        if (gb_writeTill && gb_writeTill == gb->as_container()) {
469            return false;
470        }
471        if (!gb_writeFrom) {
472            for (int i=indent+1; i--;) putc('\t', out);
473            fprintf(out, "%%) /*%s*/\n\n", GB_KEY(gb));
474        }
475    }
476    else {
477        GBENTRY *gbe = gb->as_entry();
478        switch (gbe->type()) {
479            case GB_STRING: {
480                GB_CSTR strng = GB_read_char_pntr(gbe);
481                if (!strng) {
482                    strng = "<entry was broken - replaced during ASCIIsave/arb_repair>";
483                    GB_warningf("- replaced broken DB entry '%s' (data lost)\n", GB_get_db_path(gbe));
484                }
485                if (*strng == '%') {
486                    putc('%', out);
487                    putc('s', out);
488                    putc('\t', out);
489                }
490                GBS_fwrite_string(strng, out);
491                putc('\n', out);
492                break;
493            }
494            case GB_OBSOLETE: {
495                GB_warningf("- deleted obsolete type GB_LINK for entry '%s'\n", GB_get_db_path(gbe));
496                break;
497            }
498            case GB_INT:
499                fprintf(out, "%%i %li\n", GB_read_int(gbe));
500                break;
501            case GB_FLOAT:
502                fprintf(out, "%%f %s\n", ARB_float_2_ascii(GB_read_float(gbe)));
503                break;
504            case GB_BITS:
505                fprintf(out, "%%I\t\"%s\"\n",
506                        GB_read_bits_pntr(gbe, '-', '+'));
507                break;
508            case GB_BYTES: {
509                const char *s = gb_bin_2_ascii(gbe);
510                fprintf(out, "%%Y\t%s\n", s);
511                break;
512            }
513            case GB_INTS: {
514                const char *s = gb_bin_2_ascii(gbe);
515                fprintf(out, "%%N\t%s\n", s);
516                break;
517            }
518            case GB_FLOATS: {
519                const char *s = gb_bin_2_ascii(gbe);
520                fprintf(out, "%%F\t%s\n", s);
521                break;
522            }
523            case GB_DB:
524                gb_assert(0);
525                break;
526            case GB_BYTE:
527                fprintf(out, "%%y %i\n", GB_read_byte(gbe));
528                break;
529            default:
530                fprintf(stderr,
531                        "ARBDB ERROR Key \'%s\' is of unknown type\n",
532                        GB_KEY(gbe));
533                fprintf(out, "%%%% (%% %%) /* unknown type */\n");
534                break;
535        }
536    }
537    return true;
538}
539
540static bool gb_write_childs(FILE *out, GBCONTAINER *gbc, GBCONTAINER*& gb_writeFrom, GBCONTAINER *gb_writeTill, int indent) {
541    /*! write database entries to stream (used by ASCII database save)
542     * @param out           output stream
543     * @param gbc           container to write (including all subentries if gb_writeFrom and gb_writeTill are NULp).
544     * @param gb_writeFrom  if specified -> assume opening tag of container gb_writeFrom and all its subentries have already been written.
545     *                                      Save closing tag and all following containers.
546     * @param gb_writeTill  if specified -> write till opening tag of container gb_writeTill
547     * @param indent        indentation for container gbc
548     * @return true if closing containers shall be written
549     */
550
551    gb_assert(!gb_writeFrom || !gb_writeTill); // not supported (yet)
552
553    for (GBDATA *gb = GB_child(gbc); gb; gb = GB_nextChild(gb)) {
554        if (gb->flags.temporary) continue;
555
556        bool closeTags = gb_write_one_child(out, gb, gb_writeFrom, gb_writeTill, indent);
557        if (!closeTags) return false;
558    }
559
560    return true;
561}
562
563// -------------------------
564//      Read Binary File
565
566long gb_read_bin_error(FILE *in, GBDATA *gbd, const char *text) {
567    long p = (long)ftell(in);
568    GB_export_errorf("%s in reading GB_file (loc %li=%lX) reading %s\n",
569                     text, p, p, GB_KEY(gbd));
570    GB_print_error();
571    return 0;
572}
573
574// --------------------------
575//      Write Binary File
576
577static int gb_is_writeable(gb_header_list *header, GBDATA *gbd, long version, long diff_save) {
578    /* Test whether to write any data to disc.
579     *
580     * version 1       write only latest data
581     * version 2       write deleted entries too (which are not already stored to master file!)
582     *
583     * try to avoid to access gbd (to keep it swapped out)
584     */
585    if (version == 2 && header->flags.changed==GB_DELETED) return 1;    // save delete flag
586    if (!gbd) return 0;
587    if (diff_save) {
588        if (!header->flags.ever_changed) return 0;
589        if (!gbd->ext || (gbd->ext->update_date<diff_save && gbd->ext->creation_date < diff_save))
590            return 0;
591    }
592    if (gbd->flags.temporary) return 0;
593    return 1;
594}
595
596static int gb_write_bin_sub_containers(FILE *out, GBCONTAINER *gbc, long version, long diff_save, int is_root);
597
598static bool seen_corrupt_data = false;
599
600static long gb_write_bin_rek(FILE *out, GBDATA *gbd, long version, long diff_save, long index_of_master_file) {
601    int          i;
602    GBCONTAINER *gbc  = NULp;
603    GBENTRY     *gbe  = NULp;
604    long         size = 0;
605    GB_TYPES     type = gbd->type();
606
607    if (type == GB_DB) {
608        gbc = gbd->as_container();
609    }
610    else {
611        gbe = gbd->as_entry();
612        if (type == GB_STRING || type == GB_STRING_SHRT) {
613            size = gbe->size();
614            if (!gbe->flags.compressed_data && size < GBTUM_SHORT_STRING_SIZE) {
615                const char *data = gbe->data();
616                size_t      len  = strlen(data); // w/o zero-byte!
617
618                if ((long)len == size) {
619                    type = GB_STRING_SHRT;
620                }
621                else {
622                    // string contains zero-byte inside data or misses trailing zero-byte
623                    type              = GB_STRING; // fallback to safer type
624                    seen_corrupt_data = true;
625                    GB_warningf("Corrupted entry detected:\n"
626                                "entry: '%s'\n"
627                                "data:  '%s'",
628                                GB_get_db_path(gbe),
629                                data);
630                }
631            }
632            else {
633                type = GB_STRING;
634            }
635        }
636    }
637
638    i =     (type<<4)
639        +   (gbd->flags.security_delete<<1)
640        +   (gbd->flags.security_write>>2);
641    putc(i, out);
642
643    i =     ((gbd->flags.security_write &3) << 6)
644        +   (gbd->flags.security_read<<3)
645        +   (gbd->flags.compressed_data<<2)
646        +   ((GB_ARRAY_FLAGS(gbd).flags&1)<<1)
647        +   (gbd->flags.unused);
648    putc(i, out);
649
650    gb_put_number(GB_ARRAY_FLAGS(gbd).key_quark, out);
651
652    if (diff_save) {
653        gb_put_number(index_of_master_file, out);
654    }
655
656    i = gbd->flags2.last_updated;
657    putc(i, out);
658
659    if (type == GB_STRING_SHRT) {
660        const char *data = gbe->data();
661        gb_assert((long)strlen(data) == size);
662
663        i = fwrite(data, size+1, 1, out);
664        return i <= 0 ? -1 : 0;
665    }
666
667    switch (type) {
668        case GB_DB:
669            i = gb_write_bin_sub_containers(out, gbc, version, diff_save, 0);
670            return i;
671
672        case GB_INT: {
673            GB_UINT4 buffer = (GB_UINT4)htonl(gbe->info.i);
674            if (!fwrite((char *)&buffer, sizeof(float), 1, out)) return -1;
675            return 0;
676        }
677        case GB_FLOAT:
678            if (!fwrite((char *)&gbe->info.i, sizeof(float), 1, out)) return -1;
679            return 0;
680        case GB_BITS:
681        case GB_BYTES:
682        case GB_INTS:
683        case GB_FLOATS:
684            size = gbe->size();
685            // fall-through
686        case GB_STRING: {
687            long memsize = gbe->memsize();
688            gb_put_number(size, out);
689            gb_put_number(memsize, out);
690            i = fwrite(gbe->data(), (size_t)memsize, 1, out);
691            if (memsize && !i) return -1;
692            return 0;
693        }
694        case GB_BYTE:
695            putc((int)(gbe->info.i), out);
696            return 0;
697        default:
698            gb_assert(0); // unknown type
699            return -1;
700    }
701}
702
703static int gb_write_bin_sub_containers(FILE *out, GBCONTAINER *gbc, long version, long diff_save, int is_root) {
704    gb_header_list *header;
705    uint32_t        i, index;
706
707    header = GB_DATA_LIST_HEADER(gbc->d);
708    gb_assert(gbc->d.nheader >= 0);
709    for (i=0, index = 0; index < (uint32_t)gbc->d.nheader; index++) {
710        if (gb_is_writeable(&(header[index]), GB_HEADER_LIST_GBD(header[index]), version, diff_save)) i++;
711    }
712
713    if (!is_root) {
714        gb_put_number(i, out);
715    }
716    else {
717        gb_write_out_uint32(i, out);
718    }
719
720    uint32_t counter = 0;
721    for (index = 0; index < (uint32_t)gbc->d.nheader; index++) {
722        GBDATA *h_gbd;
723
724        if (header[index].flags.changed == GB_DELETED_IN_MASTER) {  // count deleted items in master, because of index renaming
725            counter ++;
726            continue;
727        }
728
729        h_gbd = GB_HEADER_LIST_GBD(header[index]);
730
731        if (!gb_is_writeable(&(header[index]), h_gbd, version, diff_save)) {
732            if (version <= 1 && header[index].flags.changed == GB_DELETED) {
733                header[index].flags.changed = GB_DELETED_IN_MASTER; // mark deleted in master
734            }
735            continue;
736        }
737
738        if (h_gbd) {
739            i = (int)gb_write_bin_rek(out, h_gbd, version, diff_save, index-counter);
740            if (i) return i;
741        }
742        else {
743            if (header[index].flags.changed == GB_DELETED) {
744                putc(0, out);
745                putc(1, out);
746                gb_put_number(index - counter, out);
747            }
748        }
749    }
750    return 0;
751}
752
753static int gb_write_bin(FILE *out, GBCONTAINER *gbc, uint32_t version) {
754    /* version 1 write master arb file
755     * version 2 write slave arb file (aka quick save file)
756     */
757
758    gb_assert(!seen_corrupt_data);
759
760    int           diff_save = 0;
761    GB_MAIN_TYPE *Main      = GBCONTAINER_MAIN(gbc);
762
763    gb_write_out_uint32(GBTUM_MAGIC_NUMBER, out);
764    fprintf(out, "\n this is the binary version of the gbtum data file version %li\n", (long)version);
765    putc(0, out);
766    fwrite("vers", 4, 1, out);
767    gb_write_out_uint32(0x01020304, out);
768    gb_write_out_uint32(version, out);
769    fwrite("keys", 4, 1, out);
770
771    for (long i=1; i<Main->keycnt; i++) {
772        gb_Key &KEY = Main->keys[i];
773        if (KEY.nref>0) {
774            gb_put_number(KEY.nref, out);
775            fputs(KEY.key, out);
776        }
777        else {
778            putc(0, out);       // 0 nref
779            putc(1, out);       // empty key
780        }
781        putc(0, out);
782
783    }
784    putc(0, out);
785    putc(0, out);
786    fwrite("time", 4, 1, out); {
787        unsigned int k;
788        for (k=0; k<Main->last_updated; k++) {
789            fprintf(out, "%s", Main->dates[k]);
790            putc(0, out);
791        }
792    }
793    putc(0, out);
794    fwrite("data", 4, 1, out);
795    if (version == 2) diff_save = (int)Main->last_main_saved_transaction+1;
796
797    return gb_write_bin_sub_containers(out, gbc, version, diff_save, 1);
798}
799
800// ----------------------
801//      save database
802
803GB_ERROR GB_save(GBDATA *gb, const char *path, const char *savetype) {
804    /*! save database
805     * @param gb database root
806     * @param path filename (if NULp -> use name stored in DB; otherwise store name in DB)
807     * @param savetype @see GB_save_as()
808     */
809    if (path && !strchr(savetype, 'S')) { // 'S' dumps to stdout -> do not change path
810        freedup(GB_MAIN(gb)->path, path);
811    }
812    return GB_save_as(gb, path, savetype);
813}
814
815GB_ERROR GB_create_parent_directory(const char *path) {
816    GB_ERROR error = NULp;
817    char *parent;
818    GB_split_full_path(path, &parent, NULp, NULp, NULp);
819    if (parent) {
820        if (!GB_is_directory(parent)) error = GB_create_directory(parent);
821        free(parent);
822    }
823    return error;
824}
825
826GB_ERROR GB_create_directory(const char *path) {
827    GB_ERROR error = NULp;
828    if (!GB_is_directory(path)) {
829        error = GB_create_parent_directory(path);
830        if (!error) {
831            int res = mkdir(path, ACCESSPERMS);
832            if (res) error = GB_IO_error("creating directory", path);
833        }
834        error = GB_failedTo_error("GB_create_directory", path, error);
835    }
836    return error;
837}
838
839GB_ERROR GB_save_in_arbprop(GBDATA *gb, const char *path, const char *savetype) {
840    /*! save database inside arb-properties-directory.
841     * Automatically creates subdirectories as needed.
842     * @param gb database root
843     * @param path filename (if NULp -> use name stored in DB)
844     * @param savetype @see GB_save_as()
845     */
846
847    char     *fullname = ARB_strdup(GB_path_in_arbprop(path ? path : GB_MAIN(gb)->path));
848    GB_ERROR  error    = GB_create_parent_directory(fullname);
849    if (!error) error = GB_save_as(gb, fullname, savetype);
850    free(fullname);
851
852    return error;
853}
854
855GB_ERROR GB_MAIN_TYPE::check_saveable(const char *new_path, const char *flags) const {
856    /* Check wether file can be stored at destination
857     *  'f' in flags means 'force' => ignores main->disabled_path
858     *  'q' in flags means 'quick save'
859     *  'n' in flags means destination must be empty
860     */
861
862    GB_ERROR error = NULp;
863    if (is_client()) {
864        error = "You cannot save a remote database,\nplease use save button in master program";
865    }
866    else if (opentype == gb_open_read_only_all) {
867        error = "Database is read only";
868    }
869    else if (strchr(new_path, ':')) {
870        error = "Your database name may not contain a ':' character\nChoose a different name";
871    }
872    else {
873        char *fullpath = gb_full_path(new_path);
874        if (disabled_path && !strchr(flags, 'f')) {
875            if (GBS_string_matches(fullpath, disabled_path, GB_MIND_CASE)) {
876                error = GBS_global_string("You are not allowed to save your database in this directory,\n"
877                                          "Please select 'save as' and save your data to a different location");
878            }
879        }
880
881        if (!error) {
882            // check whether destination directory exists
883            char *lslash = strrchr(fullpath, '/');
884            if (lslash) {
885                lslash[0] = 0;
886                if (!GB_is_directory(fullpath)) {
887                    error = GBS_global_string("Directory '%s' doesn't exist", fullpath);
888                }
889                lslash[0] = '/';
890            }
891        }
892        free(fullpath);
893    }
894   
895    if (!error && !strchr(flags, 'q')) {
896        long mode = GB_mode_of_link(new_path);
897        if (mode >= 0 && !(mode & S_IWUSR)) { // no write access -> looks like a master file
898            error = GBS_global_string("Your selected file '%s'\n"
899                                      "already exists and is write protected!\n"
900                                      "This happens e.g. if your file is a MASTER ARB FILE which is\n"
901                                      "used by multiple quicksaved databases.\n"
902                                      "If you want to save it nevertheless, delete it first, but\n"
903                                      "note that doing this will render all these quicksaves useless!",
904                                      new_path);
905        }
906    }
907   
908    if (!error && strchr(flags, 'n') && GB_time_of_file(new_path)) {
909        error = GBS_global_string("Your destination file '%s' already exists.\n"
910                                  "Delete it manually!", new_path);
911    }
912
913#if (MEMORY_TEST==1)
914    if (!error && strchr(flags, 'm')) {
915        error = "It's impossible to save mapfiles (ARBDB is MEMORY_TEST mode 1)";
916    }
917#endif
918
919    return error;
920}
921
922static GB_ERROR protect_corruption_error(const char *savepath) {
923    GB_ERROR error = NULp;
924    gb_assert(seen_corrupt_data);
925    if (!strstr(savepath, "CORRUPTED")) {
926        error = "Severe error: Corrupted data detected during save\n"
927            "ARB did NOT save your database!\n"
928            "Advices:\n"                                                    //|
929            "* If your previous (quick)save was not long ago, your savest\n"
930            "  option is to drop the changes since then, by reloading the not\n"
931            "  corrupted database and redo your changes. If you can reproduce\n"
932            "  the bug that corrupted the entries, please report it!\n"
933            "* If that is no option (because too much work would be lost)\n"
934            "  you can force saving the corrupted database by adding the text\n"
935            "  'CORRUPTED' to the database name. After doing that, do NOT\n"
936            "  quit ARB, instead try to find and fix all corrupted entries\n"
937            "  that were listed below. Manually enter their original values\n"
938            "  (in case you want to lookup or copy&paste some values, you may\n"
939            "   open the last saved version of this database using\n"
940            "   'Start second database').\n"
941            "  Saving the database again will show all remaining unfixed\n"
942            "  entries. If no more corrupted entries show up, you can safely\n"
943            "  continue to work with that database.";
944    }
945    else {
946        GB_warning("Warning: Saved corrupt database");
947    }
948    seen_corrupt_data = false;
949    return error;
950}
951
952#define SUPPORTED_COMPRESSION_FLAGS "zBx"
953
954const char *GB_get_supported_compression_flags(bool verboose) {
955    if (verboose) {
956        GBS_strstruct doc(50);
957        for (int f = 0; SUPPORTED_COMPRESSION_FLAGS[f]; ++f) {
958            if (f) doc.cat(", ");
959            switch (SUPPORTED_COMPRESSION_FLAGS[f]) {
960                // Note: before changing produced format, see callers (esp. AWT_insert_DBcompression_selector)
961                case 'z': doc.cat("z=gzip"); break;
962                case 'B': doc.cat("B=bzip2"); break;
963                case 'x': doc.cat("x=xz"); break;
964                default:
965                    gb_assert(0); // undocumented flag
966                    break;
967            }
968        }
969        return GBS_static_string(doc.get_data());
970    }
971    return SUPPORTED_COMPRESSION_FLAGS;
972}
973
974
975class ArbDBWriter : virtual Noncopyable {
976    GB_MAIN_TYPE *Main;
977
978    GB_ERROR error;
979
980    FILE *out;
981    bool  saveASCII;
982    bool  saveMapfile;
983
984    char       *given_path;
985    const char *as_path;
986    char       *sec_path;
987    char       *mappath;
988    char       *sec_mappath;
989
990    struct Levels {
991        int security;
992        int transaction;
993
994        void readFrom(GB_MAIN_TYPE *main) {
995            security    = main->security_level;
996            transaction = main->get_transaction_level();
997        }
998        void writeTo(GB_MAIN_TYPE *main) {
999            main->security_level       = security;
1000            if (main->transaction_level>0) {
1001                GB_commit_transaction(main->root_container);
1002            }
1003            if (transaction) {
1004                GB_begin_transaction(main->root_container);
1005            }
1006            gb_assert(main->transaction_level == transaction || main->transaction_level == -1);
1007        }
1008
1009    };
1010
1011    Levels save; // wanted levels during save (i.e. inside saveFromTill/finishSave)
1012
1013    enum {
1014        CONSTRUCTED,
1015        STARTED,
1016        FINISHED,
1017    } state;
1018
1019    bool dump_to_stdout;
1020    bool outOfOrderSave;
1021    bool deleteQuickAllowed;
1022
1023public:
1024    ArbDBWriter(GB_MAIN_TYPE *Main_)
1025        : Main(Main_),
1026          error(NULp),
1027          out(NULp),
1028          saveASCII(false),
1029          saveMapfile(false),
1030          given_path(NULp),
1031          sec_path(NULp),
1032          mappath(NULp),
1033          sec_mappath(NULp),
1034          state(CONSTRUCTED),
1035          dump_to_stdout(false),
1036          outOfOrderSave(false),
1037          deleteQuickAllowed(false)
1038    {}
1039    ~ArbDBWriter() {
1040        gb_assert(state == FINISHED); // you have to call finishSave()! (even in error-case)
1041
1042        free(sec_mappath);
1043        free(mappath);
1044        free(sec_path);
1045        free(given_path);
1046    }
1047
1048    GB_ERROR startSaveAs(const char *given_path_, const char *savetype) {
1049        gb_assert(!error);
1050        gb_assert(state == CONSTRUCTED);
1051        state = STARTED;
1052
1053        given_path = nulldup(given_path_);
1054        as_path    = given_path;
1055
1056        if (strchr(savetype, 'a'))      saveASCII = true;
1057        else if (strchr(savetype, 'b')) saveASCII = false;
1058        else error                                = GBS_global_string("Invalid savetype '%s' (expected 'a' or 'b')", savetype);
1059
1060        if (!error) {
1061            if (!as_path) as_path              = Main->path;
1062            if (!as_path || !as_path[0]) error = "Please specify a savename";
1063            else error                         = Main->check_saveable(as_path, savetype);
1064        }
1065
1066        FileCompressionMode compressMode = ZFILE_UNCOMPRESSED;
1067        if (!error) {
1068            struct {
1069                char                flag;
1070                FileCompressionMode mode;
1071            } supported[] = {
1072                { 'z', ZFILE_GZIP },
1073                { 'B', ZFILE_BZIP2 },
1074                { 'x', ZFILE_XZ },
1075                // Please document new flags in GB_save_as() below.
1076            };
1077
1078            STATIC_ASSERT(ARRAY_ELEMS(supported) == ZFILE_REAL_CMODES); // (after adding a new FileCompressionMode it should be supported here)
1079
1080            for (size_t comp = 0; !error && comp<ARRAY_ELEMS(supported); ++comp) {
1081                if (strchr(savetype, supported[comp].flag)) {
1082                    if (compressMode == ZFILE_UNCOMPRESSED) {
1083                        compressMode = supported[comp].mode;
1084                    }
1085                    else {
1086                        error = "Multiple compression modes specified";
1087                    }
1088                }
1089                gb_assert(strchr(SUPPORTED_COMPRESSION_FLAGS, supported[comp].flag)); // flag gets not tested -> add to SUPPORTED_COMPRESSION_FLAGS
1090            }
1091        }
1092
1093        if (!error && Main->transaction_level>1) {
1094            error = "Save only allowed with transaction_level <= 1";
1095        }
1096
1097        save.readFrom(Main); // values for error case
1098        if (!error) {
1099            // unless saving to a fifo, we append ~ to the file name we write to,
1100            // and move that file if and when everything has gone well
1101            sec_path       = ARB_strdup(GB_is_fifo(as_path) ? as_path : gb_overwriteName(as_path));
1102            dump_to_stdout = strchr(savetype, 'S');
1103            out            = dump_to_stdout ? stdout : ARB_zfopen(sec_path, "w", compressMode, error, false);
1104
1105            if (!out) {
1106                gb_assert(error);
1107                error = GBS_global_string("While saving database '%s': %s", sec_path, error);
1108            }
1109            else {
1110                save.security    = 7;
1111                save.transaction = 1;
1112
1113                seen_corrupt_data = false;
1114
1115                outOfOrderSave     = strchr(savetype, 'f');
1116                deleteQuickAllowed = !outOfOrderSave && !dump_to_stdout;
1117                {
1118                    if (saveASCII) {
1119                        fprintf(out, "/*ARBDB ASCII*/\n");
1120                    }
1121                    else {
1122                        saveMapfile = strchr(savetype, 'm');
1123                    }
1124                }
1125            }
1126        }
1127
1128        return error;
1129    }
1130
1131private:
1132    void writeContainerOrChilds(GBCONTAINER *top, GBCONTAINER *from, GBCONTAINER *till) {
1133        if (top == Main->root_container) {
1134            gb_write_childs(out, top, from, till, 0);
1135        }
1136        else {
1137            int root_indent = 0;
1138            {
1139                GBCONTAINER *stepUp = top;
1140                while (stepUp != Main->root_container) {
1141                    stepUp = stepUp->get_father();
1142                    ++root_indent;
1143                    gb_assert(stepUp); // fails if 'top' is not member of 'Main'
1144                }
1145                --root_indent; // use 0 for direct childs of root_container
1146            }
1147            gb_assert(root_indent>=0);
1148            gb_write_one_child(out, top, from, till, root_indent);
1149        }
1150    }
1151public:
1152
1153    GB_ERROR saveFromTill(GBCONTAINER *gb_from, GBCONTAINER *gb_till) {
1154        gb_assert(!error);
1155        gb_assert(state == STARTED);
1156
1157        Levels org; org.readFrom(Main);
1158        save.writeTo(Main); // set save transaction-state and security_level
1159
1160        if (saveASCII) {
1161            if (gb_from == gb_till) {
1162                writeContainerOrChilds(gb_from, NULp, NULp);
1163            }
1164            else {
1165                bool from_is_ancestor = false;
1166                bool till_is_ancestor = false;
1167
1168                GBCONTAINER *gb_from_ancestor = gb_from->get_father();
1169                GBCONTAINER *gb_till_ancestor = gb_till->get_father();
1170
1171                while (gb_from_ancestor || gb_till_ancestor) {
1172                    if (gb_from_ancestor && gb_from_ancestor == gb_till) till_is_ancestor = true;
1173                    if (gb_till_ancestor && gb_till_ancestor == gb_from) from_is_ancestor = true;
1174
1175                    if (gb_from_ancestor) gb_from_ancestor = gb_from_ancestor->get_father();
1176                    if (gb_till_ancestor) gb_till_ancestor = gb_till_ancestor->get_father();
1177                }
1178
1179                if (from_is_ancestor) {
1180                    writeContainerOrChilds(gb_from, NULp, gb_till);
1181                }
1182                else if (till_is_ancestor) {
1183                    writeContainerOrChilds(gb_till, gb_from, NULp);
1184                }
1185                else {
1186                    error = "Invalid use (one container has to be an ancestor of the other)";
1187                }
1188            }
1189        }
1190        else { // save binary (performed in finishSave)
1191            if (gb_from != Main->root_container || gb_till != Main->root_container) {
1192                error = "streamed saving only supported for ascii database format";
1193            }
1194        }
1195
1196        org.writeTo(Main); // restore original transaction-state and security_level
1197        return error;
1198    }
1199    GB_ERROR finishSave() {
1200        gb_assert(state == STARTED);
1201        state = FINISHED;
1202
1203        Levels org; org.readFrom(Main);
1204        save.writeTo(Main); // set save transaction-state and security_level
1205
1206        if (out) {
1207            int result = 0;
1208            if (!error) {
1209                if (saveASCII) {
1210                    freedup(Main->qs.quick_save_disabled, "Database saved in ASCII mode");
1211                    if (deleteQuickAllowed) error = gb_remove_all_but_main(Main, as_path);
1212                }
1213                else {
1214                    mappath = ARB_strdup(gb_mapfile_name(as_path));
1215                    if (saveMapfile) {
1216                        // it's necessary to save the mapfile FIRST,
1217                        // cause this re-orders all GB_CONTAINERs containing NULp-entries in their header
1218                        sec_mappath       = ARB_strdup(gb_overwriteName(mappath));
1219                        if (!error) error = gb_save_mapfile(Main, sec_mappath);
1220                    }
1221                    else GB_unlink_or_warn(mappath, &error); // delete old mapfile
1222                    if (!error) result |= gb_write_bin(out, Main->root_container, 1);
1223                }
1224            }
1225
1226            // org.writeTo(Main); // Note: was originally done here
1227
1228            if (result != 0 && !error) {
1229                error = GB_IO_error("writing", sec_path);
1230                if (!dump_to_stdout) {
1231                    GB_ERROR close_error   = ARB_zfclose(out);
1232                    if (close_error) error = GBS_global_string("%s\n(close reports: %s)", error, close_error);
1233                }
1234            }
1235            else {
1236                if (!dump_to_stdout) {
1237                    GB_ERROR close_error = ARB_zfclose(out);
1238                    if (!error) error    = close_error;
1239                }
1240            }
1241
1242            if (!error && seen_corrupt_data) {
1243                error = protect_corruption_error(as_path);
1244            }
1245
1246            if (!error && !saveASCII) {
1247                if (!outOfOrderSave) freenull(Main->qs.quick_save_disabled); // delete reason, why quicksaving was disallowed
1248                if (deleteQuickAllowed) error = gb_remove_quick_saved(Main, as_path);
1249            }
1250
1251            if (!dump_to_stdout) {
1252                if (error) {
1253                    if (sec_mappath) GB_unlink_or_warn(sec_mappath, NULp);
1254                    GB_unlink_or_warn(sec_path, NULp);
1255                }
1256                else {
1257                    bool unlinkMapfiles = false;
1258                    if (strcmp(sec_path, as_path) != 0) {
1259                        error = GB_move_file(sec_path, as_path);
1260                    }
1261
1262                    if (error) {
1263                        unlinkMapfiles = true;
1264                    }
1265                    else if (sec_mappath) {
1266                        error             = GB_move_file(sec_mappath, mappath);
1267                        if (!error) error = GB_set_mode_of_file(mappath, GB_mode_of_file(as_path)); // set mapfile to same mode ...
1268                        if (!error) error = GB_set_time_of_file(mappath, GB_time_of_file(as_path)); // ... and same time as DB file
1269                        if (error) {
1270                            GB_warningf("Error: %s\n[Falling back to non-fastload-save]", error);
1271                            error          = NULp;
1272                            unlinkMapfiles = true;
1273                        }
1274                    }
1275
1276                    if (unlinkMapfiles) {
1277                        GB_unlink_or_warn(sec_mappath, NULp);
1278                        GB_unlink_or_warn(mappath, NULp);
1279                    }
1280
1281                    if (!error) {
1282                        error = !Main->qs.quick_save_disabled
1283                            ? gb_create_reference(as_path)
1284                            : gb_delete_reference(as_path);
1285                    }
1286                }
1287            }
1288
1289            if (!error && !outOfOrderSave) {
1290                Main->last_saved_transaction      = GB_read_clock(Main->root_container);
1291                Main->last_main_saved_transaction = GB_read_clock(Main->root_container);
1292                Main->last_saved_time             = GB_time_of_day();
1293            }
1294        }
1295
1296        org.writeTo(Main); // restore original transaction-state and security_level
1297        return error;
1298    }
1299};
1300
1301GB_ERROR GB_MAIN_TYPE::save_as(const char *as_path, const char *savetype) {
1302    ArbDBWriter dbwriter(this);
1303    GB_ERROR    error = dbwriter.startSaveAs(as_path, savetype);
1304    if (!error) error = dbwriter.saveFromTill(root_container, root_container);
1305    error = dbwriter.finishSave();
1306    return error;
1307}
1308
1309// AISC_MKPT_PROMOTE:class ArbDBWriter;
1310
1311GB_ERROR GB_start_streamed_save_as(GBDATA *gbd, const char *path, const char *savetype, ArbDBWriter*& writer) {
1312    /*! special save implementation for use in pipelines (e.g. silva)
1313     * 'savetype' has contain 'a' (ascii)
1314     * 'writer' has to be NULp (object will be constructed and assigned to)
1315     *
1316     * Actual saving of data is triggered by GB_stream_save_part().
1317     * When done with saving part, call GB_finish_stream_save() - even in case of error!
1318    */
1319    gb_assert(!writer);
1320    writer = new ArbDBWriter(GB_MAIN(gbd));
1321    return writer->startSaveAs(path, savetype);
1322}
1323GB_ERROR GB_stream_save_part(ArbDBWriter *writer, GBDATA *from, GBDATA *till) {
1324    /*! saves a part of the database.
1325     *
1326     * save complete database:
1327     *     GB_stream_save_part(w, gb_main, gb_main);
1328     *
1329     * save database in parts:
1330     *     GB_stream_save_part(w, gb_main, gb_some_container);
1331     *     forall (gb_some_sub_container) {
1332     *         // you may create/modify gb_some_sub_container here
1333     *         GB_stream_save_part(w, gb_some_sub_container, gb_some_sub_container);
1334     *         // you may delete/modify gb_some_sub_container here
1335     *     }
1336     *     GB_stream_save_part(w, gb_some_container, gb_main);
1337     *
1338     *     Note: gb_some_sub_container has to be a child of gb_some_container
1339     */
1340    GB_ERROR error;
1341    if (from->is_container() && till->is_container()) {
1342        error = writer->saveFromTill(from->as_container(), till->as_container());
1343    }
1344    else {
1345        error = "Invalid use of GB_stream_save_part: need to pass 2 containers";
1346    }
1347    return error;
1348}
1349GB_ERROR GB_finish_stream_save(ArbDBWriter*& writer) {
1350    /*! complete save-process started with GB_start_streamed_save_as()
1351     * Has to be called (even if GB_start_streamed_save_as or GB_stream_save_part has returned an error!)
1352     */
1353    GB_ERROR error = writer->finishSave();
1354
1355    delete writer;
1356    writer = NULp;
1357
1358    return error;
1359}
1360
1361
1362GB_ERROR GB_save_as(GBDATA *gbd, const char *path, const char *savetype) {
1363    /*! Save whole database
1364     *
1365     * @param gbd database root
1366     * @param path filename (if NULp -> use name stored in DB)
1367     * @param savetype
1368     *          'a' ascii
1369     *          'b' binary
1370     *          'm' save mapfile (only together with binary)
1371     *
1372     *          'f' force saving even in disabled path to a different directory (out of order save)
1373     *          'S' save to stdout (used in arb_2_ascii when saving to stdout; also useful for debugging)
1374     *
1375     *          Extra compression flags:
1376     *          'z' stream through gzip/pigz
1377     *          'B' stream through bzip2
1378     *          'x' stream through xz
1379     */
1380
1381    GB_ERROR error = NULp;
1382
1383    gb_assert(savetype);
1384
1385    if (!gbd) {
1386        error = "got no DB";
1387    }
1388    else {
1389        error = GB_MAIN(gbd)->save_as(path, savetype);
1390    }
1391
1392    RETURN_ERROR(error);
1393}
1394
1395GB_ERROR GB_MAIN_TYPE::check_quick_save() const {
1396    /* is quick save allowed?
1397     * return NULp if allowed, error why disallowed otherwise.
1398     */
1399
1400    if (qs.quick_save_disabled) {
1401        return GBS_global_string("Save Changes Disabled, because\n"
1402                                 "    '%s'\n"
1403                                 "    Save whole database using binary mode first",
1404                                 qs.quick_save_disabled);
1405    }
1406    return NULp;
1407}
1408
1409GB_ERROR GB_delete_database(GB_CSTR filename) {
1410    GB_ERROR error = NULp;
1411
1412    if (GB_unlink(filename)<0) error = GB_await_error();
1413    else error                       = gb_remove_all_but_main(NULp, filename);
1414
1415    return error;
1416}
1417
1418GB_ERROR GB_MAIN_TYPE::save_quick_as(const char *as_path) {
1419    GB_ERROR error = NULp;
1420    if (!as_path || !strlen(as_path)) {
1421        error = "Please specify a file name";
1422    }
1423    else if (strcmp(as_path, path) == 0) {    // same name (no rename)
1424        error = save_quick(as_path);
1425    }
1426    else {
1427        error = check_quick_saveable(as_path, "bn");
1428
1429        if (!error) {
1430            FILE *fmaster = fopen(path, "r");         // old master !!!!
1431            if (!fmaster) {                                 // Oh no, where is my old master
1432                error = GBS_global_string("Save Changes is missing master ARB file '%s',\n"
1433                                          "    save database first", path);
1434            }
1435            else {
1436                fclose(fmaster);
1437            }
1438        }
1439        if (!error) {
1440            if (GB_unlink(as_path)<0) { // delete old file
1441                error = GBS_global_string("File '%s' already exists and could not be deleted\n"
1442                                          "(Reason: %s)",
1443                                          as_path, GB_await_error());
1444            }
1445        }
1446        if (!error) {
1447            char *org_master = S_ISLNK(GB_mode_of_link(path))
1448                ? GB_follow_unix_link(path)
1449                : ARB_strdup(path);
1450
1451            error = gb_remove_all_but_main(this, as_path);
1452            if (!error) {
1453                long mode = GB_mode_of_file(org_master);
1454                if (mode & S_IWUSR) {
1455                    GB_ERROR sm_error = GB_set_mode_of_file(org_master, mode & ~(S_IWUSR | S_IWGRP | S_IWOTH));
1456                    if (sm_error) {
1457                        GB_warningf("%s\n"
1458                                    "Ask the owner to remove write permissions from that master file.\n"
1459                                    "NEVER delete or change it, otherwise your quicksaves will be rendered useless!", 
1460                                    sm_error);
1461                    }
1462                }
1463                char *full_path_of_source;
1464                if (strchr(as_path, '/') || strchr(org_master, '/')) {
1465                    // dest or source in different directory
1466                    full_path_of_source = gb_full_path(org_master);
1467                }
1468                else {
1469                    full_path_of_source = ARB_strdup(org_master);
1470                }
1471
1472                error = GB_symlink(full_path_of_source, as_path);
1473                if (!error) {
1474                    if ((uid_t)GB_getuid_of_file(full_path_of_source) != getuid()) {
1475                        GB_warningf("**** WARNING ******\n"
1476                                    "   You are using the file '%s' \n"
1477                                    "   as reference for your saved changes.\n"
1478                                    "   That file is owned by ANOTHER USER.\n"
1479                                    "   If that user deletes or overwrites that file, your saved\n"
1480                                    "   changes will get useless (=they will be lost)!\n"
1481                                    "   You should only 'save changes as' if you understand what that means.\n"
1482                                    "   Otherwise use 'Save whole database as' NOW!", full_path_of_source);
1483                    }
1484
1485                    GB_ERROR warning = gb_add_reference(full_path_of_source, as_path);
1486                    if (warning) GB_warning(warning);
1487
1488                    freedup(path, as_path);                      // Symlink created -> rename allowed
1489
1490                    qs.last_index = -1; // Start with new quicks (next index will be 0)
1491                    error = save_quick(as_path);
1492                }
1493                free(full_path_of_source);
1494            }
1495            free(org_master);
1496        }
1497    }
1498
1499    RETURN_ERROR(error);
1500}
1501
1502GB_ERROR GB_save_quick_as(GBDATA *gbd, const char *path) {
1503    return GB_MAIN(gbd)->save_quick_as(path);
1504}
1505
1506GB_ERROR GB_MAIN_TYPE::save_quick(const char *refpath) {
1507    GB_ERROR error = check_quick_saveable(refpath, "q");
1508
1509    if (!error && refpath && strcmp(refpath, path) != 0) {
1510        error = GBS_global_string("master file rename '%s'!= '%s',\n"
1511                                  "save database first", refpath, path);
1512    }
1513    if (!error) {
1514        FILE *fmaster = fopen(path, "r");
1515
1516        if (!fmaster) {
1517            error = GBS_global_string("Quick save is missing master ARB file '%s',\n"
1518                                      "save database first", refpath);
1519        }
1520        else {
1521            fclose(fmaster);
1522        }
1523    }
1524    if (!error && is_client()) error = "You cannot save a remote database";
1525    if (!error) {
1526        qs.last_index++;
1527        if (qs.last_index > GB_MAX_QUICK_SAVE_INDEX) renameQuicksaves(this);
1528
1529        GB_CSTR qck_path = gb_quicksaveName(path, qs.last_index);
1530        GB_CSTR sec_path = gb_overwriteName(qck_path);
1531
1532        FILE *out       = fopen(sec_path, "w");
1533        if (!out) error = GBS_global_string("Cannot save file to '%s'", sec_path);
1534        else {
1535            long erg;
1536            {
1537                const int org_security_level    = security_level;
1538                int       org_transaction_level = get_transaction_level();
1539
1540                if (!org_transaction_level) transaction_level = 1;
1541                else {
1542                    if (org_transaction_level> 0) {
1543                        GB_commit_transaction(root_container);
1544                        GB_begin_transaction(root_container);
1545                    }
1546                }
1547
1548                security_level    = 7;
1549                seen_corrupt_data = false;
1550
1551                erg = gb_write_bin(out, root_container, 2);
1552
1553                security_level    = org_security_level;
1554                transaction_level = org_transaction_level;
1555            }
1556
1557            erg |= fclose(out);
1558
1559            if (erg!=0) error = GBS_global_string("Cannot write to '%s'", sec_path);
1560            else {
1561                if (seen_corrupt_data) {
1562                    gb_assert(!error);
1563                    error = protect_corruption_error(qck_path);
1564                }
1565                if (!error) error = GB_move_file(sec_path, qck_path);
1566                if (error) GB_unlink_or_warn(sec_path, NULp);
1567            }
1568        }
1569
1570        if (error) qs.last_index--; // undo index increment
1571        else {
1572            last_saved_transaction = GB_read_clock(root_container);
1573            last_saved_time        = GB_time_of_day();
1574
1575            error = deleteSuperfluousQuicksaves(this);
1576        }
1577    }
1578
1579    RETURN_ERROR(error);
1580}
1581
1582GB_ERROR GB_save_quick(GBDATA *gbd, const char *refpath) {
1583    return GB_MAIN(gbd)->save_quick(refpath);
1584}
1585
1586
1587void GB_disable_path(GBDATA *gbd, const char *path) {
1588    // disable directories for save
1589    freeset(GB_MAIN(gbd)->disabled_path, path ? GBS_eval_env(path) : NULp);
1590}
1591
1592long GB_last_saved_clock(GBDATA *gb_main) {
1593    return GB_MAIN(gb_main)->last_saved_transaction;
1594}
1595
1596GB_ULONG GB_last_saved_time(GBDATA *gb_main) {
1597    return GB_MAIN(gb_main)->last_saved_time;
1598}
1599
1600// --------------------------------------------------------------------------------
1601
1602#ifdef UNIT_TESTS
1603
1604#include <test_unit.h>
1605
1606// #define TEST_AUTO_UPDATE // uncomment to auto-update binary and quicksave testfiles (needed once after changing ascii testfile or modify_db())
1607
1608#if defined(TEST_AUTO_UPDATE)
1609#define TEST_UPDATE_OR_EXPECT_TEXTFILES_EQUAL(src,dest) TEST_COPY_FILE(src,dest)
1610#else
1611#define TEST_UPDATE_OR_EXPECT_TEXTFILES_EQUAL(src,dest) TEST_EXPECT_TEXTFILES_EQUAL(src,dest)
1612#endif
1613
1614#define SAVE_AND_COMPARE(gbd, save_as, savetype, compare_with)  \
1615    TEST_EXPECT_NO_ERROR(GB_save_as(gbd, save_as, savetype));   \
1616    TEST_UPDATE_OR_EXPECT_TEXTFILES_EQUAL(save_as, compare_with)
1617
1618#define SAVE_AND_COMPARE__BROKEN(gbd, save_as, savetype, compare_with)  \
1619    TEST_EXPECT_NO_ERROR(GB_save_as(gbd, save_as, savetype));           \
1620    TEST_EXPECT_FILES_EQUAL__BROKEN(save_as, compare_with)
1621
1622static GB_ERROR modify_db(GBDATA *gb_main) {
1623    GB_transaction ta(gb_main);
1624
1625    GB_ERROR  error          = NULp;
1626    GBDATA   *gb_container   = GB_create_container(gb_main, "container");
1627    if (!gb_container) error = GB_await_error();
1628    else {
1629        GBDATA *gb_entry     = GB_create(gb_container, "str", GB_STRING);
1630        if (!gb_entry) error = GB_await_error();
1631        else    error        = GB_write_string(gb_entry, "text");
1632        // else    error        = GB_write_string(gb_entry, "bla"); // provoke error in file compare
1633    }
1634    return error;
1635}
1636
1637#define TEST_loadsave_CLEANUP() TEST_EXPECT_ZERO(system("rm -f [abr]2[ab]*.* master.* slave.* renamed.* fast.* fast2a.* TEST_loadsave.ARF"))
1638
1639void TEST_SLOW_loadsave() {
1640    GB_shell shell;
1641    TEST_loadsave_CLEANUP();
1642
1643    // test non-existing DB
1644    TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_open("nonexisting.arb", "r"), "'nonexisting.arb' not found");
1645    TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_open("nonexisting.arb", "rw"), "'nonexisting.arb' not found");
1646    {
1647        GBDATA *gb_new;
1648        TEST_EXPECT_RESULT__NOERROREXPORTED(gb_new = GB_open("nonexisting.arb", "w")); // create new DB
1649        GB_close(gb_new);
1650    }
1651
1652    // the following DBs have to be provided in directory ../UNIT_TESTER/run
1653    const char *bin_db = "TEST_loadsave.arb";
1654    const char *asc_db = "TEST_loadsave_ascii.arb";
1655
1656    GBDATA *gb_asc;
1657    TEST_EXPECT_RESULT__NOERROREXPORTED(gb_asc = GB_open(asc_db, "rw"));
1658
1659#if defined(TEST_AUTO_UPDATE)
1660    TEST_EXPECT_NO_ERROR(GB_save_as(gb_asc, bin_db, "b"));
1661#endif // TEST_AUTO_UPDATE
1662
1663    GBDATA *gb_bin;
1664    TEST_EXPECT_RESULT__NOERROREXPORTED(gb_bin = GB_open(bin_db, "rw"));
1665
1666    // test ASCII / BINARY compatibility
1667    SAVE_AND_COMPARE(gb_asc, "a2a.arb", "a", asc_db);
1668    SAVE_AND_COMPARE(gb_asc, "a2b.arb", "b", bin_db);
1669    SAVE_AND_COMPARE(gb_bin, "b2a.arb", "a", asc_db);
1670    SAVE_AND_COMPARE(gb_bin, "b2b.arb", "b", bin_db);
1671
1672    // test extra database stream compression
1673    const char *compFlag = SUPPORTED_COMPRESSION_FLAGS;
1674
1675    int successful_compressed_saves = 0;
1676    for (int c = 0; compFlag[c]; ++c) {
1677        for (char dbtype = 'a'; dbtype<='b'; ++dbtype) {
1678            TEST_ANNOTATE(GBS_global_string("dbtype=%c compFlag=%c", dbtype, compFlag[c]));
1679            char *zipd_db = GBS_global_string_copy("a2%c_%c.arb", dbtype, compFlag[c]);
1680
1681            char savetype[] = "??";
1682            savetype[0]     = dbtype;
1683            savetype[1]     = compFlag[c];
1684
1685            GB_ERROR error = GB_save_as(gb_asc, zipd_db, savetype);
1686            if (error && strstr(error, "failed with exitcode=127")) {
1687                fprintf(stderr, "Assuming compression utility for flag '%c' is not installed\n", compFlag[c]);
1688            }
1689            else {
1690                TEST_EXPECT_NO_ERROR(error);
1691
1692                // reopen saved database, save again + compare
1693                {
1694                    GBDATA *gb_reloaded = GB_open(zipd_db, "rw");
1695                    TEST_REJECT_NULL(gb_reloaded); // reading compressed database failed
1696
1697                    SAVE_AND_COMPARE(gb_reloaded, "r2b.arb", "b", bin_db); // check binary content
1698                    SAVE_AND_COMPARE(gb_reloaded, "r2a.arb", "a", asc_db); // check ascii content
1699
1700                    GB_close(gb_reloaded);
1701                }
1702                successful_compressed_saves++;
1703            }
1704
1705            free(zipd_db);
1706        }
1707    }
1708
1709    TEST_EXPECT(successful_compressed_saves>=2); // at least gzip and bzip2 should be installed
1710
1711#if (MEMORY_TEST == 0)
1712    {
1713        GBDATA *gb_nomap;
1714        TEST_EXPECT_RESULT__NOERROREXPORTED(gb_nomap = GB_open(bin_db, "rw"));
1715        TEST_EXPECT_NO_ERROR(GB_save_as(gb_nomap, "fast.arb", "bm"));
1716        TEST_EXPECT(GB_is_regularfile("fast.ARM")); // assert map file has been saved
1717        TEST_EXPECT_EQUAL(GB_time_of_file("fast.ARM"), GB_time_of_file("fast.arb"));
1718        GB_close(gb_nomap);
1719    }
1720    {
1721        // open DB with mapfile
1722        GBDATA *gb_map;
1723        TEST_EXPECT_RESULT__NOERROREXPORTED(gb_map = GB_open("fast.arb", "rw"));
1724        // SAVE_AND_COMPARE(gb_map, "fast2b.arb", "b", bin_db); // fails now (because 3 keys have different key-ref-counts)
1725        // Surprise: these three keys are 'tmp', 'message' and 'pending'
1726        // (key-ref-counts include temporary entries, but after saving they vanish and remain wrong)
1727        SAVE_AND_COMPARE(gb_map, "fast2a.arb", "a", asc_db); // using ascii avoids that problem (no keys stored there)
1728
1729        GB_close(gb_map);
1730    }
1731    {
1732        // test alloc/free (no real test, just call it for code coverage)
1733        char *small_block = (char*)gbm_get_mem(30, 5);
1734        gbm_free_mem(small_block, 30, 5);
1735
1736        char *big_block = (char*)gbm_get_mem(3000, 6);
1737        gbm_free_mem(big_block, 3000, 6);
1738    }
1739#endif
1740
1741    {
1742        // test opening saved DBs
1743        GBDATA *gb_a2b = GB_open("a2b.arb", "rw"); TEST_REJECT_NULL(gb_a2b);
1744        GBDATA *gb_b2b = GB_open("b2b.arb", "rw"); TEST_REJECT_NULL(gb_b2b);
1745
1746        // modify ..
1747        TEST_EXPECT_NO_ERROR(modify_db(gb_a2b));
1748        TEST_EXPECT_NO_ERROR(modify_db(gb_b2b));
1749
1750        // .. and quicksave
1751        TEST_EXPECT_NO_ERROR(GB_save_quick(gb_a2b, "a2b.arb"));
1752        TEST_EXPECT_NO_ERROR(GB_save_quick(gb_b2b, "b2b.arb"));
1753
1754#if defined(TEST_AUTO_UPDATE)
1755        TEST_COPY_FILE("a2b.a00", "TEST_loadsave_quick.a00");
1756#endif // TEST_AUTO_UPDATE
1757
1758        TEST_EXPECT_FILES_EQUAL("TEST_loadsave_quick.a00", "a2b.a00");
1759        TEST_EXPECT_FILES_EQUAL("a2b.a00", "b2b.a00");
1760
1761        TEST_EXPECT_NO_ERROR(GB_save_quick_as(gb_a2b, "a2b.arb"));
1762
1763        // check wether quicksave can be disabled
1764        GB_disable_quicksave(gb_a2b, "test it");
1765
1766        TEST_EXPECT_ERROR_CONTAINS(GB_save_quick(gb_a2b, "a2b.arb"), "Save Changes Disabled");
1767        TEST_EXPECT_ERROR_CONTAINS(GB_save_quick_as(gb_a2b, "a2b.arb"), "Save Changes Disabled");
1768
1769        const char *mod_db = "a2b_modified.arb";
1770        TEST_EXPECT_NO_ERROR(GB_save_as(gb_a2b, mod_db, "a")); // save modified DB (now ascii to avoid key-ref-problem)
1771        // test loading quicksave
1772        {
1773            GBDATA *gb_quickload = GB_open("a2b.arb", "rw"); // load DB which has a quicksave
1774            SAVE_AND_COMPARE(gb_quickload, "a2b_quickloaded.arb", "a", mod_db); // use ascii version (binary has key-ref-diffs)
1775            GB_close(gb_quickload);
1776        }
1777
1778        {
1779            // check master/slave DBs
1780            TEST_EXPECT_NO_ERROR(GB_save_as(gb_b2b, "master.arb", "b"));
1781
1782            GBDATA *gb_master = GB_open("master.arb", "rw"); TEST_REJECT_NULL(gb_master);
1783            TEST_EXPECT_NO_ERROR(modify_db(gb_master));
1784
1785            TEST_EXPECT_NO_ERROR(GB_save_quick(gb_master, "master.arb"));
1786            TEST_EXPECT_NO_ERROR(GB_save_quick_as(gb_master, "master.arb"));
1787
1788            TEST_EXPECT_ERROR_CONTAINS(GB_save_quick(gb_master, "renamed.arb"), "master file rename"); // quicksave with wrong name
1789
1790            // check if master gets protected by creating slave-DB
1791            TEST_EXPECT_NO_ERROR(GB_save_as(gb_master, "master.arb", "b")); // overwrite
1792            TEST_EXPECT_NO_ERROR(GB_save_quick_as(gb_master, "slave.arb")); // create slave -> master now protected
1793            TEST_EXPECT_ERROR_CONTAINS(GB_save_as(gb_master, "master.arb", "b"), "already exists and is write protected"); // overwrite should fail now
1794
1795            {
1796                GBDATA *gb_slave = GB_open("slave.arb", "rw"); TEST_REJECT_NULL(gb_slave); // load slave DB
1797                TEST_EXPECT_NO_ERROR(modify_db(gb_slave));
1798                TEST_EXPECT_NO_ERROR(GB_save_quick(gb_slave, "slave.arb"));
1799                TEST_EXPECT_NO_ERROR(GB_save_quick_as(gb_slave, "renamed.arb"));
1800                GB_close(gb_slave);
1801            }
1802            GB_close(gb_master);
1803        }
1804
1805        // test various error conditions:
1806
1807        TEST_EXPECT_ERROR_CONTAINS(GB_save_as(gb_b2b, "",        "b"),   "specify a savename"); // empty name
1808        TEST_EXPECT_ERROR_CONTAINS(GB_save_as(gb_b2b, "b2b.arb", "bzB"), "Multiple compression modes");
1809
1810        TEST_EXPECT_NO_ERROR(GB_set_mode_of_file(mod_db, 0444)); // write-protect
1811        TEST_EXPECT_ERROR_CONTAINS(GB_save_as(gb_b2b, mod_db, "b"), "already exists and is write protected"); // try to overwrite write-protected DB
1812
1813        TEST_EXPECT_ERROR_CONTAINS(GB_save_quick_as(gb_b2b, NULp), "specify a file name"); // no name
1814        TEST_EXPECT_ERROR_CONTAINS(GB_save_quick_as(gb_b2b, ""), "specify a file name"); // empty name
1815
1816        GB_close(gb_b2b);
1817        GB_close(gb_a2b);
1818    }
1819
1820    GB_close(gb_asc);
1821    GB_close(gb_bin);
1822
1823    TEST_loadsave_CLEANUP();
1824}
1825
1826#define TEST_quicksave_CLEANUP() TEST_EXPECT_ZERO(system("rm -f min_bin.a[0-9]* min_bin.ARF"))
1827
1828inline bool quicksave_exists(int i) {
1829    const char *qsformat = "min_bin.a%02i";
1830    return GB_is_regularfile(GBS_global_string(qsformat, (i)));
1831}
1832inline bool quicksave_missng(int i)          { return !quicksave_exists(i); }
1833inline bool is_first_quicksave(int i)        { return quicksave_exists(i) && !quicksave_exists(i-1); }
1834inline bool is_last_quicksave(int i)         { return quicksave_exists(i) && !quicksave_exists(i+1); }
1835inline bool quicksaves_range(int from, int to) { return is_first_quicksave(from) && is_last_quicksave(to); }
1836
1837#define TEST_QUICK_RANGE(s,e) TEST_EXPECT(quicksaves_range(s,e))
1838#define TEST_QUICK_GONE(i)    TEST_EXPECT(quicksave_missng(i))
1839
1840void TEST_SLOW_quicksave_names() {
1841    // check quicksave delete and wrap around
1842    TEST_quicksave_CLEANUP();
1843    const char *bname = "min_bin.arb";
1844
1845    GB_shell shell;
1846   
1847#if 0
1848    {
1849        // update min_bin.arb from min_ascii.arb
1850        const char *aname    = "min_ascii.arb";
1851        GBDATA     *gb_ascii = GB_open(aname, "rw"); TEST_REJECT_NULL(gb_ascii);
1852
1853        TEST_EXPECT_NO_ERROR(GB_save_as(gb_ascii, bname, "b"));
1854        GB_close(gb_ascii);
1855    }
1856#endif
1857    GBDATA *gb_bin = GB_open(bname, "rw"); TEST_REJECT_NULL(gb_bin);
1858    for (int i = 0; i <= 100; ++i) {
1859        TEST_EXPECT_NO_ERROR(GB_save_quick(gb_bin, bname));
1860        switch (i) {
1861            case  0: TEST_QUICK_RANGE( 0,  0); break;
1862            case  1: TEST_QUICK_RANGE( 0,  1); break;
1863            case 10: TEST_QUICK_RANGE( 1, 10); break;
1864            case 98: TEST_QUICK_RANGE(89, 98); break;
1865            case 99: TEST_QUICK_RANGE(90, 99);
1866                TEST_QUICK_GONE(0);    // should not exist yet
1867                break;
1868            case 100: TEST_QUICK_RANGE(0, 9);
1869                TEST_QUICK_GONE((i-8));
1870                TEST_QUICK_GONE((i-1));
1871                TEST_QUICK_GONE(i);
1872                break;
1873        }
1874        if (i == 10) {
1875            // speed-up-hack
1876            GB_MAIN_TYPE *Main   = GB_MAIN(gb_bin);
1877            i                   += 78; // -> 88 (afterwards run 10 times w/o checks to fake correct state)
1878            Main->qs.last_index += 78;
1879        }
1880    }
1881
1882    GB_close(gb_bin);
1883   
1884    TEST_quicksave_CLEANUP();
1885}
1886
1887void TEST_db_filenames() {
1888    TEST_EXPECT_EQUAL(gb_quicksaveName("nosuch.arb", 0), "nosuch.a00");
1889    TEST_EXPECT_EQUAL(gb_quicksaveName("nosuch", 1), "nosuch.a01");
1890}
1891
1892void TEST_SLOW_corruptedEntries_saveProtection() {
1893    // see #499 and #501
1894    GB_shell shell;
1895
1896    const char *name_NORMAL[] = {
1897        "corrupted.arb",
1898        "corrupted2.arb",
1899    };
1900    const char *name_CORRUPTED[] = {
1901        "corrupted_CORRUPTED.arb",
1902        "corrupted2_CORRUPTED.arb",
1903    };
1904
1905    const char *quickname           = "corrupted.a00";
1906    const char *quickname_CORRUPTED = "corrupted_CORRUPTED.a00";
1907    const char *quickname_unwanted  = "corrupted_CORRUPTED.a01";
1908
1909    const char **name = name_NORMAL;
1910
1911    const char *INITIAL_VALUE = "initial value";
1912    const char *CHANGED_VALUE = "changed";
1913
1914    GB_unlink("*~");
1915    GB_unlink(quickname_unwanted);
1916
1917    for (int corruption = 0; corruption<=3; ++corruption) {
1918        TEST_ANNOTATE(GBS_global_string("corruption level %i", corruption));
1919
1920        GB_unlink(name[0]);
1921
1922        // create simple DB
1923        {
1924            GBDATA *gb_main = GB_open(name[0], "cwr");
1925            TEST_REJECT_NULL(gb_main);
1926
1927            {
1928                GB_transaction ta(gb_main);
1929
1930                GBDATA *gb_entry = GB_create(gb_main, "sth", GB_STRING);
1931                TEST_REJECT_NULL(gb_entry);
1932                TEST_EXPECT_NO_ERROR(GB_write_string(gb_entry, INITIAL_VALUE));
1933
1934                GBDATA *gb_other = GB_create(gb_main, "other", GB_INT);
1935                TEST_REJECT_NULL(gb_other);
1936                TEST_EXPECT_NO_ERROR(GB_write_int(gb_other, 4711));
1937            }
1938
1939            TEST_EXPECT_NO_ERROR(GB_save(gb_main, NULp, "b"));
1940            GB_close(gb_main);
1941        }
1942
1943        // reopen DB, change the entry, quick save + full save with different name
1944        {
1945            GBDATA *gb_main = GB_open(name[0], "wr");
1946            TEST_REJECT_NULL(gb_main);
1947
1948            {
1949                GB_transaction ta(gb_main);
1950
1951                GBDATA *gb_entry = GB_entry(gb_main, "sth");
1952                TEST_REJECT_NULL(gb_entry);
1953
1954                const char *content = GB_read_char_pntr(gb_entry);
1955                TEST_EXPECT_EQUAL(content, INITIAL_VALUE);
1956
1957                TEST_EXPECT_NO_ERROR(GB_write_string(gb_entry, CHANGED_VALUE));
1958
1959                content = GB_read_char_pntr(gb_entry);
1960                TEST_EXPECT_EQUAL(content, CHANGED_VALUE);
1961
1962                // now corrupt the DB entry:
1963                if (corruption>0) {
1964                    char *illegal_access = (char*)content;
1965                    illegal_access[2]    = 0;
1966
1967                    if (corruption>1) {
1968                        gb_entry = GB_create(gb_main, "sth", GB_STRING);
1969                        TEST_REJECT_NULL(gb_entry);
1970                        TEST_EXPECT_NO_ERROR(GB_write_string(gb_entry, INITIAL_VALUE));
1971
1972                        if (corruption>2) {
1973                            // fill rest of string with zero bytes (similar to copying a truncated string into calloced memory)
1974                            int len = strlen(CHANGED_VALUE);
1975                            for (int i = 3; i<len; ++i) {
1976                                illegal_access[i] = 0;
1977                            }
1978                        }
1979                    }
1980
1981// #define PERFORM_DELETE
1982#ifdef PERFORM_DELETE
1983                    // delete "other"
1984                    GBDATA *gb_other = GB_entry(gb_main, "other");
1985                    TEST_REJECT_NULL(gb_other);
1986                    TEST_EXPECT_NO_ERROR(GB_delete(gb_other));
1987#endif
1988                }
1989            }
1990
1991            GB_ERROR quick_error = GB_save_quick(gb_main, name[0]);
1992            TEST_REJECT(seen_corrupt_data);
1993            if (corruption) {
1994                TEST_EXPECT_CONTAINS(quick_error, "Corrupted data detected during save");
1995                quick_error = GB_save_quick_as(gb_main, name_CORRUPTED[0]); // save with special name (as user should do)
1996                TEST_REJECT(seen_corrupt_data);
1997            }
1998            TEST_REJECT(quick_error);
1999
2000            GB_ERROR full_error = GB_save(gb_main, name[1], "b");
2001            TEST_REJECT(seen_corrupt_data);
2002            if (corruption) {
2003                TEST_EXPECT_CONTAINS(full_error, "Corrupted data detected during save");
2004                full_error  = GB_save(gb_main, name_CORRUPTED[1], "b"); // save with special name (as user should do)
2005                TEST_REJECT(seen_corrupt_data);
2006                name = name_CORRUPTED; // from now on use these names (for load and save)
2007            }
2008            TEST_REJECT(full_error);
2009
2010            GB_close(gb_main);
2011        }
2012
2013        for (int full = 0; full<2; ++full) {
2014            TEST_ANNOTATE(GBS_global_string("corruption level %i / full=%i", corruption, full));
2015
2016            // reopen DB (full==0 -> load quick save; ==1 -> load full save)
2017            GBDATA *gb_main = GB_open(name[full], "r");
2018            TEST_REJECT_NULL(gb_main);
2019
2020            if (gb_main) {
2021                {
2022                    GB_transaction ta(gb_main);
2023
2024                    GBDATA *gb_entry = GB_entry(gb_main, "sth");
2025                    TEST_REJECT_NULL(gb_entry);
2026
2027                    const char *content = GB_read_char_pntr(gb_entry);
2028
2029                    switch (corruption) {
2030                        case 0:
2031                            TEST_EXPECT_EQUAL(content, CHANGED_VALUE);
2032                            break;
2033                        default:
2034                            TEST_EXPECT_EQUAL(content, "ch");
2035                            break;
2036                    }
2037
2038                    // check 2nd entry
2039                    gb_entry = GB_nextEntry(gb_entry);
2040                    if (corruption>1) {
2041                        TEST_REJECT_NULL(gb_entry);
2042
2043                        content = GB_read_char_pntr(gb_entry);
2044                        TEST_EXPECT_EQUAL(content, INITIAL_VALUE);
2045                    }
2046                    else {
2047                        TEST_REJECT(gb_entry);
2048                    }
2049
2050                    // check int entry
2051                    GBDATA *gb_other = GB_entry(gb_main, "other");
2052#if defined(PERFORM_DELETE)
2053                    bool    deleted  = corruption>0;
2054#else // !defined(PERFORM_DELETE)
2055                    bool    deleted  = false;
2056#endif
2057
2058                    if (deleted) {
2059                        TEST_REJECT(gb_other);
2060                    }
2061                    else {
2062                        TEST_REJECT_NULL(gb_other);
2063                        TEST_EXPECT_EQUAL(GB_read_int(gb_other), 4711);
2064                    }
2065                }
2066
2067                GB_close(gb_main);
2068            }
2069            GB_unlink(name_NORMAL[full]);
2070            GB_unlink(name_CORRUPTED[full]);
2071        }
2072        GB_unlink(quickname);
2073        GB_unlink(quickname_CORRUPTED);
2074
2075        TEST_REJECT(GB_is_regularfile(quickname_unwanted));
2076
2077        name = name_NORMAL; // restart with normal names
2078    }
2079}
2080
2081TEST_PUBLISH(TEST_SLOW_corruptedEntries_saveProtection);
2082
2083#endif // UNIT_TESTS
Note: See TracBrowser for help on using the repository browser.