e4981191创建于 4月21日历史提交
/****************************************************************************
 * fs/zipfs/zip_vfs.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you 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.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <debug.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/statfs.h>

#include <archive_entry.h>
#include <archive.h>

#include <nuttx/mutex.h>

#include "fs_heap.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/****************************************************************************
 * Private Types
 ****************************************************************************/

struct zipfs_priv_s
{
  FAR struct archive_entry *entry;
  FAR struct archive *a;

  /* callback data */

  struct file file;
  char buffer[CONFIG_FS_ZIPFS_BUFFER_SIZE];

  /* seek data */

  FAR char *seekbuf;
  mutex_t lock;
};

struct zipfs_dir_s
{
  struct fs_dirent_s dir;
  FAR struct zipfs_priv_s *priv;
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

static int zipfs_open(FAR struct file *filep, FAR const char *relpath,
                      int oflags, mode_t mode);
static int zipfs_close(FAR struct file *filep);
static ssize_t zipfs_read(FAR struct file *filep, FAR char *buffer,
                          size_t buflen);
static off_t zipfs_seek(FAR struct file *filep, off_t offset,
                        int whence);
static int zipfs_dup(FAR const struct file *oldp,
                     FAR struct file *newp);
static int zipfs_fstat(FAR const struct file *filep,
                       FAR struct stat *buf);
static int zipfs_opendir(FAR struct inode *mountpt,
                         FAR const char *relpath,
                         FAR struct fs_dirent_s **dir);
static int zipfs_closedir(FAR struct inode *mountpt,
                          FAR struct fs_dirent_s *dir);
static int zipfs_readdir(FAR struct inode *mountpt,
                         FAR struct fs_dirent_s *dir,
                         FAR struct dirent *entry);
static int zipfs_rewinddir(FAR struct inode *mountpt,
                           FAR struct fs_dirent_s *dir);
static int zipfs_statfs(FAR struct inode *mountpt,
                        FAR struct statfs *buf);
static int zipfs_stat(FAR struct inode *mountpt,
                      FAR const char *relpath,
                      FAR struct stat *buf);
static int zipfs_bind(FAR struct inode *driver, FAR const void *data,
                      FAR void **handle);
static int zipfs_unbind(FAR void *handle, FAR struct inode **driver,
                        unsigned int flags);

/****************************************************************************
 * Private Data
 ****************************************************************************/

const struct mountpt_operations g_zipfs_operations =
{
  zipfs_open,          /* open */
  zipfs_close,         /* close */
  zipfs_read,          /* read */
  NULL,                /* write */
  zipfs_seek,          /* seek */
  NULL,                /* ioctl */
  NULL,                /* mmap */
  NULL,                /* truncate */
  NULL,                /* poll */
  NULL,                /* readv */
  NULL,                /* writev */
  NULL,                /* sync */
  zipfs_dup,           /* dup */
  zipfs_fstat,         /* fstat */
  NULL,                /* fchstat */

  zipfs_opendir,       /* opendir */
  zipfs_closedir,      /* closedir */
  zipfs_readdir,       /* readdir */
  zipfs_rewinddir,     /* rewinddir */

  zipfs_bind,          /* bind */
  zipfs_unbind,        /* unbind */
  zipfs_statfs,        /* statfs */

  NULL,                /* unlink */
  NULL,                /* mkdir */
  NULL,                /* rmdir */
  NULL,                /* rename */
  zipfs_stat,          /* stat */
  NULL                 /* chstat */
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static int zipfs_convert_result(int ret)
{
  switch (ret)
    {
      case ARCHIVE_RETRY:
        return -EAGAIN;
      case ARCHIVE_WARN:
        return -ENOEXEC;
      case ARCHIVE_FAILED:
        return -EINVAL;
      case ARCHIVE_FATAL:
        return -EPERM;

      default:
        return ret;
    }
}

static int zipfs_close_cb(FAR struct archive *a, FAR void *client_data)
{
  FAR struct zipfs_priv_s *priv = client_data;

  return file_close(&priv->file);
}

static la_ssize_t zipfs_read_cb(FAR struct archive *a,
                                FAR void *client_data,
                                FAR const void **buff)
{
  FAR struct zipfs_priv_s *priv = client_data;
  ssize_t ret;

  ret = file_read(&priv->file, priv->buffer,
                  CONFIG_FS_ZIPFS_BUFFER_SIZE);
  if (ret <= 0)
    {
      return ret;
    }

  *buff = priv->buffer;
  return ret;
}

static la_int64_t zipfs_seek_cb(FAR struct archive *a,
                                FAR void *client_data,
                                la_int64_t offset, int whence)
{
  FAR struct zipfs_priv_s *priv = client_data;

  return file_seek(&priv->file, offset, whence);
}

static off_t zipfs_skip(FAR struct zipfs_priv_s *priv, off_t amount)
{
  off_t next = 0;

  if (priv->seekbuf == NULL)
    {
      priv->seekbuf = fs_heap_malloc(CONFIG_FS_ZIPFS_BUFFER_SIZE);
      if (priv->seekbuf == NULL)
        {
          return -ENOMEM;
        }
    }

  while (next < amount)
    {
      off_t remain = amount - next;

      if (remain > CONFIG_FS_ZIPFS_BUFFER_SIZE)
        {
          remain = CONFIG_FS_ZIPFS_BUFFER_SIZE;
        }

      remain = archive_read_data(priv->a, priv->seekbuf, remain);
      remain = zipfs_convert_result(remain);
      if (remain <= 0)
        {
          return next ? next : remain;
        }

      next += remain;
    }

  return next;
}

int zipfs_new(FAR const char *abspath, FAR const char *relpath,
              FAR struct zipfs_priv_s **priv)
{
  FAR struct zipfs_priv_s *newp;
  int ret = -ENOMEM;

  newp = fs_heap_zalloc(sizeof(struct zipfs_priv_s));
  if (newp == NULL)
    {
      return ret;
    }

  newp->a = archive_read_new();
  if (newp->a == NULL)
    {
      ferr("ERROR: archive_read_new() failed\n");
      goto err_with_priv;
    }

  nxmutex_init(&newp->lock);
#ifdef CONFIG_FS_ZIPFS_FORMAT_ALL
  ret = zipfs_convert_result(archive_read_support_format_all(newp->a));
  if (ret < 0)
    {
      ferr("ERROR: archive_read_support_format_all() failed\n");
      goto err_with_lock;
    }
#else
#  ifdef CONFIG_FS_ZIPFS_FORMAT_ZIP
  ret = zipfs_convert_result(archive_read_support_format_zip(newp->a));
  if (ret < 0)
    {
      ferr("ERROR: archive_read_support_format_zip() failed\n");
      goto err_with_lock;
    }
#  endif

#  ifdef CONFIG_FS_ZIPFS_FORMAT_7ZIP
  ret = zipfs_convert_result(archive_read_support_format_7zip(newp->a));
  if (ret < 0)
    {
      ferr("ERROR: archive_read_support_format_7zip() failed\n");
      goto err_with_lock;
    }
#  endif

#  ifdef CONFIG_FS_ZIPFS_FORMAT_AR
  ret = zipfs_convert_result(archive_read_support_format_ar(newp->a));
  if (ret < 0)
    {
      ferr("ERROR: archive_read_support_format_ar() failed\n");
      goto err_with_lock;
    }
#  endif

#  ifdef CONFIG_FS_ZIPFS_FORMAT_CAB
  ret = zipfs_convert_result(archive_read_support_format_cab(newp->a));
  if (ret < 0)
    {
      ferr("ERROR: archive_read_support_format_cab() failed\n");
      goto err_with_lock;
    }
#  endif

#  ifdef CONFIG_FS_ZIPFS_FORMAT_CPIO
  ret = zipfs_convert_result(archive_read_support_format_cpio(newp->a));
  if (ret < 0)
    {
      ferr("ERROR: archive_read_support_format_cpio() failed\n");
      goto err_with_lock;
    }
#  endif

#  ifdef CONFIG_FS_ZIPFS_FORMAT_EMPTY
  ret = zipfs_convert_result(archive_read_support_format_empty(newp->a));
  if (ret < 0)
    {
      ferr("ERROR: archive_read_support_format_empty() failed\n");
      goto err_with_lock;
    }
#  endif

#  ifdef CONFIG_FS_ZIPFS_FORMAT_ISO9660
  ret = archive_read_support_format_iso9660(newp->a);
  ret = zipfs_convert_result(ret);
  if (ret < 0)
    {
      ferr("ERROR: archive_read_support_format_iso9660() failed\n");
      goto err_with_lock;
    }
#  endif

#  ifdef CONFIG_FS_ZIPFS_FORMAT_LHA
  ret = zipfs_convert_result(archive_read_support_format_lha(newp->a));
  if (ret < 0)
    {
      ferr("ERROR: archive_read_support_format_lha() failed\n");
      goto err_with_lock;
    }
#  endif

#  ifdef CONFIG_FS_ZIPFS_FORMAT_MTREE
  ret = zipfs_convert_result(archive_read_support_format_mtree(newp->a));
  if (ret < 0)
    {
      ferr("ERROR: archive_read_support_format_mtree() failed\n");
      goto err_with_lock;
    }
#  endif

#  ifdef CONFIG_FS_ZIPFS_FORMAT_RAR
  ret = zipfs_convert_result(archive_read_support_format_rar(newp->a));
  if (ret < 0)
    {
      ferr("ERROR: archive_read_support_format_rar() failed\n");
      goto err_with_lock;
    }
#  endif

#  ifdef CONFIG_FS_ZIPFS_FORMAT_RAR_V5
  ret = zipfs_convert_result(archive_read_support_format_rar5(newp->a));
  if (ret < 0)
    {
      ferr("ERROR: archive_read_support_format_rar5() failed\n");
      goto err_with_lock;
    }
#  endif

#  ifdef CONFIG_FS_ZIPFS_FORMAT_RAW
  ret = zipfs_convert_result(archive_read_support_format_raw(newp->a));
  if (ret < 0)
    {
      ferr("ERROR: archive_read_support_format_raw() failed\n");
      goto err_with_lock;
    }
#  endif

#  ifdef CONFIG_FS_ZIPFS_FORMAT_TAR
  ret = zipfs_convert_result(archive_read_support_format_tar(newp->a));
  if (ret < 0)
    {
      ferr("ERROR: archive_read_support_format_tar() failed\n");
      goto err_with_lock;
    }
#  endif

#  ifdef CONFIG_FS_ZIPFS_FORMAT_WARC
  ret = zipfs_convert_result(archive_read_support_format_warc(newp->a));
  if (ret < 0)
    {
      ferr("ERROR: archive_read_support_format_warc() failed\n");
      goto err_with_lock;
    }
#  endif

#  ifdef CONFIG_FS_ZIPFS_FORMAT_XAR
  ret = zipfs_convert_result(archive_read_support_format_xar(newp->a));
  if (ret < 0)
    {
      ferr("ERROR: archive_read_support_format_xar() failed\n");
      goto err_with_lock;
    }
#  endif
#endif

  ret = file_open(&newp->file, abspath, O_RDONLY);
  if (ret < 0)
    {
      ferr("ERROR: file_open() failed\n");
      goto err_with_lock;
    }

  archive_read_set_seek_callback(newp->a, zipfs_seek_cb);
  ret = archive_read_open(newp->a, newp, NULL,
                          zipfs_read_cb, zipfs_close_cb);
  ret = zipfs_convert_result(ret);
  if (ret < 0)
    {
      ferr("ERROR: archive_read_open() failed\n");
      goto err_with_open;
    }

  newp->entry = archive_entry_new2(newp->a);
  if (newp->entry == NULL)
    {
      ferr("ERROR: archive_entry_new2() failed\n");
      ret = -ENOMEM;
      goto err_with_close;
    }

  if (relpath == NULL)
    {
      /* Open root dir  */

      *priv = newp;
      return OK;
    }

  while (true)
    {
      ret = archive_read_next_header2(newp->a, newp->entry);
      ret = zipfs_convert_result(ret);
      if (ret < 0)
        {
          break;
        }

      if (strcmp(archive_entry_pathname(newp->entry), relpath) == 0)
        {
          *priv = newp;
          return OK;
        }
    }

  ret = -ENOENT;
  archive_entry_free(newp->entry);
err_with_close:
  archive_read_close(newp->a);
err_with_open:
  file_close(&newp->file);
err_with_lock:
  nxmutex_destroy(&newp->lock);
  archive_read_free(newp->a);
err_with_priv:
  fs_heap_free(newp);
  return ret;
}

static void zipfs_free(FAR struct zipfs_priv_s *priv)
{
  if (priv->seekbuf != NULL)
    {
      fs_heap_free(priv->seekbuf);
    }

  archive_entry_free(priv->entry);
  archive_read_close(priv->a);
  nxmutex_destroy(&priv->lock);
  archive_read_free(priv->a);
  fs_heap_free(priv);
}

static int zipfs_stats_common(FAR struct zipfs_priv_s *priv,
                              FAR struct stat *buf)
{
  FAR const struct stat *stat = archive_entry_stat(priv->entry);

  if (stat == NULL)
    {
      return -EINVAL;
    }

  memcpy(buf, stat, sizeof(struct stat));
  return OK;
}

/****************************************************************************
 * Zipfs methods
 ****************************************************************************/

static int zipfs_open(FAR struct file *filep, FAR const char *relpath,
                      int oflags, mode_t mode)
{
  FAR const char *abspath = filep->f_inode->i_private;
  FAR struct zipfs_priv_s *priv;
  int ret;

  ret = zipfs_new(abspath, relpath, &priv);
  if (ret < 0)
    {
      return ret;
    }

  filep->f_priv = priv;
  return OK;
}

static int zipfs_close(FAR struct file *filep)
{
  FAR struct zipfs_priv_s *priv = filep->f_priv;

  zipfs_free(priv);
  return OK;
}

static ssize_t zipfs_read(FAR struct file *filep, FAR char *buffer,
                          size_t buflen)
{
  FAR struct zipfs_priv_s *priv = filep->f_priv;
  ssize_t ret;

  nxmutex_lock(&priv->lock);
  ret = zipfs_convert_result(archive_read_data(priv->a, buffer, buflen));
  if (ret > 0)
    {
      filep->f_pos += ret;
    }

  nxmutex_unlock(&priv->lock);
  return ret;
}

static off_t zipfs_seek(FAR struct file *filep, off_t offset,
                        int whence)
{
  FAR const char *abspath = filep->f_inode->i_private;
  FAR struct zipfs_priv_s *priv = filep->f_priv;
  FAR struct zipfs_priv_s *newp;
  off_t ret = 0;

  nxmutex_lock(&priv->lock);
  switch (whence)
    {
      case SEEK_SET:
        break;
      case SEEK_CUR:
        offset += filep->f_pos;
        break;
      case SEEK_END:
        offset += archive_entry_size(priv->entry);
        break;
      default:
        ret = -EINVAL;
        goto err_with_lock;
    }

  if (filep->f_pos == offset)
    {
      goto err_with_lock;
    }
  else if (filep->f_pos > offset)
    {
      ret = zipfs_new(abspath, archive_entry_pathname(priv->entry),
                      &newp);
      if (ret < 0)
        {
          goto err_with_lock;
        }

      nxmutex_unlock(&priv->lock);
      zipfs_free(priv);
      priv = newp;
      nxmutex_lock(&priv->lock);
      filep->f_priv = newp;
      filep->f_pos = 0;
    }

  ret = zipfs_skip(priv, offset - filep->f_pos);
  if (ret < 0)
    {
      goto err_with_lock;
    }

  filep->f_pos += ret;

err_with_lock:
  nxmutex_unlock(&priv->lock);
  return ret < 0 ? ret : filep->f_pos;
}

static int zipfs_dup(FAR const struct file *oldp, FAR struct file *newp)
{
  FAR struct zipfs_priv_s *priv = oldp->f_priv;
  FAR const char *relpath = archive_entry_pathname(priv->entry);

  return zipfs_open(newp, relpath, oldp->f_oflags, 0);
}

static int zipfs_fstat(FAR const struct file *filep,
                       FAR struct stat *buf)
{
  FAR struct zipfs_priv_s *priv = filep->f_priv;

  return zipfs_stats_common(priv, buf);
}

static int zipfs_opendir(FAR struct inode *mountpt,
                         FAR const char *relpath,
                         FAR struct fs_dirent_s **dir)
{
  FAR const char *abspath = mountpt->i_private;
  FAR struct zipfs_dir_s *adir;
  int ret;

  adir = fs_heap_zalloc(sizeof(struct zipfs_dir_s));
  if (adir == NULL)
    {
      return -ENOMEM;
    }

  ret = zipfs_new(abspath, NULL, &adir->priv);
  if (ret < 0)
    {
      fs_heap_free(adir);
      return ret;
    }

  *dir = &adir->dir;
  return OK;
}

static int zipfs_closedir(FAR struct inode *mountpt,
                          FAR struct fs_dirent_s *dir)
{
  FAR struct zipfs_dir_s *adir = (FAR struct zipfs_dir_s *)dir;

  zipfs_free(adir->priv);
  fs_heap_free(adir);
  return OK;
}

static int zipfs_readdir(FAR struct inode *mountpt,
                         FAR struct fs_dirent_s *dir,
                         FAR struct dirent *entry)
{
  FAR struct zipfs_dir_s *adir = (FAR struct zipfs_dir_s *)dir;
  FAR struct zipfs_priv_s *priv = adir->priv;
  int ret;

  nxmutex_lock(&priv->lock);
  ret = archive_read_next_header2(priv->a, priv->entry);
  ret = zipfs_convert_result(ret);
  if (ret < 0)
    {
      goto err;
    }

  if (ret == ARCHIVE_EOF)
    {
      ret = EOF;
      goto err;
    }

  strlcpy(entry->d_name, archive_entry_pathname(priv->entry),
          sizeof(entry->d_name));
err:
  nxmutex_unlock(&priv->lock);
  return ret;
}

static int zipfs_rewinddir(FAR struct inode *mountpt,
                           FAR struct fs_dirent_s *dir)
{
  FAR struct zipfs_dir_s *adir = (FAR struct zipfs_dir_s *)dir;
  FAR struct zipfs_priv_s *priv = adir->priv;

  nxmutex_lock(&priv->lock);
  archive_entry_free(priv->entry);
  priv->entry = archive_entry_new2(priv->a);
  if (priv->entry == NULL)
    {
      nxmutex_unlock(&priv->lock);
      return -ENOMEM;
    }

  nxmutex_unlock(&priv->lock);
  return OK;
}

static int zipfs_statfs(FAR struct inode *mountpt,
                        FAR struct statfs *buf)
{
  buf->f_type = ZIPFS_MAGIC;
  buf->f_namelen = NAME_MAX;
  return OK;
}

static int zipfs_stat(FAR struct inode *mountpt,
                      FAR const char *relpath,
                      FAR struct stat *buf)
{
  FAR const char *abspath = mountpt->i_private;
  FAR struct zipfs_priv_s *priv;
  int ret;

  if (relpath[0] == 0)
    {
      buf->st_mode = S_IFDIR | 0555;
      return OK;
    }

  ret = zipfs_new(abspath, relpath, &priv);
  if (ret < 0)
    {
      return ret;
    }

  ret = zipfs_stats_common(priv, buf);
  zipfs_free(priv);
  return ret;
}

static int zipfs_bind(FAR struct inode *driver, FAR const void *data,
                      FAR void **handle)
{
  FAR struct zipfs_priv_s *priv;
  FAR char *abspath;
  int ret;

  if (data == NULL)
    {
      return -ENODEV;
    }

  /* Try access to zip file */

  ret = zipfs_new(data, NULL, &priv);
  if (ret < 0)
    {
      return ret;
    }

  zipfs_free(priv);
  abspath = fs_heap_zalloc(PATH_MAX);
  if (abspath == NULL)
    {
      return -ENOMEM;
    }

  *handle = abspath;
  strlcpy(abspath, data, PATH_MAX);
  return OK;
}

static int zipfs_unbind(FAR void *handle, FAR struct inode **driver,
                        unsigned int flags)
{
  fs_heap_free(handle);
  return OK;
}