| /**************************************************************************** |
| * 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/lib/lib.h> |
| #include <nuttx/fs/fs.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 */ |
| NULL, /* pool */ |
| |
| 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->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) |
| { |
| lib_free(ui->ui_fs[0].um_prefix); |
| } |
| |
| if (ui->ui_fs[1].um_prefix) |
| { |
| lib_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 */ |
| |
| ui = 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 */ |
| |
| ui = 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 */ |
| |
| ui = 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 */ |
| |
| ui = 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 */ |
| |
| ui = 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 */ |
| |
| ui = 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 */ |
| |
| ui = 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 = 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 */ |
| |
| ui = 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 */ |
| |
| ui = 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 */ |
| |
| ui = 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 = 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) |
| { |
| lib_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 = 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 = 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 = 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); |
| lib_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 = 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 = 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 = 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 = 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 = 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 = 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 = 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) |
| { |
| lib_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 |
| ****************************************************************************/ |
| |
| #endif /* !CONFIG_DISABLE_MOUNTPOINT && CONFIG_FS_UNIONFS */ |