参考引用
- UNIX 环境高级编程 (第3版)
- 嵌入式Linux C应用编程-正点原子
普通文件(regular file)也就是一般意义上的文件
普通文件可以分为两大类:文本文件和二进制文件
在 Linux 系统下,可以通过 stat 命令或者 ls -l 命令来查看文件类型
$ stat test.c # 会直观把文件类型显示出来
$ ls -l test.c
# 第一个字符('-')就用于表示文件的类型
# '-':普通文件
# 'd':目录文件
# 'c':字符设备文件
# 'b':块设备文件
# 'l':符号链接文件
# 's':套接字文件
# 'p':管道文件
$ vi tcp_socket
" ============================================================================
" Netrw Directory Listing (netrw v156)
" /home/yxd/tcp_socket
" Sorted by name
" Sort sequence: [\/]$,\,\.h$,\.c$,\.cpp$,\~\=\*$,*,\.o$,\
" Quick Help: :help -:go up dir D:delete R:rename s:sort-by x:special
" ==============================================================================
../
./
client.c
server.c
client*
server*
#include
#include
#include
// pathname:用于指定一个需要查看属性的文件路径。
// buf:调用 stat 函数的时候需要传入一个 struct stat 变量的指针,获取到的文件属性信息就记录在 struct stat 结构体中
// 返回值:成功返回 0;失败返回 -1,并设置 error
int stat(const char *pathname, struct stat *buf);
struct stat {
dev_t st_dev; /* 文件所在设备的 ID,用于描述此文件所在的设备,不常用 */
ino_t st_ino; /* 文件对应 inode 节点编号 */
mode_t st_mode; /* 文件对应的模式,如文件类型、文件权限都记录在该变量中 */
nlink_t st_nlink; /* 文件的链接数 */
uid_t st_uid; /* 文件所有者的用户 ID */
gid_t st_gid; /* 文件所有者的组 ID */
dev_t st_rdev; /* 设备号(只针对设备文件) */
off_t st_size; /* 文件大小(以字节为单位) */
blksize_t st_blksize; /* 文件内容存储的块大小 */
blkcnt_t st_blocks; /* 文件内容所占块数 */
struct timespec st_atim; /* 文件最后被访问的时间 */
struct timespec st_mtim; /* 文件内容最后被修改的时间 */
struct timespec st_ctim; /* 文件状态最后被改变的时间 */
};
st_mode 是 struct stat 结构体中的一个成员变量,是一个 32 位无符号整型数据,该变量记录了文件的类型、文件的权限这些信息,其表示方法如下所示
表示权限的宏定义
S_IRWXU 00700 owner has read, write, and execute permission
S_IRUSR 00400 owner has read permission
S_IWUSR 00200 owner has write permission
S_IXUSR 00100 owner has execute permission
S_IRWXG 00070 group has read, write, and execute permission
S_IRGRP 00040 group has read permission
S_IWGRP 00020 group has write permission
S_IXGRP 00010 group has execute permission
S_IRWXO 00007 others (not in group) have read, write, and execute permission
S_IROTH 00004 others have read permission
S_IWOTH 00002 others have write permission
S_IXOTH 00001 others have execute permission
“文件类型” 这 4 个 bit 位用于描述该文件的类型,如:该文件是普通文件、还是链接文件、亦或者是一个目录等
S_IFSOCK 0140000 socket(套接字文件)
S_IFLNK 0120000 symbolic link(链接文件)
S_IFREG 0100000 regular file(普通文件)
S_IFBLK 0060000 block device(块设备文件)
S_IFDIR 0040000 directory(目录)
S_IFCHR 0020000 character device(字符设备文件)
S_IFIFO 0010000 FIFO(管道文件)
// S_IFMT 宏是文件类型字段位掩码
S_IFMT 0170000
通过 st_mode 变量判断文件类型(假设 st 是 struct stat 类型变量)
/* 判断是不是普通文件 */
if ((st.st_mode & S_IFMT) == S_IFREG) {
/* 是 */
}
/* 判断是不是链接文件 */
if ((st.st_mode & S_IFMT) == S_IFLNK) {
/* 是 */
}
还可以使用 Linux 系统封装好的宏来判断文件类型(m 是 st_mode 变量)
S_ISREG(m) # 判断是不是普通文件,如果是返回 true,否则返回 false
S_ISDIR(m) # 判断是不是目录,如果是返回 true,否则返回 false
S_ISCHR(m) # 判断是不是字符设备文件,如果是返回 true,否则返回 false
S_ISBLK(m) # 判断是不是块设备文件,如果是返回 true,否则返回 false
S_ISFIFO(m) # 判断是不是管道文件,如果是返回 true,否则返回 false
S_ISLNK(m) # 判断是不是链接文件,如果是返回 true,否则返回 false
S_ISSOCK(m) # 判断是不是套接字文件,如果是返回 true,否则返回 false
/* 判断是不是普通文件 */
if (S_ISREG(st.st_mode)) {
/* 是 */
}
/* 判断是不是目录 */
if (S_ISDIR(st.st_mode)) {
/* 是 */
}
struct timespec {
// time_t 其实是指 long int 类型
time_t tv_sec; /* 秒 */
syscall_slong_t tv_nsec; /* 纳秒 */
};
1、获取文件的 inode 节点编号以及文件大小,并将它们打印出来
#include
#include
#include
#include
#include
int main(void) {
struct stat file_stat;
int ret;
/* 获取文件属性 */
ret = stat("./test_file", &file_stat);
if (ret == -1) {
perror("stat error");
exit(-1);
}
/* 打印文件大小和 inode 编号 */
printf("file size: %ld bytes\ninode number: %ld\n", file_stat.st_size, file_stat.st_ino);
exit(0);
}
$ ls -li test_file
2883653 -rw-rw-r-- 1 yue yue 13 12月 20 21:37 test_file
$ gcc demo01.c -o demo01
$ ./demo01
file size: 13 bytes
inode number: 2883653
2、获取文件的类型,判断此文件对于其它用户(Other)是否具有可读可写权限
#include
#include
#include
#include
#include
int main(void) {
struct stat file_stat;
int ret;
/* 获取文件属性 */
ret = stat("./test_file", &file_stat);
if (ret == -1) {
perror("stat error");
exit(-1);
}
/* 判读文件类型 */
switch (file_stat.st_mode & S_IFMT) {
case S_IFSOCK: printf("socket"); break;
case S_IFLNK: printf("symbolic link"); break;
case S_IFREG: printf("regular file"); break;
case S_IFBLK: printf("block device"); break;
case S_IFDIR: printf("directory"); break;
case S_IFCHR: printf("character device"); break;
case S_IFIFO: printf("FIFO"); break;
}
printf("\n");
/* 判断该文件对其它用户是否具有读权限 */
if (file_stat.st_mode & S_IROTH)
printf("Read: Yes\n");
else
printf("Read: No\n");
/* 判断该文件对其它用户是否具有写权限 */
if (file_stat.st_mode & S_IWOTH)
printf("Write: Yes\n");
else
printf("Write: No\n");
exit(0);
}
$ gcc demo02.c -o demo02
$ ./demo02
regular file
Read: Yes
Write: No
3、获取文件的时间属性,包括文件最后被访问的时间、文件内容最后被修改的时间以及文件状态最后被改变的时间,并使用字符串形式将其打印出来,包括时间和日期、表示形式自定
#include
#include
#include
#include
#include
#include
int main(void) {
struct stat file_stat;
struct tm file_tm;
char time_str[100];
int ret;
/* 获取文件属性 */
ret = stat("./test_file", &file_stat);
if (-1 == ret) {
perror("stat error");
exit(-1);
}
/* 打印文件最后被访问的时间 */
localtime_r(&file_stat.st_atim.tv_sec, &file_tm);
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", &file_tm);
printf("time of last access: %s\n", time_str);
/* 打印文件内容最后被修改的时间 */
localtime_r(&file_stat.st_mtim.tv_sec, &file_tm);
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", &file_tm);
printf("time of last modification: %s\n", time_str);
/* 打印文件状态最后改变的时间 */
localtime_r(&file_stat.st_ctim.tv_sec, &file_tm);
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", &file_tm);
printf("time of last status change: %s\n", time_str);
exit(0);
}
$ gcc demo03.c -o demo03
$ ./demo03
time of last access: 2023-12-20 21:37:14
time of last modification: 2023-12-20 21:37:14
time of last status change: 2023-12-20 21:37:14
#include
#include
#include
int fstat(int fd, struct stat *buf);
#include
#include
#include
#include
#include
#include
int main(void) {
struct stat file_stat;
int fd;
int ret;
/* 打开文件 */
fd = open("./test_file", O_RDONLY);
if (-1 == fd) {
perror("open error");
exit(-1);
}
/* 获取文件属性 */
ret = fstat(fd, &file_stat);
if (-1 == ret)
perror("fstat error");
close(fd);
exit(ret);
}
#include
#include
#include
int lstat(const char *pathname, struct stat *buf);
chown 是一个系统调用,该系统调用可用于改变文件的所有者(用户 ID)和所属组(组 ID)
#include
// pathname:用于指定一个需要修改所有者和所属组的文件路径
// owner:将文件的所有者修改为该参数指定的用户(以用户 ID 的形式描述)
// group:将文件的所属组修改为该参数指定的用户组(以用户组 ID 的形式描述)
int chown(const char *pathname, uid_t owner, gid_t group);
该函数有以下两个限制条件
示例
#include
#include
#include
int main(void) {
// 将 test_file 文件的用户 ID 和用户组 ID 修改为 0、0。0 指的就是 root 用户和 root 用户组
if (chown("./test_file", 0, 0) == -1) {
perror("chown error");
exit(-1);
}
exit(0);
}
$ gcc demo
$ sudo ./a.out # 必须使用 sudo 超级用户权限才可执行
File: test_file
Size: 13 Blocks: 8 IO Block: 4096 regular file
Device: 801h/2049d Inode: 2246591 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2023-12-21 20:16:57.033979115 +0800
Modify: 2023-12-21 20:16:21.528235441 +0800
Change: 2023-12-21 20:16:57.033979115 +0800
Birth: -
在 Linux 系统下,可以使用 getuid 和 getgid 两个系统调用分别用于获取当前进程的用户 ID 和用户组 ID,这里说的进程的用户 ID 和用户组 ID 指的就是进程的实际用户 ID 和实际组 ID
#include
#include
uid_t getuid(void);
gid_t getgid(void);
// 使用示例
printf("uid: %d\n", getuid());
下述 ‘-rwxrwxr-x’ 就描述了该文件的 9 个访问权限以及文件类型
$ ls -l
total 20
-rwxrwxr-x 1 yxd yxd 8384 12月 21 20:16 a.out
-rw-rw-r-- 1 yxd yxd 287 12月 21 20:15 demo.c
-rw-rw-r-- 1 root root 13 12月 21 20:16 test_file
// 以下数字使用的是八进制方式表示
// 对应的 bit 位数字为 1,则表示设置了该权限、为 0 则表示并未设置该权限
S_ISUID 04000 set-user-ID bit
S_ISGID 02000 set-group-ID bit
S_ISVTX 01000 sticky bit
Linux 系统下绝大部分的文件都没有设置 set-user-ID 位权限和 set-group-ID 位权限,通常情况下,进程的有效用户 = 实际用户(有效用户 ID = 实际用户 ID),有效组 = 实际组(有效组 ID = 实际组 ID)
#include
// pathname:需要进行权限检查的文件路径
// mode:该参数可以取以下值(除了单独使用外,还可通过按位或运算符 "|" 组合)
// F_OK:检查文件是否存在
// R_OK:检查是否拥有读权限
// W_OK:检查是否拥有写权限
// X_OK:检查是否拥有执行权限
int access(const char *pathname, int mode);
在 Linux 系统下,可以使用 chmod 命令修改文件权限,该命令内部实现方法其实是调用了 chmod 函数,chmod 函数是一个系统调用
#include
// pathname:需要进行权限修改的文件路径,若该参数所指为符号链接,实际改变权限的文件是符号链接所指向的文件,而不是符号链接文件本身
// mode :该参数用于描述文件权限,可以直接使用八进制数据来描述,也可以使用相应的权限宏
int chmod(const char *pathname, mode_t mode);
fchmod 函数
#include
int fchmod(int fd, mode_t mode);
umask 命令用于查看/设置权限掩码,权限掩码主要用于对新建文件的权限进行屏蔽,权限掩码的表示方式与文件权限的表示方式相同,但是需要去除特殊权限位,umask 不能对特殊权限位进行屏蔽
$ umask
0002
当新建文件时,文件实际的权限并不等于所设置的权限,如:调用 open 函数新建文件时,文件实际的权限并不等于 mode 参数所描述的权限,而是通过如下关系得到实际权限
mode & ~umask
0777 & (~0002) = 0775
umask 权限掩码是进程的一种属性,用于指明该进程新建文件或目录时,应屏蔽哪些权限位。Linux 系统提供了 umask 函数用于设置进程的权限掩码,该函数是一个系统调用
#include
#include
mode_t umask(mode_t mask);
文件的时间属性虽然会在对文件进行相关操作(如:读、写)时发生改变,但这些改变都是隐式、被动的改变
#include
#include
// filename:需要修改时间属性的文件路径
// times:将时间属性修改为该参数所指定的时间值,times 是一个 struct utimbuf 结构体类型的指针,如果将 times 设置为 NULL
// 则会将文件的访问时间和修改时间设置为系统当前时间
int utime(const char *filename, const struct utimbuf *times);
struct utimbuf 结构体
struct utimbuf {
// time_t 其实就是 long int 类型,所以这两个时间是以秒为单位的
time_t actime; /* 访问时间 */
time_t modtime; /* 内容修改时间 */
}
对文件时间属性的修改也不是任何用户都可以随便修改的,只有以下几种进程可对其进行修改
示例
#include
#include
#include
#include
#include
#include
#define MY_FILE "./test_file"
int main(void) {
struct utimbuf utm_buf;
time_t cur_sec;
int ret;
/* 检查文件是否存在 */
ret = access(MY_FILE, F_OK);
if (ret == -1) {
printf("Error: %s file does not exist!\n", MY_FILE);
exit(-1);
}
/* 获取当前时间 */
time(&cur_sec);
utm_buf.actime = cur_sec;
utm_buf.modtime = cur_sec;
/* 修改文件时间戳 */
ret = utime(MY_FILE, &utm_buf);
if (ret == -1) {
perror("utime error");
exit(-1);
}
exit(0);
}
$ gcc demo03.c
$ sudo ./a.out
$ stat test_file
File: test_file
Size: 13 Blocks: 8 IO Block: 4096 regular file
Device: 801h/2049d Inode: 2246591 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 0/ root) Gid: ( 0/ root)
# test_file 文件的访问时间和内容修改时间均被更改为当前时间
Access: 2023-12-22 12:36:27.458464548 +0800
Modify: 2023-12-22 12:36:27.000000000 +0800
Change: 2023-12-22 12:36:27.458464548 +0800
Birth: -
utimes() 也是系统调用,功能与 utime()函数一致,只是参数、细节上有些许不同,utimes() 与 utime() 最大的区别在于前者可以以微秒级精度来指定时间值
#include
// filename:需要修改时间属性的文件路径
// times:将时间属性修改为该参数所指定的时间值,times 是一个 struct timeval 结构体类型的数组
// 数组共有两个元素,第一个元素用于指定访问时间,第二个元素用于指定内容修改时间
// 如果 times 参数为 NULL,则会将文件的访问时间和修改时间设置为当前时间
int utimes(const char *filename, const struct timeval times[2]);
struct timeval 结构体
struct timeval {
long tv_sec; /* 秒 */
long tv_usec; /* 微秒 */
};
#include
#include
// fd:文件描述符。
// times:将时间属性修改为该参数所指定的时间值,times 指向拥有 2 个 struct timespec 结构体类型变量的数组
// 数组共有两个元素,第一个元素用于指定访问时间,第二个元素用于指定内容修改时间
int futimens(int fd, const struct timespec times[2]);
#include
#include
// dirfd:该参数可以是一个目录的文件描述符,也可以是特殊值 AT_FDCWD;如果 pathname 参数指定的是文件的绝对路径,则此参数会被忽略
// pathname:指定文件路径。如果 pathname 参数指定的是一个相对路径、并且 dirfd 参数不等于特殊值 AT_FDCWD,则实际操作的文件路径是相对于文件描述符 dirfd 指向的目录进行解析
// 如果 pathname 参数指定的是一个相对路径、并且 dirfd 参数等于特殊值 AT_FDCWD,则实际操作的文件路径是相对于调用进程的当前工作目录进行解析
// times:与 futimens() 的 times 参数含义相同
// flags:此参数可以为 0, 也可以设置为 AT_SYMLINK_NOFOLLOW, 如果设置为 AT_SYMLINK_NOFOLLOW
// 当 pathname 参数指定的文件是符号链接,则修改的是该符号链接的时间戳,而不是它所指向的文件
int utimensat(int dirfd, const char *pathname, const struct timespec times[2], int flags);
#include
// oldpath:用于指定被链接的源文件路径,应避免 oldpath 参数指定为软链接文件,为软链接文件创建硬链接没有意义,虽然并不会报错
// newpath:用于指定硬链接文件路径,如果 newpath 指定的文件路径已存在,则会产生错误
int link(const char *oldpath, const char *newpath);
#include
#include
#include
int main(void) {
int ret;
ret = link("./test_file", "./hard");
if (ret == -1) {
perror("link error");
exit(-1);
}
exit(0);
}
#include
// target:用于指定被链接的源文件路径,target 参数指定的也可以是一个软链接文件
// linkpath:用于指定硬链接文件路径,如果 newpath 指定的文件路径已存在,则会产生错误
int symlink(const char *target, const char *linkpath);
软链接文件数据块中存储的是被链接文件的路径信息,使用系统调用 readlink 读取出软链接文件中存储的路径信息
#include
// pathname:需要读取的软链接文件路径。只能是软链接文件路径,不能是其它类型文件,否则调用函数将报错
// buf:用于存放路径信息的缓冲区
// bufsiz:读取大小,一般读取的大小需要大于链接文件数据块中存储的文件路径信息字节大小
// 返回值:失败将返回-1,并会设置 errno;成功将返回读取到的字节数
ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
示例
#include
#include
#include
#include
int main(void) {
char buf[50];
int ret;
memset(buf, 0x0, sizeof(buf));
ret = readlink("./soft", buf, sizeof(buf));
if (ret == -1) {
perror("readlink error");
exit(-1);
}
printf("%s\n", buf);
exit(0);
}
#include
#include
// pathname:需要创建的目录路径,该路径名可以是相对路径或绝对路径,若指定的路径名已经存在,则调用将会失败
// mode:新建目录的权限设置,设置方式与 open 函数的 mode 参数一样,最终权限为(mode & ~umask)
int mkdir(const char *pathname, mode_t mode);
#include
#include
#include
#include
int main(void) {
int ret;
ret = mkdir("./new_dir", S_IRWXU |
S_IRGRP | S_IXGRP |
S_IROTH | S_IXOTH);
if (ret == -1) {
perror("mkdir error");
exit(-1);
}
exit(0);
}
#include
// pathname:需要删除的目录对应的路径名,并且该目录必须是一个空目录,也就是该目录下只有 . 和 .. 这两个目录项
// pathname 指定的路径名不能是软链接文件,即使该链接文件指向了一个空目录
int rmdir(const char *pathname);
#include
#include
#include
int main(void) {
int ret;
ret = rmdir("./new_dir");
if (ret == -1) {
perror("rmdir error");
exit(-1);
}
exit(0);
}
#include
#include
// name:指定需要打开的目录路径名,可以是绝对路径,也可以是相对路径。
// 返回值:成功将返回指向该目录的句柄,一个 DIR 指针(实质是一个结构体指针),作用类似 open 函数返回的文件描述符 fd
// 后续对该目录的操作需要使用该 DIR 指针变量;若调用失败,则返回 NULL
DIR *opendir(const char *name);
#include
// dirp:目录句柄 DIR 指针
// 返回值:返回一个指向 struct dirent 结构体的指针,该结构体表示 dirp 指向的目录流中的下一个目录条目
// 在到达目录流的末尾或发生错误时,它返回 NULL
struct dirent *readdir(DIR *dirp);
// 只需要关注 d_ino 和 d_name 两个字段即可,分别记录了文件的 inode 编号和文件名,其余字段并不是所有系统都支持
struct dirent {
ino_t d_ino; /* inode 编号 */
off_t d_off; /* not an offset; see NOTES */
unsigned short d_reclen; /* length of this record */
unsigned char d_type; /* type of file; not supported by all filesystem types */
char d_name[256]; /* 文件名 */
};
#include
#include
// dirp:目录句柄
void rewinddir(DIR *dirp);
#include
#include
int closedir(DIR *dirp);
#include
#include
#include
#include
#include
int main(void) {
struct dirent *dir;
DIR *dirp;
int ret = 0;
/* 打开目录 */
dirp = opendir("./test");
if (dirp == NULL) {
perror("opendir error");
exit(-1);
}
/* 循环读取目录流中的所有目录条目 */
errno = 0;
while ((dir = readdir(dirp)) != NULL) {
printf("%s %ld\n", dir->d_name, dir->d_ino);
}
if (0 != errno) {
perror("readdir error");
ret = -1;
goto err;
} else
printf("End of directory!\n");
err:
closedir(dirp);
exit(ret);
}
$ gcc dir.c
$ ./a.out
. 2246427
demo02.c 2246598
rm.c 2246603
test_file 2246591
mkdir.c 2246599
.. 2228289
demo03.c 2246592
demo.c 2246475
a.out 2246385
End of directory!
可通过系统调用 getcwd 函数来获取进程的当前工作目录
#include
// buf:getcwd() 将内含当前工作目录绝对路径的字符串存放在 buf 缓冲区中
// size:缓冲区的大小,分配的缓冲区大小必须要大于字符串长度,否则调用将会失败
// 返回值:如果调用成功将返回指向 buf 的指针,失败将返回 NULL,并设置 errno
char *getcwd(char *buf, size_t size);
示例:读取进程的当前工作目录
#include
#include
#include
#include
int main(void) {
char buf[100];
char *ptr;
memset(buf, 0x0, sizeof(buf));
ptr = getcwd(buf, sizeof(buf));
if (NULL == ptr) {
perror("getcwd error");
exit(-1);
}
printf("Current working directory: %s\n", buf);
exit(0);
}
$ gcc dir2.c
$ ./a.out
Current working directory: /home/yxd/Desktop/test
#include
// path:将进程的当前工作目录更改为 path 参数指定的目录,可以是绝对路径或相对路径,指定的目录必须要存在,否则会报错
// fd:将进程的当前工作目录更改为 fd 文件描述符所指定的目录
int chdir(const char *path);
int fchdir(int fd);
#include
#include
#include
#include
int main(void) {
char buf[100];
char *ptr;
int ret;
/* 获取更改前的工作目录 */
memset(buf, 0x0, sizeof(buf));
ptr = getcwd(buf, sizeof(buf));
if (NULL == ptr) {
perror("getcwd error");
exit(-1);
}
printf("Before the change: %s\n", buf);
/* 更改进程的当前工作目录 */
ret = chdir("./new_dir");
if (-1 == ret) {
perror("chdir error");
exit(-1);
}
/* 获取更改后的工作目录 */
memset(buf, 0x0, sizeof(buf));
ptr = getcwd(buf, sizeof(buf));
if (NULL == ptr) {
perror("getcwd error");
exit(-1);
}
printf("After the change: %s\n", buf);
exit(0);
}
#include
int unlink(const char *pathname);
#include
int remove(const char *pathname);
#include
// oldpath:原文件路径
// newpath:新文件路径
// 返回值:成功返回 0;失败将返回-1,并设置 errno
int rename(const char *oldpath, const char *newpath);