source: trunk/ARBDB/ad_save_load.cxx

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