4.1 stat
stat/lstat函数返回文件的信息结构体stat。st_mode中包含了文件的格式信息。
例子
判断文件或目录类型
#include "apue.h"
int main(int argc, char *argv[]) {
int i;
struct stat s;
char *ptr;
for (i = 1; i < argc; i++) {
if (lstat(argv[i], &s) == -1)
err_sys("lstat error");
if (S_ISREG(s.st_mode)) {
ptr = "regular file";
} else if (S_ISDIR(s.st_mode)) {
ptr = "directory";
} else if (S_ISLNK(s.st_mode)) {
ptr = "link";
} else if (S_ISCHR(s.st_mode)) {
ptr = "character";
} else if (S_ISBLK(s.st_mode)) {
ptr = "block";
} else {
ptr = "unknown type";
}
printf("%s is %s\n", argv[i], ptr);
}
}
4.2 设置用户ID组ID
ID分类
实际用户ID组ID 表示我们实际是谁
有效用户ID组ID 用于文件权限检查
保存用户ID组ID 由exec保存
通常,有效用户ID等于实际用户ID,有效组ID等于实际组ID。
可以通过设置st_mode中的标志位,在进程执行文件时将有效用户ID组ID修改为文件所有者的用户ID组ID,从而使执行文件的进程拥有超过进程本身的权限。
4.4 文件访问权限
st_mode值也包含了对文件的访问权限位。分为用户(所有者)权限位,组权限位,其他用户权限位。
对文件的权限比较好理解。对目录的读权限允许我们获得在该目录中所有文件名的列表。对目录的执行权限使我们可进入该目录,访问目录中的文件。为了在一个目录中创建或删除一个文件,必须对该目录具有写权限和执行权限。
访问文件的时候都会进行权限校验,对比文件的权限属性和进程的有效用户ID和组ID。
- 若有效用户ID为0,则允许访问
- 有效用户ID等于文件所有者ID,且权限位满足设置
- 有效组ID属于文件组ID,且权限位设置满足
- 其他用户权限位设置满足
4.5 新文件和目录所有权
新文件目录的所有者ID是当前进程的有效用户ID,组ID可以是进程的有效组ID,可以是当前目录的组ID。在mac下默认是后者,Linux下取决于目录的设置组ID位是否被设置。
查看有效用户ID,组ID
查看文件ID
4.6 access和faccessat
使用实际用户ID和组ID检验文件权限
#include "apue.h"
#include "fcntl.h"
int main(void) {
int fd;
char *file = "huahua.txt";
//以只读打开
if ((fd = open(file, O_RDONLY|O_CREAT, 0400)) == -1)
err_sys("open error");
else
printf("open good");
//没有写权限
if (access(file, W_OK) < 0)
err_sys("can not write");
else
printf("write access good");
close(fd);
//以读写权限打开
if ((fd = open(file, O_RDWR)) == -1)
err_sys("open error");
else
printf("open with write ok");
//可写
if (access(file, W_OK) < 0)
err_sys("can not write");
else
printf("write access ok");
exit(0);
}
4.7 umask 权限屏蔽
每个进程都有一个文件模式创建屏蔽字,在进程创建一个新文件或新目录时一定会使用文件模式创建屏蔽字指定哪些权限被关闭。
umask函数为进程设置文件模式创建屏蔽字。
4.8 chmod
chmod、 fchmod和fchmodat这3个函数使我们可以更改现有文件的访问权限。
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
int fchmodat(int fd, const char *pathname, mode_t mode, int flag);
为了改变一个文件的权限位,进程的有效用户ID必须等于文件的所有者ID,或者该进程必须具有超级用户权限。
例子
#include "apue.h"
#include "fcntl.h"
int main(void) {
char *file1 = "huahua1.txt";
char *file2 = "huahua2.txt";
//设置新生成的文件为rwxrwxrwx
umask(0);
if ((open(file1, O_RDONLY|O_CREAT, 0777)) == -1)
err_sys("open error");
else
printf("open good");
//设置新生成文件为rwx--x--x
umask(S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
if ((open(file2, O_RDONLY|O_CREAT, 0777)) == -1)
err_sys("open error");
else
printf("open with write ok");
//修改file1为rw-r--r--
chmod(file1, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
exit(0);
}
4.9 黏着位
S_ISVTX 黏着位,是为了让文件保留在交换区提高可执行程序的执行效率。设置了黏着位的目录,只有对目录有写权限并且拥有此文件/拥有此目录/是超级用户,才能删除或重命名目录下的文件。
4.10 修改文件所有者
#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)
基于BSD的系统一直规定只有超级用户才能更改一个文件的所有者。这样做的原因是防止用户改变其文件的所有者从而摆脱磁盘空间限
额对他们的限制。System V则允许任一用户更改他们所拥有的文件的所有者。
4.11 文件长度
stat结构成员st_size表示以字节为单位的文件的长度。此字段只对普通文件、目录文件和符号链接有意义。
对于目录,文件长度通常是一个数(如16或512) 的整倍数.
对于符号链接,文件长度是在文件名中的实际字节数。
du -s file //文件占用的512字节块的块数
ls -l file //显示实际字节数
对于含有空洞的文件会看到ls出来的大小更大。如果使用io对文件进行复制,空洞会被填满为0,du的大小变大。
4.12 截断文件
#include <unistd.h>
int truncate(const char *pathname, off_t length);
int ftruncate(int fd, off_t length)
把给定文件截断为指定长度,如果length大于实际长度还会扩展。
4.13 HFS文件系统
文件系统的层次:
磁盘的每个分区可以包含一个单独的文件系统。
文件系统由自举块,超级块和多个柱面组构成。
柱面组内包括多个i节点和数据块。
从文件表目录项指向i节点的链接是硬链接。每个i节点中都有一个链接计数,其值是指向该i节点的目录项数。只有当链接计数减少至0时才可删除该文件。在stat结构中,链接计数包含在st_nlink成员中。
符号链接文件的实际内容(在数据块中)包含了该符号链接所指向的文件的名字。
因为目录项中的i节点编号指向同一文件系统中的相应i节点,一个目录项不能指向另一个文件系统的i节点。
目录文件的链接计数大于等于2,因为每个目录下都至少有.和…两个子目录,每个子目录都会增加一个链接计数。
4.14 link和unlink
任何一个文件可以有多个目录项指向其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。
为了删除一个现有的目录项,可以调用unlink函数。
#include <unistd.h>
int unlink(const char *pathname);
int unlinkat(int fd, const char *pathname, int flag);
这两个函数删除目录项,并将由pathname所引用文件的链接计数减1。
为了解除对文件的链接,必须对包含该目录项的目录具有写和执行权限。如果pathname是符号链接名,则只删除符号链接本身。
删除文件内容的条件:
- 打开该文件的进程个数为0
- 文件的链接计数为0
可以用 remove函数解除对一个文件或目录的链接。对于文件,remove 的功能与unlink相同。 对于目录,remove的功能与rmdir相同。
例子
#include "apue.h"
#include "fcntl.h"
int main(void) {
char *file = "huahua.txt";
char *file2 = "huahua2.txt";
int fd;
char buf[] = "hello world";
if ((fd = open(file, O_RDWR|O_CREAT, 0600)) == -1) //创建文件file
err_sys("open error\n");
if (write(fd, buf, strlen(buf)) < 0)
err_sys("write error\n");
printf("open done\n");
sleep(5);
if (link(file, file2) == -1) //创建新链接file2引用file
err_sys("link error\n");
else
printf("link done\n");
sleep(5);
//删除file2
if (unlink(file2) == -1)
err_sys("unlink error\n");
else
printf("unlink file done\n");
sleep(5);
//删除file
if (unlink(file) == -1)
err_sys("unlink error\n");
else
printf("unlink file2 done\n");
sleep(5);
printf("done\n");
exit(0);
}
4.15 重命名
文件或目录可以用rename函数或者renameat函数进行重命名。重命名对文件i节点的内容和引用计数都不改变。
#include <stdio.h>
int rename(const char *oldname, const char *newname);
int renameat(int oldfd, const char *oldname, int newfd, const char *newname);
在重命名时根据oldname是目录还是文件,newname已经存在是否,有不同的操作。
oldname是文件,newname若存在必须是文件,则先删除new目录项再重命名。
oldname是目录,newname如果存在必须是个空目录,会删除new目录然后重命名。
不能对.和…重命名。
4.16 符号链接
硬链接直接指向文件的i节点,硬链接通常要求链接和文件位于同一文件系统中,只有超级用户才能创建指向目录的硬链接。
对符号链接以及它指向何种对象并无任何文件系统限制,任何用户都可以创建指向目录的符号链接。 符号链接一般用于将一个文件或整个目录结构移到系统中另一个位置。
4.17 创建和读取符号链接
创建符号链接的源路径不一定存在;读取符号链接拿到实际路径名称。
#include "apue.h"
int main(void) {
char buf[1024];
int len;
char* actualPath = "~/bin2";
char* symPath = "fakebin";
if (symlink(actualPath, symPath) == -1) { //创建符号链接
err_sys("link error");
}
if ((len = readlink(symPath, buf, 1024)) == -1) { //读取符号链接的真实内容
err_sys("read link error");
}
buf[len] = '\0';
printf("%s", buf);
exit(0);
}
4.18 文件时间
每个文件维护三个时间,最近访问时间(st_atim),修改时间(st_mtim)和状态更改时间(st_ctim)。
修改时间是文件内容最后一次被修改的时间。 状态更改时间是该文件的i节点最后一次被修改的时间。修改文件权限,所有者都只修改i节点状态,没有修改内容。
4.19 mkdir和rmdir
int mkdir(const char *pathname, mode_t mode);
int mkdirat(int fd, const char *pathname, mode_t mode);
对于目录通常至少要设置一个执行权限位,以允许访问该目录中的文件名。
基于BSD的系统中,新创建的文件和目录总是继承父目录的组ID,这与是否设置了设置组ID位无关。
用rmdir函数可以删除一个空目录。 空目录是只包含.和…这两项的目录。
#include <unistd.h>
int rmdir(const char *pathname);
如果调用此函数使目录的链接计数成为 0,并且也没有其他进程打开此目录,则释放由此目录占用的空间。
如果在链接计数达到0时,有一个或多个进程打开此目录,则在此函数返回前删除最后一个链接及.和…项。 另外, 在此目录中不能再创建新文件。
4.20 读目录
只有内核才能写目录。
一个目录的写权限位和执行权限位决定了在该目录中能否创建新文件以及删除文件,它们并不表示能否写目录本身。
目录的实际格式依赖于 UNIX 系统实现和文件系统的设计。 系统提供了接口用于屏蔽底层的具体实现。
DIR *opendir(const char *pathname);
DIR *fdopendir(int fd);
4.21 工作目录
每个进程都有一个当前工作目录,此目录是搜索所有相对路径名的起点。
修改当前进程的工作目录。
#include <unistd.h>
int chdir(const char *pathname);
获得当前目录的绝对路径
#include <unistd.h>
char *getcwd(char *buf, s i z e_t size);
int main(void) {
char buf[1024];
getcwd(buf, 1024);
printf("%s\n", buf);
if(chdir("/Users/lifan/VmShare/APUE/APUE/4_file_and_directory") < 0) {
err_sys("chdir error");
}
getcwd(buf, 1024);
printf("%s\n", buf);
exit(0);
}
4.23 设备文件
每个文件系统所在的存储设备都由其主、次设备号表示。设备号所用的数据类型是基本系统数据类型dev_t。
主设备号标识设备驱动程序,有时编码为与其通信的外设板;次设备号标识特定的子设备。