《apue》读书笔记 第四章 文件和目录(1)

第四章 文件和目录

1.stat,fstat,lstat函数

#include <sys/stat.h>

int stat(const char *restrict pathname, struct stat *restrict buf);

int fstat(int filedes, struct stat *buf);

int lstat(const char *restrict pathname, struct stat *restrict buf);

返回:如果成功均返回0,错误返回-1

stat返回pathname所指定的文件的相关信息结构到buf中;

fstat返回filedes描述符上打开的文件的相关信息到buf中;

lstat返回pathname所指定的文件的信息到buf中,与stat不同的是,如果pathname代表一个符号链接,那么它返回符号链接的信息而不像stat那样返回符号链接所指向的文件的信息。

buf指向的stat结构基本定义如下:

 struct stat {
  mode_t    st_mode;      /* file type & mode (permissions) */
  ino_t     st_ino;       /* i-node number (serial number) */
  dev_t     st_dev;       /* device number (file system) */
  dev_t     st_rdev;      /* device number for special files */
  nlink_t   st_nlink;     /* number of links */
  uid_t     st_uid;       /* user ID of owner */
  gid_t     st_gid;       /* group ID of owner */
  off_t     st_size;      /* size in bytes, for regular files */
  time_t    st_atime;     /* time of last access */
  time_t    st_mtime;     /* time of last modification */
  time_t    st_ctime;     /* time of last file status change */
  blksize_t st_blksize;   /* best I/O block size */
  blkcnt_t  st_blocks;    /* number of disk blocks allocated */
 };

需要注意的是每个成员都被指定成系统基本数据类型。

实际上,”ls -l”命令就是调用stat函数,返回文件所有信息。

2.文件类型

(a) 普通文件(regular file)。这是最常见的文件类型,它包含了某种形式的数据。这种数据可以是文本也可以是二进制数据,这对于内核而言并无区别。对普通文件内容的解释由处理该文件的应用程序进行。

(b) 目录文件(directory file)。这种文件包含了其他文件的名字以及指向与这些文件有关信息的指针。对一个目录文件具有读许可权的任一进程都可以读该目录的内容,但只有内核可以写目录文件。

(c) 字符特殊文件(character special file)。也称字符设备文件,这种文件用于系统中某些类型的设备,例如某些键盘,鼠标等。

(d) 块特殊文件(block special file)。也称块设备文件,磁盘设备就是这种类型的。

系统中的所有设备文件要么是字符特殊文件,要么是块特殊文件,用它来表示一个设备,通过系统调用操作这些设备文件,进而达到操作设备的目的,这也是Unix/Linux设备管理,基于文件系统的一个原因。

(e) FIFO。这类文件用于进程间的通信,也称为有名管道。

(f) 套接字( socket )。这类文件用于进程间的网络通信。套接字也可用于在一台宿主机上的进程之间的非网络通信。

(g) 符号连接(symbolic link)。这种文件指向另一个文件。相当于”Windows”下指向某一个文件的“快捷方式”。

文件类型信息包含在stat结构的st_mode成员中。

下表给出用来确定文件类型的的宏。这些宏的参数都是stat结构中的st_mode成员:


这里写图片描述

实例:取命令行参数,并针对每个参数打印其文件类型

#include "apue.h"

int
main(int argc, char *argv[])
{
    int         i;
    struct stat buf;
    char        *ptr;

    for (i = 1; i < argc; i++) {
        printf("%s: ", argv[i]);
        if (lstat(argv[i], &buf) < 0) {
            err_ret("lstat error");
            continue;
        }
        if (S_ISREG(buf.st_mode))
            ptr = "regular";
        else if (S_ISDIR(buf.st_mode))
            ptr = "directory";
        else if (S_ISCHR(buf.st_mode))
            ptr = "character special";
        else if (S_ISBLK(buf.st_mode))
            ptr = "block special";
        else if (S_ISFIFO(buf.st_mode))
            ptr = "fifo";
        else if (S_ISLNK(buf.st_mode))
            ptr = "symbolic link";
        else if (S_ISSOCK(buf.st_mode))
            ptr = "socket";
        else
            ptr = "** unknown mode **";
        printf("%s\n", ptr);
    }
    exit(0);
}

结果:

$ gcc filetype.c -o filetype -l apue
$ ./filetype /etc/passwd /etc /dev/log /dev/tty /dev/cdrom
/etc/passwd: regular
/etc: directory
/dev/log: socket
/dev/tty: character special
/dev/cdrom: symbolic link

注意,为了检测符号链接,我们特意用了lstat而不是stat。

3.Set-User-ID和Set-Group-ID (SUID和SGID)

进程对文件操作权限有三对ID:

real user ID
real group ID
表示当前真正的用户究竟是谁,这个字段(例如real user ID)是从passwd中取得的。

effective user ID
effective group ID
supplementary group IDs(附加组ID)
利用这个ID来决定进程访问文件的权限。

saved set-user-ID
saved set-group-ID
这个ID在进程运行的时候,会保留一份effective ID的副本。

另外,在文件的stat结构中的st_mode描述了文件的类型和权限,st_uid描述了文件的所有者。

每个文件都有一个所有者和组所有者。所有者通过stat结构的st_uid成员指定;组所有者通过st_gid成员指定。

一般程序执行的时候,进程的effective user ID等于real user ID;effective group ID等于real group ID。但是如果文件设置了set-user-ID(在stat中的s_mode中设置),那么程序运行的时候,effectiveID就和文件的所有者ID一样,这样就有特殊的权限了。set-group-ID类似。

比如:一个文件的所有者是超级用户,并且这个文件的set-user-ID位被设置,那么当那个程序文件被作为一个进程运行的时候,它就有了超级用户的权限。例如,UNIX 系统中,任何用户都可以修改他们自己的密码,修改的命令是passwd,这个命令实际修改的是系统文件,而系统文件只能被超级用户修改;但是由于这个passwd命令是set-user-ID被设置了的程序,这样我们就可以将密码信息写入系统的密码文件中(/etc/passwd或者/etc/shaw文件)。【也就是说,这些系统文件通常只有超级用户能够修改,而这个passwd的set-user-ID位,使得运行这个程序的用户也有了超级用户的权限。】因为被设置了set-user-ID的程序,可以让其他用户具有更多的权限,所以我们需要仔细考虑其安全性。

运行stat函数的时候set-user-ID位以及set-group-ID位被包含到文件的st_mode值中,这两个位可以通过S_ISUID和S_ISGID被检测到。

4.文件访问权限

stat的st_mode成员,其值也包含了文件的访问权限。所有文件类型(目录,字符特别文件等)都有访问权限。每个文件有9个存取许可权位,可将它们分成三类,分别为user,group,others。
每一类有rwx 3个权限,分别表示读,写,执行。


这里写图片描述

chmod(1)命令可以修改这9个权限位, 3个分类的表示分别为 u, g, o。

使用ls -l 可以查看这9个权限位。

一些规则:

(a)我们用路径名打开任一类型的文件时,对该路径中包含的每一个目录,包括它可能隐含的当前工作目录都应具有执行权。因此,对于目录,其执行位常被称为搜索位。
例如,为了打开文件/usr/include/stdio.h,需要具有对目录/, /usr, /usr/include的执行权限。然后,还要有对该文件本身的适当权限,这取决于以何种方式打开它(只读,读-写等)。如果当前目录是/usr/inlcude ,那么为了打开文件stdio.h,我们需要有对该目录的执行权限。如在指定打开文件stdio.h时,可以隐含当前目录,而不用显式地提及/usr/include,也可使用./stdio.h。

××注意,对于目录的读权限和执行权限的意义不同。读权限允许我们读目录,【获得在该目录中所有文件名的列表】。而当一个目录是我们要存取文件的路径名的一个分量时,对该目录的执行权限使我们可进入该目录(也就是搜索该目录,寻找一个特定的文件名)。

我们可以实际测试一下:

#拿掉对目录cpp的所有权限
$ chmod u-rwx ./cpp 
$ ls cpp
ls: 无法打开目录cpp: 权限不够
#赋予r权限
$ chmod u+r  ./cpp 
#此时可以列出目录下的文件名
$ ls cpp
ls: 无法访问cpp/test.o: 权限不够
ls: 无法访问cpp/test.sh: 权限不够
ls: 无法访问cpp/test1.cpp: 权限不够
ls: 无法访问cpp/test: 权限不够
ls: 无法访问cpp/test.cpp: 权限不够
test  test1.cpp  test.cpp  test.o  test.sh
#无法查看文件的细节
$ ls -l ./cpp
ls: 无法访问./cpp/test.o: 权限不够
ls: 无法访问./cpp/test.sh: 权限不够
ls: 无法访问./cpp/test1.cpp: 权限不够
ls: 无法访问./cpp/test: 权限不够
ls: 无法访问./cpp/test.cpp: 权限不够
总用量 0
-????????? ? ? ? ?             ? test
-????????? ? ? ? ?             ? test1.cpp
-????????? ? ? ? ?             ? test.cpp
-????????? ? ? ? ?             ? test.o
-????????? ? ? ? ?             ? test.sh
#但是进入不了
$ cd cpp
bash: cd: cpp: 权限不够
#赋予x权限
$ chmod u+x  ./cpp 
$ ls cpp
test  test1.cpp  test.cpp  test.o  test.sh
$ cd cpp
jiange@jiange-Inspiron-5437:~/cpp$ 

探讨:我们获得cpp目录的r权限之后,因为没有x权限,所以【进入】不了cpp目录来查看具体的文件信息,但是为什么可以查看文件名呢?因为文件名是写在cpp目录的inode中!而我们对cpp目录有r权限,看不到目录内文件信息是因为文件的具体信息存放在文件的inode中。

(b)对于一个文件的读权限决定了我们是否能够打开该文件进行读操作。这对应于open函数的O_RDONL和O_RDWR标志。

(c)对于一个文件的写权限决定了我们是否能够打开该文件进行写操作。这对应于open函数的O_WRONL和O_RDWR标志。

(d)在open函数中对一个文件指定O_TRUNC标志之时,首先必须对该文件具有写权限。

(e)在一个目录中创建一个新文件之时,首先必须对该目录具有写权限和执行权限。

(f)删除一个文件,必须对包含该文件的目录具有写权限和执行权限。【而对该文件本身则不需要有读、写权】。

(g)如果用6个exec函数中的任何一个执行某个文件,都必须对该文件具有执行权限。

进程每次打开、创建或删除一个文件时,内核就进行文件存取权检查,而这种检查可能涉及文件的所有者(stat结构的st_uid和st_gid成员),进程的有效ID(有效用户ID和有效组ID)以及进程的添加组ID (若支持的话)。两个所有者ID是文件的性质,而有效ID和添加组ID则是进程的性质。内核判断进程访问文件权限进行检查的过程是:
a.如果进程的effective user ID是0,那么访问被无条件允许。
b.如果进程的effective user ID等于文件的owner ID(也就是进程拥有这个文件),那么访问权限依照文件的user访问权限而定。也就是说,如果user是可读的,那么进程能够读取,如果user可写的那么进程可以写,等等,如果设置user权限不可读写,那么即使进程effective user ID和owner ID(也就是user ID)一样,也是无法进行相应的访问的。
c.如果进程的effective group ID或者supplementary group ID和文件的group ID一样,那么访问权限会依照文件组权限的设定而定。
d.如果文件的other组的指定的访问权限被指定了,那么相应权限的访问会被允许。
以上过程是依照次序进行的,如果在1-4步骤中,进入了某一步骤的判断,并且确定了权限,那么下一个步骤会被忽略的。

5.新文件和目录的所有权

文件的userID将被设置为进程的effective user ID.

文件的groupID可以是【进程】的effective group ID,也可以是【文件父目录】的group ID.

具体如下:
如果文件所在的目录设置了set-group-ID,那么就是父目录的groupID,否则就是进程的effective groupID,这是Linux2.4和solaris的特性。ext2,ext3可以在mount的时候指定这两种方式。FreeBSD 5.2.1 和 Mac OS X 10.3却一直是所在父目录的group ID.

(目录的规则与文件相同)

6.access函数

当打开一个文件时,内核以进程的effective id为基础来测试其访问权限,但有时进程也希望使用real id来进行测试。例如当有的进程在set-user-id的情况下具有了root权限可以访问,我们还是需要知道这个进程的real id来验证它是否能访问一个给定的文件。

access函数根据进程的real userid 和real group id来进行判断权限(判断的过程是把前面的四步的effective id换成real id),其声明如下:

 #include <unistd.h>
 int access(const char *pathname, int mode);
 返回:如果成功返回0,如果错误返回-1

这里的mode参数是如下常数的按位或:


这里写图片描述

例子:

#include "apue.h"
#include <fcntl.h>

int
main(int argc, char *argv[])
{
    if (argc != 2)
        err_quit("usage: a.out <pathname>");
    if (access(argv[1], R_OK) < 0)
        err_ret("access error for %s", argv[1]);
    else
        printf("read access OK\n");
    if (open(argv[1], O_RDONLY) < 0)
        err_ret("open error for %s", argv[1]);
    else
        printf("open for reading OK\n");
    exit(0);
}

测试:

$ ls -l access
-rwxrwxr-x 1 jiange jiange 8099 1127 09:31 access
#access的effective id和real id对access都有r权限
$ ./access access
read access OK
open for reading OK
$ ls -l /etc/shadow
-rw-r----- 1 root shadow 1072 11月 18 23:08 /etc/shadow
#access的effective id和real id对/etc/shadow都没有r权限
$ ./access /etc/shadow
access error for /etc/shadow: Permission denied
open error for /etc/shadow: Permission denied
#修改拥有者,并给予s权限
$ sudo chown root access
$ sudo chmod u+s access
$ ls -l access
-rwsrwxr-x 1 root jiange 8099 1127 09:31 access
#access的effective id对/etc/shadow有r权限(因为此时取得了超级用户权限),而real id对access并没有r权限
$ ./access /etc/shadow
access error for /etc/shadow: Permission denied
open for reading OK
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值