UNIX环境C语言编程(3)-文件与目录

时间:2021-09-07 22:09:01

1、函数stat()、fstat()、lstat() --获取文件信息

int stat ( const char * restrict pathname, struct stat *restrict buf );
int fstat ( int fildes , struct stat * buf );
int lstat ( const char *restrict pathname, struct stat *restrict buf );
如果 pathname 表示一个符号连接时

  lstat获取符号连接自身的信息,而不是连接指向的目标文件

  stat则获取目标文件的信息

本章将围绕 3 stat 函数及它们的返回信息展开讨论
ls -l 或许是 stat 函数的最大用户

 

2、struct stat定义示例

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 */

  time_t st_atime/* time of last access */

  time_t st_mtime/* time of last modification */

  time_t st_ctime/* time of last file status change */

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

  blkcnt_t st_blocks/* number of disk blocks allocated */

};

 

3、文件类型

普通文件、 目录、块特殊文件、字符特殊文件、 FIFO SOCKET 、符号连接
文件类型编码在 st_mode 字段中,相关宏列举如下:

  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

例子:判别文件类型
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

int main(int argc, char **argv)
{
    int   i;
    char *ptr;

    struct stat buf;

    for( i = 1; i < argc; i++ )
    {
        printf("%s: ", argv[i]);

        /* 获取文件信息 */
        if( lstat(argv[i], &buf) < 0 )
        {
            perror("lstat");
            continue;
        }

        /* 判别文件类型 */
        if( S_ISREG(buf.st_mode) )
            ptr = "regular";
        else if( S_ISDIR(buf.st_mode) )
            ptr = "directory";
        else if( S_ISCHR(buf.st_mode) )
            ptr = "character special";
        else if( S_ISBLK(buf.st_mode) )
            ptr = "block special";
        else if( S_ISFIFO(buf.st_mode) )
            ptr = "fifo";
        else if( S_ISLNK(buf.st_mode) )
            ptr = "symbolic link";
        else if( S_ISSOCK(buf.st_mode) )
            ptr = "socket";
        else
            ptr = "** unknown mode **";
        printf("%s\n", ptr);
    }

    return(0);
}


 

4、设置-用户-ID、设置-组-ID

每个进程有 6 个或更多相关的 ID

  实际用户ID、实际组ID //实际上我们是谁

  有效用户ID、有效组ID、附加组ID //用于文件权限检查

  保存的设置-用户-ID、保存的设置--ID //exec()保存

通常,进程执行时其 有效用户 ID 等于 实际用户 ID

  如果程序文件设置了设置-用户-ID,那么进程的有效用户ID等于文件的属主

设置 - 用户 -ID 设置 - -ID 包含在 st_mode 字段中

  可以分别使用S_ISUIDS_ISGID检测

 

5、文件访问权限

文件访问权限同样编码在 st_mode 字段中

  此处提及的文件包含前面描述的任何类型的文件,不仅限普通文件

  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

  访问权限分为三组,共9

 

6、文件权限的使用方式

打开一个路径名称时,对其中包含的每一个目录都应具有 执行权限

  不包含目录时,意指当前工作目录

对于文件的 读权限 决定能否打开文件进行读操作
对于文件的 写权限 决定能否打开文件进行写操作
open () 中指定 O_TRUNC 标志,必须对文件具有 写权限
在目录中创建一个新文件,必须对该 目录 具有 写权限 执行权限
删除文件时,需要 目录的 写权限 执行权限 ,与文件自身权限无关
使用 exec () 一族函数执行某个文件时,对文件必须具有 执行权限

 

7、内核对文件权限的检查策略

如果进程的有效用户 ID 等于 0 (超级用户),授权
如果进程的有效用户 ID 等于文件的属主 ID

  (a) 若适当的用户存取权限位被设置,则允许存取

  (b) 否则拒绝存取

如果进程的有效组 ID 或附加组 ID 等于文件的组 ID

  (a) 若适当的组存取权限位被设置,则允许存取

  (b) 否则拒绝存取

若适当的 其他用户存取权限 位被设置,则允许存取,否则拒绝存取

 

8、新建文件(或目录)的所有权

新文件的属主 ID 设置为进程的有效用户 ID
文件组 ID 的设置与不同系统的实现相关

  (a) 可以设置为进程的组ID

  (b) 可以设置为所在目录的组ID

如果设置了目录的设置 - -ID 位,可以确保 (b)

 

9、函数access() --文件访问权限检测

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

  R_OK  test for read permission

  W_OK  test for write permission

  X_OK  test for execute permission

  F_OK  test for existence of file

access () 能够告诉你有没有某种权限,不能告诉你有什么权限
基于 实际用户 ID 实际组 ID 检测对文件的访问权限

  注意:不是有效用户ID与有效组ID

思考:在 设置 - 用户 -ID 程序中, access () 返回的结果与实际权限是否一致?

 

10、函数umask() --设置文件创建掩码

mode_t umask ( mode_t cmask );

  设置进程的文件创建掩码,并返回之前的取值

  umask是少数几个不会返回错误的函数之一

创建文件时 open () creat () 的参数 mode 指定文件的访问权限

  进程的umask设置同样影响新建文件的实际访问权限

  文件创建掩码中打开的位,将导致mode中相应的位被关闭

以符号形式查看进程的文件创建掩码

  umask -S

 

11、函数chmod()、fchmod() --修改文件权限

int chmod (const char *pathname, mode_t mode);
int fchmod (int fildes, mode_t mode);

  mode取值示例:

  S_IRWXU  read, write, and execute by user

  S_IRGRP  read by group

 

下述情形中, chmod () 会自动清除两个权限位:

  (a) 如果进程没有超级用户特权,尝试设置普通文件的粘着位时,粘着位将被清除

  (b) 如果新建文件的组ID不等于进程有效组ID或附加组ID之一时,文件的设置--ID位将被清除

 

12、粘着位

如果一个可执行程序文件设置了粘着位

  程序第一次执行结束时,该程序正文的一个文本被保存在交换区

  这使得下次执行该程序时能较快地装入内存区

  一种历史遗留技术

如果对一个目录设置了粘住位

  只有对该目录具有写权限的用户并且满足下列条件之一,才能删除或更名该目录下的文件:

  (a) 超级用户

  (b) 拥有此文件

  (c) 拥有此目录

  drwxrwxrwt 17tbcs    staff       167936 Feb 29 09:47 /tmp

 

13、函数chown()、fchown()、lchown() --修改属主/组

int chown ( const char *pathname, uid_t owner, gid_t group);
int fchown ( int fildes , uid_t owner, gid_t group);
int lchown ( const char *pathname, uid_t owner, gid_t group);

  lchown作用于符号连接自身

对于文件属主 / 组的修改,某些系统限制如下:

  (a) 普通用户只能修改自己的文件的属组

  (b) 且只能改为自己所属的组(有效组ID,或附加组ID之一)

普通用户修改后, 设置 - 用户 -ID 设置 - -ID 被自动清除

 

14、文件大小

文件大小保存在 st_size 字段中
仅对 普通文件 目录 符号连接 有意义

  对于目录,文件大小通常是一个数字的整数倍数

  对于符号连接,文件大小是文件名称中的实际字节数

  lrwxrwxrwx  1 root    system    21 Feb 21 2006 /unix ->/usr/lib/boot/unix_64

回顾一下,文件中的空洞

 

15、文件截断

int truncate ( const char *pathname, off_t length);
int ftruncate ( int fildes , off_t length);
将文件截断到指定大小
思考一下,如果 length 参数大于当前文件大小,后果如何?

 

16、文件系统

UNIX环境C语言编程(3)-文件与目录

图中有两个目录项指向同一个 i 节点 ,每个 i 节点 都有一个连接计数
另外一种连接类型称为 符号连接 ,文件的实际内容(在数据块中)包含了它所指向的文件的名字
i 节点 包含了所有与文件相关的信息:类型、权限、大小、指向数据的指针等
目录项中的 i 节点 编号 只能指向同一文件系统中 i 节点
文件改名时,数据并不移动

 

UNIX环境C语言编程(3)-文件与目录

编号为 2549 i 节点,类型为目录,连接计数为 2 。数值 2 来自于命名该目录( testdir )的目录项以及在该目录中的 .
编号为 1267 i 节点,目录类型,连接计数 >=3 。至少有三个目录项指向它:一个是命名它的目录项,第二个是在该目录中的 . 项,第三个是在其子目录 testdir 中的 ..

 

17、函数link()、unlink()、remove()、rename()

int link ( const char * existingpath , const char * newpath );
int unlink ( const char *pathname);

  仅当文件的连接计数为0时,文件内容才可能同时被删除;

  另外一个限制:只要文件保持打开状态,文件内容不会被删除

int remove ( const char *pathname);
int rename ( const char * oldn , const char * newn );

  oldn是文件时:如果newn存在,也必须是文件,它将首先被删除

  oldn是目录时:如果newn存在,也必须是空目录,它将首先被删除

  如果oldnnewn引用一个符号连接:将处理连接自身

  如果oldnnewn引用相同的文件:函数不做任何处理

 

18、符号连接

以下函数不跟随符号连接(即处理符号连接自身):

  chownlchownlstatreadlinkremoverenameunlink

  chown是否跟随符号连接因系统而异

  其它函数,如accessstatchmodopen等,将处理符号连接引用的文件(指定O_CREATO_EXCL选项调用open时,不跟随)

符号连接可能会在文件系统中引入循环
UNIX环境C语言编程(3)-文件与目录
open 打开符号连接时,一个令人迷惑的场景分析
     $ ln -s /no/such/file myfile            # 创建一个符号连接,指向一个实际并不存在的文件
     $ ls myfile
     myfile                                  # 符号连接已创建
     $ cat myfile                            # 显示文件内容
     cat: myfile: No such file or directory
     $ ls -l myfile                          # 显示完整信息
     lrwxrwxrwx 1 sar        13 Jan 22 00:26 myfile -> /no/such/file
 
19、函数symlink()、readlink()
int symlink ( const char * actualpath , const char * sympath );
创建一个符号连接
ssize_t readlink ( const char* restrict pathname, char *restrict buf , size_t bufsize );
读取符号连接对应的文件名称,返回读入到 buf 中的字节数
注意: buf 并不以 \0 终止

 

20、文件时间

与文件相关的 3 个时间值

UNIX环境C语言编程(3)-文件与目录

 

21、函数utime() --更改文件的访问/修改时间

int utime ( const char *pathname, const struct utimbuf *times);
touch 命令调用 utime () ,更改文件时间

 

22、目录操作函数mkdir()、rmdir() …

int mkdir (const char *pathname, mode_t mode);   // 创建目录
int rmdir (const char *pathname);   // 删除目录
DIR * opendir ( const char *pathname);  // 打开目录
struct dirent * readdir (DIR * dp );  // 读取目录
void rewinddir (DIR * dp );  // 重新定位到目录开始
int closedir (DIR * dp );  // 关闭目录
long telldir (DIR * dp );  // 获取当前位置
void seekdir (DIR * dp , long loc );  // 在目录中定位
int chdir ( const char *pathname);  // 改变当前目录
int fchdir ( int filedes );  // 改变当前目录
char * getcwd (char * buf , size_t size);  // 获取当前目录