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