您的当前位置:首页正文

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

2024-11-30 来源:个人技术集锦

本章主要描述文件系统的其他特征和文件的性质。从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);

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;
  • 普通文件
  • 目录文件
  • 块特殊文件
  • 字特殊文件
  • 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文件系统,文件的各数据块很可能是随机存放的。对于常用的应用程序,例如文本编辑程序和编译程序的各部分常常设置它们所在文件的粘住位。

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

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.文件长度

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

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节点的目录项数。只有当链接计数减少为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)命令的通常操作方式。
int link(const char *existingpath, const char *newpath);
int linkat(int efd, const char *existingpath, int nfd, const char *newpath, int flag);
int unlink(const char *pathname);
int unlinkat(int fd, const char *pathname, int flag);
int remove(const char *pathname);
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 */
};
int mkdir(const char *pathname, mode_t mode);
int mkdirat(int fd, const char *pathname, mode_t mode);
int rmdir(const char *pathname);
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

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值。此值包含该实际设备的设备号。
显示全文