Commit 9723961f authored by J. R. Okajima's avatar J. R. Okajima
Browse files

aufs: show-whiteout option



Generally aufs hides the name of whiteouts. But in some cases, to show
them is very useful for users. For instance, creating a new middle layer
(branch) by merging existing layers.

See also the document in this commit.
Signed-off-by: default avatarJ. R. Okajima <hooanon05g@gmail.com>
parent 59444fad
# Copyright (C) 2005-2019 Junjiro R. Okajima
Show Whiteout Mode (shwh)
----------------------------------------------------------------------
Generally aufs hides the name of whiteouts. But in some cases, to show
them is very useful for users. For instance, creating a new middle layer
(branch) by merging existing layers.
(borrowing aufs1 HOW-TO from a user, Michael Towers)
When you have three branches,
- Bottom: 'system', squashfs (underlying base system), read-only
- Middle: 'mods', squashfs, read-only
- Top: 'overlay', ram (tmpfs), read-write
The top layer is loaded at boot time and saved at shutdown, to preserve
the changes made to the system during the session.
When larger changes have been made, or smaller changes have accumulated,
the size of the saved top layer data grows. At this point, it would be
nice to be able to merge the two overlay branches ('mods' and 'overlay')
and rewrite the 'mods' squashfs, clearing the top layer and thus
restoring save and load speed.
This merging is simplified by the use of another aufs mount, of just the
two overlay branches using the 'shwh' option.
# mount -t aufs -o ro,shwh,br:/livesys/overlay=ro+wh:/livesys/mods=rr+wh \
aufs /livesys/merge_union
A merged view of these two branches is then available at
/livesys/merge_union, and the new feature is that the whiteouts are
visible!
Note that in 'shwh' mode the aufs mount must be 'ro', which will disable
writing to all branches. Also the default mode for all branches is 'ro'.
It is now possible to save the combined contents of the two overlay
branches to a new squashfs, e.g.:
# mksquashfs /livesys/merge_union /path/to/newmods.squash
This new squashfs archive can be stored on the boot device and the
initramfs will use it to replace the old one at the next boot.
......@@ -109,6 +109,14 @@ config AUFS_DIRREN
to specify the mount option `dirren.'
See details in aufs.5 and the design documents.
config AUFS_SHWH
bool "Show whiteouts"
help
If you want to make the whiteouts in aufs visible, then enable
this option and specify 'shwh' mount option. Although it may
sounds like philosophy or something, but in technically it
simply shows the name of whiteout with keeping its behaviour.
config AUFS_DEBUG
bool "Debug aufs"
help
......
......@@ -101,6 +101,14 @@ out:
return h_dentry;
}
static int au_test_shwh(struct super_block *sb, const struct qstr *name)
{
if (unlikely(!au_opt_test(au_mntflags(sb), SHWH)
&& !strncmp(name->name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)))
return -EPERM;
return 0;
}
/*
* returns the number of lower positive dentries,
* otherwise an error.
......@@ -120,6 +128,10 @@ int au_lkup_dentry(struct dentry *dentry, aufs_bindex_t btop,
struct super_block *sb;
sb = dentry->d_sb;
err = au_test_shwh(sb, args.name);
if (unlikely(err))
goto out;
err = au_wh_name_alloc(&args.whname, args.name);
if (unlikely(err))
goto out;
......
......@@ -513,12 +513,18 @@ out:
#define AuTestEmpty_WHONLY 1
#define AuTestEmpty_CALLED (1 << 1)
#define AuTestEmpty_SHWH (1 << 2)
#define au_ftest_testempty(flags, name) ((flags) & AuTestEmpty_##name)
#define au_fset_testempty(flags, name) \
do { (flags) |= AuTestEmpty_##name; } while (0)
#define au_fclr_testempty(flags, name) \
do { (flags) &= ~AuTestEmpty_##name; } while (0)
#ifndef CONFIG_AUFS_SHWH
#undef AuTestEmpty_SHWH
#define AuTestEmpty_SHWH 0
#endif
struct test_empty_arg {
struct dir_context ctx;
struct au_nhash *whlist;
......@@ -554,7 +560,8 @@ static int test_empty_cb(struct dir_context *ctx, const char *__name,
namelen -= AUFS_WH_PFX_LEN;
if (!au_nhash_test_known_wh(arg->whlist, name, namelen))
arg->err = au_nhash_append_wh
(arg->whlist, name, namelen, ino, d_type, arg->bindex);
(arg->whlist, name, namelen, ino, d_type, arg->bindex,
au_ftest_testempty(arg->flags, SHWH));
out:
/* smp_mb(); */
......@@ -665,6 +672,8 @@ int au_test_empty_lower(struct dentry *dentry)
arg.flags = 0;
arg.whlist = &whlist;
btop = au_dbtop(dentry);
if (au_opt_test(au_mntflags(dentry->d_sb), SHWH))
au_fset_testempty(arg.flags, SHWH);
test_empty = do_test_empty;
if (au_opt_test(au_mntflags(dentry->d_sb), DIRPERM1))
test_empty = sio_test_empty;
......@@ -704,6 +713,8 @@ int au_test_empty(struct dentry *dentry, struct au_nhash *whlist)
err = 0;
arg.whlist = whlist;
arg.flags = AuTestEmpty_WHONLY;
if (au_opt_test(au_mntflags(dentry->d_sb), SHWH))
au_fset_testempty(arg.flags, SHWH);
btail = au_dbtaildir(dentry);
for (bindex = au_dbtop(dentry); !err && bindex <= btail; bindex++) {
struct dentry *h_dentry;
......
......@@ -43,7 +43,13 @@ struct au_vdir_de {
struct au_vdir_wh {
struct hlist_node wh_hash;
#ifdef CONFIG_AUFS_SHWH
ino_t wh_ino;
aufs_bindex_t wh_bindex;
unsigned char wh_type;
#else
aufs_bindex_t wh_bindex;
#endif
/* caution: packed */
struct au_vdir_destr wh_str;
} __packed;
......@@ -86,7 +92,8 @@ int au_nhash_test_longer_wh(struct au_nhash *whlist, aufs_bindex_t btgt,
int limit);
int au_nhash_test_known_wh(struct au_nhash *whlist, char *name, int nlen);
int au_nhash_append_wh(struct au_nhash *whlist, char *name, int nlen, ino_t ino,
unsigned int d_type, aufs_bindex_t bindex);
unsigned int d_type, aufs_bindex_t bindex,
unsigned char shwh);
void au_vdir_free(struct au_vdir *vdir);
int au_vdir_init(struct file *file);
int au_vdir_fill_de(struct file *file, struct dir_context *ctx);
......
......@@ -245,6 +245,11 @@ static int set_inode(struct inode *inode, struct dentry *dentry)
/* do not set hnotify for whiteouted dirs (SHWH mode) */
flags = au_hi_flags(inode, isdir);
if (au_opt_test(au_mntflags(dentry->d_sb), SHWH)
&& au_ftest_hi(flags, HNOTIFY)
&& dentry->d_name.len > AUFS_WH_PFX_LEN
&& !memcmp(dentry->d_name.name, AUFS_WH_PFX, AUFS_WH_PFX_LEN))
au_fclr_hi(flags, HNOTIFY);
iinfo = au_ii(inode);
iinfo->ii_btop = btop;
iinfo->ii_bbot = btail;
......
......@@ -127,6 +127,16 @@ int au_test_ro(struct super_block *sb, aufs_bindex_t bindex,
int au_test_h_perm(struct inode *h_inode, int mask);
int au_test_h_perm_sio(struct inode *h_inode, int mask);
static inline int au_wh_ino(struct super_block *sb, aufs_bindex_t bindex,
ino_t h_ino, unsigned int d_type, ino_t *ino)
{
#ifdef CONFIG_AUFS_SHWH
return au_ino(sb, bindex, h_ino, d_type, ino);
#else
return 0;
#endif
}
/* i_op.c */
enum {
AuIop_SYMLINK,
......
......@@ -24,6 +24,7 @@ enum {
Opt_trunc_xino, Opt_trunc_xino_v, Opt_notrunc_xino,
Opt_trunc_xino_path, Opt_itrunc_xino,
Opt_trunc_xib, Opt_notrunc_xib,
Opt_shwh, Opt_noshwh,
Opt_plink, Opt_noplink, Opt_list_plink,
Opt_udba,
Opt_dio, Opt_nodio,
......@@ -98,6 +99,11 @@ static match_table_t options = {
{Opt_warn_perm, "warn_perm"},
{Opt_nowarn_perm, "nowarn_perm"},
#ifdef CONFIG_AUFS_SHWH
{Opt_shwh, "shwh"},
#endif
{Opt_noshwh, "noshwh"},
{Opt_dirperm1, "dirperm1"},
{Opt_nodirperm1, "nodirperm1"},
......@@ -580,6 +586,12 @@ static void dump_opts(struct au_opts *opts)
case Opt_notrunc_xib:
AuLabel(notrunc_xib);
break;
case Opt_shwh:
AuLabel(shwh);
break;
case Opt_noshwh:
AuLabel(noshwh);
break;
case Opt_dirperm1:
AuLabel(dirperm1);
break;
......@@ -1089,6 +1101,8 @@ int au_opts_parse(struct super_block *sb, char *str, struct au_opts *opts)
case Opt_noxino:
case Opt_trunc_xib:
case Opt_notrunc_xib:
case Opt_shwh:
case Opt_noshwh:
case Opt_dirperm1:
case Opt_nodirperm1:
case Opt_plink:
......@@ -1313,6 +1327,13 @@ static int au_opt_simple(struct super_block *sb, struct au_opt *opt,
sbinfo->si_rdhash = AUFS_RDHASH_DEF;
break;
case Opt_shwh:
au_opt_set(sbinfo->si_mntflags, SHWH);
break;
case Opt_noshwh:
au_opt_clr(sbinfo->si_mntflags, SHWH);
break;
case Opt_dirperm1:
au_opt_set(sbinfo->si_mntflags, DIRPERM1);
break;
......@@ -1482,6 +1503,8 @@ int au_opts_verify(struct super_block *sb, unsigned long sb_flags,
if (!(sb_flags & SB_RDONLY)) {
if (unlikely(!au_br_writable(au_sbr_perm(sb, 0))))
pr_warn("first branch should be rw\n");
if (unlikely(au_opt_test(sbinfo->si_mntflags, SHWH)))
pr_warn_once("shwh should be used with ro\n");
}
if (au_opt_test((sbinfo->si_mntflags | pending), UDBA_HNOTIFY)
......
......@@ -25,6 +25,7 @@ struct file;
#define AuOpt_UDBA_NONE (1 << 2) /* users direct branch access */
#define AuOpt_UDBA_REVAL (1 << 3)
#define AuOpt_UDBA_HNOTIFY (1 << 4)
#define AuOpt_SHWH (1 << 5) /* show whiteout */
#define AuOpt_PLINK (1 << 6) /* pseudo-link */
#define AuOpt_DIRPERM1 (1 << 7) /* ignore the lower dir's perm
bits */
......@@ -43,6 +44,10 @@ struct file;
#undef AuOpt_DIRREN
#define AuOpt_DIRREN 0
#endif
#ifndef CONFIG_AUFS_SHWH
#undef AuOpt_SHWH
#define AuOpt_SHWH 0
#endif
#define AuOpt_Def (AuOpt_XINO \
| AuOpt_UDBA_REVAL \
......
......@@ -256,6 +256,7 @@ static int aufs_show_options(struct seq_file *m, struct dentry *dentry)
AuBool(TRUNC_XINO, trunc_xino);
AuStr(UDBA, udba);
AuBool(SHWH, shwh);
AuBool(PLINK, plink);
AuBool(DIO, dio);
AuBool(DIRPERM1, dirperm1);
......
......@@ -218,10 +218,20 @@ static int test_known(struct au_nhash *delist, char *name, int nlen)
return 0;
}
static void au_shwh_init_wh(struct au_vdir_wh *wh, ino_t ino,
unsigned char d_type)
{
#ifdef CONFIG_AUFS_SHWH
wh->wh_ino = ino;
wh->wh_type = d_type;
#endif
}
/* ---------------------------------------------------------------------- */
int au_nhash_append_wh(struct au_nhash *whlist, char *name, int nlen, ino_t ino,
unsigned int d_type, aufs_bindex_t bindex)
unsigned int d_type, aufs_bindex_t bindex,
unsigned char shwh)
{
int err;
struct au_vdir_destr *str;
......@@ -237,6 +247,8 @@ int au_nhash_append_wh(struct au_nhash *whlist, char *name, int nlen, ino_t ino,
err = 0;
wh->wh_bindex = bindex;
if (shwh)
au_shwh_init_wh(wh, ino, d_type);
str = &wh->wh_str;
str->len = nlen;
memcpy(str->name, name, nlen);
......@@ -404,12 +416,18 @@ static int reinit_vdir(struct au_vdir *vdir)
#define AuFillVdir_CALLED 1
#define AuFillVdir_WHABLE (1 << 1)
#define AuFillVdir_SHWH (1 << 2)
#define au_ftest_fillvdir(flags, name) ((flags) & AuFillVdir_##name)
#define au_fset_fillvdir(flags, name) \
do { (flags) |= AuFillVdir_##name; } while (0)
#define au_fclr_fillvdir(flags, name) \
do { (flags) &= ~AuFillVdir_##name; } while (0)
#ifndef CONFIG_AUFS_SHWH
#undef AuFillVdir_SHWH
#define AuFillVdir_SHWH 0
#endif
struct fillvdir_arg {
struct dir_context ctx;
struct file *file;
......@@ -429,6 +447,7 @@ static int fillvdir(struct dir_context *ctx, const char *__name, int nlen,
char *name = (void *)__name;
struct super_block *sb;
ino_t ino;
const unsigned char shwh = !!au_ftest_fillvdir(arg->flags, SHWH);
arg->err = 0;
sb = arg->file->f_path.dentry->d_sb;
......@@ -453,12 +472,16 @@ static int fillvdir(struct dir_context *ctx, const char *__name, int nlen,
if (au_nhash_test_known_wh(&arg->whlist, name, nlen))
goto out; /* already whiteouted */
ino = 0; /* just to suppress a warning */
if (shwh)
arg->err = au_wh_ino(sb, arg->bindex, h_ino, d_type,
&ino);
if (!arg->err) {
if (nlen <= AUFS_MAX_NAMELEN + AUFS_WH_PFX_LEN)
d_type = DT_UNKNOWN;
arg->err = au_nhash_append_wh
(&arg->whlist, name, nlen, ino, d_type,
arg->bindex);
arg->bindex, shwh);
}
}
......@@ -470,12 +493,58 @@ out:
return arg->err;
}
static int au_handle_shwh(struct super_block *sb, struct au_vdir *vdir,
struct au_nhash *whlist, struct au_nhash *delist)
{
#ifdef CONFIG_AUFS_SHWH
int err;
unsigned int nh, u;
struct hlist_head *head;
struct au_vdir_wh *pos;
struct hlist_node *n;
char *p, *o;
struct au_vdir_destr *destr;
AuDebugOn(!au_opt_test(au_mntflags(sb), SHWH));
err = -ENOMEM;
o = p = (void *)__get_free_page(GFP_NOFS);
if (unlikely(!p))
goto out;
err = 0;
nh = whlist->nh_num;
memcpy(p, AUFS_WH_PFX, AUFS_WH_PFX_LEN);
p += AUFS_WH_PFX_LEN;
for (u = 0; u < nh; u++) {
head = whlist->nh_head + u;
hlist_for_each_entry_safe(pos, n, head, wh_hash) {
destr = &pos->wh_str;
memcpy(p, destr->name, destr->len);
err = append_de(vdir, o, destr->len + AUFS_WH_PFX_LEN,
pos->wh_ino, pos->wh_type, delist);
if (unlikely(err))
break;
}
}
free_page((unsigned long)o);
out:
AuTraceErr(err);
return err;
#else
return 0;
#endif
}
static int au_do_read_vdir(struct fillvdir_arg *arg)
{
int err;
unsigned int rdhash;
loff_t offset;
aufs_bindex_t bbot, bindex, btop;
unsigned char shwh;
struct file *hf, *file;
struct super_block *sb;
......@@ -495,6 +564,11 @@ static int au_do_read_vdir(struct fillvdir_arg *arg)
err = 0;
arg->flags = 0;
shwh = 0;
if (au_opt_test(au_mntflags(sb), SHWH)) {
shwh = 1;
au_fset_fillvdir(arg->flags, SHWH);
}
btop = au_fbtop(file);
bbot = au_fbbot_dir(file);
for (bindex = btop; !err && bindex <= bbot; bindex++) {
......@@ -509,8 +583,9 @@ static int au_do_read_vdir(struct fillvdir_arg *arg)
arg->bindex = bindex;
au_fclr_fillvdir(arg->flags, WHABLE);
if (bindex != bbot
&& au_br_whable(au_sbr_perm(sb, bindex)))
if (shwh
|| (bindex != bbot
&& au_br_whable(au_sbr_perm(sb, bindex))))
au_fset_fillvdir(arg->flags, WHABLE);
do {
arg->err = 0;
......@@ -526,6 +601,10 @@ static int au_do_read_vdir(struct fillvdir_arg *arg)
* use it since it will cause a lockdep problem.
*/
}
if (!err && shwh)
err = au_handle_shwh(sb, arg->vdir, &arg->whlist, &arg->delist);
au_nhash_wh_free(&arg->whlist);
out_delist:
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment