2006-02-13 19:03:39

by Chris Stromsoe

[permalink] [raw]
Subject: any FS with tree-based quota system?

I'm looking for a file system with a tree-based quota system. XFS on IRIX
has projects, but that functionality didn't get ported over to Linux that
I can see.

I'm building a closed-box system to serve web pages. I expect to have 20k
to 30k different user trees. User's will not have direct access to the
box and will not be assigned a uid/gid. Every tree will be owned by the
same uid/gid.

I would like to be able to apply a quota to a particular tree, and have
every file and directory in the path of that tree count toward that tree's
quota usage. I can prevent hard links across trees.

I noticed that Neil Brown wrote some patches fairly early on in the 2.4
cycle to do tree-based quota by UID. The last patch-set I found was
against 2.4.14 (http://cgi.cse.unsw.edu.au/~neilb/patches/linux/2.4.14/)
from late 2001, and did not come with patches to quota-tools.

How much change has the quota system undergone between 2.4.14 and 2.4.32?
Between 2.4 and 2.6? What can I read to get a decent understanding of FS
basics and how the quota system interacts? Any pointers at all would be
appreciated. (I'm equally open to "that's the stupidest idea I've ever
heard" as well, or pointers to reasons why it wouldn't be workable.)

Thanks.


-Chris


2006-02-13 20:58:39

by Sam Vilain

[permalink] [raw]
Subject: Re: any FS with tree-based quota system?

Chris Stromsoe wrote:
> I'm looking for a file system with a tree-based quota system. XFS on
> IRIX has projects, but that functionality didn't get ported over to
> Linux that I can see.

Linux-VServer has some extra quota features, not quite what you're
asking for but perhaps relevant for looking at a patch playing with
quotas on a recent kernel. The way it works is that you bind mount
parts of the VFS to another part, and the new mount can have seperate
quota settings.

The most recent split out patch for that is at:

http://vserver.13thfloor.at/Experimental/del-2.6.16-rc1-vs2.1.0.9/40_quota.diff

(see it colourised in my gitweb -
http://vserver.utsl.gen.nz/gitweb/?p=vserver.git;a=commitdiff;h=04a1f42903356f18ebdb89613c50923e54506536
)

But it might not stand on its own just yet; it probably at least needs
the Bind Mount Extensions (bme) patch too; it's probably easiest to
apply the entire VServer patch for experimentation.

Sam.

2006-02-13 21:33:58

by Nathan Scott

[permalink] [raw]
Subject: Re: any FS with tree-based quota system?

On Mon, Feb 13, 2006 at 11:03:35AM -0800, Chris Stromsoe wrote:
> I'm looking for a file system with a tree-based quota system. XFS on IRIX
> has projects, but that functionality didn't get ported over to Linux that
> I can see.

You didn't look very closely then. ;)

cheers.

--
Nathan

2006-02-13 22:09:29

by NeilBrown

[permalink] [raw]
Subject: Re: any FS with tree-based quota system?

On Monday February 13, [email protected] wrote:
> I'm looking for a file system with a tree-based quota system. XFS on IRIX
> has projects, but that functionality didn't get ported over to Linux that
> I can see.
>
> I'm building a closed-box system to serve web pages. I expect to have 20k
> to 30k different user trees. User's will not have direct access to the
> box and will not be assigned a uid/gid. Every tree will be owned by the
> same uid/gid.
>
> I would like to be able to apply a quota to a particular tree, and have
> every file and directory in the path of that tree count toward that tree's
> quota usage. I can prevent hard links across trees.
>
> I noticed that Neil Brown wrote some patches fairly early on in the 2.4
> cycle to do tree-based quota by UID. The last patch-set I found was
> against 2.4.14 (http://cgi.cse.unsw.edu.au/~neilb/patches/linux/2.4.14/)
> from late 2001, and did not come with patches to quota-tools.

Following is my tree-quota patch updated to 2.6.14.3. However it
doesn't do exactly what you claim to want.
You still need to assign a uid to each user (the kernel needs some
number to use as an index into the quotas file). But only the
top-level directory of each tree needs to be owned by the uid. Files
beneath the top can be owned by anyone.

I can dig-up a patch for quota-utils if you want to proceed with
this.

If you are using a more recent kernel and get a conflict in fs.h,
you'll just need to #define ATTR_TID to be 16384, because 8192 was
taken by ATTR_FILE.

NeilBrown




Signed-off-by: Neil Brown <[email protected]>

### Diffstat output
./fs/attr.c | 16 ++++++++++++
./fs/dquot.c | 20 +++++++++++++++
./fs/ext2/ialloc.c | 1
./fs/ext2/inode.c | 3 ++
./fs/ext2/super.c | 1
./fs/ext3/ialloc.c | 1
./fs/ext3/inode.c | 5 +++
./fs/ext3/super.c | 45 +++++++++++++++++++++++++++++-----
./fs/namei.c | 36 ++++++++++++++++++++++++++-
./fs/quota_v1.c | 3 +-
./fs/stat.c | 1
./include/linux/ext2_fs.h | 4 +--
./include/linux/ext3_fs.h | 2 +
./include/linux/fs.h | 3 ++
./include/linux/quota.h | 16 +++++++++---
./include/linux/quotaops.h | 58 +++++++++++++++++++++++++++++++++++++++++++++
./include/linux/stat.h | 1
17 files changed, 200 insertions(+), 16 deletions(-)

diff ./fs/attr.c~current~ ./fs/attr.c
--- ./fs/attr.c~current~ 2005-12-07 15:51:56.000000000 +1100
+++ ./fs/attr.c 2005-12-07 11:28:43.000000000 +1100
@@ -87,6 +87,8 @@ int inode_setattr(struct inode * inode,
inode->i_uid = attr->ia_uid;
if (ia_valid & ATTR_GID)
inode->i_gid = attr->ia_gid;
+ if (ia_valid & ATTR_TID)
+ inode->i_tid = attr->ia_tid;
if (ia_valid & ATTR_ATIME)
inode->i_atime = timespec_trunc(attr->ia_atime,
inode->i_sb->s_time_gran);
@@ -154,6 +156,19 @@ int notify_change(struct dentry * dentry
if (ia_valid & ATTR_SIZE)
down_write(&dentry->d_inode->i_alloc_sem);

+ if (!(ia_valid & ATTR_TID)
+ && (ia_valid & ATTR_UID)
+ && !treequota_parent_uid_ok(inode, dentry->d_parent->d_inode,
+ attr->ia_uid)) {
+ if (dentry == dentry->d_parent)
+ attr->ia_tid = attr->ia_uid;
+ else
+ attr->ia_tid = treequota_tid(dentry->d_parent->d_inode,
+ attr->ia_uid);
+ ia_valid |= ATTR_TID;
+ attr->ia_valid = ia_valid;
+ }
+
if (inode->i_op && inode->i_op->setattr) {
error = security_inode_setattr(dentry, attr);
if (!error)
@@ -164,6 +179,7 @@ int notify_change(struct dentry * dentry
error = security_inode_setattr(dentry, attr);
if (!error) {
if ((ia_valid & ATTR_UID && attr->ia_uid != inode->i_uid) ||
+ (ia_valid & ATTR_TID && attr->ia_tid != inode->i_tid) ||
(ia_valid & ATTR_GID && attr->ia_gid != inode->i_gid))
error = DQUOT_TRANSFER(inode, attr) ? -EDQUOT : 0;
if (!error)

diff ./fs/dquot.c~current~ ./fs/dquot.c
--- ./fs/dquot.c~current~ 2005-12-07 15:51:56.000000000 +1100
+++ ./fs/dquot.c 2005-12-07 11:29:26.000000000 +1100
@@ -784,6 +784,8 @@ static inline int need_print_warning(str
return current->fsuid == dquot->dq_id;
case GRPQUOTA:
return in_group_p(dquot->dq_id);
+ case TREEQUOTA:
+ return current->fsuid == dquot->dq_id;
}
return 0;
}
@@ -955,6 +957,9 @@ int dquot_initialize(struct inode *inode
case GRPQUOTA:
id = inode->i_gid;
break;
+ case TREEQUOTA:
+ id = inode->i_tid;
+ break;
}
inode->i_dquot[cnt] = dqget(inode->i_sb, id, cnt);
}
@@ -1167,6 +1172,8 @@ int dquot_transfer(struct inode *inode,
struct dquot *transfer_to[MAXQUOTAS];
int cnt, ret = NO_QUOTA, chuid = (iattr->ia_valid & ATTR_UID) && inode->i_uid != iattr->ia_uid,
chgid = (iattr->ia_valid & ATTR_GID) && inode->i_gid != iattr->ia_gid;
+ int chtreeid = (iattr->ia_valid & ATTR_TID) && inode->i_tid != iattr->ia_tid;
+
char warntype[MAXQUOTAS];

/* First test before acquiring semaphore - solves deadlocks when we
@@ -1199,6 +1206,11 @@ int dquot_transfer(struct inode *inode,
continue;
transfer_to[cnt] = dqget(inode->i_sb, iattr->ia_gid, cnt);
break;
+ case TREEQUOTA:
+ if (!chtreeid)
+ continue;
+ transfer_to[cnt] = dqget(inode->i_sb, iattr->ia_tid, cnt);
+ break;
}
}
spin_lock(&dq_data_lock);
@@ -1208,6 +1220,8 @@ int dquot_transfer(struct inode *inode,
if (transfer_to[cnt] == NODQUOT)
continue;
transfer_from[cnt] = inode->i_dquot[cnt];
+ if (iattr->ia_valid & ATTR_FORCE)
+ continue; /* don't check, just do */
if (check_idq(transfer_to[cnt], 1, warntype+cnt) == NO_QUOTA ||
check_bdq(transfer_to[cnt], space, 0, warntype+cnt) == NO_QUOTA)
goto warn_put_all;
@@ -1297,6 +1311,9 @@ static inline void set_enable_flags(stru
case GRPQUOTA:
dqopt->flags |= DQUOT_GRP_ENABLED;
break;
+ case TREEQUOTA:
+ dqopt->flags |= DQUOT_TREE_ENABLED;
+ break;
}
}

@@ -1309,6 +1326,9 @@ static inline void reset_enable_flags(st
case GRPQUOTA:
dqopt->flags &= ~DQUOT_GRP_ENABLED;
break;
+ case TREEQUOTA:
+ dqopt->flags &= ~DQUOT_TREE_ENABLED;
+ break;
}
}


diff ./fs/ext2/ialloc.c~current~ ./fs/ext2/ialloc.c
--- ./fs/ext2/ialloc.c~current~ 2005-12-07 15:51:56.000000000 +1100
+++ ./fs/ext2/ialloc.c 2005-12-07 11:28:43.000000000 +1100
@@ -564,6 +564,7 @@ got:
sb->s_dirt = 1;
mark_buffer_dirty(bh2);
inode->i_uid = current->fsuid;
+ inode->i_tid = treequota_tid(dir, inode->i_uid);
if (test_opt (sb, GRPID))
inode->i_gid = dir->i_gid;
else if (dir->i_mode & S_ISGID) {

diff ./fs/ext2/inode.c~current~ ./fs/ext2/inode.c
--- ./fs/ext2/inode.c~current~ 2005-12-07 15:51:56.000000000 +1100
+++ ./fs/ext2/inode.c 2005-12-07 11:28:43.000000000 +1100
@@ -1079,6 +1079,7 @@ void ext2_read_inode (struct inode * ino
inode->i_mode = le16_to_cpu(raw_inode->i_mode);
inode->i_uid = (uid_t)le16_to_cpu(raw_inode->i_uid_low);
inode->i_gid = (gid_t)le16_to_cpu(raw_inode->i_gid_low);
+ inode->i_tid = (uid_t)le32_to_cpu(raw_inode->i_e2_tid);
if (!(test_opt (inode->i_sb, NO_UID32))) {
inode->i_uid |= le16_to_cpu(raw_inode->i_uid_high) << 16;
inode->i_gid |= le16_to_cpu(raw_inode->i_gid_high) << 16;
@@ -1216,6 +1217,7 @@ static int ext2_update_inode(struct inod
raw_inode->i_uid_high = 0;
raw_inode->i_gid_high = 0;
}
+ raw_inode->i_e2_tid = cpu_to_le32(inode->i_tid);
raw_inode->i_links_count = cpu_to_le16(inode->i_nlink);
raw_inode->i_size = cpu_to_le32(inode->i_size);
raw_inode->i_atime = cpu_to_le32(inode->i_atime.tv_sec);
@@ -1302,6 +1304,7 @@ int ext2_setattr(struct dentry *dentry,
if (error)
return error;
if ((iattr->ia_valid & ATTR_UID && iattr->ia_uid != inode->i_uid) ||
+ (iattr->ia_valid & ATTR_TID && iattr->ia_tid != inode->i_tid) ||
(iattr->ia_valid & ATTR_GID && iattr->ia_gid != inode->i_gid)) {
error = DQUOT_TRANSFER(inode, iattr) ? -EDQUOT : 0;
if (error)

diff ./fs/ext2/super.c~current~ ./fs/ext2/super.c
--- ./fs/ext2/super.c~current~ 2005-12-07 15:51:56.000000000 +1100
+++ ./fs/ext2/super.c 2005-12-07 11:28:44.000000000 +1100
@@ -315,6 +315,7 @@ static match_table_t tokens = {
{Opt_xip, "xip"},
{Opt_grpquota, "grpquota"},
{Opt_ignore, "noquota"},
+ {Opt_ignore, "treequota"},
{Opt_quota, "quota"},
{Opt_usrquota, "usrquota"},
{Opt_err, NULL}

diff ./fs/ext3/ialloc.c~current~ ./fs/ext3/ialloc.c
--- ./fs/ext3/ialloc.c~current~ 2005-12-07 15:51:56.000000000 +1100
+++ ./fs/ext3/ialloc.c 2005-12-07 11:28:43.000000000 +1100
@@ -545,6 +545,7 @@ got:
sb->s_dirt = 1;

inode->i_uid = current->fsuid;
+ inode->i_tid = treequota_tid(dir, inode->i_uid);
if (test_opt (sb, GRPID))
inode->i_gid = dir->i_gid;
else if (dir->i_mode & S_ISGID) {

diff ./fs/ext3/inode.c~current~ ./fs/ext3/inode.c
--- ./fs/ext3/inode.c~current~ 2005-12-07 15:51:56.000000000 +1100
+++ ./fs/ext3/inode.c 2005-12-07 11:28:43.000000000 +1100
@@ -2446,6 +2446,7 @@ void ext3_read_inode(struct inode * inod
inode->i_mode = le16_to_cpu(raw_inode->i_mode);
inode->i_uid = (uid_t)le16_to_cpu(raw_inode->i_uid_low);
inode->i_gid = (gid_t)le16_to_cpu(raw_inode->i_gid_low);
+ inode->i_tid = (uid_t)le32_to_cpu(raw_inode->i_e3_tid);
if(!(test_opt (inode->i_sb, NO_UID32))) {
inode->i_uid |= le16_to_cpu(raw_inode->i_uid_high) << 16;
inode->i_gid |= le16_to_cpu(raw_inode->i_gid_high) << 16;
@@ -2608,6 +2609,7 @@ static int ext3_do_update_inode(handle_t
raw_inode->i_uid_high = 0;
raw_inode->i_gid_high = 0;
}
+ raw_inode->i_e3_tid = cpu_to_le32(inode->i_tid);
raw_inode->i_links_count = cpu_to_le16(inode->i_nlink);
raw_inode->i_size = cpu_to_le32(ei->i_disksize);
raw_inode->i_atime = cpu_to_le32(inode->i_atime.tv_sec);
@@ -2760,6 +2762,7 @@ int ext3_setattr(struct dentry *dentry,
return error;

if ((ia_valid & ATTR_UID && attr->ia_uid != inode->i_uid) ||
+ (ia_valid & ATTR_TID && attr->ia_tid != inode->i_tid) ||
(ia_valid & ATTR_GID && attr->ia_gid != inode->i_gid)) {
handle_t *handle;

@@ -2782,6 +2785,8 @@ int ext3_setattr(struct dentry *dentry,
inode->i_uid = attr->ia_uid;
if (attr->ia_valid & ATTR_GID)
inode->i_gid = attr->ia_gid;
+ if (attr->ia_valid & ATTR_TID)
+ inode->i_tid = attr->ia_tid;
error = ext3_mark_inode_dirty(handle, inode);
ext3_journal_stop(handle);
}

diff ./fs/ext3/super.c~current~ ./fs/ext3/super.c
--- ./fs/ext3/super.c~current~ 2005-12-07 15:51:56.000000000 +1100
+++ ./fs/ext3/super.c 2005-12-07 15:32:13.000000000 +1100
@@ -533,19 +533,25 @@ static int ext3_show_options(struct seq_
if (sbi->s_qf_names[GRPQUOTA])
seq_printf(seq, ",grpjquota=%s", sbi->s_qf_names[GRPQUOTA]);

+ if (sbi->s_qf_names[TREEQUOTA])
+ seq_printf(seq, ",treejquota=%s", sbi->s_qf_names[TREEQUOTA]);
+
if (sbi->s_mount_opt & EXT3_MOUNT_USRQUOTA)
seq_puts(seq, ",usrquota");

if (sbi->s_mount_opt & EXT3_MOUNT_GRPQUOTA)
seq_puts(seq, ",grpquota");
+
+ if (sbi->s_mount_opt & EXT3_MOUNT_TREEQUOTA)
+ seq_puts(seq, ",treequota");
#endif

return 0;
}

#ifdef CONFIG_QUOTA
-#define QTYPE2NAME(t) ((t)==USRQUOTA?"user":"group")
-#define QTYPE2MOPT(on, t) ((t)==USRQUOTA?((on)##USRJQUOTA):((on)##GRPJQUOTA))
+#define QTYPE2NAME(t) ((t)==USRQUOTA?"user":(t)==GRPQUOTA?"group":"tree")
+#define QTYPE2MOPT(on, t) ((t)==USRQUOTA?((on)##USRJQUOTA):(t)==GRPQUOTA?((on)##GRPJQUOTA):((on)##TREEJQUOTA))

static int ext3_dquot_initialize(struct inode *inode, int type);
static int ext3_dquot_drop(struct inode *inode);
@@ -622,10 +628,11 @@ enum {
Opt_reservation, Opt_noreservation, Opt_noload, Opt_nobh,
Opt_commit, Opt_journal_update, Opt_journal_inum,
Opt_abort, Opt_data_journal, Opt_data_ordered, Opt_data_writeback,
- Opt_usrjquota, Opt_grpjquota, Opt_offusrjquota, Opt_offgrpjquota,
+ Opt_usrjquota, Opt_grpjquota, Opt_treejquota,
+ Opt_offusrjquota, Opt_offgrpjquota, Opt_offtreejquota,
Opt_jqfmt_vfsold, Opt_jqfmt_vfsv0, Opt_quota, Opt_noquota,
Opt_ignore, Opt_barrier, Opt_err, Opt_resize, Opt_usrquota,
- Opt_grpquota
+ Opt_grpquota, Opt_treequota
};

static match_table_t tokens = {
@@ -667,10 +674,13 @@ static match_table_t tokens = {
{Opt_usrjquota, "usrjquota=%s"},
{Opt_offgrpjquota, "grpjquota="},
{Opt_grpjquota, "grpjquota=%s"},
+ {Opt_offtreejquota, "treejquota="},
+ {Opt_treejquota, "treejquota=%s"},
{Opt_jqfmt_vfsold, "jqfmt=vfsold"},
{Opt_jqfmt_vfsv0, "jqfmt=vfsv0"},
{Opt_grpquota, "grpquota"},
{Opt_noquota, "noquota"},
+ {Opt_treequota, "treequota"},
{Opt_quota, "quota"},
{Opt_usrquota, "usrquota"},
{Opt_barrier, "barrier=%u"},
@@ -875,6 +885,9 @@ static int parse_options (char * options
}
break;
#ifdef CONFIG_QUOTA
+ case Opt_treejquota:
+ qtype = TREEQUOTA;
+ goto set_qf_name;
case Opt_usrjquota:
qtype = USRQUOTA;
goto set_qf_name;
@@ -913,6 +926,9 @@ set_qf_name:
}
set_opt(sbi->s_mount_opt, QUOTA);
break;
+ case Opt_offtreejquota:
+ qtype = TREEQUOTA;
+ goto clear_qf_name;
case Opt_offusrjquota:
qtype = USRQUOTA;
goto clear_qf_name;
@@ -946,6 +962,10 @@ clear_qf_name:
set_opt(sbi->s_mount_opt, QUOTA);
set_opt(sbi->s_mount_opt, GRPQUOTA);
break;
+ case Opt_treequota:
+ set_opt(sbi->s_mount_opt, QUOTA);
+ set_opt(sbi->s_mount_opt, TREEQUOTA);
+ break;
case Opt_noquota:
if (sb_any_quota_enabled(sb)) {
printk(KERN_ERR "EXT3-fs: Cannot change quota "
@@ -960,10 +980,13 @@ clear_qf_name:
case Opt_quota:
case Opt_usrquota:
case Opt_grpquota:
+ case Opt_treequota:
case Opt_usrjquota:
case Opt_grpjquota:
+ case Opt_treejquota:
case Opt_offusrjquota:
case Opt_offgrpjquota:
+ case Opt_offtreejquota:
case Opt_jqfmt_vfsold:
case Opt_jqfmt_vfsv0:
printk(KERN_ERR
@@ -1007,7 +1030,8 @@ clear_qf_name:
}
}
#ifdef CONFIG_QUOTA
- if (sbi->s_qf_names[USRQUOTA] || sbi->s_qf_names[GRPQUOTA]) {
+ if (sbi->s_qf_names[USRQUOTA] || sbi->s_qf_names[GRPQUOTA] ||
+ sbi->s_qf_names[TREEQUOTA]) {
if ((sbi->s_mount_opt & EXT3_MOUNT_USRQUOTA) &&
sbi->s_qf_names[USRQUOTA])
clear_opt(sbi->s_mount_opt, USRQUOTA);
@@ -1016,6 +1040,11 @@ clear_qf_name:
sbi->s_qf_names[GRPQUOTA])
clear_opt(sbi->s_mount_opt, GRPQUOTA);

+ if ((sbi->s_mount_opt & EXT3_MOUNT_TREEQUOTA) &&
+ sbi->s_qf_names[TREEQUOTA])
+ clear_opt(sbi->s_mount_opt, TREEQUOTA);
+
+/* FIXME */
if ((sbi->s_qf_names[USRQUOTA] &&
(sbi->s_mount_opt & EXT3_MOUNT_GRPQUOTA)) ||
(sbi->s_qf_names[GRPQUOTA] &&
@@ -2453,7 +2482,8 @@ static int ext3_mark_dquot_dirty(struct
{
/* Are we journalling quotas? */
if (EXT3_SB(dquot->dq_sb)->s_qf_names[USRQUOTA] ||
- EXT3_SB(dquot->dq_sb)->s_qf_names[GRPQUOTA]) {
+ EXT3_SB(dquot->dq_sb)->s_qf_names[GRPQUOTA] ||
+ EXT3_SB(dquot->dq_sb)->s_qf_names[TREEQUOTA]) {
dquot_mark_dquot_dirty(dquot);
return ext3_write_dquot(dquot);
} else {
@@ -2500,7 +2530,8 @@ static int ext3_quota_on(struct super_bl
return -EINVAL;
/* Not journalling quota? */
if (!EXT3_SB(sb)->s_qf_names[USRQUOTA] &&
- !EXT3_SB(sb)->s_qf_names[GRPQUOTA])
+ !EXT3_SB(sb)->s_qf_names[GRPQUOTA] &&
+ !EXT3_SB(sb)->s_qf_names[TREEQUOTA])
return vfs_quota_on(sb, type, format_id, path);
err = path_lookup(path, LOOKUP_FOLLOW, &nd);
if (err)

diff ./fs/namei.c~current~ ./fs/namei.c
--- ./fs/namei.c~current~ 2005-12-07 15:51:56.000000000 +1100
+++ ./fs/namei.c 2005-12-07 11:30:13.000000000 +1100
@@ -808,6 +808,8 @@ static fastcall int __link_path_walk(con
if (err)
break;

+ treequota_check(next.dentry);
+
err = -ENOENT;
inode = next.dentry->d_inode;
if (!inode)
@@ -861,6 +863,9 @@ last_component:
err = do_lookup(nd, &this, &next);
if (err)
break;
+
+ treequota_check(next.dentry);
+
inode = next.dentry->d_inode;
if ((lookup_flags & LOOKUP_FOLLOW)
&& inode && inode->i_op && inode->i_op->follow_link) {
@@ -1092,6 +1097,7 @@ static struct dentry * __lookup_hash(str
else
dput(new);
}
+ treequota_check(dentry);
out:
return dentry;
}
@@ -1994,6 +2000,8 @@ int vfs_link(struct dentry *old_dentry,
if (dir->i_sb != inode->i_sb)
return -EXDEV;

+ if (!treequota_parent_ok(inode, dir))
+ return -EXDEV;
/*
* A link to an append-only or immutable file cannot be created.
*/
@@ -2100,6 +2108,7 @@ static int vfs_rename_dir(struct inode *
{
int error = 0;
struct inode *target;
+ struct iattr attr;

/*
* If we are going to change the parent - check write permissions,
@@ -2111,6 +2120,11 @@ static int vfs_rename_dir(struct inode *
return error;
}

+ if (!treequota_parent_ok(old_dentry->d_inode, new_dir)
+ && !capable(CAP_CHOWN))
+ return -EXDEV;
+
+
error = security_inode_rename(old_dir, old_dentry, new_dir, new_dentry);
if (error)
return error;
@@ -2120,8 +2134,14 @@ static int vfs_rename_dir(struct inode *
down(&target->i_sem);
dentry_unhash(new_dentry);
}
+ attr.ia_valid = ATTR_TID;
+ attr.ia_tid = treequota_tid(new_dir, old_dentry->d_inode->i_uid);
+
if (d_mountpoint(old_dentry)||d_mountpoint(new_dentry))
error = -EBUSY;
+ else if (!treequota_parent_ok(old_dentry->d_inode, new_dir)
+ && (error = notify_change(old_dentry, &attr)))
+ ;
else
error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry);
if (target) {
@@ -2153,8 +2173,20 @@ static int vfs_rename_other(struct inode
down(&target->i_sem);
if (d_mountpoint(old_dentry)||d_mountpoint(new_dentry))
error = -EBUSY;
- else
- error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry);
+ else {
+ error = 0;
+ if (!treequota_parent_ok(old_dentry->d_inode, new_dir)) {
+ struct iattr attr;
+ if (old_dentry->d_inode->i_nlink > 1)
+ return -EXDEV;
+ attr.ia_valid = ATTR_TID;
+ attr.ia_tid = treequota_tid(new_dir,
+ old_dentry->d_inode->i_uid);
+ error = notify_change(old_dentry, &attr);
+ }
+ if (!error)
+ error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry);
+ }
if (!error) {
/* The following d_move() should become unconditional */
if (!(old_dir->i_sb->s_type->fs_flags & FS_ODD_RENAME))

diff ./fs/quota_v1.c~current~ ./fs/quota_v1.c
--- ./fs/quota_v1.c~current~ 2005-12-07 15:51:56.000000000 +1100
+++ ./fs/quota_v1.c 2005-12-07 11:28:43.000000000 +1100
@@ -91,7 +91,8 @@ out:
/* Magics of new quota format */
#define V2_INITQMAGICS {\
0xd9c01f11, /* USRQUOTA */\
- 0xd9c01927 /* GRPQUOTA */\
+ 0xd9c01927, /* GRPQUOTA */\
+ 0xd9c01987 /* TREEQUOTA - a lie */\
}

/* Header of new quota format */

diff ./fs/stat.c~current~ ./fs/stat.c
--- ./fs/stat.c~current~ 2005-12-07 15:51:56.000000000 +1100
+++ ./fs/stat.c 2005-12-07 11:28:43.000000000 +1100
@@ -28,6 +28,7 @@ void generic_fillattr(struct inode *inod
stat->uid = inode->i_uid;
stat->gid = inode->i_gid;
stat->rdev = inode->i_rdev;
+ stat->tid = inode->i_tid;
stat->atime = inode->i_atime;
stat->mtime = inode->i_mtime;
stat->ctime = inode->i_ctime;

diff ./include/linux/ext2_fs.h~current~ ./include/linux/ext2_fs.h
--- ./include/linux/ext2_fs.h~current~ 2005-12-07 15:51:56.000000000 +1100
+++ ./include/linux/ext2_fs.h 2005-12-07 11:28:43.000000000 +1100
@@ -243,7 +243,7 @@ struct ext2_inode {
__u16 i_pad1;
__le16 l_i_uid_high; /* these 2 fields */
__le16 l_i_gid_high; /* were reserved2[0] */
- __u32 l_i_reserved2;
+ __u32 l_i_tid; /* tree-id for quotas, no longer l_i_reserved2 */
} linux2;
struct {
__u8 h_i_frag; /* Fragment number */
@@ -272,7 +272,7 @@ struct ext2_inode {
#define i_gid_low i_gid
#define i_uid_high osd2.linux2.l_i_uid_high
#define i_gid_high osd2.linux2.l_i_gid_high
-#define i_reserved2 osd2.linux2.l_i_reserved2
+#define i_e2_tid osd2.linux2.l_i_tid
#endif

#ifdef __hurd__

diff ./include/linux/ext3_fs.h~current~ ./include/linux/ext3_fs.h
--- ./include/linux/ext3_fs.h~current~ 2005-12-07 15:51:56.000000000 +1100
+++ ./include/linux/ext3_fs.h 2005-12-07 15:51:58.000000000 +1100
@@ -322,6 +322,7 @@ struct ext3_inode {
#define i_uid_high osd2.linux2.l_i_uid_high
#define i_gid_high osd2.linux2.l_i_gid_high
#define i_reserved2 osd2.linux2.l_i_reserved2
+#define i_e3_tid i_reserved2

#elif defined(__GNU__)

@@ -375,6 +376,7 @@ struct ext3_inode {
#define EXT3_MOUNT_QUOTA 0x80000 /* Some quota option set */
#define EXT3_MOUNT_USRQUOTA 0x100000 /* "old" user quota */
#define EXT3_MOUNT_GRPQUOTA 0x200000 /* "old" group quota */
+#define EXT3_MOUNT_TREEQUOTA 0x400000 /* "old" tree quota */

/* Compatibility, for having both ext2_fs.h and ext3_fs.h included at once */
#ifndef _LINUX_EXT2_FS_H

diff ./include/linux/fs.h~current~ ./include/linux/fs.h
--- ./include/linux/fs.h~current~ 2005-12-07 15:51:56.000000000 +1100
+++ ./include/linux/fs.h 2005-12-07 11:55:46.000000000 +1100
@@ -264,6 +264,7 @@ typedef void (dio_iodone_t)(struct kiocb
#define ATTR_ATTR_FLAG 1024
#define ATTR_KILL_SUID 2048
#define ATTR_KILL_SGID 4096
+#define ATTR_TID 8192

/*
* This is the Inode Attributes structure, used for notify_change(). It
@@ -283,6 +284,7 @@ struct iattr {
struct timespec ia_atime;
struct timespec ia_mtime;
struct timespec ia_ctime;
+ uid_t ia_tid;
};

/*
@@ -430,6 +432,7 @@ struct inode {
unsigned int i_nlink;
uid_t i_uid;
gid_t i_gid;
+ uid_t i_tid; /* tree-id for quotas */
dev_t i_rdev;
loff_t i_size;
struct timespec i_atime;

diff ./include/linux/quota.h~current~ ./include/linux/quota.h
--- ./include/linux/quota.h~current~ 2005-12-07 15:51:56.000000000 +1100
+++ ./include/linux/quota.h 2005-12-07 11:28:43.000000000 +1100
@@ -56,9 +56,10 @@ extern spinlock_t dq_data_lock;
#define kb2qb(x) ((x) >> (QUOTABLOCK_BITS-10))
#define toqb(x) (((x) + QUOTABLOCK_SIZE - 1) >> QUOTABLOCK_BITS)

-#define MAXQUOTAS 2
+#define MAXQUOTAS 3
#define USRQUOTA 0 /* element used for user quotas */
#define GRPQUOTA 1 /* element used for group quotas */
+#define TREEQUOTA 2 /* element used for tree quotas */

/*
* Definitions for the default names of the quotas files.
@@ -66,6 +67,7 @@ extern spinlock_t dq_data_lock;
#define INITQFNAMES { \
"user", /* USRQUOTA */ \
"group", /* GRPQUOTA */ \
+ "tree", /* TREEQUOTA */ \
"undefined", \
};

@@ -282,6 +284,7 @@ struct quota_format_type {

#define DQUOT_USR_ENABLED 0x01 /* User diskquotas enabled */
#define DQUOT_GRP_ENABLED 0x02 /* Group diskquotas enabled */
+#define DQUOT_TREE_ENABLED 0x04 /* Tree diskquotas enabled */

struct quota_info {
unsigned int flags; /* Flags for diskquotas on this device */
@@ -299,11 +302,16 @@ int mark_dquot_dirty(struct dquot *dquot

#define dquot_dirty(dquot) test_bit(DQ_MOD_B, &(dquot)->dq_flags)

-#define sb_has_quota_enabled(sb, type) ((type)==USRQUOTA ? \
- (sb_dqopt(sb)->flags & DQUOT_USR_ENABLED) : (sb_dqopt(sb)->flags & DQUOT_GRP_ENABLED))
+#define sb_has_quota_enabled(sb, type) \
+ ((type)==USRQUOTA ? \
+ (sb_dqopt(sb)->flags & DQUOT_USR_ENABLED) : \
+ ((type)==GRPQUOTA ? \
+ (sb_dqopt(sb)->flags & DQUOT_GRP_ENABLED) : \
+ (sb_dqopt(sb)->flags & DQUOT_TREE_ENABLED)))

#define sb_any_quota_enabled(sb) (sb_has_quota_enabled(sb, USRQUOTA) | \
- sb_has_quota_enabled(sb, GRPQUOTA))
+ sb_has_quota_enabled(sb, GRPQUOTA) | \
+ sb_has_quota_enabled(sb, TREEQUOTA))

int register_quota_format(struct quota_format_type *fmt);
void unregister_quota_format(struct quota_format_type *fmt);

diff ./include/linux/quotaops.h~current~ ./include/linux/quotaops.h
--- ./include/linux/quotaops.h~current~ 2005-12-07 15:51:56.000000000 +1100
+++ ./include/linux/quotaops.h 2005-12-07 11:31:27.000000000 +1100
@@ -183,6 +183,59 @@ static __inline__ int DQUOT_OFF(struct s
return ret;
}

+
+static __inline__ int treequota_parent_uid_ok(const struct inode *inode, const struct inode *dir, const uid_t uid)
+{
+ if ( !(inode->i_sb->s_dquot.flags & DQUOT_TREE_ENABLED))
+ return 1;
+ if ((dir->i_tid && dir != inode)
+ ? (inode->i_tid == dir->i_tid)
+ : (inode->i_tid == uid))
+ return 1;
+ return 0;
+}
+
+static __inline__ int treequota_parent_ok(const struct inode *inode, const struct inode *dir)
+{
+ return treequota_parent_uid_ok(inode,dir, inode->i_uid);
+}
+
+static __inline__ int treequota_tid(const struct inode *dir, const uid_t uid)
+{
+ if (!(dir->i_sb->s_dquot.flags & DQUOT_TREE_ENABLED))
+ return 0;
+ return dir->i_tid
+ ? dir->i_tid
+ : uid;
+}
+
+static __inline__ void treequota_check(struct dentry *dentry)
+{
+ struct inode *inode;
+ struct iattr attr;
+
+ if (!dentry || IS_ERR(dentry))
+ return;
+ inode = dentry->d_inode;
+ if (!inode || ! (inode->i_sb->s_dquot.flags & DQUOT_TREE_ENABLED))
+ return;
+ if (treequota_parent_ok(inode, dentry->d_parent->d_inode))
+ return;
+
+ attr.ia_valid = ATTR_FORCE | ATTR_TID;
+ attr.ia_tid = treequota_tid(dentry->d_parent->d_inode,
+ inode->i_uid);
+ if (!S_ISDIR(inode->i_mode)
+ && inode->i_nlink > 1) {
+ printk(KERN_WARNING "treequota: file with multiple links has wrong tree-id\n");
+ printk(KERN_WARNING " dev=%x ino=%ld dino=%ld\n",
+ inode->i_sb->s_dev, inode->i_ino,
+ dentry->d_parent->d_inode->i_ino);
+ printk(KERN_WARNING " basename=%s\n", dentry->d_name.name);
+ }
+ notify_change(dentry, &attr);
+}
+
#else

/*
@@ -235,6 +288,11 @@ extern __inline__ void DQUOT_FREE_SPACE(
mark_inode_dirty(inode);
}

+#define treequota_parent_uid_ok(inode,dir,uid) (1)
+#define treequota_parent_ok(inode,dir) (1)
+#define treequota_tid(inode,uid) (0)
+#define treequota_check(dentry) ((void)(0))
+
#endif /* CONFIG_QUOTA */

#define DQUOT_PREALLOC_BLOCK_NODIRTY(inode, nr) DQUOT_PREALLOC_SPACE_NODIRTY(inode, ((qsize_t)(nr)) << (inode)->i_sb->s_blocksize_bits)

diff ./include/linux/stat.h~current~ ./include/linux/stat.h
--- ./include/linux/stat.h~current~ 2005-12-07 15:51:56.000000000 +1100
+++ ./include/linux/stat.h 2005-12-07 11:28:43.000000000 +1100
@@ -63,6 +63,7 @@ struct kstat {
unsigned int nlink;
uid_t uid;
gid_t gid;
+ uid_t tid;
dev_t rdev;
loff_t size;
struct timespec atime;

2006-02-13 23:11:15

by Chris Stromsoe

[permalink] [raw]
Subject: Re: any FS with tree-based quota system?

On Tue, 14 Feb 2006, Nathan Scott wrote:
> On Mon, Feb 13, 2006 at 11:03:35AM -0800, Chris Stromsoe wrote:
>> I'm looking for a file system with a tree-based quota system. XFS on
>> IRIX has projects, but that functionality didn't get ported over to
>> Linux that I can see.
>
> You didn't look very closely then. ;)

This is from a Debian stable machine, with xfsprogs 2.6.20. :) I'm more
than happy to be proven wrong.

cbs:/usr/share/doc/xfsprogs > zcat README.quota.gz | tail -13

On IRIX, XFS supports project quota. This is not (ever) likely to be
supported on Linux/XFS, as the concept of a project is specific to IRIX.
A filesystem that has used user quota on IRIX, however, can be migrated
to Linux, and vice-versa, as the ondisk format is shared between both
versions of XFS (and Linux/XFS is "endian clean"). Group quota support
has been implemented only in more recent versions of IRIX 6.5f, and the
same level of ondisk compatibility is now available as for user quota.

Have fun,

-- [email protected]




-Chris

2006-02-13 23:15:42

by Chris Stromsoe

[permalink] [raw]
Subject: Re: any FS with tree-based quota system?

On Tue, 14 Feb 2006, Neil Brown wrote:
> On Monday February 13, [email protected] wrote:
>>
>> I would like to be able to apply a quota to a particular tree, and have
>> every file and directory in the path of that tree count toward that
>> tree's quota usage. I can prevent hard links across trees.
>>
>> I noticed that Neil Brown wrote some patches fairly early on in the 2.4
>> cycle to do tree-based quota by UID. The last patch-set I found was
>> against 2.4.14
>> (http://cgi.cse.unsw.edu.au/~neilb/patches/linux/2.4.14/) from late
>> 2001, and did not come with patches to quota-tools.
>
> Following is my tree-quota patch updated to 2.6.14.3. However it
> doesn't do exactly what you claim to want.

Thanks.

> You still need to assign a uid to each user (the kernel needs some
> number to use as an index into the quotas file). But only the top-level
> directory of each tree needs to be owned by the uid. Files beneath the
> top can be owned by anyone.

I'm hoping to modify your patch to use the inode at the root of the tree
instead of a particular uid as the quota owner, so that setting a quota of
50Mb on /some/location would charge usage for any children of
/some/location to the inode of /some/location.

> I can dig-up a patch for quota-utils if you want to proceed with this.

I would appreciate that.



-Chris

2006-02-13 23:16:58

by Nathan Scott

[permalink] [raw]
Subject: Re: any FS with tree-based quota system?

On Mon, Feb 13, 2006 at 03:11:12PM -0800, Chris Stromsoe wrote:
> On Tue, 14 Feb 2006, Nathan Scott wrote:
> > On Mon, Feb 13, 2006 at 11:03:35AM -0800, Chris Stromsoe wrote:
> >> I'm looking for a file system with a tree-based quota system. XFS on
> >> IRIX has projects, but that functionality didn't get ported over to
> >> Linux that I can see.
> >
> > You didn't look very closely then. ;)
>
> This is from a Debian stable machine,

Well, yeah - thats ye olde worlde code.

> with xfsprogs 2.6.20. :) I'm more
> than happy to be proven wrong.
>
> cbs:/usr/share/doc/xfsprogs > zcat README.quota.gz | tail -13

Things have moved on since then - a more recent xfsprogs has an
xfs_quota(8) man page which will be of use to you here.

cheers.

--
Nathan

2006-02-13 23:17:59

by Chris Stromsoe

[permalink] [raw]
Subject: Re: any FS with tree-based quota system?

On Tue, 14 Feb 2006, Nathan Scott wrote:
> On Mon, Feb 13, 2006 at 03:11:12PM -0800, Chris Stromsoe wrote:
>> On Tue, 14 Feb 2006, Nathan Scott wrote:
>>> On Mon, Feb 13, 2006 at 11:03:35AM -0800, Chris Stromsoe wrote:
>>>> I'm looking for a file system with a tree-based quota system. XFS on
>>>> IRIX has projects, but that functionality didn't get ported over to
>>>> Linux that I can see.
>>>
>>> You didn't look very closely then. ;)
>>
>> This is from a Debian stable machine,
>
> Well, yeah - thats ye olde worlde code.

Yeah, a bit. :) (but, it's stable!)

>> with xfsprogs 2.6.20. :) I'm more
>> than happy to be proven wrong.
>>
>> cbs:/usr/share/doc/xfsprogs > zcat README.quota.gz | tail -13
>
> Things have moved on since then - a more recent xfsprogs has an
> xfs_quota(8) man page which will be of use to you here.

Thanks. I'll take a (better) look at it.


-Chris

2006-02-14 02:38:13

by NeilBrown

[permalink] [raw]
Subject: Re: any FS with tree-based quota system?

On Monday February 13, [email protected] wrote:
> On Tue, 14 Feb 2006, Neil Brown wrote:
> > You still need to assign a uid to each user (the kernel needs some
> > number to use as an index into the quotas file). But only the top-level
> > directory of each tree needs to be owned by the uid. Files beneath the
> > top can be owned by anyone.
>
> I'm hoping to modify your patch to use the inode at the root of the tree
> instead of a particular uid as the quota owner, so that setting a quota of
> 50Mb on /some/location would charge usage for any children of
> /some/location to the inode of /some/location.

That sounds possible...
So you are, in fact, assigning a uid to each user, but you are using
the filesystem to do it - the inode number that the filesystem uses
for the directory becomes the uid for the corresponding user.
You wouldn't even need to change the kernel patch.
When you make a directory do
mkdir $dir
chown `stat -c '%i' $dir` $dir

and the quotas will do what you want (you might need to fix
group-access so that you controlling application can access the files
properly.


>
> > I can dig-up a patch for quota-utils if you want to proceed with this.
>
> I would appreciate that.
>

The following seems to be against 3.09. There are actually two
patches, the main one and a little fixup afterwards.

These patches *don't* update the documentation (sorry). They fairly
consistently use 'Y' to request tree quotas, because:
- a 'Y' looks a bit like a tree and
- both 't' and 'T' were already taken in some tools.

NeilBrown


-----------------------
diff -Naur quota-tools.orig/configure quota-tools/configure
--- quota-tools.orig/configure 2002-11-13 04:00:32.000000000 +1100
+++ quota-tools/configure 2003-07-14 12:42:00.000000000 +1000
@@ -1133,6 +1133,8 @@
cat > conftest.$ac_ext <<EOF
#line 1135 "configure"
#include "confdefs.h"
+#include <stdio.h>
+#include <ext2fs/ext2_fs.h>
#include <ext2fs/ext2fs.h>
EOF
ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
diff -Naur quota-tools.orig/convertquota.c quota-tools/convertquota.c
--- quota-tools.orig/convertquota.c 2002-11-12 21:00:05.000000000 +1100
+++ quota-tools/convertquota.c 2003-07-14 12:42:00.000000000 +1000
@@ -28,7 +28,7 @@

char *mntpoint;
char *progname;
-int ucv, gcv;
+int ucv, gcv, tcv;
struct quota_handle *qn; /* Handle of new file */
int action; /* Action to be performed */

@@ -44,7 +44,7 @@
int ret;

action = ACT_FORMAT;
- while ((ret = getopt(argcnt, argstr, "Vugefh:")) != -1) {
+ while ((ret = getopt(argcnt, argstr, "YVugefh:")) != -1) {
switch (ret) {
case '?':
case 'h':
@@ -58,6 +58,9 @@
case 'g':
gcv = 1;
break;
+ case 'Y':
+ tcv = 1;
+ break;
case 'e':
action = ACT_ENDIAN;
break;
@@ -72,7 +75,7 @@
usage();
}

- if (!(ucv | gcv))
+ if (!(ucv | gcv | tcv))
ucv = 1;

mntpoint = argstr[optind];
@@ -355,6 +358,8 @@
ret |= convert_file(USRQUOTA, mnt);
if (gcv)
ret |= convert_file(GRPQUOTA, mnt);
+ if (tcv)
+ ret |= convert_file(TREEQUOTA, mnt);
end_mounts_scan();

if (ret)
diff -Naur quota-tools.orig/edquota.c quota-tools/edquota.c
--- quota-tools.orig/edquota.c 2003-07-14 14:18:16.000000000 +1000
+++ quota-tools/edquota.c 2003-07-14 12:42:00.000000000 +1000
@@ -100,9 +100,9 @@
dirname = NULL;
quotatype = USRQUOTA;
#if defined(RPC_SETQUOTA)
- while ((ret = getopt(argc, argv, "ugrntTVp:F:f:")) != -1) {
+ while ((ret = getopt(argc, argv, "YugrntTVp:F:f:")) != -1) {
#else
- while ((ret = getopt(argc, argv, "ugtTVp:F:f:")) != -1) {
+ while ((ret = getopt(argc, argv, "YugtTVp:F:f:")) != -1) {
#endif
switch (ret) {
case 'p':
@@ -112,6 +112,8 @@
case 'g':
quotatype = GRPQUOTA;
break;
+ case 'Y':
+ quotatype = TREEQUOTA;
#if defined(RPC_SETQUOTA)
case 'n':
case 'r':
diff -Naur quota-tools.orig/mntopt.h quota-tools/mntopt.h
--- quota-tools.orig/mntopt.h 2003-07-14 14:18:16.000000000 +1000
+++ quota-tools/mntopt.h 2003-07-14 12:42:00.000000000 +1000
@@ -18,6 +18,7 @@
#define MNTOPT_QUOTA "quota" /* enforce user quota */
#define MNTOPT_USRQUOTA "usrquota" /* enforce user quota */
#define MNTOPT_GRPQUOTA "grpquota" /* enforce group quota */
+#define MNTOPT_TREEQUOTA "treequota" /* enforce tree quota */
#define MNTOPT_RSQUASH "rsquash" /* root as ordinary user */
#define MNTOPT_BIND "bind" /* binded mount */
#define MNTOPT_LOOP "loop" /* loopback mount */
diff -Naur quota-tools.orig/quota.c quota-tools/quota.c
--- quota-tools.orig/quota.c 2003-07-14 14:18:16.000000000 +1000
+++ quota-tools/quota.c 2003-07-14 14:17:56.000000000 +1000
@@ -68,6 +68,7 @@
#define FL_LOCALONLY 32
#define FL_QUIETREFUSE 64
#define FL_NOAUTOFS 128
+#define FL_TREE 256

int flags, fmt = -1;
char *progname;
@@ -85,7 +86,7 @@
gettexton();
progname = basename(argv[0]);

- while ((ret = getopt(argc, argv, "guqvsVliQF:")) != -1) {
+ while ((ret = getopt(argc, argv, "YtguqvsVliQF:")) != -1) {
switch (ret) {
case 'g':
flags |= FL_GROUP;
@@ -93,6 +94,10 @@
case 'u':
flags |= FL_USER;
break;
+ case 'Y':
+ case 't':
+ flags |= FL_TREE;
+ break;
case 'q':
flags |= FL_QUIET;
break;
@@ -125,7 +130,8 @@
argc -= optind;
argv += optind;

- if (!(flags & FL_USER) && !(flags & FL_GROUP))
+ if (!(flags & FL_USER) && !(flags & FL_GROUP)
+ && !(flags & FL_TREE))
flags |= FL_USER;
init_kernel_interface();

@@ -140,16 +146,21 @@
for (i = 0; i < ngroups; i++)
ret |= showquotas(GRPQUOTA, gidset[i]);
}
+ if (flags & FL_TREE)
+ ret |= showquotas(TREEQUOTA, getuid());
exit(ret);
}

- if ((flags & FL_USER) && (flags & FL_GROUP))
+ if ((flags & (FL_USER|FL_TREE)) && (flags & FL_GROUP))
usage();

if (flags & FL_USER)
for (; argc > 0; argc--, argv++)
ret |= showquotas(USRQUOTA, user2uid(*argv));
- else if (flags & FL_GROUP)
+ if (flags & FL_TREE)
+ for (; argc > 0; argc--, argv++)
+ ret |= showquotas(TREEQUOTA, user2uid(*argv));
+ if (flags & FL_GROUP)
for (; argc > 0; argc--, argv++)
ret |= showquotas(GRPQUOTA, group2gid(*argv));
return ret;
@@ -158,8 +169,9 @@
void usage(void)
{
errstr( "%s%s%s",
- _("Usage: quota [-guqvs] [-l | -Q] [-i] [-F quotaformat]\n"),
+ _("Usage: quota [-gutYqvs] [-l | -Q] [-i] [-F quotaformat]\n"),
_("\tquota [-qvs] [-l | -Q] [-i] [-F quotaformat] -u username ...\n"),
+ _("\tquota [-qvs] [-l | -Q] [-i] [-F quotaformat] -Y username ...\n"),
_("\tquota [-qvs] [-l | -Q] [-i] [-F quotaformat] -g groupname ...\n"));
fprintf(stderr, _("Bugs to: %s\n"), MY_EMAIL);
exit(1);
diff -Naur quota-tools.orig/quota.h quota-tools/quota.h
--- quota-tools.orig/quota.h 2002-05-31 09:39:26.000000000 +1000
+++ quota-tools/quota.h 2003-07-14 12:42:00.000000000 +1000
@@ -6,9 +6,10 @@
typedef u_int32_t qid_t; /* Type in which we store ids in memory */
typedef u_int64_t qsize_t; /* Type in which we store size limitations */

-#define MAXQUOTAS 2
+#define MAXQUOTAS 3
#define USRQUOTA 0 /* element used for user quotas */
#define GRPQUOTA 1 /* element used for group quotas */
+#define TREEQUOTA 2 /* element used for tree quotas */

/*
* Definitions for the default names of the quotas files.
@@ -16,6 +17,7 @@
#define INITQFNAMES { \
"user", /* USRQUOTA */ \
"group", /* GRPQUOTA */ \
+ "tree", /* TREEQUOTA */ \
"undefined", \
}

@@ -24,7 +26,8 @@
*/
#define INITQMAGICS {\
0xd9c01f11, /* USRQUOTA */\
- 0xd9c01927 /* GRPQUOTA */\
+ 0xd9c01927, /* GRPQUOTA */\
+ 0xd9c01abc /* TREEQUOTA */\
}

/* Size of blocks in which are counted size limits in generic utility parts */
diff -Naur quota-tools.orig/quotacheck.c quota-tools/quotacheck.c
--- quota-tools.orig/quotacheck.c 2003-07-14 14:18:16.000000000 +1000
+++ quota-tools/quotacheck.c 2003-07-14 14:14:34.000000000 +1000
@@ -28,7 +28,7 @@
#include <sys/mount.h>

#if defined(EXT2_DIRECT)
-#include <linux/ext2_fs.h>
+#include <ext2fs/ext2_fs.h>
#include <ext2fs/ext2fs.h>
#endif

@@ -63,7 +63,7 @@
dev_t cur_dev; /* Device we are working on */
int files_done, dirs_done;
int flags, fmt = -1, cfmt; /* Options from command line; Quota format to use spec. by user; Actual format to check */
-int uwant, gwant, ucheck, gcheck; /* Does user want to check user/group quota; Do we check user/group quota? */
+int uwant, gwant, twant, ucheck, gcheck, tcheck; /* Does user want to check user/group quota; Do we check user/group quota? */
char *mntpoint; /* Mountpoint to check */
char *progname;
struct util_dqinfo old_info[MAXQUOTAS]; /* Loaded infos */
@@ -181,7 +181,7 @@
/*
* Add a number of blocks and inodes to a quota.
*/
-static void add_to_quota(int type, ino_t i_num, uid_t i_uid, gid_t i_gid, mode_t i_mode,
+static void add_to_quota(int type, ino_t i_num, uid_t i_uid, gid_t i_gid, uid_t i_tid, mode_t i_mode,
nlink_t i_nlink, loff_t i_space, int need_remember)
{
qid_t wanted;
@@ -189,6 +189,8 @@

if (type == USRQUOTA)
wanted = i_uid;
+ else if (type == TREEQUOTA)
+ wanted = i_tid;
else
wanted = i_gid;

@@ -281,7 +283,7 @@

static void usage(void)
{
- printf(_("Utility for checking and repairing quota files.\n%s [-gucfinvdmMR] [-F <quota-format>] filesystem|-a\n"), progname);
+ printf(_("Utility for checking and repairing quota files.\n%s [-gucfinvdmtYMR] [-F <quota-format>] filesystem|-a\n"), progname);
printf(_("Bugs to %s\n"), MY_EMAIL);
exit(1);
}
@@ -290,7 +292,7 @@
{
int ret;

- while ((ret = getopt(argcnt, argstr, "VhbcvugidnfF:mMRa")) != -1) {
+ while ((ret = getopt(argcnt, argstr, "tYVhbcvugidnfF:mMRa")) != -1) {
switch (ret) {
case 'b':
flags |= FL_BACKUPS;
@@ -301,6 +303,10 @@
case 'u':
uwant = 1;
break;
+ case 't':
+ case 'Y':
+ twant = 1;
+ break;
case 'd':
flags |= FL_DEBUG;
setlinebuf(stderr);
@@ -343,7 +349,7 @@
usage();
}
}
- if (!(uwant | gwant))
+ if (!(uwant | gwant | twant))
uwant = 1;
if ((argcnt == optind && !(flags & FL_ALL)) || (argcnt > optind && flags & FL_ALL)) {
fputs(_("Bad number of arguments.\n"), stderr);
@@ -375,6 +381,7 @@
uid_t uid;
gid_t gid;

+#define i_tid i_reserved2
if ((error = ext2fs_open(device, 0, 0, 0, unix_io_manager, &fs))) {
errstr(_("error (%d) while opening %s\n"), (int)error, device);
return -1;
@@ -410,11 +417,15 @@
if (inode.i_uid_high | inode.i_gid_high)
debug(FL_DEBUG, _("High uid detected.\n"));
if (ucheck)
- add_to_quota(USRQUOTA, i_num, uid, gid,
+ add_to_quota(USRQUOTA, i_num, uid, gid, inode.i_tid,
inode.i_mode, inode.i_links_count,
inode.i_blocks << 9, 0);
if (gcheck)
- add_to_quota(GRPQUOTA, i_num, uid, gid,
+ add_to_quota(GRPQUOTA, i_num, uid, gid, inode.i_tid,
+ inode.i_mode, inode.i_links_count,
+ inode.i_blocks << 9, 0);
+ if (tcheck)
+ add_to_quota(TREEQUOTA, i_num, uid, gid, inode.i_tid,
inode.i_mode, inode.i_links_count,
inode.i_blocks << 9, 0);
if (S_ISDIR(inode.i_mode))
@@ -453,10 +464,13 @@
}
qspace = getqsize(pathname, &st);
if (ucheck)
- add_to_quota(USRQUOTA, st.st_ino, st.st_uid, st.st_gid, st.st_mode,
+ add_to_quota(USRQUOTA, st.st_ino, st.st_uid, st.st_gid, 0, st.st_mode,
st.st_nlink, qspace, 0);
if (gcheck)
- add_to_quota(GRPQUOTA, st.st_ino, st.st_uid, st.st_gid, st.st_mode,
+ add_to_quota(GRPQUOTA, st.st_ino, st.st_uid, st.st_gid, 0, st.st_mode,
+ st.st_nlink, qspace, 0);
+ if (tcheck)
+ add_to_quota(TREEQUOTA, st.st_ino, st.st_uid, st.st_gid, 0, st.st_mode,
st.st_nlink, qspace, 0);

if ((dp = opendir(pathname)) == (DIR *) NULL)
@@ -477,10 +491,13 @@

qspace = getqsize(de->d_name, &st);
if (ucheck)
- add_to_quota(USRQUOTA, st.st_ino, st.st_uid, st.st_gid, st.st_mode,
+ add_to_quota(USRQUOTA, st.st_ino, st.st_uid, st.st_gid, 0, st.st_mode,
st.st_nlink, qspace, !S_ISDIR(st.st_mode));
if (gcheck)
- add_to_quota(GRPQUOTA, st.st_ino, st.st_uid, st.st_gid, st.st_mode,
+ add_to_quota(GRPQUOTA, st.st_ino, st.st_uid, st.st_gid, 0, st.st_mode,
+ st.st_nlink, qspace, !S_ISDIR(st.st_mode));
+ if (tcheck)
+ add_to_quota(TREEQUOTA, st.st_ino, st.st_uid, st.st_gid, 0, st.st_mode,
st.st_nlink, qspace, !S_ISDIR(st.st_mode));

if (S_ISDIR(st.st_mode)) {
@@ -766,7 +783,10 @@
if (gcheck)
if (process_file(mnt, GRPQUOTA) < 0)
gcheck = 0;
- if (!ucheck && !gcheck) /* Nothing to check? */
+ if (tcheck)
+ if (process_file(mnt, TREEQUOTA) < 0)
+ tcheck = 0;
+ if (!ucheck && !gcheck && !tcheck) /* Nothing to check? */
return;
if (!(flags & FL_NOREMOUNT)) {
/* Now we try to remount fs read-only to prevent races when scanning filesystem */
@@ -817,6 +837,8 @@
dump_to_file(mnt, USRQUOTA);
if (gcheck)
dump_to_file(mnt, GRPQUOTA);
+ if (tcheck)
+ dump_to_file(mnt, TREEQUOTA);
out:
remove_list();
}
@@ -834,6 +856,10 @@
else if ((option = hasmntopt(mnt, MNTOPT_QUOTA)))
option += strlen(MNTOPT_QUOTA);
}
+ else if (type == TREEQUOTA) {
+ if ((option = hasmntopt(mnt, MNTOPT_TREEQUOTA)))
+ option += strlen(MNTOPT_TREEQUOTA);
+ }
else {
if ((option = hasmntopt(mnt, MNTOPT_GRPQUOTA)))
option += strlen(MNTOPT_GRPQUOTA);
@@ -881,10 +907,14 @@
gcheck = 1;
else
gcheck = 0;
- if (!ucheck && !gcheck)
+ if (twant && hasquota(mnt, TREEQUOTA))
+ tcheck = 1;
+ else
+ tcheck = 0;
+ if (!ucheck && !gcheck && !tcheck)
continue;
if (cfmt == -1) {
- if ((cfmt = detect_filename_format(mnt, ucheck ? USRQUOTA : GRPQUOTA)) == -1) {
+ if ((cfmt = detect_filename_format(mnt, ucheck ? USRQUOTA : tcheck ? TREEQUOTA : GRPQUOTA)) == -1) {
errstr(_("Cannot guess format from filename on %s. Please specify format on commandline.\n"),
mnt->mnt_fsname);
continue;
diff -Naur quota-tools.orig/quotaon.c quota-tools/quotaon.c
--- quota-tools.orig/quotaon.c 2002-06-17 05:00:46.000000000 +1000
+++ quota-tools/quotaon.c 2003-07-14 12:42:00.000000000 +1000
@@ -55,6 +55,7 @@
#define FL_ALL 8
#define FL_STAT 16
#define FL_OFF 32
+#define FL_TREE 64

int flags, fmt = -1;
char *progname;
@@ -72,7 +73,7 @@
{
int c;

- while ((c = getopt(argcnt, argstr, "afvugpx:VF:")) != -1) {
+ while ((c = getopt(argcnt, argstr, "tYafvugpx:VF:")) != -1) {
switch (c) {
case 'a':
flags |= FL_ALL;
@@ -86,6 +87,10 @@
case 'u':
flags |= FL_USER;
break;
+ case 't':
+ case 'Y':
+ flags |= FL_TREE;
+ break;
case 'v':
flags |= FL_VERBOSE;
break;
@@ -114,8 +119,8 @@
fputs(_("Can't turn on/off quotas via RPC.\n"), stderr);
exit(1);
}
- if (!(flags & (FL_USER | FL_GROUP)))
- flags |= FL_USER | FL_GROUP;
+ if (!(flags & (FL_USER | FL_GROUP | FL_TREE)))
+ flags |= FL_USER | FL_GROUP | FL_TREE;
if (!(flags & FL_ALL)) {
mntpoints = argstr + optind;
mntcnt = argcnt - optind;
@@ -330,12 +335,16 @@
errs += newstate(mnt, GRPQUOTA, xarg);
if (flags & FL_USER)
errs += newstate(mnt, USRQUOTA, xarg);
+ if (flags & FL_TREE)
+ errs += newstate(mnt, TREEQUOTA, xarg);
}
else {
if (flags & FL_GROUP)
errs += print_state(mnt, GRPQUOTA);
if (flags & FL_USER)
errs += print_state(mnt, USRQUOTA);
+ if (flags & FL_TREE)
+ errs += print_state(mnt, TREEQUOTA);
}
}
end_mounts_scan();
diff -Naur quota-tools.orig/quotaops.c quota-tools/quotaops.c
--- quota-tools.orig/quotaops.c 2002-11-22 05:37:58.000000000 +1100
+++ quota-tools/quotaops.c 2003-07-14 12:42:00.000000000 +1000
@@ -104,6 +104,7 @@
#if defined(BSD_BEHAVIOUR)
switch (handles[i]->qh_type) {
case USRQUOTA:
+ case TREEQUOTA:
euid = geteuid();
if (euid != id && euid != 0) {
uid2user(id, name);
diff -Naur quota-tools.orig/quotasys.c quota-tools/quotasys.c
--- quota-tools.orig/quotasys.c 2003-07-14 14:18:16.000000000 +1000
+++ quota-tools/quotasys.c 2003-07-14 12:42:00.000000000 +1000
@@ -97,7 +97,7 @@
*/
int name2id(char *name, int qtype)
{
- if (qtype == USRQUOTA)
+ if (qtype == USRQUOTA || qtype == TREEQUOTA)
return user2uid(name);
else
return group2gid(name);
@@ -140,7 +140,7 @@
*/
int id2name(int id, int qtype, char *buf)
{
- if (qtype == USRQUOTA)
+ if (qtype == USRQUOTA || qtype == TREEQUOTA)
return uid2user(id, buf);
else
return gid2group(id, buf);
@@ -393,6 +393,8 @@
return 1;
if ((type == GRPQUOTA) && hasmntopt(mnt, MNTOPT_GRPQUOTA))
return 1;
+ if ((type == TREEQUOTA) && hasmntopt(mnt, MNTOPT_TREEQUOTA))
+ return 1;
if ((type == USRQUOTA) && hasmntopt(mnt, MNTOPT_QUOTA))
return 1;
return 0;
@@ -446,6 +448,10 @@
if (*(pathname = option + strlen(MNTOPT_GRPQUOTA)) == '=')
has_quota_file_definition = 1;
}
+ else if (type == TREEQUOTA && (option = hasmntopt(mnt, MNTOPT_TREEQUOTA))) {
+ if (*(pathname = option + strlen(MNTOPT_TREEQUOTA)) == '=')
+ has_quota_file_definition = 1;
+ }
else if (type == USRQUOTA && (option = hasmntopt(mnt, MNTOPT_QUOTA))) {
if (*(pathname = option + strlen(MNTOPT_QUOTA)) == '=')
has_quota_file_definition = 1;
@@ -625,7 +631,7 @@
static int v1_kern_quota_on(const char *dev, int type)
{
char tmp[1024]; /* Just temporary buffer */
- qid_t id = (type == USRQUOTA) ? getuid() : getgid();
+ qid_t id = (type != GRPQUOTA) ? getuid() : getgid();

if (!quotactl(QCMD(Q_V1_GETQUOTA, type), dev, id, tmp)) /* OK? */
return 1;
@@ -636,7 +642,7 @@
static int v2_kern_quota_on(const char *dev, int type)
{
char tmp[1024]; /* Just temporary buffer */
- qid_t id = (type == USRQUOTA) ? getuid() : getgid();
+ qid_t id = (type != GRPQUOTA) ? getuid() : getgid();

if (!quotactl(QCMD(Q_V2_GETQUOTA, type), dev, id, tmp)) /* OK? */
return 1;
diff -Naur quota-tools.orig/repquota.c quota-tools/repquota.c
--- quota-tools.orig/repquota.c 2003-07-14 14:18:16.000000000 +1000
+++ quota-tools/repquota.c 2003-07-14 13:45:08.000000000 +1000
@@ -33,6 +33,7 @@
#define FL_NONAME 64 /* Don't translate ids to names */
#define FL_NOCACHE 128 /* Don't cache dquots before resolving */
#define FL_NOAUTOFS 256 /* Ignore autofs mountpoints */
+#define FL_TREE 512 /* Tree-based quotas */

int flags, fmt = -1;
char **mnt;
@@ -53,7 +54,7 @@
int ret;
int cache_specified = 0;

- while ((ret = getopt(argcnt, argstr, "VavughtsncCiF:")) != -1) {
+ while ((ret = getopt(argcnt, argstr, "YVavughtsncCiF:")) != -1) {
switch (ret) {
case '?':
case 'h':
@@ -67,6 +68,9 @@
case 'g':
flags |= FL_GROUP;
break;
+ case 'Y':
+ flags |= FL_TREE;
+ break;
case 'v':
flags |= FL_VERBOSE;
break;
@@ -112,7 +116,7 @@
fputs(_("Specified both -n and -t but only one of them can be used.\n"), stderr);
exit(1);
}
- if (!(flags & (FL_USER | FL_GROUP)))
+ if (!(flags & (FL_USER | FL_GROUP | FL_TREE)))
flags |= FL_USER;
if (!(flags & FL_ALL)) {
mnt = argstr + optind;
@@ -173,13 +177,12 @@

if (!cached_dquots)
return;
- if (type == USRQUOTA) {
+ if (type == USRQUOTA || type == TREEQUOTA ) {
struct passwd *pwent;

- setpwent();
- while ((pwent = getpwent())) {
- for (i = 0; i < cached_dquots && pwent->pw_uid != dquot_cache[i].dq_id; i++);
- if (i < cached_dquots && !(dquot_cache[i].dq_flags & DQ_PRINTED)) {
+ for (i = 0 ; i < cached_dquots; i++) {
+ pwent = getpwuid(dquot_cache[i].dq_id);
+ if (pwent && !(dquot_cache[i].dq_flags & DQ_PRINTED)) {
print(dquot_cache+i, pwent->pw_name);
dquot_cache[i].dq_flags |= DQ_PRINTED;
}
@@ -189,10 +192,10 @@
else {
struct group *grent;

- setgrent();
- while ((grent = getgrent())) {
- for (i = 0; i < cached_dquots && grent->gr_gid != dquot_cache[i].dq_id; i++);
- if (i < cached_dquots && !(dquot_cache[i].dq_flags & DQ_PRINTED)) {
+
+ for (i = 0; i < cached_dquots ; i++) {
+ grent = getgrgid(dquot_cache[i].dq_id);
+ if (grent && !(dquot_cache[i].dq_flags & DQ_PRINTED)) {
print(dquot_cache+i, grent->gr_name);
dquot_cache[i].dq_flags |= DQ_PRINTED;
}
@@ -243,7 +246,7 @@
time2str(h->qh_info.dqi_igrace, igbuf, TF_ROUND);
printf(_("Block grace time: %s; Inode grace time: %s\n"), bgbuf, igbuf);
printf(_(" Block limits File limits\n"));
- printf(_("%-9s used soft hard grace used soft hard grace\n"), (type == USRQUOTA)?_("User"):_("Group"));
+ printf(_("%-9s used soft hard grace used soft hard grace\n"), (type == USRQUOTA)?_("User"):(type == GRPQUOTA)?_("Group"):_("Tree"));
printf("----------------------------------------------------------------------\n");

if (h->qh_ops->scan_dquots(h, output) < 0)
@@ -284,5 +287,8 @@
if (flags & FL_GROUP)
report(GRPQUOTA);

+ if (flags & FL_TREE)
+ report(TREEQUOTA);
+
return 0;
}
diff -Naur quota-tools.orig/rquota_server.c quota-tools/rquota_server.c
--- quota-tools.orig/rquota_server.c 2003-07-14 14:18:16.000000000 +1000
+++ quota-tools/rquota_server.c 2003-07-14 12:42:00.000000000 +1000
@@ -223,6 +223,11 @@
return (&result);
}

+ if (type == TREEQUOTA && unix_cred->aup_uid && unix_cred->aup_uid != id) {
+ result.status = Q_EPERM;
+ return (&result);
+ }
+
if (type == GRPQUOTA && unix_cred->aup_uid && unix_cred->aup_gid != id &&
!in_group((gid_t *) unix_cred->aup_gids, unix_cred->aup_len, id)) {
result.status = Q_EPERM;
diff -Naur quota-tools.orig/set_limits_example.c quota-tools/set_limits_example.c
--- quota-tools.orig/set_limits_example.c 2001-07-18 07:58:31.000000000 +1000
+++ quota-tools/set_limits_example.c 2003-07-14 12:42:00.000000000 +1000
@@ -28,6 +28,29 @@
}
}

+int copy_tree_quota_limits(const char *block_device, uid_t from, uid_t to)
+{
+ struct dqblk dq;
+
+ if (quotactl(QCMD(Q_GETQUOTA, TREEQUOTA), block_device, from, (caddr_t) & dq) == 0) {
+ if (quotactl(QCMD(Q_SETQLIM, TREEQUOTA), block_device, to, (caddr_t) & dq) == 0) {
+ return (0);
+ }
+ else {
+ errstr(
+ _("copy_tree_quota_limits: Failed to set treequota for uid %ld : %s\n"),
+ to, strerror(errno));
+ return (1);
+ }
+ }
+ else {
+ errstr(
+ _("copy_tree_quota_limits: Failed to get treequota for uid %ld : %s\n"),
+ from, strerror(errno));
+ return (1);
+ }
+}
+
int copy_group_quota_limits(const char *block_device, gid_t from, gid_t to)
{
struct dqblk dq;
diff -Naur quota-tools.orig/setquota.c quota-tools/setquota.c
--- quota-tools.orig/setquota.c 2003-07-14 14:18:16.000000000 +1000
+++ quota-tools/setquota.c 2003-07-14 12:42:00.000000000 +1000
@@ -28,6 +28,7 @@
#define FL_PROTO 16
#define FL_GRACE 32
#define FL_INDIVIDUAL_GRACE 64
+#define FL_TREE 128

int flags, fmt = -1;
char **mnt;
@@ -78,6 +79,8 @@
return USRQUOTA;
if (flags & FL_GROUP)
return GRPQUOTA;
+ if (flags & FL_TREE)
+ return TREEQUOTA;
return -1;
}

@@ -88,9 +91,9 @@
char *protoname = NULL;

#ifdef RPC_SETQUOTA
- char *opts = "igp:urVF:taT";
+ char *opts = "Yigp:urVF:taT";
#else
- char *opts = "igp:uVF:taT";
+ char *opts = "Yigp:uVF:taT";
#endif

while ((ret = getopt(argcnt, argstr, opts)) != -1) {
@@ -104,6 +107,9 @@
case 'u':
flags |= FL_USER;
break;
+ case 'Y':
+ flags |= FL_TREE;
+ break;
case 'p':
flags |= FL_PROTO;
protoname = optarg;
@@ -150,7 +156,7 @@
fputs(_("Bad number of arguments.\n"), stderr);
usage();
}
- if (!(flags & (FL_USER | FL_GROUP)))
+ if (!(flags & (FL_USER | FL_GROUP | FL_TREE)))
flags |= FL_USER;
if (!(flags & FL_GRACE)) {
id = name2id(argstr[optind++], flag2type(flags));
@@ -247,7 +253,7 @@
q->dq_dqb.dqb_itime = toset.dqb_itime;
}
if (putprivs(curprivs, COMMIT_TIMES) == -1)
- errstr(_("Can't write times for %s. Maybe kernel doesn't support such operation?\n"), type2name(flags & FL_USER ? USRQUOTA : GRPQUOTA));
+ errstr(_("Can't write times for %s. Maybe kernel doesn't support such operation?\n"), type2name(flags & FL_USER ? USRQUOTA : flags & FL_GROUP ? GRPQUOTA : TREEQUOTA));
freeprivs(curprivs);
}

------------------------------------------------------
--- quotatools/quotasys.c~ 2003-08-04 16:23:59.000000000 +1000
+++ quotatools/quotasys.c 2003-08-04 16:28:54.000000000 +1000
@@ -775,7 +775,7 @@

/* Further we are not interested in mountpoints without quotas and
we don't want to touch them */
- if (!CORRECT_FSTYPE(mnt->mnt_type) || hasmntopt(mnt, MNTOPT_NOQUOTA) || !(hasmntopt(mnt, MNTOPT_USRQUOTA) || hasmntopt(mnt, MNTOPT_GRPQUOTA) || hasmntopt(mnt, MNTOPT_QUOTA) || !strcmp(mnt->mnt_type, MNTTYPE_NFS))) {
+ if (!CORRECT_FSTYPE(mnt->mnt_type) || hasmntopt(mnt, MNTOPT_NOQUOTA) || !(hasmntopt(mnt, MNTOPT_USRQUOTA) || hasmntopt(mnt, MNTOPT_GRPQUOTA) || hasmntopt(mnt, MNTOPT_TREEQUOTA) || hasmntopt(mnt, MNTOPT_QUOTA) || !strcmp(mnt->mnt_type, MNTTYPE_NFS))) {
free((char *)devname);
continue;
}