文章目录
chapter04 文件和目录(二)
函数chown,fchown,fchownat和lchown
下面几个chown
函数可用于更改文件的用户ID和组ID。如果两个参数owner
或group
中的任意一个是-1
,则对应的ID不变:
#include<unistd.h>
int chown(const char* pathname,uid_t owner,git_t group);
int fchown(int fd,uid_t owner,git_t group);
int fchownat(int fd,const char* pathname,uid_t owner,git_t group,int flag);
int lchown(const char* pathname,uid_t owner,git_t group);
这里的
lchown
可以更改符号链接本身,UNIX中很多命名以l-
开头的函数都可以改变符号链接本身,而不是跟随符号链接.
文件长度
stat
结构成员st_size
表示以字节为单位的文件的长度。此字段只对普通文件、目录文件和符号链接有意义:
- 对于普通文件,其文件长度可以是0,在开始读这种文件时,将得到文件结束(end-of-file)指示
- 对于目录,文件长度通常是一个数(如16或512)的整倍数
- 对于符号链接,文件长度是在文件名中的实际字节数,比如
lib->user/lib
这样的符号链接,文件长度7就是路径名usr/lib
的长度
空洞是由所设置的偏移量超过文件尾端,并写入某些数据后造成的。需要注意的是,空洞不会实际写入字节数据,但是如果使用实用程序复制这个文件,那么所有这些空洞都会被填满,其中所有实际数据字节皆填写为0.
文件截断
为了截断文件可以调用函数truncate
和ftruncate
,所谓文件截断,可以理解为将文件长度设为指定值。
#include<unistd.h>
int truncate(const char* pathname,off_t length);
int ftruncate(int fd,off_t length);
文件系统
我们可以把一个磁盘分成一个或多个分区。每个分区可以包含一个文件系统。i节点是固定长度的记录项,它包含有关文件的大部分信息。
更加详细的柱面组合i节点核数据块如下图所示:
以下几点需要注意:
- 每个i节点中都有一个链接计数,其值是指向该i节点的目录项数。只有当链接计数减少至0时,才可删除该文件。在stat结构中,链接计数包含在
st_nlink
成员中,这种链接类型也称为硬连接
。在POSIX.1中LINK_MAX
指定了一个文件链接数的最大值。 - 另外一种链接类型称为
符号链接
.符号链接
文件的实际内容包含了该符号链接所指向的文件的名字 - i节点包含了文件有关的所有信息:文件类型、文件访问权限位、文件长度和指向文件数据块的指针等。
stat
结构中的大多数信息都取自i节点,只有两项重要数据存放在目录项中:文件名和i节点编号. - 因为目录项中的i节点编号指向同一文件系统中的相应i节点,一个目录项不能指向另一个文件系统的i节点
- 当在不更换文件系统的情况下一个文件重命名时,该文件的实际内容并未移动,只需构造一个指向现有i节点的新目录项,并删除老的目录项。链接计数不会改变。
函数link、linkat、unlink、unlinkat和remove
任何一个文件可以有多个目录项指向其i节点。创建一个指向现有文件的(硬)链接的方法是使用link
函数或linkat
函数
#include<unistd.h>
int link(const char* existingpath,const char* newpath);
int linkat(int efd,const char* existingpath,int nfd,const char* newpath,int flag);
这两个函数创建一个新目录项newpath
,它引用现有文件existingpath
.
当现有文件是符号链接时,由flag
参数来控制linkat
函数是创建指向现有符号链接的链接还是创建指向现有符号链接所指向的文件的链接。如果在flag
参数中设置了AT_SYMLINK_FOLLOW
标志,就创建指向符号链接目标的链接。如果这个标志被清除了,则创建一个指向符号链接本身的链接。
为了删除一个现有的目录项,可以调用unlink
函数:
#include<unistd.h>
int unlink(const char* pathname);
int unlink(int fd,const char* pathname,int flag);
当链接计数达到0时,该文件的内容才可被删除。另一个条件也会阻止删除文件的内容————只要有进程打开了该文件,其内容也不能删除。关闭一个文件时,内核首先检查打开该文件的进程个数;如果这个计数达到0,内核再去检查其链接计数;如果计数也是0,那么就删除该文件的内容。
我们也可以用remove
函数解除对一个文件或目录的链接。对于文件,remove
的功能与unlink
相同。对于目录,remove
的功能与rmdir
相同
#include<stdio.h>
int remove(const char* pathname);
实例:打开一个文件,然后unlink它
#include "apue.h"
#include<fcntl.h>
int main(void)
{
if(open("tempfile",O_RDWR)<0)
err_sys("open error");
if(unlink("tempfile")<0)
err_sys("unlinnk error");
printf("file unlinked\n");
sleep(15);
printf("done\n");
exit(0);
}
unlink
这种特性可以用于处理程序崩溃时,创建的临时文件也不会遗留下来。
函数rename和renameat
文件或目录可以用rename
函数或者renameat
函数进行重命名
#include<stdio.h>
int rename(const char* oldname,const char* newname);
int renameat(int oldfd,const char* oldname,int newfd,const char* newname);
使用rename
有以下几点需要注意:
- 如若
oldname
或newname
引用符号链接,则处理的是符号链接本身,而不是它所引用的文件 - 不能对
.
或..
重命名。更确切地说,.
和..
都不能出现在oldname
和newname
的最后部分 - 作为一个特例,如果
oldname
和newname
引用同一文件,则函数不做任何更改而成功返回
符号链接
符号链接是对一个文件的间接指针,它与上一节所述的硬连接有所不同,硬链接直接指向文件的i节点。引入符号链接的原因是为了避免硬链接的一些限制:
- 硬链接通常要求链接和文件位于同一文件系统中
- 只有超级用户才能创建指向目录的硬链接
当使用以名字引用文件的函数时,应当了解该函数是否处理符号链接。也就是该函数是否跟随符号链接到达它所链接的文件。如若该函数具有处理符号链接的能力,则其路径名参数引用由符号链接指向的文件。否则,一个路径名参数引用链接本身,而不是由该链接指向的文件。
各个函数对符号链接的处理如下图所示:
使用符号链接可能在文件系统中引入循环。大多数查找路径名的函数在这种情况发生时都将出错返回,errno
值为ELOOP
.对于符号链接造成的循环很容易消除,因为unlink
并不跟随符号链接,所以可以用unlink
消除循环。但是如果创建了构成循环的硬循环,那么就很难消除它,这就是为什么link
函数不允许构造指向目录的硬链接的原因。
创建和读取符号链接
可以用symlink
或symlinkat
函数创建一个符号链接:
#include<unistd.h>
int symlink(const char* actualpath,const char* sympath);
int symlink(const char* actualpath,int fd,const char* sympath);
函数创建一个指向actualpath
的新目录项sympath
,在创建此符号链接时,并不要求actualpath
已经存在,并且actualpath
和sympath
并不需要位于同一文件系统中
因为open
函数跟随符号链接,所以需要有一种方法打开该链接本身,并读该链接中的名字。readlink
和readlinkat
函数提供了这种功能
#include<unistd.h>
ssize_t readlink(const char* restrict pathname,char* restrict buf,size_t bufsize);
ssize_t readlinkat(int fd,const char* restrict pathname,char* restrict buf,size_t bufsize);
文件的时间
对每个文件都维护了3个时间字段,分别是st_atim
(文件数据的最后访问时间)、st_mtim
(文件数据的最后修改时间)、st_ctim
(i节点状态的最后更改时间).而每个文件属性所保存的实际精度依赖于文件系统的实现,对于把时间戳记录在秒级的文件系统来说,纳秒这个字段就会被填充为0.对于时间戳的记录精度高于秒级的文件系统来说,不足秒的值被转换成纳秒并记录在纳秒这个字段中.
注意,修改时间(st_mtim)和状态更改时间(st_ctim)之间的区别,修改时间是文件内容最后一次被修改的时间,状态更改时间是该文件的i节点最后一次被修改的时间。同时,系统并不维护一个i节点的最后一次访问时间,所以access
和stat
函数并不更改这3个时间中的任一个。
函数futimens、utimensat和utimes
#include<sys/stat.h>
int futimens(int fd,const struct timespec times[2]);
int utimensat(int fd,const char* path,const struct timespec times[2],int flag);
这两个函数的times
数组参数的第一个元素包含访问时间,第二元素包含修改时间,当调用这两个函数时,状态更改时间会自动更新。
SUS标准定义utimes
函数修改时间:
#include<sys/time.h>
int utimes(const char* pathname,const struct timeval times[2]);
同理,使用utimes
时,状态更新时间会自动更新。
函数mkdir、rmdir、opendir和readdir
用mkdir
函数创建目录,用rmdir
函数删除目录,用opendir
打开目录,用readdir
读取目录项
#include<sys/stat.h>
#include<unistd.h>
#include<dirent.h>
int mkdir(const char* pathname,mode_t mode);
int rmdir(const char* pathname); //可以删除一个空目录,空目录是只包含.和..这两项的目录
DIR* opendir(const char* pathname);
struct dirent* readdir(DIR* dp);
其中dirent
的定义至少包含下列两个成员:
ino_t d_ino; /* i-node number */
char d_name[]; /* null-terminated filename */
设备特殊文件
每个文件系统所在的存储设备都由其主、次设备号表示:主设备号标识设备驱动程序,次设备号标识特定的子设备。
系统中与每个文件名关联的st_dev
值是文件系统的设备号,该文件系统包含了这一文件名以及与其对应的i节点。
只有字符特殊文件和块特殊文件才有st_rdev
值,此值包含实际设备的设备号。
我们通常可以使用两个宏:major
和minor
来访问主、次设备号。
打印设备号
#include "aput.h"
#ifdef SOLARIS
#include<sys/mkdev.h>
#endif
int main(int argc,char* argv)
{
int i;
struct stat buf;
for(i=1;i<argc;i++)
{
printf("%s: ",argv[i]);
if(stat(argv[i],&buf)<0){
err_ret("stat error");
continue;
}
printf("dev = %d/%d",major(buf.st_dev),minor(buf.st_dev));
if(S_ISCHR(buf.st_mode) || S_ISBLK(buf.st_mode)){
printf(" (%s) rdev=%d/%d",(S_ISCHR(buf.st_mode))?"character":"block",major(buf.st_rdev),minor(buf.st_rdev));
}
printf("\n");
}
exit(0);
}