Files and Directories 文件和目录

4.1介绍 Introduction

 

前边一章介绍了 基础的 I/O 函数。 这些讨论都聚焦在 常规文件的I/O, 如 打开,读,写一个文件。 现在我们将看看  文件系统的其他功能, 和 文件的属性。我们将 以stat 函数开始,来过一遍 stat结构体的每一个成员,来看看文件的所有属性。 在这个过程, 我们将描述 每一个函数都修改那些属性: 改变owner, 改变 权限,等等。 这章的最后,看看操作目录的函数,并且我们开发一个函数,它往下(descends)遍历一个目录的结构。

4.2  stat, fstat, fstatat, and lstat 函数

#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);

                                                                                All four return: 0 if OK, -1 on error

lstat 函数 和 stat 很类似,区别是 文件名是个 符号链接(symbolic link), lstat 返回的是symbolic link的信息,而不是 被符号链接引用的文件。(4.22, 4.17 又会描述到)

fstatat 函数提供了一种方式, 返回 pathname 相对于 fd 的文件的信息。 flag参数控制 symbolic links 有如下的:

AT_SYMLINK_NOFOLLOW  flag ,fstatat 将不会跟随 symbolic links, 而是返回 link本身的信息。

其他,默认的是 跟随symbolic link ,返回 symbolic link 指向的文件。

如果fd 是 AT_FDCWD, 并且 pathname 是相对的路径名, fstatat 会使用 pathname 参数相对于当前目录。 如果pathname 是绝对路径名,那么 fd 参数会被忽略。

在flag 的这两种cases, fstatat 的行为 要么像 stat  ,要么像 lstat。

buf参数是一个指针,指向一个结构体,结构体的定义,在每个实现之间都是不同的,但是它可能看起来如下:

struct stat {

mode_t              st_mode;                                  /* file type & mode (permissions) */

ino_t                  st_ino;                                       /* i-node number (serial number) */

dev_t                  st_dev;                                      /* device number (file system) */

dev_t                  st_rdev;                                      /* device number for special files */

nlink_t                  st_nlink;                                     /* number of 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_atim;                                  /* time of last access */

struct timespec         st_mtim;                                  /* time of last modification */

struct timespec         st_ctim;                                  /* time of last file status change */

blksize_t                  st_blksize;                  ​​​​​​​        ​​​​​​​        /* best I/O block size */

blkcnt_t                  st_blocks;                                  /* number of disk blocks allocated */

};

timespec 结构体,它至少包含如下字段。

time_t   tv_sec;

long tv_nsec;

4.3  File Types 文件类型

  • 1.  常规文件 (Regular file)。对于unix 内核来说,数据是 text 还是 binary 都是没有 区分的。对于常规文件的内容的解释 留给了 应用。
  •      注意: 一个值得注意的例外情况是 binary executable files。 为了执行一个程序, 内核必须理解它的格式。所有的二进制可执行文件 的格式 都能让内核知道到哪个位置 去加载程序的 text 和 data。
  • 2. 目录文件 (Directory file)。一个file ,它包含了 其他文件的 名字,并且指向了 这些文件。 任何进程(process), 有 对目录的读权限,就可以读取目录的内容, 但仅仅只有内核能直接 写 一个目录文件。 进程(processes) 必须使用这章描述的函数来改变目录。
  • 3. 块特殊 文件(Block special file)。  提供了 有缓冲的 I/O 访问, 访问 固定大小的单元。例如 disk drives。
  • 4. 字符特殊 文件(Character special file)。提供 无缓冲的 I/O 访问, 访问 devices上 可变大小的单元。 在系统上的所有 设备(devices) 要么是block special files 要么是 character special files。
  • 5.  FIFO. 用于进程间通信,它有时也叫做 pipe。(15.5 中会描述)
  • 6. Socket。 用于多进程间的 网络通信。socket也可以用于 一个 host 上的 多个进程间的 非网络通信。 (16章会提到)
  • 7.  Symbolic link 。 一种文件类型,它指向 另一个文件。 (4.17)

文件的类型 被编码在 stat 结构体的 st_mode 成员中。我们可以使用 如下4.1图中的 宏来检测 file type。 这些宏 的参数 是  stat 结构体中的 st_mode 成员。

Macro

Type of file

S_ISREG()

regular file

S_ISDIR()

directory file

S_ISCHR()

character special file

S_ISBLK()

block special file

S_ISFIFO()

pipe or FIFO

S_ISLNK()

symbolic link

S_ISSOCK()

socket

Figure 4.1 File type macros in <sys/stat.h>

POSIX.1 允许 实现 去 将这些  进程间通信(IPC) 对象,例如 message queues, semaphores. 视作 文件(file)

在下面图4.2的图中的宏,允许我们 去检测 IPC对象的类型。 不是传stat结构体的st_mode 成员作为参数,而是传一个 指针(它指向 stat 结构体)。

Macro

Type of object

S_TYPEISMQ()

message queue

S_TYPEISSEM()

semaphore

S_TYPEISSHM()

shared memory object

Figure 4.2 IPC type macros in <sys/stat.h>

然而,在这本书中讨论的 各种unix 系统的实现,还没有把些对象 视作文件。

4.3 打印文件类型,对于命令行中给出的参数

filedir/filetype.c

1.#include "apue.h"  
2.  
3.int  
4.main(int argc, char *argv[])  
5.{  
6.    int         i;  
7.    struct stat buf;  
8.    char        *ptr;  
9.  
10.    for (i = 1; i < argc; i++) {  
11.        printf("%s: ", argv[i]);  
12.        if (lstat(argv[i], &buf) < 0) {  
13.            err_ret("lstat error");  
14.            continue;  
15.        }  
16.        if (S_ISREG(buf.st_mode))  
17.            ptr = "regular";  
18.        else if (S_ISDIR(buf.st_mode))  
19.            ptr = "directory";  
20.        else if (S_ISCHR(buf.st_mode))  
21.            ptr = "character special";  
22.        else if (S_ISBLK(buf.st_mode))  
23.            ptr = "block special";  
24.        else if (S_ISFIFO(buf.st_mode))  
25.            ptr = "fifo";  
26.        else if (S_ISLNK(buf.st_mode))  
27.            ptr = "symbolic link";  
28.        else if (S_ISSOCK(buf.st_mode))  
29.            ptr = "socket";  
30.        else  
31.            ptr = "** unknown mode **";  
32.        printf("%s\n", ptr);  
33.    }  
34.    exit(0);  
35.}  
36.  
37./** 
38. * 
39.[xingqiji@work78 filedir]$ ./filetype /etc/passwd /etc  /dev/log  /dev/tty /var/lib/oprofile/opd_pipe  /dev/sr0  /dev/cdrom 
40./etc/passwd: regular 
41./etc: directory 
42./dev/log: socket 
43./dev/tty: character special 
44./var/lib/oprofile/opd_pipe: lstat error: No such file or directory 
45./dev/sr0: block special 
46./dev/cdrom: symbolic link 
47.[xingqiji@work78 filedir]$  
48. 
49. *  
50. */ 

说明:我们尤其使用 lstat 而不是stat 来 侦测symbolic link, 如果我们使用 stat函数,我们将从不会看到 symbolic links。

历史地, 早期的unix system 版本不提供 S_ISXXX 宏, 替代地 ,我们不得不 ,在<sys/stat.h> 我们检查file, 我将发现这个 S_ISDIR 宏 定义 像如下:

#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)

 4.4 Set-User-ID  and  Set-Group-ID

每个进程 有六个或更多的ID  和它关联。这些展示在图4.5 中。

real user ID

who we really are

real group ID

effective user ID

used for file access permission checks

effective group ID

supplementary group IDs

saved set-user-ID

saved by exec functions

saved set-group-ID

                                        4.5 每个进程 关联的 User IDs , group IDs

  1. real user ID, real group ID 鉴定了 我们真实是谁。 这两个字段 从 当我们登录时,用到的password file 文件中 获取。正常地,这些值 在我们整个 login session都不会改变, 尽管超级用户有办法改变它们(8.11 会描述)
  2.  effective user ID, effective group ID, supplementary group IDs 决定了我们的文件访问权限,就像下个部分描述的那样。
  3.  saved set-user-ID  和  saved set-group-ID  包含了  effective user ID, effective group ID 的一个备份,  生成的时机就是 当程序被执行后。

saved IDs 在2001 POSIX.1 才是必要的。在更老的POSIX, 这是可选的。 一个应用可以在 编译时 测试 _POSIX_SAVED_IDS 或 在运行时 调用 sysconf , 参数是 _SC_SAVED_IDS , 来看实现 是否支持此功能。

通常地, effective user ID 等效于 real user ID,  effective group ID 等效于 real group ID。

每个文件都有 一个 owner 和 group owner 。 这个owner 是 stat结构体中的 st_uid 成员, group owner 是 stat结构体中的st_gid 成员。

当我们执行一个程序文件使, 进程的 the effective user ID  通常就是  real user ID, effective group ID 通常是  real group ID.   然而我们 可以  在文件的 mode (stat 的st_mode) 设置 一个 special flag 。含义就是说: 当这个文件被执行, 进程的effective user ID  设置为 和 file的 owner 一样(stat 的st_uid)。 类似地,我们可以在file 的mode word 设置另一个bit, 它将引起 进程的 effective group ID 设置为 和 file的 group owner(即 stat的 st_gid) 一样。这两个bit 在file mode word 中被叫做 set-user-ID 位 和 set-group-ID 位。

例如,file的 owner 是superuser 并且 file的 set-user-ID 位被设置了,那么当程序(这个file)被执行 作为进程,  这个进程 就有了 superuser privileges 。这种情况会发生,且 它 忽视不管  执行这个文件的 产生的进程的 real user ID 是什么。 举个例子,unix 系统 有个程序(passwod(1)) 允许 任何人 可以改变 他或她的密码 ,  这个程序就是 一个set-user-ID 的程序。有需求去 write  /etc/passwd 或 /etc/shadow 文件, 正常地这些文件只能被 superuser 去write。( 这样的程序,第8章还会更详细的讨论)。

回到stat 函数的讨论, set-user-ID bit 和 set-group-ID 位 被包含在 file的st_mode 值中。这两个bits 可以依据 常量S_ISUID 和 S_ISGID 来测试。

4.5 File Access Permissions  文件访问权限

st_mode 的值 也编码了 文件的 access permission bits。 这里说的file 指的可以是是前面提到的任何文件类型。 所有的文件类型 ,目录、character special files,等等 都有权限.

每个文件有9个权限位,被分成了3类。它们如图4.6:

st_mode mask

Meaning

S_IRUSR

user-read

S_IWUSR

user-write

S_IXUSR

user-execute

S_IRGRP

group-read

S_IWGRP

group-write

S_IXGRP

group-execute

S_IROTH

other-read

S_IWOTH

other-write

S_IXOTH

other-execute

Figure 4.6 The nine fifile access permission bits, from <sys/stat.h>

chmod(1) 命令可以修改这9种权限bits。

a.  首先规则是, 不管任何时候,我们想通过name去open任何类型的文件,我们必须有 对name中提到的每个目录 有执行权限,包括当前目录(如果它被含有)。这就是为什么对于目录的 execute permission bit 又常常被叫做 search bit。

例如:  为了去 打开 /usr/include/stdio.h, 我们需要 目录 / 的 执行权限, 目录 /usr 上的执行权限, 目录 /usr/include 上的执行权限, 对文件本身也要有合适的权限(依赖于我们如何open文件是 read-only,read-write,等等.

如果当前目录是 /usr/include ,那么我们需要当前目录的执行权限。这有个当前目录的被包含(不明显的提及)例子。就是 ./stdio.h 这样写法。

注意 一个目录的  读权限 和 执行权限 是很不同的。read 权限让我们 读目录,获取在目录下的所有文件名(filenames)。 执行权限 让我们 通过目录,当它是 一个pathname 的一个组成部分(我们需要去search目录来寻找特定的filename)

另一个不明显的隐含的目录引用是 PATH 环境变量(8.10 描述),指定了一个目录,但没有执行权限,在这种情况,在这个目录下就不能找到任何的 可执行文件。

b. 一个文件的 读权限 决定我们是否可以打开一个已存在的文件,来读。open函数O_RDONLY, 或 O_RDWR flags

c.  一个文件的写 权限 决定我们是否可以打开一个已存在的文件, 来写。open函数O_WRONLY 或 O_RDWR flags

d. 如果在open 中指定了 O_TRUNC flag ,那么我们必须对这个文件有 write 权限

e.  如果想在一个目录下 创建一个新文件,那么我们必须对这个目录 有写 和 执行 权限。

f.  为了去删除一个已存在的文件,我们需要 对文件所在的目录 有 写 和 执行权限。我们对文件本身不需要有 读 或 写 权限。

g.  如果我们想用 7个 exec函数来执行 一个文件,那么文件的执行权限必须开启,并且这个文件要是一个 regular file。

每次进程 open, create, 或delete 一个文件 ,内核就会执行 file access 检查,这个检查依据是: 文件的属主(st_uid, st_gid) , 进程的 effective IDs (包括 effective user ID 和 effective group ID),  进程的补充group IDs (如果支持的话)。 两个owner IDs 是文件的属性, 两个effective IDs 和 补充的group IDs  是进程的属性,  内核是按如下来检查的:

1. 如果进程的 effective user ID 是 0 ( 超级用户 superuser) , access 就是允许的。 这个使得超级用户 能 自由的 进入 整个文件系统。

2.  如果 进程的 effective user ID 等于 文件的 owner ID ( 例如 进程 拥有这个文件); 并且 文件 的 合适的 用户访问权限位 被设置。 则 access 就是允许的, 其他情况下,access是被禁止的。 “合适的 访问权限bit” 含义是: 如果进程 要打开这个文件来读,那么 user-read bit 必须被打开;如果进程要打开这个文件来写, 那么user-write bit 必须被打开; 如果进程要执行这个文件,那么 user-execute bit 必须被打开。

3.  如果进程的 effective group ID  或者 进程的补充group IDs中的一个  和文件的group ID 相等。 并且 合适的group 访问权限bit 被设置,那么 access 就是 允许的。否则access就是被禁止的。

4.  如果 合适的 other access 权限bit 被设置, access 是允许的,否则access就是被禁止的。

这个四个步骤,是被尝试着按顺序来的。 注意如果 进程拥有文件(step 2) ,  access 被授权或 拒绝 取决于 user access permissions bit, 就从来不看 group permissions bit了。类似地,如果进程 没有拥有文件,但是属于一个 合适的 group, access 是 被授权还是拒绝 取决于文件的 group access permissions bit , 就从来不看 other permissions bit 了

4. 6  ownership of new file and Directories   新文件  新目录的属主

新文件的 user ID  被设置为 和进程的 effective user ID 一样。POSIX.1 允许一个实现 去选择 如下的选项来 决定 一个新文件的 group ID:

  1.   新文件的 group ID 可以 和 进程的 effective group ID 一样。
  2.   新文件的 group ID 可以 和 这个文件所在目录的 group ID  一样。

Linux3.2.0 的默认行为,来决定新文件group ID  取决于 文件所在的目录的 set-group ID bit 是否被设置。如果这个bit 被设置了,新文件的 group ID 就从 目录上 copy;  否则新文件的 group ID 就设置的 和  进程的 effective group ID 一样。

4.7 access  and faccessat 函数

有些时候一个进程 想去 基于 real user  group IDs 来测试 可访问性access  faccessat 函数 来检查 是基于 real user  group IDs

#include <unistd.h>

int access(const char *pathname, int mode);

int faccessat(int fd, const char *pathname, int mode, int flag);

                                                                                        Both return: 0 if OK, -1 on error

mode  要么是 F_OK (测试一个文件是否存在) ,那么是 如下 flags  OR 组合  4.7:

mode

Description

R_OK

test for read  permission

W_OK

test for write  permission

X_OK

test for execute permission

faccessat 函数的行为和 access 是一样的,特别是当: pathname 是绝对的 或 fd 是AT_FDCWD 并且 pathname 是相对的。 否则  ,faccessat 在计算pathname时 就会 相对着 这个目录(fd指向的目录)来。

flag 参数 可被 用于来 改变 facccessat 的行为。 如果 AT_EACCESS flag 被设置, access 检查就会使用 调用进程的 effective user ID , effective group ID 来检查 (而不是使用 进程的real user 和 group IDs)。

例子4.8

access 函数的使用。

#include "apue.h"  
#include <fcntl.h>  
  
int  
main(int argc, char *argv[])  
{  
    if (argc != 2)  
        err_quit("usage: a.out <pathname>");  
    if (access(argv[1], R_OK) < 0)  
        err_ret("access error for %s", argv[1]);  
    else  
        printf("read access OK\n");  
    if (open(argv[1], O_RDONLY) < 0)  
        err_ret("open error for %s", argv[1]);  
    else  
        printf("open for reading OK\n");  
    exit(0);  
}  
  
/** 
[xingqiji@work78 filedir]$ ./access /etc/passwd 
read access OK 
open for reading OK 
*/ 

4.8 umask 函数

umask 函数 设置  进程的  file mode creation mask , 并且 返回 先前的 value。

(这个函数 是少有的 没有 error返回的函数 之一)

#include <sys/stat.h>

mode_t umask(mode_t cmask);

                                                                        Returns: previous file mode creation mask

cmask参数的格式是: 9个 常量(S_IRUSR, S_IWUSR 等)的 OR 组合。

file mode creation mask 通常 在进程创建 文件 或目录时 使用。  在file mode creation mask 中 被关闭的,在file的mode中 都被 打开。

例子:4.9

这个程序 创建两个文件:  一个 带着 umask 0 来创建, 一个 带着 umask 禁用 所有group 和 other permission bits 来创建。

filedir/umask.c

1.#include "apue.h"  
2.#include <fcntl.h>  
3.  
4.#define RWRWRW (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)  
5.  
6.int  
7.main(void)  
8.{  
9.    umask(0);  
10.    if (creat("foo", RWRWRW) < 0)  
11.        err_sys("creat error for foo");  
12.    umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);  
13.    if (creat("bar", RWRWRW) < 0)  
14.        err_sys("creat error for bar");  
15.    exit(0);  
16.}  
17.  
18./** 
19.[xingqiji@work78 filedir]$ ls -l foo bar 
20.-rw------- 1 xingqiji xingqiji 0 2月  23 10:18 bar 
21.-rw-rw-rw- 1 xingqiji xingqiji 0 2月  23 10:18 foo 
22.*/ 

通常在登录的时候 shell的 start-up file 会设置一次 (使用umask) mask , 并且不再改变。 当 程序在创建文件时,想确保指定的 access permission bit 被启用, 我们必须修改 正在运行着的进程的 umask value 。

在先前的例子,我们使用 shell的umask 命令 在程序运行前和 完成后去打印 file mode creation mask  。 这向我们展示了  进程改变了 file mode creation mask  不会影响 parent (常常是 shell) 的mask。 所有的shell 都有 一个内建的命令 来使我们可以使用它去打印或设置当前的 file mode creation mask。

设置umask value 用 十六进制表示。 图4.10  通过设置相应位就可以 禁用/关闭 对应的 permissions bit (要创建文件的) , 例如  027  表示 阻止 group members 写, 阻止other 读,写, 执行你的文件。

Mask bit

Meaning

0400

user-read

0200

user-write

0100

user-execute

0040

group-read

0020

group-write

0010

group-execute

0004

other-read

0002

other-write

0001

other-execute

                                        4.10  the umask   file access permission bits

UNIX 规范 要求  umask 命令支持 符号表示 umask , 如下:

$ umask                          first print the current file mode creation mask

002

$ umask -S                          print the symbolic form

u=rwx,g=rwx,o=rx

$ umask 027                          change the file mode creation mask

$ umask -S                                  print the symbolic form

u=rwx,g=rx,o=

4.9 chmod , fchmod, and 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);

                                                                                All three return: 0 if OK, -1 on error

flag 参数通常用于改变 fchmodat 行为, 当 AT_SYMLINK_NOFOLLOW flag 被设置, fchmodat 不会 跟随 symbolic links。

为了改变 文件的 permission bits  , 进程的 effective user ID 必须 等于 文件的 owner ID, 或者进程 有 超级用户权限。

mode 参数 被指定 为  下面常量的 OR  4.11 :

mode

描述

S_ISUID

set-user-ID on execution

S_ISGID

set-group-ID on execution

S_ISVTX

saved-text (sticky bit)

S_IRWXU

read,write, execute (owner)

S_IRUSR

read by user(owner)

S_IWUSR

write by user(owner)

S_IXUSR

execute by user (owner)

S_IRWXG

read,write, and execute by group

S_IRGRP

read by group

S_IWGRP

write by group

s_IXGRP

execute by group

S_IRWXO

read, write, execute (world)

S_IROTH

read by other (world)

S_IWOTH

write by other (world)

S_IXOTH

execute by other (world)

4.11 The mode constants for chmod functions, from <sys/stat.h>

注意: 4.11 中的9个  和 4.6 的9个file access permission bits 是一样的。这里 添加了 2个 set-ID  常量(S_ISUID  和 S_ISGID)  , saved-text 常量(S_ISVTX),  和 3个 合并常量( S_IRWXU, S_IRWXG, S_IRWXO).

4.12 例子:

filedir/changemod.c

1.#include "apue.h"  
2.  
3.int  
4.main(void)  
5.{  
6.    struct stat     statbuf;  
7.  
8.    /* turn on set-group-ID and turn off group-execute */  
9.  
10.    if (stat("foo", &statbuf) < 0)  
11.        err_sys("stat error for foo");  
12.    if (chmod("foo", (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0)  
13.        err_sys("chmod error for foo");  
14.  
15.    /* set absolute mode to "rw-r--r--" */  
16.  
17.    if (chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0)  
18.        err_sys("chmod error for bar");  
19.  
20.    exit(0);  
21.}  
22.  
23./** 
24.[xingqiji@work78 filedir]$ ls -l foo bar 
25.-rw------- 1 xingqiji xingqiji 0 2月  23 10:18 bar 
26.-rw-rw-rw- 1 xingqiji xingqiji 0 2月  23 10:18 foo 
27.[xingqiji@work78 filedir]$ ./changemod 
28.[xingqiji@work78 filedir]$ ls -l foo bar 
29.-rw-r--r-- 1 xingqiji xingqiji 0 2月  23 10:18 bar 
30.-rw-rwSrw- 1 xingqiji xingqiji 0 2月  23 10:18 foo 
31. 
32.*/ 

注意 ls命令 列出 foo的  group-execute 权限为 S  ,表明了 set-group-ID bit 是被设置,并且没有group-execute bit 被设置。

最后, 注意 ls 命令 列出的 时间 和日期 在经过 ./changemod 程序后 ,并没有改变。

在下面的情况下: chmod 函数会自动 清除 2个权限bit

a: 对 regular files 设置  sticky bit 时,如果我们不是 superuser privileges ,在mode中的sticky bit 设置 就会被自动的 关闭(turned off) 。为了防止恶意的用户 设置 sticky bit 并且 有害地影响系统性能, 只能是 superuser 可以设置 一个 regular file 的 sticky bit。

b:  一个新的将要被创建的文件的 group ID 可能 和 调用的进程的group ID 不一样。回想下4.6 新文件的 group ID  可以和 父目录的 group ID 一样。 尤其是特别地  ,新文件的 group ID

不等于 进程的 effective group ID 或 也不是 补充group IDs 的其中一个,并且进程没有超级用户权限,那么 set-group-ID bit 就会自动地被关闭(turned off)

4.10 Sticky Bit

S_ISVTX bit  有一个有趣的历史。 如果对 一个可执行程序 设置了 sticky bit , 那么当程序被第一执行过, 程序的text (程序机器指令) 的一份copy 就会被保存在 swap area。 下次它被执行时,程序将被很快地加载到 memory 中, 因为 swap area 是 个 连续的(contiguous file)  (普通的 Unix file system 的data blocks 是随机的).   这个sticky bit 常常被设置在哪些 通用的公共的程序,如 text editor , c编译器 等。 自然地 对 设置 sticky files的数量有限制的。 之后的 Unix system  把 sticky 叫做 saved-text bit ,对应的常量是 S_ISVTX。 现在的系统,大部分有一个虚拟内存系统,并且有一个更快的file system , 所以对这个技术的 需求已经消失了。

在当代的(contemporary system ) ,sticky bit 的使用已经被扩展了, Single Unix 规范允许 对

目录设置  sticky bit 。 如果一个目录 被设置了 , 在目录下的  文件 可以被删除 或重命名 ,只需要 用户对目录有 写权限 并且 有如下其一标准(criteria):

owns 这文件;

owns 这个目录;

是超级用户。

/tmp , /var/tmp 是典型的使用 sticky bit 的目录。 在这个目录下用户可以创建文件,这两个目录的权限通常是: read, write, execute (user, group, other)。 但是用户不能删除或重命名 别人拥有的的文件。

drwxrwxrwt.  28 root root 4096 2月  23 10:35 tmp

drwxrwxrwt. 17 root root 4096 2月  23 10:06 tmp

4.11 chown, fchown, fchownat, lchown 函数

chown 函数允许我们改变文件的 user ID  和 group ID.  但是 如果参数  owner 或 group 是 -1, 那么对应的ID 就保持不变。

 #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);

                                                                                All four return: 0 if OK, -1 on error

lchown , fchownat( 带着/带随着  AT_SYMLINK_NOFOLLOW flag 被设置),他两只是改变 symbolic link 本身的 所有权,而不是改变 symbolic link 引用的文件的

fchown 函数 改变 fd 参数指向的 打开的文件 的 所有权。由于它操作的是一个已打开的文件,所以这个函数不能改变一个 symbolic link的 所有权(ownership)

只有超级用户进程可以改变 文件的 user ID

非超级用户的进程 可以改变 文件的 group ID , 当 进程拥有文件( 进程的effective user ID 等于 文件的 user ID),  此时 owner 参数应该是 -1 或者 等于文件的user ID, 并且 group 参数 等于 要么是 进程的 effective group ID ,要么是 进程的补充group IDs 中的一个。

在这些函数 被  非超级用户进程 调用后,成功返回后, set-user-ID bit 和 set-group-ID bit 都会被清除。

4.12 File Size

stat 结构体的成员st_size  包含了 文件的大小(字节为单位),这个字段/成员 只对 regular files, 目录, symbolic links 是有意义的。

对regular file , 文件的 大小 是 0 是被允许的。 对于目录, file size 通常是 一个数字的倍数,如 16 or 512。 我们在 4.22 部分讨论 reading 目录。

对于 symbolic link , file size 就是 文件名filename的 字节数。例如,在下面的例子, 7 是 usr/lib 的长度

lrwxrwxrwx.   1 root root    7 9月  23 2020 lib -> usr/lib

lrwxrwxrwx.   1 root root    9 9月  23 2020 lib64 -> usr/lib64

注意 symbolic links 不包含 标准 C 里的 null byte (字符串末尾的),因为长度是 st_size 来指定的。

大多数现代Unix 系统 提供了 st_blksize 和 st_blocks 字段。 第一个是 对文件的I/O 来说是首选的 block size。 第二个是 真实的 512 字节的 block 的数量。

Holes in File  文件中的空洞

$ ls -l core

-rw-r--r-- 1 sar 8483248 Nov 18 12:18 core

$ du -s core

272 core

size of file 是 超过了 8M, du命令 显示是 272 个 512-byte blocks (139264 字节)。明显地file 有许多holes.

在Linux 上 报告的单位 取决于 POSIXLY_CORRECT 环境是否被设置。如果被设置 报告单位就是 1024-byte block units; 没有被设置时, 报告的就是 512-byte block units。

就像我们在3.6 部分提到的, read 函数 对read的 任何位置上没有被写时,返回的都是 data bytes of 0 :

$ wc -c core

8483248 core

wc(1) 命令 带上 -c  选项统计  file中的 字符数量。

如果我们 copy 这个文件, 使用 cat(1), 所有的这些 holes 写出去的都写作  data bytes of 0 :

$ cat core > core.copy

$ ls -l core*

-rw-r--r-- 1 sar 8483248 Nov 18 12:18 core

-rw-rw-r-- 1 sar 8483248 Nov 18 12:27 core.copy

$ du -s core*

272 core

16592 core.copy

文件的真实 字节数是 8,495,104 (512 * 16,592)

4.13 File Truncation

#include <unistd.h>

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

int ftruncate(int fd, off_t length);

                                                                                Both return: 0 if OK, −1 on error

这两个函数truncate 已存在文件  length bytes。 如果之前的 文件大小 大于 参数length, 在length后边的数据就不再 可访问否则如果之前的文件大小小于  参数 length , file size 将增加老文件末尾  新文件末尾之间的数据将被读做 0 ( 在这个文件中可能产生hole)

4.14 File Systems  文件系统

理解 一个 i-node  和 一个 directory entry 之间的不同 是很有用的。

目前,有多种 UNIX file system .

我们可以把一个磁盘(disk) 分成一个或多个分区(partitions)。 每个分区都可以包含一个file system 。i-nodes  是固定长度的entries ,包含了 一个文件的大部分信息。

说明:cylinder group : 柱面组

i-node 和 data block  更详细的内容如下图:

上图说明:

两个 directory entries 指向/引用 相同的 i-node entry . 每个i-node 有 一个link count(连接数):“包含了directory entries 指向它的数量”。仅当 link count 变成0时,文件才能被删除(删除指:释放文件关联的 blocks)。这就是 为什么“unlinking a file” 不总是意味着“删除文件关联的 blocks” 。 这就是为什么 移除一个 directory entry 被叫做 unlink , 而不是 delete。在 stat 结构体 中 st_nlink member 代表了link count。st_nlink 的数据类型 是 nlink_t。 这种链接类型 被叫做 hard links

另一种 链接类型 叫做 symbolic link (符合链接)。一个symbolic link , 它的 文件内容/data blocks : 存储了文件的名称。 下面例子中 directory entry 是包含了3个字符(lib) 的文件名。

lrwxrwxrwx 1 root 7 Sep 25 07:14 lib -> usr/lib

在i-node中的file type 是S_IFLNK 目的是 让系统知道它的类型是 symbolic link.

i-node 包含了文件的所有信息:文件类型,文件的访问权限位,文件大小,指向data blocks的指针,等等。 在stat结构体中的大多数信息都是从i-node 获取的。只有两项重要的数据是 从 directory entry 中获取,就是 filename, i-node number。另外两项数据:文件名的长度,目录记录的长度 不在此讨论。i-node number 的数据类型是ino_t

一个directory entry 不能引用 不同文件系统的一个i-node. 这就是为什么ln(1) 命令(创建一个新的directory entry 它 指向一个已存在的文件)不能跨越 file systems.

当重命名一个文件(前提不改变文件系统),实际的文件的内容不需要改变,仅仅是添加一个新的 directory entry 并且删除 老的 directory entry ,link count 保持相同。这是mv(1)命令通常的操作。

那么 一个目录的 link count 又是什么概念? 假如我们如下创建一个目录:

mkdir testdir

上图说明:i-node 它的 number是2549 ,它的link count 等于 2。 任何叶子目录(里边不包含任何其它目录)总是 link count 等于 2. 2的怎么来的一个 来自哪个包含名字的directory entry,另一个来自在那个目录的 dot 代表的directory entry 。number 是 1267 它的link count 肯定是 大于等于 3.  3怎么来的,一个是 他的名字对应的 directory entry (注意上图没有画出来!),一个是 dot ,一个是来自testdir 里边的 dot-dot。注意子目录每多一个都会引起本目录的link count 增加 1

4.15 link , linkat, unlink, unlinkat, remove 函数

就像在先前部分那样,一个file 可以 是  有多个directory entries 指向它的i-node。我们可以使用 link 或 linkat 函数来 对一个已存在的file 创建link。

#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);

                                                                                        Both return: 0 if OK, -1 on error

如果newpath 已经存在, error 就会返回,

当existing file 是 symbolic link , flag参数 控制 linkat 函数是创建一个 symbolic link 的 link, 还是 创建一个 针对symbolic link 指向file 的 link 。如果AT_SYMLINK_FOLLOW flag 被设置,那么 创建的link 指向  symbolic link的目标。如果flag 被清除,那么 创建的link 指向的是 symbolic link 本身。

注意:新new directory entry 的创建 和 link count 的增加 必须是一个 原子操作。

大多数实现 要求 两个pathnames 在同一个 file system 上。

如果一个实现支持 对目录 创建 hard links ,那么它一定是 仅对于 超级用户。这个约束存在的原因是 hard links  在file system可能引起 loop。 基于如上 原因,许多file system 的实现 都不允许 对目录 hard links.

为了 移除 已存在的 directory entry ,我们可以调用unlink 函数

#include <unistd.h>

int unlink(const char *pathname);

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

                                                                        Both return: 0 if OK, -1 on error

这些函数 移除 directory entry 并且减少 文件( pathname指定的文件)的 link count 。如果这里有其他link 指向这个文件,  那么仍然可以通过其他link 来访问文件中的数据。

就像早先提到的, 去unlink 一个file ,我们必须 对  directory entry 所在的 目录 有write 和 execute 权限。也如 4.10提到的,如果 这个目录的 sticky bit 被设置了,我们必须 对 这个目录有 write 权限 并且 满足如下的标准之一:

own 这个文件

own 这个目录

有超级用户权限

仅当 link count 达到0 时,file的contents才可能被删除,另一个情况会阻止文件被删除的是: 只要有进程open  file , 它的contents就不能被删除。当一个file被closed , 内核首先检查 进程打开此file的数量,如果这个数量达到0了,内核再检查 link count, 如果是0 ,file的内容就会被删除。

flag 参数 给了调用者 一种改变unlinkat 函数行为的方式。 当AT_REMOVEDIR flag 被设置, unlinkat函数通常被用来 移除 一个目录,类似 rmdir。 如果flag被清除,那么unlinkat 操作就像 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("unlink error");  
    printf("file unlinked\n");  
    sleep(15);  
    printf("done\n");  
    exit(0);  
}  
  
/** 
[xingqiji@work78 filedir]$ echo 3344 > tempfile 
[xingqiji@work78 filedir]$ ./unlink 
file unlinked 
 
done 
[xingqiji@work78 filedir]$  
 

unlink 常常被用来 确保程序创建的 临时文件 不会被留下(在程序奔溃时)。调用unlink 后文件仍然没有被删除,因为 它仍然open, 仅当进程 要么关闭file 或者 终止(terminates), 这会引起 内核去关闭所有程序打开的 files。这个时候 file 被删除。

如果pathname 是 symbolic link ,  unlink 移除的是  symbolic link 不是 link指向的file. 这里没有函数 能 移除 symbolic link 指向的file.

我们可以 用remove 函数来 unlink 一个文件或  目录, 对于 文件 remove  完全等效于  unlink。 对于目录,remove 完全等效于 rmdir。

#include <stdio.h>

int remove(const char *pathname);

                                                                                                Returns: 0 if OK, -1 on error

4.16  rename  and  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);

                                                                                                Both return: 0 if OK, −1 on error

对于这些函数,这里有几种情况 需要来描述,这些情况 取决于 oldname 指向的是 file ,是 directory 还是 symbolic link。我们也要描述下 如果 newname 已经存在了,会发生什么。

  1. oldname 指向  file 或 symbolic link。在这种情况下, 如果newname 存在, 它不能是目录。如果newname存在 并且不是目录,那么它被删除,并且 oldname 被重命名为 newname。我们必须对 包含 oldname 的目录 以及 包含 newname 的目录 有写权限,(由于,我们正都改变两个目录)
  2.  如果oldname 指向一个目录,那么我们正重命名一个目录。如果newname 存在,它必须指向一个目录,并且这个目录 必须是空的。如果newname 存在 并且是空的,它就被删除,并且 oldname 被重命名为 newname。 另外 newname 不能包含 一个path prefix 它的名是 oldname。 例如:我们不能把  /usr/foo 命名到 /usr/foo/testdir  。
  3. 如果  oldname 或 newname 指向一个 symbolic link ,那么 link 本身被处理,而不是link 指向的file 被处理
  4. 我们不能 重命名 dot 或 dot-dot 。更加准确(precisely)的说法是, dot 和 dot-dot 不能出现在 oldname 或 newname 的组成的最后一个部分。

  5. 有个特殊情况, 如果oldname 和 newname 指向 相同的file,那么函数返回成功, 并且不改变任何事情。

  6. oldfd 或 newfd 参数都可以被设置为 AT_FDCWD , 这样在解析 pathname时就参考的是当前目录。

4.17 Symbolic Links

符号链接 是一个 非直接指向file, 这不像hard links (它直接指向 file的i-node ). symbolic links 被 介绍进来 是为了  规避/绕开(get around)  hard links 的局限性。

   hard links 正常需要 link 和 file 存在 相同的 file system

   只有超级用户可以对 目录创建 hard link.

4.17 总结了 哪些函数 会 follow symbolic link。 函数 mkdir, mkfifo.mknod, rmdir 没出现在下面的图中,是因为 当 pathname 是symbolic link 就会返回error.  还有,接收 file descriptor 参数的这些函数也不回出现,如 fstat  ,fchomd。 在过去的历史上, 不同的实现对 chown 函数是否 follow links 是有不同的做法。在现代的系统,chown 会 follow symbolic links。

function

不跟随 symbolic link

跟随symbolic link

access

chdir

chmod

chown

creat

exec

lchown

link

lstat

open

opendir

pathconf

readlink

remove

rename

stat

truncate

unlink

有个异常的情况。当open 函数 被调用时 带着 O_CREAT 和 O_EXCL 设置,如果pathname指向一个symbolic link , open将失败并且 errno被设置为EEXIST。 这个行为的目的是 关闭一个安全漏洞 (防止 有权限的进程 被欺骗 地 写到了错误的文件中)

例子:

在文件系统中 通过使用 symbolic links 而造成 loops 是有可能的。当这种错误发生时,大多数函数(look up pathname) 都会返回ELOOP  类型的 errno。

$ mkdir foo make a new directory

$ touch foo/a create a 0-length fifile

$ ln -s ../foo foo/testdir create a symbolic link

$ ls -l foo

total 0

-rw-r----- 1 sar 0 Jan 22 00:16 a

lrwxrwxrwx 1 sar 6 Jan 22 00:16 testdir -> ../foo

 当我们open一个file, 如果pathname 指向一个symbolic lik 。那么open就会跟随 link 到 指定的file。如果symbolic link 指向的file 不存在,open 返回 error 。这个

例如:

$ ln -s /no/such/file myfile create a symbolic link

$ ls myfile

myfile ls says it’s there

$ cat myfile so we try to look at it

cat: myfile: No such file or directory

$ ls -l myfile try -l option

lrwxrwxrwx 1 sar 13 Jan 22 00:26 myfile -> /no/such/file

4.18 Creating and Reading Symbolic Links 创建和 读Symbolic

symbolic link 要么是使用 symlink ,要么是使用 symlinkat 来创建。

#include <unistd.h>

int symlink(const char *actualpath, const char *sympath);

int symlinkat(const char *actualpath, int fd, const char *sympath);

                                                                Both return: 0 if OK, -1 on error

当symbolic link 被创建时不要求 actualpath 存在。 还有actualpath 和 sympath 不需要在 相同的file system 中。

如果sympath 参数指定了 绝对pathname 或者 fd 参数 有 特殊的值 AT_FDCWD,那么symlinkat 行为就像 symlink。

因为Open 函数 follow symbolic link ,我们需要一种打开link 本身的方式。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);

                                                                Both return: number of bytes read if OK, -1 on error

这些函数 合并 open, read, close 行为。如果成功返回 放置到buf里的字节数量。 symbolic link 的内容 被返回到buf 中的 没有 null terminated.

4.19 File Times

每个文件有3个 time field 被维护。目的 被总结为如下图:

字段

描述

Example

ls(1) option

st_atim

last-access time of file data

read

-u

st_mtim

last-modification time of file data

write

default

st_ctim

last-change time of i-node status

chmod, chown

-c

Figure 4.19 The three time values associated with each file

change-status time 的操作有:如:改变文件访问权限, 改变user ID, 改变links的数量 等等。

注意 系统不会维护 i-node 的last-access time 。这是为什么access ,stat 不会改变3个time的原因。

ls 命令通常显示 三个时间中的一个. 默认地, 当调用时 带着 -l  或者 -t 选项, 它会展示文件的修改时间, -u 会展示 access time , -c 选项会展示changed-status time。

4.20 总结了各种函数 对这三个时间的影响。

function

参数file 或目录

父目录

Section

Note

a

m

c

a

m

c

chmod, fchmod

4.9

chown,fchown

4.11

creat

3.4

O_CREAT 新file

creat

3.4

O_TRUNC 已存在文件

exec

8.10

lchown

4.11

link

4.15

mkdir

4.21

mkfifo

15.5

open

3.3

O_CREAT new file

open

3.3

O_TRUNC existing file

pipe

read

3.7

remove

4.15

remove file = unlink

remove

4.15

remove directory=rmdir

rename

4.16

for bath arguments

rmdir

4.21

truncate,ftruncate

4.13

unlink

4.15

utimes,utimensat,futimens

4.20

write

3.8

Figure 4.20 Effect of various functions on the access, modifification, and changed-status times

4.20 futimens,utimensat, utimes Functions

有几个函数 ,可以改变 文件的access time , modification time 。futimens ,utimensat 函数 提供了 纳秒粒度(granularity) 来指定时间戳。使用 timespec 结构体。

#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);

                                                                                Both return: 0 if OK, -1 on error

在这两个函数中,times数组的第一个元素 包含 access time, 第二个元素包含 modification time。  两个时间都是 日历时间,

  1. times 是个 null 指针,这种情况,两个timestamps 被设置为 当前时间。
  2. times 指向一个有两个timespec结构体的数组。 如果tv_nsec 字段有特殊的值 UTIME_NOW, 相应的timestamp 被设置为当前时间。相应的的tv_sec 字段被忽略。
  3. times 指向一个有两个timespec结构体的数组。如果 tv_nsec 字段有特殊值 UTIME_OMIT, 那么对应的timestamp 不改变,对应的 tv_sec 字段 被忽略。
  4. times 指向一个有两个timespec结构体的数组。并且 tv_nsec 包含的值 既不是UTIME_NOW 也不是 UTIME_OMIT。在这种情况,相应的时间戳 是 由 tv_sec 和 tv_nsec 字段来设置的。

执行这些函数 需要什么权限 是 取决于times 参数的

a 如果 times 是 null 指针 或者tv_nsec 字段被设置为 UTIME_NOW ,  要么 进程的effective user ID 等于file的owner ,进程还必须对file 有 write 权限,要么进程是超级用户进程。

b  如果times 非 null指针,并且 tv_nsec 字段有 一个值 不是UTIME_NOW 或UTIME_OMIT,进程的有效 user ID 必须等于 file的owner ID , 或者进程是个超级用户进程。仅仅(Merely)  不需要有写权限

c  如果times 是 非 null 指针,并且tv_nsec 字段被设置为 UTIME_OMIT, 这种 不执行权限检查

flag : AT_SYMLINK_NOFOLLOW flag 被设置,那么,如果pathname指向symbolic link,  symbolic link 自身的times 被改变。默认的行为是 follow symbolic link ,修改link指向的file的times。

futimens , utimensat 被包含在 POSIX.1 . utimes 被包含在Single UNIX specification 做为XSI的一部分。

#include <sys/time.h>

int utimes(const char *pathname, const struct timeval times[2]);

                                                                                        Returns: 0 if OK, -1 on error

times 参数是一个指针 指向 有两个timestamps的数组 (access time, modification time) ,但是他们被 表示为 seconds 和 microseconds:

struct timeval {

                time_t tv_sec; /* seconds */

                long tv_usec; /* microseconds */

};

例子:

4.21  使用open (O_TRUNC)  truncate files 到 zero length, 但是 这个 不改变 access time 或 modification time。 为了做到如此, 程序首先使用 stat 函数获取 times, truncate 文件,并且然后,使用 futimens函数重置 times.

1.#include "apue.h"  
2.#include <fcntl.h>  
3.  
4.int  
5.main(int argc, char *argv[])  
6.{  
7.    int             i, fd;  
8.    struct stat     statbuf;  
9.    struct timespec times[2];  
10.  
11.    for (i = 1; i < argc; i++) {  
12.        if (stat(argv[i], &statbuf) < 0) {   /* fetch current times */  
13.            err_ret("%s: stat error", argv[i]);  
14.            continue;  
15.        }  
16.        if ((fd = open(argv[i], O_RDWR | O_TRUNC)) < 0) { /* truncate */  
17.            err_ret("%s: open error", argv[i]);  
18.            continue;  
19.        }  
20.        times[0] = statbuf.st_atim;  
21.        times[1] = statbuf.st_mtim;  
22.        if (futimens(fd, times) < 0)     /* reset times */  
23.            err_ret("%s: futimens error", argv[i]);  
24.        close(fd);  
25.    }  
26.    exit(0);  
27.}  

Figure 4.21 Example of futimens function
​​​​​​​
$ ls -l changemod times                                 look at sizes and last-modifification times
-rwxr-xr-x 1 sar 13792 Jan 22 01:26 changemod
-rwxr-xr-x 1 sar 13824 Jan 22 01:26 times
$ ls -lu changemod times                                 look at last-access times
-rwxr-xr-x 1 sar 13792 Jan 22 22:22 changemod
-rwxr-xr-x 1 sar 13824 Jan 22 22:22 times
$ date                                                                 print today’s date
Fri Jan 27 20:53:46 EST 2012
$ ./a.out changemod times                                 run the program in Figure 4.21
$ ls -l changemod times                                         and check the results
-rwxr-xr-x 1 sar 0 Jan 22 01:26 changemod
-rwxr-xr-x 1 sar 0 Jan 22 01:26 times
$ ls -lu changemod times                                 check the last-access times also
-rwxr-xr-x 1 sar 0 Jan 22 22:22 changemod
-rwxr-xr-x 1 sar 0 Jan 22 22:22 times
$ ls -lc changemod times                                         and the changed-status times
-rwxr-xr-x 1 sar 0 Jan 27 20:53 changemod
-rwxr-xr-x 1 sar 0 Jan 27 20:53 times

4.21 mkdir , mkdirat ,rmdir函数

#include <sys/stat.h>

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

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

                                                                                        Both return: 0 if OK, -1 on error

这些函数,创建一个 新的,空的目录。 相应的 dot, 和 dot-dot 的entries 被自动的创建。

参数mode  会被进程的 file mode creation mask 修改。

   一个常见的错误是 像指定文件那样 只给 读,写权限。但是对于目录,正常地 我们想要至少一个 execute bits 被打开,为的是能访问 目录里的 文件名。

#include <unistd.h>

int rmdir(const char *pathname);

                                                                Returns: 0 if OK, -1 on error

如果 用rmdir这个调用,把目录的link count 变成0,并且没有其他其他进程打开这个目录,那么目录所占用的空间就会被释放。如果一个或多个进程 打开了 某个目录,这个目录这时的link count 达到了0,最后的link 被(rmdir函数)remove 时,连带着这时 dot,dot-dot 目录也会在 函数返回前  被remove。 另外 ,在这个目录下 不能创建新文件。这个目录不能被freed(释放),直到最后一个进程close 它(才能释放这个目录).

4.22 Reading Directories

目录: 任何进程/用户 对目录有访问权限就可以 访问目录,但是 只有kernel 可以write 目录, 这样是为了 保持(preserve) 文件系统 正常的(sanity)。

许多实现 阻止应用程序区使用 read 函数去 访问目录的内容,

#include <dirent.h>

DIR *opendir(const char *pathname);

DIR *fdopendir(int fd);

                                                Both return: pointer if OK, NULL on error

struct dirent *readdir(DIR *dp);

                                                        Returns: pointer if OK, NULL at end of directory or error

void rewinddir(DIR *dp);

int closedir(DIR *dp);

                                                                Returns: 0 if OK, -1 on error

long telldir(DIR *dp);

Returns: current location in directory associated with dp

void seekdir(DIR *dp, long loc);

fdopendir 函数,它提供了一个 把打开的 file descriptor 转换为 DIR 结构体

dirent 结构体被定义在 <dirent.h> 。实现定义这个结构体至少包含下面两个成员:

ino_t   d_ino;     /* i-node  number */

char   d_name[];   /* null-terminated filename */

注意d_name 的大小没有被指定, 但是它被保证至少容纳NAME_MAX 字符,不包括 terminating null byte

opendir 函数 会做些初始化的事,目的是 readdir 返回这个目录下的first entry。 当使用fdopendir函数返回的DIR 结构体时,readdir 返回的first entry 取决于 传给 fdopendir 的fd的 file offset 。 注意 在这个目录中 entries的顺序 由实现决定,并且 不通常是 按字母排序的(alphabetical)

例子:

使用这几个目录函数,来写一个程序,这个程序 遍历一个 目录。这个目标是 统计 各种类型的file 的数量。Solaris 提供了 函数ftw(3),它遍历层级,并每个文件调用用户定义的函数。

这个函数的问题是 它会跟随 symbolic links 。例如 遍历 /  (root) , /lib symbolic link 指向 /usr/lib    ,所有在/usr/lib 下的文件都会被统计2次,为了纠正这个问题, nftw(3) 它阻止跟随 symbolic links 。 尽管可以使用nftw, 我们写自己的简单的file walker 来 使用下这几个目录routines。

1.#include "apue.h"  
2.#include <dirent.h>  
3.#include <limits.h>  
4.  
5./* function type that is called for each filename */  
6.typedef int Myfunc(const char *, const struct stat *, int);  
7.  
8.static Myfunc   myfunc;  
9.static int      myftw(char *, Myfunc *);  
10.static int      dopath(Myfunc *);  
11.  
12.static long nreg, ndir, nblk, nchr, nfifo, nslink, nsock, ntot;  
13.  
14.int  
15.main(int argc, char *argv[])  
16.{  
17.    int     ret;  
18.  
19.    if (argc != 2)  
20.        err_quit("usage:  ftw  <starting-pathname>");  
21.  
22.    ret = myftw(argv[1], myfunc);       /* does it all */  
23.  
24.    ntot = nreg + ndir + nblk + nchr + nfifo + nslink + nsock;  
25.    if (ntot == 0)  
26.        ntot = 1;       /* avoid divide by 0; print 0 for all counts */  
27.    printf("regular files  = %7ld, %5.2f %%\n", nreg,  
28.      nreg*100.0/ntot);  
29.    printf("directories    = %7ld, %5.2f %%\n", ndir,  
30.      ndir*100.0/ntot);  
31.    printf("block special  = %7ld, %5.2f %%\n", nblk,  
32.      nblk*100.0/ntot);  
33.    printf("char special   = %7ld, %5.2f %%\n", nchr,  
34.      nchr*100.0/ntot);  
35.    printf("FIFOs          = %7ld, %5.2f %%\n", nfifo,  
36.      nfifo*100.0/ntot);  
37.    printf("symbolic links = %7ld, %5.2f %%\n", nslink,  
38.      nslink*100.0/ntot);  
39.    printf("sockets        = %7ld, %5.2f %%\n", nsock,  
40.      nsock*100.0/ntot);  
41.    exit(ret);  
42.}  
43.  
44./* 
45. * Descend through the hierarchy, starting at "pathname". 
46. * The caller's func() is called for every file. 
47. */  
48.#define FTW_F   1       /* file other than directory */  
49.#define FTW_D   2       /* directory */  
50.#define FTW_DNR 3       /* directory that can't be read */  
51.#define FTW_NS  4       /* file that we can't stat */  
52.  
53.static char *fullpath;      /* contains full pathname for every file */  
54.static size_t pathlen;  
55.  
56.static int                  /* we return whatever func() returns */  
57.myftw(char *pathname, Myfunc *func)  
58.{  
59.    fullpath = path_alloc(&pathlen);    /* malloc PATH_MAX+1 bytes */  
60.                                        /* ({Prog pathalloc}) */  
61.    if (pathlen <= strlen(pathname)) {  
62.        pathlen = strlen(pathname) * 2;  
63.        if ((fullpath = realloc(fullpath, pathlen)) == NULL)  
64.            err_sys("realloc failed");  
65.    }  
66.    strcpy(fullpath, pathname);  
67.    return(dopath(func));  
68.}  
69.  
70./* 
71. * Descend through the hierarchy, starting at "fullpath". 
72. * If "fullpath" is anything other than a directory, we lstat() it, 
73. * call func(), and return.  For a directory, we call ourself 
74. * recursively for each name in the directory. 
75. */  
76.static int                  /* we return whatever func() returns */  
77.dopath(Myfunc* func)  
78.{  
79.    struct stat     statbuf;  
80.    struct dirent   *dirp;  
81.    DIR             *dp;  
82.    int             ret, n;  
83.  
84.    if (lstat(fullpath, &statbuf) < 0)   /* stat error */  
85.        return(func(fullpath, &statbuf, FTW_NS));  
86.    if (S_ISDIR(statbuf.st_mode) == 0)  /* not a directory */  
87.        return(func(fullpath, &statbuf, FTW_F));  
88.  
89.    /* 
90.     * It's a directory.  First call func() for the directory, 
91.     * then process each filename in the directory. 
92.     */  
93.    if ((ret = func(fullpath, &statbuf, FTW_D)) != 0)  
94.        return(ret);  
95.  
96.    n = strlen(fullpath);  
97.    if (n + NAME_MAX + 2 > pathlen) {    /* expand path buffer */  
98.        pathlen *= 2;  
99.        if ((fullpath = realloc(fullpath, pathlen)) == NULL)  
100.            err_sys("realloc failed");  
101.    }  
102.    fullpath[n++] = '/';  
103.    fullpath[n] = 0;  
104.  
105.    if ((dp = opendir(fullpath)) == NULL)   /* can't read directory */  
106.        return(func(fullpath, &statbuf, FTW_DNR));  
107.  
108.    while ((dirp = readdir(dp)) != NULL) {  
109.        if (strcmp(dirp->d_name, ".") == 0  ||  
110.            strcmp(dirp->d_name, "..") == 0)  
111.                continue;       /* ignore dot and dot-dot */  
112.        strcpy(&fullpath[n], dirp->d_name);  /* append name after "/" */  
113.        if ((ret = dopath(func)) != 0)      /* recursive */  
114.            break;  /* time to leave */  
115.    }  
116.    fullpath[n-1] = 0;  /* erase everything from slash onward */  
117.  
118.    if (closedir(dp) < 0)  
119.        err_ret("can't close directory %s", fullpath);  
120.    return(ret);  
121.}  
122.  
123.static int  
124.myfunc(const char *pathname, const struct stat *statptr, int type)  
125.{  
126.    switch (type) {  
127.    case FTW_F:  
128.        switch (statptr->st_mode & S_IFMT) {  
129.        case S_IFREG:   nreg++;     break;  
130.        case S_IFBLK:   nblk++;     break;  
131.        case S_IFCHR:   nchr++;     break;  
132.        case S_IFIFO:   nfifo++;    break;  
133.        case S_IFLNK:   nslink++;   break;  
134.        case S_IFSOCK:  nsock++;    break;  
135.        case S_IFDIR:   /* directories should have type = FTW_D */  
136.            err_dump("for S_IFDIR for %s", pathname);  
137.        }  
138.        break;  
139.    case FTW_D:  
140.        ndir++;  
141.        break;  
142.    case FTW_DNR:  
143.        err_ret("can't read directory %s", pathname);  
144.        break;  
145.    case FTW_NS:  
146.        err_ret("stat error for %s", pathname);  
147.        break;  
148.    default:  
149.        err_dump("unknown type %d for pathname %s", type, pathname);  
150.    }  
151.    return(0);  
152.}  
153.  
154.  
155./** 
156. 
157.[xingqiji@work78 filedir]$ ./ftw8 /usr 
158.can't read directory /usr/share/polkit-1/rules.d/: Permission denied 
159.can't read directory /usr/libexec/initscripts/legacy-actions/auditd/: Permission denied 
160.regular files  =  129070, 86.41 % 
161.directories    =    9752,  6.53 % 
162.block special  =       0,  0.00 % 
163.char special   =       0,  0.00 % 
164.FIFOs          =       0,  0.00 % 
165.symbolic links =   10546,  7.06 % 
166.sockets        =       0,  0.00 % 
167.[xingqiji@work78 filedir]$  
168. 
*/

另外 使用这种技术来 向下遍历 一个文件系统,的命令有很多如:find, ls, tar,  等等

4.23 chdir, fchdir, getcwd 函数

每个进程 都有一个当前工作目录, 这个目录是 所有相对pathnames 开始 搜索的地方。

当用户登录到unix系统上, 当前工作目录就是 /etc/passwd file 中的sixth field (用户的家目录)。 当前工作目录(current working directory) 是进程的一个属性; 家目录 是 login name的一个属性。

通过调用 chdir , fchdir 函数可以改变调用进程的 当前工作目录。

#include <unistd.h>

int chdir(const char *pathname);

int fchdir(int fd);

                                                        Both return: 0 if OK, -1 on error

例子:filedir/mycd.c

#include "apue.h"

int
main(void)
{
	if (chdir("/tmp") < 0)
		err_sys("chdir failed");
	printf("chdir to /tmp succeeded\n");
	exit(0);
}

​​​​​​4.23 Example of chdir function

$ pwd

/usr/lib

$ mycd

chdir to /tmp succeeded

$ pwd

/usr/lib

执行mycd 程序的shell 它的当前工作目录 不会改变

因为kernel 必须维护当前工作目录的信息,我们必须能获取它的current value。 不幸的是内核不维护 目录的 full pathname .替代地,内核会保存关于目录的一些信息,比如 一个指针 指向目录的 v-node

#include <unistd.h>

char *getcwd(char *buf, size_t size);

                                                                                Returns: buf if OK, NULL on error

buffer 必须足够大 来容纳(accommodate)  绝对文件名 + terminating null byte ,或者是返回的error .

例子:filedir/cdpwd.c

      在图4.24中的程序 改变到一个特定的目录,然后调用 getcwd 去打印 工作目录,如果我们运行这个程序,我们得到

#include "apue.h"

int
main(void)
{
	char	*ptr;
	size_t		size;

	/* if (chdir("/usr/spool/uucppublic") < 0) */
	if (chdir("/etc/rc0.d") < 0)
		err_sys("chdir failed");

	ptr = path_alloc(&size);	/* our own function */
	if (getcwd(ptr, size) == NULL)
		err_sys("getcwd failed");

	printf("cwd = %s\n", ptr);
	exit(0);
}


/**

[xingqiji@localhost filedir]$ ./cdpwd
cwd = /etc/rc.d/rc0.d
[xingqiji@localhost filedir]$ ll /etc/rc0.d
lrwxrwxrwx 1 root root 10 12月  7 2020 /etc/rc0.d -> rc.d/rc0.d
[xingqiji@localhost filedir]$ 


*/

getcwd 函数 是很有用的,当我们的应用程序需要 返回到文件系统 开始出去的地方。我们可以调用getcwd 保存开始的位置,然后再切换工作目录。完成处理后,再传pathname (之前getcwd获得的)给chdir 来返回到 文件系统的 开始的位置。

fchdir 函数 提供了一种完成这项任务的更简单的方式。我们可以打开当前目录,并且保存file descriptor ,然后切换到不同的位置,当想要回到开始的地方,我们可以简单地传file descriptor 给 fchdir 。

4.24  Device Special Files

每个文件系统 都有 major 和 minor device numbers , 这个被编码在 正数的数据类型 dev_t 中。 回想下 4.13 disk drive  常常包含 几个文件系统。 在相同disk drive上几个文件系统,通常有相同的major number, 但是 有不同的 minor number。

我们通常通过两个macros :major ,minor 来获得 major,minor numbers。

st_dev 值 是 每个文件的  device number  (所在的文件系统的device number)

st_rdev 值  只有 字符special files 和 块special file 有 st_rdev 值,这个值包含了 真实设备的 device number

例子:filedir/devrdev.c

在图4.25中程序  打印 每个命令行参数对应的 device number。另外,如果参数 引用到了 一个 character special file 或 一个 block special file , 那么针对这个文件的 st_rdev value 就会被打印。

#include "apue.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);
}

/**

[xingqiji@localhost filedir]$ ./devrdev  /home/sar /dev/tty[01]
/home/sar: stat error: No such file or directory
/dev/tty0: dev = 0/5 (character) rdev = 4/0
/dev/tty1: dev = 0/5 (character) rdev = 4/1
[xingqiji@localhost filedir]$ ls -l /dev/tty[01] /dev/sda[34]
ls: 无法访问/dev/sda[34]: 没有那个文件或目录
crw--w---- 1 root tty 4, 0 5月  30 09:38 /dev/tty0
crw--w---- 1 root tty 4, 1 5月  30 09:39 /dev/tty1

*/

4.25 File Access Permission Bits 总结

常量

描述

对regular file影响

对目录的影响

S_ISUID

set-user-ID

在execution 设置effective user ID

不使用

S_ISGID

set-group-ID

如果group-execute set ,在 execution 上设置 effective group ID, 否则 启用mandatory record locking(如果支持的话)

在这个目录下新创建的文件 的 group ID 设置的跟目录的group ID 一样

S_ISVTX

sticky bit

control caching of file contents (如果支持)

约束(restrict) 在这个目录下 移除和重命名file

S_IRUSR

user-read

user 读权限

user 读 directory entries 权限

S_IWUSR

user-write

user 写 文件权限

user 在这个目录中 移除或创建文件 权限

S_IXUSR

user-write

user 执行 文件权限

user 在这个目录中 搜索pathname 的权限

S_IRGRP

group-read

group 读文件的权限

group 读directory entries 的权限

S_IWGRP

group-write

group 写文件的权限

group 在这个目录中

移除或创建文件的权限 

S_IXGRP

group-execute

group 执行文件的权限

group 在这个目录中搜索pathname 的权限

S_IROTH

other-read

other读文件的权限

other 读 directory entries

S_IWOTH

other-write

other 写文件的权限

other 在这个目录移除 和创建文件的权限

S_IXOTH

other-execute

other 执行文件的权限

other 在这个目录搜索pathname的权限

The final nine constants can also be grouped into threes, as follows:

S_IRWXU = S_IRUSR | S_IWUSR | S_IXUSR

S_IRWXG = S_IRGRP | S_IWGRP | S_IXGRP

S_IRWXO = S_IROTH | S_IWOTH | S_IXOTH

​​​​​​​4.26 Summary

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
可以使用C++的文件操作库和Linux系统调用来实现遍历路径下所有文件目录,并输出文件目录的名称。下面是一个示例代码: ```cpp #include <iostream> #include <dirent.h> #include <sys/stat.h> #include <unistd.h> void traverseDir(const std::string& path) { DIR* dir = opendir(path.c_str()); if (!dir) { std::perror("opendir"); return; } struct dirent* entry; while ((entry = readdir(dir)) != nullptr) { if (entry->d_name[0] == '.') { continue; // skip hidden files and directories } std::string fullPath = path + "/" + entry->d_name; struct stat fileStat; if (lstat(fullPath.c_str(), &fileStat) == -1) { std::perror("lstat"); continue; } if (S_ISDIR(fileStat.st_mode)) { std::cout << "Directory: " << fullPath << std::endl; traverseDir(fullPath); } else if (S_ISREG(fileStat.st_mode)) { std::cout << "File: " << fullPath << std::endl; } } closedir(dir); } int main(int argc, char* argv[]) { if (argc != 2) { std::cerr << "Usage: " << argv[0] << " <directory>" << std::endl; return 1; } traverseDir(argv[1]); return 0; } ``` 这个程序接受一个参数,即要遍历的目录路径。程序使用`opendir()`打开目录,使用`readdir()`逐个读取目录项,使用`lstat()`获取每个目录项的文件状态。根据文件状态的`st_mode`成员,可以判断一个目录项是文件还是目录。如果是目录,递归调用`traverseDir()`函数遍历它。如果是文件,输出文件路径。注意,程序跳过了所有以`.`开头的文件目录,因为它们通常被认为是隐藏文件目录

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值