source: branches/help/ARBDB/ad_save_load.cxx

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