setgroupsシステムコール
int gidsetsizeはグループID数で、gid_t grouplist[]はgidsetsize個のグループIDです。設定できる最大グループID数は、NGROUPS_MAX=65536(グループID=0を含めて16ビット内のグループID数)です。
groups_alloc()でgidsetsizeに応じたstruct group_info group_infoを取得し、引数のgrouplistを複写し、current->cred->group_info = group_infoとし、プロセスが引数のグループ群に属する事になります。この時、groups_search()でグループIDの検索の効率化のため、groups_sort()でgroup_info内のグループIDを昇順に並び替えます。
ngroupsはグループID数、nblocksはblocks[]の動的に割り当てるPAGE単位ブロックの配列数です。nblocks<NGROUPS_SMALLなら、small_block[]に設定されます。
groups_alloc()でgidsetsizeに応じたstruct group_info group_infoを取得し、引数のgrouplistを複写し、current->cred->group_info = group_infoとし、プロセスが引数のグループ群に属する事になります。この時、groups_search()でグループIDの検索の効率化のため、groups_sort()でgroup_info内のグループIDを昇順に並び替えます。
ngroupsはグループID数、nblocksはblocks[]の動的に割り当てるPAGE単位ブロックの配列数です。nblocks<NGROUPS_SMALLなら、small_block[]に設定されます。
struct group_info { atomic_t usage; int ngroups; int nblocks; gid_t small_block[NGROUPS_SMALL]; gid_t *blocks[0]; }; #define NGROUPS_MAX 65536 #define NGROUPS_SMALL 32 SYSCALL_DEFINE2(setgroups, int, gidsetsize, gid_t __user *, grouplist) { struct group_info *group_info; int retval; if (!nsown_capable(CAP_SETGID)) return -EPERM; if ((unsigned)gidsetsize > NGROUPS_MAX) return -EINVAL; group_info = groups_alloc(gidsetsize); if (!group_info) return -ENOMEM; retval = groups_from_user(group_info, grouplist); if (retval) { put_group_info(group_info); return retval; } retval = set_current_groups(group_info); put_group_info(group_info); return retval; }グループIDがNGROUPS_SMALL以下の場合、small_block[NGROUPS_SMALL]に割り当てgroup_info->blocks[0] = small_block、そうでないならPAGE単位で動的に割り当てgroup_info->blocks[i] = bとします。
#define NGROUPS_PER_BLOCK ((unsigned int)(PAGE_SIZE / sizeof(gid_t))) struct group_info *groups_alloc(int gidsetsize) { struct group_info *group_info; int nblocks; int i; nblocks = (gidsetsize + NGROUPS_PER_BLOCK - 1) / NGROUPS_PER_BLOCK; nblocks = nblocks ? : 1; group_info = kmalloc(sizeof(*group_info) + nblocks*sizeof(gid_t *), GFP_USER); if (!group_info) return NULL; group_info->ngroups = gidsetsize; group_info->nblocks = nblocks; atomic_set(&group_info->usage, 1); if (gidsetsize <= NGROUPS_SMALL) group_info->blocks[0] = group_info->small_block; else { for (i = 0; i < nblocks; i++) { gid_t *b; b = (void *)__get_free_page(GFP_USER); if (!b) goto out_undo_partial_alloc; group_info->blocks[i] = b; } } return group_info; out_undo_partial_alloc: while (--i >= 0) { free_page((unsigned long)group_info->blocks[i]); } kfree(group_info); return NULL; } int set_current_groups(struct group_info *group_info) { struct cred *new; int ret; new = prepare_creds(); if (!new) return -ENOMEM; ret = set_groups(new, group_info); if (ret < 0) { abort_creds(new); return ret; } return commit_creds(new); } int set_groups(struct cred *new, struct group_info *group_info) { put_group_info(new->group_info); groups_sort(group_info); get_group_info(group_info); new->group_info = group_info; return 0; }ファイル参照時グループに掛かるチェックでコールされ、inodeのgipがcurrent->cred->group_infoに有しているかチェックします。group_info->blocks[0]から順に走査するのでなく、検索グループ数の半分の位置のIDより大きければ上位半分を、小さければ下位半分を、同じように1/2単位での検索する効率的な実装です。故ユーザ引数のグループを並び替えしてプロセスに設定しているわけです。
int groups_search(const struct group_info *group_info, gid_t grp) { unsigned int left, right; if (!group_info) return 0; left = 0; right = group_info->ngroups; while (left < right) { unsigned int mid = (left+right)/2; if (grp > GROUP_AT(group_info, mid)) left = mid + 1; else if (grp < GROUP_AT(group_info, mid)) right = mid; else return 1; } return 0; }i / NGROUPS_PER_BLOCKはブロック位置、i % NGROUPS_PER_BLOCKはそのブロックバッファ内の位置
#define GROUP_AT(gi, i) \ ((gi)->blocks[(i) / NGROUPS_PER_BLOCK][(i) % NGROUPS_PER_BLOCK])