当前位置: 首页 > news >正文

模板网站可以自己买空间吗吗西安网约车租车公司哪家好

模板网站可以自己买空间吗吗,西安网约车租车公司哪家好,成都哪个网站建设比较好,常用的软件开发的工具目录 Linux 系统中的文件类型普通文件目录文件字符设备文件和块设备文件符号链接文件管道文件套接字文件总结 stat 函数struct stat 结构体st_mode 变量struct timespec 结构体练习 fstat 和lstat 函数fstat 函数lstat 函数 文件属主有效用户ID 和有效组IDchown 函数fchown 和l… 目录 Linux 系统中的文件类型普通文件目录文件字符设备文件和块设备文件符号链接文件管道文件套接字文件总结 stat 函数struct stat 结构体st_mode 变量struct timespec 结构体练习 fstat 和lstat 函数fstat 函数lstat 函数 文件属主有效用户ID 和有效组IDchown 函数fchown 和lchown 函数 文件访问权限普通权限和特殊权限目录权限检查文件权限access修改文件权限chmodumask 函数 文件的时间属性utime()、utimes()修改时间属性futimens()、utimensat()修改时间属性 符号链接软链接与硬链接创建链接文件读取软链接文件 目录目录存储形式创建和删除目录打开、读取以及关闭目录进程的当前工作目录 删除文件文件重命名总结 在前面的章节内容中都是围绕普通文件I/O 操作进行的一系列讨论譬如打开文件、读写文件、关闭文件等本章将抛开文件I/O 相关话题来讨论Linux 文件系统的其它特性以及文件相关属性我们将从系统调用stat 开始可利用其返回一个包含多种文件属性包括文件时间戳、文件所有权以及文件权限等的结构体逐个说明stat 结构中的每一个成员以了解文件的所有属性然后将向大家介绍用以改变文件属性的各种系统调用除此之外还会向大家介绍Linux 系统中的符号链接以及目录相关的操作。 Linux 系统中的文件类型 Linux 下一切皆文件文件作为Linux 系统设计思想的核心理念在Linux 系统下显得尤为重要。在前面章节内容中我们都是以普通文件文本文件、二进制文件等为例来给大家讲解文件I/O 相关的知识内容虽然在Linux 系统中大部分文件都是普通文件但并不仅仅只有普通文件那么本小节将向大家介绍 Linux 系统中的文件类型。 在Windows 系统下操作系统识别文件类型一般是通过文件名后缀来判断譬如C 语言头文件.h、C 语言源文件.c、.txt 文本文件、压缩包文件.zip 等在Windows 操作系统下打开文件首先会识别文件名后缀得到该文件的类型然后再使用相应的调用相应的程序去打开它譬如.c 文件则会使用C 代码编辑器去打开它.zip 文件则会使用解压软件去打开它。 但是在Linux 系统下并不会通过文件后缀名来识别一个文件的类型话虽如此但并不是意味着大家可以随便给文件加后缀文件名也好、后缀也好都是给“人”看的虽然Linux 系统并不会通过后缀来识别文件但是文件后缀也要规范、需要根据文件本身的功能属性来添加譬如C 源文件就以.c 为后缀、C 头文件就以.h 为后缀、shell 脚本文件就以.sh 为后缀、这是为了我们自己方便查看、浏览。 Linux 系统下一共分为7 种文件类型下面依次给大家介绍。 普通文件 普通文件regular file在Linux 系统下是最常见的譬如文本文件、二进制文件我们编写的源代码文件这些都是普通文件也就是一般意义上的文件。普通文件中的数据存在系统磁盘中可以访问文件中的内容文件中的内容以字节为单位进行存储于访问。 普通文件可以分为两大类文本文件和二进制文件。 ⚫ 文本文件文件中的内容是由文本构成的所谓文本指的是ASCII 码字符。文件中的内容其本质上都是数字因为计算机本身只有0 和1存储在磁盘上的文件内容也都是由0 和1 所构成而文本文件中的数字应该被理解为这个数字所对应的ASCII 字符码譬如常见的.c、.h、.sh、.txt 等这些都是文本文件文本文件的好处就是方便人阅读、浏览以及编写。 ⚫ 二进制文件二进制文件中存储的本质上也是数字只不过对于二进制文件来说这些数字并不是文本字符编码而是真正的数字。譬如Linux 系统下的可执行文件、C 代码编译之后得到的.o 文件、.bin 文件等都是二进制文件。 在Linux 系统下可以通过stat 命令或者ls 命令来查看文件类型如下所示 stat 命令非常友好会直观把文件类型显示出来对于ls 命令来说并没有直观的显示出文件的类型而是通过符号表示出来在图5.1.2 中画红色框位置显示出的一串字符中其中第一个字符’ - ‘就用于表示文件的类型减号’ - 就表示该文件是一个普通文件除此之外来看看其它文件类型使用什么字符表示 ⚫ ’ - 普通文件 ⚫ ’ d 目录文件 ⚫ ’ c 字符设备文件 ⚫ ’ b 块设备文件 ⚫ ’ l 符号链接文件 ⚫ ’ s 套接字文件 ⚫ ’ p 管道文件 关于普通文件就给大家介绍这么多。 目录文件 目录directory就是文件夹文件夹在Linux 系统中也是一种文件是一种特殊文件同样我们也可以使用vi 编辑器来打开文件夹如下所示 可以看到文件夹中记录了该文件夹本省的路径以及该文件夹下所存放的文件。文件夹作为一种特殊文件本身并不适合使用前面给大家介绍的文件I/O 的方式来读写在Linux 系统下会有一些专门的系统调用用于读写文件夹这部分内容后面再给大家介绍。 字符设备文件和块设备文件 学过Linux 驱动编程开发的读者对字符设备文件character、块设备文件block这些文件类型应该并不陌生Linux 系统下一切皆文件也包括各种硬件设备。设备文件字符设备文件、块设备文件对应的是硬件设备在Linux 系统中硬件设备会对应到一个设备文件应用程序通过对设备文件的读写来操控、使用硬件设备譬如LCD 显示屏、串口、音频、按键等在本教程的进阶篇内容中将会向大家介绍如何通过设备文件操控、使用硬件设备。 Linux 系统中可将硬件设备分为字符设备和块设备所以就有了字符设备文件和块设备文件两种文件类型。虽然有设备文件但是设备文件并不对应磁盘上的一个文件也就是说设备文件并不存在于磁盘中而是由文件系统虚拟出来的一般是由内存来维护当系统关机时设备文件都会消失字符设备文件一般存放在Linux 系统/dev/目录下所以/dev 也称为虚拟文件系统devfs。以Ubuntu 系统为例如下所示 上图中agpgart、autofs、btrfs-control、console 等这些都是字符设备文件而loop0、loop1 这些便是块设备文件。 符号链接文件 符号链接文件link类似于Windows 系统中的快捷方式文件是一种特殊文件它的内容指向的是另一个文件路径当对符号链接文件进行操作时系统根据情况会对这个操作转移到它指向的文件上去而不是对它本身进行操作譬如读取一个符号链接文件内容时实际上读到的是它指向的文件的内容。 如果大家理解了Windows 下的快捷方式那么就会很容易理解Linux 下的符号链接文件。图5.1.4 中的cdrom、cdrw、fd、initctl 等这些文件都是符号链接文件箭头所指向的文件路径便是符号链接文件所指向的文件。 关于链接文件在后面的内容中还会给大家进行介绍这里暂时给大家介绍这么多 管道文件 管道文件pipe主要用于进程间通信当学习到相关知识内容的时候再给大家详解。 套接字文件 套接字文件socket也是一种进程间通信的方式与管道文件不同的是它们可以在不同主机上的进程间通信实际上就是网络通信当学习到网络编程相关知识内容再给大家介绍。 总结 本小节给大家简单地介绍了Linux 系统中的7 种文件类型包括普通文件、目录、字符设备文件、块设备文件、符号链接文件、管道文件以及套接字文件下面对它们进行一个简单地概括 普通文件是最常见的文件类型 目录也是一种文件类型 设备文件对应于硬件设备 符号链接文件类似于Windows 的快捷方式 管道文件用于进程间通信 套接字文件用于网络通信。 stat 函数 Linux 下可以使用stat 命令查看文件的属性其实这个命令内部就是通过调用stat()函数来获取文件属性的stat 函数是Linux 中的系统调用用于获取文件相关的信息函数原型如下所示可通过man 2 stat 命令查看 #include sys/types.h #include sys/stat.h #include unistd.h int stat(const char *pathname, struct stat *buf);首先使用该函数需要包含sys/types.h、sys/stat.h以及unistd.h这三个头文件。 函数参数及返回值含义如下 pathname用于指定一个需要查看属性的文件路径。 bufstruct stat 类型指针用于指向一个struct stat 结构体变量。调用stat 函数的时候需要传入一个struct stat 变量的指针获取到的文件属性信息就记录在struct stat 结构体中稍后给大家介绍struct stat 结构体中有记录了哪些信息。 返回值成功返回0失败返回-1并设置error。 struct stat 结构体 struct stat 是内核定义的一个结构体在sys/stat.h头文件中申明所以可以在应用层使用这个结构体中的所有元素加起来构成了文件的属性信息结构体内容如下所示 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_dev该字段用于描述此文件所在的设备。不常用可以不用理会。 st_ino文件的inode 编号。 st_mode该字段用于描述文件的模式譬如文件类型、文件权限都记录在该变量中关于该变量的介绍请看5.2.2 小节。 st_nlink该字段用于记录文件的硬链接数也就是为该文件创建了多少个硬链接文件。链接文件可以分为软链接符号链接文件和硬链接文件关于这些内容后面再给大家介绍。 st_uid、st_gid此两个字段分别用于描述文件所有者的用户ID 以及文件所有者的组ID后面再给大家介绍。 st_rdev该字段记录了设备号设备号只针对于设备文件包括字符设备文件和块设备文件不用理会。 st_size该字段记录了文件的大小逻辑大小以字节为单位。 st_atim、st_mtim、st_ctim此三个字段分别用于记录文件最后被访问的时间、文件内容最后被修改的时间以及文件状态最后被改变的时间都是struct timespec 类型变量具体介绍请看5.2.3 小节。 st_mode 变量 st_mode 是struct stat 结构体中的一个成员变量是一个32 位无符号整形数据该变量记录了文件的类型、文件的权限这些信息其表示方法如下所示 看到图5.2.1 的时候大家有没有似曾相识的感觉确实前面章节内容给大家介绍open 函数的第三个参数mode 时也用到了类似的图如图2.3.2 所示。唯一不同的在于open 函数的mode 参数只涉及到S、 U、G、O 这12 个bit 位并不包括用于描述文件类型的4 个bit 位。 O 对应的3 个bit 位用于描述其它用户的权限 G 对应的3 个bit 位用于描述同组用户的权限 U 对应的3 个bit 位用于描述文件所有者的权限 S 对应的3 个bit 位用于描述文件的特殊权限。 这些bit 位表达内容与open 函数的mode 参数相对应这里不再重述。同样在mode 参数中表示权限的宏定义在这里也是可以使用的这些宏定义如下以下数字使用的是八进制方式表示 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譬如判断文件所有者对该文件是否具有可执行权限可以通过以下方法测试假设st 是struct stat 类型变量 if (st.st_mode S_IXUSR) {//有权限 } else {//无权限 }这里我们重点来看看“文件类型”这4 个bit 位这4 个bit 位用于描述该文件的类型譬如该文件是普通文件、还是链接文件、亦或者是一个目录等那么就可以通过这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管道文件注意上面这些数字使用的是八进制方式来表示的在C 语言中八进制方式表示一个数字需要在数字前面添加一个0零。所以由上面可知当“文件类型”这4 个bit 位对应的数字是14八进制时表示该文件是一个套接字文件、当“文件类型”这4 个bit 位对应的数字是12八进制时表示该文件是一个链接文件、当“文件类型”这4 个bit 位对应的数字是10八进制时表示该文件是一个普通文件等。 所以通过st_mode 变量判断文件类型就很简单了如下假设st 是struct stat 类型变量 /* 判断是不是普通文件*/ if ((st.st_mode S_IFMT) S_IFREG) {/* 是*/ } /* 判断是不是链接文件*/ if ((st.st_mode S_IFMT) S_IFLNK) {/* 是*/ }S_IFMT 宏是文件类型字段位掩码 S_IFMT 0170000 除了这样判断之外我们还可以使用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)) {/* 是*/ }关于st_mode 变量就给大家介绍这么多。 struct timespec 结构体 该结构体定义在time.h头文件中是Linux 系统中时间相关的结构体。应用程序中包含了time.h头文件就可以在应用程序中使用该结构体了结构体内容如下所示 struct timespec {time_t tv_sec; /* 秒*/syscall_slong_t tv_nsec; /* 纳秒*/ };struct timespec 结构体中只有两个成员变量一个秒tv_sec、一个纳秒tv_nsectime_t 其实指的就是long int 类型所以由此可知该结构体所表示的时间可以精确到纳秒当然对于文件的时间属性来说并不需要这么高的精度往往只需精确到秒级别即可。 在Linux 系统中time_t 时间指的是一个时间段从某一个时间点到某一个时间点所经过的秒数譬如对于文件的三个时间属性来说指的是从过去的某一个时间点这个时间点是一个起始基准时间点到文件最后被访问、文件内容最后被修改、文件状态最后被改变的这个时间点所经过的秒数。time_t 时间在Linux 下被称为日历时间7.2 小计中对此有详细介绍。 由示例代码5.2.1 可知struct stat 结构体中包含了三个文件相关的时间属性但这里得到的仅仅只是以秒微秒为单位的时间值对于我们来说并不利用查看我们一般喜欢的是“2020-10-10 18:30:30”这种形式表示的时间直观、明了那有没有办法通过秒来得到这种形式表达的时间呢答案当然是可以譬如可以通过localtime()/localtime_r()或者strftime()来得到更利于我们查看的时间表达方式关于这些函数的介绍以及使用方法在7.2.4 小节有详细说明。 练习 到这里本小节内容就给大家介绍完了主要给大家介绍了stat 函数以及由此引出来的一系列知识内容。为了巩固本小节所学内容这里出一些简单地编程练习题大家可以根据本小节所学知识完成它。 (1)获取文件的inode 节点编号以及文件大小并将它们打印出来。 (2)获取文件的类型判断此文件对于其它用户Other是否具有可读可写权限。 (3)获取文件的时间属性包括文件最后被访问的时间、文件内容最后被修改的时间以及文件状态最后被改变的时间并使用字符串形式将其打印出来包括时间和日期、表示形式自定。 以上就是根据本小节内容整理出来的一些简单的编程练习题下面笔者将给出对应的示例代码。 (1)编程实战练习1 #include sys/types.h #include sys/stat.h #include unistd.h #include stdio.h #include stdlib.h int main(void) {struct stat file_stat;int ret;/* 获取文件属性*/ret stat(./test_file, file_stat);if (-1 ret){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 命令查看test_file 文件的inode 节点和大小如下 从图中可以得知此文件的大小为8864 个字节inode 编号为3701841接下来编译我们的测试程序、并运行 (2)编程实战练习2 #include sys/types.h #include sys/stat.h #include unistd.h #include stdio.h #include stdlib.h int main(void) {struct stat file_stat;int ret;/* 获取文件属性*/ret stat(./test_file, file_stat);if (-1 ret){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);elseprintf(Read: No\n);/* 判断该文件对其它用户是否具有写权限*/if (file_stat.st_mode S_IWOTH)printf(Write: Yes\n);elseprintf(Write: No\n);exit(0); } 测试 (3)编程实战练习3 #include sys/types.h #include sys/stat.h #include unistd.h #include stdio.h #include stdlib.h #include time.h 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); } 测试 可以使用stat 命令查看test_file 文件的这些时间属性对比程序打印出来是否正确 fstat 和lstat 函数 前面给大家介绍了stat 系统调用起始除了stat 函数之外还可以使用fstat 和lstat 两个系统调用来获取文件属性信息。fstat、lstat 与stat 的作用一样但是参数、细节方面有些许不同。 fstat 函数 fstat 与stat 区别在于stat 是从文件名出发得到文件属性信息不需要先打开文件而fstat 函数则是从文件描述符出发得到文件属性信息所以使用fstat 函数之前需要先打开文件得到文件描述符。具体该用stat 还是fstat看具体的情况譬如并不想通过打开文件来得到文件属性信息那么就使用stat如果文件已经打开了那么就使用fstat。 fstat 函数原型如下可通过man 2 fstat命令查看 #include sys/types.h #include sys/stat.h #include unistd.h int fstat(int fd, struct stat *buf); 第一个参数fd 表示文件描述符第二个参数以及返回值与stat 一样。fstat 函数使用示例如下 #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include stdio.h #include stdlib.h 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); } lstat 函数 lstat()与stat、fstat 的区别在于对于符号链接文件stat、fstat 查阅的是符号链接文件所指向的文件对应的文件属性信息而lstat 查阅的是符号链接文件本身的属性信息。 lstat 函数原型如下所示 #include sys/types.h #include sys/stat.h #include unistd.h int lstat(const char *pathname, struct stat *buf);函数参数列表、返回值与stat 函数一样使用方法也一样这里不再重述 文件属主 Linux 是一个多用户操作系统系统中一般存在着好几个不同的用户而Linux 系统中的每一个文件都有一个与之相关联的用户和用户组通过这个信息可以判断文件的所有者和所属组。 文件所有者表示该文件属于“谁”也就是属于哪个用户。一般来说文件在创建时其所有者就是创建该文件的那个用户。譬如当前登录用户为dt使用touch 命令创建了一个文件那么这个文件的所有者就是dt同理在程序中调用open 函数创建新文件时也是如此执行该程序的用户是谁其文件所有者便是谁。 文件所属组则表示该文件属于哪一个用户组。在Linux 中系统并不是通过用户名或用户组名来识别不同的用户和用户组而是通过ID。ID 就是一个编号Linux 系统会为每一个用户或用户组分配一个ID将用户名或用户组名与对应的ID 关联起来所以系统通过用户IDUID或组IDGID就可以识别出不同的用户和用户组。 Tips用户ID 简称UID、用户组ID 简称GID。这些都是Linux 操作系统的基础知识如果对用户和用户组的概念尚不熟悉建议先自行学习这些基础知识。 譬如使用ls 命令或stat 命令便可以查看到文件的所有者和所属组如下所示 由上图可知testApp.c 文件的用户ID 是1000用户组ID 也是1000。 文件的用户ID 和组ID 分别由struct stat 结构体中的st_uid 和st_gid 所指定。既然Linux 下的每一个文件都有与之相关联的用户ID 和组ID那么对于一个进程来说亦是如此与一个进程相关联的ID 有5 个或更多如下表所示 ⚫ 实际用户ID 和实际组ID 标识我们究竟是谁也就是执行该进程的用户是谁、以及该用户对应的所属组实际用户ID 和实际组ID 确定了进程所属的用户和组。 ⚫ 进程的有效用户ID、有效组ID 以及附属组ID 用于文件访问权限检查详情请查看5.4.1 小节内容。 有效用户ID 和有效组ID 首先对于有效用户ID 和有效组ID 来说这是进程所持有的概念对于文件来说并无此属性有效用户ID 和有效组ID 是站在操作系统的角度用于给操作系统判断当前执行该进程的用户在当前环境下对某个文件是否拥有相应的权限。 在Linux 系统中当进程对文件进行读写操作时系统首先会判断该进程是否具有对该文件的读写权限那如何判断呢自然是通过该文件的权限位来判断struct stat 结构体中的st_mode 字段中就记录了该文件的权限位以及文件类型。关于文件权限检查相关内容将会在5.5 小节中说明。 当进行权限检查时并不是通过进程的实际用户和实际组来参与权限检查的而是通过有效用户和有效组来参与文件权限检查。通常绝大部分情况下进程的有效用户等于实际用户有效用户ID 等于实际用户ID有效组等于实际组有效组ID 等于实际组ID。 那么大家可能就要问了什么情况下有效用户ID 不等于实际用户ID、有效组ID 不等于实际组ID那么关于这个问题后面将给大家揭晓 Tips文中所指的进程对文件是否拥有xx 权限其实质是当前执行该进程的用户是否拥有对文件的xx 权限。若无特别指出文中的描述均为此意 chown 函数 chown 是一个系统调用该系统调用可用于改变文件的所有者用户ID和所属组组ID。其实在 Linux 系统下也有一个chown 命令该命令的作用也是用于改变文件的所有者和所属组譬如将testApp.c 文件的所有者和所属组修改为root sudo chown root:root testApp.c可以看到通过该命令确实可以改变文件的所有者和所属组这个命令内部其实就是调用了chown 函数来实现功能的chown 函数原型如下所示可通过man 2 chown命令查看 #include unistd.h int chown(const char *pathname, uid_t owner, gid_t group);首先使用该命令需要包含头文件unistd.h。 函数参数和返回值如下所示 pathname用于指定一个需要修改所有者和所属组的文件路径。 owner将文件的所有者修改为该参数指定的用户以用户ID 的形式描述 group将文件的所属组修改为该参数指定的用户组以用户组ID 的形式描述 返回值成功返回0失败将返回-1兵并且会设置errno。 该函数的用法非常简单只需指定对应的文件路径以及相应的owner 和group 参数即可如果只需要修改文件的用户ID 和用户组ID 当中的一个那么又该如何做呢方法很简单只需将其中不用修改的ID用户ID 或用户组ID与文件当前的ID用户ID 或用户组ID保持一致即可即调用chown 函数时传入的用户ID 或用户组ID 就是该文件当前的用户ID 或用户组ID而文件当前的用户ID 或用户组ID 可以通过 stat 函数查询获取。 虽然该函数用法很简单但是有以下两个限制条件 ⚫ 只有超级用户进程能更改文件的用户ID ⚫ 普通用户进程可以将文件的组ID 修改为其所从属的任意附属组ID前提条件是该进程的有效用户ID 等于文件的用户ID而超级用户进程可以将文件的组ID 修改为任意值。 所以由此可知文件的用户ID 和组ID 并不是随随便便就可以更改的其实这种设计是为系统安全着想如果系统中的任何普通用户进程都可以随便更改系统文件的用户ID 和组ID那么也就意味着任何普通用户对系统文件都有任意权限了这对于操作系统来说将是非常不安全的。 测试 接下来看一些chown 函数的使用例程如下所示 #include unistd.h #include stdio.h #include stdlib.h int main(void) {if (-1 chown(./test_file, 0, 0)){perror(chown error);exit(-1);}exit(0); }代码很简单直接调用chown 函数将test_file 文件的用户ID 和用户组ID 修改为0、0。0 指的就是root 用户和root 用户组接下来我们测试下 在运行测试代码之前先使用了stat 命令查看到test_file 文件的用户ID 和用户组ID 都等于1000然后执行测试程序结果报错Operation not permitted显示不允许操作接下来重新执行程序此时加上sudo如下 此时便可以看到执行之后没有打印错误提示信息说明chown 函数调用成功了并且通过stat 命令也可以看到文件的用户ID 和组ID 确实都被修改为0 了也就是root 用户。原因在于加上sudo 执行应用程序而此时应用程序便可以临时获得root 用户的权限也就是会以root 用户的身份运行程序也就意味着此时该应用程序的用户ID也就是前面给大家提到的实际用户ID变成了root 超级用户的ID也就是0自然chown 函数便可以调用成功。 在Linux 系统下可以使用getuid 和getgid 两个系统调用分别用于获取当前进程的用户ID 和用户组 ID这里说的进程的用户ID 和用户组ID 指的就是进程的实际用户ID 和实际组ID这两个系统调用函数原型如下所示 #include unistd.h #include sys/types.h uid_t getuid(void); gid_t getgid(void); 我们可以在示例代码5.4.1 中加入打印用户ID 的语句如下所示 #include unistd.h #include stdio.h #include stdlib.h int main(void) {printf(uid: %d\n, getuid());if (-1 chown(./test_file, 0, 0)){perror(chown error);exit(-1);}exit(0); } 再来重复上面的测试 很明显可以看到两次执行同一个应用程序它们的用户ID 是不一样的因为加上了sudo 使得应用程序的用户ID 由原本的普通用户ID 1000 变成了超级用户ID 0使得该进程变成了超级用户进程所以调用 chown 函数就不会报错。 关于chown 就给大家介绍这么多在实际应用编程中此系统调用被用到的概率并不多但是理论性知识还是得知道。 fchown 和lchown 函数 这两个同样也是系统调用作用与chown 函数相同只是参数、细节方面有些许不同。fchown()、lchown() 这两个函数与chown()的区别就像是fstat()、lstat()与stat 的区别本小节就不再重述这种问题了如果大家对此还不清楚可以看5.3 小节具体使用fchown、lchown 还是chown看情况而定。 文件访问权限 struct stat 结构体中的st_mode 字段记录了文件的访问权限位。当提及到文件时指的是前面给大家介绍的任何类型的文件并不仅仅指的是普通文件所有文件类型目录、设备文件都有访问权限access permission可能有很多人认为只有普通文件才有访问权限这是一种误解 普通权限和特殊权限 文件的权限可以分为两个大类分别是普通权限和特殊权限也可称为附加权限。普通权限包括对文件的读、写以及执行而特殊权限则包括一些对文件的附加权限譬如Set-User-ID、Set-Group-ID 以及Sticky。接下来分别对普通权限和特殊权限进行介绍。 普通权限 每个文件都有9 个普通的访问权限位可将它们分为3 类如下表 譬如使用ls 命令或stat 命令可以查看到文件的这9 个访问权限如下所示 每一行打印信息中前面的一串字符串就描述了该文件的9 个访问权限以及文件类型譬如-rwxrwxr- x 最前面的一个字符表示该文件的类型这个前面给大家介绍过 - 表示该文件是一个普通文件。 r 表示具有读权限 w 表示具有写权限 x 表示具有执行权限 -表示无此权限。 当进程每次对文件进行读、写、执行等操作时内核就会对文件进行访问权限检查以确定该进程对文件是否拥有相应的权限。而文件的权限检查就涉及到了文件的所有者st_uid、文件所属组st_gid以及其它用户当然这里指的是从文件的角度来看而对于进程来说参与文件权限检查的是进程的有效用户、有效用户组以及进程的附属组用户。 如何判断权限首先要搞清楚该进程对于需要进行操作的文件来说是属于哪一类“角色” ⚫ 如果进程的有效用户ID 等于文件所有者IDst_uid意味着该进程以文件所有者的角色存在 ⚫ 如果进程的有效用户ID 并不等于文件所有者ID意味着该进程并不是文件所有者身份但是进程的有效用户组ID 或进程的附属组ID 之一等于文件的组IDst_gid那么意味着该进程以文件所属组成员的角色存在也就是文件所属组的同组用户成员。 ⚫ 如果进程的有效用户ID 不等于文件所有者ID、并且进程的有效用户组ID 或进程的所有附属组ID 均不等于文件的组IDst_gid那么意味着该进程以其它用户的角色存在。 ⚫ 如果进程的有效用户ID 等于0root 用户则无需进行权限检查直接对该文件拥有最高权限。 确定了进程对于文件来说是属于哪一类“角色”之后相应的权限就直接“对号入座”即可。接下来聊一聊文件的附加的特殊权限。 特殊权限 st_mode 字段中除了记录文件的9 个普通权限之外还记录了文件的3 个特殊权限也就是图5.2.1 中所表示的S 字段权限位S 字段三个bit 位中从高位到低位依次表示文件的set-user-ID 位权限、set-group-ID 位权限以及sticky 位权限如下所示 这三种权限分别使用S_ISUID、S_ISGID 和S_ISVTX 三个宏来表示 S_ISUID 04000 set-user-ID bit S_ISGID 02000 set-group-ID bit (see below) S_ISVTX 01000 sticky bit (see below)同样以上数字使用的是八进制方式表示。对应的bit 位数字为1则表示设置了该权限、为0 则表示并未设置该权限譬如通过st_mode 变量判断文件是否设置了set-user-ID 位权限代码如下 if (st.st_mode S_ISUID) {//设置了set-user-ID 位权限 } else {//没有设置set-user-ID 位权限 }这三个权限位具体有什么作用呢接下里给大家简单地介绍一下 ⚫ 当进程对文件进行操作的时候、将进行权限检查如果文件的set-user-ID 位权限被设置内核会将进程的有效ID 设置为该文件的用户ID文件所有者ID意味着该进程直接获取了文件所有者的权限、以文件所有者的身份操作该文件。 ⚫ 当进程对文件进行操作的时候、将进行权限检查如果文件的set-group-ID 位权限被设置内核会将进程的有效用户组ID 设置为该文件的用户组ID文件所属组ID意味着该进程直接获取了文件所属组成员的权限、以文件所属组成员的身份操作该文件。 看到这里大家可能就要问了如果两个权限位同时被设置呢关于这个问题我们后面可以进行相应的测试答案自然会揭晓 当然set-user-ID 位和set-group-ID 位权限的作用并不如此简单关于其它的功能本文档便不再叙述了因为这些特殊权限位实际中用到的机会确实不多。除此之外Sticky 位权限也不再给大家介绍了笔者对此也不是很了解有兴趣的读者可以自行查阅相关的书籍。 Linux 系统下绝大部分的文件都没有设置set-user-ID 位权限和set-group-ID 位权限所以通常情况下进程的有效用户等于实际用户有效用户ID 等于实际用户ID有效组等于实际组有效组ID 等于实际组ID。 目录权限 前面我们一直谈论的都是文件的读、写、执行权限那对于创建文件、删除文件等这些操作难道就不需要相应的权限了吗事实并不如此譬如有时删除文件或创建文件也会提示权限不够如下所示 那说明删除文件、创建文件这些操作也是需要相应权限的那这些权限又是从哪里获取的呢答案就是目录。目录文件夹在Linux 系统下也是一种文件拥有与普通文件相同的权限方案S/U/G/O只是这些权限的含义另有所指。 ⚫ 目录的读权限可列出譬如通过ls 命令目录之下的内容即目录下有哪些文件。 ⚫ 目录的写权限可以在目录下创建文件、删除文件。 ⚫ 目录的执行权限可访问目录下的文件譬如对目录下的文件进行读、写、执行等操作。 拥有对目录的读权限用户只能查看目录中的文件列表譬如使用ls 命令进行查看 通过ls -l命令可以查看到2_chapter 目录对于文件所有者只有读权限当前操作的用户正是该目录所有者dt之后通过ls 2_chapter命令查看该目录下的文件确实获取到了该目录下的3 个文件file1、file2、 file3说明只有读权限时可以查看到目录下有哪些文件、显示出文件的名称但是会看到上面打印出了一些权限不够信息这是因为Ubuntu 发行版对ls 命令做了别名处理执行ls 命令的时候携带了一些选项而这些选项会访问文件的一些信息所以导致出现权限不够问题这也说明只拥有读权限、是没法访问目录下的文件的为了确保使用的是ls 命令本身执行时需要给出路径的完整路径/bin/ls 要想访问目录下的文件譬如查看文件的inode 节点、大小、权限等信息还需要对目录拥有执行权限。 反之若拥有对目录的执行权限、而无读权限只要知道目录内文件的名称仍可对其进行访问但不能列出目录下的内容即目录下包含的其它文件的名称。 要想在目录下创建文件或删除原有文件需要同时拥有对该目录的执行和写权限。 所以由此可知如果需要对文件进行读、写或执行等操作不光是需要拥有该文件本身的读、写或执行权限还需要拥有文件所在目录的执行权限。 检查文件权限access 通过前面的介绍大家应该知道了文件的权限检查不单单只讨论文件本身的权限还需要涉及到文件所在目录的权限只有同时都满足了才能通过操作系统的权限检查进而才可以对文件进行相关操作所以程序当中对文件进行相关操作之前需要先检查执行进程的用户是否对该文件拥有相应的权限。那如何检查呢可以使用access 系统调用函数原型如下 #include unistd.h int access(const char *pathname, int mode);首先使用该函数需要包含头文件unistd.h。 函数参数和返回值含义如下 pathname需要进行权限检查的文件路径。 mode该参数可以取以下值 ⚫ F_OK检查文件是否存在 ⚫ R_OK检查是否拥有读权限 ⚫ W_OK检查是否拥有写权限 ⚫ X_OK检查是否拥有执行权限 除了可以单独使用之外还可以通过按位或运算符 | 组合在一起。 返回值检查项通过则返回0表示拥有相应的权限并且文件存在否则返回-1如果多个检查项组合在一起只要其中任何一项不通过都会返回-1。 测试 通过access 函数检查文件是否存在若存在、则继续检查执行进程的用户对该文件是否有读、写、执行权限。 #include unistd.h #include stdio.h #include stdlib.h #define MY_FILE ./test_file int main(void) {int ret;/* 检查文件是否存在*/ret access(MY_FILE, F_OK);if (-1 ret){printf(%: file does not exist.\n, MY_FILE);exit(-1);}/* 检查权限*/ret access(MY_FILE, R_OK);if (!ret)printf(Read permission: Yes\n);elseprintf(Read permission: NO\n);ret access(MY_FILE, W_OK);if (!ret)printf(Write permission: Yes\n);elseprintf(Write permission: NO\n);ret access(MY_FILE, X_OK);if (!ret)printf(Execution permission: Yes\n);elseprintf(Execution permission: NO\n);exit(0); } 接下来编译测试 修改文件权限chmod 在Linux 系统下可以使用chmod 命令修改文件权限该命令内部实现方法其实是调用了chmod 函数 chmod 函数是一个系统调用函数原型如下所示可通过man 2 chmod命令查看 #include sys/stat.h int chmod(const char *pathname, mode_t mode);首先使用该函数需要包含头文件sys/stat.h。 函数参数及返回值如下所示 pathname需要进行权限修改的文件路径若该参数所指为符号链接实际改变权限的文件是符号链接所指向的文件而不是符号链接文件本身。 mode该参数用于描述文件权限与open 函数的第三个参数一样这里不再重述可以直接使用八进制数据来描述也可以使用相应的权限宏单个或通过位或运算符 | 组合。 返回值成功返回0失败返回-1并设置errno。 文件权限对于文件来说是非常重要的属性是不能随随便便被任何用户所修改的要想更改文件权限要么是超级用户root进程、要么进程有效用户ID 与文件的用户ID文件所有者相匹配。 fchmod 函数 该函数功能与chmod 一样参数略有不同。fchmod()与chmod()的区别在于使用了文件描述符来代替文件路径就像是fstat 与stat 的区别。函数原型如下所示 #include sys/stat.h int fchmod(int fd, mode_t mode);使用了文件描述符fd 代替了文件路径pathname其它功能都是一样的。 测试 #include sys/stat.h #include stdio.h #include stdlib.h int main(void) {int ret;ret chmod(./test_file, 0777);if (-1 ret){perror(chmod error);exit(-1);}exit(0); } 上述代码中通过调用chmod 函数将当前目录下的test_file 文件其权限修改为0777八进制表示方式也可以使用S_IRUSR、S_IWUSR 等这些宏来表示也就是文件所有者、文件所属组用户以及其它用户都拥有读、写、执行权限接下来编译测试 执行程序之前test_file 文件的权限为rw-r–r–0644程序执行完成之后再次查看文件权限为 rwxrwxrwx0777修改成功 umask 函数 在Linux 下有一个umask 命令在Ubuntu 系统下执行看看 可以看到该命令打印出了0002这数字表示什么意思呢这就要从umask 命令的作用说起了umask 命令用于查看/设置权限掩码权限掩码主要用于对新建文件的权限进行屏蔽。权限掩码的表示方式与文件权限的表示方式相同但是需要去除特殊权限位umask 不能对特殊权限位进行屏蔽。 当新建文件时文件实际的权限并不等于我们所设置的权限譬如调用open 函数新建文件时文件实际的权限并不等于mode 参数所描述的权限而是通过如下关系得到实际权限 mode ~umask譬如调用open 函数新建文件时mode 参数指定为0777假设umask 为0002那么实际权限为 0777 (~0002) 0775前面给大家介绍open 函数的mode 参数时并未向大家提及到umask所以这里重新向大家说明。 umask 权限掩码是进程的一种属性用于指明该进程新建文件或目录时应屏蔽哪些权限位。进程的 umask 通常继承至其父进程关于父、子进程相关的内容将会在后面章节给大家介绍譬如在Ubuntu shell 终端下执行的应用程序它的umask 继承至该shell 进程。 当然Linux 系统提供了umask 函数用于设置进程的权限掩码该函数是一个系统调用函数原型如下所示可通过man 2 umask命令查看 #include sys/types.h #include sys/stat.h mode_t umask(mode_t mask);首先使用该命令需要包含头文件sys/types.h和sys/stat.h。 函数参数和返回值含义如下 mask需要设置的权限掩码值可以发现make 参数的类型与open 函数、chmod 函数中的mode 参数对应的类型一样所以其表示方式也是一样的前面也给大家介绍了既可以使用数字表示譬如八进制数也可以直接使用宏S_IRUSR、S_IWUSR 等。 返回值返回设置之前的umask 值也就是旧的umask。 测试 接下来我们编写一个测试代码使用umask()函数修改进程的umask 权限掩码测试代码如下所示 #include sys/types.h #include sys/stat.h #include stdio.h #include stdlib.h int main(void) {mode_t old_mask;old_mask umask(0003);printf(old mask: %04o\n, old_mask);exit(0); }上述代码中使用umask 函数将该进程的umask 设置为0003八进制返回得到的old_mask 则是设置之前旧的umask 值然后将其打印出来 从打印信息可以看出旧的umask 等于0002这个umask 是从当前vscode 的shell 终端继承下来的如果没有修改进程的umask 值默认就是从父进程继承下来的umask。 这里再次强调umask 是进程自身的一种属性、A 进程的umask 与B 进程的umask 无关父子进程关系除外。在shell 终端下可以使用umask 命令设置shell 终端的umask 值但是该shell 终端关闭之后、再次打开一个终端新打开的终端将与之前关闭的终端并无任何瓜葛 文件的时间属性 前面给大家介绍了3 个文件的时间属性文件最后被访问的时间、文件内容最后被修改的时间以及文件状态最后被改变的时间分别记录在struct stat 结构体的st_atim、st_mtim 以及st_ctim 变量中如下所示 ⚫ 文件最后被访问的时间访问指的是读取文件内容文件内容最后一次被读取的时间譬如使用 read()函数读取文件内容便会改变该时间属性 ⚫ 文件内容最后被修改的时间文件内容发生改变譬如使用write()函数写入数据到文件中便会改变该时间属性 ⚫ 文件状态最后被改变的时间状态更改指的是该文件的inode 节点最后一次被修改的时间譬如更改文件的访问权限、更改文件的用户ID、用户组ID、更改链接数等但它们并没有更改文件的实际内容也没有访问读取文件内容。为什么文件状态的更改指的是inode 节点的更改呢3.1 小节给大家介绍inode 节点的时候给大家介绍过inode 中包含了很多文件信息譬如文件字节大小、文件所有者、文件对应的读/写/执行权限、文件时间戳时间属性、文件数据存储的block 块等所以由此可知状态的更改指的就是inode 节点内容的更改。譬如chmod()、chown()等这些函数都能改变该时间属性。 表5.6.2 列出了一些系统调用或C 库函数对文件时间属性的影响有些操作并不仅仅只会影响文件本身的时间属性还会影响到其父目录的相关时间属性。 utime()、utimes()修改时间属性 文件的时间属性虽然会在我们对文件进行相关操作譬如读、写的时候发生改变但这些改变都是隐式、被动的发生改变除此之外还可以使用Linux 系统提供的系统调用显式的修改文件的时间属性。本小节给大家介绍如何使用utime()和utimes()函数来修改文件的时间属性。 Tips只能显式修改文件的最后一次访问时间和文件内容最后被修改的时间不能显式修改文件状态最后被改变的时间大家可以想一想为什么笔者把这个作为思考题留给大家 utime()函数 utime()函数原型如下所示 #include sys/types.h #include utime.h int utime(const char *filename, const struct utimbuf *times);首先使用该函数需要包含头文件sys/types.h和utime.h。 函数参数和返回值含义如下 filename需要修改时间属性的文件路径。 times将时间属性修改为该参数所指定的时间值times 是一个struct utimbuf 结构体类型的指针稍后给大家介绍如果将times 参数设置为NULL则会将文件的访问时间和修改时间设置为系统当前时间。 返回值成功返回值0失败将返回-1并会设置errno。 来看看struct utimbuf 结构体 struct utimbuf {time_t actime; /* 访问时间*/time_t modtime; /* 内容修改时间*/ };该结构体中包含了两个time_t 类型的成员分别用于表示访问时间和内容修改时间time_t 类型其实就是long int 类型所以这两个时间是以秒为单位的所以由此可知utime()函数设置文件的时间属性精度只能到秒。 同样对于文件来说时间属性也是文件非常重要的属性之一对文件时间属性的修改也不是任何用户都可以随便修改的只有以下两种进程可对其进行修改 ⚫ 超级用户进程以root 身份运行的进程。 ⚫ 有效用户ID 与该文件用户ID文件所有者相匹配的进程。 ⚫ 在参数times 等于NULL 的情况下对文件拥有写权限的进程。 除以上三种情况之外的用户进程将无法对文件时间戳进行修改。 utime 测试 接下来我们编写一个简单地测试程序使用utime()函数修改文件的访问时间和内容修改时间示例代码如下 #include sys/types.h #include utime.h #include unistd.h #include stdio.h #include stdlib.h #include time.h #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 (-1 ret){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 (-1 ret){perror(utime error);exit(-1);}exit(0); } 上述代码尝试将test_file 文件的访问时间和内容修改时间修改为当前系统时间。程序中使用到了time() 函数time()是Linux 系统调用用于获取当前时间也可以直接将times 参数设置为NULL这样就不需要使用time 函数来获取当前时间了单位为秒关于该函数在后面的章节内容中会给大家介绍这里简单地了解一下。接下来编译测试在运行程序之间先使用stat 命令查看test_file 文件的时间戳如下 接下来编译程序、运行测试 会发现执行完测试程序之后test_file 文件的访问时间和内容修改时间均被更改为当前时间了大家可以使用date 命令查看当前系统时间并且会发现状态更改时间也会修改为当前时间了当然这个不是在程序中修改、而是内核帮它自动修改的为什么会这样呢如果大家理解了之前介绍的知识内容完全可以理解这个问题这里笔者不再重述 utimes()函数 utimes()也是系统调用功能与utime()函数一致只是参数、细节上有些许不同utimes()与utime()最大的区别在于前者可以以微秒级精度来指定时间值其函数原型如下所示 #include sys/time.h int utimes(const char *filename, const struct timeval times[2]);首先使用该函数需要包含头文件sys/time.h。 函数参数和返回值含义如下 filename需要修改时间属性的文件路径。 times将时间属性修改为该参数所指定的时间值times 是一个struct timeval 结构体类型的数组数组共有两个元素第一个元素用于指定访问时间第二个元素用于指定内容修改时间稍后给大家介绍如果 times 参数为NULL则会将文件的访问时间和修改时间设置为当前时间。 返回值成功返回0失败返回-1并且会设置errno。 来看看struct timeval 结构体 struct timeval {long tv_sec; /* 秒*/long tv_usec; /* 微秒*/ }; 该结构体包含了两个成员变量tv_sec 和tv_usec分别用于表示秒和微秒。 utimes()遵循与utime()相同的时间戳修改权限规则。 utimes 测试 #include unistd.h #include time.h #include stdio.h #include stdlib.h #include sys/time.h #define MY_FILE ./test_file int main(void) {struct timeval tmval_arr[2];time_t cur_sec;int ret;int i;/* 检查文件是否存在*/ret access(MY_FILE, F_OK);if (-1 ret){printf(Error: %s file does not exist!\n, MY_FILE);exit(-1);}/* 获取当前时间*/time(cur_sec);for (i 0; i 2; i){tmval_arr[i].tv_sec cur_sec;tmval_arr[i].tv_usec 0;}/* 修改文件时间戳*/ret utimes(MY_FILE, tmval_arr);if (-1 ret){perror(utimes error);exit(-1);}exit(0); } 代码不再给大家进行介绍了功能与示例代码5.6.2 相同大家可以自己动手编译、运行测试。 futimens()、utimensat()修改时间属性 除了上面给大家介绍了两个系统调用外这里再向大家介绍两个系统调用功能与utime()和utimes()函数功能一样用于显式修改文件时间戳它们是futimens()和utimensat()。 这两个系统调用相对于utime 和utimes 函数有以下三个优点 ⚫ 可按纳秒级精度设置时间戳。相对于提供微秒级精度的utimes()这是重大改进 ⚫ 可单独设置某一时间戳。譬如只设置访问时间、而修改时间保持不变如果要使用utime()或utimes() 来实现此功能则需要首先使用stat()获取另一个时间戳的值然后再将获取值与打算变更的时间戳一同指定。 ⚫ 可独立将任一时间戳设置为当前时间。使用utime()或utimes()函数虽然也可以通过将times 参数设置为NULL 来达到将时间戳设置为当前时间的效果但是不能单独指定某一个时间戳必须全部设置为当前时间不考虑使用额外函数获取当前时间的方式譬如time()。 futimens()函数 futimens 函数原型如下所示可通过man 2 utimensat命令查看 #include fcntl.h #include sys/stat.h int futimens(int fd, const struct timespec times[2]);函数原型和返回值含义如下 fd文件描述符。 times将时间属性修改为该参数所指定的时间值times 指向拥有2 个struct timespec 结构体类型变量的数组数组共有两个元素第一个元素用于指定访问时间第二个元素用于指定内容修改时间该结构体在5.2.3 小节给大家介绍过了这里不再重述 返回值成功返回0失败将返回-1并设置errno。 所以由此可知使用futimens()设置文件时间戳需要先打开文件获取到文件描述符。 该函数的时间戳可以按下列4 种方式之一进行指定 ⚫ 如果times 参数是一个空指针也就是NULL则表示将访问时间和修改时间都设置为当前时间。 ⚫ 如果times 参数指向两个struct timespec 结构体类型变量的数组任一数组元素的tv_nsec 字段的值设置为UTIME_NOW则表示相应的时间戳设置为当前时间此时忽略相应的tv_sec 字段。 ⚫ 如果times 参数指向两个struct timespec 结构体类型变量的数组任一数组元素的tv_nsec 字段的值设置为UTIME_OMIT则表示相应的时间戳保持不变此时忽略tv_sec 字段。 ⚫ 如果times 参数指向两个struct timespec 结构体类型变量的数组且tv_nsec 字段的值既不是 UTIME_NOW 也不是UTIME_OMIT在这种情况下相应的时间戳设置为相应的tv_sec 和tv_nsec 字段指定的值。 TipsUTIME_NOW 和UTIME_OMIT 是两个宏定义。 使用futimens()函数只有以下进程可对文件时间戳进行修改 ⚫ 超级用户进程。 ⚫ 在参数times 等于NULL 的情况下对文件拥有写权限的进程。 ⚫ 有效用户ID 与该文件用户ID文件所有者相匹配的进程。 futimens()测试 #include fcntl.h #include sys/stat.h #include unistd.h #include sys/types.h #include time.h #include stdio.h #include stdlib.h #define MY_FILE ./test_file int main(void) {struct timespec tmsp_arr[2];int ret;int fd;/* 检查文件是否存在*/ret access(MY_FILE, F_OK);if (-1 ret){printf(Error: %s file does not exist!\n, MY_FILE);exit(-1);}/* 打开文件*/fd open(MY_FILE, O_RDONLY);if (-1 fd){perror(open error);exit(-1);} /* 修改文件时间戳*/ #if 1ret futimens(fd, NULL); // 同时设置为当前时间 #endif #if 0 tmsp_arr[0].tv_nsec UTIME_OMIT;//访问时间保持不变 tmsp_arr[1].tv_nsec UTIME_NOW;//内容修改时间设置为当期时间 ret futimens(fd, tmsp_arr); #endif #if 0 tmsp_arr[0].tv_nsec UTIME_NOW;//访问时间设置为当前时间 tmsp_arr[1].tv_nsec UTIME_OMIT;//内容修改时间保持不变 ret futimens(fd, tmsp_arr); #endifif (-1 ret){perror(futimens error);goto err;} err:close(fd);exit(ret); } 代码不再给大家进行介绍大家可以自己动手编译、运行测试。 utimensat()函数 utimensat()与futimens()函数在功能上是一样的同样可以实现纳秒级精度设置时间戳、单独设置某一时间戳、独立将任一时间戳设置为当前时间与futimens()在参数以及细节上存在一些差异使用futimens()函数需要先将文件打开通过文件描述符进行操作utimensat()可以直接使用文件路径方式进行操作。 utimensat 函数原型如下所示 #include fcntl.h #include sys/stat.h int utimensat(int dirfd, const char *pathname, const struct timespec times[2], int flags);首先使用该函数需要包含头文件fcntl.h和sys/stat.h。 函数参数和返回值含义如下 dirfd该参数可以是一个目录的文件描述符也可以是特殊值AT_FDCWD如果pathname 参数指定的是文件的绝对路径则此参数会被忽略。 pathname指定文件路径。如果pathname 参数指定的是一个相对路径、并且dirfd 参数不等于特殊值 AT_FDCWD则实际操作的文件路径是相对于文件描述符dirfd 指向的目录进行解析。如果pathname 参数指定的是一个相对路径、并且dirfd 参数等于特殊值AT_FDCWD则实际操作的文件路径是相对于调用进程的当前工作目录进行解析关于进程的工作目录在5.7 小节中有介绍。 times与futimens()的times 参数含义相同。 flags 此参数可以为0 也可以设置为AT_SYMLINK_NOFOLLOW 如果设置为 AT_SYMLINK_NOFOLLOW当pathname 参数指定的文件是符号链接则修改的是该符号链接的时间戳而不是它所指向的文件。 返回值成功返回0失败返回-1、并会设置时间戳。 utimensat()遵循与futimens()相同的时间戳修改权限规则。 utimensat()函数测试 #include fcntl.h #include sys/stat.h #include unistd.h #include stdio.h #include stdlib.h #define MY_FILE /home/dt/vscode_ws/2_chapter/test_file int main(void) {struct timespec tmsp_arr[2];int ret;/* 检查文件是否存在*/ret access(MY_FILE, F_OK);if (-1 ret){printf(Error: %s file does not exist!\n, MY_FILE);exit(-1);} /* 修改文件时间戳*/ #if 1ret utimensat(-1, MY_FILE, NULL, AT_SYMLINK_NOFOLLOW); // 同时设置为当前时间 #endif #if 0 tmsp_arr[0].tv_nsec UTIME_OMIT;//访问时间保持不变 tmsp_arr[1].tv_nsec UTIME_NOW;//内容修改时间设置为当期时间 ret utimensat(-1, MY_FILE, tmsp_arr, AT_SYMLINK_NOFOLLOW); #endif #if 0 tmsp_arr[0].tv_nsec UTIME_NOW;//访问时间设置为当前时间 tmsp_arr[1].tv_nsec UTIME_OMIT;//内容修改时间保持不变 ret utimensat(-1, MY_FILE, tmsp_arr, AT_SYMLINK_NOFOLLOW); #endifif (-1 ret){perror(futimens error);exit(-1);}exit(0); } 符号链接软链接与硬链接 在Linux 系统中有两种链接文件分为软链接也叫符号链接文件和硬链接文件软链接文件也就是前面给大家的Linux 系统下的七种文件类型之一其作用类似于Windows 下的快捷方式。那么硬链接文件又是什么呢本小节就来聊一聊它们之间的区别。 首先从使用角度来讲两者没有任何区别都与正常的文件访问方式一样支持读、写以及执行。那它们的区别在哪呢在底层原理上为了说明这个问题先来创建一个硬链接文件如下所示 Tips使用ln 命令可以为一个文件创建软链接文件或硬链接文件用法如下 硬链接ln 源文件链接文件 软链接ln -s 源文件链接文件 关于该命令其它用法可以查看man 手册。 从图5.7.1 中可知使用ln 命令创建的两个硬链接文件与源文件test_file 都拥有相同的inode 号既然 inode 相同也就意味着它们指向了物理硬盘的同一个区块仅仅只是文件名字不同而已创建出来的硬链接文件与源文件对文件系统来说是完全平等的关系。那么大家可能要问了如果删除了硬链接文件或源文件其中之一那文件所对应的inode 以及文件内容在磁盘中的数据块会被文件系统回收吗事实上并不会这样因为inode 数据结结构中会记录文件的链接数这个链接数指的就是硬链接数struct stat 结构体中的 st_nlink 成员变量就记录了文件的链接数这些内容前面已经给大家介绍过了。 当为文件每创建一个硬链接inode 节点上的链接数就会加一每删除一个硬链接inode 节点上的链接数就会减一直到为0inode 节点和对应的数据块才会被文件系统所回收也就意味着文件已经从文件系统中被删除了。从图5.7.1 中可知使用ls -li命令查看到此时链接数为3dt 用户名前面的那个数字我们明明创建了2 个链接文件为什么链接数会是3其实源文件test_file 本身就是一个硬链接文件所以这里才是3。 当我们删除其中任何一个文件后链接数就会减少如下所示 接下来再来聊一聊软链接文件软链接文件与源文件有着不同的inode 号如图5.7.3 所示所以也就是意味着它们之间有着不同的数据块但是软链接文件的数据块中存储的是源文件的路径名链接文件可以通过这个路径找到被链接的源文件它们之间类似于一种“主从”关系当源文件被删除之后软链接文件依然存在但此时它指向的是一个无效的文件路径这种链接文件被称为悬空链接如图5.7.4 所示。 从图中还可看出inode 节点中记录的链接数并未将软链接计算在内。 介绍完它们之间的区别之后大家可能觉得硬链接相对于软链接来说有较大的优势其实并不是这样对于硬链接来说存在一些限制情况如下 ⚫ 不能对目录创建硬链接超级用户可以创建但必须在底层文件系统支持的情况下。 ⚫ 硬链接通常要求链接文件和源文件位于同一文件系统中。 而软链接文件的使用并没有上述限制条件优点如下所示 ⚫ 可以对目录创建软链接 ⚫ 可以跨越不同文件系统 ⚫ 可以对不存在的文件创建软链接。 创建链接文件 在Linux 系统下可以使用系统调用创建硬链接文件或软链接文件本小节向大家介绍如何通过这些系统调用创建链接文件。 创建硬链接link() link()系统调用用于创建硬链接文件函数原型如下可通过man 2 link命令查看 #include unistd.h int link(const char *oldpath, const char *newpath);首先使用该函数需要包含头文件unistd.h。 函数原型和返回值含义如下 oldpath用于指定被链接的源文件路径应避免oldpath 参数指定为软链接文件为软链接文件创建硬链接没有意义虽然并不会报错。 newpath用于指定硬链接文件路径如果newpath 指定的文件路径已存在则会产生错误。 返回值成功返回0失败将返回-1并且会设置errno。 link 函数测试 接下来我们编写一个简单地程序演示link 函数如何使用 #include stdio.h #include stdlib.h #include unistd.h int main(void) {int ret;ret link(./test_file, ./hard);if (-1 ret){perror(link error);exit(-1);}exit(0); } 程序中通过link 函数为当前目录下的test_file 文件创建了一个硬链接hard编译测试 创建软链接symlink() symlink()系统调用用于创建软链接文件函数原型如下可通过man 2 symlink命令查看 #include unistd.h int symlink(const char *target, const char *linkpath); 首先使用该函数需要包含头文件unistd.h。 函数参数和返回值含义如下 target用于指定被链接的源文件路径target 参数指定的也可以是一个软链接文件。 linkpath用于指定硬链接文件路径如果newpath 指定的文件路径已存在则会产生错误。 返回值成功返回0失败将返回-1并会设置errno。 创建软链接时并不要求target 参数指定的文件路径已经存在如果文件不存在那么创建的软链接将成为“悬空链接”。 symlink 函数测试 接下来我们编写一个简单地程序演示symlink 函数如何使用 #include stdio.h #include stdlib.h #include unistd.h int main(void) {int ret;ret symlink(./test_file, ./soft);if (-1 ret){perror(symlink error);exit(-1);}exit(0); } 程序中通过symlink 函数为当前目录下的test_file 文件创建了一个软链接soft编译测试 读取软链接文件 前面给大家介绍到软链接文件数据块中存储的是被链接文件的路径信息那如何读取出软链接文件中存储的路径信息呢大家认为使用read 函数可以吗答案是不可以因为使用read 函数之前需要先open 打开该文件得到文件描述符但是调用open 打开一个链接文件本身是不会成功的因为打开的并不是链接文件本身、而是其指向的文件所以不能使用read 来读取那怎么办呢可以使用系统调用readlink。 readlink 函数原型如下所示 #include unistd.h ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);函数参数和返回值含义如下 pathname需要读取的软链接文件路径。只能是软链接文件路径不能是其它类型文件否则调用函数将报错。 buf用于存放路径信息的缓冲区。 bufsiz读取大小一般读取的大小需要大于链接文件数据块中存储的文件路径信息字节大小。 返回值失败将返回-1并会设置errno成功将返回读取到的字节数。 readlink 函数测试 接下来我们编写一个简单地程序演示readlink 函数如何使用 #include stdio.h #include stdlib.h #include unistd.h #include string.h int main(void) {char buf[50];int ret;memset(buf, 0x0, sizeof(buf));ret readlink(./soft, buf, sizeof(buf));if (-1 ret){perror(readlink error);exit(-1);}printf(%s\n, buf);exit(0); } 使用readlink 函数读取当前目录下的软链接文件soft并将读取到的信息打印出来测试如下 目录 目录文件夹在Linux 系统也是一种文件是一种特殊文件同样可以使用前面给大家介绍open、 read 等这些系统调用以及C 库函数对其进行操作但是目录作为一种特殊文件并不适合使用前面介绍的文件I/O 方式进行读写等操作。在Linux 系统下会有一些专门的系统调用或C 库函数用于对文件夹进行操作譬如打开、创建文件夹、删除文件夹、读取文件夹以及遍历文件夹中的文件等那么本小节将向大家介绍目录相关的知识内容。 目录存储形式 3.1 小节中给大家介绍了普通文件的管理形式或存储形式本小节聊一聊目录这种特殊文件在文件系统中的存储形式其实目录在文件系统中的存储方式与常规文件类似常规文件包括了inode 节点以及文件内容数据存储块block参考图3.1.1 所示但对于目录来说其存储形式则是由inode 节点和目录块所构成目录块当中记录了有哪些文件组织在这个目录下记录它们的文件名以及对应的inode 编号。 其存储形式如下图所示 目录块当中有多个目录项或叫目录条目每一个目录项或目录条目都会对应到该目录下的某一个文件目录项当中记录了该文件的文件名以及它的inode 节点编号所以通过目录的目录块便可以遍历找到该目录下的所有文件以及所对应的inode 节点。 所以对此总结如下 ⚫ 普通文件由inode 节点和数据块构成 ⚫ 目录由inode 节点和目录块构成 创建和删除目录 使用open 函数可以创建一个普通文件但不能用于创建目录文件在Linux 系统下提供了专门用于创建目录mkdir()以及删除目录rmdir 相关的系统调用。 mkdir 函数 函数原型如下所示 #include sys/stat.h #include sys/types.h int mkdir(const char *pathname, mode_t mode);函数参数和返回值含义如下 pathname需要创建的目录路径。 mode新建目录的权限设置设置方式与open 函数的mode 参数一样最终权限为mode ~umask。 返回值成功返回0失败将返回-1并会设置errno。 pathname 参数指定的新建目录的路径该路径名可以是相对路径也可以是绝对路径若指定的路径名已经存在则调用mkdir()将会失败。 mode 参数指定了新目录的权限目录拥有与普通文件相同的权限位但是其表示的含义与普通文件却有不同5.5.2 小计对此作了说明。 mkdir 函数测试 #include stdio.h #include stdlib.h #include sys/stat.h #include sys/types.h int main(void) {int ret;ret mkdir(./new_dir, S_IRWXU |S_IRGRP | S_IXGRP |S_IROTH | S_IXOTH);if (-1 ret){perror(mkdir error);exit(-1);}exit(0); } 上述代码中我们通过mkdir 函数在当前目录下创建了一个目录new_dir并将其权限设置为0755八进制编译运行 rmdir 函数 rmdir()用于删除一个目录 #include unistd.h int rmdir(const char *pathname);首先使用该函数需要包含头文件unistd.h。 函数参数和返回值含义如下 pathname需要删除的目录对应的路径名并且该目录必须是一个空目录也就是该目录下只有.和…这两个目录项pathname 指定的路径名不能是软链接文件即使该链接文件指向了一个空目录。 返回值成功返回0失败将返回-1并会设置errno。 rmdir 函数测试 #include stdio.h #include stdlib.h #include unistd.h int main(void) {int ret;ret rmdir(./new_dir);if (-1 ret){perror(rmdir error);exit(-1);}exit(0); } 打开、读取以及关闭目录 打开、读取、关闭一个普通文件可以使用open()、read()、close()而对于目录来说可以使用opendir()、 readdir()和closedir()来打开、读取以及关闭目录接下来将向大家介绍这3 个C 库函数的用法。 打开文件opendir opendir()函数用于打开一个目录并返回指向该目录的句柄供后续操作使用。Opendir 是一个C 库函数opendir()函数原型如下所示 #include sys/types.h #include dirent.h DIR *opendir(const char *name);函数参数和返回值含义如下 name指定需要打开的目录路径名可以是绝对路径也可以是相对路径。 返回值成功将返回指向该目录的句柄一个DIR 指针其实质是一个结构体指针其作用类似于 open 函数返回的文件描述符fd后续对该目录的操作需要使用该DIR 指针变量若调用失败则返回NULL。 读取目录readdir readdir()用于读取目录获取目录下所有文件的名称以及对应inode 号。这里给大家介绍的readdir()是一个C 库函数事实上Linux 系统还提供了一个readdir 系统调用其函数原型如下所示 #include dirent.h struct dirent *readdir(DIR *dirp);首先使用该函数需要包含头文件dirent.h。 函数参数和返回值含义如下 dirp目录句柄DIR 指针。 返回值返回一个指向struct dirent 结构体的指针该结构体表示dirp 指向的目录流中的下一个目录条目。在到达目录流的末尾或发生错误时它返回NULL。 Tips“流”是从自然界中抽象出来的一种概念有点类似于自然界当中的水流在文件操作中文件内容数据类似池塘中存储的水N 个字节数据被读取出来或将N 个字节数据写入到文件中这些数据就构成了字节流。 “流”这个概念是动态的而不是静态的。编程当中提到这个概念一般都是与I/O 相关所以也经常叫做I/O 流但对于目录这种特殊文件来说这里将目录块中存储的数据称为目录流存储了一个一个的目录项目录条目。 struct dirent 结构体内容如下所示 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]; /* 文件名*/ }; 对于struct dirent 结构体我们只需要关注d_ino 和d_name 两个字段即可分别记录了文件的inode 编号和文件名其余字段并不是所有系统都支持所以也不再给大家介绍这些字段一般也不会使用到。 每调用一次readdir()就会从drip 所指向的目录流中读取下一条目录项目录条目并返回struct dirent 结构体指针指向经静态分配而得的struct dirent 类型结构每次调用readdir()都会覆盖该结构。一旦遇到目录结尾或是出错readdir()将返回NULL针对后一种情况还会设置errno 以示具体错误。那如何区别究竟是到了目录末尾还是出错了呢可通过如下代码进行判断 error 0; direntp readdir(dirp); if (NULL direntp) {if (0 ! error){/* 出现了错误*/}else{/* 已经到了目录末尾*/} } 使用readdir()返回时并未对文件名进行排序而是按照文件在目录中出现的天然次序这取决于文件系统向目录添加文件时所遵循的次序及其在删除文件后对目录列表中空隙的填补方式。 当使用opendir()打开目录时目录流将指向了目录列表的头部0使用readdir()读取一条目录条目之后目录流将会向后移动、指向下一个目录条目。这其实跟open()类似当使用open()打开文件的时候文件位置偏移量默认指向了文件头部当使用read()或write()进行读写时文件偏移量会自动向后移动。 rewinddir 函数 rewinddir()是C 库函数可将目录流重置为目录起点以便对readdir()的下一次调用将从目录列表中的第一个文件开始。rewinddir 函数原型如下所示 #include sys/types.h #include dirent.h void rewinddir(DIR *dirp);首先使用该函数需要包含头文件dirent.h。 函数参数和返回值含义如下 dirp目录句柄。 返回值无返回值。 关闭目录closedir 函数 closedir()函数用于关闭处于打开状态的目录同时释放它所使用的资源其函数原型如下所示 #include sys/types.h #include dirent.h int closedir(DIR *dirp);首先使用该函数需要包含头文件sys/types.h和dirent.h。 函数参数和返回值含义如下 dirp目录句柄。 返回值成功返回0失败将返回-1并设置errno。 练习 根据本小节所学知识内容可以做一个简单地编程练习打开一个目录、并将目录下的所有文件的名称以及其对应inode 编号打印出来。示例代码如下所示 #include stdio.h #include stdlib.h #include dirent.h #include sys/types.h #include errno.h int main(void) {struct dirent *dir;DIR *dirp;int ret 0;/* 打开目录*/dirp opendir(./my_dir);if (NULL dirp){perror(opendir error);exit(-1);}/* 循环读取目录流中的所有目录条目*/errno 0;while (NULL ! (dir readdir(dirp)))printf(%s %ld\n, dir-d_name, dir-d_ino);if (0 ! errno){perror(readdir error);ret -1;goto err;}elseprintf(End of directory!\n); err:closedir(dirp);exit(ret); } 使用opendir()打开了当前目录下的my_dir 目录该目录下的文件如下所示 接下来编译、运行 由此可知示例代码5.8.4 能够将my_dir 目录下的所有文件全部扫描出来打印出它们的名字以及inode 节点。 进程的当前工作目录 Linux 下的每一个进程都有自己的当前工作目录current working directory当前工作目录是该进程解析、搜索相对路径名的起点不是以 / 斜杆开头的绝对路径。譬如代码中调用open 函数打开文件时传入的文件路径使用相对路径方式进行表示那么该进程解析这个相对路径名时、会以进程的当前工作目录作为参考目录。 一般情况下运行一个进程时、其父进程的当前工作目录将被该进程所继承成为该进程的当前工作目录。可通过getcwd 函数来获取进程的当前工作目录如下所示 #include unistd.h char *getcwd(char *buf, size_t size);这是一个系统调用使用该函数之前需要包含头文件unistd.h。 函数参数和返回值含义如下 bufgetcwd()将内含当前工作目录绝对路径的字符串存放在buf 缓冲区中。 size缓冲区的大小分配的缓冲区大小必须要大于字符串长度否则调用将会失败。 返回值如果调用成功将返回指向buf 的指针失败将返回NULL并设置errno。 Tips若传入的buf 为NULL且size 为0则getcwd()内部会按需分配一个缓冲区并将指向该缓冲区的指针作为函数的返回值为了避免内存泄漏调用者使用完之后必须调用free()来释放这一缓冲区所占内存空间。 测试 接下来我们编写一个简单地测试程序用于读取进程的当前工作目录 #include stdio.h #include stdlib.h #include unistd.h #include string.h 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); } 编译运行 改变当前工作目录 系统调用chdir()和fchdir()可以用于更改进程的当前工作目录函数原型如下所示 #include unistd.h int chdir(const char *path); int fchdir(int fd);首先使用这两个函数之一需要包含头文件unistd.h。 函数参数和返回值含义如下 path将进程的当前工作目录更改为path 参数指定的目录可以是绝对路径、也可以是相对路径指定的目录必须要存在否则会报错。 fd将进程的当前工作目录更改为fd 文件描述符所指定的目录譬如使用open 函数打开一个目录。 返回值成功均返回0失败均返回-1并设置errno。 此两函数的区别在于指定目录的方式不同chdir()是以路径的方式进行指定而fchdir()则是通过文件描述符文件描述符可调用open()打开相应的目录时获得。 测试 #include stdio.h #include stdlib.h #include unistd.h #include string.h 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); } 上述程序会在更改工作目录之前获取当前工作目录、并将其打印出来之后调用chdir 函数将进程的工作目录更改为当前目录下的new_dir 目录更改成功之后再将进程的当前工作目录获取并打印出来接下来编译测试 删除文件 前面给大家介绍了如何删除一个目录使用rmdir()函数即可显然该函数并不能删除一个普通文件那如何删除一个普通文件呢方法就是通过系统调用unlink()或使用C 库函数remove()。 使用unlink 函数删除文件 unlink()用于删除一个文件不包括目录函数原型如下所示 #include unistd.h int unlink(const char *pathname);使用该函数需要包含头文件unistd.h。 函数参数和返回值含义如下 pathname需要删除的文件路径可使用相对路径、也可使用绝对路径如果pathname 参数指定的文件不存在则调用unlink()失败。 返回值成功返回0失败将返回-1并设置errno。 前面给大家介绍link 函数用于创建一个硬链接文件创建硬链接时inode 节点上的链接数就会增加 unlink()的作用与link()相反unlink()系统调用用于移除/删除一个硬链接从其父级目录下删除该目录条目。 所以unlink()系统调用实质上是移除pathname 参数指定的文件路径对应的目录项从其父级目录中移除该目录项并将文件的inode 链接计数将1如果该文件还有其它硬链接则任可通过其它链接访问该文件的数据只有当链接计数变为0 时该文件的内容才可被删除。另一个条件也会阻止删除文件的内容- –只要有进程打开了该文件其内容也不能被删除。关闭一个文件时内核会检查打开该文件的进程个数如果这个计数达到0内核再去检查其链接计数如果链接计数也是0那么就删除该文件对应的内容也就是文件对应的inode 以及数据块被回收如果一个文件存在多个硬链接删除其中任何一个硬链接其 inode 和数据块并没有被回收还可通过其它硬链接访问文件的数据。 unlink()系统调用并不会对软链接进行解引用操作若pathname 指定的文件为软链接文件则删除软链接文件本身而非软链接所指定的文件。 测试 #include stdio.h #include stdlib.h #include unistd.h int main(void) {int ret;ret unlink(./test_file);if (-1 ret){perror(unlink error);exit(-1);}exit(0); } 上述代码调用unlink()删除当前目录下的test_file 文件编译测试 使用remove 函数删除文件 remove()是一个C 库函数用于移除一个文件或空目录其函数原型如下所示 #include stdio.h int remove(const char *pathname);使用该函数需要包含C 库函数头文件stdio.h。 函数参数和返回值含义如下 pathname需要删除的文件或目录路径可以是相对路径、也可是决定路径。 返回值成功返回0失败将返回-1并设置errno。 pathname 参数指定的是一个非目录文件那么remove()去调用unlink()如果pathname 参数指定的是一个目录那么remove()去调用rmdir()。 与unlink()、rmdir()一样remove()不对软链接进行解引用操作若pathname 参数指定的是一个软链接文件则remove()会删除链接文件本身、而非所指向的文件。 测试 #include stdio.h #include stdlib.h int main(void) {int ret;ret remove(./test_file);if (-1 ret){perror(remove error);exit(-1);}exit(0); } 文件重命名 本小节给大家介绍rename()系统调用借助于rename()既可以对文件进行重命名又可以将文件移至同一文件系统中的另一个目录下其函数原型如下所示 #include stdio.h int rename(const char *oldpath, const char *newpath);使用该函数需要包含头文件stdio.h。 函数参数和返回值含义如下 oldpath原文件路径。 newpath新文件路径。 返回值成功返回0失败将返回-1并设置errno。 调用rename()会将现有的一个路径名oldpath 重命名为newpath 参数所指定的路径名。rename()调用仅操作目录条目而不移动文件数据不改变文件inode 编号、不移动文件数据块中存储的内容重命名既不影响指向该文件的其它硬链接也不影响已经打开该文件的进程譬如在重命名之前该文件已被其它进程打开了而且还未被关闭。 根据oldpath、newpath 的不同有以下不同的情况需要进行说明 ⚫ 若newpath 参数指定的文件或目录已经存在则将其覆盖 ⚫ 若newpath 和oldpath 指向同一个文件则不发生变化且调用成功。 ⚫ rename()系统调用对其两个参数中的软链接均不进行解引用。如果oldpath 是一个软链接那么将重命名该软链接如果newpath 是一个软链接则会将其移除、被覆盖。 ⚫ 如果oldpath 指代文件而非目录那么就不能将newpath 指定为一个目录的路径名。要想重命名一个文件到某一个目录下newpath 必须包含新的文件名。 ⚫ 如果oldpath 指代为一个目录在这种情况下newpath 要么不存在要么必须指定为一个空目录。 ⚫ oldpath 和newpath 所指代的文件必须位于同一文件系统。由前面的介绍可以得出此结论 ⚫ 不能对.当前目录和…上一级目录进行重命名。 测试 #include stdio.h #include stdlib.h int main(void) {int ret;ret rename(./test_file, ./new_file);if (-1 ret){perror(rename error);exit(-1);}exit(0); } 将当前目录下的test_file 文件重命名为new_file接下来编译测试 从图中可以知道使用rename 进行文件重命名之后其inode 号并未改变。 总结 本章所介绍的内容比较多主要是围绕文件属性以及目录展开的一系列相关话题本章开头先给大家介绍Linux 系统下的7 种文件类型包括普通文件、目录、设备文件字符设备文件、块设备文件、符号链接文件软链接文件、管道文件以及套接字文件。 接着围绕stat 系统调用详细给大家介绍了struct stat 结构体中的每一个成员这使得我们对Linux 下文件的各个属性都有所了解。接着分别给大家详细介绍了文件属主、文件访问权限、文件时间戳、软链接与硬链接以及目录等相关内容让大家知道在应用编程中如何去修改文件的这些属性以及它们所需要满足的条件。 至此本章内容到这里就结束了相信大家已经学习到了不少知识内容大家加油
http://www.zqtcl.cn/news/252422/

相关文章:

  • 个人网站开发 服务器货源之家官网
  • 教育培训学校网站建设策划局域网 wordpress
  • 重庆建网站有哪些网站做曲线的源代码
  • 龙岩网站设计找哪家公司网站建设没有业务怎么办
  • 网站建设专业学什么建材 团购 网站怎么做
  • 电器工程东莞网站建设wordpress虚拟资源下载源码
  • 无限个网站虚拟空间网站运行维护
  • 宝思哲手表网站关于计算机网站建设的论文
  • uc投放广告网站要自己做吗dw制作企业网站
  • 山东网站制作南京软件外包公司
  • 铁岭建设银行网站网站验证码原理
  • 做网站需要什么专业方向的员工钱多网站
  • 网站建设合同要存档几年7星彩网站开发
  • 网站建设好后 如何验收什么网站可以做护考题
  • 网站安全怎么做wordpress代币插件
  • 吉林网站建设电话龙华网站建设专业定制企业
  • 个人导航网站怎么备案js调用wordpress文章列表
  • 网站微信推广方案衡水外贸网站建设
  • 怎么打造自己的网站如何做自已网站
  • 美容美发网站模板wordpress适合优化吗
  • 网站开发的著作权和版权沧州市做网站价格
  • 优客逸家网站源码酒吧装修
  • 深圳网站制作的公司怎么样开工作室做网站怎样找资源
  • 大连城乡建设局网站seo编辑招聘
  • 网站建设意见怎么在中国移动做网站备案
  • 做内贸哪个网站找客户网络外包
  • 古玩网站建设意义钟山县住房和城乡建设局网站
  • 网站开发微信公众号自定义菜单规则网站建设
  • 营销网站建设工作教育培训wordpress主题
  • 温州地区做网站公司如何注册新公司