ACCESS请求

来源:互联网 发布:doom4优化 编辑:程序博客网 时间:2024/05/16 10:41

    ACCESS是NFSv3中增加的一个请求,这个请求的作用是检查用户对文件的访问权限。由于服务器端可能对用户进行匿名映射,因此通常的uid/gid机制就不起作用了。NFSv2中客户端直接向服务器发起各种请求(如READ和WRITE)。如果用户没有相应的访问权限,则失败,客户端进行处理。NFSv3中客户端先通过ACCESS检查用户的访问权限,如果权限不满足就不发起后续请求了。如果权限满足,再发送后续请求。

    NFS中,用户对文件的访问权限用数据结构nfs_access_entry表示

struct nfs_access_entry {        struct rb_node          rb_node;        struct list_head        lru;        unsigned long           jiffies;        // 这是一个时间戳        struct rpc_cred *       cred;           // 这里保存了用户信息        int                     mask;           // 这是用户的访问权限};

客户端所有用户对文件的访问权限保存在文件索引节点结构中的一棵红黑树中,同时保存在一个lru链表中。

struct nfs_inode {        ....        struct rb_root          access_cache;        struct list_head        access_cache_entry_lru;        ....}

NFS中检查用户对文件访问权限的函数是nfs_do_access(),这个函数首先在文件索引节点结构里的红黑树中查找用户的访问权限,如果找到了直接检查权限;如果不存在就向服务器发起ACCESS请求,将返回的访问权限添加到红黑树中,然后检查用户的访问权限。

参数inode:这是一个文件索引节点,表示一个文件

参数cred:这是用户信息,表示客户端一个用户

参数mask:这是请求的访问权限,如MAY_READ、MAY_WRITE、MAY_EXEC

static int nfs_do_access(struct inode *inode, struct rpc_cred *cred, int mask){        struct nfs_access_entry cache;        int status;        // 步骤1   在红黑树中查找访问权限        status = nfs_access_get_cached(inode, cred, &cache);        if (status == 0)        // 0表示找到了,不需要发起ACCESS请求了.                goto out;       // 直接检查访问权限        /* Be clever: ask server to check for all possible rights */        // 步骤2   发起ACCESS请求        cache.mask = MAY_EXEC | MAY_WRITE | MAY_READ;   // 只查找读、写、执行权限.        cache.cred = cred;              // 用户信息,请求这个用户的访问权限        cache.jiffies = jiffies;        // 这是一个时间戳        status = NFS_PROTO(inode)->access(inode, &cache);       // 向服务器发起ACCESS请求        if (status != 0) {      // ACCESS过程出错.                if (status == -ESTALE) {                        nfs_zap_caches(inode);  // 设置文件在本地的缓存无效                        if (!S_ISDIR(inode->i_mode))                                set_bit(NFS_INO_STALE, &NFS_I(inode)->flags);   // 这个文件已经过期了.                }                return status;        }        // 步骤3   将访问权限添加到红黑树中        nfs_access_add_cache(inode, &cache);out:        // 步骤4   检查访问权限        if ((mask & ~cache.mask & (MAY_READ | MAY_WRITE | MAY_EXEC)) == 0)                return 0;       // 权限满足,可以访问        return -EACCES;         // 权限不满足,不可以访问.}

步骤1:用户对文件的访问权限保存在一棵红黑树中,现在红黑树中查找访问权限。如果存在就跳转到步骤4进行比较。

步骤2:用户客户端没有缓存用户的访问权限,或者缓存已经过期了,则向服务器发起ACCESS请求。

步骤3:将ACCESS返回的访问权限添加到红黑树中

步骤4:检查访问权限,判断用户是否可以访问文件。

在红黑树中查找用户访问权限的函数是nfs_access_get_cached()。

nfs_do_access --> nfs_access_get_cached

static int nfs_access_get_cached(struct inode *inode, struct rpc_cred *cred, struct nfs_access_entry *res){        struct nfs_inode *nfsi = NFS_I(inode);  // 找到nfs_inode结构        struct nfs_access_entry *cache;        int err = -ENOENT;        spin_lock(&inode->i_lock);        // 检查本地缓存的文件访问权限是否有效,如果无效就不能使用了.        if (nfsi->cache_validity & NFS_INO_INVALID_ACCESS)                      goto out_zap;   // 访问权限无效了,需要删除客户端缓存的所有访问权限。        // 在红黑树中查找用户的访问权限        cache = nfs_access_search_rbtree(inode, cred);        if (cache == NULL)      // 客户端没有缓存用户对文件的访问权限,直接退出。                goto out;        // 判断缓存的访问权限是否过了有效期        if (!nfs_have_delegated_attributes(inode) &&            !time_in_range_open(jiffies, cache->jiffies, cache->jiffies + nfsi->attrtimeo))                goto out_stale; // 过了有效期,不能使用了,需要从红黑树中删除。        // 客户端缓存的访问权限有效,可以使用。        res->jiffies = cache->jiffies;        res->cred = cache->cred;        res->mask = cache->mask;        list_move_tail(&cache->lru, &nfsi->access_cache_entry_lru);        err = 0;out:        spin_unlock(&inode->i_lock);        return err;out_stale:      // 客户端缓存的访问权限已经过了有效期了        rb_erase(&cache->rb_node, &nfsi->access_cache); // 从红黑树中删除.        list_del(&cache->lru);  // 从lru链表中删除        spin_unlock(&inode->i_lock);        nfs_access_free_entry(cache);   // 释放内存        return -ENOENT;out_zap:        spin_unlock(&inode->i_lock);        // 这个函数删除了所有用户对文件的访问权限,然后清除了标志位NFS_INO_INVALID_ACCESS.        nfs_access_zap_cache(inode);            return -ENOENT;}

    NFS文件索引节点结构中包含一个标志位NFS_INO_INVALID_ACCESS,这个标志位表示客户端缓存的用户访问权限无效了。比如服务器端修改了文件的用户/用户组,则GETATTR请求会返回新的用户/用户组。当更新客户端缓存的文件属性时就会设置这个标志位。因此nfs_access_get_cached()首先检查了这个标志位,如果设置了这个标志位就需要释放客户端缓存的访问权限,这是由函数nfs_access_zap_cache()实现的。

    如果客户端没有设置标志位NFS_INO_INVALID_ACCESS,则表示客户端缓存的用户访问权限有效,这时nfs_access_get_cached()调用nfs_access_search_rbtree()在红黑树中查找用户的访问权限。但是没有找到则直接退出。如果找到了还需要检查访问权限是否有效。因为这个访问权限保存在客户端,服务器端可能已经修改了访问权限,因此每个访问权限设置了一个有效期。如果超过了有效期就认为访问权限已经无效了,不能使用了,将访问权限从红黑树和lru链表中删除,然后释放内存。如果在有效期内就返回这个访问权限。

    在红黑树中查找用户访问权限的函数是nfs_access_search_rbtree(),这个函数就是将用户信息和红黑树中的节点进行比较,检查是否匹配。

nfs_do_access --> nfs_access_get_cached --> nfs_access_search_rbtree

static struct nfs_access_entry *nfs_access_search_rbtree(struct inode *inode, struct rpc_cred *cred){        // 取出文件访问权限红黑树的根节点        struct rb_node *n = NFS_I(inode)->access_cache.rb_node;                 struct nfs_access_entry *entry;        while (n != NULL) {                entry = rb_entry(n, struct nfs_access_entry, rb_node);                if (cred < entry->cred)                        n = n->rb_left;                else if (cred > entry->cred)                        n = n->rb_right;                else                        return entry;        }        return NULL;}

向服务器发起ACCESS请求的代码段如下:

        cache.mask = MAY_EXEC | MAY_WRITE | MAY_READ;   // 只查找读、写、执行权限.        cache.cred = cred;              // 用户信息        cache.jiffies = jiffies;        // 时间        status = NFS_PROTO(inode)->access(inode, &cache);       // 向服务器查找访问权限.
cache是一个nfs_access_entry结构,cache.mask表示请求检查的访问权限,这里只检查读、写、执行权限。cache.cred表示检查哪个用户对文件的访问权限。设置好这些信息之后就可以发起ACCESS请求了。

static int nfs3_proc_access(struct inode *inode, struct nfs_access_entry *entry){        struct nfs3_accessargs  arg = {                .fh             = NFS_FH(inode),        // 获取文件句柄        };        struct nfs3_accessres   res;        struct rpc_message msg = {                .rpc_proc       = &nfs3_procedures[NFS3PROC_ACCESS],                .rpc_argp       = &arg,         // ACCESS请求的参数                .rpc_resp       = &res,         // ACCESS请求的返回值                .rpc_cred       = entry->cred,      // 这是用户信息        };        int mode = entry->mask;         // 取出用户请求的访问权限 MAY_READ MAY_WRITE MAY_EXEC        int status = -ENOMEM;        dprintk("NFS call  access\n");        if (mode & MAY_READ)        // 检查读权限                arg.access |= NFS3_ACCESS_READ;        if (S_ISDIR(inode->i_mode)) {   // 这是一个目录                if (mode & MAY_WRITE)   // 检查写权限                        arg.access |= NFS3_ACCESS_MODIFY | NFS3_ACCESS_EXTEND | NFS3_ACCESS_DELETE;                if (mode & MAY_EXEC)    // 检查执行权限                        arg.access |= NFS3_ACCESS_LOOKUP;        } else {                // 这是一个普通文件                if (mode & MAY_WRITE)   // 检查写权限                        arg.access |= NFS3_ACCESS_MODIFY | NFS3_ACCESS_EXTEND;                if (mode & MAY_EXEC)    // 检查执行权限                        arg.access |= NFS3_ACCESS_EXECUTE;        }        res.fattr = nfs_alloc_fattr();      // 未文件属性数据结构分配内存        if (res.fattr == NULL)                goto out;        status = rpc_call_sync(NFS_CLIENT(inode), &msg, 0);     // 发起ACCESS请求        nfs_refresh_inode(inode, res.fattr);        // 更新本地文件属性信息        if (status == 0) {      // res.access保存了用户对文件的访问权限                entry->mask = 0;                if (res.access & NFS3_ACCESS_READ)      // 具有读权限                        entry->mask |= MAY_READ;                if (res.access & (NFS3_ACCESS_MODIFY | NFS3_ACCESS_EXTEND | NFS3_ACCESS_DELETE))        // 具有写权限                        entry->mask |= MAY_WRITE;                if (res.access & (NFS3_ACCESS_LOOKUP|NFS3_ACCESS_EXECUTE))      // 具有执行权限                        entry->mask |= MAY_EXEC;        }        nfs_free_fattr(res.fattr);      // 释放属性数据结构占用的内存out:        dprintk("NFS reply access: %d\n", status);        return status;}
当nfs3_proc_access()执行完毕后,access中就保存了用户对文件实际的访问权限,就可以将这个访问权限添加到红黑树中了,这是由函数nfs_access_add_cache()完成的。

nfs_do_access --> nfs_access_add_cache

static void nfs_access_add_cache(struct inode *inode, struct nfs_access_entry *set){        // 分配内存        struct nfs_access_entry *cache = kmalloc(sizeof(*cache), GFP_KERNEL);        if (cache == NULL)                return;        RB_CLEAR_NODE(&cache->rb_node);        cache->jiffies = set->jiffies;          // 获取访问权限的时间戳        cache->cred = get_rpccred(set->cred);   // 用户信息        cache->mask = set->mask;                // 用户对文件的访问权限        nfs_access_add_rbtree(inode, cache);    // 添加到红黑树中.        /* Update accounting */        // nfs_access_nr_entries 是一个全局变量,表示客户端缓存的访问权限总量        smp_mb__before_atomic_inc();        atomic_long_inc(&nfs_access_nr_entries);                smp_mb__after_atomic_inc();        /* Add inode to global LRU list */        if (!test_bit(NFS_INO_ACL_LRU_SET, &NFS_I(inode)->flags)) {                spin_lock(&nfs_access_lru_lock);                if (!test_and_set_bit(NFS_INO_ACL_LRU_SET, &NFS_I(inode)->flags))                        list_add_tail(&NFS_I(inode)->access_cache_inode_lru,                                        &nfs_access_lru_list);  // 添加到lru链表中                spin_unlock(&nfs_access_lru_lock);        }}

    最后需要说明一点,即使nfs_do_access()检查通过了,也不代码用户一定有文件的访问权限。因为服务器可能在客户端发起ACCESS请求后修改了访问权限,因此客户端发起后续操作时仍可能失败,nfs_do_access()只起一种指导作用,客户端仍需要对后续操作中的错误情况进行处理。但是,通过nfs_do_accesss()可以增加后续操作的成功率,很大程度上避免了后续操作错误情况的发生。

原创粉丝点击