APUE第4章 文件和目录

1、概述

i、本章将描述文件系统的其他特征(如,文件类型、设置用户和组ID、文件访问权限)和文件性质

ii、从stat结构的每一个成员了解文件所有属性

iii、介绍各个可修改属性的函数

iv、UNIX文件系统结构及符号链接

2、函数stat,fstat,lstat及其结构struct stat{...};

#include<sys/stat.h>
int stat(const char *restrict pathname, struct stat *restrict buf);
int fstat(int fd, struct stat* buf);
int lstat(const char* restrict pathname, struct stat* restrict buf);
				//所有4个函数的返回值:若成功,返回0;若出错,返回-1

stat函数返回与此命名文件有关的信息结构。

fstat函数获得已在描述符fd上打开文件的有关信息。

lstat函数类似于stat,但是当命名的文件是一个符号链接时,lstat返回该符号链接的有关信息,而不是由该符号链接引用的文件的信息。

上述三个函数的第2个参数是一个指针,指向以下给出的结构:

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 *///普通文件
	struct timespec	st_atime;	/* time of last access *///最后访问时间
	struct timespec	st_mtime;	/* time of last modification *///最后修改时间
	struct timespec	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函数,从而可以获得有关一个文件的所有信息。

3、文件类型及利用宏类型判断文件类型

文件类型包括以下7种:

普通文件(regular file)、目录文件(directory file)、块特殊文件(block special file)、字符特殊文件(character special file)、FIFO、套接字(socket)、符号链接(symbolic link)。

文件类型信息包含在stat结构的st_mode成员中。可利用下述表格中的宏确定文件类型。这些宏的参数都是stat结构中的st_mode成员。

在<sys/stat.h>中的文件类型宏
文件类型
S_ISREG()普通文件
S_ISDIR()目录文件
S_ISCHR()字符特殊文件
S_ISBLK()块特殊文件
S_ISFIFO()管道或FIFO
S_ISLNK()符号链接
S_ISSOCK()套接字

对于IPC对象(队列、信号量、共享存储对象)也可视为文件。下表中的宏可用来从stat结构中确定IPC对象的结构,但这些宏的参数并非st_mode,而是指向stat结构的结构。

在<sys/stat.h>中的IPC类型宏
对象的类型
S_TYPEISMQ()消息队列
S_TYPEISSEM()信号量
S_TYPEISSHM()共享存储对象

以下程序是根据所输入的文件,打印其文件类型。

#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);
}

4、与进程相关联的ID

与每个进程相关联的用户ID和组ID
实际用户ID我们实际上是谁
实际组ID
有效用户ID用于文件访问权限检查
有效组ID
附属组ID
保存的设置用户ID由exec函数保存
保存的设置组ID

i、实际用户ID和实际组ID标识我们究竟是谁。这两个字段在登录时取自口令文件中的登录项。通常,在一个登录会话期间这些值并不改变,但是超级用户进程有方法改变它们。

ii、有效用户ID、有效组ID以及附属组ID决定了我们的文件访问权限。(通常,有效用户ID等于实际用户ID,有效组ID等于实际组ID)

iii、保存的设置用户ID和保存的设置组ID在执行一个程序时包含了有效用户ID和有效组ID的副本。

文件模式字中设置一个特殊标志,其含义是“当执行此文件时”,将进程的有效用户ID设置为文件所有者的用户ID(st_uid)。与此同时可以设置另一位,它将执行此文件的进程的有效组ID设置为文件的组所有者ID(st_gid)。在文件模式字中的这两位被称为设置用户ID(set-user-ID)位和设置组ID(set-group-ID)位。

5、文件访问权限、新文件和目录所有权

a、所有文件类型(目录、字符特别文件等)都有访问权限。根据访问权限位,分为3类,见下表。

9个访问权限位,取自<sys/stat.h>
st_mode屏蔽含义
S_IRUSR用户读
S_IWUSR用户写
S_IXUSR用户执行
S_IRGRP组读
S_IWGRP组写
S_IXGRP组执行
S_IROTH其他读
S_IWOTH其他写
S_IXOTH其他执行

i、用名字打开任一类型的文件时,对该名字中包含的每一个目录,包括它可能隐含的当前工作目录都应具有执行权限。即,对目录而言,R表示可以读目录,获得在该目录中所有文件名列表。X表示在目录中可搜索指定文件

ii、对于一个文件的读权限的目录决定了我们是否能够打开现有文件进行读操作。

iii、对于一个文件的写权限的目录决定了我们是否能够打开现有文件进行写操作。

iv、为了在open函数中对一个文件指定O_TRUNC标志,必须对该文件具有写权限。

v、为了在一个目录中创建一个新文件,必须对该目录具有写权限和执行权限。

vi、为了删除一个现有文件,必须对包含该文件的目录具有写权限和执行权限。对该文件本身则不需要有读、写权限。

vii、如果用7个exec函数中的任何一个执行某个文件,都必须对该文件具有执行权限。该文件还必须是一个普通文件。

 

b、进程每次打开、创建或删除一个文件时,内核就进行文件访问权限测试顺序:

i、有效用户是0(超级用户),允许访问。

ii、若进程的有效用户ID等于文件的所有者ID(也就是进程拥有此文件),那么如果所有者适当的访问权限位被设置,则允许访问,否则拒绝访问。

iii、若进程的有效组ID或附属组ID等于文件的组ID,那么如果组适当的访问权限位被设置,则允许访问,否则拒绝访问。

iv、若其他用户知觉的访问权限位被设置,则允许访问,否则拒绝访问。

 

c、新文件或目的所有权

i、新文件的用户ID设置为进程的有效用户ID

ii、新文件的组ID可以是进程的有效组ID

iii、新文件的组ID可以是它所在目录的组ID(继承目录的组ID)

6、access/umask/chmod/chown/fchown/lchown/truncate函数

a、access函数:测试实际用户能否访问给定文件。其是按实际用户ID和实际组ID进行访问权限测试的。

#include<unistd.h>
int access(const char *pathname, int mode);
						//两个函数的返回值:若成功,返回0;若出错,返回-1

其中,如果测试文件是否已经存,mode就为F_OK。否则mode是下图中所列常量的按位或。

access函数的标志,取自<unistd.h>
mode说明
R_OK测试读权限
W_OK测试写权限
X_OK测试执行权限

使用方法举例代码:

#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)//成功返回0,出错返回-1
		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);
}

 

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

#include<sys/stat.h>
mode_t umask(mode_t cmask);
						//返回值:之前的文件模式创建屏蔽字

其中,参数cmask是9个权限位(S_IRUSR、S_IWUSR等)中的若干个按位”或“构成的。

以下代码是创建两个文件,创建第一个时,umask值为0,创建第二个时,umask值禁止所有组和其他用户的访问权限。

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

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

int
main(void)
{
	umask(0);
	if (creat("foo", RWRWRW) < 0)//拥有rw
		err_sys("creat error for foo");
	umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);//被禁的权限
	if (creat("bar", RWRWRW) < 0)
		err_sys("creat error for bar");
	exit(0);
}

 

c、chmod函数:可以更改现有文件的访问权限。

#include<sys/stat.h>
int chmod(const char *pathname, mode_t mode);
						//返回值:若成功,返回0;若出错,返回-1

chmod函数在指定的文件上进行操作。

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

参数mode是一些常量的按位或。如S_ISUID、S_ISGID、S_ISVTX(保存正文)S_ITWXU、S_IRUSR、S_IWUSR、S_IXUSR、S_IRWXG、S_IRGRP、S_IWGRP、S_IXGRP、S_IRWXO、S_IROTH、S_IWOTH、S_IXOTH。

以代码演示chmod的用法:

#include "apue.h"

int
main(void)
{
	struct stat		statbuf;

	/* turn on set-group-ID and turn off group-execute */

	if (stat("foo", &statbuf) < 0)
		err_sys("stat error for foo");
	if (chmod("foo", (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0)
		err_sys("chmod error for foo");

	/* set absolute mode to "rw-r--r--" */

	if (chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0)
		err_sys("chmod error for bar");

	exit(0);
}


d、chown、fchown、lchown函数:可用于更改文件的用户ID和组ID。如果两个参数owner或group中的任意一个是-1,则对应的ID不变。

#include<unistd.h>
int chown(const char* pathname, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int lchown(const char* pathname, uid_t owner, gid_t group);
						//返回值:若成功,返回0;若出错,返回-1

lchown更改符号链接本身的所有者,而不是该符号链接所指向的文件的所有者。

fchown函数改变fd参数指向的打开文件的所有者,既然它在一个已打开的文件上操作,就不能用于改变符号链接的所有者。

 

e、truncate函数:截短文件。

#include<unistd.h>
int truncate(int fd, off_t length);
						//返回值:若成功,返回0;若出错,返回-1

将文件长度截断为length。如该文件以前的长度大于length,则超过length以外的数据就不再能访问。如果以前的长度小于length,文件长度将增加,在以前的文件尾端和新的文件尾端之间的数据将读作0(也就是可能在文件中创建一个空洞)。

7、文件长度、文件中的空洞

对于谱通文件,其文件长度可以是0,在开始读这种文件时,将得到文件结束(end-of-file)指示。

对于目录,文件长度通常是一个数的整数倍。

对于符号链接,文件长度是在文件名中的实际字节数(包含路径名哈)。

 

文件中的空洞是指:由所设置的偏移量超过文件尾端,并写入了某些数据(0,不会占用磁盘空间)后造成的。可利用du -s filename查看真实所占用的磁盘空间问题。

8、文件系统

UNIX文件系统的基本结构:可以把一个磁盘分成一个或多个分区,每个分区可以包含一个文件系统,i节点是固定长度的记录项,包含有关文件的大部分信息。

其中i节点和数据块部分,详情可见下图:

上述图的总结:

i、图中有两个目录块指向同一个i节点。每个i节点中都有一个链接计数,其值是指向该i节点的目录块数。只有当链接计数减少到0时,才可删除该文件(也就是可以释放该文件占用的数据块)。

ii、符号链接文件转跳了一下,即链接所指向的文件的名字。

iii、i节点包含的信息:文件类型、文件访问权限位、文件长度和指向文件数据块的指针等。目录块包含的信息:文件名和i节点编号。

iv、文件系统管理的独立性:因为目录项中的i节点编号指向同一文件系统中的相应i节点,一个目录块不能指向另一个文件系统的i节点。

v、更名不改变存储位置:当在不更换文件系统的情况下为一个文件重命名时,该文件的实际内容并未移动,只需构造一个指向现有i节点的新目录项,并删除老的目录项。链接计数不会改变。

9、link/unlink/remove/rename函数

a、link函数:创建一个指向现有文件的链接(因为任何一个文件可以有多个目录块指向其i节点)

#include<unistd.h>
int link(const char* exitingpath, const char* newpath);
						//返回值:若成功,返回0;若出错,返回-1

创建一个新目录项newpath,它引用现有文件existingpath。如果newpath已经存在,则返回出错。只创建newpath中的最后一个分量,路径中的其他部分应当已经存在。

 

b、unlink函数:删除一个现有的目录项。

#include<unistd.h>
int unlink(const char* pathname);
						//返回值:若成功,返回0;若出错,返回-1

该函数删除目录项,并将由pathname所引用文个把的链接计数减1。如果对该文件还有其他链接,仍可通过其他链接访问该文件的数据。如果出错,则不对该文件做任何更改。(也就是说,只有链接计数减为0时,才可以真正删除目录块)

删除一个文件,首先需要具有权限,其次检查打开该文件进程个数,最后再检查链接计数。只有链接计数减为0时,才可以真正删除目录块

unlink具有链接计数的特性常被程序用来确保即使是在程序崩溃时,它所创建的临时文件也不会遗留下来。常见用法:先open一个文件,再立即调用unlink,确保进程结束时,自动删除内容(小小编程技巧)。

 

c、remove函数:解除对一个文件或目录的链接,对于文件,remove的功能与unlink相同,对于目录,remove的功能与rmdir相同。

#include<stdio.h>
int remove(const char* pathname);
						//返回值:若成功,返回0;若出错,返回-1

remove的功能是unlink与rmdir的功能之和。

 

d、rename函数:可对文件或目录进行重命名。

#include<stdio.h>
int rename(const char* oldname, const char* newname);
						//返回值:若成功,返回0;若出错,返回-1

不作过多解释,记住,只对命名本身操作,而不是所引用的文件,不能对.和..重命名。

10、符号链接及symlink函数

符号链接也称为软链接。引入符号链接的原因是为了避开硬链接的一些限制。

硬链接限制:i、硬链接通常要求链接和文件位于同一文件系统中;ii、只有超级用户才能创建指向目录的硬链接(在底层文件系统支持的情况下);iii、硬链接形成的循环,破解起来极其麻烦。

能跟随符号链接(表明能够跟随符号链接处理所指向的文件)的函数:

access chdir chmod chown creat exec link open opendir pathconf stat truncate

不跟随符号链接的函数:

lchown lstat readlink remove rename unlink

 

symlink函数:创建一个符号链接。

#include<unistd.h>
int symlink(const char* actualpath, const char * sympath);//符号目录/文件名指向真实的目录/文件名
						//返回值:若成功,返回0;若出错,返回-1

该函数创建一个指向actualpath的新目录项sympath。在创建此符号链接时,并不要求actualpath已经存在。并且,actualpath和sympath并不需要位于同一文件系统中。

11、mkdir/rmdir及读目录相关函数、chdir、getcwd函数

a、mkdir函数:创建目录,rmdir函数:删除目录。

#include<sys/stat.h>
int mkdir(const char* pathname, mode_t mode);
						//返回值:若成功,返回0;若出错,返回-1

该函数创建一个新的空目录。其中,.和..目录项是自动创建的。所指定的文件访问权限mode由进程的文件模式创建屏蔽字修改。

常见的错误是指定与文件相同的mode(只指定读、写权限)。但是,对于目录通常至少要设置一个执行权限们,以允许访问该目录中的文件名。

#include<unistd.h>
int rmdir(const char * pathname);
						//返回值:若成功,返回0;若出错,返回-1

如果调用此rmdir函数使链接计数成为0,并且也没有其他进程打开此目录,则释放由此目录占用的空间。如果在链接计数达到0时,有一个或多个进程打开此目录,则在此函数返回前删除最后一个链接及.和..项。另外,在此目录中不能再创建新文件。但是在最后一个进程关闭它之前并不释放此目录。(为了使rmdir函数成功执行,该目录必须是空的)

 

b、读目录相关的函数

#include<dirent.h>
DIR* opendir(const char* pathname);
DIR* fdopendir(int fd);
						//两个函数返回值:若成功,返回指针;若出错,返回NULL
struct dirent* readdir(DIR* dp);
						//返回值:若成功,返回指针;若在目录尾部或出错,返回NULL
void rewinddir(DIR* dp);
int closedir(DIR* dp);
						//返回值:若成功,返回0;若出错,返回-1
long telldir(DIR* dp);
						//返回值:若成功,返回0;若出错,返回-1
void seekdir(DIR* dp, long loc);

struct dirent {
	ino_t	d_ino;		/* i-node number *///i-node的数量
	char	d_name[];	/* null-terminated filename *///文件名
};

DIR结构是一个内部结构,上述7个函数用这个内部结构保存当前正在被读的目录的有关信息。其作用类似于FILE结构。FILE结构由标准I/O库维护。

fdopendir可以把打开文件描述符转换成目录处理函数需要的DIR结构。

由opendir和fdopendir返回的指向DIR结构的指针由另外5个函数使用。opendir执行初始化操作,使第一个readdir返回目录中的第一个目录项。DIR结构由fdopendir创建时,readdir返回的第一项取决于传给fdopendir函数的文件描述相关联的文件偏移量。(目录中各目录的顺序与实现有关)。

opendir和readdir函数使用举例,代码如下所示。

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

int
main(int argc, char *argv[])
{
	DIR				*dp;
	struct dirent	*dirp;

	if (argc != 2)
		err_quit("usage: ls directory_name");

	if ((dp = opendir(argv[1])) == NULL)//打开一个目录
		err_sys("can't open %s", argv[1]);
	while ((dirp = readdir(dp)) != NULL)//读取目录里的文件
		printf("%s\n", dirp->d_name);

	closedir(dp);//关闭打开的目录
	exit(0);
}

 

c、进程调用chdir/fchdir函数可以更改当前工作目录。

#include<unistd.h>
int chdir(const char* pathname);
int fchdir(int fd);
						//两个函数的返回值:若成功,返回0;若出错,返回-1

此两函数中,分别用pathname或打开文件描述符来指定新的当前工作目录。

值得注意一点,因为当前工作目录是进程的一个属性,所以它只影响调用chdir的进程本身,而不影响其他进程。

 

d、getcwd函数:从当前工作目录(.)开始,用..找到其上一级目录,然后读其目录项,直到该目录项中的i节点编号与工作目录i节点编号相同。该函数按此方法,逐层上移,直到遇到根。这样就得到了当前工作目录完整的绝对路径名。

#include<unistd.h>
char* getcwd(char* buf, size_t size);//会沿着进程工作目录上溯到顶端,并将绝对路径存储在buf中
						//返回值:若成,返回buf;若出错,返回NULL

必须向getcwd函数传递两个参数,一个是缓冲区地址buf,另一个是缓冲区的长度是size(以字节为单位)。该缓冲区必须有足够的长度以容纳绝对路径名再加上一个终止null字节,否则返回出错。

以下程序将工作目录更改至一个指定的目录,然后调用getcwd,最后打印该工作目录。代码如下:

#include "apue.h"

int
main(void)
{
	char	*ptr;
	size_t		size;

	if (chdir("/usr/spool/uucppublic") < 0)
		err_sys("chdir failed");

	ptr = path_alloc(&size);	/* our own function *///This function is defined in Page70.
	if (getcwd(ptr, size) == NULL)
		err_sys("getcwd failed");

	printf("cwd = %s\n", ptr);
	exit(0);
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值