檔案和目錄
一、 stat 結構和權限相關
- 四個
函數:傳回檔案或者目錄的資訊結構:stat
#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);
- 參數:
-
:檔案或者目錄的名字pathname
-
:存放資訊結構的緩沖區buf
-
:打開的檔案描述符fd
- 對于
,該檔案就是待檢視資訊的檔案fstat
- 對于
,該檔案是并不是待檢視資訊的檔案。待檢視資訊的檔案時已該fstatat
對于的目錄相對路徑定位的fd
- 對于
-
:控制着flag
函數是否跟随一個符号連結。fstatat
函數:fstatat
- 待檢視的檔案名是由
和fd
共同決定的。pathname
- 如果
是個絕對路徑,則忽略pathname
參數fd
- 如果
是個相對路徑路徑,且pathname
,則在目前工作目錄的路徑下查找fd=AT_FDCWD
pathname
- 如果
是個相對路徑路徑,且pathname
,則在fd!=AT_FDCWD
對應的打開目錄下查找fd
pathname
- 如果
-
:控制着flag
函數是否跟随一個符号連結。當fstatat
标志被設定時,檢視的是!AT_SYMLINK_FOLLOW
(如果它是個符号連結)本身的資訊;否則預設檢視的是pathname
(如果它是個符号連結)連結引用的檔案的資訊。pathname
-
- 傳回值:
- 成功:傳回 0
- 失敗: 傳回 -1
-
類似于lstat
,但是當stat
是個符号連結時,pathname
檢視的是該符号連結的有關資訊;而lstat
是檢視該符号連結引用的檔案的資訊。stat
- 在
上,雖然有ubuntu 16.04
這個常量,但是不支援。必須用AT_SYMLINK_NOFOLLOW
。其常量定義為:!AT_SYMLINK_FOLLOW
-
: 1024 (有效)AT_SYMLINK_FOLLOW
-
: 0(有效)!AT_SYMLINK_FOLLOW
-
: 256(無效)AT_SYMLINK_NOFOLLOW
-
: -1025(無效)AT_SYMLINK_FOLLOW
-
- 參數:
-
資料結構:其定義可能與具體作業系統相關,但是基本形式為:stat
其中struct stat{ mode_t st_mode; //檔案權限和類型資訊 ino_t st_ino; //i-node 号 dev_t st_dev; // 裝置号 dev_t st_rdev; // 特殊檔案的裝置号 nlink_t st_nlink; // 硬連結數量 uid_t st_uid; // owner 的使用者ID gid_t st_gid; // owner 的組ID off_t st_size; //對普通檔案,它是檔案位元組大小 struct timespec st_atime; // 上次通路時間 struct timespec st_mtile; // 上次修改時間 struct timespec st_ctime; // 上次檔案狀态改變的時間 blksize_t st_blksize; // 最佳的 I/O block 大小 blkcnt_t st_blocks; //配置設定的磁盤塊數量 }
結構與具體作業系統相關,但是至少包括下面兩個字段:timespec
struct timespec{ time_t tv_sec; // 秒 long tv_nsec; //納秒 }
- UNIX 檔案類型:
- 普通檔案:最常見的檔案類型,這種檔案包含了某種形式的資料。至于這種資料是二進制還是文本,對核心無差別。普通檔案的内容解釋由具體的應用程式進行。
- 目錄檔案:這種檔案包含了其他檔案的名字,以及指向這些檔案有關資訊的指針。
- 隻有核心可以直接寫目錄檔案(通常使用者寫目錄檔案都要通過核心)
- 對某個目錄檔案具有讀權限的任何一個程序都可以讀取該目錄的内容
- 塊特殊檔案:這種類型的檔案提供對裝置(如磁盤)帶緩沖的通路。每次通路以固定長度為機關進行。
- 字元特殊檔案:這種類型的檔案提供對裝置不帶緩沖的通路,每次通路長度可變。
系統的所有裝置,要麼是字元特殊檔案,要麼是塊特殊檔案
-
:這種類型的檔案用于程序間通信,有時也稱為命名管道FIFO
- 套接字:這種類型的檔案用于程序間的網絡通信(也可用于單機上程序的非網絡通信)
- 符号連結:這種類型的檔案指向另一個檔案
- 檔案類型資訊存放在
成員中,可以用下列的宏測試檔案類型:stat.st_mode
-
:測試是否普通檔案S_ISREG()
-
:測試是否目錄檔案S_ISDIR()
-
:測試是否字元特殊檔案S_ISCHR()
-
:測試是否塊特殊檔案S_ISBLK()
-
:測試是否S_ISFIFO()
FIFO
-
:測試是否符号連結檔案S_ISLNK()
-
:測試是否套接字S_ISSOCK()
允許将程序間通信對象說明為檔案。但是下面的宏測試的不是POSIX.1
,而是stat.st_mode
(stat*
指針):stat
-
:測試是否消息隊列S_TYPEISMQ()
-
:測試是否信号量S_TYPEISSEM()
-
:測試是否共享存儲對象S_TYPEISSHM()
-
- 與一個程序有關的ID有很多:
- 實際使用者 ID 和實際組 ID: 标志我們究竟是誰。當我們登入進作業系統時,這兩個值就确定了!
- 有效使用者 ID、有效組ID、附屬組 ID: 用于檔案通路權限檢查。
- 儲存的設定使用者ID、儲存的設定組ID:由
函數儲存exec
和stat.st_uid
指定。當一個檔案時可執行檔案時,如果執行這個檔案,那麼程序的有效使用者ID就是實際使用者ID,有效組ID就是實際組ID,除了下面的情況:stat.st_gid
- 當在
中設定了一個特殊标志:設定使用者ID位時,則将程序的有效使用者ID設定為檔案所有者的使用者IDstat.st_mode
- 當在
中設定了一個特殊标志:設定組ID位時,則将程序的有效組ID設定為檔案所有者的組IDstat.st_mode
任何程序都是由可執行檔案被執行而得到。是以位于磁盤上的可執行檔案的所屬的使用者ID群組ID會影響到程序的使用者ID群組ID
,且該檔案的設定使用者ID位已經被設定,那麼無論誰執行這個可執行檔案時,該可執行檔案産生的程序就具有超級使用者權限。 設定使用者ID位、設定組ID位 都包含在`stat.st_mode`中,可以通過下列兩個宏測試:root
-
:測試是否設定了設定使用者ID位S_ISUID()
- ·S_ISGID()`:測試是否設定了設定組ID位
- 檔案通路權限:所有檔案類型(包括目錄,字元特别檔案等)都有通路權限。每個檔案都有9個通路權限位:
-
:使用者讀S_IRUSR
-
:使用者寫S_IWUSR
-
:使用者執行S_IXUSR
-
:組讀S_IRGRP
-
:組寫S_IWGRP
-
:組執行S_IXGRP
-
:其他讀S_IROTH
-
:其他寫S_IWOTH
-
:其他執行S_IXOTH
- 當用名字
打開任何一個類型的檔案時,對pathname
中包含的每一個目錄,包括pathname
可能隐含的目前工作目錄都應該具有執行權限pathname
是以目錄的執行權限位也稱之為搜尋位
- 對一個檔案的讀權限決定了我們能否打開現有檔案進行讀操作
- 對一個檔案的寫權限決定了我們能否打開現有檔案進行寫操作
- 如果你在
函數中對一個檔案指定了open
标志,則必須對該檔案具有寫權限O_TRUNC
- 為了在一個目錄中常見一個新檔案,必須對該目錄具有寫權限和執行權限
- 為了删除一個現有檔案,必須對包含該檔案的目錄具有寫權限和執行權限。對該檔案本身沒有權限的限制
- 如果用7個
函數中的任何一個執行某個檔案,則必須對該檔案具有執行權限,且該檔案必須是個普通檔案exec
- 若程序的有效使用者ID是0(超級使用者),則對該檔案的任何通路行為都準許
- 若程序的有效使用者ID等于檔案的所有者ID(也就是程序擁有此檔案):
- 如果該檔案的使用者讀權限開放,則核心允許程序讀該檔案
- 如果該檔案的使用者寫權限開放,則核心允許程序寫該檔案
- 如果該檔案的使用者執行權限開放,則核心允許程序執行該檔案
- 若程序的有效組ID或者程序的附屬組ID之一等于檔案的組ID:
- 如果該檔案的組讀權限開放,則核心允許程序讀該檔案
- 如果該檔案的組寫權限開放,則核心允許程序寫該檔案
- 如果該檔案的使用者執行權限開放,則核心允許程序執行該檔案
- 否則:
- 如果該檔案的其他讀權限開放,則核心允許程序讀該檔案
- 如果該檔案的其他寫權限開放,則核心允許程序寫該檔案
- 如果該檔案的其他戶執行權限開放,則核心允許程序執行該檔案
-
- 對一個目錄的讀權限和可執行權限是不同的:
- 目錄讀權限:允許讀目錄,進而獲得在該目錄中所有檔案名的清單
- 目錄可執行權限:允許搜尋該目錄,進而尋找一個特定的檔案名
- 當一個程序通過
或者open
建立一個新檔案時:creat
- 新檔案的使用者ID被設定為程序的有效使用者ID
- 新檔案的組ID可以有兩個值之一:
- 程序的有效組ID
- 檔案所在目錄的組ID
具體選擇哪個,由具體作業系統決定
- 示例
#include <stdio.h> #include<sys/stat.h> #include<unistd.h> #include<fcntl.h> typedef struct stat Stat; void test_file_type(Stat* file_stat,const char* file_name) { printf("\t%s type is:",file_name); if(S_ISREG(file_stat->st_mode)) printf(" regular file, ") ; if(S_ISDIR(file_stat->st_mode)) printf("directory file ") ; if(S_ISCHR(file_stat->st_mode)) printf("char file, ") ; if(S_ISBLK(file_stat->st_mode)) printf(" block file, ") ; if(S_ISFIFO(file_stat->st_mode)) printf("fifo file, ") ; if(S_ISLNK(file_stat->st_mode)) printf("ink file, ") ; if(S_ISSOCK(file_stat->st_mode)) printf("socket, ") ; if(S_TYPEISMQ(file_stat)) printf("message queue file, ") ; if(S_TYPEISSEM(file_stat)) printf("semaphore, ") ; if(S_TYPEISSHM(file_stat)) printf("share memory, ") ; printf("\n"); } void test_file_id(Stat* file_stat,const char* file_name) { printf("\t%s file id:"); printf("owner id < %d >, ",file_stat->st_uid); printf("group id < %d >, ",file_stat->st_gid); if(S_ISUID&file_stat->st_mode) printf("set_user_id, "); if(S_ISGID&file_stat->st_mode) printf("set_group_id , "); printf("\n"); } void test_file_permission(Stat* file_stat,const char* file_name) { printf("\t%s file permission:"); if(S_IRUSR&file_stat->st_mode) printf("user read, "); if(S_IWUSR&file_stat->st_mode) printf("user write, "); if(S_IXUSR&file_stat->st_mode) printf("user exec, "); if(S_IRGRP&file_stat->st_mode) printf("group read, "); if(S_IWGRP&file_stat->st_mode) printf("group write, "); if(S_IXGRP&file_stat->st_mode) printf("group exec, "); if(S_IROTH&file_stat->st_mode) printf("other read, "); if(S_IWOTH&file_stat->st_mode) printf("other write, "); if(S_IXOTH&file_stat->st_mode) printf("other exec, "); printf("\n"); } int main(int argc, char *argv[]) { Stat stat_buf; int fd; printf("Test regular file:\n"); stat("/home/huaxz1986/main.c",&stat_buf); test_file_type(&stat_buf,"/home/huaxz1986/main.c"); test_file_id(&stat_buf,"/home/huaxz1986/main.c"); test_file_permission(&stat_buf,"/home/huaxz1986/main.c"); printf("Test directory file:\n"); stat("/home/huaxz1986/APUE",&stat_buf); test_file_type(&stat_buf,"/home/huaxz1986/APUE"); test_file_id(&stat_buf,"/home/huaxz1986/APUE"); test_file_permission(&stat_buf,"/home/huaxz1986/APUE"); printf("Test block file:\n"); stat("/dev/loop0",&stat_buf); test_file_type(&stat_buf,"/dev/loop0E"); test_file_id(&stat_buf,"/dev/loop0"); test_file_permission(&stat_buf,"/dev/loop0"); printf("Test char file:\n"); stat("/dev/mem",&stat_buf); test_file_type(&stat_buf,"/dev/mem"); test_file_id(&stat_buf,"/dev/mem"); test_file_permission(&stat_buf,"/dev/mem"); printf("Test link file:\n"); stat("/dev/cdrom",&stat_buf); test_file_type(&stat_buf,"/dev/cdrom"); test_file_id(&stat_buf,"/dev/cdrom"); test_file_permission(&stat_buf,"/dev/cdrom"); printf("Test fifo file:\n"); stat("/run/systemd/initctl/fifo",&stat_buf); test_file_type(&stat_buf,"/run/systemd/initctl/fifo"); test_file_id(&stat_buf,"/run/systemd/initctl/fifo"); test_file_permission(&stat_buf,"/run/systemd/initctl/fifo"); printf("Create new file:\n"); fd=openat(AT_FDCWD,"test",O_WRONLY|O_CREAT,S_IRUSR); fstat(fd,&stat_buf); test_file_type(&stat_buf,"./test"); test_file_id(&stat_buf,"./test"); test_file_permission(&stat_buf,"./test"); return ; }
二、通路測試和檔案模式建立屏蔽字
- 當用
函數打開一個檔案時,核心根據程序的有效使用者ID和有效組ID為依據來執行通路權限測試。但是如果你想測試程序的實際使用者ID和實際組ID是否能夠通過權限測試時,可以用下列兩個函數:open()
#include<unistd.h> int access(const char *pathname,int mode); int faccess(int fd,const char*pathname,int mode,int flag);
- 參數:
-
:檔案路徑名pathname
-
:指定要測試的模式。mode
- 如果要測試檔案是否已存在,則
設為mode
F_OK
- 如果要測試程序的實際使用者ID和實際組ID的權限,則可以為下列常量的按位或
-
:測試讀權限R_OK
-
:測試寫權限W_OK
-
:測試執行權限X_OK
-
- 如果要測試檔案是否已存在,則
函數:faccess
-
:一個打開目錄檔案的描述符,或者fd
AT_FDCWD
-
:pathname
- 如果為絕對路徑,則忽略
參數fd
- 如果為相對路徑,則相對路徑的目錄由
指定。fd
- 若
,則表示相對于目前工作目錄fd=AT_FDCWD
- 否則相對于
對于的打開的目錄fd
- 若
- 如果為絕對路徑,則忽略
-
:如果是flag
,則通路檢查使用程序的有效使用者ID和有效組ID,而不是實際使用者ID和實際組IDAT_EACCESS
-
- 傳回值:
- 成功:傳回0
- 出錯: 傳回 -1
- 參數:
- 檔案模式建立屏蔽字:當程序建立一個新的目錄或者檔案時,會使用檔案模式建立屏蔽字。在檔案模式建立屏蔽字中為1的位,在檔案
中的相應位一定被關閉。設定程序的檔案模式建立屏蔽字的函數為:mode
#include<sys/stat.h> mode_t umask(mode_t cmask);
- 參數:
-
:要設定的新的檔案模式建立屏蔽字cmask
-
- 傳回值:
- 成功:舊的檔案模式建立屏蔽字
- 函數未指定失敗時傳回何值
或者creat
函數指定了open
,那麼該mode
必須通過檔案模式建立屏蔽字的屏蔽之後才是最終新建立的檔案的權限模式。mode
shell 有一個
指令。我們可以通過該指令來設定或者列印目前的檔案模式建立屏蔽字umask
- 參數:
- 示例
可以看到:#include<stdio.h> #include<unistd.h> #include<string.h> #include<errno.h> #include<sys/stat.h> #include<fcntl.h> typedef struct stat Stat; void test_access(const char*pathname,int mode) { int ok; printf("\tAccess %s:",pathname); ok=access(pathname,mode); if(ok==-) { printf("failed! Beause %s \n",strerror(errno)); } else { printf("ok!\n"); } } void test_umask(mode_t cmask) { mode_t old_mode; old_mode=umask(cmask); printf("\t old mask is:"); print_mode(old_mode); printf("\t current mask is:"); print_mode(cmask); } void print_mode(mode_t expected_mode) { if(S_IRUSR&expected_mode) printf("U_RD, "); if(S_IWUSR&expected_mode) printf("U_WR, "); if(S_IXUSR&expected_mode) printf("U_EXC, "); if(S_IRGRP&expected_mode) printf("G_RD, "); if(S_IWGRP&expected_mode) printf("G_WR, "); if(S_IXGRP&expected_mode) printf("G_EXC, "); if(S_IROTH&expected_mode) printf("O_RD, "); if(S_IWOTH&expected_mode) printf("O_WR, "); if(S_IXOTH&expected_mode) printf("O_EXC, "); printf("\n"); } void test_file_mode(int expected_mode,const char* file_name) { printf("\t Expected file mode:"); print_mode(expected_mode); Stat stat_buf; int fd; fd=openat(AT_FDCWD,file_name,O_CREAT|O_EXCL|O_RDWR,expected_mode); if(fd==-) { printf("open %s in test_file_mode,because %s",file_name,strerror(errno)); return ; } fstat(fd,&stat_buf); printf("\n \t Real file mode:"); print_mode(stat_buf.st_mode); unlinkat(AT_FDCWD,"test_file",!AT_SYMLINK_FOLLOW); } int main(int argc, char *argv[]) { printf("Test access: no exist file:\n"); test_access("/no/exist",F_OK); printf("Test access: no write:\n"); test_access("/etc/shadow",W_OK); printf("Test access: write ok:\n"); test_access("/home/huaxz1986/APUE",W_OK); printf("\n\n\n"); printf("Current creat file:\n"); test_file_mode(S_IRWXU|S_IRWXG|S_IRWXO,"test1"); printf("umask:\n"); test_umask(S_IRUSR|S_IRGRP|S_IROTH); printf("After umask :\n"); test_file_mode(S_IRWXU|S_IRWXG|S_IRWXO,"test2"); return ; }
-
函數:對于不存在的檔案名通路失敗;對沒有寫權限的名字寫通路失敗access
- 被建立的檔案的通路權限是由檔案建立屏蔽字、建立檔案時指定的權限二者共同作用的
-
三、修改檔案通路權限和檔案所屬使用者
- 修改檔案的現有的通路權限:
#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);
- 參數:
-
:檔案路徑名pathname
-
:檔案修改後的權限。mode
函數:fchmod
-
:打開的檔案描述符fd
函數:fchmod
-
:一個打開目錄檔案的描述符,或者fd
AT_FDCWD
-
:pathname
- 如果為絕對路徑,則忽略
參數fd
- 如果為相對路徑,則相對路徑的目錄由
指定。fd
- 若
,則表示相對于目前工作目錄fd=AT_FDCWD
- 否則相對于
對于的打開的目錄fd
- 若
- 如果為絕對路徑,則忽略
-
:如果是flag
,則!AT_SYMLINK_FOLLOW
并不跟随符号連結fchmodtat
-
- 傳回值:
- 成功:傳回0
- 出錯: 傳回 -1
可以是下面常量的按位或:(來自頭檔案mode
<sys/stat.h>
-
:執行時設定使用者IDS_ISUID
-
:執行時設定組IDS_ISGID
-
:粘着位S_ISVTX
-
:使用者讀、寫和執行S_IRWXU
-
:使用者讀S_IRUSR
-
:使用者寫S_IWUSR
-
:使用者執行S_IXUSR
-
:組讀、寫和執行S_IRWXG
-
:使用者讀S_IRGRP
-
:使用者寫S_IWGRP
-
:使用者執行S_IXGRP
-
:其他讀、寫和執行S_IRWXO
-
:使用者讀S_IROTH
-
:使用者寫S_IWOTH
-
:使用者執行S_IXOTH
- 參數:
- 粘着位:如果對一個目錄設定了粘着位,則任何對該目錄具有寫權限的程序都能夠在該目錄中建立檔案。但是:隻有滿足下列條件之一的使用者才能删除或者重命名該目錄下的檔案:
- 擁有此檔案
- 擁有此目錄
- 是超級使用者
對于未設定粘着位的目錄,則隻要使用者對該目錄有寫權限,那麼就有修改和重命名該目錄下其他檔案的能力
- 修改使用者的ID群組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);
- 參數:
-
:檔案路徑名pathname
-
:檔案修改後的使用者IDowner
-
:檔案修改後的組IDgroup
函數:fchown
-
:打開的檔案描述符,要修改的就是這個檔案fd
函數:fchmod
-
:一個打開目錄檔案的描述符,或者fd
AT_FDCWD
-
:pathname
- 如果為絕對路徑,則忽略
參數fd
- 如果為相對路徑,則相對路徑的目錄由
指定。fd
- 若
,則表示相對于目前工作目錄fd=AT_FDCWD
- 否則相對于
對于的打開的目錄fd
- 若
- 如果為絕對路徑,則忽略
-
:如果是flag
,則!AT_SYMLINK_FOLLOW
并不跟随符号連結,修改的是符号連結本身而不是符号連結指向的檔案fchmodtat
-
- 傳回值:
- 成功: 傳回 0
- 出錯: 傳回 -1
-
函數更改的是符号連結本身,而lchown
遇到符号連結時更改的是符号連結指向的檔案chown
- 如果這些函數由非超級使用者程序調用,則成功傳回時,該檔案的設定使用者ID和設定組ID位都被清除
- 參數:
- 示例
可以看到:#include <stdio.h> #include<fcntl.h> #include<sys/stat.h> #include<unistd.h> #include<errno.h> #include<string.h> typedef struct stat Stat; void print_mode(mode_t expected_mode) { printf("\t"); if(S_IRUSR&expected_mode) printf("U_RD, "); if(S_IWUSR&expected_mode) printf("U_WR, "); if(S_IXUSR&expected_mode) printf("U_EXC, "); if(S_IRGRP&expected_mode) printf("G_RD, "); if(S_IWGRP&expected_mode) printf("G_WR, "); if(S_IXGRP&expected_mode) printf("G_EXC, "); if(S_IROTH&expected_mode) printf("O_RD, "); if(S_IWOTH&expected_mode) printf("O_WR, "); if(S_IXOTH&expected_mode) printf("O_EXC, "); printf("\n"); } int main(int argc, char *argv[]) { Stat stat_buf; int fd; fd=openat(AT_FDCWD,"test",O_CREAT|O_RDWR,S_IRWXU|S_IRWXG|S_IRWXO); fstat(fd,&stat_buf); printf("Current mode:\n"); print_mode(stat_buf.st_mode); printf("Current user: %d, current group: %d\n",stat_buf.st_uid,stat_buf.st_gid); if(fchmod(fd,S_IRWXU)==-) { printf("chmod failed,beause of %s",strerror(errno)); }else { printf("After Change mode:\n"); fstat(fd,&stat_buf); print_mode(stat_buf.st_mode); } if(fchown(fd,,)==-) { printf("chown failed,beause of %s\n",strerror(errno)); }else { fstat(fd,&stat_buf); printf("After Change owner--> user : %d, current group: % d\n",stat_buf.st_uid,stat_buf.st_gid); } return ; }
- 修改檔案所屬的使用者群組,需要超級使用者權限。普通使用者無法修改,即使該使用者就是該檔案的所有者也不行
四、修改檔案長度
- 檔案長度:
字段存放的是以位元組為機關的檔案的長度。此字段隻對普通檔案、目錄檔案、符号連結才有意義:stat.st_size
- 對普通檔案:其長度就是檔案的大小。長度為0表示該檔案為空
- 對目錄檔案:其長度通常是個整數(如16或者512)的整數倍
- 對符号連結:其長度是符号連結本身存放的某個檔案名的實際位元組數(它并不包含字元串的
位元組,因為這些字元是存放在檔案中,而不是存放在記憶體中的字元串)null
存放的是對于檔案 I/O 較合适的塊長度;stat.st_blksize
存放的是所配置設定的塊的數量(一個塊512位元組)。注意:stat.st_blocks
- 對于普通檔案,可能包含空洞。空洞是由于設定的檔案偏移量超過了檔案末尾,然後寫入了某些資料造成的。對于空洞檔案:
- 空洞檔案的存儲需要的磁盤塊數量可能遠小于檔案大小。檔案大小是檔案末尾到檔案頭的位元組數
- 讀取空洞檔案的空洞時,對于沒有寫過的位元組位置
傳回的是位元組0read
- 截斷檔案:通常可以用帶
選項的O_TRUNC
函數來清空一個檔案(截斷到0)。但是如果希望截斷檔案使得檔案大小為指定位元組數,則可以用下列的函數:open()
#include<unistd.h> int truncate(const char*pathname,off_t length); int ftruncate(int fd,off_t length);
- 參數:
-
:檔案路徑名pathname
-
:檔案修改後大小(位元組數)length
-
:打開的檔案描述符,要修改的就是這個檔案fd
-
- 傳回值:
- 成功: 傳回 0
- 出錯: 傳回 -1
- 若
小于檔案的原大小,則修改檔案大小之後,檔案新的尾端之後的位置不再可以通路length
- 若
大于檔案的原大小,則修改檔案大小之後,會形成空洞。即從檔案原大小新的尾端形成了空洞length
- 參數:
- 示例
可以看到:#include <stdio.h> #include<sys/stat.h> #include<string.h> #include<errno.h> #include<unistd.h> #include<fcntl.h> typedef struct stat Stat; void print_file_size(int fd) { int ok; Stat stat; ok=fstat(fd,&stat); if(ok==) { printf("\tfstat error,because of %s\n",strerror(errno)); }else { printf("\tfile size is :%d; blksize:%d; blocks:%d \n",stat.st_size,stat.st_blksize,stat.st_blocks); } } void test_truncate(int fd,off_t len) { int ok; ok=ftruncate(fd,len); if(ok==-) { printf("\tftruncate error,because of %s\n",strerror(errno)); }else { print_file_size(fd); } } void test_open_trunc(const char* filename) { int fd; fd=openat(AT_FDCWD,filename,O_TRUNC|O_RDWR); if(fd==-) { printf("\topenat %s failed,because of %s\n",strerror(errno)); }else { print_file_size(fd); } } void read_fd(int fd,int len) { char bytes[]; int ok; ok=read(fd,bytes,len); int i; if(ok==-) { printf("\tread error,because of %s\n",strerror(errno)); }else { lseek(fd,,SEEK_SET); printf("\tthe bytes is:"); for(i=;i<ok;i++) { printf("\t%d",bytes[i]); } printf("\n"); } } int main(int argc, char *argv[]) { int fd; fd=openat(AT_FDCWD,"test",O_CREAT|O_RDWR,S_IRWXU); if(fd==-) return; write(fd,"abcdefg",); lseek(fd,,SEEK_SET); printf("Original size:\n"); print_file_size(fd); printf("Truncate to 100\n"); test_truncate(fd,); read_fd(fd,); printf("Truncate to 2\n"); test_truncate(fd,); read_fd(fd,); close(fd); printf("Test open with trunc:\n"); test_open_trunc("test"); read_fd(fd,); return ; }
- 對于檔案空洞,它不占用任何磁盤空間;空洞部分讀出的内容全為0
- 對于非常小的檔案,比如這裡的 8 位元組文字,磁盤配置設定了 8個塊(4kb)。
五、UNIX檔案系統、硬連結、軟連結、删除、重命名
- UNIX檔案系統簡介(傳統的基于BSD的UNIX檔案系統,稱作
):UFS
- 一個磁盤可以劃分成一個或者多個分區,每個分區可以包含一個檔案系統。每個檔案系統包含一些柱面組。每個柱面組包括:
- 一個 i 節點圖:用于訓示哪些 i 節點已經被使用,哪些未被使用
- 一個 塊位圖:用于訓示哪些資料塊已經被使用,哪些為被使用
- 一個 i 節點組。它包含的是許多 i 節點。
- 一個資料區:存放具體的資料塊和目錄塊
- 資料區包含兩種類型的塊:
- 目錄塊:它的内容是
這種格式的記錄的清單<i 節點編号>|<檔案名>
- 資料塊:它的内容就是具體檔案的資料
- 目錄塊:它的内容是
- i 節點是固定長度的記錄項,它包含有關檔案的大部分資訊
- 每個 i 節點都有一個連結計數,其值是指向 i 節點的目錄的項數(這種連結類型稱之為硬連結)。隻有當該連結計數減少為0時,才可以删除該連結檔案(也就是釋放該檔案占用的資料塊)。
- 在
結構中,連結計數包含在stat
成員中(POSIX常量:st_nlink
指定了一個檔案連結數的最大值)LINK_MAX
- 在
- 每個 i 節點包含了檔案有關的所有資訊:檔案類型、檔案權限通路位、檔案長度和指向檔案資料塊的指針
-
結構中的大多數資訊來自于 i 結點。隻有兩項重要資料存放在目錄項中:檔案名、i節點編号stat
-
- 目錄項中的 i 節點編号隻能指向同一個檔案系統中的相應的 i 節點。
- 每個 i 節點都有一個連結計數,其值是指向 i 節點的目錄的項數(這種連結類型稱之為硬連結)。隻有當該連結計數減少為0時,才可以删除該連結檔案(也就是釋放該檔案占用的資料塊)。
是以硬連結不能跨檔案系統
- 一個磁盤可以劃分成一個或者多個分區,每個分區可以包含一個檔案系統。每個檔案系統包含一些柱面組。每個柱面組包括:
-
當在不更換檔案系統的情況下重命名一個檔案時,該檔案的實際内容并未移動。隻需要構造一個指向現有 i 節點的新目錄項,并删除來的目錄項。此時該 i節點的連結計數不會改變
這就是
指令的操作方式mv
- 與硬連結對應的概念是軟連結。軟連結也稱作符号連結,它是一種特殊的檔案。該檔案的實際内容(在資料塊中)包含了該符号連結所指向的檔案的名字。同時該檔案的 i 節點訓示了該檔案類型是
,于是系統知道了這個檔案是個符号連結。S_IFLNK
- 硬連結直接指向檔案的
節點i
- 軟連結是對一個檔案的間接指針
- 硬連結通常要求連結和檔案位于同一個檔案系統中
- 隻有超級使用者才能建立指向目錄的硬連結(在底層檔案系統支援的情況下)
對于符号連結以及它指向何種類型的檔案并沒有什麼限制。任何使用者都可以建立指向目錄的符号連結。但是使用符号連結有可能在檔案系統中引入循環
對于處理檔案和目錄的函數,如果傳遞的是一個符号連結的檔案名,則應該注意:函數是否跟随符号連結,即函數是處理符号連結指向的檔案,還是處理符号連結本身。
- 跟随符号連結(即處理符号連結指向的檔案)的函數有:
、access
、chdir
、chmod
、chown
、creat
、exec
、link
、open
、opendir
、pathconf
、stat
truncate
- 不跟随符号連結(即處理符号連結檔案本身)的函數有:
、lchown
、lstat
、readlink
、remove
、rename
unlink
- 一個例外的情況:如果用
和O_CREAT
選項調用O_EXCL
,此時若參數是個符号連結的檔案名,則open
出錯傳回(并不考慮符号連結指向的檔案是否存在),同時将open
設為errno
EEXIST
- 一個例外的情況:如果用
- 硬連結直接指向檔案的
- 任何一個目錄
的硬連結至少為2:dirxxx
- 該目錄的内容中有一條名為的
記錄,該記錄的.
指向<i節點編号>
目錄的節點dirxxx
- 該目錄的父目錄的内容中有一條記錄,記錄的名字
,記錄的dirxxx
指向<i節點編号>
目錄的節點dirxxx
- 若該目錄有子目錄。
的任何子目錄的内容有一條名為dirxxx
的記錄,該記錄的..
指向<i節點編号>
目錄的節點dirxxx
是以父目錄中的每個子目錄都使得父目錄的連結計數加 1
- 該目錄的内容中有一條名為的
-
函數:建立一個指向現有檔案的硬連結link/linkat
#include<unistd.h> int link(const char *existingpath,const char *newpath); int linkat(int efd,const char*existingpath,int nfd,const char *newpath,int flag);
- 參數:
-
:現有的檔案的檔案名(新建立的硬連結指向它)existingpath
-
:新建立的目錄項newpath
- 如果
已存在,則傳回出錯newpath
- 隻建立
中的最後一個分量,路徑中的其他部分應當已經存在。newpath
- 如果
假設
為:newpath
,則要求/home/aaa/b/c.txt
已經存在,隻建立/home/aaa/b
c.txt
-
- 參數:
- 對于
函數:linkat
- 現有的檔案名是通過
和efd
指定。existingpath
- 若
是絕對路徑,則忽略existingpath
efd
- 若
是相對路徑,則:existingpath
- 若
,則efd=AT_FDCWD
是相對于目前工作目錄來計算existingpath
- 若
是一個打開的目錄檔案的檔案描述符,則efd
是相對于existingpath
對應的目錄檔案efd
- 若
- 若
- 建立的檔案名是通過
和nfd
指定。newpath
- 若
是絕對路徑,則忽略newpath
nfd
- 若
是相對路徑,則:newpath
- 若
,則nfd=AT_FDCWD
是相對于目前工作目錄來計算newpath
- 若
是一個打開的目錄檔案的檔案描述符,則nfd
是相對于newpath
對應的目錄檔案nfd
- 若
- 若
-
:當現有檔案是符号連結時的行為:flag
-
:建立符号連結指向的檔案的硬連結(跟随行為)flag=AT_SYMLINK_FOLLOW
-
:建立符号連結本身的硬連結(預設行為)flag=!AT_SYMLINK_FOLLOW
-
- 現有的檔案名是通過
- 傳回值:
- 成功: 傳回 0
- 失敗: 傳回 -1
-
這兩個函數建立新目錄項并對連結計數加1。建立新目錄項和增加連結計數是一個原子操作。
另外,大多數作業系統中,隻有超級使用者才能建立指向一個目錄的硬連結,因為這樣做很有可能在檔案系統中形成循環。
-
函數:删除一個現有的目錄項unlink
#include<unistd.h> int unlink(const char*pathname); int unlinkat(int fd,const char*pathname,int flag);
- 參數:
-
:現有的、待删除的目錄項的完整路徑名。pathname
函數:unlinkat
- 現有的檔案名是通過
和fd
指定。pathname
- 若
是絕對路徑,則忽略pathname
fd
- 若
是相對路徑,則:pathname
- 若
,則fd=AT_FDCWD
是相對于目前工作目錄來計算pathname
- 若
是一個打開的目錄檔案的檔案描述符,則fd
是相對于pathname
對應的目錄檔案fd
- 若
- 若
-
:flag
-
:可以類似于flag=AT_REMOVEDIR
一樣的删除目錄rmdir
-
:與flag=!AT_REMOVEDIR
執行同樣的操作unlink
-
-
- 傳回值:
- 成功: 傳回 0
- 失敗: 傳回 -1
- 擁有該檔案
- 擁有該目錄
- 具有超級使用者權限
- 如果該檔案的硬連結數不為0, 則還可以通過其他連結通路該檔案的内容
- 如果該檔案的硬連結數為0,而沒有程序打開該檔案,則該檔案的内容才有被删除
- 如果該檔案的硬連結數為0,但是有程序打開了該檔案,則該檔案的内容不能被删除。當程序關閉檔案時,核心會檢查打開該檔案的程序個數;當這個數量為0,核心再去檢查其連結計數。如果連結計數也是0,則就删除該檔案的内容。
這個特性常用于建立臨時檔案,先
一個檔案,然後立即調用open,create
。這樣即使程式崩潰,它所建立的臨時檔案也不會遺留下來unlink
- 參數:
- 如果删除目錄項出錯,則不對該檔案做任何更改
- 如果
是個符号連結,則pathname
unlink
删除該符号連結,而不會删除由該符号連結所引用的檔案。
如果僅僅給出符号連結的檔案名,沒有一個函數可以删除由該符号連結所引用的檔案
如果檔案系統支援,超級使用者可以調用
,其參數unlink
指定一個目錄pathname
通常推薦用
函數,其語義更加清晰rmdir
-
執行個體:link/unlink
可以看到:#include<stdio.h> #include<unistd.h> #include<sys/stat.h> #include<fcntl.h> #include<string.h> #include<errno.h> typedef struct stat Stat; void print_link_num(const char*path) { Stat stat; if(-==fstatat(AT_FDCWD,path,&stat,)) { printf("\tfstatat %s error,because %s\n",path,strerror(errno)); }else { printf("\t%s link is %d\n",path,stat.st_nlink); } } void add_link(const char *path,const char *new_path) { if(-==linkat(AT_FDCWD,path,AT_FDCWD,new_path,)) { printf("\tadd_link %s error,because %s\n",path,strerror(errno)); }else { print_link_num(path); } } void del_link(const char *path) { if(-==unlinkat(AT_FDCWD,path,)) { printf("\tun_link %s error,because %s\n",path,strerror(errno)); }else { print_link_num(path); } } int main(int argc, char *argv[]) { int fd; fd=openat(AT_FDCWD,"test",O_CREAT|O_RDWR,S_IRWXU); if (-==fd) return; close(fd); printf("The original link num\n"); print_link_num("test"); // link_num=1 printf("Add link\n"); add_link("test","new_test");// link_num=2 printf("\t The new file link:\n\t"); print_link_num("new_test"); printf("Del new file link\n"); del_link("new_test"); // link_num=1 printf("\tThe original file link\n\t"); print_link_num("test"); printf("Del original file link\n"); del_link("test"); // link_num=0 // test link is 0 printf("Del after he original file link is 0:\n"); del_link("test"); return ; }
-
和test
這兩個檔案共享一個 i 結點。是以該節點的 硬連結數為2new_test
- 一旦删除
,則對new_test
執行new_test
失敗(因為已經被fstatat
)。同時unlink
的硬連結數為1test
- 一旦
也被删除,則test
節點被釋放。執行i
失敗。unlink
-
-
函數:解除對一個目錄或者檔案的連結。remove
#include<stdio.h> int remove(const char *pathname);
- 參數
-
:檔案名或者目錄名pathname
-
- 傳回值:
- 成功:傳回0
- 失敗:傳回 -1
功能與remove
相同;對于目錄,unlink
功能與remove
相同rmdir
- 參數
-
函數:重命名檔案或目錄rename/renameat
#inluce<stdio.h> int rename(const char*oldname,const char *newname); int renameat(int oldfd,const char*oldname,int newfd,const char* newname);
- 參數:
-
:現有的檔案名或者目錄名oldname
-
:重命名的名字newname
- 如果
是個檔案名,則為該檔案或者符号連結重命名。oldname
- 此時若
已存在:若newname
是個目錄則報錯;若newname
不是個目錄:則先将newname
目錄項删除,然後将newname
重命名為oldname
newname
- 此時若
不存在:則直接将newname
重命名為oldname
newname
- 此時若
- 如果
是個目錄名,則為該目錄重命名。oldname
- 此時若
已存在:若newname
是個目錄且該目錄是個空目錄,則先将它删除,然後newname
重命名為oldname
;若newname
是個目錄且該目錄不是個空目錄,則報錯;若newname
不是個目錄,則報錯newname
- 此時若
不存在:則直接将newname
重命名為oldname
newname
- 此時若
- 如果
不能是oldname
的字首。因為重命名時,需要删除newname
oldname
-
- 參數:
- 如果
或者oldname
引用的是符号連結,則處理的是符号連結本身,而不是它引用的檔案newname
- 不能對
和.
重命名。即..
和.
不能出現在..
和oldname
的最後部分newname
- 若
和newname
引用同一個檔案,則函數不作任何更改而成功傳回oldname
- 對于
函數:renameat
- 現有的檔案名或目錄名是通過
和oldfd
指定。oldname
- 若
是絕對路徑,則忽略oldname
oldfd
- 若
是相對路徑,則:oldname
- 若
,則oldfd=AT_FDCWD
是相對于目前工作目錄來計算oldname
- 若
是一個打開的目錄檔案的檔案描述符,則oldfd
是相對于oldname
對應的目錄檔案oldfd
- 若
- 若
- 重命名的檔案名或目錄名是通過
和newfd
指定。newname
- 若
是絕對路徑,則忽略newname
newfd
- 若
是相對路徑,則:newname
- 若
,則newfd=AT_FDCWD
是相對于目前工作目錄來計算newname
- 若
是一個打開的目錄檔案的檔案描述符,則newfd
是相對于newname
對應的目錄檔案newfd
- 若
- 若
-
:當現有檔案是符号連結時的行為:flag
-
:建立符号連結指向的檔案的硬連結(跟随行為)flag=AT_SYMLINK_FOLLOW
-
:建立符号連結本身的硬連結(預設行為)flag=!AT_SYMLINK_FOLLOW
-
- 現有的檔案名或目錄名是通過
- 傳回值:
- 成功: 傳回 0
- 失敗: 傳回 -1
- 對于包含
以及oldname
的目錄,調用程序必須具有寫和執行的權限,因為将同時更改這兩個目錄。newname
-
函數:建立一個符号連結symlink/symlinkat
#include<unistd.h> int symlink(const char*actualpath,const char *sympath); int symlinkat(const char*actualpath,int fd,const char*sympath);
- 參數:
-
:符号連結要指向的檔案或者目錄(可能尚不存在)actualpath
-
:符号連結的名字sympath
二者不要求位于同一個檔案系統中
-
函數:symlinkat
- 符号連結的名字是通過
和fd
指定。sympath
- 若
是絕對路徑,則忽略sympath
fd
- 若
是相對路徑,則:sympath
- 若
,則fd=AT_FDCWD
是相對于目前工作目錄來計算sympath
- 若
是一個打開的目錄檔案的檔案描述符,則fd
是相對于sympath
對應的目錄檔案fd
- 若
- 若
- 參數:
- 傳回值:
- 成功: 傳回 0
- 失敗: 傳回 -1
-
函數:打開符号連結本身readlink/readlinkat
函數是跟随連結的,即打開符号連結指向的檔案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);
- 參數:
-
:符号連結的名字pathname
-
:存放符号連結内容的緩沖區buf
-
:期望讀入緩沖區的位元組數bufsize
函數:readlinkat
- 符号連結的名字是通過
和fd
指定。pathname
- 若
是絕對路徑,則忽略pathname
fd
- 若
是相對路徑,則:pathname
- 若
,則fd=AT_FDCWD
是相對于目前工作目錄來計算pathname
- 若
是一個打開的目錄檔案的檔案描述符,則fd
是相對于pathname
對應的目錄檔案fd
- 若
- 若
-
- 傳回值:
- 成功: 傳回實際上讀取的位元組數
- 失敗: 傳回 -1
和readlink
函數組合了readlinkat
open、read、close
函數的所有操作。
注意:讀入
中的符号連結的内容,并不是以buf
位元組終止。null
以
位元組終止的是記憶體中的字元串這種資料結構。而符号連結檔案的内容是簡單的字元序列,并不是字元串。null
- 參數:
- 符号連結示例:
可以看到:#include <stdio.h> #include<unistd.h> #include<sys/stat.h> #include<string.h> #include<errno.h> #include<fcntl.h> typedef struct stat Stat; void test_file_type(const char* file_name) { Stat _stat; if(-==stat(file_name,&_stat)) { printf("test_file_type file %s fail,because of %s \n",file_name,strerror(errno)); return; } printf("%s file type is:",file_name); if(S_ISLNK(_stat.st_mode)) printf("link file, ") ; if(S_ISREG(_stat.st_mode)) printf(" regular file, ") ; printf("\n"); } void creat_file(const char* file_name) { int fd; fd=open(file_name,O_CREAT|O_RDWR,S_IRWXU); if(-==fd) { printf("creat file %s fail,because of %s\n",file_name,strerror(errno)); } else{ close(fd); } } int creat_symlink(const char* actual_name,const char* sym_name) { int ok; ok=symlink(actual_name,sym_name); if(-==ok) { printf("creat symlink %s fail,because of %s\n",sym_name,strerror(errno)); } return ok; } void read_symlink(const char* sym_name) { char buffer[]; int len,i; len=readlink(sym_name,&buffer,); if(-==len) { printf("read symlink %s fail,because of %s\n",sym_name,strerror(errno)); }else { printf("length <%d>, content:",len); for(i=;i<len;i++) { printf("%c",buffer[i]); } printf("\n"); } } int main(int argc, char *argv[]) { creat_file("/home/huaxz1986/test"); test_file_type("/home/huaxz1986/test"); creat_symlink("/home/huaxz1986/test","/home/huaxz1986/sym_test"); test_file_type("/home/huaxz1986/sym_test"); read_symlink("/home/huaxz1986/sym_test"); }
- 符号連結檔案的内容就是它連結到的那個檔案的絕對路徑名,其中路徑名字元序列不包含
位元組null
- 在
中,經多次測試,符号連結檔案和普通檔案的ubuntu 16.04
完全相同。st_mode
- 符号連結檔案的内容就是它連結到的那個檔案的絕對路徑名,其中路徑名字元序列不包含
六、修改檔案的時間
- 檔案的時間:在
結構中存放着檔案的三個時間:stat
-
:檔案資料的最後通路時間st_atim
-
:檔案資料的最後修改時間st_mtim
-
: i 節點狀态的最後更改時間st_ctim
- 有很多操作,比如修改檔案權限,修改檔案的所有者等操作,他們隻修改 i 節點狀态(隻影響
),但是并不修改檔案資料,也并不通路檔案資料st_ctim
- 系統并不維護對
節點的最後通路時間。是以對于i
函數和access
函數,他們并不修改這三個時間中的任何一個stat
- 建立一個檔案不僅影響了檔案本身的這三個時間,也會影響該檔案目錄的這三個時間
-
-
函數:修改檔案的通路和修改時間futimens/utimensat/utimes
#include<sys/stat.h> int futimens(int fd,const struct timespec times[]); int utimensat(int fd,const char*path,const struct timespec times[],int flag); #include<sys/time.h> int utimes(const char*pathname,const struct timeval times[]);
-
參數:
對于
和futimens
函數:utimensat
-
:指向待修改檔案的指定的檔案資料通路和檔案資料修改時間的指針。times
對于C語言,參數中的數組自動轉換為指向數組的指針
- 這兩個時間是月曆時間,是自 1970:01:01–00:00:00 以來經曆的秒數。不足秒的部分用納秒表示
- 數組的第一個元素指定
;數組的第二個元素指定st_atim
st_ctim
-
可以按照下列四種方式之一指定:times
-
為空指針: 則将檔案的資料通路時間和檔案資料修改時間設定為目前時間times
-
此時要求程序的有效使用者ID等于該檔案所有者的ID;或者程序對該檔案有寫權限;或者程序是個超級使用者程序
-
參數是指向times
數組的指針:timespec
- 若數組的任何一個元素的
字段為tv_nsec
,則相應的時間戳就設定為目前時間,忽略相應的UTIME_NOW
tv_sec
字段
此時要求程序的有效使用者ID等于該檔案所有者的ID;或者程序對該檔案有寫權限;或者程序是個超級使用者程序
- 若數組的任何一個元素的
字段為tv_nsec
,則相應的時間戳保持不變,忽略相應的UTIME_OMIT
tv_sec
字段
若兩個時間戳都忽略,則不需要任何權限限制
- 若數組的任何一個元素的
字段為不是上面的兩種之一,則相應的時間戳就設定為相應的tv_nsec
和tv_sec
tv_nsec
字段
此時要求程序的有效使用者ID等于該檔案所有者的ID;或者程序是個超級使用者程序(對檔案隻有寫權限是不夠的)
- 若數組的任何一個元素的
-
-
- 對于
函數:utimes
-
:檔案的路徑名pathname
-
:指向times
數組的指針。timeval
結構用秒和微秒表示。timeval
struct timeval{ time_t tv_sec;//秒 long tv_usec; //微秒
函數:futimens
-
:待修改檔案的打開的檔案描述符fd
函數:utimensat
- 待打開檔案的名字是通過
和fd
指定。path
- 若
是絕對路徑,則忽略path
fd
- 若
是相對路徑,則:path
- 若
,則fd=AT_FDCWD
是相對于目前工作目錄來計算path
- 若
是一個打開的目錄檔案的檔案描述符,則fd
是相對于path
對應的目錄檔案fd
- 若
- 若
-
:若待修改的檔案是符号連結flag
- 如果為
,則符号連結本身的時間就會被修改!AT_SYMLINK_FOLLOW
- 預設情況下,修改的是符号連結指向的檔案的時間(跟随行為)
- 如果為
-
- 傳回值:
- 成功: 傳回 0
- 失敗: 傳回 -1
- 我們不能對
(i節點最後被修改時間)指定一個值。這個時間是被自動更新的。st_ctim
- 示例:
可以看到:#include <stdio.h> #include<sys/stat.h> #include<string.h> #include<errno.h> #include<unistd.h> #include<fcntl.h> typedef struct stat Stat; typedef struct timespec Timespec; void print_time(const char*path) { Stat buffer; if(-==stat(path,&buffer)) { printf("stat file <%s> error,because %s",path,strerror(errno)); }else { printf("\t%s time:\n",path); printf("\t\tdata last access time:<%d s,%d ns >\n",buffer.st_atim.tv_sec,buffer.st_atim.tv_nsec); printf("\t\tdata last modify time:<%d s,%d ns> \n",buffer.st_mtim.tv_sec,buffer.st_mtim.tv_nsec); printf("\t\tinfo last access time:<%d s,%d ns >\n",buffer.st_ctim.tv_sec,buffer.st_ctim.tv_nsec); } } void creat_file(const char* file_name) { int fd; fd=open(file_name,O_CREAT|O_RDWR,S_IRWXU); if(-==fd) { printf("creat file %s fail,because of %s\n",file_name,strerror(errno)); } else{ close(fd); } } void set_time(const char* file_name,Timespec time[]) { if(utimensat(AT_FDCWD,file_name,time,)==-) { printf("utimensat file <%s> error,because %s",file_name,strerror(errno)); }else { print_time(file_name); } } int main(int argc, char *argv[]) { Timespec times[]; times[].tv_nsec=; times[].tv_sec=; times[].tv_nsec=; printf("Create file:\n"); creat_file("/home/huaxz1986/test_time"); print_time("/home/huaxz1986/test_time"); sleep(); printf("Access not modify time:\n"); access("/home/huaxz1986/test_time",F_OK); print_time("/home/huaxz1986/test_time"); sleep(); printf("Chmod only modify st_ctime:\n"); chmod("/home/huaxz1986/test_time",S_IRUSR|S_IWUSR); print_time("/home/huaxz1986/test_time"); sleep(); printf("Set data access time , data modify time to now:"); set_time("/home/huaxz1986/test_time",NULL); sleep(); printf("Set data access time , data modify time "); set_time("/home/huaxz1986/test_time",times); return ; }
-
是由系統自動維護的,程式員無法手動指定st_ctim
-
七、目錄操作
-
函數建立一個空目錄:mkdir/mkdirat
#include<sys/stat.h> int mkdir(const char*pathname,mode_t mode); int mkdirat(int fd,const char *pathname,mode_t mode);
- 參數:
-
:被建立目錄的名字pathname
-
:被建立目錄的權限mode
,被建立目錄的名字是由mkdirat
和fd
共同決定的。pathname
- 若
是絕對路徑,則忽略pathname
fd
- 若
是相對路徑,則:pathname
- 若
,則fd=AT_FDCWD
是相對于目前工作目錄來計算pathname
- 若
是一個打開的目錄檔案的檔案描述符,則fd
是相對于pathname
對應的目錄檔案fd
- 若
-
- 傳回值:
- 成功: 傳回0
- 失敗: 傳回 -1
- 他們建立的目錄是空目錄。
- 對于目錄,通常至少要設定一個執行權限位,以允許通路該目錄中的檔案名
- 參數:
-
函數:删除一個空目錄rmdir
#include<unistd.h> int rmdir(const char *pathname);
- 參數:
-
:待删除的空目錄的名字pathname
-
- 傳回值:
- 成功: 傳回0
- 失敗: 傳回 -1
- 如果此時沒有其他程序打開該目錄,則釋放由此目錄占用的空間。
- 如果此時有一個或者多個程序打開此目錄,則在此函數傳回時删除最後一個連結以及
和.
項,直到最後一個打開該目錄的程序關閉該目錄時此目錄才真正被釋放。..
- 此時,在此目錄中不能再建立新檔案。
- 參數:
- 讀、寫目錄:對于某個目錄具有通路權限的任何使用者都可以讀該目錄。但是為了防止檔案系統産生混亂,隻有核心才能寫目錄。
一個目錄的寫權限和執行權限位決定了在該目錄中能否建立新檔案以及删除檔案,它們并不能寫目錄本身
各個函數:#include<dirent.h> DIR *opendir(const char *pathname); DIR *fdopendir(int fd); struct dirent *readdir(DIR *dp); void rewinddir(DIR *dp); int closedir(DIR *dp); long telldir(DIR *dp); void seekdir(DIR *dp,long loc);
-
:打開目錄。opendir
- 參數:
:目錄的名字pathname
- 傳回值:成功傳回目錄指針;失敗傳回
NULL
- 參數:
-
:打開目錄。fdopendir
- 參數:
:目錄檔案的檔案描述符fd
- 傳回值:成功傳回目錄指針;失敗傳回
NULL
- 參數:
-
:讀取目錄readdir
- 參數:
:目錄指針dp
- 傳回值: 成功則傳回目錄項的指針;失敗傳回
NULL
- 參數:
-
:将目錄的檔案偏移量清零(這樣下次讀取就是從頭開始)rewinddir
- 參數:
:目錄指針dp
- 參數:
-
:關閉目錄。closedir
- 參數:
:目錄指針dp
- 傳回值:成功傳回 0 ;失敗傳回 -1
- 參數:
-
:傳回目錄的檔案偏移量telldir
- 參數:
:目錄指針dp
- 傳回值:成功傳回目錄的檔案偏移量 ;失敗傳回 -1
- 參數:
-
:設定目錄的目前位置seekdir
- 參數:
:目錄指針;dp
:要設定的檔案偏移量loc
- 參數:
結構,它是一個内部結構。起作用類似于DIR
FILE
結構。
對于
結構,它是定義在dirent
頭檔案中。其與具體作業系統相關。但是它至少定義了兩個成員:<dirent.h>
struct dirent{ ino_t d_ino; // i 節點編号 char d_name[];// 以 null 結尾的檔案名字元串 }
項的大小并沒有指定,但必須保證它能包含至少d_name
個位元組(不包含終止NAME_MAX
位元組)null
-
- 目前工作目錄:每個程序都有一個目前工作目錄。此目錄是搜尋所有相對路徑名的起點。
目前工作目錄是本程序的一個屬性
與目前工作目錄相關的有三個函數:
各個函數:#include<unistd.h> int chdir(const char *pathname); int fchdir(int fd); char *getcwd(char *buf,size_t size);
-
:更改目前工作目錄。chdir
- 參數:
:将該目錄作為目前工作目錄pathname
- 傳回值:成功傳回 0 ;失敗傳回 -1
- 參數:
-
:更改目前工作目錄。fchdir
- 參數:
:将該fd
檔案描述符對應的目錄作為目前工作目錄fd
- 傳回值:成功傳回 0 ;失敗傳回 -1
- 參數:
-
:傳回目前工作目錄的名字getcwd
- 參數:
:緩沖區位址;buf
:緩沖區長度。這兩個參數決定了目前工作目錄名字字元串存放的位置。size
- 參數:
緩沖區必須足夠長以容納絕對路徑名加上一個終止
位元組。否則傳回出錯。null
-
- 傳回值: 成功則傳回
;失敗傳回buf
NULL
- 示例
#include <stdio.h> #include<unistd.h> #include<string.h> #include<errno.h> #include<dirent.h>S typedef struct dirent Dirent; void print_current_work_dir() { char buffer[]; if(getcwd(buffer,)==NULL) { printf("\t getcwd failed,because:%s\n",strerror(errno)); }else { printf("\t current work dir is :%s\n",buffer); } } void change_current_work_dir(const char*path) { if(chdir(path)==-) { printf("change current work dir to %s failed,because:%s\n" ,path,strerror(errno)); }else { print_current_work_dir(); } } void list_dir(const char *path) { DIR * dir; Dirent * dir_ent; dir=opendir(path); if(dir==NULL) { printf("\t open dir %s failed,because:%s\n",path,strerror(errno)); return; } printf("%s contents is :\n",path); while((dir_ent=readdir(dir)) !=NULL) { printf("\tid:<%d>, file_name :<%s>\n",dir_ent->d_ino,dir_ent->d_name); } closedir(dir); } int main(int argc, char *argv[]) { printf("Current work dir:\n"); print_current_work_dir(); printf("Expected change current work dir to /home/huaxz1986\n"); change_current_work_dir("/home/huaxz1986"); list_dir("/home/huaxz1986"); return ; }