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 "prepare_systemd.hpp"
22 #include "update_systemd.hpp"
23 
24 #include <algorithm>
25 #include <cstdio>
26 #include <exception>
27 #include <fstream>
28 #include <nlohmann/json.hpp>
29 #include <phosphor-logging/log.hpp>
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::vector<HandlerConfig> buildHandlerFromJson(const nlohmann::json& data)
57 {
58     std::vector<HandlerConfig> handlers;
59 
60     for (const auto& item : data)
61     {
62         try
63         {
64             HandlerConfig output;
65 
66             /* at() throws an exception when the key is not present. */
67             item.at("blob").get_to(output.blobId);
68 
69             /* handler is required. */
70             const auto& h = item.at("handler");
71             const std::string handlerType = h.at("type");
72             if (handlerType == "file")
73             {
74                 const auto& path = h.at("path");
75                 output.handler = std::make_unique<FileHandler>(path);
76             }
77             else
78             {
79                 throw std::runtime_error("Invalid handler type: " +
80                                          handlerType);
81             }
82 
83             /* actions are required (presently). */
84             const auto& a = item.at("actions");
85             std::unique_ptr<ActionPack> pack = std::make_unique<ActionPack>();
86 
87             /* It hasn't been fully determined if any action being optional is
88              * useful, so for now they will be required.
89              * TODO: Evaluate how the behaviors change if some actions are
90              * missing, does the code just assume it was successful?  I would
91              * think not.
92              */
93             const auto& prep = a.at("preparation");
94             const std::string prepareType = prep.at("type");
95             if (prepareType == "systemd")
96             {
97                 const auto& unit = prep.at("unit");
98                 pack->preparation = SystemdPreparation::CreatePreparation(
99                     sdbusplus::bus::new_default(), unit);
100             }
101             else
102             {
103                 throw std::runtime_error("Invalid preparation type: " +
104                                          prepareType);
105             }
106 
107             const auto& verify = a.at("verification");
108             const std::string verifyType = verify.at("type");
109             if (verifyType == "fileSystemdVerify")
110             {
111                 pack->verification = std::move(buildFileSystemd(verify));
112             }
113             else
114             {
115                 throw std::runtime_error("Invalid verification type:" +
116                                          verifyType);
117             }
118 
119             const auto& update = a.at("update");
120             const std::string updateType = update.at("type");
121             if (updateType == "reboot")
122             {
123                 static constexpr auto rebootTarget = "reboot.target";
124                 static constexpr auto rebootMode = "replace-irreversibly";
125                 pack->update = SystemdUpdateMechanism::CreateSystemdUpdate(
126                     sdbusplus::bus::new_default(), rebootTarget, rebootMode);
127             }
128             else if (updateType == "fileSystemdUpdate")
129             {
130                 pack->update = std::move(buildFileSystemd(update));
131             }
132             else if (updateType == "systemd")
133             {
134                 const auto& unit = update.at("unit");
135 
136                 /* the mode parameter is optional. */
137                 std::string systemdMode = "replace";
138                 const auto& mode = update.find("mode");
139                 if (mode != update.end())
140                 {
141                     systemdMode = update.at("mode").get<std::string>();
142                 }
143 
144                 pack->update = SystemdUpdateMechanism::CreateSystemdUpdate(
145                     sdbusplus::bus::new_default(), unit, systemdMode);
146             }
147             else
148             {
149                 throw std::runtime_error("Invalid update type: " + updateType);
150             }
151 
152             output.actions = std::move(pack);
153             handlers.push_back(std::move(output));
154         }
155         catch (const std::exception& e)
156         {
157             /* TODO: Once phosphor-logging supports unit-test injection, fix
158              * this to log.
159              */
160             std::fprintf(stderr,
161                          "Excepted building HandlerConfig from json: %s\n",
162                          e.what());
163         }
164     }
165 
166     return handlers;
167 }
168 
169 std::vector<HandlerConfig> BuildHandlerConfigs(const std::string& directory)
170 {
171     using namespace phosphor::logging;
172 
173     std::vector<HandlerConfig> output;
174 
175     std::vector<std::string> jsonPaths = GetJsonList(directory);
176 
177     for (const auto& path : jsonPaths)
178     {
179         std::ifstream jsonFile(path);
180         if (!jsonFile.is_open())
181         {
182             log<level::ERR>("Unable to open json file",
183                             entry("PATH=%s", path.c_str()));
184             continue;
185         }
186 
187         auto data = nlohmann::json::parse(jsonFile, nullptr, false);
188         if (data.is_discarded())
189         {
190             log<level::ERR>("Parsing json failed",
191                             entry("PATH=%s", path.c_str()));
192             continue;
193         }
194 
195         std::vector<HandlerConfig> configs = buildHandlerFromJson(data);
196         std::move(configs.begin(), configs.end(), std::back_inserter(output));
197     }
198 
199     return output;
200 }
201 
202 } // namespace ipmi_flash
203