1 /*
2  * Copyright 2019 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "buildjson.hpp"
17 
18 #include "file_handler.hpp"
19 #include "fs.hpp"
20 #include "general_systemd.hpp"
21 
22 #include <algorithm>
23 #include <cstdio>
24 #include <exception>
25 #include <fstream>
26 #include <nlohmann/json.hpp>
27 #include <phosphor-logging/log.hpp>
28 #include <regex>
29 #include <sdbusplus/bus.hpp>
30 #include <string>
31 #include <vector>
32 
33 namespace ipmi_flash
34 {
35 
36 std::unique_ptr<TriggerableActionInterface>
37     buildFileSystemd(const nlohmann::json& data)
38 {
39     /* This type of action requires a path and unit, and optionally a mode. */
40     const auto& path = data.at("path");
41     const auto& unit = data.at("unit");
42 
43     /* the mode parameter is optional. */
44     std::string systemdMode = "replace";
45     const auto& mode = data.find("mode");
46     if (mode != data.end())
47     {
48         systemdMode = data.at("mode").get<std::string>();
49     }
50 
51     return SystemdWithStatusFile::CreateSystemdWithStatusFile(
52         sdbusplus::bus::new_default(), path, unit, systemdMode);
53 }
54 
55 std::unique_ptr<TriggerableActionInterface>
56     buildSystemd(const nlohmann::json& data)
57 {
58     /* This type of action requires a unit, and optionally a mode. */
59     const auto& unit = data.at("unit");
60 
61     /* the mode parameter is optional. */
62     std::string systemdMode = "replace";
63     const auto& mode = data.find("mode");
64     if (mode != data.end())
65     {
66         systemdMode = data.at("mode").get<std::string>();
67     }
68 
69     return SystemdNoFile::CreateSystemdNoFile(sdbusplus::bus::new_default(),
70                                               unit, systemdMode);
71 }
72 
73 std::vector<HandlerConfig> buildHandlerFromJson(const nlohmann::json& data)
74 {
75     std::vector<HandlerConfig> handlers;
76 
77     for (const auto& item : data)
78     {
79         try
80         {
81             HandlerConfig output;
82 
83             /* at() throws an exception when the key is not present. */
84             item.at("blob").get_to(output.blobId);
85 
86             /* name must be: /flash/... */
87             if (!std::regex_match(output.blobId, std::regex("^\\/flash\\/.+")))
88             {
89                 throw std::runtime_error("Invalid blob name: '" +
90                                          output.blobId +
91                                          "' must start with /flash/");
92             }
93 
94             /* handler is required. */
95             const auto& h = item.at("handler");
96             const std::string handlerType = h.at("type");
97             if (handlerType == "file")
98             {
99                 const auto& path = h.at("path");
100                 output.handler = std::make_unique<FileHandler>(path);
101             }
102             else
103             {
104                 throw std::runtime_error("Invalid handler type: " +
105                                          handlerType);
106             }
107 
108             /* actions are required (presently). */
109             const auto& a = item.at("actions");
110             std::unique_ptr<ActionPack> pack = std::make_unique<ActionPack>();
111 
112             /* It hasn't been fully determined if any action being optional is
113              * useful, so for now they will be required.
114              * TODO: Evaluate how the behaviors change if some actions are
115              * missing, does the code just assume it was successful?  I would
116              * think not.
117              */
118             const auto& prep = a.at("preparation");
119             const std::string prepareType = prep.at("type");
120             if (prepareType == "systemd")
121             {
122                 pack->preparation = std::move(buildSystemd(prep));
123             }
124             else
125             {
126                 throw std::runtime_error("Invalid preparation type: " +
127                                          prepareType);
128             }
129 
130             const auto& verify = a.at("verification");
131             const std::string verifyType = verify.at("type");
132             if (verifyType == "fileSystemdVerify")
133             {
134                 pack->verification = std::move(buildFileSystemd(verify));
135             }
136             else
137             {
138                 throw std::runtime_error("Invalid verification type:" +
139                                          verifyType);
140             }
141 
142             const auto& update = a.at("update");
143             const std::string updateType = update.at("type");
144             if (updateType == "reboot")
145             {
146                 pack->update = SystemdNoFile::CreateSystemdNoFile(
147                     sdbusplus::bus::new_default(), "reboot.target",
148                     "replace-irreversibly");
149             }
150             else if (updateType == "fileSystemdUpdate")
151             {
152                 pack->update = std::move(buildFileSystemd(update));
153             }
154             else if (updateType == "systemd")
155             {
156                 pack->update = std::move(buildSystemd(update));
157             }
158             else
159             {
160                 throw std::runtime_error("Invalid update type: " + updateType);
161             }
162 
163             output.actions = std::move(pack);
164             handlers.push_back(std::move(output));
165         }
166         catch (const std::exception& e)
167         {
168             /* TODO: Once phosphor-logging supports unit-test injection, fix
169              * this to log.
170              */
171             std::fprintf(stderr,
172                          "Excepted building HandlerConfig from json: %s\n",
173                          e.what());
174         }
175     }
176 
177     return handlers;
178 }
179 
180 std::vector<HandlerConfig> BuildHandlerConfigs(const std::string& directory)
181 {
182     using namespace phosphor::logging;
183 
184     std::vector<HandlerConfig> output;
185 
186     std::vector<std::string> jsonPaths = GetJsonList(directory);
187 
188     for (const auto& path : jsonPaths)
189     {
190         std::ifstream jsonFile(path);
191         if (!jsonFile.is_open())
192         {
193             log<level::ERR>("Unable to open json file",
194                             entry("PATH=%s", path.c_str()));
195             continue;
196         }
197 
198         auto data = nlohmann::json::parse(jsonFile, nullptr, false);
199         if (data.is_discarded())
200         {
201             log<level::ERR>("Parsing json failed",
202                             entry("PATH=%s", path.c_str()));
203             continue;
204         }
205 
206         std::vector<HandlerConfig> configs = buildHandlerFromJson(data);
207         std::move(configs.begin(), configs.end(), std::back_inserter(output));
208     }
209 
210     return output;
211 }
212 
213 } // namespace ipmi_flash
214