diff --git a/fs/unionfs/fs_unionfs.c b/fs/unionfs/fs_unionfs.c index 9b99324f778..3cab6d3c6a8 100644 --- a/fs/unionfs/fs_unionfs.c +++ b/fs/unionfs/fs_unionfs.c @@ -45,6 +45,7 @@ #include #include +#include #include #include #include @@ -133,6 +134,8 @@ 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 void unionfs_unhooked(FAR struct unionfs_inode_s *ui); static void unionfs_destroy(FAR struct unionfs_inode_s *ui); @@ -560,6 +563,52 @@ static int unionfs_tryrmdir(FAR struct inode *inode, FAR const char *relpath, return ops->rmdir(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_unhooked ****************************************************************************/ @@ -1123,13 +1172,26 @@ static int unionfs_opendir(FAR struct inode *mountpt, FAR const char *relpath, DEBUGASSERT(dir); fu = &dir->u.unionfs; + /* Clone the path. We will need this when we traverse file system 2 to + * omit duplicates on file system 1. + */ + + if (relpath && strlen(relpath) > 0) + { + fu->fu_relpath = strdup(relpath); + if (!fu->fu_relpath) + { + goto errout_with_semaphore; + } + } + /* Allocate another dirent structure for the lower file system */ lowerdir = (FAR struct fs_dirent_s *)kmm_zalloc(sizeof(struct fs_dirent_s)); if (lowerdir == NULL) { ret = -ENOMEM; - goto errout_with_semaphore; + goto errout_with_relpath; } /* Check file system 2 first. */ @@ -1186,7 +1248,7 @@ static int unionfs_opendir(FAR struct inode *mountpt, FAR const char *relpath, { /* Neither file system was opened! */ - goto errout_with_semaphore; + goto errout_with_relpath; } } @@ -1208,6 +1270,12 @@ errout_with_fs2open: kmm_free(fu->fu_lower[1]); +errout_with_relpath: + if (fu->fu_relpath != NULL) + { + kmm_free(fu->fu_relpath); + } + errout_with_semaphore: unionfs_semgive(ui); return ret; @@ -1267,7 +1335,15 @@ static int unionfs_closedir(FAR struct inode *mountpt, } } + /* Free any allocated path */ + + if (fu->fu_relpath != NULL) + { + kmm_free(fu->fu_relpath); + } + fu->fu_ndx = 0; + fu->fu_relpath = NULL; fu->fu_lower[0] = NULL; fu->fu_lower[1] = NULL; @@ -1296,8 +1372,12 @@ static int unionfs_readdir(struct inode *mountpt, struct fs_dirent_s *dir) { FAR struct unionfs_inode_s *ui; FAR struct unionfs_mountpt_s *um; + FAR struct unionfs_mountpt_s *um0; FAR const struct mountpt_operations *ops; FAR struct fs_unionfsdir_s *fu; + FAR char *relpath; + struct stat buf; + bool duplicate; int ret = -ENOSYS; /* Recover the union file system data from the struct inode instance */ @@ -1320,39 +1400,89 @@ static int unionfs_readdir(struct inode *mountpt, struct fs_dirent_s *dir) if (ops->readdir) { - ret = ops->readdir(um->um_node, fu->fu_lower[fu->fu_ndx]); + /* Loop if we discard duplicate directory entries in filey system 2 */ - /* Did the read operation fail because we reached the end of the - * directory? In that case, the error would be -ENOENT. If we hit - * the end-of-directory on file system, we need to seamlessly move - * to the second file system (if there is one). - */ - - if (ret == -ENOENT && fu->fu_ndx == 0 && fu->fu_lower[1] != NULL) + do { - /* Switch to the second file system */ + /* Read the directory entry */ - fu->fu_ndx = 1; - um = &ui->ui_fs[1]; + ret = ops->readdir(um->um_node, fu->fu_lower[fu->fu_ndx]); - 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. + /* 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 (ops->rewinddir != NULL) + if (ret == -ENOENT && fu->fu_ndx == 0 && fu->fu_lower[1] != NULL) { - ret = ops->rewinddir(um->um_node, fu->fu_lower[1]); + /* Switch to the second file system */ + + fu->fu_ndx = 1; + um = &ui->ui_fs[1]; + + DEBUGASSERT(um != NULL && um->um_node != NULL && + um->um_node->u.i_mops != NULL); + ops = um->um_node->u.i_mops; + + /* Make sure that the second file system directory enumeration + * is rewound to the beginning of the directory. + */ + + if (ops->rewinddir != NULL) + { + ret = ops->rewinddir(um->um_node, fu->fu_lower[1]); + } + + /* Then try the read operation again */ + + ret = ops->readdir(um->um_node, fu->fu_lower[1]); } - /* Then try the read operation again */ + /* 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). + */ - ret = ops->readdir(um->um_node, fu->fu_lower[1]); + duplicate = false; + if (ret >= 0 && fu->fu_ndx == 1 && fu->fu_lower[0] != NULL) + { + /* Get the relative path to the same file on file system 1. + * NOTE: the on any failures we just assume that the filep + * is not a duplicate. + */ + + relpath = unionfs_relpath(fu->fu_relpath, + fu->fu_lower[1]->fd_dir.d_name); + if (relpath) + { + int tmp; + + /* Check if anything exists at this path on file system 1 */ + + um0 = &ui->ui_fs[0]; + tmp = unionfs_trystat(um0->um_node, relpath, + um0->um_prefix, &buf); + if (tmp >= 0) + { + /* There is something there! + * REVISIT: We could allow files and directories to + * have duplicat names. + */ + + duplicate = true; + } + + /* Free the allocated relpath */ + + kmm_free(relpath); + } + } } + while (duplicate); - /* Copy the return information into the diret structure that the + /* Copy the return information into the dirent structure that the * application will see. */ diff --git a/include/nuttx/fs/dirent.h b/include/nuttx/fs/dirent.h index fcc8e25456c..1501b770d4f 100644 --- a/include/nuttx/fs/dirent.h +++ b/include/nuttx/fs/dirent.h @@ -157,6 +157,7 @@ struct fs_dirent_s; /* Forward reference */ struct fs_unionfsdir_s { uint8_t fu_ndx; /* Index of file system being enumerated */ + FAR char *fu_relpath; /* Path being enumerated */ FAR struct fs_dirent_s *fu_lower[2]; /* dirent struct used by contained file system */ }; #endif