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