~vcs-imports/qemu/git

« back to all changes in this revision

Viewing changes to block-vvfat.c

  • Committer: pbrook
  • Date: 2006-10-22 00:18:54 UTC
  • Revision ID: git-v1:e6e5906b6e0a81718066ca43aef57515026c6624
ColdFire target.


git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2196 c046a42c-6fe2-441c-8c8c-71466251a162

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
/* vim:set shiftwidth=4 ts=8: */
2
2
/*
3
3
 * QEMU Block driver for virtual VFAT (shadows a local directory)
4
 
 *
 
4
 * 
5
5
 * Copyright (c) 2004,2005 Johannes E. Schindelin
6
 
 *
 
6
 * 
7
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
 * of this software and associated documentation files (the "Software"), to deal
9
9
 * in the Software without restriction, including without limitation the rights
25
25
#include <sys/stat.h>
26
26
#include <dirent.h>
27
27
#include <assert.h>
28
 
#include "qemu-common.h"
 
28
#include "vl.h"
29
29
#include "block_int.h"
30
30
 
31
31
#ifndef S_IWGRP
38
38
/* TODO: add ":bootsector=blabla.img:" */
39
39
/* LATER TODO: add automatic boot sector generation from
40
40
    BOOTEASY.ASM and Ranish Partition Manager
41
 
    Note that DOS assumes the system files to be the first files in the
 
41
    Note that DOS assumes the system files to be the first files in the 
42
42
    file system (test if the boot sector still relies on that fact)! */
43
43
/* MAYBE TODO: write block-visofs.c */
44
44
/* TODO: call try_commit() only after a timeout */
53
53
#define stderr STDERR
54
54
FILE* stderr = NULL;
55
55
 
56
 
static void checkpoint(void);
 
56
static void checkpoint();
57
57
 
58
58
#ifdef __MINGW32__
59
59
void nonono(const char* file, int line, const char* msg) {
153
153
            index_to<0 || index_to>=array->next ||
154
154
            index_from<0 || index_from>=array->next)
155
155
        return -1;
156
 
 
 
156
    
157
157
    if(index_to==index_from)
158
158
        return 0;
159
159
 
167
167
        memmove(to+is*count,to,from-to);
168
168
    else
169
169
        memmove(from,from+is*count,to-from);
170
 
 
 
170
    
171
171
    memcpy(to,buf,is*count);
172
172
 
173
173
    free(buf);
175
175
    return 0;
176
176
}
177
177
 
178
 
static inline int array_remove_slice(array_t* array,int index, int count)
 
178
inline int array_remove_slice(array_t* array,int index, int count)
179
179
{
180
180
    assert(index >=0);
181
181
    assert(count > 0);
186
186
    return 0;
187
187
}
188
188
 
189
 
static int array_remove(array_t* array,int index)
 
189
int array_remove(array_t* array,int index)
190
190
{
191
191
    return array_remove_slice(array, index, 1);
192
192
}
193
193
 
194
194
/* return the index for a given member */
195
 
static int array_index(array_t* array, void* pointer)
 
195
int array_index(array_t* array, void* pointer)
196
196
{
197
197
    size_t offset = (char*)pointer - array->pointer;
198
198
    assert(offset >= 0);
242
242
    uint8_t magic[2];
243
243
} __attribute__((packed)) bootsector_t;
244
244
 
245
 
typedef struct {
246
 
    uint8_t head;
247
 
    uint8_t sector;
248
 
    uint8_t cylinder;
249
 
} mbr_chs_t;
250
 
 
251
245
typedef struct partition_t {
252
246
    uint8_t attributes; /* 0x80 = bootable */
253
 
    mbr_chs_t start_CHS;
254
 
    uint8_t   fs_type; /* 0x1 = FAT12, 0x6 = FAT16, 0xe = FAT16_LBA, 0xb = FAT32, 0xc = FAT32_LBA */
255
 
    mbr_chs_t end_CHS;
 
247
    uint8_t start_head;
 
248
    uint8_t start_sector;
 
249
    uint8_t start_cylinder;
 
250
    uint8_t fs_type; /* 0x1 = FAT12, 0x6 = FAT16, 0xb = FAT32 */
 
251
    uint8_t end_head;
 
252
    uint8_t end_sector;
 
253
    uint8_t end_cylinder;
256
254
    uint32_t start_sector_long;
257
 
    uint32_t length_sector_long;
 
255
    uint32_t end_sector_long;
258
256
} __attribute__((packed)) partition_t;
259
257
 
260
258
typedef struct mbr_t {
261
 
    uint8_t ignored[0x1b8];
262
 
    uint32_t nt_id;
263
 
    uint8_t ignored2[2];
 
259
    uint8_t ignored[0x1be];
264
260
    partition_t partition[4];
265
261
    uint8_t magic[2];
266
262
} __attribute__((packed)) mbr_t;
323
319
    BlockDriverState* bs; /* pointer to parent */
324
320
    unsigned int first_sectors_number; /* 1 for a single partition, 0x40 for a disk with partition table */
325
321
    unsigned char first_sectors[0x40*0x200];
326
 
 
 
322
    
327
323
    int fat_type; /* 16 or 32 */
328
324
    array_t fat,directory,mapping;
329
 
 
 
325
   
330
326
    unsigned int cluster_size;
331
327
    unsigned int sectors_per_cluster;
332
328
    unsigned int sectors_per_fat;
336
332
    uint32_t sector_count; /* total number of sectors of the partition */
337
333
    uint32_t cluster_count; /* total number of clusters of this partition */
338
334
    uint32_t max_fat_value;
339
 
 
 
335
   
340
336
    int current_fd;
341
337
    mapping_t* current_mapping;
342
338
    unsigned char* cluster; /* points to current cluster */
354
350
    int downcase_short_names;
355
351
} BDRVVVFATState;
356
352
 
357
 
/* take the sector position spos and convert it to Cylinder/Head/Sector position
358
 
 * if the position is outside the specified geometry, fill maximum value for CHS
359
 
 * and return 1 to signal overflow.
360
 
 */
361
 
static int sector2CHS(BlockDriverState* bs, mbr_chs_t * chs, int spos){
362
 
    int head,sector;
363
 
    sector   = spos % (bs->secs);  spos/= bs->secs;
364
 
    head     = spos % (bs->heads); spos/= bs->heads;
365
 
    if(spos >= bs->cyls){
366
 
        /* Overflow,
367
 
        it happens if 32bit sector positions are used, while CHS is only 24bit.
368
 
        Windows/Dos is said to take 1023/255/63 as nonrepresentable CHS */
369
 
        chs->head     = 0xFF;
370
 
        chs->sector   = 0xFF;
371
 
        chs->cylinder = 0xFF;
372
 
        return 1;
373
 
    }
374
 
    chs->head     = (uint8_t)head;
375
 
    chs->sector   = (uint8_t)( (sector+1) | ((spos>>8)<<6) );
376
 
    chs->cylinder = (uint8_t)spos;
377
 
    return 0;
378
 
}
379
353
 
380
354
static void init_mbr(BDRVVVFATState* s)
381
355
{
382
356
    /* TODO: if the files mbr.img and bootsect.img exist, use them */
383
357
    mbr_t* real_mbr=(mbr_t*)s->first_sectors;
384
358
    partition_t* partition=&(real_mbr->partition[0]);
385
 
    int lba;
386
359
 
387
360
    memset(s->first_sectors,0,512);
388
 
 
389
 
    /* Win NT Disk Signature */
390
 
    real_mbr->nt_id= cpu_to_le32(0xbe1afdfa);
391
 
 
 
361
   
392
362
    partition->attributes=0x80; /* bootable */
393
 
 
394
 
    /* LBA is used when partition is outside the CHS geometry */
395
 
    lba = sector2CHS(s->bs, &partition->start_CHS, s->first_sectors_number-1);
396
 
    lba|= sector2CHS(s->bs, &partition->end_CHS,   s->sector_count);
397
 
 
398
 
    /*LBA partitions are identified only by start/length_sector_long not by CHS*/
399
 
    partition->start_sector_long =cpu_to_le32(s->first_sectors_number-1);
400
 
    partition->length_sector_long=cpu_to_le32(s->sector_count - s->first_sectors_number+1);
401
 
 
 
363
    partition->start_head=1;
 
364
    partition->start_sector=1;
 
365
    partition->start_cylinder=0;
402
366
    /* FAT12/FAT16/FAT32 */
403
 
    /* DOS uses different types when partition is LBA,
404
 
       probably to prevent older versions from using CHS on them */
405
 
    partition->fs_type= s->fat_type==12 ? 0x1:
406
 
                        s->fat_type==16 ? (lba?0xe:0x06):
407
 
                         /*fat_tyoe==32*/ (lba?0xc:0x0b);
 
367
    partition->fs_type=(s->fat_type==12?0x1:s->fat_type==16?0x6:0xb);
 
368
    partition->end_head=s->bs->heads-1;
 
369
    partition->end_sector=0xff; /* end sector & upper 2 bits of cylinder */;
 
370
    partition->end_cylinder=0xff; /* lower 8 bits of end cylinder */;
 
371
    partition->start_sector_long=cpu_to_le32(s->bs->secs);
 
372
    partition->end_sector_long=cpu_to_le32(s->sector_count);
408
373
 
409
374
    real_mbr->magic[0]=0x55; real_mbr->magic[1]=0xaa;
410
375
}
412
377
/* direntry functions */
413
378
 
414
379
/* dest is assumed to hold 258 bytes, and pads with 0xffff up to next multiple of 26 */
415
 
static inline int short2long_name(char* dest,const char* src)
 
380
static inline int short2long_name(unsigned char* dest,const char* src)
416
381
{
417
382
    int i;
418
 
    int len;
419
383
    for(i=0;i<129 && src[i];i++) {
420
384
        dest[2*i]=src[i];
421
385
        dest[2*i+1]=0;
422
386
    }
423
 
    len=2*i;
424
387
    dest[2*i]=dest[2*i+1]=0;
425
388
    for(i=2*i+2;(i%26);i++)
426
389
        dest[i]=0xff;
427
 
    return len;
 
390
    return i;
428
391
}
429
392
 
430
393
static inline direntry_t* create_long_filename(BDRVVVFATState* s,const char* filename)
441
404
        entry->begin=0;
442
405
        entry->name[0]=(number_of_entries-i)|(i==0?0x40:0);
443
406
    }
444
 
    for(i=0;i<26*number_of_entries;i++) {
 
407
    for(i=0;i<length;i++) {
445
408
        int offset=(i%26);
446
409
        if(offset<10) offset=1+offset;
447
410
        else if(offset<22) offset=14+offset-10;
515
478
    for(i=0;i<11;i++)
516
479
        chksum=(((chksum&0xfe)>>1)|((chksum&0x01)?0x80:0))
517
480
            +(unsigned char)entry->name[i];
518
 
 
 
481
    
519
482
    return chksum;
520
483
}
521
484
 
567
530
        uint16_t* entry=array_get(&(s->fat),cluster);
568
531
        return le16_to_cpu(*entry);
569
532
    } else {
570
 
        const uint8_t* x=(uint8_t*)(s->fat.pointer)+cluster*3/2;
 
533
        const uint8_t* x=s->fat.pointer+cluster*3/2;
571
534
        return ((x[0]|(x[1]<<8))>>(cluster&1?4:0))&0x0fff;
572
535
    }
573
536
}
591
554
                s->sectors_per_fat * 0x200 / s->fat.item_size - 1);
592
555
    }
593
556
    memset(s->fat.pointer,0,s->fat.size);
594
 
 
 
557
    
595
558
    switch(s->fat_type) {
596
559
        case 12: s->max_fat_value=0xfff; break;
597
560
        case 16: s->max_fat_value=0xffff; break;
616
579
        memcpy(entry->name,filename,strlen(filename));
617
580
        return entry;
618
581
    }
619
 
 
 
582
    
620
583
    entry_long=create_long_filename(s,filename);
621
 
 
622
 
    i = strlen(filename);
 
584
  
 
585
    i = strlen(filename); 
623
586
    for(j = i - 1; j>0  && filename[j]!='.';j--);
624
587
    if (j > 0)
625
588
        i = (j > 8 ? 8 : j);
628
591
 
629
592
    entry=array_get_next(&(s->directory));
630
593
    memset(entry->name,0x20,11);
631
 
    strncpy((char*)entry->name,filename,i);
632
 
 
 
594
    strncpy(entry->name,filename,i);
 
595
    
633
596
    if(j > 0)
634
597
        for (i = 0; i < 3 && filename[j+1+i]; i++)
635
598
            entry->extension[i] = filename[j+1+i];
655
618
        if(entry1==entry) /* no dupe found */
656
619
            break;
657
620
 
658
 
        /* use all 8 characters of name */
 
621
        /* use all 8 characters of name */      
659
622
        if(entry->name[7]==' ') {
660
623
            int j;
661
624
            for(j=6;j>0 && entry->name[j]==' ';j--)
712
675
        mapping->end = mapping->begin;
713
676
        return -1;
714
677
    }
715
 
 
 
678
   
716
679
    i = mapping->info.dir.first_dir_index =
717
680
            first_cluster == 0 ? 0 : s->directory.next;
718
681
 
719
 
    /* actually read the directory, and allocate the mappings */
 
682
    /* actually read the directory, and allocate the mappings */ 
720
683
    while((entry=readdir(dir))) {
721
684
        unsigned int length=strlen(dirname)+2+strlen(entry->d_name);
722
685
        char* buffer;
727
690
 
728
691
        if(first_cluster == 0 && (is_dotdot || is_dot))
729
692
            continue;
730
 
 
 
693
        
731
694
        buffer=(char*)malloc(length);
732
695
        assert(buffer);
733
696
        snprintf(buffer,length,"%s/%s",dirname,entry->d_name);
802
765
        memset(array_get(&(s->directory), cur), 0,
803
766
                (ROOT_ENTRIES - cur) * sizeof(direntry_t));
804
767
    }
805
 
 
 
768
        
806
769
     /* reget the mapping, since s->mapping was possibly realloc()ed */
807
770
    mapping = (mapping_t*)array_get(&(s->mapping), mapping_index);
808
771
    first_cluster += (s->directory.next - mapping->info.dir.first_dir_index)
811
774
 
812
775
    direntry = (direntry_t*)array_get(&(s->directory), mapping->dir_index);
813
776
    set_begin_of_direntry(direntry, mapping->begin);
814
 
 
 
777
   
815
778
    return 0;
816
779
}
817
780
 
862
825
     */
863
826
    i = 1+s->sectors_per_cluster*0x200*8/s->fat_type;
864
827
    s->sectors_per_fat=(s->sector_count+i)/i; /* round up */
865
 
 
 
828
    
866
829
    array_init(&(s->mapping),sizeof(mapping_t));
867
830
    array_init(&(s->directory),sizeof(direntry_t));
868
831
 
870
833
    {
871
834
        direntry_t* entry=array_get_next(&(s->directory));
872
835
        entry->attributes=0x28; /* archive | volume label */
873
 
        snprintf((char*)entry->name,11,"QEMU VVFAT");
 
836
        snprintf(entry->name,11,"QEMU VVFAT");
874
837
    }
875
838
 
876
839
    /* Now build FAT, and write back information into directory */
894
857
 
895
858
    for (i = 0, cluster = 0; i < s->mapping.next; i++) {
896
859
        int j;
897
 
        /* MS-DOS expects the FAT to be 0 for the root directory
 
860
        /* MS-DOS expects the FAT to be 0 for the root directory 
898
861
         * (except for the media byte). */
899
862
        /* LATER TODO: still true for FAT32? */
900
863
        int fix_fat = (i != 0);
1010
973
 
1011
974
    s->fat_type=16;
1012
975
    /* LATER TODO: if FAT32, adjust */
 
976
    s->sector_count=0xec04f;
1013
977
    s->sectors_per_cluster=0x10;
1014
 
    /* 504MB disk*/
1015
 
    bs->cyls=1024; bs->heads=16; bs->secs=63;
 
978
    /* LATER TODO: this could be wrong for FAT32 */
 
979
    bs->cyls=1023; bs->heads=15; bs->secs=63;
1016
980
 
1017
981
    s->current_cluster=0xffffffff;
1018
982
 
1023
987
    s->qcow_filename = NULL;
1024
988
    s->fat2 = NULL;
1025
989
    s->downcase_short_names = 1;
1026
 
 
 
990
    
1027
991
    if (!strstart(dirname, "fat:", NULL))
1028
992
        return -1;
1029
993
 
 
994
    if (strstr(dirname, ":rw:")) {
 
995
        if (enable_write_target(s))
 
996
            return -1;
 
997
        bs->read_only = 0;
 
998
    }
 
999
 
1030
1000
    if (strstr(dirname, ":floppy:")) {
1031
1001
        floppy = 1;
1032
1002
        s->fat_type = 12;
1035
1005
        bs->cyls = 80; bs->heads = 2; bs->secs = 36;
1036
1006
    }
1037
1007
 
1038
 
    s->sector_count=bs->cyls*bs->heads*bs->secs;
1039
 
 
1040
1008
    if (strstr(dirname, ":32:")) {
1041
1009
        fprintf(stderr, "Big fat greek warning: FAT32 has not been tested. You are welcome to do so!\n");
1042
1010
        s->fat_type = 32;
1047
1015
        s->sector_count=2880;
1048
1016
    }
1049
1017
 
1050
 
    if (strstr(dirname, ":rw:")) {
1051
 
        if (enable_write_target(s))
1052
 
            return -1;
1053
 
        bs->read_only = 0;
1054
 
    }
1055
 
 
1056
1018
    i = strrchr(dirname, ':') - dirname;
1057
1019
    assert(i >= 3);
1058
1020
    if (dirname[i-2] == ':' && isalpha(dirname[i-1]))
1062
1024
        dirname += i+1;
1063
1025
 
1064
1026
    bs->total_sectors=bs->cyls*bs->heads*bs->secs;
1065
 
 
 
1027
    if (s->sector_count > bs->total_sectors)
 
1028
        s->sector_count = bs->total_sectors;
1066
1029
    if(init_directories(s, dirname))
1067
1030
        return -1;
1068
1031
 
1069
 
    s->sector_count = s->faked_sectors + s->sectors_per_cluster*s->cluster_count;
1070
 
 
1071
1032
    if(s->first_sectors_number==0x40)
1072
1033
        init_mbr(s);
1073
1034
 
1115
1076
        assert(index1<=index2);
1116
1077
        DLOG(mapping=array_get(&(s->mapping),index1);
1117
1078
        assert(mapping->begin<=cluster_num);
1118
 
        assert(index2 >= s->mapping.next ||
 
1079
        assert(index2 >= s->mapping.next || 
1119
1080
                ((mapping = array_get(&(s->mapping),index2)) &&
1120
1081
                mapping->end>cluster_num)));
1121
1082
    }
1189
1150
                s->current_mapping = mapping;
1190
1151
read_cluster_directory:
1191
1152
                offset = s->cluster_size*(cluster_num-s->current_mapping->begin);
1192
 
                s->cluster = (unsigned char*)s->directory.pointer+offset
 
1153
                s->cluster = s->directory.pointer+offset
1193
1154
                        + 0x20*s->current_mapping->info.dir.first_dir_index;
1194
1155
                assert(((s->cluster-(unsigned char*)s->directory.pointer)%s->cluster_size)==0);
1195
1156
                assert((char*)s->cluster+s->cluster_size <= s->directory.pointer+s->directory.next*s->directory.item_size);
1278
1239
}
1279
1240
#endif
1280
1241
 
1281
 
static int vvfat_read(BlockDriverState *bs, int64_t sector_num,
 
1242
static int vvfat_read(BlockDriverState *bs, int64_t sector_num, 
1282
1243
                    uint8_t *buf, int nb_sectors)
1283
1244
{
1284
1245
    BDRVVVFATState *s = bs->opaque;
1459
1420
    }
1460
1421
 
1461
1422
    if (pointer[0] & 0x40)
1462
 
        lfn->len = offset + strlen((char*)lfn->name + offset);
 
1423
        lfn->len = offset + strlen(lfn->name + offset);
1463
1424
 
1464
1425
    return 0;
1465
1426
}
1498
1459
    } else
1499
1460
        lfn->name[i + j + 1] = '\0';
1500
1461
 
1501
 
    lfn->len = strlen((char*)lfn->name);
 
1462
    lfn->len = strlen(lfn->name);
1502
1463
 
1503
1464
    return 0;
1504
1465
}
1713
1674
}
1714
1675
 
1715
1676
/*
1716
 
 * This function looks at the modified data (qcow).
 
1677
 * This function looks at the modified data (qcow). 
1717
1678
 * It returns 0 upon inconsistency or error, and the number of clusters
1718
1679
 * used by the directory, its subdirectories and their files.
1719
1680
 */
1748
1709
    } else
1749
1710
        /* new directory */
1750
1711
        schedule_mkdir(s, cluster_num, strdup(path));
1751
 
 
 
1712
                
1752
1713
    lfn_init(&lfn);
1753
1714
    do {
1754
1715
        int i;
1794
1755
                    fprintf(stderr, "Error in short name (%d)\n", subret);
1795
1756
                    goto fail;
1796
1757
                }
1797
 
                if (subret > 0 || !strcmp((char*)lfn.name, ".")
1798
 
                        || !strcmp((char*)lfn.name, ".."))
 
1758
                if (subret > 0 || !strcmp(lfn.name, ".")
 
1759
                        || !strcmp(lfn.name, ".."))
1799
1760
                    continue;
1800
1761
            }
1801
1762
            lfn.checksum = 0x100; /* cannot use long name twice */
1804
1765
                fprintf(stderr, "Name too long: %s/%s\n", path, lfn.name);
1805
1766
                goto fail;
1806
1767
            }
1807
 
            strcpy(path2 + path_len + 1, (char*)lfn.name);
 
1768
            strcpy(path2 + path_len + 1, lfn.name);
1808
1769
 
1809
1770
            if (is_directory(direntries + i)) {
1810
1771
                if (begin_of_direntry(direntries + i) == 0) {
2088
2049
            }
2089
2050
 
2090
2051
            next_mapping->dir_index = mapping->dir_index;
2091
 
            next_mapping->first_mapping_index =
 
2052
            next_mapping->first_mapping_index = 
2092
2053
                mapping->first_mapping_index < 0 ?
2093
2054
                array_index(&(s->mapping), mapping) :
2094
2055
                mapping->first_mapping_index;
2108
2069
 
2109
2070
            mapping = next_mapping;
2110
2071
        }
2111
 
 
 
2072
                
2112
2073
        cluster = c1;
2113
2074
    }
2114
2075
 
2236
2197
        assert(size >= 0);
2237
2198
 
2238
2199
        ret = vvfat_read(s->bs, cluster2sector(s, c),
2239
 
            (uint8_t*)cluster, (rest_size + 0x1ff) / 0x200);
 
2200
            cluster, (rest_size + 0x1ff) / 0x200);
2240
2201
 
2241
2202
        if (ret < 0)
2242
2203
            return ret;
2594
2555
        return ret;
2595
2556
    }
2596
2557
 
2597
 
    /* copy FAT (with bdrv_read) */
 
2558
    /* copy FAT (with bdrv_read) */ 
2598
2559
    memcpy(s->fat.pointer, s->fat2, 0x200 * s->sectors_per_fat);
2599
2560
 
2600
2561
    /* recurse direntries from root (using bs->bdrv_read) */
2636
2597
    return do_commit(s);
2637
2598
}
2638
2599
 
2639
 
static int vvfat_write(BlockDriverState *bs, int64_t sector_num,
 
2600
static int vvfat_write(BlockDriverState *bs, int64_t sector_num, 
2640
2601
                    const uint8_t *buf, int nb_sectors)
2641
2602
{
2642
 
    BDRVVVFATState *s = bs->opaque;
 
2603
    BDRVVVFATState *s = bs->opaque; 
2643
2604
    int i, ret;
2644
2605
 
2645
2606
DLOG(checkpoint());
2678
2639
                    begin = sector_num;
2679
2640
                if (end > sector_num + nb_sectors)
2680
2641
                    end = sector_num + nb_sectors;
2681
 
                dir_index  = mapping->dir_index +
 
2642
                dir_index  = mapping->dir_index + 
2682
2643
                    0x10 * (begin - mapping->begin * s->sectors_per_cluster);
2683
2644
                direntries = (direntry_t*)(buf + 0x200 * (begin - sector_num));
2684
2645
 
2737
2698
        *n = nb_sectors;
2738
2699
    else if (*n < 0)
2739
2700
        return 0;
2740
 
    return 1;
 
2701
    return 1;   
2741
2702
}
2742
2703
 
2743
2704
static int write_target_commit(BlockDriverState *bs, int64_t sector_num,
2813
2774
};
2814
2775
 
2815
2776
#ifdef DEBUG
2816
 
static void checkpoint(void) {
 
2777
static void checkpoint() {
2817
2778
    assert(((mapping_t*)array_get(&(vvv->mapping), 0))->end == 2);
2818
2779
    check1(vvv);
2819
2780
    check2(vvv);