当前位置: 首页 > news >正文

Linux-0.11 文件系统namei.c详解

Linux-0.11 文件系统namei.c详解

模块简介

namei.c是整个linux-0.11版本的内核中最长的函数,总长度为700+行。其核心是namei函数,即根据文件路径寻找对应的i节点。 除此以外,该模块还包含一些创建目录,删除目录,创建目录项等系统调用。

在接触本模块的具体函数之前,可以回顾一下不同的i节点,这将对理解本模块的函数非常有帮助。

对于目录节点,其i_zone[0]指向的block中存放的是dir_entry。

对于文件节点,其i_zone[0] - i_zone[6]是直接寻址块。i_zone[7]是一次间接寻址块,i_zone[8]是二次间接寻址块。

对于设备节点, 其i_zone[0]存放的是设备号。

对于管道节点, 其i_size指向的是管道缓冲区的起始位置,i_zone[0]为已用缓冲区的头指针, i_zone[1]是已用缓冲区的尾指针。

不同的inode节点

函数详解

permission

static int permission(struct m_inode * inode,int mask)

该函数用于检查进程操作文件inode的权限。

inode的权限存储在i_mode字段中,其是一个16位长度的无符号整型数据。其0-2位代表其他用户对该i节点的操作权限,其3-5位代表同一个用户组的用户对该i节点的操作权限,其6-8位代表文件所属用户的对i节点的操作权限。如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UnnaCjuB-1685200748390)(null)]

有了对i_mode的理解之后,可以更好地理解permission函数。

入参mask用于检查权限,其检查内容可以从下表中获取。

mask的值含义
mask = 4检查进程是否有权限读该inode
mask = 2检查进程是否有权限写该inode
mask = 1检查进程是否有权限执行该inode
mask = 5检查进程是否有权限读和执行该inode
mask = 3检查进程是否有权限写和执行该inode
mask = 6检查进程是否有权限读和写该inode
mask = 7检查进程是否有权限读写和执行该inode

下面开始理解permission中的代码。

	int mode = inode->i_mode;//首先从inode节点中的i_mode字段获取i节点权限/* special case: not even root can read/write a deleted file */if (inode->i_dev && !inode->i_nlinks)//如果一个i节点已经被删除是不可以被读取的return 0;else if (current->euid==inode->i_uid)//如果用户id相同,向右移动6位mode >>= 6;else if (current->egid==inode->i_gid)//如果组id相同,向右移动3位mode >>= 3;if (((mode & mask & 0007) == mask) || suser())//访问权限和掩码相同,或者是超级用户return 1;return 0;

match

static int match(int len,const char * name,struct dir_entry * de)

该函数用于比较name的和de->name的前len个字符是否相等。(注意name处于用户空间)。

首先对参数进行校验。如果目录项指针de为空, 或者de中的inode节点指针为空,或者长度len大于文件名的最大长度,则直接返回0。

接下来是对目录项中文件名的长度进行校验,如果其长度大于len,则de->name[len]的值不为NULL。在这种情况下,直接返回0。

register int same ;if (!de || !de->inode || len > NAME_LEN)return 0;
if (len < NAME_LEN && de->name[len])return 0;

下面通过一段汇编实现字符串指定长度的比较。

其中,esi指向name,edi指向de->name, ecx的值为len, 将eax的值赋值给same。

__asm__("cld\n\t"//清方向位"fs ; repe ; cmpsb\n\t"//用户空间 执行循环比较 while(ecx--) fs:esi++ == es:edi++"setz %%al"//如果结果一样,则设置al = 1:"=a" (same):"0" (0),"S" ((long) name),"D" ((long) de->name),"c" (len));
return same;

其中,下面这行汇编较难理解,

fs ; repe ; cmpsb

cmpsb指令用于比较ds:esi和es:edi指向的一个字节的内容。 而加上了前缀repe之后,就代表重复执行cmpsb指令,直到ecx等于0,或者ds:esi和es:edi的值不等。 但是由于name在用户空间,因此还需要加上fs前缀,使得被操作数修改为fs:esi。

find_entry

static struct buffer_head * find_entry(struct m_inode ** dir,const char * name, int namelen, struct dir_entry ** res_dir)

该函数的作用是是去指定的目录下用文件名搜索相应的文件,返回对应的dir_entry结构。

假设现在有一个路径/home/work/test.txt,dir指向的是/home,name指向的是work/test.txt,namelen=4, 那么该函数将会找到/home/work对应的dir_entry(dir_entry中包含了inode号和目录名字)。

整个find的过程可以参考下面这张图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1cmz0JXb-1685200747440)(null)]

刚开始定义了一些参数,并对一些参数的有效性进行了校验。 如果定义了宏NO_TRUNCATE, 如果长度超长,就直接返回NULL。如果没有定义该宏, 长度超长,则进行截断。

	int entries;int block,i;struct buffer_head * bh;struct dir_entry * de;struct super_block * sb;#ifdef NO_TRUNCATEif (namelen > NAME_LEN)return NULL;
#elseif (namelen > NAME_LEN)namelen = NAME_LEN;
#endif

接下来取出当前的目录中有多少个目录项。

entries = (*dir)->i_size / (sizeof (struct dir_entry));
*res_dir = NULL;
if (!namelen)return NULL;

接下来对一些特别的场景进行处理,即当name=..的情况。

首先,假设一个进程的根目录是/home/work,但是目前访问的地址是/home/work/.., 这种情况是不被允许的,因此这里会将/home/work/..处理成/home/work/.

另外,如果该目录的i节点号等于1,说明是某个文件系统的根inode节点。这个时候如果要执行…操作就需要特殊处理。 首先取出文件系统的超级块,查看该文件系统被安装到了哪个inode节点上,如果该节点是存在的,那么会将(*dir)指向安装的inode节点。

/* check for '..', as we might have to do some "magic" for it */if (namelen==2 && get_fs_byte(name)=='.' && get_fs_byte(name+1)=='.') {
/* '..' in a pseudo-root results in a faked '.' (just change namelen) */if ((*dir) == current->root)namelen=1;else if ((*dir)->i_num == ROOT_INO) {
/* '..' over a mount-point results in 'dir' being exchanged for the mounteddirectory-inode. NOTE! We set mounted, so that we can iput the new dir */sb=get_super((*dir)->i_dev);if (sb->s_imount) {iput(*dir);(*dir)=sb->s_imount;(*dir)->i_count++;}}}

如果name不为…,则进入下面的逻辑。

接着取出dir对应的inode节点对应的数据块的内容(dir目录下的文件)。

if (!(block = (*dir)->i_zone[0]))return NULL;
if (!(bh = bread((*dir)->i_dev,block)))return NULL;
i = 0;
de = (struct dir_entry *) bh->b_data;

接下来便进行遍历所有的目录项的内容,比较de->name和name 相同的项。

while (i < entries) {if ((char *)de >= BLOCK_SIZE+bh->b_data) {brelse(bh);bh = NULL;if (!(block = bmap(*dir,i/DIR_ENTRIES_PER_BLOCK)) ||!(bh = bread((*dir)->i_dev,block))) {i += DIR_ENTRIES_PER_BLOCK;continue;}de = (struct dir_entry *) bh->b_data;}if (match(namelen,name,de)) { //如果匹配上了,就返回对应的dir_entry*res_dir = de;return bh;}de++;i++;
}

add_entry

static struct buffer_head * add_entry(struct m_inode * dir,const char * name, int namelen, struct dir_entry ** res_dir)

该函数用于向指定的目录添加一个目录项。

刚开始定义了一些参数,并对一些参数的有效性进行了校验。 如果定义了宏NO_TRUNCATE, 如果长度超长,就直接返回NULL。如果没有定义该宏, 长度超长,则进行截断。

	int block,i;struct buffer_head * bh;struct dir_entry * de;*res_dir = NULL;
#ifdef NO_TRUNCATEif (namelen > NAME_LEN)return NULL;
#elseif (namelen > NAME_LEN)namelen = NAME_LEN;
#endifif (!namelen)return NULL;

下面从读取i_zone[0]对应的磁盘块,其中存储了dir_entry。

	if (!(block = dir->i_zone[0]))return NULL;if (!(bh = bread(dir->i_dev,block)))return NULL;i = 0;de = (struct dir_entry *) bh->b_data;

接下来的过程便是遍历所有的目录项,从中找到空的目录项,用于创建新的目录项。

如果当前目录项的所有数据块都已经搜索完毕,但是还是没有找到需要的空目录,则会去下一个逻辑块中查找。如果下一个逻辑块不存在则会进行创建。

	while (1) {if ((char *)de >= BLOCK_SIZE+bh->b_data) {brelse(bh);bh = NULL;block = create_block(dir,i/DIR_ENTRIES_PER_BLOCK);if (!block)return NULL;if (!(bh = bread(dir->i_dev,block))) {i += DIR_ENTRIES_PER_BLOCK;continue;}de = (struct dir_entry *) bh->b_data;}if (i*sizeof(struct dir_entry) >= dir->i_size) {de->inode=0;dir->i_size = (i+1)*sizeof(struct dir_entry);dir->i_dirt = 1;dir->i_ctime = CURRENT_TIME;}if (!de->inode) {dir->i_mtime = CURRENT_TIME;for (i=0; i < NAME_LEN ; i++)de->name[i]=(i<namelen)?get_fs_byte(name+i):0;bh->b_dirt = 1;*res_dir = de;return bh;}de++;i++;}brelse(bh);return NULL;

get_dir

static struct m_inode * get_dir(const char * pathname)

该函数的作用是搜寻最下层的目录的inode号。 该函数将在dir_namei中被调用。

如果pathname是/home/work/test.txt,那么get_dir将返回/home/work目录的inode。

如果pathname是/home/work/test,那么get_dir将返回/home/work目录的inode。

如果pathname是/home/work/test/,那么get_dir将返回/home/work/test目录的inode。

程序的开始检测路径是绝对路径还是相对路径。绝对路径就从current->root开始寻找,相对路径就从current->pwd开始寻找。

	char c;const char * thisname;struct m_inode * inode;struct buffer_head * bh;int namelen,inr,idev;struct dir_entry * de;if (!current->root || !current->root->i_count)//对current->root进行校验panic("No root inode");if (!current->pwd || !current->pwd->i_count)//对current->pwd进行校验panic("No cwd inode");if ((c=get_fs_byte(pathname))=='/') {// 起始字符是/,说明是绝对路径inode = current->root;pathname++;} else if (c) //否则是相对路径inode = current->pwd;elsereturn NULL;	/* empty name is bad */inode->i_count++;

接下来进行循环,每次读取路径中的一个目录(文件)名。 因为路径名是以/进行分割的,因此可以将/作为标记进行寻找。当c=get_fs_byte(pathname++) = NULL时,寻找结束,代表已经到达了pathname字符串的末尾。

这里需要考虑目录名是其他文件系统的挂载节点的场景, 这个场景在iget函数内部会进行处理,即当一个目录是另一个文件系统的挂载节点的时候,就会去超级块中寻找真正的i节点。

while (1) {thisname = pathname;if (!S_ISDIR(inode->i_mode) || !permission(inode,MAY_EXEC)) {iput(inode);return NULL;}for(namelen=0;(c=get_fs_byte(pathname++))&&(c!='/');namelen++)/* nothing */ ;if (!c)return inode;if (!(bh = find_entry(&inode,thisname,namelen,&de))) {iput(inode);return NULL;}inr = de->inode;idev = inode->i_dev;brelse(bh);iput(inode);if (!(inode = iget(idev,inr)))return NULL;
}

dir_namei

static struct m_inode * dir_namei(const char * pathname,int * namelen, const char ** name)

该函数的作用是返回指定路径(pathname)所在目录的i节点, 并返回pathname路径最末端的文件名。

例如:pathname = /home/work/test.txt, dir_namei将返回目录/home/work的i节点,同时设置name = “test.txt”, namelen = 8。

	char c;const char * basename;struct m_inode * dir;if (!(dir = get_dir(pathname)))//调用get_dir函数获取靠近末端的上层目录的i节点return NULL;basename = pathname;while ((c=get_fs_byte(pathname++)))if (c=='/')basename=pathname;*namelen = pathname-basename-1; //获取路径中最右侧的文件名*name = basename;return dir;

namei

struct m_inode * namei(const char * pathname)

该函数的作用是根据路径名字获取文件的inode节点,是该文件中最重要的函数。namei综合运行了上面的dir_namei和find_entry函数。

	const char * basename;int inr,dev,namelen;struct m_inode * dir;struct buffer_head * bh;struct dir_entry * de;if (!(dir = dir_namei(pathname,&namelen,&basename)))//调用dir_namei获取上层目录的i节点和最右侧文件的名字。return NULL;if (!namelen)			/* special case: '/usr/' etc */return dir;bh = find_entry(&dir,basename,namelen,&de);//获取文件的dir_entry项if (!bh) {iput(dir);return NULL;}inr = de->inode;//从dir_entry中获取i节点编号dev = dir->i_dev;//从目录的i节点中获取设备号brelse(bh);iput(dir);dir=iget(dev,inr);//调用iget获取i节点if (dir) {dir->i_atime=CURRENT_TIME;dir->i_dirt=1;}return dir;

open_namei

int open_namei(const char * pathname, int flag, int mode,struct m_inode ** res_inode)

该函数作用是根据文件路径pathname打开一个文件,找到其inode。是open函数使用的namei函数。

函数的开始对文件的flag对一些检查,关于这些flag的含义可以参考 Q & A 2.open 文件时的一些flag的作用。

如果文件访问模式是只读, 但是文件截0标志O_TRUNC却置位了,则需要在文件打开标志中增加只写标志O_WRONLY,原因是截0操作必须要有文件可写。

	const char * basename;int inr,dev,namelen;struct m_inode * dir, *inode;struct buffer_head * bh;struct dir_entry * de;if ((flag & O_TRUNC) && !(flag & O_ACCMODE))flag |= O_WRONLY;  //增加可写的标记mode &= 0777 & ~current->umask; //和进程的打开文件的掩码相与mode |= I_REGULAR;//创建普通文件

接下来根据路径名寻找文件上层目录的i节点。需要处理路径为/usr/这种以/结尾的路径。

	if (!(dir = dir_namei(pathname,&namelen,&basename)))return -ENOENT;if (!namelen) {			/* special case: '/usr/' etc */if (!(flag & (O_ACCMODE|O_CREAT|O_TRUNC))) { //如果路径是/usr/, 若操作不是读写,创建和文件长度截0,则表示是在打开一个目录名文件操作。*res_inode=dir;return 0;}iput(dir);return -EISDIR;}

下面使用find_entry查看要打开的文件是否已经存在。下面这段是文件不存在进行创建的逻辑。

	bh = find_entry(&dir,basename,namelen,&de);//查找打开文件的dir_entryif (!bh) {   //不存在if (!(flag & O_CREAT)) {  //检查0_CREAT标记iput(dir);return -ENOENT;}if (!permission(dir,MAY_WRITE)) {//检查目录写权限iput(dir);return -EACCES;}inode = new_inode(dir->i_dev);if (!inode) {iput(dir);return -ENOSPC;}inode->i_uid = current->euid;inode->i_mode = mode;inode->i_dirt = 1;bh = add_entry(dir,basename,namelen,&de);//添加到目录中if (!bh) {inode->i_nlinks--;iput(inode);iput(dir);return -ENOSPC;}de->inode = inode->i_num;bh->b_dirt = 1;brelse(bh);iput(dir);*res_inode = inode;return 0;}

下面这段逻辑要打开的文件已经存在的逻辑。通过iget返回文件的i节点。

	inr = de->inode;dev = dir->i_dev;brelse(bh);iput(dir);if (flag & O_EXCL)return -EEXIST;if (!(inode=iget(dev,inr)))return -EACCES;if ((S_ISDIR(inode->i_mode) && (flag & O_ACCMODE)) ||!permission(inode,ACC_MODE(flag))) {iput(inode);return -EPERM;}inode->i_atime = CURRENT_TIME;if (flag & O_TRUNC)  //如果设置了截0标记truncate(inode); //调用truncate将文件长度截为0*res_inode = inode;return 0;

sys_mknod

int sys_mknod(const char * filename, int mode, int dev)

该函数的作用是创建一个设备特殊文件或者普通文件节点。

根据mode的值的不同会创建不同的节点,当mode为块设备或者是字符设备,则是创建一个设备i节点,除此以外,其他mode将会创建普通i节点。

设备节点和普通节点的区别可以参考Q&A 1.S_ISREG/S_ISDIR/S_ISCHR/S_ISBLK/S_ISFIFO 是如何判断文件类型的。

	const char * basename;int namelen;struct m_inode * dir, * inode;struct buffer_head * bh;struct dir_entry * de;if (!suser())//如果不是超级用户,返回出错return -EPERM;if (!(dir = dir_namei(filename,&namelen,&basename)))///获取上层的目录i节点return -ENOENT;if (!namelen) {iput(dir);return -ENOENT;}if (!permission(dir,MAY_WRITE)) {//检查是否有上层目录的写权限iput(dir);return -EPERM;}bh = find_entry(&dir,basename,namelen,&de);//检查是否已经存在if (bh) {brelse(bh);iput(dir);return -EEXIST;}inode = new_inode(dir->i_dev);//否则创建一个i节点if (!inode) {iput(dir);return -ENOSPC;}inode->i_mode = mode;if (S_ISBLK(mode) || S_ISCHR(mode))inode->i_zone[0] = dev;  //i_zone存放设备号inode->i_mtime = inode->i_atime = CURRENT_TIME; //修改时间inode->i_dirt = 1;//将该i节点标记为含有脏数据bh = add_entry(dir,basename,namelen,&de);//添加到目录下if (!bh) {iput(dir);inode->i_nlinks=0;iput(inode);return -ENOSPC;}de->inode = inode->i_num;//将dir_entry中的i节点序号指向inode->i_numbh->b_dirt = 1;iput(dir);iput(inode);brelse(bh);return 0;

sys_mkdir

int sys_mkdir(const char * pathname, int mode)

该函数的作用是用于创建一个目录。

	const char * basename;int namelen;struct m_inode * dir, * inode;struct buffer_head * bh, *dir_block;struct dir_entry * de;if (!suser())  //如果不是超级用户,则返回权限问题。return -EPERM;if (!(dir = dir_namei(pathname,&namelen,&basename)))//获取该路径所在目录的i节点return -ENOENT;if (!namelen) {iput(dir);return -ENOENT;}if (!permission(dir,MAY_WRITE)) {//如果对该目录没有写权限iput(dir);return -EPERM;}bh = find_entry(&dir,basename,namelen,&de);//从该目录中if (bh) {brelse(bh);iput(dir);return -EEXIST;}inode = new_inode(dir->i_dev);//创建一个新的i节点if (!inode) {iput(dir);return -ENOSPC;}inode->i_size = 32;     //设置目录的大小,因为有两个默认的目录.和..inode->i_dirt = 1;inode->i_mtime = inode->i_atime = CURRENT_TIME;if (!(inode->i_zone[0]=new_block(inode->i_dev))) {iput(dir);inode->i_nlinks--;iput(inode);return -ENOSPC;}inode->i_dirt = 1;if (!(dir_block=bread(inode->i_dev,inode->i_zone[0]))) {iput(dir);free_block(inode->i_dev,inode->i_zone[0]);inode->i_nlinks--;iput(inode);return -ERROR;}de = (struct dir_entry *) dir_block->b_data;de->inode=inode->i_num;     strcpy(de->name,".");   //创建.目录项de++;de->inode = dir->i_num;strcpy(de->name,"..");  //创建..目录项inode->i_nlinks = 2;dir_block->b_dirt = 1;brelse(dir_block);inode->i_mode = I_DIRECTORY | (mode & 0777 & ~current->umask);inode->i_dirt = 1;bh = add_entry(dir,basename,namelen,&de);if (!bh) {iput(dir);free_block(inode->i_dev,inode->i_zone[0]);inode->i_nlinks=0;iput(inode);return -ENOSPC;}de->inode = inode->i_num;bh->b_dirt = 1;dir->i_nlinks++;//新建目录项会增加上层目录的链接数,与此对应的是删除目录的时候会减少上层目录的链接数dir->i_dirt = 1;iput(dir);iput(inode);brelse(bh);return 0;

empty_dir

static int empty_dir(struct m_inode * inode)

该函数的作用是检查指定的目录是否为空。

	int nr,block;int len;struct buffer_head * bh;struct dir_entry * de;len = inode->i_size / sizeof (struct dir_entry);if (len<2 || !inode->i_zone[0] ||!(bh=bread(inode->i_dev,inode->i_zone[0]))) {   //如果当前目录的目录项小于两个, 获取i_zone[0]为空printk("warning - bad directory on dev %04x\n",inode->i_dev);return 0;}de = (struct dir_entry *) bh->b_data;if (de[0].inode != inode->i_num || !de[1].inode || strcmp(".",de[0].name) || strcmp("..",de[1].name)) { //检查目录项是否是.或者..printk("warning - bad directory on dev %04x\n",inode->i_dev);return 0;}nr = 2;de += 2;while (nr<len) {if ((void *) de >= (void *) (bh->b_data+BLOCK_SIZE)) { //循环检测剩下所有的目录项的指向的i节点值是否会等于0brelse(bh);block=bmap(inode,nr/DIR_ENTRIES_PER_BLOCK);if (!block) {nr += DIR_ENTRIES_PER_BLOCK;continue;}if (!(bh=bread(inode->i_dev,block)))return 0;de = (struct dir_entry *) bh->b_data;}if (de->inode) {brelse(bh);return 0;}de++;nr++;}brelse(bh);return 1;

sys_rmdir

int sys_rmdir(const char * name)

该函数的作用是用于删除目录。

	const char * basename;int namelen;struct m_inode * dir, * inode;struct buffer_head * bh;struct dir_entry * de;if (!suser())   //如果不是超级用户,返回权限错误return -EPERM;if (!(dir = dir_namei(name,&namelen,&basename)))//获取路径的上一层目录的i节点return -ENOENT;if (!namelen) {iput(dir);return -ENOENT;}if (!permission(dir,MAY_WRITE)) {//检查是否有目录的写权限iput(dir);return -EPERM;}bh = find_entry(&dir,basename,namelen,&de);//查找目录项dir_entry项if (!bh) {iput(dir);return -ENOENT;}if (!(inode = iget(dir->i_dev, de->inode))) {//获取目录项对应的i节点iput(dir);brelse(bh);return -EPERM;}if ((dir->i_mode & S_ISVTX) && current->euid && //如果目录设置了SBIT, 如果当前有效用户不等于该i节点的用户,并且不是root用户,则无权进行删除inode->i_uid != current->euid) {   iput(dir);iput(inode);brelse(bh);return -EPERM;}if (inode->i_dev != dir->i_dev || inode->i_count>1) {iput(dir);iput(inode);brelse(bh);return -EPERM;}if (inode == dir) {	//如果删除的是.目录iput(inode);iput(dir);brelse(bh);return -EPERM;}if (!S_ISDIR(inode->i_mode)) {//如果该i节点不是一个目录类型的i节点,则不能进行操作。iput(inode);       //放回i节点iput(dir);         //放回目录节点brelse(bh);        //释放高速缓冲块return -ENOTDIR;}if (!empty_dir(inode)) {  //如果被删除的目录不是一个空目录则不能进行删除iput(inode);       //放回i节点iput(dir);         //放回目录节点brelse(bh);        //释放高速缓冲块return -ENOTEMPTY;}if (inode->i_nlinks != 2)printk("empty directory has nlink!=2 (%d)",inode->i_nlinks);de->inode = 0;     //将目录项指向的i节点设置为0bh->b_dirt = 1;    //将bh块设置为有脏数据brelse(bh);        //释放该bh块inode->i_nlinks=0; //将i节点的链接数设置为0inode->i_dirt=1;   //将i节点设置为有脏数据dir->i_nlinks--;   //减少目录节点的链接数dir->i_ctime = dir->i_mtime = CURRENT_TIME;   //修改目录i节点的修改时间dir->i_dirt=1;    //将目录i节点设置为有脏数据iput(dir);         //放回要删除的目录的上层目录的i节点iput(inode);       //放回要删除的目录的i节点return 0;

sys_unlink

int sys_unlink(const char * name)

该函数的作用是用于删除文件的一个链接。

	const char * basename;int namelen;struct m_inode * dir, * inode;struct buffer_head * bh;struct dir_entry * de;if (!(dir = dir_namei(name,&namelen,&basename)))//获取文件所在目录的i节点return -ENOENT;if (!namelen) {//如果文件名长度为0iput(dir);return -ENOENT;}if (!permission(dir,MAY_WRITE)) {//检查是否有目录的写权限iput(dir);return -EPERM;}bh = find_entry(&dir,basename,namelen,&de);//查找该文件的dir_entryif (!bh) {iput(dir);return -ENOENT;}if (!(inode = iget(dir->i_dev, de->inode))) {//获取该文件的i节点iput(dir);brelse(bh);return -ENOENT;}if ((dir->i_mode & S_ISVTX) && !suser() &&current->euid != inode->i_uid &&current->euid != dir->i_uid) {//检查用户权限iput(dir);iput(inode);brelse(bh);return -EPERM;}if (S_ISDIR(inode->i_mode)) {//检查是否是目录iput(inode);iput(dir);brelse(bh);return -EPERM;}if (!inode->i_nlinks) {printk("Deleting nonexistent file (%04x:%d), %d\n",inode->i_dev,inode->i_num,inode->i_nlinks);inode->i_nlinks=1;}de->inode = 0;//将dir_entry指向的inode设置为0bh->b_dirt = 1;brelse(bh);inode->i_nlinks--;//将i节点中的i_nlinks减1inode->i_dirt = 1;inode->i_ctime = CURRENT_TIME;iput(inode);iput(dir);return 0;

sys_link

int sys_link(const char * oldname, const char * newname)

该函数的作用是为一个已经存在文件设置一个硬链接。

	struct dir_entry * de;struct m_inode * oldinode, * dir;struct buffer_head * bh;const char * basename;int namelen;oldinode=namei(oldname);//获取已经存在的文件的i节点if (!oldinode)//源文件的i节点不存在,则返回错误return -ENOENT;if (S_ISDIR(oldinode->i_mode)) {//如果源文件的是一个目录,则放回该i节点,返回错误iput(oldinode);return -EPERM;}dir = dir_namei(newname,&namelen,&basename);//获取新路径所在目录的i节点if (!dir) { //如果新路径所在的目录不存哎,则返回错误iput(oldinode);return -EACCES;}if (!namelen) {iput(oldinode);iput(dir);return -EPERM;}if (dir->i_dev != oldinode->i_dev) {//硬链接不能跨越文件系统,因为每个文件系统的都有各自的i节点序号iput(dir);iput(oldinode);return -EXDEV;}if (!permission(dir,MAY_WRITE)) {//检查是否对新的目录具有写权限iput(dir);iput(oldinode);return -EACCES;}bh = find_entry(&dir,basename,namelen,&de);//检查新的路径是否已经存在,如果已经存在则也不能进行创建if (bh) {brelse(bh);iput(dir);iput(oldinode);return -EEXIST;}bh = add_entry(dir,basename,namelen,&de);//在该目录下创建一个新的entry,entry的名字是basenameif (!bh) {iput(dir);iput(oldinode);return -ENOSPC;}de->inode = oldinode->i_num;//将该entry指向源文件所在的i节点bh->b_dirt = 1;//设置该目录i节点上存在脏数据brelse(bh);iput(dir);oldinode->i_nlinks++;//将旧的i节点的硬链接数+1oldinode->i_ctime = CURRENT_TIME;oldinode->i_dirt = 1;iput(oldinode);return 0;

Q & A

1.S_ISREG/S_ISDIR/S_ISCHR/S_ISBLK/S_ISFIFO 是如何判断文件类型的?

这里需要再次重温一下i节点的i_mode的格式, 如下图所示,其中最高的四个bit位代表文件的类型:

inode权限

这四个位所表示的文件类型可以参考下面这张表:

bit值含义
1 0 0 0普通文件
0 1 0 0目录文件
0 0 1 0字符设备文件
0 1 1 0块设备文件
0 0 0 1管道文件

2.open 文件时的一些flag的作用

O_TRUNC标志

使用这个标志,在调用open函数打开文件的时候会将文件原本内容全部丢弃,文件大小变为0;

3.Linux的特殊权限位

对于一个文件的i节点,其9-11位是3个特殊权限位,SUID,SGID,SBIT。SUID和SGID用于执行时进行提权。

inode权限

对于SUID:

  • SUID 权限仅对二进制可执行文件有效
  • 如果执行者对于该二进制可执行文件具有 x 的权限,执行者将具有该文件的所有者的权限
  • 本权限仅在执行该二进制可执行文件的过程中有效

提权过程对二进制文件有效, 对于shell等脚本文件可能不生效

对于SGUID:

当 SGID 作用于普通文件时,和 SUID 类似,在执行该文件时,用户将获得该文件所属组的权限。

当 SGID 作用于目录时,意义就非常重大了。当用户对某一目录有写和执行权限时,该用户就可以在该目录下建立文件,如果该目录用 SGID 修饰,则该用户在这个目录下建立的文件都是属于这个目录所属的组。

对于SBIT

SBIT 与 SUID 和 SGID 的关系并不大。

SBIT 是 the restricted deletion flag or sticky bit 的简称。

SBIT 目前只对目录有效,用来阻止非文件的所有者删除文件。比较常见的例子就是 /tmp 目录,

[xu@localhost shell_proj]$ ls / -al |grep tmp
drwxrwxrwt.  23 root root 4096 Apr 20 22:50 tmp

权限信息中最后一位 t 表明该目录被设置了 SBIT 权限。SBIT 对目录的作用是:当用户在该目录下创建新文件或目录时,仅有自己和 root 才有权力删除。

相关文章:

Linux-0.11 文件系统namei.c详解

Linux-0.11 文件系统namei.c详解 模块简介 namei.c是整个linux-0.11版本的内核中最长的函数&#xff0c;总长度为700行。其核心是namei函数&#xff0c;即根据文件路径寻找对应的i节点。 除此以外&#xff0c;该模块还包含一些创建目录&#xff0c;删除目录&#xff0c;创建目…...

计算机网络学习笔记

<!-- GFM-TOC --> 计算机网络体系结构 传输层&#xff1a;TCP和UDP 什么是三次握手&#xff1f; 什么是四次挥手&#xff1f; TCP如何实现流量控制&#xff1f; TCP的拥塞控制是怎么实现的&#xff1f; TCP如何最大利用带宽&#xff1f; TCP与UDP的区别 TCP如何保…...

Pod相关操作命令

Pod相关操作命令 Pod setup # CocoaPods 将信息下载到~/.cocoapods/repos 目录下。如果安装 CocoaPods 时不执行此命令&#xff0c;在初次执行pod intall 命令时&#xff0c;系统也会自动执行该指令 pod --version # 检查 CocoaPods 是否安装成功及其版本号 pod repo update #…...

图灵完备游戏:信号计数 解法记录

使用1个全加器 2个半加器完成。这关的思想主旨在于如何把输出4&#xff0c;输出2&#xff0c;输出1的情况统一在一根导线上。 首先用一个全加器来完成输入2-4这三个引脚的计数&#xff0c;因为全加器输出范围二进制是00 - 11&#xff0c;而输入正好有两个引脚数位是2和1&…...

数据结构图的基础概念

1、图的概念 图(Graph)&#xff1a;是由顶点的有穷非空集合和顶点之间边的集合组成。顶点(Vertex)&#xff1a;图中的数据元素。边(Edge)&#xff1a;顶点之间的逻辑关系,边可以是有向的或无向的&#xff0c;也可以带有权重&#xff08;可以表示距离&#xff0c;花费等&#xf…...

一场九年前的“出发”:奠基多模态,逐鹿大模型

原创&#xff1a;谭婧 全球AI大模型的技术路线&#xff0c;没有多少秘密&#xff0c;就那几条路线&#xff0c;一只手都数得过来。 而举世闻名的GPT-4浑身上下都是秘密。 这两件事并不矛盾。为什么呢&#xff1f; 这就好比&#xff0c;回答“如何制造一台光刻机&#xff1f;”。…...

什么是url跳转漏洞?

什么是url跳转漏洞 简介原因&#xff1a;如何防止 简介 URL跳转漏洞是一种Web应用程序安全问题&#xff0c;指的是在应用程序处理URL跳转时&#xff0c;由于程序员的疏忽或设计不当&#xff0c;攻击者可能通过构造恶意URL来实现对应用程序的攻击。 原因&#xff1a; 跳转条件…...

生物学经典blast比对算法,R语言和Python如何实现?

Blast比对算法原理与实现方式 做生物的同学肯定听说过blast比对这个方法&#xff0c;一般在NCBI等网站上可以在线进行比对&#xff0c;也可以在本地服务器进行比对&#xff0c;那么blast算法究竟是怎么实现对不同序列的比对呢&#xff1f; 本文分享经典blast算法的基础原理&…...

Android 开机动画支持mp4格式视频播放

前 言 Android系统在启动的过程中&#xff0c;最多可以出现三个画面&#xff0c;每一个画面都用来描述一个不同的启动阶段。无论是哪一个画面&#xff0c;它们都是在一个称为帧缓冲区&#xff08;frame buffer&#xff0c;简称fb&#xff09;的硬件设备上进行渲染的。 自定义…...

软考A计划-试题模拟含答案解析-卷十

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…...

Kafka入门(安装和SpringBoot整合)

文章目录 一、Docker安装Kafka1. 创建网络2. 安装zookeeper3. 安装Kafka 二、Kafka介绍1. Kafka简介 三、SpringBoot整合Kafka1. 引入pom依赖2. application.propertise配置3. Hello Kafka(Producer)4. Consumer Kafka5. 带回调的生产者6. 自定义分区器7. kafka事务提交8. 指定…...

gitLab相关命令

gitLab相关命令 1) 远程仓库相关命令 git clone 远程仓库地址 #检出仓库git remote -v #查看远程仓库git remote add [name][url] #添加远程仓库&#xff0c;git remote add origin 远程仓库地址git remote rm [name] #删除远程仓库&#xff0c;git remote rm origingit remo…...

一些查看日志时的常用命令

文章目录 1、grep -r 搜索内容 *2、l * 关键字 *3、tail -f 文件名4、tail -n X 文件名5、cat 文件名 | grep "关键字" -C X同理可得&#xff0c;-A同理可得&#xff0c;-B 一些查看日志时的常用命令 1、grep -r 搜索内容 * 作用&#xff1a;在一堆文件里&#xff0…...

Javascript 的执行环境(execution context)和作用域(scope)及垃圾回收

执行环境有全局执行环境和函数执行环境之分&#xff0c;每次进入一个新执行环境&#xff0c;都会创建一个搜索变量和函数的作用域链。函数的局部环境不仅有权访问函数作用于中的变量&#xff0c;而且可以访问其外部环境&#xff0c;直到全局环境。全局执行环境只能访问全局执行…...

CRDT协同算法

CRDT的英文全称是Conflict-free Replicated Data Type&#xff0c;最初是由协同文本编辑和移动计算而发展的&#xff0c;现在还被用作在线聊天系统、音频分发平台等等。当前CRDT算法在富文本编辑器领域的协同依旧是典型的场景&#xff0c;常用于作为实现文档协同的底层算法&…...

近代中国的三次思想文化运动

1、戊戌变法中维新派顽固派论战 第一次思想解放潮流是1898年维新派与顽固势力的论战。论战的内容有&#xff1a;要不要变法&#xff0c;要不要兴民权、实行君主立宪&#xff0c;要不要提倡西学、改变教育制度。此次论争是资本主义思想同封建主义思想的正面交锋&#xff0c;此后…...

《地铁上的面试题》--目录

第一部分&#xff1a;基础 数据结构与算法 1.1 数组和链表 1.2 栈和队列 1.3 树和图 1.4 排序和搜索算法 1.5 动态规划和贪心算法 操作系统 2.1 进程与线程 2.2 内存管理 2.3 文件系统 2.4 进程同步与通信 2.5 虚拟化和容器化技术 计算机网络 3.1 TCP/IP协议 3.2 HTTP和HTTPS…...

在VIVADO下烧写ZC706板载FLASH的操作步骤

1&#xff0c;原理图分析 首先看原理图&#xff0c;我们兼容ZC706的板子有两片 FLASH&#xff0c;型号是S25FL128A,连接方式如下&#xff1a; 可以看到两片是分别接在了XC7Z045芯片的引脚上&#xff0c;是互不相干的并联方式&#xff0c;每个FLASH芯片支持X4模式&#xff0c;也…...

第二期:链表经典例题(两数相加,删除链表倒数第N个节点,合并两个有序列表)

每道题后都有解析帮助你分析做题&#xff0c;答案在最下面&#xff0c;关注博主每天持续更新。 PS&#xff1a;每道题解题方法不唯一&#xff0c;欢迎讨论&#xff01; 1.两数相加 题目描述 给你两个非空的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照逆序的方式…...

ESP32设备驱动-SHT35湿度传感器驱动

SHT35湿度传感器驱动 1、SHT35介绍 SHT35 数字温湿度传感器基于 Sensirion SHT35 传感器 IC。 得益于Sensirion的CMOSens技术,高度集成的电容式湿度传感元件和带隙温度传感元件,SHT35具有高可靠性和长期稳定性,功耗低,响应速度快,抗干扰能力强。 传感器支持IIC通信,兼容…...

变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析

一、变量声明设计&#xff1a;let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性&#xff0c;这种设计体现了语言的核心哲学。以下是深度解析&#xff1a; 1.1 设计理念剖析 安全优先原则&#xff1a;默认不可变强制开发者明确声明意图 let x 5; …...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望

文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例&#xff1a;使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例&#xff1a;使用OpenAI GPT-3进…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)

CSI-2 协议详细解析 (一&#xff09; 1. CSI-2层定义&#xff08;CSI-2 Layer Definitions&#xff09; 分层结构 &#xff1a;CSI-2协议分为6层&#xff1a; 物理层&#xff08;PHY Layer&#xff09; &#xff1a; 定义电气特性、时钟机制和传输介质&#xff08;导线&#…...

条件运算符

C中的三目运算符&#xff08;也称条件运算符&#xff0c;英文&#xff1a;ternary operator&#xff09;是一种简洁的条件选择语句&#xff0c;语法如下&#xff1a; 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true&#xff0c;则整个表达式的结果为“表达式1”…...

【Go】3、Go语言进阶与依赖管理

前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课&#xff0c;做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程&#xff0c;它的核心机制是 Goroutine 协程、Channel 通道&#xff0c;并基于CSP&#xff08;Communicating Sequential Processes&#xff0…...

ServerTrust 并非唯一

NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...

css的定位(position)详解:相对定位 绝对定位 固定定位

在 CSS 中&#xff0c;元素的定位通过 position 属性控制&#xff0c;共有 5 种定位模式&#xff1a;static&#xff08;静态定位&#xff09;、relative&#xff08;相对定位&#xff09;、absolute&#xff08;绝对定位&#xff09;、fixed&#xff08;固定定位&#xff09;和…...

什么是EULA和DPA

文章目录 EULA&#xff08;End User License Agreement&#xff09;DPA&#xff08;Data Protection Agreement&#xff09;一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA&#xff08;End User License Agreement&#xff09; 定义&#xff1a; EULA即…...

Rapidio门铃消息FIFO溢出机制

关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系&#xff0c;以下是深入解析&#xff1a; 门铃FIFO溢出的本质 在RapidIO系统中&#xff0c;门铃消息FIFO是硬件控制器内部的缓冲区&#xff0c;用于临时存储接收到的门铃消息&#xff08;Doorbell Message&#xff09;。…...