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])





