source: tags/ms_r16q2/ARBDB/ad_save_load.cxx

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