SQLiteStorageBase.cpp (7025B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "Public/winget/SQLiteStorageBase.h" 5 #include "Public/winget/SQLiteMetadataTable.h" 6 #include "AppInstallerDateTime.h" 7 8 namespace AppInstaller::SQLite 9 { 10 namespace 11 { 12 static char const* const GetOpenDispositionString(SQLiteStorageBase::OpenDisposition disposition) 13 { 14 switch (disposition) 15 { 16 case SQLiteStorageBase::OpenDisposition::Read: 17 return "Read"; 18 case SQLiteStorageBase::OpenDisposition::ReadWrite: 19 return "ReadWrite"; 20 case SQLiteStorageBase::OpenDisposition::Immutable: 21 return "ImmutableRead"; 22 default: 23 return "Unknown"; 24 } 25 } 26 27 std::filesystem::path AddSuffix(const std::filesystem::path& source, std::wstring_view suffix) 28 { 29 std::filesystem::path result{ source }; 30 31 if (!suffix.empty()) 32 { 33 std::wstring filename = result.filename().wstring(); 34 filename += suffix; 35 result.replace_filename(std::move(filename)); 36 } 37 38 return result; 39 } 40 } 41 42 // One method for converting open disposition to proper open disposition 43 // another method for obtaining the right flags 44 void SQLiteStorageBase::SetLastWriteTime() 45 { 46 MetadataTable::SetNamedValue(m_dbconn, s_MetadataValueName_LastWriteTime, Utility::GetCurrentUnixEpoch()); 47 } 48 49 // Recording last write time based on MSDN documentation stating that time returns a POSIX epoch time and thus 50 // should be consistent across systems. 51 std::chrono::system_clock::time_point SQLiteStorageBase::GetLastWriteTime() const 52 { 53 int64_t lastWriteTime = MetadataTable::GetNamedValue<int64_t>(m_dbconn, s_MetadataValueName_LastWriteTime); 54 return Utility::ConvertUnixEpochToSystemClock(lastWriteTime); 55 } 56 57 std::string SQLiteStorageBase::GetDatabaseIdentifier() const 58 { 59 return MetadataTable::TryGetNamedValue<std::string>(m_dbconn, s_MetadataValueName_DatabaseIdentifier).value_or(std::string{}); 60 } 61 62 void SQLiteStorageBase::RenameSQLiteDatabase(const std::filesystem::path& source, const std::filesystem::path& destination, bool overwrite) 63 { 64 auto fileSuffixes = { L"", L"-journal", L"-wal" }; 65 66 THROW_WIN32_IF(ERROR_FILE_NOT_FOUND, !std::filesystem::exists(source)); 67 THROW_WIN32_IF(ERROR_DIRECTORY, std::filesystem::is_directory(source)); 68 69 if (overwrite) 70 { 71 for (const auto& suffix : fileSuffixes) 72 { 73 std::filesystem::path target = AddSuffix(destination, suffix); 74 75 if (std::filesystem::exists(target)) 76 { 77 std::filesystem::remove_all(target); 78 } 79 } 80 } 81 82 for (const auto& suffix : fileSuffixes) 83 { 84 std::filesystem::path target = AddSuffix(source, suffix); 85 86 if (std::filesystem::exists(target)) 87 { 88 std::filesystem::rename(target, AddSuffix(destination, suffix)); 89 } 90 } 91 } 92 93 SQLiteStorageBase::SQLiteStorageBase(const std::string& filePath, OpenDisposition disposition, Utility::ManagedFile&& file) : 94 m_indexFile(std::move(file)) 95 { 96 AICLI_LOG(Repo, Info, << "Opening database for " << GetOpenDispositionString(disposition) << " at '" << filePath << "'"); 97 switch (disposition) 98 { 99 case OpenDisposition::Read: 100 m_dbconn = SQLite::Connection::Create(filePath, SQLite::Connection::OpenDisposition::ReadOnly, SQLite::Connection::OpenFlags::None); 101 break; 102 case OpenDisposition::ReadWrite: 103 m_dbconn = SQLite::Connection::Create(filePath, SQLite::Connection::OpenDisposition::ReadWrite, SQLite::Connection::OpenFlags::None); 104 break; 105 case OpenDisposition::Immutable: 106 { 107 // Following the algorithm set forth at https://sqlite.org/uri.html [3.1] to convert to a URI path 108 // The execution order builds out the string so that it shouldn't require any moves (other than growing) 109 std::string target; 110 // Add an 'arbitrary' growth size to prevent the majority of needing to grow (adding 'file:/' and '?immutable=1') 111 target.reserve(filePath.size() + 20); 112 113 target += "file:"; 114 115 bool wasLastCharSlash = false; 116 117 if (filePath.size() >= 2 && filePath[1] == ':' && 118 ((filePath[0] >= 'a' && filePath[0] <= 'z') || 119 (filePath[0] >= 'A' && filePath[0] <= 'Z'))) 120 { 121 target += '/'; 122 wasLastCharSlash = true; 123 } 124 125 for (char c : filePath) 126 { 127 bool wasThisCharSlash = false; 128 switch (c) 129 { 130 case '?': target += "%3f"; break; 131 case '#': target += "%23"; break; 132 case '\\': 133 case '/': 134 { 135 wasThisCharSlash = true; 136 if (!wasLastCharSlash) 137 { 138 target += '/'; 139 } 140 break; 141 } 142 default: target += c; break; 143 } 144 145 wasLastCharSlash = wasThisCharSlash; 146 } 147 148 target += "?immutable=1"; 149 m_dbconn = SQLite::Connection::Create(filePath, SQLite::Connection::OpenDisposition::ReadOnly, SQLite::Connection::OpenFlags::Uri); 150 break; 151 } 152 default: 153 THROW_HR(E_UNEXPECTED); 154 } 155 156 m_version = Version::GetSchemaVersion(m_dbconn); 157 } 158 159 SQLiteStorageBase::SQLiteStorageBase(const std::string& target, const Version& version) : 160 m_dbconn(SQLite::Connection::Create(target, SQLite::Connection::OpenDisposition::Create)) 161 { 162 m_version = version; 163 MetadataTable::Create(m_dbconn); 164 165 // Write a new identifier for this database 166 GUID databaseIdentifier; 167 THROW_IF_FAILED(CoCreateGuid(&databaseIdentifier)); 168 std::ostringstream stream; 169 stream << databaseIdentifier; 170 MetadataTable::SetNamedValue(m_dbconn, s_MetadataValueName_DatabaseIdentifier, stream.str()); 171 } 172 173 SQLiteStorageBase::SQLiteStorageBase(const std::string& target, SQLiteStorageBase& source) : 174 m_dbconn(SQLite::Connection::Create(target, SQLite::Connection::OpenDisposition::Create)), 175 m_version(source.m_version) 176 { 177 std::string mainDatabase = "main"; 178 Backup backup = Backup::Create(m_dbconn, mainDatabase, source.m_dbconn, mainDatabase); 179 backup.Step(); 180 } 181 }