|
@@ -17,6 +17,7 @@
|
|
#include <linux/statfs.h>
|
|
#include <linux/statfs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/posix_acl_xattr.h>
|
|
#include <linux/posix_acl_xattr.h>
|
|
|
|
+#include <linux/exportfs.h>
|
|
#include "overlayfs.h"
|
|
#include "overlayfs.h"
|
|
|
|
|
|
MODULE_AUTHOR("Miklos Szeredi <miklos@szeredi.hu>");
|
|
MODULE_AUTHOR("Miklos Szeredi <miklos@szeredi.hu>");
|
|
@@ -50,6 +51,11 @@ module_param_named(nfs_export, ovl_nfs_export_def, bool, 0644);
|
|
MODULE_PARM_DESC(ovl_nfs_export_def,
|
|
MODULE_PARM_DESC(ovl_nfs_export_def,
|
|
"Default to on or off for the NFS export feature");
|
|
"Default to on or off for the NFS export feature");
|
|
|
|
|
|
|
|
+static bool ovl_xino_auto_def = IS_ENABLED(CONFIG_OVERLAY_FS_XINO_AUTO);
|
|
|
|
+module_param_named(xino_auto, ovl_xino_auto_def, bool, 0644);
|
|
|
|
+MODULE_PARM_DESC(ovl_xino_auto_def,
|
|
|
|
+ "Auto enable xino feature");
|
|
|
|
+
|
|
static void ovl_entry_stack_free(struct ovl_entry *oe)
|
|
static void ovl_entry_stack_free(struct ovl_entry *oe)
|
|
{
|
|
{
|
|
unsigned int i;
|
|
unsigned int i;
|
|
@@ -236,11 +242,12 @@ static void ovl_free_fs(struct ovl_fs *ofs)
|
|
if (ofs->upperdir_locked)
|
|
if (ofs->upperdir_locked)
|
|
ovl_inuse_unlock(ofs->upper_mnt->mnt_root);
|
|
ovl_inuse_unlock(ofs->upper_mnt->mnt_root);
|
|
mntput(ofs->upper_mnt);
|
|
mntput(ofs->upper_mnt);
|
|
- for (i = 0; i < ofs->numlower; i++) {
|
|
|
|
|
|
+ for (i = 0; i < ofs->numlower; i++)
|
|
mntput(ofs->lower_layers[i].mnt);
|
|
mntput(ofs->lower_layers[i].mnt);
|
|
- free_anon_bdev(ofs->lower_layers[i].pseudo_dev);
|
|
|
|
- }
|
|
|
|
|
|
+ for (i = 0; i < ofs->numlowerfs; i++)
|
|
|
|
+ free_anon_bdev(ofs->lower_fs[i].pseudo_dev);
|
|
kfree(ofs->lower_layers);
|
|
kfree(ofs->lower_layers);
|
|
|
|
+ kfree(ofs->lower_fs);
|
|
|
|
|
|
kfree(ofs->config.lowerdir);
|
|
kfree(ofs->config.lowerdir);
|
|
kfree(ofs->config.upperdir);
|
|
kfree(ofs->config.upperdir);
|
|
@@ -325,6 +332,23 @@ static const char *ovl_redirect_mode_def(void)
|
|
return ovl_redirect_dir_def ? "on" : "off";
|
|
return ovl_redirect_dir_def ? "on" : "off";
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+enum {
|
|
|
|
+ OVL_XINO_OFF,
|
|
|
|
+ OVL_XINO_AUTO,
|
|
|
|
+ OVL_XINO_ON,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static const char * const ovl_xino_str[] = {
|
|
|
|
+ "off",
|
|
|
|
+ "auto",
|
|
|
|
+ "on",
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static inline int ovl_xino_def(void)
|
|
|
|
+{
|
|
|
|
+ return ovl_xino_auto_def ? OVL_XINO_AUTO : OVL_XINO_OFF;
|
|
|
|
+}
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* ovl_show_options
|
|
* ovl_show_options
|
|
*
|
|
*
|
|
@@ -350,6 +374,8 @@ static int ovl_show_options(struct seq_file *m, struct dentry *dentry)
|
|
if (ofs->config.nfs_export != ovl_nfs_export_def)
|
|
if (ofs->config.nfs_export != ovl_nfs_export_def)
|
|
seq_printf(m, ",nfs_export=%s", ofs->config.nfs_export ?
|
|
seq_printf(m, ",nfs_export=%s", ofs->config.nfs_export ?
|
|
"on" : "off");
|
|
"on" : "off");
|
|
|
|
+ if (ofs->config.xino != ovl_xino_def())
|
|
|
|
+ seq_printf(m, ",xino=%s", ovl_xino_str[ofs->config.xino]);
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -384,6 +410,9 @@ enum {
|
|
OPT_INDEX_OFF,
|
|
OPT_INDEX_OFF,
|
|
OPT_NFS_EXPORT_ON,
|
|
OPT_NFS_EXPORT_ON,
|
|
OPT_NFS_EXPORT_OFF,
|
|
OPT_NFS_EXPORT_OFF,
|
|
|
|
+ OPT_XINO_ON,
|
|
|
|
+ OPT_XINO_OFF,
|
|
|
|
+ OPT_XINO_AUTO,
|
|
OPT_ERR,
|
|
OPT_ERR,
|
|
};
|
|
};
|
|
|
|
|
|
@@ -397,6 +426,9 @@ static const match_table_t ovl_tokens = {
|
|
{OPT_INDEX_OFF, "index=off"},
|
|
{OPT_INDEX_OFF, "index=off"},
|
|
{OPT_NFS_EXPORT_ON, "nfs_export=on"},
|
|
{OPT_NFS_EXPORT_ON, "nfs_export=on"},
|
|
{OPT_NFS_EXPORT_OFF, "nfs_export=off"},
|
|
{OPT_NFS_EXPORT_OFF, "nfs_export=off"},
|
|
|
|
+ {OPT_XINO_ON, "xino=on"},
|
|
|
|
+ {OPT_XINO_OFF, "xino=off"},
|
|
|
|
+ {OPT_XINO_AUTO, "xino=auto"},
|
|
{OPT_ERR, NULL}
|
|
{OPT_ERR, NULL}
|
|
};
|
|
};
|
|
|
|
|
|
@@ -511,6 +543,18 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config)
|
|
config->nfs_export = false;
|
|
config->nfs_export = false;
|
|
break;
|
|
break;
|
|
|
|
|
|
|
|
+ case OPT_XINO_ON:
|
|
|
|
+ config->xino = OVL_XINO_ON;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case OPT_XINO_OFF:
|
|
|
|
+ config->xino = OVL_XINO_OFF;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case OPT_XINO_AUTO:
|
|
|
|
+ config->xino = OVL_XINO_AUTO;
|
|
|
|
+ break;
|
|
|
|
+
|
|
default:
|
|
default:
|
|
pr_err("overlayfs: unrecognized mount option \"%s\" or missing value\n", p);
|
|
pr_err("overlayfs: unrecognized mount option \"%s\" or missing value\n", p);
|
|
return -EINVAL;
|
|
return -EINVAL;
|
|
@@ -700,6 +744,7 @@ static int ovl_check_namelen(struct path *path, struct ovl_fs *ofs,
|
|
static int ovl_lower_dir(const char *name, struct path *path,
|
|
static int ovl_lower_dir(const char *name, struct path *path,
|
|
struct ovl_fs *ofs, int *stack_depth, bool *remote)
|
|
struct ovl_fs *ofs, int *stack_depth, bool *remote)
|
|
{
|
|
{
|
|
|
|
+ int fh_type;
|
|
int err;
|
|
int err;
|
|
|
|
|
|
err = ovl_mount_dir_noesc(name, path);
|
|
err = ovl_mount_dir_noesc(name, path);
|
|
@@ -719,15 +764,19 @@ static int ovl_lower_dir(const char *name, struct path *path,
|
|
* The inodes index feature and NFS export need to encode and decode
|
|
* The inodes index feature and NFS export need to encode and decode
|
|
* file handles, so they require that all layers support them.
|
|
* file handles, so they require that all layers support them.
|
|
*/
|
|
*/
|
|
|
|
+ fh_type = ovl_can_decode_fh(path->dentry->d_sb);
|
|
if ((ofs->config.nfs_export ||
|
|
if ((ofs->config.nfs_export ||
|
|
- (ofs->config.index && ofs->config.upperdir)) &&
|
|
|
|
- !ovl_can_decode_fh(path->dentry->d_sb)) {
|
|
|
|
|
|
+ (ofs->config.index && ofs->config.upperdir)) && !fh_type) {
|
|
ofs->config.index = false;
|
|
ofs->config.index = false;
|
|
ofs->config.nfs_export = false;
|
|
ofs->config.nfs_export = false;
|
|
pr_warn("overlayfs: fs on '%s' does not support file handles, falling back to index=off,nfs_export=off.\n",
|
|
pr_warn("overlayfs: fs on '%s' does not support file handles, falling back to index=off,nfs_export=off.\n",
|
|
name);
|
|
name);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /* Check if lower fs has 32bit inode numbers */
|
|
|
|
+ if (fh_type != FILEID_INO32_GEN)
|
|
|
|
+ ofs->xino_bits = 0;
|
|
|
|
+
|
|
return 0;
|
|
return 0;
|
|
|
|
|
|
out_put:
|
|
out_put:
|
|
@@ -951,6 +1000,7 @@ static int ovl_make_workdir(struct ovl_fs *ofs, struct path *workpath)
|
|
{
|
|
{
|
|
struct vfsmount *mnt = ofs->upper_mnt;
|
|
struct vfsmount *mnt = ofs->upper_mnt;
|
|
struct dentry *temp;
|
|
struct dentry *temp;
|
|
|
|
+ int fh_type;
|
|
int err;
|
|
int err;
|
|
|
|
|
|
err = mnt_want_write(mnt);
|
|
err = mnt_want_write(mnt);
|
|
@@ -1000,12 +1050,16 @@ static int ovl_make_workdir(struct ovl_fs *ofs, struct path *workpath)
|
|
}
|
|
}
|
|
|
|
|
|
/* Check if upper/work fs supports file handles */
|
|
/* Check if upper/work fs supports file handles */
|
|
- if (ofs->config.index &&
|
|
|
|
- !ovl_can_decode_fh(ofs->workdir->d_sb)) {
|
|
|
|
|
|
+ fh_type = ovl_can_decode_fh(ofs->workdir->d_sb);
|
|
|
|
+ if (ofs->config.index && !fh_type) {
|
|
ofs->config.index = false;
|
|
ofs->config.index = false;
|
|
pr_warn("overlayfs: upper fs does not support file handles, falling back to index=off.\n");
|
|
pr_warn("overlayfs: upper fs does not support file handles, falling back to index=off.\n");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /* Check if upper fs has 32bit inode numbers */
|
|
|
|
+ if (fh_type != FILEID_INO32_GEN)
|
|
|
|
+ ofs->xino_bits = 0;
|
|
|
|
+
|
|
/* NFS export of r/w mount depends on index */
|
|
/* NFS export of r/w mount depends on index */
|
|
if (ofs->config.nfs_export && !ofs->config.index) {
|
|
if (ofs->config.nfs_export && !ofs->config.index) {
|
|
pr_warn("overlayfs: NFS export requires \"index=on\", falling back to nfs_export=off.\n");
|
|
pr_warn("overlayfs: NFS export requires \"index=on\", falling back to nfs_export=off.\n");
|
|
@@ -1108,6 +1162,35 @@ out:
|
|
return err;
|
|
return err;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+/* Get a unique fsid for the layer */
|
|
|
|
+static int ovl_get_fsid(struct ovl_fs *ofs, struct super_block *sb)
|
|
|
|
+{
|
|
|
|
+ unsigned int i;
|
|
|
|
+ dev_t dev;
|
|
|
|
+ int err;
|
|
|
|
+
|
|
|
|
+ /* fsid 0 is reserved for upper fs even with non upper overlay */
|
|
|
|
+ if (ofs->upper_mnt && ofs->upper_mnt->mnt_sb == sb)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ for (i = 0; i < ofs->numlowerfs; i++) {
|
|
|
|
+ if (ofs->lower_fs[i].sb == sb)
|
|
|
|
+ return i + 1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ err = get_anon_bdev(&dev);
|
|
|
|
+ if (err) {
|
|
|
|
+ pr_err("overlayfs: failed to get anonymous bdev for lowerpath\n");
|
|
|
|
+ return err;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ofs->lower_fs[ofs->numlowerfs].sb = sb;
|
|
|
|
+ ofs->lower_fs[ofs->numlowerfs].pseudo_dev = dev;
|
|
|
|
+ ofs->numlowerfs++;
|
|
|
|
+
|
|
|
|
+ return ofs->numlowerfs;
|
|
|
|
+}
|
|
|
|
+
|
|
static int ovl_get_lower_layers(struct ovl_fs *ofs, struct path *stack,
|
|
static int ovl_get_lower_layers(struct ovl_fs *ofs, struct path *stack,
|
|
unsigned int numlower)
|
|
unsigned int numlower)
|
|
{
|
|
{
|
|
@@ -1119,23 +1202,27 @@ static int ovl_get_lower_layers(struct ovl_fs *ofs, struct path *stack,
|
|
GFP_KERNEL);
|
|
GFP_KERNEL);
|
|
if (ofs->lower_layers == NULL)
|
|
if (ofs->lower_layers == NULL)
|
|
goto out;
|
|
goto out;
|
|
|
|
+
|
|
|
|
+ ofs->lower_fs = kcalloc(numlower, sizeof(struct ovl_sb),
|
|
|
|
+ GFP_KERNEL);
|
|
|
|
+ if (ofs->lower_fs == NULL)
|
|
|
|
+ goto out;
|
|
|
|
+
|
|
for (i = 0; i < numlower; i++) {
|
|
for (i = 0; i < numlower; i++) {
|
|
struct vfsmount *mnt;
|
|
struct vfsmount *mnt;
|
|
- dev_t dev;
|
|
|
|
|
|
+ int fsid;
|
|
|
|
|
|
- err = get_anon_bdev(&dev);
|
|
|
|
- if (err) {
|
|
|
|
- pr_err("overlayfs: failed to get anonymous bdev for lowerpath\n");
|
|
|
|
|
|
+ err = fsid = ovl_get_fsid(ofs, stack[i].mnt->mnt_sb);
|
|
|
|
+ if (err < 0)
|
|
goto out;
|
|
goto out;
|
|
- }
|
|
|
|
|
|
|
|
mnt = clone_private_mount(&stack[i]);
|
|
mnt = clone_private_mount(&stack[i]);
|
|
err = PTR_ERR(mnt);
|
|
err = PTR_ERR(mnt);
|
|
if (IS_ERR(mnt)) {
|
|
if (IS_ERR(mnt)) {
|
|
pr_err("overlayfs: failed to clone lowerpath\n");
|
|
pr_err("overlayfs: failed to clone lowerpath\n");
|
|
- free_anon_bdev(dev);
|
|
|
|
goto out;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
+
|
|
/*
|
|
/*
|
|
* Make lower layers R/O. That way fchmod/fchown on lower file
|
|
* Make lower layers R/O. That way fchmod/fchown on lower file
|
|
* will fail instead of modifying lower fs.
|
|
* will fail instead of modifying lower fs.
|
|
@@ -1143,16 +1230,41 @@ static int ovl_get_lower_layers(struct ovl_fs *ofs, struct path *stack,
|
|
mnt->mnt_flags |= MNT_READONLY | MNT_NOATIME;
|
|
mnt->mnt_flags |= MNT_READONLY | MNT_NOATIME;
|
|
|
|
|
|
ofs->lower_layers[ofs->numlower].mnt = mnt;
|
|
ofs->lower_layers[ofs->numlower].mnt = mnt;
|
|
- ofs->lower_layers[ofs->numlower].pseudo_dev = dev;
|
|
|
|
ofs->lower_layers[ofs->numlower].idx = i + 1;
|
|
ofs->lower_layers[ofs->numlower].idx = i + 1;
|
|
|
|
+ ofs->lower_layers[ofs->numlower].fsid = fsid;
|
|
|
|
+ if (fsid) {
|
|
|
|
+ ofs->lower_layers[ofs->numlower].fs =
|
|
|
|
+ &ofs->lower_fs[fsid - 1];
|
|
|
|
+ }
|
|
ofs->numlower++;
|
|
ofs->numlower++;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * When all layers on same fs, overlay can use real inode numbers.
|
|
|
|
+ * With mount option "xino=on", mounter declares that there are enough
|
|
|
|
+ * free high bits in underlying fs to hold the unique fsid.
|
|
|
|
+ * If overlayfs does encounter underlying inodes using the high xino
|
|
|
|
+ * bits reserved for fsid, it emits a warning and uses the original
|
|
|
|
+ * inode number.
|
|
|
|
+ */
|
|
|
|
+ if (!ofs->numlowerfs || (ofs->numlowerfs == 1 && !ofs->upper_mnt)) {
|
|
|
|
+ ofs->xino_bits = 0;
|
|
|
|
+ ofs->config.xino = OVL_XINO_OFF;
|
|
|
|
+ } else if (ofs->config.xino == OVL_XINO_ON && !ofs->xino_bits) {
|
|
|
|
+ /*
|
|
|
|
+ * This is a roundup of number of bits needed for numlowerfs+1
|
|
|
|
+ * (i.e. ilog2(numlowerfs+1 - 1) + 1). fsid 0 is reserved for
|
|
|
|
+ * upper fs even with non upper overlay.
|
|
|
|
+ */
|
|
|
|
+ BUILD_BUG_ON(ilog2(OVL_MAX_STACK) > 31);
|
|
|
|
+ ofs->xino_bits = ilog2(ofs->numlowerfs) + 1;
|
|
|
|
+ }
|
|
|
|
|
|
- /* Check if all lower layers are on same sb */
|
|
|
|
- if (i == 0)
|
|
|
|
- ofs->same_sb = mnt->mnt_sb;
|
|
|
|
- else if (ofs->same_sb != mnt->mnt_sb)
|
|
|
|
- ofs->same_sb = NULL;
|
|
|
|
|
|
+ if (ofs->xino_bits) {
|
|
|
|
+ pr_info("overlayfs: \"xino\" feature enabled using %d upper inode bits.\n",
|
|
|
|
+ ofs->xino_bits);
|
|
}
|
|
}
|
|
|
|
+
|
|
err = 0;
|
|
err = 0;
|
|
out:
|
|
out:
|
|
return err;
|
|
return err;
|
|
@@ -1263,6 +1375,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
|
|
|
|
|
|
ofs->config.index = ovl_index_def;
|
|
ofs->config.index = ovl_index_def;
|
|
ofs->config.nfs_export = ovl_nfs_export_def;
|
|
ofs->config.nfs_export = ovl_nfs_export_def;
|
|
|
|
+ ofs->config.xino = ovl_xino_def();
|
|
err = ovl_parse_opt((char *) data, &ofs->config);
|
|
err = ovl_parse_opt((char *) data, &ofs->config);
|
|
if (err)
|
|
if (err)
|
|
goto out_err;
|
|
goto out_err;
|
|
@@ -1276,6 +1389,10 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
|
|
|
|
|
|
sb->s_stack_depth = 0;
|
|
sb->s_stack_depth = 0;
|
|
sb->s_maxbytes = MAX_LFS_FILESIZE;
|
|
sb->s_maxbytes = MAX_LFS_FILESIZE;
|
|
|
|
+ /* Assume underlaying fs uses 32bit inodes unless proven otherwise */
|
|
|
|
+ if (ofs->config.xino != OVL_XINO_OFF)
|
|
|
|
+ ofs->xino_bits = BITS_PER_LONG - 32;
|
|
|
|
+
|
|
if (ofs->config.upperdir) {
|
|
if (ofs->config.upperdir) {
|
|
if (!ofs->config.workdir) {
|
|
if (!ofs->config.workdir) {
|
|
pr_err("overlayfs: missing 'workdir'\n");
|
|
pr_err("overlayfs: missing 'workdir'\n");
|
|
@@ -1305,8 +1422,6 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
|
|
/* If the upper fs is nonexistent, we mark overlayfs r/o too */
|
|
/* If the upper fs is nonexistent, we mark overlayfs r/o too */
|
|
if (!ofs->upper_mnt)
|
|
if (!ofs->upper_mnt)
|
|
sb->s_flags |= SB_RDONLY;
|
|
sb->s_flags |= SB_RDONLY;
|
|
- else if (ofs->upper_mnt->mnt_sb != ofs->same_sb)
|
|
|
|
- ofs->same_sb = NULL;
|
|
|
|
|
|
|
|
if (!(ovl_force_readonly(ofs)) && ofs->config.index) {
|
|
if (!(ovl_force_readonly(ofs)) && ofs->config.index) {
|
|
err = ovl_get_indexdir(ofs, oe, &upperpath);
|
|
err = ovl_get_indexdir(ofs, oe, &upperpath);
|