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