Hadoop文件访问权限处理与linux采用类似的方式,文件对操作对应有:读,写,执行。文件有其属主,以及群。
该部分主要涉及的类如下:
FsAction
public enum FsAction
这是一个enum对象,描述对文件的操作行为,主要的成员为:
NONE(0, "---"),
EXECUTE(1, "--x"),
WRITE(2, "-w-"),
WRITE_EXECUTE(3, "-wx"),
READ(4, "r--"),
READ_EXECUTE(5, "r-x"),
READ_WRITE(6, "rw-"),
ALL(7, "rwx");
//constants
/** Octal representation */
public final int INDEX;
/** Symbolic representation */
public final String SYMBOL;
//构造函数,构造枚举类型
private FsAction(int v, String s)
{
INDEX = v;
SYMBOL = s;
}
几个枚举类型对应的实例主要为:
none,执行,写,写并和执行,读,读并可执行,读并可写,ALL
同时该对象提供的主要方法:
/** AND operation. */
public FsAction and(FsAction that) {
return values()[this.INDEX & that.INDEX];
}
/** OR operation. */
public FsAction or(FsAction that) {
return values()[this.INDEX | that.INDEX];
}
/** NOT operation. */
public FsAction not() {
return values()[7 - INDEX];
}
//这个方法最为重要,主要是检测用户的某个行为是否具有权限。
public boolean implies(FsAction that)
{
if (that != null)
{
return (this.INDEX & that.INDEX) == that.INDEX;
}
return false;
}
FsPermission
public class FsPermission implements Writable
该对象主要描述各类用户(文件属主,群用户以及其他用户)对文件的访问权限。
该对象主要的三个属性为:
private FsAction useraction = null;
private FsAction groupaction = null;
private FsAction otheraction = null;
从属性命名中我们也可以了解到,这表示一个文件的各类用户对该文件的不同访问权限。
值得一提的方法:
public void fromShort(short n)
{
//枚举中的values方法为静态方法,返回该枚举类型所有值列表
FsAction[] v = FsAction.values();
set(v[(n >>> 6) & 7], v[(n >>> 3) & 7], v[n & 7]);
}
该方法可以从一个short中,生成文件的访问权限。
如0666,表示所有用户可以读写。
PermissionStatus
public class PermissionStatus implements Writable
该对象包含文件与权限相关的所有信息,主要的属性如下:
private String username;
private String groupname;
private FsPermission permission;
可以看到,该对象描述了文件的属主,文件所属的群,文件各类用户的访问权限。
以上提到的权限相关对象,如何在系统中发挥作用呢?
上文已经提到过INode表示文件目录树形结构中一个抽象的节点,它包含一个重要的属性:
// Only updated by updatePermissionStatus(...).
// Other codes should not modify it.
private long permission;
所有节点INodeFile,INodeDirectory 都继承与INode,所有类型节点都包含这个属性,如何通过这个属性构建出节点的PermissionStatus呢?
INode中值得一提的对象:
private static enum PermissionStatusFormat
{
MODE(0, 16), GROUP(MODE.OFFSET + MODE.LENGTH, 25),
USER(GROUP.OFFSET + GROUP.LENGTH, 23);
final int OFFSET;
final int LENGTH; // bit length
final long MASK;
PermissionStatusFormat(int offset, int length)
{
OFFSET = offset;
LENGTH = length;
MASK = ((-1L) >>> (64 - LENGTH)) << OFFSET;
}
}
上文提到,private long permission表示文件的访问权限信息, 如何通过它的到PermissionStatus呢, 我们知道PermissionStatus中包含userName,groupName以及一个FsPermission对象,如何通过一个(long permission)长整型对象恢复出上述三个对象呢?我们知道FsPermission可以通过fromShort(short n)这个函数进行恢复。
PermissionStatusFormat其实主要存在三个枚举值:
MODE,GROUP,USER。
1.MODE存储FsPermission在long(长整型) 中的开始字节数以及字节总数。
2.GROUP存储groupName在long(长整型) 中的开始字节数以及字节总数。
3.GROUP存储userName在long(长整型) 中的开始字节数以及字节总数。
long retrieve(long record)
{
return (record & MASK) >>> OFFSET;
}
该函数从一个长整形中,截取OFFSET偏移量处,LENGTH长度的bits。
OK,FsPermission可以截取前16个字节进行恢复,那userName和groupName呢?
我们看一下代码:
public String getGroupName()
{
int n = (int) PermissionStatusFormat.GROUP.retrieve(permission);
return SerialNumberManager.INSTANCE.getGroup(n);
}
其实原理和FsPermission有些相似,只不过是通过截取long中部分字节做为index获得最终userName。
PermissionChecker
class PermissionChecker
这个类从命名上就很容易看出一些端倪,这个类主要的作用就是进行用户行为的权限验证
该对象主要的方法:
private void check(INode inode, FsAction access)
throws AccessControlException
{
if (inode == null)
{
return;
}
FsPermission mode = inode.getFsPermission();
//如果访问该节点的用户是节点属主
if (user.equals(inode.getUserName()))
{ // user class
if (mode.getUserAction().implies(access))
{
return;
}
}
//如果访问该节点的用户包含在文件所属群中
else if (groups.contains(inode.getGroupName()))
{ // group class
if (mode.getGroupAction().implies(access))
{
return;
}
}
//其他用户
else
{ // other class
if (mode.getOtherAction().implies(access))
{
return;
}
}
throw new AccessControlException("Permission denied: user=" + user + ", access=" + access + ", inode=" + inode);
}
下面我们一起通过构建文件时权限处理流程来熟悉这部分。
在这里我们通过以上文件创建流程的一个实例,了解文件权限该模块儿的主要实现,其中可能包含一些前面未曾提到的类(后面会逐个详细讲解),本模块主要是讲解权限模块儿的实现,主要首先关注创建流程与权限验证部分的实现,其中绿色字体部分为权限相关部分。
1.FileSystem(非常重要的抽象类,文件系统访问的主要接口)首先利用FSPermission得到缺省的文件访问权限
permission = FsPermission.getDefault();
2.DFSClient.create(src,permission)
(DFSClient是用户与文件打交道的接口类,可以理解为前项层)
在权限对象上应用权限umask(该功能类似于unix中的 umask)
FsPermission masked = permission
.applyUMask(FsPermission.getUMask(conf));
后续该参数会一直传递到具体文件创建部分。
3.构建DFSOutputStream实例(后续的文件写操作就是通过操作该Stream对象)
new DFSOutputStream(src, masked, overwrite, replication, blockSize, progress, buffersize, conf.getInt(
"io.bytes.per.checksum", 512));
可以看到masked(前面提到的应用过umask后的权限访问对象)和src这两个关键参数进行往下传递。
4.利用RPC调用Namenode上的create方法。
namenode.create(src, masked, clientName, overwrite,
replication, blockSize);
5.在session中取得当前用戶,利用当前用户构建文件权限相关信息,文件的属主属于当前用户,访问权限为开始时创建并应用了umask的权限访问对象。
new PermissionStatus(UserGroupInformation.getCurrentUGI().getUserName(), null, masked)
6.调用namesystem的文件创建方法
namesystem.startFile(src,new PermissionStatus(UserGroupInformation.getCurrentUGI().getUserName(), null, masked),clientName, clientMachine, overwrite, replication, blockSize);
其中重要的参数src和上述已经提到的new出来的PermissionStatus
7.调用调用namesystem的startFileInternal方法
startFileInternal(src, permissions, holder, clientMachine, overwrite,
false, replication, blockSize);
其中重要的参数src和上述已经提到的new出来的PermissionStatus
8.好,文件访问权限的关键点来了。
//一定会有权限检验的
if (isPermissionEnabled)
{
if (append || (overwrite && dir.exists(src)))
{
checkPathAccess(src, FsAction.WRITE);
}
else
{
//主要是这里,判断用户对父节点是否有可写权限。
checkAncestorAccess(src, FsAction.WRITE);
}
}
9.new出一个PermissionChecker实例
10.
INode[] inodes = root.getExistingPathINodes(path);
int ancestorIndex = inodes.length - 2;
for (; ancestorIndex >= 0 && inodes[ancestorIndex] == null; ancestorIndex--);
checkTraverse(inodes, ancestorIndex);
该部分代码的主要作用,举例:
用户创建文件/user/boss/tmp/tmp2/tmp3/data,但是tmp2/tmp3这个子目录不存在。
那就要找到所谓的ancestorIndex,就是/user/boss/tmp这个文件夹。
就需要判断/user/boss/tmp这个文件加用户是否具有写权限
check(inodes, ancestorIndex, ancestorAccess);
最终调用
check(INode inode, FsAction access)
FsPermission mode = inode.getFsPermission();
if (user.equals(inode.getUserName()))
{ if (mode.getUserAction().implies(access))
{return;} }
else if (groups.contains(inode.getGroupName()))
{ // group class
if (mode.getGroupAction().implies(access))
{return;} }
else
{ // other class
if (mode.getOtherAction().implies(access))
{return;} }
11.用户对祖先目录具有写权限以后,就可以首先创建不存在的子目录,再创建末节点的文件。
所有创建的INode都会将第5步创建的PermissionStatus,转化并赋值给INode的private long permission。