作者LoganChien (簡子翔)
看板b97902HW
標題Re: [系程] 教學: 簡介 link, stat, chdir, opendir
時間Sun Apr 11 18:17:22 2010
簡介 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