以下内容全部摘自UNIX环境高级编程(第3版)
用名字打开任意类型的文件时,对该名字中包含的每一个目录,包括它可能隐含的当前工作目录都应具有执行权限
例如,为了打开文件/usr/include/stdio.h,需要对目录/,/usr和/usr/include具有执行权限。
对目录的读权限允许我们读目录,获得在该目录中所有文件名的列表。
创建文件时,用户ID设置为进程的有效用户ID。组ID有两种方式设置:
(1)新文件的组ID可以是进程的有效组ID
(2)新文件的组ID可以是它所在目录的组ID
函数access和faccessat
#include <unistd.h>
int access(const char *pathname, int mode);
int faccessat(int fd, const char *pathname, int mode, int flag);
两个函数的返回值:若成功,返回0;若出错,返回-1
测试文件是否存在,mode为F_OK
测试读权限,mode为R_OK
测试写权限,mode为W_OK
测试执行权限,mode为X_OK
faccessat函数与access函数在下面两种情况下相同的:一种是pathname参数为绝对路径,
另一种是fd参数取值为AT_FDCWD
而pathname参数为相对路径。否则,faccessat
计算相对于打开目录(由fd参数指向)的pathname.
flag参数可以用于改变faccessat
的行为,如果flag设置为AT_EACCESS
,访问检查用的是调用进程的有效用户ID和有效组ID,而不是实际用户ID和实际组ID。
函数chmod、fchmod和fchmodat
#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);
三个函数的返回值:若成功,返回0;若出错,返回-1
fchmodat函数与chmod函数在下面两种情况下相同的:一种是pathname参数为绝对路径,
另一种是fd参数取值为AT_FDCWD
而pathname参数为相对路径。否则,fchmodat
计算相对于打开目录(由fd参数指向)的pathname.
flag参数可以用于改变fchmodat
的行为,当设置了AT_SYMLINK_NOFOLLOW
标志时,fchmodat
并不会跟随符号链接。
S_ISUID
: 执行时设置用户ID
S_ISGID
: 执行时设置组ID
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
: 其他执行
函数chown、fchown、fchownat和lchown
下面几个chown
函数可用于更改文件的用户ID和组ID。如果两个参数owner或group中的任意一个是-1,则对应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);
四个函数的返回值:若成功,返回0;若出错,返回-1
在符号链接情况下,lchown
和fchownat
(flag
设置了AT_SYMLINK_NOFOLLOW
标志)更改符号链接本身的所有者,而不是该符号链接所指向文件 的所有者。
fchownat
函数与chown
或者lchown
函数在下面两种情况下相同的:一种是pathname参数为绝对路径,
另一种是fd参数取值为AT_FDCWD
而pathname参数为相对路径。在这两种情况下,如果flag
参数中设置了AT_SYMLINK_NOFOLLOW
标志时,fchownat
与lchown
行为相同。
如果flag
参数清除了AT_SYMLINK_NOFOLLOW
标志,则fchownat
与chown
行为相同。
如果fd参数设置为打开目录的文件描述符,并且pathname参数是一个相对路径名,fchownat
函数计算相对于打开目录的pathname。
文件长度
stat
结构成员st_size
表示以字节为单位的文件的长度。此字段只对普通文件、目录文件和符号链接有意义。
FreeBSD8.0、Mac OS X 10.6.8和Solaris10对管道也定义了文件长度,它表示可从该管道中读到的字节数。
- 对于普通文件,其文件长度可以是0,在开始读这种文件时,将得到文件结束(end-of-file)指示。
- 对于目录,文件长度通常是一个数(如16或512)的整数倍。
- 对于符号链接,文件长度是文件名中的实际字节数。例如下面例子中,文件长度7就是路径名usr/lib的长度:
lrwxrwxrwx 1 root 7 Sep 25 07:14 lib -> usr/lib
文件截断
#include <unistd.h>
#include <sys/types.h>
int truncate(const char *pathname, off_t length);
int ftruncate(int fd, off_t length);
两个函数的返回值:若成功,返回0;若出错,返回-1
这两个函数将一个现有文件长度截断为length。如果该文件以前的长度大于length,则超过length以外的长度就不再能访问。
如果以前长度小于length,文件长度将增加,在以前的文件尾端和新的文件尾端之间的数据将读作0(也就是可能在文件中创建了一个空洞)。
文件系统
任何一个叶目录(不包含任何其他目录的目录)的链接计数总是2,数值2来自于命名该目录(testdir)的目录项以及在该目录中.项。
编号为1267的i节点,其类型字段表示它是ige目录,链接计数大于或等于3.它大于或等于3的原因是,至少有3个目录项指向它:
- 一个是命名它的目录项
- 第二个是在该目录中的. 项
- 第三个是在其子目录testdir中.. 项。 注意,在父目录中的每一个子目录都使该父目录的链接数增加1。
函数link、linkat、unlink、unlinkat和remove
hard link!!!!
任何一个文件都可以有多个目录项指向其i节点。创建一个指向现有文件的链接的方法是使用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);
两个函数的返回值:若成功,返回0;若出错,返回-1
这两个函数创建一个新目录项newpath,它引用现有文件existingpath。如果newpath已经存在,则返回出错。
只创建newpath中的最后一个分量,路径中的其他部分应当已经存在。
对于linkat
函数,现有文件是通过efd和existingpath参数指定的,新的路径名是通过nfd和newpath参数指定的。
默认情况下,如果两个路径名中的任一个是相对路径,那么它需要通过相对于对应的文件描述符进行计算。
如果两个文件描述符中的任一个设置为AT_FDCWD,那么相对应的路径名(如果它是相对路径)就通过对于当前目录进行计算。
如果任一路径名是绝对路径,相应的文件描述符参数就会被忽略。
当现有文件是符号链接时,由flag参数来控制linkat
函数是创建指向现有文件符号链接的链接还是创建指向现有符号链接所指向的文件的链接。
如果在flag参数中设置了AT_SYMLINK_FOLLOW
标志,就创建指向符号链接目标的链接。
如果这个标志被清除了,则创建一个指向符号链接本身的链接。
为了删除一个现有的目录项,可以调用unlink
函数
#include <unistd.h>
int unlink(const char *pathname);
int unlinkat(int fd, const char *pathname, int flag);
两个函数的返回值:若成功,返回0;若出错,返回-1
这两个函数删除目录项,并将由pathname所引用文件的链接计数减1。如果对该文件还有其他链接,
则仍可通过其他链接访问该文件的数据。如果出错,则不对该文件做任何更改。
为了解除对文件的链接,必须对包含该目录项的目录具有写和执行权限。
如果对该目录设置了粘着位,则对该目录必须具有写权限,并且具备下面三个条件之一:
* 拥有该文件;
* 拥有该目录;
* 具有超级用户权限。
只有当链接计数达到0时,该文件的内容才可被删除。另一个条件也会阻止删除文件的内容——只要有进程打开了该文件,其内容也不能删除。
关闭一个文件时,内核首先检查打开文件的进程个数;如果这个计数达到0,内核再去检查其链接计数;
如果计数也是0,那么就删除该文件的内容。
如果pathname参数是相对路径名,那么unlinkat
函数计算相对于由fd文件描述符参数代表的路径名。
如果fd参数设置为AT_FDCWD
,那么通过相对于调用进程的当前工作目录来计算路径名。
如果pathname参数是绝对路径名,那么fd参数被忽略。
flag参数给出了一种方法,是调用进程可以改变unlinkat
函数的默认行为。
当AT_REMOVEDIR
标志被设置时,unlinkat
函数可以类似于rmdir
一样删除目录。如果这个标志被清除,unlinkat
与unlink
执行同样的操作。
unlink
的特性经常被程序用来确保即使在程序崩溃时,他所创建的临时文件也不会遗留下来。
进程用open
或creat
创建一个文件,然后立即调用unlink
,因为该文件仍旧是打开的,所以不会将其内容删除。
只有当进程关闭该文件或终止时(在这种情况下,内核关闭该进程所打开的全部文件),该文件的内容才被删除。
如果pathname
是符号链接,那么unlink
删除符号链接,而不是删除由该链接所引用的文件。
给出符号链接名的情况下,没有一个函数能删除由该链接所引用的文件。
remove
函数
我们也可以用remove
函数解除对一个文件或者目录的链接。对于文件,remove
的功能与unlink
相同。
对于目录,remove
的功能与rmdir
相同。
#include <stdio.h>
int remove(const char* pathname);
返回值:若成功,返回0;若出错,返回-1
函数rename
和renameat
文件或目录可以用rename
函数或者renameat
函数进行重命名。
#include <stdio.h>
int rename(const char *oldname, const char *newname);
int renameat(int oldfd, const char *oldname, int newfd, const char *new name);
两个函数的返回值:若成功,返回0;若出粗,返回-1;
根据oldname是指文件、目录还是符号链接,有几种情况需要加以说明。我们也必须说明如果newname已经存在时将会发生什么。
- 如果oldname指的是一个文件而不是目录,那么为该文件或符号链接重命名。
在这种情况下,如果newname已存在,则它不能引用一个目录。
如果newname已存在,而且不是一个目录,则先将该目录项删除然后将oldname重命名为newname。
对包含oldname的目录以及包含newname的目录,调用进程必须具有写权限,因为将更改这两个目录。 - 如若oldname指的是一个目录,那么为该目录重命名。如果newname已存在,则它必须引用一个目录,
而且该目录应当是空目录(空目录指的是该目录中只有. 和 .. 项)。如果newname存在(而且是一个空目录),
则先将其删除,然后将oldname重命名为newname。另外,当为一个目录重命名时,newname不能包含oldname作为其路径前缀。
例如,不能将/usr/foo
重命名为重命名为/usr/foo/testdir
,因为旧名字(/usr/foo
)是新名字的路径前缀,因而不能将其删除。 - 如若oldname或newname引用符号链接,则处理的是符号链接本身,而不是他所引用的文件。
- 不能对. 和 .. 重命名。更确切地说,. 和 .. 都不能出现在oldname和newname的最后部分。
- 作为一个特例,如果oldname和newname引用同一个文件,则函数不做任何更改而成功返回。
如果newname已经存在,则调用进程对它需要有写权限(如同删除情况一样)。另外,调用进程将删除oldname目录项,
并可能要创建newname目录项,素以它需要对包含oldname及包含newname的目录具有写和执行权限。
除了当oldname或newname指向相对路径名时,其他情况下renameat
函数与rename
函数功能相同。
如果oldname参数指定了相对路径,就相对于oldfd参数引用的目录来计算oldname。
类似的,如果newname指定了相对路径,就像对于newfd引用的目录来计算newname。
oldfd或newfd参数(或两者)都能设置为AT_FDCWD
,此时相对于当前目录来计算相应的路径名。
符号链接
符号链接是对一个文件的间接指针,它与上一节所述的硬链接有所不同, 硬链接直接指向文件的i结点。
引入符号链接的原因是为了避开硬链接的一些限制。
* 硬链接通常要求链接和文件位于同一个文件系统中。
* 只有超级用户才能创建指向目录的硬链接(在底层文件系统支持的情况下)。
对符号链接以及指向何种对象并无任何文件系统限制,任何用户都可以创建指向目录的符号链接。
符号链接一般用于将一个文件或整个目录结构一道系统中另一个位置。
创建和读取符号链接
可以用symlink
或symlinkat
函数创建一个符号链接。
#include <unistd.h>
int symlink(const char *actualpath, const char *sympath);
int symlinkat(const char *actualpath, int fd, const char *sympath);
两个函数的返回值:若成功,返回0;若出错,返回-1
函数创建了一个指向actualpath的新目录项sympath。在创建此符号链接时,并不要求actualpath已经存在。
并且,actualpath和sympath并不需要位于同一个文件系统中。
symlinkat
函数与symlink
函数类似,但symlink
参数根据相对于打开文件描述符引用的目录(由fd参数指定)进行计算。
如果sympath参数指定的是绝对路径或者fd参数设置了AT_FDCWD
值,那么symlinkat
就等同于symlink
函数。
因为open
函数跟随符号链接,所以需要有一种方法打开该链接本身,并读该链接中的名字。
readlink
和readlinkat
函数提供了这种功能。
#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 buf);
两个函数的返回值:若成功,返回读取的字节数;若出错,返回-1
两个函数组合了open
、read
和close
的所有操作。如果函数成功执行,则返回读入buf的字节数。
在buf中返回的符号链接的内容不以null字节终止。
当pathname参数指定的是绝对路径名或者fd参数的值为AT_FDCWD
,readlinkat
函数的行为与readlink
相同。
但是,如果fd参数是一个打开目录的有效文件描述符并且pathname参数是相对路径名,则readlinkat
计算相对于有fd代表的打开目录的路径名。
文件的时间
字段 说明 例子 ls(1)选项
st_atim
文件数据的最后访问时间 read
-u
st_mtim
文件数据的最后修改时间 write
默认
st_ctim
i节点状态的最后修改时间 chmod
、chown
-c
a 访问
m(modify) 修改
c i节点修改
函数futimens
、utimensat
和utimes
一个文件的访问和修改时间可以用以下几个函数更改。futimens
和utimensat
函数可以指定纳秒级精度的时间戳。
用到的数据结构是与stat
函数族相同的timespec
结构。
#include <sys/stat.h>
#include <fcntl.h> /* Definition of AT_* constants */
int futimens(int fd, const struct timespec times[2]);
int utimensat(int fd, const char *path, const struct timespec times[2], int flag);
两个函数返回值:若成功,返回0;若出错,返回-1
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
这两个函数的times数组参数的第一个元素包含访问时间,第二个元素包含修改时间。这两个时间值是日历时间,
如1.10节所述,这是自特定时间(1970年1月1日00:00:00)以来所经过的秒数。不足秒的部分用纳秒表示。
时间戳可以按下列4种方式之一进行指定。
1. 如果times参数是一个空指针,则访问时间和修改时间两者都设定为当前时间。
2. 如果times参数指向两个timespec
结构的数组,任一数组元素的tv_nsec
字段的值为UTIME_NOW
,
相应的时间戳就设置为当前时间,忽略相应的tv_sec
字段。
3. 如果times参数指向两个timespec
结构的数组,任一数组元素的tv_nsec
字段的值为UTIME_OMIT
,
相应的时间戳保持不变,忽略相应的tv_sec
字段。
4. 如果times参数指向两个timespec
结构的数组,且tv_nsec
字段的值为既不是UTIME_NOW
也不是UTIME_OMIT
,
在这种情况下,相应的时间戳设置为相应的tv_sec
和tv_nsec
字段的值。
执行这些函数所要求的优先级取决于times参数的值
* 如果times是一个空指针,或者任一tv_nsec
字段设为UTIME_NOW
,则进程的有效用户ID
必须等于该文件的所有者ID
;
进程对该文件必须具有写权限或者进程是一个超级用户进程。
* 如果times是非空指针,并且任一tv_nsec
字段的值既不是UTIME_NOW
也不是UTIME_OMIT
,
则进程的有效用户ID
必须等于该文件的所有者ID
,或者进程必须是一个超级用户进程。对文件只具有写权限是不够的。
* 如果times是非空指针,并且两个tv_nsec
字段的值都为UTIME_OMIT
,就不执行任何的权限检查。
futimens
函数需要打开文件来更改它的时间,utimensat
函数提供了一种使用文件名更改文件时间的方法。
pathname参数是相对于fd参数进行计算的,fd要么是打开目录的文件描述符,要么设置为特殊值AT_FDCWD
(强制
通过相对于调用进程的当前目录计算pathname)。如果pathname指定了绝对路径,那么fd参数被忽略。
utimensat
的flag参数可用于进一步修改默认行为。如果设置了AT_ SYMLINK_NOFOLLOW
标志,
则符号链接本身的时间就会被修改(如果路径名指向符号链接)。默认的行为是跟随符号链接,并修改符号链接指向的文件的时间。
utimes
函数
#include <sys/time.h>
int utimes(const char *pathname, const struct timeval times[2]);
函数返回值:若成功,返回0;若出错,返回-1
utimes
函数对路径名进行操作。times参数是指向包含两个时间戳(访问时间和修改时间)元素的数组的指针,两个时间戳使用秒和微妙表示的。
struct timeval {
time_t tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
注意,我们不能对状态更改时间st_ctim
(i节点最近被修改时间)指定一个值,因为调用utimes
函数时,此字段会被自动更新。
函数mkdir
、mkdirat
和rmdir
用mkdir
和mkdirat
函数创建目录,用rmdir
函数删除目录。
#include <sys/stat.h>
int mkdir(const char *pathname, mode_t mode);
int mkdirat(int fd, const char *pathname, mode_t mode);
两个函数返回值,若成功,返回0;若出错,返回-1
这两个函数创建一个新的空目录。其中, . 和 .. 目录项是自动创建的。所指定的文件访问权限mode
由进程的文件模式创建屏蔽字修改。
常见的错误是指定与文件相同的mode(只指定读、写权限)。但是,对于目录通常至少要设置一个执行权限位,以允许访问该目录中的文件名。
mkdirat
函数与mkdir
函数类似。当fd参数具有特殊值AT_FDCWD
或者pathname参数指定了绝对路径名时,
mkdirat
与mkdir
完全一样。否则,fd参数是一个打开目录,相对路径名根据此打开目录进行计算。
用rmdir
函数可以删除一个空目录。空目录是只包含 . 和 .. 这两项的目录。
#include <unistd.h>
int rmdir(const char *pathname);
返回值:若成功,返回0;若出错,返回-1
读目录
#include <dirent.h>
DIR *opendir (const char *pathname);
DIR *fdopendir (int fd);
两个函数返回值:若成功,返回指针;若出错,返回NULL
struct dirent *readdir(DIR *dp);
返回值:若成功,返回指针;若在目录尾或出错,返回NULL
void rewinddir (DIR *dp); /* reset directory stream */
int closedir (DIR *dp);
返回值:若成功,返回0;若出错,返回-1
long telldir (DIR *dp); /* return current location in directory stream */
返回值:与dp关联的目录中的当前位置
void seekdir (DIR *dp, long loc);
定义在头文件<dirent.h>
中的dirent
结构与实现有关。实现对此结构所做的定义至少包含下列两个成员:
ino_t d_ino; /* i-node number */
char d_name[]; /* null-terminated filename */
函数chdir
、fchdir
和getcwd
进程调用chdir
或fchdir
函数可以更改当前工作目录
#include <unistd.h>
int chdir (const char *pathname);
int fchdir (int fd);
两个函数的返回值:若成功,返回0;若出错,返回-1
用函数getcwd
得到当前工作目录完整的绝对路径名
#include <unistd.h>
char *getcwd(char *buf, size_t size);
返回值:若成功,返回buf,若出错,返回NULL
必须向此函数传递两个参数,一个是缓冲区地址buf,另一个是缓冲区的长度size(以字节为单位)。
该缓冲区必须有足够的长度以容纳绝对路径名再加上一个终止NULL字符,否则返回出错。