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