diff --git a/.travis.yml b/.travis.yml index 5c0988b5..92111061 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,12 +22,13 @@ script: - make test QUIET=1 CFLAGS+="-DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512" - make test QUIET=1 CFLAGS+="-DLFS_BLOCK_COUNT=1023 -DLFS_LOOKAHEAD=2048" + - make clean test QUIET=1 CFLAGS+="-DLFS_INLINE_MAX=0" - make clean test QUIET=1 CFLAGS+="-DLFS_NO_INTRINSICS" # compile and find the code size with the smallest configuration - make clean size OBJ="$(ls lfs*.o | tr '\n' ' ')" - CFLAGS+="-DLFS_NO{ASSERT,DEBUG,WARN,ERROR}" + CFLAGS+="-DLFS_NO_ASSERT -DLFS_NO_DEBUG -DLFS_NO_WARN -DLFS_NO_ERROR" | tee sizes # update status if we succeeded, compare with master if possible diff --git a/LICENSE.md b/LICENSE.md index 59cd3f8a..f7949727 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,165 +1,36 @@ -Apache License -Version 2.0, January 2004 -http://www.apache.org/licenses/ +Copyright (c) 2017, Arm Limited. All rights reserved. -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: -1. Definitions. +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. +- Neither the name of ARM nor the names of its contributors may be used to + endorse or promote products derived from this software without specific prior + written permission. -"License" shall mean the terms and conditions for use, reproduction, and -distribution as defined by Sections 1 through 9 of this document. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -"Licensor" shall mean the copyright owner or entity authorized by the copyright -owner that is granting the License. +--- -"Legal Entity" shall mean the union of the acting entity and all other entities -that control, are controlled by, or are under common control with that entity. -For the purposes of this definition, "control" means (i) the power, direct or -indirect, to cause the direction or management of such entity, whether by -contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the -outstanding shares, or (iii) beneficial ownership of such entity. +*Note*: +Individual files contain the following tag instead of the full license text. -"You" (or "Your") shall mean an individual or Legal Entity exercising -permissions granted by this License. -"Source" form shall mean the preferred form for making modifications, including -but not limited to software source code, documentation source, and configuration -files. -"Object" form shall mean any form resulting from mechanical transformation or -translation of a Source form, including but not limited to compiled object code, -generated documentation, and conversions to other media types. + SPDX-License-Identifier: BSD-3-Clause -"Work" shall mean the work of authorship, whether in Source or Object form, made -available under the License, as indicated by a copyright notice that is included -in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that -is based on (or derived from) the Work and for which the editorial revisions, -annotations, elaborations, or other modifications represent, as a whole, an -original work of authorship. For the purposes of this License, Derivative Works -shall not include works that remain separable from, or merely link (or bind by -name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version -of the Work and any modifications or additions to that Work or Derivative Works -thereof, that is intentionally submitted to Licensor for inclusion in the Work -by the copyright owner or by an individual or Legal Entity authorized to submit -on behalf of the copyright owner. For the purposes of this definition, -"submitted" means any form of electronic, verbal, or written communication sent -to the Licensor or its representatives, including but not limited to -communication on electronic mailing lists, source code control systems, and -issue tracking systems that are managed by, or on behalf of, the Licensor for -the purpose of discussing and improving the Work, but excluding communication -that is conspicuously marked or otherwise designated in writing by the copyright -owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf -of whom a Contribution has been received by Licensor and subsequently -incorporated within the Work. - -2. Grant of Copyright License. - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable copyright license to reproduce, prepare Derivative Works of, -publicly display, publicly perform, sublicense, and distribute the Work and such -Derivative Works in Source or Object form. - -3. Grant of Patent License. - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable (except as stated in this section) patent license to make, have -made, use, offer to sell, sell, import, and otherwise transfer the Work, where -such license applies only to those patent claims licensable by such Contributor -that are necessarily infringed by their Contribution(s) alone or by combination -of their Contribution(s) with the Work to which such Contribution(s) was -submitted. If You institute patent litigation against any entity (including a -cross-claim or counterclaim in a lawsuit) alleging that the Work or a -Contribution incorporated within the Work constitutes direct or contributory -patent infringement, then any patent licenses granted to You under this License -for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. - -You may reproduce and distribute copies of the Work or Derivative Works thereof -in any medium, with or without modifications, and in Source or Object form, -provided that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of -this License; and -You must cause any modified files to carry prominent notices stating that You -changed the files; and -You must retain, in the Source form of any Derivative Works that You distribute, -all copyright, patent, trademark, and attribution notices from the Source form -of the Work, excluding those notices that do not pertain to any part of the -Derivative Works; and -If the Work includes a "NOTICE" text file as part of its distribution, then any -Derivative Works that You distribute must include a readable copy of the -attribution notices contained within such NOTICE file, excluding those notices -that do not pertain to any part of the Derivative Works, in at least one of the -following places: within a NOTICE text file distributed as part of the -Derivative Works; within the Source form or documentation, if provided along -with the Derivative Works; or, within a display generated by the Derivative -Works, if and wherever such third-party notices normally appear. The contents of -the NOTICE file are for informational purposes only and do not modify the -License. You may add Your own attribution notices within Derivative Works that -You distribute, alongside or as an addendum to the NOTICE text from the Work, -provided that such additional attribution notices cannot be construed as -modifying the License. -You may add Your own copyright statement to Your modifications and may provide -additional or different license terms and conditions for use, reproduction, or -distribution of Your modifications, or for any such Derivative Works as a whole, -provided Your use, reproduction, and distribution of the Work otherwise complies -with the conditions stated in this License. - -5. Submission of Contributions. - -Unless You explicitly state otherwise, any Contribution intentionally submitted -for inclusion in the Work by You to the Licensor shall be under the terms and -conditions of this License, without any additional terms or conditions. -Notwithstanding the above, nothing herein shall supersede or modify the terms of -any separate license agreement you may have executed with Licensor regarding -such Contributions. - -6. Trademarks. - -This License does not grant permission to use the trade names, trademarks, -service marks, or product names of the Licensor, except as required for -reasonable and customary use in describing the origin of the Work and -reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. - -Unless required by applicable law or agreed to in writing, Licensor provides the -Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, -including, without limitation, any warranties or conditions of TITLE, -NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are -solely responsible for determining the appropriateness of using or -redistributing the Work and assume any risks associated with Your exercise of -permissions under this License. - -8. Limitation of Liability. - -In no event and under no legal theory, whether in tort (including negligence), -contract, or otherwise, unless required by applicable law (such as deliberate -and grossly negligent acts) or agreed to in writing, shall any Contributor be -liable to You for damages, including any direct, indirect, special, incidental, -or consequential damages of any character arising as a result of this License or -out of the use or inability to use the Work (including but not limited to -damages for loss of goodwill, work stoppage, computer failure or malfunction, or -any and all other commercial damages or losses), even if such Contributor has -been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. - -While redistributing the Work or Derivative Works thereof, You may choose to -offer, and charge a fee for, acceptance of support, warranty, indemnity, or -other liability obligations and/or rights consistent with this License. However, -in accepting such obligations, You may act only on Your own behalf and on Your -sole responsibility, not on behalf of any other Contributor, and only if You -agree to indemnify, defend, and hold each Contributor harmless for any liability -incurred by, or claims asserted against, such Contributor by reason of your -accepting any such warranty or additional liability. +This enables machine processing of license information based on the SPDX +License Identifiers that are here available: http://spdx.org/licenses/ diff --git a/Makefile b/Makefile index c0933139..55c95dbd 100644 --- a/Makefile +++ b/Makefile @@ -33,8 +33,9 @@ size: $(OBJ) $(SIZE) -t $^ .SUFFIXES: -test: test_format test_dirs test_files test_seek test_truncate \ - test_interspersed test_alloc test_paths test_orphan test_move test_corrupt +test: test_format test_dirs test_files test_seek test_truncate test_entries \ + test_interspersed test_alloc test_paths test_attrs \ + test_orphan test_move test_corrupt test_%: tests/test_%.sh ifdef QUIET @./$< | sed -n '/^[-=]/p' diff --git a/emubd/lfs_emubd.c b/emubd/lfs_emubd.c index b1595963..ed3414ab 100644 --- a/emubd/lfs_emubd.c +++ b/emubd/lfs_emubd.c @@ -1,19 +1,8 @@ /* * Block device emulated on standard files * - * Copyright (c) 2017 ARM Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause */ #include "emubd/lfs_emubd.h" diff --git a/emubd/lfs_emubd.h b/emubd/lfs_emubd.h index 4f87ccec..85718de1 100644 --- a/emubd/lfs_emubd.h +++ b/emubd/lfs_emubd.h @@ -1,19 +1,8 @@ /* * Block device emulated on standard files * - * Copyright (c) 2017 ARM Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause */ #ifndef LFS_EMUBD_H #define LFS_EMUBD_H diff --git a/lfs.c b/lfs.c index 3aef9fcc..c892fe99 100644 --- a/lfs.c +++ b/lfs.c @@ -1,19 +1,8 @@ /* * The little filesystem * - * Copyright (c) 2017 ARM Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause */ #include "lfs.h" #include "lfs_util.h" @@ -24,7 +13,7 @@ static int lfs_cache_read(lfs_t *lfs, lfs_cache_t *rcache, const lfs_cache_t *pcache, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { uint8_t *data = buffer; - LFS_ASSERT(block < lfs->cfg->block_count); + LFS_ASSERT(block != 0xffffffff); while (size > 0) { if (pcache && block == pcache->block && off >= pcache->off && @@ -68,6 +57,7 @@ static int lfs_cache_read(lfs_t *lfs, lfs_cache_t *rcache, } // load to cache, first condition can no longer fail + LFS_ASSERT(block < lfs->cfg->block_count); rcache->block = block; rcache->off = off - (off % lfs->cfg->read_size); int err = lfs->cfg->read(lfs->cfg, rcache->block, @@ -121,6 +111,7 @@ static int lfs_cache_crc(lfs_t *lfs, lfs_cache_t *rcache, static int lfs_cache_flush(lfs_t *lfs, lfs_cache_t *pcache, lfs_cache_t *rcache) { if (pcache->block != 0xffffffff) { + LFS_ASSERT(pcache->block < lfs->cfg->block_count); int err = lfs->cfg->prog(lfs->cfg, pcache->block, pcache->off, pcache->buffer, lfs->cfg->prog_size); if (err) { @@ -149,7 +140,8 @@ static int lfs_cache_prog(lfs_t *lfs, lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { const uint8_t *data = buffer; - LFS_ASSERT(block < lfs->cfg->block_count); + LFS_ASSERT(block != 0xffffffff); + LFS_ASSERT(off + size <= lfs->cfg->block_size); while (size > 0) { if (block == pcache->block && off >= pcache->off && @@ -181,6 +173,7 @@ static int lfs_cache_prog(lfs_t *lfs, lfs_cache_t *pcache, if (off % lfs->cfg->prog_size == 0 && size >= lfs->cfg->prog_size) { // bypass pcache? + LFS_ASSERT(block < lfs->cfg->block_count); lfs_size_t diff = size - (size % lfs->cfg->prog_size); int err = lfs->cfg->prog(lfs->cfg, block, off, data, diff); if (err) { @@ -240,6 +233,7 @@ static int lfs_bd_crc(lfs_t *lfs, lfs_block_t block, } static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) { + LFS_ASSERT(block < lfs->cfg->block_count); return lfs->cfg->erase(lfs->cfg, block); } @@ -360,6 +354,9 @@ static void lfs_superblock_fromle32(struct lfs_disk_superblock *d) { d->block_size = lfs_fromle32(d->block_size); d->block_count = lfs_fromle32(d->block_count); d->version = lfs_fromle32(d->version); + d->inline_size = lfs_fromle32(d->inline_size); + d->attrs_size = lfs_fromle32(d->attrs_size); + d->name_size = lfs_fromle32(d->name_size); } static void lfs_superblock_tole32(struct lfs_disk_superblock *d) { @@ -368,6 +365,29 @@ static void lfs_superblock_tole32(struct lfs_disk_superblock *d) { d->block_size = lfs_tole32(d->block_size); d->block_count = lfs_tole32(d->block_count); d->version = lfs_tole32(d->version); + d->inline_size = lfs_tole32(d->inline_size); + d->attrs_size = lfs_tole32(d->attrs_size); + d->name_size = lfs_tole32(d->name_size); +} + +/// Other struct functions /// +static inline lfs_size_t lfs_entry_elen(const lfs_entry_t *entry) { + return (lfs_size_t)(entry->d.elen) | + ((lfs_size_t)(entry->d.alen & 0xc0) << 2); +} + +static inline lfs_size_t lfs_entry_alen(const lfs_entry_t *entry) { + return entry->d.alen & 0x3f; +} + +static inline lfs_size_t lfs_entry_nlen(const lfs_entry_t *entry) { + return entry->d.nlen; +} + +static inline lfs_size_t lfs_entry_size(const lfs_entry_t *entry) { + return 4 + lfs_entry_elen(entry) + + lfs_entry_alen(entry) + + lfs_entry_nlen(entry); } @@ -396,10 +416,6 @@ static inline bool lfs_pairsync( (paira[0] == pairb[1] && paira[1] == pairb[0]); } -static inline lfs_size_t lfs_entry_size(const lfs_entry_t *entry) { - return 4 + entry->d.elen + entry->d.alen + entry->d.nlen; -} - static int lfs_dir_alloc(lfs_t *lfs, lfs_dir_t *dir) { // allocate pair of dir blocks for (int i = 0; i < 2; i++) { @@ -484,26 +500,176 @@ static int lfs_dir_fetch(lfs_t *lfs, } struct lfs_region { + enum { + LFS_FROM_MEM, + LFS_FROM_REGION, + LFS_FROM_ATTRS, + } type; + lfs_off_t oldoff; - lfs_size_t oldlen; - const void *newdata; - lfs_size_t newlen; + lfs_size_t oldsize; + const void *buffer; + lfs_size_t newsize; +}; + +struct lfs_region_attrs { + const struct lfs_attr *attrs; + int count; +}; + +struct lfs_region_region { + lfs_block_t block; + lfs_off_t off; + struct lfs_region *regions; + int count; }; +static int lfs_commit_region(lfs_t *lfs, uint32_t *crc, + lfs_block_t oldblock, lfs_off_t oldoff, + lfs_block_t newblock, lfs_off_t newoff, + lfs_off_t regionoff, lfs_size_t regionsize, + const struct lfs_region *regions, int count) { + int i = 0; + lfs_size_t newend = newoff + regionsize; + while (newoff < newend) { + // commit from different types of regions + if (i < count && regions[i].oldoff == oldoff - regionoff) { + switch (regions[i].type) { + case LFS_FROM_MEM: { + lfs_crc(crc, regions[i].buffer, regions[i].newsize); + int err = lfs_bd_prog(lfs, newblock, newoff, + regions[i].buffer, regions[i].newsize); + if (err) { + return err; + } + newoff += regions[i].newsize; + oldoff += regions[i].oldsize; + break; + } + case LFS_FROM_REGION: { + const struct lfs_region_region *disk = regions[i].buffer; + int err = lfs_commit_region(lfs, crc, + disk->block, disk->off, + newblock, newoff, + disk->off, regions[i].newsize, + disk->regions, disk->count); + if (err) { + return err; + } + newoff += regions[i].newsize; + oldoff -= regions[i].oldsize; + break; + } + case LFS_FROM_ATTRS: { + const struct lfs_region_attrs *attrs = regions[i].buffer; + + // order doesn't matter, so we write new attrs first. this + // is still O(n^2) but only O(n) disk access + for (int j = 0; j < attrs->count; j++) { + if (attrs->attrs[j].size == 0) { + continue; + } + + lfs_entry_attr_t attr; + attr.d.type = attrs->attrs[j].type; + attr.d.len = attrs->attrs[j].size; + + lfs_crc(crc, &attr.d, sizeof(attr.d)); + int err = lfs_bd_prog(lfs, newblock, newoff, + &attr.d, sizeof(attr.d)); + if (err) { + return err; + } + + lfs_crc(crc, + attrs->attrs[j].buffer, attrs->attrs[j].size); + err = lfs_bd_prog(lfs, newblock, newoff+sizeof(attr.d), + attrs->attrs[j].buffer, attrs->attrs[j].size); + if (err) { + return err; + } + + newoff += 2+attrs->attrs[j].size; + } + + // copy over attributes without updates + lfs_off_t oldend = oldoff + regions[i].oldsize; + while (oldoff < oldend) { + lfs_entry_attr_t attr; + int err = lfs_bd_read(lfs, oldblock, oldoff, + &attr.d, sizeof(attr.d)); + if (err) { + return err; + } + + bool updating = false; + for (int j = 0; j < attrs->count; j++) { + if (attr.d.type == attrs->attrs[j].type) { + updating = true; + } + } + + if (!updating) { + err = lfs_commit_region(lfs, crc, + oldblock, oldoff, + newblock, newoff, + 0, 2+attr.d.len, + NULL, 0); + if (err) { + return err; + } + + newoff += 2+attr.d.len; + } + + oldoff += 2+attr.d.len; + } + + break; + } + } + + i += 1; + } else { + // copy data from old block if not covered by region + uint8_t data; + int err = lfs_bd_read(lfs, oldblock, oldoff, &data, 1); + if (err) { + return err; + } + + lfs_crc(crc, &data, 1); + err = lfs_bd_prog(lfs, newblock, newoff, &data, 1); + if (err) { + return err; + } + + oldoff += 1; + newoff += 1; + } + } + + // sanity check our commit math + LFS_ASSERT(newoff == newend); + return 0; +} + static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir, const struct lfs_region *regions, int count) { + // state for copying over + const lfs_block_t oldpair[2] = {dir->pair[1], dir->pair[0]}; + bool relocated = false; + // increment revision count dir->d.rev += 1; // keep pairs in order such that pair[0] is most recent lfs_pairswap(dir->pair); for (int i = 0; i < count; i++) { - dir->d.size += regions[i].newlen - regions[i].oldlen; + dir->d.size += regions[i].newsize; + dir->d.size -= regions[i].oldsize; } - const lfs_block_t oldpair[2] = {dir->pair[0], dir->pair[1]}; - bool relocated = false; - while (true) { if (true) { int err = lfs_bd_erase(lfs, dir->pair[0]); @@ -514,6 +680,7 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir, return err; } + // commit header uint32_t crc = 0xffffffff; lfs_dir_tole32(&dir->d); lfs_crc(&crc, &dir->d, sizeof(dir->d)); @@ -526,47 +693,23 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir, return err; } - int i = 0; - lfs_off_t oldoff = sizeof(dir->d); - lfs_off_t newoff = sizeof(dir->d); - while (newoff < (0x7fffffff & dir->d.size)-4) { - if (i < count && regions[i].oldoff == oldoff) { - lfs_crc(&crc, regions[i].newdata, regions[i].newlen); - err = lfs_bd_prog(lfs, dir->pair[0], - newoff, regions[i].newdata, regions[i].newlen); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; - } - return err; - } - - oldoff += regions[i].oldlen; - newoff += regions[i].newlen; - i += 1; - } else { - uint8_t data; - err = lfs_bd_read(lfs, oldpair[1], oldoff, &data, 1); - if (err) { - return err; - } - - lfs_crc(&crc, &data, 1); - err = lfs_bd_prog(lfs, dir->pair[0], newoff, &data, 1); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; - } - return err; - } - - oldoff += 1; - newoff += 1; + // commit region + err = lfs_commit_region(lfs, &crc, + dir->pair[1], sizeof(dir->d), + dir->pair[0], sizeof(dir->d), + 0, (0x7fffffff & dir->d.size)-sizeof(dir->d)-4, + regions, count); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; } + return err; } + // commit crc crc = lfs_tole32(crc); - err = lfs_bd_prog(lfs, dir->pair[0], newoff, &crc, 4); + err = lfs_bd_prog(lfs, dir->pair[0], + (0x7fffffff & dir->d.size)-4, &crc, 4); crc = lfs_fromle32(crc); if (err) { if (err == LFS_ERR_CORRUPT) { @@ -597,6 +740,7 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir, } break; + relocate: //commit was corrupted LFS_DEBUG("Bad block at %d", dir->pair[0]); @@ -639,71 +783,111 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir, return 0; } -static int lfs_dir_update(lfs_t *lfs, lfs_dir_t *dir, - lfs_entry_t *entry, const void *data) { - lfs_entry_tole32(&entry->d); - int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){ - {entry->off, sizeof(entry->d), &entry->d, sizeof(entry->d)}, - {entry->off+sizeof(entry->d), entry->d.nlen, data, entry->d.nlen} - }, data ? 2 : 1); - lfs_entry_fromle32(&entry->d); - return err; +static int lfs_dir_get(lfs_t *lfs, const lfs_dir_t *dir, + lfs_off_t off, void *buffer, lfs_size_t size) { + return lfs_bd_read(lfs, dir->pair[0], off, buffer, size); } -static int lfs_dir_append(lfs_t *lfs, lfs_dir_t *dir, - lfs_entry_t *entry, const void *data) { - // check if we fit, if top bit is set we do not and move on - while (true) { - if (dir->d.size + lfs_entry_size(entry) <= lfs->cfg->block_size) { - entry->off = dir->d.size - 4; - - lfs_entry_tole32(&entry->d); - int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){ - {entry->off, 0, &entry->d, sizeof(entry->d)}, - {entry->off, 0, data, entry->d.nlen} - }, 2); - lfs_entry_fromle32(&entry->d); - return err; - } +static int lfs_dir_set(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry, + struct lfs_region *regions, int count) { + lfs_ssize_t diff = 0; + for (int i = 0; i < count; i++) { + diff += regions[i].newsize; + diff -= regions[i].oldsize; + } - // we need to allocate a new dir block - if (!(0x80000000 & dir->d.size)) { - lfs_dir_t olddir = *dir; - int err = lfs_dir_alloc(lfs, dir); + lfs_size_t oldsize = entry->size; + if (entry->off == 0) { + entry->off = (0x7fffffff & dir->d.size) - 4; + } + + if ((0x7fffffff & dir->d.size) + diff > lfs->cfg->block_size) { + lfs_dir_t olddir = *dir; + lfs_off_t oldoff = entry->off; + + if (oldsize) { + // mark as moving + uint8_t type; + int err = lfs_dir_get(lfs, &olddir, oldoff, &type, 1); if (err) { return err; } - dir->d.tail[0] = olddir.d.tail[0]; - dir->d.tail[1] = olddir.d.tail[1]; - entry->off = dir->d.size - 4; - lfs_entry_tole32(&entry->d); - err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){ - {entry->off, 0, &entry->d, sizeof(entry->d)}, - {entry->off, 0, data, entry->d.nlen} - }, 2); - lfs_entry_fromle32(&entry->d); + type |= LFS_STRUCT_MOVED; + err = lfs_dir_commit(lfs, &olddir, (struct lfs_region[]){ + {LFS_FROM_MEM, oldoff, 1, &type, 1}}, 1); if (err) { return err; } + } + + lfs_dir_t pdir = olddir; + + // find available block or create a new one + while ((0x7fffffff & dir->d.size) + oldsize + diff + > lfs->cfg->block_size) { + // we need to allocate a new dir block + if (!(0x80000000 & dir->d.size)) { + pdir = *dir; + int err = lfs_dir_alloc(lfs, dir); + if (err) { + return err; + } + + dir->d.tail[0] = pdir.d.tail[0]; + dir->d.tail[1] = pdir.d.tail[1]; + + break; + } + + int err = lfs_dir_fetch(lfs, dir, dir->d.tail); + if (err) { + return err; + } + } + + // writing out new entry + entry->off = dir->d.size - 4; + entry->size += diff; + int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){ + {LFS_FROM_REGION, entry->off, 0, &(struct lfs_region_region){ + olddir.pair[0], oldoff, + regions, count}, entry->size}}, 1); + if (err) { + return err; + } + // update pred dir, unless pred == old we can coalesce + if (!oldsize || lfs_paircmp(pdir.pair, olddir.pair) != 0) { + pdir.d.size |= 0x80000000; + pdir.d.tail[0] = dir->pair[0]; + pdir.d.tail[1] = dir->pair[1]; + + err = lfs_dir_commit(lfs, &pdir, NULL, 0); + if (err) { + return err; + } + } else if (oldsize) { olddir.d.size |= 0x80000000; olddir.d.tail[0] = dir->pair[0]; olddir.d.tail[1] = dir->pair[1]; - return lfs_dir_commit(lfs, &olddir, NULL, 0); } - int err = lfs_dir_fetch(lfs, dir, dir->d.tail); - if (err) { - return err; + // remove old entry + if (oldsize) { + lfs_entry_t oldentry; + oldentry.off = oldoff; + err = lfs_dir_set(lfs, &olddir, &oldentry, (struct lfs_region[]){ + {LFS_FROM_MEM, 0, oldsize, NULL, 0}}, 1); + if (err) { + return err; + } } + + goto shift; } -} -static int lfs_dir_remove(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { - // check if we should just drop the directory block - if ((dir->d.size & 0x7fffffff) == sizeof(dir->d)+4 - + lfs_entry_size(entry)) { + if ((0x7fffffff & dir->d.size) + diff == sizeof(dir->d)+4) { lfs_dir_t pdir; int res = lfs_pred(lfs, dir->pair, &pdir); if (res < 0) { @@ -714,26 +898,34 @@ static int lfs_dir_remove(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { pdir.d.size &= dir->d.size | 0x7fffffff; pdir.d.tail[0] = dir->d.tail[0]; pdir.d.tail[1] = dir->d.tail[1]; - return lfs_dir_commit(lfs, &pdir, NULL, 0); + int err = lfs_dir_commit(lfs, &pdir, NULL, 0); + if (err) { + return err; + } + goto shift; } } - // shift out the entry - int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){ - {entry->off, lfs_entry_size(entry), NULL, 0}, - }, 1); + for (int i = 0; i < count; i++) { + regions[i].oldoff += entry->off; + } + + int err = lfs_dir_commit(lfs, dir, regions, count); if (err) { return err; } + entry->size += diff; + +shift: // shift over any files/directories that are affected for (lfs_file_t *f = lfs->files; f; f = f->next) { if (lfs_paircmp(f->pair, dir->pair) == 0) { - if (f->poff == entry->off) { + if (f->pairoff == entry->off && entry->size == 0) { f->pair[0] = 0xffffffff; f->pair[1] = 0xffffffff; - } else if (f->poff > entry->off) { - f->poff -= lfs_entry_size(entry); + } else if (f->pairoff > entry->off) { + f->pairoff += diff; } } } @@ -741,8 +933,8 @@ static int lfs_dir_remove(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { for (lfs_dir_t *d = lfs->dirs; d; d = d->next) { if (lfs_paircmp(d->pair, dir->pair) == 0) { if (d->off > entry->off) { - d->off -= lfs_entry_size(entry); - d->pos -= lfs_entry_size(entry); + d->off += diff; + d->pos += diff; } } } @@ -751,7 +943,7 @@ static int lfs_dir_remove(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { } static int lfs_dir_next(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { - while (dir->off + sizeof(entry->d) > (0x7fffffff & dir->d.size)-4) { + while (dir->off >= (0x7fffffff & dir->d.size)-4) { if (!(0x80000000 & dir->d.size)) { entry->off = dir->off; return LFS_ERR_NOENT; @@ -766,23 +958,23 @@ static int lfs_dir_next(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { dir->pos += sizeof(dir->d) + 4; } - int err = lfs_bd_read(lfs, dir->pair[0], dir->off, - &entry->d, sizeof(entry->d)); + int err = lfs_dir_get(lfs, dir, dir->off, &entry->d, sizeof(entry->d)); lfs_entry_fromle32(&entry->d); if (err) { return err; } entry->off = dir->off; - dir->off += lfs_entry_size(entry); - dir->pos += lfs_entry_size(entry); + entry->size = lfs_entry_size(entry); + dir->off += entry->size; + dir->pos += entry->size; return 0; } static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry, const char **path) { const char *pathname = *path; - size_t pathlen; + lfs_size_t pathlen; while (true) { nextname: @@ -793,10 +985,7 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir, // special case for root dir if (pathname[0] == '\0') { *entry = (lfs_entry_t){ - .d.type = LFS_TYPE_DIR, - .d.elen = sizeof(entry->d) - 4, - .d.alen = 0, - .d.nlen = 0, + .d.type = LFS_STRUCT_DIR | LFS_TYPE_DIR, .d.u.dir[0] = lfs->root[0], .d.u.dir[1] = lfs->root[1], }; @@ -812,7 +1001,7 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir, // skip if matched by '..' in name const char *suffix = pathname + pathlen; - size_t sufflen; + lfs_size_t sufflen; int depth = 1; while (true) { suffix += strspn(suffix, "/"); @@ -844,14 +1033,14 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir, return err; } - if (((0x7f & entry->d.type) != LFS_TYPE_REG && - (0x7f & entry->d.type) != LFS_TYPE_DIR) || + if (((0xf & entry->d.type) != LFS_TYPE_REG && + (0xf & entry->d.type) != LFS_TYPE_DIR) || entry->d.nlen != pathlen) { continue; } int res = lfs_bd_cmp(lfs, dir->pair[0], - entry->off + 4+entry->d.elen+entry->d.alen, + entry->off + entry->size - pathlen, pathname, pathlen); if (res < 0) { return res; @@ -864,13 +1053,13 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir, } // check that entry has not been moved - if (entry->d.type & 0x80) { + if (entry->d.type & LFS_STRUCT_MOVED) { int moved = lfs_moved(lfs, &entry->d.u); if (moved < 0 || moved) { return (moved < 0) ? moved : LFS_ERR_NOENT; } - entry->d.type &= ~0x80; + entry->d.type &= ~LFS_STRUCT_MOVED; } pathname += pathlen; @@ -880,7 +1069,7 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir, } // continue on if we hit a directory - if (entry->d.type != LFS_TYPE_DIR) { + if ((0xf & entry->d.type) != LFS_TYPE_DIR) { return LFS_ERR_NOTDIR; } @@ -891,6 +1080,131 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir, } } +/// Internal attribute operations /// +static int lfs_dir_getinfo(lfs_t *lfs, + lfs_dir_t *dir, const lfs_entry_t *entry, struct lfs_info *info) { + memset(info, 0, sizeof(*info)); + info->type = 0xf & entry->d.type; + if (entry->d.type == (LFS_STRUCT_CTZ | LFS_TYPE_REG)) { + info->size = entry->d.u.file.size; + } else if (entry->d.type == (LFS_STRUCT_INLINE | LFS_TYPE_REG)) { + info->size = lfs_entry_elen(entry); + } + + if (lfs_paircmp(entry->d.u.dir, lfs->root) == 0) { + strcpy(info->name, "/"); + } else { + int err = lfs_dir_get(lfs, dir, + entry->off + entry->size - entry->d.nlen, + info->name, entry->d.nlen); + if (err) { + return err; + } + } + + return 0; +} + +static int lfs_dir_getattrs(lfs_t *lfs, + lfs_dir_t *dir, const lfs_entry_t *entry, + const struct lfs_attr *attrs, int count) { + // set to zero in case we can't find the attributes or size mismatch + for (int j = 0; j < count; j++) { + memset(attrs[j].buffer, 0, attrs[j].size); + } + + // search for attribute in attribute region + lfs_off_t off = entry->off + 4+lfs_entry_elen(entry); + lfs_off_t end = off + lfs_entry_alen(entry); + while (off < end) { + lfs_entry_attr_t attr; + int err = lfs_dir_get(lfs, dir, off, &attr.d, sizeof(attr.d)); + if (err) { + return err; + } + + for (int j = 0; j < count; j++) { + if (attrs[j].type == attr.d.type) { + if (attrs[j].size < attr.d.len) { + return LFS_ERR_RANGE; + } + + err = lfs_dir_get(lfs, dir, off+sizeof(attr.d), + attrs[j].buffer, attr.d.len); + if (err) { + return err; + } + } + } + + off += 2+attr.d.len; + } + + return 0; +} + +static lfs_ssize_t lfs_dir_checkattrs(lfs_t *lfs, + lfs_dir_t *dir, lfs_entry_t *entry, + const struct lfs_attr *attrs, int count) { + // check that attributes fit + // two separate passes so disk access is O(n) + lfs_size_t nsize = 0; + for (int j = 0; j < count; j++) { + if (attrs[j].size > 0) { + nsize += 2+attrs[j].size; + } + } + + lfs_off_t off = entry->off + 4+lfs_entry_elen(entry); + lfs_off_t end = off + lfs_entry_alen(entry); + while (off < end) { + lfs_entry_attr_t attr; + int err = lfs_dir_get(lfs, dir, off, &attr.d, sizeof(attr.d)); + if (err) { + return err; + } + + bool updated = false; + for (int j = 0; j < count; j++) { + if (attr.d.type == attrs[j].type) { + updated = true; + } + } + + if (!updated) { + nsize += 2+attr.d.len; + } + + off += 2+attr.d.len; + } + + if (nsize > lfs->attrs_size || ( + lfs_entry_size(entry) - lfs_entry_alen(entry) + nsize + > lfs->cfg->block_size)) { + return LFS_ERR_NOSPC; + } + + return nsize; +} + +static int lfs_dir_setattrs(lfs_t *lfs, + lfs_dir_t *dir, lfs_entry_t *entry, + const struct lfs_attr *attrs, int count) { + // make sure attributes fit + lfs_size_t oldlen = lfs_entry_alen(entry); + lfs_ssize_t newlen = lfs_dir_checkattrs(lfs, dir, entry, attrs, count); + if (newlen < 0) { + return newlen; + } + + // commit to entry, majority of work is in LFS_FROM_ATTRS + entry->d.alen = (0xc0 & entry->d.alen) | newlen; + return lfs_dir_set(lfs, dir, entry, (struct lfs_region[]){ + {LFS_FROM_MEM, 0, 4, &entry->d, 4}, + {LFS_FROM_ATTRS, 4+lfs_entry_elen(entry), oldlen, + &(struct lfs_region_attrs){attrs, count}, newlen}}, 2); +} + /// Top level directory operations /// int lfs_mkdir(lfs_t *lfs, const char *path) { @@ -915,6 +1229,12 @@ int lfs_mkdir(lfs_t *lfs, const char *path) { return err ? err : LFS_ERR_EXIST; } + // check that name fits + lfs_size_t nlen = strlen(path); + if (nlen > lfs->name_size) { + return LFS_ERR_NAMETOOLONG; + } + // build up new directory lfs_alloc_ack(lfs); @@ -931,17 +1251,20 @@ int lfs_mkdir(lfs_t *lfs, const char *path) { return err; } - entry.d.type = LFS_TYPE_DIR; + entry.d.type = LFS_STRUCT_DIR | LFS_TYPE_DIR; entry.d.elen = sizeof(entry.d) - 4; entry.d.alen = 0; - entry.d.nlen = strlen(path); + entry.d.nlen = nlen; entry.d.u.dir[0] = dir.pair[0]; entry.d.u.dir[1] = dir.pair[1]; + entry.size = 0; cwd.d.tail[0] = dir.pair[0]; cwd.d.tail[1] = dir.pair[1]; - - err = lfs_dir_append(lfs, &cwd, &entry, path); + lfs_entry_tole32(&entry.d); + err = lfs_dir_set(lfs, &cwd, &entry, (struct lfs_region[]){ + {LFS_FROM_MEM, 0, 0, &entry.d, sizeof(entry.d)}, + {LFS_FROM_MEM, 0, 0, path, nlen}}, 2); if (err) { return err; } @@ -963,7 +1286,7 @@ int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) { err = lfs_dir_find(lfs, dir, &entry, &path); if (err) { return err; - } else if (entry.d.type != LFS_TYPE_DIR) { + } else if (entry.d.type != (LFS_STRUCT_DIR | LFS_TYPE_DIR)) { return LFS_ERR_NOTDIR; } @@ -1021,13 +1344,13 @@ int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { return (err == LFS_ERR_NOENT) ? 0 : err; } - if ((0x7f & entry.d.type) != LFS_TYPE_REG && - (0x7f & entry.d.type) != LFS_TYPE_DIR) { + if ((0xf & entry.d.type) != LFS_TYPE_REG && + (0xf & entry.d.type) != LFS_TYPE_DIR) { continue; } // check that entry has not been moved - if (entry.d.type & 0x80) { + if (entry.d.type & LFS_STRUCT_MOVED) { int moved = lfs_moved(lfs, &entry.d.u); if (moved < 0) { return moved; @@ -1037,20 +1360,13 @@ int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { continue; } - entry.d.type &= ~0x80; + entry.d.type &= ~LFS_STRUCT_MOVED; } break; } - info->type = entry.d.type; - if (info->type == LFS_TYPE_REG) { - info->size = entry.d.u.file.size; - } - - int err = lfs_bd_read(lfs, dir->pair[0], - entry.off + 4+entry.d.elen+entry.d.alen, - info->name, entry.d.nlen); + int err = lfs_dir_getinfo(lfs, dir, &entry, info); if (err) { return err; } @@ -1318,40 +1634,31 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file, return LFS_ERR_NOENT; } + // check that name fits + lfs_size_t nlen = strlen(path); + if (nlen > lfs->name_size) { + return LFS_ERR_NAMETOOLONG; + } + // create entry to remember name - entry.d.type = LFS_TYPE_REG; - entry.d.elen = sizeof(entry.d) - 4; + entry.d.type = LFS_STRUCT_INLINE | LFS_TYPE_REG; + entry.d.elen = 0; entry.d.alen = 0; - entry.d.nlen = strlen(path); - entry.d.u.file.head = 0xffffffff; - entry.d.u.file.size = 0; - err = lfs_dir_append(lfs, &cwd, &entry, path); + entry.d.nlen = nlen; + entry.size = 0; + + err = lfs_dir_set(lfs, &cwd, &entry, (struct lfs_region[]){ + {LFS_FROM_MEM, 0, 0, &entry.d, 4}, + {LFS_FROM_MEM, 0, 0, path, nlen}}, 2); if (err) { return err; } - } else if (entry.d.type == LFS_TYPE_DIR) { + } else if ((0xf & entry.d.type) == LFS_TYPE_DIR) { return LFS_ERR_ISDIR; } else if (flags & LFS_O_EXCL) { return LFS_ERR_EXIST; } - // setup file struct - file->pair[0] = cwd.pair[0]; - file->pair[1] = cwd.pair[1]; - file->poff = entry.off; - file->head = entry.d.u.file.head; - file->size = entry.d.u.file.size; - file->flags = flags; - file->pos = 0; - - if (flags & LFS_O_TRUNC) { - if (file->size != 0) { - file->flags |= LFS_F_DIRTY; - } - file->head = 0xffffffff; - file->size = 0; - } - // allocate buffer if needed file->cache.block = 0xffffffff; if (lfs->cfg->file_buffer) { @@ -1368,6 +1675,51 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file, } } + // setup file struct + file->pair[0] = cwd.pair[0]; + file->pair[1] = cwd.pair[1]; + file->pairoff = entry.off; + file->flags = flags; + file->pos = 0; + + // calculate max inline size based on the size of the entry + file->inline_size = lfs_min(lfs->inline_size, + lfs->cfg->block_size - (sizeof(cwd.d)+4) - + (lfs_entry_size(&entry) - lfs_entry_elen(&entry))); + + if ((0x70 & entry.d.type) == LFS_STRUCT_INLINE) { + // load inline files + file->head = 0xfffffffe; + file->size = lfs_entry_elen(&entry); + file->flags |= LFS_F_INLINE; + file->cache.block = file->head; + file->cache.off = 0; + err = lfs_dir_get(lfs, &cwd, + entry.off + 4, + file->cache.buffer, file->size); + if (err) { + lfs_free(file->cache.buffer); + return err; + } + } else { + // use ctz list from entry + file->head = entry.d.u.file.head; + file->size = entry.d.u.file.size; + } + + // truncate if requested + if (flags & LFS_O_TRUNC) { + if (file->size != 0) { + file->flags |= LFS_F_DIRTY; + } + + file->head = 0xfffffffe; + file->size = 0; + file->flags |= LFS_F_INLINE; + file->cache.block = file->head; + file->cache.off = 0; + } + // add to list of files file->next = lfs->files; lfs->files = file; @@ -1395,9 +1747,7 @@ int lfs_file_close(lfs_t *lfs, lfs_file_t *file) { } static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) { -relocate: - LFS_DEBUG("Bad block at %d", file->block); - +relocate:; // just relocate what exists into new block lfs_block_t nblock; int err = lfs_alloc(lfs, &nblock); @@ -1444,61 +1794,64 @@ static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) { static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) { if (file->flags & LFS_F_READING) { - // just drop read cache - file->cache.block = 0xffffffff; file->flags &= ~LFS_F_READING; } if (file->flags & LFS_F_WRITING) { lfs_off_t pos = file->pos; - // copy over anything after current branch - lfs_file_t orig = { - .head = file->head, - .size = file->size, - .flags = LFS_O_RDONLY, - .pos = file->pos, - .cache = lfs->rcache, - }; - lfs->rcache.block = 0xffffffff; - - while (file->pos < file->size) { - // copy over a byte at a time, leave it up to caching - // to make this efficient - uint8_t data; - lfs_ssize_t res = lfs_file_read(lfs, &orig, &data, 1); - if (res < 0) { - return res; - } + if (!(file->flags & LFS_F_INLINE)) { + // copy over anything after current branch + lfs_file_t orig = { + .head = file->head, + .size = file->size, + .flags = LFS_O_RDONLY, + .pos = file->pos, + .cache = lfs->rcache, + }; + lfs->rcache.block = 0xffffffff; - res = lfs_file_write(lfs, file, &data, 1); - if (res < 0) { - return res; - } + while (file->pos < file->size) { + // copy over a byte at a time, leave it up to caching + // to make this efficient + uint8_t data; + lfs_ssize_t res = lfs_file_read(lfs, &orig, &data, 1); + if (res < 0) { + return res; + } - // keep our reference to the rcache in sync - if (lfs->rcache.block != 0xffffffff) { - orig.cache.block = 0xffffffff; - lfs->rcache.block = 0xffffffff; - } - } + res = lfs_file_write(lfs, file, &data, 1); + if (res < 0) { + return res; + } - // write out what we have - while (true) { - int err = lfs_cache_flush(lfs, &file->cache, &lfs->rcache); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; + // keep our reference to the rcache in sync + if (lfs->rcache.block != 0xffffffff) { + orig.cache.block = 0xffffffff; + lfs->rcache.block = 0xffffffff; } - return err; } - break; + // write out what we have + while (true) { + int err = lfs_cache_flush(lfs, &file->cache, &lfs->rcache); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + break; relocate: - err = lfs_file_relocate(lfs, file); - if (err) { - return err; + LFS_DEBUG("Bad block at %d", file->block); + err = lfs_file_relocate(lfs, file); + if (err) { + return err; + } } + } else { + file->size = lfs_max(file->pos, file->size); } // actual file updates @@ -1529,19 +1882,52 @@ int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) { return err; } - lfs_entry_t entry = {.off = file->poff}; - err = lfs_bd_read(lfs, cwd.pair[0], entry.off, - &entry.d, sizeof(entry.d)); - lfs_entry_fromle32(&entry.d); + lfs_entry_t entry = {.off = file->pairoff}; + err = lfs_dir_get(lfs, &cwd, entry.off, &entry.d, 4); if (err) { return err; } + entry.size = lfs_entry_size(&entry); + + LFS_ASSERT((0xf & entry.d.type) == LFS_TYPE_REG); + lfs_size_t oldelen = lfs_entry_elen(&entry); + lfs_size_t oldalen = lfs_entry_alen(&entry); + const void *buffer; + lfs_size_t size; + + // either update the references or inline the whole file + if (!(file->flags & LFS_F_INLINE)) { + entry.d.type = LFS_STRUCT_CTZ | LFS_TYPE_REG; + entry.d.u.file.head = file->head; + entry.d.u.file.size = file->size; + + lfs_entry_tole32(&entry.d); + buffer = (const uint8_t *)&entry.d + 4; + size = sizeof(entry.d) - 4; + } else { + entry.d.type = LFS_STRUCT_INLINE | LFS_TYPE_REG; + + buffer = file->cache.buffer; + size = file->size; + } - LFS_ASSERT(entry.d.type == LFS_TYPE_REG); - entry.d.u.file.head = file->head; - entry.d.u.file.size = file->size; + // get new alen from disk + lfs_ssize_t newalen = lfs_dir_checkattrs(lfs, &cwd, &entry, + file->attrs, file->attrcount); + if (newalen < 0) { + return newalen; + } - err = lfs_dir_update(lfs, &cwd, &entry, NULL); + entry.d.elen = size & 0xff; + entry.d.alen = (newalen & 0x3f) | ((size >> 2) & 0xc0); + + // write out update + err = lfs_dir_set(lfs, &cwd, &entry, (struct lfs_region[]){ + {LFS_FROM_MEM, 0, 4, &entry.d, 4}, + {LFS_FROM_MEM, 4, oldelen, buffer, size}, + {LFS_FROM_ATTRS, 4+oldelen, oldalen, + &(struct lfs_region_attrs){file->attrs, file->attrcount}, + newalen}}, 3); if (err) { return err; } @@ -1581,11 +1967,16 @@ lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, // check if we need a new block if (!(file->flags & LFS_F_READING) || file->off == lfs->cfg->block_size) { - int err = lfs_ctz_find(lfs, &file->cache, NULL, - file->head, file->size, - file->pos, &file->block, &file->off); - if (err) { - return err; + if (!(file->flags & LFS_F_INLINE)) { + int err = lfs_ctz_find(lfs, &file->cache, NULL, + file->head, file->size, + file->pos, &file->block, &file->off); + if (err) { + return err; + } + } else { + file->block = 0xfffffffe; + file->off = file->pos; } file->flags |= LFS_F_READING; @@ -1642,32 +2033,54 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, } } + if ((file->flags & LFS_F_INLINE) && + file->pos + nsize >= file->inline_size) { + // inline file doesn't fit anymore + file->block = 0xfffffffe; + file->off = file->pos; + + lfs_alloc_ack(lfs); + int err = lfs_file_relocate(lfs, file); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } + + file->flags &= ~LFS_F_INLINE; + file->flags |= LFS_F_WRITING; + } + while (nsize > 0) { // check if we need a new block if (!(file->flags & LFS_F_WRITING) || file->off == lfs->cfg->block_size) { - if (!(file->flags & LFS_F_WRITING) && file->pos > 0) { - // find out which block we're extending from - int err = lfs_ctz_find(lfs, &file->cache, NULL, - file->head, file->size, - file->pos-1, &file->block, &file->off); + if (!(file->flags & LFS_F_INLINE)) { + if (!(file->flags & LFS_F_WRITING) && file->pos > 0) { + // find out which block we're extending from + int err = lfs_ctz_find(lfs, &file->cache, NULL, + file->head, file->size, + file->pos-1, &file->block, &file->off); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } + + // mark cache as dirty since we may have read data into it + file->cache.block = 0xffffffff; + } + + // extend file with new blocks + lfs_alloc_ack(lfs); + int err = lfs_ctz_extend(lfs, &lfs->rcache, &file->cache, + file->block, file->pos, + &file->block, &file->off); if (err) { file->flags |= LFS_F_ERRED; return err; } - - // mark cache as dirty since we may have read data into it - file->cache.block = 0xffffffff; - } - - // extend file with new blocks - lfs_alloc_ack(lfs); - int err = lfs_ctz_extend(lfs, &lfs->rcache, &file->cache, - file->block, file->pos, - &file->block, &file->off); - if (err) { - file->flags |= LFS_F_ERRED; - return err; + } else { + file->block = 0xfffffffe; + file->off = file->pos; } file->flags |= LFS_F_WRITING; @@ -1810,6 +2223,87 @@ lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) { } } +int lfs_file_getattrs(lfs_t *lfs, lfs_file_t *file, + const struct lfs_attr *attrs, int count) { + // set to null in case we can't find the attrs (missing file?) + for (int j = 0; j < count; j++) { + memset(attrs[j].buffer, 0, attrs[j].size); + } + + // load from disk if we haven't already been deleted + if (!lfs_pairisnull(file->pair)) { + lfs_dir_t cwd; + int err = lfs_dir_fetch(lfs, &cwd, file->pair); + if (err) { + return err; + } + + lfs_entry_t entry = {.off = file->pairoff}; + err = lfs_dir_get(lfs, &cwd, entry.off, &entry.d, 4); + if (err) { + return err; + } + entry.size = lfs_entry_size(&entry); + + err = lfs_dir_getattrs(lfs, &cwd, &entry, attrs, count); + if (err) { + return err; + } + } + + // override an attrs we have stored locally + for (int i = 0; i < file->attrcount; i++) { + for (int j = 0; j < count; j++) { + if (attrs[j].type == file->attrs[i].type) { + if (attrs[j].size < file->attrs[i].size) { + return LFS_ERR_RANGE; + } + + memset(attrs[j].buffer, 0, attrs[j].size); + memcpy(attrs[j].buffer, + file->attrs[i].buffer, file->attrs[i].size); + } + } + } + + return 0; +} + +int lfs_file_setattrs(lfs_t *lfs, lfs_file_t *file, + const struct lfs_attr *attrs, int count) { + if ((file->flags & 3) == LFS_O_RDONLY) { + return LFS_ERR_BADF; + } + + // at least make sure attributes fit + if (!lfs_pairisnull(file->pair)) { + lfs_dir_t cwd; + int err = lfs_dir_fetch(lfs, &cwd, file->pair); + if (err) { + return err; + } + + lfs_entry_t entry = {.off = file->pairoff}; + err = lfs_dir_get(lfs, &cwd, entry.off, &entry.d, 4); + if (err) { + return err; + } + entry.size = lfs_entry_size(&entry); + + lfs_ssize_t res = lfs_dir_checkattrs(lfs, &cwd, &entry, attrs, count); + if (res < 0) { + return res; + } + } + + // just tack to the file, will be written at sync time + file->attrs = attrs; + file->attrcount = count; + file->flags |= LFS_F_DIRTY; + + return 0; +} + /// General fs operations /// int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) { @@ -1825,24 +2319,7 @@ int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) { return err; } - memset(info, 0, sizeof(*info)); - info->type = entry.d.type; - if (info->type == LFS_TYPE_REG) { - info->size = entry.d.u.file.size; - } - - if (lfs_paircmp(entry.d.u.dir, lfs->root) == 0) { - strcpy(info->name, "/"); - } else { - err = lfs_bd_read(lfs, cwd.pair[0], - entry.off + 4+entry.d.elen+entry.d.alen, - info->name, entry.d.nlen); - if (err) { - return err; - } - } - - return 0; + return lfs_dir_getinfo(lfs, &cwd, &entry, info); } int lfs_remove(lfs_t *lfs, const char *path) { @@ -1867,7 +2344,7 @@ int lfs_remove(lfs_t *lfs, const char *path) { } lfs_dir_t dir; - if (entry.d.type == LFS_TYPE_DIR) { + if ((0xf & entry.d.type) == LFS_TYPE_DIR) { // must be empty before removal, checking size // without masking top bit checks for any case where // dir is not empty @@ -1880,13 +2357,14 @@ int lfs_remove(lfs_t *lfs, const char *path) { } // remove the entry - err = lfs_dir_remove(lfs, &cwd, &entry); + err = lfs_dir_set(lfs, &cwd, &entry, (struct lfs_region[]){ + {LFS_FROM_MEM, 0, entry.size, NULL, 0}}, 1); if (err) { return err; } // if we were a directory, find pred, replace tail - if (entry.d.type == LFS_TYPE_DIR) { + if ((0xf & entry.d.type) == LFS_TYPE_DIR) { int res = lfs_pred(lfs, dir.pair, &cwd); if (res < 0) { return res; @@ -1943,13 +2421,23 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { bool prevexists = (err != LFS_ERR_NOENT); bool samepair = (lfs_paircmp(oldcwd.pair, newcwd.pair) == 0); + // check that name fits + lfs_size_t nlen = strlen(newpath); + if (nlen > lfs->name_size) { + return LFS_ERR_NAMETOOLONG; + } + + if (oldentry.size - oldentry.d.nlen + nlen > lfs->cfg->block_size) { + return LFS_ERR_NOSPC; + } + // must have same type if (prevexists && preventry.d.type != oldentry.d.type) { return LFS_ERR_ISDIR; } lfs_dir_t dir; - if (prevexists && preventry.d.type == LFS_TYPE_DIR) { + if (prevexists && (0xf & preventry.d.type) == LFS_TYPE_DIR) { // must be empty before removal, checking size // without masking top bit checks for any case where // dir is not empty @@ -1962,8 +2450,10 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { } // mark as moving - oldentry.d.type |= 0x80; - err = lfs_dir_update(lfs, &oldcwd, &oldentry, NULL); + oldentry.d.type |= LFS_STRUCT_MOVED; + err = lfs_dir_set(lfs, &oldcwd, &oldentry, (struct lfs_region[]){ + {LFS_FROM_MEM, 0, 1, &oldentry.d.type, 1}}, 1); + oldentry.d.type &= ~LFS_STRUCT_MOVED; if (err) { return err; } @@ -1976,19 +2466,20 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { // move to new location lfs_entry_t newentry = preventry; newentry.d = oldentry.d; - newentry.d.type &= ~0x80; - newentry.d.nlen = strlen(newpath); - - if (prevexists) { - err = lfs_dir_update(lfs, &newcwd, &newentry, newpath); - if (err) { - return err; - } - } else { - err = lfs_dir_append(lfs, &newcwd, &newentry, newpath); - if (err) { - return err; - } + newentry.d.type &= ~LFS_STRUCT_MOVED; + newentry.d.nlen = nlen; + newentry.size = prevexists ? preventry.size : 0; + + lfs_size_t newsize = oldentry.size - oldentry.d.nlen + newentry.d.nlen; + err = lfs_dir_set(lfs, &newcwd, &newentry, (struct lfs_region[]){ + {LFS_FROM_REGION, 0, prevexists ? preventry.size : 0, + &(struct lfs_region_region){ + oldcwd.pair[0], oldentry.off, (struct lfs_region[]){ + {LFS_FROM_MEM, 0, 4, &newentry.d, 4}, + {LFS_FROM_MEM, newsize-nlen, 0, newpath, nlen}}, 2}, + newsize}}, 1); + if (err) { + return err; } // update pair if newcwd == oldcwd @@ -1997,13 +2488,14 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { } // remove old entry - err = lfs_dir_remove(lfs, &oldcwd, &oldentry); + err = lfs_dir_set(lfs, &oldcwd, &oldentry, (struct lfs_region[]){ + {LFS_FROM_MEM, 0, oldentry.size, NULL, 0}}, 1); if (err) { return err; } // if we were a directory, find pred, replace tail - if (prevexists && preventry.d.type == LFS_TYPE_DIR) { + if (prevexists && (0xf & preventry.d.type) == LFS_TYPE_DIR) { int res = lfs_pred(lfs, dir.pair, &newcwd); if (res < 0) { return res; @@ -2022,6 +2514,40 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { return 0; } +int lfs_getattrs(lfs_t *lfs, const char *path, + const struct lfs_attr *attrs, int count) { + lfs_dir_t cwd; + int err = lfs_dir_fetch(lfs, &cwd, lfs->root); + if (err) { + return err; + } + + lfs_entry_t entry; + err = lfs_dir_find(lfs, &cwd, &entry, &path); + if (err) { + return err; + } + + return lfs_dir_getattrs(lfs, &cwd, &entry, attrs, count); +} + +int lfs_setattrs(lfs_t *lfs, const char *path, + const struct lfs_attr *attrs, int count) { + lfs_dir_t cwd; + int err = lfs_dir_fetch(lfs, &cwd, lfs->root); + if (err) { + return err; + } + + lfs_entry_t entry; + err = lfs_dir_find(lfs, &cwd, &entry, &path); + if (err) { + return err; + } + + return lfs_dir_setattrs(lfs, &cwd, &entry, attrs, count); +} + /// Filesystem operations /// static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { @@ -2069,6 +2595,26 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4)) <= lfs->cfg->block_size); + // check that the size limits are sane + LFS_ASSERT(lfs->cfg->inline_size <= LFS_INLINE_MAX); + LFS_ASSERT(lfs->cfg->inline_size <= lfs->cfg->read_size); + lfs->inline_size = lfs->cfg->inline_size; + if (!lfs->inline_size) { + lfs->inline_size = lfs_min(LFS_INLINE_MAX, lfs->cfg->read_size); + } + + LFS_ASSERT(lfs->cfg->attrs_size <= LFS_ATTRS_MAX); + lfs->attrs_size = lfs->cfg->attrs_size; + if (!lfs->attrs_size) { + lfs->attrs_size = LFS_ATTRS_MAX; + } + + LFS_ASSERT(lfs->cfg->name_size <= LFS_NAME_MAX); + lfs->name_size = lfs->cfg->name_size; + if (!lfs->name_size) { + lfs->name_size = LFS_NAME_MAX; + } + // setup default state lfs->root[0] = 0xffffffff; lfs->root[1] = 0xffffffff; @@ -2130,40 +2676,36 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { lfs->root[0] = root.pair[0]; lfs->root[1] = root.pair[1]; + superdir.d.tail[0] = lfs->root[0]; + superdir.d.tail[1] = lfs->root[1]; - // write superblocks - lfs_superblock_t superblock = { - .off = sizeof(superdir.d), - .d.type = LFS_TYPE_SUPERBLOCK, - .d.elen = sizeof(superblock.d) - sizeof(superblock.d.magic) - 4, - .d.nlen = sizeof(superblock.d.magic), - .d.version = LFS_DISK_VERSION, - .d.magic = {"littlefs"}, - .d.block_size = lfs->cfg->block_size, - .d.block_count = lfs->cfg->block_count, - .d.root = {lfs->root[0], lfs->root[1]}, - }; - superdir.d.tail[0] = root.pair[0]; - superdir.d.tail[1] = root.pair[1]; - superdir.d.size = sizeof(superdir.d) + sizeof(superblock.d) + 4; - - // write both pairs to be safe + // write one superblock + lfs_superblock_t superblock; + superblock.d.version = LFS_DISK_VERSION, + superblock.d.root[0] = lfs->root[0]; + superblock.d.root[1] = lfs->root[1]; + superblock.d.block_size = lfs->cfg->block_size; + superblock.d.block_count = lfs->cfg->block_count; + superblock.d.inline_size = lfs->inline_size; + superblock.d.attrs_size = lfs->attrs_size; + superblock.d.name_size = lfs->name_size; + + lfs_entry_t superentry; + superentry.d.type = LFS_STRUCT_DIR | LFS_TYPE_SUPERBLOCK; + superentry.d.elen = sizeof(superblock.d); + superentry.d.alen = 0; + superentry.d.nlen = strlen("littlefs"); + superentry.off = sizeof(superdir.d); + superentry.size = 0; + + lfs_entry_tole32(&superentry.d); lfs_superblock_tole32(&superblock.d); - bool valid = false; - for (int i = 0; i < 2; i++) { - err = lfs_dir_commit(lfs, &superdir, (struct lfs_region[]){ - {sizeof(superdir.d), sizeof(superblock.d), - &superblock.d, sizeof(superblock.d)} - }, 1); - if (err && err != LFS_ERR_CORRUPT) { - return err; - } - - valid = valid || !err; - } - - if (!valid) { - return LFS_ERR_CORRUPT; + err = lfs_dir_set(lfs, &superdir, &superentry, (struct lfs_region[]){ + {LFS_FROM_MEM, 0, 0, &superentry.d, 4}, + {LFS_FROM_MEM, 0, 0, &superblock.d, sizeof(superblock.d)}, + {LFS_FROM_MEM, 0, 0, "littlefs", superentry.d.nlen}}, 3); + if (err) { + return err; } // sanity check that fetch works @@ -2172,7 +2714,6 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { return err; } - lfs_alloc_ack(lfs); return lfs_deinit(lfs); } @@ -2190,25 +2731,39 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { // load superblock lfs_dir_t dir; - lfs_superblock_t superblock; err = lfs_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1}); - if (err && err != LFS_ERR_CORRUPT) { + if (err) { + if (err == LFS_ERR_CORRUPT) { + LFS_ERROR("Invalid superblock at %d %d", 0, 1); + } return err; } - if (!err) { - err = lfs_bd_read(lfs, dir.pair[0], sizeof(dir.d), - &superblock.d, sizeof(superblock.d)); - lfs_superblock_fromle32(&superblock.d); - if (err) { - return err; - } + lfs_entry_t entry = {.off = sizeof(dir.d)}; + err = lfs_dir_get(lfs, &dir, entry.off, &entry.d, 4); + if (err) { + return err; + } + + lfs_superblock_t superblock; + memset(&superblock.d, 0, sizeof(superblock.d)); + err = lfs_dir_get(lfs, &dir, + sizeof(dir.d)+4, &superblock.d, + lfs_min(sizeof(superblock.d), lfs_entry_elen(&entry))); + lfs_superblock_fromle32(&superblock.d); + if (err) { + return err; + } - lfs->root[0] = superblock.d.root[0]; - lfs->root[1] = superblock.d.root[1]; + char magic[8]; + err = lfs_dir_get(lfs, &dir, + sizeof(dir.d)+lfs_entry_size(&entry)-entry.d.nlen, magic, + lfs_min(sizeof(magic), entry.d.nlen)); + if (err) { + return err; } - if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) { + if (memcmp(magic, "littlefs", 8) != 0) { LFS_ERROR("Invalid superblock at %d %d", 0, 1); return LFS_ERR_CORRUPT; } @@ -2221,6 +2776,39 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { return LFS_ERR_INVAL; } + if (superblock.d.inline_size) { + if (superblock.d.inline_size > lfs->inline_size) { + LFS_ERROR("Unsupported inline size (%d > %d)", + superblock.d.inline_size, lfs->inline_size); + return LFS_ERR_INVAL; + } + + lfs->inline_size = superblock.d.inline_size; + } + + if (superblock.d.attrs_size) { + if (superblock.d.attrs_size > lfs->attrs_size) { + LFS_ERROR("Unsupported attrs size (%d > %d)", + superblock.d.attrs_size, lfs->attrs_size); + return LFS_ERR_INVAL; + } + + lfs->attrs_size = superblock.d.attrs_size; + } + + if (superblock.d.name_size) { + if (superblock.d.name_size > lfs->name_size) { + LFS_ERROR("Unsupported name size (%d > %d)", + superblock.d.name_size, lfs->name_size); + return LFS_ERR_INVAL; + } + + lfs->name_size = superblock.d.name_size; + } + + lfs->root[0] = superblock.d.root[0]; + lfs->root[1] = superblock.d.root[1]; + return 0; } @@ -2229,15 +2817,13 @@ int lfs_unmount(lfs_t *lfs) { } -/// Littlefs specific operations /// +/// Internal filesystem filesystem operations /// int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) { if (lfs_pairisnull(lfs->root)) { return 0; } // iterate over metadata pairs - lfs_dir_t dir; - lfs_entry_t entry; lfs_block_t cwd[2] = {0, 1}; while (true) { @@ -2248,22 +2834,24 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) { } } + lfs_dir_t dir; int err = lfs_dir_fetch(lfs, &dir, cwd); if (err) { return err; } // iterate over contents + lfs_entry_t entry; while (dir.off + sizeof(entry.d) <= (0x7fffffff & dir.d.size)-4) { - err = lfs_bd_read(lfs, dir.pair[0], dir.off, - &entry.d, sizeof(entry.d)); + err = lfs_dir_get(lfs, &dir, + dir.off, &entry.d, sizeof(entry.d)); lfs_entry_fromle32(&entry.d); if (err) { return err; } dir.off += lfs_entry_size(&entry); - if ((0x70 & entry.d.type) == (0x70 & LFS_TYPE_REG)) { + if ((0x70 & entry.d.type) == LFS_STRUCT_CTZ) { err = lfs_ctz_traverse(lfs, &lfs->rcache, NULL, entry.d.u.file.head, entry.d.u.file.size, cb, data); if (err) { @@ -2282,7 +2870,7 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) { // iterate over any open files for (lfs_file_t *f = lfs->files; f; f = f->next) { - if (f->flags & LFS_F_DIRTY) { + if ((f->flags & LFS_F_DIRTY) && !(f->flags & LFS_F_INLINE)) { int err = lfs_ctz_traverse(lfs, &lfs->rcache, &f->cache, f->head, f->size, cb, data); if (err) { @@ -2290,7 +2878,7 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) { } } - if (f->flags & LFS_F_WRITING) { + if ((f->flags & LFS_F_WRITING) && !(f->flags & LFS_F_INLINE)) { int err = lfs_ctz_traverse(lfs, &lfs->rcache, &f->cache, f->block, f->pos, cb, data); if (err) { @@ -2353,7 +2941,7 @@ static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2], break; } - if (((0x70 & entry->d.type) == (0x70 & LFS_TYPE_DIR)) && + if (((0x70 & entry->d.type) == LFS_STRUCT_DIR) && lfs_paircmp(entry->d.u.dir, dir) == 0) { return true; } @@ -2393,7 +2981,7 @@ static int lfs_moved(lfs_t *lfs, const void *e) { break; } - if (!(0x80 & entry.d.type) && + if (!(LFS_STRUCT_MOVED & entry.d.type) && memcmp(&entry.d.u, e, sizeof(entry.d.u)) == 0) { return true; } @@ -2417,8 +3005,10 @@ static int lfs_relocate(lfs_t *lfs, // update disk, this creates a desync entry.d.u.dir[0] = newpair[0]; entry.d.u.dir[1] = newpair[1]; - - int err = lfs_dir_update(lfs, &parent, &entry, NULL); + lfs_entry_tole32(&entry.d); + int err = lfs_dir_set(lfs, &parent, &entry, (struct lfs_region[]){ + {LFS_FROM_MEM, 0, sizeof(entry.d), + &entry.d, sizeof(entry.d)}}, 1); if (err) { return err; } @@ -2525,7 +3115,7 @@ int lfs_deorphan(lfs_t *lfs) { } // found moved entry - if (entry.d.type & 0x80) { + if (entry.d.type & LFS_STRUCT_MOVED) { int moved = lfs_moved(lfs, &entry.d.u); if (moved < 0) { return moved; @@ -2534,15 +3124,17 @@ int lfs_deorphan(lfs_t *lfs) { if (moved) { LFS_DEBUG("Found move %d %d", entry.d.u.dir[0], entry.d.u.dir[1]); - err = lfs_dir_remove(lfs, &cwd, &entry); + err = lfs_dir_set(lfs, &cwd, &entry, (struct lfs_region[]){ + {LFS_FROM_MEM, 0, entry.size, NULL, 0}}, 1); if (err) { return err; } } else { LFS_DEBUG("Found partial move %d %d", entry.d.u.dir[0], entry.d.u.dir[1]); - entry.d.type &= ~0x80; - err = lfs_dir_update(lfs, &cwd, &entry, NULL); + entry.d.type &= ~LFS_STRUCT_MOVED; + err = lfs_dir_set(lfs, &cwd, &entry, (struct lfs_region[]){ + {LFS_FROM_MEM, 0, 1, &entry.d, 1}}, 1); if (err) { return err; } @@ -2556,3 +3148,54 @@ int lfs_deorphan(lfs_t *lfs) { return 0; } + +/// External filesystem filesystem operations /// +int lfs_fs_getattrs(lfs_t *lfs, const struct lfs_attr *attrs, int count) { + lfs_dir_t dir; + int err = lfs_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1}); + if (err) { + return err; + } + + lfs_entry_t entry = {.off = sizeof(dir.d)}; + err = lfs_dir_get(lfs, &dir, entry.off, &entry.d, 4); + if (err) { + return err; + } + entry.size = lfs_entry_size(&entry); + + return lfs_dir_getattrs(lfs, &dir, &entry, attrs, count); +} + +int lfs_fs_setattrs(lfs_t *lfs, const struct lfs_attr *attrs, int count) { + lfs_dir_t dir; + int err = lfs_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1}); + if (err) { + return err; + } + + lfs_entry_t entry = {.off = sizeof(dir.d)}; + err = lfs_dir_get(lfs, &dir, entry.off, &entry.d, 4); + if (err) { + return err; + } + entry.size = lfs_entry_size(&entry); + + return lfs_dir_setattrs(lfs, &dir, &entry, attrs, count); +} + +static int lfs_fs_size_count(void *p, lfs_block_t block) { + lfs_size_t *size = p; + *size += 1; + return 0; +} + +lfs_ssize_t lfs_fs_size(lfs_t *lfs) { + lfs_size_t size = 0; + int err = lfs_traverse(lfs, lfs_fs_size_count, &size); + if (err) { + return err; + } + + return size; +} diff --git a/lfs.h b/lfs.h index 376776f6..4e75ecb9 100644 --- a/lfs.h +++ b/lfs.h @@ -1,19 +1,8 @@ /* * The little filesystem * - * Copyright (c) 2017 ARM Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause */ #ifndef LFS_H #define LFS_H @@ -27,14 +16,14 @@ // Software library version // Major (top-nibble), incremented on backwards incompatible changes // Minor (bottom-nibble), incremented on feature additions -#define LFS_VERSION 0x00010003 +#define LFS_VERSION 0x00010004 #define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16)) #define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0)) // Version of On-disk data structures // Major (top-nibble), incremented on backwards incompatible changes // Minor (bottom-nibble), incremented on feature additions -#define LFS_DISK_VERSION 0x00010001 +#define LFS_DISK_VERSION 0x00010002 #define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16)) #define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >> 0)) @@ -50,51 +39,80 @@ typedef int32_t lfs_soff_t; typedef uint32_t lfs_block_t; -// Max name size in bytes +// Maximum inline file size in bytes. Large inline files require a larger +// read and prog cache, but if a file can be inline it does not need its own +// data block. LFS_ATTRS_MAX + LFS_INLINE_MAX must be <= 0xffff. Stored in +// superblock and must be respected by other littlefs drivers. +#ifndef LFS_INLINE_MAX +#define LFS_INLINE_MAX 0x3ff +#endif + +// Maximum size of all attributes per file in bytes, may be redefined but a +// a smaller LFS_ATTRS_MAX has no benefit. LFS_ATTRS_MAX + LFS_INLINE_MAX +// must be <= 0xffff. Stored in superblock and must be respected by other +// littlefs drivers. +#ifndef LFS_ATTRS_MAX +#define LFS_ATTRS_MAX 0x3f +#endif + +// Max name size in bytes, may be redefined to reduce the size of the +// info struct. Stored in superblock and must be respected by other +// littlefs drivers. #ifndef LFS_NAME_MAX -#define LFS_NAME_MAX 255 +#define LFS_NAME_MAX 0xff #endif // Possible error codes, these are negative to allow // valid positive return values enum lfs_error { - LFS_ERR_OK = 0, // No error - LFS_ERR_IO = -5, // Error during device operation - LFS_ERR_CORRUPT = -52, // Corrupted - LFS_ERR_NOENT = -2, // No directory entry - LFS_ERR_EXIST = -17, // Entry already exists - LFS_ERR_NOTDIR = -20, // Entry is not a dir - LFS_ERR_ISDIR = -21, // Entry is a dir - LFS_ERR_NOTEMPTY = -39, // Dir is not empty - LFS_ERR_BADF = -9, // Bad file number - LFS_ERR_INVAL = -22, // Invalid parameter - LFS_ERR_NOSPC = -28, // No space left on device - LFS_ERR_NOMEM = -12, // No more memory available + LFS_ERR_OK = 0, // No error + LFS_ERR_IO = -5, // Error during device operation + LFS_ERR_CORRUPT = -52, // Corrupted + LFS_ERR_NOENT = -2, // No directory entry + LFS_ERR_EXIST = -17, // Entry already exists + LFS_ERR_NOTDIR = -20, // Entry is not a dir + LFS_ERR_ISDIR = -21, // Entry is a dir + LFS_ERR_NOTEMPTY = -39, // Dir is not empty + LFS_ERR_BADF = -9, // Bad file number + LFS_ERR_INVAL = -22, // Invalid parameter + LFS_ERR_NOSPC = -28, // No space left on device + LFS_ERR_NOMEM = -12, // No more memory available + LFS_ERR_NAMETOOLONG = -36, // File name too long + LFS_ERR_NODATA = -61, // No data/attr available + LFS_ERR_RANGE = -34, // Result not representable }; // File types enum lfs_type { - LFS_TYPE_REG = 0x11, - LFS_TYPE_DIR = 0x22, - LFS_TYPE_SUPERBLOCK = 0x2e, + // file type + LFS_TYPE_REG = 0x01, + LFS_TYPE_DIR = 0x02, + LFS_TYPE_SUPERBLOCK = 0x0e, + + // on disk structure + LFS_STRUCT_CTZ = 0x10, + LFS_STRUCT_DIR = 0x20, + LFS_STRUCT_INLINE = 0x30, + LFS_STRUCT_MOVED = 0x80, }; // File open flags enum lfs_open_flags { // open flags - LFS_O_RDONLY = 1, // Open a file as read only - LFS_O_WRONLY = 2, // Open a file as write only - LFS_O_RDWR = 3, // Open a file as read and write - LFS_O_CREAT = 0x0100, // Create a file if it does not exist - LFS_O_EXCL = 0x0200, // Fail if a file already exists - LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size - LFS_O_APPEND = 0x0800, // Move to end of file on every write + LFS_O_RDONLY = 1, // Open a file as read only + LFS_O_WRONLY = 2, // Open a file as write only + LFS_O_RDWR = 3, // Open a file as read and write + LFS_O_CREAT = 0x0100, // Create a file if it does not exist + LFS_O_EXCL = 0x0200, // Fail if a file already exists + LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size + LFS_O_APPEND = 0x0800, // Move to end of file on every write // internally used flags - LFS_F_DIRTY = 0x10000, // File does not match storage - LFS_F_WRITING = 0x20000, // File has been written since last flush - LFS_F_READING = 0x40000, // File has been read since last flush - LFS_F_ERRED = 0x80000, // An error occured during write + LFS_F_DIRTY = 0x010000, // File does not match storage + LFS_F_WRITING = 0x020000, // File has been written since last flush + LFS_F_READING = 0x040000, // File has been read since last flush + LFS_F_ERRED = 0x080000, // An error occured during write + LFS_F_INLINE = 0x100000, // Currently inlined in directory entry }; // File seek flags @@ -171,6 +189,25 @@ struct lfs_config { // Optional, statically allocated buffer for files. Must be program sized. // If enabled, only one file may be opened at a time. void *file_buffer; + + // Optional upper limit on inlined files in bytes. Large inline files + // require a larger read and prog cache, but if a file can be inlined it + // does not need its own data block. Must be smaller than the read size + // and prog size. Defaults to min(LFS_INLINE_MAX, read_size) when zero. + // Stored in superblock and must be respected by other littlefs drivers. + lfs_size_t inline_size; + + // Optional upper limit on attributes per file in bytes. No downside for + // larger attributes size but must be less than LFS_ATTRS_MAX. Defaults to + // LFS_ATTRS_MAX when zero.Stored in superblock and must be respected by + // other littlefs drivers. + lfs_size_t attrs_size; + + // Optional upper limit on length of file names in bytes. No downside for + // larger names except the size of the info struct which is controlled by + // the LFS_NAME_MAX define. Defaults to LFS_NAME_MAX when zero. Stored in + // superblock and must be respected by other littlefs drivers. + lfs_size_t name_size; }; @@ -186,10 +223,23 @@ struct lfs_info { char name[LFS_NAME_MAX+1]; }; +// Custom attribute structure +struct lfs_attr { + // Type of attribute, provided by user and used to identify the attribute + uint8_t type; + + // Pointer to buffer containing the attribute + void *buffer; + + // Size of attribute in bytes, limited to LFS_ATTRS_MAX + lfs_size_t size; +}; + /// littlefs data structures /// typedef struct lfs_entry { lfs_off_t off; + lfs_size_t size; struct lfs_disk_entry { uint8_t type; @@ -206,6 +256,13 @@ typedef struct lfs_entry { } d; } lfs_entry_t; +typedef struct lfs_entry_attr { + struct lfs_disk_entry_attr { + uint8_t type; + uint8_t len; + } d; +} lfs_entry_attr_t; + typedef struct lfs_cache { lfs_block_t block; lfs_off_t off; @@ -215,16 +272,20 @@ typedef struct lfs_cache { typedef struct lfs_file { struct lfs_file *next; lfs_block_t pair[2]; - lfs_off_t poff; + lfs_off_t pairoff; lfs_block_t head; lfs_size_t size; uint32_t flags; + lfs_size_t inline_size; lfs_off_t pos; lfs_block_t block; lfs_off_t off; lfs_cache_t cache; + + const struct lfs_attr *attrs; + int attrcount; } lfs_file_t; typedef struct lfs_dir { @@ -243,18 +304,16 @@ typedef struct lfs_dir { } lfs_dir_t; typedef struct lfs_superblock { - lfs_off_t off; - struct lfs_disk_superblock { - uint8_t type; - uint8_t elen; - uint8_t alen; - uint8_t nlen; lfs_block_t root[2]; - uint32_t block_size; - uint32_t block_count; + + lfs_size_t block_size; + lfs_size_t block_count; uint32_t version; - char magic[8]; + + lfs_size_t inline_size; + lfs_size_t attrs_size; + lfs_size_t name_size; } d; } lfs_superblock_t; @@ -279,6 +338,10 @@ typedef struct lfs { lfs_free_t free; bool deorphaned; + + lfs_size_t inline_size; + lfs_size_t attrs_size; + lfs_size_t name_size; } lfs_t; @@ -329,6 +392,26 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath); // Returns a negative error code on failure. int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info); +// Get custom attributes +// +// Attributes are looked up based on the type id. If the stored attribute is +// smaller than the buffer, it is padded with zeros. It the stored attribute +// is larger than the buffer, LFS_ERR_RANGE is returned. +// +// Returns a negative error code on failure. +int lfs_getattrs(lfs_t *lfs, const char *path, + const struct lfs_attr *attrs, int count); + +// Set custom attributes +// +// The array of attributes will be used to update the attributes stored on +// disk based on their type id. Unspecified attributes are left unmodified. +// Specifying an attribute with zero size deletes the attribute. +// +// Returns a negative error code on failure. +int lfs_setattrs(lfs_t *lfs, const char *path, + const struct lfs_attr *attrs, int count); + /// File operations /// @@ -402,6 +485,30 @@ int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file); // Returns the size of the file, or a negative error code on failure. lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file); +// Get custom attributes attached to a file +// +// Attributes are looked up based on the type id. If the stored attribute is +// smaller than the buffer, it is padded with zeros. It the stored attribute +// is larger than the buffer, LFS_ERR_RANGE is returned. +// +// Returns a negative error code on failure. +int lfs_file_getattrs(lfs_t *lfs, lfs_file_t *file, + const struct lfs_attr *attrs, int count); + +// Set custom attributes on a file +// +// The array of attributes will be used to update the attributes stored on +// disk based on their type id. Unspecified attributes are left unmodified. +// Specifying an attribute with zero size deletes the attribute. +// +// Note: Attributes are not written out until a call to lfs_file_sync +// or lfs_file_close and must be allocated until the file is closed or +// lfs_file_setattrs is called with a count of zero. +// +// Returns a negative error code on failure. +int lfs_file_setattrs(lfs_t *lfs, lfs_file_t *file, + const struct lfs_attr *attrs, int count); + /// Directory operations /// @@ -450,6 +557,37 @@ lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir); int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir); +/// Filesystem filesystem operations /// + +// Get custom attributes on the filesystem +// +// Attributes are looked up based on the type id. If the stored attribute is +// smaller than the buffer, it is padded with zeros. It the stored attribute +// is larger than the buffer, LFS_ERR_RANGE is returned. +// +// Returns a negative error code on failure. +int lfs_fs_getattrs(lfs_t *lfs, const struct lfs_attr *attrs, int count); + +// Set custom attributes on the filesystem +// +// The array of attributes will be used to update the attributes stored on +// disk based on their type id. Unspecified attributes are left unmodified. +// Specifying an attribute with zero size deletes the attribute. +// +// Note: Filesystem level attributes are not available for wear-leveling +// +// Returns a negative error code on failure. +int lfs_fs_setattrs(lfs_t *lfs, const struct lfs_attr *attrs, int count); + +// Finds the current size of the filesystem +// +// Note: Result is best effort. If files share COW structures, the returned +// size may be larger than the filesystem actually is. +// +// Returns the number of allocated blocks, or a negative error code on failure. +lfs_ssize_t lfs_fs_size(lfs_t *lfs); + + /// Miscellaneous littlefs specific operations /// // Traverse through all blocks in use by the filesystem diff --git a/lfs_util.c b/lfs_util.c index 3a2a351b..9ca0756d 100644 --- a/lfs_util.c +++ b/lfs_util.c @@ -1,19 +1,8 @@ /* * lfs util functions * - * Copyright (c) 2017 ARM Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause */ #include "lfs_util.h" diff --git a/lfs_util.h b/lfs_util.h index 3527ce6c..51005dc3 100644 --- a/lfs_util.h +++ b/lfs_util.h @@ -1,19 +1,8 @@ /* * lfs utility functions * - * Copyright (c) 2017 ARM Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause */ #ifndef LFS_UTIL_H #define LFS_UTIL_H diff --git a/tests/test.py b/tests/test.py index 24b0d1a3..e93ccec6 100755 --- a/tests/test.py +++ b/tests/test.py @@ -10,7 +10,7 @@ def generate(test): template = file.read() lines = [] - for line in re.split('(?<=[;{}])\n', test.read()): + for line in re.split('(?<=(?:.;| [{}]))\n', test.read()): match = re.match('(?: *\n)*( *)(.*)=>(.*);', line, re.DOTALL | re.MULTILINE) if match: tab, test, expect = match.groups() diff --git a/tests/test_alloc.sh b/tests/test_alloc.sh index 8c814908..d3c8da40 100755 --- a/tests/test_alloc.sh +++ b/tests/test_alloc.sh @@ -274,9 +274,12 @@ TEST tests/test.py << TEST lfs_mount(&lfs, &cfg) => 0; - // create one block whole for half a directory + // create one block hole for half a directory lfs_file_open(&lfs, &file[0], "bump", LFS_O_WRONLY | LFS_O_CREAT) => 0; - lfs_file_write(&lfs, &file[0], (void*)"hi", 2) => 2; + for (lfs_size_t i = 0; i < cfg.block_size; i += 2) { + memcpy(&buffer[i], "hi", 2); + } + lfs_file_write(&lfs, &file[0], buffer, cfg.block_size) => cfg.block_size; lfs_file_close(&lfs, &file[0]) => 0; lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT); @@ -295,7 +298,10 @@ tests/test.py << TEST lfs_mkdir(&lfs, "splitdir") => 0; lfs_file_open(&lfs, &file[0], "splitdir/bump", LFS_O_WRONLY | LFS_O_CREAT) => 0; - lfs_file_write(&lfs, &file[0], buffer, size) => LFS_ERR_NOSPC; + for (lfs_size_t i = 0; i < cfg.block_size; i += 2) { + memcpy(&buffer[i], "hi", 2); + } + lfs_file_write(&lfs, &file[0], buffer, cfg.block_size) => LFS_ERR_NOSPC; lfs_file_close(&lfs, &file[0]) => 0; lfs_unmount(&lfs) => 0; diff --git a/tests/test_attrs.sh b/tests/test_attrs.sh new file mode 100755 index 00000000..286b909d --- /dev/null +++ b/tests/test_attrs.sh @@ -0,0 +1,292 @@ +#!/bin/bash +set -eu + +echo "=== Attr tests ===" +rm -rf blocks +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "hello") => 0; + lfs_file_open(&lfs, &file[0], "hello/hello", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file[0], "hello", strlen("hello")) + => strlen("hello"); + lfs_file_close(&lfs, &file[0]); + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Set/get attribute ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_setattrs(&lfs, "hello", (struct lfs_attr[]){ + {'A', "aaaa", 4}, + {'B', "bbbbbb", 6}, + {'C', "ccccc", 5}}, 3) => 0; + lfs_getattrs(&lfs, "hello", (struct lfs_attr[]){ + {'A', buffer, 4}, + {'B', buffer+4, 6}, + {'C', buffer+10, 5}}, 3) => 0; + memcmp(buffer, "aaaa", 4) => 0; + memcmp(buffer+4, "bbbbbb", 6) => 0; + memcmp(buffer+10, "ccccc", 5) => 0; + + lfs_setattrs(&lfs, "hello", (struct lfs_attr[]){ + {'B', "", 0}}, 1) => 0; + lfs_getattrs(&lfs, "hello", (struct lfs_attr[]){ + {'A', buffer, 4}, + {'B', buffer+4, 6}, + {'C', buffer+10, 5}}, 3) => 0; + memcmp(buffer, "aaaa", 4) => 0; + memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0; + memcmp(buffer+10, "ccccc", 5) => 0; + + lfs_setattrs(&lfs, "hello", (struct lfs_attr[]){ + {'B', "dddddd", 6}}, 1) => 0; + lfs_getattrs(&lfs, "hello", (struct lfs_attr[]){ + {'A', buffer, 4}, + {'B', buffer+4, 6}, + {'C', buffer+10, 5}}, 3) => 0; + memcmp(buffer, "aaaa", 4) => 0; + memcmp(buffer+4, "dddddd", 6) => 0; + memcmp(buffer+10, "ccccc", 5) => 0; + + lfs_setattrs(&lfs, "hello", (struct lfs_attr[]){ + {'B', "eee", 3}}, 1) => 0; + lfs_getattrs(&lfs, "hello", (struct lfs_attr[]){ + {'A', buffer, 4}, + {'B', buffer+4, 6}, + {'C', buffer+10, 5}}, 3) => 0; + memcmp(buffer, "aaaa", 4) => 0; + memcmp(buffer+4, "eee\0\0\0", 6) => 0; + memcmp(buffer+10, "ccccc", 5) => 0; + + lfs_setattrs(&lfs, "hello", (struct lfs_attr[]){ + {'A', buffer, LFS_ATTRS_MAX+1}}, 1) => LFS_ERR_NOSPC; + lfs_setattrs(&lfs, "hello", (struct lfs_attr[]){ + {'B', "fffffffff", 9}}, 1) => 0; + lfs_getattrs(&lfs, "hello", (struct lfs_attr[]){ + {'A', buffer, 4}, + {'B', buffer+4, 6}, + {'C', buffer+10, 5}}, 3) => LFS_ERR_RANGE; + + lfs_unmount(&lfs) => 0; +TEST +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_getattrs(&lfs, "hello", (struct lfs_attr[]){ + {'A', buffer, 4}, + {'B', buffer+4, 9}, + {'C', buffer+13, 5}}, 3) => 0; + memcmp(buffer, "aaaa", 4) => 0; + memcmp(buffer+4, "fffffffff", 9) => 0; + memcmp(buffer+13, "ccccc", 5) => 0; + + lfs_file_open(&lfs, &file[0], "hello/hello", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file[0], buffer, sizeof(buffer)) => strlen("hello"); + memcmp(buffer, "hello", strlen("hello")) => 0; + lfs_file_close(&lfs, &file[0]); + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Set/get fs attribute ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_fs_setattrs(&lfs, (struct lfs_attr[]){ + {'A', "aaaa", 4}, + {'B', "bbbbbb", 6}, + {'C', "ccccc", 5}}, 3) => 0; + lfs_fs_getattrs(&lfs, (struct lfs_attr[]){ + {'A', buffer, 4}, + {'B', buffer+4, 6}, + {'C', buffer+10, 5}}, 3) => 0; + memcmp(buffer, "aaaa", 4) => 0; + memcmp(buffer+4, "bbbbbb", 6) => 0; + memcmp(buffer+10, "ccccc", 5) => 0; + + lfs_fs_setattrs(&lfs, (struct lfs_attr[]){ + {'B', "", 0}}, 1) => 0; + lfs_fs_getattrs(&lfs, (struct lfs_attr[]){ + {'A', buffer, 4}, + {'B', buffer+4, 6}, + {'C', buffer+10, 5}}, 3) => 0; + memcmp(buffer, "aaaa", 4) => 0; + memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0; + memcmp(buffer+10, "ccccc", 5) => 0; + + lfs_fs_setattrs(&lfs, (struct lfs_attr[]){ + {'B', "dddddd", 6}}, 1) => 0; + lfs_fs_getattrs(&lfs, (struct lfs_attr[]){ + {'A', buffer, 4}, + {'B', buffer+4, 6}, + {'C', buffer+10, 5}}, 3) => 0; + memcmp(buffer, "aaaa", 4) => 0; + memcmp(buffer+4, "dddddd", 6) => 0; + memcmp(buffer+10, "ccccc", 5) => 0; + + lfs_fs_setattrs(&lfs, (struct lfs_attr[]){ + {'B', "eee", 3}}, 1) => 0; + lfs_fs_getattrs(&lfs, (struct lfs_attr[]){ + {'A', buffer, 4}, + {'B', buffer+4, 6}, + {'C', buffer+10, 5}}, 3) => 0; + memcmp(buffer, "aaaa", 4) => 0; + memcmp(buffer+4, "eee\0\0\0", 6) => 0; + memcmp(buffer+10, "ccccc", 5) => 0; + + lfs_fs_setattrs(&lfs, (struct lfs_attr[]){ + {'A', buffer, LFS_ATTRS_MAX+1}}, 1) => LFS_ERR_NOSPC; + lfs_fs_setattrs(&lfs, (struct lfs_attr[]){ + {'B', "fffffffff", 9}}, 1) => 0; + lfs_fs_getattrs(&lfs, (struct lfs_attr[]){ + {'A', buffer, 4}, + {'B', buffer+4, 6}, + {'C', buffer+10, 5}}, 3) => LFS_ERR_RANGE; + lfs_unmount(&lfs) => 0; +TEST +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_fs_getattrs(&lfs, (struct lfs_attr[]){ + {'A', buffer, 4}, + {'B', buffer+4, 9}, + {'C', buffer+13, 5}}, 3) => 0; + memcmp(buffer, "aaaa", 4) => 0; + memcmp(buffer+4, "fffffffff", 9) => 0; + memcmp(buffer+13, "ccccc", 5) => 0; + + lfs_file_open(&lfs, &file[0], "hello/hello", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file[0], buffer, sizeof(buffer)) => strlen("hello"); + memcmp(buffer, "hello", strlen("hello")) => 0; + lfs_file_close(&lfs, &file[0]); + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Set/get file attribute ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "hello/hello", LFS_O_WRONLY) => 0; + + struct lfs_attr attr[3]; + attr[0] = (struct lfs_attr){'A', "aaaa", 4}; + attr[1] = (struct lfs_attr){'B', "bbbbbb", 6}; + attr[2] = (struct lfs_attr){'C', "ccccc", 5}; + lfs_file_setattrs(&lfs, &file[0], attr, 3) => 0; + lfs_file_getattrs(&lfs, &file[0], (struct lfs_attr[]){ + {'A', buffer, 4}, + {'B', buffer+4, 6}, + {'C', buffer+10, 5}}, 3) => 0; + memcmp(buffer, "aaaa", 4) => 0; + memcmp(buffer+4, "bbbbbb", 6) => 0; + memcmp(buffer+10, "ccccc", 5) => 0; + lfs_file_sync(&lfs, &file[0]) => 0; + + attr[0] = (struct lfs_attr){'B', "", 0}; + lfs_file_setattrs(&lfs, &file[0], attr, 1) => 0; + lfs_file_getattrs(&lfs, &file[0], (struct lfs_attr[]){ + {'A', buffer, 4}, + {'B', buffer+4, 6}, + {'C', buffer+10, 5}}, 3) => 0; + memcmp(buffer, "aaaa", 4) => 0; + memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0; + memcmp(buffer+10, "ccccc", 5) => 0; + lfs_file_sync(&lfs, &file[0]) => 0; + + attr[0] = (struct lfs_attr){'B', "dddddd", 6}; + lfs_file_setattrs(&lfs, &file[0], attr, 1) => 0; + lfs_file_getattrs(&lfs, &file[0], (struct lfs_attr[]){ + {'A', buffer, 4}, + {'B', buffer+4, 6}, + {'C', buffer+10, 5}}, 3) => 0; + memcmp(buffer, "aaaa", 4) => 0; + memcmp(buffer+4, "dddddd", 6) => 0; + memcmp(buffer+10, "ccccc", 5) => 0; + lfs_file_sync(&lfs, &file[0]) => 0; + + attr[0] = (struct lfs_attr){'B', "eee", 3}; + lfs_file_setattrs(&lfs, &file[0], attr, 1); + lfs_file_getattrs(&lfs, &file[0], (struct lfs_attr[]){ + {'A', buffer, 4}, + {'B', buffer+4, 6}, + {'C', buffer+10, 5}}, 3) => 0; + memcmp(buffer, "aaaa", 4) => 0; + memcmp(buffer+4, "eee\0\0\0", 6) => 0; + memcmp(buffer+10, "ccccc", 5) => 0; + lfs_file_sync(&lfs, &file[0]) => 0; + + attr[0] = (struct lfs_attr){'A', buffer, LFS_ATTRS_MAX+1}; + lfs_file_setattrs(&lfs, &file[0], attr, 1) => LFS_ERR_NOSPC; + attr[0] = (struct lfs_attr){'B', "fffffffff", 9}; + lfs_file_open(&lfs, &file[1], "hello/hello", LFS_O_RDONLY) => 0; + lfs_file_setattrs(&lfs, &file[1], attr, 1) => LFS_ERR_BADF; + lfs_file_close(&lfs, &file[1]) => 0; + lfs_file_setattrs(&lfs, &file[0], attr, 1) => 0; + lfs_file_getattrs(&lfs, &file[0], (struct lfs_attr[]){ + {'A', buffer, 4}, + {'B', buffer+4, 6}, + {'C', buffer+10, 5}}, 3) => LFS_ERR_RANGE; + + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "hello/hello", LFS_O_RDONLY) => 0; + + lfs_file_getattrs(&lfs, &file[0], (struct lfs_attr[]){ + {'A', buffer, 4}, + {'B', buffer+4, 9}, + {'C', buffer+13, 5}}, 3) => 0; + memcmp(buffer, "aaaa", 4) => 0; + memcmp(buffer+4, "fffffffff", 9) => 0; + memcmp(buffer+13, "ccccc", 5) => 0; + + lfs_file_open(&lfs, &file[0], "hello/hello", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file[0], buffer, sizeof(buffer)) => strlen("hello"); + memcmp(buffer, "hello", strlen("hello")) => 0; + lfs_file_close(&lfs, &file[0]); + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Deferred file attributes ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "hello/hello", LFS_O_RDWR) => 0; + + struct lfs_attr attr[] = { + {'B', "gggg", 4}, + {'C', "", 0}, + {'D', "hhhh", 4}, + }; + + lfs_file_setattrs(&lfs, &file[0], attr, 3) => 0; + lfs_file_getattrs(&lfs, &file[0], (struct lfs_attr[]){ + {'B', buffer, 9}, + {'C', buffer+9, 9}, + {'D', buffer+18, 9}}, 3) => 0; + memcmp(buffer, "gggg\0\0\0\0\0", 9) => 0; + memcmp(buffer+9, "\0\0\0\0\0\0\0\0\0", 9) => 0; + memcmp(buffer+18, "hhhh\0\0\0\0\0", 9) => 0; + + lfs_getattrs(&lfs, "hello/hello", (struct lfs_attr[]){ + {'B', buffer, 9}, + {'C', buffer+9, 9}, + {'D', buffer+18, 9}}, 3) => 0; + memcmp(buffer, "fffffffff", 9) => 0; + memcmp(buffer+9, "ccccc\0\0\0\0", 9) => 0; + memcmp(buffer+18, "\0\0\0\0\0\0\0\0\0", 9) => 0; + + lfs_file_sync(&lfs, &file[0]) => 0; + lfs_getattrs(&lfs, "hello/hello", (struct lfs_attr[]){ + {'B', buffer, 9}, + {'C', buffer+9, 9}, + {'D', buffer+18, 9}}, 3) => 0; + memcmp(buffer, "gggg\0\0\0\0\0", 9) => 0; + memcmp(buffer+9, "\0\0\0\0\0\0\0\0\0", 9) => 0; + memcmp(buffer+18, "hhhh\0\0\0\0\0", 9) => 0; + + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Results ---" +tests/stats.py diff --git a/tests/test_corrupt.sh b/tests/test_corrupt.sh index 44f1caee..1491dac3 100755 --- a/tests/test_corrupt.sh +++ b/tests/test_corrupt.sh @@ -73,7 +73,7 @@ lfs_mktree lfs_chktree echo "--- Block corruption ---" -for i in {0..33} +for i in {2..33} do rm -rf blocks mkdir blocks @@ -83,12 +83,12 @@ do done echo "--- Block persistance ---" -for i in {0..33} +for i in {2..33} do rm -rf blocks mkdir blocks lfs_mktree - chmod a-w blocks/$(printf '%x' $i) + chmod a-w blocks/$(printf '%x' $i) || true lfs_mktree lfs_chktree done diff --git a/tests/test_entries.sh b/tests/test_entries.sh new file mode 100755 index 00000000..447d9bca --- /dev/null +++ b/tests/test_entries.sh @@ -0,0 +1,220 @@ +#!/bin/bash +set -eu + +# Note: These tests are intended for 512 byte inline size at different +# inline sizes they should still pass, but won't be testing anything + +echo "=== Directory tests ===" +function read_file { +cat << TEST + + size = $2; + lfs_file_open(&lfs, &file[0], "$1", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file[0], rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file[0]) => 0; +TEST +} + +function write_file { +cat << TEST + + size = $2; + lfs_file_open(&lfs, &file[0], "$1", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file[0], wbuffer, size) => size; + lfs_file_close(&lfs, &file[0]) => 0; +TEST +} + +echo "--- Entry grow test ---" +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + $(write_file "hi0" 20) + $(write_file "hi1" 20) + $(write_file "hi2" 20) + $(write_file "hi3" 20) + + $(read_file "hi1" 20) + $(write_file "hi1" 200) + + $(read_file "hi0" 20) + $(read_file "hi1" 200) + $(read_file "hi2" 20) + $(read_file "hi3" 20) + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Entry shrink test ---" +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + $(write_file "hi0" 20) + $(write_file "hi1" 200) + $(write_file "hi2" 20) + $(write_file "hi3" 20) + + $(read_file "hi1" 200) + $(write_file "hi1" 20) + + $(read_file "hi0" 20) + $(read_file "hi1" 20) + $(read_file "hi2" 20) + $(read_file "hi3" 20) + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Entry spill test ---" +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + $(write_file "hi0" 200) + $(write_file "hi1" 200) + $(write_file "hi2" 200) + $(write_file "hi3" 200) + + $(read_file "hi0" 200) + $(read_file "hi1" 200) + $(read_file "hi2" 200) + $(read_file "hi3" 200) + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Entry push spill test ---" +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + $(write_file "hi0" 200) + $(write_file "hi1" 20) + $(write_file "hi2" 200) + $(write_file "hi3" 200) + + $(read_file "hi1" 20) + $(write_file "hi1" 200) + + $(read_file "hi0" 200) + $(read_file "hi1" 200) + $(read_file "hi2" 200) + $(read_file "hi3" 200) + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Entry push spill two test ---" +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + $(write_file "hi0" 200) + $(write_file "hi1" 20) + $(write_file "hi2" 200) + $(write_file "hi3" 200) + $(write_file "hi4" 200) + + $(read_file "hi1" 20) + $(write_file "hi1" 200) + + $(read_file "hi0" 200) + $(read_file "hi1" 200) + $(read_file "hi2" 200) + $(read_file "hi3" 200) + $(read_file "hi4" 200) + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Entry drop test ---" +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + $(write_file "hi0" 200) + $(write_file "hi1" 200) + $(write_file "hi2" 200) + $(write_file "hi3" 200) + + lfs_remove(&lfs, "hi1") => 0; + lfs_stat(&lfs, "hi1", &info) => LFS_ERR_NOENT; + $(read_file "hi0" 200) + $(read_file "hi2" 200) + $(read_file "hi3" 200) + + lfs_remove(&lfs, "hi2") => 0; + lfs_stat(&lfs, "hi2", &info) => LFS_ERR_NOENT; + $(read_file "hi0" 200) + $(read_file "hi3" 200) + + lfs_remove(&lfs, "hi3") => 0; + lfs_stat(&lfs, "hi3", &info) => LFS_ERR_NOENT; + $(read_file "hi0" 200) + + lfs_remove(&lfs, "hi0") => 0; + lfs_stat(&lfs, "hi0", &info) => LFS_ERR_NOENT; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Create too big ---" +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + memset(buffer, 'm', 200); + buffer[200] = '\0'; + + size = 400; + lfs_file_open(&lfs, &file[0], (char*)buffer, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file[0], wbuffer, size) => size; + lfs_file_close(&lfs, &file[0]) => 0; + + size = 400; + lfs_file_open(&lfs, &file[0], (char*)buffer, LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file[0], rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Resize too big ---" +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + memset(buffer, 'm', 200); + buffer[200] = '\0'; + + size = 40; + lfs_file_open(&lfs, &file[0], (char*)buffer, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file[0], wbuffer, size) => size; + lfs_file_close(&lfs, &file[0]) => 0; + + size = 40; + lfs_file_open(&lfs, &file[0], (char*)buffer, LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file[0], rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file[0]) => 0; + + size = 400; + lfs_file_open(&lfs, &file[0], (char*)buffer, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + memset(wbuffer, 'c', size); + lfs_file_write(&lfs, &file[0], wbuffer, size) => size; + lfs_file_close(&lfs, &file[0]) => 0; + + size = 400; + lfs_file_open(&lfs, &file[0], (char*)buffer, LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file[0], rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Results ---" +tests/stats.py diff --git a/tests/test_format.sh b/tests/test_format.sh index b9071015..cff0baca 100755 --- a/tests/test_format.sh +++ b/tests/test_format.sh @@ -30,20 +30,10 @@ echo "--- Invalid mount ---" tests/test.py << TEST lfs_format(&lfs, &cfg) => 0; TEST -rm blocks/0 blocks/1 +rm -f blocks/0 blocks/1 tests/test.py << TEST lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT; TEST -echo "--- Valid corrupt mount ---" -tests/test.py << TEST - lfs_format(&lfs, &cfg) => 0; -TEST -rm blocks/0 -tests/test.py << TEST - lfs_mount(&lfs, &cfg) => 0; - lfs_unmount(&lfs) => 0; -TEST - echo "--- Results ---" tests/stats.py diff --git a/tests/test_paths.sh b/tests/test_paths.sh index f277e451..33843296 100755 --- a/tests/test_paths.sh +++ b/tests/test_paths.sh @@ -123,5 +123,23 @@ tests/test.py << TEST lfs_unmount(&lfs) => 0; TEST +echo "--- Max path test ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + memset(buffer, 'w', LFS_NAME_MAX+1); + buffer[LFS_NAME_MAX+2] = '\0'; + lfs_mkdir(&lfs, (char*)buffer) => LFS_ERR_NAMETOOLONG; + lfs_file_open(&lfs, &file[0], (char*)buffer, + LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NAMETOOLONG; + + memcpy(buffer, "coffee/", strlen("coffee/")); + memset(buffer+strlen("coffee/"), 'w', LFS_NAME_MAX+1); + buffer[strlen("coffee/")+LFS_NAME_MAX+2] = '\0'; + lfs_mkdir(&lfs, (char*)buffer) => LFS_ERR_NAMETOOLONG; + lfs_file_open(&lfs, &file[0], (char*)buffer, + LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NAMETOOLONG; + lfs_unmount(&lfs) => 0; +TEST + echo "--- Results ---" tests/stats.py