b97902HW 板


LINE

简介 link/stat/chdir/opendir 我终於要来介绍 link, stat, chdir, opendir 这几个 System Call 了! link/symlink 建立档案连结 #include <unistd.h> int link(char const *目标档案, char const *档案连结); int symlink(char const *目标档案, char const *档案连结); 这里,我们有二个 API,其中 link 是用来建立 Hard Link, 而 symlink 是用来建立 Symbolic Link 用的。二个函式的 第一个参数都是「目标档案」的档案路径,也就是新建立的 档案连结要连结的档案;而第二个参数是档案连结本身要被 储存在哪里,要叫什麽档名。例如: int main() { link("test1.txt", "test2.lnk"); } 就是建立一个名称为 test2.lnk 的档案连结指向 test1.txt。 symlink 也是一样的! int main() { symlink("test1.txt", "test2.lnk"); } 不过请记得,因为 Hard Link 一定要有对应的 inode,所以 如果 path1 不是一个存在的档案,link() 会回传 ENOENT 错 误。而 symlink() 就没有这一个问题。 另外,link() 可能无法为我们建立一个指向目录的 Hard Link,并且回传 EPERM。 想想看,以下程式的执行结果是什麽? int main() { close(open("test.txt", O_WRONLY | O_TRUNC | O_CREAT, 0644)); symlink("test.txt", "link0.lnk"); link("link0.lnk", "link1.lnk"); } unlink 删除档案连结 unlink() 可以为我们删除档案的 Hard Link。也就是从「目录 档案」的档案内容之中,移除一个 filename->inode 对应记录。 如果一个档案的 st_nlink 变成 0,这一个档案就会被删掉。所 以 unlink() 事实上是 Unix 删除档案的 api。unlink 的使用 方式如下: #include <unistd.h> int unlink(char const *档案路径); 我们如果要 unlink 一个 Hard Link,我们可以这样写: int main() { unlink("hardlink.name"); } 我们当然也可以用 unlink 来移除一个 Symbolic Link,不过概念上 这二者不太一样。当我们 unlink 一个 Symbolic Link,是让 Symbolic Link 本身的 inode 的 st_nlink 少 1,如果 inode 变成 0,就相当 於删除这一个 Symbolic Link。 如果我们要重新命名一个档案,除了使用 rename(),我们也可以使 用 link() + unlink() 来达成类似的工作: int main(int argc, char **argv) { if (argc < 2) { return EXIT_FAILURE; } link(argv[1], "renamed.txt"); unlink(argv[1]); } 另外有一点值得注意:一个档案如果有被 open() 开启,他的 st_nlink 就会加 1,也就是 File Descriptor 也应该被视为 类似 Hard Link 的东西。所以即便所有的目录下都没有 Hard Link 指向一个 inode,这个 inode 也不一定会被删掉!因为 File Descriptor 也有一点像是 Hard Link。不过在 close() 函式被呼叫的时候,可能会有类似 unlink() 的动作,所以如 果 close() 的时候发现 st_nlink 变成 0,这个 inode 与他 的 blocks 就会被回收。 例如以下的程式: /* fd-test.c */ int main() { int fd = open("file.txt", O_RDONLY); sleep(10); printf("close the file.\n"); close(fd); } $ gcc fd-test.c $ dd if=/dev/zero of=file.txt bs=1024 count=102400 $ df /dev/sda7 39373712 13994432 23379192 38% /home $ ./a.out [1] 4992 $ unlink file.txt $ df /dev/sda7 39373712 13994432 23379192 38% /home ^^^^^^^^ unlink 之後没有变小 ... 等 10 秒之後 ... $ close the file [1]+ Done ./a.out $ df /dev/sda7 39373712 13892032 23481592 38% /home ^^^^^^^^ close() 之後就变小了! 所以我们可以用 open() + unlink() 实作 tmpfile() 函式! 程式码如下: FILE *tmpfile() { int fd = open("secretname", O_RDWR | O_CREAT | O_TRUNC, 0644); if (fd < 0) { return NULL; } unlink("secretname"); /* 这样除了我,所有的人都不会知道有这一个 inode 而且当这个 fd 被关闭时,inode 与 block 就会被 回收。 */ return fdopen(fd, "w+b"); } stat/lstat/fstat 查询档案的相关资料 有时我们想要查询和档案有关的资料,例如:一个档案的拥有者是谁、 最後一次改动、存取的时间、档案的 st_ino、档案的大小等资料,我 们就可以使用 stat 函式。 #include <sys/stat.h> #include <unistd.h> struct stat { dev_t st_dev; ino_t st_ino; /* 档案独有的编号 */ mode_t st_mode; /* 档案的种类 */ nlink_t st_nlink; /* 档案的 Hard Link 个数 */ uid_t st_uid; /* 档案的拥有者 */ gid_t st_gid; /* 档案的有效群组 */ dev_t st_rdev; off_t st_size; /* 档案的大小 */ time_t st_atime; /* 档案最後一次的存取时间 */ time_t st_mtime; /* 档案最後一次的修改时间 */ time_t st_ctime; blksize_t st_blksize; /* 档案系统建议的 block size。(一次读写多少byte) */ blkcnt_t st_blocks; }; int stat(char const *档案路径, struct stat *档案状态); int lstat(char const *档案路径, struct stat *档案状态); int fstat(int fd, struct stat *档案状态); 基本上 stat 与 lstat 的差别在「会不会自动跟随 Symbolic Link」。 stat 如果接收到一个 Symbolic Link,会自动跟随 Symbolic Link, 而回传 Symbolic Link 指向的档案的档案状态。不过如果 Symbolic Link 太多,stat() 有可能回传 ELOOP。而 lstat 就「不会」跟随 Symbolic Link,取而代之的是回传 Symbolic Link 本身的档案状 态。至於 fstat() 就和 stat 一样,不过不同的是 fstat() 是接收 一个 file descriptor。 我们可以看看范例: $ touch test1.txt $ ln test1.txt test2.txt $ ln -s test1.txt test3.txt /* stat-test.c */ int main() { struct stat sb[3]; if (stat("test1.txt", &sb[0]) < 0) { printf("Error: stat() with test1.txt\n"); } if (stat("test2.txt", &sb[1]) < 0) { printf("Error: stat() with test2.txt\n"); } if (stat("test3.txt", &sb[2]) < 0) { printf("Error: stat() with test3.txt\n"); } printf("INO: %d %d %d\n", sb[0].st_ino, sb[1].st_ino, sb[2].st_ino); return EXIT_SUCCESS; } /* Prints: INO: 925583 925583 925583 */ /* lstat-test.c */ int main() { struct stat sb[3]; if (lstat("test1.txt", &sb[0]) < 0) { printf("Error: lstat() with test1.txt\n"); } if (lstat("test2.txt", &sb[1]) < 0) { printf("Error: lstat() with test2.txt\n"); } if (lstat("test3.txt", &sb[2]) < 0) { printf("Error: lstat() with test3.txt\n"); } printf("INO: %d %d %d\n", sb[0].st_ino, sb[1].st_ino, sb[2].st_ino); return EXIT_SUCCESS; } /* Prints: INO: 925583 925583 925584 */ 由以上二个例子,我们可以看出:lstat 不会自动去找 Symbolic Link 指向的档案,而 stat 会。另外,因为 Hard Link 是共用 inode 所以不论是 stat 或者 lstat 都会有一样的输出结果。 至於稍早,我们有提到 st_mode 可以让我们知道档案的种类。这 要怎麽用呢? 事实上 sys/stat.h 还定义了 6 个 Macro 可以帮我们检查这个 档案是不是属於某个种类。这 6 个 Macro 如下: S_ISREG(st_mode) -- 是不是一般的档案(Regular File) s_ISDIR(st_mode) -- 是不是一个目录(Directory) S_ISLNK(st_mode) -- 是不是一个档案软连结(Symbolic Link) S_ISBLK(st_mode) -- 是不是一个区块装置(Block Special Device) S_ISCHR(st_mode) -- 是不是一个文字装置(Character Special Device) S_ISFIFO(st_mode) -- 是不是 Interprocess Communication 的 Named Pipe 以下是一个范例程式: /* filemode.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/stat.h> #include <unistd.h> int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "Usage: ./filemode [FILEPATH]\n"); exit(EXIT_FAILURE); } struct stat sb; if (lstat(argv[1], &sb) < 0) { fprintf(stderr, "Error: %s\n", strerror(errno)); exit(EXIT_FAILURE); } if (S_ISREG(sb.st_mode)) { printf("Regular File\n"); } else if (S_ISDIR(sb.st_mode)) { printf("Directory\n"); } else if (S_ISLNK(sb.st_mode)) { printf("Symbolic Link\n"); } else if (S_ISBLK(sb.st_mode)) { printf("Block Special Device\n"); } else if (S_ISCHR(sb.st_mode)) { printf("Character Special Device\n"); } else if (S_ISFIFO(sb.st_mode)) { printf("FIFO\n"); } else { printf("Unknown\n"); } return EXIT_SUCCESS; } /* 一些测试 */ $ ./filemode filemode.c Regular File $ ./filemode . Directory $ ln -s filemode.c filemode.lnk $ ./filemode filemode.lnk Symbolic Link $ ./filemode /dev/sda Block Special File $ ./filemode /dev/tty1 Character Special File chdir/fchdir 改变工作目录 所谓的工作目录就是一个 Process 解析相对路径时做为基底的目录。 比如说现在我有一个档名:test.txt,我要把这一个路径换成绝对 路径之前,必需要先知道工作目录是什麽,如果工作目录是 /home/logan,那 test.txt 的绝对路径就是 /home/logan/test.txt。 如果工作目录是 /home/b97902073,那 test.txt 的绝对路径就是 /home/b97902073/test.txt。 chdir() 与 fchdir() 就是可以用来改变工作目录的系统呼叫,让 我们的 process 可以在不同的工作目录下执行。其 API 如下: #include <unistd.h> int chdir(char const *path); int fchdir(int fd); 另外,提醒一下,chdir() 或者 fchdir() 这二个函式,只会影响 自己这个 process 或者未来 fork 出来的 process 的工作目录。 所以子 process 改变工作目录时不会影响 parent process。例如: /* chdir-test.c */ #include <limits.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { pid_t pid = fork(); char dir[PATH_MAX]; if (pid < 0) { printf("error to fork.\n"); } else if (pid == 0) { chdir(".."); printf("child proc cwd: %s\n", getcwd(dir, sizeof(dir))); } else { int status; wait(&status); printf("child proc terminated.\n"); printf("parent proc cwd: %s\n", getcwd(dir, sizeof(dir))); } return EXIT_SUCCESS; } 有时候工作目录的路径太长,使用 getcwd() 也没有办法读出完整的 path。 然而,我们必需要暂时改变工作目录然後再回来时,我们该怎麽办呢?这时 fchdir() 就可以派上用场了! int cwd_fd; int main() { cwd_fd = open(".", O_RDONLY); chdir("the-other-directory"); fchdir(cwd_fd); /* get back! */ close(cwd_fd); return EXIT_SUCCESS; } 这样一来,即便我们无法知道目前工作目录的路径,我们也可以回到 同一个工作目录。 opendir/readdir/closedir 读取「目录档案」里面的记录 还记得在第一篇的时候,我有提到目录事实上也是一个档案,里面储 存着一大堆 indoe-filename 的对照表。那我们要怎麽从「目录档案」 里面读出所有的档名呢? 这时候就要使用 opendir/readdir/closedir 这二个函式了!首先 opendir 是开启一个目录,readdir 是读取一笔记录,closedir 是关闭一个目录。他们的 API 如下: #include <sys/types.h> #include <dirent.h> DIR *opendir(const char *目录路径); int closedir(DIR *DIR指标); struct dirent *readdir(DIR *DIR指标); DIR 的 struct 长什麽样子,我们不需要知道!我们只要记得 DIR 的指标是被当成一个像是 handle 的东西,传给 closedir 与 readdir 就可以了!至於 struct dirent 是长这个样子: struct dirent { ino_t d_ino; /* 档案的 st_ino */ char d_name[]; /* 档案(包含资料夹)名称 */ }; readdir() 函式在成功的时候,会回传一个指向 struct dirent 的指标,而在失败的时候,会回传 NULL。所以我们的程式的大 结构长这个样子: DIR *dir = opendir(目录路径); if (dir) { struct dirent *entry; while ((entry = readdir(dir)) != NULL) { /* 存取 entry 的 member,例如:printf("%s\n", entry->d_name); */ } closedir(dir); } 注意,如果 opendir 成功地回传 DIR *,就一定要记得使用 closedir() 回收相关的资源。然而,readdir 回传的 struct dirent * 不可以使用 free 把他释放掉。如果有释放记忆体空间的需求,closedir 会自动地 释放它们。 还有一点要注意!readdir() 回传的 struct dirent * 的内容并不保证 不会随着 readdir() 的呼叫而改变。也就是说,第一次用 readdir() 呼叫得到的 struct dirent *,在第二次呼叫的时候就失效了(至少 POSIX 不保证他的正确性)。所以如果你需要 d_ino 或者d_name 要记得自己复制 一份! 终 -- LoganChien ----- from PTT2 个板 logan ----- --



※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 61.224.102.50 ※ 编辑: LoganChien 来自: 61.224.102.50 (04/11 18:17) ※ 编辑: LoganChien 来自: 140.112.247.159 (04/11 23:27) ※ 编辑: LoganChien 来自: 140.112.247.159 (04/12 00:23) ※ 编辑: LoganChien 来自: 140.112.247.159 (04/12 00:59) ※ 编辑: LoganChien 来自: 140.112.247.159 (04/12 01:45)
1F:推 averangeall:推 辛苦了 好棒喔 04/18 19:32
2F:推 Bingojkt:教学文全消推5@w< 04/19 18:17







like.gif 您可能会有兴趣的文章
icon.png[问题/行为] 猫晚上进房间会不会有憋尿问题
icon.pngRe: [闲聊] 选了错误的女孩成为魔法少女 XDDDDDDDDDD
icon.png[正妹] 瑞典 一张
icon.png[心得] EMS高领长版毛衣.墨小楼MC1002
icon.png[分享] 丹龙隔热纸GE55+33+22
icon.png[问题] 清洗洗衣机
icon.png[寻物] 窗台下的空间
icon.png[闲聊] 双极の女神1 木魔爵
icon.png[售车] 新竹 1997 march 1297cc 白色 四门
icon.png[讨论] 能从照片感受到摄影者心情吗
icon.png[狂贺] 贺贺贺贺 贺!岛村卯月!总选举NO.1
icon.png[难过] 羡慕白皮肤的女生
icon.png阅读文章
icon.png[黑特]
icon.png[问题] SBK S1安装於安全帽位置
icon.png[分享] 旧woo100绝版开箱!!
icon.pngRe: [无言] 关於小包卫生纸
icon.png[开箱] E5-2683V3 RX480Strix 快睿C1 简单测试
icon.png[心得] 苍の海贼龙 地狱 执行者16PT
icon.png[售车] 1999年Virage iO 1.8EXi
icon.png[心得] 挑战33 LV10 狮子座pt solo
icon.png[闲聊] 手把手教你不被桶之新手主购教学
icon.png[分享] Civic Type R 量产版官方照无预警流出
icon.png[售车] Golf 4 2.0 银色 自排
icon.png[出售] Graco提篮汽座(有底座)2000元诚可议
icon.png[问题] 请问补牙材质掉了还能再补吗?(台中半年内
icon.png[问题] 44th 单曲 生写竟然都给重复的啊啊!
icon.png[心得] 华南红卡/icash 核卡
icon.png[问题] 拔牙矫正这样正常吗
icon.png[赠送] 老莫高业 初业 102年版
icon.png[情报] 三大行动支付 本季掀战火
icon.png[宝宝] 博客来Amos水蜡笔5/1特价五折
icon.pngRe: [心得] 新鲜人一些面试分享
icon.png[心得] 苍の海贼龙 地狱 麒麟25PT
icon.pngRe: [闲聊] (君の名は。雷慎入) 君名二创漫画翻译
icon.pngRe: [闲聊] OGN中场影片:失踪人口局 (英文字幕)
icon.png[问题] 台湾大哥大4G讯号差
icon.png[出售] [全国]全新千寻侘草LED灯, 水草

请输入看板名称,例如:WOW站内搜寻

TOP