b723e90356
if struct statfs add new members, such as f_fsid, no additional code changes are required. Signed-off-by: zhanghongyu <zhanghongyu@xiaomi.com>
2750 lines
77 KiB
C
2750 lines
77 KiB
C
/****************************************************************************
|
|
* fs/unionfs/fs_unionfs.c
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <sys/param.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 <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/ioctl.h>
|
|
#include <nuttx/mutex.h>
|
|
|
|
#include "inode/inode.h"
|
|
|
|
#if !defined(CONFIG_DISABLE_MOUNTPOINT) && defined(CONFIG_FS_UNIONFS)
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
struct unionfs_dir_s
|
|
{
|
|
struct fs_dirent_s fu_base; /* Vfs directory structure */
|
|
uint8_t fu_ndx; /* Index of file system being enumerated */
|
|
bool fu_eod; /* True: At end of directory */
|
|
bool fu_prefix[2]; /* True: Fake directory in prefix */
|
|
FAR char *fu_relpath; /* Path being enumerated */
|
|
FAR struct fs_dirent_s *fu_lower[2]; /* dirent struct used by contained file system */
|
|
};
|
|
|
|
/* 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 */
|
|
mutex_t ui_lock; /* 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 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_trychstat(FAR struct inode *inode,
|
|
FAR const char *relpath, FAR const char *prefix,
|
|
FAR const struct stat *buf, int flags);
|
|
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_fchstat(FAR const struct file *filep,
|
|
FAR const struct stat *buf, int flags);
|
|
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,
|
|
FAR struct dirent *entry);
|
|
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);
|
|
static int unionfs_chstat(FAR struct inode *mountpt,
|
|
FAR const char *relpath,
|
|
FAR const struct stat *buf, int flags);
|
|
|
|
/* 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 g_unionfs_operations =
|
|
{
|
|
unionfs_open, /* open */
|
|
unionfs_close, /* close */
|
|
unionfs_read, /* read */
|
|
unionfs_write, /* write */
|
|
unionfs_seek, /* seek */
|
|
unionfs_ioctl, /* ioctl */
|
|
NULL, /* mmap */
|
|
unionfs_truncate, /* truncate */
|
|
|
|
unionfs_sync, /* sync */
|
|
unionfs_dup, /* dup */
|
|
unionfs_fstat, /* fstat */
|
|
unionfs_fchstat, /* fchstat */
|
|
|
|
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 */
|
|
unionfs_chstat /* chstat */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* 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_trychstat
|
|
****************************************************************************/
|
|
|
|
static int unionfs_trychstat(FAR struct inode *inode,
|
|
FAR const char *relpath, FAR const char *prefix,
|
|
FAR const struct stat *buf, int flags)
|
|
{
|
|
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 change the status */
|
|
|
|
ops = inode->u.i_mops;
|
|
if (!ops->chstat)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
return ops->chstat(inode, trypath, buf, flags);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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 system 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 */
|
|
|
|
nxmutex_destroy(&ui->ui_lock);
|
|
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 = nxmutex_lock(&ui->ui_lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Allocate a container to hold the open file system information */
|
|
|
|
uf = (FAR struct unionfs_file_s *)
|
|
kmm_zalloc(sizeof(struct unionfs_file_s));
|
|
if (uf == NULL)
|
|
{
|
|
ret = -ENOMEM;
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
/* 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_inode = um->um_node;
|
|
|
|
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_inode = um->um_node;
|
|
|
|
ret = unionfs_tryopen(&uf->uf_file, relpath, um->um_prefix, oflags,
|
|
mode);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_lock;
|
|
}
|
|
|
|
/* 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_lock:
|
|
nxmutex_unlock(&ui->ui_lock);
|
|
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 */
|
|
|
|
ret = nxmutex_lock(&ui->ui_lock);
|
|
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;
|
|
|
|
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);
|
|
}
|
|
else
|
|
{
|
|
nxmutex_unlock(&ui->ui_lock);
|
|
}
|
|
|
|
/* Free the open file container */
|
|
|
|
kmm_free(uf);
|
|
filep->f_priv = NULL;
|
|
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;
|
|
|
|
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;
|
|
|
|
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 */
|
|
|
|
return ops->read ? ops->read(&uf->uf_file, buffer, buflen) : -EPERM;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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;
|
|
|
|
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;
|
|
|
|
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 */
|
|
|
|
return ops->write ? ops->write(&uf->uf_file, buffer, buflen) : -EPERM;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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;
|
|
|
|
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;
|
|
|
|
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
|
|
{
|
|
int ret;
|
|
|
|
/* Get exclusive access to the file system data structures */
|
|
|
|
ret = nxmutex_lock(&ui->ui_lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
nxmutex_unlock(&ui->ui_lock);
|
|
}
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
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 */
|
|
|
|
return ops->ioctl ? ops->ioctl(&uf->uf_file, cmd, arg) : -ENOTTY;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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;
|
|
|
|
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;
|
|
|
|
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 */
|
|
|
|
return ops->sync ? ops->sync(&uf->uf_file) : -EINVAL;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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;
|
|
|
|
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;
|
|
|
|
finfo("filep=%p buf=%p\n", filep, buf);
|
|
|
|
/* 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;
|
|
|
|
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 */
|
|
|
|
return ops->fstat ? ops->fstat(&uf->uf_file, buf) : -EPERM;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: unionfs_fchstat
|
|
*
|
|
* Description:
|
|
* Change information about an open file associated with the file
|
|
* descriptor 'filep'.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int unionfs_fchstat(FAR const struct file *filep,
|
|
FAR const struct stat *buf, int flags)
|
|
{
|
|
FAR struct unionfs_inode_s *ui;
|
|
FAR struct unionfs_file_s *uf;
|
|
FAR struct unionfs_mountpt_s *um;
|
|
FAR const struct mountpt_operations *ops;
|
|
|
|
finfo("filep=%p buf=%p\n", filep, buf);
|
|
|
|
/* 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;
|
|
|
|
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 change operation */
|
|
|
|
return ops->fchstat ? ops->fchstat(&uf->uf_file, buf, flags) : -EPERM;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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;
|
|
|
|
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;
|
|
|
|
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 */
|
|
|
|
return ops->truncate ? ops->truncate(&uf->uf_file, length) : -EPERM;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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 unionfs_dir_s *udir;
|
|
int ret;
|
|
|
|
finfo("relpath: \"%s\"\n", relpath ? relpath : "NULL");
|
|
|
|
if (!relpath)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
udir = kmm_zalloc(sizeof(*udir));
|
|
if (udir == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Get exclusive access to the file system data structures */
|
|
|
|
ret = nxmutex_lock(&ui->ui_lock);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_udir;
|
|
}
|
|
|
|
DEBUGASSERT(dir);
|
|
|
|
/* Clone the path. We will need this when we traverse file system 2 to
|
|
* omit duplicates on file system 1.
|
|
*/
|
|
|
|
if (strlen(relpath) > 0)
|
|
{
|
|
udir->fu_relpath = strdup(relpath);
|
|
if (!udir->fu_relpath)
|
|
{
|
|
goto errout_with_lock;
|
|
}
|
|
}
|
|
|
|
/* Check file system 2 first. */
|
|
|
|
um = &ui->ui_fs[1];
|
|
ret = unionfs_tryopendir(um->um_node, relpath, um->um_prefix,
|
|
&udir->fu_lower[1]);
|
|
if (ret >= 0)
|
|
{
|
|
/* Save the file system 2 access info */
|
|
|
|
udir->fu_ndx = 1;
|
|
udir->fu_lower[1]->fd_root = um->um_node;
|
|
}
|
|
|
|
/* 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 */
|
|
|
|
udir->fu_ndx = 1;
|
|
udir->fu_prefix[1] = true;
|
|
}
|
|
|
|
/* Check file system 1 last, possibly overwriting fu_ndx */
|
|
|
|
um = &ui->ui_fs[0];
|
|
ret = unionfs_tryopendir(um->um_node, relpath, um->um_prefix,
|
|
&udir->fu_lower[0]);
|
|
if (ret >= 0)
|
|
{
|
|
/* Save the file system 1 access info */
|
|
|
|
udir->fu_ndx = 0;
|
|
udir->fu_lower[0]->fd_root = um->um_node;
|
|
}
|
|
else
|
|
{
|
|
/* 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
|
|
*/
|
|
|
|
udir->fu_ndx = 0;
|
|
udir->fu_prefix[0] = true;
|
|
udir->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 (udir->fu_lower[1] == NULL && !udir->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);
|
|
|
|
nxmutex_unlock(&ui->ui_lock);
|
|
*dir = &udir->fu_base;
|
|
return OK;
|
|
|
|
errout_with_relpath:
|
|
if (udir->fu_relpath != NULL)
|
|
{
|
|
kmm_free(udir->fu_relpath);
|
|
}
|
|
|
|
errout_with_lock:
|
|
nxmutex_unlock(&ui->ui_lock);
|
|
|
|
errout_with_udir:
|
|
kmm_free(udir);
|
|
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 unionfs_dir_s *udir;
|
|
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 */
|
|
|
|
ret = nxmutex_lock(&ui->ui_lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
DEBUGASSERT(dir);
|
|
udir = (FAR struct unionfs_dir_s *)dir;
|
|
|
|
/* Close both contained file systems */
|
|
|
|
for (i = 0; i < 2; i++)
|
|
{
|
|
/* Was this file system opened? */
|
|
|
|
if (udir->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, udir->fu_lower[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Free any allocated path */
|
|
|
|
if (udir->fu_relpath != NULL)
|
|
{
|
|
kmm_free(udir->fu_relpath);
|
|
}
|
|
|
|
kmm_free(udir);
|
|
|
|
/* 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
|
|
{
|
|
nxmutex_unlock(&ui->ui_lock);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: unionfs_readdir
|
|
****************************************************************************/
|
|
|
|
static int unionfs_readdir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir,
|
|
FAR struct dirent *entry)
|
|
{
|
|
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 unionfs_dir_s *udir;
|
|
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);
|
|
udir = (FAR struct unionfs_dir_s *)dir;
|
|
|
|
/* Check if we are at the end of the directory listing. */
|
|
|
|
if (udir->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(udir->fu_ndx == 0 || udir->fu_ndx == 1);
|
|
um = &ui->ui_fs[udir->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 (udir->fu_prefix[udir->fu_ndx])
|
|
{
|
|
DEBUGASSERT(udir->fu_lower[udir->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.
|
|
*/
|
|
|
|
strlcpy(entry->d_name, um->um_prefix, sizeof(entry->d_name));
|
|
|
|
/* Describe this as a read only directory */
|
|
|
|
entry->d_type = DTYPE_DIRECTORY;
|
|
|
|
/* Increment the index to file system 2 (maybe) */
|
|
|
|
if (udir->fu_ndx == 0 && (udir->fu_prefix[1] ||
|
|
udir->fu_lower[1] != NULL))
|
|
{
|
|
/* Yes.. set up to do file system 2 next time */
|
|
|
|
udir->fu_ndx++;
|
|
}
|
|
else
|
|
{
|
|
/* No.. we are finished */
|
|
|
|
udir->fu_eod = true;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/* This is a normal, mediated file system readdir() */
|
|
|
|
DEBUGASSERT(udir->fu_lower[udir->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", udir->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, udir->fu_lower[udir->fu_ndx],
|
|
entry);
|
|
|
|
/* 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 && udir->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 (udir->fu_prefix[1])
|
|
{
|
|
DEBUGASSERT(udir->fu_lower[1] == NULL);
|
|
|
|
/* Switch to the second file system */
|
|
|
|
udir->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.
|
|
*/
|
|
|
|
strlcpy(entry->d_name, um->um_prefix,
|
|
sizeof(entry->d_name));
|
|
|
|
/* Describe this as a read only directory */
|
|
|
|
entry->d_type = DTYPE_DIRECTORY;
|
|
|
|
/* Mark the end of the directory listing */
|
|
|
|
udir->fu_eod = true;
|
|
|
|
/* Check if have already reported something of this name
|
|
* in file system 1.
|
|
*/
|
|
|
|
relpath = unionfs_relpath(udir->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 */
|
|
|
|
lib_free(relpath);
|
|
|
|
/* Check for a duplicate */
|
|
|
|
if (tmp >= 0)
|
|
{
|
|
/* There is something there!
|
|
* REVISIT: We could allow files and directories to
|
|
* have duplicate names.
|
|
*/
|
|
|
|
return -ENOENT;
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/* No.. check for a normal directory access */
|
|
|
|
else if (udir->fu_lower[1] != NULL)
|
|
{
|
|
/* Switch to the second file system */
|
|
|
|
udir->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, udir->fu_lower[1]);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* Then try the read operation again */
|
|
|
|
ret = ops->readdir(um->um_node, udir->fu_lower[1], entry);
|
|
}
|
|
}
|
|
|
|
/* 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 && udir->fu_ndx == 1 && udir->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(udir->fu_relpath, entry->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 duplicate names.
|
|
*/
|
|
|
|
duplicate = true;
|
|
}
|
|
|
|
/* Free the allocated relpath */
|
|
|
|
lib_free(relpath);
|
|
}
|
|
}
|
|
}
|
|
while (duplicate);
|
|
}
|
|
|
|
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 unionfs_dir_s *udir;
|
|
int ret = -EINVAL;
|
|
|
|
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;
|
|
|
|
DEBUGASSERT(dir);
|
|
udir = (FAR struct unionfs_dir_s *)dir;
|
|
|
|
/* Were we currently enumerating on file system 1? If not, is an
|
|
* enumeration possible on file system 1?
|
|
*/
|
|
|
|
DEBUGASSERT(udir->fu_ndx == 0 || udir->fu_ndx == 1);
|
|
if (/* udir->fu_ndx != 0 && */ udir->fu_prefix[0] || udir->fu_lower[0] != NULL)
|
|
{
|
|
/* Yes.. switch to file system 1 */
|
|
|
|
udir->fu_ndx = 0;
|
|
}
|
|
|
|
if (!udir->fu_prefix[udir->fu_ndx])
|
|
{
|
|
DEBUGASSERT(udir->fu_lower[udir->fu_ndx] != NULL);
|
|
um = &ui->ui_fs[udir->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, udir->fu_lower[udir->fu_ndx]);
|
|
}
|
|
}
|
|
|
|
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;
|
|
int ret;
|
|
|
|
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 */
|
|
|
|
ret = nxmutex_lock(&ui->ui_lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
nxmutex_unlock(&ui->ui_lock);
|
|
unionfs_destroy(ui);
|
|
}
|
|
else
|
|
{
|
|
nxmutex_unlock(&ui->ui_lock);
|
|
}
|
|
|
|
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;
|
|
|
|
/* 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;
|
|
|
|
memset(&buf1, 0, sizeof(struct statfs));
|
|
memset(&buf2, 0, sizeof(struct statfs));
|
|
if (ops1->statfs != NULL && ops2->statfs != NULL)
|
|
{
|
|
ret = ops1->statfs(um1->um_node, &buf1);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Get stafs info from file system 2 */
|
|
|
|
ret = ops2->statfs(um2->um_node, &buf2);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
}
|
|
else if (ops1->statfs != NULL)
|
|
{
|
|
/* We have statfs for file system 1 only */
|
|
|
|
return ops1->statfs(um1->um_node, buf);
|
|
}
|
|
else if (ops2->statfs != NULL)
|
|
{
|
|
/* We have statfs for file system 2 only */
|
|
|
|
return ops2->statfs(um2->um_node, buf);
|
|
}
|
|
else
|
|
{
|
|
/* We could not get stafs info from either file system */
|
|
|
|
return -ENOSYS;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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;
|
|
|
|
/* 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 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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
/* 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];
|
|
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?
|
|
*/
|
|
|
|
return (ret1 >= 0 || ret2 >= 0) ? OK : ret1;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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 ret = -ENOENT;
|
|
int tmp;
|
|
|
|
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;
|
|
|
|
/* 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)
|
|
{
|
|
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?
|
|
*/
|
|
}
|
|
|
|
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 ret = -ENOENT;
|
|
int tmp;
|
|
|
|
finfo("oldrelpath: %s newrelpath: %s\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;
|
|
|
|
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.
|
|
*/
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
|
|
/* 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[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.
|
|
*/
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: unionfs_chstat
|
|
****************************************************************************/
|
|
|
|
static int unionfs_chstat(FAR struct inode *mountpt, FAR const char *relpath,
|
|
FAR const struct stat *buf, int flags)
|
|
{
|
|
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;
|
|
|
|
/* chstat this path on file system 1 */
|
|
|
|
um = &ui->ui_fs[0];
|
|
ret = unionfs_trychstat(um->um_node, relpath, um->um_prefix, buf, flags);
|
|
if (ret >= 0)
|
|
{
|
|
/* Return on the first success. The first instance of the file will
|
|
* shadow the second anyway.
|
|
*/
|
|
|
|
return OK;
|
|
}
|
|
|
|
/* chstat failed on the file system 1. Try again on file system 2. */
|
|
|
|
um = &ui->ui_fs[1];
|
|
ret = unionfs_trychstat(um->um_node, relpath, um->um_prefix, buf, flags);
|
|
if (ret >= 0)
|
|
{
|
|
return OK;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
nxmutex_init(&ui->ui_lock);
|
|
|
|
/* 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:
|
|
nxmutex_destroy(&ui->ui_lock);
|
|
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.
|
|
*/
|
|
|
|
ret = inode_reserve(mountpt, 0777, &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");
|
|
return ret;
|
|
}
|
|
|
|
/* Populate the inode with driver specific information. */
|
|
|
|
INODE_SET_MOUNTPT(mpinode);
|
|
|
|
mpinode->u.i_mops = &g_unionfs_operations;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
return OK;
|
|
|
|
errout_with_mountpt:
|
|
inode_release(mpinode);
|
|
return ret;
|
|
}
|
|
#endif /* !CONFIG_DISABLE_MOUNTPOINT && CONFIG_FS_UNIONFS */
|