static int fs_format(int cidx) {
  s32_t r;
  struct mount_info *m = &s_fsm;
  _u32 fsc_size = FS_CONTAINER_SIZE(FS_SIZE);
  dprintf(("formatting %d s=%u, cs=%d\n", cidx, FS_SIZE, (int) fsc_size));

  m->cidx = cidx;
  r = fs_create_container(cidx, FS_SIZE);
  if (r < 0) goto out;
  m->fh = r;
  m->valid = m->rw = m->formatting = 1;
  /* Touch a byte at the end to open a "hole". */
  r = sl_FsWrite(m->fh, fsc_size - 1, (_u8 *) "\xff", 1);
  dprintf(("write 1 @ %d %d\n", (int) (fsc_size - 1), (int) r));
  if (r != 1) goto out_close;

  /* There must be a mount attempt before format. It'll fail and that's ok. */
  r = fs_mount_spiffs(m, FS_SIZE, FS_BLOCK_SIZE, FS_PAGE_SIZE);
  dprintf(("mount: %d\n", (int) r));
  r = SPIFFS_format(&m->fs);
  dprintf(("format: %d\n", (int) r));
  if (r != SPIFFS_OK) goto out_close;

  m->seq = INITIAL_SEQ;
  r = fs_write_meta(m);

out_close:
  sl_FsClose(m->fh, NULL, NULL, 0);
out:
  m->fh = -1;
  m->valid = m->formatting = m->rw = 0;
  return r;
}
static _i32 fs_create_container(int cidx, _u32 fs_size) {
  _i32 fh = -1;
  const _u8 *fname = container_fname(cidx);
  _u32 fsize = FS_CONTAINER_SIZE(fs_size);
  int r = sl_FsDel(fname, 0);
  dprintf(("del %s -> %d\n", fname, r));
  r = sl_FsOpen(fname, FS_MODE_OPEN_CREATE(fsize, 0), NULL, &fh);
  dprintf(("open %s %d -> %d %d\n", fname, (int) fsize, (int) r, (int) fh));
  if (r != 0) return r;
  return fh;
}
_i32 fs_create_container(const char *cpfx, int cidx, _u32 fs_size) {
  _i32 fh = -1;
  _u8 fname[MAX_FS_CONTAINER_FNAME_LEN];
  _u32 fsize = FS_CONTAINER_SIZE(fs_size);
  fs_container_fname(cpfx, cidx, fname);
  int r = sl_FsDel(fname, 0);
  DBG(("del %s -> %d", fname, r));
  r = sl_FsOpen(fname, FS_MODE_OPEN_CREATE(fsize, 0), NULL, &fh);
  LOG((r == 0 ? LL_DEBUG : LL_ERROR),
      ("open %s %d -> %d %d", fname, (int) fsize, (int) r, (int) fh));
  if (r != 0) return r;
  return fh;
}
static _i32 fs_write_meta(struct mount_info *m) {
  int r;
  _u32 offset;
  union fs_meta meta;
  memset(&meta, 0xff, sizeof(meta));
  meta.info.seq = m->seq;
  meta.info.fs_size = m->fs.cfg.phys_size;
  meta.info.fs_block_size = m->fs.cfg.log_block_size;
  meta.info.fs_page_size = m->fs.cfg.log_page_size;

  offset = FS_CONTAINER_SIZE(meta.info.fs_size) - sizeof(meta);
  r = sl_FsWrite(m->fh, offset, (_u8 *) &meta, sizeof(meta));
  dprintf(("write meta %llu @ %d: %d\n", m->seq, (int) offset, (int) r));
  if (r == sizeof(meta)) r = 0;
  return r;
}
_i32 fs_write_meta(_i32 fh, _u64 seq, _u32 fs_size, _u32 fs_block_size,
                   _u32 fs_page_size, _u32 fs_erase_size) {
  int r;
  _u32 offset;
  union fs_container_meta meta;
  memset(&meta, 0xff, sizeof(meta));
  meta.info.seq = seq;
  meta.info.fs_size = fs_size;
  meta.info.fs_block_size = fs_block_size;
  meta.info.fs_page_size = fs_page_size;
  meta.info.fs_erase_size = fs_erase_size;

  offset = FS_CONTAINER_SIZE(meta.info.fs_size) - sizeof(meta);
  r = sl_FsWrite(fh, offset, (_u8 *) &meta, sizeof(meta));
  DBG(("write meta %llu @ %d: %d", seq, (int) offset, (int) r));
  if (r == sizeof(meta)) r = 0;
  return r;
}
static int tcmp(const struct json_token *tok, const char *str) {
  struct mg_str s = {.p = tok->ptr, .len = tok->len};
  return mg_vcmp(&s, str);
}

enum mgos_upd_file_action mgos_upd_file_begin(
    struct mgos_upd_hal_ctx *ctx, const struct mgos_upd_file_info *fi) {
  struct mg_str part_name = MG_MK_STR("");
  enum mgos_upd_file_action ret = MGOS_UPDATER_SKIP_FILE;
  struct find_part_info find_part_info = {fi->name, &part_name, &ctx->cur_part};
  ctx->cur_part.len = part_name.len = 0;
  json_walk(ctx->parts->ptr, ctx->parts->len, find_part, &find_part_info);
  if (ctx->cur_part.len == 0) return ret;
  /* Drop any indexes from part name, we'll add our own. */
  while (1) {
    char c = part_name.p[part_name.len - 1];
    if (c != '.' && !(c >= '0' && c <= '9')) break;
    part_name.len--;
  }
  struct json_token type = JSON_INVALID_TOKEN;
  const char *fname = NULL;
  uint32_t falloc = 0;
  json_scanf(ctx->cur_part.ptr, ctx->cur_part.len,
             "{load_addr:%u, falloc:%u, type: %T}", &ctx->app_load_addr,
             &falloc, &type);

  if (falloc == 0) falloc = fi->size;
  if (tcmp(&type, "app") == 0) {
    struct boot_cfg cur_cfg;
    int r = read_boot_cfg(ctx->cur_boot_cfg_idx, &cur_cfg);
    if (r < 0) {
      ctx->status_msg = "Could not read current boot cfg";
      return MGOS_UPDATER_ABORT;
    }
#if CC3200_SAFE_CODE_UPDATE
    /*
     * When safe code update is enabled, we write code to a new file.
     * Otherwise we write to the same slot we're using currently, which is
     * unsafe, makes reverting code update not possible, but saves space.
     */
    create_fname(
        mg_mk_str_n(cur_cfg.app_image_file, strlen(cur_cfg.app_image_file) - 2),
        ctx->new_boot_cfg_idx, ctx->app_image_file,
        sizeof(ctx->app_image_file));
#else
    {
      strncpy(ctx->app_image_file, cur_cfg.app_image_file,
              sizeof(ctx->app_image_file));
    }
#endif
    if (ctx->app_load_addr >= 0x20000000) {
      fname = ctx->app_image_file;
    } else {
      ctx->status_msg = "Bad/missing app load_addr";
      ret = MGOS_UPDATER_ABORT;
    }
  } else if (tcmp(&type, "fs") == 0) {
    json_scanf(
        ctx->cur_part.ptr, ctx->cur_part.len,
        "{fs_size: %u, fs_block_size: %u, fs_page_size: %u, fs_erase_size: %u}",
        &ctx->fs_size, &ctx->fs_block_size, &ctx->fs_page_size,
        &ctx->fs_erase_size);
    if (ctx->fs_size > 0 && ctx->fs_block_size > 0 && ctx->fs_page_size > 0 &&
        ctx->fs_erase_size > 0) {
      char fs_container_prefix[MAX_FS_CONTAINER_PREFIX_LEN];
      create_fname(part_name, ctx->new_boot_cfg_idx, fs_container_prefix,
                   sizeof(fs_container_prefix));
      /* Delete container 1 (if any) so that 0 is the only one. */
      cc32xx_vfs_dev_slfs_container_delete_container(fs_container_prefix, 1);
      cc32xx_vfs_dev_slfs_container_fname(fs_container_prefix, 0,
                                          (_u8 *) ctx->fs_container_file);
      fname = ctx->fs_container_file;
      if (fi->size > ctx->fs_size) {
        /* Assume meta has already been added. */
        falloc = fi->size;
      } else {
        falloc = FS_CONTAINER_SIZE(fi->size);
      }
    } else {
      ctx->status_msg = "Missing FS parameters";
      ret = MGOS_UPDATER_ABORT;
    }
  }
  if (fname != NULL) {
    int r = prepare_to_write(ctx, fi, fname, falloc, &ctx->cur_part);
    if (r < 0) {
      LOG(LL_ERROR, ("err = %d", r));
      ret = MGOS_UPDATER_ABORT;
    } else {
      ret = (r > 0 ? MGOS_UPDATER_PROCESS_FILE : MGOS_UPDATER_SKIP_FILE);
    }
  }
  if (ret == MGOS_UPDATER_SKIP_FILE) {
    DBG(("Skipping %s %.*s", fi->name, (int) part_name.len, part_name.p));
  }
  return ret;
}

int mgos_upd_file_data(struct mgos_upd_hal_ctx *ctx,
                       const struct mgos_upd_file_info *fi,
                       struct mg_str data) {
  _i32 r = sl_FsWrite(ctx->cur_fh, fi->processed, (_u8 *) data.p, data.len);
  if (r != data.len) {
    ctx->status_msg = "Write failed";
    r = -1;
  }
  return r;
}