• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

taosdata / TDengine / #4988

16 Mar 2026 12:26PM UTC coverage: 75.821% (+1.9%) from 73.883%
#4988

push

travis-ci

web-flow
feat: support secure delete option. (#34591)

274 of 464 new or added lines in 29 files covered. (59.05%)

4404 existing lines in 23 files now uncovered.

337108 of 444611 relevant lines covered (75.82%)

146708292.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

5.95
/source/dnode/vnode/src/tsdb/tsdbSecureErase.c
1
/*
2
 * Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
3
 *
4
 * This program is free software: you can use, redistribute, and/or modify
5
 * it under the terms of the GNU Affero General Public License, version 3
6
 * or later ("AGPL"), as published by the Free Software Foundation.
7
 *
8
 * This program is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU Affero General Public License, version 3 or later.
10
 */
11

12
/*
13
 * SECURE ERASE: Manual file-level overwrite of physically deleted sensitive data.
14
 *
15
 * DESIGN:
16
 *   After adding delete markers to the memtable, scan all on-disk TSDB files
17
 *   (both DATA files and STT files) and physically overwrite the blocks that
18
 *   contain data for the target (suid, uid) in [sKey, eKey].
19
 *
20
 * TWO STRATEGIES:
21
 *   1. Fully-contained blocks (block range ⊆ [sKey, eKey]):
22
 *      - Direct raw-bytes overwrite at block offset in the data file.
23
 *      - For zero mode: fill with zeros.
24
 *      - For random mode: fill with random bytes.
25
 *      - The page CRC is updated through tsdbWriteFile / tsdbFsyncFile.
26
 *      - Safe because delete markers guarantee TDengine will never read these rows.
27
 *
28
 *   2. Partially-overlapping blocks (block range ∩ [sKey, eKey] ≠ ∅ but not fully contained):
29
 *      - Must preserve non-deleted rows' data.
30
 *      - Decompress → zero VALUE bytes for target rows → recompress → write back.
31
 *      - Always use zero fill for decompressed values (random fill causes
32
 *        recompressed output to be larger than the original, making in-place
33
 *        write impossible without file reorganization).
34
 *
35
 * KNOWN PROBLEMS AND LIMITATIONS:
36
 *
37
 *   P1: SIZE CONSTRAINT WITH RANDOM FILL (recompress path only)
38
 *       After decompressing and zeroing VALUES, the recompressed output is
39
 *       guaranteed to be ≤ original because zeros are highly compressible.
40
 *       However, if random fill were applied, random data resists compression
41
 *       and the output could exceed the original block size, making in-place
42
 *       write impossible. Therefore, the recompress path always uses zero fill
43
 *       regardless of tsSecureEraseMode.
44
 *
45
 *   P2: CONCURRENT READERS
46
 *       While we overwrite a block, a concurrent query might be reading the
47
 *       same file page. The page CRC is updated atomically by tsdbFsyncFile,
48
 *       but there is a narrow window where a reader sees the old data before
49
 *       the new page is flushed. This is generally acceptable: the delete
50
 *       markers ensure the deleted rows won't be returned anyway; any read of
51
 *       the old data during the window is a transient race, not a security
52
 *       breach in practice.
53
 *
54
 *   P3: OS PAGE CACHE
55
 *       After fsync, the OS may still hold the old data in RAM (page cache).
56
 *       True physical erasure requires encryption-at-rest with key destruction,
57
 *       or hardware-level secure erase (ATA Secure Erase / NVMe Sanitize).
58
 *
59
 *   P4: WAL STILL CONTAINS ORIGINAL DATA
60
 *       The original write was journaled through WAL. Even after this overwrite,
61
 *       the WAL files may still contain the plaintext original data. WAL trimming
62
 *       (which happens after a checkpoint) will eventually remove old entries,
63
 *       but there is no immediate guarantee. For environments requiring immediate
64
 *       WAL erasure, WAL encryption or external WAL management is needed.
65
 *
66
 *   P5: MULTI-REPLICA CONSISTENCY
67
 *       This code runs only on the Raft leader. Follower replicas apply the
68
 *       logical DELETE via WAL replay but do NOT trigger the file-level overwrite.
69
 *       All replicas must independently run the overwrite to achieve full erasure.
70
 *       Current architecture does not support replicating physical file operations
71
 *       via Raft; this is a design gap for future work.
72
 *
73
 *   P6: STT MULTI-TABLE BLOCKS
74
 *       An STT block with uid==0 holds rows for multiple child tables. The
75
 *       surgical decompress→zero→recompress path handles this, but it is
76
 *       more expensive than a direct overwrite.
77
 *
78
 *   P7: SSD WEAR LEVELING
79
 *       On SSDs, the controller may map the same logical block to a different
80
 *       physical cell for wear leveling. The previous physical cell may retain
81
 *       the old data even after an overwrite. ATA Secure Erase or full-disk
82
 *       encryption is required for complete physical protection.
83
 *
84
 *   P8: OLD FORMAT (tsdb1, pTsdb->pFS == NULL)
85
 *       This implementation only handles the new tsdb2 file format. Old-format
86
 *       files are not yet supported and rely on eventual compaction for erasure.
87
 */
88

89
#include "meta.h"
90
#include "tdataformat.h"
91
#include "tglobal.h"
92
#include "tsdbDataFileRW.h"
93
#include "tsdbDef.h"
94
#include "tsdbFS2.h"
95
#include "tsdbFSet2.h"
96
#include "tsdbSttFileRW.h"
97
#include "tsdbUtil2.h"
98
#include "ttypes.h"
99

100
#define TSDB_SECURE_ERASE_LOG(vid, lino, code) \
101
  tsdbError("vgId:%d secureErase failed at %s:%d since %s", vid, __FILE__, lino, tstrerror(code))
102

103
/* ---------------------------------------------------------------------------
104
 * Zero (or random-fill) a byte buffer according to tsSecureEraseMode.
105
 * For the recompress path we always zero regardless of mode (see P1 above).
106
 * --------------------------------------------------------------------------*/
NEW
107
static void tsdbFillEraseBuf(uint8_t *buf, int32_t size, bool forceZero) {
×
NEW
108
  if (forceZero || tsSecureEraseMode == 0) {
×
NEW
109
    memset(buf, 0, size);
×
110
  } else {
NEW
111
    for (int32_t i = 0; i < size; i++) {
×
NEW
112
      buf[i] = (uint8_t)(taosRand() & 0xFF);
×
113
    }
114
  }
NEW
115
}
×
116

117
/* ---------------------------------------------------------------------------
118
 * Zero the VALUE bytes stored in pColData->pData for rows whose timestamp
119
 * falls in [sKey, eKey] AND whose uid matches (for multi-table STT blocks).
120
 *
121
 * Layout of pColData->pData:
122
 *   • Fixed-size types: pData[valueIdx * typeBytes .. (valueIdx+1)*typeBytes-1]
123
 *   • Variable-size types: pData[aOffset[valueIdx] .. aOffset[valueIdx+1]-1]
124
 *     (last entry: pData[aOffset[last] .. nData-1])
125
 *
126
 * Only rows with bit==2 (HAS_VALUE) have an entry in pData; NONE/NULL rows
127
 * do not occupy space in pData.  We walk all nVal rows and count VALUE rows
128
 * (valueIdx) to locate the byte range for each target row.
129
 * --------------------------------------------------------------------------*/
NEW
130
static void tsdbSecureEraseColDataRows(SColData *pColData, const int64_t *aTSKEY,
×
131
                                       const int64_t *aUid,  /* NULL for single-table blocks */
132
                                       tb_uid_t uid, TSKEY sKey, TSKEY eKey) {
NEW
133
  if (!(pColData->flag & HAS_VALUE)) return;
×
134

NEW
135
  bool   isVar      = IS_VAR_DATA_TYPE(pColData->type);
×
NEW
136
  int32_t typeBytes = isVar ? 0 : tDataTypes[pColData->type].bytes;
×
NEW
137
  int32_t valueIdx  = 0;
×
138

NEW
139
  for (int32_t iVal = 0; iVal < pColData->nVal; iVal++) {
×
NEW
140
    uint8_t bit = tColDataGetBitValue(pColData, iVal);
×
NEW
141
    if (bit != 2) continue;  /* not a VALUE row, skip */
×
142

NEW
143
    bool inRange  = (aTSKEY[iVal] >= sKey && aTSKEY[iVal] <= eKey);
×
NEW
144
    bool uidMatch = (aUid == NULL || aUid[iVal] == uid);
×
145

NEW
146
    if (inRange && uidMatch) {
×
NEW
147
      if (!isVar) {
×
NEW
148
        memset(pColData->pData + (int64_t)valueIdx * typeBytes, 0, typeBytes);
×
149
      } else {
NEW
150
        int32_t start = pColData->aOffset[valueIdx];
×
NEW
151
        int32_t end =
×
NEW
152
            (valueIdx + 1 < pColData->numOfValue) ? pColData->aOffset[valueIdx + 1] : pColData->nData;
×
NEW
153
        if (end > start) {
×
NEW
154
          memset(pColData->pData + start, 0, end - start);
×
155
        }
156
      }
157
    }
NEW
158
    valueIdx++;
×
159
  }
160
}
161

162
/* ---------------------------------------------------------------------------
163
 * Decompress a block from the file, zero column values for target rows, then
164
 * recompress and write back in-place.  If the recompressed block is smaller
165
 * than the original, zero-pad the remainder so that the original byte range
166
 * is fully overwritten.
167
 *
168
 * This path is used for:
169
 *   – Partially-overlapping DATA-file blocks.
170
 *   – Multi-table STT blocks (uid==0).
171
 * --------------------------------------------------------------------------*/
NEW
172
static int32_t tsdbSecureEraseBlockSurgical(STsdb *pTsdb, STsdbFD *pFD,
×
173
                                             int64_t blockOffset, int32_t blockSize,
174
                                             tb_uid_t suid, tb_uid_t uid,
175
                                             TSKEY sKey, TSKEY eKey) {
NEW
176
  int32_t   code = 0, lino = 0;
×
NEW
177
  int32_t   vid  = TD_VID(pTsdb->pVnode);
×
NEW
178
  SBuffer   buffer = {0}, assist = {0};
×
NEW
179
  SBlockData bData  = {0};
×
NEW
180
  SBuffer   buffers[8] = {0};
×
181

NEW
182
  SEncryptData *pEncryptData = &pTsdb->pVnode->config.tsdbCfg.encryptData;
×
183

184
  /* 1. Read compressed block bytes */
185
  tBufferInit(&buffer);
NEW
186
  TAOS_CHECK_GOTO(tsdbReadFileToBuffer(pFD, blockOffset, blockSize, &buffer, 0, pEncryptData),
×
187
                  &lino, _exit);
188

189
  /* 2. Decompress */
NEW
190
  SBufferReader br = BUFFER_READER_INITIALIZER(0, &buffer);
×
NEW
191
  TAOS_CHECK_GOTO(tBlockDataDecompress(&br, &bData, &assist), &lino, _exit);
×
192

193
  /* 3. Zero VALUE bytes for target rows (always zero fill, see P1) */
NEW
194
  for (int32_t iCol = 0; iCol < bData.nColData; iCol++) {
×
NEW
195
    tsdbSecureEraseColDataRows(&bData.aColData[iCol], bData.aTSKEY, bData.aUid, uid, sKey, eKey);
×
196
  }
197

198
  /* 4. Recompress – use column compression settings from meta if available */
NEW
199
  SColCompressInfo cmprInfo = {.defaultCmprAlg = pTsdb->pVnode->config.tsdbCfg.compression};
×
NEW
200
  (void)metaGetColCmpr(pTsdb->pVnode->pMeta, bData.suid != 0 ? bData.suid : bData.uid,
×
201
                       &cmprInfo.pColCmpr);
202

NEW
203
  for (int i = 0; i < 8; i++) tBufferInit(&buffers[i]);
×
NEW
204
  TAOS_CHECK_GOTO(tBlockDataCompress(&bData, &cmprInfo, buffers, buffers + 4), &lino, _exit);
×
205

NEW
206
  int32_t newSize = 0;
×
NEW
207
  for (int i = 0; i < 4; i++) newSize += (int32_t)buffers[i].size;
×
208

209
  /* 5. Write new compressed block back at original offset */
NEW
210
  int64_t writeOff = blockOffset;
×
NEW
211
  for (int i = 0; i < 4; i++) {
×
NEW
212
    if (buffers[i].size) {
×
NEW
213
      TAOS_CHECK_GOTO(tsdbWriteFile(pFD, writeOff, buffers[i].data, buffers[i].size, pEncryptData),
×
214
                      &lino, _exit);
NEW
215
      writeOff += buffers[i].size;
×
216
    }
217
  }
218

219
  /* 6. Zero-pad to fill the original block size so no original bytes remain */
NEW
220
  if (newSize < blockSize) {
×
NEW
221
    int32_t padSize = blockSize - newSize;
×
NEW
222
    uint8_t *zeroBuf = taosMemoryCalloc(1, padSize);
×
NEW
223
    if (zeroBuf) {
×
NEW
224
      (void)tsdbWriteFile(pFD, writeOff, zeroBuf, padSize, pEncryptData);
×
NEW
225
      taosMemoryFree(zeroBuf);
×
226
    }
227
  }
228

NEW
229
_exit:
×
NEW
230
  tBlockDataDestroy(&bData);
×
231
  tBufferDestroy(&buffer);
232
  tBufferDestroy(&assist);
NEW
233
  for (int i = 0; i < 8; i++) tBufferDestroy(&buffers[i]);
×
NEW
234
  if (cmprInfo.pColCmpr) taosHashCleanup(cmprInfo.pColCmpr);
×
NEW
235
  if (code) TSDB_SECURE_ERASE_LOG(vid, lino, code);
×
NEW
236
  return code;
×
237
}
238

239
/* ---------------------------------------------------------------------------
240
 * Process a DATA file: find all blocks for (suid, uid) in [sKey, eKey] via
241
 * the brin index, then overwrite them in the .data file.
242
 * --------------------------------------------------------------------------*/
NEW
243
static int32_t tsdbSecureEraseDataFile(STsdb *pTsdb, const STFileSet *fset,
×
244
                                        tb_uid_t suid, tb_uid_t uid,
245
                                        TSKEY sKey, TSKEY eKey) {
NEW
246
  if (!fset->farr[TSDB_FTYPE_HEAD] || !fset->farr[TSDB_FTYPE_DATA]) return 0;
×
247

NEW
248
  int32_t code = 0, lino = 0;
×
NEW
249
  int32_t vid  = TD_VID(pTsdb->pVnode);
×
250

251
  /* Open reader for the .head + .data files */
NEW
252
  SDataFileReaderConfig config = {.tsdb = pTsdb, .szPage = pTsdb->pVnode->config.tsdbPageSize};
×
NEW
253
  config.files[TSDB_FTYPE_HEAD].exist = true;
×
NEW
254
  config.files[TSDB_FTYPE_HEAD].file  = fset->farr[TSDB_FTYPE_HEAD]->f[0];
×
NEW
255
  config.files[TSDB_FTYPE_DATA].exist = true;
×
NEW
256
  config.files[TSDB_FTYPE_DATA].file  = fset->farr[TSDB_FTYPE_DATA]->f[0];
×
257

NEW
258
  SDataFileReader *pReader = NULL;
×
NEW
259
  TAOS_CHECK_GOTO(tsdbDataFileReaderOpen(NULL, &config, &pReader), &lino, _exit);
×
260

261
  /* Load brin block array */
NEW
262
  const TBrinBlkArray *pBrinBlkArray = NULL;
×
NEW
263
  TAOS_CHECK_GOTO(tsdbDataFileReadBrinBlk(pReader, &pBrinBlkArray), &lino, _closeReader);
×
264

265
  /* Collect matching SBrinRecords */
NEW
266
  SArray *pRecords = taosArrayInit(8, sizeof(SBrinRecord));
×
NEW
267
  if (!pRecords) { TAOS_CHECK_GOTO(terrno, &lino, _closeReader); }
×
268

NEW
269
  for (int32_t iBlk = 0; iBlk < TARRAY2_SIZE(pBrinBlkArray); iBlk++) {
×
NEW
270
    const SBrinBlk *pBrinBlk = TARRAY2_GET_PTR(pBrinBlkArray, iBlk);
×
271
    /* Quick suid/uid range check on the brin block */
NEW
272
    if (pBrinBlk->maxTbid.suid < suid || pBrinBlk->minTbid.suid > suid) continue;
×
NEW
273
    if (pBrinBlk->maxTbid.uid < uid || pBrinBlk->minTbid.uid > uid) continue;
×
274

NEW
275
    SBrinBlock brinBlock = {0};
×
NEW
276
    code = tsdbDataFileReadBrinBlock(pReader, pBrinBlk, &brinBlock);
×
NEW
277
    if (code) { code = 0; continue; }
×
278

NEW
279
    for (int32_t i = 0; i < brinBlock.numOfRecords; i++) {
×
NEW
280
      SBrinRecord record = {0};
×
NEW
281
      if (tBrinBlockGet(&brinBlock, i, &record) != 0) continue;
×
NEW
282
      if (record.suid != suid || record.uid != uid) continue;
×
283
      /* time range check using key.ts (SBrinRecord.firstKey/lastKey are STsdbRowKey) */
NEW
284
      if (record.lastKey.key.ts < sKey || record.firstKey.key.ts > eKey) continue;
×
NEW
285
      if (NULL == taosArrayPush(pRecords, &record)) {
×
NEW
286
        code = terrno;
×
NEW
287
        goto _exit;
×
288
      }
289
    }
NEW
290
    tBrinBlockDestroy(&brinBlock);
×
291
  }
292

NEW
293
  tsdbDataFileReaderClose(&pReader);
×
NEW
294
  pReader = NULL;
×
295

NEW
296
  if (taosArrayGetSize(pRecords) == 0) goto _exit;
×
297

298
  /* Open the .data file for read+write to overwrite blocks in-place */
NEW
299
  char     dataFname[TSDB_FILENAME_LEN] = {0};
×
NEW
300
  tsdbTFileName(pTsdb, &fset->farr[TSDB_FTYPE_DATA]->f[0], dataFname);
×
301

NEW
302
  STsdbFD *pFD  = NULL;
×
NEW
303
  int32_t  lcn  = fset->farr[TSDB_FTYPE_DATA]->f[0].lcn;
×
NEW
304
  TAOS_CHECK_GOTO(tsdbOpenFile(dataFname, pTsdb, TD_FILE_READ | TD_FILE_WRITE, &pFD, lcn),
×
305
                  &lino, _exit);
306

NEW
307
  SEncryptData *pEncryptData = &pTsdb->pVnode->config.tsdbCfg.encryptData;
×
308

NEW
309
  for (int32_t i = 0; i < taosArrayGetSize(pRecords); i++) {
×
NEW
310
    SBrinRecord *pRec = taosArrayGet(pRecords, i);
×
311

NEW
312
    bool fullyContained =
×
NEW
313
        (pRec->firstKey.key.ts >= sKey && pRec->lastKey.key.ts <= eKey);
×
314

NEW
315
    if (fullyContained) {
×
316
      /* Direct raw-bytes overwrite: safe because delete markers cover all rows */
NEW
317
      uint8_t *buf = taosMemoryMalloc(pRec->blockSize);
×
NEW
318
      if (!buf) continue;
×
NEW
319
      tsdbFillEraseBuf(buf, pRec->blockSize, false);
×
NEW
320
      (void)tsdbWriteFile(pFD, pRec->blockOffset, buf, pRec->blockSize, pEncryptData);
×
NEW
321
      taosMemoryFree(buf);
×
322
    } else {
323
      /* Partial overlap: surgical decompress → zero target rows → recompress */
NEW
324
      (void)tsdbSecureEraseBlockSurgical(pTsdb, pFD, pRec->blockOffset, pRec->blockSize,
×
325
                                         suid, uid, sKey, eKey);
326
    }
327
  }
328

329
  /* Flush with updated page CRCs */
NEW
330
  (void)tsdbFsyncFile(pFD, pEncryptData);
×
NEW
331
  tsdbCloseFile(&pFD);
×
332

NEW
333
_exit:
×
NEW
334
  taosArrayDestroy(pRecords);
×
NEW
335
  if (pReader) tsdbDataFileReaderClose(&pReader);
×
NEW
336
  if (code) TSDB_SECURE_ERASE_LOG(vid, lino, code);
×
NEW
337
  return code;
×
338

NEW
339
_closeReader:
×
NEW
340
  tsdbDataFileReaderClose(&pReader);
×
NEW
341
  goto _exit;
×
342
}
343

344
/* ---------------------------------------------------------------------------
345
 * Process a single STT file: find blocks that may contain (suid, uid) data
346
 * in [sKey, eKey] and overwrite them.
347
 *
348
 * STT block overwrite strategy:
349
 *   • If sttBlk.minUid == sttBlk.maxUid == uid (single-table block) AND
350
 *     the time range is fully contained → direct raw-bytes overwrite.
351
 *   • Otherwise → surgical decompress → zero → recompress path.
352
 * --------------------------------------------------------------------------*/
NEW
353
static int32_t tsdbSecureEraseOneSttFile(STsdb *pTsdb, const STFile *pFile,
×
354
                                          tb_uid_t suid, tb_uid_t uid,
355
                                          TSKEY sKey, TSKEY eKey) {
NEW
356
  int32_t code = 0, lino = 0;
×
NEW
357
  int32_t vid  = TD_VID(pTsdb->pVnode);
×
358

NEW
359
  SSttFileReaderConfig config = {
×
360
      .tsdb   = pTsdb,
NEW
361
      .szPage = pTsdb->pVnode->config.tsdbPageSize,
×
362
      .file   = {pFile[0]},
363
  };
364

NEW
365
  SSttFileReader *pReader = NULL;
×
NEW
366
  TAOS_CHECK_GOTO(tsdbSttFileReaderOpen(NULL, &config, &pReader), &lino, _exit);
×
367

NEW
368
  const TSttBlkArray *pSttBlkArray = NULL;
×
NEW
369
  TAOS_CHECK_GOTO(tsdbSttFileReadSttBlk(pReader, &pSttBlkArray), &lino, _closeReader);
×
370

371
  /* Collect matching SSttBlk entries */
NEW
372
  SArray *pBlks = taosArrayInit(8, sizeof(SSttBlk));
×
NEW
373
  if (!pBlks) { TAOS_CHECK_GOTO(terrno, &lino, _closeReader); }
×
374

NEW
375
  for (int32_t iBlk = 0; iBlk < TARRAY2_SIZE(pSttBlkArray); iBlk++) {
×
NEW
376
    const SSttBlk *pBlk = TARRAY2_GET_PTR(pSttBlkArray, iBlk);
×
NEW
377
    if (pBlk->suid != suid) continue;
×
NEW
378
    if (pBlk->minUid > uid || pBlk->maxUid < uid) continue;
×
NEW
379
    if (pBlk->maxKey < sKey || pBlk->minKey > eKey) continue;
×
NEW
380
    if (NULL == taosArrayPush(pBlks, pBlk)) {
×
NEW
381
      code = terrno;
×
NEW
382
      goto _exit;
×
383
    }
384
  }
385

NEW
386
  tsdbSttFileReaderClose(&pReader);
×
NEW
387
  pReader = NULL;
×
388

NEW
389
  if (taosArrayGetSize(pBlks) == 0) goto _exit;
×
390

391
  /* Open STT file for read+write */
NEW
392
  char    sttFname[TSDB_FILENAME_LEN] = {0};
×
NEW
393
  tsdbTFileName(pTsdb, pFile, sttFname);
×
394

NEW
395
  STsdbFD *pFD  = NULL;
×
NEW
396
  int32_t  lcn  = pFile->lcn;
×
NEW
397
  TAOS_CHECK_GOTO(tsdbOpenFile(sttFname, pTsdb, TD_FILE_READ | TD_FILE_WRITE, &pFD, lcn),
×
398
                  &lino, _exit);
399

NEW
400
  SEncryptData *pEncryptData = &pTsdb->pVnode->config.tsdbCfg.encryptData;
×
401

NEW
402
  for (int32_t i = 0; i < taosArrayGetSize(pBlks); i++) {
×
NEW
403
    SSttBlk *pSttBlk = taosArrayGet(pBlks, i);
×
404

NEW
405
    bool singleTable   = (pSttBlk->minUid == pSttBlk->maxUid && pSttBlk->minUid == uid);
×
NEW
406
    bool fullyContained = (pSttBlk->minKey >= sKey && pSttBlk->maxKey <= eKey);
×
407

NEW
408
    if (singleTable && fullyContained) {
×
NEW
409
      uint8_t *buf = taosMemoryMalloc(pSttBlk->bInfo.szBlock);
×
NEW
410
      if (!buf) continue;
×
NEW
411
      tsdbFillEraseBuf(buf, pSttBlk->bInfo.szBlock, false);
×
NEW
412
      (void)tsdbWriteFile(pFD, pSttBlk->bInfo.offset, buf, pSttBlk->bInfo.szBlock, pEncryptData);
×
NEW
413
      taosMemoryFree(buf);
×
414
    } else {
NEW
415
      (void)tsdbSecureEraseBlockSurgical(pTsdb, pFD,
×
416
                                         pSttBlk->bInfo.offset, pSttBlk->bInfo.szBlock,
417
                                         suid, uid, sKey, eKey);
418
    }
419
  }
420

NEW
421
  (void)tsdbFsyncFile(pFD, pEncryptData);
×
NEW
422
  tsdbCloseFile(&pFD);
×
423

NEW
424
_exit:
×
NEW
425
  taosArrayDestroy(pBlks);
×
NEW
426
  if (pReader) tsdbSttFileReaderClose(&pReader);
×
NEW
427
  if (code) TSDB_SECURE_ERASE_LOG(vid, lino, code);
×
NEW
428
  return code;
×
429

NEW
430
_closeReader:
×
NEW
431
  tsdbSttFileReaderClose(&pReader);
×
NEW
432
  goto _exit;
×
433
}
434

435
/* ---------------------------------------------------------------------------
436
 * Public entry point.
437
 *
438
 * Called from tsdbDeleteTableData when secureDelete is enabled, after the
439
 * delete markers have been added to the memtable.  Iterates over all on-disk
440
 * file sets and overwrites blocks that contain data for (suid, uid) in the
441
 * deleted time range [sKey, eKey].
442
 *
443
 * Only handles the new tsdb2 format (pTsdb->pFS != NULL).  Old-format files
444
 * are skipped silently and rely on eventual compaction for erasure.
445
 * --------------------------------------------------------------------------*/
446
int32_t tsdbSecureEraseFileRange(STsdb *pTsdb, tb_uid_t suid, tb_uid_t uid,
1,176✔
447
                                  TSKEY sKey, TSKEY eKey) {
448
  if (!pTsdb || !pTsdb->pFS) return 0;  /* old format: not supported yet */
1,176✔
449

450
  int32_t        code    = 0, lino = 0;
1,176✔
451
  int32_t        vid     = TD_VID(pTsdb->pVnode);
1,176✔
452
  TFileSetArray *fsetArr = NULL;
1,176✔
453

454
  /* Take a reference snapshot so file sets aren't freed under us */
455
  TAOS_CHECK_GOTO(tsdbFSCreateRefSnapshot(pTsdb->pFS, &fsetArr), &lino, _exit);
1,176✔
456

457
  const STFileSet *fset;
458
  TARRAY2_FOREACH(fsetArr, fset) {
1,176✔
459
    /* --- DATA file --- */
NEW
460
    (void)tsdbSecureEraseDataFile(pTsdb, fset, suid, uid, sKey, eKey);
×
461

462
    /* --- STT files (all levels) --- */
463
    const SSttLvl *lvl;
NEW
464
    TARRAY2_FOREACH(fset->lvlArr, lvl) {
×
465
      const STFileObj *fobj;
NEW
466
      TARRAY2_FOREACH(lvl->fobjArr, fobj) {
×
NEW
467
        (void)tsdbSecureEraseOneSttFile(pTsdb, fobj->f, suid, uid, sKey, eKey);
×
468
      }
469
    }
470
  }
471

472
_exit:
1,176✔
473
  tsdbFSDestroyRefSnapshot(&fsetArr);
1,176✔
474
  if (code) TSDB_SECURE_ERASE_LOG(vid, lino, code);
1,176✔
475
  return code;
1,176✔
476
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc