【UNIX环境高级编程】文件和目录详解
stat、fstat和lstat函数
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
char path[64];
snprintf(path, sizeof(path), "/home/xinyu/projects/demo/test.txt");
int stat(path, stat);
int fd = open(path, O_RDWR | O_APPEND);
int fstat(fd, stat);
int lstat(path, stat);
一旦给出pathname, stat函数就返回与此命名文件有关的信息结构。
fstat函数获取已在描述符fd上打开文件的有关信息。
lstat函数类似于stat,但是当命名的文件是一个符号链接时,lstat返回该符号链接的有关信息,而不是由该符号链接引用文件的信息。
第二个参数statbuf 是一个指针,指向一个我们必须提供的结构。#include <sys/stat.h>
在这个包下包含stat结构体struct stat
使用stat函数最多的可能是ls -l命令,用其可以获得有关一个文件的所有信息。
如果成功,则返回0。发生错误时,返回-1。
文件类型
普通文件
这是最常见的文件类型,这种文件包含了某种形式的数据。
目录文件
这种文件包含了其他文件的名字以及指向与这些文件有关信息的指针,对一个目录文件具有读取权限的任意进程都可以读该目录的内容,但只有内核可以直接写目录文件。
块特殊文件
这种文件类型提供对设备带缓冲的访问,每次访问以固定长度为单位进行。
字符特殊文件
这种文件类型提供对设备不带缓冲的访问,每次访问长度可变,系统中的所有设备要么是字符特殊文件,要么是块特殊文件。
FIFO
这种类型文件用于进程间通信,有时也将其称为命名管道。
套接字(socket)
这种文件类型用于进程间的网络通信,套接字也可用于在一台宿主机上进程之间的非网络通信。
符号链接
这种文件类型指向另一个文件。
代码示例
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
void main(int argc, char *argv[])
{
int i;
struct stat buf;
char *ptr;
for (i = 1; i < argc; i++)
{
printf("%s:\n", argv[i]);
if (lstat(argv[i], &buf) < 0)
{
printf("lstat error");
}
if (S_ISREG(buf.st_mode))
{
ptr = "regular";
}
else if (S_ISDIR(buf.st_mode))
{
ptr = "directory";
}
else if (S_ISCHR(buf.st_mode))
{
ptr = "character special";
}
else if (S_ISBLK(buf.st_mode))
{
ptr = "block special";
}
else if (S_ISFIFO(buf.st_mode))
{
ptr = "fifo";
}
else if (S_ISLNK(buf.st_mode))
{
ptr = "symbolic link";
}
else if (S_ISSOCK(buf.st_mode))
{
ptr = "socket";
}
else
{
ptr = "** unknown mode **";
}
printf("%s:\n", ptr);
}
return;
}
access函数
access函数是按实际用户ID和实际组ID进行访问权限测试的。成功时返回0,失败时返回-1。
#include <unistd.h>
int access(const char *pathname, int mode);
mode的常量
- R_OK:测试读权限;
- W_OK:测试写权限;
- X_OK:测试执行权限;
- F_OK:测试文件是否存在;
代码示例
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
void main(int argc, char *argv[])
{
if (argc != 2)
{
printf("");
}
if (access(argv[1], R_OK) < 0)
{
printf("read error\n");
}
else
{
printf("read access\n");
}
if (access(argv[1], W_OK) < 0)
{
printf("write error\n");
}
else
{
printf("write access\n");
}
if (open(argv[1], O_RDONLY) < 0)
{
printf("open error\n");
}
else
{
printf("open success\n");
}
return;
}
umask函数
umask函数为进程设置文件模式创建屏蔽字,并返回以前的值。
#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t mask);
umask(S_IRUSR | S_IRGRP | S_IROTH);
mask常量
- S_IRUSR:用户读;
- S_IWUSR:用户写;
- S_IXUSR:用户执行;
- S_IRGRP:组读;
- S_IWGRP:组写;
- S_IXGRP:组执行;
- S_IROTH:其他读;
- S_IWOTH:其他写;
- S_IXOTH:其他执行;
UNIX系统的大多数用户从不处理他们的umask值,通常在登录时,由shell的启动文件设置一次,然后从不改变。尽管如此,当编写创建新文件的程序时,如果我们想确保指定的访问权限位已经激活,那么必须在进程运行时修改umask值。如果想确保任何用户都能读文件,则应将umask设置为0.
chmod和fchmod函数
这两个函数更改现有文件的访问权限。
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
chmod("text.txt", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
chmod函数在指定的文件上进行操作。
fchmod函数则对已打开的文件进行操作。
为了改变一个文件的权限位,进程的有效用户ID必须等于文件的所有者ID,或者该进程必须具有超级用户权限。
mode常量
-
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_IRWXG:其他读、写、执行
- S_IROTH:其他读
- S_IWOTH:其他写
- S_IXOTH:其他执行
chown、fchown和lchown函数
这几个函数用于更改文件的用户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 lchown(const char *pathname, uid_t owner, gid_t group);
除了所引用的文件是符号链接以外,这三个函数的操作相似,在符号链接的情况下,lchown更改符号链接本身的所有者,而不是该符号链接所指向的文件。
若两个参数owner和group中的任意一个是-1,则对应的ID不变。
文件截短
我们需要在文件尾端处截去一些数据以缩短文件,将一个文件清空为0是一个特例,在打开文件时使用O_TRUNC标志可以做到这一点。
以下两个函数将现有的文件长度截短为length字节,如果该文件以前长度大于length,则超过length以外的数据就不能再访问了。如果以前长度短于length,则其效果与系统有关。若是UNIX系统实现扩展了该文件,则在以前的文件尾端和新的文件尾端之间的数据读作0(也就是可能在文件中创建了一个空洞文件)。
#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
link、unlink、remove、rename函数
link
创建一个指向现有文件的链接的方法是使用link函数。此函数创建一个新目录项newpath,它引用现有的oldpath,若newpath已存在,则返回出错。
#include <unistd.h>
int link(const char *oldpath, const char *newpath);
为了删除一个现有的目录项,可以调用unlink函数。
此函数删除目录项,并将由pathname所引用文件的链接计数减1,如果还有指向该文件的其他链接,则仍可以通过其他链接访问该文件的数据,如果出错,不会对该文件做任何更改。
只有当链接计数达到0时,该文件的内容才可被删除。只要有进程打开了该文件,其内容也不能删除。关闭一个文件时,内核首先检查打开该文件的进程数,如果该数达到0,然后内核检查其链接数,如果这个数也是0,那么久删除该文件的内容。
成功返回0,失败返回-1。
unlink
#include <unistd.h>
int unlink(const char *pathname);
unlink的这种性质经常被程序用来确保即使是在该程序崩溃时,它所创建的临时文件也不会遗留下来,进程用open或creat创建一个文件,然后立即调用unlink函数,因为该文件仍旧是打开的,所以不会将其内容删除,只有当进程关闭该文件或终止时,该文件的内容才会被删除。
如果pathname是符号链接,那么unlink删除的是符号链接,而不会删除由该链接所引用的文件,给出符号链接名情况下,没有一个函数能删除由该链接所引用的文件。
成功返回0,失败返回-1。
remove
解除对一个文件或目录的链接,对于文件remove与unlink功能相同,对于目录,remove与rmdir功能相同。成功返回0,失败返回-1。
#include <stdio.h>
int remove(const char *pathname);
rename
文件或目录用rename函数更名。成功返回0,失败返回-1。
#include <stdio.h>
int rename(const char *oldpath, const char *newpath);
如果oldpath指的是一个文件而不是目录,那么为该文件或符号链接更名,在这种情况下,如果newpath已存在,则它不能引用一个目录。如果newpath已存在,而不是一个目录,则先将该目录项删除然后将oldpath更名为newpath。
如果oldpath指的是一个目录,那么为该目录更名,如果newpath已存在,则它必须引用一个目录,而且该目录应当是空目录。如果如果newpath存在并且是空目录,则先将其删除,然后将oldpath更名为newpath。
如果oldpath或newpath引用符号链接,则处理的是符号链接本身,而不是它所引用的文件。
符号链接
符号链接是指向一个文件的间接链接。对符号链接以及它指向何种对象并无任何文件系统限制,任何用户都可创建指向目录的符号链接,符号链接一般用于将一个文件或目录结构移到系统中的另一个位置。
symlink和readlink函数
symlink
symlink函数创建一个符号链接。
该函数创建了一个指向target的新目录项linkpath,在创建此符号链接时,并不要求target已存在,并且target和linkpath不需要位于同一文件系统中。
#include <unistd.h>
int symlink(const char *target, const char *linkpath);
readlink
open函数跟随符号链接,所以需要有一种方法打开该链接本身,并读该链接中的名字,readlink函数提供了这种功能。
若成功返回读到的字节数,失败返回-1。
#include <unistd.h>
ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
utime函数
一个文件的访问和修改时间可以用utime函数更改。
#include <sys/types.h>
#include <utime.h>
int utime(const char *filename, const struct utimbuf *times);
#include <sys/time.h>
int utimes(const char *filename, const struct timeval times[2]);
mkdir和rmdir函数
mkdir
用mkdir函数创建空目录。
#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);
rmdir
用rmdir函数删除空目录。
#include <unistd.h>
int rmdir(const char *pathname);
如果调用此函数使目录的链接计数成为0,并且也没有其他进程打开此目录,则释放由此目录占用的空间,如果在链接计数达到0时,有一个或几个进程打开了此目录,则在此函数返回前删除最后一个链接及.和…项。另外在此目录中不能再创建新文件。但是在最后一个进程关闭它之前并不释放此目录。
chdir、fchdir和getcwd函数
每一个进程都有一个当前工作目录,此目录是搜索所有相对路径名的起点。
进程通过调用chdir或fchdir函数可以更改当前工作目录。在这两个函数中,分别用path或打开文件描述符来指定新的当前工作目录。
#include <unistd.h>
int chdir(const char *path);
int fchdir(int fd);
chdir("/tmp");
getcwd函数可以获取到当前工作目录完整的绝对路径名。若成功返回buf,失败返回NULL。
#include <unistd.h>
char *getcwd(char *buf, size_t size);
第一个参数:缓冲地址buf。
第二个参数:缓冲的长度size。