1 #include "config.h"
2 
3 #include "version.hpp"
4 
5 #include "xyz/openbmc_project/Common/error.hpp"
6 
7 #include <openssl/evp.h>
8 
9 #include <phosphor-logging/elog-errors.hpp>
10 #include <phosphor-logging/lg2.hpp>
11 
12 #include <fstream>
13 #include <iostream>
14 #include <sstream>
15 #include <stdexcept>
16 #include <string>
17 
18 namespace phosphor
19 {
20 namespace software
21 {
22 namespace manager
23 {
24 
25 PHOSPHOR_LOG2_USING;
26 using namespace phosphor::logging;
27 using Argument = xyz::openbmc_project::common::InvalidArgument;
28 using namespace sdbusplus::error::xyz::openbmc_project::common;
29 
getValue(const std::string & manifestFilePath,std::string key)30 std::string Version::getValue(const std::string& manifestFilePath,
31                               std::string key)
32 {
33     std::vector<std::string> values = getRepeatedValues(manifestFilePath, key);
34     if (values.empty())
35     {
36         return std::string{};
37     }
38     if (values.size() > 1)
39     {
40         error("Multiple values found in MANIFEST file for key: {KEY}", "KEY",
41               key);
42     }
43     return values.at(0);
44 }
45 
getRepeatedValues(const std::string & manifestFilePath,std::string key)46 std::vector<std::string> Version::getRepeatedValues(
47     const std::string& manifestFilePath, std::string key)
48 {
49     key = key + "=";
50     auto keySize = key.length();
51 
52     if (manifestFilePath.empty())
53     {
54         error("ManifestFilePath is empty.");
55         elog<InvalidArgument>(
56             Argument::ARGUMENT_NAME("manifestFilePath"),
57             Argument::ARGUMENT_VALUE(manifestFilePath.c_str()));
58     }
59 
60     std::vector<std::string> values{};
61     std::ifstream efile;
62     std::string line;
63     efile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
64 
65     // Too many GCC bugs (53984, 66145) to do this the right way...
66     try
67     {
68         efile.open(manifestFilePath);
69         while (getline(efile, line))
70         {
71             if (!line.empty() && line.back() == '\r')
72             {
73                 // If the manifest has CRLF line terminators, e.g. is created on
74                 // Windows, the line will contain \r at the end, remove it.
75                 line.pop_back();
76             }
77             if (line.compare(0, keySize, key) == 0)
78             {
79                 values.push_back(line.substr(keySize));
80             }
81         }
82         efile.close();
83     }
84     catch (const std::exception& e)
85     {
86         if (!efile.eof())
87         {
88             error("Error occurred when reading MANIFEST file: {ERROR}", "KEY",
89                   key, "ERROR", e);
90         }
91     }
92 
93     if (values.empty())
94     {
95         info("No values found in MANIFEST file for key: {KEY}", "KEY", key);
96     }
97 
98     return values;
99 }
100 
101 using EVP_MD_CTX_Ptr =
102     std::unique_ptr<EVP_MD_CTX, decltype(&::EVP_MD_CTX_free)>;
103 
getId(const std::string & version)104 std::string Version::getId(const std::string& version)
105 {
106     if (version.empty())
107     {
108         error("Version is empty.");
109         elog<InvalidArgument>(Argument::ARGUMENT_NAME("Version"),
110                               Argument::ARGUMENT_VALUE(version.c_str()));
111     }
112 
113     std::array<unsigned char, EVP_MAX_MD_SIZE> digest{};
114     EVP_MD_CTX_Ptr ctx(EVP_MD_CTX_new(), &::EVP_MD_CTX_free);
115 
116     EVP_DigestInit(ctx.get(), EVP_sha512());
117     EVP_DigestUpdate(ctx.get(), version.c_str(), strlen(version.c_str()));
118     EVP_DigestFinal(ctx.get(), digest.data(), nullptr);
119 
120     // We are only using the first 8 characters.
121     char mdString[9];
122     snprintf(mdString, sizeof(mdString), "%02x%02x%02x%02x",
123              (unsigned int)digest[0], (unsigned int)digest[1],
124              (unsigned int)digest[2], (unsigned int)digest[3]);
125 
126     return mdString;
127 }
128 
getBMCMachine(const std::string & releaseFilePath)129 std::string Version::getBMCMachine(const std::string& releaseFilePath)
130 {
131     std::string machineKey = "OPENBMC_TARGET_MACHINE=";
132     std::string machine{};
133     std::ifstream efile(releaseFilePath);
134     std::string line;
135 
136     while (getline(efile, line))
137     {
138         if (line.substr(0, machineKey.size()).find(machineKey) !=
139             std::string::npos)
140         {
141             std::size_t pos = line.find_first_of('"') + 1;
142             machine = line.substr(pos, line.find_last_of('"') - pos);
143             break;
144         }
145     }
146 
147     if (machine.empty())
148     {
149         error("Unable to find OPENBMC_TARGET_MACHINE");
150         elog<InternalFailure>();
151     }
152 
153     return machine;
154 }
155 
getBMCExtendedVersion(const std::string & releaseFilePath)156 std::string Version::getBMCExtendedVersion(const std::string& releaseFilePath)
157 {
158     std::string extendedVersionKey = "EXTENDED_VERSION=";
159     std::string extendedVersionValue{};
160     std::string extendedVersion{};
161     std::ifstream efile(releaseFilePath);
162     std::string line;
163 
164     while (getline(efile, line))
165     {
166         if (line.substr(0, extendedVersionKey.size())
167                 .find(extendedVersionKey) != std::string::npos)
168         {
169             extendedVersionValue = line.substr(extendedVersionKey.size());
170             std::size_t pos = extendedVersionValue.find_first_of('"') + 1;
171             extendedVersion = extendedVersionValue.substr(
172                 pos, extendedVersionValue.find_last_of('"') - pos);
173             break;
174         }
175     }
176 
177     return extendedVersion;
178 }
179 
getBMCVersion(const std::string & releaseFilePath)180 std::string Version::getBMCVersion(const std::string& releaseFilePath)
181 {
182     std::string versionKey = "VERSION_ID=";
183     std::string versionValue{};
184     std::string version{};
185     std::ifstream efile;
186     std::string line;
187     efile.open(releaseFilePath);
188 
189     while (getline(efile, line))
190     {
191         if (line.substr(0, versionKey.size()).find(versionKey) !=
192             std::string::npos)
193         {
194             // Support quoted and unquoted values
195             // 1. Remove the versionKey so that we process the value only.
196             versionValue = line.substr(versionKey.size());
197 
198             // 2. Look for a starting quote, then increment the position by 1 to
199             //    skip the quote character. If no quote is found,
200             //    find_first_of() returns npos (-1), which by adding +1 sets pos
201             //    to 0 (beginning of unquoted string).
202             std::size_t pos = versionValue.find_first_of('"') + 1;
203 
204             // 3. Look for ending quote, then decrease the position by pos to
205             //    get the size of the string up to before the ending quote. If
206             //    no quote is found, find_last_of() returns npos (-1), and pos
207             //    is 0 for the unquoted case, so substr() is called with a len
208             //    parameter of npos (-1) which according to the documentation
209             //    indicates to use all characters until the end of the string.
210             version =
211                 versionValue.substr(pos, versionValue.find_last_of('"') - pos);
212             break;
213         }
214     }
215     efile.close();
216 
217     if (version.empty())
218     {
219         error("BMC current version is empty");
220         elog<InternalFailure>();
221     }
222 
223     return version;
224 }
225 
delete_()226 void Delete::delete_()
227 {
228     if (parent.eraseCallback)
229     {
230         parent.eraseCallback(parent.id);
231     }
232 }
233 
234 } // namespace manager
235 } // namespace software
236 } // namespace phosphor
237