1 /*
2 * linux/fs/namei.c
3 *
4 * (C) 1991 Linus Torvalds
5 */
6
7 /*
8 * Some corrections by tytso.
9 */
10
11 #include <linux/sched.h> // 调度程序头文件,定义了任务结构task_struct、任务0的数据等。
12 #include <linux/kernel.h> // 内核头文件。含有一些内核常用函数的原形定义。
13 #include <asm/segment.h> // 段操作头文件。定义了有关段寄存器操作的嵌入式汇编函数。
14
15 #include <string.h> // 字符串头文件。主要定义了一些有关字符串操作的嵌入函数。
16 #include <fcntl.h> // 文件控制头文件。文件及其描述符的操作控制常数符号的定义。
17 #include <errno.h> // 错误号头文件。包含系统中各种出错号。
18 #include <const.h> // 常数符号头文件。目前仅定义i节点中i_mode字段的各标志位。
19 #include <sys/stat.h> // 文件状态头文件。含有文件或文件系统状态结构stat{}和常量。
20
// 由文件名查找对应i节点的内部函数。
21 static struct m_inode * _namei(const char * filename, struct m_inode * base,
22 int follow_links);
23
// 下面宏中右侧表达式是访问数组的一种特殊使用方法。它基于这样的一个事实,即用数组名和
// 数组下标所表示的数组项(例如a[b])的值等同于使用数组首指针(地址)加上该项偏移地址
// 的形式的值 *(a + b),同时可知项a[b]也可以表示成b[a]的形式。因此对于字符数组项形式
// 为 "LoveYou"[2](或者2["LoveYou"])就等同于*("LoveYou" + 2)。另外,字符串"LoveYou"
// 在内存中被存储的位置就是其地址,因此数组项"LoveYou"[2]的值就是该字符串中索引值为2
// 的字符"v"所对应的ASCII码值0x76,或用八进制表示就是0166。在C语言中,字符也可以用
// 其ASCII码值来表示,方法是在字符的ASCII码值前面加一个反斜杠。例如字符 "v"可以表示
// 成"\x76"或者"\166"。因此对于不可显示的字符(例如ASCII码值为0x00--0x1f的控制字符)
// 就可用其ASCII码值来表示。
//
// 下面是访问模式宏。x是头文件include/fcntl.h中第7行开始定义的文件访问(打开)标志。
// 这个宏根据文件访问标志x的值来索引双引号中对应的数值。双引号中有4个八进制数值(实
// 际表示4个控制字符):"\004\002\006\377",分别表示读、写和执行的权限为: r、w、rw
// 和wxrwxrwx,并且分别对应x的索引值0--3。 例如,如果x为2,则该宏返回八进制值006,
// 表示可读可写(rw)。另外,其中O_ACCMODE = 00003,是索引值x的屏蔽码。
24 #define ACC_MODE(x) ("\004\002\006\377"[(x)&O_ACCMODE])
25
26 /*
27 * comment out this line if you want names > NAME_LEN chars to be
28 * truncated. Else they will be disallowed.
29 */
/*
* 如果想让文件名长度 > NAME_LEN个的字符被截掉,就将下面定义注释掉。
*/
30 /* #define NO_TRUNCATE */
31
32 #define MAY_EXEC 1 // 可执行(可进入)。
33 #define MAY_WRITE 2 // 可写。
34 #define MAY_READ 4 // 可读。
35
36 /*
37 * permission()
38 *
39 * is used to check for read/write/execute permissions on a file.
40 * I don't know if we should look at just the euid or both euid and
41 * uid, but that should be easily changed.
42 */
/*
* permission()
*
* 该函数用于检测一个文件的读/写/执行权限。我不知道是否只需检查euid,
* 还是需要检查euid和uid两者,不过这很容易修改。
*/
//// 检测文件访问许可权限。
// 参数:inode - 文件的i节点指针;mask - 访问属性屏蔽码。
// 返回:访问许可返回1,否则返回0。
43 static int permission(struct m_inode * inode,int mask)
44 {
45 int mode = inode->i_mode; // 文件访问属性。
46
47 /* special case: not even root can read/write a deleted file */
/* 特殊情况:即使是超级用户(root)也不能读/写一个已被删除的文件 */
// 如果i节点有对应的设备,但该i节点的链接计数值等于0,表示该文件已被删除,则返回。
// 否则,如果进程的有效用户id(euid)与i节点的用户id相同,则取文件宿主的访问权限。
// 否则,如果进程的有效组id(egid)与i节点的组id相同,则取组用户的访问权限。
48 if (inode->i_dev && !inode->i_nlinks)
49 return 0;
50 else if (current->euid==inode->i_uid)
51 mode >>= 6;
52 else if (in_group_p(inode->i_gid))
53 mode >>= 3;
// 最后判断如果所取的的访问权限与屏蔽码相同,或者是超级用户,则返回1,否则返回0。
54 if (((mode & mask & 0007) == mask) || suser())
55 return 1;
56 return 0;
57 }
58
59 /*
60 * ok, we cannot use strncmp, as the name is not in our data space.
61 * Thus we'll have to use match. No big problem. Match also makes
62 * some sanity tests.
63 *
64 * NOTE! unlike strncmp, match returns 1 for success, 0 for failure.
65 */
/*
* ok,我们不能使用strncmp字符串比较函数,因为名称不在我们的数据空间
* (不在内核空间)。 因而我们只能使用 match()。问题不大,match()同样
* 也处理一些完整的测试。
*
* 注意!与strncmp不同的是match()成功时返回1,失败时返回0。
*/
//// 指定长度字符串比较函数。
// 参数:len - 比较的字符串长度;name - 文件名指针;de - 目录项结构。
// 返回:相同返回1,不同返回0。
// 第68行上定义了一个局部寄存器变量same。该变量将被保存在eax寄存器中,以便于高效
// 访问。
66 static int match(int len,const char * name,struct dir_entry * de)
67 {
68 register int same __asm__("ax");
69
// 首先判断函数参数的有效性。如果目录项指针空,或者目录项i节点等于0,或者要比较的
// 字符串长度超过文件名长度,则返回0(不匹配)。如果比较的长度len等于0并且目录项
// 中文件名的第1个字符是 '.',并且只有这么一个字符,那么我们就认为是相同的,因此返
// 回1(匹配)。如果要比较的长度len小于NAME_LEN,但是目录项中文件名长度超过len,
// 则也返回0(不匹配)。
// 第75行上对目录项中文件名长度是否超过 len 的判断方法是检测 name[len] 是否为NULL。
// 若长度超过len,则name[len]处就是一个不是NULL的普通字符。而对于长度为len的字符
// 串name,字符name[len]就应该是NULL。
70 if (!de || !de->inode || len > NAME_LEN)
71 return 0;
72 /* "" means "." ---> so paths like "/usr/lib//libc.a" work */
/* "" 当作 "." 来看待 ---> 这样就能处理象 "/usr/lib//libc.a" 那样的路径名 */
73 if (!len && (de->name[0]=='.') && (de->name[1]=='\0'))
74 return 1;
75 if (len < NAME_LEN && de->name[len])
76 return 0;
// 然后使用嵌入汇编语句进行快速比较操作。它会在用户数据空间(fs段)执行字符串的比较
// 操作。%0 - eax(比较结果same);%1 - eax(eax初值0);%2 - esi(名字指针);
// %3 - edi(目录项名指针);%4 - ecx(比较的字节长度值len)。
77 __asm__("cld\n\t" // 清方向标志位。
78 "fs ; repe ; cmpsb\n\t" // 用户空间执行循环比较[esi++]和[edi++]操作,
79 "setz %%al" // 若比较结果一样(zf=0)则置al=1(same=eax)。
80 :"=a" (same)
81 :"" (0),"S" ((long) name),"D" ((long) de->name),"c" (len)
82 :"cx","di","si");
83 return same; // 返回比较结果。
84 }
85
86 /*
87 * find_entry()
88 *
89 * finds an entry in the specified directory with the wanted name. It
90 * returns the cache buffer in which the entry was found, and the entry
91 * itself (as a parameter - res_dir). It does NOT read the inode of the
92 * entry - you'll have to do that yourself if you want to.
93 *
94 * This also takes care of the few special cases due to '..'-traversal
95 * over a pseudo-root and a mount point.
96 */
/*
* find_entry()
*
* 在指定目录中寻找一个与名字匹配的目录项。返回一个含有找到目录项的高速
* 缓冲块以及目录项本身(作为一个参数 - res_dir)。该函数并不读取目录项
* 的i节点 - 如果需要的话则自己操作。
*
* 由于有'..'目录项,因此在操作期间也会对几种特殊情况分别处理 - 比如横越
* 一个伪根目录以及安装点。
*/
//// 查找指定目录和文件名的目录项。
// 参数:*dir - 指定目录i节点的指针;name - 文件名;namelen - 文件名长度;
// 该函数在指定目录的数据(文件)中搜索指定文件名的目录项。并对指定文件名是'..'的
// 情况根据当前进行的相关设置进行特殊处理。关于函数参数传递指针的指针的作用,请参
// 见linux/sched.c第151行前的注释。
// 返回:成功则函数高速缓冲区指针,并在*res_dir处返回的目录项结构指针。失败则返回
// 空指针NULL。
97 static struct buffer_head * find_entry(struct m_inode ** dir,
98 const char * name, int namelen, struct dir_entry ** res_dir)
99 {
100 int entries;
101 int block,i;
102 struct buffer_head * bh;
103 struct dir_entry * de;
104 struct super_block * sb;
105
// 同样,本函数一上来也需要对函数参数的有效性进行判断和验证。如果我们在前面第30行
// 定义了符号常数NO_TRUNCATE,那么如果文件名长度超过最大长度NAME_LEN,则不予处理。
// 如果没有定义过NO_TRUNCATE,那么在文件名长度超过最大长度NAME_LEN时截短之。
106 #ifdef NO_TRUNCATE
107 if (namelen > NAME_LEN)
108 return NULL;
109 #else
110 if (namelen > NAME_LEN)
111 namelen = NAME_LEN;
112 #endif
// 首先计算本目录中目录项项数entries。 目录i节点 i_size字段中含有本目录包含的数据
// 长度,因此其除以一个目录项的长度(16字节)即可得到该目录中目录项数。然后置空返回
// 目录项结构指针。
113 entries = (*dir)->i_size / (sizeof (struct dir_entry));
114 *res_dir = NULL;
// 接下来我们对目录项文件名是'..'的情况进行特殊处理。如果当前进程指定的根i节点就是
// 函数参数指定的目录,则说明对于本进程来说,这个目录就是它的伪根目录,即进程只能访
// 问该目录中的项而不能后退到其父目录中去。也即对于该进程本目录就如同是文件系统的根
// 目录。因此我们需要将文件名修改为'.'。
// 否则,如果该目录的i节点号等于ROOT_INO(1号)的话,说明确实是文件系统的根i节点。
// 则取文件系统的超级块。如果被安装到的i节点存在,则先放回原i节点,然后对被安装到
// 的i节点进行处理。于是我们让*dir指向该被安装到的i节点;并且该i节点的引用数加1。
// 即针对这种情况,我们悄悄地进行了“偷梁换柱”工程:)
115 /* check for '..', as we might have to do some "magic" for it */
/* 检查目录项 '..',因为我们可能需要对其进行特殊处理 */
116 if (namelen==2 && get_fs_byte(name)=='.' && get_fs_byte(name+1)=='.') {
117 /* '..' in a pseudo-root results in a faked '.' (just change namelen) */
/* 伪根中的 '..' 如同一个假 '.'(只需改变名字长度) */
118 if ((*dir) == current->root)
119 namelen=1;
120 else if ((*dir)->i_num == ROOT_INO) {
121 /* '..' over a mount-point results in 'dir' being exchanged for the mounted
122 directory-inode. NOTE! We set mounted, so that we can iput the new dir */
/* 在一个安装点上的 '..' 将导致目录交换到被安装文件系统的目录i节点上。注意!
由于我们设置了mounted标志,因而我们能够放回该新目录 */
123 sb=get_super((*dir)->i_dev);
124 if (sb->s_imount) {
125 iput(*dir);
126 (*dir)=sb->s_imount;
127 (*dir)->i_count++;
128 }
129 }
130 }
// 现在我们开始正常操作,查找指定文件名的目录项在什么地方。因此我们需要读取目录的数
// 据,即取出目录i节点对应块设备数据区中的数据块(逻辑块)信息。这些逻辑块的块号保
// 存在i节点结构的 i_zone[9]数组中。我们先取其中第1个块号。如果目录i节点指向的第
// 一个直接磁盘块号为0,则说明该目录竟然不含数据,这不正常。于是返回NULL退出。否则
// 我们就从节点所在设备读取指定的目录项数据块。当然,如果不成功,则也返回NULL退出。
131 if (!(block = (*dir)->i_zone[0]))
132 return NULL;
133 if (!(bh = bread((*dir)->i_dev,block)))
134 return NULL;
// 此时我们就在这个读取的目录i节点数据块中搜索匹配指定文件名的目录项。首先让de指
// 向缓冲块中的数据块部分,并在不超过目录中目录项数的条件下,循环执行搜索。其中i是
// 目录中的目录项索引号,在循环开始时初始化为0。
135 i = 0;
136 de = (struct dir_entry *) bh->b_data;
137 while (i < entries) {
// 如果当前目录项数据块已经搜索完,还没有找到匹配的目录项,则释放当前目录项数据块。
// 再读入目录的下一个逻辑块。若这块为空,则只要还没有搜索完目录中的所有目录项,就
// 跳过该块,继续读目录的下一逻辑块。若该块不空,就让 de 指向该数据块,然后在其中
// 继续搜索。其中141行上i/DIR_ENTRIES_PER_BLOCK可得到当前搜索的目录项所在目录文
// 件中的块号,而bmap()函数(inode.c,第142行)则可计算出在设备上对应的逻辑块号。
138 if ((char *)de >= BLOCK_SIZE+bh->b_data) {
139 brelse(bh);
140 bh = NULL;
141 if (!(block = bmap(*dir,i/DIR_ENTRIES_PER_BLOCK)) ||
142 !(bh = bread((*dir)->i_dev,block))) {
143 i += DIR_ENTRIES_PER_BLOCK;
144 continue;
145 }
146 de = (struct dir_entry *) bh->b_data;
147 }
// 如果找到匹配的目录项的话,则返回该目录项结构指针de和该目录项i节点指针*dir以
// 及该目录项数据块指针bh,并退出函数。否则继续在目录项数据块中比较下一个目录项。
148 if (match(namelen,name,de)) {
149 *res_dir = de;
150 return bh;
151 }
152 de++;
153 i++;
154 }
// 如果指定目录中的所有目录项都搜索完后,还没有找到相应的目录项,则释放目录的数据
// 块,最后返回NULL(失败)。
155 brelse(bh);
156 return NULL;
157 }
158
159 /*
160 * add_entry()
161 *
162 * adds a file entry to the specified directory, using the same
163 * semantics as find_entry(). It returns NULL if it failed.
164 *
165 * NOTE!! The inode part of 'de' is left at 0 - which means you
166 * may not sleep between calling this and putting something into
167 * the entry, as someone else might have used it while you slept.
168 */
/*
* add_entry()
* 使用与find_entry()同样的方法,往指定目录中添加一指定文件名的目
* 录项。如果失败则返回NULL。
*
* 注意!!'de'(指定目录项结构指针)的i节点部分被设置为0 - 这表
* 示在调用该函数和往目录项中添加信息之间不能去睡眠。 因为如果睡眠,
* 那么其他人(进程)可能会使用了该目录项。
*/
//// 根据指定的目录和文件名添加目录项。
// 参数:dir - 指定目录的i节点;name - 文件名;namelen - 文件名长度;
// 返回:高速缓冲区指针;res_dir - 返回的目录项结构指针;
169 static struct buffer_head * add_entry(struct m_inode * dir,
170 const char * name, int namelen, struct dir_entry ** res_dir)
171 {
172 int block,i;
173 struct buffer_head * bh;
174 struct dir_entry * de;
175
// 同样,本函数一上来也需要对函数参数的有效性进行判断和验证。如果我们在前面第30行
// 定义了符号常数NO_TRUNCATE,那么如果文件名长度超过最大长度NAME_LEN,则不予处理。
// 如果没有定义过NO_TRUNCATE,那么在文件名长度超过最大长度NAME_LEN时截短之。
176 *res_dir = NULL; // 用于返回目录项结构指针。
177 #ifdef NO_TRUNCATE
178 if (namelen > NAME_LEN)
179 return NULL;
180 #else
181 if (namelen > NAME_LEN)
182 namelen = NAME_LEN;
183 #endif
// 现在我们开始操作,向指定目录中添加一个指定文件名的目录项。因此我们需要先读取目录
// 的数据,即取出目录i节点对应块设备数据区中的数据块(逻辑块)信息。这些逻辑块的块
// 号保存在i节点结构的 i_zone[9]数组中。我们先取其中第1个块号。如果目录i节点指向
// 的第一个直接磁盘块号为0,则说明该目录竟然不含数据,这不正常。于是返回NULL退出。
// 否则我们就从节点所在设备读取指定的目录项数据块。当然,如果不成功,则也返回NULL
// 退出。另外,如果参数提供的文件名长度等于0,则也返回NULL退出。
184 if (!namelen)
185 return NULL;
186 if (!(block = dir->i_zone[0]))
187 return NULL;
188 if (!(bh = bread(dir->i_dev,block)))
189 return NULL;
// 此时我们就在这个目录i节点数据块中循环查找最后未使用的空目录项。首先让目录项结构
// 指针de指向缓冲块中的数据块部分,即第一个目录项处。其中i是目录中的目录项索引号,
// 在循环开始时初始化为0。
190 i = 0;
191 de = (struct dir_entry *) bh->b_data;
192 while (1) {
// 如果当前目录项数据块已经搜索完毕,但还没有找到需要的空目录项,则释放当前目录项数
// 据块,再读入目录的下一个逻辑块。如果对应的逻辑块不存在就创建一块。若读取或创建操
// 作失败则返回空。如果此次读取的磁盘逻辑块数据返回的缓冲块指针为空,说明这块逻辑块
// 可能是因为不存在而新创建的空块,则把目录项索引值加上一块逻辑块所能容纳的目录项数
// DIR_ENTRIES_PER_BLOCK,用以跳过该块并继续搜索。否则说明新读入的块上有目录项数据,
// 于是让目录项结构指针de指向该块的缓冲块数据部分,然后在其中继续搜索。其中196行
// 上的 i/DIR_ENTRIES_PER_BLOCK 可计算得到当前搜索的目录项i 所在目录文件中的块号,
// 而create_block()函数(inode.c,第147行)则可读取或创建出在设备上对应的逻辑块。
193 if ((char *)de >= BLOCK_SIZE+bh->b_data) {
194 brelse(bh);
195 bh = NULL;
196 block = create_block(dir,i/DIR_ENTRIES_PER_BLOCK);
197 if (!block)
198 return NULL;
199 if (!(bh = bread(dir->i_dev,block))) { // 若空则跳过该块继续。
200 i += DIR_ENTRIES_PER_BLOCK;
201 continue;
202 }
203 de = (struct dir_entry *) bh->b_data;
204 }
// 如果当前所操作的目录项序号i乘上目录结构大小所的长度值已经超过了该目录i节点信息
// 所指出的目录数据长度值 i_size ,则说明整个目录文件数据中没有由于删除文件留下的空
// 目录项,因此我们只能把需要添加的新目录项附加到目录文件数据的末端处。于是对该处目
// 录项进行设置(置该目录项的i节点指针为空),并更新该目录文件的长度值(加上一个目
// 录项的长度),然后设置目录的i节点已修改标志,再更新该目录的改变时间为当前时间。
205 if (i*sizeof(struct dir_entry) >= dir->i_size) {
206 de->inode=0;
207 dir->i_size = (i+1)*sizeof(struct dir_entry);
208 dir->i_dirt = 1;
209 dir->i_ctime = CURRENT_TIME;
210 }
// 若当前搜索的目录项 de 的i节点为空,则表示找到一个还未使用的空闲目录项或是添加的
// 新目录项。于是更新目录的修改时间为当前时间,并从用户数据区复制文件名到该目录项的
// 文件名字段,置含有本目录项的相应高速缓冲块已修改标志。返回该目录项的指针以及该高
// 速缓冲块的指针,退出。
211 if (!de->inode) {
212 dir->i_mtime = CURRENT_TIME;
213 for (i=0; i < NAME_LEN ; i++)
214 de->name[i]=(i<namelen)?get_fs_byte(name+i):0;
215 bh->b_dirt = 1;
216 *res_dir = de;
217 return bh;
218 }
219 de++; // 如果该目录项已经被使用,则继续检测下一个目录项。
220 i++;
221 }
// 本函数执行不到这里。这也许是Linus在写这段代码时,先复制了上面find_entry()函数
// 的代码,而后修改成本函数的J。
222 brelse(bh);
223 return NULL;
224 }
225
//// 查找符号链接的i节点。
// 参数:dir - 目录i节点;inode - 目录项i节点。
// 返回:返回符号链接到文件的i节点指针。出错返回NULL。
226 static struct m_inode * follow_link(struct m_inode * dir, struct m_inode * inode)
227 {
228 unsigned short fs; // 用于临时保存fs段寄存器值。
229 struct buffer_head * bh;
230
// 首先判断函数参数的有效性。如果没有给出目录i节点,我们就使用进程任务结构中设置的
// 根i节点,并把链接数增1。如果没有给出目录项i节点,则放回目录i节点后返回NULL。
// 如果指定目录项不是一个符号链接,就直接返回目录项对应的i节点inode。
231 if (!dir) {
232 dir = current->root;
233 dir->i_count++;
234 }
235 if (!inode) {
236 iput(dir);
237 return NULL;
238 }
239 if (!S_ISLNK(inode->i_mode)) {
240 iput(dir);
241 return inode;
242 }
// 然后取fs段寄存器值。fs通常保存着指向任务数据段的选择符0x17。如果fs没有指向用户
// 数据段,或者给出的目录项i节点第1个直接块块号等于0,或者是读取第1个直接块出错,
// 则放回dir和inode两个i节点并返回NULL退出。否则说明现在fs正指向用户数据段、并且
// 我们已经成功地读取了这个符号链接目录项的文件内容,并且文件内容已经在 bh 指向的缓冲
// 块数据区中。实际上,这个缓冲块数据区中仅包含一个链接指向的文件路径名字符串。
243 __asm__("mov %%fs,%0":"=r" (fs));
244 if (fs != 0x17 || !inode->i_zone[0] ||
245 !(bh = bread(inode->i_dev, inode->i_zone[0]))) {
246 iput(dir);
247 iput(inode);
248 return NULL;
249 }
// 此时我们已经不需要符号链接目录项的i节点了,于是把它放回。现在碰到一个问题,那就
// 是内核函数处理的用户数据都是存放在用户数据空间中的,并使用了 fs 段寄存器来从用户
// 空间传递数据到内核空间中。而这里需要处理的数据却在内核空间中。因此为了正确地处理
// 位于内核中的用户数据,我们需要让fs段寄存器临时指向内核空间,即让fs =0x10。并在
// 调用函数处理完后再恢复原fs的值。最后释放相应缓冲块,并返回 _namei()解析得到的符
// 号链接指向的文件i节点。
250 iput(inode);
251 __asm__("mov %0,%%fs"::"r" ((unsigned short) 0x10));
252 inode = _namei(bh->b_data,dir,0);
253 __asm__("mov %0,%%fs"::"r" (fs));
254 brelse(bh);
255 return inode;
256 }
257
258 /*
259 * get_dir()
260 *
261 * Getdir traverses the pathname until it hits the topmost directory.
262 * It returns NULL on failure.
263 */
/*
* get_dir()
*
* 该函数根据给出的路径名进行搜索,直到达到最顶端的目录。
* 如果失败则返回NULL。
*/
//// 从指定目录开始搜寻指定路径名的目录(或文件名)的i节点。
// 参数:pathname - 路径名;inode - 指定起始目录的i节点。
// 返回:目录或文件的i节点指针。失败时返回NULL。
264 static struct m_inode * get_dir(const char * pathname, struct m_inode * inode)
265 {
266 char c;
267 const char * thisname;
268 struct buffer_head * bh;
269 int namelen,inr;
270 struct dir_entry * de;
271 struct m_inode * dir;
272
// 首先判断参数有效性。如果给出的指定目录的i节点指针inode为空,则使用当前进程的当
// 前工作目录i节点。如果用户指定路径名的第1个字符是'/',则说明路径名是绝对路径名。
// 则应该从当前进程任务结构中设置的根(或伪根)i节点开始操作。于是我们需要先放回参
// 数指定的或者设定的目录i节点,并取得进程使用的根i节点。然后把该i节点的引用计数
// 加1,并删除路径名的第1个字符 '/'。这样就可以保证当前进程只能以其设定的根i节点
// 作为搜索的起点。
273 if (!inode) {
274 inode = current->pwd; // 进程的当前工作目录i节点。
275 inode->i_count++;
276 }
277 if ((c=get_fs_byte(pathname))=='/') {
278 iput(inode); // 放回原i节点。
279 inode = current->root; // 为进程指定的根i节点。
280 pathname++;
281 inode->i_count++;
282 }
// 然后针对路径名中的各个目录名部分和文件名进行循环处理。在循环处理过程中,我们先要
// 对当前正在处理的目录名部分的 i节点进行有效性判断,并且把变量thisname 指向当前正
// 在处理的目录名部分。如果该i节点表明当前处理的目录名部分不是目录类型,或者没有可
// 进入该目录的访问许可,则放回该i节点,并返回NULL退出。 当然在刚进入循环时,当前
// 目录的i节点inode就是进程根i节点或者是当前工作目录的i节点,或者是参数指定的某
// 个搜索起始目录的i节点。
283 while (1) {
284 thisname = pathname;
285 if (!S_ISDIR(inode->i_mode) || !permission(inode,MAY_EXEC)) {
286 iput(inode);
287 return NULL;
288 }
// 每次循环我们处理路径名中一个目录名(或文件名)部分。因此在每次循环中我们都要从路
// 径名字符串中分离出一个目录名(或文件名)。方法是从当前路径名指针 pathname 开始处
// 搜索检测字符,直到字符是一个结尾符(NULL)或者是一个'/'字符。此时变量 namelen 正
// 好是当前处理目录名部分的长度,而变量 thisname 正指向该目录名部分的开始处。此时如
// 果字符是结尾符NULL,则表明已经搜索到路径名末尾,并已到达最后指定目录名或文件名,
// 则返回该i节点指针退出。
// 注意!如果路径名中最后一个名称也是一个目录名,但其后面没有加上 '/'字符,则函数不
// 会返回该最后目录名的i节点!例如:对于路径名/usr/src/linux,该函数将只返回src/目
// 录名的i节点。
289 for(namelen=0;(c=get_fs_byte(pathname++))&&(c!='/');namelen++)
290 /* nothing */ ;
291 if (!c)
292 return inode;
// 在得到当前目录名部分(或文件名)后,我们调用查找目录项函数find_entry()在当前处
// 理的目录中寻找指定名称的目录项。如果没有找到,则放回该i节点,并返回NULL退出。
// 然后在找到的目录项中取出其i节点号inr和设备号idev,释放包含该目录项的高速缓冲
// 块并放回该i节点。 然后取节点号inr的i节点inode,并以该目录项为当前目录继续循
// 环处理路径名中的下一目录名部分(或文件名)。如果当前处理的目录项是一个符号链接
// 名,则使用follow_link()就可以得到其指向的目录项名的i节点。
293 if (!(bh = find_entry(&inode,thisname,namelen,&de))) {
294 iput(inode);
295 return NULL;
296 }
297 inr = de->inode; // 当前目录名部分的i节点号。
298 brelse(bh);
299 dir = inode;
300 if (!(inode = iget(dir->i_dev,inr))) { // 取i节点内容。
301 iput(dir);
302 return NULL;
303 }
304 if (!(inode = follow_link(dir,inode)))
305 return NULL;
306 }
307 }
308
309 /*
310 * dir_namei()
311 *
312 * dir_namei() returns the inode of the directory of the
313 * specified name, and the name within that directory.
314 */
/*
* dir_namei()
*
* dir_namei()函数返回指定目录名的i节点指针,以及在最顶层
* 目录的名称。
*/
// 参数:pathname - 目录路径名;namelen - 路径名长度;name - 返回的最顶层目录名。
// base - 搜索起始目录的i节点。
// 返回:指定目录名最顶层目录的i节点指针和最顶层目录名称及长度。出错时返回NULL。
// 注意!!这里“最顶层目录”是指路径名中最靠近末端的目录。
315 static struct m_inode * dir_namei(const char * pathname,
316 int * namelen, const char ** name, struct m_inode * base)
317 {
318 char c;
319 const char * basename;
320 struct m_inode * dir;
321
// 首先取得指定路径名最顶层目录的i节点。然后对路径名pathname进行搜索检测,查出最后
// 一个'/'字符后面的名字字符串,计算其长度,并且返回最顶层目录的i节点指针。注意!如
// 果路径名最后一个字符是斜杠字符'/',那么返回的目录名为空,并且长度为0。但返回的i
// 节点指针仍然指向最后一个'/'字符前目录名的i节点。参见第289行上的“注意”说明。
322 if (!(dir = get_dir(pathname,base))) // base是指定的起始目录i节点。
323 return NULL;
324 basename = pathname;
325 while (c=get_fs_byte(pathname++))
326 if (c=='/')
327 basename=pathname;
328 *namelen = pathname-basename-1;
329 *name = basename;
330 return dir;
331 }
332
//// 取指定路径名的i节点内部函数。
// 参数:pathname - 路径名;base - 搜索起点目录i节点;follow_links - 是否跟随
// 符号链接的标志,1 - 需要,0不需要。
// 返回:对应的i节点。
333 struct m_inode * _namei(const char * pathname, struct m_inode * base,
334 int follow_links)
335 {
336 const char * basename;
337 int inr,namelen;
338 struct m_inode * inode;
339 struct buffer_head * bh;
340 struct dir_entry * de;
341
// 首先查找指定路径名中最顶层目录的目录名并得到其i节点。若不存在,则返回NULL退出。
// 如果返回的最顶层名字的长度是0,则表示该路径名以一个目录名为最后一项。因此说明我
// 们已经找到对应目录的i节点,可以直接返回该i节点退出。如果返回的名字长度不是0,
// 则我们以指定的起始目录base,再次调用dir_namei()函数来搜索顶层目录名,并根据返回
// 的信息作类似判断。
311 if (!(dir = dir_namei(pathname,&namelen,&basename)))
312 return NULL;
313 if (!namelen) /* special case: '/usr/' etc */
314 return dir; /* 对应于'/usr/'等情况 */
342 if (!(base = dir_namei(pathname,&namelen,&basename,base)))
343 return NULL;
344 if (!namelen) /* special case: '/usr/' etc */
345 return base;
// 然后在返回的顶层目录中寻找指定文件名目录项的i节点。注意!因为如果最后也是一个目
// 录名,但其后没有加'/',则不会返回该最后目录的i节点! 例如:/usr/src/linux,将只
// 返回 src/目录名的i节点。因为函数dir_namei() 将不以'/'结束的最后一个名字当作一个
// 文件名来看待,因此这里需要单独对这种情况使用寻找目录项i节点函数find_entry()进行
// 处理。此时de中含有寻找到的目录项指针,而dir是包含该目录项的目录的i节点指针。
346 bh = find_entry(&base,basename,namelen,&de);
347 if (!bh) {
348 iput(base);
349 return NULL;
350 }
// 接着取该目录项的i节点号,并释放包含该目录项的高速缓冲块并放回目录i节点。然后取
// 对应节点号的i节点,修改其被访问时间为当前时间,并置已修改标志。最后返回该i节点
// 指针inode。如果当前处理的目录项是一个符号链接名,则使用follow_link()得到其指向的
// 目录项名的i节点。
351 inr = de->inode;
352 brelse(bh);
353 if (!(inode = iget(base->i_dev,inr))) {
354 iput(base);
355 return NULL;
356 }
357 if (follow_links)
358 inode = follow_link(base,inode);
359 else
360 iput(base);
361 inode->i_atime=CURRENT_TIME;
362 inode->i_dirt=1;
363 return inode;
364 }
365
//// 取指定路径名的i节点,不跟随符号链接。
// 参数:pathname - 路径名。
// 返回:对应的i节点。
366 struct m_inode * lnamei(const char * pathname)
367 {
368 return _namei(pathname, NULL, 0);
369 }
370
371 /*
372 * namei()
373 *
374 * is used by most simple commands to get the inode of a specified name.
375 * Open, link etc use their own routines, but this is enough for things
376 * like 'chmod' etc.
377 */
/*
* namei()
*
* 该函数被许多简单命令用于取得指定路径名称的i节点。open、link等则使用它们
* 自己的相应函数。但对于象修改模式'chmod'等这样的命令,该函数已足够用了。
*/
//// 取指定路径名的i节点,跟随符号链接。
// 参数:pathname - 路径名。
// 返回:对应的i节点。
378 struct m_inode * namei(const char * pathname)
379 {
380 return _namei(pathname,NULL,1);
381 }
382
383 /*
384 * open_namei()
385 *
386 * namei for open - this is in fact almost the whole open-routine.
387 */
/*
* open_namei()
*
* open()函数使用的namei函数 - 这其实几乎是完整的打开文件程序。
*/
//// 文件打开namei函数。
// 参数filename是文件路径名,flag是打开文件标志,可取值O_RDONLY(只读)、O_WRONLY
// (只写)或O_RDWR(读写),以及O_CREAT(创建)、O_EXCL(被创建文件必须不存在)、
// O_APPEND(在文件尾添加数据)等其他一些标志的组合。如果本调用创建了一个新文件,则
// mode就用于指定文件的许可属性。这些属性有S_IRWXU(文件宿主具有读、写和执行权限)、
// S_IRUSR(用户具有读文件权限)、S_IRWXG(组成员具有读、写和执行权限)等等。对于新
// 创建的文件,这些属性只应用于将来对文件的访问,创建了只读文件的打开调用也将返回一
// 个可读写的文件句柄。参见包含文件sys/stat.h、fcntl.h。
// 返回:成功返回0,否则返回出错码;res_inode - 返回对应文件路径名的i节点指针。
388 int open_namei(const char * pathname, int flag, int mode,
389 struct m_inode ** res_inode)
390 {
391 const char * basename;
392 int inr,dev,namelen;
393 struct m_inode * dir, *inode;
394 struct buffer_head * bh;
395 struct dir_entry * de;
396
// 首先对函数参数进行合理的处理。如果文件访问模式标志是只读(0),但是文件截零标志
// O_TRUNC却置位了,则在文件打开标志中添加只写标志O_WRONLY。这样做的原因是由于截零
// 标志O_TRUNC必须在文件可写情况下才有效。然后使用当前进程的文件访问许可屏蔽码,屏
// 蔽掉给定模式中的相应位,并添上普通文件标志I_REGULAR。该标志将用于打开的文件不存
// 在而需要创建文件时,作为新文件的默认属性。参见下面411行上的注释。
397 if ((flag & O_TRUNC) && !(flag & O_ACCMODE))
398 flag |= O_WRONLY;
399 mode &= 0777 & ~current->umask;
400 mode |= I_REGULAR; // 常规文件标志。见参见include/const.h文件)。
// 然后根据指定的路径名寻找到对应的i节点,以及最顶端目录名及其长度。此时如果最顶端
// 目录名长度为0( 例如'/usr/' 这种路径名的情况),那么若操作不是读写、创建和文件长
// 度截0,则表示是在打开一个目录名文件操作。于是直接返回该目录的i节点并返回0退出。
// 否则说明进程操作非法,于是放回该i节点,返回出错码。
401 if (!(dir = dir_namei(pathname,&namelen,&basename,NULL)))
402 return -ENOENT;
403 if (!namelen) { /* special case: '/usr/' etc */
404 if (!(flag & (O_ACCMODE|O_CREAT|O_TRUNC))) {
405 *res_inode=dir;
406 return 0;
407 }
408 iput(dir);
409 return -EISDIR;
410 }
// 接着根据上面得到的最顶层目录名的i节点dir,在其中查找取得路径名字符串中最后的文
// 件名对应的目录项结构de,并同时得到该目录项所在的高速缓冲区指针。 如果该高速缓冲
// 指针为NULL,则表示没有找到对应文件名的目录项,因此只可能是创建文件操作。 此时如
// 果不是创建文件,则放回该目录的i节点,返回出错号退出。如果用户在该目录没有写的权
// 力,则放回该目录的i节点,返回出错号退出。
411 bh = find_entry(&dir,basename,namelen,&de);
412 if (!bh) {
413 if (!(flag & O_CREAT)) {
414 iput(dir);
415 return -ENOENT;
416 }
417 if (!permission(dir,MAY_WRITE)) {
418 iput(dir);
419 return -EACCES;
420 }
// 现在我们确定了是创建操作并且有写操作许可。 因此我们就在目录i节点对应设备上申请
// 一个新的i节点给路径名上指定的文件使用。 若失败则放回目录的i节点,并返回没有空
// 间出错码。否则使用该新 i节点,对其进行初始设置:置节点的用户id;对应节点访问模
// 式;置已修改标志。然后并在指定目录dir中添加一个新目录项。
421 inode = new_inode(dir->i_dev);
422 if (!inode) {
423 iput(dir);
424 return -ENOSPC;
425 }
426 inode->i_uid = current->euid;
427 inode->i_mode = mode;
428 inode->i_dirt = 1;
429 bh = add_entry(dir,basename,namelen,&de);
// 如果返回的应该含有新目录项的高速缓冲区指针为NULL,则表示添加目录项操作失败。于是
// 将该新i节点的引用连接计数减1,放回该i节点与目录的i节点并返回出错码退出。 否则
// 说明添加目录项操作成功。 于是我们来设置该新目录项的一些初始值:置i节点号为新申请
// 到的i节点的号码;并置高速缓冲区已修改标志。 然后释放该高速缓冲区,放回目录的i节
// 点。返回新目录项的i节点指针,并成功退出。
430 if (!bh) {
431 inode->i_nlinks--;
432 iput(inode);
433 iput(dir);
434 return -ENOSPC;
435 }
436 de->inode = inode->i_num;
437 bh->b_dirt = 1;
438 brelse(bh);
439 iput(dir);
440 *res_inode = inode;
441 return 0;
442 }
// 若上面(411行)在目录中取文件名对应目录项结构的操作成功(即bh不为NULL),则说
// 明指定打开的文件已经存在。于是取出该目录项的i节点号和其所在设备号,并释放该高速
// 缓冲区以及放回目录的i节点。如果此时独占操作标志O_EXCL置位,但现在文件已经存在,
// 则返回文件已存在出错码退出。
443 inr = de->inode;
444 dev = dir->i_dev;
445 brelse(bh);
446 if (flag & O_EXCL) {
447 iput(dir);
448 return -EEXIST;
449 }
// 然后我们读取该目录项的i节点内容。若该i节点是一个目录的i节点并且访问模式是只
// 写或读写,或者没有访问的许可权限,则放回该i节点,返回访问权限出错码退出。
450 if (!(inode = follow_link(dir,iget(dev,inr))))
451 return -EACCES;
452 if ((S_ISDIR(inode->i_mode) && (flag & O_ACCMODE)) ||
453 !permission(inode,ACC_MODE(flag))) {
454 iput(inode);
455 return -EPERM;
456 }
// 接着我们更新该i节点的访问时间字段值为当前时间。如果设立了截0标志,则将该i节
// 点的文件长度截为0。最后返回该目录项i节点的指针,并返回0(成功)。
457 inode->i_atime = CURRENT_TIME;
458 if (flag & O_TRUNC)
459 truncate(inode);
460 *res_inode = inode;
461 return 0;
462 }
463
//// 创建一个设备特殊文件或普通文件节点(node)。
// 该函数创建名称为filename,由mode和dev指定的文件系统节点(普通文件、设备特殊文
// 件或命名管道)。
// 参数:filename - 路径名;mode - 指定使用许可以及所创建节点的类型;dev - 设备号。
// 返回:成功则返回0,否则返回出错码。
464 int sys_mknod(const char * filename, int mode, int dev)
465 {
466 const char * basename;
467 int namelen;
468 struct m_inode * dir, * inode;
469 struct buffer_head * bh;
470 struct dir_entry * de;
471
// 首先检查操作许可和参数的有效性并取路径名中顶层目录的i节点。如果不是超级用户,则
// 返回访问许可出错码。如果找不到对应路径名中顶层目录的i节点,则返回出错码。如果最
// 顶端的文件名长度为0,则说明给出的路径名最后没有指定文件名,放回该目录i节点,返
// 回出错码退出。如果在该目录中没有写的权限,则放回该目录的i节点,返回访问许可出错
// 码退出。如果不是超级用户,则返回访问许可出错码。
472 if (!suser())
473 return -EPERM;
474 if (!(dir = dir_namei(filename,&namelen,&basename, NULL)))
475 return -ENOENT;
476 if (!namelen) {
477 iput(dir);
478 return -ENOENT;
479 }
480 if (!permission(dir,MAY_WRITE)) {
481 iput(dir);
482 return -EPERM;
483 }
// 然后我们搜索一下路径名指定的文件是否已经存在。若已经存在则不能创建同名文件节点。
// 如果对应路径名上最后的文件名的目录项已经存在,则释放包含该目录项的缓冲区块并放回
// 目录的i节点,返回文件已经存在的出错码退出。
484 bh = find_entry(&dir,basename,namelen,&de);
485 if (bh) {
486 brelse(bh);
487 iput(dir);
488 return -EEXIST;
489 }
// 否则我们就申请一个新的i节点,并设置该i节点的属性模式。如果要创建的是块设备文件
// 或者是字符设备文件,则令i节点的直接逻辑块指针0等于设备号。即对于设备文件来说,
// 其i节点的i_zone[0]中存放的是该设备文件所定义设备的设备号。然后设置该i节点的修
// 改时间、访问时间为当前时间,并设置i节点已修改标志。
490 inode = new_inode(dir->i_dev);
491 if (!inode) { // 若不成功则放回目录i节点,返回无空间出错码退出。
492 iput(dir);
493 return -ENOSPC;
494 }
495 inode->i_mode = mode;
496 if (S_ISBLK(mode) || S_ISCHR(mode))
497 inode->i_zone[0] = dev;
498 inode->i_mtime = inode->i_atime = CURRENT_TIME;
499 inode->i_dirt = 1;
// 接着为这个新的i节点在目录中新添加一个目录项。如果失败(包含该目录项的高速缓冲
// 块指针为NULL),则放回目录的i节点;把所申请的i节点引用连接计数复位,并放回该
// i节点,返回出错码退出。
500 bh = add_entry(dir,basename,namelen,&de);
501 if (!bh) {
502 iput(dir);
503 inode->i_nlinks=0;
504 iput(inode);
505 return -ENOSPC;
506 }
// 现在添加目录项操作也成功了,于是我们来设置这个目录项内容。令该目录项的i节点字
// 段等于新i节点号,并置高速缓冲区已修改标志,放回目录和新的i节点,释放高速缓冲
// 区,最后返回0(成功)。
507 de->inode = inode->i_num;
508 bh->b_dirt = 1;
509 iput(dir);
510 iput(inode);
511 brelse(bh);
512 return 0;
513 }
514
//// 创建一个目录。
// 参数:pathname - 路径名;mode - 目录使用的权限属性。
// 返回:成功则返回0,否则返回出错码。
515 int sys_mkdir(const char * pathname, int mode)
516 {
517 const char * basename;
518 int namelen;
519 struct m_inode * dir, * inode;
520 struct buffer_head * bh, *dir_block;
521 struct dir_entry * de;
522
// 首先检查参数的有效性并取路径名中顶层目录的i节点。如果找不到对应路径名中顶层目录
// 的i节点,则返回出错码。如果最顶端的文件名长度为0,则说明给出的路径名最后没有指
// 定文件名,放回该目录i节点,返回出错码退出。如果在该目录中没有写的权限,则放回该
// 目录的i节点,返回访问许可出错码退出。如果不是超级用户,则返回访问许可出错码。
523 if (!(dir = dir_namei(pathname,&namelen,&basename, NULL)))
524 return -ENOENT;
525 if (!namelen) {
526 iput(dir);
527 return -ENOENT;
528 }
529 if (!permission(dir,MAY_WRITE)) {
530 iput(dir);
531 return -EPERM;
532 }
// 然后我们搜索一下路径名指定的目录名是否已经存在。若已经存在则不能创建同名目录节点。
// 如果对应路径名上最后的目录名的目录项已经存在,则释放包含该目录项的缓冲区块并放回
// 目录的i节点,返回文件已经存在的出错码退出。否则我们就申请一个新的i节点,并设置
// 该i节点的属性模式:置该新i节点对应的文件长度为32字节 (2个目录项的大小)、置
// 节点已修改标志,以及节点的修改时间和访问时间。2个目录项分别用于'.'和'..'目录。
533 bh = find_entry(&dir,basename,namelen,&de);
534 if (bh) {
535 brelse(bh);
536 iput(dir);
537 return -EEXIST;
538 }
539 inode = new_inode(dir->i_dev);
540 if (!inode) { // 若不成功则放回目录的i节点,返回无空间出错码。
541 iput(dir);
542 return -ENOSPC;
543 }
544 inode->i_size = 32;
545 inode->i_dirt = 1;
546 inode->i_mtime = inode->i_atime = CURRENT_TIME;
// 接着为该新i节点申请一用于保存目录项数据的磁盘块,并令i节点的第一个直接块指针等
// 于该块号。如果申请失败则放回对应目录的i节点;复位新申请的i节点连接计数;放回该
// 新的i节点,返回没有空间出错码退出。否则置该新的i节点已修改标志。
547 if (!(inode->i_zone[0]=new_block(inode->i_dev))) {
548 iput(dir);
549 inode->i_nlinks--;
550 iput(inode);
551 return -ENOSPC;
552 }
553 inode->i_dirt = 1;
// 从设备上读取新申请的磁盘块(目的是把对应块放到高速缓冲区中)。若出错,则放回对应
// 目录的i节点;释放申请的磁盘块;复位新申请的i节点连接计数;放回该新的i节点,返
// 回没有空间出错码退出。
554 if (!(dir_block=bread(inode->i_dev,inode->i_zone[0]))) {
555 iput(dir);
556 inode->i_nlinks--;
557 iput(inode);
558 return -ERROR;
559 }
// 然后我们在缓冲块中建立起所创建目录文件中的2个默认的新目录项('.'和'..')结构数
// 据。首先令de指向存放目录项的数据块,然后置该目录项的i节点号字段等于新申请的i
// 节点号,名字字段等于"."。 然后de指向下一个目录项结构,并在该结构中存放上级目录
// 的 i节点号和名字".."。然后设置该高速缓冲块已修改标志,并释放该缓冲块。再初始化
// 设置新i节点的模式字段,并置该i节点已修改标志。
560 de = (struct dir_entry *) dir_block->b_data;
561 de->inode=inode->i_num; // 设置'.'目录项。
562 strcpy(de->name,".");
563 de++;
564 de->inode = dir->i_num; // 设置'..'目录项。
565 strcpy(de->name,"..");
566 inode->i_nlinks = 2;
567 dir_block->b_dirt = 1;
568 brelse(dir_block);
569 inode->i_mode = I_DIRECTORY | (mode & 0777 & ~current->umask);
570 inode->i_dirt = 1;
// 现在我们在指定目录中新添加一个目录项,用于存放新建目录的i节点和目录名。如果失
// 败(包含该目录项的高速缓冲区指针为NULL),则放回目录的i节点;所申请的i节点引
// 用连接计数复位,并放回该i节点。返回出错码退出。
571 bh = add_entry(dir,basename,namelen,&de);
572 if (!bh) {
573 iput(dir);
574 inode->i_nlinks=0;
575 iput(inode);
576 return -ENOSPC;
577 }
// 最后令该新目录项的i节点字段等于新i节点号,并置高速缓冲块已修改标志,放回目录
// 和新的i节点,释放高速缓冲区,最后返回0(成功)。
578 de->inode = inode->i_num;
579 bh->b_dirt = 1;
580 dir->i_nlinks++;
581 dir->i_dirt = 1;
582 iput(dir);
583 iput(inode);
584 brelse(bh);
585 return 0;
586 }
587
588 /*
589 * routine to check that the specified directory is empty (for rmdir)
590 */
/*
* 用于检查指定的目录是否为空的子程序(用于rmdir系统调用)。
*/
//// 检查指定目录是否空。
// 参数:inode - 指定目录的i节点指针。
// 返回:1 – 目录中是空的;0 - 不空。
591 static int empty_dir(struct m_inode * inode)
592 {
593 int nr,block;
594 int len;
595 struct buffer_head * bh;
596 struct dir_entry * de;
597
// 首先计算指定目录中现有目录项个数并检查开始两个特定目录项中信息是否正确。一个目录
// 中应该起码有2个目录项:即"."和".."。 如果目录项个数少于2个或者该目录i节点的第
// 1个直接块没有指向任何磁盘块号,或者该直接块读不出,则显示警告信息“设备dev 上目
// 录错”,返回0(失败)。
598 len = inode->i_size / sizeof (struct dir_entry); // 目录中目录项个数。
599 if (len<2 || !inode->i_zone[0] ||
600 !(bh=bread(inode->i_dev,inode->i_zone[0]))) {
601 printk("warning - bad directory on dev %04x\n",inode->i_dev);
602 return 0;
603 }
// 此时bh所指缓冲块中含有目录项数据。我们让目录项指针de指向缓冲块中第1个目录项。
// 对于第1个目录项("."),它的i节点号字段inode应该等于当前目录的i节点号。对于
// 第2个目录项(".."),它的i节点号字段 inode 应该等于上一层目录的i节点号,不会
// 为0。因此如果第1个目录项的i节点号字段值不等于该目录的i节点号,或者第2个目录
// 项的i节点号字段为零,或者两个目录项的名字字段不分别等于"."和"..",则显示出错警
// 告信息“设备dev上目录错”,并返回0。
604 de = (struct dir_entry *) bh->b_data;
605 if (de[0].inode != inode->i_num || !de[1].inode ||
606 strcmp(".",de[0].name) || strcmp("..",de[1].name)) {
607 printk("warning - bad directory on dev %04x\n",inode->i_dev);
608 return 0;
609 }
// 然后我们令nr等于目录项序号(从0开始计);de指向第三个目录项。并循环检测该目录
// 中其余所有的(len - 2)个目录项,看有没有目录项的i节点号字段不为0(被使用)。
610 nr = 2;
611 de += 2;
612 while (nr<len) {
// 如果该块磁盘块中的目录项已经全部检测完毕,则释放该磁盘块的缓冲块,并读取目录数据
// 文件中下一块含有目录项的磁盘块。读取的方法是根据当前检测的目录项序号 nr 计算出对
// 应目录项在目录数据文件中的数据块号(nr/DIR_ENTRIES_PER_BLOCK),然后使用 bmap()
// 函数取得对应的盘块号 block,再使用读设备盘块函数bread() 把相应盘块读入缓冲块中,
// 并返回该缓冲块的指针。若所读取的相应盘块没有使用(或已经不用,如文件已经删除等),
// 则继续读下一块,若读不出,则出错返回0。否则让de指向读出块的首个目录项。
613 if ((void *) de >= (void *) (bh->b_data+BLOCK_SIZE)) {
614 brelse(bh);
615 block=bmap(inode,nr/DIR_ENTRIES_PER_BLOCK);
616 if (!block) {
617 nr += DIR_ENTRIES_PER_BLOCK;
618 continue;
619 }
620 if (!(bh=bread(inode->i_dev,block)))
621 return 0;
622 de = (struct dir_entry *) bh->b_data;
623 }
// 对于de指向的当前目录项,如果该目录项的i节点号字段不等于0,则表示该目录项目前正
// 被使用,则释放该高速缓冲区,返回0退出。否则,若还没有查询完该目录中的所有目录项,
// 则把目录项序号nr增1、de指向下一个目录项,继续检测。
624 if (de->inode) {
625 brelse(bh);
626 return 0;
627 }
628 de++;
629 nr++;
630 }
// 执行到这里说明该目录中没有找到已用的目录项(当然除了头两个以外),则释放缓冲块返回1。
631 brelse(bh);
632 return 1;
633 }
//// 删除目录。
// 参数: name - 目录名(路径名)。
// 返回:返回0表示成功,否则返回出错号。
635 int sys_rmdir(const char * name)
636 {
637 const char * basename;
638 int namelen;
639 struct m_inode * dir, * inode;
640 struct buffer_head * bh;
641 struct dir_entry * de;
// 首先检查参数的有效性并取路径名中顶层目录的i节点。如果找不到对应路径名中顶层目录
// 的i节点,则返回出错码。如果最顶端的文件名长度为0,则说明给出的路径名最后没有指
// 定文件名,放回该目录i节点,返回出错码退出。如果在该目录中没有写的权限,则放回该
// 目录的i节点,返回访问许可出错码退出。如果不是超级用户,则返回访问许可出错码。
643 if (!(dir = dir_namei(name,&namelen,&basename, NULL)))
644 return -ENOENT;
645 if (!namelen) {
646 iput(dir);
647 return -ENOENT;
648 }
649 if (!permission(dir,MAY_WRITE)) {
650 iput(dir);
651 return -EPERM;
652 }
// 然后根据指定目录的i节点和目录名利用函数find_entry()寻找对应目录项,并返回包含该
// 目录项的缓冲块指针bh、包含该目录项的目录的i节点指针dir和该目录项指针de。再根据
// 该目录项de 中的i节点号利用 iget()函数得到对应的i节点 inode。如果对应路径名上最
// 后目录名的目录项不存在,则释放包含该目录项的高速缓冲区,放回目录的 i节点,返回文
// 件已经存在出错码,并退出。如果取目录项的i节点出错,则放回目录的 i节点,并释放含
// 有目录项的高速缓冲区,返回出错号。
653 bh = find_entry(&dir,basename,namelen,&de);
654 if (!bh) {
655 iput(dir);
656 return -ENOENT;
657 }
658 if (!(inode = iget(dir->i_dev, de->inode))) {
659 iput(dir);
660 brelse(bh);
661 return -EPERM;
662 }
// 此时我们已有包含要被删除目录项的目录i节点dir、要被删除目录项的i节点inode和要
// 被删除目录项指针de。下面我们通过对这3个对象中信息的检查来验证删除操作的可行性。
// 若该目录设置了受限删除标志并且进程的有效用户id(euid)不是root,并且进程的有效
// 用户id(euid)不等于该i节点的用户id,则表示当前进程没有权限删除该目录,于是放
// 回包含要删除目录名的目录 i节点和该要删除目录的 i节点,然后释放高速缓冲区,返回
// 出错码。
663 if ((dir->i_mode & S_ISVTX) && current->euid &&
664 inode->i_uid != current->euid) {
665 iput(dir);
666 iput(inode);
667 brelse(bh);
668 return -EPERM;
669 }
// 如果要被删除的目录项i节点的设备号不等于包含该目录项的目录的设备号,或者该被删除
// 目录的引用连接计数大于 1(表示有符号连接等),则不能删除该目录。于是释放包含要删
// 除目录名的目录i节点和该要删除目录的i节点,释放高速缓冲块,返回出错码。
670 if (inode->i_dev != dir->i_dev || inode->i_count>1) {
671 iput(dir);
672 iput(inode);
673 brelse(bh);
674 return -EPERM;
675 }
// 如果要被删除目录的目录项i节点就等于包含该需删除目录的目录i节点,则表示试图删除
// "."目录,这是不允许的。于是放回包含要删除目录名的目录i节点和要删除目录的i节点,
// 释放高速缓冲块,返回出错码。
676 if (inode == dir) { /* we may not delete ".", but "../dir" is ok */
677 iput(inode);
678 iput(dir);
679 brelse(bh);
680 return -EPERM;
681 }
// 若要被删除目录i节点的属性表明这不是一个目录,则本删除操作的前提完全不存在。于是
// 放回包含删除目录名的目录i节点和该要删除目录的i节点,释放高速缓冲块,返回出错码。
682 if (!S_ISDIR(inode->i_mode)) {
683 iput(inode);
684 iput(dir);
685 brelse(bh);
686 return -ENOTDIR;
687 }
// 若该需被删除的目录不空,则也不能删除。于是放回包含要删除目录名的目录i节点和该要
// 删除目录的i节点,释放高速缓冲块,返回出错码。
688 if (!empty_dir(inode)) {
689 iput(inode);
690 iput(dir);
691 brelse(bh);
692 return -ENOTEMPTY;
693 }
// 对于一个空目录,其目录项链接数应该为2(链接到上层目录和本目录)。若该需被删除目
// 录的i节点的连接数不等于2,则显示警告信息。但删除操作仍然继续执行。于是置该需被
// 删除目录的目录项的i节点号字段为0,表示该目录项不再使用,并置含有该目录项的高速
// 缓冲块已修改标志,并释放该缓冲块。然后再置被删除目录i节点的链接数为0(表示空闲),
// 并置i节点已修改标志。
694 if (inode->i_nlinks != 2)
695 printk("empty directory has nlink!=2 (%d)",inode->i_nlinks);
696 de->inode = 0;
697 bh->b_dirt = 1;
698 brelse(bh);
699 inode->i_nlinks=0;
700 inode->i_dirt=1;
// 再将包含被删除目录名的目录的i节点链接计数减1,修改其改变时间和修改时间为当前时
// 间,并置该节点已修改标志。最后放回包含要删除目录名的目录i节点和该要删除目录的i
// 节点,返回0(删除操作成功)。
701 dir->i_nlinks--;
702 dir->i_ctime = dir->i_mtime = CURRENT_TIME;
703 dir->i_dirt=1;
704 iput(dir);
705 iput(inode);
706 return 0;
707 }
//// 删除(释放)文件名对应的目录项。
// 从文件系统删除一个名字。如果是文件的最后一个链接,并且没有进程正打开该文件,则该
// 文件也将被删除,并释放所占用的设备空间。
// 参数:name - 文件名(路径名)。
// 返回:成功则返回0,否则返回出错号。
709 int sys_unlink(const char * name)
710 {
711 const char * basename;
712 int namelen;
713 struct m_inode * dir, * inode;
714 struct buffer_head * bh;
715 struct dir_entry * de;
716
// 首先检查参数的有效性并取路径名中顶层目录的i节点。如果找不到对应路径名中顶层目录
// 的 i节点,则返回出错码。如果最顶端的文件名长度为0,则说明给出的路径名最后没有指
// 定文件名,放回该目录i节点,返回出错码退出。如果在该目录中没有写的权限,则放回该
// 目录的i节点,返回访问许可出错码退出。如果找不到对应路径名顶层目录的i节点,则返
// 回出错码。
717 if (!(dir = dir_namei(name,&namelen,&basename, NULL)))
718 return -ENOENT;
719 if (!namelen) {
720 iput(dir);
721 return -ENOENT;
722 }
723 if (!permission(dir,MAY_WRITE)) {
724 iput(dir);
725 return -EPERM;
726 }
// 然后根据指定目录的i节点和目录名利用函数find_entry()寻找对应目录项,并返回包含该
// 目录项的缓冲块指针bh、包含该目录项的目录的i节点指针dir和该目录项指针de。再根据
// 该目录项de 中的i节点号利用 iget()函数得到对应的i节点 inode。如果对应路径名上最
// 后目录名的目录项不存在,则释放包含该目录项的高速缓冲区,放回目录的 i节点,返回文
// 件已经存在出错码,并退出。如果取目录项的i节点出错,则放回目录的 i节点,并释放含
// 有目录项的高速缓冲区,返回出错号。
727 bh = find_entry(&dir,basename,namelen,&de);
728 if (!bh) {
729 iput(dir);
730 return -ENOENT;
731 }
732 if (!(inode = iget(dir->i_dev, de->inode))) {
733 iput(dir);
734 brelse(bh);
735 return -ENOENT;
736 }
// 此时我们已有包含要被删除目录项的目录i节点dir、要被删除目录项的i节点inode和要
// 被删除目录项指针de。下面我们通过对这3个对象中信息的检查来验证删除操作的可行性。
// 若该目录设置了受限删除标志并且进程的有效用户id(euid)不是root,并且进程的euid
// 不等于该i节点的用户id,并且进程的euid也不等于目录i节点的用户id,则表示当前进
// 程没有权限删除该目录,于是放回包含要删除目录名的目录i节点和该要删除目录的i节点,
// 然后释放高速缓冲块,返回出错码。
737 if ((dir->i_mode & S_ISVTX) && !suser() &&
738 current->euid != inode->i_uid &&
739 current->euid != dir->i_uid) {
740 iput(dir);
741 iput(inode);
742 brelse(bh);
743 return -EPERM;
744 }
// 如果该指定文件名是一个目录,则也不能删除。放回该目录i节点和该文件名目录项的i节
// 点,释放包含该目录项的缓冲块,返回出错号。
745 if (S_ISDIR(inode->i_mode)) {
746 iput(inode);
747 iput(dir);
748 brelse(bh);
749 return -EPERM;
750 }
// 如果该i节点的链接计数值已经为0,则显示警告信息,并修正其为1。
751 if (!inode->i_nlinks) {
752 printk("Deleting nonexistent file (%04x:%d), %d\n",
753 inode->i_dev,inode->i_num,inode->i_nlinks);
754 inode->i_nlinks=1;
755 }
// 现在我们可以删除文件名对应的目录项了。于是将该文件名目录项中的i节点号字段置为0,
// 表示释放该目录项,并设置包含该目录项的缓冲块已修改标志,释放该高速缓冲块。
756 de->inode = 0;
757 bh->b_dirt = 1;
758 brelse(bh);
// 然后把文件名对应i节点的链接数减1,置已修改标志,更新改变时间为当前时间。最后放
// 回该i节点和目录的i节点,返回0(成功)。如果是文件的最后一个链接,即i节点链接
// 数减1后等于0,并且此时没有进程正打开该文件,那么在调用iput()放回i节点时,该文
// 件也将被删除,并释放所占用的设备空间。参见fs/inode.c,第183行。
759 inode->i_nlinks--;
760 inode->i_dirt = 1;
761 inode->i_ctime = CURRENT_TIME;
762 iput(inode);
763 iput(dir);
764 return 0;
765 }
766
//// 建立符号链接。
// 为一个已存在文件创建一个符号链接(也称为软连接 - hard link)。
// 参数:oldname - 原路径名;newname - 新的路径名。
// 返回:若成功则返回0,否则返回出错号。
767 int sys_symlink(const char * oldname, const char * newname)
768 {
769 struct dir_entry * de;
770 struct m_inode * dir, * inode;
771 struct buffer_head * bh, * name_block;
772 const char * basename;
773 int namelen, i;
774 char c;
775
// 首先查找新路径名的最顶层目录的i节点dir,并返回最后的文件名及其长度。如果目录的
// i节点没有找到,则返回出错号。如果新路径名中不包括文件名,则放回新路径名目录的i
// 节点,返回出错号。另外,如果用户没有在新目录中写的权限,则也不能建立连接,于是放
// 回新路径名目录的i节点,返回出错号。
776 dir = dir_namei(newname,&namelen,&basename, NULL);
777 if (!dir)
778 return -EACCES;
779 if (!namelen) {
780 iput(dir);
781 return -EPERM;
782 }
783 if (!permission(dir,MAY_WRITE)) {
784 iput(dir);
785 return -EACCES;
786 }
// 现在我们在目录指定设备上申请一个新的i节点,并设置该i节点模式为符号链接类型以及
// 进程规定的模式屏蔽码。并且设置该i节点已修改标志。
787 if (!(inode = new_inode(dir->i_dev))) {
788 iput(dir);
789 return -ENOSPC;
790 }
791 inode->i_mode = S_IFLNK | (0777 & ~current->umask);
792 inode->i_dirt = 1;
// 为了保存符号链接路径名字符串信息,我们需要为该i节点申请一个磁盘块,并让i节点的
// 第1个直接块号i_zone[0]等于得到的逻辑块号。然后置i节点已修改标志。如果申请失败
// 则放回对应目录的i节点;复位新申请的i节点链接计数;放回该新的i节点,返回没有空
// 间出错码退出。
793 if (!(inode->i_zone[0]=new_block(inode->i_dev))) {
794 iput(dir);
795 inode->i_nlinks--;
796 iput(inode);
797 return -ENOSPC;
798 }
799 inode->i_dirt = 1;
// 然后从设备上读取新申请的磁盘块(目的是把对应块放到高速缓冲区中)。若出错,则放回
// 对应目录的i节点;复位新申请的i节点链接计数;放回该新的i节点,返回没有空间出错
// 码退出。
800 if (!(name_block=bread(inode->i_dev,inode->i_zone[0]))) {
801 iput(dir);
802 inode->i_nlinks--;
803 iput(inode);
804 return -ERROR;
805 }
// 现在我们可以把符号链接名字符串放入这个盘块中了。盘块长度为1024字节,因此默认符号
// 链接名长度最大也只能是1024字节。我们把用户空间中的符号链接名字符串复制到盘块所在
// 的缓冲块中,并置缓冲块已修改标志。为防止用户提供的字符串没有以null结尾,我们在缓
// 冲块数据区最后一个字节处放上一个NULL。然后释放该缓冲块,并设置i节点对应文件中数
// 据长度等于符号链接名字符串长度,并置i节点已修改标志。
806 i = 0;
807 while (i < 1023 && (c=get_fs_byte(oldname++)))
808 name_block->b_data[i++] = c;
809 name_block->b_data[i] = 0;
810 name_block->b_dirt = 1;
811 brelse(name_block);
812 inode->i_size = i;
813 inode->i_dirt = 1;
// 然后我们搜索一下路径名指定的符号链接文件名是否已经存在。若已经存在则不能创建同名
// 目录项i节点。如果对应符号链接文件名已经存在,则释放包含该目录项的缓冲区块,复位
// 新申请的i节点连接计数,并放回目录的i节点,返回文件已经存在的出错码退出。
814 bh = find_entry(&dir,basename,namelen,&de);
815 if (bh) {
816 inode->i_nlinks--;
817 iput(inode);
818 brelse(bh);
819 iput(dir);
820 return -EEXIST;
821 }
// 现在我们在指定目录中新添加一个目录项,用于存放新建符号链接文件名的i节点号和目录
// 名。如果失败(包含该目录项的高速缓冲区指针为NULL),则放回目录的i节点;所申请的
// i节点引用连接计数复位,并放回该i节点。返回出错码退出。
822 bh = add_entry(dir,basename,namelen,&de);
823 if (!bh) {
824 inode->i_nlinks--;
825 iput(inode);
826 iput(dir);
827 return -ENOSPC;
828 }
// 最后令该新目录项的i节点字段等于新i节点号,并置高速缓冲块已修改标志,释放高速缓
// 冲块,放回目录和新的i节点,最后返回0(成功)。
829 de->inode = inode->i_num;
830 bh->b_dirt = 1;
831 brelse(bh);
832 iput(dir);
833 iput(inode);
834 return 0;
835 }
836
//// 为文件建立一个文件名目录项。
// 为一个已存在的文件创建一个新链接(也称为硬连接 - hard link)。
// 参数:oldname - 原路径名;newname - 新的路径名。
// 返回:若成功则返回0,否则返回出错号。
837 int sys_link(const char * oldname, const char * newname)
838 {
839 struct dir_entry * de;
840 struct m_inode * oldinode, * dir;
841 struct buffer_head * bh;
842 const char * basename;
843 int namelen;
844
// 首先对原文件名进行有效性验证,它应该存在并且不是一个目录名。所以我们先取原文件路
// 径名对应的i节点oldinode。如果为0,则表示出错,返回出错号。如果原路径名对应的是
// 一个目录名,则放回该i节点,也返回出错号。
845 oldinode=namei(oldname);
846 if (!oldinode)
847 return -ENOENT;
848 if (S_ISDIR(oldinode->i_mode)) {
849 iput(oldinode);
850 return -EPERM;
851 }
// 然后查找新路径名的最顶层目录的i节点dir,并返回最后的文件名及其长度。如果目录的
// i节点没有找到,则放回原路径名的i节点,返回出错号。如果新路径名中不包括文件名,
// 则放回原路径名i节点和新路径名目录的i节点,返回出错号。
852 dir = dir_namei(newname,&namelen,&basename, NULL);
853 if (!dir) {
854 iput(oldinode);
855 return -EACCES;
856 }
857 if (!namelen) {
858 iput(oldinode);
859 iput(dir);
860 return -EPERM;
861 }
// 我们不能跨设备建立硬链接。因此如果新路径名顶层目录的设备号与原路径名的设备号
// 不一样,则放回新路径名目录的i节点和原路径名的i节点,返回出错号。另外,如果用户
// 没有在新目录中写的权限,则也不能建立连接,于是放回新路径名目录的i节点和原路径名
// 的i节点,返回出错号。
862 if (dir->i_dev != oldinode->i_dev) {
863 iput(dir);
864 iput(oldinode);
865 return -EXDEV;
866 }
867 if (!permission(dir,MAY_WRITE)) {
868 iput(dir);
869 iput(oldinode);
870 return -EACCES;
871 }
// 现在查询该新路径名是否已经存在,如果存在则也不能建立链接。于是释放包含该已存在目
// 录项的高速缓冲块,放回新路径名目录的i节点和原路径名的i节点,返回出错号。
872 bh = find_entry(&dir,basename,namelen,&de);
873 if (bh) {
874 brelse(bh);
875 iput(dir);
876 iput(oldinode);
877 return -EEXIST;
878 }
// 现在所有条件都满足了,于是我们在新目录中添加一个目录项。若失败则放回该目录的i节
// 点和原路径名的i节点,返回出错号。否则初始设置该目录项的i节点号等于原路径名的i
// 节点号,并置包含该新添目录项的缓冲块已修改标志,释放该缓冲块,放回目录的i节点。
879 bh = add_entry(dir,basename,namelen,&de);
880 if (!bh) {
881 iput(dir);
882 iput(oldinode);
883 return -ENOSPC;
884 }
885 de->inode = oldinode->i_num;
886 bh->b_dirt = 1;
887 brelse(bh);
888 iput(dir);
// 再将原节点的链接计数加1,修改其改变时间为当前时间,并设置i节点已修改标志。最后
// 放回原路径名的i节点,并返回0(成功)。
889 oldinode->i_nlinks++;
890 oldinode->i_ctime = CURRENT_TIME;
891 oldinode->i_dirt = 1;
892 iput(oldinode);
893 return 0;
894 }
895