/*
* linux/fs/namei.c
*
* (C) 1991 Linus Torvalds
*/
/*
* Some corrections by tytso.
*/
/*
* tytso 作了一些纠正。
*/
#include <linux/sched.h> // 调度程序头文件,定义了任务结构task_struct、初始任务0 的数据,
// 还有一些有关描述符参数设置和获取的嵌入式汇编函数宏语句。
#include <linux/kernel.h> // 内核头文件。含有一些内核常用函数的原形定义。
#include <asm/segment.h> // 段操作头文件。定义了有关段寄存器操作的嵌入式汇编函数。
#include <string.h> // 字符串头文件。主要定义了一些有关字符串操作的嵌入函数。
#include <fcntl.h> // 文件控制头文件。用于文件及其描述符的操作控制常数符号的定义。
#include <errno.h> // 错误号头文件。包含系统中各种出错号。(Linus 从minix 中引进的)。
#include <const.h> // 常数符号头文件。目前仅定义了i 节点中i_mode 字段的各标志位。
#include <sys/stat.h> // 文件状态头文件。含有文件或文件系统状态结构stat{}和常量。
// 访问模式宏。x 是include/fcntl.h 第7 行开始定义的文件访问标志。
// 根据x 值索引对应数值(数值表示rwx 权限: r, w, rw, wxrwxrwx)(数值是8 进制)。
#define ACC_MODE(x) ( "\004\002\006\377"[(x)&O_ACCMODE])
/*
* comment out this line if you want names > NAME_LEN chars to be
* truncated. Else they will be disallowed.
*/
/*
* 如果想让文件名长度>NAME_LEN 的字符被截掉,就将下面定义注释掉。
*/
/* #define NO_TRUNCATE */
#define MAY_EXEC 1 // 可执行(可进入)。
#define MAY_WRITE 2 // 可写。
#define MAY_READ 4 // 可读。
/*
* permission()
*
* is used to check for read/write/execute permissions on a file.
* I don't know if we should look at just the euid or both euid and
* uid, but that should be easily changed.
*/
/*
* permission()
* 该函数用于检测一个文件的读/写/执行权限。我不知道是否只需检查euid,还是
* 需要检查euid 和uid 两者,不过这很容易修改。
*/
//// 检测文件访问许可权限。
// 参数:inode - 文件对应的i 节点;mask - 访问属性屏蔽码。
// 返回:访问许可返回1,否则返回0。
static int
permission (struct m_inode *inode, int mask)
{
int mode = inode->i_mode; // 文件访问属性
/* special case: not even root can read/write a deleted file */
/* 特殊情况:即使是超级用户(root)也不能读/写一个已被删除的文件 */
// 如果i 节点有对应的设备,但该i 节点的连接数等于0,则返回。
if (inode->i_dev && !inode->i_nlinks)
return 0;
// 否则,如果进程的有效用户id(euid)与i 节点的用户id 相同,则取文件宿主的用户访问权限。
else if (current->euid == inode->i_uid)
mode >>= 6;
// 否则,如果进程的有效组id(egid)与i 节点的组id 相同,则取组用户的访问权限。
else if (current->egid == inode->i_gid)
mode >>= 3;
// 如果上面所取的的访问权限与屏蔽码相同,或者是超级用户,则返回1,否则返回0。
if (((mode & mask & 0007) == mask) || suser ())
return 1;
return 0;
}
/*
* ok, we cannot use strncmp, as the name is not in our data space.
* Thus we'll have to use match. No big problem. Match also makes
* some sanity tests.
*
* NOTE! unlike strncmp, match returns 1 for success, 0 for failure.
*/
/*
* ok,我们不能使用strncmp 字符串比较函数,因为名称不在我们的数据空间(不在内核空间)。
* 因而我们只能使用match()。问题不大。match()同样也处理一些完整的测试。
*
* 注意!与strncmp 不同的是match()成功时返回1,失败时返回0。
*/
//// 指定长度字符串比较函数。
// 参数:len - 比较的字符串长度;name - 文件名指针;de - 目录项结构。
// 返回:相同返回1,不同返回0。
static int
match (int len, const char *name, struct dir_entry *de)
{
register int same __asm__ ("ax");
// 如果目录项指针空,或者目录项i 节点等于0,或者要比较的字符串长度超过文件名长度,则返回0。
if (!de || !de->inode || len > NAME_LEN)
return 0;
// 如果要比较的长度len 小于NAME_LEN,但是目录项中文件名长度超过len,则返回0。
if (len < NAME_LEN && de->name[len])
return 0;
// 下面嵌入汇编语句,在用户数据空间(fs)执行字符串的比较操作。
// %0 - eax(比较结果same);%1 - eax(eax 初值0);%2 - esi(名字指针);%3 - edi(目录项名指针);
// %4 - ecx(比较的字节长度值len)。
__asm__ ("cld\n\t" // 清方向位。
"fs ; repe ; cmpsb\n\t" // 用户空间执行循环比较[esi++]和[edi++]操作,
"setz %%al" // 若比较结果一样(z=0)则设置al=1(same=eax)。
: "=a" (same): "" (0), "S" ((long) name), "D" ((long) de->name), "c" (len):"cx", "di",
"si");
return same; // 返回比较结果。
}
/*
* find_entry()
*
* finds an entry in the specified directory with the wanted name. It
* returns the cache buffer in which the entry was found, and the entry
* itself (as a parameter - res_dir). It does NOT read the inode of the
* entry - you'll have to do that yourself if you want to.
*
* This also takes care of the few special cases due to '..'-traversal
* over a pseudo-root and a mount point.
*/
/*
* find_entry()
* 在指定的目录中寻找一个与名字匹配的目录项。返回一个含有找到目录项的高速
* 缓冲区以及目录项本身(作为一个参数 - res_dir)。并不读目录项的i 节点 - 如
* 果需要的话需自己操作。
*
* '..'目录项,操作期间也会对几种特殊情况分别处理 - 比如横越一个伪根目录以
* 及安装点。
*/
//// 查找指定目录和文件名的目录项。
// 参数:dir - 指定目录i 节点的指针;name - 文件名;namelen - 文件名长度;
// 返回:高速缓冲区指针;res_dir - 返回的目录项结构指针;
static struct buffer_head *
find_entry (struct m_inode **dir,
const char *name, int namelen, struct dir_entry **res_dir)
{
int entries;
int block, i;
struct buffer_head *bh;
struct dir_entry *de;
struct super_block *sb;
// 如果定义了NO_TRUNCATE,则若文件名长度超过最大长度NAME_LEN,则返回。
#ifdef NO_TRUNCATE
if (namelen > NAME_LEN)
return NULL;
//如果没有定义NO_TRUNCATE,则若文件名长度超过最大长度NAME_LEN,则截短之。
#else
if (namelen > NAME_LEN)
namelen = NAME_LEN;
#endif
// 计算本目录中目录项项数entries。置空返回目录项结构指针。
entries = (*dir)->i_size / (sizeof (struct dir_entry));
*res_dir = NULL;
// 如果文件名长度等于0,则返回NULL,退出。
if (!namelen)
return NULL;
/* 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;
// 否则如果该目录的i 节点号等于ROOT_INO(1)的话,说明是文件系统根节点。则取文件系统的超级块。
else if ((*dir)->i_num == ROOT_INO)
{
/* '..' over a mount-point results in 'dir' being exchanged for the mounted
directory-inode. NOTE! We set mounted, so that we can iput the new dir */
/* 在一个安装点上的'..'将导致目录交换到安装到文件系统的目录i 节点。
注意!由于设置了mounted 标志,因而我们能够取出该新目录 */
sb = get_super ((*dir)->i_dev);
// 如果被安装到的i 节点存在,则先释放原i 节点,然后对被安装到的i 节点进行处理。
// 让*dir 指向该被安装到的i 节点;该i 节点的引用数加1。
if (sb->s_imount)
{
iput (*dir);
(*dir) = sb->s_imount;
(*dir)->i_count++;
}
}
}
// 如果该i 节点所指向的第一个直接磁盘块号为0,则返回NULL,退出。
if (!(block = (*dir)->i_zone[0]))
return NULL;
// 从节点所在设备读取指定的目录项数据块,如果不成功,则返回NULL,退出。
if (!(bh = bread ((*dir)->i_dev, block)))
return NULL;
// 在目录项数据块中搜索匹配指定文件名的目录项,首先让de 指向数据块,并在不超过目录中目录项数
// 的条件下,循环执行搜索。
i = 0;
de = (struct dir_entry *) bh->b_data;
while (i < entries)
{
// 如果当前目录项数据块已经搜索完,还没有找到匹配的目录项,则释放当前目录项数据块。
if ((char *) de >= BLOCK_SIZE + bh->b_data)
{
brelse (bh);
bh = NULL;
// 在读入下一目录项数据块。若这块为空,则只要还没有搜索完目录中的所有目录项,就跳过该块,
// 继续读下一目录项数据块。若该块不空,就让de 指向该目录项数据块,继续搜索。
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))
{
*res_dir = de;
return bh;
}
// 否则继续在目录项数据块中比较下一个目录项。