/ Check-in [e358c3de]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Fix various problems in fts5 revealed by fault-injection tests.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | fts5
Files: files | file ages | folders
SHA1: e358c3de5c916f2c851ab9324ceaae4e4e7a0fbd
User & Date: dan 2014-12-18 18:25:48
Context
2014-12-18
20:01
Fix a problem with prefix queries and the AND operator. check-in: 38b3c65e user: dan tags: fts5
18:25
Fix various problems in fts5 revealed by fault-injection tests. check-in: e358c3de user: dan tags: fts5
2014-12-03
17:27
Begin testing fts5 OOM and IO error handling. check-in: 2037dba6 user: dan tags: fts5
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to ext/fts5/fts5.c.

   219    219   
   220    220       case FTS5_COMMIT:
   221    221         assert( p->ts.eState==2 );
   222    222         p->ts.eState = 0;
   223    223         break;
   224    224   
   225    225       case FTS5_ROLLBACK:
   226         -      assert( p->ts.eState==1 || p->ts.eState==2 );
          226  +      assert( p->ts.eState==1 || p->ts.eState==2 || p->ts.eState==0 );
   227    227         p->ts.eState = 0;
   228    228         break;
   229    229   
   230    230       case FTS5_SAVEPOINT:
   231    231         assert( p->ts.eState==1 );
   232    232         assert( iSavepoint>=0 );
   233    233         assert( iSavepoint>p->ts.iSavepoint );

Changes to ext/fts5/fts5_config.c.

    70     70   static int fts5ConfigParseSpecial(
    71     71     Fts5Config *pConfig,            /* Configuration object to update */
    72     72     char *zCmd,                     /* Special command to parse */
    73     73     char *zArg,                     /* Argument to parse */
    74     74     char **pzErr                    /* OUT: Error message */
    75     75   ){
    76     76     if( sqlite3_stricmp(zCmd, "prefix")==0 ){
           77  +    const int nByte = sizeof(int) * FTS5_MAX_PREFIX_INDEXES;
           78  +    int rc = SQLITE_OK;
    77     79       char *p;
    78     80       if( pConfig->aPrefix ){
    79     81         *pzErr = sqlite3_mprintf("multiple prefix=... directives");
    80         -      return SQLITE_ERROR;
           82  +      rc = SQLITE_ERROR;
           83  +    }else{
           84  +      pConfig->aPrefix = sqlite3Fts5MallocZero(&rc, nByte);
    81     85       }
    82         -    pConfig->aPrefix = sqlite3_malloc(sizeof(int) * FTS5_MAX_PREFIX_INDEXES);
    83     86       p = zArg;
    84         -    while( p[0] ){
           87  +    while( rc==SQLITE_OK && p[0] ){
    85     88         int nPre = 0;
    86     89         while( p[0]==' ' ) p++;
    87     90         while( p[0]>='0' && p[0]<='9' && nPre<1000 ){
    88     91           nPre = nPre*10 + (p[0] - '0');
    89     92           p++;
    90     93         }
    91     94         while( p[0]==' ' ) p++;
    92     95         if( p[0]==',' ){
    93     96           p++;
    94     97         }else if( p[0] ){
    95     98           *pzErr = sqlite3_mprintf("malformed prefix=... directive");
    96         -        return SQLITE_ERROR;
           99  +        rc = SQLITE_ERROR;
    97    100         }
    98         -      if( nPre==0 || nPre>=1000 ){
          101  +      if( rc==SQLITE_OK && (nPre==0 || nPre>=1000) ){
    99    102           *pzErr = sqlite3_mprintf("prefix length out of range: %d", nPre);
   100         -        return SQLITE_ERROR;
          103  +        rc = SQLITE_ERROR;
   101    104         }
   102    105         pConfig->aPrefix[pConfig->nPrefix] = nPre;
   103    106         pConfig->nPrefix++;
   104    107       }
   105         -    return SQLITE_OK;
          108  +    return rc;
   106    109     }
   107    110   
   108    111     *pzErr = sqlite3_mprintf("unrecognized directive: \"%s\"", zCmd);
   109    112     return SQLITE_ERROR;
   110    113   }
   111    114   
   112    115   /*
................................................................................
   187    190                 rc = fts5ConfigParseSpecial(pRet, zDup, zArg, pzErr);
   188    191                 sqlite3_free(zDup);
   189    192                 zDup = 0;
   190    193               }
   191    194             }
   192    195   
   193    196             /* If it is not a special directive, it must be a column name. In
   194         -           ** this case, check that it is not the reserved column name "rank". */
          197  +          ** this case, check that it is not the reserved column name "rank". */
   195    198             if( zDup ){
   196    199               sqlite3Fts5Dequote(zDup);
   197    200               pRet->azCol[pRet->nCol++] = zDup;
   198    201               if( sqlite3_stricmp(zDup, FTS5_RANK_NAME)==0 ){
   199    202                 *pzErr = sqlite3_mprintf("reserved fts5 column name: %s", zDup);
   200    203                 rc = SQLITE_ERROR;
   201    204               }

Changes to ext/fts5/fts5_expr.c.

   209    209     sqlite3Fts5ParserFree(pEngine, fts5ParseFree);
   210    210   
   211    211     assert( sParse.pExpr==0 || (sParse.rc==SQLITE_OK && sParse.zErr==0) );
   212    212     if( sParse.rc==SQLITE_OK ){
   213    213       *ppNew = pNew = sqlite3_malloc(sizeof(Fts5Expr));
   214    214       if( pNew==0 ){
   215    215         sParse.rc = SQLITE_NOMEM;
          216  +      sqlite3Fts5ParseNodeFree(sParse.pExpr);
   216    217       }else{
   217    218         pNew->pRoot = sParse.pExpr;
   218    219         pNew->pIndex = 0;
   219    220         pNew->apExprPhrase = sParse.apPhrase;
   220    221         pNew->nPhrase = sParse.nPhrase;
   221    222         sParse.apPhrase = 0;
   222    223       }
................................................................................
   767    768         pTerm = &pPhrase->aTerm[j];
   768    769         rc = sqlite3Fts5IndexQuery(
   769    770             pExpr->pIndex, pTerm->zTerm, strlen(pTerm->zTerm),
   770    771             (pTerm->bPrefix ? FTS5INDEX_QUERY_PREFIX : 0) |
   771    772             (pExpr->bAsc ? FTS5INDEX_QUERY_ASC : 0),
   772    773             &pTerm->pIter
   773    774         );
   774         -      if( pTerm->pIter && sqlite3Fts5IterEof(pTerm->pIter) ){
          775  +      assert( rc==SQLITE_OK || pTerm->pIter==0 );
          776  +      if( pTerm->pIter==0 || sqlite3Fts5IterEof(pTerm->pIter) ){
   775    777           pNode->bEof = 1;
   776    778           break;
   777    779         }
   778    780       }
   779    781     }
   780    782   
   781    783     return rc;
................................................................................
  1200   1202     int bPrefix                     /* True if there is a trailing "*" */
  1201   1203   ){
  1202   1204     Fts5Config *pConfig = pParse->pConfig;
  1203   1205     TokenCtx sCtx;                  /* Context object passed to callback */
  1204   1206     int rc;                         /* Tokenize return code */
  1205   1207     char *z = 0;
  1206   1208   
         1209  +  memset(&sCtx, 0, sizeof(TokenCtx));
         1210  +  sCtx.pPhrase = pPhrase;
         1211  +
  1207   1212     if( pPhrase==0 ){
  1208   1213       if( (pParse->nPhrase % 8)==0 ){
  1209   1214         int nByte = sizeof(Fts5ExprPhrase*) * (pParse->nPhrase + 8);
  1210   1215         Fts5ExprPhrase **apNew;
  1211   1216         apNew = (Fts5ExprPhrase**)sqlite3_realloc(pParse->apPhrase, nByte);
  1212         -      if( apNew==0 ) return 0;
         1217  +      if( apNew==0 ){
         1218  +        pParse->rc = SQLITE_NOMEM;
         1219  +        fts5ExprPhraseFree(pPhrase);
         1220  +        return 0;
         1221  +      }
  1213   1222         pParse->apPhrase = apNew;
  1214   1223       }
  1215   1224       pParse->nPhrase++;
  1216   1225     }
  1217   1226   
  1218         -  pParse->rc = fts5ParseStringFromToken(pToken, &z);
  1219         -  if( z==0 ) return 0;
  1220         -  sqlite3Fts5Dequote(z);
  1221         -
  1222         -  memset(&sCtx, 0, sizeof(TokenCtx));
  1223         -  sCtx.pPhrase = pPhrase;
  1224         -  rc = sqlite3Fts5Tokenize(pConfig, z, strlen(z), &sCtx, fts5ParseTokenize);
         1227  +  rc = fts5ParseStringFromToken(pToken, &z);
         1228  +  if( rc==SQLITE_OK ){
         1229  +    sqlite3Fts5Dequote(z);
         1230  +    rc = sqlite3Fts5Tokenize(pConfig, z, strlen(z), &sCtx, fts5ParseTokenize);
         1231  +  }
  1225   1232     if( rc ){
  1226   1233       pParse->rc = rc;
  1227   1234       fts5ExprPhraseFree(sCtx.pPhrase);
  1228   1235       sCtx.pPhrase = 0;
  1229   1236     }else if( sCtx.pPhrase->nTerm>0 ){
  1230   1237       sCtx.pPhrase->aTerm[sCtx.pPhrase->nTerm-1].bPrefix = bPrefix;
  1231   1238     }

Changes to ext/fts5/fts5_index.c.

   598    598   /*
   599    599   ** Allocate and return a buffer at least nByte bytes in size.
   600    600   **
   601    601   ** If an OOM error is encountered, return NULL and set the error code in
   602    602   ** the Fts5Index handle passed as the first argument.
   603    603   */
   604    604   static void *fts5IdxMalloc(Fts5Index *p, int nByte){
   605         -  void *pRet;
   606         -  assert( p->rc==SQLITE_OK );
   607         -  pRet = sqlite3_malloc(nByte);
   608         -  if( pRet==0 ){
   609         -    p->rc = SQLITE_NOMEM;
   610         -  }else{
   611         -    memset(pRet, 0, nByte);
          605  +  void *pRet = 0;
          606  +  if( p->rc==SQLITE_OK ){
          607  +    pRet = sqlite3_malloc(nByte);
          608  +    if( pRet==0 ){
          609  +      p->rc = SQLITE_NOMEM;
          610  +    }else{
          611  +      memset(pRet, 0, nByte);
          612  +    }
   612    613     }
   613    614     return pRet;
   614    615   }
   615    616   
   616    617   /*
   617    618   ** Compare the contents of the pLeft buffer with the pRight/nRight blob.
   618    619   **
................................................................................
   658    659   
   659    660   
   660    661   /*
   661    662   ** Close the read-only blob handle, if it is open.
   662    663   */
   663    664   static void fts5CloseReader(Fts5Index *p){
   664    665     if( p->pReader ){
   665         -    sqlite3_blob_close(p->pReader);
          666  +    sqlite3_blob *pReader = p->pReader;
   666    667       p->pReader = 0;
          668  +    sqlite3_blob_close(pReader);
   667    669     }
   668    670   }
   669    671   
   670    672   static Fts5Data *fts5DataReadOrBuffer(
   671    673     Fts5Index *p, 
   672    674     Fts5Buffer *pBuf, 
   673    675     i64 iRowid
................................................................................
   703    705         );
   704    706       }
   705    707   
   706    708       if( rc==SQLITE_OK ){
   707    709         int nByte = sqlite3_blob_bytes(p->pReader);
   708    710         if( pBuf ){
   709    711           fts5BufferZero(pBuf);
   710         -        fts5BufferGrow(&rc, pBuf, nByte);
   711         -        rc = sqlite3_blob_read(p->pReader, pBuf->p, nByte, 0);
   712         -        if( rc==SQLITE_OK ) pBuf->n = nByte;
          712  +        if( SQLITE_OK==fts5BufferGrow(&rc, pBuf, nByte) ){
          713  +          rc = sqlite3_blob_read(p->pReader, pBuf->p, nByte, 0);
          714  +          if( rc==SQLITE_OK ) pBuf->n = nByte;
          715  +        }
   713    716         }else{
   714    717           pRet = (Fts5Data*)fts5IdxMalloc(p, sizeof(Fts5Data) + nByte);
   715    718           if( !pRet ) return 0;
   716    719   
   717    720           pRet->n = nByte;
   718    721           pRet->p = (u8*)&pRet[1];
   719    722           pRet->nRef = 1;
................................................................................
   849    852   ** Remove all records associated with segment iSegid in index iIdx.
   850    853   */
   851    854   static void fts5DataRemoveSegment(Fts5Index *p, int iIdx, int iSegid){
   852    855     i64 iFirst = FTS5_SEGMENT_ROWID(iIdx, iSegid, 0, 0);
   853    856     i64 iLast = FTS5_SEGMENT_ROWID(iIdx, iSegid+1, 0, 0)-1;
   854    857     fts5DataDelete(p, iFirst, iLast);
   855    858   }
          859  +
          860  +/*
          861  +** Release a reference to an Fts5Structure object returned by an earlier 
          862  +** call to fts5StructureRead() or fts5StructureDecode().
          863  +*/
          864  +static void fts5StructureRelease(Fts5Structure *pStruct){
          865  +  if( pStruct ){
          866  +    int i;
          867  +    for(i=0; i<pStruct->nLevel; i++){
          868  +      sqlite3_free(pStruct->aLevel[i].aSeg);
          869  +    }
          870  +    sqlite3_free(pStruct);
          871  +  }
          872  +}
   856    873   
   857    874   /*
   858    875   ** Deserialize and return the structure record currently stored in serialized
   859    876   ** form within buffer pData/nData.
   860    877   **
   861    878   ** The Fts5Structure.aLevel[] and each Fts5StructureLevel.aSeg[] array
   862    879   ** are over-allocated by one slot. This allows the structure contents
................................................................................
   914    931           pLvl->nSeg = nTotal;
   915    932           for(iSeg=0; iSeg<nTotal; iSeg++){
   916    933             i += getVarint32(&pData[i], pLvl->aSeg[iSeg].iSegid);
   917    934             i += getVarint32(&pData[i], pLvl->aSeg[iSeg].nHeight);
   918    935             i += getVarint32(&pData[i], pLvl->aSeg[iSeg].pgnoFirst);
   919    936             i += getVarint32(&pData[i], pLvl->aSeg[iSeg].pgnoLast);
   920    937           }
          938  +      }else{
          939  +        fts5StructureRelease(pRet);
          940  +        pRet = 0;
   921    941         }
   922    942       }
   923    943     }
   924    944   
   925    945     *ppOut = pRet;
   926    946     return rc;
   927    947   }
................................................................................
  1005   1025     p->rc = fts5StructureDecode(pData->p, pData->n, &iCookie, &pRet);
  1006   1026   
  1007   1027     if( p->rc==SQLITE_OK && p->pConfig->iCookie!=iCookie ){
  1008   1028       p->rc = sqlite3Fts5ConfigLoad(p->pConfig, iCookie);
  1009   1029     }
  1010   1030   
  1011   1031     fts5DataRelease(pData);
         1032  +  if( p->rc!=SQLITE_OK ){
         1033  +    fts5StructureRelease(pRet);
         1034  +    pRet = 0;
         1035  +  }
  1012   1036     return pRet;
  1013   1037   }
  1014   1038   
  1015         -/*
  1016         -** Release a reference to an Fts5Structure object returned by an earlier 
  1017         -** call to fts5StructureRead() or fts5StructureDecode().
  1018         -*/
  1019         -static void fts5StructureRelease(Fts5Structure *pStruct){
  1020         -  int i;
  1021         -  for(i=0; i<pStruct->nLevel; i++){
  1022         -    sqlite3_free(pStruct->aLevel[i].aSeg);
  1023         -  }
  1024         -  sqlite3_free(pStruct);
  1025         -}
  1026         -
  1027   1039   /*
  1028   1040   ** Return the total number of segments in index structure pStruct.
  1029   1041   */
  1030   1042   static int fts5StructureCountSegments(Fts5Structure *pStruct){
  1031   1043     int nSegment = 0;               /* Total number of segments */
  1032   1044     int iLvl;                       /* Used to iterate through levels */
  1033   1045   
................................................................................
  1041   1053   /*
  1042   1054   ** Serialize and store the "structure" record for index iIdx.
  1043   1055   **
  1044   1056   ** If an error occurs, leave an error code in the Fts5Index object. If an
  1045   1057   ** error has already occurred, this function is a no-op.
  1046   1058   */
  1047   1059   static void fts5StructureWrite(Fts5Index *p, int iIdx, Fts5Structure *pStruct){
  1048         -  int nSegment;                   /* Total number of segments */
  1049         -  Fts5Buffer buf;                 /* Buffer to serialize record into */
  1050         -  int iLvl;                       /* Used to iterate through levels */
  1051         -  int iCookie;                    /* Cookie value to store */
  1052         -
  1053         -  nSegment = fts5StructureCountSegments(pStruct);
  1054         -  memset(&buf, 0, sizeof(Fts5Buffer));
  1055         -
  1056         -  /* Append the current configuration cookie */
  1057         -  iCookie = p->pConfig->iCookie;
  1058         -  if( iCookie<0 ) iCookie = 0;
  1059         -  fts5BufferAppend32(&p->rc, &buf, iCookie);
  1060         -
  1061         -  fts5BufferAppendVarint(&p->rc, &buf, pStruct->nLevel);
  1062         -  fts5BufferAppendVarint(&p->rc, &buf, nSegment);
  1063         -  fts5BufferAppendVarint(&p->rc, &buf, (i64)pStruct->nWriteCounter);
  1064         -
  1065         -  for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
  1066         -    int iSeg;                     /* Used to iterate through segments */
  1067         -    Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl];
  1068         -    fts5BufferAppendVarint(&p->rc, &buf, pLvl->nMerge);
  1069         -    fts5BufferAppendVarint(&p->rc, &buf, pLvl->nSeg);
  1070         -    assert( pLvl->nMerge<=pLvl->nSeg );
  1071         -
  1072         -    for(iSeg=0; iSeg<pLvl->nSeg; iSeg++){
  1073         -      fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].iSegid);
  1074         -      fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].nHeight);
  1075         -      fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].pgnoFirst);
  1076         -      fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].pgnoLast);
  1077         -    }
  1078         -  }
  1079         -
  1080         -  fts5DataWrite(p, FTS5_STRUCTURE_ROWID(iIdx), buf.p, buf.n);
  1081         -  fts5BufferFree(&buf);
         1060  +  if( p->rc==SQLITE_OK ){
         1061  +    int nSegment;                 /* Total number of segments */
         1062  +    Fts5Buffer buf;               /* Buffer to serialize record into */
         1063  +    int iLvl;                     /* Used to iterate through levels */
         1064  +    int iCookie;                  /* Cookie value to store */
         1065  +
         1066  +    nSegment = fts5StructureCountSegments(pStruct);
         1067  +    memset(&buf, 0, sizeof(Fts5Buffer));
         1068  +
         1069  +    /* Append the current configuration cookie */
         1070  +    iCookie = p->pConfig->iCookie;
         1071  +    if( iCookie<0 ) iCookie = 0;
         1072  +    fts5BufferAppend32(&p->rc, &buf, iCookie);
         1073  +
         1074  +    fts5BufferAppendVarint(&p->rc, &buf, pStruct->nLevel);
         1075  +    fts5BufferAppendVarint(&p->rc, &buf, nSegment);
         1076  +    fts5BufferAppendVarint(&p->rc, &buf, (i64)pStruct->nWriteCounter);
         1077  +
         1078  +    for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
         1079  +      int iSeg;                     /* Used to iterate through segments */
         1080  +      Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl];
         1081  +      fts5BufferAppendVarint(&p->rc, &buf, pLvl->nMerge);
         1082  +      fts5BufferAppendVarint(&p->rc, &buf, pLvl->nSeg);
         1083  +      assert( pLvl->nMerge<=pLvl->nSeg );
         1084  +
         1085  +      for(iSeg=0; iSeg<pLvl->nSeg; iSeg++){
         1086  +        fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].iSegid);
         1087  +        fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].nHeight);
         1088  +        fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].pgnoFirst);
         1089  +        fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].pgnoLast);
         1090  +      }
         1091  +    }
         1092  +
         1093  +    fts5DataWrite(p, FTS5_STRUCTURE_ROWID(iIdx), buf.p, buf.n);
         1094  +    fts5BufferFree(&buf);
         1095  +  }
  1082   1096   }
  1083   1097   
  1084   1098   #if 0
  1085   1099   static void fts5PrintStructure(const char *zCaption, Fts5Structure *pStruct){
  1086   1100     int rc = SQLITE_OK;
  1087   1101     Fts5Buffer buf;
  1088   1102     memset(&buf, 0, sizeof(buf));
................................................................................
  1860   1874       int res;
  1861   1875       pIter->iLeafOffset = fts5GetU16(&pIter->pLeaf->p[2]);
  1862   1876       fts5SegIterLoadTerm(p, pIter, 0);
  1863   1877       do {
  1864   1878         res = fts5BufferCompareBlob(&pIter->term, pTerm, nTerm);
  1865   1879         if( res>=0 ) break;
  1866   1880         fts5SegIterNext(p, pIter);
  1867         -    }while( pIter->pLeaf );
         1881  +    }while( pIter->pLeaf && p->rc==SQLITE_OK );
  1868   1882   
  1869   1883       if( bGe==0 && res ){
  1870   1884         /* Set iterator to point to EOF */
  1871   1885         fts5DataRelease(pIter->pLeaf);
  1872   1886         pIter->pLeaf = 0;
  1873   1887       }
  1874   1888     }
  1875   1889   
  1876         -  if( bGe==0 ){
         1890  +  if( p->rc==SQLITE_OK && bGe==0 ){
  1877   1891       pIter->flags |= FTS5_SEGITER_ONETERM;
  1878   1892       if( pIter->pLeaf ){
  1879   1893         if( flags & FTS5INDEX_QUERY_ASC ){
  1880   1894           pIter->flags |= FTS5_SEGITER_REVERSE;
  1881   1895         }
  1882   1896         if( bDlidx ){
  1883   1897           fts5SegIterLoadDlidx(p, iIdx, pIter);
................................................................................
  2418   2432   */
  2419   2433   static void fts5IndexDiscardData(Fts5Index *p){
  2420   2434     assert( p->apHash || p->nPendingData==0 );
  2421   2435     if( p->apHash ){
  2422   2436       Fts5Config *pConfig = p->pConfig;
  2423   2437       int i;
  2424   2438       for(i=0; i<=pConfig->nPrefix; i++){
  2425         -      sqlite3Fts5HashClear(p->apHash[i]);
         2439  +      if( p->apHash[i] ) sqlite3Fts5HashClear(p->apHash[i]);
  2426   2440       }
  2427   2441       p->nPendingData = 0;
  2428   2442     }
  2429   2443   }
  2430   2444   
  2431   2445   /*
  2432   2446   ** Return the size of the prefix, in bytes, that buffer (nNew/pNew) shares
................................................................................
  2605   2619     Fts5Index *p, 
  2606   2620     Fts5SegWriter *pWriter,
  2607   2621     int nTerm, const u8 *pTerm 
  2608   2622   ){
  2609   2623     int nPrefix;                    /* Bytes of prefix compression for term */
  2610   2624     Fts5PageWriter *pPage = &pWriter->aWriter[0];
  2611   2625   
  2612         -  assert( pPage->buf.n==0 || pPage->buf.n>4 );
  2613         -  if( pPage->buf.n==0 ){
         2626  +  assert( pPage==0 || pPage->buf.n==0 || pPage->buf.n>4 );
         2627  +  if( pPage && pPage->buf.n==0 ){
  2614   2628       /* Zero the first term and first docid fields */
  2615   2629       static const u8 zero[] = { 0x00, 0x00, 0x00, 0x00 };
  2616   2630       fts5BufferAppendBlob(&p->rc, &pPage->buf, 4, zero);
  2617   2631       assert( pPage->term.n==0 );
  2618   2632     }
  2619   2633     if( p->rc ) return;
  2620   2634     
................................................................................
  2656   2670   ** Append a docid to the writers output. 
  2657   2671   */
  2658   2672   static void fts5WriteAppendRowid(
  2659   2673     Fts5Index *p, 
  2660   2674     Fts5SegWriter *pWriter,
  2661   2675     i64 iRowid
  2662   2676   ){
  2663         -  Fts5PageWriter *pPage = &pWriter->aWriter[0];
  2664         -
  2665         -  /* If this is to be the first docid written to the page, set the 
  2666         -  ** docid-pointer in the page-header. Also append a value to the dlidx
  2667         -  ** buffer, in case a doclist-index is required.  */
  2668         -  if( pWriter->bFirstRowidInPage ){
  2669         -    fts5PutU16(pPage->buf.p, pPage->buf.n);
  2670         -    fts5WriteDlidxAppend(p, pWriter, iRowid);
  2671         -  }
  2672         -
  2673         -  /* Write the docid. */
  2674         -  if( pWriter->bFirstRowidInDoclist || pWriter->bFirstRowidInPage ){
  2675         -    fts5BufferAppendVarint(&p->rc, &pPage->buf, iRowid);
  2676         -  }else{
  2677         -    assert( iRowid<pWriter->iPrevRowid );
  2678         -    fts5BufferAppendVarint(&p->rc, &pPage->buf, pWriter->iPrevRowid - iRowid);
  2679         -  }
  2680         -  pWriter->iPrevRowid = iRowid;
  2681         -  pWriter->bFirstRowidInDoclist = 0;
  2682         -  pWriter->bFirstRowidInPage = 0;
  2683         -
  2684         -  if( pPage->buf.n>=p->pConfig->pgsz ){
  2685         -    fts5WriteFlushLeaf(p, pWriter);
  2686         -    pWriter->bFirstRowidInPage = 1;
         2677  +  if( p->rc==SQLITE_OK ){
         2678  +    Fts5PageWriter *pPage = &pWriter->aWriter[0];
         2679  +
         2680  +    /* If this is to be the first docid written to the page, set the 
         2681  +    ** docid-pointer in the page-header. Also append a value to the dlidx
         2682  +    ** buffer, in case a doclist-index is required.  */
         2683  +    if( pWriter->bFirstRowidInPage ){
         2684  +      fts5PutU16(pPage->buf.p, pPage->buf.n);
         2685  +      fts5WriteDlidxAppend(p, pWriter, iRowid);
         2686  +    }
         2687  +
         2688  +    /* Write the docid. */
         2689  +    if( pWriter->bFirstRowidInDoclist || pWriter->bFirstRowidInPage ){
         2690  +      fts5BufferAppendVarint(&p->rc, &pPage->buf, iRowid);
         2691  +    }else{
         2692  +      assert( p->rc || iRowid<pWriter->iPrevRowid );
         2693  +      fts5BufferAppendVarint(&p->rc, &pPage->buf, pWriter->iPrevRowid - iRowid);
         2694  +    }
         2695  +    pWriter->iPrevRowid = iRowid;
         2696  +    pWriter->bFirstRowidInDoclist = 0;
         2697  +    pWriter->bFirstRowidInPage = 0;
         2698  +
         2699  +    if( pPage->buf.n>=p->pConfig->pgsz ){
         2700  +      fts5WriteFlushLeaf(p, pWriter);
         2701  +      pWriter->bFirstRowidInPage = 1;
         2702  +    }
  2687   2703     }
  2688   2704   }
  2689   2705   
  2690   2706   static void fts5WriteAppendPoslistInt(
  2691   2707     Fts5Index *p, 
  2692   2708     Fts5SegWriter *pWriter,
  2693   2709     int iVal
  2694   2710   ){
  2695         -  Fts5PageWriter *pPage = &pWriter->aWriter[0];
  2696         -  fts5BufferAppendVarint(&p->rc, &pPage->buf, iVal);
  2697         -  if( pPage->buf.n>=p->pConfig->pgsz ){
  2698         -    fts5WriteFlushLeaf(p, pWriter);
  2699         -    pWriter->bFirstRowidInPage = 1;
         2711  +  if( p->rc==SQLITE_OK ){
         2712  +    Fts5PageWriter *pPage = &pWriter->aWriter[0];
         2713  +    fts5BufferAppendVarint(&p->rc, &pPage->buf, iVal);
         2714  +    if( pPage->buf.n>=p->pConfig->pgsz ){
         2715  +      fts5WriteFlushLeaf(p, pWriter);
         2716  +      pWriter->bFirstRowidInPage = 1;
         2717  +    }
  2700   2718     }
  2701   2719   }
  2702   2720   
  2703   2721   static void fts5WriteAppendPoslistData(
  2704   2722     Fts5Index *p, 
  2705   2723     Fts5SegWriter *pWriter, 
  2706   2724     const u8 *aData, 
................................................................................
  2740   2758   static void fts5WriteFinish(
  2741   2759     Fts5Index *p, 
  2742   2760     Fts5SegWriter *pWriter,         /* Writer object */
  2743   2761     int *pnHeight,                  /* OUT: Height of the b-tree */
  2744   2762     int *pnLeaf                     /* OUT: Number of leaf pages in b-tree */
  2745   2763   ){
  2746   2764     int i;
  2747         -  *pnLeaf = pWriter->aWriter[0].pgno;
  2748         -  if( *pnLeaf==1 && pWriter->aWriter[0].buf.n==0 ){
  2749         -    *pnLeaf = 0;
  2750         -    *pnHeight = 0;
  2751         -  }else{
  2752         -    fts5WriteFlushLeaf(p, pWriter);
  2753         -    if( pWriter->nWriter==1 && pWriter->nEmpty>=FTS5_MIN_DLIDX_SIZE ){
  2754         -      fts5WriteBtreeGrow(p, pWriter);
  2755         -    }
  2756         -    if( pWriter->nWriter>1 ){
  2757         -      fts5WriteBtreeNEmpty(p, pWriter);
  2758         -    }
  2759         -    *pnHeight = pWriter->nWriter;
  2760         -
  2761         -    for(i=1; i<pWriter->nWriter; i++){
  2762         -      Fts5PageWriter *pPg = &pWriter->aWriter[i];
  2763         -      fts5DataWrite(p, 
  2764         -          FTS5_SEGMENT_ROWID(pWriter->iIdx, pWriter->iSegid, i, pPg->pgno), 
  2765         -          pPg->buf.p, pPg->buf.n
  2766         -      );
         2765  +  if( p->rc==SQLITE_OK ){
         2766  +    *pnLeaf = pWriter->aWriter[0].pgno;
         2767  +    if( *pnLeaf==1 && pWriter->aWriter[0].buf.n==0 ){
         2768  +      *pnLeaf = 0;
         2769  +      *pnHeight = 0;
         2770  +    }else{
         2771  +      fts5WriteFlushLeaf(p, pWriter);
         2772  +      if( pWriter->nWriter==1 && pWriter->nEmpty>=FTS5_MIN_DLIDX_SIZE ){
         2773  +        fts5WriteBtreeGrow(p, pWriter);
         2774  +      }
         2775  +      if( pWriter->nWriter>1 ){
         2776  +        fts5WriteBtreeNEmpty(p, pWriter);
         2777  +      }
         2778  +      *pnHeight = pWriter->nWriter;
         2779  +
         2780  +      for(i=1; i<pWriter->nWriter; i++){
         2781  +        Fts5PageWriter *pPg = &pWriter->aWriter[i];
         2782  +        fts5DataWrite(p, 
         2783  +            FTS5_SEGMENT_ROWID(pWriter->iIdx, pWriter->iSegid, i, pPg->pgno), 
         2784  +            pPg->buf.p, pPg->buf.n
         2785  +        );
         2786  +      }
  2767   2787       }
  2768   2788     }
  2769   2789     for(i=0; i<pWriter->nWriter; i++){
  2770   2790       Fts5PageWriter *pPg = &pWriter->aWriter[i];
  2771         -    fts5BufferFree(&pPg->term);
  2772         -    fts5BufferFree(&pPg->buf);
         2791  +    assert( pPg || p->rc!=SQLITE_OK );
         2792  +    if( pPg ){
         2793  +      fts5BufferFree(&pPg->term);
         2794  +      fts5BufferFree(&pPg->buf);
         2795  +    }
  2773   2796     }
  2774   2797     sqlite3_free(pWriter->aWriter);
  2775   2798     sqlite3Fts5BufferFree(&pWriter->dlidx);
  2776   2799   }
  2777   2800   
  2778   2801   static void fts5WriteInit(
  2779   2802     Fts5Index *p, 
................................................................................
  3021   3044   */
  3022   3045   static void fts5IndexWork(
  3023   3046     Fts5Index *p,                   /* FTS5 backend object */
  3024   3047     int iIdx,                       /* Index to work on */
  3025   3048     Fts5Structure **ppStruct,       /* IN/OUT: Current structure of index */
  3026   3049     int nLeaf                       /* Number of output leaves just written */
  3027   3050   ){
  3028         -  Fts5Structure *pStruct = *ppStruct;
  3029         -  i64 nWrite;                     /* Initial value of write-counter */
  3030         -  int nWork;                      /* Number of work-quanta to perform */
  3031         -  int nRem;                       /* Number of leaf pages left to write */
  3032         -
  3033         -  /* Update the write-counter. While doing so, set nWork. */
  3034         -  nWrite = pStruct->nWriteCounter;
  3035         -  nWork = ((nWrite + nLeaf) / p->nWorkUnit) - (nWrite / p->nWorkUnit);
  3036         -  pStruct->nWriteCounter += nLeaf;
  3037         -  nRem = p->nWorkUnit * nWork * pStruct->nLevel;
  3038         -
  3039         -  while( nRem>0 ){
  3040         -    int iLvl;                   /* To iterate through levels */
  3041         -    int iBestLvl = 0;           /* Level offering the most input segments */
  3042         -    int nBest = 0;              /* Number of input segments on best level */
  3043         -
  3044         -    /* Set iBestLvl to the level to read input segments from. */
  3045         -    assert( pStruct->nLevel>0 );
  3046         -    for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
  3047         -      Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl];
  3048         -      if( pLvl->nMerge ){
  3049         -        if( pLvl->nMerge>nBest ){
         3051  +  if( p->rc==SQLITE_OK ){
         3052  +    Fts5Structure *pStruct = *ppStruct;
         3053  +    i64 nWrite;                   /* Initial value of write-counter */
         3054  +    int nWork;                    /* Number of work-quanta to perform */
         3055  +    int nRem;                     /* Number of leaf pages left to write */
         3056  +
         3057  +    /* Update the write-counter. While doing so, set nWork. */
         3058  +    nWrite = pStruct->nWriteCounter;
         3059  +    nWork = ((nWrite + nLeaf) / p->nWorkUnit) - (nWrite / p->nWorkUnit);
         3060  +    pStruct->nWriteCounter += nLeaf;
         3061  +    nRem = p->nWorkUnit * nWork * pStruct->nLevel;
         3062  +
         3063  +    while( nRem>0 ){
         3064  +      int iLvl;                   /* To iterate through levels */
         3065  +      int iBestLvl = 0;           /* Level offering the most input segments */
         3066  +      int nBest = 0;              /* Number of input segments on best level */
         3067  +
         3068  +      /* Set iBestLvl to the level to read input segments from. */
         3069  +      assert( pStruct->nLevel>0 );
         3070  +      for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
         3071  +        Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl];
         3072  +        if( pLvl->nMerge ){
         3073  +          if( pLvl->nMerge>nBest ){
         3074  +            iBestLvl = iLvl;
         3075  +            nBest = pLvl->nMerge;
         3076  +          }
         3077  +          break;
         3078  +        }
         3079  +        if( pLvl->nSeg>nBest ){
         3080  +          nBest = pLvl->nSeg;
  3050   3081             iBestLvl = iLvl;
  3051         -          nBest = pLvl->nMerge;
  3052   3082           }
  3053         -        break;
  3054   3083         }
  3055         -      if( pLvl->nSeg>nBest ){
  3056         -        nBest = pLvl->nSeg;
  3057         -        iBestLvl = iLvl;
  3058         -      }
  3059         -    }
  3060   3084   
  3061         -    /* If nBest is still 0, then the index must be empty. */
         3085  +      /* If nBest is still 0, then the index must be empty. */
  3062   3086   #ifdef SQLITE_DEBUG
  3063         -    for(iLvl=0; nBest==0 && iLvl<pStruct->nLevel; iLvl++){
  3064         -      assert( pStruct->aLevel[iLvl].nSeg==0 );
  3065         -    }
         3087  +      for(iLvl=0; nBest==0 && iLvl<pStruct->nLevel; iLvl++){
         3088  +        assert( pStruct->aLevel[iLvl].nSeg==0 );
         3089  +      }
  3066   3090   #endif
  3067   3091   
  3068         -    if( nBest<p->pConfig->nAutomerge 
  3069         -     && pStruct->aLevel[iBestLvl].nMerge==0 
  3070         -    ){
  3071         -      break;
         3092  +      if( nBest<p->pConfig->nAutomerge 
         3093  +          && pStruct->aLevel[iBestLvl].nMerge==0 
         3094  +        ){
         3095  +        break;
         3096  +      }
         3097  +      fts5IndexMergeLevel(p, iIdx, &pStruct, iBestLvl, &nRem);
         3098  +      fts5StructurePromote(p, iBestLvl+1, pStruct);
         3099  +      assert( nRem==0 || p->rc==SQLITE_OK );
         3100  +      *ppStruct = pStruct;
  3072   3101       }
  3073         -    fts5IndexMergeLevel(p, iIdx, &pStruct, iBestLvl, &nRem);
  3074         -    fts5StructurePromote(p, iBestLvl+1, pStruct);
  3075         -    assert( nRem==0 || p->rc==SQLITE_OK );
  3076         -    *ppStruct = pStruct;
  3077   3102     }
  3078   3103   }
  3079   3104   
  3080   3105   static void fts5IndexCrisisMerge(
  3081   3106     Fts5Index *p,                   /* FTS5 backend object */
  3082   3107     int iIdx,                       /* Index to work on */
  3083   3108     Fts5Structure **ppStruct        /* IN/OUT: Current structure of index */
................................................................................
  3119   3144   static int fts5FlushNewEntry(
  3120   3145     void *pCtx, 
  3121   3146     i64 iRowid, 
  3122   3147     const u8 *aPoslist, 
  3123   3148     int nPoslist
  3124   3149   ){
  3125   3150     Fts5FlushCtx *p = (Fts5FlushCtx*)pCtx;
  3126         -  int rc = SQLITE_OK;
         3151  +  Fts5Index *pIdx = p->pIdx;
  3127   3152   
  3128   3153     /* Append the rowid itself */
  3129         -  fts5WriteAppendRowid(p->pIdx, &p->writer, iRowid);
         3154  +  fts5WriteAppendRowid(pIdx, &p->writer, iRowid);
  3130   3155   
  3131   3156     /* Append the size of the position list in bytes */
  3132         -  fts5WriteAppendPoslistInt(p->pIdx, &p->writer, nPoslist);
         3157  +  fts5WriteAppendPoslistInt(pIdx, &p->writer, nPoslist);
  3133   3158   
  3134   3159     /* And the poslist data */
  3135         -  fts5WriteAppendPoslistData(p->pIdx, &p->writer, aPoslist, nPoslist);
  3136         -  return rc;
         3160  +  fts5WriteAppendPoslistData(pIdx, &p->writer, aPoslist, nPoslist);
         3161  +  return pIdx->rc;
  3137   3162   }
  3138   3163   
  3139   3164   /*
  3140   3165   ** Flush the contents of in-memory hash table iHash to a new level-0 
  3141   3166   ** segment on disk. Also update the corresponding structure record.
  3142   3167   **
  3143   3168   ** If an error occurs, set the Fts5Index.rc error code. If an error has 
................................................................................
  3481   3506   */
  3482   3507   static void fts5MultiIterPoslist(
  3483   3508     Fts5Index *p,
  3484   3509     Fts5MultiSegIter *pMulti,
  3485   3510     int bSz,
  3486   3511     Fts5Buffer *pBuf
  3487   3512   ){
  3488         -  Fts5ChunkIter iter;
  3489         -  Fts5SegIter *pSeg = &pMulti->aSeg[ pMulti->aFirst[1] ];
  3490         -  assert( fts5MultiIterEof(p, pMulti)==0 );
  3491         -  fts5ChunkIterInit(p, pSeg, &iter);
  3492         -  if( fts5ChunkIterEof(p, &iter)==0 ){
  3493         -    if( bSz ){
  3494         -      fts5BufferAppendVarint(&p->rc, pBuf, iter.nRem);
         3513  +  if( p->rc==SQLITE_OK ){
         3514  +    Fts5ChunkIter iter;
         3515  +    Fts5SegIter *pSeg = &pMulti->aSeg[ pMulti->aFirst[1] ];
         3516  +    assert( fts5MultiIterEof(p, pMulti)==0 );
         3517  +    fts5ChunkIterInit(p, pSeg, &iter);
         3518  +    if( fts5ChunkIterEof(p, &iter)==0 ){
         3519  +      if( bSz ){
         3520  +        fts5BufferAppendVarint(&p->rc, pBuf, iter.nRem);
         3521  +      }
         3522  +      while( fts5ChunkIterEof(p, &iter)==0 ){
         3523  +        fts5BufferAppendBlob(&p->rc, pBuf, iter.n, iter.p);
         3524  +        fts5ChunkIterNext(p, &iter);
         3525  +      }
  3495   3526       }
  3496         -    while( fts5ChunkIterEof(p, &iter)==0 ){
  3497         -      fts5BufferAppendBlob(&p->rc, pBuf, iter.n, iter.p);
  3498         -      fts5ChunkIterNext(p, &iter);
  3499         -    }
         3527  +    fts5ChunkIterRelease(&iter);
  3500   3528     }
  3501         -  fts5ChunkIterRelease(&iter);
  3502   3529   }
  3503   3530   
  3504   3531   static void fts5DoclistIterNext(Fts5DoclistIter *pIter){
  3505   3532     if( pIter->i<pIter->n ){
  3506   3533       if( pIter->i ){
  3507   3534         i64 iDelta;
  3508   3535         pIter->i += getVarint(&pIter->a[pIter->i], (u64*)&iDelta);

Changes to src/vtab.c.

   785    785   ** the offset of the method to call in the sqlite3_module structure.
   786    786   **
   787    787   ** The array is cleared after invoking the callbacks. 
   788    788   */
   789    789   static void callFinaliser(sqlite3 *db, int offset){
   790    790     int i;
   791    791     if( db->aVTrans ){
          792  +    VTable **aVTrans = db->aVTrans;
          793  +    db->aVTrans = 0;
   792    794       for(i=0; i<db->nVTrans; i++){
   793         -      VTable *pVTab = db->aVTrans[i];
          795  +      VTable *pVTab = aVTrans[i];
   794    796         sqlite3_vtab *p = pVTab->pVtab;
   795    797         if( p ){
   796    798           int (*x)(sqlite3_vtab *);
   797    799           x = *(int (**)(sqlite3_vtab *))((char *)p->pModule + offset);
   798    800           if( x ) x(p);
   799    801         }
   800    802         pVTab->iSavepoint = 0;
   801    803         sqlite3VtabUnlock(pVTab);
   802    804       }
   803         -    sqlite3DbFree(db, db->aVTrans);
          805  +    sqlite3DbFree(db, aVTrans);
   804    806       db->nVTrans = 0;
   805         -    db->aVTrans = 0;
   806    807     }
   807    808   }
   808    809   
   809    810   /*
   810    811   ** Invoke the xSync method of all virtual tables in the sqlite3.aVTrans
   811    812   ** array. Return the error code for the first error that occurs, or
   812    813   ** SQLITE_OK if all xSync operations are successful.

Changes to test/fts5fault1.test.

    18     18   set testprefix fts5fault1
    19     19   
    20     20   # If SQLITE_ENABLE_FTS3 is defined, omit this file.
    21     21   ifcapable !fts5 {
    22     22     finish_test
    23     23     return
    24     24   }
           25  +
           26  +# Simple tests:
           27  +#
           28  +#   1: CREATE VIRTUAL TABLE
           29  +#   2: INSERT statement
           30  +#   3: DELETE statement
           31  +#   4: MATCH expressions
           32  +#
           33  +
           34  +if 1 {
    25     35   
    26     36   faultsim_save_and_close
    27     37   do_faultsim_test 1 -prep {
    28     38     faultsim_restore_and_reopen
    29     39   } -body {
    30         -  execsql { CREATE VIRTUAL TABLE t1 USING fts5(a) }
           40  +  execsql { CREATE VIRTUAL TABLE t1 USING fts5(a, b, prefix='1, 2, 3') }
           41  +} -test {
           42  +  faultsim_test_result {0 {}} 
           43  +}
           44  +
           45  +reset_db
           46  +do_execsql_test 2.0 {
           47  +  CREATE VIRTUAL TABLE t1 USING fts5(a, b, prefix='1, 2, 3');
           48  +}
           49  +faultsim_save_and_close
           50  +do_faultsim_test 2 -prep {
           51  +  faultsim_restore_and_reopen
           52  +} -body {
           53  +  execsql { 
           54  +    INSERT INTO t1 VALUES('a b c', 'a bc def ghij klmno');
           55  +  }
           56  +} -test {
           57  +  faultsim_test_result {0 {}} 
           58  +}
           59  +
           60  +reset_db
           61  +do_execsql_test 3.0 {
           62  +  CREATE VIRTUAL TABLE t1 USING fts5(a, b, prefix='1, 2, 3');
           63  +  INSERT INTO t1 VALUES('a b c', 'a bc def ghij klmno');
           64  +}
           65  +faultsim_save_and_close
           66  +do_faultsim_test 3 -prep {
           67  +  faultsim_restore_and_reopen
           68  +} -body {
           69  +  execsql { DELETE FROM t1 }
    31     70   } -test {
    32     71     faultsim_test_result {0 {}} 
    33     72   }
    34     73   
           74  +}
           75  +
           76  +reset_db
           77  +do_execsql_test 4.0 {
           78  +  CREATE VIRTUAL TABLE t2 USING fts5(a, b);
           79  +  INSERT INTO t2 VALUES('m f a jj th q jr ar',   'hj n h h sg j i m');
           80  +  INSERT INTO t2 VALUES('nr s t g od j kf h',    'sb h aq rg op rb n nl');
           81  +  INSERT INTO t2 VALUES('do h h pb p p q fr',    'c rj qs or cr a l i');
           82  +  INSERT INTO t2 VALUES('lk gp t i lq mq qm p',  'h mr g f op ld aj h');
           83  +  INSERT INTO t2 VALUES('ct d sq kc qi k f j',   'sn gh c of g s qt q');
           84  +  INSERT INTO t2 VALUES('d ea d d om mp s ab',   'dm hg l df cm ft pa c');
           85  +  INSERT INTO t2 VALUES('tc dk c jn n t sr ge',  'a a kn bc n i af h');
           86  +  INSERT INTO t2 VALUES('ie ii d i b sa qo rf',  'a h m aq i b m fn');
           87  +  INSERT INTO t2 VALUES('gs r fo a er m h li',   'tm c p gl eb ml q r');
           88  +  INSERT INTO t2 VALUES('k fe fd rd a gi ho kk', 'ng m c r d ml rm r');
           89  +}
           90  +faultsim_save_and_close
    35     91   
           92  +foreach {tn expr res} {
           93  +  1 { dk  }           7
           94  +  2 { m f }           1
           95  +  3 { f*  }           {10 9 8 6 5 4 3 1}
           96  +  4 { m OR f }        {10 9 8 5 4 1}
           97  +  5 { sn + gh }       {5}
           98  +  6 { "sn gh" }       {5}
           99  +  7 { NEAR(r a, 5) }  {9}
          100  +} {
          101  +  do_faultsim_test 4.$tn -prep {
          102  +    faultsim_restore_and_reopen
          103  +  } -body "
          104  +    execsql { SELECT rowid FROM t2 WHERE t2 MATCH '$expr' }
          105  +  " -test "
          106  +    faultsim_test_result {[list 0 $res]}
          107  +  "
          108  +}
    36    109   
    37    110   finish_test
          111  +

Changes to test/malloc_common.tcl.

   125    125       if {$n != "interrupt"} {lappend DEFAULT(-faults) $n}
   126    126     }
   127    127     set DEFAULT(-prep)          ""
   128    128     set DEFAULT(-body)          ""
   129    129     set DEFAULT(-test)          ""
   130    130     set DEFAULT(-install)       ""
   131    131     set DEFAULT(-uninstall)     ""
          132  +  set DEFAULT(-start)          1
          133  +  set DEFAULT(-end)            0
   132    134   
   133    135     fix_testname name
   134    136   
   135    137     array set O [array get DEFAULT]
   136    138     array set O $args
   137    139     foreach o [array names O] {
   138    140       if {[info exists DEFAULT($o)]==0} { error "unknown option: $o" }
................................................................................
   142    144     foreach f $O(-faults) {
   143    145       set flist [array names FAULTSIM $f]
   144    146       if {[llength $flist]==0} { error "unknown fault: $f" }
   145    147       set faultlist [concat $faultlist $flist]
   146    148     }
   147    149   
   148    150     set testspec [list -prep $O(-prep) -body $O(-body) \
   149         -      -test $O(-test) -install $O(-install) -uninstall $O(-uninstall)
          151  +      -test $O(-test) -install $O(-install) -uninstall $O(-uninstall) \
          152  +      -start $O(-start) -end $O(-end)
   150    153     ]
   151    154     foreach f [lsort -unique $faultlist] {
   152    155       eval do_one_faultsim_test "$name-$f" $FAULTSIM($f) $testspec
   153    156     }
   154    157   }
   155    158   
   156    159   
................................................................................
   314    317   #
   315    318   #     -prep             Script to execute before -body.
   316    319   #
   317    320   #     -body             Script to execute (with fault injection).
   318    321   #
   319    322   #     -test             Script to execute after -body.
   320    323   #
          324  +#     -start            Index of first fault to inject (default 1)
          325  +#
   321    326   proc do_one_faultsim_test {testname args} {
   322    327   
   323    328     set DEFAULT(-injectstart)     "expr"
   324    329     set DEFAULT(-injectstop)      "expr 0"
   325    330     set DEFAULT(-injecterrlist)   [list]
   326    331     set DEFAULT(-injectinstall)   ""
   327    332     set DEFAULT(-injectuninstall) ""
   328    333     set DEFAULT(-prep)            ""
   329    334     set DEFAULT(-body)            ""
   330    335     set DEFAULT(-test)            ""
   331    336     set DEFAULT(-install)         ""
   332    337     set DEFAULT(-uninstall)       ""
          338  +  set DEFAULT(-start)           1
          339  +  set DEFAULT(-end)             0
   333    340   
   334    341     array set O [array get DEFAULT]
   335    342     array set O $args
   336    343     foreach o [array names O] {
   337    344       if {[info exists DEFAULT($o)]==0} { error "unknown option: $o" }
   338    345     }
   339    346   
................................................................................
   342    349       uplevel faultsim_test_result_int \$args [list $O(-injecterrlist)]
   343    350     "
   344    351   
   345    352     eval $O(-injectinstall)
   346    353     eval $O(-install)
   347    354   
   348    355     set stop 0
   349         -  for {set iFail 1} {!$stop} {incr iFail} {
          356  +  for {set iFail $O(-start)}                        \
          357  +      {!$stop && ($O(-end)==0 || $iFail<=$O(-end))} \
          358  +      {incr iFail}                                  \
          359  +  {
   350    360   
   351    361       # Evaluate the -prep script.
   352    362       #
   353    363       eval $O(-prep)
   354    364   
   355    365       # Start the fault-injection. Run the -body script. Stop the fault
   356    366       # injection. Local var $nfail is set to the total number of faults