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

taosdata / TDengine / #4924

09 Jan 2026 08:13AM UTC coverage: 65.354% (-0.02%) from 65.373%
#4924

push

travis-ci

web-flow
merge: from main to 3.0 branch #34232

33 of 56 new or added lines in 8 files covered. (58.93%)

3192 existing lines in 116 files now uncovered.

198218 of 303297 relevant lines covered (65.35%)

118995160.34 hits per line

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

69.23
/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) {
78,366✔
49
  int32_t   code = 0;
78,366✔
50
  int32_t   lino = 0;
78,366✔
51
  TdFilePtr pFile = NULL;
78,366✔
52
  char      tempFile[PATH_MAX] = {0};
78,366✔
53

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

59
  // Validate algorithm
60
  if (algorithm < 0) {
78,366✔
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) {
78,366✔
67
    code = TSDB_CODE_INVALID_PARA;
×
68
    TSDB_CHECK_CODE(code, lino, _exit);
×
69
  }
70

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

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

82
  // Open temp file
83
  pFile = taosOpenFile(tempFile, TD_FILE_CREATE | TD_FILE_WRITE | TD_FILE_TRUNC);
78,366✔
84
  if (pFile == NULL) {
78,366✔
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));
78,366✔
91
  if (written != sizeof(STdEncryptFileHeader)) {
78,366✔
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) {
78,366✔
98
    written = taosWriteFile(pFile, data, dataLen);
78,366✔
99
    if (written != dataLen) {
78,366✔
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);
78,366✔
107
  TSDB_CHECK_CODE(code, lino, _exit);
78,366✔
108

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

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

116
_exit:
78,366✔
117
  if (pFile != NULL) {
78,366✔
118
    (void)taosCloseFile(&pFile);
×
119
  }
120
  if (code != 0) {
78,366✔
121
    if (tempFile[0] != '\0') {
×
122
      (void)taosRemoveFile(tempFile);
×
123
    }
124
    uError("%s failed at %s:%d since %s, file:%s", __func__, __FILE__, lino, tstrerror(code), filepath);
×
125
    terrno = code;
×
126
  }
127
  return code;
78,366✔
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) {
9,613,120✔
143
  int32_t   code = 0;
9,613,120✔
144
  int32_t   lino = 0;
9,613,120✔
145
  TdFilePtr pFile = NULL;
9,613,120✔
146

147
  if (filepath == NULL || header == NULL) {
9,613,120✔
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);
9,613,120✔
154
  if (pFile == NULL) {
9,607,525✔
155
    code = terrno;
×
156
    TSDB_CHECK_CODE(code, lino, _exit);
×
157
  }
158

159
  // Read header
160
  int64_t nread = taosReadFile(pFile, header, sizeof(STdEncryptFileHeader));
9,607,525✔
161
  if (nread != sizeof(STdEncryptFileHeader)) {
9,612,804✔
162
    code = TSDB_CODE_FILE_CORRUPTED;
1,280,000✔
163
    TSDB_CHECK_CODE(code, lino, _exit);
1,280,000✔
164
  }
165

166
  // Verify magic number
167
  if (strncmp(header->magic, TD_ENCRYPT_FILE_MAGIC, strlen(TD_ENCRYPT_FILE_MAGIC)) != 0) {
8,333,066✔
168
    code = TSDB_CODE_FILE_CORRUPTED;
8,317,605✔
169
    TSDB_CHECK_CODE(code, lino, _exit);
8,317,605✔
170
  }
171

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

178
_exit:
9,609,672✔
179
  if (pFile != NULL) {
9,608,412✔
180
    (void)taosCloseFile(&pFile);
9,613,173✔
181
  }
182
  if (code != 0) {
9,609,409✔
183
    uDebug("%s failed at %s:%d since %s, file:%s", __func__, __FILE__, lino, tstrerror(code), filepath);
9,601,152✔
184
    terrno = code;
9,601,152✔
185
  }
186
  return code;
9,606,039✔
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) {
12,226,059✔
201
  int32_t lino = 0;
12,226,059✔
202
  
203
  if (filepath == NULL) {
12,226,059✔
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)) {
12,226,059✔
211
    return false;
2,612,504✔
212
  }
213

214
  // Read header
215
  STdEncryptFileHeader header;
9,613,385✔
216
  int32_t              code = taosReadEncryptFileHeader(filepath, &header);
9,614,537✔
217

218
  if (code != 0) {
9,612,903✔
219
    return false;
9,599,885✔
220
  }
221

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

227
  return true;
13,018✔
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) {
59,251,103✔
249
  int32_t   code = 0;
59,251,103✔
250
  int32_t   lino = 0;
59,251,103✔
251
  TdFilePtr pFile = NULL;
59,251,103✔
252
  char      tempFile[PATH_MAX] = {0};
59,253,156✔
253
  char     *plainBuf = NULL;
59,250,525✔
254
  char     *encryptedBuf = NULL;
59,250,525✔
255

256
  if (filepath == NULL || data == NULL || dataLen <= 0) {
59,250,525✔
257
    code = TSDB_CODE_INVALID_PARA;
×
258
    TSDB_CHECK_CODE(code, lino, _exit);
×
259
  }
260

261
  snprintf(tempFile, sizeof(tempFile), "%s.tmp", filepath);
59,250,525✔
262

263
  // Check if CFG_KEY encryption is enabled
264
  if (tsCfgKey[0] == '\0') {
59,250,525✔
265
    // No encryption, write file normally with atomic operation
266
    pFile = taosOpenFile(tempFile, TD_FILE_CREATE | TD_FILE_WRITE | TD_FILE_TRUNC);
59,172,147✔
267
    if (pFile == NULL) {
59,164,077✔
268
      code = terrno;
426✔
269
      TSDB_CHECK_CODE(code, lino, _exit);
426✔
270
    }
271

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

277
    code = taosFsyncFile(pFile);
59,160,370✔
278
    TSDB_CHECK_CODE(code, lino, _exit);
59,177,247✔
279

280
    (void)taosCloseFile(&pFile);
59,177,247✔
281
    pFile = NULL;
59,167,230✔
282

283
    // Atomic replacement - rename temp file to target
284
    code = taosRenameFile(tempFile, filepath);
59,167,230✔
285
    TSDB_CHECK_CODE(code, lino, _exit);
59,158,950✔
286

287
    return 0;
59,158,950✔
288
  }
289

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

293
  // Allocate buffer for padding
294
  plainBuf = taosMemoryMalloc(cryptedDataLen);
78,378✔
295
  if (plainBuf == NULL) {
78,366✔
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);
78,366✔
302
  (void)memcpy(plainBuf, data, dataLen);
78,366✔
303

304
  // Allocate buffer for encrypted data
305
  encryptedBuf = taosMemoryMalloc(cryptedDataLen);
78,366✔
306
  if (encryptedBuf == NULL) {
78,366✔
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};
78,366✔
313
  opts.len = cryptedDataLen;
78,366✔
314
  opts.source = plainBuf;
78,366✔
315
  opts.result = encryptedBuf;
78,366✔
316
  opts.unitLen = 16;
78,366✔
317
  opts.pOsslAlgrName = TSDB_ENCRYPT_ALGO_SM4_STR;
78,366✔
318
  tstrncpy((char *)opts.key, tsCfgKey, ENCRYPT_KEY_LEN + 1);
78,366✔
319

320
  // Encrypt the data
321
  int32_t count = Builtin_CBC_Encrypt(&opts);
78,366✔
322
  if (count != opts.len) {
78,366✔
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);
78,366✔
329
  TSDB_CHECK_CODE(code, lino, _exit);
78,366✔
330

331
_exit:
78,792✔
332
  if (pFile != NULL) {
78,792✔
333
    (void)taosCloseFile(&pFile);
×
334
  }
335
  if (plainBuf != NULL) {
78,792✔
336
    taosMemoryFree(plainBuf);
78,366✔
337
  }
338
  if (encryptedBuf != NULL) {
78,792✔
339
    taosMemoryFree(encryptedBuf);
78,366✔
340
  }
341
  if (code != 0) {
78,792✔
342
    if (tempFile[0] != '\0') {
426✔
343
      (void)taosRemoveFile(tempFile);
426✔
344
    }
345
    uError("%s failed at %s:%d since %s, file:%s", __func__, __FILE__, lino, tstrerror(code), filepath);
426✔
346
    terrno = code;
426✔
347
  }
348
  return code;
78,792✔
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) {
12,220,187✔
367
  int32_t              code = 0;
12,220,187✔
368
  int32_t              lino = 0;
12,220,187✔
369
  TdFilePtr            pFile = NULL;
12,220,187✔
370
  char                *fileContent = NULL;
12,219,781✔
371
  char                *plainContent = NULL;
12,219,781✔
372
  STdEncryptFileHeader header;
12,218,310✔
373

374
  if (filepath == NULL || data == NULL || dataLen == NULL) {
12,220,322✔
375
    code = TSDB_CODE_INVALID_PARA;
20✔
376
    TSDB_CHECK_CODE(code, lino, _exit);
20✔
377
  }
378

379
  *data = NULL;
12,220,322✔
380
  *dataLen = 0;
12,220,332✔
381
  // Check if file is encrypted
382
  bool isEncrypted = taosIsEncryptedFile(filepath, NULL);
12,220,332✔
383

384
  // Open file for reading
385
  pFile = taosOpenFile(filepath, TD_FILE_READ);
12,217,849✔
386
  if (pFile == NULL) {
12,220,368✔
387
    code = terrno;
2,612,222✔
388
    TSDB_CHECK_CODE(code, lino, _exit);
2,612,222✔
389
  }
390

391
  // Get file size
392
  int64_t fileSize = 0;
9,608,146✔
393
  code = taosFStatFile(pFile, &fileSize, NULL);
9,603,252✔
394
  TSDB_CHECK_CODE(code, lino, _exit);
9,600,261✔
395

396
  if (fileSize <= 0) {
9,600,261✔
397
    code = TSDB_CODE_FILE_CORRUPTED;
×
398
    TSDB_CHECK_CODE(code, lino, _exit);
×
399
  }
400

401
  if (isEncrypted) {
9,600,261✔
402
    // File is encrypted - read header first
403
    int64_t nread = taosReadFile(pFile, &header, sizeof(STdEncryptFileHeader));
13,018✔
404
    if (nread != sizeof(STdEncryptFileHeader)) {
13,018✔
405
      code = (terrno != 0) ? terrno : TSDB_CODE_FILE_CORRUPTED;
×
406
      TSDB_CHECK_CODE(code, lino, _exit);
×
407
    }
408

409
    // Verify magic number
410
    if (strncmp(header.magic, TD_ENCRYPT_FILE_MAGIC, strlen(TD_ENCRYPT_FILE_MAGIC)) != 0) {
13,018✔
411
      code = TSDB_CODE_FILE_CORRUPTED;
×
412
      TSDB_CHECK_CODE(code, lino, _exit);
×
413
    }
414

415
    // Read encrypted data
416
    int32_t encryptedDataLen = header.dataLen;
13,018✔
417
    if (encryptedDataLen <= 0 || encryptedDataLen > fileSize) {
13,018✔
418
      code = TSDB_CODE_FILE_CORRUPTED;
×
419
      TSDB_CHECK_CODE(code, lino, _exit);
×
420
    }
421

422
    fileContent = taosMemoryMalloc(encryptedDataLen);
13,018✔
423
    if (fileContent == NULL) {
13,018✔
424
      code = TSDB_CODE_OUT_OF_MEMORY;
×
425
      TSDB_CHECK_CODE(code, lino, _exit);
×
426
    }
427

428
    nread = taosReadFile(pFile, fileContent, encryptedDataLen);
13,018✔
429
    if (nread != encryptedDataLen) {
13,018✔
430
      code = (terrno != 0) ? terrno : TSDB_CODE_FILE_CORRUPTED;
×
431
      TSDB_CHECK_CODE(code, lino, _exit);
×
432
    }
433

434
    (void)taosCloseFile(&pFile);
13,018✔
435
    pFile = NULL;
13,018✔
436

437
    // Check if CFG_KEY is available
438
    if (tsCfgKey[0] == '\0') {
13,018✔
439
      code = TSDB_CODE_FAILED;
×
440
      TSDB_CHECK_CODE(code, lino, _exit);
×
441
    }
442

443
    // Decrypt data (reference: sdbFile.c decrypt implementation)
444
    // Allocate buffer for plaintext (same size as encrypted data + 1 for null terminator)
445
    plainContent = taosMemoryMalloc(encryptedDataLen + 1);
13,018✔
446
    if (plainContent == NULL) {
13,018✔
447
      code = TSDB_CODE_OUT_OF_MEMORY;
×
448
      TSDB_CHECK_CODE(code, lino, _exit);
×
449
    }
450

451
    // Setup decryption options
452
    SCryptOpts opts = {0};
13,018✔
453
    opts.len = encryptedDataLen;
13,018✔
454
    opts.source = fileContent;
13,018✔
455
    opts.result = plainContent;
13,018✔
456
    opts.unitLen = 16;
13,018✔
457
    tstrncpy(opts.key, tsCfgKey, ENCRYPT_KEY_LEN + 1);
13,018✔
458

459
    // Decrypt the data
460
    int32_t count = Builtin_CBC_Decrypt(&opts);
13,018✔
461
    if (count != encryptedDataLen) {
13,018✔
462
      code = TSDB_CODE_FAILED;
×
463
      TSDB_CHECK_CODE(code, lino, _exit);
×
464
    }
465

466
    taosMemoryFree(fileContent);
13,018✔
467
    fileContent = NULL;
13,018✔
468

469
    // Add null terminator for JSON parser (cJSON_Parse requires null-terminated string)
470
    plainContent[encryptedDataLen] = '\0';
13,018✔
471

472
    // Return decrypted data
473
    *data = plainContent;
13,018✔
474
    *dataLen = encryptedDataLen;
13,018✔
475
    plainContent = NULL;  // Transfer ownership to caller
13,018✔
476

477
  } else {
478
    // File is not encrypted - read directly
479
    fileContent = taosMemoryMalloc(fileSize + 1);
9,587,243✔
480
    if (fileContent == NULL) {
9,589,794✔
UNCOV
481
      code = TSDB_CODE_OUT_OF_MEMORY;
×
UNCOV
482
      TSDB_CHECK_CODE(code, lino, _exit);
×
483
    }
484

485
    int64_t nread = taosReadFile(pFile, fileContent, fileSize);
9,589,794✔
486
    if (nread != fileSize) {
9,595,197✔
UNCOV
487
      code = (terrno != 0) ? terrno : TSDB_CODE_FILE_CORRUPTED;
×
UNCOV
488
      TSDB_CHECK_CODE(code, lino, _exit);
×
489
    }
490

491
    (void)taosCloseFile(&pFile);
9,595,197✔
492
    pFile = NULL;
9,591,155✔
493

494
    fileContent[fileSize] = '\0';
9,591,155✔
495

496
    // Return file content
497
    *data = fileContent;
9,593,402✔
498
    *dataLen = fileSize;
9,591,605✔
499
    fileContent = NULL;  // Transfer ownership to caller
9,592,781✔
500
  }
501

502
_exit:
12,218,021✔
503
  if (pFile != NULL) {
12,219,295✔
UNCOV
504
    (void)taosCloseFile(&pFile);
×
505
  }
506
  if (fileContent != NULL) {
12,219,295✔
UNCOV
507
    taosMemoryFree(fileContent);
×
508
  }
509
  if (plainContent != NULL) {
12,219,295✔
UNCOV
510
    taosMemoryFree(plainContent);
×
511
  }
512
  if (code != 0) {
12,219,295✔
513
    uError("%s failed at %s:%d since %s, file:%s", __func__, __FILE__, lino, tstrerror(code), filepath);
2,612,504✔
514
    terrno = code;
2,612,504✔
515
  }
516
  return code;
12,219,295✔
517
}
518

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

534
  if (filepath == NULL) {
2,936✔
UNCOV
535
    code = TSDB_CODE_INVALID_PARA;
×
UNCOV
536
    TSDB_CHECK_CODE(code, lino, _exit);
×
537
  }
538

539
  // Check if file exists
540
  if (!taosCheckExistFile(filepath)) {
2,936✔
541
    // File doesn't exist, nothing to do
UNCOV
542
    return 0;
×
543
  }
544

545
  // Check if file is already encrypted
546
  if (taosIsEncryptedFile(filepath, NULL)) {
2,936✔
547
    // Already encrypted, nothing to do
UNCOV
548
    return 0;
×
549
  }
550

551
  // Read plaintext file
552
  pFile = taosOpenFile(filepath, TD_FILE_READ);
2,936✔
553
  if (pFile == NULL) {
2,936✔
UNCOV
554
    code = terrno;
×
UNCOV
555
    TSDB_CHECK_CODE(code, lino, _exit);
×
556
  }
557

558
  int64_t fileSize = 0;
2,936✔
559
  code = taosFStatFile(pFile, &fileSize, NULL);
2,936✔
560
  TSDB_CHECK_CODE(code, lino, _exit);
2,936✔
561

562
  if (fileSize <= 0) {
2,936✔
UNCOV
563
    (void)taosCloseFile(&pFile);
×
UNCOV
564
    pFile = NULL;
×
565
    // Empty file, just skip it
566
    return 0;
×
567
  }
568

569
  plainData = taosMemoryMalloc(fileSize);
2,936✔
570
  if (plainData == NULL) {
2,936✔
UNCOV
571
    code = TSDB_CODE_OUT_OF_MEMORY;
×
UNCOV
572
    TSDB_CHECK_CODE(code, lino, _exit);
×
573
  }
574

575
  int64_t nread = taosReadFile(pFile, plainData, fileSize);
2,936✔
576
  if (nread != fileSize) {
2,936✔
UNCOV
577
    code = (terrno != 0) ? terrno : TSDB_CODE_FILE_CORRUPTED;
×
UNCOV
578
    TSDB_CHECK_CODE(code, lino, _exit);
×
579
  }
580

581
  (void)taosCloseFile(&pFile);
2,936✔
582
  pFile = NULL;
2,936✔
583

584
  // Encrypt the file using taosWriteCfgFile (which handles encryption and atomic write)
585
  code = taosWriteCfgFile(filepath, plainData, fileSize);
2,936✔
586
  TSDB_CHECK_CODE(code, lino, _exit);
2,936✔
587

588
_exit:
2,936✔
589
  if (pFile != NULL) {
2,936✔
UNCOV
590
    (void)taosCloseFile(&pFile);
×
591
  }
592
  if (plainData != NULL) {
2,936✔
593
    taosMemoryFree(plainData);
2,936✔
594
  }
595
  if (code != 0) {
2,936✔
UNCOV
596
    uError("%s failed at %s:%d since %s, file:%s", __func__, __FILE__, lino, tstrerror(code), filepath);
×
597
  }
598
  return code;
2,936✔
599
}
600

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

622
  if (dataDir == NULL) {
1,468✔
UNCOV
623
    code = TSDB_CODE_INVALID_PARA;
×
UNCOV
624
    TSDB_CHECK_CODE(code, lino, _exit);
×
625
  }
626

627
  // Check if encryption is enabled
628
  if (tsCfgKey[0] == '\0') {
1,468✔
629
    // Encryption not enabled, nothing to do
UNCOV
630
    return 0;
×
631
  }
632

633
  // 1. Encrypt dnode config files
634
  // dnode.info
635
  snprintf(filepath, sizeof(filepath), "%s%sdnode%sdnode.info", dataDir, TD_DIRSEP, TD_DIRSEP);
1,468✔
636
  if (taosCheckExistFile(filepath) && !taosIsEncryptedFile(filepath, NULL)) {
1,468✔
637
    code = taosEncryptSingleCfgFile(filepath);
1,468✔
638
    TSDB_CHECK_CODE(code, lino, _exit);
1,468✔
639
    uInfo("successfully encrypted file %s", filepath);
1,468✔
640
  }
641

642
  // dnode.json (ep.json)
643
  snprintf(filepath, sizeof(filepath), "%s%sdnode%sdnode.json", dataDir, TD_DIRSEP, TD_DIRSEP);
1,468✔
644
  if (taosCheckExistFile(filepath) && !taosIsEncryptedFile(filepath, NULL)) {
1,468✔
645
    code = taosEncryptSingleCfgFile(filepath);
1,468✔
646
    TSDB_CHECK_CODE(code, lino, _exit);
1,468✔
647
    uInfo("successfully encrypted file %s", filepath);
1,468✔
648
  }
649

650
  // 2. Encrypt mnode config files
651
  snprintf(filepath, sizeof(filepath), "%s%smnode%smnode.json", dataDir, TD_DIRSEP, TD_DIRSEP);
1,468✔
652
  if (taosCheckExistFile(filepath) && !taosIsEncryptedFile(filepath, NULL)) {
1,468✔
UNCOV
653
    code = taosEncryptSingleCfgFile(filepath);
×
UNCOV
654
    TSDB_CHECK_CODE(code, lino, _exit);
×
UNCOV
655
    uInfo("successfully encrypted file %s", filepath);
×
656
  }
657

658
  snprintf(filepath, sizeof(filepath), "%s%smnode%ssync%sraft_config.json", dataDir, TD_DIRSEP, TD_DIRSEP, TD_DIRSEP);
1,468✔
659
  if (taosCheckExistFile(filepath) && !taosIsEncryptedFile(filepath, NULL)) {
1,468✔
UNCOV
660
    code = taosEncryptSingleCfgFile(filepath);
×
UNCOV
661
    TSDB_CHECK_CODE(code, lino, _exit);
×
UNCOV
662
    uInfo("successfully encrypted file %s", filepath);
×
663
  }
664

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

672
  // 3. Encrypt snode config files
673
  snprintf(filepath, sizeof(filepath), "%s%ssnode%ssnode.json", dataDir, TD_DIRSEP, TD_DIRSEP);
1,468✔
674
  if (taosCheckExistFile(filepath) && !taosIsEncryptedFile(filepath, NULL)) {
1,468✔
UNCOV
675
    code = taosEncryptSingleCfgFile(filepath);
×
UNCOV
676
    TSDB_CHECK_CODE(code, lino, _exit);
×
677
    uInfo("successfully encrypted file %s", filepath);
×
678
  }
679

680
  // 4. Encrypt vnode config files
681
  // vnodes.json
682
  snprintf(filepath, sizeof(filepath), "%s%svnode%svnodes.json", dataDir, TD_DIRSEP, TD_DIRSEP);
1,468✔
683
  if (taosCheckExistFile(filepath) && !taosIsEncryptedFile(filepath, NULL)) {
1,468✔
UNCOV
684
    code = taosEncryptSingleCfgFile(filepath);
×
UNCOV
685
    TSDB_CHECK_CODE(code, lino, _exit);
×
686
    uInfo("successfully encrypted file %s", filepath);
×
687
  }
688

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

693
  uInfo("finished encrypting existing config files");
1,468✔
694

695
_exit:
1,468✔
696
  if (code != 0) {
1,468✔
UNCOV
697
    uError("%s failed at %s:%d since %s, dataDir:%s", __func__, __FILE__, lino, tstrerror(code), dataDir);
×
698
  }
699
  return code;
1,468✔
700
}
701

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

718
  if (tsSkipKeyCheckMode) {
126,658,618✔
719
    uDebug("skip encryption key verification in some special check mode");
5,433✔
720
    return 0;
5,433✔
721
  }
722

723
  int32_t encryptKeysLoaded = atomic_load_32(&tsEncryptKeysStatus);
126,653,185✔
724
  if (encryptKeysLoaded == TSDB_ENCRYPT_KEY_STAT_LOADED || encryptKeysLoaded == TSDB_ENCRYPT_KEY_STAT_NOT_EXIST ||
126,652,502✔
725
      encryptKeysLoaded == TSDB_ENCRYPT_KEY_STAT_DISABLED) {
726
    uDebug("CFG encryption key loaded successfully");
126,652,502✔
727
    return 0;
126,652,502✔
728
  }
UNCOV
729
  uDebug("CFG encryption key not loaded, waiting for %d ms", TD_ENCRYPT_KEY_WAIT_TIMEOUT_MS);
×
730

731
  const int32_t checkIntervalMs = 100;  // Check every 100ms
×
UNCOV
732
  int32_t       elapsedMs = 0;
×
733

734
  while (elapsedMs < TD_ENCRYPT_KEY_WAIT_TIMEOUT_MS) {
×
735
    // Check if CFG key is loaded
736
    encryptKeysLoaded = atomic_load_32(&tsEncryptKeysStatus);
×
UNCOV
737
    if (encryptKeysLoaded == TSDB_ENCRYPT_KEY_STAT_LOADED || encryptKeysLoaded == TSDB_ENCRYPT_KEY_STAT_NOT_EXIST ||
×
738
        encryptKeysLoaded == TSDB_ENCRYPT_KEY_STAT_DISABLED) {
739
      uDebug("CFG encryption key loaded successfully after %d ms", elapsedMs);
×
UNCOV
740
      return 0;
×
741
    }
742

743
    // Sleep for check interval
UNCOV
744
    taosMsleep(checkIntervalMs);
×
UNCOV
745
    elapsedMs += checkIntervalMs;
×
746
  }
747

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