source: tags/ms_r18q1/ARBDB/ad_save_load.cxx

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