linux内核文件权限管理

来源:互联网 发布:淘宝网工程抢险车警灯 编辑:程序博客网 时间:2024/06/05 17:13

【摘要】

【正文一:文件管理】

【常见情况分析】

【正文二:目录管理】

【总结】


注意:请使用谷歌浏览器阅读(IE浏览器排版混乱)


【摘要】

本文以实例介绍linux系统对文件权限的管理.linux kernel对文件权限的检查、关键处理和难点,都发生在打开文件时.至于读写文件,检查方法非常简单,都是基于打开文件时设置的一些权限标志位,所以本文只针对打开文件做介绍.

【正文一:文件管理】

1 打开文件的接口:

glibc/uclibc中open接口为:

int open(const char *pathname,int flags)

说明:以flags方式打开文件,如果需要创建文件则文件属性为040,即文件所在用户组的用户有读权限.

int open(const char *pathname, int flags, mode_t mode)

说明:以flags方式打开文件。如果需要创建新文件,则指定文件属性为mode;如果不需要创建文件(即flags未设置O_CREATE标志)则mode字段不起作用.

系统中会修改mode&=022;所以即使此时mode权限为0777,也会被改为0755可,参看后文分析,即open创建文件时不允许非文件所属用户具有写权限.不过可以通过chmod重新修改为0777权限.

内核态对应系统调用为:

sys_open(const char* __user *filename,int flags,umode_t mode);

2 文件打开方式与文件权限

2.1 文件常用打开方式(即open函数的入参flags):

#define O_RDONLY  00000000       表示以只读方式打开文件;

#define O_WRONLY 00000001        表示以只写方式打开文件;

#define O_RDWR      00000002    表示以读写方式打开文件;

#define O_CREATE   00000100      表示如果文件不存在,则创建文件;

2.2 文件访问权限(即open函数的入参mode):

说明:R表示文件有读权限;W表示文件有写权限;X表示文件有执行权限;

    U表示文件所属用户;G表示文件所属用户所在组的用户;O表示其他用户。

1)文件所属用户对文件的访问权限:

#define S_IRWXU 00700      表示文件所属用户,对文件有读写和执行权限

#define S_IRUSR 00400      表示文件所属用户,对文件有读权限

#define S_IWUSR 00200      表示文件所属用户,对文件有写权限

#define S_IXUSR  00100     表示文件所属用户,对文件有执行权限

2)文件所属用户所在组用户对文件的访问权限:

#define S_IRWXG 00070      表示对文件有读写和执行权限

#define S_IRGRP 00040      表示对文件有读权限

#define S_IWGRP 00020      表示对文件有写权限

#define S_IXGRP  00010     表示对文件有执行权限

3)其他用户对文件的访问权限:

#define S_IRWXO 00007     表示对文件有读写和执行权限

#define S_IROTH 00004     表示对文件有读权限

#define S_IWOTH 00002     表示对文件有写权限

#define S_IXOTH 00001     表示对文件有执行权限

4)进程权限

RUID(real user id)是启动该进程的用户的ID,它与父进程用户ID相同,除非被改变.

EUID是内核检查权限时使用的实际ID,因此它确定了进程的权限.

SSUID:设置用户ID,表示执行时设置用户ID程序的进程映像文件的所有者ID.

允许一个进程将其EUID更改为其RUID/SSUID.当然root特权进程可做任何事.

当访问一个文件时,进程的有效用户ID(Effective User ID,EUID)与文件所有者的UID进行对比.如果该用户不是所有者,那么再对GID进行比较.

文件还有一个执行时设置用户ID位(set-user-ID-on-execution)S_ISUID表示的.该位可以设置给一个可执行进程二进制文件,表示以可执行文件所有者的特权而不是用户组的特权运行(即EUID设置为文件所有者ID).

3 常见文件访问权限问题分析

3.1 在/mnt/testdir目录下创建文件.

1)情景再现:

/mnt/testdir目录访问权限0777: drwx-rwx-rwx   2   root   root      testdir;

open("/mnt/testdir/testfile",flags,mode);

2)分析:open时系统都做了什么?

long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode){struct open_flags op;/* 根据open入参flags和mode生产open_flags,以后打开文件操作do_filp_open以此为入参 */int fd = build_open_flags(flags, mode, &op);struct filename *tmp;if (fd)return fd;tmp = getname(filename);if (IS_ERR(tmp))return PTR_ERR(tmp);fd = get_unused_fd_flags(flags);if (fd >= 0) {/* 打开文件操作*/struct file *f = do_filp_open(dfd, tmp, &op);if (IS_ERR(f)) {put_unused_fd(fd);fd = PTR_ERR(f);} else {fsnotify_open(f);fd_install(fd, f);}}putname(tmp);return fd;}

根据open入参flags和mode生产open_flags,以后打开文件操作do_filp_open以此为入参:

调用过程:do_sys_open->build_open_flags

static inline int build_open_flags(int flags, umode_t mode, struct open_flags *op){int lookup_flags = 0;int acc_mode;/* 注意此处:1如果open时flags没有设置O_CREATE标志,则无论open入参mode是何值,系统都认为是0,即未使用mode入参。2如果open时flags设置O_CREATE标志,则需要重新生成mode,其中S_IALLUGO是所有访问权限的并集。  例如:以mode=0777打开文件,经此处理后变为0100777;S_IFREG=0100000 表示普通文件S_IFDIR=040000  表示目录文件 */if (flags & (O_CREAT | __O_TMPFILE))op->mode = (mode & S_IALLUGO) | S_IFREG;elseop->mode = 0;flags &= ~FMODE_NONOTIFY & ~O_CLOEXEC;if (flags & __O_SYNC)flags |= O_DSYNC;if (flags & __O_TMPFILE) {if ((flags & O_TMPFILE_MASK) != O_TMPFILE)return -EINVAL;acc_mode = MAY_OPEN | ACC_MODE(flags);if (!(acc_mode & MAY_WRITE))return -EINVAL;} else if (flags & O_PATH) {flags &= O_DIRECTORY | O_NOFOLLOW | O_PATH;acc_mode = 0;} else {/*acc_mode主要是对读写权限的校验:        真正打开文件之前may_open通过inode_permission函数把acc_mode与inode->i_mode做比较,实现访问权限校验,inode->i_mode就是ls中看到的文件形如-rwx-的权限.举例: ls /mnt/testdir/testfile -l :-rwx-rwx-rwx 即0777;open("/mnt/testdir/testfile",O_RW);以读写方式打开一个文件,即flags=O_RW,从而acc_mode=06 (表示rwx中r位和w位都为1)打开文件之前会在inode_permission中校验inode->imode是否有读写权限,如本例中0777显然对所有用户都有读写权限,此处可以参考inode_permission一起分析.acc_mode=MAY_OPEN|ACC_MODE(flags):其中:MAY_OPEN=0x20.ACC_MODE(flags)取值为:当open函数入参flags = O_RDONLY (O_RDONLY=0)时,ACC_MODE(flags)=004 即:004代表bit2=1表示读权限打开,acc_mode=024.当flags = O_WRONLY =1时ACC_MODE(flags)=002 即:002代表bit1=1表示写权限,acc_mode=0x22.当flags = O_RDWR = 2时ACC_MODE(flags)=006 即:006代表bit2=1|bit1=1表示读写权限,acc_mode=0x26.*/acc_mode = MAY_OPEN | ACC_MODE(flags);}op->open_flag = flags;/* O_TRUNC implies we need access checks for write permissions */if (flags & O_TRUNC)acc_mode |= MAY_WRITE;/* Allow the LSM permission hook to distinguish append   access from general write access. */if (flags & O_APPEND)acc_mode |= MAY_APPEND;op->acc_mode = acc_mode;op->intent = flags & O_PATH ? 0 : LOOKUP_OPEN;if (flags & O_CREAT) {op->intent |= LOOKUP_CREATE;if (flags & O_EXCL)op->intent |= LOOKUP_EXCL;}if (flags & O_DIRECTORY)lookup_flags |= LOOKUP_DIRECTORY;if (!(flags & O_NOFOLLOW))lookup_flags |= LOOKUP_FOLLOW;op->lookup_flags = lookup_flags;return 0;}

do_filp_open->path_openat打开文件:

static struct file *path_openat(int dfd, struct filename *pathname,struct nameidata *nd, const struct open_flags *op, int flags){struct file *base = NULL;struct file *file;struct path path;int opened = 0;int error;        /* 创建struct *file并初始化,open过程中会把一个文件句柄与该file绑定,用户读写文件时会通过文件句柄找到该file进行一系列操作 */file = get_empty_filp();if (IS_ERR(file))return file;file->f_flags = op->open_flag;if (unlikely(file->f_flags & __O_TMPFILE)) {error = do_tmpfile(dfd, pathname, nd, flags, op, file, &opened);goto out2;}/* 作用:1 初始化nameidata 包括文件名赋值给nd->last.name等;2 初始化nd->path.dentry为该superblock根目录 / 对应的目录项.此时nd->path.dentry->d_iname=/;dentry->d_inode->i_ino为根目录/对应的inode num;         该函数为下一步link_path_walk中查找文件所在目录文件的目录项做准备,因为此时dentry初始化为根目录,所以从根目录开始查找文件所在目录.实际上path_init中将nd->path,nd->root都初始化为current->fs->root,nd->root也是struct path结构;而当前进程的current->fs->root是根目录"/";*/error = path_init(dfd, pathname->name, flags | LOOKUP_PARENT, nd, &base);if (unlikely(error))goto out;current->total_link_count = 0;/*作用:找到文件所在目录的目录项。举例:open("/mnt/testdir/testfile",flags,mode);此时nd->path.dentry->d_iname为/mnt/testdir即:dentry->d_iname=testdir;dentry->d_inode->i_ino=testdir对应的inode number。*/error = link_path_walk(pathname->name, nd);if (unlikely(error))goto out;/*关键函数 该函数中会初始化struct file;*/error = do_last(nd, &path, file, op, &opened, pathname);return file;}
path_openat->link_path_walk()该函数十分重要,它遍历文件所在路径上的所有目录项,而且能够校验每个目录项对应inode的权限;

这个函数通过调用walk_component->lookup_fast/lookup_slow找到每个目录项对应的inode.

static int link_path_walk(const char *name, struct nameidata *nd){struct path next;int err;while (*name=='/')name++;if (!*name)return 0;/* At this point we know we have a real path component. */for(;;) {u64 hash_len;int type;/*for循环里,通过may_lookup函数校验文件所在路径上每个目录项对应的inode权限。其实在may_open和may_create时都需要对inode权限进行检查。注意它和may_open区别:第一点区别:may_open是打开文件前,对文件对应的inode的权限检查,而没有检查文件所在路径上每个目录项对应的inode,正是因为在此检查过了。第二点区别:may_open检查inode的acc_mode主要对读写bit进行检查,即may_open->inode_permission(inode,acc_mode);acc_mode可以参考build_open_flags和acl_permission_check两个函数的分析;may_lookup中检查的是MAY_EXEC|MAY_NOT_BLOCK;即may_lookup->inode_permisson(inode,MAY_EXEC|MAY_NOT_BLOCK),其实主要检查每个inode的rwx中的x位;通过may_lookup和may_open一个文件inode的rwx都检查到了。举例:open(/mnt/testdir/testfile,O_RDWR);testdir权限为0641;处理过程:系统通过link_path_walk遍历了mnt,testdir,testfile对应的目录项,并通过may_lookup检查了每个目录项对应inode的权限注意只检查了执行权限,即x位,如果检查通过,会在以后的may_open中继续检查rw位,may_open的检查可以看后续分析。还有一点如果文件不存在,还会在lookup_open->vfs_create->may_create中检查文件所在目录testdir的写权限.值得注意的是,link_path_walk之后path->dentry指向了testdir对应目录项,而不是testfile对应的目录项;link_path_walk虽然在for循环中遍历了/mnt/testdir/testfile所有目录项和inode,但并没有对testfile文件对应的目录项dentry和inode做校验。而是在以后的处理流程中处理的,可以参考本文以下介绍。*/err = may_lookup(nd); if (err)break;hash_len = hash_name(name);type = LAST_NORM;if (name[0] == '.') switch (hashlen_len(hash_len)) {case 2:if (name[1] == '.') {type = LAST_DOTDOT;nd->flags |= LOOKUP_JUMPED;}break;case 1:type = LAST_DOT;}if (likely(type == LAST_NORM)) {struct dentry *parent = nd->path.dentry;nd->flags &= ~LOOKUP_JUMPED;if (unlikely(parent->d_flags & DCACHE_OP_HASH)) {struct qstr this = { { .hash_len = hash_len }, .name = name };err = parent->d_op->d_hash(parent, &this);if (err < 0)break;hash_len = this.hash_len;name = this.name;}}nd->last.hash_len = hash_len;nd->last.name = name;nd->last_type = type;name += hashlen_len(hash_len);if (!*name)return 0;/* * If it wasn't NUL, we know it was '/'. Skip that * slash, and continue until no more slashes. */do {name++;} while (unlikely(*name == '/'));if (!*name)return 0;/*for循环中通过wak_component找到文件路径上每个目录项和对应的inode,并在for循环中通过may_lookup检查权限。检查到的目录项和inode赋值给nd->path->dentry和nd->path->dentry->d_inode*/err = walk_component(nd, &next, LOOKUP_FOLLOW);if (err < 0)return err;if (err) {err = nested_symlink(&next, nd);if (err)return err;}if (!d_can_lookup(nd->path.dentry)) {err = -ENOTDIR; break;}}terminate_walk(nd);return err;}

path_openat->do_last()打开文件:

static int do_last(struct nameidata *nd, struct path *path,   struct file *file, const struct open_flags *op,   int *opened, struct filename *name){struct dentry *dir = nd->path.dentry;int open_flag = op->open_flag;bool will_truncate = (open_flag & O_TRUNC) != 0;bool got_write = false;int acc_mode = op->acc_mode;struct inode *inode;bool symlink_ok = false;struct path save_parent = { .dentry = NULL, .mnt = NULL };bool retried = false;int error;nd->flags &= ~LOOKUP_PARENT;nd->flags |= op->intent;if (nd->last_type != LAST_NORM) {error = handle_dots(nd, nd->last_type);if (error)return error;goto finish_open;}/* 没有设置O_CREATE标志,此时op->mode=0 */if (!(open_flag & O_CREAT)) {if (nd->last.name[nd->last.len])nd->flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;if (open_flag & O_PATH && !(nd->flags & LOOKUP_FOLLOW))symlink_ok = true;/* we _can_ be in RCU mode here */error = lookup_fast(nd, path, &inode);/*对于已经存在的文件,lookup_fast找到inode,并跳过lookup_open函数;对于不存在的文件,lookup_open在创建新文件时使用,会为新文件申请inode等操作。*/if (likely(!error))goto finish_lookup;/* 未找到 inode,打开文件失败*/if (error < 0)goto out;BUG_ON(nd->inode != dir->d_inode);} else {error = complete_walk(nd);if (error)return error;/* 设置了O_CREATE标志 ,op->mode为build_open_flags中生成*/audit_inode(name, dir, LOOKUP_PARENT);error = -EISDIR;/* trailing slashes? */if (nd->last.name[nd->last.len])goto out;}/* open新建文件时 在此申请新文件的目录项dentry和inode结点open已创建文件时,不需要执行,直接goto finish_lookup:*/retry_lookup:if (op->open_flag & (O_CREAT | O_TRUNC | O_WRONLY | O_RDWR)) {error = mnt_want_write(nd->path.mnt);if (!error)got_write = true;}mutex_lock(&dir->d_inode->i_mutex);/*当需要创建新文件时,此函数完成inode申请及初始化其中包括文件权限的初始化:inode->i_mode;目录项的申请和初始化;may_open校验文件权限时会用到inode->i_mode文件权限*/error = lookup_open(nd, path, file, op, got_write, opened);mutex_unlock(&dir->d_inode->i_mutex);if (error <= 0) {if (error)goto out;if ((*opened & FILE_CREATED) ||    !S_ISREG(file_inode(file)->i_mode))will_truncate = false;audit_inode(name, file->f_path.dentry, 0);goto opened;}/*新建文件时lookup_open中将opened |= FILE_CREATED;此时acc_mode=MAY_OPEN,所以接下来,打开文件之前may_open->inode_permission->acl_permission_check中不再进行rwx权限检查.*/if (*opened & FILE_CREATED) {/* Don't check for write permission, don't truncate */open_flag &= ~O_TRUNC;will_truncate = false;acc_mode = MAY_OPEN;path_to_nameidata(path, nd);goto finish_open_created;}if (d_is_positive(path->dentry))audit_inode(name, path->dentry, 0);if (got_write) {mnt_drop_write(nd->path.mnt);got_write = false;}error = -EEXIST;if ((open_flag & (O_EXCL | O_CREAT)) == (O_EXCL | O_CREAT))goto exit_dput;error = follow_managed(path, nd->flags);if (error < 0)goto exit_dput;if (error)nd->flags |= LOOKUP_JUMPED;BUG_ON(nd->flags & LOOKUP_RCU);inode = path->dentry->d_inode;finish_lookup:/* we _can_ be in RCU mode here */error = -ENOENT;if (!inode || d_is_negative(path->dentry)) {path_to_nameidata(path, nd);goto out;}if (should_follow_link(path->dentry, !symlink_ok)) {if (nd->flags & LOOKUP_RCU) {if (unlikely(nd->path.mnt != path->mnt ||     unlazy_walk(nd, path->dentry))) {error = -ECHILD;goto out;}}BUG_ON(inode != path->dentry->d_inode);return 1;}if ((nd->flags & LOOKUP_RCU) || nd->path.mnt != path->mnt) {path_to_nameidata(path, nd);} else {save_parent.dentry = nd->path.dentry;save_parent.mnt = mntget(path->mnt);nd->path.dentry = path->dentry;}nd->inode = inode;/* Why this, you ask?  _Now_ we might have grown LOOKUP_JUMPED... */finish_open:error = complete_walk(nd);if (error) {path_put(&save_parent);return error;}audit_inode(name, nd->path.dentry, 0);error = -EISDIR;if ((open_flag & O_CREAT) && d_is_dir(nd->path.dentry))goto out;error = -ENOTDIR;if ((nd->flags & LOOKUP_DIRECTORY) && !d_can_lookup(nd->path.dentry))goto out;if (!S_ISREG(nd->inode->i_mode))will_truncate = false;if (will_truncate) {error = mnt_want_write(nd->path.mnt);if (error)goto out;got_write = true;}finish_open_created:/*文件访问权限的校验,主要是校验是否有权限访问inode,函数may_open->inode_permission中实现;其中may_open函数能根据nd->path找到文件对应的目录项dentry和inode结点.acc_mode是在build_open_flags根据open函数入参flags生成的.对于新建文件,acc_mode可能被更改.如果新建文件时lookup_open中将opened |= FILE_CREATED则在上面retry_lookup中acc_mode=MAY_OPEN.acc_mode主要是对读写权限的校验:        真正打开文件之前may_open通过inode_permission函数把acc_mode与inode->i_mode做比较,实现访问权限校验,inode->i_mode就是ls中看到的文件形如-rwx-的权限。举例: ls /mnt/testdir/testfile -l :-rwx-rwx-rwx 即0777;open("/mnt/testdir/testfile",O_RW);以读写方式打开一个文件,即flags=O_RW,从而acc_mode=06 (表示rwx中r位和w位都为1)打开文件之前会在inode_permission中校验inode->imode是否有读写权限,如本例中0777显然对所有用户都有读写权限,此处可以参考inode_permission一起分析。acc_mode=MAY_OPEN|ACC_MODE(flags):其中:MAY_OPEN=0x20ACC_MODE(flags)取值为:     当open函数入参flags=O_RDONLY=0时ACC_MODE(flags)=004 即:004代表bit2=1表示读权限打开;     当flags=O_WRONLY=1时ACC_MODE(flags)=002 即:002代表bit1=1表示写权限;     当flags=O_RDWR=2时ACC_MODE(flags)=006 即:006代表bit2=1|bit1=1表示读权限;*/error = may_open(&nd->path, acc_mode, open_flag);if (error)goto out;BUG_ON(*opened & FILE_OPENED); /* once it's opened, it's opened */        /* 真正的文件打开操作:vfs_open->do_dentry_open系列调用中初始化struct file*/error = vfs_open(&nd->path, file, current_cred());if (!error) {*opened |= FILE_OPENED;} else {if (error == -EOPENSTALE)goto stale_open;goto out;}opened:error = open_check_o_direct(file);if (error)goto exit_fput;error = ima_file_check(file, op->acc_mode, *opened);if (error)goto exit_fput;if (will_truncate) {error = handle_truncate(file);if (error)goto exit_fput;}out:if (got_write)mnt_drop_write(nd->path.mnt);path_put(&save_parent);terminate_walk(nd);return error;exit_dput:path_put_conditional(path, nd);goto out;exit_fput:fput(file);goto out;stale_open:/* If no saved parent or already retried then can't retry */if (!save_parent.dentry || retried)goto out;BUG_ON(save_parent.dentry != dir);path_put(&nd->path);nd->path = save_parent;nd->inode = dir->d_inode;save_parent.mnt = NULL;save_parent.dentry = NULL;if (got_write) {mnt_drop_write(nd->path.mnt);got_write = false;}retried = true;goto retry_lookup;}
do_last->lookup_open 打开文件:
static int lookup_open(struct nameidata *nd, struct path *path,struct file *file,const struct open_flags *op,bool got_write, int *opened){struct dentry *dir = nd->path.dentry;struct inode *dir_inode = dir->d_inode;struct dentry *dentry;int error;bool need_lookup;*opened &= ~FILE_CREATED;/*作用:为文件创建目录项。找到文件对应的目录项,如果没找到则为新文件申请目录项;函数link_path_walk中nd->path.dentry赋值为文件所在目录的目录项。此时根据nd->path.dentry生成文件对应的目录项dentrylookup_dcache->d_alloc中申请新建文件对应的dentry,并初始化该dentry*/dentry = lookup_dcache(&nd->last, dir, nd->flags, &need_lookup);if (IS_ERR(dentry))return PTR_ERR(dentry);/* Cached positive dentry: will open in f_op->open */if (!need_lookup && dentry->d_inode)goto out_no_open;if ((nd->flags & LOOKUP_OPEN) && dir_inode->i_op->atomic_open) {return atomic_open(nd, dentry, path, file, op, got_write,   need_lookup, opened);}if (need_lookup) {BUG_ON(dentry->d_inode);dentry = lookup_real(dir_inode, dentry, nd->flags);if (IS_ERR(dentry))return PTR_ERR(dentry);}/* Negative dentry, just create the file */if (!dentry->d_inode && (op->open_flag & O_CREAT)) {umode_t mode = op->mode;/*int current_umask(void){return current->fs->umask;}VFS使用umask,此处mode=mode&022,即非文件所属用户不能写current->fs->umask是何时设置的:进程创建时会copy_fs_struct()该函数继承了父进程中的umask;其实都是继承于 INIT_TASK->init_fs.umask=022所以open时即使mode权限为0777,在此也会改为0755;注意最开始build_open_flags也可能根据O_CREATE标志修改mode*/if (!IS_POSIXACL(dir->d_inode))mode &= ~current_umask();if (!got_write) {error = -EROFS;goto out_dput;}*opened |= FILE_CREATED;error = security_path_mknod(&nd->path, dentry, mode, 0);if (error)goto out_dput;/*注意:此时dir对应的目录项为文件所在目录对应的目录项:例如文件:/mnt/testdir/testfile                1>dir->d_iname=testdir2>dentry对应的目录项为文件对应的目录项dentry->d_iname=testfilevfs_create中又修改了mode=mode|S_IFREG所以open时既使mode权限为0777,在此也会改为100755注意:对于新建文件,vfs_create->may_create中会针对文件所在目录testdir的目录项对应的inode,即dentry->d_inode进行写权限和执行权限检查,即:may_create->inode_permission(inode,MAY_WRITE|MAY_EXEC);可以参考link_path_walk->may_lookup和may_open中介绍。*/error = vfs_create(dir->d_inode, dentry, mode,   nd->flags & LOOKUP_EXCL);if (error)goto out_dput;}out_no_open:path->dentry = dentry;path->mnt = nd->path.mnt;return 1;out_dput:dput(dentry);return error;}
do_last->lookup_open->lookup_dcache->d_alloc->__d_alloc() 申请新建文件对应的目录项dentry:
struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name){struct dentry *dentry;char *dname;/*作用:申请新建文件对应的dentry举例:open("/mnt/testdir/testfile",flags,mode);此时申请的dentry->d_iname=testfiledentry->d_inode初始化为NULL,后面vfs_create中申请inode*/dentry = kmem_cache_alloc(dentry_cache, GFP_KERNEL);if (!dentry)return NULL;dentry->d_iname[DNAME_INLINE_LEN-1] = 0;if (name->len > DNAME_INLINE_LEN-1) {size_t size = offsetof(struct external_name, name[1]);struct external_name *p = kmalloc(size + name->len, GFP_KERNEL);if (!p) {kmem_cache_free(dentry_cache, dentry); return NULL;}atomic_set(&p->u.count, 1);dname = p->name;} else  {dname = dentry->d_iname;}dentry->d_name.len = name->len;dentry->d_name.hash = name->hash;memcpy(dname, name->name, name->len);dname[name->len] = 0;/* Make sure we always see the terminating NUL character */smp_wmb();dentry->d_name.name = dname;dentry->d_lockref.count = 1;dentry->d_flags = 0;spin_lock_init(&dentry->d_lock);seqcount_init(&dentry->d_seq);dentry->d_inode = NULL;dentry->d_parent = dentry;dentry->d_sb = sb;dentry->d_op = NULL;dentry->d_fsdata = NULL;INIT_HLIST_BL_NODE(&dentry->d_hash);INIT_LIST_HEAD(&dentry->d_lru);INIT_LIST_HEAD(&dentry->d_subdirs);INIT_HLIST_NODE(&dentry->d_u.d_alias);INIT_LIST_HEAD(&dentry->d_child);d_set_d_op(dentry, dentry->d_sb->s_d_op);this_cpu_inc(nr_dentry);return dentry;}

函数继续执行,因为是ubifs文件系统,所以

do_last->lookup_open->vfs_create->ubifs_create->ubifs_new_inode:

注意:操作系统中的inode信息(dram上描述inode的信息,区别于flash上的inode信息)的初始化时机包括:

1>是在do_last->lookup_open->vfs_create->ubifs_create->ubifs_new_inode创建一个文件时初始化的.

2>对于已存在文件,是在do_last->lookup_open->lookup_real->ubifs_lookup->ubifs_iget

struct inode *ubifs_new_inode(struct ubifs_info *c, const struct inode *dir,      umode_t mode){struct inode *inode;struct ubifs_inode *ui;        /* inode申请并初始化: new_inode->new_inode_pseudo->alloc_inode->inode_init_always*/inode = new_inode(c->vfs_sb);ui = ubifs_inode(inode);if (!inode)return ERR_PTR(-ENOMEM);inode->i_flags |= S_NOCMTIME;/* 初始化inode的权限和用户信息*/inode_init_owner(inode, dir, mode);inode->i_mtime = inode->i_atime = inode->i_ctime = ubifs_current_time(inode);inode->i_mapping->nrpages = 0;/* Disable readahead */inode->i_mapping->backing_dev_info = &c->bdi;switch (mode & S_IFMT) {case S_IFREG:inode->i_mapping->a_ops = &ubifs_file_address_operations;inode->i_op = &ubifs_file_inode_operations;inode->i_fop = &ubifs_file_operations;break;case S_IFDIR:inode->i_op  = &ubifs_dir_inode_operations;inode->i_fop = &ubifs_dir_operations;inode->i_size = ui->ui_size = UBIFS_INO_NODE_SZ;break;case S_IFLNK:inode->i_op = &ubifs_symlink_inode_operations;break;case S_IFSOCK:case S_IFIFO:case S_IFBLK:case S_IFCHR:inode->i_op  = &ubifs_file_inode_operations;break;default:BUG();}ui->flags = inherit_flags(dir, mode);ubifs_set_inode_flags(inode);if (S_ISREG(mode))ui->compr_type = c->default_compr;elseui->compr_type = UBIFS_COMPR_NONE;ui->synced_i_size = 0;spin_lock(&c->cnt_lock);/* Inode number overflow is currently not supported */if (c->highest_inum >= INUM_WARN_WATERMARK) {if (c->highest_inum >= INUM_WATERMARK) {spin_unlock(&c->cnt_lock);ubifs_err("out of inode numbers");make_bad_inode(inode);iput(inode);return ERR_PTR(-EINVAL);}ubifs_warn("running out of inode numbers (current %lu, max %d)",   (unsigned long)c->highest_inum, INUM_WATERMARK);}inode->i_ino = ++c->highest_inum;ui->creat_sqnum = ++c->max_sqnum;spin_unlock(&c->cnt_lock);return inode;}

初始化文件权限,所属用户,包括:inode->i_mode,inode->i_uid,inode->i_gid:ubifs_new_inode->inode_init_owner:

void inode_init_owner(struct inode *inode, const struct inode *dir,umode_t mode){/* 初始化 inode->i_uid,inode->i_gid为当前进程的fsuid如:打开文件的进程所属用户为uid=1001,gid=1001*/inode->i_uid = current_fsuid();if (dir && dir->i_mode & S_ISGID) {inode->i_gid = dir->i_gid;if (S_ISDIR(mode))mode |= S_ISGID;} elseinode->i_gid = current_fsgid();/*初始化inode->i_mode为mode如:open时指定0777权限,则此处为100755*/inode->i_mode = mode;}

真正打开文件之前对,文件权限的校验:may_open->inode_permission->generic_permission()->acl_permission_check()

static int acl_permission_check(struct inode *inode, int mask){unsigned int mode = inode->i_mode;/*作用:操作文件的进程和当前文件属于同一用户,mode>>6这是因为最高3位表示同一用户访问文件的权限。如ls /mnt/testdir/testfile -l 为0777 那么最高3位0700表示同一用户访问权限。中间的7表示,同组不同用户权限,最低位表示其他用户,即不同组,不同用户的权限所以其他用户时mode不用移位,即if和else两个分支都不走。*/if (likely(uid_eq(current_fsuid(), inode->i_uid)))mode >>= 6;else {/*操作文件的进程和当前文件不在同一用户,但在同一组。如0777中中间的7,所以mode>>3*/if (IS_POSIXACL(inode) && (mode & S_IRWXG)) {int error = check_acl(inode, mask);if (error != -EAGAIN)return error;}if (in_group_p(inode->i_gid))mode >>= 3;}/* * If the DACs are ok we don't need any capability check. *//*作用:把文件的打开方式与文件所属用户  具有的文件访问权限inode->i_mode做比较如果testfile文件所属用户、所属组、其他用户都是只读权限,即0444, ls testfile -l :--r--r--r--;那么任何用户:open("testfile",O_WRONLY)都会打开失败。这是因为flags=O_WRONLY,在build_open_flags中acc_mode=02|MAY_OPEN;inode->i_mode=0444对于任何用户,rwx中r位都是1,即bit2=1;(mask=acc_mode=02)&(~mode=03)&007  !=  0 所以不能访问。*/if ((mask & ~mode & (MAY_READ | MAY_WRITE | MAY_EXEC)) == 0)return 0;return -EACCES;}

至此完成了文件访问权限的校验,前面的分析,其实都是为acl_permission_check()做铺垫,值得注意的时,超级用户权限不完全受acl_permission_check()检查的限制,如果它检查不过,会在acl_permission_check()之后进一步做其他检查,此处不再分析,感兴趣的同学可以看下.

通过上面分析,可以总结出open一个文件时,几个权限校验的关键节点:

第一步权限检查: 最开始对文件所在路径上每个目录项对应的inode进行执行权限检查.

对应代码:path_openat->link_path_walk->may_lookup->inode_permission;

第二步权限检查:如果是新建文件,对文件所在目录项的inode做写和可执行权限检查.

对应代码:path_openat->do_last->lookup_open->vfs_create->may_create->inode_permission.

第三步权限检查:真正打开文件之前,对文件所对应的inode做读写权限检查.

对应代码:path_openat->do_last->may_open->inode_permission.

可以在常见情况分析中看到,实际使用情况。

【常见情况分析】

1 /mnt/testdir属性为0666,属于root用户。即:ls /mnt -l :-drw-rw-rw root roottestdir

非root用户创建新文件:

操作:open("/mnt/testdir/testfile",O_CREATE|O_RDWR,S_IRWO);以读写方式打开文件,如果新建文件则inode属性设为其他用户可读写,即007;

结果:open失败;

原因分析:根据上面分析,在代码path_openat->link_path_walk->may_lookup->inode_permission中,即在权限校验的第一步中,会校验testdir的执行权限,此处校验失败;

2  /mnt/testdir属性为0661,属于root用户。即:ls /mnt -l :-drw-rw-x root root testdir

操作:open("/mnt/testdir/testfile",O_CREATE|O_RDWR,S_IRWO);以读写方式打开文件,如果新建文件则inode属性设为其他用户可读写,即007;

结果:open失败;

原因分析:根据上面分析,在代码path_openat->do_last->lookup_open->vfs_create->may_create->inode_permission中,即在权限校验的第二步中,会校验testdir的写权限和执行权限,此处校验失败;

3  /mnt/testdir属性为0663,属于root用户。即:ls /mnt -l :-drw-rw-xw root root testdir

操作:open("/mnt/testdir/testfile",O_CREATE|O_RDWR,S_IRWO);以读写方式打开文件,如果新建文件则inode属性设为其他用户可读写,即007;

结果:open成功;

原因分析:根据上面分析,在代码path_openat->do_last->lookup_open->vfs_create->may_create->inode_permission中,即在权限校验的第二步中,会校验testdir的写权限和执行权限,此处testdir权限为0663,所以其他用户权限是003,可见是有写权限和执行权限的;但是在打开文件时需要检查读写权限(可以参考acc_mode的生成),本该打开失败,为什么还会成功呢?这是因为对于新建文件时,do_last中会将acc_mode=MAY_OPEN,所以只要能完成第二步权限检查,在第三步权限检查中就不再进行rwx权限检查了(可参考do_last实现)。

4 创建新文件权限为007,即具有rwx权限
open("/mnt/testdir/testfile",O_CREATE|O_RDWR,S_IRWO)
结果:创建出来的文件为005,即具有rx权限,这是因为创建文件时,权限都要与上~022,即非文件所有者用户不能写文件,可参考上文介绍。

【正文二:目录管理】

目录权限的管理虽然和文件权限的管理不是同一个软件流程,但管理方法大同小异,所有可以参考文件权限管理的流程,去参看目录权限管理.

目前权限管理的简单介绍:

创建目录时mkdir:

第一步权限检查: 最开始对目录所在路径上每个目录项对应的inode进行执行权限检查。

对应代码:path_lookupat->link_path_walk->may_lookup->inode_permission ;

第二步权限检查:目录所在目录项对应的inode做写和可执行权限检查。

对应代码:vfs_mkdir->may_create->inode_permission。

删除目录时rmdir:

第一步权限检查:path_lookupat->link_path_walk->may_lookup->inode_permission ;

第二步权限检查:vfs_rmdir->may_delete中检查目录所在目录项对应的inode的写和可执行权限。

针对几种情况做分析:

1 /mnt/testdir目录访问权限0666:

drw-rw-rw   2   root   root      testdir

场景:文件所属用户(root用户组 uid=0,gid=0)可读写;同组用户(uid!=0,gid=0)可读写、其他用户(uid!=0,gid!=0)可读写

操作:如果其他用户(如uid=1001,gid=1001)要在testdir目录下创建子目录:

结果:mkdir("/mnt/testdir/subdir",0666) 创建会失败;

原因:这是因为目录创建和文件创建一样,也会在may_create中对目录项对应的inode的写权限和可执行权限做检查。

2 /mnt/testdir目录访问权限0777:

drwx-rwx-rwx   2   root   root      testdir

1)如果其他用户(如uid=1001,gid=1001)要在testdir目录下创建子目录:mkdir("/mnt/testdir/subdir",0777);

drwx-rwx-rwx   2   1001   1001      subdir

rmdir("/mnt/testdir")成功;

rmdir("/mnt/testdir/subidr")成功;

【总结】

通过上面的分析,我们看到一个文件同时对应一个目录项dentry和一个inode,而且一个文件的目录项dentry->d_inode保存了这个文件对应的inode,所以如果查找一个文件对应的inode,要先查找这个文件对应的目录项dentry。系统为什么要这么做,为什么不直接用一个结构体表达文件,而要同时用dentry和inode表示文件。这是因为文件一般都存在于一个绝对路径之下,比如/mnt/testdir/testfile,一般来说系统里目录项dentry更倾向于表达一个文件整个路径上每个目录,如:mnt,testdir,testfile都对应一个目录项,虽然他们也同时对应inode,但是因为用户操作文件时通常只是想操作testfile对应的inode,所以inode更倾向于表示testfile的inode。不知道是否描述清楚,举个实际例子说明问题吧:

例子:用户操作文件/mnt/testdir/testfile,系统查找testfile对应inode的过程:

1 首先从根目录查起/,根目录“/“”对应一个目录项。

2 link_path_walk中遍历路径上的所有目录项,一直查到testdir对应的目录项,都可以查找到。此过程会对每个目录项对应的inode进行执行权限检查。

3 lookup_dcache中查找到了文件testfile对应的目录项,注意他是基于第2步中查找到的testdir对应的目录项开始查找的。

4 当查找testfile对应的目录项时,有两种情况:

一、文件已经存在,则查找到testfile对应目录项,接下来根据dentry->d_inode,又查找到了文件对应的inode,然后就可以对文件进行操作了。

二、文件不存在,如果时open函数,则可以创建testfile文件,创建文件testfile,首先创建testfile对应的目录项dentry(looup_open->lookup_dcache中完成),目录项创建成功后,再创建inode结点(lookup_open->vfs_create中完成),vfs_create过程会通过d_instantatiate将inode赋值给dentry->d_inode,所以第一种情况,可以根据先查找到的testfile对应的目录项dentry找到testfile对应的inode。创建inode过程会对文件所属目录项对应的inode,进行写权限和执行权限检查。

5 may_open真正打开文件之前会对文件读写权限进行检查。

打开文件时,系统对文件权限的检查,其实就发生在根据文件所属路径查找文件的过程中。


0 0