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

taosdata / TDengine / #4936

23 Jan 2026 09:40AM UTC coverage: 66.746% (+0.04%) from 66.708%
#4936

push

travis-ci

web-flow
fix: case failuer caused by the modification of the error description (#34391)

204023 of 305671 relevant lines covered (66.75%)

124768167.97 hits per line

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

69.61
/source/common/src/tencrypt.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 distributed in the hope that it will be useful, but WITHOUT
9
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10
 * FITNESS FOR A PARTICULAR PURPOSE.
11
 *
12
 * You should have received a copy of the GNU Affero General Public License
13
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14
 */
15

16
#define _DEFAULT_SOURCE
17
#include "tencrypt.h"
18
#include "crypt.h"
19
#include "os.h"
20
#include "tdef.h"
21
#include "tglobal.h"
22

23
/**
24
 * Write file with encryption header using atomic file replacement.
25
 *
26
 * This function writes data to a file with an encryption header at the beginning.
27
 * The encryption header contains:
28
 * - Magic number "tdEncrypt" for quick identification
29
 * - Algorithm identifier (e.g., SM4 = 1)
30
 * - File format version
31
 * - Length of encrypted data
32
 *
33
 * Atomic file replacement strategy:
34
 * 1. Write to temporary file: filepath.tmp.timestamp
35
 * 2. Sync temporary file to disk
36
 * 3. Atomically rename temporary file to target filepath
37
 * 4. Remove old file if rename succeeds
38
 *
39
 * This ensures the operation is atomic - no partial writes or corrupted files
40
 * even if the process is interrupted.
41
 *
42
 * @param filepath Target file path
43
 * @param algorithm Encryption algorithm identifier
44
 * @param data Data buffer to write (can be NULL for empty file with header only)
45
 * @param dataLen Length of data to write (0 for empty file)
46
 * @return 0 on success, error code on failure
47
 */
48
int32_t taosWriteEncryptFileHeader(const char *filepath, int32_t algorithm, const void *data, int32_t dataLen) {
102,648✔
49
  int32_t   code = 0;
102,648✔
50
  int32_t   lino = 0;
102,648✔
51
  TdFilePtr pFile = NULL;
102,648✔
52
  char      tempFile[PATH_MAX] = {0};
102,648✔
53

54
  if (filepath == NULL) {
102,648✔
55
    code = TSDB_CODE_INVALID_PARA;
×
56
    TSDB_CHECK_CODE(code, lino, _exit);
×
57
  }
58

59
  // Validate algorithm
60
  if (algorithm < 0) {
102,648✔
61
    code = TSDB_CODE_INVALID_PARA;
×
62
    TSDB_CHECK_CODE(code, lino, _exit);
×
63
  }
64

65
  // Validate data parameters
66
  if (dataLen > 0 && data == NULL) {
102,648✔
67
    code = TSDB_CODE_INVALID_PARA;
×
68
    TSDB_CHECK_CODE(code, lino, _exit);
×
69
  }
70

71
  // Prepare encryption header (plaintext)
72
  STdEncryptFileHeader header;
102,648✔
73
  memset(&header, 0, sizeof(STdEncryptFileHeader));
102,648✔
74
  strncpy(header.magic, TD_ENCRYPT_FILE_MAGIC, TD_ENCRYPT_MAGIC_LEN - 1);
102,648✔
75
  header.algorithm = algorithm;
102,648✔
76
  header.version = TD_ENCRYPT_FILE_VERSION;
102,648✔
77
  header.dataLen = dataLen;
102,648✔
78

79
  // Create temporary file for atomic write
80
  snprintf(tempFile, sizeof(tempFile), "%s.tmp", filepath);
102,648✔
81

82
  // Open temp file
83
  pFile = taosOpenFile(tempFile, TD_FILE_CREATE | TD_FILE_WRITE | TD_FILE_TRUNC);
102,648✔
84
  if (pFile == NULL) {
102,648✔
85
    code = terrno;
×
86
    TSDB_CHECK_CODE(code, lino, _exit);
×
87
  }
88

89
  // Write header (plaintext)
90
  int64_t written = taosWriteFile(pFile, &header, sizeof(STdEncryptFileHeader));
102,648✔
91
  if (written != sizeof(STdEncryptFileHeader)) {
102,648✔
92
    code = (terrno != 0) ? terrno : TSDB_CODE_FILE_CORRUPTED;
×
93
    TSDB_CHECK_CODE(code, lino, _exit);
×
94
  }
95

96
  // Write data if present
97
  if (dataLen > 0 && data != NULL) {
102,648✔
98
    written = taosWriteFile(pFile, data, dataLen);
102,648✔
99
    if (written != dataLen) {
102,648✔
100
      code = (terrno != 0) ? terrno : TSDB_CODE_FILE_CORRUPTED;
×
101
      TSDB_CHECK_CODE(code, lino, _exit);
×
102
    }
103
  }
104

105
  // Sync to disk
106
  code = taosFsyncFile(pFile);
102,648✔
107
  TSDB_CHECK_CODE(code, lino, _exit);
102,648✔
108

109
  // Close temp file
110
  (void)taosCloseFile(&pFile);
102,648✔
111

112
  // Atomic replacement - rename temp file to target
113
  code = taosRenameFile(tempFile, filepath);
102,648✔
114
  TSDB_CHECK_CODE(code, lino, _exit);
102,648✔
115

116
_exit:
102,648✔
117
  if (pFile != NULL) {
102,648✔
118
    (void)taosCloseFile(&pFile);
×
119
  }
120
  if (code != 0) {
102,648✔
121
    if (tempFile[0] != '\0') {
735✔
122
      (void)taosRemoveFile(tempFile);
735✔
123
    }
124
    uError("%s failed at %s:%d since %s, file:%s", __func__, __FILE__, lino, tstrerror(code), filepath);
735✔
125
    terrno = code;
735✔
126
  }
127
  return code;
102,648✔
128
}
129

130
/**
131
 * Read encryption header from file.
132
 *
133
 * Reads and validates the encryption header from the beginning of a file.
134
 * Checks:
135
 * - Magic number matches "tdEncrypt"
136
 * - Version is supported
137
 *
138
 * @param filepath File path to read
139
 * @param header Output parameter for header data
140
 * @return 0 on success, error code on failure
141
 */
142
int32_t taosReadEncryptFileHeader(const char *filepath, STdEncryptFileHeader *header) {
10,489,888✔
143
  int32_t   code = 0;
10,489,888✔
144
  int32_t   lino = 0;
10,489,888✔
145
  TdFilePtr pFile = NULL;
10,489,888✔
146

147
  if (filepath == NULL || header == NULL) {
10,490,255✔
148
    code = TSDB_CODE_INVALID_PARA;
×
149
    TSDB_CHECK_CODE(code, lino, _exit);
×
150
  }
151

152
  // Open file for reading
153
  pFile = taosOpenFile(filepath, TD_FILE_READ);
10,490,255✔
154
  if (pFile == NULL) {
10,486,755✔
155
    code = terrno;
×
156
    TSDB_CHECK_CODE(code, lino, _exit);
×
157
  }
158

159
  // Read header
160
  int64_t nread = taosReadFile(pFile, header, sizeof(STdEncryptFileHeader));
10,486,755✔
161
  if (nread != sizeof(STdEncryptFileHeader)) {
10,490,868✔
162
    code = TSDB_CODE_FILE_CORRUPTED;
706,728✔
163
    TSDB_CHECK_CODE(code, lino, _exit);
706,728✔
164
  }
165

166
  // Verify magic number
167
  if (strncmp(header->magic, TD_ENCRYPT_FILE_MAGIC, strlen(TD_ENCRYPT_FILE_MAGIC)) != 0) {
9,784,140✔
168
    code = TSDB_CODE_FILE_CORRUPTED;
9,762,662✔
169
    TSDB_CHECK_CODE(code, lino, _exit);
9,762,662✔
170
  }
171

172
  // Verify version (currently only version 1 is supported)
173
  if (header->version != TD_ENCRYPT_FILE_VERSION) {
16,595✔
174
    code = TSDB_CODE_FILE_CORRUPTED;
×
175
    TSDB_CHECK_CODE(code, lino, _exit);
×
176
  }
177

178
_exit:
10,482,709✔
179
  if (pFile != NULL) {
10,486,241✔
180
    (void)taosCloseFile(&pFile);
10,490,272✔
181
  }
182
  if (code != 0) {
10,486,999✔
183
    uDebug("%s failed at %s:%d since %s, file:%s", __func__, __FILE__, lino, tstrerror(code), filepath);
10,471,962✔
184
    terrno = code;
10,471,962✔
185
  }
186
  return code;
10,481,666✔
187
}
188

189
/**
190
 * Check if file has encryption header.
191
 *
192
 * Quickly checks if a file begins with the encryption magic number.
193
 * This is faster than reading the full header when you only need to
194
 * know if the file is encrypted.
195
 *
196
 * @param filepath File path to check
197
 * @param algorithm Output parameter for algorithm (can be NULL)
198
 * @return true if file is encrypted, false otherwise
199
 */
200
bool taosIsEncryptedFile(const char *filepath, int32_t *algorithm) {
13,147,832✔
201
  int32_t lino = 0;
13,147,832✔
202
  
203
  if (filepath == NULL) {
13,147,832✔
204
    terrno = TSDB_CODE_INVALID_PARA;
×
205
    uError("%s failed at %s:%d since %s, file:%s", __func__, __FILE__, lino, tstrerror(terrno), "NULL");
×
206
    return false;
×
207
  }
208

209
  // Check if file exists
210
  if (!taosCheckExistFile(filepath)) {
13,147,832✔
211
    return false;
2,657,023✔
212
  }
213

214
  // Read header
215
  STdEncryptFileHeader header;
10,484,178✔
216
  int32_t              code = taosReadEncryptFileHeader(filepath, &header);
10,490,562✔
217

218
  if (code != 0) {
10,489,369✔
219
    return false;
10,470,301✔
220
  }
221

222
  // Return algorithm if requested
223
  if (algorithm != NULL) {
19,068✔
224
    *algorithm = header.algorithm;
×
225
  }
226

227
  return true;
19,068✔
228
}
229

230
/**
231
 * Write configuration file with encryption support using atomic file replacement.
232
 *
233
 * This function writes a configuration file with optional encryption based on tsCfgKey.
234
 * If tsCfgKey is enabled (not empty), it encrypts the data using SM4 CBC algorithm
235
 * and writes it with an encryption header. Otherwise, it writes the file normally.
236
 *
237
 * Atomic file replacement strategy (same for both encrypted and plain files):
238
 * 1. Write to temporary file: filepath.tmp
239
 * 2. Sync temporary file to disk
240
 * 3. Atomically rename temporary file to target filepath
241
 * 4. Remove old file if rename succeeds
242
 *
243
 * @param filepath Target file path
244
 * @param data Data buffer to write
245
 * @param dataLen Length of data to write
246
 * @return 0 on success, error code on failure
247
 */
248
int32_t taosWriteCfgFile(const char *filepath, const void *data, int32_t dataLen) {
61,878,672✔
249
  int32_t   code = 0;
61,878,672✔
250
  int32_t   lino = 0;
61,878,672✔
251
  TdFilePtr pFile = NULL;
61,878,672✔
252
  char      tempFile[PATH_MAX] = {0};
61,881,383✔
253
  char     *plainBuf = NULL;
61,876,340✔
254
  char     *encryptedBuf = NULL;
61,876,340✔
255

256
  if (filepath == NULL || data == NULL || dataLen <= 0) {
61,876,340✔
257
    code = TSDB_CODE_INVALID_PARA;
193✔
258
    TSDB_CHECK_CODE(code, lino, _exit);
193✔
259
  }
260

261
  snprintf(tempFile, sizeof(tempFile), "%s.tmp", filepath);
61,876,340✔
262

263
  // Check if CFG_KEY encryption is enabled
264
  if (tsCfgKey[0] == '\0') {
61,876,340✔
265
    // No encryption, write file normally with atomic operation
266
    pFile = taosOpenFile(tempFile, TD_FILE_CREATE | TD_FILE_WRITE | TD_FILE_TRUNC);
61,773,774✔
267
    if (pFile == NULL) {
61,766,691✔
268
      code = terrno;
×
269
      TSDB_CHECK_CODE(code, lino, _exit);
×
270
    }
271

272
    if (taosWriteFile(pFile, data, dataLen) != dataLen) {
61,766,691✔
273
      code = (terrno != 0) ? terrno : TSDB_CODE_FILE_CORRUPTED;
×
274
      TSDB_CHECK_CODE(code, lino, _exit);
×
275
    }
276

277
    code = taosFsyncFile(pFile);
61,759,320✔
278
    TSDB_CHECK_CODE(code, lino, _exit);
61,769,400✔
279

280
    (void)taosCloseFile(&pFile);
61,769,400✔
281
    pFile = NULL;
61,752,570✔
282

283
    // Atomic replacement - rename temp file to target
284
    code = taosRenameFile(tempFile, filepath);
61,752,570✔
285
    TSDB_CHECK_CODE(code, lino, _exit);
61,761,723✔
286

287
    return 0;
61,761,723✔
288
  }
289

290
  // Encryption enabled - encrypt data first
291
  int32_t cryptedDataLen = ENCRYPTED_LEN(dataLen);
102,648✔
292

293
  // Allocate buffer for padding
294
  plainBuf = taosMemoryMalloc(cryptedDataLen);
102,648✔
295
  if (plainBuf == NULL) {
102,648✔
296
    code = TSDB_CODE_OUT_OF_MEMORY;
×
297
    TSDB_CHECK_CODE(code, lino, _exit);
×
298
  }
299

300
  // Copy data and zero padding
301
  (void)memset(plainBuf, 0, cryptedDataLen);
102,648✔
302
  (void)memcpy(plainBuf, data, dataLen);
102,648✔
303

304
  // Allocate buffer for encrypted data
305
  encryptedBuf = taosMemoryMalloc(cryptedDataLen);
102,648✔
306
  if (encryptedBuf == NULL) {
102,648✔
307
    code = TSDB_CODE_OUT_OF_MEMORY;
×
308
    TSDB_CHECK_CODE(code, lino, _exit);
×
309
  }
310

311
  // Setup encryption options (similar to walWrite.c)
312
  SCryptOpts opts = {0};
102,648✔
313
  opts.len = cryptedDataLen;
102,648✔
314
  opts.source = plainBuf;
102,648✔
315
  opts.result = encryptedBuf;
102,648✔
316
  opts.unitLen = 16;
102,648✔
317
  opts.pOsslAlgrName = TSDB_ENCRYPT_ALGO_SM4_STR;
102,648✔
318
  tstrncpy((char *)opts.key, tsCfgKey, ENCRYPT_KEY_LEN + 1);
102,648✔
319

320
  // Encrypt the data
321
  int32_t count = Builtin_CBC_Encrypt(&opts);
102,648✔
322
  if (count != opts.len) {
102,648✔
323
    code = TSDB_CODE_FAILED;
×
324
    TSDB_CHECK_CODE(code, lino, _exit);
×
325
  }
326

327
  // Write encrypted file with header (uses atomic operation internally)
328
  code = taosWriteEncryptFileHeader(filepath, TSDB_ENCRYPT_ALGO_SM4, encryptedBuf, cryptedDataLen);
102,648✔
329
  TSDB_CHECK_CODE(code, lino, _exit);
102,648✔
330

331
_exit:
102,648✔
332
  if (pFile != NULL) {
102,648✔
333
    (void)taosCloseFile(&pFile);
×
334
  }
335
  if (plainBuf != NULL) {
102,648✔
336
    taosMemoryFree(plainBuf);
102,648✔
337
  }
338
  if (encryptedBuf != NULL) {
102,648✔
339
    taosMemoryFree(encryptedBuf);
102,648✔
340
  }
341
  if (code != 0) {
102,648✔
342
    if (tempFile[0] != '\0') {
735✔
343
      (void)taosRemoveFile(tempFile);
735✔
344
    }
345
    uError("%s failed at %s:%d since %s, file:%s", __func__, __FILE__, lino, tstrerror(code), filepath);
735✔
346
    terrno = code;
735✔
347
  }
348
  return code;
102,648✔
349
}
350

351
/**
352
 * Read configuration file with automatic decryption support.
353
 *
354
 * This function reads a configuration file and automatically handles decryption if needed.
355
 * It checks if the file has an encryption header:
356
 * - If encrypted: reads header, reads encrypted data, decrypts using tsCfgKey
357
 * - If not encrypted: reads file content directly
358
 *
359
 * The caller is responsible for freeing the returned buffer.
360
 *
361
 * @param filepath File path to read
362
 * @param data Output parameter for data buffer (caller must free)
363
 * @param dataLen Output parameter for data length (actual plaintext length)
364
 * @return 0 on success, error code on failure
365
 */
366
int32_t taosReadCfgFile(const char *filepath, char **data, int32_t *dataLen) {
13,144,892✔
367
  int32_t              code = 0;
13,144,892✔
368
  int32_t              lino = 0;
13,144,892✔
369
  TdFilePtr            pFile = NULL;
13,144,892✔
370
  char                *fileContent = NULL;
13,145,053✔
371
  char                *plainContent = NULL;
13,145,053✔
372
  STdEncryptFileHeader header;
13,135,700✔
373

374
  if (taosWaitCfgKeyLoaded() != 0) {
13,145,113✔
375
    code = terrno;
×
376
    TSDB_CHECK_CODE(code, lino, _exit);
×
377
  }
378

379
  if (filepath == NULL || data == NULL || dataLen == NULL) {
13,144,938✔
380
    code = TSDB_CODE_INVALID_PARA;
77✔
381
    TSDB_CHECK_CODE(code, lino, _exit);
77✔
382
  }
383

384
  *data = NULL;
13,144,938✔
385
  *dataLen = 0;
13,144,553✔
386
  // Check if file is encrypted
387
  bool isEncrypted = taosIsEncryptedFile(filepath, NULL);
13,144,595✔
388

389
  // Open file for reading
390
  pFile = taosOpenFile(filepath, TD_FILE_READ);
13,142,928✔
391
  if (pFile == NULL) {
13,143,206✔
392
    code = terrno;
2,657,023✔
393
    TSDB_CHECK_CODE(code, lino, _exit);
2,657,023✔
394
  }
395

396
  // Get file size
397
  int64_t fileSize = 0;
10,486,874✔
398
  code = taosFStatFile(pFile, &fileSize, NULL);
10,482,300✔
399
  TSDB_CHECK_CODE(code, lino, _exit);
10,480,937✔
400

401
  if (fileSize <= 0) {
10,480,937✔
402
    code = TSDB_CODE_FILE_CORRUPTED;
×
403
    TSDB_CHECK_CODE(code, lino, _exit);
×
404
  }
405

406
  if (isEncrypted) {
10,480,937✔
407
    // File is encrypted - read header first
408
    int64_t nread = taosReadFile(pFile, &header, sizeof(STdEncryptFileHeader));
19,068✔
409
    if (nread != sizeof(STdEncryptFileHeader)) {
19,068✔
410
      code = (terrno != 0) ? terrno : TSDB_CODE_FILE_CORRUPTED;
×
411
      TSDB_CHECK_CODE(code, lino, _exit);
×
412
    }
413

414
    // Verify magic number
415
    if (strncmp(header.magic, TD_ENCRYPT_FILE_MAGIC, strlen(TD_ENCRYPT_FILE_MAGIC)) != 0) {
19,068✔
416
      code = TSDB_CODE_FILE_CORRUPTED;
×
417
      TSDB_CHECK_CODE(code, lino, _exit);
×
418
    }
419

420
    // Read encrypted data
421
    int32_t encryptedDataLen = header.dataLen;
19,068✔
422
    if (encryptedDataLen <= 0 || encryptedDataLen > fileSize) {
19,068✔
423
      code = TSDB_CODE_FILE_CORRUPTED;
×
424
      TSDB_CHECK_CODE(code, lino, _exit);
×
425
    }
426

427
    fileContent = taosMemoryMalloc(encryptedDataLen);
19,068✔
428
    if (fileContent == NULL) {
19,068✔
429
      code = TSDB_CODE_OUT_OF_MEMORY;
×
430
      TSDB_CHECK_CODE(code, lino, _exit);
×
431
    }
432

433
    nread = taosReadFile(pFile, fileContent, encryptedDataLen);
19,068✔
434
    if (nread != encryptedDataLen) {
19,068✔
435
      code = (terrno != 0) ? terrno : TSDB_CODE_FILE_CORRUPTED;
×
436
      TSDB_CHECK_CODE(code, lino, _exit);
×
437
    }
438

439
    (void)taosCloseFile(&pFile);
19,068✔
440
    pFile = NULL;
19,068✔
441

442
    // Check if CFG_KEY is available
443
    if (tsCfgKey[0] == '\0') {
19,068✔
444
      code = TSDB_CODE_FAILED;
×
445
      TSDB_CHECK_CODE(code, lino, _exit);
×
446
    }
447

448
    // Decrypt data (reference: sdbFile.c decrypt implementation)
449
    // Allocate buffer for plaintext (same size as encrypted data + 1 for null terminator)
450
    plainContent = taosMemoryMalloc(encryptedDataLen + 1);
19,068✔
451
    if (plainContent == NULL) {
19,068✔
452
      code = TSDB_CODE_OUT_OF_MEMORY;
×
453
      TSDB_CHECK_CODE(code, lino, _exit);
×
454
    }
455

456
    // Setup decryption options
457
    SCryptOpts opts = {0};
19,068✔
458
    opts.len = encryptedDataLen;
19,068✔
459
    opts.source = fileContent;
19,068✔
460
    opts.result = plainContent;
19,068✔
461
    opts.unitLen = 16;
19,068✔
462
    tstrncpy(opts.key, tsCfgKey, ENCRYPT_KEY_LEN + 1);
19,068✔
463

464
    // Decrypt the data
465
    int32_t count = Builtin_CBC_Decrypt(&opts);
19,068✔
466
    if (count != encryptedDataLen) {
19,068✔
467
      code = TSDB_CODE_FAILED;
×
468
      TSDB_CHECK_CODE(code, lino, _exit);
×
469
    }
470

471
    taosMemoryFree(fileContent);
19,068✔
472
    fileContent = NULL;
19,068✔
473

474
    // Add null terminator for JSON parser (cJSON_Parse requires null-terminated string)
475
    plainContent[encryptedDataLen] = '\0';
19,068✔
476

477
    // Return decrypted data
478
    *data = plainContent;
19,068✔
479
    *dataLen = encryptedDataLen;
19,068✔
480
    plainContent = NULL;  // Transfer ownership to caller
19,068✔
481

482
  } else {
483
    // File is not encrypted - read directly
484
    fileContent = taosMemoryMalloc(fileSize + 1);
10,461,869✔
485
    if (fileContent == NULL) {
10,462,667✔
486
      code = TSDB_CODE_OUT_OF_MEMORY;
×
487
      TSDB_CHECK_CODE(code, lino, _exit);
×
488
    }
489

490
    int64_t nread = taosReadFile(pFile, fileContent, fileSize);
10,462,667✔
491
    if (nread != fileSize) {
10,467,617✔
492
      code = (terrno != 0) ? terrno : TSDB_CODE_FILE_CORRUPTED;
×
493
      TSDB_CHECK_CODE(code, lino, _exit);
×
494
    }
495

496
    (void)taosCloseFile(&pFile);
10,467,617✔
497
    pFile = NULL;
10,463,317✔
498

499
    fileContent[fileSize] = '\0';
10,463,317✔
500

501
    // Return file content
502
    *data = fileContent;
10,464,363✔
503
    *dataLen = fileSize;
10,465,398✔
504
    fileContent = NULL;  // Transfer ownership to caller
10,461,461✔
505
  }
506

507
_exit:
13,136,861✔
508
  if (pFile != NULL) {
13,141,243✔
509
    (void)taosCloseFile(&pFile);
×
510
  }
511
  if (fileContent != NULL) {
13,141,243✔
512
    taosMemoryFree(fileContent);
×
513
  }
514
  if (plainContent != NULL) {
13,141,243✔
515
    taosMemoryFree(plainContent);
×
516
  }
517
  if (code != 0) {
13,141,243✔
518
    uError("%s failed at %s:%d since %s, file:%s", __func__, __FILE__, lino, tstrerror(code), filepath);
2,657,023✔
519
    terrno = code;
2,657,023✔
520
  }
521
  return code;
13,141,243✔
522
}
523

524
/**
525
 * Encrypt a single configuration file if it's not already encrypted.
526
 *
527
 * This function checks if a file exists and is not encrypted, then encrypts it in place.
528
 * The operation is atomic - uses temporary file and rename.
529
 *
530
 * @param filepath File path to encrypt
531
 * @return 0 on success or file already encrypted, error code on failure
532
 */
533
static int32_t taosEncryptSingleCfgFile(const char *filepath) {
1,470✔
534
  int32_t   code = 0;
1,470✔
535
  int32_t   lino = 0;
1,470✔
536
  TdFilePtr pFile = NULL;
1,470✔
537
  char     *plainData = NULL;
1,470✔
538

539
  if (filepath == NULL) {
1,470✔
540
    code = TSDB_CODE_INVALID_PARA;
×
541
    TSDB_CHECK_CODE(code, lino, _exit);
×
542
  }
543

544
  // Check if file exists
545
  if (!taosCheckExistFile(filepath)) {
1,470✔
546
    // File doesn't exist, nothing to do
547
    return 0;
×
548
  }
549

550
  // Check if file is already encrypted
551
  if (taosIsEncryptedFile(filepath, NULL)) {
1,470✔
552
    // Already encrypted, nothing to do
553
    return 0;
×
554
  }
555

556
  // Read plaintext file
557
  pFile = taosOpenFile(filepath, TD_FILE_READ);
1,470✔
558
  if (pFile == NULL) {
1,470✔
559
    code = terrno;
×
560
    TSDB_CHECK_CODE(code, lino, _exit);
×
561
  }
562

563
  int64_t fileSize = 0;
1,470✔
564
  code = taosFStatFile(pFile, &fileSize, NULL);
1,470✔
565
  TSDB_CHECK_CODE(code, lino, _exit);
1,470✔
566

567
  if (fileSize <= 0) {
1,470✔
568
    (void)taosCloseFile(&pFile);
×
569
    pFile = NULL;
×
570
    // Empty file, just skip it
571
    return 0;
×
572
  }
573

574
  plainData = taosMemoryMalloc(fileSize);
1,470✔
575
  if (plainData == NULL) {
1,470✔
576
    code = TSDB_CODE_OUT_OF_MEMORY;
×
577
    TSDB_CHECK_CODE(code, lino, _exit);
×
578
  }
579

580
  int64_t nread = taosReadFile(pFile, plainData, fileSize);
1,470✔
581
  if (nread != fileSize) {
1,470✔
582
    code = (terrno != 0) ? terrno : TSDB_CODE_FILE_CORRUPTED;
×
583
    TSDB_CHECK_CODE(code, lino, _exit);
×
584
  }
585

586
  (void)taosCloseFile(&pFile);
1,470✔
587
  pFile = NULL;
1,470✔
588

589
  // Encrypt the file using taosWriteCfgFile (which handles encryption and atomic write)
590
  code = taosWriteCfgFile(filepath, plainData, fileSize);
1,470✔
591
  TSDB_CHECK_CODE(code, lino, _exit);
1,470✔
592

593
_exit:
1,470✔
594
  if (pFile != NULL) {
1,470✔
595
    (void)taosCloseFile(&pFile);
×
596
  }
597
  if (plainData != NULL) {
1,470✔
598
    taosMemoryFree(plainData);
1,470✔
599
  }
600
  if (code != 0) {
1,470✔
601
    uError("%s failed at %s:%d since %s, file:%s", __func__, __FILE__, lino, tstrerror(code), filepath);
×
602
  }
603
  return code;
1,470✔
604
}
605

606
/**
607
 * Encrypt existing configuration files that are not yet encrypted.
608
 *
609
 * This function scans common configuration file locations and encrypts any
610
 * plaintext files it finds. It's called after encryption keys are loaded
611
 * to ensure all sensitive config files are encrypted.
612
 *
613
 * Files checked:
614
 * - dnode: dnode.info, dnode.json
615
 * - mnode: mnode.json, raft_config.json, raft_store.json
616
 * - vnode: vnodes.json, vnode.json (all vnodes), raft_config.json, raft_store.json, current.json
617
 * - snode: snode.json
618
 *
619
 * @param dataDir Data directory path (tsDataDir)
620
 * @return 0 on success, error code on failure (first error encountered)
621
 */
622
int32_t taosEncryptExistingCfgFiles(const char *dataDir) {
1,470✔
623
  int32_t code = 0;
1,470✔
624
  int32_t lino = 0;
1,470✔
625
  char    filepath[PATH_MAX];
1,470✔
626

627
  if (dataDir == NULL) {
1,470✔
628
    code = TSDB_CODE_INVALID_PARA;
×
629
    TSDB_CHECK_CODE(code, lino, _exit);
×
630
  }
631

632
  // Check if encryption is enabled
633
  if (tsCfgKey[0] == '\0') {
1,470✔
634
    // Encryption not enabled, nothing to do
635
    return 0;
×
636
  }
637

638
  // 1. Encrypt dnode config files
639
  // dnode.info
640
  // snprintf(filepath, sizeof(filepath), "%s%sdnode%sdnode.info", dataDir, TD_DIRSEP, TD_DIRSEP);
641
  // if (taosCheckExistFile(filepath) && !taosIsEncryptedFile(filepath, NULL)) {
642
  //   code = taosEncryptSingleCfgFile(filepath);
643
  //   TSDB_CHECK_CODE(code, lino, _exit);
644
  //   uInfo("successfully encrypted file %s", filepath);
645
  // }
646

647
  // dnode.json (ep.json)
648
  snprintf(filepath, sizeof(filepath), "%s%sdnode%sdnode.json", dataDir, TD_DIRSEP, TD_DIRSEP);
1,470✔
649
  if (taosCheckExistFile(filepath) && !taosIsEncryptedFile(filepath, NULL)) {
1,470✔
650
    code = taosEncryptSingleCfgFile(filepath);
1,470✔
651
    TSDB_CHECK_CODE(code, lino, _exit);
1,470✔
652
    uInfo("successfully encrypted file %s", filepath);
1,470✔
653
  }
654

655
  // 2. Encrypt mnode config files
656
  snprintf(filepath, sizeof(filepath), "%s%smnode%smnode.json", dataDir, TD_DIRSEP, TD_DIRSEP);
1,470✔
657
  if (taosCheckExistFile(filepath) && !taosIsEncryptedFile(filepath, NULL)) {
1,470✔
658
    code = taosEncryptSingleCfgFile(filepath);
×
659
    TSDB_CHECK_CODE(code, lino, _exit);
×
660
    uInfo("successfully encrypted file %s", filepath);
×
661
  }
662

663
  snprintf(filepath, sizeof(filepath), "%s%smnode%ssync%sraft_config.json", dataDir, TD_DIRSEP, TD_DIRSEP, TD_DIRSEP);
1,470✔
664
  if (taosCheckExistFile(filepath) && !taosIsEncryptedFile(filepath, NULL)) {
1,470✔
665
    code = taosEncryptSingleCfgFile(filepath);
×
666
    TSDB_CHECK_CODE(code, lino, _exit);
×
667
    uInfo("successfully encrypted file %s", filepath);
×
668
  }
669

670
  snprintf(filepath, sizeof(filepath), "%s%smnode%ssync%sraft_store.json", dataDir, TD_DIRSEP, TD_DIRSEP, TD_DIRSEP);
1,470✔
671
  if (taosCheckExistFile(filepath) && !taosIsEncryptedFile(filepath, NULL)) {
1,470✔
672
    code = taosEncryptSingleCfgFile(filepath);
×
673
    TSDB_CHECK_CODE(code, lino, _exit);
×
674
    uInfo("successfully encrypted file %s", filepath);
×
675
  }
676

677
  // 3. Encrypt snode config files
678
  snprintf(filepath, sizeof(filepath), "%s%ssnode%ssnode.json", dataDir, TD_DIRSEP, TD_DIRSEP);
1,470✔
679
  if (taosCheckExistFile(filepath) && !taosIsEncryptedFile(filepath, NULL)) {
1,470✔
680
    code = taosEncryptSingleCfgFile(filepath);
×
681
    TSDB_CHECK_CODE(code, lino, _exit);
×
682
    uInfo("successfully encrypted file %s", filepath);
×
683
  }
684

685
  // 4. Encrypt vnode config files
686
  // vnodes.json
687
  snprintf(filepath, sizeof(filepath), "%s%svnode%svnodes.json", dataDir, TD_DIRSEP, TD_DIRSEP);
1,470✔
688
  if (taosCheckExistFile(filepath) && !taosIsEncryptedFile(filepath, NULL)) {
1,470✔
689
    code = taosEncryptSingleCfgFile(filepath);
×
690
    TSDB_CHECK_CODE(code, lino, _exit);
×
691
    uInfo("successfully encrypted file %s", filepath);
×
692
  }
693

694
  // Note: Individual vnode directories (vnode1, vnode2, etc.) are not traversed here
695
  // because they would require scanning the vnode directory structure.
696
  // These files will be encrypted on next write by taosWriteCfgFile.
697

698
  uInfo("finished encrypting existing config files");
1,470✔
699

700
_exit:
1,470✔
701
  if (code != 0) {
1,470✔
702
    uError("%s failed at %s:%d since %s, dataDir:%s", __func__, __FILE__, lino, tstrerror(code), dataDir);
×
703
  }
704
  return code;
1,470✔
705
}
706

707
/**
708
 * Wait for CFG encryption key to be loaded with timeout.
709
 *
710
 * This function polls the encryption key status at regular intervals (100ms).
711
 * It returns immediately if the key is already loaded, otherwise it waits
712
 * until either the key is loaded or the timeout expires.
713
 *
714
 * Timeout is controlled by TD_ENCRYPT_KEY_WAIT_TIMEOUT_MS macro.
715
 *
716
 * @return 0 if key loaded successfully, TSDB_CODE_TIMEOUT_ERROR if timeout occurs
717
 */
718
int32_t taosWaitCfgKeyLoaded(void) {
141,226,520✔
719
#if defined(TD_ENTERPRISE) || defined(TD_ASTRA_TODO)
720
  int32_t code = 0;
141,226,520✔
721
  int32_t lino = 0;
141,226,520✔
722

723
  if (tsSkipKeyCheckMode) {
141,226,520✔
724
    uDebug("skip encryption key verification in some special check mode");
427,928✔
725
    return 0;
427,928✔
726
  }
727

728
  int32_t encryptKeysLoaded = atomic_load_32(&tsEncryptKeysStatus);
140,798,592✔
729
  if (encryptKeysLoaded == TSDB_ENCRYPT_KEY_STAT_LOADED || encryptKeysLoaded == TSDB_ENCRYPT_KEY_STAT_NOT_EXIST ||
140,796,984✔
730
      encryptKeysLoaded == TSDB_ENCRYPT_KEY_STAT_DISABLED) {
731
    uDebug("CFG encryption key loaded successfully");
140,796,984✔
732
    return 0;
140,797,738✔
733
  }
734
  uDebug("CFG encryption key not loaded, waiting for %d ms", TD_ENCRYPT_KEY_WAIT_TIMEOUT_MS);
×
735

736
  const int32_t checkIntervalMs = 100;  // Check every 100ms
×
737
  int32_t       elapsedMs = 0;
×
738

739
  while (elapsedMs < TD_ENCRYPT_KEY_WAIT_TIMEOUT_MS) {
×
740
    // Check if CFG key is loaded
741
    encryptKeysLoaded = atomic_load_32(&tsEncryptKeysStatus);
×
742
    if (encryptKeysLoaded == TSDB_ENCRYPT_KEY_STAT_LOADED || encryptKeysLoaded == TSDB_ENCRYPT_KEY_STAT_NOT_EXIST ||
×
743
        encryptKeysLoaded == TSDB_ENCRYPT_KEY_STAT_DISABLED) {
744
      uDebug("CFG encryption key loaded successfully after %d ms", elapsedMs);
×
745
      return 0;
×
746
    }
747

748
    // Sleep for check interval
749
    taosMsleep(checkIntervalMs);
×
750
    elapsedMs += checkIntervalMs;
×
751
  }
752

753
  // Timeout occurred
754
  code = TSDB_CODE_TIMEOUT_ERROR;
×
755
  lino = __LINE__;
×
756
  uError("%s failed at %s:%d since %s, waited %d ms", __func__, __FILE__, lino, tstrerror(code), elapsedMs);
×
757
  terrno = code;
×
758
  return code;
×
759
#else
760
  uDebug("skip encryption key verification in non-enterprise mode");
761
  return 0;
762
#endif
763
}
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