root/trunk/ARBDB/ad_save_load.cxx

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