PackageInstalledStatus.cpp (13032B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "Public/winget/InstalledStatus.h" 5 #include "Public/winget/PackageVersionSelection.h" 6 #include <winget/Filesystem.h> 7 8 using namespace AppInstaller::Settings; 9 using namespace std::chrono_literals; 10 11 namespace AppInstaller::Repository 12 { 13 namespace 14 { 15 HRESULT CheckInstalledLocationStatus(const std::filesystem::path& installedLocation) 16 { 17 HRESULT installLocationStatus = WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE; 18 if (!installedLocation.empty()) 19 { 20 // Use the none throw version, if the directory cannot be reached, it's treated as not found and later file checks are not performed. 21 std::error_code error; 22 installLocationStatus = 23 std::filesystem::exists(installedLocation, error) && std::filesystem::is_directory(installedLocation, error) ? 24 WINGET_INSTALLED_STATUS_INSTALL_LOCATION_FOUND : 25 WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_FOUND; 26 } 27 28 return installLocationStatus; 29 } 30 31 // Map to cache already calculated file hashes. 32 struct FilePathComparator 33 { 34 bool operator()(const std::filesystem::path& a, const std::filesystem::path& b) const 35 { 36 if (std::filesystem::equivalent(a, b)) 37 { 38 return false; 39 } 40 41 return a < b; 42 } 43 }; 44 using FileHashMap = std::map<std::filesystem::path, Utility::SHA256::HashBuffer, FilePathComparator>; 45 46 HRESULT CheckInstalledFileStatus( 47 const std::filesystem::path& filePath, 48 const Utility::SHA256::HashBuffer& expectedHash, 49 FileHashMap& fileHashes) 50 { 51 HRESULT fileStatus = WINGET_INSTALLED_STATUS_FILE_NOT_FOUND; 52 try 53 { 54 if (std::filesystem::exists(filePath) && std::filesystem::is_regular_file(filePath)) 55 { 56 fileStatus = WINGET_INSTALLED_STATUS_FILE_FOUND_WITHOUT_HASH_CHECK; 57 if (!expectedHash.empty()) 58 { 59 auto itr = fileHashes.find(filePath); 60 if (itr == fileHashes.end()) 61 { 62 // If not found in cache, compute the hash. 63 std::ifstream in{ filePath, std::ifstream::binary }; 64 itr = fileHashes.emplace(filePath, Utility::SHA256::ComputeHash(in)).first; 65 } 66 67 fileStatus = Utility::SHA256::AreEqual(expectedHash, itr->second) ? 68 WINGET_INSTALLED_STATUS_FILE_HASH_MATCH : WINGET_INSTALLED_STATUS_FILE_HASH_MISMATCH; 69 } 70 } 71 } 72 catch (...) 73 { 74 fileStatus = WINGET_INSTALLED_STATUS_FILE_ACCESS_ERROR; 75 } 76 77 return fileStatus; 78 } 79 80 std::vector<InstallerInstalledStatus> CheckInstalledStatusInternal( 81 const std::shared_ptr<ICompositePackage>& package, 82 InstalledStatusType checkTypes) 83 { 84 using namespace AppInstaller::Manifest; 85 86 std::vector<InstallerInstalledStatus> result; 87 bool checkFileHash = false; 88 std::shared_ptr<IPackageVersion> installedVersion = GetInstalledVersion(package); 89 std::shared_ptr<IPackageVersion> availableVersion; 90 FileHashMap fileHashes; 91 92 // Variables for metadata from installed version. 93 InstallerTypeEnum installedType = InstallerTypeEnum::Unknown; 94 ScopeEnum installedScope = ScopeEnum::Unknown; 95 std::filesystem::path installedLocation; 96 std::string installedLocale; 97 Utility::Architecture installedArchitecture = Utility::Architecture::Unknown; 98 HRESULT installedLocationStatus = WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE; 99 100 std::shared_ptr<IPackageVersionCollection> availableVersions = GetAvailableVersionsForInstalledVersion(package); 101 102 // Prepare installed metadata from installed version. 103 // Determine the available package version to be used for installed status checking. 104 // Only perform file hash check if we find an available version that matches installed version. 105 if (installedVersion) 106 { 107 // Installed metadata. 108 auto installedMetadata = installedVersion->GetMetadata(); 109 installedType = ConvertToInstallerTypeEnum(installedMetadata[PackageVersionMetadata::InstalledType]); 110 installedScope = ConvertToScopeEnum(installedMetadata[PackageVersionMetadata::InstalledScope]); 111 installedLocation = Filesystem::GetExpandedPath(installedMetadata[PackageVersionMetadata::InstalledLocation]); 112 installedLocale = installedMetadata[PackageVersionMetadata::InstalledLocale]; 113 installedArchitecture = Utility::ConvertToArchitectureEnum(installedMetadata[PackageVersionMetadata::InstalledArchitecture]); 114 installedLocationStatus = CheckInstalledLocationStatus(installedLocation); 115 116 // Determine available version. 117 Utility::Version installedVersionAsVersion{ installedVersion->GetProperty(PackageVersionProperty::Version) }; 118 auto installedChannel = installedVersion->GetProperty(PackageVersionProperty::Channel); 119 PackageVersionKey versionKey; 120 versionKey.Channel = installedChannel.get(); 121 122 if (installedVersionAsVersion.IsApproximate()) 123 { 124 // Use the base version as available version if installed version is mapped to be an approximate. 125 versionKey.Version = installedVersionAsVersion.GetBaseVersion().ToString(); 126 availableVersion = availableVersions->GetVersion(versionKey); 127 // It's unexpected if the installed version is already mapped to some version. 128 THROW_HR_IF(E_UNEXPECTED, !availableVersion); 129 } 130 else 131 { 132 versionKey.Version = installedVersionAsVersion.ToString(); 133 availableVersion = availableVersions->GetVersion(versionKey); 134 if (availableVersion) 135 { 136 checkFileHash = true; 137 } 138 } 139 } 140 141 if (!availableVersion) 142 { 143 // No installed version, or installed version not found in available versions, 144 // then attempt to check installed status using latest version. 145 availableVersion = availableVersions->GetLatestVersion(); 146 THROW_HR_IF(E_UNEXPECTED, !availableVersion); 147 } 148 149 auto manifest = availableVersion->GetManifest(); 150 for (auto const& installer : manifest.Installers) 151 { 152 InstallerInstalledStatus installerStatus; 153 installerStatus.Installer = installer; 154 155 // ARP related checks 156 if (WI_IsAnyFlagSet(checkTypes, InstalledStatusType::AllAppsAndFeaturesEntryChecks)) 157 { 158 bool isMatchingInstaller = 159 installedVersion && 160 IsInstallerTypeCompatible(installedType, installer.EffectiveInstallerType()) && 161 (installedScope == ScopeEnum::Unknown || installer.Scope == ScopeEnum::Unknown || installedScope == installer.Scope) && // Treat unknown scope as compatible 162 (installedArchitecture == Utility::Architecture::Unknown || installer.Arch == Utility::Architecture::Neutral || installedArchitecture == installer.Arch) && // Treat unknown installed architecture as compatible 163 (installedLocale.empty() || installer.Locale.empty() || !Locale::IsWellFormedBcp47Tag(installedLocale) || Locale::GetDistanceOfLanguage(installedLocale, installer.Locale) >= Locale::MinimumDistanceScoreAsCompatibleMatch); // Treat invalid locale as compatible 164 165 // ARP entry status 166 if (WI_IsFlagSet(checkTypes, InstalledStatusType::AppsAndFeaturesEntry)) 167 { 168 installerStatus.Status.emplace_back( 169 InstalledStatusType::AppsAndFeaturesEntry, 170 "", 171 isMatchingInstaller ? WINGET_INSTALLED_STATUS_ARP_ENTRY_FOUND : WINGET_INSTALLED_STATUS_ARP_ENTRY_NOT_FOUND); 172 } 173 174 // ARP install location status 175 if (isMatchingInstaller && WI_IsFlagSet(checkTypes, InstalledStatusType::AppsAndFeaturesEntryInstallLocation)) 176 { 177 installerStatus.Status.emplace_back( 178 InstalledStatusType::AppsAndFeaturesEntryInstallLocation, 179 installedLocation.u8string(), 180 installedLocationStatus); 181 } 182 183 // ARP install location files 184 if (isMatchingInstaller && 185 installedLocationStatus == WINGET_INSTALLED_STATUS_INSTALL_LOCATION_FOUND && 186 WI_IsFlagSet(checkTypes, InstalledStatusType::AppsAndFeaturesEntryInstallLocationFile)) 187 { 188 for (auto const& file : installer.InstallationMetadata.Files) 189 { 190 std::filesystem::path filePath = installedLocation / Utility::ConvertToUTF16(file.RelativeFilePath); 191 auto fileStatus = CheckInstalledFileStatus(filePath, checkFileHash ? file.FileSha256 : Utility::SHA256::HashBuffer{}, fileHashes); 192 193 installerStatus.Status.emplace_back( 194 InstalledStatusType::AppsAndFeaturesEntryInstallLocationFile, 195 filePath.u8string(), 196 fileStatus); 197 } 198 } 199 } 200 201 // Default install location related checks 202 if (WI_IsAnyFlagSet(checkTypes, InstalledStatusType::AllDefaultInstallLocationChecks) && installer.InstallationMetadata.HasData()) 203 { 204 auto defaultInstalledLocation = Filesystem::GetExpandedPath(installer.InstallationMetadata.DefaultInstallLocation); 205 HRESULT defaultInstalledLocationStatus = CheckInstalledLocationStatus(defaultInstalledLocation); 206 207 // Default install location status 208 if (WI_IsFlagSet(checkTypes, InstalledStatusType::DefaultInstallLocation)) 209 { 210 installerStatus.Status.emplace_back( 211 InstalledStatusType::DefaultInstallLocation, 212 defaultInstalledLocation.u8string(), 213 defaultInstalledLocationStatus); 214 } 215 216 // Default install location files 217 if (defaultInstalledLocationStatus == WINGET_INSTALLED_STATUS_INSTALL_LOCATION_FOUND && 218 WI_IsFlagSet(checkTypes, InstalledStatusType::DefaultInstallLocationFile)) 219 { 220 for (auto const& file : installer.InstallationMetadata.Files) 221 { 222 std::filesystem::path filePath = defaultInstalledLocation / Utility::ConvertToUTF16(file.RelativeFilePath); 223 auto fileStatus = CheckInstalledFileStatus(filePath, checkFileHash ? file.FileSha256 : Utility::SHA256::HashBuffer{}, fileHashes); 224 225 installerStatus.Status.emplace_back( 226 InstalledStatusType::DefaultInstallLocationFile, 227 filePath.u8string(), 228 fileStatus); 229 } 230 } 231 } 232 233 if (!installerStatus.Status.empty()) 234 { 235 result.emplace_back(std::move(installerStatus)); 236 } 237 } 238 239 return result; 240 } 241 } 242 243 std::vector<InstallerInstalledStatus> CheckPackageInstalledStatus(const std::shared_ptr<ICompositePackage>& package, InstalledStatusType checkTypes) 244 { 245 return CheckInstalledStatusInternal(package, checkTypes); 246 } 247 }