APUE第四章 文件和目录

函数stat、fstat、fstatat和lstat

函数stat

#include<sys/stat.h>
int stat(const char *restrict pathname, struct stat 
*restrict buf);//stat函数以文件的路径名pathname为参数,返回与此命名文件相关的信息结构。

示例:获取利用stat获取文件的修改时间、创建时间等信息

struct timespec{
    time_t tv_sec;
    long tv_nsec;
}
char* formatdate(char *str, time_t val){
    strftime(str, 36, "%Y.%m.%d %H:%M:%S",     
    localtime(&val));
    return str;
 }
void Stat(char *pathname){
   struct stat *file_info = malloc(sizeof(struct      
   stat));
   stat(pathname, file_info);
   char date[36];
   printf("Access:%s\n", formatdate(date, 
   file_info->st_atimespec.tv_sec));
   printf("Modify:%s\n", formatdate(date, file_info->st_mtimespec.tv_sec));
   printf("Change: %s\n", formatdate(date, file_info->st_ctimespec.tv_sec));
   free(file_info);}

以文件路径名为参数,得到的输出结果为:
Access:2017.07.13 14:25:31
Modify:2017.07.13 14:25:31
Change: 2017.07.13 14:25:32

文件类型

文件类型判断

void CheckFileType(char *pathname){
 struct stat *buf = (struct stat *)malloc(sizeof(struct stat));
char *ptr;
stat(pathname, buf);
if(S_ISREG(buf->st_mode))
    ptr = "regular file";
else if(S_ISDIR(buf->st_mode))
    ptr = "directory";
else if(S_ISCHR(buf->st_mode))
    ptr = "character file";
else if(S_ISBLK(buf->st_mode))
    ptr = "block file";
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 file";
else ptr = "unknown mode";
printf("%s\n", ptr);

}

设置用户ID和设置组ID

struct stat数据结构中的st_mode设置了进程的有效uid和gid。
与进程相关联的ID大致有以下这6个

我们实际上是谁:以下两个字段在登录时取自口令文件中的登录项。

  • 实际用户ID

  • 实际组ID

    用于文件访问权限检查:以下三个字段决定了我们的文件访问权限

  • 有效用户ID

  • 有效组ID

  • 附属组ID

    由exec函数保存:包含了有效用户ID和有效组ID的副本

  • 保存的设置用户ID

  • 保存的设置组ID

    通常有效用户ID等于实际用户ID,有效组ID等于实际组ID。
    printf(“%d, %d”, getuid(), geteuid());
    在main中直接执行得到的结果确实相等。
    当执行一个程序文件时,进程的有效用户ID就是实际用户ID,有效组ID就是实际组ID。但是可以在文件模式字(st_mode)中设置一个特殊标志,其含义是“当执行此文件时,将进程的有效用户ID设置为文件所有者的用户ID(st_uid)。

文件访问权限

st_mode值也包含了对文件的访问权限位,所有类型的文件都有访问权限。
每个文件有9个访问权限为,可将它们分为3类:

进程每次打开、创建或删除一个文件时,内核就进行文件访问权限测试。注意,以下四部为顺序执行

  1. 若进程的有效用户ID是0(超级用户),则允许访问。
  2. 若进程有效用户ID等于文件的所有者ID(也就是进程拥有该文件),如果所有者适当的访问权限被设置,则允许访问。如文件所有者可以读,同组用户和其他用户没有任何权限,即r——–,则进程只可以读该文件。
  3. 若进程的有效组ID或进程的附属组ID之一等于文件的组ID,那么如果组适当的访问权限被设置,则允许访问。如同组用户可以读写该文件,即rw-rw—,则进程可以读写该文件。
  4. 若其他用户适当的访问权限为被设置,则允许访问。如rw-rw-r–,其他用户的进程可以读该文件。

新文件和目录的所有权

新文件的用户ID设置为进程的有效用户ID
(1)新文件的组ID可以是进程的有效组ID
(2)新文件的组ID可以是它所在目录的组ID
以下代码说明上述观点:

printf("进程有效组ID%d\n", getegid());
printf("进程有效用户ID%d\n", geteuid());
int fd = creat("/Users/hupac/Public/a.c", 0666);
struct stat *buf = (struct stat *)malloc(sizeof(struct stat));
fstat(fd, buf);
printf("文件有效用户ID%d\n", buf->st_uid);
printf("文件有效组ID%d\n", buf->st_gid);

函数access和faccessat

access和faccessat函数是按实际用户ID和实际组ID进程访问权限测试的。

 #include<unistd.h>
 int access(const char *pathname, int mode);
 int faccessat(int fd, const char *pathname, int mode, int flag);
 //成功返回0,出错返回-1
 示例:
 void Access(){
if (access(path, F_OK))
    err_sys("access error for %s", path);
else
    err_msg("access ok");
if (open(path, 0666) < 0 )
    err_sys("open error for %s", path);
else
    err_msg("open ok");
}

函数umask

与文件相关联的有9个访问权限位,umask函数为进程设置文件模式创建屏蔽字,也就是说umask的参数在文件访问权限中是要屏蔽的。

  #include<sys/stat.h>
  mode_t umask(mode_t cmask);
  //参数cmask是9个访问权限位中的若干个按位或构成的
  //例如S_IWUSR|S_IWGRP|S_IWOTH,若用这三个参数作为
  //umask的参数,则创建文件时,用户不能写、同组用户不能写、
  //其他用户也不能写(需首先定义文件创建权限为rwrwrw)。
  示例
  #define RWRWRW (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) //u.g.o都有读写权限
void Umask(){
    umask(0);
    if (creat("/Users/hupac/Public/a.c", RWRWRW) < 0)
    err_sys("creat error for foo");
    umask(S_IROTH|S_IWOTH); //从rw中去掉其他用户的读和写权限
if (creat("/Users/hupac/Public/b.c", RWRWRW) < 0)
    err_sys("creat error for bar");   
}

运行结果:
-rw-rw-rw- 1 hupac staff 0 7 13 17:16 a.c
-rw-rw—- 1 hupac staff 0 7 13 17:16 b.c
结论:用户可以设置umask值以控制他们所创建文件的默认权限。该值表示成八进制数。如果我们想确保任何用户都能读取文件,则应将umask设置为0。
如umask 027可以更改文件模式创建屏蔽字。

函数chmod、fchmod和fchmodat

#include<sys/stat.h>
int chmod(const char *pathname, mode_t mode);
int fchmod(const char *pathname, mode_t mode);
int fchmodat(int fd, const char *pathname, mode_t mode, int flag);
//若成功返回0,出错返回-1
chmod(path, S_IRWXU | S_IRGRP);  

chmod类函数可以修改文件的访问权限。mode为9个文件访问权限中的若干个按位或组成。
注意:
1. chmod函数更新的只是索引节点最近一次被修改的时间,而文件内容最后被修改的时间并不更改。
2. 为了改变一个文件的权限位,进程的有效用户ID必须等于文件的所有者ID,或者该进程必须具有超级用户权限。

粘着位

概念:如果一个可执行程序文件的这一位被设置了,那么当改程序第一次被执行,在其终止时,程序正文部分(机器指令)的一个副本仍被保存在交换区。因为在系统自举前,文件的正文部分总是在交换区中,此现象称为“粘着”。

函数chown、fchown、fchownat和lchown

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

chown函数族可以更改文件的用户ID和组ID。若两个参数owner或group任意一个是-1,则*对应的*ownerID或groupID不变。

文件长度

stat结构成员st_size表示以字节为单位的文件长度。此字段只对普通文件、目录文件和符号链接有意义。
st_blksize指示对文件I/O较合适的块长度,st_blocks是所分配的实际512字节块块数。

文件中的空洞 file hole

空洞是由所设置的文件偏移量超过文件尾端,并写入了某些数据后造成的。若有大小相同的两个文件,则包含洞的文件比不包含洞的文件占有的磁盘块多。

文件的洞是普通文件的一部分,它是一些空字符但没有存放在磁盘的任何数据块中。洞是Unix文件一直存在的一个特点。
引入文件的洞是为了避免磁盘空间的浪费。
文件洞在Ext2的实现是基于动态数据块的分配:只有当进程需要向一个块写数据时,才真正把这个块分配给文件。
磁盘实际存储中,与文件关联的inode的i_block数组(inode关联的具体物理块号数组)仅存放已分配块的逻辑块号,而数组中的其它元素都为空。

文件截断

#include<unistd.h>
 int truncate(const char *pathname, off_t lenth);
 int ftruncate(int fd, off_t length);
//成功返回0,出错返回-1

不难想到,若文件原本的长度小于length,则截断时文件长度会增加,而增加的那部分将成为洞。

文件系统

函数link、linkat、unlink、unlinkat和remove

#include<unistd.h>
int link(const char *epath, const char *npath);
int linkat(int efd, const char *existingpath, int nfd, const cahr *new path, int flag);
//成功返回0,出错返回-1

link函数是在npath上复制一个epath文件,两个文件共享一个索引节点,所以link函数创建的硬链接。
当现有文件是符号链接时,flag参数设置了AT_SYMLINK_FOLLOW标志,就创建指向符号链接目标的链接。如果未设置flag参数,则创建一个指向符号链接本身的链接。

void Link(){
    if(link(path, "/Users/hupac/Public/a.c") < 0)
        err_sys("link error\n");
    struct stat *buf = (struct stat *)malloc(sizeof(struct stat));
    stat("/Users/hupac/Public/a.c", buf);
    printf("inode %llu\n", buf->st_ino);
    struct stat *buf1 = (struct stat *)malloc(sizeof(struct stat));
    stat(path, buf1);
    printf("inode %llu\n", buf1->st_ino);
    int fd;
    if((fd = open(path, 0644))< 0) //因为x.c文件的权限为rw-r-xr-x
        err_sys("%s open error\n", path);
    linkat(fd, path, 4, "/Users/hupac/Public/b.c", AT_SYMLINK_FOLLOW);
    struct stat *buf2 = (struct stat *)malloc(sizeof(struct stat));
    stat("/Users/hupac/Public/b.c", buf2);
    printf("symbol link inode%llu\n", buf2->st_ino);
}

运行完成后通过stat命令看到,上例所创建的a.c和b.c与源文件path有着相同的索引节点号,所以这里确实创建的是硬链接,从而得到linkat函数操作非符号链接文件时,并不能创建符号链接。

#include<unistd.h>
int unlink(const char *path);
int unlinkat(int fd, const char *path, int flag);
//成功返回0,出错返回-1

测试示例:

  void Unlink(){
if(unlink("/Users/hupac/Public/a.c") < 0)
    err_sys("unlink error");
else
    err_msg("unlink ok");
}
  • 只有当文件链接计数为0时,文件的内容才被删除,即释放文件所占有的磁盘块。
  • 只要有进程打开了该文件,其内容也不能被删除。
#include<stdio.h>
int remove(const char *path);

remove函数也可以解除对一个文件或目录的链接。对于普通文件,remove的功能与unlink相同;对于目录,remove的功能和rmdir相同。

函数rename和renameat

对文件重命名。

#include<stdio.h>
int rename(const char *oname, contst char *nname);
int renameat(int ofd, const char *oname, int nfd, const char *nname);

测试示例:

void Rename(){
    if(rename(path, "/Users/hupac/public/xx.c") < 0)
        err_sys("rename error\n");
    else
        err_sys("rename ok\n");

}

符号链接

符号链接是对一个文件的间接指针,它与上一节所述的硬链接有所不同,硬链接直接指向文件的i节点,即是说硬链接文件与原文件共享一个i节点。引入符号链接是为了避开硬链接的一些限制:

  • 硬链接通常要求链接和文件位于同一文件系统中;
  • 只有超级用户才能创建指向目录的硬链接。

测试示例:
先通过 ln -s x.c b.c

void Slink(){
    int fd;
    if((fd = open("/Users/hupac/Public/b.c", 0644)) < 0)
        err_sys("oepn err \n");
}

此时程序open函数无法打开b.c,并且提示too many levels of slinks。
问题在于使用符号链接可能在文件系统中引入循环。
再看一例:

$ mkdir foo
$ touch foo/a
$ ln -s ../foo foo/testdir //创建一个符号链接

此时testdir为目录软链接,指向父目录foo,并且构成了一个循环。同样的,利用fd = open(“/Users/hupac/Public/foo/testdir”, 0644)依然会提示Too many levels of slinks。
错误原因(个人理解):对于open函数,成功时会返回打开文件的文件描述符,此时无法确定返回testdir的文件描述符还是foo的文件描述符。

创建和读取符号链接

#include<unistd.h>
int symlink(const char *actualpath,const char *sympath)
int symlinkat(const char *apath,int fd, char *spath);
int ssize_t readlink(const char *restrict pathname, char *restrict buf, size_t bufsize);
//成功返回0,失败返回-1

测试示例

void Symlink(){

    if (symlink(path, "/Users/hupac/Public/a.c") < 0)
        printf("symlink error\n");
    else
        printf("symlink ok\n");

    char buf[10];
    if (readlink( "/Users/hupac/Public/a.c", buf, 10) <0)
        printf("read error\n");
    else
        printf("read ok\n");
    printf("buf = %s\n", buf);
}

文件的时间

struct timespec{
      time_t tv_sec; /*second*/
      long tv_nsec;/*nanosecond*/
}

stat结构中的st_atim, st_ctim, st_mtim,分别标志了文件数据的最后访问时间、i节点状态的最后修改时间、文件数据的最后修改时间。
stat获取文件相关时间信息的示例:

void getTime(){
    struct stat * buf = (struct stat *)malloc(sizeof(stat));
    stat(path, buf);
    char time[50];
    printf("atime:%s\n", formatdate(time, buf->st_atimespec.tv_sec));
    printf("ctime:%s\n", formatdate(time, buf->st_ctimespec.tv_sec));
    printf("mtime:%s\n", formatdate(time, buf->st_mtimespec.tv_sec));

}

输出结果为:
atime:2017.07.14 14:43:22
ctime:2017.07.14 14:37:10
mtime:2017.07.14 14:37:10
状态修改时间st_ctim和修改时间st_mtim的区别:ctime是文件索引节点最后一次被修改的时间,mtime是文件内容最后一次被修改的时间。例如:chmod命令可以只更新ctime,而mtime不变。

函数mkdir、mkdirat和rmdir

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

读目录

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值