diff --git a/Documentation/README.html b/Documentation/README.html index 8f7092a940a..d01ea93c9be 100644 --- a/Documentation/README.html +++ b/Documentation/README.html @@ -8,7 +8,7 @@

NuttX README Files

-

Last Updated: May 7, 2015

+

Last Updated: June 5, 2015

@@ -299,8 +299,10 @@ | | | `- README.txt | | |- smartfs/ | | | `- README.txt - | | `- procfs/ - | | `- README.txt + | | |- procfs/ + | | | `- README.txt + | | `- unionfs/ + | | `- README.txt | |- graphics/ | | `- README.txt | |- lib/ diff --git a/README.txt b/README.txt index d8d2883cfbb..b6eff41d32a 100644 --- a/README.txt +++ b/README.txt @@ -1358,7 +1358,9 @@ nuttx | | `- README.txt | |- smartfs/ | | `- README.txt - | `- procfs/ + | |- procfs/ + | | `- README.txt + | `- unionfs/ | `- README.txt |- graphics/ | `- README.txt diff --git a/configs/olimex-lpc1766stk/README.txt b/configs/olimex-lpc1766stk/README.txt index 5e7ec4f186a..49e4c7a80d8 100644 --- a/configs/olimex-lpc1766stk/README.txt +++ b/configs/olimex-lpc1766stk/README.txt @@ -1131,8 +1131,10 @@ Configuration Sub-Directories STATUS: 2015-06-02. This configuration was added in an attempt to replace - thttpd-nxflat (see below). It concurrent does not run properly. - It looks like I get out-of-memory errors during execution of CGI. + thttpd-nxflat (see below). I concurrently get out-of-memory errors + during execution of CGI. The 32KiB SRAM may be insufficient for + this configuration; this might be fixed with some carefult tuning + of stack usage. thttpd-nxflat: This builds the THTTPD web server example using the THTTPD and diff --git a/fs/Kconfig b/fs/Kconfig index 8de3f51b096..a2c22a18607 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -60,6 +60,7 @@ source fs/romfs/Kconfig source fs/smartfs/Kconfig source fs/binfs/Kconfig source fs/procfs/Kconfig +source fs/unionfs/Kconfig comment "System Logging" diff --git a/fs/Makefile b/fs/Makefile index 0c725060413..9c30b61a97c 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -69,6 +69,7 @@ include nfs/Make.defs include smartfs/Make.defs include binfs/Make.defs include procfs/Make.defs +include unionfs/Make.defs endif endif @@ -80,8 +81,6 @@ OBJS = $(AOBJS) $(COBJS) BIN = libfs$(LIBEXT) -SUBDIRS = mmap fat romfs nxffs nfs binfs procfs - all: $(BIN) $(AOBJS): %$(OBJEXT): %.S diff --git a/fs/unionfs/Kconfig b/fs/unionfs/Kconfig new file mode 100644 index 00000000000..9d98a1381eb --- /dev/null +++ b/fs/unionfs/Kconfig @@ -0,0 +1,28 @@ +# +# For a description of the syntax of this configuration file, +# see misc/tools/kconfig-language.txt. +# + +config FS_UNIONFS + bool "Union File System" + default n + ---help--- + The Union file system is provides a mechanism to overlay two + different, mounted file systems so that they appear as one. In + general this works like this: + + 1) Mount file system 1 at some location, say /mnt/file1 + 2) Mount file system 2 at some location, say /mnt/file2 + 3) Call unionfs_mount() to combine and overly /mnt/file1 and + mnt/file2 as a new mount point, say /mnt/unionfs. + + /mnt/file1 and /mnt/file2 will disappear and be replaced by the + single mountpoint /mnut/unionfs. The previous contents under + /mnt/file1 and /mnt/file2 will appear merged under /mnt/unionfs. + Files at the same relative path in file system1 will take presence. + If another file of the same name and same relative location exists + in file system 2, it will not be visible because it will be occluded + by the file in file system1. + + See include/nutts/unionfs.h for additional information. + diff --git a/fs/unionfs/Make.defs b/fs/unionfs/Make.defs new file mode 100644 index 00000000000..f97820a8aef --- /dev/null +++ b/fs/unionfs/Make.defs @@ -0,0 +1,47 @@ +############################################################################ +# fs/unionfs/Make.defs +# +# Copyright (C) 2015 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. 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. +# 3. Neither the name Nuttx nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# 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 OWNER 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. +# +############################################################################ + +ifeq ($(CONFIG_FS_UNIONFS),y) +# Files required for Union file system support + +ASRCS += +CSRCS += fs_unionfs.c + +# Include Union File System build support + +DEPPATH += --dep-path unionfs +VPATH += :unionfs + +endif diff --git a/fs/unionfs/README.txt b/fs/unionfs/README.txt new file mode 100755 index 00000000000..93695313201 --- /dev/null +++ b/fs/unionfs/README.txt @@ -0,0 +1,39 @@ +fs/unionfs/README.txt +===================== + + This directory contains the NuttX Union File System. The Union file + system is provides a mechanism to overlay two different, mounted file + systems so that they appear as one. In general this works like this: + + 1) Mount file system 1 at some location, say /mnt/file1 + 2) Mount file system 2 at some location, say /mnt/file2 + 3) Call unionfs_mount() to combine and overly /mnt/file1 and mnt/file2 + as a new mount point, say /mnt/unionfs. + + /mnt/file1 and /mnt/file2 will disappear and be replaced by the single + mountpoint /mnut/unionfs. The previous contents under /mnt/file1 and + /mnt/file2 will appear merged under /mnt/unionfs. Files at the same + relative path in file system1 will take presence. If another file of the + same name and same relative location exists in file system 2, it will + not be visible because it will be occluded by the file in file system1. + + See include/nutts/unionfs.h for additional information. + + The Union File Sysem is enabled by selecting the CONFIG_FS_UNIONFS option + in the NuttX configruation file. + + The original motivation for this file was for the use of the built-in + function file system (BINFS) with a web server. In that case, the built + in functions provide CGI programs. But the BINFS file system cannot hold + content. Fixed content would need to be retained in a more standard file + system such as ROMFS. With this Union File System, you can overly the + BINFS mountpoint on the the ROMFS mountpoint, providing a single directory + that appears to contain the executables from the BINFS file system along + with the web content from the ROMFS file system. + + Another possible use for the Union File System could be to augment or + replace files in a FLASH file system. For example, suppose that you have + a product that ships with content in a ROMFS file system provided by the + on-board FLASH. Later, you overlay that ROMFS file system with additional + files from an SD card by using the Union File System to overlay, and + perhaps replace, the ROMFS files. diff --git a/fs/unionfs/fs_unionfs.c b/fs/unionfs/fs_unionfs.c new file mode 100644 index 00000000000..d88adf96498 --- /dev/null +++ b/fs/unionfs/fs_unionfs.c @@ -0,0 +1,2034 @@ +/**************************************************************************** + * fs/unionfs/fs_unionfs.c + * + * Copyright (C) 2015 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * 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 OWNER 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "inode/inode.h" + +#if !defined(CONFIG_DISABLE_MOUNTPOINT) && defined(CONFIG_FS_UNIONFS) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#undef MIN +#undef MAX +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) + +/**************************************************************************** + * Private Types + ****************************************************************************/ +/* This structure describes one contained file system mountpoint */ + +struct unionfs_mountpt_s +{ + FAR struct inode *um_node; /* Filesystem inode */ + FAR char *um_prefix; /* Path prefix to filesystem */ +}; + +/* This structure describes the union file system */ + +struct unionfs_inode_s +{ + struct unionfs_mountpt_s ui_fs[2]; /* Contained file systems */ + sem_t ui_exclsem; /* Enforces mutually exclusive access */ + int16_t ui_nopen; /* Number of open references */ + bool ui_unhooked; /* Driver is unlinked or unbound */ +}; + +/* This structure descries one opened file */ + +struct unionfs_file_s +{ + uint8_t uf_ndx; /* Filesystem index */ + FAR struct file uf_file; /* Filesystem open file description */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ +/* Helper functions */ + +static int unionfs_semtake(FAR struct unionfs_inode_s *ui, bool noint); +#define unionfs_semgive(ui) (void)sem_post(&(ui)->ui_exclsem) + +static FAR const char *unionfs_trypath(FAR const char *relpath, + FAR const char *prefix); +static int unionfs_tryopen(FAR struct file *filep, + FAR const char *relpath, FAR const char *prefix, int oflags, + mode_t mode); +static int unionfs_tryopendir(FAR struct inode *inode, + FAR const char *relpath, FAR const char *prefix, + FAR struct fs_dirent_s *dir); +static int unionfs_trymkdir(FAR struct inode *inode, + FAR const char *relpath, FAR const char *prefix, + mode_t mode); +static int unionfs_tryrmdir(FAR struct inode *inode, + FAR const char *relpath, FAR const char *prefix); +static int unionfs_tryrename(FAR struct inode *mountpt, + FAR const char *oldrelpath, FAR const char *newrelpath, + FAR const char *prefix); +static int unionfs_trystat(FAR struct inode *inode, + FAR const char *relpath, FAR const char *prefix, + FAR struct stat *buf); +static int unionfs_trystatdir(FAR struct inode *inode, + FAR const char *relpath, FAR const char *prefix); +static int unionfs_trystatfile(FAR struct inode *inode, + FAR const char *relpath, FAR const char *prefix); + +static void unionfs_unhooked(FAR struct unionfs_inode_s *ui); +static void unionfs_destroy(FAR struct unionfs_inode_s *ui); + +/* Operations on opened files (with struct file) */ + +static int unionfs_open(FAR struct file *filep, const char *relpath, + int oflags, mode_t mode); +static int unionfs_close(FAR struct file *filep); +static ssize_t unionfs_read(FAR struct file *filep, FAR char *buffer, + size_t buflen); +static ssize_t unionfs_write(FAR struct file *filep, FAR const char *buffer, + size_t buflen); +static off_t unionfs_seek(FAR struct file *filep, off_t offset, int whence); +static int unionfs_ioctl(FAR struct file *filep, int cmd, + unsigned long arg); +static int unionfs_sync(FAR struct file *filep); +static int unionfs_dup(FAR const struct file *oldp, + FAR struct file *newp); + +/* Operations on directories */ + +static int unionfs_opendir(struct inode *mountpt, const char *relpath, + FAR struct fs_dirent_s *dir); +static int unionfs_closedir(FAR struct inode *mountpt, + FAR struct fs_dirent_s *dir); +static int unionfs_readdir(FAR struct inode *mountpt, + FAR struct fs_dirent_s *dir); +static int unionfs_rewinddir(FAR struct inode *mountpt, + FAR struct fs_dirent_s *dir); + +static int unionfs_unbind(FAR void *handle, FAR struct inode **blkdriver, + unsigned int flags); +static int unionfs_statfs(FAR struct inode *mountpt, + FAR struct statfs *buf); + + /* Operations on paths */ + +static int unionfs_unlink(FAR struct inode *mountpt, + FAR const char *relpath); +static int unionfs_mkdir(FAR struct inode *mountpt, + FAR const char *relpath, mode_t mode); +static int unionfs_rmdir(FAR struct inode *mountpt, + FAR const char *relpath); +static int unionfs_rename(FAR struct inode *mountpt, + FAR const char *oldrelpath, FAR const char *newrelpath); +static int unionfs_stat(FAR struct inode *mountpt, + FAR const char *relpath, FAR struct stat *buf); + +/* Initialization */ + +static int unionfs_getmount(FAR const char *path, + FAR struct inode **inode); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/* See fs_mount.c -- this structure is explicitly extern'ed there. + * We use the old-fashioned kind of initializers so that this will compile + * with any compiler. + */ + +static const struct mountpt_operations g_unionfs_mops = +{ + unionfs_open, /* open */ + unionfs_close, /* close */ + unionfs_read, /* read */ + unionfs_write, /* write */ + unionfs_seek, /* seek */ + unionfs_ioctl, /* ioctl */ + + unionfs_sync, /* sync */ + unionfs_dup, /* dup */ + + unionfs_opendir, /* opendir */ + unionfs_closedir, /* closedir */ + unionfs_readdir, /* readdir */ + unionfs_rewinddir, /* rewinddir */ + + NULL, /* bind */ + unionfs_unbind, /* unbind */ + unionfs_statfs, /* statfs */ + + unionfs_unlink, /* unlink */ + unionfs_mkdir, /* mkdir */ + unionfs_rmdir, /* rmdir */ + unionfs_rename, /* rename */ + unionfs_stat /* stat */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: unionfs_semtake + ****************************************************************************/ + +static int unionfs_semtake(FAR struct unionfs_inode_s *ui, bool noint) +{ + int ret; + + do + { + ret = sem_wait(&ui->ui_exclsem); + if (ret < 0) + { + int errcode = errno; + DEBUGASSERT(errcode == EINTR); + if (!noint) + { + return -errno; + } + } + } + while (ret < 0); + + return OK; +} + +/**************************************************************************** + * Name: unionfs_trypath + ****************************************************************************/ + +static FAR const char *unionfs_trypath(FAR const char *relpath, + FAR const char *prefix) +{ + FAR const char *trypath; + int pfxlen; + + /* Is there a prefix on the the path to this file system? */ + + if (prefix && (pfxlen = strlen(prefix)) > 0) + { + /* Does the prefix match? */ + + if (strncmp(prefix, relpath, pfxlen) != 0) + { + /* No, then this relative cannot be within this file system */ + + return NULL; + } + + /* Skip over the prefix */ + + trypath = relpath + pfxlen; + + /* Make sure that what is left is a valid, relative path */ + + for (; *trypath == '/'; trypath++); + } + else + { + /* No.. use the full, relative path */ + + trypath = relpath; + } + + return trypath; +} + +/**************************************************************************** + * Name: unionfs_tryopen + ****************************************************************************/ + +static int unionfs_tryopen(FAR struct file *filep, FAR const char *relpath, + FAR const char *prefix, int oflags, mode_t mode) +{ + FAR const struct mountpt_operations *ops; + FAR const char *trypath; + + /* Is this path valid on this file system? */ + + trypath = unionfs_trypath(relpath, prefix); + if (trypath == NULL) + { + /* No.. return -ENOENT */ + + return -ENOENT; + } + + /* Yes.. try to open this directory */ + + DEBUGASSERT(filep->f_inode != NULL && filep->f_inode->u.i_mops != NULL); + ops = filep->f_inode->u.i_mops; + + if (!ops->open) + { + return -ENOSYS; + } + + return ops->open(filep, trypath, oflags, mode); +} + +/**************************************************************************** + * Name: unionfs_tryopendir + ****************************************************************************/ + +static int unionfs_tryopendir(FAR struct inode *inode, + FAR const char *relpath, FAR const char *prefix, + FAR struct fs_dirent_s *dir) +{ + FAR const struct mountpt_operations *ops; + FAR const char *trypath; + + /* Is this path valid on this file system? */ + + trypath = unionfs_trypath(relpath, prefix); + if (trypath == NULL) + { + /* No.. return -ENOENT */ + + return -ENOENT; + } + + /* Yes.. Try to open this directory */ + + ops = inode->u.i_mops; + DEBUGASSERT(ops && ops->opendir); + + if (!ops->opendir) + { + return -ENOSYS; + } + + return ops->opendir(inode, trypath, dir); +} + +/**************************************************************************** + * Name: unionfs_trymkdir + ****************************************************************************/ + +static int unionfs_trymkdir(FAR struct inode *inode, FAR const char *relpath, + FAR const char *prefix, mode_t mode) +{ + FAR const struct mountpt_operations *ops; + FAR const char *trypath; + + /* Is this path valid on this file system? */ + + trypath = unionfs_trypath(relpath, prefix); + if (trypath == NULL) + { + /* No.. return -ENOENT */ + + return -ENOENT; + } + + /* Yes.. Try to create the directory */ + + ops = inode->u.i_mops; + if (!ops->mkdir) + { + return -ENOSYS; + } + + return ops->mkdir(inode, trypath, mode); +} + +/**************************************************************************** + * Name: unionfs_trystat + ****************************************************************************/ + +static int unionfs_tryrename(FAR struct inode *mountpt, + FAR const char *oldrelpath, + FAR const char *newrelpath, + FAR const char *prefix) +{ + FAR const struct mountpt_operations *ops; + FAR const char *tryoldpath; + FAR const char *trynewpath; + + /* Is source path valid on this file system? */ + + tryoldpath = unionfs_trypath(oldrelpath, prefix); + if (tryoldpath == NULL) + { + /* No.. return -ENOENT. This should not fail because the existence + * of the file has already been verified. + */ + + return -ENOENT; + } + + /* Is source path valid on this file system? + * REVISIT: There is no logic currently to rename the file by copying i + * to a different file system. So we just fail if the destination name + * is not within the same file system. I might, however, be on the other + * file system and that rename should be supported as a file copy and + * delete. + */ + + trynewpath = unionfs_trypath(newrelpath, prefix); + if (trynewpath == NULL) + { + /* No.. return -ENOSYS. We should be able to do this, but we can't + * yet. + */ + + return -ENOSYS; + } + + /* Yes.. Try to rename the file */ + + ops = mountpt->u.i_mops; + if (!ops->rename) + { + return -ENOSYS; + } + + return ops->rename(mountpt, tryoldpath, trynewpath); +} + +/**************************************************************************** + * Name: unionfs_trystat + ****************************************************************************/ + +static int unionfs_trystat(FAR struct inode *inode, FAR const char *relpath, + FAR const char *prefix, FAR struct stat *buf) +{ + FAR const struct mountpt_operations *ops; + FAR const char *trypath; + + /* Is this path valid on this file system? */ + + trypath = unionfs_trypath(relpath, prefix); + if (trypath == NULL) + { + /* No.. return -ENOENT */ + + return -ENOENT; + } + + /* Yes.. Try to create the directory */ + + ops = inode->u.i_mops; + if (!ops->stat) + { + return -ENOSYS; + } + + return ops->stat(inode, trypath, buf); +} + +/**************************************************************************** + * Name: unionfs_trystatdir + ****************************************************************************/ + +static int unionfs_trystatdir(FAR struct inode *inode, + FAR const char *relpath, + FAR const char *prefix) +{ + FAR struct stat buf; + int ret; + + /* Check if relative path refers to a directory. */ + + ret = unionfs_trystat(inode, relpath, prefix, &buf); + if (ret >= 0 && !S_ISDIR(buf.st_mode)) + { + return -ENOTDIR; + } + + return ret; +} + +/**************************************************************************** + * Name: unionfs_trystatdir + ****************************************************************************/ + +static int unionfs_trystatfile(FAR struct inode *inode, + FAR const char *relpath, + FAR const char *prefix) +{ + FAR struct stat buf; + int ret; + + /* Check if relative path refers to a regular file. We specifically + * exclude directories but neither do we expect any kind of special file + * to reside on the mounted filesystem. + */ + + ret = unionfs_trystat(inode, relpath, prefix, &buf); + if (ret >= 0 && !S_ISREG(buf.st_mode)) + { + return -EISDIR; + } + + return ret; +} + +/**************************************************************************** + * Name: unionfs_tryrmdir + ****************************************************************************/ + +static int unionfs_tryrmdir(FAR struct inode *inode, FAR const char *relpath, + FAR const char *prefix) +{ + FAR const struct mountpt_operations *ops; + FAR const char *trypath; + + /* Is this path valid on this file system? */ + + trypath = unionfs_trypath(relpath, prefix); + if (trypath == NULL) + { + /* No.. return -ENOENT */ + + return -ENOENT; + } + + /* Yes.. Try to create the directory */ + + ops = inode->u.i_mops; + if (!ops->rmdir) + { + return -ENOSYS; + } + + return ops->rmdir(inode, trypath); +} + +/**************************************************************************** + * Name: unionfs_unhooked + ****************************************************************************/ + +static void unionfs_unhooked(FAR struct unionfs_inode_s *ui) +{ + fvdbg("Entry\n"); + DEBUGASSERT(ui); + + /* Mark the file system as unhooked (unlinked or unmounted) */ + + ui->ui_unhooked = true; + + /* If there are no open references, then we can destroy the file system + * now. + */ + + if (ui->ui_nopen <= 0) + { + unionfs_destroy(ui); + } +} + +/**************************************************************************** + * Name: unionfs_destroy + ****************************************************************************/ + +static void unionfs_destroy(FAR struct unionfs_inode_s *ui) +{ + DEBUGASSERT(ui != NULL && ui->ui_fs[0].um_node != NULL && + ui->ui_fs[1].um_node != NULL && ui->ui_nopen == 0); + + /* Release our references on the contained inodes */ + + inode_release(ui->ui_fs[0].um_node); + inode_release(ui->ui_fs[1].um_node); + + /* Free any allocated prefix strings */ + + if (ui->ui_fs[1].um_prefix) + { + kmm_free(ui->ui_fs[0].um_prefix); + } + + if (ui->ui_fs[1].um_prefix) + { + kmm_free(ui->ui_fs[1].um_prefix); + } + + /* And finally free the allocated unionfs state structure as well */ + + sem_destroy(&ui->ui_exclsem); + kmm_free(ui); +} + +/**************************************************************************** + * Name: unionfs_open + ****************************************************************************/ + +static int unionfs_open(FAR struct file *filep, FAR const char *relpath, + int oflags, mode_t mode) +{ + FAR struct unionfs_inode_s *ui; + FAR struct unionfs_file_s *uf; + FAR struct unionfs_mountpt_s *um; + int ret; + + /* Recover the open file data from the struct file instance */ + + DEBUGASSERT(filep != NULL && filep->f_inode != NULL); + ui = (FAR struct unionfs_inode_s *)filep->f_inode->i_private; + + fvdbg("Opening: ui_nopen=%d\n", ui->ui_nopen); + DEBUGASSERT(um != NULL && um->um_node != NULL && um->um_node->u.i_mops != NULL); + + /* Get exclusive access to the file system data structures */ + + ret = unionfs_semtake(ui, false); + if (ret < 0) + { + return ret; + } + + /* Alloate a container to hold the open file system information */ + + uf = (FAR struct unionfs_file_s *)kmm_malloc(sizeof(struct unionfs_file_s)); + if (uf == NULL) + { + ret = -ENOMEM; + goto errout_with_semaphore; + } + + /* Try to open the file on file system 1 */ + + um = &ui->ui_fs[0]; + + uf->uf_file.f_oflags = filep->f_oflags; + uf->uf_file.f_pos = 0; + uf->uf_file.f_inode = um->um_node; + uf->uf_file.f_priv = NULL; + + ret = unionfs_tryopen(&uf->uf_file, relpath, um->um_prefix, oflags, mode); + if (ret >= 0) + { + /* Successfully opened on file system 1 */ + + uf->uf_ndx = 0; + } + else + { + /* Try to open the file on file system 1 */ + + um = &ui->ui_fs[1]; + + uf->uf_file.f_oflags = filep->f_oflags; + uf->uf_file.f_pos = 0; + uf->uf_file.f_inode = um->um_node; + uf->uf_file.f_priv = NULL; + + ret = unionfs_tryopen(&uf->uf_file, relpath, um->um_prefix, oflags, mode); + if (ret < 0) + { + goto errout_with_semaphore; + } + + /* Successfully opened on file system 1 */ + + uf->uf_ndx = 1; + } + + filep->f_priv = (FAR void *)uf; + ret = OK; + +errout_with_semaphore: + unionfs_semgive(ui); + return ret; +} + +/**************************************************************************** + * Name: unionfs_close + ****************************************************************************/ + +static int unionfs_close(FAR struct file *filep) +{ + FAR struct unionfs_inode_s *ui; + FAR struct unionfs_file_s *uf; + FAR struct unionfs_mountpt_s *um; + FAR const struct mountpt_operations *ops; + int ret = OK; + + /* Recover the open file data from the struct file instance */ + + DEBUGASSERT(filep != NULL && filep->f_inode != NULL); + ui = (FAR struct unionfs_inode_s *)filep->f_inode->i_private; + + /* Get exclusive access to the file system data structures */ + + (void)unionfs_semtake(ui, false); + + DEBUGASSERT(ui != NULL && filep->f_priv != NULL); + uf = (FAR struct unionfs_file_s *)filep->f_priv; + + DEBUGASSERT(uf->uf_ndx == 0 || uf->uf_ndx == 1); + um = &ui->ui_fs[uf->uf_ndx]; + + DEBUGASSERT(um != NULL && um->um_node != NULL && um->um_node->u.i_mops != NULL); + ops = um->um_node->u.i_mops; + + fvdbg("Closing: ui_nopen=%d\n", ui->ui_nopen); + + /* Perform the lower level close operation */ + + if (ops->close) + { + ret = ops->close(&uf->uf_file); + } + + /* Decrement the count of open reference. If that count would go to zero + * and if the file system has been unmounted or if the mountpoint has been + * unlinked, then destroy the file system now. + */ + + if (--ui->ui_nopen <= 0) + { + unionfs_destroy(ui); + } + + /* Free the open file container */ + + kmm_free(uf); + filep->f_priv = NULL; + unionfs_semgive(ui); + return ret; +} + +/**************************************************************************** + * Name: unionfs_read + ****************************************************************************/ + +static ssize_t unionfs_read(FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + FAR struct unionfs_inode_s *ui; + FAR struct unionfs_file_s *uf; + FAR struct unionfs_mountpt_s *um; + FAR const struct mountpt_operations *ops; + int ret = -EPERM; + + fvdbg("buflen: %lu\n", (unsigned long)buflen); + + /* Recover the open file data from the struct file instance */ + + DEBUGASSERT(filep != NULL && filep->f_inode != NULL); + ui = (FAR struct unionfs_inode_s *)filep->f_inode->i_private; + + /* Get exclusive access to the file system data structures */ + + ret = unionfs_semtake(ui, false); + if (ret < 0) + { + return ret; + } + + DEBUGASSERT(ui != NULL && filep->f_priv != NULL); + uf = (FAR struct unionfs_file_s *)filep->f_priv; + + DEBUGASSERT(uf->uf_ndx == 0 || uf->uf_ndx == 1); + um = &ui->ui_fs[uf->uf_ndx]; + + DEBUGASSERT(um != NULL && um->um_node != NULL && um->um_node->u.i_mops != NULL); + ops = um->um_node->u.i_mops; + + /* Perform the lower level write operation */ + + if (ops->read) + { + ret = ops->read(&uf->uf_file, buffer, buflen); + } + + unionfs_semgive(ui); + return ret; +} + +/**************************************************************************** + * Name: unionfs_write + ****************************************************************************/ + +static ssize_t unionfs_write(FAR struct file *filep, FAR const char *buffer, + size_t buflen) +{ + FAR struct unionfs_inode_s *ui; + FAR struct unionfs_file_s *uf; + FAR struct unionfs_mountpt_s *um; + FAR const struct mountpt_operations *ops; + int ret = -EPERM; + + fvdbg("buflen: %lu\n", (unsigned long)buflen); + + /* Recover the open file data from the struct file instance */ + + DEBUGASSERT(filep != NULL && filep->f_inode != NULL); + ui = (FAR struct unionfs_inode_s *)filep->f_inode->i_private; + + /* Get exclusive access to the file system data structures */ + + ret = unionfs_semtake(ui, false); + if (ret < 0) + { + return ret; + } + + DEBUGASSERT(ui != NULL && filep->f_priv != NULL); + uf = (FAR struct unionfs_file_s *)filep->f_priv; + + DEBUGASSERT(uf->uf_ndx == 0 || uf->uf_ndx == 1); + um = &ui->ui_fs[uf->uf_ndx]; + + DEBUGASSERT(um != NULL && um->um_node != NULL && um->um_node->u.i_mops != NULL); + ops = um->um_node->u.i_mops; + + /* Perform the lower level write operation */ + + if (ops->write) + { + ret = ops->write(&uf->uf_file, buffer, buflen); + } + + unionfs_semgive(ui); + return ret; +} + +/**************************************************************************** + * Name: unionfs_seek + ****************************************************************************/ + +static off_t unionfs_seek(FAR struct file *filep, off_t offset, int whence) +{ + FAR struct unionfs_inode_s *ui; + FAR struct unionfs_file_s *uf; + FAR struct unionfs_mountpt_s *um; + FAR const struct mountpt_operations *ops; + int ret; + + fvdbg("offset: %lu whence: %d\n", (unsigned long)off_t, whence); + + /* Recover the open file data from the struct file instance */ + + DEBUGASSERT(filep != NULL && filep->f_inode != NULL); + ui = (FAR struct unionfs_inode_s *)filep->f_inode->i_private; + + /* Get exclusive access to the file system data structures */ + + ret = unionfs_semtake(ui, false); + if (ret < 0) + { + return ret; + } + + DEBUGASSERT(ui != NULL && filep->f_priv != NULL); + uf = (FAR struct unionfs_file_s *)filep->f_priv; + + DEBUGASSERT(uf->uf_ndx == 0 || uf->uf_ndx == 1); + um = &ui->ui_fs[uf->uf_ndx]; + + DEBUGASSERT(um != NULL && um->um_node != NULL && um->um_node->u.i_mops != NULL); + ops = um->um_node->u.i_mops; + + /* Invoke the file seek method if available */ + + if (ops->seek) + { + offset = ops->seek(&uf->uf_file, offset, whence); + } + else + { + /* No... Just set the common file position value */ + + switch (whence) + { + case SEEK_CUR: + offset += filep->f_pos; + + case SEEK_SET: + if (offset >= 0) + { + filep->f_pos = offset; /* Might be beyond the end-of-file */ + } + else + { + offset = (off_t)-EINVAL; + } + break; + + case SEEK_END: + offset = (off_t)-ENOSYS; + break; + + default: + offset = (off_t)-EINVAL; + break; + } + } + + unionfs_semgive(ui); + return offset; +} + +/**************************************************************************** + * Name: unionfs_ioctl + ****************************************************************************/ + +static int unionfs_ioctl(FAR struct file *filep, int cmd, unsigned long arg) +{ + FAR struct unionfs_inode_s *ui; + FAR struct unionfs_file_s *uf; + FAR struct unionfs_mountpt_s *um; + FAR const struct mountpt_operations *ops; + int ret = -ENOTTY; + + fvdbg("cmd: %d arg: %lu\n", cmd, arg); + + /* Recover the open file data from the struct file instance */ + + DEBUGASSERT(filep != NULL && filep->f_inode != NULL); + ui = (FAR struct unionfs_inode_s *)filep->f_inode->i_private; + + /* Get exclusive access to the file system data structures */ + + ret = unionfs_semtake(ui, false); + if (ret < 0) + { + return ret; + } + + DEBUGASSERT(ui != NULL && filep->f_priv != NULL); + uf = (FAR struct unionfs_file_s *)filep->f_priv; + + DEBUGASSERT(uf->uf_ndx == 0 || uf->uf_ndx == 1); + um = &ui->ui_fs[uf->uf_ndx]; + + DEBUGASSERT(um != NULL && um->um_node != NULL && um->um_node->u.i_mops != NULL); + ops = um->um_node->u.i_mops; + + /* Perform the lower level ioctl operation */ + + if (ops->ioctl) + { + ret = ops->ioctl(&uf->uf_file, cmd, arg); + } + + unionfs_semgive(ui); + return ret; +} + +/**************************************************************************** + * Name: unionfs_sync + ****************************************************************************/ + +static int unionfs_sync(FAR struct file *filep) +{ + FAR struct unionfs_inode_s *ui; + FAR struct unionfs_file_s *uf; + FAR struct unionfs_mountpt_s *um; + FAR const struct mountpt_operations *ops; + int ret = -EINVAL; + + fvdbg("Entry\n"); + + /* Recover the open file data from the struct file instance */ + + DEBUGASSERT(filep != NULL && filep->f_inode != NULL); + ui = (FAR struct unionfs_inode_s *)filep->f_inode->i_private; + + /* Get exclusive access to the file system data structures */ + + ret = unionfs_semtake(ui, false); + if (ret < 0) + { + return ret; + } + + DEBUGASSERT(ui != NULL && filep->f_priv != NULL); + uf = (FAR struct unionfs_file_s *)filep->f_priv; + + DEBUGASSERT(uf->uf_ndx == 0 || uf->uf_ndx == 1); + um = &ui->ui_fs[uf->uf_ndx]; + + DEBUGASSERT(um != NULL && um->um_node != NULL && um->um_node->u.i_mops != NULL); + ops = um->um_node->u.i_mops; + + /* Perform the lower level sync operation */ + + if (ops->sync) + { + ret = ops->sync(&uf->uf_file); + } + + unionfs_semgive(ui); + return ret; +} + +/**************************************************************************** + * Name: unionfs_dup + ****************************************************************************/ + +static int unionfs_dup(FAR const struct file *oldp, FAR struct file *newp) +{ + FAR struct unionfs_file_s *oldpriv; + FAR struct unionfs_file_s *newpriv; + FAR struct unionfs_inode_s *ui; + FAR struct unionfs_mountpt_s *um; + FAR const struct mountpt_operations *ops; + int ret = -ENOMEM; + + fvdbg("Entry\n"); + + /* Recover the open file data from the struct file instance */ + + DEBUGASSERT(oldp != NULL && oldp->f_inode != NULL); + ui = (FAR struct unionfs_inode_s *)oldp->f_inode->i_private; + + /* Get exclusive access to the file system data structures */ + + ret = unionfs_semtake(ui, false); + if (ret < 0) + { + return ret; + } + + DEBUGASSERT(ui != NULL && oldp->f_priv != NULL); + oldpriv = (FAR struct unionfs_file_s *)oldp->f_priv; + + DEBUGASSERT(oldpriv->uf_ndx == 0 || oldpriv->uf_ndx == 1); + um = &ui->ui_fs[oldpriv->uf_ndx]; + + DEBUGASSERT(um != NULL && um->um_node != NULL && um->um_node->u.i_mops != NULL); + ops = um->um_node->u.i_mops; + + DEBUGASSERT(newp != NULL && newp->f_priv == NULL); + + /* Allocate a new container for the union FS open file */ + + newpriv = (FAR struct unionfs_file_s *)kmm_malloc(sizeof(struct unionfs_file_s)); + if (newpriv != NULL) + { + /* Clone the old file structure into the newly allocated one */ + + memcpy(newpriv, oldpriv, sizeof(struct unionfs_file_s)); + newpriv->uf_file.f_priv = NULL; + + /* Then perform the lower lowel dup operation */ + + ret = OK; + if (ops->dup) + { + ret = ops->dup(&oldpriv->uf_file, &newpriv->uf_file); + if (ret < 0) + { + kmm_free(newpriv); + newpriv = NULL; + } + } + + /* Save the new container in the new file structure */ + + newp->f_priv = newpriv; + } + + return ret; +} + +/**************************************************************************** + * Name: unionfs_opendir + ****************************************************************************/ + +static int unionfs_opendir(FAR struct inode *mountpt, FAR const char *relpath, + FAR struct fs_dirent_s *dir) +{ + FAR struct unionfs_inode_s *ui; + FAR struct unionfs_mountpt_s *um; + FAR struct fs_unionfsdir_s *fu; + FAR const struct mountpt_operations *ops; + FAR struct fs_dirent_s *lowerdir; + int ret; + + fvdbg("relpath: \"%s\"\n", relpath ? relpath : "NULL"); + + /* Recover the filesystem data from the struct inode instance */ + + DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); + ui = (FAR struct unionfs_inode_s *)mountpt->i_private; + + /* Get exclusive access to the file system data structures */ + + ret = unionfs_semtake(ui, false); + if (ret < 0) + { + goto errout_with_lowerdir; + } + + DEBUGASSERT(dir); + fu = &dir->u.unionfs; + + /* Allocate another dirent structure for the lower file system */ + + lowerdir = (FAR struct fs_dirent_s *)kmm_zalloc(sizeof(struct fs_dirent_s)); + if (lowerdir == NULL) + { + return -ENOMEM; + } + + /* Check file system 2 first. */ + + fu->fu_ndx = 0; + fu->fu_lower[0] = NULL; + fu->fu_lower[1] = NULL; + + um = &ui->ui_fs[1]; + lowerdir->fd_root = um->um_node; + ret = unionfs_tryopendir(um->um_node, relpath, um->um_prefix, lowerdir); + if (ret >= 0) + { + /* Save the filsystem2 access info */ + + fu->fu_ndx = 1; + fu->fu_lower[1] = lowerdir; + + /* Allocate yet another dirent structure for the lower file system 1 */ + + lowerdir = (FAR struct fs_dirent_s *)kmm_zalloc(sizeof(struct fs_dirent_s)); + if (lowerdir == NULL) + { + ret = -ENOMEM; + goto errout_with_fs2open; + } + } + + /* Check file system 1 last, possibly overwriting fu_ndx */ + + um = &ui->ui_fs[1]; + lowerdir->fd_root = um->um_node; + ret = unionfs_tryopendir(um->um_node, relpath, um->um_prefix, lowerdir); + if (ret >= 0) + { + /* Save the filsystem1 access info */ + + fu->fu_ndx = 0; + fu->fu_lower[0] = lowerdir; + } + else if (fu->fu_lower[1] == NULL) + { + /* Neither file system was opened! */ + + goto errout_with_lowerdir; + } + + /* Increment the number of open references and return success */ + + ui->ui_nopen++; + DEBUGASSERT(ui->ui_nopen > 0); + + unionfs_semgive(ui); + return OK; + +errout_with_fs2open: + ops = ui->ui_fs[1].um_node->u.i_mops; + DEBUGASSERT(ops != NULL); + if (ops->closedir) + { + ret = ops->closedir(um->um_node, fu->fu_lower[fu->fu_ndx]); + } + +errout_with_lowerdir: + kmm_free(lowerdir); + unionfs_semgive(ui); + return ret; +} + +/**************************************************************************** + * Name: unionfs_closedir + ****************************************************************************/ + +static int unionfs_closedir(FAR struct inode *mountpt, + FAR struct fs_dirent_s *dir) +{ + FAR struct unionfs_inode_s *ui; + FAR struct unionfs_mountpt_s *um; + FAR const struct mountpt_operations *ops; + FAR struct fs_unionfsdir_s *fu; + int ret = OK; + int i; + + fvdbg("Entry\n"); + + /* Recover the union file system data from the struct inode instance */ + + DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); + ui = (FAR struct unionfs_inode_s *)mountpt->i_private; + + /* Get exclusive access to the file system data structures */ + + (void)unionfs_semtake(ui, true); + + DEBUGASSERT(dir); + fu = &dir->u.unionfs; + + /* Close both contained file systems */ + + for (i = 0; i < 2; i++) + { + /* Was this file system opened? */ + + if (fu->fu_lower[i] != NULL); + { + um = &ui->ui_fs[i]; + + DEBUGASSERT(um != NULL && um->um_node != NULL && um->um_node->u.i_mops != NULL); + ops = um->um_node->u.i_mops; + + /* Perform the lower level closedir operation */ + + if (ops->closedir) + { + ret = ops->closedir(um->um_node, fu->fu_lower[i]); + } + + /* Free the lower dirent structure */ + + kmm_free(fu->fu_lower[i]); + } + } + + fu->fu_ndx = 0; + fu->fu_lower[0] = NULL; + fu->fu_lower[1] = NULL; + + /* Decrement the count of open reference. If that count would go to zero + * and if the file system has been unmounted or if the mountpoint has been + * unlinked, then destroy the file system now. + */ + + if (--ui->ui_nopen <= 0 && ui->ui_unhooked) + { + unionfs_destroy(ui); + } + else + { + unionfs_semgive(ui); + } + + return ret; +} + +/**************************************************************************** + * Name: unionfs_readdir + ****************************************************************************/ + +static int unionfs_readdir(struct inode *mountpt, struct fs_dirent_s *dir) +{ + FAR struct unionfs_inode_s *ui; + FAR struct unionfs_mountpt_s *um; + FAR const struct mountpt_operations *ops; + FAR struct fs_unionfsdir_s *fu; + int ret = -ENOSYS; + + fvdbg("Entry\n"); + + /* Recover the union file system data from the struct inode instance */ + + DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); + ui = (FAR struct unionfs_inode_s *)mountpt->i_private; + + DEBUGASSERT(dir); + fu = &dir->u.unionfs; + + DEBUGASSERT(fu->fu_ndx == 0 || fu->fu_ndx == 1); + um = &ui->ui_fs[fu->fu_ndx]; + + DEBUGASSERT(um != NULL && um->um_node != NULL && um->um_node->u.i_mops != NULL); + ops = um->um_node->u.i_mops; + + /* Perform the lower level readdir operation */ + + if (ops->readdir) + { + ret = ops->readdir(um->um_node, fu->fu_lower[fu->fu_ndx]); + + /* Did the read operation fail because we reached the end of the + * directory? In that case, the error would be -ENOENT. If we hit + * the end-of-directory on file system, we need to seamlessly move + * to the second file system (if there is one). + */ + + if (ret == -ENOENT && fu->fu_ndx == 0 && fu->fu_lower[1] != NULL) + { + /* Switch to the second file system */ + + fu->fu_ndx = 1; + um = &ui->ui_fs[1]; + + DEBUGASSERT(um != NULL && um->um_node != NULL && um->um_node->u.i_mops != NULL); + ops = um->um_node->u.i_mops; + + /* Make sure that the second file system directory enumeration + * is rewound to the beginning of the directory. + */ + + if (ops->rewinddir != NULL) + { + ret = ops->rewinddir(um->um_node, fu->fu_lower[1]); + } + + /* Then try the read operation again */ + + ret = ops->readdir(um->um_node, fu->fu_lower[1]); + } + + /* Copy the return information into the diret structure that the + * application will see. + */ + + dir->fd_position = fu->fu_lower[fu->fu_ndx]->fd_position; + memcpy(&dir->fd_dir, &fu->fu_lower[fu->fu_ndx]->fd_dir, sizeof(struct dirent)); + } + + return ret; +} + +/**************************************************************************** + * Name: unionfs_rewindir + ****************************************************************************/ + +static int unionfs_rewinddir(struct inode *mountpt, struct fs_dirent_s *dir) +{ + FAR struct unionfs_inode_s *ui; + FAR struct unionfs_mountpt_s *um; + FAR const struct mountpt_operations *ops; + FAR struct fs_unionfsdir_s *fu; + int ret; + + fvdbg("Entry\n"); + + /* Recover the union file system data from the struct inode instance */ + + DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); + ui = (FAR struct unionfs_inode_s *)mountpt->i_private; + + /* Get exclusive access to the file system data structures */ + + ret = unionfs_semtake(ui, false); + if (ret < 0) + { + return ret; + } + + DEBUGASSERT(dir); + fu = &dir->u.unionfs; + + /* Were we currently enumerating on file system 1? If not, is an + * enumeration possible on file system 1? + */ + + DEBUGASSERT(fu->fu_ndx == 0 || fu->fu_ndx == 1); + if (/* fu->fu_ndx != 0 && */ fu->fu_lower[0] != 0) + { + /* Yes.. switch to file system 1 */ + + fu->fu_ndx = 0; + } + + um = &ui->ui_fs[fu->fu_ndx]; + + DEBUGASSERT(um != NULL && um->um_node != NULL && um->um_node->u.i_mops != NULL); + ops = um->um_node->u.i_mops; + + /* Perform the file system rewind operation */ + + if (ops->rewinddir != NULL) + { + ret = ops->rewinddir(um->um_node, fu->fu_lower[fu->fu_ndx]); + dir->fd_position = fu->fu_lower[fu->fu_ndx]->fd_position; + } + else + { + ret = -EINVAL; + } + + unionfs_semgive(ui); + return ret; +} + +/**************************************************************************** + * Name: unionfs_unbind + ****************************************************************************/ + +static int unionfs_unbind(FAR void *handle, FAR struct inode **blkdriver, + unsigned int flags) +{ + unionfs_unhooked((FAR struct unionfs_inode_s *)handle); + return OK; +} + +/**************************************************************************** + * Name: unionfs_statfs + ****************************************************************************/ + +static int unionfs_statfs(FAR struct inode *mountpt, FAR struct statfs *buf) +{ + FAR struct unionfs_inode_s *ui; + FAR struct unionfs_mountpt_s *um; + FAR const struct mountpt_operations *ops; + FAR struct statfs *adj; + struct statfs buf1; + struct statfs buf2; + uint64_t tmp; + uint32_t ratiob16; + int ret; + + fvdbg("Entry\n"); + + /* Recover the union file system data from the struct inode instance */ + + DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); + ui = (FAR struct unionfs_inode_s *)mountpt->i_private; + + /* Get exclusive access to the file system data structures */ + + ret = unionfs_semtake(ui, false); + if (ret < 0) + { + return ret; + } + + /* Get statfs info from file system 1 */ + + um = &ui->ui_fs[0]; + DEBUGASSERT(um != NULL && um->um_node != NULL && um->um_node->u.i_mops != NULL); + ops = um->um_node->u.i_mops; + + if (ops->statfs) + { + ret = ops->statfs(um->um_node, &buf1); + if (ret < 0) + { + goto errout_with_semaphore; + } + } + + /* Get statfs info from file system 2 */ + + um = &ui->ui_fs[1]; + DEBUGASSERT(um != NULL && um->um_node != NULL && um->um_node->u.i_mops != NULL); + ops = um->um_node->u.i_mops; + + if (ops->statfs) + { + ret = ops->statfs(um->um_node, &buf2); + if (ret < 0) + { + goto errout_with_semaphore; + } + } + + /* Now try to reconcile the statfs info */ + + memset(buf, 0, sizeof(struct statfs)); + buf->f_type = UNIONFS_MAGIC; + buf->f_namelen = MIN(buf1.f_namelen, buf2.f_namelen); + buf->f_files = buf1.f_files + buf2.f_files; + buf->f_ffree = buf1.f_ffree + buf2.f_ffree; + + /* Things expressed in units of blocks are the only tricky ones. We will + * depend on a uint64_t * temporary to avoid arithmetic overflow. + */ + + if (buf1.f_bsize != buf2.f_bsize) + { + if (buf1.f_bsize < buf2.f_bsize) + { + buf->f_bsize = buf1.f_bsize; + tmp = (((uint64_t)buf2.f_blocks * (uint64_t)buf2.f_bsize) << 16); + ratiob16 = (uint32_t)(tmp / buf1.f_bsize); + adj = &buf2; + } + else + { + buf->f_bsize = buf2.f_bsize; + tmp = (((uint64_t)buf1.f_blocks * (uint64_t)buf1.f_bsize) << 16); + ratiob16 = (uint32_t)(tmp / buf2.f_bsize); + adj = &buf2; + } + + tmp = (uint16_t)adj->f_blocks * ratiob16; + adj->f_blocks = (off_t)(tmp >> 16); + + tmp = (uint16_t)adj->f_bfree * ratiob16; + adj->f_bfree = (off_t)(tmp >> 16); + + tmp = (uint16_t)adj->f_bavail * ratiob16; + adj->f_bavail = (off_t)(tmp >> 16); + } + + /* Then we can just sum the adjusted sizes */ + + buf->f_blocks = buf1.f_blocks + buf2.f_blocks; + buf->f_bfree = buf1.f_bfree + buf2.f_bfree; + buf->f_bavail = buf1.f_bavail + buf2.f_bavail; + + ret = OK; + +errout_with_semaphore: + unionfs_semgive(ui); + return ret; +} + +/**************************************************************************** + * Name: unionfs_unlink + ****************************************************************************/ + +static int unionfs_unlink(FAR struct inode *mountpt, + FAR const char *relpath) +{ + FAR struct unionfs_inode_s *ui; + int ret; + + fdbg("relpath: %s\n", relpath); + + /* Recover the union file system data from the struct inode instance */ + + DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL && relpath != NULL); + ui = (FAR struct unionfs_inode_s *)mountpt->i_private; + + /* Get exclusive access to the file system data structures */ + + ret = unionfs_semtake(ui, false); + if (ret < 0) + { + return ret; + } + + /* Unhook/unlink the union file system */ + + unionfs_unhooked((FAR struct unionfs_inode_s *)mountpt->i_private); + unionfs_semgive(ui); + return OK; +} + +/**************************************************************************** + * Name: unionfs_mkdir + ****************************************************************************/ + +static int unionfs_mkdir(FAR struct inode *mountpt, FAR const char *relpath, + mode_t mode) +{ + FAR struct unionfs_inode_s *ui; + FAR struct unionfs_mountpt_s *um; + struct stat buf; + int ret1; + int ret2; + int ret; + + fdbg("relpath: %s\n", relpath); + + /* Recover the union file system data from the struct inode instance */ + + DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL && relpath != NULL); + ui = (FAR struct unionfs_inode_s *)mountpt->i_private; + + /* Get exclusive access to the file system data structures */ + + ret = unionfs_semtake(ui, false); + if (ret < 0) + { + return ret; + } + + /* Is there anything with this name on either file system? */ + + um = &ui->ui_fs[0]; + ret = unionfs_trystat(um->um_node, relpath, um->um_prefix, &buf); + if (ret >= 0) + { + return -EEXIST; + } + + um = &ui->ui_fs[1]; + ret = unionfs_trystat(um->um_node, relpath, um->um_prefix, &buf); + if (ret >= 0) + { + return -EEXIST; + } + + /* Try to create the directory on both file systems. */ + + um = &ui->ui_fs[0]; + ret1 = unionfs_trymkdir(um->um_node, relpath, um->um_prefix, mode); + + um = &ui->ui_fs[1]; + ret1 = unionfs_trymkdir(um->um_node, relpath, um->um_prefix, mode); + + /* We will say we were successful if we were able to create the + * directory on either file system. Perhaps one file system is + * read-only and the other is write-able? + */ + + if (ret1 >= 0 || ret2 >= 0) + { + ret = OK; + } + else + { + /* Otherwise, pick one */ + + ret = ret1; + } + + unionfs_semgive(ui); + return ret; +} + +/**************************************************************************** + * Name: unionfs_rmdir + ****************************************************************************/ + +static int unionfs_rmdir(FAR struct inode *mountpt, FAR const char *relpath) +{ + FAR struct unionfs_inode_s *ui; + FAR struct unionfs_mountpt_s *um; + int tmp; + int ret = -ENOENT; + + fdbg("relpath: %s\n", relpath); + + /* Recover the union file system data from the struct inode instance */ + + DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL && relpath != NULL); + ui = (FAR struct unionfs_inode_s *)mountpt->i_private; + + /* Get exclusive access to the file system data structures */ + + ret = unionfs_semtake(ui, false); + if (ret < 0) + { + return ret; + } + + /* We really don't know any better so we will try to remove the directory + * from both file systems. + */ + + /* Is there a directory with this name on file system 1 */ + + um = &ui->ui_fs[0]; + tmp = unionfs_trystatdir(um->um_node, relpath, um->um_prefix); + if (tmp >= 0) + { + /* Yes.. remove it. Since we know that the directory exists, any + * failure to remove it is a showstopper. + */ + + ret = unionfs_tryrmdir(um->um_node, relpath, um->um_prefix); + if (ret < 0) + { + unionfs_semgive(ui); + return ret; + } + } + + /* Either the directory does not exist on file system 1, or we + * successfully removed it. Try again on file system 2. + */ + + um = &ui->ui_fs[1]; + tmp = unionfs_trystatdir(um->um_node, relpath, um->um_prefix); + if (tmp >= 0) + { + /* Yes.. remove it. Since we know that the directory exists, any + * failure to remove it is a showstopper. + */ + + ret = unionfs_tryrmdir(um->um_node, relpath, um->um_prefix); + + /* REVISIT: Should we try to restore the directory on file system 1 + * if we failure to removed the directory on file system 2? + */ + } + + unionfs_semgive(ui); + return ret; +} + +/**************************************************************************** + * Name: unionfs_rename + ****************************************************************************/ + +static int unionfs_rename(FAR struct inode *mountpt, + FAR const char *oldrelpath, + FAR const char *newrelpath) +{ + FAR struct unionfs_inode_s *ui; + FAR struct unionfs_mountpt_s *um; + int tmp; + int ret = -ENOENT; + + fdbg("oldrelpath: %s newrelpath\n", oldrelpath, newrelpath); + + /* Recover the union file system data from the struct inode instance */ + + DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); + ui = (FAR struct unionfs_inode_s *)mountpt->i_private; + + /* Get exclusive access to the file system data structures */ + + ret = unionfs_semtake(ui, false); + if (ret < 0) + { + return ret; + } + + DEBUGASSERT(oldrelpath != NULL && oldrelpath != NULL); + + /* Is there a file with this name on file system 1 */ + + um = &ui->ui_fs[0]; + tmp = unionfs_trystatfile(um->um_node, oldrelpath, um->um_prefix); + if (tmp >= 0) + { + /* Yes.. rename it. Since we know that the directory exists, any + * failure to remove it is a showstopper. + */ + + ret = unionfs_tryrename(um->um_node, oldrelpath, newrelpath, + um->um_prefix); + if (ret >= 0) + { + /* Return immediately on success. In the event that the file + * exists in both file systems, this will produce the odd behavior + * that one file on file system 1 was renamed but another obscured + * file of the same relative path will become visible. + */ + + unionfs_semgive(ui); + return OK; + } + } + + /* Either the file does not exist on file system 1, or we failed to rename + * it (perhaps because the file system was read-only). Try again on file + * system 2. + */ + + um = &ui->ui_fs[1]; + tmp = unionfs_trystatfile(um->um_node, oldrelpath, um->um_prefix); + if (tmp >= 0) + { + /* Yes.. remove it. Since we know that the directory exists, any + * failure to remove it is a showstopper. + */ + + ret = unionfs_tryrename(um->um_node, oldrelpath, newrelpath, + um->um_prefix); + } + + unionfs_semgive(ui); + return ret; +} + +/**************************************************************************** + * Name: unionfs_stat + ****************************************************************************/ + +static int unionfs_stat(FAR struct inode *mountpt, FAR const char *relpath, + FAR struct stat *buf) +{ + FAR struct unionfs_inode_s *ui; + FAR struct unionfs_mountpt_s *um; + int ret; + + fdbg("relpath: %s\n", relpath); + + /* Recover the union file system data from the struct inode instance */ + + DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL && relpath != NULL); + ui = (FAR struct unionfs_inode_s *)mountpt->i_private; + + /* Get exclusive access to the file system data structures */ + + ret = unionfs_semtake(ui, false); + if (ret < 0) + { + return ret; + } + + /* stat this path on file system 1 */ + + um = &ui->ui_fs[0]; + ret = unionfs_trystat(um->um_node, relpath, um->um_prefix, buf); + if (ret >= 0) + { + /* Return on the first success. The first instance of the file will + * shadow the second anyway. + */ + + return OK; + } + + /* stat failed on the file system 1. Try again on file system 2. */ + + um = &ui->ui_fs[0]; + ret = unionfs_trystat(um->um_node, relpath, um->um_prefix, buf); + + unionfs_semgive(ui); + return ret; +} + +/**************************************************************************** + * Name: unionfs_getmount + ****************************************************************************/ + +static int unionfs_getmount(FAR const char *path, FAR struct inode **inode) +{ + FAR struct inode *minode; + + /* Find the mountpt */ + + minode = inode_find(path, NULL); + if (!minode) + { + /* Mountpoint inode not found */ + + return -ENOENT; + } + + /* Verify that the inode is a mountpoint */ + + if (!INODE_IS_MOUNTPT(minode)) + { + /* Inode was found, but is it is a mounpoint */ + + inode_release(minode); + return -EINVAL; + } + + /* Success! */ + + *inode = minode; + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: unionfs_mount + * + * Description: + * Create and mount a union file system + * + * Input Parameters: + * fspath1 - The full path to the first file system mountpoint + * prefix1 - An optiona prefix that may be applied to make the first + * file system appear a some path below the unionfs mountpoint, + * fspath2 - The full path to the second file system mountpoint + * prefix2 - An optiona prefix that may be applied to make the first + * file system appear a some path below the unionfs mountpoint, + * mountpt - The full path to the mountpoint for the union file system + * + * Returned value: + * Zero (OK) is returned if the union file system was correctly created and + * mounted. On any failure, a negated error value will be returned to + * indicate the nature of the failure. + * + ****************************************************************************/ + +int unionfs_mount(FAR const char *fspath1, FAR const char *prefix1, + FAR const char *fspath2, FAR const char *prefix2, + FAR const char *mountpt) +{ + FAR struct unionfs_inode_s *ui; + FAR struct inode *mpinode; + int ret; + + DEBUGASSERT(fspath1 != NULL && fspath2 != NULL && mountpt != NULL); + + /* Allocate a structure a structure that will describe the union file + * system. + */ + + ui = (FAR struct unionfs_inode_s *)kmm_zalloc(sizeof(struct unionfs_inode_s)); + if (!ui) + { + fdbg("ERROR: Failed to allocated union FS state structure\n"); + return -ENOMEM; + } + + sem_init(&ui->ui_exclsem, 0, 1); + + /* Get the inodes associated with fspath1 and fspath2 */ + + ret = unionfs_getmount(fspath1, &ui->ui_fs[0].um_node); + if (ret < 0) + { + fdbg("ERROR: unionfs_getmount(fspath1) failed: %d\n", ret); + goto errout_with_uinode; + } + + ret = unionfs_getmount(fspath2, &ui->ui_fs[1].um_node); + if (ret < 0) + { + fdbg("ERROR: unionfs_getmount(fspath2) failed: %d\n", ret); + goto errout_with_fs1; + } + + /* Duplicate the prefix strings */ + + if (prefix1 && strlen(prefix1) > 0) + { + ui->ui_fs[0].um_prefix = strdup(prefix1); + if (ui->ui_fs[0].um_prefix == NULL) + { + fdbg("ERROR: strdup(prefix1) failed\n"); + ret = -ENOMEM; + goto errout_with_fs2; + } + } + + if (prefix2 && strlen(prefix2) > 0) + { + ui->ui_fs[1].um_prefix = strdup(prefix2); + if (ui->ui_fs[1].um_prefix == NULL) + { + fdbg("ERROR: strdup(prefix2) failed\n"); + ret = -ENOMEM; + goto errout_with_prefix1; + } + } + + /* Finally, mount the union FS. We should adapt the standard mount to do + * this using optional parameters. This custom mount should do the job + * for now, however. + */ + + /* Insert a dummy node -- we need to hold the inode semaphore + * to do this because we will have a momentarily bad structure. + */ + + inode_semtake(); + ret = inode_reserve(mountpt, &mpinode); + if (ret < 0) + { + /* inode_reserve can fail for a couple of reasons, but the most likely + * one is that the inode already exists. inode_reserve may return: + * + * -EINVAL - 'path' is invalid for this operation + * -EEXIST - An inode already exists at 'path' + * -ENOMEM - Failed to allocate in-memory resources for the operation + */ + + fdbg("ERROR: Failed to reserve inode\n"); + goto errout_with_semaphore; + } + + /* Populate the inode with driver specific information. */ + + INODE_SET_MOUNTPT(mpinode); + + mpinode->u.i_mops = &g_unionfs_mops; +#ifdef CONFIG_FILE_MODE + mpinode->i_mode = 0755; +#endif + mpinode->i_private = ui; + + /* Unlink the contained mountpoint inodes from the pseudo file system. + * The inodes will be marked as deleted so that they will be removed when + * the reference count decrements to zero in inode_release(). Because we + * hold a reference count on the inodes, they will not be deleted until + * this logic calls inode_release() in the unionfs_destroy() function. + */ + + (void)inode_remove(fspath1); + (void)inode_remove(fspath2); + inode_semgive(); + return OK; + +errout_with_semaphore: + inode_semgive(); + +//errout_with_prefix2: + if (ui->ui_fs[1].um_prefix != NULL) + { + kmm_free(ui->ui_fs[1].um_prefix); + } + +errout_with_prefix1: + if (ui->ui_fs[0].um_prefix != NULL) + { + kmm_free(ui->ui_fs[0].um_prefix); + } + +errout_with_fs2: + inode_release(ui->ui_fs[1].um_node); + +errout_with_fs1: + inode_release(ui->ui_fs[0].um_node); + +errout_with_uinode: + sem_destroy(&ui->ui_exclsem); + kmm_free(ui); + return ret; +} +#endif /* !CONFIG_DISABLE_MOUNTPOINT && CONFIG_FS_UNIONFS */ diff --git a/include/nuttx/fs/dirent.h b/include/nuttx/fs/dirent.h index 8c21fbe7159..fcc8e25456c 100644 --- a/include/nuttx/fs/dirent.h +++ b/include/nuttx/fs/dirent.h @@ -1,7 +1,7 @@ /**************************************************************************** * include/nuttx/fs/dirent.h * - * Copyright (C) 2007, 2009, 2011-2013 Gregory Nutt. All rights reserved. + * Copyright (C) 2007, 2009, 2011-2013, 2015 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without @@ -144,7 +144,20 @@ struct fs_smartfsdir_s { uint16_t fs_firstsector; /* First sector of directory list */ uint16_t fs_currsector; /* Current sector of directory list */ - uint16_t fs_curroffset; /* Current offset withing current sector */ + uint16_t fs_curroffset; /* Current offset within current sector */ +}; +#endif + +#ifdef CONFIG_FS_UNIONFS +/* The Union File System can be used to merge to different mountpoints so + * that they appear as a single merged directory. + */ + +struct fs_dirent_s; /* Forward reference */ +struct fs_unionfsdir_s +{ + uint8_t fu_ndx; /* Index of file system being enumerated */ + FAR struct fs_dirent_s *fu_lower[2]; /* dirent struct used by contained file system */ }; #endif @@ -208,6 +221,9 @@ struct fs_dirent_s #ifdef CONFIG_FS_SMARTFS struct fs_smartfsdir_s smartfs; #endif +#ifdef CONFIG_FS_UNIONFS + struct fs_unionfsdir_s unionfs; +#endif #endif /* !CONFIG_DISABLE_MOUNTPOINT */ } u; diff --git a/include/nuttx/fs/unionfs.h b/include/nuttx/fs/unionfs.h new file mode 100644 index 00000000000..35815de8c3e --- /dev/null +++ b/include/nuttx/fs/unionfs.h @@ -0,0 +1,103 @@ +/**************************************************************************** + * include/nuttx/fs/unionfs.h + * + * Copyright (C) 2015 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * 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 OWNER 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. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_FS_UNIONFS_H +#define __INCLUDE_NUTTX_FS_UNIONFS_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef CONFIG_FS_UNIONFS + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Type Definitions + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: unionfs_mount + * + * Description: + * Create and mount a union file system + * + * Input Parameters: + * fspath1 - The full path to the first file system mountpoint + * prefix1 - An optiona prefix that may be applied to make the first + * file system appear a some path below the unionfs mountpoint, + * fspath2 - The full path to the second file system mountpoint + * prefix2 - An optiona prefix that may be applied to make the first + * file system appear a some path below the unionfs mountpoint, + * mountpt - The full path to the mountpoint for the union file system + * + * Returned value: + * Zero (OK) is returned if the union file system was correctly created and + * mounted. On any failure, a negated error value will be returned to + * indicate the nature of the failure. + * + ****************************************************************************/ + +int unionfs_mount(FAR const char *fspath1, FAR const char *prefix1, + FAR const char *fspath2, FAR const char *prefix2, + FAR const char *mountpt); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* CONFIG_FS_UNIONFS */ +#endif /* __INCLUDE_NUTTX_FS_UNIONFS_H */ diff --git a/include/sys/statfs.h b/include/sys/statfs.h index de13de0d920..4daf188e9a0 100644 --- a/include/sys/statfs.h +++ b/include/sys/statfs.h @@ -102,6 +102,7 @@ #define PROCFS_MAGIC 0x434f5250 #define NXFFS_MAGIC 0x4747 #define SMARTFS_MAGIC 0x54524D53 +#define UNIONFS_MAGIC 0x53464e55 /**************************************************************************** * Type Definitions