UNIX环境高级编程学习笔记(五)文件和目录

本章主要描述文件系统的其他特征和文件的性质。从stat函数开始,逐个说明stat结构的每一个成员,并详细说明UNIX文件系统的结构和符号链接。

1.四个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);
int fstatat(int fd, const char *restrict pathname, struct stat *restrict buf, int flag);

一旦给出pathname,stat函数将返回与此命名文件有关的信息结构。
fstat函数获得一个已在文件描述符fd上打开文件的有关信息。
lstat函数类似于stat,但是当命名的文件是一个符号链接时,lstat返回该符号链接的信息,而不是由该符号链接引用的文件的信息。
fstatat函数为一个相对于当前打开目录(由fd参数指向)的路径名返回文件信息。flag参数控制是否跟随着一个符号链接,当AT_SYMLINK_NOFOLLOW标志被设置时,fstatat不会跟随符号链接,否则,默认返回符号链接实际指向的文件的信息。若fd为AT_FDCWD,且pathname是一个相对路径,fstatat会计算相对于当前目录的pathname。若pathname是绝对路径,则fd被忽略。

stat类型的实际定义可能随具体实现有所不同,但基本形式是:

struct stat {
    mode_t          st_mode;    /* file type & mode (permissions) */
    ino_t           st_ino;     /* inode number (serial number) */
    dev_t           st_dev;     /* device number (file system) */
    dev_t           st_rdev;    /* device number for special file) */
    nlink_t         st_nlink;   /* number of hard 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 blocksize */
    blkcnt_t        st_blocks;  /* number of disk blocks allocated */
};

timespec结构类型按照秒和纳秒定义了时间,至少包括两个字段:

time_t tv_sec;
long tv_nsec;

2.文件类型
UNIX系统的大多数文件是普通文件或目录,但也有一些其他文件类型。文件类型主要包括:

  • 普通文件
  • 目录文件
  • 块特殊文件
  • 字特殊文件
  • FIFO,也称命名管道(named pipe)
  • 套接字
  • 符号链接

Linux 系统中的文件共分为 7 种类型:-dcbpls, 分别对应于以上七种文件类型。

文件类型信息包含在st_mode成员中。以下宏可以确定文件类型,参数为st_mode:

  • S_ISREG( )
  • S_ISDIR( )
  • S_ISCHR( )
  • S_ISBLK( )
  • S_ISFIFO( )
  • S_ISLNK( )
  • S_ISSOCK( )

POSIX.1允许将进程间通信(IPC)对象说明为文件。对应的确定其类型的宏的参数是指向stat结构的指针,而非st_mode:

  • S_TYPEISMQ( )
  • S_TYPEISSEM( )
  • S_TYPEISSHM( )

3.设置用户ID和组ID
与一个进程相关联的ID有6个或者更多:

  • 实际用户ID和实际组ID:标识我们究竟是谁
  • 有效用户ID、有效组ID和附属组ID:决定我们的文件访问权限
  • 保存的设置用户ID和保存的设置组ID:在执行程序时保存有效用户ID和有效组ID的副本

4.文件访问权限
每个文件都有访问权限。每个文件有9个访问权限位,分为三类:

st_mode屏蔽含义
S_IRUSR用户读
S_IWUSR用户写
S_IXUSR用户执行
S_IRGRP组读
S_IWGRP组写
S_IXGRP组执行
S_IROTH其他读
S_IWOTH其他写
S_IXOTH其他执行

文件访问权限规则:

  • 用名字打开任意类型文件,对该名字包含的每个目录都应具有执行权限
  • 对一个文件的读写权限分别决定了我们是否能够打开该文件进行读写操作
  • 在open函数中对文件指定O_TRUNC标志,必须对该文件具有写权限
  • 在目录中创建文件,必须对该目录具有写权限和执行权限
  • 在目录中删除文件,必须对包含该文件的目录具有写权限和执行权限,该文件本身访问权限不作要求
  • exec函数执行一个文件,则该文件必须是普通文件且具有执行权限

5.函数access和faccessat

按照实际用户ID和实际用户组进行访问权限测试。

#include <unistd.h>
int access(const char *pathname, int mode);
int faccessat(int fd, const char *pathname, int mode, int flag);

6.函数umask

umask函数为进程设置文件模式创建屏蔽字,并返回之前的值。

#include <sys/stat.h>
mode_t umask(mode_t cmask);

7.函数chmod、fchmod、fchmodat
这三个函数可以改变现有文件的访问权限

#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
int fchmodeat(int fd, const char *pathname, mode_t mode, int flag);

8.粘着位

在UNIX的早期版本中,有一位被称为粘住位(sticky bit)。如果一个可执行程序文件的这一位被设置了,那么在该程序第一次执行并结束时,该程序正文的一个文本被保存在交换区(程序的正文部分是机器指令部分)。这使得下次执行该程序时能较快地将其装入内存区。其原因是:在交换区,该文件是被连续存放的,而在一般的UNIX文件系统,文件的各数据块很可能是随机存放的。对于常用的应用程序,例如文本编辑程序和编译程序的各部分常常设置它们所在文件的粘住位。

如果对一个目录设置了粘着位,只有对该目录具有写权限的用户且满足下列条件之一,才能删除或重命名该目录下的文件:

  • 拥有此文件
  • 拥有此目录
  • 是超级用户

目录/tmp、/var/tmp是设置粘着位的典型候选者——任何用户都可以在这两个目录创建文件。

9.函数chown、fchown、fchownat和lchown

#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 fchownat(int fd, const char *pathname, uid_t owner, gid_t group, int flag);
int lchown(const char* pathname,uid_t owner,gid_t group);

这四个函数用于更改文件的用户ID和组ID。

10.文件长度

stat的成员st_size表示以字节为单位的文件的长度。此字段只对普通文件、目录文件和符号链接有意义。

文件空洞是由所设置的偏移量超过文件尾端,并写入了某些数据后造成的。

11.文件截断

truncate和ftruncate函数用于截断文件。

int truncate(const char* pathname,off_t length);
int ftruncate(int fd,off_t length);

12.文件系统
UNIX文件系统的基本结构。传统的基于BSD的UNIX文件系统称为UFS,UFS是以Berkeley快速文件系统为基础的。

一个磁盘被分为一个和多个分区。每个分区可以包含一个文件系统。i节点是固定长度的记录项,它包含有关文件的大部分信息。
每个分区(一个文件系统)包括:自举块、超级块、n个柱面组
每个柱面组包括:超级块副本、配置信息、i节点图、块位图、n个i节点、数据块
这里写图片描述
若仔细观察一个柱面的i节点和数据块部分,可以看到:
这里写图片描述

  • 列表内容在图中有两个目录项指向同一i节点。每个i节点中都有一个链接计数,其值是指向该i节点的目录项数。只有当链接计数减少为0时,才可删除该文件(也就是可以释放该文件占用的数据块)。这就是为什么”解除对一个文件的链接“操作并不总是意味着“释放该文件占用的磁盘块”的原因。这也就是为什么删除一个目录项的函数被称之为unlink而不是delete的原因。在stat结构中,链接计数包含在stnlink成员中,其基本系统数据类型是nlinkt。这种链接类型称之为硬链接。POSIX.1常数LINKMAX指定了一个文件链接数的最大值。
  • 另外一种链接类型称之为符号链接(symbolic link),也叫软链接。对于这种链接,该文件的实际内容(在数据块中)包含了该符号链接所指向的文件的名字。
  • i节点包含了文件有关的所有信息:文件类型、文件访问权限位、文件长度和指向文件数据块的指针等。stat结构中的大多数信息都取自i节点。只有两项数据存放在目录项中:文件名和i节点编号数。i节点编号数的数据类型是inot。
  • 因为目录项中的i节点编号数指向同一文件系统中的i节点,所以不能使一个目录项指向另一个文件系统的i节点。这就是为什么ln(1)命令(构造一个指向一个现存文件的新目录项),不能跨越文件系统的原因。
  • 当在不更改文件系统的情况下为一个文件更名时,该文件的实际内容并未移动,只需构造一个指向现存i节点的新目录项,并删除老的目录项。链接计数不会改变。mv(1)命令的通常操作方式。

13.函数link、linkat、unlink、unlinkat和remove
任何一个文件可以有多个目录项指向其i节点。link和linkat函数用于创建一个指向现有文件的链接。

int link(const char *existingpath, const char *newpath);
int linkat(int efd, const char *existingpath, int nfd, const char *newpath, int flag);

创建新目录项和增加链接计数应当是一个原子操作。
超级用户才能创建指向目录的硬链接,理由是大多数文件系统的实用程序都不能处理这种情况。

为了删除一个现存的目录项,可以调用unlink函数:

int unlink(const char *pathname);
int unlinkat(int fd, const char *pathname, int flag);

只有当链接计数达到0时,该文件的内容才可被删除。另一个条件也阻止删除文件的内容——只要有进程打开了该文件,其内容也不能删除。关闭一个文件时,内核首先检查使该文件打开的进程计数。如果该计数达到0,然后内核检查其链接计数,如果这也是0,那么就删除该文件的内容。
unlink的这种特性经常被程序用来确保即使是在程序崩溃时,它所创建的临时文件也不会遗留下来。进程用open或creat创建一个文件,然后立即调用unlink。因为该文件仍旧是打开的,所以不会将其内容删除。只有当进程关闭该文件或终止时(在这种情况下,内核关闭该进程所打开的全部文件),该文件的内容才被删除。
如果pathname是符号链接,那么unlink涉及的是符号链接而不是由该链接所引用的文件。
remove解除对一个文件或目录的链接,对于文件,remove和unlink功能相同。对于目录,remove的功能与rmdir相同:

int remove(const char *pathname);

14.函数rename和renameat
文件或目录可以使用rename和renameat进行重命名:

int rename(const char *oldname, const char *newname);
int renameat(int oldfd, const char *oldname, int newfd, const char *newname);

不能对.和..重命名。
15.符号链接
符号链接是对一个文件的间接指针,它与上一节所述的硬链接有所不同,硬链接直接指向文件的i节点。
当使用以名字引用一个文件的函数时,应当了解该函数是否处理符号链接功能。也就是是否跟随符号链接到达它所链接的文件。如若该函数处理符号链接功能,则该函数的路径名参数引用由符号链接指向的文件。否则,一个路径名参数引用链接本身,而不是由该链接指向的文件。
符号链接可能在文件系统中引入循环,大多数查找路径名的函数在这种情况发生时都返回出错。
16.创建和读取符号链接

int symlink(const char *actualpath, const char *sympath);
int symlinkat(const char *actualpath, int fd, const char *sympath);
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);

17.文件的时间
对每个文件维护三个时间:

字段说明例子ls(1)选项
st_atim文件数据最后访问时间read-u
st_mtim文件数据最后修改时间write默认
st_ctimi节点状态最后更改时间chmod、chown-c

18.函数futimens、utimensat、和utimes
一个文件的访问和修改时间可以用以下几个函数更改。futimens和utimensat可以指定纳秒级精度的时间戳。utimes函数对路径名

int futimens(int fd, const struct timespec times[2]);
int utimensat(int fd, const char *path, const struct timespec times[2], int flag);
int utimes(const char *pathname, const struct timeval times[2]);

timeval定义如下:

struct timeval {
    time_t tv_sec;  /* seconds */
    long tv_usec;   /* microseconds */
};

19.函数mkdir、mkdirat、rmdir
用mkdir、mkdirat函数创建目录,用rmdir函数删除目录。

int mkdir(const char *pathname, mode_t mode);
int mkdirat(int fd, const char *pathname, mode_t mode);
int rmdir(const char *pathname);

20.读目录
对某个目录具有存取许可权的任一用户都可读该目录,但是为防止文件系统发生混乱,只有内核才能写目录。

DIR *opendir(const char *pathname);
DIR *fdopendir(int fd);
struct dirent *readdir(DIR *dp);
void rewinddir(DIR *dp);
int closedir(DIR *dp);
long telldir(DIR *dp);
void seekdir(DIR *dp, long loc);

21.函数chdir、fchdir和getcwd

每个进程都有一个当前工作目录,此目录是搜索所有相对路径名的起点(不以斜线开始的路径名为相对路径名)。当用户登录到UNIX系统时,其当前工作目录通常是口令文件( /etc/passwd )中该用户登录项的第6个字段—用户的起始目录。当前工作目录是进程的一个属性,起始目录则是登录名的一个属性。

进程调用chdir或fchdir函数可以更改当前工作目录。getcwd获取当前工作目录的决定路径名。

int chdir(const char *pathname);
int fchdir(int fd);
char *getcwd(char *buf, size_t size);

22.设备特殊文件
stdev和strdev这两个字段经常引起混淆,讨论ttyname函数时,需要使用这两个字段。有关规则很简单:

  • 每个文件系统都由其主、次设备号而为人所知。设备号所用的数据类型是基本系统数据类型devt。一个磁盘经常包含若干个文件系统。
  • 我们通常可以使用两个大多数实现都定义的宏:major和minor来存取主、次设备号。这就意味着我们无需关心这两个数是如何存放在devt对象中的。
  • 系统中每个文件名的stdev值是文件系统的设备号,该文件系统包含了该文件名和其对应的i节点。
  • 只有字符特殊文件和块特殊文件才有strdev值。此值包含该实际设备的设备号。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值