diff -u linux/fs/nfs/dir.c.nfs-ngroups linux/fs/nfs/dir.c --- linux/fs/nfs/dir.c.nfs-ngroups Mon Jul 31 23:49:39 2000 +++ linux/fs/nfs/dir.c Wed Aug 2 22:52:04 2000 @@ -521,6 +521,9 @@ /* * Do a new lookup and check the dentry attributes. */ + if (__rpc_register_group(dir_i->i_gid)) + goto out_bad; /* no way to pass -ENOMEM back */ + error = NFS_PROTO(dir_i)->lookup(dir, &dentry->d_name, &fhandle, &fattr); if (error) @@ -638,6 +641,9 @@ dfprintk(VFS, "NFS: lookup(%s/%s)\n", dir->d_name.name, dentry->d_name.name); + error = __rpc_register_group(dir_i->i_gid); + if (error) + goto out; error = -ENAMETOOLONG; if (dentry->d_name.len > NFS_SERVER(dir_i)->namelen) @@ -704,6 +710,9 @@ dfprintk(VFS, "NFS: create(%x/%ld, %s\n", dir_i->i_dev, dir_i->i_ino, dentry->d_name.name); + error = __rpc_register_group(dir_i->i_gid); + if (error) + goto out; attr.ia_mode = mode; attr.ia_valid = ATTR_MODE; @@ -721,6 +730,7 @@ error = nfs_instantiate(dentry, &fhandle, &fattr); if (error || fhandle.size == 0) d_drop(dentry); +out: return error; } @@ -737,6 +747,9 @@ dfprintk(VFS, "NFS: mknod(%x/%ld, %s\n", dir_i->i_dev, dir_i->i_ino, dentry->d_name.name); + error = __rpc_register_group(dir_i->i_gid); + if (error) + goto out; attr.ia_mode = mode; attr.ia_valid = ATTR_MODE; @@ -748,6 +761,7 @@ error = nfs_instantiate(dentry, &fhandle, &fattr); if (error || fhandle.size == 0) d_drop(dentry); +out: return error; } @@ -764,6 +778,9 @@ dfprintk(VFS, "NFS: mkdir(%x/%ld, %s\n", dir_i->i_dev, dir_i->i_ino, dentry->d_name.name); + error = __rpc_register_group(dir_i->i_gid); + if (error) + goto out; attr.ia_valid = ATTR_MODE; attr.ia_mode = mode | S_IFDIR; @@ -784,6 +801,7 @@ error = nfs_instantiate(dentry, &fhandle, &fattr); if (error || fhandle.size == 0) d_drop(dentry); +out: return error; } @@ -794,9 +812,12 @@ dfprintk(VFS, "NFS: rmdir(%x/%ld, %s\n", dir_i->i_dev, dir_i->i_ino, dentry->d_name.name); + error = __rpc_register_group(dir_i->i_gid); - nfs_zap_caches(dir_i); - error = NFS_PROTO(dir_i)->rmdir(dir, &dentry->d_name); + if (!error) { + nfs_zap_caches(dir_i); + error = NFS_PROTO(dir_i)->rmdir(dir, &dentry->d_name); + } return error; } @@ -935,6 +956,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) { @@ -943,6 +968,7 @@ nfs_renew_times(dentry); } } +out: return error; } @@ -959,6 +985,9 @@ dfprintk(VFS, "NFS: symlink(%x/%ld, %s, %s)\n", dir_i->i_dev, dir_i->i_ino, dentry->d_name.name, symname); + error = __rpc_register_group(dir_i->i_gid); + if (error) + goto out; error = -ENAMETOOLONG; maxlen = (NFS_PROTO(dir_i)->version==2) ? NFS2_MAXPATHLEN : NFS3_MAXPATHLEN; @@ -1006,6 +1035,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->i_gid); + if (error) + goto out; /* * Drop the dentry in advance to force a new lookup. @@ -1016,6 +1048,7 @@ nfs_zap_caches(dir_i); NFS_CACHEINV(inode); error = NFS_PROTO(dir_i)->link(old_dentry, dir, &dentry->d_name); +out: return error; } @@ -1049,7 +1082,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, @@ -1064,6 +1097,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 @@ -1073,6 +1111,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)) diff -u linux/fs/nfs/inode.c.nfs-ngroups linux/fs/nfs/inode.c --- linux/fs/nfs/inode.c.nfs-ngroups Mon Jul 31 23:50:00 2000 +++ linux/fs/nfs/inode.c Wed Aug 2 22:46:17 2000 @@ -816,6 +816,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; + } error = nfs_wb_all(inode); if (error) @@ -886,13 +891,17 @@ { struct rpc_auth *auth; struct rpc_cred *cred; + int error; - lock_kernel(); - auth = NFS_CLIENT(inode)->cl_auth; - cred = rpcauth_lookupcred(auth, 0); - filp->private_data = cred; - unlock_kernel(); - return 0; + error = __rpc_register_group(inode->i_gid); + if (!error) { + lock_kernel(); + auth = NFS_CLIENT(inode)->cl_auth; + cred = rpcauth_lookupcred(auth, 0); + filp->private_data = cred; + unlock_kernel(); + } + return error; } int nfs_release(struct inode *inode, struct file *filp) @@ -942,6 +951,10 @@ } } NFS_FLAGS(inode) |= NFS_INO_REVALIDATING; + + status = __rpc_register_group(inode->i_gid); + if (status) + goto out; status = NFS_PROTO(inode)->getattr(dentry, &fattr); if (status) { diff -u linux/kernel/fork.c.nfs-ngroups linux/kernel/fork.c --- linux/kernel/fork.c.nfs-ngroups Mon Jul 31 23:50:24 2000 +++ linux/kernel/fork.c Mon Jul 31 23:50:27 2000 @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -636,6 +637,18 @@ p->flags = new_flags; } +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 @@ -739,11 +752,13 @@ 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; retval = copy_thread(0, clone_flags, usp, p, regs); if (retval) - goto bad_fork_cleanup_sighand; + goto bad_fork_cleanup_gidcache; p->semundo = NULL; /* Our parent execution domain becomes current domain @@ -790,6 +805,8 @@ down(&sem); return retval; +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 Mon Jul 31 23:50:24 2000 +++ linux/kernel/exit.c Mon Jul 31 23:50:27 2000 @@ -12,6 +12,7 @@ #ifdef CONFIG_BSD_PROCESS_ACCT #include #endif +#include #include #include @@ -271,6 +272,19 @@ __exit_sighand(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. @@ -440,6 +454,7 @@ #ifdef CONFIG_BSD_PROCESS_ACCT acct_process(code); #endif + __exit_gidcache(tsk); lock_kernel(); sem_exit(); __exit_mm(tsk); diff -u linux/kernel/sys.c.nfs-ngroups linux/kernel/sys.c --- linux/kernel/sys.c.nfs-ngroups Mon Jul 31 23:50:25 2000 +++ linux/kernel/sys.c Mon Jul 31 23:50:27 2000 @@ -911,6 +911,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/include/linux/sunrpc/clnt.h.nfs-ngroups linux/include/linux/sunrpc/clnt.h --- linux/include/linux/sunrpc/clnt.h.nfs-ngroups Mon Jul 31 23:49:12 2000 +++ linux/include/linux/sunrpc/clnt.h Mon Jul 31 23:55:09 2000 @@ -120,6 +120,15 @@ void rpc_restart_call(struct rpc_task *); void rpc_clnt_sigmask(struct rpc_clnt *clnt, sigset_t *oldset); void rpc_clnt_sigunmask(struct rpc_clnt *clnt, sigset_t *oldset); +int rpc_register_group(gid_t gid); + +static __inline__ +int __rpc_register_group(gid_t gid) +{ + if (current->ngroups <= RPC_NGROUPS || gid == current->fsgid) + 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 Mon Apr 7 20:35:32 1997 +++ linux/include/linux/sunrpc/msg_prot.h Mon Jul 31 23:50:27 2000 @@ -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 Mon Jul 31 23:50:27 2000 +++ linux/include/linux/sunrpc/gidcache.h Mon Jul 31 23:50:27 2000 @@ -0,0 +1,48 @@ +/* + * include/linux/sunrpc/gidcache.h + * + * Copyright (C) 2000, 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 Mon Jul 31 23:50:24 2000 +++ linux/include/linux/sched.h Mon Jul 31 23:52:21 2000 @@ -330,6 +330,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; @@ -689,6 +690,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 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 Mon Mar 20 17:14:04 2000 +++ linux/net/sunrpc/auth_unix.c Mon Jul 31 23:50:27 2000 @@ -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 @@ -78,18 +78,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; } return (struct rpc_cred *) cred; @@ -135,7 +145,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 @@ -143,11 +154,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; } @@ -188,7 +207,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 Mon Jul 31 23:49:47 2000 +++ linux/net/sunrpc/clnt.c Mon Jul 31 23:50:27 2000 @@ -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 @@ -348,6 +354,89 @@ task->tk_action = call_reserve; rpcproc_count(task->tk_client, task->tk_msg.rpc_proc)++; +} + +/* + * 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; + } + + 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; } /* diff -u linux/net/sunrpc/sunrpc_syms.c.nfs-ngroups linux/net/sunrpc/sunrpc_syms.c --- linux/net/sunrpc/sunrpc_syms.c.nfs-ngroups Sat Apr 22 01:08:52 2000 +++ linux/net/sunrpc/sunrpc_syms.c Mon Jul 31 23:50:27 2000 @@ -49,6 +49,7 @@ EXPORT_SYMBOL(rpc_clnt_sigunmask); EXPORT_SYMBOL(rpc_delay); EXPORT_SYMBOL(rpc_restart_call); +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 Sat Apr 29 07:50:39 2000 +++ linux/net/sunrpc/svcauth.c Mon Jul 31 23:50:27 2000 @@ -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++);