forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
896 lines
20 KiB
896 lines
20 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* Copyright (C) 2005-2020 Junjiro R. Okajima |
|
* |
|
* This program, aufs is free software; you can redistribute it and/or modify |
|
* it under the terms of the GNU General Public License as published by |
|
* the Free Software Foundation; either version 2 of the License, or |
|
* (at your option) any later version. |
|
* |
|
* This program is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
* GNU General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU General Public License |
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
*/ |
|
|
|
/* |
|
* virtual or vertical directory |
|
*/ |
|
|
|
#include <linux/iversion.h> |
|
#include "aufs.h" |
|
|
|
static unsigned int calc_size(int nlen) |
|
{ |
|
return ALIGN(sizeof(struct au_vdir_de) + nlen, sizeof(ino_t)); |
|
} |
|
|
|
static int set_deblk_end(union au_vdir_deblk_p *p, |
|
union au_vdir_deblk_p *deblk_end) |
|
{ |
|
if (calc_size(0) <= deblk_end->deblk - p->deblk) { |
|
p->de->de_str.len = 0; |
|
/* smp_mb(); */ |
|
return 0; |
|
} |
|
return -1; /* error */ |
|
} |
|
|
|
/* returns true or false */ |
|
static int is_deblk_end(union au_vdir_deblk_p *p, |
|
union au_vdir_deblk_p *deblk_end) |
|
{ |
|
if (calc_size(0) <= deblk_end->deblk - p->deblk) |
|
return !p->de->de_str.len; |
|
return 1; |
|
} |
|
|
|
static unsigned char *last_deblk(struct au_vdir *vdir) |
|
{ |
|
return vdir->vd_deblk[vdir->vd_nblk - 1]; |
|
} |
|
|
|
/* ---------------------------------------------------------------------- */ |
|
|
|
/* estimate the appropriate size for name hash table */ |
|
unsigned int au_rdhash_est(loff_t sz) |
|
{ |
|
unsigned int n; |
|
|
|
n = UINT_MAX; |
|
sz >>= 10; |
|
if (sz < n) |
|
n = sz; |
|
if (sz < AUFS_RDHASH_DEF) |
|
n = AUFS_RDHASH_DEF; |
|
/* pr_info("n %u\n", n); */ |
|
return n; |
|
} |
|
|
|
/* |
|
* the allocated memory has to be freed by |
|
* au_nhash_wh_free() or au_nhash_de_free(). |
|
*/ |
|
int au_nhash_alloc(struct au_nhash *nhash, unsigned int num_hash, gfp_t gfp) |
|
{ |
|
struct hlist_head *head; |
|
unsigned int u; |
|
size_t sz; |
|
|
|
sz = sizeof(*nhash->nh_head) * num_hash; |
|
head = kmalloc(sz, gfp); |
|
if (head) { |
|
nhash->nh_num = num_hash; |
|
nhash->nh_head = head; |
|
for (u = 0; u < num_hash; u++) |
|
INIT_HLIST_HEAD(head++); |
|
return 0; /* success */ |
|
} |
|
|
|
return -ENOMEM; |
|
} |
|
|
|
static void nhash_count(struct hlist_head *head) |
|
{ |
|
#if 0 /* debugging */ |
|
unsigned long n; |
|
struct hlist_node *pos; |
|
|
|
n = 0; |
|
hlist_for_each(pos, head) |
|
n++; |
|
pr_info("%lu\n", n); |
|
#endif |
|
} |
|
|
|
static void au_nhash_wh_do_free(struct hlist_head *head) |
|
{ |
|
struct au_vdir_wh *pos; |
|
struct hlist_node *node; |
|
|
|
hlist_for_each_entry_safe(pos, node, head, wh_hash) |
|
au_kfree_rcu(pos); |
|
} |
|
|
|
static void au_nhash_de_do_free(struct hlist_head *head) |
|
{ |
|
struct au_vdir_dehstr *pos; |
|
struct hlist_node *node; |
|
|
|
hlist_for_each_entry_safe(pos, node, head, hash) |
|
au_cache_free_vdir_dehstr(pos); |
|
} |
|
|
|
static void au_nhash_do_free(struct au_nhash *nhash, |
|
void (*free)(struct hlist_head *head)) |
|
{ |
|
unsigned int n; |
|
struct hlist_head *head; |
|
|
|
n = nhash->nh_num; |
|
if (!n) |
|
return; |
|
|
|
head = nhash->nh_head; |
|
while (n-- > 0) { |
|
nhash_count(head); |
|
free(head++); |
|
} |
|
au_kfree_try_rcu(nhash->nh_head); |
|
} |
|
|
|
void au_nhash_wh_free(struct au_nhash *whlist) |
|
{ |
|
au_nhash_do_free(whlist, au_nhash_wh_do_free); |
|
} |
|
|
|
static void au_nhash_de_free(struct au_nhash *delist) |
|
{ |
|
au_nhash_do_free(delist, au_nhash_de_do_free); |
|
} |
|
|
|
/* ---------------------------------------------------------------------- */ |
|
|
|
int au_nhash_test_longer_wh(struct au_nhash *whlist, aufs_bindex_t btgt, |
|
int limit) |
|
{ |
|
int num; |
|
unsigned int u, n; |
|
struct hlist_head *head; |
|
struct au_vdir_wh *pos; |
|
|
|
num = 0; |
|
n = whlist->nh_num; |
|
head = whlist->nh_head; |
|
for (u = 0; u < n; u++, head++) |
|
hlist_for_each_entry(pos, head, wh_hash) |
|
if (pos->wh_bindex == btgt && ++num > limit) |
|
return 1; |
|
return 0; |
|
} |
|
|
|
static struct hlist_head *au_name_hash(struct au_nhash *nhash, |
|
unsigned char *name, |
|
unsigned int len) |
|
{ |
|
unsigned int v; |
|
/* const unsigned int magic_bit = 12; */ |
|
|
|
AuDebugOn(!nhash->nh_num || !nhash->nh_head); |
|
|
|
v = 0; |
|
if (len > 8) |
|
len = 8; |
|
while (len--) |
|
v += *name++; |
|
/* v = hash_long(v, magic_bit); */ |
|
v %= nhash->nh_num; |
|
return nhash->nh_head + v; |
|
} |
|
|
|
static int au_nhash_test_name(struct au_vdir_destr *str, const char *name, |
|
int nlen) |
|
{ |
|
return str->len == nlen && !memcmp(str->name, name, nlen); |
|
} |
|
|
|
/* returns found or not */ |
|
int au_nhash_test_known_wh(struct au_nhash *whlist, char *name, int nlen) |
|
{ |
|
struct hlist_head *head; |
|
struct au_vdir_wh *pos; |
|
struct au_vdir_destr *str; |
|
|
|
head = au_name_hash(whlist, name, nlen); |
|
hlist_for_each_entry(pos, head, wh_hash) { |
|
str = &pos->wh_str; |
|
AuDbg("%.*s\n", str->len, str->name); |
|
if (au_nhash_test_name(str, name, nlen)) |
|
return 1; |
|
} |
|
return 0; |
|
} |
|
|
|
/* returns found(true) or not */ |
|
static int test_known(struct au_nhash *delist, char *name, int nlen) |
|
{ |
|
struct hlist_head *head; |
|
struct au_vdir_dehstr *pos; |
|
struct au_vdir_destr *str; |
|
|
|
head = au_name_hash(delist, name, nlen); |
|
hlist_for_each_entry(pos, head, hash) { |
|
str = pos->str; |
|
AuDbg("%.*s\n", str->len, str->name); |
|
if (au_nhash_test_name(str, name, nlen)) |
|
return 1; |
|
} |
|
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 char shwh) |
|
{ |
|
int err; |
|
struct au_vdir_destr *str; |
|
struct au_vdir_wh *wh; |
|
|
|
AuDbg("%.*s\n", nlen, name); |
|
AuDebugOn(!whlist->nh_num || !whlist->nh_head); |
|
|
|
err = -ENOMEM; |
|
wh = kmalloc(sizeof(*wh) + nlen, GFP_NOFS); |
|
if (unlikely(!wh)) |
|
goto out; |
|
|
|
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); |
|
hlist_add_head(&wh->wh_hash, au_name_hash(whlist, name, nlen)); |
|
/* smp_mb(); */ |
|
|
|
out: |
|
return err; |
|
} |
|
|
|
static int append_deblk(struct au_vdir *vdir) |
|
{ |
|
int err; |
|
unsigned long ul; |
|
const unsigned int deblk_sz = vdir->vd_deblk_sz; |
|
union au_vdir_deblk_p p, deblk_end; |
|
unsigned char **o; |
|
|
|
err = -ENOMEM; |
|
o = au_krealloc(vdir->vd_deblk, sizeof(*o) * (vdir->vd_nblk + 1), |
|
GFP_NOFS, /*may_shrink*/0); |
|
if (unlikely(!o)) |
|
goto out; |
|
|
|
vdir->vd_deblk = o; |
|
p.deblk = kmalloc(deblk_sz, GFP_NOFS); |
|
if (p.deblk) { |
|
ul = vdir->vd_nblk++; |
|
vdir->vd_deblk[ul] = p.deblk; |
|
vdir->vd_last.ul = ul; |
|
vdir->vd_last.p.deblk = p.deblk; |
|
deblk_end.deblk = p.deblk + deblk_sz; |
|
err = set_deblk_end(&p, &deblk_end); |
|
} |
|
|
|
out: |
|
return err; |
|
} |
|
|
|
static int append_de(struct au_vdir *vdir, char *name, int nlen, ino_t ino, |
|
unsigned int d_type, struct au_nhash *delist) |
|
{ |
|
int err; |
|
unsigned int sz; |
|
const unsigned int deblk_sz = vdir->vd_deblk_sz; |
|
union au_vdir_deblk_p p, *room, deblk_end; |
|
struct au_vdir_dehstr *dehstr; |
|
|
|
p.deblk = last_deblk(vdir); |
|
deblk_end.deblk = p.deblk + deblk_sz; |
|
room = &vdir->vd_last.p; |
|
AuDebugOn(room->deblk < p.deblk || deblk_end.deblk <= room->deblk |
|
|| !is_deblk_end(room, &deblk_end)); |
|
|
|
sz = calc_size(nlen); |
|
if (unlikely(sz > deblk_end.deblk - room->deblk)) { |
|
err = append_deblk(vdir); |
|
if (unlikely(err)) |
|
goto out; |
|
|
|
p.deblk = last_deblk(vdir); |
|
deblk_end.deblk = p.deblk + deblk_sz; |
|
/* smp_mb(); */ |
|
AuDebugOn(room->deblk != p.deblk); |
|
} |
|
|
|
err = -ENOMEM; |
|
dehstr = au_cache_alloc_vdir_dehstr(); |
|
if (unlikely(!dehstr)) |
|
goto out; |
|
|
|
dehstr->str = &room->de->de_str; |
|
hlist_add_head(&dehstr->hash, au_name_hash(delist, name, nlen)); |
|
room->de->de_ino = ino; |
|
room->de->de_type = d_type; |
|
room->de->de_str.len = nlen; |
|
memcpy(room->de->de_str.name, name, nlen); |
|
|
|
err = 0; |
|
room->deblk += sz; |
|
if (unlikely(set_deblk_end(room, &deblk_end))) |
|
err = append_deblk(vdir); |
|
/* smp_mb(); */ |
|
|
|
out: |
|
return err; |
|
} |
|
|
|
/* ---------------------------------------------------------------------- */ |
|
|
|
void au_vdir_free(struct au_vdir *vdir) |
|
{ |
|
unsigned char **deblk; |
|
|
|
deblk = vdir->vd_deblk; |
|
while (vdir->vd_nblk--) |
|
au_kfree_try_rcu(*deblk++); |
|
au_kfree_try_rcu(vdir->vd_deblk); |
|
au_cache_free_vdir(vdir); |
|
} |
|
|
|
static struct au_vdir *alloc_vdir(struct file *file) |
|
{ |
|
struct au_vdir *vdir; |
|
struct super_block *sb; |
|
int err; |
|
|
|
sb = file->f_path.dentry->d_sb; |
|
SiMustAnyLock(sb); |
|
|
|
err = -ENOMEM; |
|
vdir = au_cache_alloc_vdir(); |
|
if (unlikely(!vdir)) |
|
goto out; |
|
|
|
vdir->vd_deblk = kzalloc(sizeof(*vdir->vd_deblk), GFP_NOFS); |
|
if (unlikely(!vdir->vd_deblk)) |
|
goto out_free; |
|
|
|
vdir->vd_deblk_sz = au_sbi(sb)->si_rdblk; |
|
if (!vdir->vd_deblk_sz) { |
|
/* estimate the appropriate size for deblk */ |
|
vdir->vd_deblk_sz = au_dir_size(file, /*dentry*/NULL); |
|
/* pr_info("vd_deblk_sz %u\n", vdir->vd_deblk_sz); */ |
|
} |
|
vdir->vd_nblk = 0; |
|
vdir->vd_version = 0; |
|
vdir->vd_jiffy = 0; |
|
err = append_deblk(vdir); |
|
if (!err) |
|
return vdir; /* success */ |
|
|
|
au_kfree_try_rcu(vdir->vd_deblk); |
|
|
|
out_free: |
|
au_cache_free_vdir(vdir); |
|
out: |
|
vdir = ERR_PTR(err); |
|
return vdir; |
|
} |
|
|
|
static int reinit_vdir(struct au_vdir *vdir) |
|
{ |
|
int err; |
|
union au_vdir_deblk_p p, deblk_end; |
|
|
|
while (vdir->vd_nblk > 1) { |
|
au_kfree_try_rcu(vdir->vd_deblk[vdir->vd_nblk - 1]); |
|
/* vdir->vd_deblk[vdir->vd_nblk - 1] = NULL; */ |
|
vdir->vd_nblk--; |
|
} |
|
p.deblk = vdir->vd_deblk[0]; |
|
deblk_end.deblk = p.deblk + vdir->vd_deblk_sz; |
|
err = set_deblk_end(&p, &deblk_end); |
|
/* keep vd_dblk_sz */ |
|
vdir->vd_last.ul = 0; |
|
vdir->vd_last.p.deblk = vdir->vd_deblk[0]; |
|
vdir->vd_version = 0; |
|
vdir->vd_jiffy = 0; |
|
/* smp_mb(); */ |
|
return err; |
|
} |
|
|
|
/* ---------------------------------------------------------------------- */ |
|
|
|
#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; |
|
struct au_vdir *vdir; |
|
struct au_nhash delist; |
|
struct au_nhash whlist; |
|
aufs_bindex_t bindex; |
|
unsigned int flags; |
|
int err; |
|
}; |
|
|
|
static int fillvdir(struct dir_context *ctx, const char *__name, int nlen, |
|
loff_t offset __maybe_unused, u64 h_ino, |
|
unsigned int d_type) |
|
{ |
|
struct fillvdir_arg *arg = container_of(ctx, struct fillvdir_arg, ctx); |
|
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; |
|
au_fset_fillvdir(arg->flags, CALLED); |
|
/* smp_mb(); */ |
|
if (nlen <= AUFS_WH_PFX_LEN |
|
|| memcmp(name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)) { |
|
if (test_known(&arg->delist, name, nlen) |
|
|| au_nhash_test_known_wh(&arg->whlist, name, nlen)) |
|
goto out; /* already exists or whiteouted */ |
|
|
|
arg->err = au_ino(sb, arg->bindex, h_ino, d_type, &ino); |
|
if (!arg->err) { |
|
if (unlikely(nlen > AUFS_MAX_NAMELEN)) |
|
d_type = DT_UNKNOWN; |
|
arg->err = append_de(arg->vdir, name, nlen, ino, |
|
d_type, &arg->delist); |
|
} |
|
} else if (au_ftest_fillvdir(arg->flags, WHABLE)) { |
|
name += AUFS_WH_PFX_LEN; |
|
nlen -= AUFS_WH_PFX_LEN; |
|
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, shwh); |
|
} |
|
} |
|
|
|
out: |
|
if (!arg->err) |
|
arg->vdir->vd_jiffy = jiffies; |
|
/* smp_mb(); */ |
|
AuTraceErr(arg->err); |
|
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; |
|
|
|
file = arg->file; |
|
sb = file->f_path.dentry->d_sb; |
|
SiMustAnyLock(sb); |
|
|
|
rdhash = au_sbi(sb)->si_rdhash; |
|
if (!rdhash) |
|
rdhash = au_rdhash_est(au_dir_size(file, /*dentry*/NULL)); |
|
err = au_nhash_alloc(&arg->delist, rdhash, GFP_NOFS); |
|
if (unlikely(err)) |
|
goto out; |
|
err = au_nhash_alloc(&arg->whlist, rdhash, GFP_NOFS); |
|
if (unlikely(err)) |
|
goto out_delist; |
|
|
|
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++) { |
|
hf = au_hf_dir(file, bindex); |
|
if (!hf) |
|
continue; |
|
|
|
offset = vfsub_llseek(hf, 0, SEEK_SET); |
|
err = offset; |
|
if (unlikely(offset)) |
|
break; |
|
|
|
arg->bindex = bindex; |
|
au_fclr_fillvdir(arg->flags, WHABLE); |
|
if (shwh |
|
|| (bindex != bbot |
|
&& au_br_whable(au_sbr_perm(sb, bindex)))) |
|
au_fset_fillvdir(arg->flags, WHABLE); |
|
do { |
|
arg->err = 0; |
|
au_fclr_fillvdir(arg->flags, CALLED); |
|
/* smp_mb(); */ |
|
err = vfsub_iterate_dir(hf, &arg->ctx); |
|
if (err >= 0) |
|
err = arg->err; |
|
} while (!err && au_ftest_fillvdir(arg->flags, CALLED)); |
|
|
|
/* |
|
* dir_relax() may be good for concurrency, but aufs should not |
|
* 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: |
|
au_nhash_de_free(&arg->delist); |
|
out: |
|
return err; |
|
} |
|
|
|
static int read_vdir(struct file *file, int may_read) |
|
{ |
|
int err; |
|
unsigned long expire; |
|
unsigned char do_read; |
|
struct fillvdir_arg arg = { |
|
.ctx = { |
|
.actor = fillvdir |
|
} |
|
}; |
|
struct inode *inode; |
|
struct au_vdir *vdir, *allocated; |
|
|
|
err = 0; |
|
inode = file_inode(file); |
|
IMustLock(inode); |
|
IiMustWriteLock(inode); |
|
SiMustAnyLock(inode->i_sb); |
|
|
|
allocated = NULL; |
|
do_read = 0; |
|
expire = au_sbi(inode->i_sb)->si_rdcache; |
|
vdir = au_ivdir(inode); |
|
if (!vdir) { |
|
do_read = 1; |
|
vdir = alloc_vdir(file); |
|
err = PTR_ERR(vdir); |
|
if (IS_ERR(vdir)) |
|
goto out; |
|
err = 0; |
|
allocated = vdir; |
|
} else if (may_read |
|
&& (!inode_eq_iversion(inode, vdir->vd_version) |
|
|| time_after(jiffies, vdir->vd_jiffy + expire))) { |
|
do_read = 1; |
|
err = reinit_vdir(vdir); |
|
if (unlikely(err)) |
|
goto out; |
|
} |
|
|
|
if (!do_read) |
|
return 0; /* success */ |
|
|
|
arg.file = file; |
|
arg.vdir = vdir; |
|
err = au_do_read_vdir(&arg); |
|
if (!err) { |
|
/* file->f_pos = 0; */ /* todo: ctx->pos? */ |
|
vdir->vd_version = inode_query_iversion(inode); |
|
vdir->vd_last.ul = 0; |
|
vdir->vd_last.p.deblk = vdir->vd_deblk[0]; |
|
if (allocated) |
|
au_set_ivdir(inode, allocated); |
|
} else if (allocated) |
|
au_vdir_free(allocated); |
|
|
|
out: |
|
return err; |
|
} |
|
|
|
static int copy_vdir(struct au_vdir *tgt, struct au_vdir *src) |
|
{ |
|
int err, rerr; |
|
unsigned long ul, n; |
|
const unsigned int deblk_sz = src->vd_deblk_sz; |
|
|
|
AuDebugOn(tgt->vd_nblk != 1); |
|
|
|
err = -ENOMEM; |
|
if (tgt->vd_nblk < src->vd_nblk) { |
|
unsigned char **p; |
|
|
|
p = au_krealloc(tgt->vd_deblk, sizeof(*p) * src->vd_nblk, |
|
GFP_NOFS, /*may_shrink*/0); |
|
if (unlikely(!p)) |
|
goto out; |
|
tgt->vd_deblk = p; |
|
} |
|
|
|
if (tgt->vd_deblk_sz != deblk_sz) { |
|
unsigned char *p; |
|
|
|
tgt->vd_deblk_sz = deblk_sz; |
|
p = au_krealloc(tgt->vd_deblk[0], deblk_sz, GFP_NOFS, |
|
/*may_shrink*/1); |
|
if (unlikely(!p)) |
|
goto out; |
|
tgt->vd_deblk[0] = p; |
|
} |
|
memcpy(tgt->vd_deblk[0], src->vd_deblk[0], deblk_sz); |
|
tgt->vd_version = src->vd_version; |
|
tgt->vd_jiffy = src->vd_jiffy; |
|
|
|
n = src->vd_nblk; |
|
for (ul = 1; ul < n; ul++) { |
|
tgt->vd_deblk[ul] = kmemdup(src->vd_deblk[ul], deblk_sz, |
|
GFP_NOFS); |
|
if (unlikely(!tgt->vd_deblk[ul])) |
|
goto out; |
|
tgt->vd_nblk++; |
|
} |
|
tgt->vd_nblk = n; |
|
tgt->vd_last.ul = tgt->vd_last.ul; |
|
tgt->vd_last.p.deblk = tgt->vd_deblk[tgt->vd_last.ul]; |
|
tgt->vd_last.p.deblk += src->vd_last.p.deblk |
|
- src->vd_deblk[src->vd_last.ul]; |
|
/* smp_mb(); */ |
|
return 0; /* success */ |
|
|
|
out: |
|
rerr = reinit_vdir(tgt); |
|
BUG_ON(rerr); |
|
return err; |
|
} |
|
|
|
int au_vdir_init(struct file *file) |
|
{ |
|
int err; |
|
struct inode *inode; |
|
struct au_vdir *vdir_cache, *allocated; |
|
|
|
/* test file->f_pos here instead of ctx->pos */ |
|
err = read_vdir(file, !file->f_pos); |
|
if (unlikely(err)) |
|
goto out; |
|
|
|
allocated = NULL; |
|
vdir_cache = au_fvdir_cache(file); |
|
if (!vdir_cache) { |
|
vdir_cache = alloc_vdir(file); |
|
err = PTR_ERR(vdir_cache); |
|
if (IS_ERR(vdir_cache)) |
|
goto out; |
|
allocated = vdir_cache; |
|
} else if (!file->f_pos && vdir_cache->vd_version != file->f_version) { |
|
/* test file->f_pos here instead of ctx->pos */ |
|
err = reinit_vdir(vdir_cache); |
|
if (unlikely(err)) |
|
goto out; |
|
} else |
|
return 0; /* success */ |
|
|
|
inode = file_inode(file); |
|
err = copy_vdir(vdir_cache, au_ivdir(inode)); |
|
if (!err) { |
|
file->f_version = inode_query_iversion(inode); |
|
if (allocated) |
|
au_set_fvdir_cache(file, allocated); |
|
} else if (allocated) |
|
au_vdir_free(allocated); |
|
|
|
out: |
|
return err; |
|
} |
|
|
|
static loff_t calc_offset(struct au_vdir *vdir) |
|
{ |
|
loff_t offset; |
|
union au_vdir_deblk_p p; |
|
|
|
p.deblk = vdir->vd_deblk[vdir->vd_last.ul]; |
|
offset = vdir->vd_last.p.deblk - p.deblk; |
|
offset += vdir->vd_deblk_sz * vdir->vd_last.ul; |
|
return offset; |
|
} |
|
|
|
/* returns true or false */ |
|
static int seek_vdir(struct file *file, struct dir_context *ctx) |
|
{ |
|
int valid; |
|
unsigned int deblk_sz; |
|
unsigned long ul, n; |
|
loff_t offset; |
|
union au_vdir_deblk_p p, deblk_end; |
|
struct au_vdir *vdir_cache; |
|
|
|
valid = 1; |
|
vdir_cache = au_fvdir_cache(file); |
|
offset = calc_offset(vdir_cache); |
|
AuDbg("offset %lld\n", offset); |
|
if (ctx->pos == offset) |
|
goto out; |
|
|
|
vdir_cache->vd_last.ul = 0; |
|
vdir_cache->vd_last.p.deblk = vdir_cache->vd_deblk[0]; |
|
if (!ctx->pos) |
|
goto out; |
|
|
|
valid = 0; |
|
deblk_sz = vdir_cache->vd_deblk_sz; |
|
ul = div64_u64(ctx->pos, deblk_sz); |
|
AuDbg("ul %lu\n", ul); |
|
if (ul >= vdir_cache->vd_nblk) |
|
goto out; |
|
|
|
n = vdir_cache->vd_nblk; |
|
for (; ul < n; ul++) { |
|
p.deblk = vdir_cache->vd_deblk[ul]; |
|
deblk_end.deblk = p.deblk + deblk_sz; |
|
offset = ul; |
|
offset *= deblk_sz; |
|
while (!is_deblk_end(&p, &deblk_end) && offset < ctx->pos) { |
|
unsigned int l; |
|
|
|
l = calc_size(p.de->de_str.len); |
|
offset += l; |
|
p.deblk += l; |
|
} |
|
if (!is_deblk_end(&p, &deblk_end)) { |
|
valid = 1; |
|
vdir_cache->vd_last.ul = ul; |
|
vdir_cache->vd_last.p = p; |
|
break; |
|
} |
|
} |
|
|
|
out: |
|
/* smp_mb(); */ |
|
if (!valid) |
|
AuDbg("valid %d\n", !valid); |
|
return valid; |
|
} |
|
|
|
int au_vdir_fill_de(struct file *file, struct dir_context *ctx) |
|
{ |
|
unsigned int l, deblk_sz; |
|
union au_vdir_deblk_p deblk_end; |
|
struct au_vdir *vdir_cache; |
|
struct au_vdir_de *de; |
|
|
|
if (!seek_vdir(file, ctx)) |
|
return 0; |
|
|
|
vdir_cache = au_fvdir_cache(file); |
|
deblk_sz = vdir_cache->vd_deblk_sz; |
|
while (1) { |
|
deblk_end.deblk = vdir_cache->vd_deblk[vdir_cache->vd_last.ul]; |
|
deblk_end.deblk += deblk_sz; |
|
while (!is_deblk_end(&vdir_cache->vd_last.p, &deblk_end)) { |
|
de = vdir_cache->vd_last.p.de; |
|
AuDbg("%.*s, off%lld, i%lu, dt%d\n", |
|
de->de_str.len, de->de_str.name, ctx->pos, |
|
(unsigned long)de->de_ino, de->de_type); |
|
if (unlikely(!dir_emit(ctx, de->de_str.name, |
|
de->de_str.len, de->de_ino, |
|
de->de_type))) { |
|
/* todo: ignore the error caused by udba? */ |
|
/* return err; */ |
|
return 0; |
|
} |
|
|
|
l = calc_size(de->de_str.len); |
|
vdir_cache->vd_last.p.deblk += l; |
|
ctx->pos += l; |
|
} |
|
if (vdir_cache->vd_last.ul < vdir_cache->vd_nblk - 1) { |
|
vdir_cache->vd_last.ul++; |
|
vdir_cache->vd_last.p.deblk |
|
= vdir_cache->vd_deblk[vdir_cache->vd_last.ul]; |
|
ctx->pos = deblk_sz * vdir_cache->vd_last.ul; |
|
continue; |
|
} |
|
break; |
|
} |
|
|
|
/* smp_mb(); */ |
|
return 0; |
|
}
|
|
|