《UNIX 环境高级编程》笔记0x3:文件和目录

此为第四章笔记。讲了好多的API啊,根本记不住,烦死了啊。对于文件系统那一部分我觉得我还是需要再看几遍的,有一点难度。


函数 stat、fstat、fstatat和lstat

函数原型分别如下

1
2
3
4
5
6
#include <sys/stat.h>

int stat(const char *restrict pathname, struct stat *restrict buf);
int fstae(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 做一个实验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
struct stat sb;

if (argc != 2) {
fprintf(stderr, "Usage: %s <pathname>\n", argv[0]);
}

if (stat(argv[1], &sb) == -1) {
perror("stat");
exit(EXIT_FAILURE);
}

printf("File type:\t");
switch (sb.st_mode & S_IFMT) {
case S_IFDIR: printf("directionary\n"); break;
// mutiply file types ...
case S_IFREG: printf("regular file\n"); break;
default: printf("unknown\n"); break;
}

printf("Preferred I/O block size: %ld bytes\n", (long)sb.st_blksize);
printf("File size: %lld bytes\n", (long long)sb.st_size);
printf("Blocks allocated: %lld\n", (long long) sb.st_blocks);

printf("Last status change: %s", ctime(&sb.st_ctime));
printf("Last file access: %s", ctime(&sb.st_atime));
printf("Last file modification: %s", ctime(&sb.st_mtime));

exit(EXIT_SUCCESS);
}
1
2
3
4
5
6
7
8
> ./a.out ls_test.c
File type: regular file
Preferred I/O block size: 4096 bytes
File size: 1008 bytes
Blocks allocated: 8
Last status change: Wed Aug 21 08:53:08 2019
Last file access: Wed Aug 21 08:53:14 2019
Last file modification: Wed Aug 21 08:53:08 2019

这个函数可以获取文件的大部分状态信息,并存放至结构体 stat 中。全部的信息如下

image1.png

image2.png

当然,还有另外一个方式判断文件类型:使用其预定义的宏(PDFp96)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <apue.h>

int main(int argc, char *argv[])
{
int i;
struct stat buf;
char *ptr;

for (i = 1; i < argc; i++) {
printf("%s: ", argv[i]);
if (lstat(argv[i], &buf) < 0) {
err_ret("lstat error");
continue;
}

if (S_ISREG(buf.st_mode))
ptr = "regular";
else if (S_ISDIR(buf.st_mode))
ptr = "directory";
// ...... many file types
else if (S_ISLNK(buf.st_mode))
ptr = "symbolic link";
else
ptr = "** unknown node **";

printf("%s\n", ptr);
}

exit(EXIT_SUCCESS);
}
1
2
3
4
> ./a.out /dev/cdrom /etc/passwd /etc
/dev/cdrom: symbolic link
/etc/passwd: regular
/etc: directory

其实宏中定义就像第一种方法

1
2
3
#define	__S_ISTYPE(mode, mask)	(((mode) & __S_IFMT) == (mask))

#define S_ISDIR(mode) __S_ISTYPE((mode), __S_IFDIR)

和进程相关的ID

  • 当前用户
    • 实际用户 ID
    • 实际组 ID
  • 文件访问权限
    • 有效用户 ID
    • 有效组 ID
    • 附属组 ID
  • 保存时的设置
    • 保存的设置用户 ID
    • 保存的设置组 ID

文件访问权限

每个文件有9个访问权限位,常量定义在 <sys/stat.h>

  • 用户 u
    • S_IRUSER
    • S_IWUSER
    • S_IXUSER 执行
  • g
    • S_IRGRP
    • S_IWGRP
    • S_IXGRP 执行
  • 其他 o
    • S_IROTH
    • S_IWOTH
    • S_IXOTH 执行

可以使用系统调用 accessfaccessat 来验证当前用户能否访问

1
2
3
4
#include <unistd.h>

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

mode 列表如下

mode 说明
R_OK 测试读权限
W_OK 测试写权限
X_OK 测试执行权限

第二的函数的 flag 若为 AT_EACCESS,则测试的是调用进程的有效用户ID和有效组ID,而不是实际用户ID和实际组ID

第二个函数在下列几种情况下和第一个函数作用相同

  • pathname 为绝对路径
  • fdAT_FDCWD 当前工作路径、pathname 为相对路径

否则其计算的是相对于打开目录( fd 参数指向)的 pathname


使用屏蔽词umask

通过设置屏蔽词来关闭文件 mode 中的相应位

系统调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdlib.h>
#include <fcntl.h>
#include <apue.h>

#define RWRWRW (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)

int main(int argc, char *argv[])
{
// 设置屏蔽词
umask(0);

if (creat("foo", RWRWRW) < 0)
err_sys("creat error for foo");

// 修改屏蔽词
umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);

if (creat("bar", RWRWRW) < 0)
err_sys("creat error for bar");

exit(EXIT_SUCCESS);
}

执行此程序后,发现 bar 文件的全部和设置的并不一致,这是因为设置了 umask

1
2
-rw------- 1 parallels parallels   0 8月  21 13:45 bar
-rw-rw-rw- 1 parallels parallels 0 8月 21 13:45 foo

命令行用法

umask的文件访问位

屏蔽位 含义
用户 -
0400
0200
0100 执行
-
0040
0020
0010 执行
其他 -
0004
0002
0001 执行
1
2
3
4
5
6
> umask
002

> umask 027
> umask -S
u=rwx,g=rx,o=

常用的umask组合

  • 002 阻止其他用户写入你的文件
  • 022 阻止其他用户以及同组用户写入你的文件
  • 027 阻止同组用户写入你的文件,以及其他用户读、写或执行你的文件

粘着位

如果执行如下指令

1
2
ls -dl /tmp
# drwxrwxrwt 14 root root 4096 8月 21 14:48 /tmp

输出的权限位列表中有一个 t,这个就是 粘着位 (sticky bit)。对于 /tmp

  • 任何用户都可以在其中创建文件。任一用户对此目录的权限都是读、写和执行。
  • 任何用户(除了root)都不可以删除或重命名属于其他人的文件。

命令行创建

1
2
3
chmod o+t   tmp
chmod +t tmp
chmod 1757 tmp

代码创建 S_ISVTX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdlib.h>
#include <sys/stat.h>
#include <apue.h>

int main(int argc, char *argv[])
{
struct stat statbuf;
char* dirname = "tmp";

if (stat(dirname, &statbuf) < 0)
err_sys("stat error for %s", dirname);

if (chmod(dirname, (statbuf.st_mode | S_ISVTX)) < 0)
err_sys("chmod error for %s", dirname);

exit(EXIT_SUCCESS);
}

文件系统

PDFp111


使用 unlink 来确保程序的临时文件在退出的时候可以彻底删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <apue.h>
#include <fcntl.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
char *filename = "output.file";
if (open (filename, O_RDWR) < 0)
err_sys("open error");

if (unlink(filename) < 0)
err_sys("unlink error");

printf("file unlinked\n");

sleep(15);
printf("done\n");
exit(EXIT_SUCCESS);
}

程序执行时文件立刻被删除,但是占用空间是在进程退出时删除的。


文件的时间

字段 说明 例子 ls 选项
st_atim 最后访问时间 read -u
st_mtim 最后修改时间 write 默认
st_ctim i节点状态的最后更改时间 chmod, chown -c

处理文件时间的函数 futimensutimensat 以及 utimes 需要传入的 timespec 数组中,第一个数组元素为访问时间,第二个数组元素为修改时间,分别对应 stat 结构体中的 st_atimst_mtim

下列代码对文件信息进行截断,同时不改变其访问和修改日期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <apue.h>
#include <fcntl.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
int i,fd;
struct stat statbuf;
struct timespec times[2];

for (i = 1; i < argc; i++) {
if (stat(argv[i], &statbuf) < 0) { /* 获取当前文件时间信息 */
err_ret("%s: stat error", argv[i]);
continue;
}
if ((fd = open(argv[i], O_RDWR | O_TRUNC)) < 0) { /* 对文件进行截断操作 */
err_ret("%s: open error", argv[i]);
continue;
}
times[0] = statbuf.st_atim;
times[1] = statbuf.st_mtim;
if (futimens(fd, times) < 0) { /* 修改文件的时间 */
err_ret("%s: futimens error", argv[i]);
}
close(fd);
}
exit(EXIT_SUCCESS);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
> ls -lu a.txt
-rw-rw-r-- 1 parallels parallels 23 8月 21 18:04 a.txt

> ls -l a.txt
-rw-rw-r-- 1 parallels parallels 23 8月 21 18:03 a.txt

> ./a.out a.txt

> ls -lu a.txt
-rw-rw-r-- 1 parallels parallels 0 8月 21 18:04 a.txt

> ls -l a.txt
-rw-rw-r-- 1 parallels parallels 23 8月 21 18:03 a.txt

> ls -lc a.txt # 这里节点状态信息修改时间改变了
-rw-rw-r-- 1 parallels parallels 0 8月 21 18:05 a.txt

当前的工作路径

使用函数 chdirfchdir,定义在 <unistd.h>

文件名以及路径的最大长度设置在 <limits.h> 文件中

1
2
#define NAME_MAX         255	/* # chars in a file name */
#define PATH_MAX 4096 /* # chars in a path name including nul */

修改当前的路径,并打印其绝对路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <apue.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
char *ptr;
size_t size;

if (chdir("/usr/libexec/docker") < 0) {
err_sys("chdir failed");
}

ptr = path_alloc(&size);

if (getcwd(ptr, size) == NULL) {
err_sys("getcwd failed");
}

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

exit(EXIT_SUCCESS);
}

识别设备编号

每个文件系统所在的存储设备都由其 主、次设备号 表示。设备号所用的数据类型是基本系统数据类型 dev_t主设备号 标识 设备驱动程序与其通信的外设板次设备号 标识 特定的子设备

通过宏 majorminor 来访问主、次设备号。对于Linux,这两个宏定义在头文件 <sys/sysmacros.h> 中。

st_dev 为文件系统的设备号,包含 文件名 及与其对应的 i节点 。 只有 字符特殊文件块特殊文件 才有 st_rdev 值,此值包含实际设备的设备号。

下列函数能打印设备号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <apue.h>
#include <stdlib.h>
#include <stat.h>
#include <sys/sysmacros.h>

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(EXIT_SUCCESS);
}

编译执行后有如下输出

1
2
3
> ./a.out /home/parallels /dev/tty
/home/parallels: dev = 8/1
/dev/tty: dev = 0/6 (character) rdev = 5/0