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