source: branches/profile/ARBDB/ad_save_load.cxx

Last change on this file was 12438, checked in by westram, 10 years ago
  • activate errors on missing test-source location
    • publish all missing tests
    • missing location apparently only occurs
      • in dynamic libraries and
      • for last test of a module Note: the reverse is not true!
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 56.7 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
18#include "gb_key.h"
19#include "gb_map.h"
20#include "gb_load.h"
21#include "ad_io_inline.h"
22
23GB_MAIN_TYPE *gb_main_array[GB_MAIN_ARRAY_SIZE];
24
25char *gb_findExtension(char *path) {
26    char *punkt = strrchr(path, '.');
27    if (punkt) {
28        char *slash = strchr(punkt, '/');
29        if (slash) punkt = 0;   //  slash after '.' -> no extension
30    }
31    return punkt;
32}
33
34
35/* CAUTION!!!
36 *
37 * The following functions (gb_quicksaveName, gb_oldQuicksaveName, gb_mapfile_name, gb_overwriteName)
38 * use static buffers for the created filenames.
39 *
40 * So you have to make sure, to use only one instance of every of these
41 * functions or to dup the string before using the second instance
42 */
43
44inline char *STATIC_BUFFER(SmartCharPtr& strvar, int minlen) {
45    gb_assert(minlen > 0);
46    if (strvar.isNull() || (strlen(&*strvar) < (size_t)(minlen-1))) {
47        strvar = (char*)GB_calloc(minlen, 1);
48    }
49    return &*strvar;
50}
51
52GB_CSTR gb_oldQuicksaveName(GB_CSTR path, int nr) {
53    static SmartCharPtr Qname;
54
55    size_t  len   = strlen(path);
56    char   *qname = STATIC_BUFFER(Qname, len+15);
57    strcpy(qname, path);
58
59    char *ext     = gb_findExtension(qname);
60    if (!ext) ext = qname + len;
61
62    if (nr==-1) sprintf(ext, ".arb.quick?");
63    else    sprintf(ext, ".arb.quick%i", nr);
64
65    return qname;
66}
67
68GB_CSTR gb_quicksaveName(GB_CSTR path, int nr) {
69    static SmartCharPtr Qname;
70
71    char *qname = STATIC_BUFFER(Qname, strlen(path)+4);
72    strcpy(qname, path);
73
74    char *ext     = gb_findExtension(qname);
75    if (!ext) ext = qname + strlen(qname);
76
77    if (nr==-1) sprintf(ext, ".a??");
78    else    sprintf(ext, ".a%02i", nr);
79
80    return qname;
81}
82
83GB_CSTR gb_mapfile_name(GB_CSTR path) {
84    static SmartCharPtr Mapname;
85
86    char *mapname = STATIC_BUFFER(Mapname, strlen(path)+4+1);
87    strcpy(mapname, path);
88
89    char *ext     = gb_findExtension(mapname);
90    if (!ext) ext = mapname + strlen(mapname);
91
92    strcpy(ext, ".ARM");
93
94    return mapname;
95}
96
97GB_CSTR GB_mapfile(GBDATA *gb_main) {
98    GB_MAIN_TYPE *Main = GB_MAIN(gb_main);
99    return gb_mapfile_name(Main->path);
100}
101
102static GB_CSTR gb_overwriteName(GB_CSTR path) {
103    static SmartCharPtr Oname;
104
105    int   len   = strlen(path);
106    char *oname = STATIC_BUFFER(Oname, len+2);
107
108    strcpy(oname, path);
109    strcpy(oname+len, "~");                         // append ~
110
111    return oname;
112}
113
114static GB_CSTR gb_reffile_name(GB_CSTR path) {
115    static SmartCharPtr Refname;
116
117    size_t  len     = strlen(path);
118    char   *refname = STATIC_BUFFER(Refname, len+4+1);
119    memcpy(refname, path, len+1);
120
121    const char *ext        = gb_findExtension(refname);
122    size_t      ext_offset = ext ? (size_t)(ext-refname) : len;
123
124    strcpy(refname+ext_offset, ".ARF");
125
126    return refname;
127}
128
129static char *gb_full_path(const char *path) {
130    char *res = 0;
131
132    if (path[0] == '/') res = strdup(path);
133    else {
134        const char *cwd = GB_getcwd();
135
136        if (path[0] == 0) res = strdup(cwd);
137        else res              = GBS_global_string_copy("%s/%s", cwd, path);
138    }
139    return res;
140}
141
142static GB_ERROR gb_delete_reference(const char *master) {
143    GB_ERROR    error      = 0;
144    char       *fullmaster = gb_full_path(master);
145    const char *fullref    = gb_reffile_name(fullmaster);
146
147    GB_unlink_or_warn(fullref, &error);
148
149    free(fullmaster);
150    return error;
151}
152
153static GB_ERROR gb_create_reference(const char *master) {
154    char       *fullmaster = gb_full_path(master);
155    const char *fullref    = gb_reffile_name(fullmaster);
156    GB_ERROR    error      = 0;
157    FILE       *out        = fopen(fullref, "w");
158
159    if (out) {
160        fprintf(out, "***** The following files may be a link to %s ********\n", fullmaster);
161        fclose(out);
162        error = GB_set_mode_of_file(fullref, 00666);
163    }
164    else {
165        error = GBS_global_string("Cannot create reference file '%s'\n"
166                                  "Your database was saved, but you should check "
167                                  "write permissions in the destination directory!", fullref);
168    }
169
170    free(fullmaster);
171    return error;
172}
173
174static GB_ERROR gb_add_reference(const char *master, const char *changes) {
175    char       *fullmaster  = gb_full_path(master);
176    char       *fullchanges = gb_full_path(changes);
177    const char *fullref     = gb_reffile_name(fullmaster);
178    GB_ERROR    error       = 0;
179    FILE       *out         = fopen(fullref, "a");
180
181    if (out) {
182        fprintf(out, "%s\n", fullchanges);
183        fclose(out);
184        error = GB_set_mode_of_file(fullref, 00666);
185    }
186    else {
187        error = GBS_global_string("Cannot add your file '%s'\n"
188                                  "to the list of references of '%s'.\n"
189                                  "Please ask the owner of that file not to delete it\n"
190                                  "or save the entire database (that's recommended!)",
191                                  fullchanges, fullref);
192    }
193
194    free(fullchanges);
195    free(fullmaster);
196
197    return error;
198}
199
200static GB_ERROR gb_remove_quick_saved(GB_MAIN_TYPE *Main, const char *path) {
201    int i;
202    GB_ERROR error = 0;
203
204    for (i=0; i<GB_MAX_QUICK_SAVE_INDEX && !error; i++) GB_unlink_or_warn(gb_quicksaveName(path, i), &error);
205    for (i=0; i<10 && !error; i++) GB_unlink_or_warn(gb_oldQuicksaveName(path, i), &error);
206    if (Main) Main->qs.last_index = -1;
207
208    RETURN_ERROR(error);
209}
210
211static GB_ERROR gb_remove_all_but_main(GB_MAIN_TYPE *Main, const char *path) {
212    GB_ERROR error = gb_remove_quick_saved(Main, path);
213    if (!error) GB_unlink_or_warn(gb_mapfile_name(path), &error); // delete old mapfile
214
215    RETURN_ERROR(error);
216}
217
218static GB_ERROR deleteSuperfluousQuicksaves(GB_MAIN_TYPE *Main) {
219    int       cnt   = 0;
220    int       i;
221    char     *path  = Main->path;
222    GB_ERROR  error = 0;
223
224    for (i=0; i <= GB_MAX_QUICK_SAVE_INDEX; i++) {
225        GB_CSTR qsave = gb_quicksaveName(path, i);
226        if (GB_is_regularfile(qsave)) cnt++;
227    }
228
229    for (i=0; cnt>GB_MAX_QUICK_SAVES && i <= GB_MAX_QUICK_SAVE_INDEX && !error; i++) {
230        GB_CSTR qsave = gb_quicksaveName(path, i);
231        if (GB_is_regularfile(qsave)) {
232            if (GB_unlink(qsave)<0) error = GB_await_error();
233            else cnt--;
234        }
235    }
236
237    return error;
238}
239
240static GB_ERROR renameQuicksaves(GB_MAIN_TYPE *Main) {
241    /* After hundred quicksaves rename the quicksave-files to a00...a09
242     * to keep MS-DOS-compatibility (8.3)
243     * Note: This has to be called just before saving.
244     */
245
246    GB_ERROR error = deleteSuperfluousQuicksaves(Main);
247    if (!error) {
248        const char *path = Main->path;
249        int         i;
250        int         j;
251
252        for (i=0, j=0; i <= GB_MAX_QUICK_SAVE_INDEX; i++) {
253            GB_CSTR qsave = gb_quicksaveName(path, i);
254
255            if (GB_is_regularfile(qsave)) {
256                if (i!=j) {                         // otherwise the filename is correct
257                    char    *qdup = strdup(qsave);
258                    GB_CSTR  qnew = gb_quicksaveName(path, j);
259
260                    if (error) GB_warning(error);
261                    error = GB_rename_file(qdup, qnew);
262                    free(qdup);
263                }
264
265                j++;
266            }
267        }
268
269        gb_assert(j <= GB_MAX_QUICK_SAVES);
270        Main->qs.last_index = j-1;
271    }
272
273    return error;
274}
275
276// ------------------------
277//      Ascii to Binary
278
279long gb_ascii_2_bin(const char *source, GBENTRY *gbe) {
280    const char *s = source;
281    char        c = *(s++);
282
283    A_TO_I(c);
284    gbe->flags.compressed_data = c;
285
286    long size;
287    if (*s == ':') {
288        size = 0;
289        s++;
290    }
291    else {
292        long i, k;
293        for (i = 0, k = 8; k && (c = *(s++)); k--) {
294            A_TO_I(c);
295            i = (i<<4)+c;
296        }
297        size = i;
298    }
299    source = s;
300
301    long len = 0;
302    while ((c = *(s++))) {
303        if ((c == '.') || (c=='-')) {
304            len++;
305            continue;
306        }
307        if ((c == ':') || (c=='=')) {
308            len += 2;
309            continue;
310        }
311        if (!(c = *(s++))) {
312            return 1;
313        };
314        len++;
315    }
316
317    GBENTRY_memory storage(gbe, size, len);
318
319    char *d = storage;
320    s       = source;
321
322    while ((c = *(s++))) {
323        if (c == '.') {
324            *(d++)=0;
325            continue;
326        }
327        if (c == ':') {
328            *(d++)=0;
329            *(d++)=0;
330            continue;
331        }
332        if (c == '-') {
333            *(d++) = 0xff;
334            continue;
335        }
336        if (c == '=') {
337            *(d++) = 0xff;
338            *(d++) = 0xff;
339            continue;
340        }
341        A_TO_I(c);
342        long i = c << 4;
343        c = *(s++);
344        A_TO_I(c);
345        *(d++) = (char)(i + c);
346    }
347
348    return 0;
349}
350// ------------------------
351//      Binary to Ascii
352
353#define GB_PUT(c, out) do { if (c>=10) c+='A'-10; else c += '0'; *(out++) = (char)c; } while (0)
354
355static GB_BUFFER gb_bin_2_ascii(GBENTRY *gbe) {
356    signed char   *s, *out, c, mo;
357    unsigned long  i;
358    int            j;
359    char          *buffer;
360    int            k;
361
362    const char *source     = gbe->data();
363    long        len        = gbe->memsize();
364    long        xtended    = gbe->size();
365    int         compressed = gbe->flags.compressed_data;
366
367    buffer = GB_give_buffer(len * 2 + 10);
368    out = (signed char *)buffer;
369    s = (signed char *)source, mo = -1;
370
371    GB_PUT(compressed, out);
372    if (!xtended) {
373        *(out++) = ':';
374    }
375    else {
376        for (i = 0xf0000000, j=28; j>=0; j-=4, i=i>>4) {
377            k = (int)((xtended & i)>>j);
378            GB_PUT(k, out);
379        }
380    }
381    for (i = len; i; i--) {
382        if (!(c = *(s++))) {
383            if ((i > 1) && !*s) {
384                *(out++) = ':';
385                s ++;
386                i--;
387                continue;
388            }
389            *(out++) = '.';
390            continue;
391        }
392        if (c == mo) {
393            if ((i > 1) && (*s == -1)) {
394                *(out++) = '=';
395                s ++;
396                i--;
397                continue;
398            }
399            *(out++) = '-';
400            continue;
401        }
402        j = ((unsigned char) c) >> 4;
403        GB_PUT(j, out);
404        j = c & 15;
405        GB_PUT(j, out);
406    }
407    *(out++) = 0;
408    return buffer;
409}
410
411// -------------------------
412//      Write Ascii File
413
414#define GB_PUT_OUT(c, out) do { if (c>=10) c+='A'-10; else c += '0'; putc(c, out); } while (0)
415
416static void gb_write_rek(FILE *out, GBCONTAINER *gbc, long deep, long big_hunk) {
417    // used by ASCII database save
418    long     i;
419    char    *s;
420    char     c;
421    GBDATA  *gb;
422    GB_CSTR  strng;
423    char    *key;
424
425    for (gb = GB_child(gbc); gb; gb = GB_nextChild(gb)) {
426        if (gb->flags.temporary) continue;
427        key = GB_KEY(gb);
428        if (!strcmp(key, GB_SYSTEM_FOLDER)) continue;   // do not save system folder
429        for (i=deep; i--;) putc('\t', out);
430        fprintf(out, "%s\t", key);
431        if ((int)strlen(key) < 8) {
432            putc('\t', out);
433        }
434        if (gb->flags.security_delete ||
435                gb->flags.security_write ||
436                gb->flags.security_read ||
437                gb->flags2.last_updated) {
438            putc(':', out);
439            c = gb->flags.security_delete;
440            GB_PUT_OUT(c, out);
441            c = gb->flags.security_write;
442            GB_PUT_OUT(c, out);
443            c = gb->flags.security_read;
444            GB_PUT_OUT(c, out);
445            fprintf(out, "%u\t", gb->flags2.last_updated);
446        }
447        else {
448            putc('\t', out);
449        }
450
451        if (gb->is_container()) {
452            fprintf(out, "%%%c (%%\n", GB_read_flag(gb) ? '$' : '%');
453            gb_write_rek(out, gb->as_container(), deep+1, big_hunk);
454            for (i=deep+1; i--;) putc('\t', out);
455            fprintf(out, "%%) /*%s*/\n\n", GB_KEY(gb));
456        }
457        else {
458            GBENTRY *gbe = gb->as_entry();
459            switch (gbe->type()) {
460                case GB_STRING:
461                    strng = GB_read_char_pntr(gbe);
462                    if (!strng) {
463                        strng = "<entry was broken - replaced during ASCIIsave/arb_repair>";
464                        GB_warningf("- replaced broken DB entry %s (data lost)\n", GB_get_db_path(gbe));
465                    }
466                    if (*strng == '%') {
467                        putc('%', out);
468                        putc('s', out);
469                        putc('\t', out);
470                    }
471                    GBS_fwrite_string(strng, out);
472                    putc('\n', out);
473                    break;
474                case GB_LINK:
475                    strng = GB_read_link_pntr(gbe);
476                    if (*strng == '%') {
477                        putc('%', out);
478                        putc('l', out);
479                        putc('\t', out);
480                    }
481                    GBS_fwrite_string(strng, out);
482                    putc('\n', out);
483                    break;
484                case GB_INT:
485                    fprintf(out, "%%i %li\n", GB_read_int(gbe));
486                    break;
487                case GB_FLOAT:
488                    fprintf(out, "%%f %g\n", GB_read_float(gbe));
489                    break;
490                case GB_BITS:
491                    fprintf(out, "%%I\t\"%s\"\n",
492                            GB_read_bits_pntr(gbe, '-', '+'));
493                    break;
494                case GB_BYTES:
495                    s = gb_bin_2_ascii(gbe);
496                    fprintf(out, "%%Y\t%s\n", s);
497                    break;
498                case GB_INTS:
499                    s = gb_bin_2_ascii(gbe);
500                    fprintf(out, "%%N\t%s\n", s);
501                    break;
502                case GB_FLOATS:
503                    s = gb_bin_2_ascii(gbe);
504                    fprintf(out, "%%F\t%s\n", s);
505                    break;
506                case GB_DB:
507                    gb_assert(0);
508                    break;
509                case GB_BYTE:
510                    fprintf(out, "%%y %i\n", GB_read_byte(gbe));
511                    break;
512                default:
513                    fprintf(stderr,
514                            "ARBDB ERROR Key \'%s\' is of unknown type\n",
515                            GB_KEY(gbe));
516                    fprintf(out, "%%%% (%% %%) /* unknown type */\n");
517                    break;
518            }
519        }
520    }
521}
522
523// -------------------------
524//      Read Binary File
525
526long gb_read_bin_error(FILE *in, GBDATA *gbd, const char *text) {
527    long p = (long)ftell(in);
528    GB_export_errorf("%s in reading GB_file (loc %li=%lX) reading %s\n",
529                     text, p, p, GB_KEY(gbd));
530    GB_print_error();
531    return 0;
532}
533
534// --------------------------
535//      Write Binary File
536
537static int gb_is_writeable(gb_header_list *header, GBDATA *gbd, long version, long diff_save) {
538    /* Test whether to write any data to disc.
539     *
540     * version 1       write only latest data
541     * version 2       write deleted entries two (which are not already stored to master file !!!
542     *
543     * try to avoid to access gbd (to keep it swapped out)
544     */
545    if (version == 2 && header->flags.changed==GB_DELETED) return 1;    // save delete flag
546    if (!gbd) return 0;
547    if (diff_save) {
548        if (!header->flags.ever_changed) return 0;
549        if (!gbd->ext || (gbd->ext->update_date<diff_save && gbd->ext->creation_date < diff_save))
550            return 0;
551    }
552    if (gbd->flags.temporary) return 0;
553    return 1;
554}
555
556static int gb_write_bin_sub_containers(FILE *out, GBCONTAINER *gbc, long version, long diff_save, int is_root);
557
558static bool seen_corrupt_data = false;
559
560static long gb_write_bin_rek(FILE *out, GBDATA *gbd, long version, long diff_save, long index_of_master_file) {
561    int          i;
562    GBCONTAINER *gbc  = 0;
563    GBENTRY     *gbe  = 0;
564    long         size = 0;
565    GB_TYPES     type = gbd->type();
566
567    if (type == GB_DB) {
568        gbc = gbd->as_container();
569    }
570    else {
571        gbe = gbd->as_entry();
572        if (type == GB_STRING || type == GB_STRING_SHRT) {
573            size = gbe->size();
574            if (!gbe->flags.compressed_data && size < GBTUM_SHORT_STRING_SIZE) {
575                const char *data = gbe->data();
576                size_t      len  = strlen(data); // w/o zero-byte!
577
578                if ((long)len == size) {
579                    type = GB_STRING_SHRT;
580                }
581                else {
582                    // string contains zero-byte inside data or misses trailing zero-byte
583                    type              = GB_STRING; // fallback to safer type
584                    seen_corrupt_data = true;
585                    GB_warningf("Corrupted entry detected:\n"
586                                "entry: '%s'\n"
587                                "data:  '%s'",
588                                GB_get_db_path(gbe),
589                                data);
590                }
591            }
592            else {
593                type = GB_STRING;
594            }
595        }
596    }
597
598    i =     (type<<4)
599        +   (gbd->flags.security_delete<<1)
600        +   (gbd->flags.security_write>>2);
601    putc(i, out);
602
603    i =     ((gbd->flags.security_write &3) << 6)
604        +   (gbd->flags.security_read<<3)
605        +   (gbd->flags.compressed_data<<2)
606        +   ((GB_ARRAY_FLAGS(gbd).flags&1)<<1)
607        +   (gbd->flags.unused);
608    putc(i, out);
609
610    gb_put_number(GB_ARRAY_FLAGS(gbd).key_quark, out);
611
612    if (diff_save) {
613        gb_put_number(index_of_master_file, out);
614    }
615
616    i = gbd->flags2.last_updated;
617    putc(i, out);
618
619    if (type == GB_STRING_SHRT) {
620        const char *data = gbe->data();
621        gb_assert((long)strlen(data) == size);
622
623        i = fwrite(data, size+1, 1, out);
624        return i <= 0 ? -1 : 0;
625    }
626
627    switch (type) {
628        case GB_DB:
629            i = gb_write_bin_sub_containers(out, gbc, version, diff_save, 0);
630            return i;
631
632        case GB_INT: {
633            GB_UINT4 buffer = (GB_UINT4)htonl(gbe->info.i);
634            if (!fwrite((char *)&buffer, sizeof(float), 1, out)) return -1;
635            return 0;
636        }
637        case GB_FLOAT:
638            if (!fwrite((char *)&gbe->info.i, sizeof(float), 1, out)) return -1;
639            return 0;
640        case GB_LINK:
641        case GB_BITS:
642        case GB_BYTES:
643        case GB_INTS:
644        case GB_FLOATS:
645            size = gbe->size();
646            // fall-through
647        case GB_STRING: {
648            long memsize = gbe->memsize();
649            gb_put_number(size, out);
650            gb_put_number(memsize, out);
651            i = fwrite(gbe->data(), (size_t)memsize, 1, out);
652            if (memsize && !i) return -1;
653            return 0;
654        }
655        case GB_BYTE:
656            putc((int)(gbe->info.i), out);
657            return 0;
658        default:
659            gb_assert(0); // unknown type
660            return -1;
661    }
662}
663
664static int gb_write_bin_sub_containers(FILE *out, GBCONTAINER *gbc, long version, long diff_save, int is_root) {
665    gb_header_list *header;
666    uint32_t        i, index;
667
668    header = GB_DATA_LIST_HEADER(gbc->d);
669    gb_assert(gbc->d.nheader >= 0);
670    for (i=0, index = 0; index < (uint32_t)gbc->d.nheader; index++) {
671        if (gb_is_writeable(&(header[index]), GB_HEADER_LIST_GBD(header[index]), version, diff_save)) i++;
672    }
673
674    if (!is_root) {
675        gb_put_number(i, out);
676    }
677    else {
678        gb_write_out_uint32(i, out);
679    }
680
681    uint32_t counter = 0;
682    for (index = 0; index < (uint32_t)gbc->d.nheader; index++) {
683        GBDATA *h_gbd;
684
685        if (header[index].flags.changed == GB_DELETED_IN_MASTER) {  // count deleted items in master, because of index renaming
686            counter ++;
687            continue;
688        }
689
690        h_gbd = GB_HEADER_LIST_GBD(header[index]);
691
692        if (!gb_is_writeable(&(header[index]), h_gbd, version, diff_save)) {
693            if (version <= 1 && header[index].flags.changed == GB_DELETED) {
694                header[index].flags.changed = GB_DELETED_IN_MASTER; // mark deleted in master
695            }
696            continue;
697        }
698
699        if (h_gbd) {
700            i = (int)gb_write_bin_rek(out, h_gbd, version, diff_save, index-counter);
701            if (i) return i;
702        }
703        else {
704            if (header[index].flags.changed == GB_DELETED) {
705                putc(0, out);
706                putc(1, out);
707                gb_put_number(index - counter, out);
708            }
709        }
710    }
711    return 0;
712}
713
714static int gb_write_bin(FILE *out, GBCONTAINER *gbc, uint32_t version) {
715    /* version 1 write master arb file
716     * version 2 write slave arb file (aka quick save file)
717     */
718
719    gb_assert(!seen_corrupt_data);
720
721    int           diff_save = 0;
722    GB_MAIN_TYPE *Main      = GBCONTAINER_MAIN(gbc);
723
724    gb_write_out_uint32(GBTUM_MAGIC_NUMBER, out);
725    fprintf(out, "\n this is the binary version of the gbtum data file version %li\n", (long)version);
726    putc(0, out);
727    fwrite("vers", 4, 1, out);
728    gb_write_out_uint32(0x01020304, out);
729    gb_write_out_uint32(version, out);
730    fwrite("keys", 4, 1, out);
731
732    for (long i=1; i<Main->keycnt; i++) {
733        if (Main->keys[i].nref>0) {
734            gb_put_number(Main->keys[i].nref, out);
735            fprintf(out, "%s", Main->keys[i].key);
736        }
737        else {
738            putc(0, out);       // 0 nref
739            putc(1, out);       // empty key
740        }
741        putc(0, out);
742
743    }
744    putc(0, out);
745    putc(0, out);
746    fwrite("time", 4, 1, out); {
747        unsigned int k;
748        for (k=0; k<Main->last_updated; k++) {
749            fprintf(out, "%s", Main->dates[k]);
750            putc(0, out);
751        }
752    }
753    putc(0, out);
754    fwrite("data", 4, 1, out);
755    if (version == 2) diff_save = (int)Main->last_main_saved_transaction+1;
756
757    return gb_write_bin_sub_containers(out, gbc, version, diff_save, 1);
758}
759
760// ----------------------
761//      save database
762
763GB_ERROR GB_save(GBDATA *gb, const char *path, const char *savetype)
764     /* savetype 'a'    ascii
765      *          'aS'   dump to stdout
766      *          'b'    binary
767      *          'bm'   binary + mapfile
768      *          0      ascii
769      */
770{
771    if (path && strchr(savetype, 'S') == 0) // 'S' dumps to stdout -> do not change path
772    {
773        freedup(GB_MAIN(gb)->path, path);
774    }
775    return GB_save_as(gb, path, savetype);
776}
777
778GB_ERROR GB_create_parent_directory(const char *path) {
779    GB_ERROR error = NULL;
780    char *parent;
781    GB_split_full_path(path, &parent, NULL, NULL, NULL);
782    if (parent) {
783        if (!GB_is_directory(parent)) error = GB_create_directory(parent);
784        free(parent);
785    }
786    return error;
787}
788
789GB_ERROR GB_create_directory(const char *path) {
790    GB_ERROR error = 0;
791    if (!GB_is_directory(path)) {
792        error = GB_create_parent_directory(path);
793        if (!error) {
794            int res = mkdir(path, ACCESSPERMS);
795            if (res) error = GB_IO_error("creating directory", path);
796        }
797        error = GB_failedTo_error("GB_create_directory", path, error);
798    }
799    return error;
800}
801
802GB_ERROR GB_save_in_arbprop(GBDATA *gb, const char *path, const char *savetype) {
803    /* savetype
804     *      'a'    ascii
805     *      'b'    binary
806     *      'bm'   binary + mapfile
807     *
808     * automatically creates subdirectories
809     */
810
811    char     *fullname = strdup(GB_path_in_arbprop(path ? path : GB_MAIN(gb)->path));
812    GB_ERROR  error    = GB_create_parent_directory(fullname);
813    if (!error) error = GB_save_as(gb, fullname, savetype);
814    free(fullname);
815
816    return error;
817}
818
819GB_ERROR GB_MAIN_TYPE::check_saveable(const char *new_path, const char *flags) const {
820    /* Check wether file can be stored at destination
821     *  'f' in flags means 'force' => ignores main->disabled_path
822     *  'q' in flags means 'quick save'
823     *  'n' in flags means destination must be empty
824     */
825
826    GB_ERROR error = NULL;
827    if (is_client()) {
828        error = "You cannot save a remote database,\nplease use save button in master program";
829    }
830    else if (opentype == gb_open_read_only_all) {
831        error = "Database is read only";
832    }
833    else if (strchr(new_path, ':')) {
834        error = "Your database name may not contain a ':' character\nChoose a different name";
835    }
836    else {
837        char *fullpath = gb_full_path(new_path);
838        if (disabled_path && !strchr(flags, 'f')) {
839            if (GBS_string_matches(fullpath, disabled_path, GB_MIND_CASE)) {
840                error = GBS_global_string("You are not allowed to save your database in this directory,\n"
841                                          "Please select 'save as' and save your data to a different location");
842            }
843        }
844
845        if (!error) {
846            // check whether destination directory exists
847            char *lslash = strrchr(fullpath, '/');
848            if (lslash) {
849                lslash[0] = 0;
850                if (!GB_is_directory(fullpath)) {
851                    error = GBS_global_string("Directory '%s' doesn't exist", fullpath);
852                }
853                lslash[0] = '/';
854            }
855        }
856        free(fullpath);
857    }
858   
859    if (!error && !strchr(flags, 'q')) {
860        long mode = GB_mode_of_link(new_path);
861        if (mode >= 0 && !(mode & S_IWUSR)) { // no write access -> looks like a master file
862            error = GBS_global_string("Your selected file '%s'\n"
863                                      "already exists and is write protected!\n"
864                                      "This happens e.g. if your file is a MASTER ARB FILE which is\n"
865                                      "used by multiple quicksaved databases.\n"
866                                      "If you want to save it nevertheless, delete it first, but\n"
867                                      "note that doing this will render all these quicksaves useless!",
868                                      new_path);
869        }
870    }
871   
872    if (!error && strchr(flags, 'n') && GB_time_of_file(new_path)) {
873        error = GBS_global_string("Your destination file '%s' already exists.\n"
874                                  "Delete it manually!", new_path);
875    }
876
877#if (MEMORY_TEST==1)
878    if (!error && strchr(flags, 'm')) {
879        error = "It's impossible to save mapfiles (ARBDB is MEMORY_TEST mode 1)";
880    }
881#endif
882
883    return error;
884}
885
886static GB_ERROR protect_corruption_error(const char *savepath) {
887    GB_ERROR error = NULL;
888    gb_assert(seen_corrupt_data);
889    if (strstr(savepath, "CORRUPTED") == 0) {
890        error = "Severe error: Corrupted data detected during save\n"
891            "ARB did NOT save your database!\n"
892            "Advices:\n"                                                    //|
893            "* If your previous (quick)save was not long ago, your savest\n"
894            "  option is to drop the changes since then, by reloading the not\n"
895            "  corrupted database and redo your changes. If you can reproduce\n"
896            "  the bug that corrupted the entries, please report it!\n"
897            "* If that is no option (because too much work would be lost)\n"
898            "  you can force saving the corrupted database by adding the text\n"
899            "  'CORRUPTED' to the database name. After doing that, do NOT\n"
900            "  quit ARB, instead try to find and fix all corrupted entries\n"
901            "  that were listed below. Manually enter their original values\n"
902            "  (in case you want to lookup or copy&paste some values, you may\n"
903            "   open the last saved version of this database using\n"
904            "   'Start second database').\n"
905            "  Saving the database again will show all remaining unfixed\n"
906            "  entries. If no more corrupted entries show up, you can safely\n"
907            "  continue to work with that database.";
908    }
909    else {
910        GB_warning("Warning: Saved corrupt database");
911    }
912    seen_corrupt_data = false;
913    return error;
914}
915
916GB_ERROR GB_MAIN_TYPE::save_as(const char *as_path, const char *savetype) {
917    GB_ERROR error = 0;
918
919    bool saveASCII                            = false;
920    if (strchr(savetype, 'a')) saveASCII      = true;
921    else if (strchr(savetype, 'b')) saveASCII = false;
922    else error                                = GBS_global_string("Invalid savetype '%s' (expected 'a' or 'b')", savetype);
923
924    if (!error) {
925        if (!as_path) as_path              = path;
926        if (!as_path || !as_path[0]) error = "Please specify a savename";
927        else error                         = check_saveable(as_path, savetype);
928    }
929
930    if (!error) {
931        char *mappath        = NULL;
932        char *sec_path       = strdup(gb_overwriteName(as_path));
933        char *sec_mappath    = NULL;
934        bool  dump_to_stdout = strchr(savetype, 'S');
935        FILE *out            = dump_to_stdout ? stdout : fopen(sec_path, "w");
936
937        if (!out) error = GB_IO_error("saving", sec_path);
938        else {
939            const int org_security_level    = security_level;
940            const int org_transaction_level = get_transaction_level();
941
942            if (!org_transaction_level) transaction_level = 1;
943            else {
944                if (org_transaction_level> 0) {
945                    GB_commit_transaction(root_container);
946                    GB_begin_transaction(root_container);
947                }
948            }
949            security_level    = 7;
950            seen_corrupt_data = false;
951
952            bool outOfOrderSave     = strchr(savetype, 'f');
953            bool deleteQuickAllowed = !outOfOrderSave && !dump_to_stdout;
954            {
955                int result = 0;
956
957                if (saveASCII) {
958                    fprintf(out, "/*ARBDB ASCII*/\n");
959                    gb_write_rek(out, root_container, 0, 1);
960                    freedup(qs.quick_save_disabled, "Database saved in ASCII mode");
961                    if (deleteQuickAllowed) error = gb_remove_all_but_main(this, as_path);
962                }
963                else {                      // save binary
964                    mappath = strdup(gb_mapfile_name(as_path));
965                    if (strchr(savetype, 'm')) {
966                        // it's necessary to save the mapfile FIRST,
967                        // cause this re-orders all GB_CONTAINERs containing NULL-entries in their header
968                        sec_mappath = strdup(gb_overwriteName(mappath));
969                        error       = gb_save_mapfile(this, sec_mappath);
970                    }
971                    else GB_unlink_or_warn(mappath, &error); // delete old mapfile
972                    if (!error) result |= gb_write_bin(out, root_container, 1);
973                }
974
975                security_level    = org_security_level;
976                transaction_level = org_transaction_level;
977
978                if (!dump_to_stdout) result |= fclose(out);
979                if (result != 0) error       = GB_IO_error("writing", sec_path);
980            }
981
982            if (!error && seen_corrupt_data) {
983                error = protect_corruption_error(as_path);
984            }
985
986            if (!error && !saveASCII) {
987                if (!outOfOrderSave) freenull(qs.quick_save_disabled); // delete reason, why quicksaving was disallowed
988                if (deleteQuickAllowed) error = gb_remove_quick_saved(this, as_path);
989            }
990
991            if (!dump_to_stdout) {
992                if (error) {
993                    if (sec_mappath) GB_unlink_or_warn(sec_mappath, NULL);
994                    GB_unlink_or_warn(sec_path, NULL);
995                }
996                else {
997                    bool unlinkMapfiles       = false;
998                    error                     = GB_rename_file(sec_path, as_path);
999                    if (error) unlinkMapfiles = true;
1000                    else if (sec_mappath) {
1001                        error             = GB_rename_file(sec_mappath, mappath);
1002                        if (!error) error = GB_set_mode_of_file(mappath, GB_mode_of_file(as_path)); // set mapfile to same mode ...
1003                        if (!error) error = GB_set_time_of_file(mappath, GB_time_of_file(as_path)); // ... and same time as DB file
1004                        if (error) {
1005                            GB_warningf("Error: %s\n[Falling back to non-fastload-save]", error);
1006                            error          = 0;
1007                            unlinkMapfiles = true;
1008                        }
1009                    }
1010
1011                    if (unlinkMapfiles) {
1012                        GB_unlink_or_warn(sec_mappath, NULL);
1013                        GB_unlink_or_warn(mappath, NULL);
1014                    }
1015                           
1016                    if (!error) {
1017                        error = qs.quick_save_disabled == 0
1018                            ? gb_create_reference(as_path)
1019                            : gb_delete_reference(as_path);
1020                    }
1021                }
1022            }
1023
1024            if (!error && !outOfOrderSave) {
1025                last_saved_transaction      = GB_read_clock(root_container);
1026                last_main_saved_transaction = GB_read_clock(root_container);
1027                last_saved_time             = GB_time_of_day();
1028            }
1029        }
1030
1031        free(sec_path);
1032        free(mappath);
1033        free(sec_mappath);
1034    }
1035    return error;
1036}
1037
1038GB_ERROR GB_save_as(GBDATA *gbd, const char *path, const char *savetype) {
1039    /* Save whole database
1040     *
1041     * savetype
1042     *          'a' ascii
1043     *          'b' binary
1044     *          'm' save mapfile (only together with binary)
1045     *          'f' force saving even in disabled path to a different directory (out of order save)
1046     *          'S' save to stdout (for debugging)
1047     */
1048
1049    GB_ERROR error = NULL;
1050
1051    gb_assert(savetype);
1052
1053    if (!gbd) {
1054        error = "got no DB";
1055    }
1056    else {
1057        error = GB_MAIN(gbd)->save_as(path, savetype);
1058    }
1059
1060    RETURN_ERROR(error);
1061}
1062
1063GB_ERROR GB_MAIN_TYPE::check_quick_save() const {
1064    /* is quick save allowed?
1065     * return NULL if allowed, error why disallowed otherwise.
1066     */
1067
1068    if (qs.quick_save_disabled) {
1069        return GBS_global_string("Save Changes Disabled, because\n"
1070                                 "    '%s'\n"
1071                                 "    Save whole database using binary mode first",
1072                                 qs.quick_save_disabled);
1073    }
1074    return NULL;
1075}
1076
1077GB_ERROR GB_delete_database(GB_CSTR filename) {
1078    GB_ERROR error = 0;
1079
1080    if (GB_unlink(filename)<0) error = GB_await_error();
1081    else error                       = gb_remove_all_but_main(0, filename);
1082
1083    return error;
1084}
1085
1086GB_ERROR GB_MAIN_TYPE::save_quick_as(const char *as_path) {
1087    GB_ERROR error = NULL;
1088    if (!as_path || !strlen(as_path)) {
1089        error = "Please specify a file name";
1090    }
1091    else if (strcmp(as_path, path) == 0) {    // same name (no rename)
1092        error = save_quick(as_path);
1093    }
1094    else {
1095        error = check_quick_saveable(as_path, "bn");
1096
1097        if (!error) {
1098            FILE *fmaster = fopen(path, "r");         // old master !!!!
1099            if (!fmaster) {                                 // Oh no, where is my old master
1100                error = GBS_global_string("Save Changes is missing master ARB file '%s',\n"
1101                                          "    save database first", path);
1102            }
1103            else {
1104                fclose(fmaster);
1105            }
1106        }
1107        if (!error) {
1108            if (GB_unlink(as_path)<0) { // delete old file
1109                error = GBS_global_string("File '%s' already exists and could not be deleted\n"
1110                                          "(Reason: %s)",
1111                                          as_path, GB_await_error());
1112            }
1113        }
1114        if (!error) {
1115            char *org_master = S_ISLNK(GB_mode_of_link(path))
1116                ? GB_follow_unix_link(path)
1117                : strdup(path);
1118
1119            error = gb_remove_all_but_main(this, as_path);
1120            if (!error) {
1121                long mode = GB_mode_of_file(org_master);
1122                if (mode & S_IWUSR) {
1123                    GB_ERROR sm_error = GB_set_mode_of_file(org_master, mode & ~(S_IWUSR | S_IWGRP | S_IWOTH));
1124                    if (sm_error) {
1125                        GB_warningf("%s\n"
1126                                    "Ask your admin to remove write permissions from that master file.\n"
1127                                    "NEVER delete or change it, otherwise your quicksaves will be rendered useless!", 
1128                                    sm_error);
1129                    }
1130                }
1131                char *full_path_of_source;
1132                if (strchr(as_path, '/') || strchr(org_master, '/')) {
1133                    // dest or source in different directory
1134                    full_path_of_source = gb_full_path(org_master);
1135                }
1136                else {
1137                    full_path_of_source = strdup(org_master);
1138                }
1139
1140                error = GB_symlink(full_path_of_source, as_path);
1141                if (!error) {
1142                    if ((uid_t)GB_getuid_of_file(full_path_of_source) != getuid()) {
1143                        GB_warningf("**** WARNING ******\n"
1144                                    "   You now using a file '%s'\n"
1145                                    "   which is owned by another user\n"
1146                                    "   Please ask him not to delete this file\n"
1147                                    "   If you don't trust him, don't save changes but\n"
1148                                    "   the WHOLE database", full_path_of_source);
1149                    }
1150
1151                    GB_ERROR warning = gb_add_reference(full_path_of_source, as_path);
1152                    if (warning) GB_warning(warning);
1153
1154                    freedup(path, as_path);                      // Symlink created -> rename allowed
1155
1156                    qs.last_index = -1; // Start with new quicks (next index will be 0)
1157                    error = save_quick(as_path);
1158                }
1159                free(full_path_of_source);
1160            }
1161            free(org_master);
1162        }
1163    }
1164
1165    RETURN_ERROR(error);
1166}
1167
1168GB_ERROR GB_save_quick_as(GBDATA *gbd, const char *path) {
1169    return GB_MAIN(gbd)->save_quick_as(path);
1170}
1171
1172GB_ERROR GB_MAIN_TYPE::save_quick(const char *refpath) {
1173    GB_ERROR error = check_quick_saveable(refpath, "q");
1174
1175    if (!error && refpath && strcmp(refpath, path) != 0) {
1176        error = GBS_global_string("master file rename '%s'!= '%s',\n"
1177                                  "save database first", refpath, path);
1178    }
1179    if (!error) {
1180        FILE *fmaster = fopen(path, "r");
1181
1182        if (!fmaster) {
1183            error = GBS_global_string("Quick save is missing master ARB file '%s',\n"
1184                                      "save database first", refpath);
1185        }
1186        else {
1187            fclose(fmaster);
1188        }
1189    }
1190    if (!error && is_client()) error = "You cannot save a remote database";
1191    if (!error) {
1192        qs.last_index++;
1193        if (qs.last_index > GB_MAX_QUICK_SAVE_INDEX) renameQuicksaves(this);
1194
1195        GB_CSTR qck_path = gb_quicksaveName(path, qs.last_index);
1196        GB_CSTR sec_path = gb_overwriteName(qck_path);
1197
1198        FILE *out       = fopen(sec_path, "w");
1199        if (!out) error = GBS_global_string("Cannot save file to '%s'", sec_path);
1200        else {
1201            long erg;
1202            {
1203                const int org_security_level    = security_level;
1204                int       org_transaction_level = get_transaction_level();
1205
1206                if (!org_transaction_level) transaction_level = 1;
1207                else {
1208                    if (org_transaction_level> 0) {
1209                        GB_commit_transaction(root_container);
1210                        GB_begin_transaction(root_container);
1211                    }
1212                }
1213
1214                security_level    = 7;
1215                seen_corrupt_data = false;
1216
1217                erg = gb_write_bin(out, root_container, 2);
1218
1219                security_level    = org_security_level;
1220                transaction_level = org_transaction_level;
1221            }
1222
1223            erg |= fclose(out);
1224
1225            if (erg!=0) error = GBS_global_string("Cannot write to '%s'", sec_path);
1226            else {
1227                if (seen_corrupt_data) {
1228                    gb_assert(!error);
1229                    error = protect_corruption_error(qck_path);
1230                }
1231                if (!error) error = GB_rename_file(sec_path, qck_path);
1232                if (error) GB_unlink_or_warn(sec_path, NULL);
1233            }
1234        }
1235
1236        if (error) qs.last_index--; // undo index increment
1237        else {
1238            last_saved_transaction = GB_read_clock(root_container);
1239            last_saved_time        = GB_time_of_day();
1240
1241            error = deleteSuperfluousQuicksaves(this);
1242        }
1243    }
1244
1245    RETURN_ERROR(error);
1246}
1247
1248GB_ERROR GB_save_quick(GBDATA *gbd, const char *refpath) {
1249    return GB_MAIN(gbd)->save_quick(refpath);
1250}
1251
1252
1253void GB_disable_path(GBDATA *gbd, const char *path) {
1254    // disable directories for save
1255    freeset(GB_MAIN(gbd)->disabled_path, path ? GBS_eval_env(path) : NULL);
1256}
1257
1258long GB_last_saved_clock(GBDATA *gb_main) {
1259    return GB_MAIN(gb_main)->last_saved_transaction;
1260}
1261
1262GB_ULONG GB_last_saved_time(GBDATA *gb_main) {
1263    return GB_MAIN(gb_main)->last_saved_time;
1264}
1265
1266// --------------------------------------------------------------------------------
1267
1268#ifdef UNIT_TESTS
1269
1270#include <test_unit.h>
1271
1272#define SAVE_AND_COMPARE(gbd, save_as, savetype, compare_with) \
1273    TEST_EXPECT_NO_ERROR(GB_save_as(gbd, save_as, savetype));  \
1274    TEST_EXPECT_FILES_EQUAL(save_as, compare_with)
1275
1276static GB_ERROR modify_db(GBDATA *gb_main) {
1277    GB_transaction ta(gb_main);
1278
1279    GB_ERROR  error          = NULL;
1280    GBDATA   *gb_container   = GB_create_container(gb_main, "container");
1281    if (!gb_container) error = GB_await_error();
1282    else {
1283        GBDATA *gb_entry     = GB_create(gb_container, "str", GB_STRING);
1284        if (!gb_entry) error = GB_await_error();
1285        else    error        = GB_write_string(gb_entry, "text");
1286        // else    error        = GB_write_string(gb_entry, "bla"); // provoke error in file compare
1287    }
1288    return error;
1289}
1290
1291// #define TEST_AUTO_UPDATE // uncomment to auto-update binary and quicksave testfiles (needed once after changing ascii testfile or modify_db())
1292
1293#define TEST_loadsave_CLEANUP() TEST_EXPECT_ZERO(system("rm -f [ab]2[ab]*.* master.* slave.* renamed.* fast.* fast2b.* TEST_loadsave.ARF"))
1294
1295void TEST_SLOW_loadsave() {
1296    GB_shell shell;
1297    TEST_loadsave_CLEANUP();
1298
1299    // test non-existing DB
1300    TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_open("nonexisting.arb", "r"), "'nonexisting.arb' not found");
1301    TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_open("nonexisting.arb", "rw"), "'nonexisting.arb' not found");
1302    {
1303        GBDATA *gb_new;
1304        TEST_EXPECT_RESULT__NOERROREXPORTED(gb_new = GB_open("nonexisting.arb", "w")); // create new DB
1305        GB_close(gb_new);
1306    }
1307
1308    // the following DBs have to be provided in directory ../UNIT_TESTER/run
1309    const char *bin_db = "TEST_loadsave.arb";
1310    const char *asc_db = "TEST_loadsave_ascii.arb";
1311
1312    GBDATA *gb_asc;
1313    TEST_EXPECT_RESULT__NOERROREXPORTED(gb_asc = GB_open(asc_db, "rw"));
1314
1315#if defined(TEST_AUTO_UPDATE)
1316    TEST_EXPECT_NO_ERROR(GB_save_as(gb_asc, bin_db, "b"));
1317#endif // TEST_AUTO_UPDATE
1318
1319    GBDATA *gb_bin;
1320    TEST_EXPECT_RESULT__NOERROREXPORTED(gb_bin = GB_open(bin_db, "rw"));
1321
1322    // test ASCII / BINARY compatibility
1323    SAVE_AND_COMPARE(gb_asc, "a2a.arb", "a", asc_db);
1324    SAVE_AND_COMPARE(gb_asc, "a2b.arb", "b", bin_db);
1325    SAVE_AND_COMPARE(gb_bin, "b2a.arb", "a", asc_db);
1326    SAVE_AND_COMPARE(gb_bin, "b2b.arb", "b", bin_db);
1327
1328#if (MEMORY_TEST == 0)
1329    {
1330        GBDATA *gb_nomap;
1331        TEST_EXPECT_RESULT__NOERROREXPORTED(gb_nomap = GB_open(bin_db, "rw"));
1332        TEST_EXPECT_NO_ERROR(GB_save_as(gb_nomap, "fast.arb", "bm"));
1333        TEST_EXPECT(GB_is_regularfile("fast.ARM")); // assert map file has been saved
1334        TEST_EXPECT_EQUAL(GB_time_of_file("fast.ARM"), GB_time_of_file("fast.arb"));
1335        GB_close(gb_nomap);
1336    }
1337    {
1338        // open DB with mapfile
1339        GBDATA *gb_map;
1340        TEST_EXPECT_RESULT__NOERROREXPORTED(gb_map = GB_open("fast.arb", "rw"));
1341        SAVE_AND_COMPARE(gb_map, "fast2b.arb", "b", bin_db);
1342        GB_close(gb_map);
1343    }
1344    {
1345        // test alloc/free (no real test, just call it for code coverage)
1346        char *small_block = (char*)gbm_get_mem(30, 5);
1347        gbm_free_mem(small_block, 30, 5);
1348
1349        char *big_block = (char*)gbm_get_mem(3000, 6);
1350        gbm_free_mem(big_block, 3000, 6);
1351    }
1352#endif
1353
1354    {
1355        // test opening saved DBs
1356        GBDATA *gb_a2b = GB_open("a2b.arb", "rw"); TEST_REJECT_NULL(gb_a2b);
1357        GBDATA *gb_b2b = GB_open("b2b.arb", "rw"); TEST_REJECT_NULL(gb_b2b);
1358
1359        // modify ..
1360        TEST_EXPECT_NO_ERROR(modify_db(gb_a2b));
1361        TEST_EXPECT_NO_ERROR(modify_db(gb_b2b));
1362
1363        // .. and quicksave
1364        TEST_EXPECT_NO_ERROR(GB_save_quick(gb_a2b, "a2b.arb"));
1365        TEST_EXPECT_NO_ERROR(GB_save_quick(gb_b2b, "b2b.arb"));
1366
1367#if defined(TEST_AUTO_UPDATE)
1368        TEST_COPY_FILE("a2b.a00", "TEST_loadsave_quick.a00");
1369#endif // TEST_AUTO_UPDATE
1370
1371        TEST_EXPECT_FILES_EQUAL("TEST_loadsave_quick.a00", "a2b.a00");
1372        TEST_EXPECT_FILES_EQUAL("a2b.a00", "b2b.a00");
1373
1374        TEST_EXPECT_NO_ERROR(GB_save_quick_as(gb_a2b, "a2b.arb"));
1375
1376        // check wether quicksave can be disabled
1377        GB_disable_quicksave(gb_a2b, "test it");
1378
1379        TEST_EXPECT_ERROR_CONTAINS(GB_save_quick(gb_a2b, "a2b.arb"), "Save Changes Disabled");
1380        TEST_EXPECT_ERROR_CONTAINS(GB_save_quick_as(gb_a2b, "a2b.arb"), "Save Changes Disabled");
1381
1382        const char *mod_db = "a2b_modified.arb";
1383        TEST_EXPECT_NO_ERROR(GB_save_as(gb_a2b, mod_db, "b")); // save modified DB
1384        // test loading quicksave
1385        {
1386            GBDATA *gb_quicksaved = GB_open("a2b.arb", "rw"); // this DB has a quicksave
1387            SAVE_AND_COMPARE(gb_quicksaved, "a2b.arb", "b", mod_db);
1388            GB_close(gb_quicksaved);
1389        }
1390
1391        {
1392            // check master/slave DBs
1393            TEST_EXPECT_NO_ERROR(GB_save_as(gb_b2b, "master.arb", "b"));
1394
1395            GBDATA *gb_master = GB_open("master.arb", "rw"); TEST_REJECT_NULL(gb_master);
1396            TEST_EXPECT_NO_ERROR(modify_db(gb_master));
1397
1398            TEST_EXPECT_NO_ERROR(GB_save_quick(gb_master, "master.arb"));
1399            TEST_EXPECT_NO_ERROR(GB_save_quick_as(gb_master, "master.arb"));
1400
1401            TEST_EXPECT_ERROR_CONTAINS(GB_save_quick(gb_master, "renamed.arb"), "master file rename"); // quicksave with wrong name
1402
1403            // check if master gets protected by creating slave-DB
1404            TEST_EXPECT_NO_ERROR(GB_save_as(gb_master, "master.arb", "b")); // overwrite
1405            TEST_EXPECT_NO_ERROR(GB_save_quick_as(gb_master, "slave.arb")); // create slave -> master now protected
1406            TEST_EXPECT_ERROR_CONTAINS(GB_save_as(gb_master, "master.arb", "b"), "already exists and is write protected"); // overwrite should fail now
1407
1408            {
1409                GBDATA *gb_slave = GB_open("slave.arb", "rw"); TEST_REJECT_NULL(gb_slave); // load slave DB
1410                TEST_EXPECT_NO_ERROR(modify_db(gb_slave));
1411                TEST_EXPECT_NO_ERROR(GB_save_quick(gb_slave, "slave.arb"));
1412                TEST_EXPECT_NO_ERROR(GB_save_quick_as(gb_slave, "renamed.arb"));
1413                GB_close(gb_slave);
1414            }
1415            GB_close(gb_master);
1416        }
1417
1418        // test various error conditions:
1419
1420        TEST_EXPECT_ERROR_CONTAINS(GB_save_as(gb_b2b, "", "b"), "specify a savename"); // empty name
1421
1422        TEST_EXPECT_NO_ERROR(GB_set_mode_of_file(mod_db, 0444)); // write-protect
1423        TEST_EXPECT_ERROR_CONTAINS(GB_save_as(gb_b2b, mod_db, "b"), "already exists and is write protected"); // try to overwrite write-protected DB
1424
1425        TEST_EXPECT_ERROR_CONTAINS(GB_save_quick_as(gb_b2b, NULL), "specify a file name"); // no name
1426        TEST_EXPECT_ERROR_CONTAINS(GB_save_quick_as(gb_b2b, ""), "specify a file name"); // empty name
1427
1428        GB_close(gb_b2b);
1429        GB_close(gb_a2b);
1430    }
1431
1432    GB_close(gb_asc);
1433    GB_close(gb_bin);
1434
1435    TEST_loadsave_CLEANUP();
1436}
1437
1438#define TEST_quicksave_CLEANUP() TEST_EXPECT_ZERO(system("rm -f min_bin.a[0-9]* min_bin.ARF"))
1439
1440inline bool quicksave_exists(int i) {
1441    const char *qsformat = "min_bin.a%02i";
1442    return GB_is_regularfile(GBS_global_string(qsformat, (i)));
1443}
1444inline bool quicksave_missng(int i)          { return !quicksave_exists(i); }
1445inline bool is_first_quicksave(int i)        { return quicksave_exists(i) && !quicksave_exists(i-1); }
1446inline bool is_last_quicksave(int i)         { return quicksave_exists(i) && !quicksave_exists(i+1); }
1447inline bool quicksaves_range(int from, int to) { return is_first_quicksave(from) && is_last_quicksave(to); }
1448
1449#define TEST_QUICK_RANGE(s,e) TEST_EXPECT(quicksaves_range(s,e))
1450#define TEST_QUICK_GONE(i)    TEST_EXPECT(quicksave_missng(i))
1451
1452void TEST_SLOW_quicksave_names() {
1453    // check quicksave delete and wrap around
1454    TEST_quicksave_CLEANUP();
1455    const char *bname = "min_bin.arb";
1456
1457    GB_shell shell;
1458   
1459#if 0
1460    {
1461        // update min_bin.arb from min_ascii.arb
1462        const char *aname    = "min_ascii.arb";
1463        GBDATA     *gb_ascii = GB_open(aname, "rw"); TEST_REJECT_NULL(gb_ascii);
1464
1465        TEST_EXPECT_NO_ERROR(GB_save_as(gb_ascii, bname, "b"));
1466        GB_close(gb_ascii);
1467    }
1468#endif
1469    GBDATA *gb_bin = GB_open(bname, "rw"); TEST_REJECT_NULL(gb_bin);
1470    for (int i = 0; i <= 100; ++i) {
1471        TEST_EXPECT_NO_ERROR(GB_save_quick(gb_bin, bname));
1472        switch (i) {
1473            case  0: TEST_QUICK_RANGE( 0,  0); break;
1474            case  1: TEST_QUICK_RANGE( 0,  1); break;
1475            case 10: TEST_QUICK_RANGE( 1, 10); break;
1476            case 98: TEST_QUICK_RANGE(89, 98); break;
1477            case 99: TEST_QUICK_RANGE(90, 99);
1478                TEST_QUICK_GONE(0);    // should not exist yet
1479                break;
1480            case 100: TEST_QUICK_RANGE(0, 9);
1481                TEST_QUICK_GONE((i-8));
1482                TEST_QUICK_GONE((i-1));
1483                TEST_QUICK_GONE(i);
1484                break;
1485        }
1486        if (i == 10) {
1487            // speed-up-hack
1488            GB_MAIN_TYPE *Main   = GB_MAIN(gb_bin);
1489            i                   += 78; // -> 88 (afterwards run 10 times w/o checks to fake correct state)
1490            Main->qs.last_index += 78;
1491        }
1492    }
1493
1494    GB_close(gb_bin);
1495   
1496    TEST_quicksave_CLEANUP();
1497}
1498
1499void TEST_db_filenames() {
1500    TEST_EXPECT_EQUAL(gb_quicksaveName("nosuch.arb", 0), "nosuch.a00");
1501    TEST_EXPECT_EQUAL(gb_quicksaveName("nosuch", 1), "nosuch.a01");
1502}
1503
1504void TEST_SLOW_corruptedEntries_saveProtection() {
1505    // see #499 and #501
1506    GB_shell shell;
1507
1508    const char *name_NORMAL[] = {
1509        "corrupted.arb",
1510        "corrupted2.arb",
1511    };
1512    const char *name_CORRUPTED[] = {
1513        "corrupted_CORRUPTED.arb",
1514        "corrupted2_CORRUPTED.arb",
1515    };
1516
1517    const char *quickname           = "corrupted.a00";
1518    const char *quickname_CORRUPTED = "corrupted_CORRUPTED.a00";
1519    const char *quickname_unwanted  = "corrupted_CORRUPTED.a01";
1520
1521    const char **name = name_NORMAL;
1522
1523    const char *INITIAL_VALUE = "initial value";
1524    const char *CHANGED_VALUE = "changed";
1525
1526    GB_unlink("*~");
1527    GB_unlink(quickname_unwanted);
1528
1529    for (int corruption = 0; corruption<=3; ++corruption) {
1530        TEST_ANNOTATE(GBS_global_string("corruption level %i", corruption));
1531
1532        GB_unlink(name[0]);
1533
1534        // create simple DB
1535        {
1536            GBDATA *gb_main = GB_open(name[0], "cwr");
1537            TEST_REJECT_NULL(gb_main);
1538
1539            {
1540                GB_transaction ta(gb_main);
1541
1542                GBDATA *gb_entry = GB_create(gb_main, "sth", GB_STRING);
1543                TEST_REJECT_NULL(gb_entry);
1544                TEST_EXPECT_NO_ERROR(GB_write_string(gb_entry, INITIAL_VALUE));
1545
1546                GBDATA *gb_other = GB_create(gb_main, "other", GB_INT);
1547                TEST_REJECT_NULL(gb_other);
1548                TEST_EXPECT_NO_ERROR(GB_write_int(gb_other, 4711));
1549            }
1550
1551            TEST_EXPECT_NO_ERROR(GB_save(gb_main, NULL, "b"));
1552            GB_close(gb_main);
1553        }
1554
1555        // reopen DB, change the entry, quick save + full save with different name
1556        {
1557            GBDATA *gb_main = GB_open(name[0], "wr");
1558            TEST_REJECT_NULL(gb_main);
1559
1560            {
1561                GB_transaction ta(gb_main);
1562
1563                GBDATA *gb_entry = GB_entry(gb_main, "sth");
1564                TEST_REJECT_NULL(gb_entry);
1565
1566                const char *content = GB_read_char_pntr(gb_entry);
1567                TEST_EXPECT_EQUAL(content, INITIAL_VALUE);
1568
1569                TEST_EXPECT_NO_ERROR(GB_write_string(gb_entry, CHANGED_VALUE));
1570
1571                content = GB_read_char_pntr(gb_entry);
1572                TEST_EXPECT_EQUAL(content, CHANGED_VALUE);
1573
1574                // now corrupt the DB entry:
1575                if (corruption>0) {
1576                    char *illegal_access = (char*)content;
1577                    illegal_access[2]    = 0;
1578
1579                    if (corruption>1) {
1580                        gb_entry = GB_create(gb_main, "sth", GB_STRING);
1581                        TEST_REJECT_NULL(gb_entry);
1582                        TEST_EXPECT_NO_ERROR(GB_write_string(gb_entry, INITIAL_VALUE));
1583
1584                        if (corruption>2) {
1585                            // fill rest of string with zero bytes (similar to copying a truncated string into calloced memory)
1586                            int len = strlen(CHANGED_VALUE);
1587                            for (int i = 3; i<len; ++i) {
1588                                illegal_access[i] = 0;
1589                            }
1590                        }
1591                    }
1592
1593// #define PERFORM_DELETE
1594#ifdef PERFORM_DELETE
1595                    // delete "other"
1596                    GBDATA *gb_other = GB_entry(gb_main, "other");
1597                    TEST_REJECT_NULL(gb_other);
1598                    TEST_EXPECT_NO_ERROR(GB_delete(gb_other));
1599#endif
1600                }
1601            }
1602
1603            GB_ERROR quick_error = GB_save_quick(gb_main, name[0]);
1604            TEST_REJECT(seen_corrupt_data);
1605            if (corruption) {
1606                TEST_EXPECT_CONTAINS(quick_error, "Corrupted data detected during save");
1607                quick_error = GB_save_quick_as(gb_main, name_CORRUPTED[0]); // save with special name (as user should do)
1608                TEST_REJECT(seen_corrupt_data);
1609            }
1610            TEST_REJECT(quick_error);
1611
1612            GB_ERROR full_error = GB_save(gb_main, name[1], "b");
1613            TEST_REJECT(seen_corrupt_data);
1614            if (corruption) {
1615                TEST_EXPECT_CONTAINS(full_error, "Corrupted data detected during save");
1616                full_error  = GB_save(gb_main, name_CORRUPTED[1], "b"); // save with special name (as user should do)
1617                TEST_REJECT(seen_corrupt_data);
1618                name = name_CORRUPTED; // from now on use these names (for load and save)
1619            }
1620            TEST_REJECT(full_error);
1621
1622            GB_close(gb_main);
1623        }
1624
1625        for (int full = 0; full<2; ++full) {
1626            TEST_ANNOTATE(GBS_global_string("corruption level %i / full=%i", corruption, full));
1627
1628            // reopen DB (full==0 -> load quick save; ==1 -> load full save)
1629            GBDATA *gb_main = GB_open(name[full], "r");
1630            TEST_REJECT_NULL(gb_main);
1631
1632            if (gb_main) {
1633                {
1634                    GB_transaction ta(gb_main);
1635
1636                    GBDATA *gb_entry = GB_entry(gb_main, "sth");
1637                    TEST_REJECT_NULL(gb_entry);
1638
1639                    const char *content = GB_read_char_pntr(gb_entry);
1640
1641                    switch (corruption) {
1642                        case 0:
1643                            TEST_EXPECT_EQUAL(content, CHANGED_VALUE);
1644                            break;
1645                        default:
1646                            TEST_EXPECT_EQUAL(content, "ch");
1647                            break;
1648                    }
1649
1650                    // check 2nd entry
1651                    gb_entry = GB_nextEntry(gb_entry);
1652                    if (corruption>1) {
1653                        TEST_REJECT_NULL(gb_entry);
1654
1655                        content = GB_read_char_pntr(gb_entry);
1656                        TEST_EXPECT_EQUAL(content, INITIAL_VALUE);
1657                    }
1658                    else {
1659                        TEST_REJECT(gb_entry);
1660                    }
1661
1662                    // check int entry
1663                    GBDATA *gb_other = GB_entry(gb_main, "other");
1664#if defined(PERFORM_DELETE)
1665                    bool    deleted  = corruption>0;
1666#else // !defined(PERFORM_DELETE)
1667                    bool    deleted  = false;
1668#endif
1669
1670                    if (deleted) {
1671                        TEST_REJECT(gb_other);
1672                    }
1673                    else {
1674                        TEST_REJECT_NULL(gb_other);
1675                        TEST_EXPECT_EQUAL(GB_read_int(gb_other), 4711);
1676                    }
1677                }
1678
1679                GB_close(gb_main);
1680            }
1681            GB_unlink(name_NORMAL[full]);
1682            GB_unlink(name_CORRUPTED[full]);
1683        }
1684        GB_unlink(quickname);
1685        GB_unlink(quickname_CORRUPTED);
1686
1687        TEST_REJECT(GB_is_regularfile(quickname_unwanted));
1688
1689        name = name_NORMAL; // restart with normal names
1690    }
1691}
1692TEST_PUBLISH(TEST_SLOW_corruptedEntries_saveProtection);
1693
1694#endif // UNIT_TESTS
Note: See TracBrowser for help on using the repository browser.