1 #pragma once
2 #include <app.h>
3 #include <tinyxml2.h>
4 
5 #include <async_resp.hpp>
6 #include <boost/algorithm/string.hpp>
7 #include <boost/container/flat_set.hpp>
8 #include <error_messages.hpp>
9 #include <filesystem>
10 #include <fstream>
11 #include <ibm/locks.hpp>
12 #include <nlohmann/json.hpp>
13 #include <regex>
14 #include <sdbusplus/message/types.hpp>
15 #include <utils/json_utils.hpp>
16 
17 // Allow save area file size to 500KB
18 #define MAX_SAVE_AREA_FILESIZE 500000
19 
20 using SType = std::string;
21 using SegmentFlags = std::vector<std::pair<std::string, uint32_t>>;
22 using LockRequest = std::tuple<SType, SType, SType, uint64_t, SegmentFlags>;
23 using LockRequests = std::vector<LockRequest>;
24 using Rc = std::pair<bool, std::variant<uint32_t, LockRequest>>;
25 using RcGetLockList =
26     std::variant<std::string, std::vector<std::pair<uint32_t, LockRequests>>>;
27 using ListOfSessionIds = std::vector<std::string>;
28 namespace crow
29 {
30 namespace ibm_mc
31 {
32 constexpr const char *methodNotAllowedMsg = "Method Not Allowed";
33 constexpr const char *resourceNotFoundMsg = "Resource Not Found";
34 constexpr const char *contentNotAcceptableMsg = "Content Not Acceptable";
35 constexpr const char *internalServerError = "Internal Server Error";
36 
37 bool createSaveAreaPath(crow::Response &res)
38 {
39     // The path /var/lib/obmc will be created by initrdscripts
40     // Create the directories for the save-area files, when we get
41     // first file upload request
42     std::error_code ec;
43     if (!std::filesystem::is_directory("/var/lib/obmc/bmc-console-mgmt", ec))
44     {
45         std::filesystem::create_directory("/var/lib/obmc/bmc-console-mgmt", ec);
46     }
47     if (ec)
48     {
49         res.result(boost::beast::http::status::internal_server_error);
50         res.jsonValue["Description"] = internalServerError;
51         BMCWEB_LOG_DEBUG
52             << "handleIbmPost: Failed to prepare save-area directory. ec : "
53             << ec;
54         return false;
55     }
56 
57     if (!std::filesystem::is_directory(
58             "/var/lib/obmc/bmc-console-mgmt/save-area", ec))
59     {
60         std::filesystem::create_directory(
61             "/var/lib/obmc/bmc-console-mgmt/save-area", ec);
62     }
63     if (ec)
64     {
65         res.result(boost::beast::http::status::internal_server_error);
66         res.jsonValue["Description"] = internalServerError;
67         BMCWEB_LOG_DEBUG
68             << "handleIbmPost: Failed to prepare save-area directory. ec : "
69             << ec;
70         return false;
71     }
72     return true;
73 }
74 void handleFilePut(const crow::Request &req, crow::Response &res,
75                    const std::string &fileID)
76 {
77     // Check the content-type of the request
78     std::string_view contentType = req.getHeaderValue("content-type");
79     if (boost::starts_with(contentType, "multipart/form-data"))
80     {
81         BMCWEB_LOG_DEBUG
82             << "This is multipart/form-data. Invalid content for PUT";
83 
84         res.result(boost::beast::http::status::not_acceptable);
85         res.jsonValue["Description"] = contentNotAcceptableMsg;
86         return;
87     }
88     else
89     {
90         BMCWEB_LOG_DEBUG << "Not a multipart/form-data. Continue..";
91     }
92 
93     BMCWEB_LOG_DEBUG
94         << "handleIbmPut: Request to create/update the save-area file";
95     if (!createSaveAreaPath(res))
96     {
97         res.result(boost::beast::http::status::not_found);
98         res.jsonValue["Description"] = resourceNotFoundMsg;
99         return;
100     }
101     // Create the file
102     std::ofstream file;
103     std::filesystem::path loc("/var/lib/obmc/bmc-console-mgmt/save-area");
104     loc /= fileID;
105 
106     std::string data = std::move(req.body);
107     BMCWEB_LOG_DEBUG << "data capaticty : " << data.capacity();
108     if (data.capacity() > MAX_SAVE_AREA_FILESIZE)
109     {
110         res.result(boost::beast::http::status::bad_request);
111         res.jsonValue["Description"] =
112             "File size exceeds maximum allowed size[500KB]";
113         return;
114     }
115     BMCWEB_LOG_DEBUG << "Creating file " << loc;
116     file.open(loc, std::ofstream::out);
117     if (file.fail())
118     {
119         BMCWEB_LOG_DEBUG << "Error while opening the file for writing";
120         res.result(boost::beast::http::status::internal_server_error);
121         res.jsonValue["Description"] = "Error while creating the file";
122         return;
123     }
124     else
125     {
126         file << data;
127         BMCWEB_LOG_DEBUG << "save-area file is created";
128         res.jsonValue["Description"] = "File Created";
129     }
130 }
131 
132 void handleConfigFileList(crow::Response &res)
133 {
134     std::vector<std::string> pathObjList;
135     std::filesystem::path loc("/var/lib/obmc/bmc-console-mgmt/save-area");
136     if (std::filesystem::exists(loc) && std::filesystem::is_directory(loc))
137     {
138         for (const auto &file : std::filesystem::directory_iterator(loc))
139         {
140             std::filesystem::path pathObj(file.path());
141             pathObjList.push_back("/ibm/v1/Host/ConfigFiles/" +
142                                   pathObj.filename().string());
143         }
144     }
145     res.jsonValue["@odata.type"] = "#FileCollection.v1_0_0.FileCollection";
146     res.jsonValue["@odata.id"] = "/ibm/v1/Host/ConfigFiles/";
147     res.jsonValue["Id"] = "ConfigFiles";
148     res.jsonValue["Name"] = "ConfigFiles";
149 
150     res.jsonValue["Members"] = std::move(pathObjList);
151     res.jsonValue["Actions"]["#FileCollection.DeleteAll"] = {
152         {"target",
153          "/ibm/v1/Host/ConfigFiles/Actions/FileCollection.DeleteAll"}};
154     res.end();
155 }
156 
157 void deleteConfigFiles(crow::Response &res)
158 {
159     std::vector<std::string> pathObjList;
160     std::error_code ec;
161     std::filesystem::path loc("/var/lib/obmc/bmc-console-mgmt/save-area");
162     if (std::filesystem::exists(loc) && std::filesystem::is_directory(loc))
163     {
164         std::filesystem::remove_all(loc, ec);
165         if (ec)
166         {
167             res.result(boost::beast::http::status::internal_server_error);
168             res.jsonValue["Description"] = internalServerError;
169             BMCWEB_LOG_DEBUG << "deleteConfigFiles: Failed to delete the "
170                                 "config files directory. ec : "
171                              << ec;
172         }
173     }
174     res.end();
175 }
176 
177 void getLockServiceData(crow::Response &res)
178 {
179     res.jsonValue["@odata.type"] = "#LockService.v1_0_0.LockService";
180     res.jsonValue["@odata.id"] = "/ibm/v1/HMC/LockService/";
181     res.jsonValue["Id"] = "LockService";
182     res.jsonValue["Name"] = "LockService";
183 
184     res.jsonValue["Actions"]["#LockService.AcquireLock"] = {
185         {"target", "/ibm/v1/HMC/LockService/Actions/LockService.AcquireLock"}};
186     res.jsonValue["Actions"]["#LockService.ReleaseLock"] = {
187         {"target", "/ibm/v1/HMC/LockService/Actions/LockService.ReleaseLock"}};
188     res.jsonValue["Actions"]["#LockService.GetLockList"] = {
189         {"target", "/ibm/v1/HMC/LockService/Actions/LockService.GetLockList"}};
190     res.end();
191 }
192 
193 void handleFileGet(crow::Response &res, const std::string &fileID)
194 {
195     BMCWEB_LOG_DEBUG << "HandleGet on SaveArea files on path: " << fileID;
196     std::filesystem::path loc("/var/lib/obmc/bmc-console-mgmt/save-area/" +
197                               fileID);
198     if (!std::filesystem::exists(loc))
199     {
200         BMCWEB_LOG_ERROR << loc << "Not found";
201         res.result(boost::beast::http::status::not_found);
202         res.jsonValue["Description"] = resourceNotFoundMsg;
203         return;
204     }
205 
206     std::ifstream readfile(loc.string());
207     if (!readfile)
208     {
209         BMCWEB_LOG_ERROR << loc.string() << "Not found";
210         res.result(boost::beast::http::status::not_found);
211         res.jsonValue["Description"] = resourceNotFoundMsg;
212         return;
213     }
214 
215     std::string contentDispositionParam =
216         "attachment; filename=\"" + fileID + "\"";
217     res.addHeader("Content-Disposition", contentDispositionParam);
218     std::string fileData;
219     fileData = {std::istreambuf_iterator<char>(readfile),
220                 std::istreambuf_iterator<char>()};
221     res.jsonValue["Data"] = fileData;
222     return;
223 }
224 
225 void handleFileDelete(crow::Response &res, const std::string &fileID)
226 {
227     std::string filePath("/var/lib/obmc/bmc-console-mgmt/save-area/" + fileID);
228     BMCWEB_LOG_DEBUG << "Removing the file : " << filePath << "\n";
229 
230     std::ifstream file_open(filePath.c_str());
231     if (static_cast<bool>(file_open))
232         if (remove(filePath.c_str()) == 0)
233         {
234             BMCWEB_LOG_DEBUG << "File removed!\n";
235             res.jsonValue["Description"] = "File Deleted";
236         }
237         else
238         {
239             BMCWEB_LOG_ERROR << "File not removed!\n";
240             res.result(boost::beast::http::status::internal_server_error);
241             res.jsonValue["Description"] = internalServerError;
242         }
243     else
244     {
245         BMCWEB_LOG_ERROR << "File not found!\n";
246         res.result(boost::beast::http::status::not_found);
247         res.jsonValue["Description"] = resourceNotFoundMsg;
248     }
249     return;
250 }
251 
252 inline void handleFileUrl(const crow::Request &req, crow::Response &res,
253                           const std::string &fileID)
254 {
255     if (req.method() == "PUT"_method)
256     {
257         handleFilePut(req, res, fileID);
258         res.end();
259         return;
260     }
261     if (req.method() == "GET"_method)
262     {
263         handleFileGet(res, fileID);
264         res.end();
265         return;
266     }
267     if (req.method() == "DELETE"_method)
268     {
269         handleFileDelete(res, fileID);
270         res.end();
271         return;
272     }
273 }
274 
275 void handleAcquireLockAPI(const crow::Request &req, crow::Response &res,
276                           std::vector<nlohmann::json> body)
277 {
278     LockRequests lockRequestStructure;
279     for (auto &element : body)
280     {
281         std::string lockType;
282         uint64_t resourceId;
283 
284         SegmentFlags segInfo;
285         std::vector<nlohmann::json> segmentFlags;
286 
287         if (!redfish::json_util::readJson(element, res, "LockType", lockType,
288                                           "ResourceID", resourceId,
289                                           "SegmentFlags", segmentFlags))
290         {
291             BMCWEB_LOG_DEBUG << "Not a Valid JSON";
292             res.result(boost::beast::http::status::bad_request);
293             res.end();
294             return;
295         }
296         BMCWEB_LOG_DEBUG << lockType;
297         BMCWEB_LOG_DEBUG << resourceId;
298 
299         BMCWEB_LOG_DEBUG << "Segment Flags are present";
300 
301         for (auto &e : segmentFlags)
302         {
303             std::string lockFlags;
304             uint32_t segmentLength;
305 
306             if (!redfish::json_util::readJson(e, res, "LockFlag", lockFlags,
307                                               "SegmentLength", segmentLength))
308             {
309                 res.result(boost::beast::http::status::bad_request);
310                 res.end();
311                 return;
312             }
313 
314             BMCWEB_LOG_DEBUG << "Lockflag : " << lockFlags;
315             BMCWEB_LOG_DEBUG << "SegmentLength : " << segmentLength;
316 
317             segInfo.push_back(std::make_pair(lockFlags, segmentLength));
318         }
319         lockRequestStructure.push_back(make_tuple(
320             req.session->uniqueId, "hmc-id", lockType, resourceId, segInfo));
321     }
322 
323     // print lock request into journal
324 
325     for (uint32_t i = 0; i < lockRequestStructure.size(); i++)
326     {
327         BMCWEB_LOG_DEBUG << std::get<0>(lockRequestStructure[i]);
328         BMCWEB_LOG_DEBUG << std::get<1>(lockRequestStructure[i]);
329         BMCWEB_LOG_DEBUG << std::get<2>(lockRequestStructure[i]);
330         BMCWEB_LOG_DEBUG << std::get<3>(lockRequestStructure[i]);
331 
332         for (const auto &p : std::get<4>(lockRequestStructure[i]))
333         {
334             BMCWEB_LOG_DEBUG << p.first << ", " << p.second;
335         }
336     }
337 
338     const LockRequests &t = lockRequestStructure;
339 
340     auto varAcquireLock = crow::ibm_mc_lock::Lock::getInstance().acquireLock(t);
341 
342     if (varAcquireLock.first)
343     {
344         // Either validity failure of there is a conflict with itself
345 
346         auto validityStatus =
347             std::get<std::pair<bool, int>>(varAcquireLock.second);
348 
349         if ((!validityStatus.first) && (validityStatus.second == 0))
350         {
351             BMCWEB_LOG_DEBUG << "Not a Valid record";
352             BMCWEB_LOG_DEBUG << "Bad json in request";
353             res.result(boost::beast::http::status::bad_request);
354             res.end();
355             return;
356         }
357         if (validityStatus.first && (validityStatus.second == 1))
358         {
359             BMCWEB_LOG_DEBUG << "There is a conflict within itself";
360             res.result(boost::beast::http::status::bad_request);
361             res.end();
362             return;
363         }
364     }
365     else
366     {
367         auto conflictStatus =
368             std::get<crow::ibm_mc_lock::Rc>(varAcquireLock.second);
369         if (!conflictStatus.first)
370         {
371             BMCWEB_LOG_DEBUG << "There is no conflict with the locktable";
372             res.result(boost::beast::http::status::ok);
373 
374             auto var = std::get<uint32_t>(conflictStatus.second);
375             nlohmann::json returnJson;
376             returnJson["id"] = var;
377             res.jsonValue["TransactionID"] = var;
378             res.end();
379             return;
380         }
381         else
382         {
383             BMCWEB_LOG_DEBUG << "There is a conflict with the lock table";
384             res.result(boost::beast::http::status::conflict);
385             auto var = std::get<std::pair<uint32_t, LockRequest>>(
386                 conflictStatus.second);
387             nlohmann::json returnJson, segments;
388             nlohmann::json myarray = nlohmann::json::array();
389             returnJson["TransactionID"] = var.first;
390             returnJson["SessionID"] = std::get<0>(var.second);
391             returnJson["HMCID"] = std::get<1>(var.second);
392             returnJson["LockType"] = std::get<2>(var.second);
393             returnJson["ResourceID"] = std::get<3>(var.second);
394 
395             for (uint32_t i = 0; i < std::get<4>(var.second).size(); i++)
396             {
397                 segments["LockFlag"] = std::get<4>(var.second)[i].first;
398                 segments["SegmentLength"] = std::get<4>(var.second)[i].second;
399                 myarray.push_back(segments);
400             }
401 
402             returnJson["SegmentFlags"] = myarray;
403 
404             res.jsonValue["Record"] = returnJson;
405             res.end();
406             return;
407         }
408     }
409 }
410 void handleRelaseAllAPI(const crow::Request &req, crow::Response &res)
411 {
412     crow::ibm_mc_lock::Lock::getInstance().releaseLock(req.session->uniqueId);
413     res.result(boost::beast::http::status::ok);
414     res.end();
415     return;
416 }
417 
418 void handleReleaseLockAPI(const crow::Request &req, crow::Response &res,
419                           const std::vector<uint32_t> &listTransactionIds)
420 {
421     BMCWEB_LOG_DEBUG << listTransactionIds.size();
422     BMCWEB_LOG_DEBUG << "Data is present";
423     for (uint32_t i = 0; i < listTransactionIds.size(); i++)
424     {
425         BMCWEB_LOG_DEBUG << listTransactionIds[i];
426     }
427 
428     std::string clientId = "hmc-id";
429     std::string sessionId = req.session->uniqueId;
430 
431     // validate the request ids
432 
433     auto varReleaselock = crow::ibm_mc_lock::Lock::getInstance().releaseLock(
434         listTransactionIds, std::make_pair(clientId, sessionId));
435 
436     if (!varReleaselock.first)
437     {
438         // validation Failed
439         res.result(boost::beast::http::status::bad_request);
440         res.end();
441         return;
442     }
443     else
444     {
445         auto statusRelease =
446             std::get<crow::ibm_mc_lock::RcRelaseLock>(varReleaselock.second);
447         if (statusRelease.first)
448         {
449             // The current hmc owns all the locks, so we already released
450             // them
451             res.result(boost::beast::http::status::ok);
452             res.end();
453             return;
454         }
455 
456         else
457         {
458             // valid rid, but the current hmc does not own all the locks
459             BMCWEB_LOG_DEBUG << "Current HMC does not own all the locks";
460             res.result(boost::beast::http::status::unauthorized);
461 
462             auto var = statusRelease.second;
463             nlohmann::json returnJson, segments;
464             nlohmann::json myArray = nlohmann::json::array();
465             returnJson["TransactionID"] = var.first;
466             returnJson["SessionID"] = std::get<0>(var.second);
467             returnJson["HMCID"] = std::get<1>(var.second);
468             returnJson["LockType"] = std::get<2>(var.second);
469             returnJson["ResourceID"] = std::get<3>(var.second);
470 
471             for (uint32_t i = 0; i < std::get<4>(var.second).size(); i++)
472             {
473                 segments["LockFlag"] = std::get<4>(var.second)[i].first;
474                 segments["SegmentLength"] = std::get<4>(var.second)[i].second;
475                 myArray.push_back(segments);
476             }
477 
478             returnJson["SegmentFlags"] = myArray;
479             res.jsonValue["Record"] = returnJson;
480             res.end();
481             return;
482         }
483     }
484 }
485 
486 void handleGetLockListAPI(const crow::Request &req, crow::Response &res,
487                           const ListOfSessionIds &listSessionIds)
488 {
489     BMCWEB_LOG_DEBUG << listSessionIds.size();
490 
491     auto status =
492         crow::ibm_mc_lock::Lock::getInstance().getLockList(listSessionIds);
493     auto var = std::get<std::vector<std::pair<uint32_t, LockRequests>>>(status);
494 
495     nlohmann::json lockRecords = nlohmann::json::array();
496 
497     for (const auto &transactionId : var)
498     {
499         for (const auto &lockRecord : transactionId.second)
500         {
501             nlohmann::json returnJson;
502 
503             returnJson["TransactionID"] = transactionId.first;
504             returnJson["SessionID"] = std::get<0>(lockRecord);
505             returnJson["HMCID"] = std::get<1>(lockRecord);
506             returnJson["LockType"] = std::get<2>(lockRecord);
507             returnJson["ResourceID"] = std::get<3>(lockRecord);
508 
509             nlohmann::json segments;
510             nlohmann::json segmentInfoArray = nlohmann::json::array();
511 
512             for (const auto &segment : std::get<4>(lockRecord))
513             {
514                 segments["LockFlag"] = segment.first;
515                 segments["SegmentLength"] = segment.second;
516                 segmentInfoArray.push_back(segments);
517             }
518 
519             returnJson["SegmentFlags"] = segmentInfoArray;
520             lockRecords.push_back(returnJson);
521         }
522     }
523     res.result(boost::beast::http::status::ok);
524     res.jsonValue["Records"] = lockRecords;
525     res.end();
526 }
527 
528 template <typename... Middlewares> void requestRoutes(Crow<Middlewares...> &app)
529 {
530 
531     // allowed only for admin
532     BMCWEB_ROUTE(app, "/ibm/v1/")
533         .requires({"ConfigureComponents", "ConfigureManager"})
534         .methods("GET"_method)(
535             [](const crow::Request &req, crow::Response &res) {
536                 res.jsonValue["@odata.type"] =
537                     "#ibmServiceRoot.v1_0_0.ibmServiceRoot";
538                 res.jsonValue["@odata.id"] = "/ibm/v1/";
539                 res.jsonValue["Id"] = "IBM Rest RootService";
540                 res.jsonValue["Name"] = "IBM Service Root";
541                 res.jsonValue["ConfigFiles"] = {
542                     {"@odata.id", "/ibm/v1/Host/ConfigFiles"}};
543                 res.jsonValue["LockService"] = {
544                     {"@odata.id", "/ibm/v1/HMC/LockService"}};
545                 res.end();
546             });
547 
548     BMCWEB_ROUTE(app, "/ibm/v1/Host/ConfigFiles")
549         .requires({"ConfigureComponents", "ConfigureManager"})
550         .methods("GET"_method)(
551             [](const crow::Request &req, crow::Response &res) {
552                 handleConfigFileList(res);
553             });
554 
555     BMCWEB_ROUTE(app,
556                  "/ibm/v1/Host/ConfigFiles/Actions/FileCollection.DeleteAll")
557         .requires({"ConfigureComponents", "ConfigureManager"})
558         .methods("POST"_method)(
559             [](const crow::Request &req, crow::Response &res) {
560                 deleteConfigFiles(res);
561             });
562 
563     BMCWEB_ROUTE(app, "/ibm/v1/Host/ConfigFiles/<path>")
564         .requires({"ConfigureComponents", "ConfigureManager"})
565         .methods("PUT"_method, "GET"_method, "DELETE"_method)(
566             [](const crow::Request &req, crow::Response &res,
567                const std::string &path) { handleFileUrl(req, res, path); });
568 
569     BMCWEB_ROUTE(app, "/ibm/v1/HMC/LockService")
570         .requires({"ConfigureComponents", "ConfigureManager"})
571         .methods("GET"_method)(
572             [](const crow::Request &req, crow::Response &res) {
573                 getLockServiceData(res);
574             });
575 
576     BMCWEB_ROUTE(app, "/ibm/v1/HMC/LockService/Actions/LockService.AcquireLock")
577         .requires({"ConfigureComponents", "ConfigureManager"})
578         .methods("POST"_method)(
579             [](const crow::Request &req, crow::Response &res) {
580                 std::vector<nlohmann::json> body;
581                 if (!redfish::json_util::readJson(req, res, "Request", body))
582                 {
583                     BMCWEB_LOG_DEBUG << "Not a Valid JSON";
584                     res.result(boost::beast::http::status::bad_request);
585                     res.end();
586                     return;
587                 }
588                 handleAcquireLockAPI(req, res, body);
589             });
590     BMCWEB_ROUTE(app, "/ibm/v1/HMC/LockService/Actions/LockService.ReleaseLock")
591         .requires({"ConfigureComponents", "ConfigureManager"})
592         .methods(
593             "POST"_method)([](const crow::Request &req, crow::Response &res) {
594             std::string type;
595             std::vector<uint32_t> listTransactionIds;
596 
597             if (!redfish::json_util::readJson(req, res, "Type", type,
598                                               "TransactionIDs",
599                                               listTransactionIds))
600             {
601                 res.result(boost::beast::http::status::bad_request);
602                 res.end();
603                 return;
604             }
605             if (type == "Transaction")
606             {
607                 handleReleaseLockAPI(req, res, listTransactionIds);
608             }
609             else if (type == "Session")
610             {
611                 handleRelaseAllAPI(req, res);
612             }
613             else
614             {
615                 BMCWEB_LOG_DEBUG << " Value of Type : " << type
616                                  << "is Not a Valid key";
617                 redfish::messages::propertyValueNotInList(res, type, "Type");
618             }
619         });
620     BMCWEB_ROUTE(app, "/ibm/v1/HMC/LockService/Actions/LockService.GetLockList")
621         .requires({"ConfigureComponents", "ConfigureManager"})
622         .methods("POST"_method)(
623             [](const crow::Request &req, crow::Response &res) {
624                 ListOfSessionIds listSessionIds;
625 
626                 if (!redfish::json_util::readJson(req, res, "SessionIDs",
627                                                   listSessionIds))
628                 {
629                     res.result(boost::beast::http::status::bad_request);
630                     res.end();
631                     return;
632                 }
633                 handleGetLockListAPI(req, res, listSessionIds);
634             });
635 }
636 
637 } // namespace ibm_mc
638 } // namespace crow
639