文件和目录

来源:互联网 发布:java迭代器遍历位置 编辑:程序博客网 时间:2024/05/15 02:01

关于文件系统详见《鸟哥Linux私房菜》P197



ext2文件系统剖析




stat、fstat 和lstat 函数

#include <sys/types.h>
#include <sys/stat.h>
int stat(const char * p a t h n a m e, struct stat * b u f) ;
int fstat(int  f i l e d e s,struct stat * b u f) ;
int lstat(const char * p a t h n a m e, struct stat * b u f) ;
三个函数的返回:若成功则为0,若出错则为- 1
给定一个p a t h n a m e,s t a t函数返回一个与此命名文件有关的信息结构, f s t a t函数获得已在描述
符f i l e d e s上打开的文件的有关信息。l s t a t函数类似于s t a t,但是当命名的文件是一个符号连接时,

l s t a t返回该符号连接的有关信息,而不是由该符号连接引用的文件的信息。




在< s y s / s t a t . h >中的文件类型宏
宏 文 件 类 型
S _ I S R E G ( ) 普通文件
S _ I S D I R ( ) 目录文件
S _ I S C H R ( ) 字符特殊文件
S _ I S B L K ( ) 块特殊文件
S _ I S F I F O ( ) 管道或F I F O
S _ I S L N K ( ) 符号连接(P O S I X . 1或S V R 4无此类型)
S _ I S S O C K ( ) 套接字(P O S I X . 1或S V R 4无此类型)



设置-用户- I D和设置-组- I D
与一个进程相关联的I D有六个或更多,它们示于表4 - 3中。
表4-3 与每个进程相关联的用户I D和组I D


• 实际用户I D和实际组I D标识我们究竟是谁。这两个字段在登录时取自口令文件中的登录
项。通常,在一个登录会话期间这些值并不改变,但是超级用户进程有方法改变它们

• 有效用户I D,有效组I D以及添加组I D决定了我们的文件访问权

通常,有效用户I D等于实际用户I D,有效组I D等于实际组I D。
每个文件有一个所有者和组所有者,所有者由 s t a t结构中的 s t _ u i d表示,组所有者则由
s t _ g i d成员表示。
当执行一个程序文件时,进程的有效用户 I D通常就是实际用户 I D,有效组I D通常是实际
组I D。但是可以在文件方式字 ( s t _ m o d e )中设置一个特殊标志,其定义是“当执行此文件时,
将进程的有效用户I D设置为文件的所有者( s t _ u i d )”。
与此相类似,在文件方式字中可以设置另
一位,它使得执行此文件的进程的有效组 I D设置为文件的组所有者 ( s t _ g i d )。在文件方式字中
的这两位被称之为设置-用户- I D ( s e t - u s e r- I D )位和设置-组- I D ( s e t - g r o u p - I D )位。
例如,若文件所有者是超级用户,而且设置了该文件的设置 -用户- I D位,然后当该程序由
一个进程运行时,则该进程具有超级用户优先权。不管执行此文件的进程的实际用户I D是什么,
都作这种处理。
作为一个例子,U N I X程序p a s s w d ( 1 )允许任一用户改变其口令,该程序是一个
设置-用户- I D程序。因为该程序应能将用户的新口令写入口令文件中 (一般是/ e t c / p a s s w d或
/etc/shadow), 而只有超级用户才具有对该文件的写许可权,所以需要使用设置 -用户- I D特征。
因为运行设置 - 用户 - I D程序的进程通常得到额外的许可权,所以编写这种程序时要特别谨慎。



文件存取许可权

每个文件有9个存取许可权位,可将它们分成三类,见表4 - 4。



(进程打开文件时内核对文件所有权的判断执行步骤)

进程每次打开、创建或删除一个文件时,内核就进行文件存取许可权测试,而这种测试可
能涉及文件的所有者( s t _ u i d和s t _ g i d ),进程的有效ID (有效用户I D和有效组I D )以及进程的添加
组ID (若支持的话)。两个所有者I D是文件的性质,而有效I D和添加组I D则是进程的性质。内
核进行的测试是:
(1) 若进程的有效用户I D是0 (超级用户),则允许存取。这给予了超级用户对文件系统进行
处理的最充分的自由。
(2) 若进程的有效用户I D等于文件的所有者I D (也就是该进程拥有此文件):
(a) 若适当的所有者存取许可权位被设置,则允许存取。
(b) 否则拒绝存取。
适当的存取许可权位指的是,若进程为读而打开该文件,则用户 -读位应为1;若进程为写
而打开该文件,则用户-写位应为1;若进程将执行该文件,则用户-执行位应为1。
(3) 若进程的有效组I D或进程的添加组I D之一等于文件的组I D:
(a) 若适当的组存取许可权位被设置,则允许存取。
(b) 否则拒绝存取。
(4) 若适当的其他用户存取许可权位被设置,则允许存取,否则拒绝存取。
按顺序执行这四步。注意,如若进程拥有此文件 (第( 2 )步),则按用户存取许可权批准或拒
绝该进程对文件的存取——不查看组存取许可权。相类似,若进程并不拥有该文件。但进程属
于某个适当的组,则按组存取许可权批准或拒绝该进程对文件的存取——不查看其他用户的存
第4章 文件和目录 5 9
下载
取许可权。



access函数
正如前面所说,当用o p e n函数打开一个文件时,内核以进程的有效用户I D和有效组I D为基
础执行其存取许可权测试。有时,进程也希望按其实际用户 I D和实际组I D来测试其存取能力。
例如当一个进程使用设置 -用户- I D,或设置-组- I D特征作为另一个用户(或组)运行时,这就可
能需要。即使一个进程可能已经设置 -用户- I D为根,它仍可能想验证实际用户能否存取一个给
定的文件。a c c e s s函数是按实际用户I D和实际组I D进行存取许可权测试的。


# i n c l u d e < u n i s t d . h >
int access(const char * p a t h n a m e, int m o d e) ;
返回:若成功则为0,若出错则为- 1


access函数的m o d e常数,取自< u n i s t d . h >
m o d e 说 明
R _ O K 测试读许可权
W _ O K 测试写许可权
X _ O K 测试执行许可权
F _ O K 测试文件是否存在


umask函数

u m a s k函数为进程设置文件方式创建屏蔽字,并返回以前的值。 (这是少数几个没有出错返
回的函数中的一个。)
#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t  c m a s k) ;
返回:以前的文件方式创建屏蔽字


chmod和f c h m o d函数
这两个函数使我们可以更改现存文件的存取许可权。
#include <sys/types.h>
#include <sys/stat.h>
int chmod(const char * p a t h n a m e, mode_t m o d e) ;
int fchmod(int  f i l e d e s, mode_t m o d e) ;
两个函数返回:若成功则为0,若出错则为- 1


chmod函数的m o d e常数,取自< s y s / s t a t . h >
m o d e 说 明
S _ I S U I D 执行时设置-用户- I D
S _ I S G I D 执行时设置-组- I D
S _ I S V T X 保存正文
S _ I R W X U 用户(所有者)读、写和执行
S _ I R U S R 用户(所有者)读
S _ I W U S R 用户(所有者)写
S _ I X U S R 用户(所有者)执行
S _ I R W X G 组读、写和执行
S _ I R G R P 组读
S _ I W G R P 组写
S _ I X G R P 组执行

S _ I R W X O 其他读、写和执行
S _ I R O T H 其他读
S _ I W O T H 其他写
S _ I X O T H 其他执行


chown, fchown和l c h o w n函数
c h o w n函数可用于更改文件的用户I D和组I D。
#include <sys/types.h>
#include <unistd.h>
int chown(const char * p a t h n a m e, uid_to w n e r, gid_t g ro u p) ;
int fchown(int  f i l e d e s, uid_t o w n e r, gid_t g ro u p) ;
int lchown(const char * p a t h n a m e, uid_t o w n e r, gid_t g ro u p) ;
三个函数返回:若成功则为0,若出错则为- 1

除了所引用的文件是符号连接以外,这三个函数的操作相类似。在符号连接情况下, l c h o w n更
改符号连接本身的所有者,而不是该符号连接所指向的文件。


(1) 只有超级用户进程能更改该文件的用户I D。
(2) 若满足下列条件,一个非超级用户进程可以更改该文件的组I D:
(a) 进程拥有此文件(其有效用户I D等于该文件的用户I D)。
(b) 参数o w n e r等于文件的用户I D,参数g ro u p等于进程的有效组I D或进程的添加组I D之一。


文件长度

对于普通文件,其文件长度可以是0,在读这种文件时,将得到文件结束指示。
对于目录,文件长度通常是一个数,例如 1 6或5 1 2的整倍数,我们将在4 . 2 1节中说明读目
录操作。
对于符号连接,文件长度是在文件名中的实际字节数。例如,
lrwxrwxrwx 1 root 7 Sep 25 07:14 lib -> usr/lib
其中,文件长度7就是路径名u s r / l i b的长度(注意,因为符号连接文件长度总是由 s t _ s i z e指
示,所以符号连接并不包含通常C语言用作名字结尾的n u l l字符)。



文件中的空洞

空洞是由所设置的偏移量超过文件尾端,并写入了某些数据后造成的。



文件截短

为了截短文件可以调用函数t r u n c a t e和f t r u n c a t e。
#include <sys/types.h>
#include <unistd.h>
int truncate(const char * p a t h n a m e, off_t l e n g t h) ;
int ftruncate(int  f i l e d e s, off_t l e n g t h) ;
两个函数返回;若成功则为0,若出错则为- 1


这两个函数将由路径名 p a t h n a m e或打开文件描述符 f i l e d e s指定的一个现存文件的长度截短为

l e n g t h。如果该文件以前的长度大于l e n g t h,则超过l e n g t h以外的数据就不再能存取。如果以前
的长度短于l e n g t h,则其后果与系统有关。如果某个实现的处理是扩展该文件,则在以前的文
件尾端和新的文件尾端之间的数据将读作0 (也就是在文件中创建了一个空洞)。



link,unlink,remove和r e n a m e函数
创建一个向现存文件连接的方法是使用l i n k函数。
#include <unistd.h>
int link(const char * e x i s t i n g p a t h, const char * n e w p a t h) ;
返回:若成功则为0,若出错则为- 1

此函数创建一个新目录项n e w p a t h,它引用现存文件e x i s t i n g p a t h。如若n e w p a t h已经存在,则返回出错。
创建新目录项以及增加连接计数应当是个原子操作


为了删除一个现存的目录项,可以调用u n l i n k函数。
#include <unistd.h>
int unlink(const char * p a t h n a m e) ;
返回:若成功则为0,若出错则为- 1
此函数删除目录项,并将由p a t h n a m e所引用的文件的连接计数减1。如果该文件还有其他连接,
则仍可通过其他连接存取该文件的数据。如果出错,则不对该文件作任何更改。


#include <stdio.h>
int remove(const char * p a t h n a m e) ;
返回:若成功则为0,若出错则为- 1


文件或目录用r e n a m e函数更名。
#include <stdio.h>
int rename(const char * o l d n a m e, const char * n e w n a m e) ;
返回:若成功则为0,若出错则为- 1


符号连接
符号连接是对一个文件的间接指针,它与上一节所述的硬连接有所不同,硬连接直接指向
文件的i节点。引进符号连接的原因是为了避免硬连接的一些限制: ( a )硬连接通常要求连接和
文件位于同一文件系统中, ( b )只有超级用户才能创建到目录的硬连接。对符号连接以及它指
向什么没有文件系统限制,任何用户都可创建指向目录的符号连接。符号连接一般用于将一个
文件或整个目录结构移到系统中其他某个位置。


s y m l i n k函数创建一个符号连接。
#include <unistd.h>
int symlink(const char * a c t u a l p a t h, const char * s y m p a t h) ;
返回:若成功则为0,若出错则为- 1
该函数创建了一个指向 a c t u a l p a t h的新目录项 s y m p a t h,在创建此符号连接时,并不要求
a c t u a l p a t h已经存在。并且,a c t u a l p a t h和s y m p a t h并不需要位于同一文件系统中。


因为o p e n函数跟随符号连接,所以需要有一种方法打开该连接本身,并读该连接中的名字。
r e a d l i n k函数提供了这种功能。
#include <unistd.h>
int readlink(const char * p a t h n a m e, char * b u f, int b u f s i z e) ;
返回:若成功则为读的字节数,若出错则为- 1
此函数组合了open, read和c l o s e的所有操作。
如果此函数成功,则它返回读入 b u f的字节数。在b u f中返回的符号连接的内容不以 n u l l字
符终止。


文件的时间
对每个文件保持有三个时间字段,它们的意义示于表4 - 8中。
表4-8 与每个文件相关的三个时间值
字 段 说 明 例 子 l s ( 1 )选择项
s t _ a t i m e 文件数据的最后存取时间 r e a d - u
s t _ m t i m e 文件数据的最后修改时间 w r i t e 缺省
s t _ c t i m e i节点状态的最后更改时间 chmod, chown - c
注意修改时间( s t _ m t i m e )和更改状态时间( s t _ c t i m e )之间的区别。修改时间是文件内容最后一次
被修改的时间。更改状态时间是该文件的 i节点最后一次被修改的时间。在本章中我们已说明
了很多操作,它们影响到i节点,但并没有更改文件的实际内容:文件的存取许可权、用户 I D、
连接数等等。因为i节点中的所有信息都是与文件的实际内容分开存放的,所以,除了文件数
据修改时间以外,还需要更改状态时间。


mkdir和r m d i r函数
用m k d i r函数创建目录,用r m d i r函数删除目录。
#include <sys/types.h>
#include <sys/stat.h>
int mkdir(const char * p a t h n a m e, mode_t m o d e) ;
返回:若成功则为0,若出错则为- 1


用r m d i r函数可以删除一个空目录。
#include <unistd.h>
int rmdir(const char * p a t h n a m e) ;
返回:若成功则为0,若出错则为- 1

如果此调用使目录的连接计数成为 0,并且也没有其他进程打开此目录,则释放由此目录占用
的空间。如果在连接计数达到 0时,有一个或几个进程打开了此目录,则在此函数返回前删除
最后一个连接及. 和.. 项。另外,在此目录中不能再创建新文件。但是在最后一个进程关闭它
之前并不释放此目录(即使某些进程打开该目录,它们在此目录下,也不能执行其他操作,因
为为使r m d i r函数成功执行,该目录必须是空的)。


读目录

#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char * p a t h n a m e) ;
返回:若成功则为指针,若出错则为N U L L
struct dirent *readdir(DIR * d p) ;
返回:若成功则为指针,若在目录尾或出错则为 N U L L
void rewinddir(DIR * d p) ;
int closedir(DIR * d p) ;
返回:若成功则为0,若出错则为- 1

  1. struct __dirstream   
  2.    {   
  3.     void *__fd;    
  4.     char *__data;    
  5.     int __entry_data;    
  6.     char *__ptr;    
  7.     int __entry_ptr;    
  8.     size_t __allocation;    
  9.     size_t __size;    
  10.     __libc_lock_define (, __lock)    
  11.    };   
  12.   
  13. typedef struct __dirstream DIR;  


  1. struct dirent   
  2. {   
  3.   long d_ino; /* inode number 索引节点号 */  
  4.      
  5.     off_t d_off; /* offset to this dirent 在目录文件中的偏移 */  
  6.      
  7.     unsigned short d_reclen; /* length of this d_name 文件名长 */  
  8.      
  9.     unsigned char d_type; /* the type of d_name 文件类型 */  
  10.      
  11.     char d_name [NAME_MAX+1]; /* file name (null-terminated) 文件名,最长255字符 */  
  12. }  


dup和d u p 2函数
下面两个函数都可用来复制一个现存的文件描述符:
#include <unistd.h>
int dup(int  f i l e d e s) ;
int dup2(int  f i l e d e s, int f i l e d e s 2) ;
两函数的返回:若成功为新的文件描述符,若出错为- 1


虚拟文件系统vfs