* 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;
struct file file;
char buffer[CONFIG_FS_ZIPFS_BUFFER_SIZE];
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,
zipfs_close,
zipfs_read,
NULL,
zipfs_seek,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
zipfs_dup,
zipfs_fstat,
NULL,
zipfs_opendir,
zipfs_closedir,
zipfs_readdir,
zipfs_rewinddir,
zipfs_bind,
zipfs_unbind,
zipfs_statfs,
NULL,
NULL,
NULL,
NULL,
zipfs_stat,
NULL
};
* 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)
{
*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;
}
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;
}