int spiffs_check_pgconsistency()

in fs/spiffs/src/spiffs_check.c [1407:2092]


int spiffs_check_pgconsistency(FAR struct spiffs_s *fs)
{
  const uint32_t bits = 4;
  const int16_t pages_per_scan = SPIFFS_GEO_PAGE_SIZE(fs) * 8 / bits;
  int16_t pgndx_offset = 0;
  int ret = OK;

  /* For each range of pages fitting into work memory */

  while (pgndx_offset < SPIFFS_GEO_PAGES_PER_BLOCK(fs) *
                        SPIFFS_GEO_BLOCK_COUNT(fs))
    {
      int16_t cur_block = 0;
      bool restart = false;

      memset(fs->work, 0, SPIFFS_GEO_PAGE_SIZE(fs));

      /* Build consistency bitmap for ID range traversing all blocks */

      while (!restart && cur_block < SPIFFS_GEO_BLOCK_COUNT(fs))
        {
          /* Traverse each page except for lookup pages */

          int16_t cur_pgndx = SPIFFS_OBJ_LOOKUP_PAGES(fs) +
                              SPIFFS_GEO_PAGES_PER_BLOCK(fs) * cur_block;

          while (!restart && cur_pgndx <
                 SPIFFS_GEO_PAGES_PER_BLOCK(fs) * (cur_block + 1))
            {
              struct spiffs_page_header_s pghdr;
              uint32_t pgndx_bytendx;
              uint8_t pgndx_bitndx;
              bool within_range;

              /* read header */

              ret =
                spiffs_cache_read(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
                                  0, SPIFFS_PAGE_TO_PADDR(fs, cur_pgndx),
                                  sizeof(struct spiffs_page_header_s),
                                  (FAR uint8_t *)&pghdr);
              if (ret < 0)
                {
                  ferr("ERROR: spiffs_cache_read() failed: %d\n", ret);
                  return ret;
                }

              within_range  = (cur_pgndx >= pgndx_offset &&
                               cur_pgndx < pgndx_offset + pages_per_scan);
              pgndx_bytendx = (cur_pgndx - pgndx_offset) / (8 / bits);
              pgndx_bitndx  = (cur_pgndx & ((8 / bits) - 1)) * bits;

              if (within_range &&
                  (pghdr.flags & SPIFFS_PH_FLAG_DELET) &&
                  (pghdr.flags & SPIFFS_PH_FLAG_USED) == 0)
                {
                  /* Used */

                  fs->work[pgndx_bytendx] |= (1 << (pgndx_bitndx + 0));
                }

              if ((pghdr.flags & SPIFFS_PH_FLAG_DELET) &&
                  (pghdr.flags & SPIFFS_PH_FLAG_NDXDELE) &&
                  (pghdr.flags & (SPIFFS_PH_FLAG_INDEX |
                                  SPIFFS_PH_FLAG_USED)) == 0)
                {
                  FAR struct spiffs_page_header_s *objndx_phdr;
                  FAR int16_t *object_page_index;
                  int16_t data_spndx_offset;
                  int entries;
                  int i;

                  /* Found non-deleted index */

                  if (within_range)
                    {
                      fs->work[pgndx_bytendx] |= (1 << (pgndx_bitndx + 2));
                    }

                  /* Load non-deleted index */

                  ret = spiffs_cache_read(fs,
                                      SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
                                      0, SPIFFS_PAGE_TO_PADDR(fs, cur_pgndx),
                                      SPIFFS_GEO_PAGE_SIZE(fs), fs->lu_work);
                  if (ret < 0)
                    {
                      ferr("ERROR: spiffs_cache_read() failed: %d\n", ret);
                      return ret;
                    }

                  /* traverse index for referenced pages */

                  objndx_phdr =
                    (FAR struct spiffs_page_header_s *)fs->lu_work;

                  if (pghdr.spndx == 0)
                    {
                      /* object header page index */

                      entries           = SPIFFS_OBJHDR_NDXLEN(fs);
                      data_spndx_offset = 0;
                      object_page_index =
                        (FAR int16_t *)((FAR uint8_t *)fs->lu_work +
                          sizeof(struct spiffs_pgobj_ndxheader_s));
                    }
                  else
                    {
                      /* Object page index */

                      entries           = SPIFFS_OBJNDX_LEN(fs);
                      data_spndx_offset = SPIFFS_OBJHDR_NDXLEN(fs) +
                                          SPIFFS_OBJNDX_LEN(fs) *
                                          (pghdr.spndx - 1);
                      object_page_index =
                        (FAR int16_t *)((FAR uint8_t *) fs->lu_work +
                          sizeof(struct spiffs_page_objndx_s));
                    }

                  /* For all entries in index */

                  for (i = 0; !restart && i < entries; i++)
                    {
                      int16_t rpgndx = object_page_index[i];
                      bool rpgndx_within_range;

                      rpgndx_within_range = (rpgndx >= pgndx_offset &&
                                             rpgndx < pgndx_offset +
                                             pages_per_scan);

                      if ((rpgndx != (int16_t) - 1 &&
                           rpgndx > SPIFFS_GEO_PAGE_COUNT(fs)) ||
                           (rpgndx_within_range &&
                            SPIFFS_IS_LOOKUP_PAGE(fs, rpgndx)))
                        {
                          int16_t data_pgndx;

                          /* Bad reference */

                          spiffs_checkinfo("pgndx=%04x bad pgndx / LU "
                                           "referenced from page %04x\n",
                                           rpgndx, cur_pgndx);

                          /* Check for data page elsewhere */

                          ret = spiffs_objlu_find_id_and_span(fs,
                                                       objndx_phdr->objid &
                                                       ~SPIFFS_OBJID_NDXFLAG,
                                                       data_spndx_offset + i,
                                                       0, &data_pgndx);
                          if (ret == -ENOENT)
                            {
                              ret = OK;
                              data_pgndx = 0;
                            }
                          else if (ret < 0)
                            {
                              ferr("ERR: spiffs_objlu_find_id_and_span %d\n",
                                    ret);
                              return ret;
                            }

                          if (data_pgndx == 0)
                            {
                              struct spiffs_page_header_s new_ph;

                              /* If not, allocate free page */

                              new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED |
                                                      SPIFFS_PH_FLAG_FINAL);
                              new_ph.objid = objndx_phdr->objid &
                                             ~SPIFFS_OBJID_NDXFLAG;
                              new_ph.spndx = data_spndx_offset + i;

                              ret = spiffs_page_allocate_data(fs,
                                                         new_ph.objid,
                                                         &new_ph, 0, 0, 0, 1,
                                                         &data_pgndx);
                              if (ret < 0)
                                {
                                  ferr("ERR: spiffs_page_allocate_data %d\n",
                                       ret);
                                  return ret;
                                }

                              spiffs_checkinfo("Found no existing data page,"
                                               " created new @ %04x\n",
                                               data_pgndx);
                            }

                          /* Remap index */

                          spiffs_checkinfo("Rewriting index pgndx=%04x\n",
                                           cur_pgndx);

                          ret =
                            spiffs_check_rewrite_index(fs,
                                                      objndx_phdr->objid |
                                                      SPIFFS_OBJID_NDXFLAG,
                                                      data_spndx_offset + i,
                                                      data_pgndx, cur_pgndx);
                          if (ret == -EFAULT)
                            {
                              /* Index bad also, cannot mend this file */

                              spiffs_checkinfo("Index bad %d, cannot mend - "
                                               "delete object\n",
                                               ret);

                              /* Delete file */

                              ret = spiffs_page_delete(fs, cur_pgndx);
                              if (ret < 0)
                                {
                                  ferr("ERROR: spiffs_page_delete(): %d\n",
                                       ret);
                                  return ret;
                                }
                            }
                          else if (ret < 0)
                            {
                              ferr("ERR: spiffs_check_rewrite_index(): %d\n",
                                   ret);
                              return ret;
                            }

                          restart = true;
                        }
                      else if (rpgndx_within_range)
                        {
                          /* Valid reference. read referenced page header */

                          struct spiffs_page_header_s rphdr;
                          ret =
                            spiffs_cache_read(fs,
                                      SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
                                      0, SPIFFS_PAGE_TO_PADDR(fs, rpgndx),
                                      sizeof(struct spiffs_page_header_s),
                                      (FAR uint8_t *)&rphdr);
                          if (ret < 0)
                            {
                              ferr("ERROR: spiffs_cache_read() failed: %d\n",
                                   ret);
                              return ret;
                            }

                          /* Cross reference page header check */

                          if (rphdr.objid != (pghdr.objid &
                                              ~SPIFFS_OBJID_NDXFLAG) ||
                              rphdr.spndx != data_spndx_offset + i ||
                              (rphdr.flags & (SPIFFS_PH_FLAG_DELET |
                                              SPIFFS_PH_FLAG_INDEX |
                                              SPIFFS_PH_FLAG_USED)) !=
                              (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_INDEX))
                            {
                              int16_t data_pgndx;

                              spiffs_checkinfo(
                                "pgndx=%04x has inconsistent page header "
                                "index objid/span:%04x/%04x, "
                                "ref objid/span:%04x/%04x flags=%02x\n",
                                rpgndx, pghdr.objid & ~SPIFFS_OBJID_NDXFLAG,
                                data_spndx_offset + i, rphdr.objid,
                                rphdr.spndx, rphdr.flags);

                              /* Try finding correct page */

                              ret =
                                spiffs_objlu_find_id_and_span(fs,
                                                       pghdr.objid &
                                                       ~SPIFFS_OBJID_NDXFLAG,
                                                       data_spndx_offset + i,
                                                       rpgndx, &data_pgndx);
                              if (ret == -ENOENT)
                                {
                                  ret = OK;
                                  data_pgndx = 0;
                                }
                              else if (ret < 0)
                                {
                                  ferr("spiffs_objlu_find_id_and_span: %d\n",
                                       ret);
                                  return ret;
                                }

                              if (data_pgndx == 0)
                                {
                                  /* Not found, this index is badly borked */

                                  spiffs_checkinfo(
                                    "Index bad, delete object objid %04x\n",
                                    pghdr.objid);

                                  ret = spiffs_check_delobj_lazy(fs,
                                                                pghdr.objid);
                                  if (ret < 0)
                                    {
                                      ferr("spiffs_check_delobj_lazy: %d\n",
                                           ret);
                                      return ret;
                                    }

                                  break;
                                }
                              else
                                {
                                  /* Found it, so rewrite index */

                                  spiffs_checkinfo(
                                    "Found correct data pgndx=%04x, "
                                    "rewrite index pgndx=%04x objid=%04x\n",
                                    data_pgndx, cur_pgndx, pghdr.objid);

                                  ret =
                                    spiffs_check_rewrite_index(
                                      fs, pghdr.objid, data_spndx_offset + i,
                                      data_pgndx, cur_pgndx);
                                  if (ret == -EFAULT)
                                    {
                                      /* Index bad, cannot mend this file */

                                      spiffs_checkinfo(
                                        "Index bad %d, cannot mend!\n", ret);

                                      ret = spiffs_check_delobj_lazy(
                                              fs, pghdr.objid);
                                    }
                                  else if (ret < 0)
                                    {
                                      ferr("spiffs_check_rewrite_index %d\n",
                                           ret);
                                      return ret;
                                    }

                                  restart = true;
                                }
                            }
                          else
                            {
                              /* Mark rpgndx as referenced */

                              const uint32_t rpgndx_byte_ix =
                                (rpgndx - pgndx_offset) / (8 / bits);
                              const uint8_t rpgndx_bit_ix =
                                (rpgndx & ((8 / bits) - 1)) * bits;

                              if ((fs->work[rpgndx_byte_ix] &
                                   (1 << (rpgndx_bit_ix + 1))) != 0)
                                {
                                  spiffs_checkinfo(
                                    "pgndx=%04x multiple referenced "
                                    "from page %04x\n",
                                    rpgndx, cur_pgndx);

                                  /* Here, we should have fixed all broken
                                   * references - getting this means there
                                   * must be multiple files with same object
                                   * ID. Only solution is to delete
                                   * the object which is referring to this
                                   * page
                                   */

                                  spiffs_checkinfo("Removing objid=%04x and"
                                                   "page=%04x\n",
                                                   pghdr.objid, cur_pgndx);

                                  ret = spiffs_check_delobj_lazy(
                                          fs, pghdr.objid);
                                  if (ret < 0)
                                    {
                                      ferr("spiffs_check_delobj_lazy: %d\n",
                                           ret);
                                      return ret;
                                    }

                                  /* Precaution, delete this page also */

                                  ret = spiffs_page_delete(fs, cur_pgndx);
                                  if (ret < 0)
                                    {
                                      ferr("ERR: spiffs_page_delete(): %d\n",
                                           ret);
                                      return ret;
                                    }

                                  restart = true;
                                }

                              fs->work[rpgndx_byte_ix] |=
                                (1 << (rpgndx_bit_ix + 1));
                            }
                        }
                    }
                }

              /* Next page */

              cur_pgndx++;
            }

          /* Next block */

          cur_block++;
        }

      /* Check consistency bitmap */

      if (!restart)
        {
          uint32_t byte_ndx;
          int16_t objndx_pgndx;
          int16_t rpgndx;
          uint8_t bit_ndx;

          for (byte_ndx = 0;
               !restart && byte_ndx < SPIFFS_GEO_PAGE_SIZE(fs);
               byte_ndx++)
            {
              for (bit_ndx = 0; !restart && bit_ndx < 8 / bits; bit_ndx++)
                {
                  uint8_t bitmask;
                  int16_t cur_pgndx;

                  bitmask   = (fs->work[byte_ndx] >> (bit_ndx * bits)) & 0x7;
                  cur_pgndx = pgndx_offset + byte_ndx * (8 / bits) + bit_ndx;

                  /* 000 ok - free, unreferenced, not index */

                  if (bitmask == 0x1)
                    {
                      struct spiffs_page_header_s pghdr;
                      bool rewrite_ndx_to_this = false;
                      bool delete_page = false;

                      /* 001 */

                      spiffs_checkinfo(
                        "pgndx=%04x USED, UNREFERENCED, not index\n",
                        cur_pgndx);

                      /* Check corresponding object index entry */

                      ret = spiffs_cache_read(fs,
                                      SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
                                      0,
                                      SPIFFS_PAGE_TO_PADDR(fs, cur_pgndx),
                                      sizeof(struct spiffs_page_header_s),
                                      (FAR uint8_t *)&pghdr);
                      if (ret < 0)
                        {
                          ferr("ERROR: spiffs_cache_read() failed: %d\n",
                               ret);
                          return ret;
                        }

                      ret = spiffs_check_get_data_pgndx(fs, pghdr.objid,
                                                        pghdr.spndx, &rpgndx,
                                                        &objndx_pgndx);
                      if (ret >= 0)
                        {
                          if (((rpgndx == (int16_t) - 1 ||
                                rpgndx > SPIFFS_GEO_PAGE_COUNT(fs)) ||
                               (SPIFFS_IS_LOOKUP_PAGE(fs, rpgndx))))
                            {
                              /* Pointing to a bad page altogether, rewrite
                               * index to this
                               */

                              rewrite_ndx_to_this = true;

                              spiffs_checkinfo(
                                "Corresponding ref is bad: "
                                "%04x, rewrite to this %04x\n",
                                rpgndx, cur_pgndx);
                            }
                          else
                            {
                              struct spiffs_page_header_s rphdr;

                              /* Pointing to something else, check what */

                              ret =
                                spiffs_cache_read(fs,
                                         SPIFFS_OP_T_OBJ_LU2 |
                                         SPIFFS_OP_C_READ,
                                         0,
                                         SPIFFS_PAGE_TO_PADDR(fs, rpgndx),
                                         sizeof(struct spiffs_page_header_s),
                                         (FAR uint8_t *)&rphdr);
                              if (ret < 0)
                                {
                                  ferr("ERROR: spiffs_cache_read(): %d\n",
                                       ret);
                                  return ret;
                                }

                              if (((pghdr.objid & ~SPIFFS_OBJID_NDXFLAG) ==
                                   rphdr.objid) &&
                                  ((rphdr.flags & (SPIFFS_PH_FLAG_INDEX |
                                                    SPIFFS_PH_FLAG_DELET |
                                                    SPIFFS_PH_FLAG_USED |
                                                    SPIFFS_PH_FLAG_FINAL)) ==
                                                  (SPIFFS_PH_FLAG_INDEX |
                                                    SPIFFS_PH_FLAG_DELET)))
                                {
                                  /* Pointing to something else valid, just
                                   * delete this page then
                                   */

                                  spiffs_checkinfo(
                                    "Corresponding ref is good but "
                                    "different: %04x, delete this %04x\n",
                                    rpgndx, cur_pgndx);

                                  delete_page = true;
                                }

                              /* Pointing to something weird, update index
                               * to point to this page instead
                               */

                              else if (rpgndx != cur_pgndx)
                                {
                                  spiffs_checkinfo
                                    ("PA: corresponding ref is weird: "
                                     "%04x %s%s%s%s, rewrite this "
                                      "%04x\n", rpgndx,
                                      (rphdr.flags & SPIFFS_PH_FLAG_INDEX) ?
                                       "" : "INDEX ",
                                      (rphdr.flags & SPIFFS_PH_FLAG_DELET) ?
                                       "" : "DELETED ",
                                      (rphdr.flags & SPIFFS_PH_FLAG_USED) ?
                                       "NOTUSED " : "",
                                      (rphdr.flags & SPIFFS_PH_FLAG_FINAL) ?
                                       "NOTFINAL " : "", cur_pgndx);

                                  rewrite_ndx_to_this = true;
                                }
                              else
                                {
                                  /* Should not happen, destined for fubar */
                                }
                            }
                        }
                      else if (ret == -ENOENT)
                        {
                          spiffs_checkinfo("Corresponding ref not found, "
                                           "delete %04x\n",
                                           cur_pgndx);

                          delete_page = true;
                          ret = OK;
                        }

                      if (rewrite_ndx_to_this)
                        {
                          /* If pointing to invalid page, redirect index to
                           * this page
                           */

                          spiffs_checkinfo(
                            "Rewrite index objid=%04x data spndx=%04x"
                            " to point to this pgndx: %04x\n",
                            pghdr.objid, pghdr.spndx, cur_pgndx);

                          ret = spiffs_check_rewrite_index(fs, pghdr.objid,
                                  pghdr.spndx, cur_pgndx, objndx_pgndx);
                          if (ret == -EFAULT)
                            {
                              int ret2;

                              /* Index bad also, cannot mend this file */

                              spiffs_checkinfo("PA: FIXUP: index bad %d"
                                               ", cannot mend!\n", ret);

                              ret2 = spiffs_page_delete(fs, cur_pgndx);
                              if (ret2 < 0)
                                {
                                  ferr("ERROR: spiffs_page_delete(): %d\n",
                                       ret2);
                                  return ret2;
                                }

                              ret2 = spiffs_check_delobj_lazy(fs,
                                                              pghdr.objid);
                              if (ret2 < 0)
                                {
                                  ferr(
                                    "ERR: spiffs_check_delobj_lazy(): %d\n",
                                    ret2);
                                  return ret2;
                                }
                            }
                          else if (ret < 0)
                            {
                              ferr("ERR: spiffs_check_rewrite_index(): %d\n",
                                   ret);
                              return ret;
                            }

                          restart = true;
                          continue;
                        }
                      else if (delete_page)
                        {
                          spiffs_checkinfo("Deleting page %04x\n",
                                           cur_pgndx);

                          ret = spiffs_page_delete(fs, cur_pgndx);
                          if (ret < 0)
                            {
                              ferr("ERROR: spiffs_page_delete(): %d\n", ret);
                              return ret;
                            }
                        }
                    }

                  if (bitmask == 0x2)
                    {
                      /* 010 */

                      spiffs_checkinfo(
                        "pgndx=%04x  FREE, REFERENCED, not index\n",
                        cur_pgndx);

                      /* No op, this should be taken care of when checking
                       * valid references
                       */
                    }

                  /* 011 OK - busy, referenced, not index */

                  if (bitmask == 0x4)
                    {
                      /* 100 */

                      spiffs_checkinfo(
                        "pgndx=%04x FREE, unreferenced, INDEX\n", cur_pgndx);

                      /* This should never happen, major fubar */
                    }

                  /* 101 OK - busy, unreferenced, index */

                  if (bitmask == 0x6)
                    {
                      /* 110 */

                      spiffs_checkinfo(
                        "pgndx=%04x FREE, REFERENCED, INDEX\n", cur_pgndx);

                      /* No op, this should be taken care of when checking
                       * valid references
                       */
                    }

                  if (bitmask == 0x7)
                    {
                      /* 111 */

                      spiffs_checkinfo(
                        "pgndx=%04x USED, REFERENCED, INDEX\n", cur_pgndx);

                      /* No op, this should be taken care of when checking
                       * valid references
                       */
                    }
                }
            }
        }

      spiffs_checkinfo("Processed %04x, restart %d\n",
                       pgndx_offset, restart);

      /* next page range */

      if (!restart)
        {
          pgndx_offset += pages_per_scan;
        }
    }

  return ret;
}