blob: 5b998920519b23e7521b18157960690054af76e4 [file] [log] [blame]
manojkiraneda0b631ae2019-12-03 17:54:28 +05301#pragma once
2
3#include <app.h>
4
5#include <boost/algorithm/string.hpp>
6#include <boost/container/flat_map.hpp>
7#include <filesystem>
Sunitha Harish8a3bb712019-12-13 03:48:09 -06008#include <fstream>
manojkiraneda0b631ae2019-12-03 17:54:28 +05309#include <nlohmann/json.hpp>
10
11namespace crow
12{
13namespace ibm_mc_lock
14{
15
16namespace fs = std::filesystem;
17using SType = std::string;
18
19/*----------------------------------------
20|Segment flags : LockFlag | SegmentLength|
21------------------------------------------*/
22
23using SegmentFlags = std::vector<std::pair<SType, uint32_t>>;
24
25// Lockrequest = session-id | hmc-id | locktype | resourceid | segmentinfo
26using LockRequest = std::tuple<SType, SType, SType, uint64_t, SegmentFlags>;
manojkiraneda0b631ae2019-12-03 17:54:28 +053027using LockRequests = std::vector<LockRequest>;
28using Rc =
29 std::pair<bool, std::variant<uint32_t, std::pair<uint32_t, LockRequest>>>;
manojkiraneda3b6dea62019-12-13 17:05:36 +053030using RcRelaseLock = std::pair<bool, std::pair<uint32_t, LockRequest>>;
manojkiraneda402b5712019-12-13 17:07:09 +053031using RcGetLockList =
32 std::variant<std::string, std::vector<std::pair<uint32_t, LockRequests>>>;
manojkiraneda3b6dea62019-12-13 17:05:36 +053033using ListOfTransactionIds = std::vector<uint32_t>;
manojkiraneda0b631ae2019-12-03 17:54:28 +053034using RcAcquireLock = std::pair<bool, std::variant<Rc, std::pair<bool, int>>>;
manojkiraneda3b6dea62019-12-13 17:05:36 +053035using RcReleaseLockApi = std::pair<bool, std::variant<bool, RcRelaseLock>>;
36using SessionFlags = std::pair<SType, SType>;
manojkiraneda402b5712019-12-13 17:07:09 +053037using ListOfSessionIds = std::vector<std::string>;
Sunitha Harish8a3bb712019-12-13 03:48:09 -060038static constexpr const char *fileName =
39 "/var/lib/obmc/bmc-console-mgmt/locks/ibm_mc_persistent_lock_data.json";
manojkiraneda0b631ae2019-12-03 17:54:28 +053040
41class Lock
42{
43 uint32_t transactionId;
44 boost::container::flat_map<uint32_t, LockRequests> lockTable;
45
46 /*
47 * This function implements the logic for validating an incomming
48 * lock request/requests.
49 *
50 * Returns : True (if Valid)
51 * Returns : False (if not a Valid lock request)
52 */
53
54 bool isValidLockRequest(const LockRequest);
55
56 /*
57 * This function implements the logic of checking if the incomming
58 * multi-lock request is not having conflicting requirements.
59 *
60 * Returns : True (if conflicting)
61 * Returns : False (if not conflicting)
62 */
63
64 bool isConflictRequest(const LockRequests);
65 /*
66 * Implements the core algorithm to find the conflicting
67 * lock requests.
68 *
69 * This functions takes two lock requests and check if both
70 * are conflicting to each other.
71 *
72 * Returns : True (if conflicting)
73 * Returns : False (if not conflicting)
74 */
75 bool isConflictRecord(const LockRequest, const LockRequest);
76
77 /*
78 * This function implements the logic of checking the conflicting
79 * locks from a incomming single/multi lock requests with the already
80 * existing lock request in the lock table.
81 *
82 */
83
84 Rc isConflictWithTable(const LockRequests);
manojkiraneda3b6dea62019-12-13 17:05:36 +053085 /*
86 * This function implements the logic of checking the ownership of the
87 * lock from the releaselock request.
88 *
89 * Returns : True (if the requesting HMC & Session owns the lock(s))
90 * Returns : False (if the request HMC or Session does not own the lock(s))
91 */
92
93 RcRelaseLock isItMyLock(const ListOfTransactionIds &, const SessionFlags &);
94
95 /*
96 * This function validates the the list of transactionID's and returns false
97 * if the transaction ID is not valid & not present in the lock table
98 */
99
100 bool validateRids(const ListOfTransactionIds &);
101
102 /*
103 * This function releases the locks that are already obtained by the
104 * requesting Management console.
105 */
106
107 void releaseLock(const ListOfTransactionIds &);
manojkiraneda0b631ae2019-12-03 17:54:28 +0530108
109 /*
Sunitha Harish8a3bb712019-12-13 03:48:09 -0600110 * This API implements the logic to persist the locks that are contained in
111 * the lock table into a json file.
112 */
113 void saveLocks();
114
115 /*
116 * This API implements the logic to load the locks that are present in the
117 * json file into the lock table.
118 */
119 void loadLocks();
120
121 /*
manojkiraneda0b631ae2019-12-03 17:54:28 +0530122 * This function implements the algorithm for checking the respective
123 * bytes of the resource id based on the lock management algorithm.
124 */
125
126 bool checkByte(uint64_t, uint64_t, uint32_t);
127
128 /*
129 * This functions implements a counter that generates a unique 32 bit
130 * number for every successful transaction. This number will be used by
131 * the Management Console for debug.
132 */
133 uint32_t generateTransactionId();
134
Sunitha Harish8a3bb712019-12-13 03:48:09 -0600135 bool createPersistentLockFilePath();
136
manojkiraneda0b631ae2019-12-03 17:54:28 +0530137 public:
138 /*
139 * This function implements the logic for acquiring a lock on a
140 * resource if the incomming request is legitimate without any
141 * conflicting requirements & without any conflicting requirement
142 * with the exsiting locks in the lock table.
143 *
144 */
145
146 RcAcquireLock acquireLock(const LockRequests);
147
manojkiraneda3b6dea62019-12-13 17:05:36 +0530148 /*
149 * This function implements the logic for releasing the lock that are
150 * owned by a management console session.
151 *
152 * The locks can be released by two ways
153 * - Using list of transaction ID's
154 * - Using a Session ID
155 *
156 * Client can choose either of the ways by using `Type` JSON key.
157 *
158 */
159 RcReleaseLockApi releaseLock(const ListOfTransactionIds &,
160 const SessionFlags &);
161
manojkiraneda402b5712019-12-13 17:07:09 +0530162 /*
163 * This function implements the logic for getting the list of locks obtained
164 * by a particular management console.
165 */
166 RcGetLockList getLockList(const ListOfSessionIds &);
167
manojkiraneda0b631ae2019-12-03 17:54:28 +0530168 Lock()
169 {
Sunitha Harish8a3bb712019-12-13 03:48:09 -0600170 loadLocks();
171 transactionId = lockTable.empty() ? 0 : prev(lockTable.end())->first;
manojkiraneda0b631ae2019-12-03 17:54:28 +0530172 }
173
174} lockObject;
175
Sunitha Harish8a3bb712019-12-13 03:48:09 -0600176bool Lock::createPersistentLockFilePath()
177{
178 // The path /var/lib/obmc will be created by initrdscripts
179 // Create the directories for the persistent lock file
180 std::error_code ec;
181 if (!std::filesystem::is_directory("/var/lib/obmc/bmc-console-mgmt", ec))
182 {
183 std::filesystem::create_directory("/var/lib/obmc/bmc-console-mgmt", ec);
184 }
185 if (ec)
186 {
187 BMCWEB_LOG_DEBUG
188 << "Failed to prepare bmc-console-mgmt directory. ec : " << ec;
189 return false;
190 }
191
192 if (!std::filesystem::is_directory("/var/lib/obmc/bmc-console-mgmt/locks",
193 ec))
194 {
195 std::filesystem::create_directory(
196 "/var/lib/obmc/bmc-console-mgmt/locks", ec);
197 }
198 if (ec)
199 {
200 BMCWEB_LOG_DEBUG
201 << "Failed to prepare persistent lock file directory. ec : " << ec;
202 return false;
203 }
204 return true;
205}
206
207void Lock::loadLocks()
208{
209 std::ifstream persistentFile(fileName);
210 if (persistentFile.is_open())
211 {
212 auto data = nlohmann::json::parse(persistentFile, nullptr, false);
213 if (data.is_discarded())
214 {
215 BMCWEB_LOG_ERROR << "Error parsing persistent data in json file.";
216 return;
217 }
218 BMCWEB_LOG_DEBUG << "The persistent lock data is available";
219 for (const auto &item : data.items())
220 {
221 BMCWEB_LOG_DEBUG << item.key();
222 BMCWEB_LOG_DEBUG << item.value();
223 LockRequests locks = item.value();
224 lockTable.insert(std::pair<uint32_t, LockRequests>(
225 std::stoul(item.key()), locks));
226 BMCWEB_LOG_DEBUG << "The persistent lock data loaded";
227 }
228 }
229}
230
231void Lock::saveLocks()
232{
233 std::error_code ec;
234 if (!std::filesystem::is_directory("/var/lib/obmc/bmc-console-mgmt/locks",
235 ec))
236 {
237 if (!createPersistentLockFilePath())
238 {
239 BMCWEB_LOG_DEBUG << "Failed to create lock persistent path";
240 return;
241 }
242 }
243 std::ofstream persistentFile(fileName);
244 // set the permission of the file to 640
245 fs::perms permission =
246 fs::perms::owner_read | fs::perms::owner_write | fs::perms::group_read;
247 fs::permissions(fileName, permission);
248 nlohmann::json data;
249 for (const auto &it : lockTable)
250 {
251 data[std::to_string(it.first)] = it.second;
252 }
253 BMCWEB_LOG_DEBUG << "data is " << data;
254 persistentFile << data;
255}
256
manojkiraneda402b5712019-12-13 17:07:09 +0530257RcGetLockList Lock::getLockList(const ListOfSessionIds &listSessionId)
258{
259
260 std::vector<std::pair<uint32_t, LockRequests>> lockList;
261
262 if (!lockTable.empty())
263 {
264 for (const auto &i : listSessionId)
265 {
266 auto it = lockTable.begin();
267 while (it != lockTable.end())
268 {
269 // Check if session id of this entry matches with session id
270 // given
271 if (std::get<0>(it->second[0]) == i)
272 {
273 BMCWEB_LOG_DEBUG << "Session id is found in the locktable";
274
275 // Push the whole lock record into a vector for returning
276 // the json
277 lockList.push_back(std::make_pair(it->first, it->second));
278 }
279 // Go to next entry in map
280 it++;
281 }
282 }
283 }
284 // we may have found at least one entry with the given session id
285 // return the json list of lock records pertaining to the given
286 // session id, or send an empty list if lock table is empty
287 return lockList;
288}
289
manojkiraneda3b6dea62019-12-13 17:05:36 +0530290RcReleaseLockApi Lock::releaseLock(const ListOfTransactionIds &p,
291 const SessionFlags &ids)
292{
293
294 bool status = validateRids(p);
295
296 if (!status)
297 {
298 // Validation of rids failed
299 BMCWEB_LOG_DEBUG << "Not a Valid request id";
300 return std::make_pair(false, status);
301 }
302 else
303 {
304 // Validation passed, check if all the locks are owned by the
305 // requesting HMC
306 auto status = isItMyLock(p, ids);
307 if (status.first)
308 {
309 // The current hmc owns all the locks, so we can release
310 // them
311 releaseLock(p);
312 }
313 return std::make_pair(true, status);
314 }
315 return std::make_pair(false, status);
316}
317
manojkiraneda0b631ae2019-12-03 17:54:28 +0530318RcAcquireLock Lock::acquireLock(const LockRequests lockRequestStructure)
319{
320
321 // validate the lock request
322
323 for (auto &lockRecord : lockRequestStructure)
324 {
325 bool status = isValidLockRequest(lockRecord);
326 if (!status)
327 {
328 BMCWEB_LOG_DEBUG << "Not a Valid record";
329 BMCWEB_LOG_DEBUG << "Bad json in request";
330 return std::make_pair(true, std::make_pair(status, 0));
331 }
332 }
333 // check for conflict record
334
335 const LockRequests &multiRequest = lockRequestStructure;
336 bool status = isConflictRequest(multiRequest);
337
338 if (status)
339 {
340 BMCWEB_LOG_DEBUG << "There is a conflict within itself";
341 return std::make_pair(true, std::make_pair(status, 1));
342 }
343 else
344 {
345 BMCWEB_LOG_DEBUG << "The request is not conflicting within itself";
346
347 // Need to check for conflict with the locktable entries.
348
349 auto conflict = isConflictWithTable(multiRequest);
350
351 BMCWEB_LOG_DEBUG << "Done with checking conflict with the locktable";
352 return std::make_pair(false, conflict);
353 }
354
355 return std::make_pair(true, std::make_pair(true, 1));
356}
357
manojkiraneda3b6dea62019-12-13 17:05:36 +0530358void Lock::releaseLock(const ListOfTransactionIds &refRids)
359{
360 for (const auto &id : refRids)
361 {
362 if (lockTable.erase(id))
363 {
364 BMCWEB_LOG_DEBUG << "Removing the locks with transaction ID : "
365 << id;
366 }
367
368 else
369 {
370 BMCWEB_LOG_DEBUG << "Removing the locks from the lock table "
371 "failed, tranasction ID: "
372 << id;
373 }
374 }
Sunitha Harish8a3bb712019-12-13 03:48:09 -0600375
376 saveLocks();
manojkiraneda3b6dea62019-12-13 17:05:36 +0530377}
378
379RcRelaseLock Lock::isItMyLock(const ListOfTransactionIds &refRids,
380 const SessionFlags &ids)
381{
382 for (const auto &id : refRids)
383 {
384 // Just need to compare the client id of the first lock records in the
385 // complete lock row(in the map), because the rest of the lock records
386 // would have the same client id
387
388 std::string expectedClientId = std::get<1>(lockTable[id][0]);
389 std::string expectedSessionId = std::get<0>(lockTable[id][0]);
390
391 if ((expectedClientId == ids.first) &&
392 (expectedSessionId == ids.second))
393 {
394 // It is owned by the currently request hmc
395 BMCWEB_LOG_DEBUG << "Lock is owned by the current hmc";
396 }
397 else
398 {
399 BMCWEB_LOG_DEBUG << "Lock is not owned by the current hmc";
400 return std::make_pair(false, std::make_pair(id, lockTable[id][0]));
401 }
402 }
403 return std::make_pair(true, std::make_pair(0, LockRequest()));
404}
405
406bool Lock::validateRids(const ListOfTransactionIds &refRids)
407{
408 for (const auto &id : refRids)
409 {
410 auto search = lockTable.find(id);
411
412 if (search != lockTable.end())
413 {
414 BMCWEB_LOG_DEBUG << "Valid Transaction id";
415 // continue for the next rid
416 }
417 else
418 {
419 BMCWEB_LOG_DEBUG << "Atleast 1 inValid Request id";
420 return false;
421 }
422 }
423 return true;
424}
425
manojkiraneda0b631ae2019-12-03 17:54:28 +0530426bool Lock::isValidLockRequest(const LockRequest refLockRecord)
427{
428
429 // validate the locktype
430
431 if (!((boost::equals(std::get<2>(refLockRecord), "Read") ||
432 (boost::equals(std::get<2>(refLockRecord), "Write")))))
433 {
434 BMCWEB_LOG_DEBUG << "Validation of LockType Failed";
435 BMCWEB_LOG_DEBUG << "Locktype : " << std::get<2>(refLockRecord);
436 return false;
437 }
438
439 BMCWEB_LOG_DEBUG << static_cast<int>(std::get<4>(refLockRecord).size());
440
441 // validate the number of segments
442 // Allowed No of segments are between 2 and 6
443 if ((static_cast<int>(std::get<4>(refLockRecord).size()) > 6) ||
444 (static_cast<int>(std::get<4>(refLockRecord).size()) < 2))
445 {
446 BMCWEB_LOG_DEBUG << "Validation of Number of Segements Failed";
447 BMCWEB_LOG_DEBUG << "Number of Segments provied : "
448 << sizeof(std::get<4>(refLockRecord));
449 return false;
450 }
451
452 int lockFlag = 0;
453 // validate the lockflags & segment length
454
455 for (const auto &p : std::get<4>(refLockRecord))
456 {
457
458 // validate the lock flags
459 // Allowed lockflags are locksame,lockall & dontlock
460
461 if (!((boost::equals(p.first, "LockSame") ||
462 (boost::equals(p.first, "LockAll")) ||
463 (boost::equals(p.first, "DontLock")))))
464 {
465 BMCWEB_LOG_DEBUG << "Validation of lock flags failed";
466 BMCWEB_LOG_DEBUG << p.first;
467 return false;
468 }
469
470 // validate the segment length
471 // Allowed values of segment length are between 1 and 4
472
473 if (p.second < 1 || p.second > 4)
474 {
475 BMCWEB_LOG_DEBUG << "Validation of Segment Length Failed";
476 BMCWEB_LOG_DEBUG << p.second;
477 return false;
478 }
479
480 if ((boost::equals(p.first, "LockSame") ||
481 (boost::equals(p.first, "LockAll"))))
482 {
483 ++lockFlag;
484 if (lockFlag >= 2)
485 {
486 return false;
487 }
488 }
489 }
490
manojkiraneda0b631ae2019-12-03 17:54:28 +0530491 return true;
492}
493
494Rc Lock::isConflictWithTable(const LockRequests refLockRequestStructure)
495{
496
497 uint32_t transactionId;
498
499 if (lockTable.empty())
500 {
501 transactionId = generateTransactionId();
502 BMCWEB_LOG_DEBUG << transactionId;
503 // Lock table is empty, so we are safe to add the lockrecords
504 // as there will be no conflict
505 BMCWEB_LOG_DEBUG << "Lock table is empty, so adding the lockrecords";
506 lockTable.emplace(std::pair<uint32_t, LockRequests>(
507 transactionId, refLockRequestStructure));
508
Sunitha Harish8a3bb712019-12-13 03:48:09 -0600509 // save the lock in the persistent file
510 saveLocks();
manojkiraneda0b631ae2019-12-03 17:54:28 +0530511 return std::make_pair(false, transactionId);
512 }
513
514 else
515 {
516 BMCWEB_LOG_DEBUG
517 << "Lock table is not empty, check for conflict with lock table";
518 // Lock table is not empty, compare the lockrequest entries with
519 // the entries in the lock table
520
521 for (const auto &lockRecord1 : refLockRequestStructure)
522 {
523 for (const auto &map : lockTable)
524 {
525 for (const auto &lockRecord2 : map.second)
526 {
527 bool status = isConflictRecord(lockRecord1, lockRecord2);
528 if (status)
529 {
530 return std::make_pair(
531 true, std::make_pair(map.first, lockRecord2));
532 }
533 }
534 }
535 }
536
537 // Reached here, so no conflict with the locktable, so we are safe to
538 // add the request records into the lock table
539
540 // Lock table is empty, so we are safe to add the lockrecords
541 // as there will be no conflict
542 BMCWEB_LOG_DEBUG << " Adding elements into lock table";
543 transactionId = generateTransactionId();
544 lockTable.emplace(
545 std::make_pair(transactionId, refLockRequestStructure));
Sunitha Harish8a3bb712019-12-13 03:48:09 -0600546
547 // save the lock in the persistent file
548 saveLocks();
manojkiraneda0b631ae2019-12-03 17:54:28 +0530549 }
550 return std::make_pair(false, transactionId);
551}
552
553bool Lock::isConflictRequest(const LockRequests refLockRequestStructure)
554{
555 // check for all the locks coming in as a part of single request
556 // return conflict if any two lock requests are conflicting
557
558 if (refLockRequestStructure.size() == 1)
559 {
560 BMCWEB_LOG_DEBUG << "Only single lock request, so there is no conflict";
561 // This means , we have only one lock request in the current
562 // request , so no conflict within the request
563 return false;
564 }
565
566 else
567 {
568 BMCWEB_LOG_DEBUG
569 << "There are multiple lock requests coming in a single request";
570
571 // There are multiple requests a part of one request
572
573 for (uint32_t i = 0; i < refLockRequestStructure.size(); i++)
574 {
575 for (uint32_t j = i + 1; j < refLockRequestStructure.size(); j++)
576 {
577 const LockRequest &p = refLockRequestStructure[i];
578 const LockRequest &q = refLockRequestStructure[j];
579 bool status = isConflictRecord(p, q);
580
581 if (status)
582 {
583 return true;
584 }
585 }
586 }
587 }
588 return false;
589}
590
591// This function converts the provided uint64_t resource id's from the two
592// lock requests subjected for comparision, and this function also compares
593// the content by bytes mentioned by a uint32_t number.
594
595// If all the elements in the lock requests which are subjected for comparison
596// are same, then the last comparision would be to check for the respective
597// bytes in the resourceid based on the segment length.
598
599bool Lock::checkByte(uint64_t resourceId1, uint64_t resourceId2,
600 uint32_t position)
601{
602 uint8_t *p = reinterpret_cast<uint8_t *>(&resourceId1);
603 uint8_t *q = reinterpret_cast<uint8_t *>(&resourceId2);
604
605 BMCWEB_LOG_DEBUG << "Comparing bytes " << std::to_string(p[position]) << ","
606 << std::to_string(q[position]);
607 if (p[position] != q[position])
608 {
609 return false;
610 }
611
612 else
613 {
614 return true;
615 }
616 return true;
617}
618
619bool Lock::isConflictRecord(const LockRequest refLockRecord1,
620 const LockRequest refLockRecord2)
621{
622 // No conflict if both are read locks
623
624 if (boost::equals(std::get<2>(refLockRecord1), "Read") &&
625 boost::equals(std::get<2>(refLockRecord2), "Read"))
626 {
627 BMCWEB_LOG_DEBUG << "Both are read locks, no conflict";
628 return false;
629 }
630
631 else
632 {
633 uint32_t i = 0;
634 for (const auto &p : std::get<4>(refLockRecord1))
635 {
636
637 // return conflict when any of them is try to lock all resources
638 // under the current resource level.
639 if (boost::equals(p.first, "LockAll") ||
640 boost::equals(std::get<4>(refLockRecord2)[i].first, "LockAll"))
641 {
642 BMCWEB_LOG_DEBUG
643 << "Either of the Comparing locks are trying to lock all "
644 "resources under the current resource level";
645 return true;
646 }
647
648 // determine if there is a lock-all-with-same-segment-size.
649 // If the current segment sizes are the same,then we should fail.
650
651 if ((boost::equals(p.first, "LockSame") ||
652 boost::equals(std::get<4>(refLockRecord2)[i].first,
653 "LockSame")) &&
654 (p.second == std::get<4>(refLockRecord2)[i].second))
655 {
656 return true;
657 }
658
659 // if segment lengths are not the same, it means two different locks
660 // So no conflict
661 if (p.second != std::get<4>(refLockRecord2)[i].second)
662 {
663 BMCWEB_LOG_DEBUG << "Segment lengths are not same";
664 BMCWEB_LOG_DEBUG << "Segment 1 length : " << p.second;
665 BMCWEB_LOG_DEBUG << "Segment 2 length : "
666 << std::get<4>(refLockRecord2)[i].second;
667 return false;
668 }
669
670 // compare segment data
671
672 for (uint32_t i = 0; i < p.second; i++)
673 {
674 // if the segment data is different , then the locks is on a
675 // different resource So no conflict between the lock records
676 if (!(checkByte(std::get<3>(refLockRecord1),
677 std::get<3>(refLockRecord2), i)))
678 {
679 return false;
680 }
681 }
682
683 ++i;
684 }
685 }
686
687 return false;
688}
689
690uint32_t Lock::generateTransactionId()
691{
692 ++transactionId;
693 return transactionId;
694}
695
696} // namespace ibm_mc_lock
697} // namespace crow