apue:文件和目录(2)

link、unlink、remove和rename函数

    任何一个文件可以有多个目录项指向其i节点,创建一个向现存文件连接的方法是使用link函数。

#include 
int link(const char* existingpath, const char* newpath);
/*返回:若成功则为0,若出错则为-1*/

    此函数创建一个新目录项newpath,它引用现存文件existingpath,如若newpath已经存在,则返回出错。只有超级用户进程可以创建指向一个目录的新连接。创建新目录项以及增加连接计数应当是个原子操作。
    为了删除一个现存的目录项,可以调用unlink函数。

#include 
int unlink(const char* pathname);
/*返回:若成功则为0,若出错则为-1*/

    此函数删除目录项,并将由pathname所引用的文件的连接计数减1,如果该文件还有其他连接,则仍可通过其他连接存取该文件的数据。如果出错,则不对该文件作任何更改。为了解除对文件的连接,必须对包含该目录项的目录具有写和执行许可权。只有当连接计数达到0时,该文件的内容才可被删除。另一个条件也阻止删除文件的内容—只要有进程打开了该文件,其内容也不能删除。关闭一个文件时,内核首先检查使该文件打开的进程计数。如果该计数达到0,然后内核检查其连接计数,如果这也是0,那么就删除该文件的内容。如果pathname是符号连接,那么unlink涉及的是符号连接而不是由该连接所引用的文件。
    也可以用remove函数解除对一个文件或目录的连接。

#include 
int remove(const char* pathname);
/*返回:若成功则为0,若出错则为-1*/

    文件或目录用rename函数更名。

#include 
int rename(const char* oldname, const char* newname);
/*返回:若成功则为0,若出错则为-1*/

     (1)如果oldname说明一个文件而不是目录,那么为该文件更名。如果newname已存在,则它不能引用一个目录。如果newname已存在,而且不是一个目录,则先将该目录项删除然后将oldname更名为newname。
     (2)如若oldname说明一个目录,那么为该目录更名。如果newname已存在,则它必须引用一个目录,而且该目录应当是空目录。如果newname存在(而且是一个空目录),则先将其删除,然后将oldname更名为newname。另外,当为一个目录更名时,newname不能包含oldname作为其路径前缀。
     (3)如果oldname和newname引用同一文件,则函数不做任何更改而成功返回。
如若newname已经存在,则调用进程需要对其有写许可权(如同删除情况一样)。另外,调用进程将删除oldname目录项,并可能要创建newname目录项,所以它需要对包含oldname及包含newname的目录具有写和执行许可权。

符号链接

    符号连接是对一个文件的间接指针,硬连接直接指向文件的i节点。任何用户都可创建指向目录的符号连接,符号连接一般用于将一个文件或整个目录结构移到系统中其他某个位置。

symlink和readlink函数

    symlink函数创建一个符号连接。

#include 
int symlink(const char* actualpath, const char* sympath);
/*返回:若成功则为0,若出错则为-1*/

    该函数创建了一个指向actualpath的新目录项sympath,在创建此符号连接时,不要求actualpath已经存在,actualpath和sympath并不需要位于同一文件系统中。
    readlink函数打开该连接本身,并读该连接中的名字。

#include 
int readlink(const char* pathname, char* buf, int bufsize);
/*返回:若成功则为读的字节数,若出错则为-1*/

    此函数组合了open,read和close的所有操作,如果此函数成功,则它返回读入buf的字节数,在buf中返回的符号连接的内容不以null字符终止。

文件的时间

    与文件相关的三个时间值如下:

字段 说明
st_atime 文件数据的最后存取时间
st_mtime 文件数据的最后修改时间
st_ctime i节点状态的最后更改时间

utime函数

    一个文件的存取和修改时间可以用utime函数更改。

#include 
#include 
int utime(const char* pathname, const struct utimbuf* times);
/*返回:若成功则为0,若出错则为-1*/

    其中用到的时间数据结构是

struct utimbuf {
    time_t actime;   /*accesstime*/
    time_t modtime;  /*modificationtime*/
};

    此结构中的两个时间值是日历时间,这是自1970年1月1日,00:00:00以来国际标准时间所经过的秒数。此函数的操作以及执行它所要求的优先权取决于times参数是否是NULL。
     (1)如果times是一个空指针,则存取时间和修改时间两者都设置为当前时间。为了执行此操作必须满足下列两条件之一:(a)进程的有效用户ID必须等于该文件的所有者ID,(b)进程对该文件必须具有写许可权。
     (2)如果times是非空指针,则存取时间和修改时间被设置为times所指向的结构中的值。此时,进程的有效用户ID必须等于该文件的所有者ID,或者进程必须是一个超级用户进程。对文件只具有写许可权是不够的。

mkdir和rmdir函数

    用mkdir函数创建目录。

#include 
#include 
int mkdir(const char* pathname, mode_t mode);
/*返回:若成功则为0,若出错则为-1*/

    此函数创建一个新的空目录。.和..目录项是自动创建的。所指定的文件存取许可权mode由进程的文件方式创建屏蔽字修改。
    用rmdir函数可以删除一个空目录。

#include 
int rmdir(const char* pathname);
/*返回:若成功则为0,若出错则为–1*/

    如果此调用使目录的连接计数成为0,并且也没有其他进程打开此目录,则释放由此目录占用的空间。如果在连接计数达到0时,有一个或几个进程打开了此目录,则在此函数返回前删除最后一个连接及.和..项。另外,在此目录中不能再创建新文件。但是在最后一个进程关闭它之前并不释放此目录。

读目录

    对某个目录具有存取许可权的任一用户都可读该目录,但是只有内核才能写目录。一个目录的写许可权位和执行许可权位决定了在该目录中能否创建新文件以及删除文件,它们并不表示能否写目录本身。存在一套关于读目录的函数。

#include 
#include 
DIR* opendir(const char* pathname);
/*返回:若成功则为指针,若出错则为NULL*/
struct dirent* readdir(DIR* dp);
/*返回:若成功则为指针,若在目录尾或出错则为NULL*/
void rewinddir(DIR* dp);
int closedir(DIR* dp);
/*返回:若成功则为0,若出错则为-1*/

    DIR结构是一个内部结构,它由这四个函数用来保存正被读的目录的有关信息。由opendir返回的指向DIR结构的指针由另外三个函数使用。opendir执行初始化操作,使第一个readdir读目录中的第一个目录项。目录中各目录项的顺序与实现有关。它们通常并不按字母顺序排列。定义在头文件dirent.h中的dirent结构与实现有关。

struct dirent{
    ino_t d_ino;  /*i-nodenumber*/
    char d_name[NAME_MAX+1];  /*null-terminatedfilename*/
}

chdir、fchdir和getcwd函数

    每个进程都有一个当前工作目录,此目录是搜索所有相对路径名的起点,当前工作目录是进程的一个属性,起始目录则是登录名的一个属性。进程调用chdir或fchdir函数可以更改当前工作目录。

#include 
int chdir(const char* pathname);
int fchdir(int filedes);
/*两个函数的返回:若成功则为0,若出错则为-1*/

    在这两个函数中,可以分别用pathname或打开文件描述符来指定新的当前工作目录。
    函数getcwd从当前工作目录开始,找到其上一级的目录,然后读其目录项,直到该目录项中的i节点编号数与工作目录i节点编号数相同,这样地就找到了其对应的文件。

#include 
char* getcwd(char* buf, size_t size);
/*返回:若成功则为buf,若出错则为NULL*/

    向此函数传递两个参数,一个是缓存地址buf,另一个是缓存的长度size。该缓存必须有足够的长度以容纳绝对路径名再加上一个null终止字符,否则返回出错。

特殊设备文件

    st_dev和st_rdev这两个字段经常引起混淆,有关规则很简单:
- 每个文件系统都由其主、次设备号而为人所知。设备号所用的数据类型是基本系统数据类型dev_t。
- 我们通常可以使用两个大多数实现都定义的宏:major和minor来存取主、次设备号。这就意味着我们无需关心这两个数是如何存放在dev_t对象中的。
- 系统中每个文件名的st_dev值是文件系统的设备号,该文件系统包含了该文件名和其对应的i节点。
- 只有字符特殊文件和块特殊文件才有st_rdev值。此值包含该实际设备的设备号。

sync和fsync函数

    传统的UNIX实现在内核中设有缓冲存储器,大多数磁盘I/O都通过缓存进行。当将数据写到文件上时,通常该数据先由内核复制到缓存中,如果该缓存尚未写满,则并不将其排入输出队列,而是等待其写满或者当内核需要重用该缓存以便存放其他磁盘块数据时,再将该缓存排入输出队列,然后待其到达队首时,才进行实际的I/O操作。这种输出方式被称之为延迟写(delayed write)。延迟写减少了磁盘读写次数,但是却降低了文件内容的更新速度,使得欲写到文件中的数据在一段时间内并没有写到磁盘上。当系统发生故障时,这种延迟可能造成文件更新内容的丢失。为了保证磁盘上实际文件系统与缓存中内容的一致性,UNIX系统提供了sync和fsync两个系统调用函数。

#include 
void sync(void);
int fsync(int filedes);
/*返回:若成功则为0,若出错则为-1*/

    sync只是将所有修改过的块的缓存排入写队列,然后就返回,它并不等待实际I/O操作结束。函数fsync只引用单个文件(由文件描述符filedes指定),它等待I/O结束,然后返回。fsync可用于数据库这样的应用程序,它确保修改过的块立即写到磁盘上。

你可能感兴趣的:(UNIX编程,UNIX编程琐事)