]> granicus.if.org Git - esp-idf/blob - components/nvs_flash/src/nvs_storage.cpp
nvs_flash: Fix nvs_set_blob hang when partition is near to FULL
[esp-idf] / components / nvs_flash / src / nvs_storage.cpp
1 // Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 #include "nvs_storage.hpp"
15
16 #ifndef ESP_PLATFORM
17 #include <map>
18 #include <sstream>
19 #endif
20
21 namespace nvs
22 {
23
24 Storage::~Storage()
25 {
26     clearNamespaces();
27 }
28
29 void Storage::clearNamespaces()
30 {
31     mNamespaces.clearAndFreeNodes();
32 }
33
34 void Storage::populateBlobIndices(TBlobIndexList& blobIdxList)
35 {
36     for (auto it = mPageManager.begin(); it != mPageManager.end(); ++it) {
37         Page& p = *it;
38         size_t itemIndex = 0;
39         Item item;
40
41         /* If the power went off just after writing a blob index, the duplicate detection
42          * logic in pagemanager will remove the earlier index. So we should never find a
43          * duplicate index at this point */
44
45         while (p.findItem(Page::NS_ANY, ItemType::BLOB_IDX, nullptr, itemIndex, item) == ESP_OK) {
46             BlobIndexNode* entry = new BlobIndexNode;
47
48             item.getKey(entry->key, sizeof(entry->key) - 1);
49             entry->nsIndex = item.nsIndex;
50             entry->chunkStart = item.blobIndex.chunkStart;
51             entry->chunkCount = item.blobIndex.chunkCount;
52
53             blobIdxList.push_back(entry);
54             itemIndex += item.span;
55         }
56     }
57 }
58
59 void Storage::eraseOrphanDataBlobs(TBlobIndexList& blobIdxList)
60 {
61     for (auto it = mPageManager.begin(); it != mPageManager.end(); ++it) {
62         Page& p = *it;
63         size_t itemIndex = 0;
64         Item item;
65         /* Chunks with same <ns,key> and with chunkIndex in the following ranges
66          * belong to same family.
67          * 1) VER_0_OFFSET <= chunkIndex < VER_1_OFFSET-1 => Version0 chunks
68          * 2) VER_1_OFFSET <= chunkIndex < VER_ANY => Version1 chunks
69          */
70         while (p.findItem(Page::NS_ANY, ItemType::BLOB_DATA, nullptr, itemIndex, item) == ESP_OK) {
71
72             auto iter = std::find_if(blobIdxList.begin(),
73                     blobIdxList.end(),
74                     [=] (const BlobIndexNode& e) -> bool
75                     {return (strncmp(item.key, e.key, sizeof(e.key) - 1) == 0)
76                             && (item.nsIndex == e.nsIndex)
77                             && (item.chunkIndex >=  static_cast<uint8_t> (e.chunkStart))
78                             && (item.chunkIndex < static_cast<uint8_t> (e.chunkStart) + e.chunkCount);});
79             if (iter == std::end(blobIdxList)) {
80                 p.eraseItem(item.nsIndex, item.datatype, item.key, item.chunkIndex);
81             }
82             itemIndex += item.span;
83         }
84     }
85 }
86
87 esp_err_t Storage::init(uint32_t baseSector, uint32_t sectorCount)
88 {
89     auto err = mPageManager.load(baseSector, sectorCount);
90     if (err != ESP_OK) {
91         mState = StorageState::INVALID;
92         return err;
93     }
94
95     // load namespaces list
96     clearNamespaces();
97     std::fill_n(mNamespaceUsage.data(), mNamespaceUsage.byteSize() / 4, 0);
98     for (auto it = mPageManager.begin(); it != mPageManager.end(); ++it) {
99         Page& p = *it;
100         size_t itemIndex = 0;
101         Item item;
102         while (p.findItem(Page::NS_INDEX, ItemType::U8, nullptr, itemIndex, item) == ESP_OK) {
103             NamespaceEntry* entry = new NamespaceEntry;
104             item.getKey(entry->mName, sizeof(entry->mName) - 1);
105             item.getValue(entry->mIndex);
106             mNamespaces.push_back(entry);
107             mNamespaceUsage.set(entry->mIndex, true);
108             itemIndex += item.span;
109         }
110     }
111     mNamespaceUsage.set(0, true);
112     mNamespaceUsage.set(255, true);
113     mState = StorageState::ACTIVE;
114
115     // Populate list of multi-page index entries.
116     TBlobIndexList blobIdxList;
117     populateBlobIndices(blobIdxList);
118
119     // Remove the entries for which there is no parent multi-page index.
120     eraseOrphanDataBlobs(blobIdxList);
121
122     // Purge the blob index list
123     blobIdxList.clearAndFreeNodes();
124
125 #ifndef ESP_PLATFORM
126     debugCheck();
127 #endif
128     return ESP_OK;
129 }
130
131 bool Storage::isValid() const
132 {
133     return mState == StorageState::ACTIVE;
134 }
135
136 esp_err_t Storage::findItem(uint8_t nsIndex, ItemType datatype, const char* key, Page* &page, Item& item, uint8_t chunkIdx, VerOffset chunkStart)
137 {
138     for (auto it = std::begin(mPageManager); it != std::end(mPageManager); ++it) {
139         size_t itemIndex = 0;
140         auto err = it->findItem(nsIndex, datatype, key, itemIndex, item, chunkIdx, chunkStart);
141         if (err == ESP_OK) {
142             page = it;
143             return ESP_OK;
144         }
145     }
146     return ESP_ERR_NVS_NOT_FOUND;
147 }
148
149 esp_err_t Storage::writeMultiPageBlob(uint8_t nsIndex, const char* key, const void* data, size_t dataSize, VerOffset chunkStart)
150 {
151     uint8_t chunkCount = 0;
152     TUsedPageList usedPages;
153     size_t remainingSize = dataSize;
154     size_t offset=0;
155     esp_err_t err = ESP_OK;
156
157     /* Check how much maximum data can be accommodated**/
158     uint32_t max_pages = mPageManager.getPageCount() - 1;
159
160     if(max_pages > (Page::CHUNK_ANY-1)/2) {
161        max_pages = (Page::CHUNK_ANY-1)/2;
162     }
163
164     if (dataSize > max_pages * Page::CHUNK_MAX_SIZE) {
165         return ESP_ERR_NVS_VALUE_TOO_LONG;
166     }
167
168     do {
169         Page& page = getCurrentPage();
170         size_t tailroom = page.getVarDataTailroom();
171         size_t chunkSize =0;
172         if (!chunkCount && tailroom < dataSize && tailroom < Page::CHUNK_MAX_SIZE/10) {
173             /** This is the first chunk and tailroom is too small ***/
174             if (page.state() != Page::PageState::FULL) {
175                 err = page.markFull();
176                 if (err != ESP_OK) {
177                     return err;
178                 }
179             }
180             err = mPageManager.requestNewPage();
181             if (err != ESP_OK) {
182                 return err;
183             } else if(getCurrentPage().getVarDataTailroom() == tailroom) {
184                 /* We got the same page or we are not improving.*/
185                 return ESP_ERR_NVS_NOT_ENOUGH_SPACE; 
186             } else {
187                 continue;
188             }
189         } else if (!tailroom) {
190             err = ESP_ERR_NVS_NOT_ENOUGH_SPACE;
191             break;
192         }
193
194         /* Split the blob into two and store the chunk of available size onto the current page */
195         assert(tailroom!=0);
196         chunkSize = (remainingSize > tailroom)? tailroom : remainingSize;
197         remainingSize -= chunkSize;
198
199         err = page.writeItem(nsIndex, ItemType::BLOB_DATA, key,
200                 static_cast<const uint8_t*> (data) + offset, chunkSize, static_cast<uint8_t> (chunkStart) + chunkCount);
201         chunkCount++;
202         assert(err != ESP_ERR_NVS_PAGE_FULL);
203         if (err != ESP_OK) {
204             break;
205         } else {
206             UsedPageNode* node = new UsedPageNode();
207             node->mPage = &page;
208             usedPages.push_back(node);
209             if (remainingSize || (tailroom - chunkSize) < Page::ENTRY_SIZE) {
210                 if (page.state() != Page::PageState::FULL) {
211                     err = page.markFull();
212                     if (err != ESP_OK) {
213                         break;
214                     }
215                 }
216                 err = mPageManager.requestNewPage();
217                 if (err != ESP_OK) {
218                     break;
219                 }
220             }
221         }
222         offset += chunkSize;
223         if (!remainingSize) {
224             /* All pages are stored. Now store the index.*/
225             Item item;
226             item.blobIndex.dataSize = dataSize;
227             item.blobIndex.chunkCount = chunkCount;
228             item.blobIndex.chunkStart = chunkStart;
229
230             err = getCurrentPage().writeItem(nsIndex, ItemType::BLOB_IDX, key, item.data, sizeof(item.data));
231             assert(err != ESP_ERR_NVS_PAGE_FULL);
232             break;
233         }
234     } while (1);
235
236     if (err != ESP_OK) {
237         /* Anything failed, then we should erase all the written chunks*/
238         int ii=0;
239         for (auto it = std::begin(usedPages); it != std::end(usedPages); it++) {
240             it->mPage->eraseItem(nsIndex, ItemType::BLOB_DATA, key, ii++);
241         }
242     }
243     usedPages.clearAndFreeNodes();
244     return err;
245 }
246
247 esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize)
248 {
249     if (mState != StorageState::ACTIVE) {
250         return ESP_ERR_NVS_NOT_INITIALIZED;
251     }
252
253     Page* findPage = nullptr;
254     Item item;
255
256     esp_err_t err;
257     if (datatype == ItemType::BLOB) {
258         err = findItem(nsIndex, ItemType::BLOB_IDX, key, findPage, item);
259     } else {
260         err = findItem(nsIndex, datatype, key, findPage, item);
261     }
262
263     if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
264         return err;
265     }
266
267     if (datatype == ItemType::BLOB) {
268         VerOffset prevStart,  nextStart;
269         prevStart = nextStart = VerOffset::VER_0_OFFSET;
270         if (findPage) {
271             if (findPage->state() == Page::PageState::UNINITIALIZED ||
272                     findPage->state() == Page::PageState::INVALID) {
273                 ESP_ERROR_CHECK(findItem(nsIndex, datatype, key, findPage, item));
274             }
275             /* Get the version of the previous index with same <ns,key> */
276             prevStart = item.blobIndex.chunkStart;
277             assert(prevStart == VerOffset::VER_0_OFFSET || prevStart == VerOffset::VER_1_OFFSET);
278
279             /* Toggle the version by changing the offset */
280             nextStart
281                 = (prevStart == VerOffset::VER_1_OFFSET) ? VerOffset::VER_0_OFFSET : VerOffset::VER_1_OFFSET;
282         }
283         /* Write the blob with new version*/
284         err = writeMultiPageBlob(nsIndex, key, data, dataSize, nextStart);
285
286         if (err == ESP_ERR_NVS_PAGE_FULL) {
287             return ESP_ERR_NVS_NOT_ENOUGH_SPACE;
288         }
289         if (err != ESP_OK) {
290             return err;
291         }
292
293         if (findPage) {
294             /* Erase the blob with earlier version*/
295             err = eraseMultiPageBlob(nsIndex, key, prevStart);
296
297             if (err == ESP_ERR_FLASH_OP_FAIL) {
298                 return ESP_ERR_NVS_REMOVE_FAILED;
299             }
300             if (err != ESP_OK) {
301                 return err;
302             }
303             
304             findPage = nullptr;
305         } else { 
306             /* Support for earlier versions where BLOBS were stored without index */
307             err = findItem(nsIndex, datatype, key, findPage, item);
308             if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
309                 return err;
310             }
311         }
312     } else {
313
314         Page& page = getCurrentPage();
315         err = page.writeItem(nsIndex, datatype, key, data, dataSize);
316         if (err == ESP_ERR_NVS_PAGE_FULL) {
317             if (page.state() != Page::PageState::FULL) {
318                 err = page.markFull();
319                 if (err != ESP_OK) {
320                     return err;
321                 }
322             }
323             err = mPageManager.requestNewPage();
324             if (err != ESP_OK) {
325                 return err;
326             }
327
328             err = getCurrentPage().writeItem(nsIndex, datatype, key, data, dataSize);
329             if (err == ESP_ERR_NVS_PAGE_FULL) {
330                 return ESP_ERR_NVS_NOT_ENOUGH_SPACE;
331             }
332             if (err != ESP_OK) {
333                 return err;
334             }
335         } else if (err != ESP_OK) {
336             return err;
337         }
338     }
339
340     if (findPage) {
341         if (findPage->state() == Page::PageState::UNINITIALIZED ||
342                 findPage->state() == Page::PageState::INVALID) {
343             ESP_ERROR_CHECK(findItem(nsIndex, datatype, key, findPage, item));
344         }
345         err = findPage->eraseItem(nsIndex, datatype, key);
346         if (err == ESP_ERR_FLASH_OP_FAIL) {
347             return ESP_ERR_NVS_REMOVE_FAILED;
348         }
349         if (err != ESP_OK) {
350             return err;
351         }
352     }
353 #ifndef ESP_PLATFORM
354     debugCheck();
355 #endif
356     return ESP_OK;
357 }
358
359 esp_err_t Storage::createOrOpenNamespace(const char* nsName, bool canCreate, uint8_t& nsIndex)
360 {
361     if (mState != StorageState::ACTIVE) {
362         return ESP_ERR_NVS_NOT_INITIALIZED;
363     }
364     auto it = std::find_if(mNamespaces.begin(), mNamespaces.end(), [=] (const NamespaceEntry& e) -> bool {
365         return strncmp(nsName, e.mName, sizeof(e.mName) - 1) == 0;
366     });
367     if (it == std::end(mNamespaces)) {
368         if (!canCreate) {
369             return ESP_ERR_NVS_NOT_FOUND;
370         }
371
372         uint8_t ns;
373         for (ns = 1; ns < 255; ++ns) {
374             if (mNamespaceUsage.get(ns) == false) {
375                 break;
376             }
377         }
378
379         if (ns == 255) {
380             return ESP_ERR_NVS_NOT_ENOUGH_SPACE;
381         }
382
383         auto err = writeItem(Page::NS_INDEX, ItemType::U8, nsName, &ns, sizeof(ns));
384         if (err != ESP_OK) {
385             return err;
386         }
387         mNamespaceUsage.set(ns, true);
388         nsIndex = ns;
389
390         NamespaceEntry* entry = new NamespaceEntry;
391         entry->mIndex = ns;
392         strncpy(entry->mName, nsName, sizeof(entry->mName) - 1);
393         entry->mName[sizeof(entry->mName) - 1] = 0;
394         mNamespaces.push_back(entry);
395
396     } else {
397         nsIndex = it->mIndex;
398     }
399     return ESP_OK;
400 }
401
402 esp_err_t Storage::readMultiPageBlob(uint8_t nsIndex, const char* key, void* data, size_t dataSize)
403 {
404     Item item;
405     Page* findPage = nullptr;
406
407     /* First read the blob index */
408     auto err = findItem(nsIndex, ItemType::BLOB_IDX, key, findPage, item);
409     if (err != ESP_OK) {
410         return err;
411     }
412
413     uint8_t chunkCount = item.blobIndex.chunkCount;
414     VerOffset chunkStart = item.blobIndex.chunkStart;
415     size_t readSize = item.blobIndex.dataSize;
416     size_t offset = 0;
417
418     assert(dataSize == readSize);
419
420     /* Now read corresponding chunks */
421     for (uint8_t chunkNum = 0; chunkNum < chunkCount; chunkNum++) {
422         err = findItem(nsIndex, ItemType::BLOB_DATA, key, findPage, item, static_cast<uint8_t> (chunkStart) + chunkNum);
423         if (err != ESP_OK) {
424             if (err == ESP_ERR_NVS_NOT_FOUND) {
425                 break;
426             }
427             return err;
428         }
429         err = findPage->readItem(nsIndex, ItemType::BLOB_DATA, key, static_cast<uint8_t*>(data) + offset, item.varLength.dataSize, static_cast<uint8_t> (chunkStart) + chunkNum);
430         if (err != ESP_OK) {
431             return err;
432         }
433         assert(static_cast<uint8_t> (chunkStart) + chunkNum == item.chunkIndex);
434         offset += item.varLength.dataSize;
435     }
436     if (err == ESP_OK) {
437         assert(offset == dataSize);
438     }
439     if (err == ESP_ERR_NVS_NOT_FOUND) {
440         eraseMultiPageBlob(nsIndex, key); // cleanup if a chunk is not found
441     }
442     return err;
443 }
444
445 esp_err_t Storage::readItem(uint8_t nsIndex, ItemType datatype, const char* key, void* data, size_t dataSize)
446 {
447     if (mState != StorageState::ACTIVE) {
448         return ESP_ERR_NVS_NOT_INITIALIZED;
449     }
450
451     Item item;
452     Page* findPage = nullptr;
453     if (datatype == ItemType::BLOB) {
454         auto err = readMultiPageBlob(nsIndex, key, data, dataSize);
455         if (err != ESP_ERR_NVS_NOT_FOUND) {
456             return err;
457         } // else check if the blob is stored with earlier version format without index
458     } 
459
460     auto err = findItem(nsIndex, datatype, key, findPage, item);
461     if (err != ESP_OK) {
462         return err;
463     }
464     return findPage->readItem(nsIndex, datatype, key, data, dataSize);
465     
466 }
467
468 esp_err_t Storage::eraseMultiPageBlob(uint8_t nsIndex, const char* key, VerOffset chunkStart)
469 {
470     if (mState != StorageState::ACTIVE) {
471         return ESP_ERR_NVS_NOT_INITIALIZED;
472     }
473     Item item;
474     Page* findPage = nullptr;
475
476     auto err = findItem(nsIndex, ItemType::BLOB_IDX, key, findPage, item, Page::CHUNK_ANY, chunkStart);
477     if (err != ESP_OK) {
478         return err;
479     }
480     /* Erase the index first and make children blobs orphan*/
481     err = findPage->eraseItem(nsIndex, ItemType::BLOB_IDX, key, Page::CHUNK_ANY, chunkStart);
482     if (err != ESP_OK) {
483         return err;
484     }
485
486     uint8_t chunkCount = item.blobIndex.chunkCount;
487
488     if (chunkStart == VerOffset::VER_ANY) {
489         chunkStart = item.blobIndex.chunkStart;
490     } else {
491         assert(chunkStart == item.blobIndex.chunkStart);
492     }
493
494     /* Now erase corresponding chunks*/
495     for (uint8_t chunkNum = 0; chunkNum < chunkCount; chunkNum++) {
496         err = findItem(nsIndex, ItemType::BLOB_DATA, key, findPage, item, static_cast<uint8_t> (chunkStart) + chunkNum);
497
498         if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
499             return err;
500         } else if (err == ESP_ERR_NVS_NOT_FOUND) {
501             continue; // Keep erasing other chunks
502         }
503         err = findPage->eraseItem(nsIndex, ItemType::BLOB_DATA, key, static_cast<uint8_t> (chunkStart) + chunkNum);
504         if (err != ESP_OK) {
505             return err;
506         }
507
508     }
509
510     return ESP_OK;
511 }
512
513 esp_err_t Storage::eraseItem(uint8_t nsIndex, ItemType datatype, const char* key)
514 {
515     if (mState != StorageState::ACTIVE) {
516         return ESP_ERR_NVS_NOT_INITIALIZED;
517     }
518
519     if (datatype == ItemType::BLOB) {
520         return eraseMultiPageBlob(nsIndex, key);
521     }
522
523     Item item;
524     Page* findPage = nullptr;
525     auto err = findItem(nsIndex, datatype, key, findPage, item);
526     if (err != ESP_OK) {
527         return err;
528     }
529
530     return findPage->eraseItem(nsIndex, datatype, key);
531 }
532
533 esp_err_t Storage::eraseNamespace(uint8_t nsIndex)
534 {
535     if (mState != StorageState::ACTIVE) {
536         return ESP_ERR_NVS_NOT_INITIALIZED;
537     }
538
539     for (auto it = std::begin(mPageManager); it != std::end(mPageManager); ++it) {
540         while (true) {
541             auto err = it->eraseItem(nsIndex, ItemType::ANY, nullptr);
542             if (err == ESP_ERR_NVS_NOT_FOUND) {
543                 break;
544             }
545             else if (err != ESP_OK) {
546                 return err;
547             }
548         }
549     }
550     return ESP_OK;
551
552 }
553
554 esp_err_t Storage::getItemDataSize(uint8_t nsIndex, ItemType datatype, const char* key, size_t& dataSize)
555 {
556     if (mState != StorageState::ACTIVE) {
557         return ESP_ERR_NVS_NOT_INITIALIZED;
558     }
559
560     Item item;
561     Page* findPage = nullptr;
562     auto err = findItem(nsIndex, datatype, key, findPage, item);
563     if (err != ESP_OK) {
564         if (datatype != ItemType::BLOB) {
565             return err;
566         }
567         err = findItem(nsIndex, ItemType::BLOB_IDX, key, findPage, item);
568         if (err != ESP_OK) {
569             return err;
570         }
571         dataSize = item.blobIndex.dataSize;
572         return ESP_OK;
573     }
574
575     dataSize = item.varLength.dataSize;
576     return ESP_OK;
577 }
578
579 void Storage::debugDump()
580 {
581     for (auto p = mPageManager.begin(); p != mPageManager.end(); ++p) {
582         p->debugDump();
583     }
584 }
585
586 #ifndef ESP_PLATFORM
587 void Storage::debugCheck()
588 {
589     std::map<std::string, Page*> keys;
590
591     for (auto p = mPageManager.begin(); p != mPageManager.end(); ++p) {
592         size_t itemIndex = 0;
593         size_t usedCount = 0;
594         Item item;
595         while (p->findItem(Page::NS_ANY, ItemType::ANY, nullptr, itemIndex, item) == ESP_OK) {
596             std::stringstream keyrepr;
597             keyrepr << static_cast<unsigned>(item.nsIndex) << "_" << static_cast<unsigned>(item.datatype) << "_" << item.key <<"_"<<static_cast<unsigned>(item.chunkIndex);
598             std::string keystr = keyrepr.str();
599             if (keys.find(keystr) != std::end(keys)) {
600                 printf("Duplicate key: %s\n", keystr.c_str());
601                 debugDump();
602                 assert(0);
603             }
604             keys.insert(std::make_pair(keystr, static_cast<Page*>(p)));
605             itemIndex += item.span;
606             usedCount += item.span;
607         }
608         assert(usedCount == p->getUsedEntryCount());
609     }
610 }
611 #endif //ESP_PLATFORM
612
613 esp_err_t Storage::fillStats(nvs_stats_t& nvsStats)
614 {
615     nvsStats.namespace_count = mNamespaces.size();
616     return mPageManager.fillStats(nvsStats);
617 }
618
619 esp_err_t Storage::calcEntriesInNamespace(uint8_t nsIndex, size_t& usedEntries)
620 {
621     usedEntries = 0;
622
623     if (mState != StorageState::ACTIVE) {
624         return ESP_ERR_NVS_NOT_INITIALIZED;
625     }
626
627     for (auto it = std::begin(mPageManager); it != std::end(mPageManager); ++it) {
628         size_t itemIndex = 0;
629         Item item;
630         while (true) {
631             auto err = it->findItem(nsIndex, ItemType::ANY, nullptr, itemIndex, item);
632             if (err == ESP_ERR_NVS_NOT_FOUND) {
633                 break;
634             }
635             else if (err != ESP_OK) {
636                 return err;
637             }
638             usedEntries += item.span;
639             itemIndex   += item.span;
640             if (itemIndex >= it->ENTRY_COUNT) break;
641         }
642     }
643     return ESP_OK;
644 }
645
646 }