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