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 #include "skip_action.hpp"
22 
23 #include <algorithm>
24 #include <cstdio>
25 #include <exception>
26 #include <fstream>
27 #include <nlohmann/json.hpp>
28 #include <phosphor-logging/log.hpp>
29 #include <regex>
30 #include <sdbusplus/bus.hpp>
31 #include <string>
32 #include <vector>
33 
34 namespace ipmi_flash
35 {
36 
37 std::unique_ptr<TriggerableActionInterface>
38     buildFileSystemd(const nlohmann::json& data)
39 {
40     /* This type of action requires a path and unit, and optionally a mode. */
41     const auto& path = data.at("path");
42     const auto& unit = data.at("unit");
43 
44     /* the mode parameter is optional. */
45     std::string systemdMode = "replace";
46     const auto& mode = data.find("mode");
47     if (mode != data.end())
48     {
49         systemdMode = data.at("mode").get<std::string>();
50     }
51 
52     return SystemdWithStatusFile::CreateSystemdWithStatusFile(
53         sdbusplus::bus::new_default(), path, unit, systemdMode);
54 }
55 
56 std::unique_ptr<TriggerableActionInterface>
57     buildSystemd(const nlohmann::json& data)
58 {
59     /* This type of action requires a unit, and optionally a mode. */
60     const auto& unit = data.at("unit");
61 
62     /* the mode parameter is optional. */
63     std::string systemdMode = "replace";
64     const auto& mode = data.find("mode");
65     if (mode != data.end())
66     {
67         systemdMode = data.at("mode").get<std::string>();
68     }
69 
70     return SystemdNoFile::CreateSystemdNoFile(sdbusplus::bus::new_default(),
71                                               unit, systemdMode);
72 }
73 
74 std::vector<HandlerConfig> buildHandlerFromJson(const nlohmann::json& data)
75 {
76     std::vector<HandlerConfig> handlers;
77 
78     for (const auto& item : data)
79     {
80         try
81         {
82             HandlerConfig output;
83 
84             /* at() throws an exception when the key is not present. */
85             item.at("blob").get_to(output.blobId);
86 
87             /* name must be: /flash/... */
88             if (!std::regex_match(output.blobId, std::regex("^\\/flash\\/.+")))
89             {
90                 throw std::runtime_error("Invalid blob name: '" +
91                                          output.blobId +
92                                          "' must start with /flash/");
93             }
94 
95             /* handler is required. */
96             const auto& h = item.at("handler");
97             const std::string handlerType = h.at("type");
98             if (handlerType == "file")
99             {
100                 const auto& path = h.at("path");
101                 output.handler = std::make_unique<FileHandler>(path);
102             }
103             else
104             {
105                 throw std::runtime_error("Invalid handler type: " +
106                                          handlerType);
107             }
108 
109             /* actions are required (presently). */
110             const auto& a = item.at("actions");
111             std::unique_ptr<ActionPack> pack = std::make_unique<ActionPack>();
112 
113             /* to make an action optional, assign type "skip" */
114             const auto& prep = a.at("preparation");
115             const std::string prepareType = prep.at("type");
116             if (prepareType == "systemd")
117             {
118                 pack->preparation = std::move(buildSystemd(prep));
119             }
120             else if (prepareType == "skip")
121             {
122                 pack->preparation = SkipAction::CreateSkipAction();
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 if (verifyType == "systemd")
137             {
138                 pack->verification = std::move(buildSystemd(verify));
139             }
140             else if (verifyType == "skip")
141             {
142                 pack->verification = SkipAction::CreateSkipAction();
143             }
144             else
145             {
146                 throw std::runtime_error("Invalid verification type:" +
147                                          verifyType);
148             }
149 
150             const auto& update = a.at("update");
151             const std::string updateType = update.at("type");
152             if (updateType == "reboot")
153             {
154                 pack->update = SystemdNoFile::CreateSystemdNoFile(
155                     sdbusplus::bus::new_default(), "reboot.target",
156                     "replace-irreversibly");
157             }
158             else if (updateType == "fileSystemdUpdate")
159             {
160                 pack->update = std::move(buildFileSystemd(update));
161             }
162             else if (updateType == "systemd")
163             {
164                 pack->update = std::move(buildSystemd(update));
165             }
166             else if (updateType == "skip")
167             {
168                 pack->update = SkipAction::CreateSkipAction();
169             }
170             else
171             {
172                 throw std::runtime_error("Invalid update type: " + updateType);
173             }
174 
175             output.actions = std::move(pack);
176             handlers.push_back(std::move(output));
177         }
178         catch (const std::exception& e)
179         {
180             /* TODO: Once phosphor-logging supports unit-test injection, fix
181              * this to log.
182              */
183             std::fprintf(stderr,
184                          "Excepted building HandlerConfig from json: %s\n",
185                          e.what());
186         }
187     }
188 
189     return handlers;
190 }
191 
192 std::vector<HandlerConfig> BuildHandlerConfigs(const std::string& directory)
193 {
194     using namespace phosphor::logging;
195 
196     std::vector<HandlerConfig> output;
197 
198     std::vector<std::string> jsonPaths = GetJsonList(directory);
199 
200     for (const auto& path : jsonPaths)
201     {
202         std::ifstream jsonFile(path);
203         if (!jsonFile.is_open())
204         {
205             log<level::ERR>("Unable to open json file",
206                             entry("PATH=%s", path.c_str()));
207             continue;
208         }
209 
210         auto data = nlohmann::json::parse(jsonFile, nullptr, false);
211         if (data.is_discarded())
212         {
213             log<level::ERR>("Parsing json failed",
214                             entry("PATH=%s", path.c_str()));
215             continue;
216         }
217 
218         std::vector<HandlerConfig> configs = buildHandlerFromJson(data);
219         std::move(configs.begin(), configs.end(), std::back_inserter(output));
220     }
221 
222     return output;
223 }
224 
225 } // namespace ipmi_flash
226