6a3c2aded6
* Simplify EINTR/ECANCEL error handling 1. Add semaphore uninterruptible wait function 2 .Replace semaphore wait loop with a single uninterruptible wait 3. Replace all sem_xxx to nxsem_xxx * Unify the void cast usage 1. Remove void cast for function because many place ignore the returned value witout cast 2. Replace void cast for variable with UNUSED macro
2862 lines
78 KiB
C
2862 lines
78 KiB
C
/****************************************************************************
|
|
* fs/unionfs/fs_unionfs.c
|
|
*
|
|
* Copyright (C) 2015, 2017-2019 Gregory Nutt. All rights reserved.
|
|
* Author: Gregory Nutt <gnutt@nuttx.org>
|
|
*
|
|
* 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 <nuttx/config.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/statfs.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/mount.h>
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <semaphore.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <fixedmath.h>
|
|
#include <debug.h>
|
|
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/fs/fs.h>
|
|
#include <nuttx/fs/unionfs.h>
|
|
#include <nuttx/fs/dirent.h>
|
|
#include <nuttx/fs/ioctl.h>
|
|
|
|
#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_unmounted; /* File system has been unmounted */
|
|
};
|
|
|
|
/* 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)nxsem_post(&(ui)->ui_exclsem)
|
|
|
|
static FAR const char *unionfs_offsetpath(FAR const char *relpath,
|
|
FAR const char *prefix);
|
|
static bool unionfs_ispartprefix(FAR const char *partprefix,
|
|
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_tryunlink(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 FAR char *unionfs_relpath(FAR const char *path,
|
|
FAR const char *name);
|
|
|
|
static int unionfs_unbind_child(FAR struct unionfs_mountpt_s *um);
|
|
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);
|
|
static int unionfs_fstat(FAR const struct file *filep,
|
|
FAR struct stat *buf);
|
|
static int unionfs_truncate(FAR struct file *filep, off_t length);
|
|
|
|
/* 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_bind(FAR struct inode *blkdriver, FAR const void *data,
|
|
FAR void **handle);
|
|
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);
|
|
static int unionfs_dobind(FAR const char *fspath1,
|
|
FAR const char *prefix1, FAR const char *fspath2,
|
|
FAR const char *prefix2, FAR void **handle);
|
|
|
|
/****************************************************************************
|
|
* 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.
|
|
*/
|
|
|
|
const struct mountpt_operations unionfs_operations =
|
|
{
|
|
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_fstat, /* fstat */
|
|
unionfs_truncate, /* truncate */
|
|
|
|
unionfs_opendir, /* opendir */
|
|
unionfs_closedir, /* closedir */
|
|
unionfs_readdir, /* readdir */
|
|
unionfs_rewinddir, /* rewinddir */
|
|
|
|
unionfs_bind, /* 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)
|
|
{
|
|
if (noint)
|
|
{
|
|
return nxsem_wait_uninterruptible(&ui->ui_exclsem);
|
|
}
|
|
else
|
|
{
|
|
return nxsem_wait(&ui->ui_exclsem);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: unionfs_offsetpath
|
|
****************************************************************************/
|
|
|
|
static FAR const char *unionfs_offsetpath(FAR const char *relpath,
|
|
FAR const char *prefix)
|
|
{
|
|
FAR const char *trypath;
|
|
int pfxlen;
|
|
|
|
/* Is there a prefix on 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_ispartprefix
|
|
****************************************************************************/
|
|
|
|
static bool unionfs_ispartprefix(FAR const char *partprefix,
|
|
FAR const char *prefix)
|
|
{
|
|
int partlen = 0;
|
|
int pfxlen = 0;
|
|
|
|
/* Trim any '/' characters in the partial prefix */
|
|
|
|
if (partprefix != NULL)
|
|
{
|
|
/* Skip over any leading '/' */
|
|
|
|
for (; *partprefix == '/'; partprefix++);
|
|
|
|
/* Skip over any tailing '/' */
|
|
|
|
partlen = strlen(partprefix);
|
|
for (; partlen > 1 && partprefix[partlen - 1] == '/'; partlen--);
|
|
}
|
|
|
|
/* Check for NUL or empty partial prefix */
|
|
|
|
if (partprefix == NULL || *partprefix == '\0')
|
|
{
|
|
/* A NUL partial prefix is always contained in the full prefix, even
|
|
* if there is no prefix.
|
|
*/
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Trim any '/' characters in the partial prefix */
|
|
|
|
if (prefix != NULL)
|
|
{
|
|
/* Skip over any leading '/' */
|
|
|
|
for (; *prefix == '/'; prefix++);
|
|
|
|
/* Skip over any tailing '/' */
|
|
|
|
pfxlen = strlen(prefix);
|
|
for (; pfxlen > 1 && prefix[pfxlen - 1] == '/'; pfxlen--);
|
|
}
|
|
|
|
/* Check for NUL or empty full prefix */
|
|
|
|
if (prefix == NULL || *prefix == '\0')
|
|
{
|
|
/* A non-NUL partial path cannot be a contained in a NUL prefix */
|
|
|
|
return false;
|
|
}
|
|
|
|
#if 0 /* Only whole offset is currently supported */
|
|
/* Both the partial path and the prefix are non-NULL. Check if the partial
|
|
* path is contained in the prefix.
|
|
*/
|
|
|
|
if (partlen > pfxlen)
|
|
{
|
|
return false;
|
|
}
|
|
#else
|
|
/* Check if the trimmed offsets are identical */
|
|
|
|
if (partlen != pfxlen)
|
|
{
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
if (strncmp(partprefix, prefix, partlen) == 0)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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_offsetpath(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_offsetpath(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_offsetpath(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_tryrename
|
|
****************************************************************************/
|
|
|
|
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_offsetpath(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_offsetpath(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_offsetpath(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_trystatfile
|
|
****************************************************************************/
|
|
|
|
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_offsetpath(relpath, prefix);
|
|
if (trypath == NULL)
|
|
{
|
|
/* No.. return -ENOENT */
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* Yes.. Try to remove the directory */
|
|
|
|
ops = inode->u.i_mops;
|
|
if (!ops->rmdir)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
return ops->rmdir(inode, trypath);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: unionfs_tryunlink
|
|
****************************************************************************/
|
|
|
|
static int unionfs_tryunlink(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_offsetpath(relpath, prefix);
|
|
if (trypath == NULL)
|
|
{
|
|
/* No.. return -ENOENT */
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* Yes.. Try to unlink the file */
|
|
|
|
ops = inode->u.i_mops;
|
|
if (!ops->unlink)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
return ops->unlink(inode, trypath);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: unionfs_relpath
|
|
****************************************************************************/
|
|
|
|
static FAR char *unionfs_relpath(FAR const char *path, FAR const char *name)
|
|
{
|
|
FAR char *relpath;
|
|
int pathlen;
|
|
int ret;
|
|
|
|
/* Check if there is a valid, non-zero-legnth path */
|
|
|
|
if (path && (pathlen = strlen(path)) > 0)
|
|
{
|
|
/* Yes.. extend the file name by prepending the path */
|
|
|
|
if (path[pathlen - 1] == '/')
|
|
{
|
|
ret = asprintf(&relpath, "%s%s", path, name);
|
|
}
|
|
else
|
|
{
|
|
ret = asprintf(&relpath, "%s/%s", path, name);
|
|
}
|
|
|
|
/* Handle errors */
|
|
|
|
if (ret < 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
return relpath;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* There is no path... just duplicate the name (so that kmm_free()
|
|
* will work later).
|
|
*/
|
|
|
|
return strdup(name);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: unionfs_unbind_child
|
|
****************************************************************************/
|
|
|
|
static int unionfs_unbind_child(FAR struct unionfs_mountpt_s *um)
|
|
{
|
|
FAR struct inode *mpinode = um->um_node;
|
|
FAR struct inode *bdinode = NULL;
|
|
int ret;
|
|
|
|
/* Unbind the block driver from the file system (destroying any fs
|
|
* private data). This logic is essentially the same as the logic in
|
|
* nuttx/fs/mount/fs_umount2.c.
|
|
*/
|
|
|
|
if (!mpinode->u.i_mops->unbind)
|
|
{
|
|
/* The filesystem does not support the unbind operation ??? */
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* The unbind method returns the number of references to the file system
|
|
* (i.e., open files), zero if the unbind was performed, or a negated
|
|
* error code on a failure.
|
|
*/
|
|
|
|
ret = mpinode->u.i_mops->unbind(mpinode->i_private, &bdinode, MNT_FORCE);
|
|
if (ret < 0)
|
|
{
|
|
/* Some failure occurred */
|
|
|
|
return ret;
|
|
}
|
|
else if (ret > 0)
|
|
{
|
|
/* REVISIT: This is bad if the file sysem cannot support a deferred
|
|
* unmount. Ideally it would perform the unmount when the last file
|
|
* is closed. But I don't think any file system do that.
|
|
*/
|
|
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* Successfully unbound */
|
|
|
|
mpinode->i_private = NULL;
|
|
|
|
/* Release the mountpoint inode and any block driver inode
|
|
* returned by the file system unbind above. This should cause
|
|
* the inode to be deleted (unless there are other references)
|
|
*/
|
|
|
|
inode_release(mpinode);
|
|
|
|
/* Did the unbind method return a contained block driver */
|
|
|
|
if (bdinode)
|
|
{
|
|
inode_release(bdinode);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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);
|
|
|
|
/* Unbind the contained file systems */
|
|
|
|
unionfs_unbind_child(&ui->ui_fs[0]);
|
|
unionfs_unbind_child(&ui->ui_fs[1]);
|
|
|
|
/* Free any allocated prefix strings */
|
|
|
|
if (ui->ui_fs[0].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 */
|
|
|
|
nxsem_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;
|
|
|
|
finfo("Opening: ui_nopen=%d\n", ui->ui_nopen);
|
|
|
|
/* Get exclusive access to the file system data structures */
|
|
|
|
ret = unionfs_semtake(ui, false);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Allocate 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];
|
|
DEBUGASSERT(um != NULL && um->um_node != NULL &&
|
|
um->um_node->u.i_mops != NULL);
|
|
|
|
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;
|
|
}
|
|
|
|
/* Increment the open reference count */
|
|
|
|
ui->ui_nopen++;
|
|
DEBUGASSERT(ui->ui_nopen > 0);
|
|
|
|
/* Save our private data in the file structure */
|
|
|
|
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 */
|
|
|
|
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;
|
|
|
|
finfo("Closing: ui_nopen=%d\n", ui->ui_nopen);
|
|
|
|
/* Perform the lower level close operation */
|
|
|
|
if (ops->close != NULL)
|
|
{
|
|
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, then destroy the file system
|
|
* now.
|
|
*/
|
|
|
|
if (--ui->ui_nopen <= 0 && ui->ui_unmounted)
|
|
{
|
|
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;
|
|
|
|
finfo("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 read operation */
|
|
|
|
if (ops->read != NULL)
|
|
{
|
|
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;
|
|
|
|
finfo("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 != NULL)
|
|
{
|
|
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;
|
|
|
|
finfo("offset: %lu whence: %d\n", (unsigned long)offset, 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 != NULL)
|
|
{
|
|
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;
|
|
|
|
finfo("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 != NULL)
|
|
{
|
|
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;
|
|
|
|
finfo("filep=%p\n", filep);
|
|
|
|
/* 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 != NULL)
|
|
{
|
|
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;
|
|
|
|
finfo("oldp=%p newp=%p\n", oldp, newp);
|
|
|
|
/* 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 != NULL)
|
|
{
|
|
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_fstat
|
|
*
|
|
* Description:
|
|
* Obtain information about an open file associated with the file
|
|
* descriptor 'fd', and will write it to the area pointed to by 'buf'.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int unionfs_fstat(FAR const struct file *filep, FAR struct stat *buf)
|
|
{
|
|
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;
|
|
|
|
finfo("filep=%p buf=%p\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 write operation */
|
|
|
|
if (ops->fstat != NULL)
|
|
{
|
|
ret = ops->fstat(&uf->uf_file, buf);
|
|
}
|
|
|
|
unionfs_semgive(ui);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: unionfs_truncate
|
|
*
|
|
* Description:
|
|
* Set the size of the file references by 'filep' to 'length'.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int unionfs_truncate(FAR struct file *filep, off_t length)
|
|
{
|
|
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;
|
|
|
|
finfo("filep=%p length=%ld\n", filep, (long)length);
|
|
|
|
/* 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->truncate != NULL)
|
|
{
|
|
ret = ops->truncate(&uf->uf_file, length);
|
|
}
|
|
|
|
unionfs_semgive(ui);
|
|
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;
|
|
|
|
finfo("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)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
DEBUGASSERT(dir);
|
|
fu = &dir->u.unionfs;
|
|
|
|
/* Clone the path. We will need this when we traverse file system 2 to
|
|
* omit duplicates on file system 1.
|
|
*/
|
|
|
|
if (relpath && strlen(relpath) > 0)
|
|
{
|
|
fu->fu_relpath = strdup(relpath);
|
|
if (!fu->fu_relpath)
|
|
{
|
|
goto errout_with_semaphore;
|
|
}
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
ret = -ENOMEM;
|
|
goto errout_with_relpath;
|
|
}
|
|
|
|
/* Check file system 2 first. */
|
|
|
|
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 file system 2 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 if the user is stat'ing some "fake" node between the unionfs root
|
|
* and the file system 1/2 root directory.
|
|
*/
|
|
|
|
else if (unionfs_ispartprefix(relpath, ui->ui_fs[1].um_prefix))
|
|
{
|
|
/* File system 2 prefix includes this relpath */
|
|
|
|
fu->fu_ndx = 1;
|
|
fu->fu_prefix[1] = true;
|
|
}
|
|
|
|
/* Check file system 1 last, possibly overwriting fu_ndx */
|
|
|
|
um = &ui->ui_fs[0];
|
|
lowerdir->fd_root = um->um_node;
|
|
ret = unionfs_tryopendir(um->um_node, relpath, um->um_prefix, lowerdir);
|
|
if (ret >= 0)
|
|
{
|
|
/* Save the file system 1 access info */
|
|
|
|
fu->fu_ndx = 0;
|
|
fu->fu_lower[0] = lowerdir;
|
|
}
|
|
else
|
|
{
|
|
/* File system 1 was not opened... then we won't be needing that last
|
|
* localdir allocation after all.
|
|
*/
|
|
|
|
kmm_free(lowerdir);
|
|
|
|
/* Check if the user is stat'ing some "fake" node between the unionfs
|
|
* root and the file system 1 root directory.
|
|
*/
|
|
|
|
if (unionfs_ispartprefix(relpath, ui->ui_fs[0].um_prefix))
|
|
{
|
|
/* File system 1 offset includes this relpath. Make sure that only one */
|
|
|
|
fu->fu_ndx = 0;
|
|
fu->fu_prefix[0] = true;
|
|
fu->fu_prefix[1] = false;
|
|
}
|
|
|
|
/* If the directory was not found on either file system, then we have
|
|
* failed to open this path on either file system.
|
|
*/
|
|
|
|
else if (fu->fu_lower[1] == NULL && !fu->fu_prefix[1])
|
|
{
|
|
/* Neither of the two path file systems include this relpath */
|
|
|
|
ret = -ENOENT;
|
|
goto errout_with_relpath;
|
|
}
|
|
}
|
|
|
|
/* 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 != NULL)
|
|
{
|
|
ret = ops->closedir(um->um_node, fu->fu_lower[1]);
|
|
}
|
|
|
|
kmm_free(fu->fu_lower[1]);
|
|
|
|
errout_with_relpath:
|
|
if (fu->fu_relpath != NULL)
|
|
{
|
|
kmm_free(fu->fu_relpath);
|
|
}
|
|
|
|
errout_with_semaphore:
|
|
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;
|
|
|
|
finfo("mountpt=%p dir=%p\n", mountpt, dir);
|
|
|
|
/* 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 */
|
|
|
|
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 != NULL)
|
|
{
|
|
ret = ops->closedir(um->um_node, fu->fu_lower[i]);
|
|
}
|
|
|
|
/* Free the lower dirent structure */
|
|
|
|
kmm_free(fu->fu_lower[i]);
|
|
}
|
|
}
|
|
|
|
/* Free any allocated path */
|
|
|
|
if (fu->fu_relpath != NULL)
|
|
{
|
|
kmm_free(fu->fu_relpath);
|
|
}
|
|
|
|
fu->fu_ndx = 0;
|
|
fu->fu_relpath = NULL;
|
|
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, then destroy the file system
|
|
* now.
|
|
*/
|
|
|
|
if (--ui->ui_nopen <= 0 && ui->ui_unmounted)
|
|
{
|
|
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 struct unionfs_mountpt_s *um0;
|
|
FAR const struct mountpt_operations *ops;
|
|
FAR struct fs_unionfsdir_s *fu;
|
|
FAR char *relpath;
|
|
struct stat buf;
|
|
bool duplicate;
|
|
int ret = -ENOSYS;
|
|
|
|
/* 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;
|
|
|
|
/* Check if we are at the end of the directory listing. */
|
|
|
|
if (fu->fu_eod)
|
|
{
|
|
/* End of file and error conditions are not distinguishable
|
|
* with readdir. Here we return -ENOENT to signal the end
|
|
* of the directory.
|
|
*/
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
DEBUGASSERT(fu->fu_ndx == 0 || fu->fu_ndx == 1);
|
|
um = &ui->ui_fs[fu->fu_ndx];
|
|
|
|
/* Special case: If the open directory is a 'fake' node in the prefix on
|
|
* one of the mounted file system, then we must also fake the return value.
|
|
*/
|
|
|
|
if (fu->fu_prefix[fu->fu_ndx])
|
|
{
|
|
DEBUGASSERT(fu->fu_lower[fu->fu_ndx] == NULL && um->um_prefix != NULL);
|
|
|
|
/* Copy the file system offset into the dirent structure.
|
|
* REVISIT: This will not handle the case where the prefix contains
|
|
* the '/' character the so the offset appears to be multiple
|
|
* directories.
|
|
*/
|
|
|
|
strncpy(dir->fd_dir.d_name, um->um_prefix, NAME_MAX + 1);
|
|
|
|
/* Describe this as a read only directory */
|
|
|
|
dir->fd_dir.d_type = DTYPE_DIRECTORY;
|
|
|
|
/* Increment the index to file system 2 (maybe) */
|
|
|
|
if (fu->fu_ndx == 0 && (fu->fu_prefix[1] || fu->fu_lower[1] != NULL))
|
|
{
|
|
/* Yes.. set up to do file system 2 next time */
|
|
|
|
fu->fu_ndx++;
|
|
}
|
|
else
|
|
{
|
|
/* No.. we are finished */
|
|
|
|
fu->fu_eod = true;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/* This is a normal, mediated file system readdir() */
|
|
|
|
DEBUGASSERT(fu->fu_lower[fu->fu_ndx] != NULL);
|
|
DEBUGASSERT(um->um_node != NULL && um->um_node->u.i_mops != NULL);
|
|
ops = um->um_node->u.i_mops;
|
|
|
|
finfo("fu_ndx: %d\n", fu->fu_ndx);
|
|
|
|
/* Perform the lower level readdir operation */
|
|
|
|
if (ops->readdir != NULL)
|
|
{
|
|
/* Loop if we discard duplicate directory entries in filey system 2 */
|
|
|
|
do
|
|
{
|
|
/* Read the directory entry */
|
|
|
|
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)
|
|
{
|
|
/* Special case: If the open directory is a 'fake' node in the
|
|
* prefix on file system2, then we must also fake the return
|
|
* value.
|
|
*/
|
|
|
|
if (fu->fu_prefix[1])
|
|
{
|
|
DEBUGASSERT(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_prefix != NULL);
|
|
|
|
/* Copy the file system offset into the dirent structure.
|
|
* REVISIT: This will not handle the case where the prefix
|
|
* contains the '/' character the so the offset appears to
|
|
* be multiple directories.
|
|
*/
|
|
|
|
strncpy(dir->fd_dir.d_name, um->um_prefix, NAME_MAX + 1);
|
|
|
|
/* Describe this as a read only directory */
|
|
|
|
dir->fd_dir.d_type = DTYPE_DIRECTORY;
|
|
|
|
/* Mark the end of the directory listing */
|
|
|
|
fu->fu_eod = true;
|
|
|
|
/* Check if have already reported something of this name
|
|
* in file system 1.
|
|
*/
|
|
|
|
relpath = unionfs_relpath(fu->fu_relpath, um->um_prefix);
|
|
if (relpath)
|
|
{
|
|
int tmp;
|
|
|
|
/* Check if anything exists at this path on file system 1 */
|
|
|
|
um0 = &ui->ui_fs[0];
|
|
tmp = unionfs_trystat(um0->um_node, relpath,
|
|
um0->um_prefix, &buf);
|
|
|
|
/* Free the allocated relpath */
|
|
|
|
kmm_free(relpath);
|
|
|
|
/* Check for a duplicate */
|
|
|
|
if (tmp >= 0)
|
|
{
|
|
/* There is something there!
|
|
* REVISIT: We could allow files and directories to
|
|
* have duplicat names.
|
|
*/
|
|
|
|
return -ENOENT;
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/* No.. check for a normal directory access */
|
|
|
|
else if (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]);
|
|
}
|
|
}
|
|
|
|
/* Did we successfully read a directory from file system 2? If
|
|
* so, we need to omit an duplicates that should be occluded by
|
|
* the matching file on file system 1 (if we are enumerating
|
|
* file system 1).
|
|
*/
|
|
|
|
duplicate = false;
|
|
if (ret >= 0 && fu->fu_ndx == 1 && fu->fu_lower[0] != NULL)
|
|
{
|
|
/* Get the relative path to the same file on file system 1.
|
|
* NOTE: the on any failures we just assume that the filep
|
|
* is not a duplicate.
|
|
*/
|
|
|
|
relpath = unionfs_relpath(fu->fu_relpath,
|
|
fu->fu_lower[1]->fd_dir.d_name);
|
|
if (relpath)
|
|
{
|
|
int tmp;
|
|
|
|
/* Check if anything exists at this path on file system 1 */
|
|
|
|
um0 = &ui->ui_fs[0];
|
|
tmp = unionfs_trystat(um0->um_node, relpath,
|
|
um0->um_prefix, &buf);
|
|
if (tmp >= 0)
|
|
{
|
|
/* There is something there!
|
|
* REVISIT: We could allow files and directories to
|
|
* have duplicat names.
|
|
*/
|
|
|
|
duplicate = true;
|
|
}
|
|
|
|
/* Free the allocated relpath */
|
|
|
|
kmm_free(relpath);
|
|
}
|
|
}
|
|
}
|
|
while (duplicate);
|
|
|
|
/* Copy the return information into the dirent 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;
|
|
|
|
finfo("mountpt=%p dir=%p\n", mountpt, dir);
|
|
|
|
/* 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_prefix[0] || fu->fu_lower[0] != NULL)
|
|
{
|
|
/* Yes.. switch to file system 1 */
|
|
|
|
fu->fu_ndx = 0;
|
|
}
|
|
|
|
if (!fu->fu_prefix[fu->fu_ndx])
|
|
{
|
|
DEBUGASSERT(fu->fu_lower[fu->fu_ndx] != NULL);
|
|
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_bind
|
|
****************************************************************************/
|
|
|
|
static int unionfs_bind(FAR struct inode *blkdriver, FAR const void *data,
|
|
FAR void **handle)
|
|
{
|
|
FAR const char *fspath1 = "";
|
|
FAR const char *prefix1 = "";
|
|
FAR const char *fspath2 = "";
|
|
FAR const char *prefix2 = "";
|
|
FAR char *dup;
|
|
FAR char *tmp;
|
|
FAR char *tok;
|
|
int ret;
|
|
|
|
/* Parse options from mount syscall */
|
|
|
|
dup = tmp = strdup(data);
|
|
if (!dup)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
while ((tok = strsep(&tmp, ",")))
|
|
{
|
|
if (tok == strstr(tok, "fspath1="))
|
|
{
|
|
fspath1 = tok + 8;
|
|
}
|
|
else if (tok == strstr(tok, "prefix1="))
|
|
{
|
|
prefix1 = tok + 8;
|
|
}
|
|
else if (tok == strstr(tok, "fspath2="))
|
|
{
|
|
fspath2 = tok + 8;
|
|
}
|
|
else if (tok == strstr(tok, "prefix2="))
|
|
{
|
|
prefix2 = tok + 8;
|
|
}
|
|
}
|
|
|
|
/* Call unionfs_dobind to do the real work. */
|
|
|
|
ret = unionfs_dobind(fspath1, prefix1, fspath2, prefix2, handle);
|
|
kmm_free(dup);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: unionfs_unbind
|
|
****************************************************************************/
|
|
|
|
static int unionfs_unbind(FAR void *handle, FAR struct inode **blkdriver,
|
|
unsigned int flags)
|
|
{
|
|
FAR struct unionfs_inode_s *ui;
|
|
|
|
finfo("handle=%p blkdriver=%p flags=%x\n", handle, blkdriver, flags);
|
|
|
|
/* Recover the union file system data from the struct inode instance */
|
|
|
|
DEBUGASSERT(handle != NULL);
|
|
ui = (FAR struct unionfs_inode_s *)handle;
|
|
|
|
/* Get exclusive access to the file system data structures */
|
|
|
|
unionfs_semtake(ui, true);
|
|
|
|
/* Mark the file system as unmounted. */
|
|
|
|
ui->ui_unmounted = true;
|
|
|
|
/* If there are no open references, then we can destroy the file system
|
|
* now.
|
|
*/
|
|
|
|
if (ui->ui_nopen <= 0)
|
|
{
|
|
unionfs_destroy(ui);
|
|
}
|
|
|
|
unionfs_semgive(ui);
|
|
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 *um1;
|
|
FAR struct unionfs_mountpt_s *um2;
|
|
FAR const struct mountpt_operations *ops1;
|
|
FAR const struct mountpt_operations *ops2;
|
|
FAR struct statfs *adj;
|
|
struct statfs buf1;
|
|
struct statfs buf2;
|
|
uint64_t tmp;
|
|
uint32_t ratiob16;
|
|
int ret;
|
|
|
|
finfo("mountpt=%p buf=%p\n", mountpt, buf);
|
|
|
|
/* Recover the union file system data from the struct inode instance */
|
|
|
|
DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL && buf != NULL);
|
|
ui = (FAR struct unionfs_inode_s *)mountpt->i_private;
|
|
|
|
memset(buf, 0, sizeof(struct statfs));
|
|
|
|
/* 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.
|
|
*
|
|
* REVISIT: What would it mean if one file system did not support statfs?
|
|
* Perhaps we could simplify the following by simply insisting that both
|
|
* file systems support the statfs method.
|
|
*/
|
|
|
|
um1 = &ui->ui_fs[0];
|
|
DEBUGASSERT(um1 != NULL && um1->um_node != NULL &&
|
|
um1->um_node->u.i_mops != NULL);
|
|
ops1 = um1->um_node->u.i_mops;
|
|
|
|
um2 = &ui->ui_fs[1];
|
|
DEBUGASSERT(um2 != NULL && um2->um_node != NULL &&
|
|
um2->um_node->u.i_mops != NULL);
|
|
ops2 = um2->um_node->u.i_mops;
|
|
|
|
if (ops1->statfs != NULL && ops2->statfs != NULL)
|
|
{
|
|
ret = ops1->statfs(um1->um_node, &buf1);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_semaphore;
|
|
}
|
|
|
|
/* Get stafs info from file system 2 */
|
|
|
|
ret = ops2->statfs(um2->um_node, &buf2);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_semaphore;
|
|
}
|
|
}
|
|
else if (ops1->statfs != NULL)
|
|
{
|
|
/* We have statfs for file system 1 only */
|
|
|
|
ret = ops1->statfs(um1->um_node, buf);
|
|
goto errout_with_semaphore;
|
|
}
|
|
else if (ops2->statfs != NULL)
|
|
{
|
|
/* We have statfs for file system 2 only */
|
|
|
|
ret = ops2->statfs(um2->um_node, buf);
|
|
goto errout_with_semaphore;
|
|
}
|
|
else
|
|
{
|
|
/* We could not get stafs info from either file system */
|
|
|
|
ret = -ENOSYS;
|
|
goto errout_with_semaphore;
|
|
}
|
|
|
|
/* We get here is we successfully obtained statfs info from both file
|
|
* systems. Now combine those results into one statfs report, trying to
|
|
* reconcile any conflicts between the file system geometries.
|
|
*/
|
|
|
|
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.
|
|
*/
|
|
|
|
buf->f_bsize = buf1.f_bsize;
|
|
if (buf1.f_bsize != buf2.f_bsize)
|
|
{
|
|
if (buf1.f_bsize < buf2.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 = &buf1;
|
|
}
|
|
|
|
tmp = (uint64_t)adj->f_blocks * ratiob16;
|
|
adj->f_blocks = (off_t)(tmp >> 16);
|
|
|
|
tmp = (uint64_t)adj->f_bfree * ratiob16;
|
|
adj->f_bfree = (off_t)(tmp >> 16);
|
|
|
|
tmp = (uint64_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;
|
|
FAR struct unionfs_mountpt_s *um;
|
|
struct stat buf;
|
|
int ret;
|
|
|
|
finfo("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;
|
|
}
|
|
|
|
/* Check if some exists at this path on file system 1. This might be
|
|
* a file or a directory
|
|
*/
|
|
|
|
um = &ui->ui_fs[0];
|
|
ret = unionfs_trystat(um->um_node, relpath, um->um_prefix, &buf);
|
|
if (ret >= 0)
|
|
{
|
|
/* Yes.. Try to unlink the file on file system 1 (perhaps exposing
|
|
* a file of the same name on file system 2). This would fail
|
|
* with -ENOSYS if file system 1 is a read-only only file system or
|
|
* -EISDIR if the path is not a file.
|
|
*/
|
|
|
|
ret = unionfs_tryunlink(um->um_node, relpath, um->um_prefix);
|
|
}
|
|
|
|
/* There is nothing at this path on file system 1 */
|
|
|
|
else
|
|
{
|
|
/* Check if the file exists with with name on file system 2. The only
|
|
* reason that we check here is so that we can return the more
|
|
* meaningful -ENOSYS if file system 2 is a read-only file system.
|
|
*/
|
|
|
|
um = &ui->ui_fs[1];
|
|
ret = unionfs_trystat(um->um_node, relpath, um->um_prefix, &buf);
|
|
if (ret >= 0)
|
|
{
|
|
/* Yes.. Try to unlink the file on file system 1. This would fail
|
|
* with -ENOSYS if file system 2 is a read-only only file system or
|
|
* -EISDIR if the path is not a file.
|
|
* */
|
|
|
|
ret = unionfs_tryunlink(um->um_node, relpath, um->um_prefix);
|
|
}
|
|
}
|
|
|
|
unionfs_semgive(ui);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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;
|
|
|
|
finfo("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)
|
|
{
|
|
ret = -EEXIST;
|
|
goto errout_with_semaphore;
|
|
}
|
|
|
|
um = &ui->ui_fs[1];
|
|
ret = unionfs_trystat(um->um_node, relpath, um->um_prefix, &buf);
|
|
if (ret >= 0)
|
|
{
|
|
ret = -EEXIST;
|
|
goto errout_with_semaphore;
|
|
}
|
|
|
|
/* 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];
|
|
ret2 = 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;
|
|
}
|
|
|
|
errout_with_semaphore:
|
|
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;
|
|
|
|
finfo("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;
|
|
}
|
|
|
|
ret = -ENOENT;
|
|
|
|
/* 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;
|
|
|
|
finfo("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;
|
|
|
|
finfo("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.
|
|
*/
|
|
|
|
unionfs_semgive(ui);
|
|
return OK;
|
|
}
|
|
|
|
/* stat failed on the file system 1. Try again on file system 2. */
|
|
|
|
um = &ui->ui_fs[1];
|
|
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.
|
|
*/
|
|
|
|
unionfs_semgive(ui);
|
|
return OK;
|
|
}
|
|
|
|
/* Special case the unionfs root directory when both file systems are
|
|
* offset. In that case, both of the above trystat calls will fail.
|
|
*/
|
|
|
|
if (ui->ui_fs[0].um_prefix != NULL && ui->ui_fs[1].um_prefix != NULL)
|
|
{
|
|
/* Most of the stat entries just do not apply */
|
|
|
|
memset(buf, 0, sizeof(struct stat));
|
|
|
|
/* Claim that this is a read-only directory */
|
|
|
|
buf->st_mode = S_IFDIR | S_IROTH | S_IRGRP | S_IRUSR;
|
|
|
|
/* Check if the user is stat'ing some "fake" node between the unionfs
|
|
* root and the file system 1 root directory.
|
|
*/
|
|
|
|
if (unionfs_ispartprefix(relpath, ui->ui_fs[0].um_prefix) ||
|
|
unionfs_ispartprefix(relpath, ui->ui_fs[1].um_prefix))
|
|
{
|
|
ret = OK;
|
|
}
|
|
else
|
|
{
|
|
ret = -ENOENT;
|
|
}
|
|
}
|
|
|
|
unionfs_semgive(ui);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: unionfs_getmount
|
|
****************************************************************************/
|
|
|
|
static int unionfs_getmount(FAR const char *path, FAR struct inode **inode)
|
|
{
|
|
FAR struct inode *minode;
|
|
struct inode_search_s desc;
|
|
int ret;
|
|
|
|
/* Find the mountpt */
|
|
|
|
SETUP_SEARCH(&desc, path, false);
|
|
|
|
ret = inode_find(&desc);
|
|
if (ret < 0)
|
|
{
|
|
/* Mountpoint inode not found */
|
|
|
|
goto errout_with_search;
|
|
}
|
|
|
|
/* Get the search results */
|
|
|
|
minode = desc.node;
|
|
DEBUGASSERT(minode != NULL);
|
|
|
|
/* Verify that the inode is a mountpoint.
|
|
*
|
|
* REVISIT: If desc.relpath points to a non-empty string, then the path
|
|
* does not really refer to a mountpoint but, rather, to a some entity
|
|
* within the mounted volume.
|
|
*/
|
|
|
|
if (!INODE_IS_MOUNTPT(minode))
|
|
{
|
|
/* Inode was found, but is it is not a mounpoint */
|
|
|
|
ret = -EINVAL;
|
|
goto errout_with_inode;
|
|
}
|
|
|
|
/* Success! */
|
|
|
|
*inode = minode;
|
|
RELEASE_SEARCH(&desc);
|
|
return OK;
|
|
|
|
errout_with_inode:
|
|
inode_release(minode);
|
|
|
|
errout_with_search:
|
|
RELEASE_SEARCH(&desc);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: unionfs_dobind
|
|
****************************************************************************/
|
|
|
|
static int unionfs_dobind(FAR const char *fspath1, FAR const char *prefix1,
|
|
FAR const char *fspath2, FAR const char *prefix2,
|
|
FAR void **handle)
|
|
{
|
|
FAR struct unionfs_inode_s *ui;
|
|
int ret;
|
|
|
|
DEBUGASSERT(fspath1 != NULL && fspath2 != NULL && handle != 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)
|
|
{
|
|
ferr("ERROR: Failed to allocated union FS state structure\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
nxsem_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)
|
|
{
|
|
ferr("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)
|
|
{
|
|
ferr("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)
|
|
{
|
|
ferr("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)
|
|
{
|
|
ferr("ERROR: strdup(prefix2) failed\n");
|
|
ret = -ENOMEM;
|
|
goto errout_with_prefix1;
|
|
}
|
|
}
|
|
|
|
/* 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.
|
|
*/
|
|
|
|
inode_remove(fspath1);
|
|
inode_remove(fspath2);
|
|
|
|
*handle = ui;
|
|
return OK;
|
|
|
|
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:
|
|
nxsem_destroy(&ui->ui_exclsem);
|
|
kmm_free(ui);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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 inode *mpinode;
|
|
int ret;
|
|
|
|
DEBUGASSERT(mountpt != NULL);
|
|
|
|
/* 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.
|
|
* NOTE that the inode will be created with a reference count of zero.
|
|
*/
|
|
|
|
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
|
|
*/
|
|
|
|
ferr("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 = &unionfs_operations;
|
|
#ifdef CONFIG_FILE_MODE
|
|
mpinode->i_mode = 0755;
|
|
#endif
|
|
|
|
/* Call unionfs_dobind to do the real work. */
|
|
|
|
ret = unionfs_dobind(fspath1, prefix1, fspath2, prefix2,
|
|
&mpinode->i_private);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_mountpt;
|
|
}
|
|
|
|
inode_semgive();
|
|
return OK;
|
|
|
|
errout_with_mountpt:
|
|
inode_release(mpinode);
|
|
|
|
errout_with_semaphore:
|
|
inode_semgive();
|
|
return ret;
|
|
}
|
|
#endif /* !CONFIG_DISABLE_MOUNTPOINT && CONFIG_FS_UNIONFS */
|