APUE学习笔记2——第四章——文件和目录

来源:互联网 发布:java object的方法 编辑:程序博客网 时间:2024/05/22 19:54

APUE学习笔记2——第四章——文件和目录


学号:16340043
中山大学
本博客为《UNIX环境高级编程》的学习笔记,希望能对大家有所帮助


1.前面的废话

额,其实这个笔记是昨天做的啦(今天把它搞成了markdown格式),所以今天会放两篇啦(今天直接用这个格式记笔记了)。习题都还没做完…先空着再补吧…好多事要干


2.博客正文

4.1 引言

本章将描述文件系统的其他特性和文本的所有属性,以及说明修改这些属性的各个函数

4.2 函数stat、fstat、fstatat和lstat

本章主要讨论4个函数和他们的返回信息

#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:文件名字

一旦给出pathname:

stat函数将返回与此命名文件相关的信息结构
lstat函数类似于stat函数,但会返回符号链接(而不是它引用的文件)的有关信息
fstat函数获取描述符fd上打开文件的相关信息
fstatat在fd为AT_FDCWD相对于fd所指当前目录的路径名返回统计信息(path为绝对路径名时fd被忽略)

  • flag:控制是否跟随一个符号链接
  • buf:一个指针,指向我们想要的信息结构(很长,后面会一一描述的吧,相关信息我加$号
  • 返回值:若成功,返回0;若出错,返回-1
  • $成员timespec结构按照秒和纳秒定义了时间,至少包含:
  time_t tv_sec;  long tv_nsec;

使用stat函数最多的地方可能就是ls -l命令。(终于看到眼熟的东西了/哭

4.3 文件类型

  • 1)普通文件(regular file)包含了某种形式的数据,无论数据是文本还是二进制,对UNIX内核无区别
  • 2)目录文件(directory file)包含了其他文件的名字以及指向与这些文件有关信息的指针
    内核才能直接写目录文件,进程必须使用本章介绍的函数来更改目录
  • 3)快特殊文件(block special file)提供对设备带缓冲的访问(每次都是固定长度)
  • 4)字符特殊文件(character special file)提供对设备不带缓冲的访问(每次长度可变)
  • 5)FIFO用于进程间的通信,也称谓命名管道
  • 6)套接字(socket)用于进程间的网络通信,也可用于在一台宿主机上进程间的非网络通信
  • 7)符号链接(symbolic link)指向另一个文件
  • $成员st_mode描述了文件类型信息
    可通过S_ISREG(buf.st_mode) 这样的宏来判断文件类型(好多我不列出来了)
    特殊地,进程间通信对象(IPC)也允许被说明为文件,它们也有自己一套宏

tip:在命令行末端输入\告知shell要在下一行继续键入命令

4.4 设置用户ID和设置组ID

与一个进程相关联的ID有6个或更多:

ID 说明 实际用户ID、实际组ID 我们实际上是谁 有效用户ID、有效组ID1、附属组ID 用于文件访问权限检查 保存的设置用户ID、保存的设置组ID 由exec函数保存
  • $成员st_uid、st_gid分别指定一个文件的所有者和组所有者
    设置用户ID位和设置用户组ID位可分别用常量S_ISUID和S_ISGID测试

4.5 文件访问权限

每个文件有9个访问权限位:

访问权限位 含义 S_IRUSR S_IWUSR S_IXUSR 用户读 用户写 用户执行 S_IRGRP S_IWGRP S_IXGRP 组读 组写 组执行 S_IROTH S_IWOTH S_IXOTH 其他读 其他写 其他执行

chmod命令可用于修改这九个权限位(u用户 g组 o其他)

当我们用名字打开任一类型的文件时,对该名字中包含的每个目录都应具有执行权限

进程每次打开、创建或删除一个文件时,内核就进行文件访问权限测试
除了超级用户,别的用户都要设置适当地访问权限来操作该文件

4.6 新文件和目录的所有权

  • 新文件的用户ID设置为进程的有效用户ID
  • 新文件的组ID可以是进程的有效组ID或它所在目录的组ID(二选其一)

4.7 函数access和faccessat

access和faccessat按实际用户ID和实际组ID测试访问权限:

#include <unistd.h>int access(const char *pathname, int mode);int faccessat(int fd, const char *pathname, int mode, int flag);
  • mode: R_OK测试读权限 W_OK测试写权限 X_OK测试执行权限
  • fd:为AT_FDCWD时为相对路径
  • flag:设置为AT_EACCESS时检查用的是有效XX而不是实际XX
  • 返回值:若是,返回0;若不是,返回-1

4.8 函数umask

umask为进程设置文件模式创建屏蔽字:

#include <sys/stat.h>mode_t umask(mode_t cmask);
  • cmask:是4.5中列出的9个常量中的若干位”或操作”构成的
  • 返回值:之前的文件模式创建屏蔽字

在文件模式创建屏蔽字中为1的位,在文件mode中的相应位一定被关闭

通过更改umask的值可以设置权限2:

用户读 用户写 用户执行 组读 组写 组执行 其他读 其他写 其他执行 0400 0200 0100 0040 0020 0010 0004 0002 0001

命令行中:

命令 用途 umask 打印当前文件模式创建屏蔽字 umask -S 打印符号格式 umask -数字 更改文件模式创建屏蔽字

4.9 函数chmod、fchmod和fchmodat

chmod、fchmod和fchmodat用于更改现有文件访问权限:

#include <sys/stat.h>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);
  • chmod在指定文件上进行操作
  • fchmod对打开文件进行操作
  • fchmodat中的fd参数与前面情况一样
  • flag:当设置了AT_SYMLINK_NOFOLLOW标志时,fchmodat不会跟随符号链接
  • mode:比起4.5列出的,多了:
访问权限位 含义 S_ISUID 执行时设置用户ID S_ISGID 执行时设置组ID S_ISVTX 保存正文(粘着位 4.10讲) S_IRWXU 用户(执行者)读、写和执行 S_IRWXG 组读、写和执行 S_IRWXO 其他读、写和执行
  • 返回值:若成功,返回0;若失败,返回-1

可以通过 statbuf.st_mode & ~S_IXGRP 这样的位操作来关闭某一位哦

tip:chmod更新的是i节点最近一次被修改的时间,而ls -l列出的是最后修改文件内容的时间,故看不到变化

若新文件的组ID不等于进程的有效组ID或进程的附属组ID中的一个(可能是父目录的组ID),而且用户没有超级权限,那么设置组ID位会被自动关闭3

4.10 粘着位

  • 老系统的粘着位:
    如果一个程序的粘着位被设置,其正文部分(机器指令)会被保存在交换区,下次执行时能更快地载入内存
    现在,由于大多较新的UNIX系统都配置了虚拟存储系统和快速文件系统,所以不再需要这种技术
  • 新系统的粘着位:
    如果一个目录设置了粘着位,只有对该目录具有写权限且满足下列条件之一,才能删除或重命名该目录下的文件:
    • a拥有此文件
    • b拥有此目录
    • c是超级用户

有了粘着位,用户可在/tmp目录中读、写和执行,但不能删除或重命名他人的文件

4.11 函数chown、fchown、fchownat和lchown

chown、fchown、fchownat和lchown用于更改用户ID和组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);
  • owner或group中的任意一个是-1,则对应ID不变
  • 其他参数类似stat那四个函数
  • flag:若设置AT_SYMLINK_NOFOLLOW,则行为与lchown相同,否则,与chown相同
  • 返回值:若成功,返回0;若出错,返回-1

一般只有超级用户才能更改一个文件的所有者

若_POSIX_CHOWN_RESTRICTED生效,则:

  • 1)超级用户可以更改该文件的用户ID
  • 2)如果进程拥有此文件,参数owner等于-1或文件的用户ID,且group等于进程的有效组ID或附属组ID之一,那么一个非超级用户可以修改一个文件的组ID(这意味着若_POSIX_CHOWN_RESTRICTED生效,你可以更改你所拥有文件的组ID)

4.12 文件长度

  • $成员st_size表示以字节为单位的文件的长度。此字段只对普通文件、目录文件和符号链接有意义
    • 对于普通文件,文件长度可以是0,一开始读就会读到EOF
    • 对于目录,文件长度通常是一个数(如16或512的整数倍)
    • 对于符号链接,文件长度就是文件名的实际字节数

现在大多数UNIX系统提供字段st_blksize和st_blocks
第一个是对文件I/O的较合适的块长度,第二个是所分配的实际XXX4字节块块数

4.13 文件截断

truncate和ftruncate在文件尾端截去一些数据以缩短文件

#include <unistd.h>int truncate(const char *pathname, off_t length);int ftruncate(int fd, off_t length);
    其中:    两个函数将一个现有文件长度截断为length            若以前的长度大于length,则超过length的部分不能再访问            若以前的长度小于length,文件长度将增加(多的都是0)    返回值:若成功,返回0;若出错,返回-1

4.14 文件系统

  • 每一种文件系统都有它各自的特性,如对大小写是否敏感等
  • 一个磁盘可以分成一个或多个分区,每个分区可以包含一个文件系统
  • i节点是固定长度的记录项,它包含有关文件的大部分信息
  • 每个i节点中都有个链接计数,其值是指向该i节点的目标项数,连接计数减为0时才可删除该文件
  • stat结构中的大部分数据都取自i节点,只有文件名和i节点编号(ino_t)存放在目录项

    一个目录项不能指向另一个文件系统的i节点,所以ln命令不能跨文件系统
    给文件重命名时,构造一个指向现有i节点的新目录项,并删除老目录项(mv的工作方式)

  • 任何一个叶目录的连接计数总是2,一个来自命名该目录的目录,一个是.项

  • 在父目录中的每个子目录都使该父目录连接计数加1

4.15 函数link、linkat、unlink、unlinkat和remove

link、linkat5创建一个指向现有文件的链接:

#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:链接的现有文件
  • efd、nfd:同前,分别辅助指定现有路径名和新路径名。若任一个被设置为AT_FDCWD,那么相应的路径名就按相对路径计算
  • flag:若指定AT_SYMLINK_FOLLOW,就创建指向符号链接目标的链接,反之,创建符号本身的链接
  • 返回值:若成功,返回0;若出错,返回-1

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

#include <unistd.h>int unlink(const char *pathname);int unlinkat(int fd, const char *pathname, int flag);
  • 删除后,pathname所引用文件的连接计数减1
  • 只要有进程打开了该文件,其内容不会删除(这时连接计数不为0),进程结束时才删除
  • 若出错,不做任何操作
  • fd:与之前类似
  • flag:当设置为AT_REMOVEDIR时,可类似于rmdir一样删除目录,反之,与unlink执行一样的操作
  • 返回值:若成功,返回0;若出错,返回-1

在给出符号链接名的情况下,没有一个函数能删除该链接所引用的文件

remove函数能直接解除一个文件或者目录的链接:

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

4.16 函数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);
  • 若oldname为文件,newname已存在,只有当newname不为目录且拥有两个文件的写权限时,才能更改
  • 若oldname为目录,newname已存在,newname包含oldname作为其路径前缀时不能修改
  • 不能对.和..重命名
  • 若oldname与newname相同,则函数不做处理
  • 返回值:若成功,返回0;若出错,返回-1

4.17 符号链接

符号链接是对一个文件的间接指针,引入符号链接是为了避开硬链接的一些限制:

  • 硬链接要求链接和文件位于同一文件系统中
  • 只有超级用户才能创建指向目标的硬链接

使用以名字引用文件的函数时,要注意函数是否能跟随符号链接到它所指的文件

使用符号链接可能在文件系统中引入循环,导致一些索引函数报错

ls使用选项-F会在文件名后添加@帮助识别符号链接(-l中第一个字符是1,有->也表明这是个符号链接)

4.18 创建和读取符号链接:

可以用symlink或symlinkat函数创建一个符号链接

#include <unistd.h>int symlink(const char *actualpath, const char *sympath);int symlinkat(const char *actualpath, int fd, const char *sympath);
  • 函数创建了指向actualpath的新目录项newpath
  • 返回值:若成功,返回0;若出错,返回-1

因为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);
  • 两个函数组合了open、read和close所有操作
  • 返回值:若成功,返回读取的字节数;若出错,返回-1

4.19 文件的时间

$成员 说明 ls选项 st_atim 文件数据的最后访问时间 ls -u st_mtim 文件数据的最后修改时间 默认 st_ctim i节点状态的最后更改时间 ls -c

系统并不维护对i节点的最后访问时间

增加、删除或修改目录项会影响它所在目录的3个时间

4.20 函数futimens、utimensat和utimes

更改文件的访问和修改时间(前两个可以指定到纳秒级精度)

#include <sys/stat.h>int flutimens(int fd, const struct timespec times[2]);int utimensat(int fd, const char *path, const struct timespec times[2], int flag);
  • timspec的结构见4.2
  • times:若为空指针,访问时间和修改时间都设定为当前时间
    其指向两个timespec结构的数组
    • 若任一数组tv_nsec的值为UTIME_NOW,相应的时间戳就设定为当前时间
    • 若任一数组tv_nsec的值为UTIME_OMIT,相应的时间戳保持不变
    • 若该字段不是上面两个,时间戳就设定为相应的tv_sec和tv_nsec的值
  • flag:用于修改是否跟随符号链接(见上)
  • 返回值:若成功,返回0;若出错,返回-1
#include <sys/stat.h>int utimes(const char *pathname, const struct timeval times[2]);//timval的定义: struct timval {    time_t tv_sec;    long tv_usec;};
  • 两个时间戳用秒和微秒来表示
  • 返回值:若成功,返回0;若出错,返回-1

我们不能对st_ctim指定一个值,因为调用utime时,此字段会被自动更新

4.21 函数mkdir、mkdirat和rmdir

用mkdir、mkdirat创建目录:

#include <sys/stat.h>int mkdir(const char *pathname, mode_t mode);int mkdirat(int fd, const char *pathname, mode_t mode);
  • 对于目录应至少指定一个执行权限位,以确保能访问到里面的文件
  • 返回值:若成功,返回0;若出错,返回-1

用rmdir函数可以删除一个空目录:

#include <unistd.h>int rmdir(const char *pathname);
  • 若有一个或多个进程打开此目录,此函数在返回前删除.和..
  • 返回值:若成功,返回0;若出错,返回-1

4.22 读目录

对某个目录具有访问权限的任意用户都可以读该目录:

#include <dirent.h>DIR *opendir(const char *pathname);DIR *fdopendir(int fd);        //返回值:若成功,返回指针;若出错,返回nullstruct dirent *readdir(DIR *dp);        //返回值:若成功,返回指针;若在文件尾出错,返回nullvoid rewinddir(DIR *dp);int closedir(DIR *dp);        //返回值:若成功,返回0;若出错,返回-1long telldir(DIR *dp);        //返回值:与dp关联的目录中的当前位置void seekdir(DIR *dp, long loc);
  • dirent结构至少包含以下成员
ino_t d_ino;            //i节点编号char d_name[];          //目录名

(书中没有详细地说这些函数,但给出了一个实例:不跟随符号链接的ftw的实现)

4.23 函数chdir、fchdir和getcwd

进程调用chdir或fchdir函数可以更改当前工作目录:

#include <unistd.h>int chdir(const char *pathname);int fchdir(int fd);

shell的当前工作目录不会随程序调用chdir而改变,为改变shell工作目录,cd内建在shell中

进程调用getcwd得到当前工作目录绝对路径名:

#include <unistd.h>char *getcwd(char *buf, size_t size);
  • 返回值:若成功,返回buf;若出错,返回null

buf应有足够的长度容纳绝对路径名加上一个终止null字节,否则出错

4.24 设备特殊文件

  • $成员st_dev和st_rdev为设备号

主设备号,标识设备驱动程序
次设备号,标识特定的子设备
宏major和minor可以访问主、次设备号(如:major(buf.st_rdev))

4.25 文件访问权限位小结

(一张大大的表)

4.26 小结

终于完了……太多了……把stat给过了一遍


3.习题


  1. 通常,有效用户ID==实际用户ID,有效组ID==实际组ID ↩
  2. 更改进程的umask的值并不影响其父进程的屏蔽字 ↩
  3. 自动关闭组ID位是为了防止恶意用户修改数据 ↩
  4. XXX取决于环境变量POSIXLY_CORRECT的设置 ↩
  5. 这两个操作为原子操作 ↩
0 0
原创粉丝点击