Build a SQLite "file:" URI with immutable=1 from a filesystem path. * immutable=1 bypasses WAL and locking and reads the main DB file directly — * used only as a fallback for read-only filesystems where the wal-index * (-shm) cannot be created. URI-special characters are percent-encoded. * Windows backslashes are normalized to '/', and a leading '/' is inserted * for drive-letter paths so the
| 679 | * for drive-letter paths so the drive is not parsed as a URI authority. |
| 680 | * Returns false if the output buffer is too small. */ |
| 681 | static bool build_immutable_uri(const char *path, char *out, size_t out_sz) { |
| 682 | static const char PREFIX[] = "file://"; |
| 683 | static const char SUFFIX[] = "?immutable=1"; |
| 684 | static const char HEX[] = "0123456789ABCDEF"; |
| 685 | size_t prefix_len = sizeof(PREFIX) - 1; |
| 686 | size_t suffix_len = sizeof(SUFFIX) - 1; |
| 687 | if (prefix_len + 1 > out_sz) { |
| 688 | return false; |
| 689 | } |
| 690 | memcpy(out, PREFIX, prefix_len); |
| 691 | size_t pos = prefix_len; |
| 692 | |
| 693 | /* Ensure the path component begins with '/' (POSIX absolute paths already |
| 694 | * do; Windows "C:\..." gets a leading '/' -> "/C:/..."). */ |
| 695 | if (path[0] != '/') { |
| 696 | if (pos + 1 >= out_sz) { |
| 697 | return false; |
| 698 | } |
| 699 | out[pos++] = '/'; |
| 700 | } |
| 701 | |
| 702 | for (const unsigned char *p = (const unsigned char *)path; *p != '\0'; p++) { |
| 703 | unsigned char c = *p; |
| 704 | if (c == '\\') { |
| 705 | c = '/'; /* normalize Windows separators */ |
| 706 | } |
| 707 | bool safe = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || |
| 708 | c == '/' || c == '.' || c == '-' || c == '_' || c == '~' || c == ':'; |
| 709 | if (safe) { |
| 710 | if (pos + 1 >= out_sz) { |
| 711 | return false; |
| 712 | } |
| 713 | out[pos++] = (char)c; |
| 714 | } else { |
| 715 | if (pos + 3 >= out_sz) { |
| 716 | return false; |
| 717 | } |
| 718 | out[pos++] = '%'; |
| 719 | out[pos++] = HEX[(c >> 4) & 0xF]; |
| 720 | out[pos++] = HEX[c & 0xF]; |
| 721 | } |
| 722 | } |
| 723 | |
| 724 | if (pos + suffix_len + 1 > out_sz) { |
| 725 | return false; |
| 726 | } |
| 727 | memcpy(out + pos, SUFFIX, suffix_len); |
| 728 | pos += suffix_len; |
| 729 | out[pos] = '\0'; |
| 730 | return true; |
| 731 | } |
| 732 | |
| 733 | cbm_store_t *cbm_store_open_path_query(const char *db_path) { |
| 734 | if (!db_path) { |
no outgoing calls
no test coverage detected