// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2005-2019 Junjiro R. Okajima */ /* * readdir in userspace. */ #include #include #include #include "aufs.h" /* bits for struct aufs_rdu.flags */ #define AuRdu_CALLED 1 #define AuRdu_CONT (1 << 1) #define AuRdu_FULL (1 << 2) #define au_ftest_rdu(flags, name) ((flags) & AuRdu_##name) #define au_fset_rdu(flags, name) \ do { (flags) |= AuRdu_##name; } while (0) #define au_fclr_rdu(flags, name) \ do { (flags) &= ~AuRdu_##name; } while (0) struct au_rdu_arg { struct dir_context ctx; struct aufs_rdu *rdu; union au_rdu_ent_ul ent; unsigned long end; struct super_block *sb; int err; }; static int au_rdu_fill(struct dir_context *ctx, const char *name, int nlen, loff_t offset, u64 h_ino, unsigned int d_type) { int err, len; struct au_rdu_arg *arg = container_of(ctx, struct au_rdu_arg, ctx); struct aufs_rdu *rdu = arg->rdu; struct au_rdu_ent ent; err = 0; arg->err = 0; au_fset_rdu(rdu->cookie.flags, CALLED); len = au_rdu_len(nlen); if (arg->ent.ul + len < arg->end) { ent.ino = h_ino; ent.bindex = rdu->cookie.bindex; ent.type = d_type; ent.nlen = nlen; if (unlikely(nlen > AUFS_MAX_NAMELEN)) ent.type = DT_UNKNOWN; /* unnecessary to support mmap_sem since this is a dir */ err = -EFAULT; if (copy_to_user(arg->ent.e, &ent, sizeof(ent))) goto out; if (copy_to_user(arg->ent.e->name, name, nlen)) goto out; /* the terminating NULL */ if (__put_user(0, arg->ent.e->name + nlen)) goto out; err = 0; /* AuDbg("%p, %.*s\n", arg->ent.p, nlen, name); */ arg->ent.ul += len; rdu->rent++; } else { err = -EFAULT; au_fset_rdu(rdu->cookie.flags, FULL); rdu->full = 1; rdu->tail = arg->ent; } out: /* AuTraceErr(err); */ return err; } static int au_rdu_do(struct file *h_file, struct au_rdu_arg *arg) { int err; loff_t offset; struct au_rdu_cookie *cookie = &arg->rdu->cookie; /* we don't have to care (FMODE_32BITHASH | FMODE_64BITHASH) for ext4 */ offset = vfsub_llseek(h_file, cookie->h_pos, SEEK_SET); err = offset; if (unlikely(offset != cookie->h_pos)) goto out; err = 0; do { arg->err = 0; au_fclr_rdu(cookie->flags, CALLED); /* smp_mb(); */ err = vfsub_iterate_dir(h_file, &arg->ctx); if (err >= 0) err = arg->err; } while (!err && au_ftest_rdu(cookie->flags, CALLED) && !au_ftest_rdu(cookie->flags, FULL)); cookie->h_pos = h_file->f_pos; out: AuTraceErr(err); return err; } static int au_rdu(struct file *file, struct aufs_rdu *rdu) { int err; aufs_bindex_t bbot; struct au_rdu_arg arg = { .ctx = { .actor = au_rdu_fill } }; struct dentry *dentry; struct inode *inode; struct file *h_file; struct au_rdu_cookie *cookie = &rdu->cookie; /* VERIFY_WRITE */ err = !access_ok(rdu->ent.e, rdu->sz); if (unlikely(err)) { err = -EFAULT; AuTraceErr(err); goto out; } rdu->rent = 0; rdu->tail = rdu->ent; rdu->full = 0; arg.rdu = rdu; arg.ent = rdu->ent; arg.end = arg.ent.ul; arg.end += rdu->sz; err = -ENOTDIR; if (unlikely(!file->f_op->iterate && !file->f_op->iterate_shared)) goto out; err = security_file_permission(file, MAY_READ); AuTraceErr(err); if (unlikely(err)) goto out; dentry = file->f_path.dentry; inode = d_inode(dentry); inode_lock_shared(inode); arg.sb = inode->i_sb; err = si_read_lock(arg.sb, AuLock_FLUSH | AuLock_NOPLM); if (unlikely(err)) goto out_mtx; err = au_alive_dir(dentry); if (unlikely(err)) goto out_si; /* todo: reval? */ fi_read_lock(file); err = -EAGAIN; if (unlikely(au_ftest_rdu(cookie->flags, CONT) && cookie->generation != au_figen(file))) goto out_unlock; err = 0; if (!rdu->blk) { rdu->blk = au_sbi(arg.sb)->si_rdblk; if (!rdu->blk) rdu->blk = au_dir_size(file, /*dentry*/NULL); } bbot = au_fbtop(file); if (cookie->bindex < bbot) cookie->bindex = bbot; bbot = au_fbbot_dir(file); /* AuDbg("b%d, b%d\n", cookie->bindex, bbot); */ for (; !err && cookie->bindex <= bbot; cookie->bindex++, cookie->h_pos = 0) { h_file = au_hf_dir(file, cookie->bindex); if (!h_file) continue; au_fclr_rdu(cookie->flags, FULL); err = au_rdu_do(h_file, &arg); AuTraceErr(err); if (unlikely(au_ftest_rdu(cookie->flags, FULL) || err)) break; } AuDbg("rent %llu\n", rdu->rent); if (!err && !au_ftest_rdu(cookie->flags, CONT)) { rdu->shwh = !!au_opt_test(au_sbi(arg.sb)->si_mntflags, SHWH); au_fset_rdu(cookie->flags, CONT); cookie->generation = au_figen(file); } ii_read_lock_child(inode); fsstack_copy_attr_atime(inode, au_h_iptr(inode, au_ibtop(inode))); ii_read_unlock(inode); out_unlock: fi_read_unlock(file); out_si: si_read_unlock(arg.sb); out_mtx: inode_unlock_shared(inode); out: AuTraceErr(err); return err; } static int au_rdu_ino(struct file *file, struct aufs_rdu *rdu) { int err; ino_t ino; unsigned long long nent; union au_rdu_ent_ul *u; struct au_rdu_ent ent; struct super_block *sb; err = 0; nent = rdu->nent; u = &rdu->ent; sb = file->f_path.dentry->d_sb; si_read_lock(sb, AuLock_FLUSH); while (nent-- > 0) { /* unnecessary to support mmap_sem since this is a dir */ err = copy_from_user(&ent, u->e, sizeof(ent)); if (!err) /* VERIFY_WRITE */ err = !access_ok(&u->e->ino, sizeof(ino)); if (unlikely(err)) { err = -EFAULT; AuTraceErr(err); break; } /* AuDbg("b%d, i%llu\n", ent.bindex, ent.ino); */ if (!ent.wh) err = au_ino(sb, ent.bindex, ent.ino, ent.type, &ino); else err = au_wh_ino(sb, ent.bindex, ent.ino, ent.type, &ino); if (unlikely(err)) { AuTraceErr(err); break; } err = __put_user(ino, &u->e->ino); if (unlikely(err)) { err = -EFAULT; AuTraceErr(err); break; } u->ul += au_rdu_len(ent.nlen); } si_read_unlock(sb); return err; } /* ---------------------------------------------------------------------- */ static int au_rdu_verify(struct aufs_rdu *rdu) { AuDbg("rdu{%llu, %p, %u | %u | %llu, %u, %u | " "%llu, b%d, 0x%x, g%u}\n", rdu->sz, rdu->ent.e, rdu->verify[AufsCtlRduV_SZ], rdu->blk, rdu->rent, rdu->shwh, rdu->full, rdu->cookie.h_pos, rdu->cookie.bindex, rdu->cookie.flags, rdu->cookie.generation); if (rdu->verify[AufsCtlRduV_SZ] == sizeof(*rdu)) return 0; AuDbg("%u:%u\n", rdu->verify[AufsCtlRduV_SZ], (unsigned int)sizeof(*rdu)); return -EINVAL; } long au_rdu_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { long err, e; struct aufs_rdu rdu; void __user *p = (void __user *)arg; err = copy_from_user(&rdu, p, sizeof(rdu)); if (unlikely(err)) { err = -EFAULT; AuTraceErr(err); goto out; } err = au_rdu_verify(&rdu); if (unlikely(err)) goto out; switch (cmd) { case AUFS_CTL_RDU: err = au_rdu(file, &rdu); if (unlikely(err)) break; e = copy_to_user(p, &rdu, sizeof(rdu)); if (unlikely(e)) { err = -EFAULT; AuTraceErr(err); } break; case AUFS_CTL_RDU_INO: err = au_rdu_ino(file, &rdu); break; default: /* err = -ENOTTY; */ err = -EINVAL; } out: AuTraceErr(err); return err; } #ifdef CONFIG_COMPAT long au_rdu_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { long err, e; struct aufs_rdu rdu; void __user *p = compat_ptr(arg); /* todo: get_user()? */ err = copy_from_user(&rdu, p, sizeof(rdu)); if (unlikely(err)) { err = -EFAULT; AuTraceErr(err); goto out; } rdu.ent.e = compat_ptr(rdu.ent.ul); err = au_rdu_verify(&rdu); if (unlikely(err)) goto out; switch (cmd) { case AUFS_CTL_RDU: err = au_rdu(file, &rdu); if (unlikely(err)) break; rdu.ent.ul = ptr_to_compat(rdu.ent.e); rdu.tail.ul = ptr_to_compat(rdu.tail.e); e = copy_to_user(p, &rdu, sizeof(rdu)); if (unlikely(e)) { err = -EFAULT; AuTraceErr(err); } break; case AUFS_CTL_RDU_INO: err = au_rdu_ino(file, &rdu); break; default: /* err = -ENOTTY; */ err = -EINVAL; } out: AuTraceErr(err); return err; } #endif