码迷,mamicode.com
首页 > 其他好文 > 详细

APUE读书笔记-第四章 文件和目录

时间:2016-06-28 11:06:43      阅读:221      评论:0      收藏:0      [点我收藏+]

标签:

到第四章了,不知什么时候才能把这本书看完,耽误的时间太多了。

第四章是在第三章的基础上,主要描述文件系统的其他性质和文件的性质。

4.2 stat、fstat、fstatat、lstat函数

首先来看看这四个函数的原型:

#include <sys/stat.h> ///usr/include/x86_64-linux-gnu/sys/
int stat (const char *__restrict __file, struct stat *__restrict __buf)
int fstat (int __fd, struct stat *__buf)
int fstatat (int __fd, const char *__restrict __file, struct stat *__restrict __buf, int __flag)
int lstat (const char *__restrict __file, struct stat *__restrict __buf)
这几个函数的功能概括起来就是:获得stat结构体。区别就在于stat函数通过文件名获得这一结构体;fstat通过文件描述符;lstat返回该符号链接的有关信息,而不是由该符号链接引用的文件的信息;fstatat返回当前文件夹下(由fd指向)某个文件(文件名为file)的stat结构体。

好了,不管上面的几个函数具体功能为何,其核心都是返回stat结构体,stat定义如下:

struct stat
  {
    __dev_t st_dev;		/* Device.  */
#ifndef __x86_64__
    unsigned short int __pad1;
#endif
#if defined __x86_64__ || !defined __USE_FILE_OFFSET64
    __ino_t st_ino;		/* File serial number.	*/
#else
    __ino_t __st_ino;			/* 32bit file serial number.	*/
#endif
#ifndef __x86_64__
    __mode_t st_mode;			/* File mode.  */
    __nlink_t st_nlink;			/* Link count.  */
#else
    __nlink_t st_nlink;		/* Link count.  */
    __mode_t st_mode;		/* File mode.  */
#endif
    __uid_t st_uid;		/* User ID of the file's owner.	*/
    __gid_t st_gid;		/* Group ID of the file's group.*/
#ifdef __x86_64__
    int __pad0;
#endif
    __dev_t st_rdev;		/* Device number, if device.  */
#ifndef __x86_64__
    unsigned short int __pad2;
#endif
#if defined __x86_64__ || !defined __USE_FILE_OFFSET64
    __off_t st_size;			/* Size of file, in bytes.  */
#else
    __off64_t st_size;			/* Size of file, in bytes.  */
#endif
    __blksize_t st_blksize;	/* Optimal block size for I/O.  */
#if defined __x86_64__  || !defined __USE_FILE_OFFSET64
    __blkcnt_t st_blocks;		/* Number 512-byte blocks allocated. */
#else
    __blkcnt64_t st_blocks;		/* Number 512-byte blocks allocated. */
#endif
#ifdef __USE_XOPEN2K8
    /* Nanosecond resolution timestamps are stored in a format
       equivalent to 'struct timespec'.  This is the type used
       whenever possible but the Unix namespace rules do not allow the
       identifier 'timespec' to appear in the <sys/stat.h> header.
       Therefore we have to handle the use of this header in strictly
       standard-compliant sources special.  */
    struct timespec st_atim;		/* Time of last access.  */
    struct timespec st_mtim;		/* Time of last modification.  */
    struct timespec st_ctim;		/* Time of last status change.  */
# define st_atime st_atim.tv_sec	/* Backward compatibility.  */
# define st_mtime st_mtim.tv_sec
# define st_ctime st_ctim.tv_sec
#else
    __time_t st_atime;			/* Time of last access.  */
    __syscall_ulong_t st_atimensec;	/* Nscecs of last access.  */
    __time_t st_mtime;			/* Time of last modification.  */
    __syscall_ulong_t st_mtimensec;	/* Nsecs of last modification.  */
    __time_t st_ctime;			/* Time of last status change.  */
    __syscall_ulong_t st_ctimensec;	/* Nsecs of last status change.  */
#endif
#ifdef __x86_64__
    __syscall_slong_t __glibc_reserved[3];
#else
# ifndef __USE_FILE_OFFSET64
    unsigned long int __glibc_reserved4;
    unsigned long int __glibc_reserved5;
# else
    __ino64_t st_ino;			/* File serial number.	*/
# endif
#endif
  };

4.3文件类型

看过了上面的stat结构体,随意感受了一下stat结构体的内容,接下来对其中几个重要的字段进行研究,首先来看:

 __mode_t st_mode;

先来看看“ __mode_t”的类型,相关定义位于:/usr/include/x86_64-linux-gnu/bits/type.h中,具体定义如下:

# define __STD_TYPE		typedef
__STD_TYPE __MODE_T_TYPE __mode_t;
#define __MODE_T_TYPE        __U32_TYPE ///usr/include/x86_64-linux-gnu/bits/typesize.h
#define __U32_TYPE        unsigned int

这一大堆,来回来去的。

根据APUE,文件共包括以下几类:

  1. 普通文件
  2. 目录文件
  3. 块特殊文件
  4. 字符特殊文件
  5. FIFO
  6. 套接字
  7. 符号链接

好了,再来看看linux的有关内容,还是位于/usr/include/x86_64-linux-gnu/sys/stat.h中

#define	__S_ISTYPE(mode, mask)	(((mode) & __S_IFMT) == (mask))

#define	S_ISDIR(mode)	 __S_ISTYPE((mode), __S_IFDIR)
#define	S_ISCHR(mode)	 __S_ISTYPE((mode), __S_IFCHR)
#define	S_ISBLK(mode)	 __S_ISTYPE((mode), __S_IFBLK)
#define	S_ISREG(mode)	 __S_ISTYPE((mode), __S_IFREG)
#ifdef __S_IFIFO
# define S_ISFIFO(mode)	 __S_ISTYPE((mode), __S_IFIFO)
#endif
#ifdef __S_IFLNK
# define S_ISLNK(mode)	 __S_ISTYPE((mode), __S_IFLNK)
#endif
# define S_ISSOCK(mode) __S_ISTYPE((mode), __S_IFSOCK)
#ifdef    __USE_POSIX199309
# define S_TYPEISMQ(buf) __S_TYPEISMQ(buf)
# define S_TYPEISSEM(buf) __S_TYPEISSEM(buf)
# define S_TYPEISSHM(buf) __S_TYPEISSHM(buf)
#endif

好了,通过上面的定义可以了解到这些宏定义说到底就是通过一个参数与__S_IFMT进行“与运算”,得到值后与函数的本来功能进行比较,最后返回0或1。

在上一章中我们留下了一个疑问,通过lseek函数可以设定文件的偏移量,但若文件描述符指向管道、FIFO(命名管道)、网络套接字,则lseek返回-1,并将errno设置为ESPIPE。所以我们得到结论文件描述符0、1、2是管道或命名管道。现在终于可以通过实验验证一下了:

#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>

int main()
{
	struct stat buf;
	fstat(STDIN_FILENO,&buf);
	if(S_ISFIFO(buf.st_mode));
		printf("stdin is fifo\n");
	fstat(STDOUT_FILENO,&buf);
	if(S_ISFIFO(buf.st_mode));
		printf("stdout is fifo\n");
	fstat(STDERR_FILENO,&buf);
	if(S_ISFIFO(buf.st_mode));
		printf("stderr is fifo\n");
	return 0;
}

运行结果如下:

./test_stat 
stdin is fifo
stdout is fifo
stderr is fifo

通过以上实验也验证了我们的推测——文件描述符0、1、2是管道或命名管道。

4.4 节主要讨论设置用户ID与用户组ID的问题,书中给出了一些分析,不过我感觉说的不是很清楚,所以给大家分享一篇blog:http://blog.chinaunix.net/uid-18905703-id-3843177.html

关于以上概念,结合我看到的一些资料,给大家做一个简单的总结。关于这几个概念的区分,还是从现象出发,一个程序要执行首先要知道程序的“有效用户ID”、“有效组ID”、“附属组ID”,知道了以上三个“ID”后还不够,我们还要检查当前的用户“ID”是否与以上三个“ID”相匹配,而这个当前的用户“ID”就是“实际用户ID”与“实际组ID”,也就是登录系统时所用到的身份验证信息。一般来说,当执行一个程序文件时,进程的有效用户ID通常就是实际用户ID,有效组ID通常是实际组ID。但也有特殊情况就是书中提到的passwd命令的执行,关于这个命令的详细分析大家可以参考上面那篇blog。

4.5 文件访问权限

这一节主要要给大家分享一下在访问文件过程中所用到的隐藏权限,这里隐藏的权限是指除了访问文件所需要的权限外,还需要的额外的权限,具体内容如下:

  1. 通过文件名打开任意类型的文件时,对文件名(绝对路径)中包含的每一个目录,包括它可能隐含的当前工作目录都应具有执行权限。这里还要强调一下目录的读权限与执行权限的不同意义,读权限允许我们读目录,获得在该目录中所有文件名的列表。当一个目录是我们要访问文件的路径名的一个组成部分时,对该目录的执行权限使我们可通过该目录(也就是搜索该目录,寻找一个特定的文件名)。
  2. 对于一个文件的读权限决定了我们是否能够打开现有文件进行读操作。这与open函数的O_RDONLY和O_RDWR相关。
  3. 对于一个文件的写权限决定了我们是否能够打开现有文件进行写操作。这与open函数的O_WRONLY和O_RDWR相关。
  4. 为了在open函数中对一个文件指定O_TRUNC标志,必须对该文件具有写权限。
  5. 为了在一个目录中创建一个新文件,必须对该目录具有写权限和执行权限。之所以需要执行权限是由于在创建文件前需要查找当前目录中是否已有该文件。
  6. 为了删除一个现有文件,必须对包含该文件的目录具有写权限和执行权限(删除之前还是要进行查找操作)。对该文件本身则不需要有读、写权限。
  7. 如果用7个exec函数中的任何一个执行某个文件,都必须对该文件具有执行权限。该文件还必须是一个不同文件。

进程每次打开、创建或删除一个文件时,内核就进行文件访问权限测试,这种测试涉及两方面:一方面是文件的所有者,另一方面是执行操作的进程。对于文件的所有者进行管理主要涉及:st_uid(用户ID)、st_gid(用户组ID),对于进程的权限管理需要三个字段:有效用户ID、有限组ID、附属组ID。

有了标识进程与文件权限的信息位,再来就是对这些信息位进行比较,若相符则代表具有权限,若不符则不代表具有相应的权限。以下是内核进行的具体测试过程:

  1. 若进程的有效用户ID是0(超级用户),则允许访问。这给予了超级用户对整个文件系统进行处理的最充分的自由。
  2. 若进程的有效用户ID等于文件的所有者ID(也就是进程拥有此文件),那么如果所有者适当的访问权限位被设置,则允许访问;否则拒绝访问。
  3. 若进程的有效组ID或进程的附属组ID之一等于文件的组ID,那么如果组适当的访问权限位被设置,则允许访问;否则拒绝访问。
  4. 若其他用户适当的访问权限位被设置,则允许访问;否则拒绝访问。

以上4个步骤顺序执行,其中若执行到一个权限规则相匹配则跳出测试,不再进行接下来的测试步骤。

4.6 新文件和目录的所有权

使用open或create创建新文件时,新文件的用户ID设置为进程的有效用户ID。关于组ID可以有以下两个选项:

  1. 新文件的组ID可以是进程的有效组ID。
  2. 新文件的组ID可以是它所在目录的组ID。这种选项的作用是使在某个目录下创建的文件和目录都具有该目录的组ID,于是文件和目录的组所有权从该点向下传递。

对linux操作系统如果设置组ID位被设置则新文件的组ID设置为目录的组ID;否则新文件的组ID设置为进程的有效组ID。

好了,说了这么多有关于权限的问题,让我们回过头来再看看在上一章中留下的问题,当时留下的问题是“先在没有写权限的情况下创建文件,即此时文件仅具有读权限,然后写入数据,根据我的实验若文件不存在则可以成功写入数据。此时文件已经存在,再通过读写权限打开文件则出现无权限错误”。对于后一点错误我们已经可以理解了,确实是没有没有权限,因此后来我们加入了“写权限”,则可以成功写入数据,但对于第一点还不是很明白:“没什么没有写权限却可以写入数据?”。

4.7 access和faccessat函数

先来看看这两个函数的原型:

#include <unistd.h>
extern int access (const char *__name, int __type) __THROW __nonnull ((1));
extern int faccessat (int __fd, const char *__file, int __type, int __flag)
     __THROW __nonnull ((2)) __wur;
这两个函数的功能是按照实际用户ID和实际组ID来测试文件的访问能力,注意此处一定要两个权限都测试成功,才会返回成功,否则则返回失败。再来看看我们能测试哪些权限,在<unistd.h>中有如下定义,上述定义用于函数的type参数。

#define	R_OK	4		/* Test for read permission.  */ //通过注释就可以了解其功能,测试读权限
#define	W_OK	2		/* Test for write permission.  */ //测试写权限
#define	X_OK	1		/* Test for execute permission.  */ //测试执行权限
#define	F_OK	0		/* Test for existence.  */ //测试是否存在

access函数通过其定义可以发现,使用文件名标识被测试的文件。faccessat函数通过fd(指向某个文件夹)与文件名共同确定被测试文件。其中若faccessat函数的__flag参数设定为AT_EACCESS,则访问检查用的是调用进程的有效用户ID和有效组ID(同样按照前面的测试顺序,遇到匹配项就跳出),而不是实际用户ID和实际组ID。

通过书中的示例,对access函数的功能进行简单的测试,源码如下:

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>

int main(int argc,char* argv[])
{
	if( access(argv[1],R_OK)<0 ) perror(argv[0]);
	else printf("read access OK\n");

	if( open(argv[1],O_RDONLY)<0 ) perror(argv[0]);
	else printf("open for reading OK\n");
	return 0;
}

运行结果如下:

./test_access test_access
read access OK
open for reading OK

再来看看/etc/shadow文件的运行情况:

./test_access /etc/shadow
./test_access: Permission denied
./test_access: Permission denied

没有权限运行,所以做一些调整再试试:

su //切换到root用户
chown root test_access //切换用户ID为root,通过这一步操作就可以使用open函数打开文件
chmod u+s test_access //打开用户设置用户ID位,设置用户ID位的作用用一句话概括就是“执行者将具有该程序所有者的权限”,在我们的例子中就是普通用户具有root用户的读
权限
ls -l test_access //此时程序的所有者已经变为root用户,同时设置用户ID位也被设置——“s”。
-rwsrwxr-x 1 root 8712  6月 26 17:36 test_access
exit //返回普通用户
./test_access /etc/shadow
./test_access: Permission denied //本来设置了“s”位,普通用户应该具有与root用户相同的权限,即普通用户同样可以读权限,但因为access函数使用实际用户ID与实际组ID进行
测试,所以出现无权限错误。
open for reading OK //同理由于设置了“s”位,而root用户具有读权限,普通用户具有与root用户相同的权限,则可以通过open函数打开。

(关于ubuntu如何切换到超级用户请见以下blog:http://blog.csdn.net/david_xtd/article/details/7229325)。

 4.8 umask函数

umask函数为进程设置文件模式创建屏蔽字,并返回之前的值(这是少数几个没有出错返回函数中的一个)。函数原型:

#include <sys/stat.h>
extern __mode_t umask (__mode_t __mask) __THROW;
其中,参数cmask是由9个常量中的若干个按位“或”构成的。在文件模式创建屏蔽字中为1的位,在文件mode中的相应位一定被关闭。

通过一个实例感受以下函数的功能,源码如下:

#include <fcntl.h>
#include <unistd.h>

#define RWRWRW (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)

int main(int argc,char* argv[])
{
	umask(0);
	if(creat("foo",RWRWRW)<0) perror(argv[0]);
	
	umask(S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
	if(creat("bar",RWRWRW)<0) perror(argv[0]);
	return 0;
}

运行结果如下:

ls -l foo bar
-rw------- 1 andywang andywang 0  6月 26 21:16 bar
-rw-rw-rw- 1 andywang andywang 0  6月 26 21:16 foo

通过实验可以发现,umask(0)没有屏蔽任何字,所以foo文件可以按照我们设定的权限进行创建,但由于在创建bar文件之前通过umask函数屏蔽了S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH,则在创建bar时文件的读写权限仅保留S_IRUSR|S_IWUSR。通过这个实验可以得出结论umask的效果可以一直持续直到进程结束。更改进程的文件模式创建屏蔽字并不影响其父进程(常常是shell)的屏蔽字。但不知道exec函数是否能够延续umask函数的效果,此处留下一个疑问?

4.9 chmod、fchmod、fchmodat函数

先来看看函数原型:

#include <sys/stat.h>
extern int chmod (const char *__file, __mode_t __mode)
     __THROW __nonnull ((1));
extern int fchmod (int __fd, __mode_t __mode) __THROW;
extern int fchmodat (int __fd, const char *__file, __mode_t __mode,
             int __flag)
     __THROW __nonnull ((2)) __wur;

以上三个函数的功能是更改现有文件的访问权限。chmod函数在指定的文件上进行操作,而fchmod函数则对已打开的文件进行操作。fchmodat函数则对已打开的文件进行操作。fchmodat函数与chmod函数在下面两种情况下是相同的:一种是pathname参数为绝对路径,另一种是fd参数取值为AT_FDCWD而pathname参数为相对路径。否则,fchmodat计算相对于打开目录(由fd参数指向)的pathname。flag参数可以用于改变fchmodat的行为,当设置了AT_SYMLINK_NOFOLLOW标志时,fchmodat并不会跟随符号链接。

为了改变一个文件的权限位,进程的有效用户ID必须等于文件所有者ID,或者该进程必须具有超级用户权限。

除了前文中提到的9个权限位外还有6个权限位,全部权限如下:

#define	S_ISUID __S_ISUID	/* Set user ID on execution.  */
#define	S_ISGID	__S_ISGID	/* Set group ID on execution.  */
# define S_ISVTX    __S_ISVTX
#define    S_IRUSR    __S_IREAD    /* Read by owner.  */
#define    S_IWUSR    __S_IWRITE    /* Write by owner.  */
#define    S_IXUSR    __S_IEXEC    /* Execute by owner.  */
#define    S_IRWXU    (__S_IREAD|__S_IWRITE|__S_IEXEC)
#define    S_IRGRP    (S_IRUSR >> 3)    /* Read by group.  */
#define    S_IWGRP    (S_IWUSR >> 3)    /* Write by group.  */
#define    S_IXGRP    (S_IXUSR >> 3)    /* Execute by group.  */
/* Read, write, and execute by group.  */
#define    S_IRWXG    (S_IRWXU >> 3)
#define    S_IROTH    (S_IRGRP >> 3)    /* Read by others.  */
#define    S_IWOTH    (S_IWGRP >> 3)    /* Write by others.  */
#define    S_IXOTH    (S_IXGRP >> 3)    /* Execute by others.  */
/* Read, write, and execute by others.  */
#define    S_IRWXO    (S_IRWXG >> 3)


好了,还是结合书中的例子看一下,源码如下:

#include <unistd.h>
#include <sys/stat.h>
#include <stdio.h>

int main(int argc,char* argv[])
{
	struct stat statbuf;
	if( stat("foo",&statbuf)<0 ) perror(argv[0]);

	if( chmod("foo", (statbuf.st_mode & ~S_IXGRP) | S_ISGID) <0 ) perror(argv[0]);

	if( chmod("bar", S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH ) <0 ) perror(argv[0]);

	return 0;
}

运行结果如下,还是结合umask函数创建的文件继续进行实验。

ls -l bar foo
-rw------- 1 andywang andywang 0  6月 27 22:31 bar
-rw-r--r-- 1 andywang andywang 0  6月 27 22:31 foo

运行chmod后结果如下:

ls -l bar foo
-rw-r--r-- 1 andywang andywang 0  6月 27 22:32 bar
-rw-rwSrw- 1 andywang andywang 0  6月 27 22:32 foo

先来看看bar文件。比较简单,就是摒弃原来的文件权限,按照新的文件权限进行设置。

再来是foo文件,首先通过stat函数获得已有的文件权限,再来通过操作关闭“组执行”权限,并打开“执行时设置组ID”标志(在ls命令中以“S”体现)。此处要注意的一点是:“S”代表不包括执行位,“s”代表包括执行位。chmod不会影响文件的最后修改时间。

好了,说了这么多让我们来看看文件权限计算到底是怎么进行的,我们已经知道了文件权限标志是如何定义的,让我们来看看具体值:

#define	__S_ISUID	04000	/* Set user ID on execution.  */
#define	__S_ISGID	02000	/* Set group ID on execution.  */
#define	__S_ISVTX	01000	/* Save swapped text after use (sticky).  */
#define	__S_IREAD	0400	/* Read by owner.  */
#define	__S_IWRITE	0200	/* Write by owner.  */
#define	__S_IEXEC	0100	/* Execute by owner.  */

(以上定义位于/usr/include/x86_64-linux-gnu/bits/stat.h中)

好了,文件权限的实际值就是:

#define	S_ISUID __S_ISUID	 //04000,数字以“0”开头,说明是8进制数字
#define	S_ISGID	__S_ISGID //02000
# define S_ISVTX    __S_ISVTX //01000
#define    S_IRUSR    __S_IREAD    //0400
#define    S_IWUSR    __S_IWRITE    //0200
#define    S_IXUSR    __S_IEXEC    //0100
#define    S_IRWXU    (__S_IREAD|__S_IWRITE|__S_IEXEC) // 0700
#define    S_IRGRP    (S_IRUSR >> 3)   //左移三位,变为0040
#define    S_IWGRP    (S_IWUSR >> 3)   //左移三位,变为0020
#define    S_IXGRP    (S_IXUSR >> 3)   //左移三位,变为0010
/* Read, write, and execute by group.  */
#define    S_IRWXG    (S_IRWXU >> 3) //左移三位,变为0070
#define    S_IROTH    (S_IRGRP >> 3)    //在0040的基础上再次左移三位,变为0004
#define    S_IWOTH    (S_IWGRP >> 3)   //0002
#define    S_IXOTH    (S_IXGRP >> 3)    //0001
/* Read, write, and execute by others.  */
#define    S_IRWXO    (S_IRWXG >> 3) //0007

通过上面的计算,可以发现权限位的值与“chmod”命令中所使用的值完全相同,不同权限之间使用“|”运算进行计算,这一点没有什么问题。

再来看看关闭一个标志位所进行的计算,通过上面的例子可以看到,关闭一个权限位所使用的是“先取反,再进行与运算”。还是结合上面的代码来看一看,“S_IXGRP”的值为“0010”编程2进制就是“000001000”(首位的0不参与运算),此时代表仅有“组执行”权限开启,取反后变为“111110111”,仅有“组执行”位关闭,此时进行与运算,则“组执行”位一定会被关闭,而其他权限位则保持不变,如此就实现了关闭“组执行”位的功能。

APUE读书笔记-第四章 文件和目录

标签:

原文地址:http://blog.csdn.net/u012927281/article/details/51737312

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!