#include
int stat(const char *restrict pathname, struct stat *restrict buf);//给出pathname:返回文件有关的信息结构。
int fstat(int fd, struct stat *buf); //获得描述符fd上打开文件的有关信息。
int lstat(const char *restrict pathname, struct stat *restrict buf); //类似stat,当命名为符号链接时,返回符号链接有关信息,而不是符号链接引用的文件信息。
//降序遍历层次结构时,需使用此函数。
int fstatat(int fd, const char *restrict pathname, struct stat *restrict buf, int flag);//为一个相对于当前打开的路径名返回文件统计信息。
//flag:控制着是否跟随着一个符号链接。
//AT_SYMLINK_NOFOLLOW标志被设置时,fstatat不会跟随符号链接,而是返 回符号链接本身的信息。
//在默认情况下,返回的是符号链接所指向的实 际文件的信息。
//fd参数的值是AT_FDCWD,并且pathname参数是一个相对 路径名, fstatat会计算相对于当前目录的pathname参数。
//若pathname是一个绝 对路径,fd参数就会被忽略。
//两种情况下,根据flag的取值,fstatat的作用就 跟stat或lstat一样。
//buf:指针,指向提供的结构,函数来填充由buf指向的结构,结构如下:
struct stat {
mode_t st_mode;
ino_t st_ino;
dev_t st_dev;
dev_t st_rdev;
nlink_t st_nlink;
uid_t st_uid;
gid_t st_gid;
off_t st_size;
struct timespec st_atime;
struct timespec st_mtime;
struct timespec st_ctime;
blksize_t st_blksize;
blkcnt_t st_blocks;
};
//timespec结构类型按照秒和纳秒定义了时间,至少包括下面两个字段:
time_t tv_sec;
long tv_nsec;
nftw函数允许对整个目录子树进行行递归遍历,并为子树中的每个文件执行某些操作。
#include
int nftw(const char *dirpath,int (*func)(const char *pathname,const struct stat *statbud,int typeflag,struct FTW *ftwbuf),int nopenfd,int flags);
//dirpath:指定目录树
//nopenfd:使用文件描述符数量最大值。
//func:为每个文件自定义函数
flags特征值:
文件扩展属性(EA),以名称-值对形式将任意元数据与文件i节点关联起来。
EA可用于实现访问列表和文件能力。
EA命名格式namespece.name
,其中namespace
用来把EA从功能上划分截然不同的几大类,而name则用来在既定命名空间内唯一标识某个EA。
namespace使用的值有4个,用途如下:
userEA:将在文件权限检查的制约下由非特性级进程操控。获取userEA值,需要有读权限,改变序有权限。在ext2、ext3、ext4或Reiserfs文件系统上,将user EA与一文件关联,在装配底层文件系统时需带有user_xattr选项。EA可为任意字符串。
trusted EA:由用户进程“驱使”,操控trustd EA进程必须拥有特权。EA可为任意字符串。
system EA:供内核使用,将系统对象与一文件关联,目前仅支持访问控制列表。内核确认的名字才可使用。
security EA:存储范围于操作系统安全模块的文件安全标签;将执行文件与能力关联起来。
一个i节点可以拥有多个相关EA,其所从属的命名空间可以相同,也可不同。
在个命名空间内的EA各均自成一体。
EA实现方面的限制:
//设置文件地EA值
#include
int setxattr(const char *pathname,const char *name,const void *value,szie_t size,int flags);
int lsetxattr(const char *pathname,const char *name,const void *value,size_t size,int flags);
int fsetxattr(int fd,const char *name,const void *value,size_t size,int flags);
//name:空字符串结尾的字符串,定义了EA的,名称。
//value:指向缓冲区的指针,包含为EA定义的新值。
//size:指明缓冲区大小。
三者区别:
默认情况,若具有给定名称的EA不存在,上述系统调用会创建新的EA。若EA存在,则将替换EA值。
XATTR_CREATE
:给定名称EA存在,则失败。XATTR_REPLACE
:给定名称EA不存在,则失败。//创建user EA:
char *value;
value = "The past is not date.";
if(setxattr(pathname,"user.x",vlaue,strlen(value),0)==-1)
errExit("setxattr");
//获取EA值
#include
ssize_t getxattr(const char *pathname,const char *name,void *value,size_t size);
sszie_t lgetxattr(const char *pathname,const char *name,void *value,size_t size);
ssize_t fgetxattr(int fd,const char *name,void *value,size_t size);
//name:空字符结尾的字符串,标识欲取值的EA。返回的EA值保存于value所指向的缓冲区中。
//缓冲区大小调用者分配,大小size指定。
//调用成功,返回复制到value所指缓冲区中的字节数。
//不包含和size过小都会调用失败。
//size指定为0,将忽略value值,但系统调用仍将返回EA值的大小。
//删除EA
#include
int removexattr(const char *pathname,const char *name);
int lremovexattr(const char *pathname,const char *name);
int fremovexattr(int fd,const char *name);
//name:以空字符结尾的字符串,用于标识删除的EA,删除不存在的EA,调用失败,返回ENODATA。
//获取与文件相关联的所有EA的名称
#include
sszie_t listxattr(const char *pathname,char *list,size_t size);
ssize_t llistxattr(const char *pathname,char *list,size_t size);
ssize_t flistxattr(int fd,char *list,size_t size);
调用将EA的名称列表以一系列以空字符结尾的字符串形式置于list所指向的缓冲区中,缓冲区大小由size指定。成功,返回复制到list中的字节数。
size置为0,系统忽略list,并返回后续调用实际获取EA名称列表时所需的缓冲区大小。
获取与某文件相关联的EA名列表,只需对文件拥有"访问权限”,对文件本身则无需任何权限。
安全考虑,list中返回的EA名称可能不包含调用进程无权访问的属性名。
例:获取并显示命令行所列文件的所有EA名和EA值。
#include
#include
#define XATTR_SIZE 10000
static void usageError(char *progName)
{
fprintf(stderr, "Usage: %s [-x] file...\n", progName);
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[])
{
char list[XATTR_SIZE], value[XATTR_SIZE];
ssize_t listLen, valueLen;
int ns, j, k, opt;
Boolean hexDisplay;
hexDisplay = 0;
while ((opt = getopt(argc, argv, "x")) != -1) {
switch (opt) {
case 'x': hexDisplay = 1; break;
case '?': usageError(argv[0]);
}
}
if (optind >= argc)
usageError(argv[0]);
for (j = optind; j < argc; j++) {
listLen = listxattr(argv[j], list, XATTR_SIZE);
if (listLen == -1)
errExit("listxattr");
printf("%s:\n", argv[j]);
/* Loop through all EA names, displaying name + value */
for (ns = 0; ns < listLen; ns += strlen(&list[ns]) + 1) {
printf(" name=%s; ", &list[ns]);
valueLen = getxattr(argv[j], &list[ns], value, XATTR_SIZE);
if (valueLen == -1) {
printf("couldn't get value");
} else if (!hexDisplay) {
printf("value=%.*s", (int) valueLen, value);
} else {
printf("value=");
for (k = 0; k < valueLen; k++)
printf("%02x ", (unsigned char) value[k]);
}
printf("\n");
}
printf("\n");
}
exit(EXIT_SUCCESS);
}
普通文件:这是最常用的文件类型,这种文件包含了 某种形式的数据。至于这种数据是文本还是二进制数据,对于UNIX内核而言并无区别。对普通文件内容的解释由处理该文件的应用程序进行。(特殊:二进制文件)。
目录文件:文件包含了其他文件的名字以及指向与这些文件有关信息的指针。对一个目录文件具有读权限的任一进程都可以读该目录的内容,但只有内核可以直接写目录文件。
块特殊文件:提供对设备(如磁盘)带缓冲的访问,每次访问以固定长度为单位进行。
字符特殊文件:提供对设备不带缓冲的访问,每次访问长度可变。系统中的所有设备要么是字符特殊文 件,要么是块特殊文件。
FIFO:用于进程间通信,有时也称为命名管道 。
套接字:用于进程间的网络通信。套接字 也可用于在一台宿主机上进程之间的非网络通信。
符号链接:这种类型的文件指向另一个文件。
文件类型信息包含在stat结构的st_mode
成员中。可以用下图中的宏确定文 件类型。这些宏的参数都是stat结构中的st_mode
成员。
POSIX.1允许实现将进程间通信(IPC)对象(如消息队列和信号量等)说 明为文件。
下图所示的宏可用来从 stat 结构
中确定 IPC 对象的类型。它们的参数并非st_mode
,而是指向stat结构的指针。
取其命令行参数,然后针对每一个命令行参数打印其文件类型。
#include "apue.h"
int main(int argc, char *argv[])
{
int i;
struct stat buf;
char *ptr;
for (i = 1; i < argc; i++) {
printf("%s: ", argv[i]);
if (lstat(argv[i], &buf) < 0) {
err_ret("lstat error");
continue;
}
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);
}
exit(0);
}
与一个进程相关联的ID有6个或更多,如下所示:
通常有效用户ID等于实际用户ID,有效组ID等于实际组ID。
每个文件有一个所有者和组所有者,所有者由stat结构中的st_uid
指定,组 所有者则由st_gid
指定。
执行程序文件时,进程的有效用户ID通常就是实际用户ID,有效组 ID通常是实际组ID。但是可以在文件模式字(st_mode)
中设置一个特殊标志, 其含义是“当执行此文件时,将进程的有效用户ID设置为文件所有者的用户 ID(st_uid)
”。
在文件模式字中可以设置另一位,它将执行此文 件的进程的有效组ID设置为文件的组所有者ID(st_gid)
。在文件模式字中的这两位被称为设置用户ID位和设置组ID。
文件所有者是超级用户,而且设置了该文件的设置用户ID位,当程序文件由进程执行是,该进程具有超级用户权限。
stat函数中,设置用户ID位及设置组ID位都包在文件的st_mode
值 中。这两位可分别用常量S_ISUID和S_ISGID
测试。
st_mode值包含了对文件的访问权限位,每个文件有9个访问的权限位,如下所示:
新文件的用户ID设置为进程的有效用户ID。关于组ID,POSIX.1允许选择下列之一作为新文件的组ID。
新文件的组ID可以是进程的有效组ID。
新文件的组ID可以是它所在目录的组ID。
使用POSIX.1所允许的第二个选项(继承目录的组ID)使得在某个目录下创建的文件和目录都具有该目录的组ID。于是文件和目录的组所有权从该点向下传递。例如,在Linux的/var/mail
目录中就使用了这种方法。
进程访问文件时,系统根据有效用户ID、有效组ID以及附属组ID确定权限。对于程序会根据真实用户和组ID来检查对文件的访问权限。
#include
//根据进程的真实用户ID和组ID,检查pathname参数所指定文件的访问权限。
//pathname为符合链接,assess函数将解引用。
//mode常量如下所示:文件测试存在,mode为F_OK,否则是图中常量的按位或。
int access(const char *pathname,int mode);
//flags:改变faccessat的行为,设置为AT_EACCESS,调用有效ID和有效组ID,而不是实际用户ID和实际组ID。
int faccessat(int fd,cosnst *pathname,int mode,int flag);
//faccessat函数与access函数在下面两种情况下是相同的:
//一种是pathname参数为绝对路径。
//另一种是fd参数取值为AT_FDCWD而pathname参数为相对路径。
//否则,faccessat计算相对于打开目录(由fd参数指向)的pathname。
//access函数使用方法:
#include "apue.h"
#include
int main(int argc, char *argv[])
{
if (argc != 2)
err_quit("usage: a.out " );
if (access(argv[1], R_OK) < 0)
err_ret("access error for %s", argv[1]);
else
printf("read access OK\n");
if (open(argv[1], O_RDONLY) < 0)
//尽管open函数能打开文件,但通过设置用户ID程序可以确定实际用户不能正常读指定的文件。
err_ret("open error for %s", argv[1]);
else
printf("open for reading OK\n");
exit(0);
}
为进程文件模式创建屏蔽字,并返回之前的值。
#include
mode_t umask(mode_t cmask);
//cmask:由下图所示的常量按位“或”构成。
在进程创建新文件或新目录时,就一定会使用文件模式创建屏蔽字。
前面open和creat
函数,这两个函数都有一 个参数mode
,它指定了新文件的访问权限位。在文件模式创建屏蔽字中为1的位,在文件mode
中的相应位一定 被关闭。
例:创建两个文件,创建第一个,umask值为0
,第二个,umask值禁止所有组和其他用户的访问权限。
#include "apue.h"
#include
#define RWRWRW (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)
int main(void)
{
umask(0);
if (creat("foo", RWRWRW) < 0)
err_sys("creat error for foo");
umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
if (creat("bar", RWRWRW) < 0)
err_sys("creat error for bar");
exit(0);
}
编写创建文件程序时,如果确保指定的访问权限位已经被激活,必须在进程运行时修改umask值,若任何用户都能读文件,则应将umask设置为0,否则,进程运行时,有效的umask值可能关闭该权限位。
umask值
控制所创建文件的默认权限,该值表示成八进制数,一位代表一种要屏蔽的权限。//用于改变现有文件的访问权限
#include
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);
//fchmodat函数与chmod函数在下面两种情况下是相同的:
//一种是 pathname参数为绝对路径。
//另一种是fd参数取值为AT_FDCWD而pathname参数 为相对路径。
//否则,fchmodat计算相对于打开目录(由fd参数指向)的 pathname。
//flag参数可以用于改变fchmodat的行为,当设置了 AT_SYMLINK_NOFOLLOW标志时,fchmodat并不会跟随符号链接。
为了改变文件的权限位,进程的有效用户ID必须等于文件的所有者ID,或者进程必须具有超级用户权限。
参数mode如下所示的常量的按位或:
#include "apue.h"
int
main(void)
{
struct stat statbuf;
/* turn on set-group-ID and turn off group-execute */
if (stat("foo", &statbuf) < 0)
err_sys("stat error for foo");
if (chmod("foo", (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0)
err_sys("chmod error for foo");
/* set absolute mode to "rw-r--r--" */
if (chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0)
err_sys("chmod error for bar");
exit(0);
}
chmod函数在下列条件自动清除两个权限位:
UNIX未请求分页技术前,S_ISVTX
位被称为粘着位。可执行程序文件被设置了,执行后,程序正文部分的一个副本任被保存在交换区。虚拟存储系统以及快速文件系统程序,并不在需要这种技术。
/tmp 和/var/tmp
是设置粘着位的典型候选者—任何用户都可在这两个 目录中创建文件。
现在系统扩展了粘着位使用范围,,Single UNIX Specification允许针对 目录
设置粘着位。若目录设置了粘着位,只有对该目录具有写权限的 用户并且满足下列条件之一,才能删除或重命名该目录下的文件:
//更改文件的用户ID和组ID,如果两个参数owner或group中的任意一个是-1,则对应的ID不变。
//除了所引用的文件是符号链接以外,这 4 个函数的操作类似。
//在符号链接情况下,lchown和fchownat(设置了AT_SYMLINK_NOFOLLOW标志)更改符号链接本身的所有者,而不是该符号链接所指向的文件的所有者。
#include
int chown(const char *pathname, uid_t owner, gid_t group);
//fchown函数改变fd参数指向的打开文件的所有者,既然它在一个已打开的 文件上操作,就不能用于改变符号链接的所有者。
int fchown(int fd, uid_t owner, gid_t group);
// fchownat函数与chown或者lchown函数在下面两种情况下是相同的:
//一种是 pathname参数为绝对路径。
//另一种是fd参数取值为AT_FDCWD而pathname参数 为相对路径。
//在这两种情况下,如果flag参数中设置了 AT_SYMLINK_NOFOLLOW标志,fchownat与lchown行为相同。
//如果flag参数 中清除了AT_SYMLINK_NOFOLLOW标志,则fchownat与chown行为相同。
//如果fd参数设置为打开目录的文件描述符,并且pathname参数是一个相对路径名,fchownat函数计算相对于打开目录的pathname。
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);
_POSIX_CHOWN_RESTRICTED
常量可选地定义在头文件
中,而且总是可以用pathconf或fpathconf
函数进行查询。若_POSIX_CHOWN_RESTRICTED
对指定的文件生效,则
只有超级用户进程能更改该文件的用户ID;
如果进程拥有此文件(其有效用户ID等于该文件的用户ID),参数 owner等于-1
或文件的用户ID,并且参数group
等于进程的有效组ID或进程的附属组ID之一,那么一个非超级用户进程可以更改该文件的组ID。
当_POSIX_CHOWN_RESTRICTED
有效时,不能更改其他用户 文件的用户ID。你可以更改你所拥用的文件的组ID,但只能改到你所属的组。
如果这些函数由非超级用户进程调用,则在成功返回时,该文件的设置用 户 ID 位和设置组ID位都被清除。
stat结构成员st_size表示以字节为单位的文件的长度。此字段只对普通文 件、目录文件和符号链接有意义。
st_blksize和st_blocks
。其中,第一 个是对文件I/O较合适的块长度,第二个是所分配的实际512字节块块数。空洞是由所设置的偏移量超过文件尾端,并写入了某些数据后造成的。
//截取文件
#include
//将一个现有文件长度截断为 length。
//文件以前的长度大于 length,则超过length 以外的数据就不再能访问。
//长度小于 length,文件长度将增加。
//,在以前的文件尾端和新的文件尾端之间的数据将读作 0(也就是可能在文件中创建了一个空洞)。
int truncate(const char *pathname, off_t length);
int ftruncate(int fd, off_t length);
把一个磁盘分成一个或多个分区。每个分区可以包含一个文件系统。i节点
是固定长度的记录项,它包含有关文件的大部分信息。
柱面组的i节点和数据块部分:
目录文件的链接计算字段,创建新目录显示结果:
//创建一个指向现有文件的链接
#include
//数创建一个新目录项newpath,引用现有文件existingpath。
//newpath已经存在,则返回出错。
//只创建newpath中的最后一个分量,路径中的其他部分应当已经存在。
int link(const char *existingpath, const char *newpath);
//现有文件是通过efd和existingpath参数指定的,新的路径名是通过nfd和newpath参数指定的。
//如果两个路径名中的任一个是 相对路径,那么它需要通过相对于对应的文件描述符进行计算。
//如果两个文件 描述符中的任一个设置为AT_FDCWD,那么相应的路径名(如果它是相对路 径)就通过相对于当前目录进行计算。
//如果任一路径名是绝对路径,相应的文 件描述符参数就会被忽略。
int linkat(int efd, const char *existingpath, int nfd, const char *newpath, int flag);
//当现有文件是符号链接时,由flag参数来控制linkat函数是创建指向现有符号链接的链接还是创建指向现有符号链接所指向的文件的链接。
//如果在flag参数 中设置了AT_SYMLINK_FOLLOW标志,就创建指向符号链接目标的链接。
//如果这个标志被清除了,则创建一个指向符号链接本身的链接。
//创建新目录项和增加链接计数应当是一个原子操作。
POSIX.1允许实现跨文件系统的链接,但实现要求现有的和新建的两个路径名在同一个文件系统中。若支持创建指向一个目录的硬链接,限于超级用户。很多文件系统不允许对目录的硬链接。
//删除一个现有的目录项:
//删除目录项,并将由pathname所引用文件的链接计数减1。
//对该文件还有其他链接,则仍可通过其他链接访问该文件的数据。
//出错,则不对该文件做任何更改。
#includ<unistd.h>
//pathname是符号链接,那么unlink删除该符号链接,而不是删除由该链 接所引用的文件。
//给出符号链接名的情况下,没有一个函数能删除由该链接所引用的文件。
int unlink(const char *pathname);
int unlinkat(int fd,const char *pathname,int flag);
//果pathname参数是相对路径名,那么unlinkat函数计算相对于由fd文件描 述符参数代表的目录的路径名。
///fd参数设置为AT_FDCWD,那么通过相对 于调用进程的当前工作目录来计算路径名。
//pathname参数是绝对路径名, 那么fd参数被忽略。
//flag参数:当AT_REMOVEDIR标志被设置时,
//unlinkat 函数可以类似于rmdir一样删除目录。
//如果这个标志被清除, unlinkat与unlink执行同样的操作。
为了解除对文件的链接,必须对包含该目录项的目录具有写和执行权限。若对该目录设置了粘着位,则对该目录必须具有写权限,并且具备下面三个条件之一:
链接计数达到0时,该文件的内容才可被删除。只要有进程打开了该文件,其内容也不能删除。关闭文件时,内核首先检查打开该文件的进程个数;如果这个计数达到0,内核再去检 查其链接计数;如果计数也是0,那么就删除该文件的内容。
//打开一个文件,然后解除它的链接。执行该进程然后睡眠15秒,然后终止。
#include "apue.h"
#include
int
main(void)
{
if (open("tempfile", O_RDWR) < 0)
err_sys("open error");
if (unlink("tempfile") < 0)
err_sys("unlink error");
printf("file unlinked\n");
sleep(15);
printf("done\n");
exit(0);
}
//解除对一个文件或目录的链接
//文件:remove和unlink相同
//目录:remove和rmdir相同
#include
int remove(const char *pathname);
//文件或目录可以用rename函数或者renameat函数进行重命名。
#include
int rename(const char *oldname, const char *newname);
int renameat(int oldfd, const char *oldname, int newfd, const char *newname);
//当oldname或newname指向相对路径名时,其他情况下renameat函数与 rename函数功能相同。
//若oldname参数指定了相对路径,就相对于oldfd参数引 用的目录来计算oldname。
//newname指定了相对路径,就相对于 newfd引用的目录来计算newname。
//oldfd或newfd参数(或两者)都能设置成 AT_FDCWD,此时相对于当前目录来计算相应的路径名。
根据oldname
是文件、目录还是符号链接和若newname
已经存在时将会发生什么,情况如下:
oldname指的是文件,那么为该文件或符号链接重命名。如果 newname已存在,而且不是一个目录,则先将该目录项删除然后将 oldname 重 命名为 newname。对包含 oldname 的目录以及包含newname的目录,调用进程 必须具有写权限,因为将更改这两个目录。
若oldname指的是一个目录,那么为该目录重命名。如果newname 已存在,则它必须引用一个目录,而且该目录应当是空目录(空目录指的是该 目录中只有.
和..
项)。如果 newname存在(而且是一个空目录),则先将其删 除,然后将oldname重命名为newname。当为目录重命名时, newname不能包含oldname作为其路径前缀。
如若oldname或newname引用符号链接,则处理的是符号链接本身, 而不是它所引用的文件。
不能对.
和..
重命名。更确切地说,.
和..
都不能出现在oldname和 newname的最后部分。
作为一个特例,如果oldname和newname引用同一文件,则函数不做任何更改而成功返回。
符合链接(软链接),是一种特殊的文件类型,其数据是另外一文件的名称(间接指针)。
如下图所示两个硬链接:/home/erena/this和/home/allyn/that
指向同一文件,符合链接/home/kiran/other
,则指代文件名/home/erena/this
。
符合链接内容可以为绝对路径,也可是相对路径。符号链接的地位不如硬链接。如文件的链接计数并未将符号链接计算在内。
移除了符号链接所指向的文件名,符号链接本身还将继续存在,,尽管无法对其进行解引用操,也将此类链接称为悬空链接。还可以为不存在文件名创建一个符号链接。
符号链接指定一个文件名,而非i-node编号。可以用来链接不同文件系统中的文件。硬链接的要求不会困扰符号链接,可以为目录创建符号链接。符号链接之间可能会形成链路。当在各个文件相关的系统调用中指定了符号链接时,内核会对一系列链接层层解去引用,直抵最终文件。
UNIX文件系统的优化措施:
构成符号链接内容字符串总长度很小,能放入i-node中通常用于存放数据指针的位置,将字符串就存放在此。省去磁盘块的分配,加速对符号链接信息访问。
ext2、ext3和ext4才用此技术将i-node通常用于存放数据块指针的60个字节转而用于存放长度合适的符号字符串。
系统调用对符号链接的解释:
/somedir/somesubdir/file
中,若somedir和somesubdir
属于符号链接,则一定会解除对着两个目录的引用。而file
取决于路径名所传入的系统调用。//数创建一个符号链接。
//创建了一个指向actualpath的新目录项sympath。
//在创建此符号链接时, 并不要求actualpath已经存在,,actualpath和sympath并不需要位于同一文件系统中。
//两个函数类似,但sympath参数根据相对于打开文件描述 符引用的目录(由 fd 参数指定)进行计算。
//果 sympath 参数指定的是绝对路 径或者 fd 参数设置了AT_FDCWD值,那么symlinkat就等同于symlink函数。
#include
int symlink(const char *actualpath, const char *sympath); int symlinkat(const char *actualpath, int fd, const char *sympath);
//为open函数跟随符号链接,所以需要有一种方法打开该链接本身,并读 该链接中的名字。下面函数提供了功能:
//组合了 open、read 和 close 的所有操作。
//果函数成功执行,则 返回读入buf的字节数。在buf中返回的符号链接的内容不以null字节终止。
//当pathname参数指定的是绝对路径名或者fd参数的值为AT_FDCWD,readlinkat函数的行为与readlink相同。
//如果fd参数是一个打开目录的有效 文件描述符并且pathname参数是相对路径名,则readlinkat计算相对于由fd代表 的打开目录的路径名。
#include
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);
对每个文件维护3个时间字段,意义如下所示:
修改 时间(st_mtim)是文件内容最后一次被修改的时间。状态更改时间(st_ctim)是该文件的i节点最后一 次被修改的时间。
i节点中的所有信息都是与文件的实际内容分开存放的,所以,除了要 记录文件数据修改时间以外,还需要记录状态更改时间,也就是更改i节点中信 息的时间。
系统并不维护对一个i节点的最后一次访问时间,所以access和stat函 数并不更改这3个时间中的任一个。
常使用访问时间来删除在一定时间范围内没有被访问过的文 件。修改时间和状态更改时间可被用来归档那些内容已经被修改或i节点已经被 更改的文件。
目录是包含目录项(文件名和相关的i节点编号)的文件,增加、删除或 修改目录项会影响到它所在目录相关的3个时间。
图中其中一列是与该文件(或目录)相关的3个时间,另一列是与所引用的文件(或目录)的父目录相关的3个时间。
下图列出了我们已说明过的各种函数对这3个时间的作用:
//文件的访问和修改时间可以用以下几个函数更改
//futimens和utimensat 函数可以指定纳秒级精度的时间戳。
//用到的数据结构是与stat函数族相同的 timespec结构。
#include
//times数组参数的第一个元素包含访问时间,第二元素包含修 改时间。
//futimens 函数需要打开文件来更改它的时间,utimensat 函数提供了一种使用文件名更改文件时间的方法。
//pathname参数是相对于fd参数进行计算的,fd要么是打开目录的文件描述符,要么设置为特殊值 AT_FDCWD(强制通过相对于调用进程的当前目录计算pathname)。
//如果pathname指定了绝对路径,那么fd 参数被忽略。
int futimens(int fd, const struct timespec times[2]);
int utimensat(int fd, const char *path, const struct timespec times[2], int flag);
//flag:ATf_SYMLINK_NOFOLLOW标志,则符号链接本身的时间就会被修改(如果路径名指向符号链接)。
//默认的行为是跟随符号链接,并把文件的时间改成符号链接的时间。
include<sys/time.h>
int utimes(const char *pathname,const struct timeval time[2]);
//utimes函数对路径名进行操作。
//不能对状态更改时间st_ctim(i节点最近被修改的时间)指定一个值,因为调用utimes函数时,此字段会被自动更新。
//times参数是指向包含两个时间戳(访问时 间和修改时间)元素的数组的指针,两个时间戳是用秒和微妙表示的。
struct timeval {
time_t tv_sec;
long tv_usec;
};
时间戳规定:
tv_nsec
字段的值为UTIME_NOW
,相应的时间戳就设置为当前时间,忽略相应的tv_sec
字段。timespec
结构的数组,任一数组元素的tv_nsec
字段的值为UTIME_OMIT
,相应的时间戳保持不变,忽略相应的tv_sec字段。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
,就 不执行任何的权限检查。
//使用带O_TRUNC选项的open函数将文件长度截断为0,但并 不更改其访问时间及修改时间。
//首先用stat函数得到这些时 间,然后截断文件,最后再用futimens函数重置这两个时间。
#include
int main(int argc, char *argv[])
{
int i, fd;
struct stat statbuf;
struct timespec times[2];
for (i = 1; i < argc; i++) {
if (stat(argv[i], &statbuf) < 0) { /* fetch current times */
err_ret("%s: stat error", argv[i]);
continue;
}
if ((fd = open(argv[i], O_RDWR | O_TRUNC)) < 0) { /* truncate */
err_ret("%s: open error", argv[i]);
continue;
}
times[0] = statbuf.st_atim;
times[1] = statbuf.st_mtim;
if (futimens(fd, times) < 0) /* reset times */
err_ret("%s: futimens error", argv[i]);
close(fd);
}
exit(0);
}
//数创建一个新的空目录。其中,.和..目录项是自动创建的。
//所指 定的文件访问权限mode由进程的文件模式创建屏蔽字修改。
//用mkdir和mkdirat函数创建目录,用rmdir函数删除目录。
//常见的错误是指定与文件相同的mode(只指定读、写权限)。
//目录通常至少要设置一个执行权限位,以允许访问该目录中的文件名。
//超级用户进程才能 使用mknod函数。
//创建目录的命令mkdir(1)必须由根用户拥有,而且对它设置了设置用户ID位。
//要通过一个进程创建一个目录,必须用 system(3)函数调用mkdir(1)命令。
#include
int mkdir(const char *pathname, mode_t mode);
//mkdirat函数与mkdir函数类似。
//当fd参数具有特殊值AT_FDCWD或者 pathname参数指定了绝对路径名时,mkdirat与mkdir完全一样。否则,fd参数是 一个打开目录,相对路径名根据此打开目录进行计算。
int mkdirat(int fd, const char *pathname, mode_t mode);
//rmdir函数可以删除一个空目录。空目录是只包含.和..这两项的目录。
//调用此函数使目录的链接计数成为0,并且也没有其他进程打开此目录,则释放由此目录占用的空间。
//果在链接计数达到0时,有一个或多个进程打开此目录,则在此函数返回前删除最后一个链接及.和..项。
//在此目录中不能再创建新文件。但是在最后一个进程关闭它之前并不释放此目录。
#include int rmdir(const char *pathname);
对某个目录具有访问权限的任一用户都可以读该目录,但是,为了防止文 件系统产生混乱,只有内核才能写目录。
//实现阻止应用程序使用read函数读取目录的内容,由此进一步将应 用程序与目录格式中与实现相关的细节隔离。
#include
DIR *opendir(const char *pathname);
//把打开文件描述符转换成目录处理函数需要的DIR结 构。
DIR *fdopendir(int fd); //两个函数返回值:若成功,返回指针;若出错,返回NULL
struct dirent *readdir(DIR *dp); //返回值:若成功,返回指针;若在目录尾或出错,返回NULL
//readdir不可重入,而readdir_r可重入
int readdir_r(DIR *dp,struct dirent *entry,struct dirent **result);
void rewinddir(DIR *dp);
int closedir(DIR *dp); //返回值:若成功,返回0;若出错,返回-1
long telldir(DIR *dp);// 返回值:与dp关联的目录中的当前位置
void seekdir(DIR *dp, long loc);
//中的dirent结构与实现有关:
//包含两个成员:
ino_t d_ino;
//d_name项的大小并没有指定,但必须保证它能包含至少 NAME_MAX个字节
char d_name[];
opendir和fdopendir
返回的指向DIR结构
的指针由另外5个函数使用。opendir
执行初始化操作,使第一个readdir
返回目录中的第一个目录项。readdir
返回的第一项取决于传给fdopendir
函数的文件描述符 相关联的文件偏移量。//返回与dirp目录流相关的文件描述符
#include
int dirfd(DIR *dirp);
遍历文件层次结构的程序:
#include "apue.h"
#include
#include
/* function type that is called for each filename */
typedef int Myfunc(const char *, const struct stat *, int);
static Myfunc myfunc;
static int myftw(char *, Myfunc *);
static int dopath(Myfunc *);
static long nreg, ndir, nblk, nchr, nfifo, nslink, nsock, ntot;
int
main(int argc, char *argv[])
{
int ret;
if (argc != 2)
err_quit("usage: ftw " );
ret = myftw(argv[1], myfunc); /* does it all */
ntot = nreg + ndir + nblk + nchr + nfifo + nslink + nsock;
if (ntot == 0)
ntot = 1; /* avoid divide by 0; print 0 for all counts */
printf("regular files = %7ld, %5.2f %%\n", nreg,
nreg*100.0/ntot);
printf("directories = %7ld, %5.2f %%\n", ndir,
ndir*100.0/ntot);
printf("block special = %7ld, %5.2f %%\n", nblk,
nblk*100.0/ntot);
printf("char special = %7ld, %5.2f %%\n", nchr,
nchr*100.0/ntot);
printf("FIFOs = %7ld, %5.2f %%\n", nfifo,
nfifo*100.0/ntot);
printf("symbolic links = %7ld, %5.2f %%\n", nslink,
nslink*100.0/ntot);
printf("sockets = %7ld, %5.2f %%\n", nsock,
nsock*100.0/ntot);
exit(ret);
}
/*
* Descend through the hierarchy, starting at "pathname".
* The caller's func() is called for every file.
*/
#define FTW_F 1 /* file other than directory */
#define FTW_D 2 /* directory */
#define FTW_DNR 3 /* directory that can't be read */
#define FTW_NS 4 /* file that we can't stat */
static char *fullpath; /* contains full pathname for every file */
static size_t pathlen;
static int /* we return whatever func() returns */
myftw(char *pathname, Myfunc *func)
{
fullpath = path_alloc(&pathlen); /* malloc PATH_MAX+1 bytes */
/* ({Prog pathalloc}) */
if (pathlen <= strlen(pathname)) {
pathlen = strlen(pathname) * 2;
if ((fullpath = realloc(fullpath, pathlen)) == NULL)
err_sys("realloc failed");
}
strcpy(fullpath, pathname);
return(dopath(func));
}
/*
* Descend through the hierarchy, starting at "fullpath".
* If "fullpath" is anything other than a directory, we lstat() it,
* call func(), and return. For a directory, we call ourself
* recursively for each name in the directory.
*/
static int /* we return whatever func() returns */
dopath(Myfunc* func)
{
struct stat statbuf;
struct dirent *dirp;
DIR *dp;
int ret, n;
if (lstat(fullpath, &statbuf) < 0) /* stat error */
return(func(fullpath, &statbuf, FTW_NS));
if (S_ISDIR(statbuf.st_mode) == 0) /* not a directory */
return(func(fullpath, &statbuf, FTW_F));
/*
* It's a directory. First call func() for the directory,
* then process each filename in the directory.
*/
if ((ret = func(fullpath, &statbuf, FTW_D)) != 0)
return(ret);
n = strlen(fullpath);
if (n + NAME_MAX + 2 > pathlen) { /* expand path buffer */
pathlen *= 2;
if ((fullpath = realloc(fullpath, pathlen)) == NULL)
err_sys("realloc failed");
}
fullpath[n++] = '/';
fullpath[n] = 0;
if ((dp = opendir(fullpath)) == NULL) /* can't read directory */
return(func(fullpath, &statbuf, FTW_DNR));
while ((dirp = readdir(dp)) != NULL) {
if (strcmp(dirp->d_name, ".") == 0 ||
strcmp(dirp->d_name, "..") == 0)
continue; /* ignore dot and dot-dot */
strcpy(&fullpath[n], dirp->d_name); /* append name after "/" */
if ((ret = dopath(func)) != 0) /* recursive */
break; /* time to leave */
}
fullpath[n-1] = 0; /* erase everything from slash onward */
if (closedir(dp) < 0)
err_ret("can't close directory %s", fullpath);
return(ret);
}
static int
myfunc(const char *pathname, const struct stat *statptr, int type)
{
switch (type) {
case FTW_F:
switch (statptr->st_mode & S_IFMT) {
case S_IFREG: nreg++; break;
case S_IFBLK: nblk++; break;
case S_IFCHR: nchr++; break;
case S_IFIFO: nfifo++; break;
case S_IFLNK: nslink++; break;
case S_IFSOCK: nsock++; break;
case S_IFDIR: /* directories should have type = FTW_D */
err_dump("for S_IFDIR for %s", pathname);
}
break;
case FTW_D:
ndir++;
break;
case FTW_DNR:
err_ret("can't read directory %s", pathname);
break;
case FTW_NS:
err_ret("stat error for %s", pathname);
break;
default:
err_dump("unknown type %d for pathname %s", type, pathname);
}
return(0);
}
//
//进程调用chdir或fchdir函数可以更改当前工作目录。
//用pathname或打开文件描述符来指定新的当前工作 目录。
#include
int chdir(const char *pathname);
int fchdir(int fd);
//例:
//因为当前工作目录是进程的一个属性,所以它只影响调用 chdir 的进程本身,而不影响其他进程
#include "apue.h"
int main(void)
{
char *ptr;
size_t size;
if (chdir("/usr/spool/uucppublic") < 0)
err_sys("chdir failed");
ptr = path_alloc(&size); /* our own function */
if (getcwd(ptr, size) == NULL)
err_sys("getcwd failed");
printf("cwd = %s\n", ptr);
exit(0);
}
内核必须维护当前工作目录的信息,所以应能获取其当前值。
由于内核为每个进程只保存指向该目录 v 节点的指针等目录本身的信息, 并不保存该目录的完整路径名。
Linux内核可以确定完整路径名。完整路径名的各个组成部分分布在mount
表和dcache
表中,然后进行重新组装,比如在取/proc/self/cwd
符号链接时。
//从当前工作目录(.)开始,用..找到其上一级目录,然后读其目录项,直到该目录项中的i节点编号与工作目录i节点编号相同, 这样地就找到了其对应的文件名。
//逐层上移,直到遇到根,这 样就得到了当前工作目录完整的绝对路径名。
//此函提供这样功能
#include
//一个是缓冲区地址buf,另一个是缓冲区的长度size(以字节为单位)。
char *getcwd(char *buf, size_t size);
//该缓冲区必须有足够的长度以容纳绝对路径名再加上 一个终止 null 字节,否则返回出错。
//将工作目录更改至一个指定的目录,然后调用 getcwd,最后 打印该工作目录。
#include "apue.h"
int main(void)
{
char *ptr;
size_t size;
if (chdir("/usr/spool/uucppublic") < 0)
err_sys("chdir failed");
ptr = path_alloc(&size); /* our own function */
//返回到它工作的出发点
if (getcwd(ptr, size) == NULL)
err_sys("getcwd failed");
printf("cwd = %s\n", ptr);
exit(0);
//fchdir函数向我们提供了一种完成此任务的便捷方法。
//在更换到文件系统中 的不同位置前,无需调用getcwd函数,而是使用open打开当前工作目录,然后 保存其返回的文件描述符。
//当希望回到原工作目录时,只要简单地将该文件描 述符传送给fchdir。
下列函数用于改变进程的根目录、
#include
int chroot(const char *pathname);
//pathname:新指定的目录。
//例:
int fd;
fd = open("/",O_RDONLY);
chroot("/home/mtk");
fchdir(fd);
chroot(".");
st_dev和st_rdev
这两个字段经常引起混淆,规则很简单:
每个文件系统所在的存储设备都由其主、次设备号表示。设备号所用的数 据类型是基本系统数据类型dev_t。主设备号标识设备驱动程序,有时编码为与 其通信的外设板;次设备号标识特定的子设备。
们通常可以使用两个宏:major和minor
来访问主、次设备号,大多数实 现都定义这两个宏。无需关心这两个数是如何存放在dev_t
对象 中的。
系统中与每个文件名关联的 st_dev
值是文件系统的设备号,该文件系统包 含了这一文件名以及与其对应的i节点。
只有字符特殊文件和块特殊文件才有st_rdev
值。此值包含实际设备的设备号。
两个终端设备(st_dev)的文件名和i节点在设备0/5上(devtmpfs伪 文件系统,它实现了/dev文件系统),但是它们的实际设备号是4/0和4/1。
//为每个命令行参数打印设备号,
//若此参数引用的是字符特殊文件或块特殊文件,则还打印该特殊文件的st_rdev值
#include "apue.h"
#ifdef SOLARIS
#include
#endif
int
main(int argc, char *argv[])
{
int i;
struct stat buf;
for (i = 1; i < argc; i++) {
printf("%s: ", argv[i]);
if (stat(argv[i], &buf) < 0) {
err_ret("stat error");
continue;
}
printf("dev = %d/%d", major(buf.st_dev), minor(buf.st_dev));
if (S_ISCHR(buf.st_mode) || S_ISBLK(buf.st_mode)) {
printf(" (%s) rdev = %d/%d",
(S_ISCHR(buf.st_mode)) ? "character" : "block",
major(buf.st_rdev), minor(buf.st_rdev));
}
printf("\n");
}
exit(0);
}
realpath
库函数对pathname
(以空字符结尾的字符串)中的所有符号一一解除引用,并解析其中所有对/.和/..
的引用,从而生成一个以空字符结尾的字符串内含相应的绝对路径。
#include
char *realpath(const char *pathname,char *resolved_path);
resolved_path
指向的缓冲区中,该字符串是一个字符数组,长度至少为PATH_MAX
字节。resolved_path
指定为空,函数经解析生成的路径名分配一个多达PATH_MAX
个字节的缓冲区,并将指向该缓冲区的指针作为结果返回。例:读取并解析一个符号链接。
#include
#include
#define BUF_SIZE PATH_MAX
int main(int argc, char *argv[])
{
struct stat statbuf;
char buf[BUF_SIZE];
ssize_t numBytes;
if (argc != 2 || strcmp(argv[1], "--help") == 0)
usageErr("%s pathname\n", argv[0]);
if (lstat(argv[1], &statbuf) == -1)
errExit("lstat");
if (!S_ISLNK(statbuf.st_mode))
fatal("%s is not a symbolic link", argv[1]);
numBytes = readlink(argv[1], buf, BUF_SIZE - 1);
if (numBytes == -1)
errExit("readlink");
buf[numBytes] = '\0'; /* Add terminating null byte */
printf("readlink: %s --> %s\n", argv[1], buf);
if (realpath(argv[1], buf) == NULL)
errExit("realpath");
printf("realpath: %s --> %s\n", argv[1], buf);
exit(EXIT_SUCCESS);
}
下列函数将一个路径名字符串分解成目录和文件名两部分。
#include
char *dirname(char *pathname);
char *basename(char *pathname);
#include
int main(int argc, char *argv[])
{
char *t1, *t2;
int j;
for (j = 1; j < argc; j++) {
t1 = strdup(argv[j]);
if (t1 == NULL)
errExit("strdup");
t2 = strdup(argv[j]);
if (t2 == NULL)
errExit("strdup");
printf("%s ==> %s + %s\n", argv[j], dirname(t1), basename(t2));
free(t1);
free(t2);
}
exit(EXIT_SUCCESS);
}
权限位对普通文件和目录文件的作用如下所示:
S_IRWXU = S_IRUSR|S_IWUSR|S_IXUSR
S_IRWXG = S_IRGRP|S_IWGRP|S_IXGRP
S_IRWXO = S_IROTH|S_IWOTH|S_IXOTH