Skip to content

Commit c1dabc9

Browse files
author
Martin Vogel
committed
fix(store): accept lowercase drive letters in root_path integrity check
The projects.root_path integrity check only accepted '/' or an uppercase 'A'-'Z' first character. On Windows, drive letters are commonly lowercase (c:/repo, y:/share), so such a path was flagged store.corrupt and the DB was auto-deleted on open — a likely cause of the mapped-drive DB deletion in #227/#367. Accept 'a'-'z' as well. Relates to #227, #367.
1 parent 6840457 commit c1dabc9

2 files changed

Lines changed: 37 additions & 36 deletions

File tree

src/store/store.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -684,12 +684,16 @@ bool cbm_store_check_integrity(cbm_store_t *s) {
684684
sqlite3_finalize(stmt);
685685

686686
if (ok) {
687-
/* Check that root_path in projects table starts with '/' or a drive letter.
688-
* Corrupt DBs often have numeric strings like "826" in root_path. */
687+
/* Check that root_path in projects table starts with '/' or a drive
688+
* letter. Corrupt DBs often have numeric strings like "826" in
689+
* root_path. Drive letters may be upper- OR lower-case on Windows
690+
* (e.g. "c:/repo", "y:/share") — rejecting lowercase here flagged
691+
* valid Windows paths as corrupt and deleted the DB (#227/#367). */
689692
rc = sqlite3_prepare_v2(s->db,
690693
"SELECT root_path FROM projects WHERE root_path != '' "
691694
"AND NOT (substr(root_path, 1, 1) = '/' "
692-
"OR (substr(root_path, 1, 1) BETWEEN 'A' AND 'Z')) LIMIT 1;",
695+
"OR (substr(root_path, 1, 1) BETWEEN 'A' AND 'Z') "
696+
"OR (substr(root_path, 1, 1) BETWEEN 'a' AND 'z')) LIMIT 1;",
693697
CBM_NOT_FOUND, &stmt, NULL);
694698
if (rc == SQLITE_OK) {
695699
if (sqlite3_step(stmt) == SQLITE_ROW) {

tests/test_store_nodes.c

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,18 @@ TEST(store_integrity_corrupt_bad_path) {
974974
PASS();
975975
}
976976

977+
TEST(store_integrity_windows_lowercase_drive_issue367) {
978+
/* Windows drive letters may be lower- or upper-case; a lowercase drive
979+
* path must NOT be treated as corrupt. Previously the check only accepted
980+
* 'A'..'Z', so "c:/repo" was flagged and the DB auto-deleted (#227/#367). */
981+
cbm_store_t *s = cbm_store_open_memory();
982+
ASSERT_NOT_NULL(s);
983+
cbm_store_upsert_project(s, "lc-drive", "c:/Users/dev/repo");
984+
ASSERT_TRUE(cbm_store_check_integrity(s));
985+
cbm_store_close(s);
986+
PASS();
987+
}
988+
977989
TEST(store_integrity_corrupt_too_many_rows) {
978990
/* Simulate corruption: >5 rows in projects table */
979991
cbm_store_t *s = cbm_store_open_memory();
@@ -1005,10 +1017,8 @@ TEST(store_node_null_project) {
10051017
ASSERT_NOT_NULL(s);
10061018

10071019
/* Upsert with NULL project — should fail gracefully */
1008-
cbm_node_t n = {.project = NULL,
1009-
.label = "Function",
1010-
.name = "Foo",
1011-
.qualified_name = "null.Foo"};
1020+
cbm_node_t n = {
1021+
.project = NULL, .label = "Function", .name = "Foo", .qualified_name = "null.Foo"};
10121022
int64_t id = cbm_store_upsert_node(s, &n);
10131023
/* Either returns error or silently succeeds; must not crash */
10141024
(void)id;
@@ -1022,10 +1032,7 @@ TEST(store_node_null_qn) {
10221032
cbm_store_upsert_project(s, "test", "/tmp/test");
10231033

10241034
/* Upsert with NULL qualified_name */
1025-
cbm_node_t n = {.project = "test",
1026-
.label = "Function",
1027-
.name = "Bar",
1028-
.qualified_name = NULL};
1035+
cbm_node_t n = {.project = "test", .label = "Function", .name = "Bar", .qualified_name = NULL};
10291036
int64_t id = cbm_store_upsert_node(s, &n);
10301037
/* Must not crash regardless of return value */
10311038
(void)id;
@@ -1085,10 +1092,8 @@ TEST(store_find_by_qn_not_found) {
10851092
cbm_store_upsert_project(s, "test", "/tmp/test");
10861093

10871094
/* Insert a node so the store is non-empty */
1088-
cbm_node_t n = {.project = "test",
1089-
.label = "Function",
1090-
.name = "Exists",
1091-
.qualified_name = "test.Exists"};
1095+
cbm_node_t n = {
1096+
.project = "test", .label = "Function", .name = "Exists", .qualified_name = "test.Exists"};
10921097
cbm_store_upsert_node(s, &n);
10931098

10941099
/* Search for a non-existent QN */
@@ -1284,22 +1289,14 @@ TEST(store_delete_by_label_verify_remaining) {
12841289
cbm_store_t *s = cbm_store_open_memory();
12851290
cbm_store_upsert_project(s, "test", "/tmp/test");
12861291

1287-
cbm_node_t n1 = {.project = "test",
1288-
.label = "Function",
1289-
.name = "FuncA",
1290-
.qualified_name = "test.FuncA"};
1291-
cbm_node_t n2 = {.project = "test",
1292-
.label = "Class",
1293-
.name = "ClassB",
1294-
.qualified_name = "test.ClassB"};
1295-
cbm_node_t n3 = {.project = "test",
1296-
.label = "Function",
1297-
.name = "FuncC",
1298-
.qualified_name = "test.FuncC"};
1299-
cbm_node_t n4 = {.project = "test",
1300-
.label = "Method",
1301-
.name = "MethodD",
1302-
.qualified_name = "test.MethodD"};
1292+
cbm_node_t n1 = {
1293+
.project = "test", .label = "Function", .name = "FuncA", .qualified_name = "test.FuncA"};
1294+
cbm_node_t n2 = {
1295+
.project = "test", .label = "Class", .name = "ClassB", .qualified_name = "test.ClassB"};
1296+
cbm_node_t n3 = {
1297+
.project = "test", .label = "Function", .name = "FuncC", .qualified_name = "test.FuncC"};
1298+
cbm_node_t n4 = {
1299+
.project = "test", .label = "Method", .name = "MethodD", .qualified_name = "test.MethodD"};
13031300
cbm_store_upsert_node(s, &n1);
13041301
cbm_store_upsert_node(s, &n2);
13051302
cbm_store_upsert_node(s, &n3);
@@ -1471,11 +1468,10 @@ TEST(store_node_properties_special_chars) {
14711468
cbm_store_upsert_project(s, "test", "/tmp/test");
14721469

14731470
/* JSON with quotes, backslashes, unicode, newlines */
1474-
const char *props =
1475-
"{\"desc\":\"line1\\nline2\","
1476-
"\"path\":\"C:\\\\Users\\\\test\","
1477-
"\"emoji\":\"\\u2603\","
1478-
"\"nested\":{\"key\":\"val with \\\"quotes\\\"\"}}";
1471+
const char *props = "{\"desc\":\"line1\\nline2\","
1472+
"\"path\":\"C:\\\\Users\\\\test\","
1473+
"\"emoji\":\"\\u2603\","
1474+
"\"nested\":{\"key\":\"val with \\\"quotes\\\"\"}}";
14791475

14801476
cbm_node_t n = {.project = "test",
14811477
.label = "Function",
@@ -1548,6 +1544,7 @@ SUITE(store_nodes) {
15481544
RUN_TEST(store_integrity_clean);
15491545
RUN_TEST(store_integrity_empty);
15501546
RUN_TEST(store_integrity_corrupt_bad_path);
1547+
RUN_TEST(store_integrity_windows_lowercase_drive_issue367);
15511548
RUN_TEST(store_integrity_corrupt_too_many_rows);
15521549
RUN_TEST(store_integrity_null_check);
15531550
RUN_TEST(store_project_crud);

0 commit comments

Comments
 (0)