diff -u linux/fs/nfs/dir.c.nfs-ngroups linux/fs/nfs/dir.c --- linux/fs/nfs/dir.c.nfs-ngroups 2002-12-03 21:40:05.000000000 +0100 +++ linux/fs/nfs/dir.c 2003-06-22 15:50:11.000000000 +0200 @@ -505,6 +505,9 @@ if (NFS_STALE(inode)) goto out_bad; + if (rpc_register_group(dir->i_gid)) + goto out_bad; /* no way to pass -ENOMEM back */ + error = NFS_PROTO(dir)->lookup(dir, &dentry->d_name, &fhandle, &fattr); if (error) goto out_bad; @@ -580,6 +583,9 @@ dfprintk(VFS, "NFS: lookup(%s/%s)\n", dentry->d_parent->d_name.name, dentry->d_name.name); + error = rpc_register_group(dir->i_gid); + if (error) + goto out; error = -ENAMETOOLONG; if (dentry->d_name.len > NFS_SERVER(dir)->namelen) @@ -648,6 +654,9 @@ dfprintk(VFS, "NFS: create(%x/%ld, %s\n", dir->i_dev, dir->i_ino, dentry->d_name.name); + error = rpc_register_group(dir->i_gid); + if (error) + goto out; attr.ia_mode = mode; attr.ia_valid = ATTR_MODE; @@ -665,6 +674,7 @@ error = nfs_instantiate(dentry, &fhandle, &fattr); else d_drop(dentry); + out: return error; } @@ -680,6 +690,9 @@ dfprintk(VFS, "NFS: mknod(%x/%ld, %s\n", dir->i_dev, dir->i_ino, dentry->d_name.name); + error = rpc_register_group(dir->i_gid); + if (error) + goto out; attr.ia_mode = mode; attr.ia_valid = ATTR_MODE; @@ -691,6 +704,7 @@ error = nfs_instantiate(dentry, &fhandle, &fattr); else d_drop(dentry); + out: return error; } @@ -706,6 +720,9 @@ dfprintk(VFS, "NFS: mkdir(%x/%ld, %s\n", dir->i_dev, dir->i_ino, dentry->d_name.name); + error = rpc_register_group(dir->i_gid); + if (error) + goto out; attr.ia_valid = ATTR_MODE; attr.ia_mode = mode | S_IFDIR; @@ -726,6 +743,7 @@ error = nfs_instantiate(dentry, &fhandle, &fattr); else d_drop(dentry); + out: return error; } @@ -735,9 +753,12 @@ dfprintk(VFS, "NFS: rmdir(%x/%ld, %s\n", dir->i_dev, dir->i_ino, dentry->d_name.name); + error = rpc_register_group(dir->i_gid); - nfs_zap_caches(dir); - error = NFS_PROTO(dir)->rmdir(dir, &dentry->d_name); + if (!error) { + nfs_zap_caches(dir); + error = NFS_PROTO(dir)->rmdir(dir, &dentry->d_name); + } if (!error) dentry->d_inode->i_nlink = 0; @@ -882,6 +903,10 @@ dfprintk(VFS, "NFS: unlink(%x/%ld, %s)\n", dir->i_dev, dir->i_ino, dentry->d_name.name); + if ( (error = rpc_register_group(dentry->d_inode->i_gid)) + || (error = rpc_register_group(dir->i_gid)) + ) + goto out; error = nfs_sillyrename(dir, dentry); if (error && error != -EBUSY) { @@ -890,6 +915,7 @@ nfs_renew_times(dentry); } } + out: return error; } @@ -905,6 +931,9 @@ dfprintk(VFS, "NFS: symlink(%x/%ld, %s, %s)\n", dir->i_dev, dir->i_ino, dentry->d_name.name, symname); + error = rpc_register_group(dir->i_gid); + if (error) + goto out; error = -ENAMETOOLONG; maxlen = (NFS_PROTO(dir)->version==2) ? NFS2_MAXPATHLEN : NFS3_MAXPATHLEN; @@ -951,6 +980,9 @@ dfprintk(VFS, "NFS: link(%s/%s -> %s/%s)\n", old_dentry->d_parent->d_name.name, old_dentry->d_name.name, dentry->d_parent->d_name.name, dentry->d_name.name); + error = rpc_register_group(dir->i_gid); + if (error) + goto out; /* * Drop the dentry in advance to force a new lookup. @@ -961,6 +993,7 @@ nfs_zap_caches(dir); NFS_CACHEINV(inode); error = NFS_PROTO(dir)->link(inode, dir, &dentry->d_name); + out: return error; } @@ -994,7 +1027,7 @@ struct inode *old_inode = old_dentry->d_inode; struct inode *new_inode = new_dentry->d_inode; struct dentry *dentry = NULL, *rehash = NULL; - int error = -EBUSY; + int error; /* * To prevent any new references to the target during the rename, @@ -1009,6 +1042,11 @@ old_dentry->d_parent->d_name.name, old_dentry->d_name.name, new_dentry->d_parent->d_name.name, new_dentry->d_name.name, atomic_read(&new_dentry->d_count)); + if ( (error = rpc_register_group(old_inode->i_gid)) + || (error = rpc_register_group(old_dir->i_gid)) + || (error = rpc_register_group(new_dir->i_gid)) + ) + goto out; /* * First check whether the target is busy ... we can't @@ -1018,6 +1056,7 @@ * silly-rename. If the silly-rename succeeds, the * copied dentry is hashed and becomes the new target. */ + error = -EBUSY; if (!new_inode) goto go_ahead; if (S_ISDIR(new_inode->i_mode)) @@ -1101,6 +1140,10 @@ && error != -EACCES) goto out; + error = rpc_register_group(inode->i_gid); + if (error) + goto out; + error = NFS_PROTO(inode)->access(inode, mask, 0); if (error == -EACCES && NFS_CLIENT(inode)->cl_droppriv && diff -u linux/fs/nfs/inode.c.nfs-ngroups linux/fs/nfs/inode.c --- linux/fs/nfs/inode.c.nfs-ngroups 2002-12-03 21:40:05.000000000 +0100 +++ linux/fs/nfs/inode.c 2003-06-22 15:50:11.000000000 +0200 @@ -760,6 +760,11 @@ if (!S_ISREG(inode->i_mode)) attr->ia_valid &= ~ATTR_SIZE; + if (attr->ia_valid & ATTR_GID) { + error = rpc_register_group(attr->ia_gid); + if (error) + goto out; + } filemap_fdatasync(inode->i_mapping); error = nfs_wb_all(inode); @@ -850,6 +855,11 @@ { struct rpc_auth *auth; struct rpc_cred *cred; + int error; + + error = rpc_register_group(inode->i_gid); + if (error) + goto out; lock_kernel(); auth = NFS_CLIENT(inode)->cl_auth; @@ -858,7 +868,8 @@ if (filp->f_mode & FMODE_WRITE) nfs_set_mmcred(inode, cred); unlock_kernel(); - return 0; + out: + return error; } int nfs_release(struct inode *inode, struct file *filp) @@ -903,6 +914,10 @@ } NFS_FLAGS(inode) |= NFS_INO_REVALIDATING; + status = rpc_register_group(inode->i_gid); + if (status) + goto out; + status = NFS_PROTO(inode)->getattr(inode, &fattr); if (status) { dfprintk(PAGECACHE, "nfs_revalidate_inode: (%x/%Ld) getattr failed, error=%d\n", diff -u linux/kernel/fork.c.nfs-ngroups linux/kernel/fork.c --- linux/kernel/fork.c.nfs-ngroups 2003-06-22 15:50:08.000000000 +0200 +++ linux/kernel/fork.c 2003-06-22 15:50:11.000000000 +0200 @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -591,6 +592,18 @@ return ret; } +static inline int copy_gidcache(struct task_struct *p) +{ + struct rpc_gidcache *gidcache = p->gidcache; + + if (gidcache) { + p->gidcache = rpc_gidcache_dup(gidcache); + if (p->gidcache == NULL) + return -1; + } + return 0; +} + /* * Ok, this is the main fork-routine. It copies the system process * information (task[nr]) and sets up the necessary registers. It also @@ -712,8 +725,10 @@ goto bad_fork_cleanup_files; if (copy_sighand(clone_flags, p)) goto bad_fork_cleanup_fs; - if (copy_mm(clone_flags, p)) + if (copy_gidcache(p)) goto bad_fork_cleanup_sighand; + if (copy_mm(clone_flags, p)) + goto bad_fork_cleanup_gidcache; if (copy_namespace(clone_flags, p)) goto bad_fork_cleanup_mm; retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs); @@ -789,6 +804,8 @@ exit_namespace(p); bad_fork_cleanup_mm: exit_mm(p); +bad_fork_cleanup_gidcache: + exit_gidcache(p); bad_fork_cleanup_sighand: exit_sighand(p); bad_fork_cleanup_fs: diff -u linux/kernel/exit.c.nfs-ngroups linux/kernel/exit.c --- linux/kernel/exit.c.nfs-ngroups 2002-12-03 21:40:07.000000000 +0100 +++ linux/kernel/exit.c 2003-06-22 15:50:11.000000000 +0200 @@ -16,6 +16,7 @@ #ifdef CONFIG_BSD_PROCESS_ACCT #include #endif +#include #include #include @@ -272,6 +273,19 @@ __exit_fs(tsk); } +static inline void __exit_gidcache(struct task_struct *tsk) +{ + if (tsk->gidcache) { + rpc_gidcache_del(tsk->gidcache); + tsk->gidcache = NULL; + } +} + +void exit_gidcache(struct task_struct *tsk) +{ + __exit_gidcache(tsk); +} + /* * We can use these to temporarily drop into * "lazy TLB" mode and back. @@ -439,6 +453,7 @@ #ifdef CONFIG_BSD_PROCESS_ACCT acct_process(code); #endif + __exit_gidcache(tsk); __exit_mm(tsk); lock_kernel(); diff -u linux/kernel/sys.c.nfs-ngroups linux/kernel/sys.c --- linux/kernel/sys.c.nfs-ngroups 2003-06-22 15:50:08.000000000 +0200 +++ linux/kernel/sys.c 2003-06-22 15:50:11.000000000 +0200 @@ -991,6 +991,7 @@ if(copy_from_user(current->groups, grouplist, gidsetsize * sizeof(gid_t))) return -EFAULT; current->ngroups = gidsetsize; + exit_gidcache(current); return 0; } diff -u linux/kernel/uid16.c.nfs-ngroups linux/kernel/uid16.c --- linux/kernel/uid16.c.nfs-ngroups 2000-01-11 03:40:26.000000000 +0100 +++ linux/kernel/uid16.c 2003-06-22 15:50:11.000000000 +0200 @@ -139,6 +139,7 @@ for (i = 0 ; i < gidsetsize ; i++) current->groups[i] = (gid_t)groups[i]; current->ngroups = gidsetsize; + exit_gidcache(current); return 0; } diff -u linux/include/linux/sunrpc/clnt.h.nfs-ngroups linux/include/linux/sunrpc/clnt.h --- linux/include/linux/sunrpc/clnt.h.nfs-ngroups 2002-12-03 21:40:07.000000000 +0100 +++ linux/include/linux/sunrpc/clnt.h 2003-06-22 15:50:11.000000000 +0200 @@ -127,6 +127,15 @@ void rpc_clnt_sigmask(struct rpc_clnt *clnt, sigset_t *oldset); void rpc_clnt_sigunmask(struct rpc_clnt *clnt, sigset_t *oldset); void rpc_setbufsize(struct rpc_clnt *, unsigned int, unsigned int); +int __rpc_register_group(gid_t gid); + +static __inline__ +int rpc_register_group(gid_t gid) +{ + if (current->ngroups <= RPC_NGROUPS) + return 0; + return __rpc_register_group(gid); +} static __inline__ int rpc_call(struct rpc_clnt *clnt, u32 proc, void *argp, void *resp, int flags) diff -u linux/include/linux/sunrpc/msg_prot.h.nfs-ngroups linux/include/linux/sunrpc/msg_prot.h --- linux/include/linux/sunrpc/msg_prot.h.nfs-ngroups 1997-04-07 20:35:32.000000000 +0200 +++ linux/include/linux/sunrpc/msg_prot.h 2003-06-22 15:50:11.000000000 +0200 @@ -57,6 +57,7 @@ #define RPC_PMAP_PORT 111 #define RPC_MAXNETNAMELEN 256 +#define RPC_NGROUPS 16 #endif /* __KERNEL__ */ #endif /* _LINUX_SUNRPC_MSGPROT_H_ */ diff -u linux/include/linux/sunrpc/gidcache.h.nfs-ngroups linux/include/linux/sunrpc/gidcache.h --- linux/include/linux/sunrpc/gidcache.h.nfs-ngroups 2003-06-22 15:50:11.000000000 +0200 +++ linux/include/linux/sunrpc/gidcache.h 2003-06-22 15:50:11.000000000 +0200 @@ -0,0 +1,48 @@ +/* + * include/linux/sunrpc/gidcache.h + * + * Copyright (C) 2000-2004, Frank van Maarseveen + */ + +#ifndef _LINUX_SUNRPC_RPCGIDCACHE_H_ +#define _LINUX_SUNRPC_RPCGIDCACHE_H_ + +#ifdef __KERNEL__ + +#include + +struct rpc_gidcache { + int ngroups; + gid_t groups[RPC_NGROUPS]; + unsigned short lru[RPC_NGROUPS]; +}; + +static inline struct rpc_gidcache *rpc_gidcache_new(void) +{ + struct rpc_gidcache *gidcache; + + gidcache = kmalloc(sizeof(struct rpc_gidcache), GFP_KERNEL); + if (gidcache) { + gidcache->ngroups = 0; + } + return gidcache; +} + +static inline void rpc_gidcache_del(struct rpc_gidcache *gidcache) +{ + kfree(gidcache); +} + +static inline struct rpc_gidcache *rpc_gidcache_dup(struct rpc_gidcache *p) +{ + struct rpc_gidcache *gidcache; + + gidcache = kmalloc(sizeof(struct rpc_gidcache), GFP_KERNEL); + if (gidcache) { + memcpy(gidcache, p, sizeof(*gidcache)); + } + return gidcache; +} + +#endif /* __KERNEL__ */ +#endif /* _LINUX_SUNRPC_RPCGIDCACHE_H_ */ diff -u linux/include/linux/sched.h.nfs-ngroups linux/include/linux/sched.h --- linux/include/linux/sched.h.nfs-ngroups 2003-06-22 15:50:08.000000000 +0200 +++ linux/include/linux/sched.h 2003-06-22 15:50:11.000000000 +0200 @@ -375,6 +375,7 @@ kernel_cap_t cap_effective, cap_inheritable, cap_permitted; int keep_capabilities:1; struct user_struct *user; + struct rpc_gidcache *gidcache; /* limits */ struct rlimit rlim[RLIM_NLIMITS]; unsigned short used_math; @@ -791,6 +792,7 @@ extern void exit_mm(struct task_struct *); extern void exit_files(struct task_struct *); extern void exit_sighand(struct task_struct *); +extern void exit_gidcache(struct task_struct *); extern void reparent_to_init(void); extern void daemonize(void); diff -u linux/net/sunrpc/auth_unix.c.nfs-ngroups linux/net/sunrpc/auth_unix.c --- linux/net/sunrpc/auth_unix.c.nfs-ngroups 2001-09-24 12:18:04.000000000 +0200 +++ linux/net/sunrpc/auth_unix.c 2003-06-22 15:50:11.000000000 +0200 @@ -12,13 +12,13 @@ #include #include #include +#include -#define NFS_NGROUPS 16 struct unx_cred { struct rpc_cred uc_base; uid_t uc_fsuid; gid_t uc_gid, uc_fsgid; - gid_t uc_gids[NFS_NGROUPS]; + gid_t uc_gids[RPC_NGROUPS]; }; #define uc_uid uc_base.cr_uid #define uc_count uc_base.cr_count @@ -80,18 +80,28 @@ cred->uc_gid = cred->uc_fsgid = 0; cred->uc_gids[0] = NOGROUP; } else { - int groups = current->ngroups; - if (groups > NFS_NGROUPS) - groups = NFS_NGROUPS; + int ngroups; + gid_t *groups; + if (current->gidcache) { + ngroups = current->gidcache->ngroups; + groups = current->gidcache->groups; + } else { + ngroups = current->ngroups; + groups = current->groups; + } + if (ngroups > RPC_NGROUPS) { + ngroups = RPC_NGROUPS; + printk(KERN_WARNING "unx_create_cred: too many groups.\n"); + } cred->uc_uid = current->uid; cred->uc_gid = current->gid; cred->uc_fsuid = current->fsuid; cred->uc_fsgid = current->fsgid; - for (i = 0; i < groups; i++) - cred->uc_gids[i] = (gid_t) current->groups[i]; - if (i < NFS_NGROUPS) - cred->uc_gids[i] = NOGROUP; + for (i = 0; i < ngroups; i++) + cred->uc_gids[i] = groups[i]; + if (i < RPC_NGROUPS) + cred->uc_gids[i] = NOGROUP; } cred->uc_base.cr_ops = &unix_credops; @@ -138,7 +148,8 @@ int i; if (!(taskflags & RPC_TASK_ROOTCREDS)) { - int groups; + int ngroups; + gid_t *groups; if (cred->uc_uid != current->uid || cred->uc_gid != current->gid @@ -146,11 +157,19 @@ || cred->uc_fsgid != current->fsgid) return 0; - groups = current->ngroups; - if (groups > NFS_NGROUPS) - groups = NFS_NGROUPS; - for (i = 0; i < groups ; i++) - if (cred->uc_gids[i] != (gid_t) current->groups[i]) + if (current->gidcache) { + ngroups = current->gidcache->ngroups; + groups = current->gidcache->groups; + } else { + ngroups = current->ngroups; + groups = current->groups; + } + if (ngroups > RPC_NGROUPS) { + ngroups = RPC_NGROUPS; + printk(KERN_WARNING "unx_match: too many groups.\n"); + } + for (i = 0; i < ngroups ; i++) + if (cred->uc_gids[i] != groups[i]) return 0; return 1; } @@ -192,7 +211,7 @@ *p++ = htonl((u32) cred->uc_fsgid); } hold = p++; - for (i = 0; i < 16 && cred->uc_gids[i] != (gid_t) NOGROUP; i++) + for (i = 0; i < RPC_NGROUPS && cred->uc_gids[i] != (gid_t) NOGROUP; i++) *p++ = htonl((u32) cred->uc_gids[i]); *hold = htonl(p - hold - 1); /* gid array length */ *base = htonl((p - base - 1) << 2); /* cred length */ diff -u linux/net/sunrpc/clnt.c.nfs-ngroups linux/net/sunrpc/clnt.c --- linux/net/sunrpc/clnt.c.nfs-ngroups 2003-06-22 15:50:11.000000000 +0200 +++ linux/net/sunrpc/clnt.c 2003-06-22 15:50:11.000000000 +0200 @@ -19,6 +19,11 @@ * * Copyright (C) 1992,1993 Rick Sladkey * Copyright (C) 1995,1996 Olaf Kirch + * + * CHANGES + * 2000-07-26 Added rpc_register_group(), allowing a way to get around the + * RPC limit of 16 groups on NFS mounted filesystems (AUTH_UNIX). + * -- Frank van Maarseveen */ #include @@ -30,6 +35,7 @@ #include #include +#include #include @@ -389,6 +395,91 @@ } /* + * Update the per-process group id cache. + * + * In rpc_gidcache, groups[n] corresponds with lru[n]. All lru[] + * numbers are unique and identify the order in which the groups have been + * registered by this function, numbering from 1 to ngroups. The oldest + * registered group has value 1. The groups array is kept sorted to avoid + * blowing up the RPC credential cache. + * The memmoves below could be replaced by something fancier but it would only + * make a difference in really awkward situations. RPC_NGROUPS is 16 at most. + * + * This function is called via rpc_register_group(), but only when the + * process is a member of too many groups for RPC. + */ +int +__rpc_register_group(gid_t gid) +{ + int ngroups, i, j, n; + gid_t *groups; + unsigned short *lru; + struct rpc_gidcache *gidcache = current->gidcache; + + if (gidcache == NULL) { + gidcache = rpc_gidcache_new(); + if (gidcache == NULL) + return -ENOMEM; + current->gidcache = gidcache; + } + if (gid == current->fsgid) + return 0; + + ngroups = gidcache->ngroups; + groups = gidcache->groups; + lru = gidcache->lru; + for (i = 0; i < ngroups; ++i) { + if (groups[i] == gid) + break; + } + if (i < ngroups) { + unsigned short order = lru[i]; + if (order != (unsigned short)ngroups) { + for (j = 0; j < ngroups; ++j) { + if (lru[j] > order) + --lru[j]; + } + lru[i] = (unsigned short)ngroups; + } + return 0; /* ok: group already cached, lru[] updated */ + } + if (!in_group_p(gid)) + return 0; /* ignore: we're not a member */ + + /* + * Delete the oldest group (lru[] == 1) when all groups are in use. + */ + if (ngroups == RPC_NGROUPS) { + j = 0; + for (i = 0; i < ngroups; ++i) { + if (!--lru[i]) + j = i; + } + n = ngroups - (j + 1); + memmove(&groups[j], &groups[j + 1], n * sizeof(*groups)); + memmove(&lru[j], &lru[j + 1], n * sizeof(*lru)); + --ngroups; + } + + /* + * Insert the new group. First find the insertion point. + */ + for (i = 0; i < ngroups; ++i) { + if (groups[i] > gid) { + break; + } + } + n = ngroups - i; + memmove(&groups[i + 1], &groups[i], n * sizeof(*groups)); + memmove(&lru[i + 1], &lru[i], n * sizeof(*lru)); + ++ngroups; + groups[i] = gid; + lru[i] = (unsigned short)ngroups; + gidcache->ngroups = ngroups; + return 0; +} + +/* * 1. Reserve an RPC call slot */ static void diff -u linux/net/sunrpc/sunrpc_syms.c.nfs-ngroups linux/net/sunrpc/sunrpc_syms.c --- linux/net/sunrpc/sunrpc_syms.c.nfs-ngroups 2003-06-22 15:50:11.000000000 +0200 +++ linux/net/sunrpc/sunrpc_syms.c 2003-06-22 15:50:11.000000000 +0200 @@ -50,6 +50,7 @@ EXPORT_SYMBOL(rpc_delay); EXPORT_SYMBOL(rpc_restart_call); EXPORT_SYMBOL(rpc_setbufsize); +EXPORT_SYMBOL(__rpc_register_group); /* Client transport */ EXPORT_SYMBOL(xprt_create_proto); diff -u linux/net/sunrpc/svcauth.c.nfs-ngroups linux/net/sunrpc/svcauth.c --- linux/net/sunrpc/svcauth.c.nfs-ngroups 2000-04-29 07:50:39.000000000 +0200 +++ linux/net/sunrpc/svcauth.c 2003-06-22 15:50:11.000000000 +0200 @@ -138,7 +138,7 @@ cred->cr_gid = ntohl(*bufp++); /* gid */ slen = ntohl(*bufp++); /* gids length */ - if (slen > 16 || (len -= slen + 2) < 0) + if (slen > RPC_NGROUPS || (len -= slen + 2) < 0) goto badcred; for (i = 0; i < NGROUPS && i < slen; i++) cred->cr_groups[i] = ntohl(*bufp++);