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