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

taosdata / TDengine / #4910

30 Dec 2025 10:52AM UTC coverage: 65.864% (+0.3%) from 65.542%
#4910

push

travis-ci

web-flow
enh: drop multi-stream (#33962)

60 of 106 new or added lines in 4 files covered. (56.6%)

999 existing lines in 108 files now uncovered.

194877 of 295877 relevant lines covered (65.86%)

121300574.4 hits per line

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

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

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

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

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

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

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

109
  // Close temp file
110
  (void)taosCloseFile(&pFile);
×
111

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

116
_exit:
×
117
  if (pFile != NULL) {
×
118
    (void)taosCloseFile(&pFile);
×
119
  }
120
  if (code != 0) {
×
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;
×
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,810,982✔
143
  int32_t   code = 0;
9,810,982✔
144
  int32_t   lino = 0;
9,810,982✔
145
  TdFilePtr pFile = NULL;
9,810,982✔
146

147
  if (filepath == NULL || header == NULL) {
9,810,982✔
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,810,982✔
154
  if (pFile == NULL) {
9,810,833✔
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,810,833✔
161
  if (nread != sizeof(STdEncryptFileHeader)) {
9,810,495✔
162
    code = TSDB_CODE_FILE_CORRUPTED;
1,321,484✔
163
    TSDB_CHECK_CODE(code, lino, _exit);
1,321,484✔
164
  }
165

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

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

178
_exit:
9,808,916✔
179
  if (pFile != NULL) {
9,809,911✔
180
    (void)taosCloseFile(&pFile);
9,810,779✔
181
  }
182
  if (code != 0) {
9,811,168✔
183
    uDebug("%s failed at %s:%d since %s, file:%s", __func__, __FILE__, lino, tstrerror(code), filepath);
9,810,633✔
184
    terrno = code;
9,810,633✔
185
  }
186
  return code;
9,812,522✔
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,478,793✔
201
  int32_t lino = 0;
12,478,793✔
202
  
203
  if (filepath == NULL) {
12,478,793✔
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,478,793✔
211
    return false;
2,666,168✔
212
  }
213

214
  // Read header
215
  STdEncryptFileHeader header;
9,810,310✔
216
  int32_t              code = taosReadEncryptFileHeader(filepath, &header);
9,811,756✔
217

218
  if (code != 0) {
9,812,105✔
219
    return false;
9,812,105✔
220
  }
221

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

227
  return true;
×
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) {
60,170,093✔
249
  int32_t   code = 0;
60,170,093✔
250
  int32_t   lino = 0;
60,170,093✔
251
  TdFilePtr pFile = NULL;
60,170,093✔
252
  char      tempFile[PATH_MAX] = {0};
60,175,965✔
253
  char     *plainBuf = NULL;
60,167,992✔
254
  char     *encryptedBuf = NULL;
60,167,992✔
255

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

261
  snprintf(tempFile, sizeof(tempFile), "%s.tmp", filepath);
60,167,992✔
262

263
  // Check if CFG_KEY encryption is enabled
264
  if (tsCfgKey[0] == '\0') {
60,167,992✔
265
    // No encryption, write file normally with atomic operation
266
    pFile = taosOpenFile(tempFile, TD_FILE_CREATE | TD_FILE_WRITE | TD_FILE_TRUNC);
60,167,992✔
267
    if (pFile == NULL) {
60,161,886✔
UNCOV
268
      code = terrno;
×
UNCOV
269
      TSDB_CHECK_CODE(code, lino, _exit);
×
270
    }
271

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

277
    code = taosFsyncFile(pFile);
60,172,749✔
278
    TSDB_CHECK_CODE(code, lino, _exit);
60,164,766✔
279

280
    (void)taosCloseFile(&pFile);
60,164,766✔
281
    pFile = NULL;
60,146,765✔
282

283
    // Atomic replacement - rename temp file to target
284
    code = taosRenameFile(tempFile, filepath);
60,146,765✔
285
    TSDB_CHECK_CODE(code, lino, _exit);
60,151,439✔
286

287
    return 0;
60,151,439✔
288
  }
289

290
  // Encryption enabled - encrypt data first
291
  int32_t cryptedDataLen = ENCRYPTED_LEN(dataLen);
×
292

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

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

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

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

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

379
  *data = NULL;
12,478,877✔
380
  *dataLen = 0;
12,478,796✔
381
  // Check if file is encrypted
382
  bool isEncrypted = taosIsEncryptedFile(filepath, NULL);
12,478,803✔
383

384
  // Open file for reading
385
  pFile = taosOpenFile(filepath, TD_FILE_READ);
12,476,827✔
386
  if (pFile == NULL) {
12,478,276✔
387
    code = terrno;
2,666,168✔
388
    TSDB_CHECK_CODE(code, lino, _exit);
2,666,168✔
389
  }
390

391
  // Get file size
392
  int64_t fileSize = 0;
9,812,108✔
393
  code = taosFStatFile(pFile, &fileSize, NULL);
9,810,797✔
394
  TSDB_CHECK_CODE(code, lino, _exit);
9,809,376✔
395

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

401
  if (isEncrypted) {
9,809,376✔
402
    // File is encrypted - read header first
403
    int64_t nread = taosReadFile(pFile, &header, sizeof(STdEncryptFileHeader));
×
404
    if (nread != sizeof(STdEncryptFileHeader)) {
×
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) {
×
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;
×
417
    if (encryptedDataLen <= 0 || encryptedDataLen > fileSize) {
×
418
      code = TSDB_CODE_FILE_CORRUPTED;
×
419
      TSDB_CHECK_CODE(code, lino, _exit);
×
420
    }
421

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

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

434
    (void)taosCloseFile(&pFile);
×
435
    pFile = NULL;
×
436

437
    // Check if CFG_KEY is available
438
    if (tsCfgKey[0] == '\0') {
×
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 for CBC padding)
445
    plainContent = taosMemoryMalloc(encryptedDataLen);
×
446
    if (plainContent == NULL) {
×
447
      code = TSDB_CODE_OUT_OF_MEMORY;
×
448
      TSDB_CHECK_CODE(code, lino, _exit);
×
449
    }
450

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

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

466
    taosMemoryFree(fileContent);
×
467
    fileContent = NULL;
×
468

469
    // Return decrypted data (JSON parser will handle the content)
470
    // Note: plainContent already has padding zeros from decryption, which is fine for JSON
471
    *data = plainContent;
×
472
    *dataLen = encryptedDataLen;
×
473
    plainContent = NULL;  // Transfer ownership to caller
×
474

475
  } else {
476
    // File is not encrypted - read directly
477
    fileContent = taosMemoryMalloc(fileSize + 1);
9,809,376✔
478
    if (fileContent == NULL) {
9,810,401✔
479
      code = TSDB_CODE_OUT_OF_MEMORY;
×
480
      TSDB_CHECK_CODE(code, lino, _exit);
×
481
    }
482

483
    int64_t nread = taosReadFile(pFile, fileContent, fileSize);
9,810,401✔
484
    if (nread != fileSize) {
9,812,808✔
485
      code = (terrno != 0) ? terrno : TSDB_CODE_FILE_CORRUPTED;
×
486
      TSDB_CHECK_CODE(code, lino, _exit);
×
487
    }
488

489
    (void)taosCloseFile(&pFile);
9,812,808✔
490
    pFile = NULL;
9,810,414✔
491

492
    fileContent[fileSize] = '\0';
9,810,414✔
493

494
    // Return file content
495
    *data = fileContent;
9,811,605✔
496
    *dataLen = fileSize;
9,810,853✔
497
    fileContent = NULL;  // Transfer ownership to caller
9,809,529✔
498
  }
499

500
_exit:
12,475,697✔
501
  if (pFile != NULL) {
12,477,761✔
502
    (void)taosCloseFile(&pFile);
×
503
  }
504
  if (fileContent != NULL) {
12,477,761✔
505
    taosMemoryFree(fileContent);
×
506
  }
507
  if (plainContent != NULL) {
12,477,761✔
508
    taosMemoryFree(plainContent);
×
509
  }
510
  if (code != 0) {
12,477,761✔
511
    uError("%s failed at %s:%d since %s, file:%s", __func__, __FILE__, lino, tstrerror(code), filepath);
2,666,168✔
512
    terrno = code;
2,666,168✔
513
  }
514
  return code;
12,477,761✔
515
}
516

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

532
  if (filepath == NULL) {
×
533
    code = TSDB_CODE_INVALID_PARA;
×
534
    TSDB_CHECK_CODE(code, lino, _exit);
×
535
  }
536

537
  // Check if file exists
538
  if (!taosCheckExistFile(filepath)) {
×
539
    // File doesn't exist, nothing to do
540
    return 0;
×
541
  }
542

543
  // Check if file is already encrypted
544
  if (taosIsEncryptedFile(filepath, NULL)) {
×
545
    // Already encrypted, nothing to do
546
    return 0;
×
547
  }
548

549
  // Read plaintext file
550
  pFile = taosOpenFile(filepath, TD_FILE_READ);
×
551
  if (pFile == NULL) {
×
552
    code = terrno;
×
553
    TSDB_CHECK_CODE(code, lino, _exit);
×
554
  }
555

556
  int64_t fileSize = 0;
×
557
  code = taosFStatFile(pFile, &fileSize, NULL);
×
558
  TSDB_CHECK_CODE(code, lino, _exit);
×
559

560
  if (fileSize <= 0) {
×
561
    (void)taosCloseFile(&pFile);
×
562
    pFile = NULL;
×
563
    // Empty file, just skip it
564
    return 0;
×
565
  }
566

567
  plainData = taosMemoryMalloc(fileSize);
×
568
  if (plainData == NULL) {
×
569
    code = TSDB_CODE_OUT_OF_MEMORY;
×
570
    TSDB_CHECK_CODE(code, lino, _exit);
×
571
  }
572

573
  int64_t nread = taosReadFile(pFile, plainData, fileSize);
×
574
  if (nread != fileSize) {
×
575
    code = (terrno != 0) ? terrno : TSDB_CODE_FILE_CORRUPTED;
×
576
    TSDB_CHECK_CODE(code, lino, _exit);
×
577
  }
578

579
  (void)taosCloseFile(&pFile);
×
580
  pFile = NULL;
×
581

582
  // Encrypt the file using taosWriteCfgFile (which handles encryption and atomic write)
583
  code = taosWriteCfgFile(filepath, plainData, fileSize);
×
584
  TSDB_CHECK_CODE(code, lino, _exit);
×
585

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

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

620
  if (dataDir == NULL) {
×
621
    code = TSDB_CODE_INVALID_PARA;
×
622
    TSDB_CHECK_CODE(code, lino, _exit);
×
623
  }
624

625
  // Check if encryption is enabled
626
  if (tsCfgKey[0] == '\0') {
×
627
    // Encryption not enabled, nothing to do
628
    return 0;
×
629
  }
630

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

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

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

656
  snprintf(filepath, sizeof(filepath), "%s%smnode%ssync%sraft_config.json", dataDir, TD_DIRSEP, TD_DIRSEP, TD_DIRSEP);
×
657
  if (taosCheckExistFile(filepath) && !taosIsEncryptedFile(filepath, NULL)) {
×
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_store.json", dataDir, TD_DIRSEP, TD_DIRSEP, TD_DIRSEP);
×
664
  if (taosCheckExistFile(filepath) && !taosIsEncryptedFile(filepath, NULL)) {
×
665
    code = taosEncryptSingleCfgFile(filepath);
×
666
    TSDB_CHECK_CODE(code, lino, _exit);
×
667
    uInfo("successfully encrypted file %s", filepath);
×
668
  }
669

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

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

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

691
  uInfo("finished encrypting existing config files");
×
692

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

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

716
  if (tsSkipKeyCheckMode) {
128,323,289✔
717
    uDebug("skip encryption key verification in some special check mode");
6,142✔
718
    return 0;
6,142✔
719
  }
720

721
  int32_t encryptKeysLoaded = atomic_load_32(&tsEncryptKeysStatus);
128,317,147✔
722
  if (encryptKeysLoaded == TSDB_ENCRYPT_KEY_STAT_LOADED || encryptKeysLoaded == TSDB_ENCRYPT_KEY_STAT_NOT_EXIST ||
128,317,708✔
723
      encryptKeysLoaded == TSDB_ENCRYPT_KEY_STAT_DISABLED) {
724
    uDebug("CFG encryption key loaded successfully");
128,317,708✔
725
    return 0;
128,317,761✔
726
  }
727
  uDebug("CFG encryption key not loaded, waiting for %d ms", TD_ENCRYPT_KEY_WAIT_TIMEOUT_MS);
×
728

729
  const int32_t checkIntervalMs = 100;  // Check every 100ms
×
730
  int32_t       elapsedMs = 0;
×
731

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

741
    // Sleep for check interval
742
    taosMsleep(checkIntervalMs);
×
743
    elapsedMs += checkIntervalMs;
×
744
  }
745

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