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