APUE(第四章)文件和目录

本文详细介绍了文件系统中的stat、fstat、lstat和fstatat函数,用于获取文件信息。这些函数返回文件的设备号、inode、模式、权限、所有者、组、大小、最后访问、修改和状态改变时间等。同时,文章讲解了文件类型,如普通文件、目录、特殊文件等,以及文件权限的设置和测试。此外,还涵盖了文件所有权的变更、文件长度、空洞、文件截断、链接、重命名、符号链接以及文件时间戳的修改。内容涉及文件系统的基本概念和操作,对理解Linux文件系统管理至关重要。

重点理解文件系统

函数stat、fstat、fstatat和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);//成功返回0;出错返回-1;
  • 以上是返回文件有关的信息结构

  • buf是传入传出参数,用来存放文件信息

  • stat函数返回与此命名文件有关的信息结构

  • fstat函数获得已在文件描述符fd上打开文件的有关信息

  • lstat类似于stat,当文件是一个符号链接时,lstat返回该符号连接的有关信息,而不是由该符号连接引用的文件的信息

  • fstatat

    • 为一个相对于当前打开目录(由fd参数指向)的路径名返回统计信息
    • flag控制是否跟随一个符号链接
      • AT_SYMLINK_NOFOLLOW标志被设置,fstatat返回符号链接本身信息否则默认为符号链接指向实际文件的信息
    • fd参数为AT_FDCWD,pathname参数是一个相对路径名,fstatat会计算相对于当前目录的pathname,如果pathname为绝对路径,fd则忽略,和stat一样
  • ubuntu 20.04 stat结构如下

struct stat
  {
    __dev_t st_dev;		/* Device.  */
#ifndef __x86_64__
    unsigned short int __pad1;
#endif
#if defined __x86_64__ || !defined __USE_FILE_OFFSET64
    __ino_t st_ino;		/* File serial number.	*/
#else
    __ino_t __st_ino;			/* 32bit file serial number.	*/
#endif
#ifndef __x86_64__
    __mode_t st_mode;			/* File mode.  */
    __nlink_t st_nlink;			/* Link count.  */
#else
    __nlink_t st_nlink;		/* Link count.  */
    __mode_t st_mode;		/* File mode.  */
#endif
    __uid_t st_uid;		/* User ID of the file's owner.	*/
    __gid_t st_gid;		/* Group ID of the file's group.*/
#ifdef __x86_64__
    int __pad0;
#endif
    __dev_t st_rdev;		/* Device number, if device.  */
#ifndef __x86_64__
    unsigned short int __pad2;
#endif
#if defined __x86_64__ || !defined __USE_FILE_OFFSET64
    __off_t st_size;			/* Size of file, in bytes.  */
#else
    __off64_t st_size;			/* Size of file, in bytes.  */
#endif
    __blksize_t st_blksize;	/* Optimal block size for I/O.  */
#if defined __x86_64__  || !defined __USE_FILE_OFFSET64
    __blkcnt_t st_blocks;		/* Number 512-byte blocks allocated. */
#else
    __blkcnt64_t st_blocks;		/* Number 512-byte blocks allocated. */
#endif
#ifdef __USE_XOPEN2K8
    /* Nanosecond resolution timestamps are stored in a format
       equivalent to 'struct timespec'.  This is the type used
       whenever possible but the Unix namespace rules do not allow the
       identifier 'timespec' to appear in the <sys/stat.h> header.
       Therefore we have to handle the use of this header in strictly
       standard-compliant sources special.  */
    struct timespec st_atim;		/* Time of last access.  */
    struct timespec st_mtim;		/* Time of last modification.  */
    struct timespec st_ctim;		/* Time of last status change.  */
# define st_atime st_atim.tv_sec	/* Backward compatibility.  */
# define st_mtime st_mtim.tv_sec
# define st_ctime st_ctim.tv_sec
#else
    __time_t st_atime;			/* Time of last access.  */
    __syscall_ulong_t st_atimensec;	/* Nscecs of last access.  */
    __time_t st_mtime;			/* Time of last modification.  */
    __syscall_ulong_t st_mtimensec;	/* Nsecs of last modification.  */
    __time_t st_ctime;			/* Time of last status change.  */
    __syscall_ulong_t st_ctimensec;	/* Nsecs of last status change.  */
#endif
#ifdef __x86_64__
    __syscall_slong_t __glibc_reserved[3];
#else
# ifndef __USE_FILE_OFFSET64
    unsigned long int __glibc_reserved4;
    unsigned long int __glibc_reserved5;
# else
    __ino64_t st_ino;			/* File serial number.	*/
# endif
#endif
  };
  • 简化后的基本形式如下
struct stat
  {
    __dev_t st_dev;		/* Device.  */


    __ino_t st_ino;		/* File serial number.	*/

    __mode_t st_mode;			/* File mode.  */
    __nlink_t st_nlink;			/* Link count.  */

    __uid_t st_uid;		/* User ID of the file's owner.	*/
    __gid_t st_gid;		/* Group ID of the file's group.*/

    __dev_t st_rdev;		/* Device number, if device.  */

    __off_t st_size;			/* Size of file, in bytes.  */

    __blksize_t st_blksize;	/* Optimal block size for I/O.  */
    __blkcnt_t st_blocks;		/* Number 512-byte blocks allocated. */


    struct timespec st_atim;		/* Time of last access.  */
    struct timespec st_mtim;		/* Time of last modification.  */
    struct timespec st_ctim;		/* Time of last status change.  */



  };
  • timespec 结构包含 time_t(第一章) tv_sec; 与 long tv_nsec分别代表 秒和纳秒

文件类型

  • 普通文件(regular file)。
  • 目录文件(directory file)。文件包含其他文件的名字以及指向指向这些文件有关信息1的额指针,对于一个目录文件按具有读权限的进程都可以都该目录的内容,但只有内核可以直接写目录文件。进程必须使用本章介绍的函数才能修改目录
  • 特殊块文件(block special file),这种类型的文件提供对设备(磁盘)带缓冲的访问,每次访问以固定长度的单位进行
  • 字符特殊文件(character special file)。这种类型的文件提供对设备不带缓冲的访问每次长度可变。系统中的所有设备要么是字符特殊文件,要么是块特殊文件
  • FIFO。用于进程间通信,又名管道
  • 套接字(socket)。用于进程间通信和网络通信,也可以用于一台宿主计算机上进程之间的非网络通信,即本地套接字
  • 符号连接(symbolic link)。

文件类型信息包含在stat中的st_mode中,具体如下:表示宏函数,具体如下

posix.1将进程间通信IPC说明为文件,具体如下

文件类型测试代码如下:

设置用户ID和组ID

与一个进程有关的ID,如下图所示

  • 实际用户ID和实际组ID标识问我们是谁,在一个登录会话期间不会改变,但超级用户进程可以改变他们

  • 有效用户ID、有效组ID以及附属组ID决定我们的访问权限

  • 保存设置用户ID和保存设置组ID在执行一个程序时包含了有效用户ID和有效组ID

通常有效用户ID等于实际用户ID,有效组ID等于实际组ID

文件访问权限

st_mode的不仅包含文件类型,还包含文件权限位,所有文件类型(目录,字符特别文件等)都有访问权限,不能误解只有普通文件有访问权限

  • 用户指文件所有者,u——所有者,g——组,o——其他,a——ugo三者

规则:

  • 用户打开任意类型的文件,那么该用户对此文件之前的路径都应当有相应权限

  • 对于一个文件的读权限决定了我们是否能够打开现有文件进行读操作
  • 对于一个文件的写权限决定了我们是否能够打开现有文件进行写操作
  • 若open函数对一个文件指定O_TRUNC,必须对该文件有写权限
  • 若在一个目录创建一个新文件,必须对该目录具有写权限和执行权限
  • 为了删除一个现有文件,必须对该文件的目录具有写权限和执行权限,对该文件本身则不需要有读写权限
  • exec函数族中的任意一个执行某个文件,都必须对该文件具有执行权限,该文件还必须是一个普通文件

进程每次打开、创建或删除一个文件时,内核进行文件访问权限测试,具体规则如下

  • 若进程有效用户ID为0,即root,超级用户,则允许访问。这给予了超级用户对整个文件系统进行处理的中最充分的自由

  • 若进程有效用户ID等于文件所有者ID,即进程拥有此文件,若所有者适当的访问权限位被设置,允许访问,否者拒绝访问。适当的访问权限位为,若读,则用户读位为1,若写,则用户写位为1,若执行,则用户执行位为1.

  • 若进程有效组ID或者附属组ID之一等于文件组ID,那么组如果有适当的访问权线位被设置,则与允许访问;否者拒绝访问

  • 若其他用户适当权限位被设置,则允许访问,否则拒绝访问

新文件和目录所有权

  • 新文件的用户ID设置为进程有效用户ID
  • 新文件组ID可以是进程的有效组ID,也可以是文件所在目录的组ID。

函数access与faccessat

文件访问权限测试,按照实际用户ID和实际组ID进行权限测试

#include <unistd.h>
int access(const char *pathname,int mode);
int faccessat(const char *pathname,int mode);//成功返回0,出错返回-1

参数mode

fxxxat与xxx的区别可以看4.2节

函数umask

umask为进程创建屏蔽字

#include <sys/stat.h>
mode_t umask(mode_t cmask); //返回值,之前文件模式闯将屏蔽字

屏蔽顾名思义,屏蔽字中为1的位权限一定是关闭的,一般在创建文件之前解除全部屏蔽,这样就不用去计算,快速设置文件权限,cmask一般为0开头的8进制,类似于chmod命令去修改文件权限一样

与ls -l的第一列对应

顺序分别是u,g,o

函数chmod、fchmod和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);//成功返回0,出错-1
  • chmod指定的文件操作
  • fchmod对已经打开的文件操作
  • fchmodat与chmod的不同与前文一样,flag也与前文一样

mode参数如下

也可以类似于chmod 命令一样,直接给相应的8进制

粘着位

粘滞位(Stickybit),或粘着位。最常见的用法在目录上设置粘滞位,如此以来,只有目录内文件的所有者或者root才可以删除或移动该文件。如果不为目录设置粘滞位,任何具有该目录写和执行权限的用户都可以删除和移动其中的文件。在我们系统中,粘滞位一般用于/tmp目录,以防止普通用户删除或移动其他用户的文件。
一个目录具有粘滞位,则在other的X位会表现为 t,或者T.大小写的区别在于,原来x位上有x权限,有了粘滞位则表现为t.否则,表现为T。

函数chown、fchown、fchownat、lchown

用于修改文件的用户ID和组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);
  • lcown与设置了flag为AT_SYMLINK_NOFOLLOW标志的fchownat,更改符号链接的所有则,而不是该符号链接指向的文件所有者
  • fchown函数作用于打开文件的所有者,不能用于改变符号链接的所有者

文件长度

stat的st_size表示以字节为单位的文件长度,只对普通文件、目录文件和符号链接有意义

  • 对于普通文件,其文件长度可以是0,开始读这种文件是,将得到文件结束eof(end of file)指示
  • 对于目录,文件长度是一个数(16或512)的整倍数
  • 对于符号链接,文件长度就是文件名的实际字节数

文件空洞

前面说到空洞不占用磁盘块,可以用du命令于ls比较,但使用复制的实用程序空洞则会被填满,实际磁盘就等于文件长度

文件截断

在文件尾端处截去一些数据以缩短文件,文件截断为0可以在打开文件时使用O_TRUNC做到

#include <unistd.h>
int truncate(const char *pathname,off_t length);
int ftruncate(int fd,off_t length);
  • 将现有文件长度截断为length,若文件长度大于length,则超过length以外的数据不能再访问。小于length文件长度增加,在以前长度与新的文件尾端数据读为0,形成空洞

文件系统

主要讨论基于BSD版本的UNIX文件系统UFS

  • 把一个磁盘分成一个或多个分区
  • 每个分区包含一个文件系统
  • i节点时固定长度记录项,包含文件的大部分信息

将i节点于数据块部分放大如下图所示

  • i节点可以指向多个数据块

  • 每个i节点都有链接计数,其值是指向i节点的目录项数,只有当链接计数减少至0时,才可删除该文件(释放该文件占用的磁盘块)

  • 链接计数包含在st_nlink中,其基本系统数据类型为nlink_t,这种被称为硬链接

  • 另外一种链接被称为符号链接,符号链接的实际内容包含了该符号链接所指向的文件名字,目录项指向的i节点文件类型为S_IFLNK。

  • i节点包含了文件有关的所有信息,文件类型、文件访问权限位、文件长度和指向文件数据块指针等,目录项存储文件名和i节点编号,i节点编号类型是ino_t

在空目录创建一个新目录,i节点示意图如下

函数link、linkat、unlink、unlinkat和remove

创建一个指向现有文件的链接

#include<unistd.h>
int link(const char *existingpath,const char*newpath);
int linkat(int efd,const char *existingpath,int efd,const char *newpath,int flag);//成功返回0,失败-1
  • 新目录项newpath,若newpath已经存在则出错

  • 现有文件existingpath

  • 创建新目录项和增加链接计数应当是一个原子操作

删除一个目录项,并将pathname所引用的文件连接数-1;

#include <unistd.h>
int unlink(const char *pathname);
int unlinkat(int fd,const char *pathname,int flag);
int remove(const char *pathname); //成功返回0,出错-1;
  • 如果该文件还有其他链接,则仍可通过其他链接访问该文件的数据

  • 若有粘着位,必须对该目录有写权限,必须具有粘着位删除文件的3个条件

  • 链接计数达到0时,该文件才可被删除,文件被打开时,也不能删除

  • remove对于文件与unlink相同,对于目录remove与rmdir

函数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);//成功返回0,出错返回-1

符号链接

  • 符号链接是对一个文件的间接指针,硬链接是指向i节点。

  • 硬链接通常要求链接和文件位于同一文件系统中,可以说是同一分区

  • 只有超级用户才能创建指向目录的硬链接

  • 符号链接以及它指向何种对象并无热呢文件系统限制,任何用户可以创建指向目录的符号链接

  • 符号链接一般用于将一个文件或目录结构移到系统另一个位置

  • 了解函数是否能处理符号链接,即是否可以区分是符号链接文件还是符号链接指向的文件

创建和读取符号链接

创建一个符号链接

#include <unistd.h>
int symlink(const char *actualpath,const char *sympath);
int symlinkat(const char *actualpath,int fd,const char *sympath);//成功返回0,出错返回-1
  • 函数创建一个指向actualpath的新目录项sympath
  • 在创建此符号链接时,并不要求actualpath已经存在
  • 并且actualpath和sympath并不需要位于同一文件系统中

由于open函数跟随符号链接,所以要想打开符号链接本身,并读取链接中的名字

#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);//成功返回读到的字节数,失败返回-1

读取的内容在buf里

文件的时间

stat信息中每个文件都有的三个时间值

  • 修改时间是指文件数据内容被修改,i节点状态更改时间,是指,i节点最后一次被修改时间,前文提到i节点内所含有的权限,用户ID等等被修改

哪些函数会影响这三个值

函数futimens、utimensat、utimes

修改文件的访问和修改时间

#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);//成功返回0,出错-1
  • times的第一个结构体元素包含访问时间,第二个结构体元素包含修改时间,均为日历时间

  • 不足秒的部分用纳秒表示

  • times是要修改的时间

    • 如果times参数是一个空指针,则防蚊时间与修改时间都设置为当前时间

    • 如果times参数指向两个timespec结构的数组,任一数组元素的tv_sec字段值为UTIME_NOW,则相应的时间戳设置为当前时间忽略tv_sec字段

    • 如果times参数指向两个timespec结构的数组,任一数组元素的tv_sec字段值为UTIME_OMIT,则相应的时间戳保持不变,忽略tv_sec字段

    • 如果times参数指向两个timespec结构的数组,任一数组元素的tv_sec字段值既不是UTIME_OMIT也不是UTIME_NOW,则相应的时间戳设置为对应的tv_sec与tv_nsec字段的值

#include <sys/time.h>
int utimes(const char *pathname,const struct timeval,times[2]);//成功返回0,出错-1

函数makdir、mkdirat、rmdir

创建目录

#include <sys/stat.h>
int mkdir(const char *path,mode_t mode);
int mkdirat(int fd,const char*pathname,mode_t mode);//成功返回0,失败返回-1

#include <unistd.h>
int rmdir(const char *pathname);//成功0错误-1

读目录

函数chdir、fchdir、getcwd

修改进程的工作目录

#include <unistd.h>
int chdir(const char *pathname);
int fchdir(int fd);

分别用pathname与fd来指定新的当前工作目录,fd表示fd所在目录

设备特殊文件

参考

《APUE》

Linux粘滞位(粘着位)

Linux–查看文件的详细信息及其含义

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值