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]是已用缓冲区的尾指针。

函数详解
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() &¤t->euid != inode->i_uid &¤t->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位代表文件的类型:

这四个位所表示的文件类型可以参考下面这张表:
| 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用于执行时进行提权。

对于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版本的内核中最长的函数,总长度为700行。其核心是namei函数,即根据文件路径寻找对应的i节点。 除此以外,该模块还包含一些创建目录,删除目录,创建目…...
计算机网络学习笔记
<!-- GFM-TOC --> 计算机网络体系结构 传输层:TCP和UDP 什么是三次握手? 什么是四次挥手? TCP如何实现流量控制? TCP的拥塞控制是怎么实现的? TCP如何最大利用带宽? TCP与UDP的区别 TCP如何保…...
Pod相关操作命令
Pod相关操作命令 Pod setup # CocoaPods 将信息下载到~/.cocoapods/repos 目录下。如果安装 CocoaPods 时不执行此命令,在初次执行pod intall 命令时,系统也会自动执行该指令 pod --version # 检查 CocoaPods 是否安装成功及其版本号 pod repo update #…...
图灵完备游戏:信号计数 解法记录
使用1个全加器 2个半加器完成。这关的思想主旨在于如何把输出4,输出2,输出1的情况统一在一根导线上。 首先用一个全加器来完成输入2-4这三个引脚的计数,因为全加器输出范围二进制是00 - 11,而输入正好有两个引脚数位是2和1&…...
数据结构图的基础概念
1、图的概念 图(Graph):是由顶点的有穷非空集合和顶点之间边的集合组成。顶点(Vertex):图中的数据元素。边(Edge):顶点之间的逻辑关系,边可以是有向的或无向的,也可以带有权重(可以表示距离,花费等…...
一场九年前的“出发”:奠基多模态,逐鹿大模型
原创:谭婧 全球AI大模型的技术路线,没有多少秘密,就那几条路线,一只手都数得过来。 而举世闻名的GPT-4浑身上下都是秘密。 这两件事并不矛盾。为什么呢? 这就好比,回答“如何制造一台光刻机?”。…...
什么是url跳转漏洞?
什么是url跳转漏洞 简介原因:如何防止 简介 URL跳转漏洞是一种Web应用程序安全问题,指的是在应用程序处理URL跳转时,由于程序员的疏忽或设计不当,攻击者可能通过构造恶意URL来实现对应用程序的攻击。 原因: 跳转条件…...
生物学经典blast比对算法,R语言和Python如何实现?
Blast比对算法原理与实现方式 做生物的同学肯定听说过blast比对这个方法,一般在NCBI等网站上可以在线进行比对,也可以在本地服务器进行比对,那么blast算法究竟是怎么实现对不同序列的比对呢? 本文分享经典blast算法的基础原理&…...
Android 开机动画支持mp4格式视频播放
前 言 Android系统在启动的过程中,最多可以出现三个画面,每一个画面都用来描述一个不同的启动阶段。无论是哪一个画面,它们都是在一个称为帧缓冲区(frame buffer,简称fb)的硬件设备上进行渲染的。 自定义…...
软考A计划-试题模拟含答案解析-卷十
点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 👉关于作者 专注于Android/Unity和各种游戏开发技巧,以及各种资源分享&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] #添加远程仓库,git remote add origin 远程仓库地址git remote rm [name] #删除远程仓库,git remote rm origingit remo…...
一些查看日志时的常用命令
文章目录 1、grep -r 搜索内容 *2、l * 关键字 *3、tail -f 文件名4、tail -n X 文件名5、cat 文件名 | grep "关键字" -C X同理可得,-A同理可得,-B 一些查看日志时的常用命令 1、grep -r 搜索内容 * 作用:在一堆文件里࿰…...
Javascript 的执行环境(execution context)和作用域(scope)及垃圾回收
执行环境有全局执行环境和函数执行环境之分,每次进入一个新执行环境,都会创建一个搜索变量和函数的作用域链。函数的局部环境不仅有权访问函数作用于中的变量,而且可以访问其外部环境,直到全局环境。全局执行环境只能访问全局执行…...
CRDT协同算法
CRDT的英文全称是Conflict-free Replicated Data Type,最初是由协同文本编辑和移动计算而发展的,现在还被用作在线聊天系统、音频分发平台等等。当前CRDT算法在富文本编辑器领域的协同依旧是典型的场景,常用于作为实现文档协同的底层算法&…...
近代中国的三次思想文化运动
1、戊戌变法中维新派顽固派论战 第一次思想解放潮流是1898年维新派与顽固势力的论战。论战的内容有:要不要变法,要不要兴民权、实行君主立宪,要不要提倡西学、改变教育制度。此次论争是资本主义思想同封建主义思想的正面交锋,此后…...
《地铁上的面试题》--目录
第一部分:基础 数据结构与算法 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,原理图分析 首先看原理图,我们兼容ZC706的板子有两片 FLASH,型号是S25FL128A,连接方式如下: 可以看到两片是分别接在了XC7Z045芯片的引脚上,是互不相干的并联方式,每个FLASH芯片支持X4模式,也…...
第二期:链表经典例题(两数相加,删除链表倒数第N个节点,合并两个有序列表)
每道题后都有解析帮助你分析做题,答案在最下面,关注博主每天持续更新。 PS:每道题解题方法不唯一,欢迎讨论! 1.两数相加 题目描述 给你两个非空的链表,表示两个非负的整数。它们每位数字都是按照逆序的方式…...
ESP32设备驱动-SHT35湿度传感器驱动
SHT35湿度传感器驱动 1、SHT35介绍 SHT35 数字温湿度传感器基于 Sensirion SHT35 传感器 IC。 得益于Sensirion的CMOSens技术,高度集成的电容式湿度传感元件和带隙温度传感元件,SHT35具有高可靠性和长期稳定性,功耗低,响应速度快,抗干扰能力强。 传感器支持IIC通信,兼容…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...
SpringTask-03.入门案例
一.入门案例 启动类: package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...
Linux 中如何提取压缩文件 ?
Linux 是一种流行的开源操作系统,它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间,使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的,要在 …...
省略号和可变参数模板
本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...
算法打卡第18天
从中序与后序遍历序列构造二叉树 (力扣106题) 给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。 示例 1: 输入:inorder [9,3,15,20,7…...
