glibc 知:手册14:文件系统接口

1. 前言

The GNU C Library Reference Manual for version 2.35

2. 文件系统接口

File System Interface

本章描述了 GNU C 库用于操作文件的函数。与输入和输出函数不同(参见输入/输出流;参见底层输入/输出),这些函数关注的是对文件本身而不是对它们的内容进行操作。

本章描述的功能包括检查或修改目录的函数、重命名和删除文件的函数以及检查和设置文件属性(如访问权限和修改时间)的函数。

2.1. 工作目录

Working Directory

每个进程都有一个与其关联的目录,称为其当前工作目录或简称为工作目录,用于解析相对文件名(请参阅文件名解析)。

当您登录并开始新会话时,您的工作目录最初设置为与系统用户数据库中的登录帐户关联的主目录。您可以使用 getpwuid 或 getpwnam 函数找到任何用户的主目录;请参阅用户数据库

用户可以使用 cd 等 shell 命令更改工作目录。本节中描述的功能是这些命令和其他程序用于检查和更改工作目录的原语。

这些函数的原型在头文件 unistd.h 中声明。

函数:char * getcwd (char *buffer, size_t size)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem fd | See POSIX Safety Concepts.

getcwd 函数返回一个代表当前工作目录的绝对文件名,并将其存储在您提供的字符数组缓冲区中。size 参数是告诉系统缓冲区分配大小的方式。

此函数的 GNU C 库版本还允许您为缓冲区参数指定空指针。然后 getcwd 会自动分配一个缓冲区,就像 malloc 一样(请参阅无约束分配)。如果大小大于零,那么缓冲区就是那么大;否则,缓冲区将与保存结果所需的一样大。

成功时返回值是缓冲区,失败时返回空指针。为此函数定义了以下 errno 错误条件:

EINVAL

size 参数为零,并且 buffer 不是空指针。

ERANGE

size 参数小于工作目录名称的长度。您需要分配一个更大的数组并重试。

EACCES

读取或搜索文件名组件的权限被拒绝。

您可以仅使用 getcwd 的标准行为来实现 GNU 的 getcwd (NULL, 0) 的行为:

char *
gnu_getcwd ()
{
  size_t size = 100;

  while (1)
    {
      char *buffer = (char *) xmalloc (size);
      if (getcwd (buffer, size) == buffer)
        return buffer;
      free (buffer);
      if (errno != ERANGE)
        return 0;
      size *= 2;
    }
}

有关 xmalloc 的信息,请参阅 malloc 示例,它不是库函数,而是大多数 GNU 软件中使用的习惯名称。

不推荐使用的函数:char * getwd (char *buffer)

Preliminary: | MT-Safe | AS-Unsafe heap i18n | AC-Unsafe mem fd | See POSIX Safety Concepts.

这类似于 getcwd,但无法指定缓冲区的大小。GNU C 库提供 getwd 只是为了向后兼容 BSD。

buffer 参数应该是一个指向至少 PATH_MAX 字节长的数组的指针(请参阅文件系统容量限制)。在 GNU/Hurd 系统上,文件名的大小没有限制,所以这不一定是足够的空间来包含目录名。这就是不推荐使用此功能的原因。

函数:char * get_current_dir_name (void)

Preliminary: | MT-Safe env | AS-Unsafe heap | AC-Unsafe mem fd | See POSIX Safety Concepts.

get_current_dir_name 函数基本上等同于 getcwd (NULL, 0),除了首先检查 PWD 环境变量的值,如果它确实对应于当前目录,则返回该值。如果 PWD 中的值所描述的路径正在使用一个或多个符号链接,则这是一个细微的差异,在这种情况下,getcwd 返回的值将解析符号链接并因此产生不同的结果。

这个函数是一个 GNU 扩展。

函数:int chdir (const char *filename)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

该函数用于将进程的工作目录设置为文件名。

来自 chdir 的正常、成功的返回值为 0。返回值 -1 表示错误。为此函数定义的 errno 错误条件是通常的文件名语法错误(请参阅文件名错误),如果文件文件名不是目录,则加上 ENOTDIR。

函数:int fchdir (int filedes)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

该函数用于将进程的工作目录设置为与文件描述符文件关联的目录。

fchdir 的正常成功返回值为 0。返回值 -1 表示错误。为此函数定义了以下 errno 错误条件:

EACCES

对由 dirname 命名的目录的读取权限被拒绝。

EBADF

filedes 参数不是有效的文件描述符。

ENOTDIR

文件描述符filedes 不与目录关联。

EINTR

函数调用被信号中断。

EIO

发生 I/O 错误。

2.2. 访问目录

Accessing Directories

本节中描述的工具使您可以读取目录文件的内容。如果您希望程序列出目录中的所有文件(可能作为菜单的一部分),这很有用。

opendir 函数打开一个目录流,其元素是目录条目。或者,如果程序需要对打开目录进行读取的方式进行更多控制,则可以使用 fdopendir,这可能具有优势。例如,这允许传递 O_NOATIME 标志来打开。

您使用目录流上的 readdir 函数来检索这些条目,表示为 struct dirent 对象。每个条目的文件名存储在此结构的 d_name 成员中。这里与普通文件的流设施有明显的相似之处,在输入/输出流中描述。

2.2.1. 目录条目的格式

Format of a Directory Entry

本节描述您在单个目录条目中找到的内容,因为您可能从目录流中获取它。所有符号都在头文件dirent.h 中声明。

数据类型:struct dirent

这是一种结构类型,用于返回有关目录条目的信息。它包含以下字段:

char d_name[]

这是以 null 结尾的文件名组件。这是您在所有 POSIX 系统中唯一可以信赖的字段。

ino_t d_fileno

这是文件序列号。对于 BSD 兼容性,您也可以将此成员称为 d_ino。在 GNU/Linux 和 GNU/Hurd 系统以及大多数 POSIX 系统上,对于大多数文件,这与 stat 将为文件返回的 st_ino 成员相同。请参阅文件属性

unsigned char d_namlen

这是文件名的长度,不包括终止空字符。它的类型是 unsigned char 因为那是适当大小的整数类型。该成员是 BSD 扩展。如果此成员可用,则定义符号 _DIRENT_HAVE_D_NAMLEN。

unsigned char d_type

这是文件的类型,可能未知。为其值定义了以下常量:

DT_UNKNOWN

类型未知。只有一些文件系统完全支持返回文件的类型,其他文件系统可能总是返回这个值。

DT_REG

一个普通的文件。

DT_DIR

一个目录。

DT_FIFO

命名管道或 FIFO。请参阅 FIFO 特殊文件

DT_SOCK

本地域套接字。

DT_CHR

字符设备。

DT_BLK

块设备。

DT_LNK

一个符号链接。

该成员是 BSD 扩展。如果此成员可用,则定义符号 _DIRENT_HAVE_D_TYPE。在使用它的系统上,它对应于 struct stat 的 st_mode 成员中的文件类型位。如果无法确定该值,则成员值为 DT_UNKNOWN。这两个宏在 d_type 值和 st_mode 值之间进行转换:

函数:int IFTODT (mode_t mode)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

这将返回与模式对应的 d_type 值。

函数:mode_t DTTOIF (int dtype)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

这将返回对应于 dtype 的 st_mode 值。

此结构将来可能包含其他成员。它们的可用性总是在编译环境中通过名为 _DIRENT_HAVE_D_xxx 的宏来宣布,其中 xxx 被新成员的名称替换。例如,某些系统上可用的成员 d_reclen 是通过宏 _DIRENT_HAVE_D_RECLEN 宣布的。

当一个文件有多个名称时,每个名称都有自己的目录条目。您可以判断目录条目属于单个文件的唯一方法是它们在 d_fileno 字段中具有相同的值。

文件属性(如大小、修改时间等)是文件本身的一部分,而不是任何特定目录条目的一部分。请参阅文件属性

2.2.2. 打开目录流

Opening a Directory Stream

本节介绍如何打开目录流。所有符号都在头文件dirent.h 中声明。

数据类型:DIR

DIR 数据类型表示目录流。

你永远不应该分配 struct dirent 或 DIR 数据类型的对象,因为目录访问函数会为你做这些。相反,您使用以下函数返回的指针来引用这些对象。

目录流是一个高级接口。在 Linux 上,提供了使用文件描述符访问目录的替代接口。请参阅底层目录访问

函数:DIR * opendir (const char *dirname)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem fd | See POSIX Safety Concepts.

opendir 函数打开并返回一个目录流,用于读取文件名为 dirname 的目录。该流的类型为 DIR *。

如果不成功,opendir 返回一个空指针。除了通常的文件名错误(请参阅文件名错误)之外,还为此函数定义了以下 errno 错误条件:

EACCES

对由 dirname 命名的目录的读取权限被拒绝。

EMFILE

该进程打开的文件过多。

ENFILE

整个系统,或者可能是包含该目录的文件系统,目前无法支持任何其他打开的文件。(这个问题在 GNU/Hurd 系统上不会发生。)

ENOMEM

没有足够的可用内存。

DIR 类型通常使用文件描述符来实现,而 opendir 函数则根据 open 函数来实现。请参阅底层输入/输出。目录流和底层文件描述符在 exec 上关闭(请参阅执行文件)。

由 opendir 打开以供读取的目录由名称标识。在某些情况下,这还不够。或者 opendir 为目录隐式创建文件描述符的方式不是程序可能想要的方式。在这些情况下,可以使用替代接口。

函数:DIR * fdopendir (int fd)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem fd | See POSIX Safety Concepts.

fdopendir 函数的工作方式与 opendir 类似,但调用者需要提供文件描述符,而不是获取文件名并为目录打开文件描述符。该文件描述符随后用于返回的目录流对象的后续使用。

调用者必须确保文件描述符与目录相关联并且允许读取。

如果 fdopendir 调用成功返回,则文件描述符现在处于系统控制之下。它可以像使用 opendir 隐式创建的描述符一样使用,但程序不能关闭描述符。

如果函数不成功,它会返回一个空指针,并且文件描述符仍然可供程序使用。为此函数定义了以下 errno 错误条件:

EBADF

文件描述符无效。

ENOTDIR

文件描述符与目录无关。

EINVAL

描述符不允许读取目录内容。

ENOMEM

没有足够的可用内存。

在某些情况下,可能需要获取由 opendir 调用创建的文件描述符。例如,要将当前工作目录切换到刚刚读取的目录,可以使用 fchdir 函数。从历史上看,DIR 类型是公开的,程序可以访问这些字段。这在 GNU C 库中不会发生。相反,提供了一个单独的功能来允许访问。

函数:int dirfd (DIR *dirstream)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

函数 dirfd 返回与目录流 dirstream 关​​联的文件描述符。在使用 closedir 关闭目录之前,可以使用此描述符。如果目录流实现不使用文件描述符,则返回值为 -1。

2.2.3. 读取和关闭目录流

Reading and Closing a Directory Stream

本节介绍如何从目录流中读取目录条目,以及如何在完成后关闭流。所有符号都在头文件dirent.h 中声明。

函数:struct dirent * readdir (DIR *dirstream)

Preliminary: | MT-Safe | AS-Unsafe lock | AC-Unsafe lock | See POSIX Safety Concepts.

此函数从目录中读取下一个条目。它通常返回一个指向包含文件信息的结构的指针。此结构与 dirstream 句柄相关联,可以通过后续调用重写。

可移植性说明:在某些系统上,readdir 可能不会返回 .和 …,即使它们在任何目录中始终是有效的文件名。请参阅文件名解析

如果目录中没有更多条目或检测到错误,则 readdir 返回一个空指针。为此函数定义了以下 errno 错误条件:

EBADF

dirstream 参数无效。

要区分目录结束条件或错误,您必须在调用 readdir 之前将 errno 设置为零。为避免进入无限循环,您应该在第一个错误后停止从目录读取。

注意: readdir 返回的指针指向 DIR 对象中的缓冲区。该缓冲区中的数据将被下一次调用 readdir 覆盖。例如,如果以后需要,您必须小心复制 d_name 字符串。

因此,在多个线程之间共享一个 DIR 对象是不安全的,除非您使用自己的锁定来确保没有线程调用 readdir 而另一个线程仍在使用前一次调用的数据。在 GNU C 库中,从多个线程调用 readdir 是安全的,只要每个线程使用自己的 DIR 对象。POSIX.1-2008 不要求这样做是安全的,但我们不知道有任何操作系统无法正常工作。

readdir_r 允许您为结构 dirent 提供自己的缓冲区,但它的可移植性不如 readdir,并且存在文件名过长的问题(见下文)。我们建议您使用 readdir,但不要共享 DIR 对象。

函数:int readdir_r (DIR *dirstream, struct dirent *entry, struct dirent **result)

Preliminary: | MT-Safe | AS-Unsafe lock | AC-Unsafe lock | See POSIX Safety Concepts.

此函数是执行内部锁定的 readdir 版本。与 readdir 一样,它返回目录中的下一个条目。为了防止同时运行的线程之间发生冲突,结果存储在入口对象中。

可移植性注意:不推荐使用 readdir_r。推荐使用 readdir 而不是 readdir_r,原因如下:

  • 在没有定义 NAME_MAX 的系统上,可能无法安全地使用 readdir_r,因为调用者没有为目录条目指定缓冲区的长度。
  • 在某些系统上,readdir_r 无法读取名称很长的目录条目。如果遇到这样的名称,则 readdir_r 的 GNU C 库实现在读取最终目录条目后返回错误代码 ENAMETOOLONG。在其他系统上,readdir_r 可能会成功返回,但 d_name 成员可能不是 NUL 终止的或可能被截断。
  • POSIX-1.2008 不保证 readdir 是线程安全的,即使对同一目录流的访问被序列化。但是在当前的实现中(包括 GNU C 库),在不同的目录流上同时调用 readdir 是安全的,因此在大多数多线程程序中不需要使用 readdir_r。在极少数情况下,多个线程需要从同一个目录流中读取,最好还是使用 readdir 和外部同步。
  • 预计 POSIX 的未来版本将淘汰 readdir_r 并要求 GNU C 库和今天的其他实现提供的 readdir 的线程安全级别。

通常 readdir_r 返回零并将 *result 设置为 entry。如果目录中没有更多条目或检测到错误,readdir_r 将 *result 设置为空指针并返回非零错误代码,该代码也存储在 errno 中,如 readdir 所述。

查看 struct dirent 类型的定义也很重要。仅将指向该类型对象的指针传递给 readdir_r 的第二个参数可能是不够的。一些系统没有足够长地定义 d_name 元素。在这种情况下,用户必须提供额外的空间。d_name 数组中必须至少有 NAME_MAX + 1 个字符的空间。调用 readdir_r 的代码可能如下所示:

  union
  {
    struct dirent d;
    char b[offsetof (struct dirent, d_name) + NAME_MAX + 1];
  } u;

  if (readdir_r (dir, &u.d, &res) == 0)

为了支持 32 位机器上的大型文件系统,有最后两个函数的 LFS 变体。

函数:struct dirent64 * readdir64 (DIR *dirstream)

Preliminary: | MT-Safe | AS-Unsafe lock | AC-Unsafe lock | See POSIX Safety Concepts.

readdir64 函数与 readdir 函数类似,不同之处在于它返回指向 struct dirent64 类型记录的指针。此数据类型的某些成员(尤其是 d_ino)可能具有不同的大小以允许大型文件系统。

在所有其他方面,此函数等效于 readdir。

函数:int readdir64_r (DIR *dirstream, struct dirent64 *entry, struct dirent64 **result)

Preliminary: | MT-Safe | AS-Unsafe lock | AC-Unsafe lock | See POSIX Safety Concepts.

不推荐使用的 readdir64_r 函数与 readdir_r 函数等效,只是它在第二和第三个位置采用基本类型 struct dirent64 而不是 struct dirent 的参数。readdir_r 文档中提到的相同预防措施也适用于此处。

函数:int closedir (DIR *dirstream)

Preliminary: | MT-Safe | AS-Unsafe heap lock/hurd | AC-Unsafe mem fd lock/hurd | See POSIX Safety Concepts.

此函数关闭目录流 dirstream。成功返回 0,失败返回 -1。

为此函数定义了以下 errno 错误条件:

EBADF

dirstream 参数无效。

2.2.4. 列出目录的简单程序

Simple Program to List a Directory

这是一个打印当前工作目录中文件名的简单程序:

#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>

int
main (void)
{
  DIR *dp;
  struct dirent *ep;

  dp = opendir ("./");
  if (dp != NULL)
    {
      while (ep = readdir (dp))
        puts (ep->d_name);
      (void) closedir (dp);
    }
  else
    perror ("Couldn't open the directory");

  return 0;
}

文件出现在目录中的顺序往往是相当随机的。一个更有用的程序会在打印条目之前对条目进行排序(可能按字母顺序排列);请参阅扫描目录的内容和数组排序功能。

2.2.5. 目录流中的随机访问

Random Access in a Directory Stream

本节介绍如何重新读取已从打开的目录流中读取的目录部分。所有符号都在头文件dirent.h 中声明。

函数:void rewinddir (DIR *dirstream)

Preliminary: | MT-Safe | AS-Unsafe lock | AC-Unsafe lock | See POSIX Safety Concepts.

rewinddir 函数用于重新初始化目录流 dirstream,因此如果您调用 readdir,它会再次返回有关目录中第一个条目的信息。该函数还注意到自从使用 opendir 打开目录后,文件是否已添加或删除到目录中。(如果自从您上次调用 opendir 或 rewinddir 后添加或删除了这些文件的条目,readdir 可能会或可能不会返回这些条目。)

函数:long int telldir (DIR *dirstream)

Preliminary: | MT-Safe | AS-Unsafe heap/bsd lock/bsd | AC-Unsafe mem/bsd lock/bsd | See POSIX Safety Concepts.

telldir 函数返回目录流 dirstream 的文件位置。您可以将此值与 seekdir 一起使用,以将目录流恢复到该位置。

函数:void seekdir (DIR *dirstream, long int pos)

Preliminary: | MT-Safe | AS-Unsafe heap/bsd lock/bsd | AC-Unsafe mem/bsd lock/bsd | See POSIX Safety Concepts.

seekdir 函数将目录流 dirstream 的文件位置设置为 pos。值 pos 必须是先前在此特定流上调用 telldir 的结果;关闭和重新打开目录会使telldir 返回的值无效。

2.2.6. 扫描目录的内容

Scanning the Content of a Directory

目录处理函数的高级接口是 scandir 函数。在它的帮助下,可以选择目录中条目的子集,可能对它们进行排序并获得名称列表作为结果。

函数:int scandir (const char *dir, struct dirent ***namelist, int (*selector) (const struct dirent *), int (*cmp) (const struct dirent **, const struct dirent **))

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem fd | See POSIX Safety Concepts.

scandir 函数扫描 dir 选择的目录的内容。*namelist 中的结果是指向 struct dirent 类型结构的指针数组,该结构描述所有选定的目录条目,并使用 malloc 分配。用户提供的函数选择器可以用来决定哪些条目在结果中,而不是总是返回所有目录条目。仅选择选择器返回非零值的条目。

最后,使用用户提供的函数 cmp 对 *namelist 中的条目进行排序。传递给 cmp 函数的参数是 struct dirent ** 类型,因此不能直接使用 strcmp 或 strcoll 函数;请参阅下面的函数 alphasort 和 versionsort。

该函数的返回值是放置在 *namelist 中的条目数。如果为 -1,则发生错误(无法打开目录进行读取或内存分配失败)并且全局变量 errno 包含有关错误的更多信息。

如上所述,scandir 函数的第四个参数必须是指向排序函数的指针。为了程序员的方便,GNU C 库包含对这个目的非常有帮助的函数的实现。

函数:int alphasort (const struct dirent **a, const struct dirent **b)

Preliminary: | MT-Safe locale | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

alphasort 函数的行为类似于 strcoll 函数(请参阅字符串/数组比较)。不同之处在于参数不是字符串指针,而是它们的类型 struct dirent **。

alphasort 的返回值小于、等于或大于零取决于两个条目 a 和 b 的顺序。

函数:int versionsort(const struct dirent **a, const struct dirent **b)

Preliminary: | MT-Safe locale | AS-Safe | AC-Safe | See POSIX Safety Concepts.

versionsort 函数与 alphasort 类似,只是它在内部使用 strverscmp 函数。

如果文件系统支持大文件,我们就不能再使用 scandir,因为 dirent 结构可能无法包含所有信息。LFS 提供了新的类型结构 dirent64。要使用它,我们需要一个新功能。

函数:int scandir64 (const char *dir, struct dirent64 ***namelist, int (*selector) (const struct dirent64 *), int (*cmp) (const struct dirent64 **, const struct dirent64 **))

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem fd | See POSIX Safety Concepts.

scandir64 函数的工作方式与 scandir 函数类似,只是它返回的目录条目由 struct dirent64 类型的元素描述。选择器指向的函数再次用于选择所需的条目,除了选择器现在必须指向一个采用 struct dirent64 * 参数的函数。

同样,cmp 函数应该期望它的两个参数是 struct dirent64 ** 类型。

由于 cmp 现在是不同类型的函数,因此无法为该参数提供函数 alphasort 和 versionsort。相反,我们提供了下面的两个替换函数。

函数:int alphasort64 (const struct dirent64 **a, const struct dirent **b)

Preliminary: | MT-Safe locale | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

alphasort64 函数的行为类似于 strcoll 函数(请参阅字符串/数组比较)。不同之处在于参数不是字符串指针,而是它们的类型 struct dirent64 **。

alphasort64 的返回值小于、等于或大于零取决于两个条目 a 和 b 的顺序。

函数:int versionsort64 (const struct dirent64 **a, const struct dirent64 **b)

Preliminary: | MT-Safe locale | AS-Safe | AC-Safe | See POSIX Safety Concepts.

versionsort64 函数类似于 alphasort64,只是它在内部使用了 strverscmp 函数。

重要的是不要混合使用 scandir 和 64 位比较函数,反之亦然。在某些系统上它可以工作,但在其他系统上它会惨遭失败。

2.2.7. 列出目录的简单程序,Mark II

Simple Program to List a Directory, Mark II

这是上面找到的目录列表器的修订版本(请参阅列出目录的简单程序)。使用 scandir 函数,我们可以避免直接处理目录内容的函数。调用后返回的条目可直接使用。

#include <stdio.h>
#include <dirent.h>

static int
one (const struct dirent *unused)
{
  return 1;
}

int
main (void)
{
  struct dirent **eps;
  int n;

  n = scandir ("./", &eps, one, alphasort);
  if (n >= 0)
    {
      int cnt;
      for (cnt = 0; cnt < n; ++cnt)
        puts (eps[cnt]->d_name);
    }
  else
    perror ("Couldn't open the directory");

  return 0;
}

请注意此示例中的简单选择器功能。由于我们想查看所有目录条目,我们总是返回 1。

2.2.8. 底层目录访问

Low-level Directory Access

基于流的目录功能不是 AS-Safe 并且不能在 vfork 之后使用。请参阅 POSIX 安全概念。下面的函数提供了可以在这些上下文中使用的替代方法。

目录数据是从由 open 函数创建的文件描述符中获取的,带有或不带有 O_DIRECTORY 标志。请参阅打开和关闭文件

函数:ssize_t getdents64 (int fd, void *buffer, size_t length)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

getdents64 函数从文件描述符 fd 中读取最多长度字节的目录条目数据,并将其存储到从缓冲区开始的字节数组中。

成功时,该函数返回写入缓冲区的字节数。如果 fd 已经在目录流的末尾,则此数字为零。出错时,该函数返回 -1 并将 errno 设置为适当的错误代码。

数据存储为 struct dirent64 记录序列,可以使用 d_reclen 成员对其进行遍历。缓冲区应该足够大以容纳最大可能的目录条目。请注意,某些文件系统支持长于 NAME_MAX 字节的文件名(例如,因为它们支持最多 255 个 Unicode 字符),因此建议缓冲区大小至少为 1024。

这个函数是 Linux 特有的。

2.3. 使用目录树

Working with Directory Trees

到目前为止描述的用于处理目录中文件的函数允许您逐位检索信息,或将所有文件作为一个组处理(请参阅scandir)。有时,处理目录及其包含的文件的整个层次结构很有用。X/Open 规范定义了两个函数来做到这一点。更简单的形式源自 System V 系统中的早期定义,因此此功能可用于 SVID 派生系统。原型和所需的定义可以在 ftw.h 头文件中找到。

该系列中有四个函数:ftw、nftw 及其 64 位对应函数 ftw64 和 nftw64。这些函数将指向适当类型的回调函数的指针作为其参数之一。

数据类型:__ftw_func_t

int (*) (const char *, const struct stat *, int)

赋予 ftw 函数的回调函数的类型。第一个参数指向文件名,第二个参数指向一个 struct stat 类型的对象,该对象是为第一个参数中命名的文件填写的。

最后一个参数是一个标志,提供有关当前文件的更多信息。它可以具有以下值:

FTW_F

该项目是普通文件或不属于以下类别之一的文件。这可能是特殊文件、套接字等。

FTW_D

该项目是一个目录。

FTW_NS

stat调用失败,所以第二个参数指向的信息无效。

FTW_DNR

该项目是无法读取的目录。

FTW_SL

该项目是一个符号链接。由于在 ftw 回调函数中看到此值通常会遵循符号链接,因此意味着引用的文件不存在。nftw 的情况有所不同。

该值仅在程序编译时使用在包含第一个标头之前定义的 _XOPEN_EXTENDED 才可用。最初的 SVID 系统没有符号链接。

如果源是用 _FILE_OFFSET_BITS == 64 编译的,那么这个类型实际上是 __ftw64_func_t 因为这个模式将 struct stat 更改为 struct stat64。

对于 LFS 接口和函数 ftw64 中的使用,头文件 ftw.h 定义了另一种函数类型。

数据类型:__ftw64_func_t

int (*) (const char *, const struct stat64 *, int)

该类型与回调函数的 __ftw_func_t 一样使用,但这次是从 ftw64 调用的。该函数的第二个参数是指向 struct stat64 类型变量的指针,该变量能够表示较大的值。

数据类型:__nftw_func_t

int (*) (const char *, const struct stat *, int, struct FTW *)

前三个参数与 __ftw_func_t 类型相同。然而,对于第三个参数,定义了一些附加值以允许更精细的区分:

FTW_DP

当前项目是一个目录,所有子目录都已经被访问和报告过。如果将 FTW_DEPTH 标志传递给 nftw(见下文),则返回此标志而不是 FTW_D。

FTW_SLN

当前项目是过时的符号链接。它指向的文件不存在。

回调函数的最后一个参数是指向带有一些额外信息的结构的指针,如下所述。

如果使用 _FILE_OFFSET_BITS == 64 编译源代码,则此类型实际上是 __nftw64_func_t,因为此模式将 struct stat 更改为 struct stat64。

对于 LFS 接口,还有一个可用的这种数据类型的变体,它必须与 nftw64 函数一起使用。

数据类型:__nftw64_func_t

int (*) (const char *, const struct stat64 *, int, struct FTW *)

该类型与回调函数的 __nftw_func_t 一样使用,但这次是从 nftw64 调用的。函数的第二个参数这次是指向 struct stat64 类型变量的指针,该变量能够表示较大的值。

数据类型:struct FTW

此结构中包含的信息有助于解释 name 参数并提供有关目录层次结构遍历的当前状态的一些信息。

int base

该值是文件名开头的第一个参数中传递给回调函数的字符串的偏移量。字符串的其余部分是文件的路径。如果在调用 nftw 时设置了 FTW_CHDIR 标志,则此信息尤其重要,因为当前目录就是当前项目所在的目录。

int level

在处理过程中,代码会跟踪找到当前文件的目录数量。对于初始目录中的文件,此嵌套级别从 0 开始(如果传递了文件,则初始文件的嵌套级别为零)。

函数:int ftw (const char *filename, __ftw_func_t func, int descriptors)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem fd | See POSIX Safety Concepts.

ftw 函数为在文件名指定的目录和下面所有目录中找到的每个项目调用参数 func 中给出的回调函数。如有必要,该函数会遵循符号链接,但不会两次处理一个项目。如果文件名不是目录,那么它本身就是返回给回调函数的唯一对象。

传递给回调函数的文件名是通过获取文件名参数并附加所有传递的目录的名称和本地文件名来构造的。所以回调函数可以使用这个参数来访问文件。ftw 还为文件调用 stat 并将该信息传递给回调函数。如果此 stat 调用不成功,则通过将回调函数的第三个参数设置为 FTW_NS 来指示失败。否则按照上面 __ftw_func_t 的说明中的说明设置。

回调函数应返回 0 以指示未发生错误并且应继续处理。如果回调函数发生错误或者想让ftw立即返回,回调函数可以返回0以外的值。这是停止函数的唯一正确方法。程序不得使用 setjmp 或类似技术从另一个地方继续。这将使 ftw 函数分配的资源未被释放。

ftw 的描述符参数指定允许使用多少个文​​件描述符。该函数运行得越快,它可以使用的描述符越多。对于目录层次结构中的每个级别,最多使用一个描述符,但对于非常深的级别,可能会超出进程或系统的打开文件描述符的任何限制。此外,多线程程序中的文件描述符限制适用于作为一个组的所有线程,因此最好对打开的描述符数量提供合理的限制。

如果所有回调函数调用返回 0 并且 ftw 执行的所有操作都成功,则 ftw 函数的返回值为 0。如果函数调用失败(除了对项目调用 stat),该函数将返回 -1。如果回调函数返回 0 以外的值,则该值作为 ftw 的返回值返回。

当在 32 位系统上使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数实际上是 ftw64,即 LFS 接口透明地替换旧接口。

函数:int ftw64 (const char *filename, __ftw64_func_t func, int descriptors)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem fd | See POSIX Safety Concepts.

此功能类似于 ftw,但它可以在具有大文件的文件系统上工作。使用 struct stat64 类型的变量报告文件信息,该变量通过引用传递给回调函数。

当在 32 位系统上使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数在名称 ftw 下可用,并透明地替换旧实现。

函数:int nftw (const char *filename, __nftw_func_t func, int descriptors, int flag)

Preliminary: | MT-Safe cwd | AS-Unsafe heap | AC-Unsafe mem fd cwd | See POSIX Safety Concepts.

nftw 函数的工作方式与 ftw 函数类似。他们为在目录文件名及以下目录中找到的所有项目调用回调函数 func。在 nftw 调用期间最多使用描述符文件描述符。

一个区别是回调函数是不同的类型。它是 struct FTW * 类型,并提供带有上述额外信息的回调函数。

第二个区别是 nftw 采用第四个参数,它是 0 或以下任何值的按位或组合。

FTW_PHYS

遍历目录时不遵循符号链接。而是使用回调函数的类型参数的 FTW_SL 值报告符号链接。如果符号链接引用的文件不存在,则返回 FTW_SLN。

FTW_MOUNT

回调函数仅对与 nftw 的文件名参数指定的目录位于同一挂载文件系统上的项目调用。

FTW_CHDIR

如果给定此标志,则在调用回调函数之前将当前工作目录更改为报告对象的目录。当 ntfw 最终返回时,当前目录恢复到原来的值。

FTW_DEPTH

如果指定了此选项,则在处理顶级目录本身之前处理其中的所有子目录和文件(深度优先处理)。这也意味着给回调函数的类型标志是 FTW_DP 而不是 FTW_D。

FTW_ACTIONRETVAL

如果指定了此选项,则回调的返回值将被不同地处理。如果回调返回 FTW_CONTINUE,则步行正常继续。FTW_STOP 表示步行停止,FTW_STOP 返回给调用者。如果 FTW_SKIP_SUBTREE 由带有 FTW_D 参数的回调返回,则跳过子树并继续遍历目录的下一个兄弟。如果回调返回 FTW_SKIP_SIBLINGS,则跳过当前条目的所有同级并在其父级中继续行走。如果设置了此选项,则回调不应返回其他返回值。此选项是 GNU 扩展。

返回值的计算方式与 ftw 相同。如果没有发生故障,nftw 返回 0,并且所有回调函数都返回 0。如果出现内部错误,例如内存问题,则返回值为 -1,并相应设置 errno。如果回调调用的返回值非零,则返回该值。

在 32 位系统上使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数实际上是 nftw64,即 LFS 接口透明地替换旧接口。

函数:int nftw64 (const char *filename, __nftw64_func_t func, int descriptors, int flag)

Preliminary: | MT-Safe cwd | AS-Unsafe heap | AC-Unsafe mem fd cwd | See POSIX Safety Concepts.

此功能类似于 nftw,但它可以在具有大文件的文件系统上工作。使用 struct stat64 类型的变量报告文件信息,该变量通过引用传递给回调函数。

当在 32 位系统上使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数在名称 nftw 下可用,并透明地替换旧实现。

2.4. 硬链接

Hard Links

在 POSIX 系统中,一个文件可以同时有多个名称。所有的名字都是一样真实的,没有一个比其他的更受欢迎。

要为文件添加名称,请使用链接功能。(新名称也称为文件的硬链接。)创建文件的新链接不会复制文件的内容;除了文件的现有名称之外,它只是简单地创建一个可以知道文件的新名称。

一个文件可以在多个目录中具有名称,因此文件系统的组织不是严格的层次结构或树。

在大多数实现中,不可能在多个文件系统中拥有指向同一个文件的硬链接。如果您尝试从另一个文件系统创建到文件的硬链接,但无法完成此操作,则链接会报告错误。

链接函数的原型在头文件 unistd.h 中声明。

函数:int link (const char *oldname, const char *newname)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

链接函数以新名称 newname 为由 oldname 命名的现有文件创建一个新链接。

如果成功,此函数返回值 0,失败时返回 -1。除了 oldname 和 newname 的常见文件名错误(请参阅文件名错误)之外,还为此函数定义了以下 errno 错误条件:

EACCES

不允许您写入要写入新链接的目录。

EEXIST

已经有一个名为 newname 的文件。如果要将此链接替换为新链接,则必须先明确删除旧链接。

EMLINK

指向以 oldname 命名的文件的链接已经太多了。(文件的最大链接数为 LINK_MAX;请参阅文件系统容量限制。)

ENOENT
oldname 命名的文件不存在。您无法链接到不存在的文件。

ENOSPC

包含新链接的目录或文件系统已满,无法扩展。

EPERM

在 GNU/Linux 和 GNU/Hurd 系统以及其他一些系统上,您不能创建指向目录的链接。许多系统只允许特权用户这样做。此错误用于报告问题。

EROFS

无法修改包含新链接的目录,因为它位于只读文件系统上。

EXDEV

newname 中指定的目录与现有文件位于不同的文件系统上。

EIO

尝试读取或写入文件系统时发生硬件错误。

函数:int linkat (int oldfd, const char *oldname, int newfd, const char *newname, int flags)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

linkat 函数类似于链接函数,不同之处在于它使用文件描述符(指目录)和路径名的组合来标识其源和目标。如果路径名不是绝对的,则相对于相应的文件描述符进行解析。特殊文件描述符 AT_FDCWD 表示当前目录。

flags 参数是以下标志的组合:

AT_SYMLINK_FOLLOW

如果由 oldfd 和 oldname 标识的源路径是符号链接,则 linkat 遵循符号链接并创建指向其目标的链接。如果未设置标志,则创建符号链接本身的链接;并非所有文件系统都支持这一点,在这种情况下,linkat 可能会失败。

AT_EMPTY_PATH

如果指定了此标志,则 oldname 可以是空字符串。在这种情况下,将创建一个指向由描述符 oldfd 表示的文件的新链接,该链接可能已使用 O_PATH 或 O_TMPFILE 打开。这个标志是一个 GNU 扩展。

2.5. 符号链接

Symbolic Links

GNU 系统支持软链接或符号链接。这是一种“文件”,本质上是指向另一个文件名的指针。与硬链接不同,符号链接可以不受限制地指向目录或跨文件系统。您还可以对不是任何文件名的名称进行符号链接。(在创建同名文件之前,打开此链接将失败。)同样,如果符号链接指向一个现有文件,该文件稍后将被删除,则符号链接将继续指向相同的文件名,即使该名称不再命名任何文件。

符号链接以它们的方式工作的原因是当您尝试打开链接时会发生特殊的事情。open 函数意识到您已经指定了链接的名称,读取链接中包含的文件名,然后打开该文件名。stat 函数同样作用于符号链接指向的文件,而不是链接本身。

相比之下,其他操作(例如删除或重命名文件)对链接本身进行操作。函数 readlink 和 lstat 也避免跟踪符号链接,因为它们的目的是获取有关链接的信息。链接,建立一个硬链接的函数,也是如此。它与符号链接建立了硬链接,这是人们很少需要的。

对于某些在文件上运行的功能,某些系统对解析路径名时遵循的符号链接数量有限制。如果存在限制,则会在 sys/param.h 头文件中发布。

宏:int MAXSYMLINKS

宏 MAXSYMLINKS 指定在返回 ELOOP 之前某个函数将遵循多少个符号链接。并非所有函数的行为都相同,并且此值与 sysconf 为 _SC_SYMLOOP 返回的值不同。事实上,sysconf 结果可以表明没有固定限制,尽管 MAXSYMLINKS 存在并且具有有限值。

本节中列出的大多数函数的原型都在 unistd.h 中。

函数:int symlink (const char *oldname, const char *newname)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

symlink 函数对名为 newname 的 oldname 进行符号链接。

符号链接的正常返回值为 0。返回值为 -1 表示错误。除了通常的文件名语法错误(请参阅文件名错误)之外,还为此函数定义了以下 errno 错误条件:

EEXIST

已经有一个名为 newname 的现有文件。

EROFS

文件 newname 将存在于只读文件系统中。

ENOSPC

无法扩展目录或文件系统以建立新链接。

EIO

在磁盘上读取或写入数据时发生硬件错误。

函数:ssize_t readlink (const char *filename, char *buffer, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

readlink 函数获取符号链接文件名的值。链接指向的文件名被复制到缓冲区中。此文件名字符串不是以空值结尾的;readlink 通常返回复制的字符数。size 参数指定要复制的最大字符数,通常是缓冲区的分配大小。

如果返回值等于大小,则无法判断是否有空间返回整个名称。所以做一个更大的缓冲区并再次调用 readlink 。这是一个例子:

char *
readlink_malloc (const char *filename)
{
  size_t size = 50;
  char *buffer = NULL;

  while (1)
    {
      buffer = xreallocarray (buffer, size, 2);
      size *= 2;
      ssize_t nchars = readlink (filename, buffer, size);
      if (nchars < 0)
        {
          free (buffer);
          return NULL;
        }
      if (nchars < size)
        return buffer;
    }
}

如果发生错误,则返回值 -1。除了通常的文件名错误(请参阅文件名错误)之外,还为此函数定义了以下 errno 错误条件:

EINVAL

命名文件不是符号链接。

EIO

在磁盘上读取或写入数据时发生硬件错误。

在某些情况下,需要解析所有符号链接以获取文件的真实名称,其中没有前缀命名符号链接,路径中没有文件名是 .或 … 例如,如果必须比较文件,在这种情况下不同的名称可以引用相同的 inode,这是可取的。

函数:char * canonicalize_file_name (const char *name)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem fd | See POSIX Safety Concepts.

canonicalize_file_name 函数返回以 name 命名的文件的绝对名称,其中不包含 .、… 组件,也不包含任何重复的路径分隔符 (/) 或符号链接。结果将作为函数的返回值传回,并在分配有 malloc 的内存块中。如果结果不再使用,则应通过调用 free 来释放内存。

如果缺少任何路径组件,该函数将返回一个 NULL 指针。如果路径长度达到或超过 PATH_MAX 个字符,这也是返回的内容。在任何情况下都会相应地设置 errno。

ENAMETOOLONG

结果路径太长。此错误仅发生在对文件名长度有限制的系统上。

EACCES

至少有一个路径组件不可读。

ENOENT

输入文件名是空的。

ENOENT

至少有一个路径组件不存在。

ELOOP

超过 MAXSYMLINKS 多个符号链接已被遵循。

该函数是 GNU 扩展,在 stdlib.h 中声明。

Unix 标准包含一个与 canonicalize_file_name 不同的类似函数,因为用户必须提供放置结果的缓冲区。

函数:char * realpath (const char *restrict name, char *restrict resolved)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem fd | See POSIX Safety Concepts.

对解析参数为 NULL 的 realpath 的调用的行为与 canonicalize_file_name 完全相同。该函数为文件名分配一个缓冲区并返回一个指向它的指针。如果resolved 不为NULL,则它指向一个缓冲区,结果将复制到该缓冲区中。分配一个足够大的缓冲区是调用者的责任。在定义 PATH_MAX 的系统上,这意味着缓冲区必须足够大以容纳此大小的路径名。对于对路径名长度没有限制的系统,无法满足要求,程序不应调用 realpath,而第二个参数为 NULL。

另一个区别是,如果函数返回 NULL 并且 errno 设置为 EACCES 或 ENOENT,则解析的缓冲区(如果非零)将包含路径组件中不存在或不可读的部分。

此函数在 stdlib.h 中声明。

使用此功能的好处是使用范围更广。缺点是它会在对文件名长度没有限制的系统上报告长路径失败。

2.6. 删除文件

Deleting Files

您可以使用取消链接或删除来删除文件。

删除实际上是删除一个文件名。如果这是文件的唯一名称,则该文件也将被删除。如果该文件有其他剩余名称(请参阅硬链接),它仍然可以在这些名称下访问。

函数:int unlink (const char *filename)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

unlink 函数删除文件名filename。如果这是文件的唯一名称,则文件本身也会被删除。(实际上,如果任何进程在发生这种情况时打开了文件,则会推迟删除,直到所有进程都关闭了文件。)

函数 unlink 在头文件 unistd.h 中声明。

此函数在成功完成时返回 0,在错误时返回 -1。除了通常的文件名错误(请参阅文件名错误)之外,还为此函数定义了以下 errno 错误条件:

EACCES

要从中删除文件的目录的写入权限被拒绝,或者该目录设置了粘性位并且您不拥有该文件。

EBUSY

此错误表明系统正在以无法取消链接的方式使用该文件。例如,如果文件名指定了文件系统的根目录或挂载点,您可能会看到此错误。

ENOENT

要删除的文件名不存在。

EPERM

在某些系统上,unlink 不能用于删除目录的名称,或者至少只能由特权用户以这种方式使用。为避免此类问题,请使用 rmdir 删除目录。(在 GNU/Linux 和 GNU/Hurd 系统上,取消链接永远不能删除目录的名称。)

EROFS

包含要删除的文件名的目录位于只读文件系统上,无法修改。

函数:int rmdir (const char *filename)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

rmdir 函数删除一个目录。目录必须为空才能删除;换句话说,它只能包含 .和 …

在大多数其他方面,rmdir 的行为类似于 unlink。为 rmdir 定义了两个额外的 errno 错误条件:

ENOTEMPTY

EEXIST

要删除的目录不为空。

这两个错误代码是同义词;有些系统使用一个,有些系统使用另一个。GNU/Linux 和 GNU/Hurd 系统总是使用 ENOTEMPTY。

此函数的原型在头文件 unistd.h 中声明。

函数:int remove (const char *filename)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

这是用于删除文件的 ISO C 函数。它的作用类似于文件的 unlink 和目录的 rmdir。在 stdio.h 中声明了 remove。

2.7. 重命名文件

Renaming Files

rename 函数用于更改文件的名称。

函数:int rename (const char *oldname, const char *newname)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

rename 函数将文件 oldname 重命名为 newname。以前以 oldname 名称可访问的文件后来可作为 newname 访问。(如果文件有除 oldname 之外的任何其他名称,它会继续使用这些名称。)

包含名称 newname 的目录必须与包含名称 oldname 的目录位于同一文件系统上。

重命名的一种特殊情况是 oldname 和 newname 是同一文件的两个名称。处理这种情况的一致方法是删除 oldname。但是,在这种情况下,POSIX 要求 rename 什么都不做并报告成功——这是不一致的。我们不知道您的操作系统会做什么。

如果 oldname 不是目录,则在重命名操作期间删除任何名为 newname 的现有文件。但是,如果 newname 是目录的名称,则在这种情况下重命名会失败。

如果 oldname 是一个目录,那么 newname 必须不存在,或者它必须命名一个空目录。在后一种情况下,首先删除名为 newname 的现有目录。名称 newname 不能指定要重命名的目录 oldname 的子目录。

rename 的一个有用特性是 newname 的含义从任何以前存在的以该名称命名的文件“原子地”更改为它的新含义(即,称为 oldname 的文件)。在旧含义和新含义“之间”没有新名称不存在的瞬间。如果在操作过程中出现系统崩溃,则两个名称都可能仍然存在;但是如果 newname 存在的话,它将永远是完整的。

如果重命名失败,则返回 -1。除了通常的文件名错误(请参阅文件名错误)之外,还为此函数定义了以下 errno 错误条件:

EACCES

包含新名称或旧名称的目录之一拒绝写入权限;或 newname 和 oldname 是目录,并且其中之一的写权限被拒绝。

EBUSY

系统正在使用由 oldname 或 newname 命名的目录,以防止重命名工作。这包括作为文件​​系统挂载点的目录,以及作为进程当前工作目录的目录。

ENOTEMPTY

EEXIST

目录 newname 不为空。GNU/Linux 和 GNU/Hurd 系统总是为此返回 ENOTEMPTY,但其他一些系统返回 EEXIST。

EINVAL

oldname 是一个包含 newname 的目录。

EISDIR

newname 是一个目录,但 oldname 不是。

EMLINK

newname 的父目录将有太多的链接(条目)。

ENOENT

文件旧名不存在。

ENOSPC

包含 newname 的目录没有空间容纳另一个条目,文件系统中也没有空间来扩展它。

EROFS

该操作将涉及写入只读文件系统上的目录。

EXDEV

两个文件名 newname 和 oldname 位于不同的文件系统上。

2.8. 创建目录

Creating Directories

目录是使用 mkdir 函数创建的。(还有一个 shell 命令 mkdir 可以做同样的事情。)

函数:int mkdir (const char *filename, mode_t mode)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

mkdir 函数创建一个名为 filename 的新的空目录。

参数 mode 指定新目录文件的文件权限。有关这方面的更多信息,请参阅访问权限的模式位

返回值 0 表示成功完成,-1 表示失败。除了通常的文件名语法错误(请参阅文件名错误)之外,还为此函数定义了以下 errno 错误条件:

EACCES

拒绝添加新目录的父目录的写入权限。

EEXIST

已存在名为 filename 的文件。

EMLINK

父目录的链接(条目)过多。

设计良好的文件系统永远不会报告此错误,因为它们允许的链接超出了您的磁盘可能容纳的数量。但是,您仍然必须考虑到此错误的可能性,因为它可能是由于网络访问另一台机器上的文件系统而导致的。

ENOSPC

文件系统没有足够的空间来创建新目录。

EROFS

正在创建的目录的父目录位于只读文件系统上,无法修改。

要使用这个函数,你的程序应该包含头文件 sys/stat.h。

2.9. 文件属性

File Attributes

当您对文件发出“ls -l”shell 命令时,它会为您提供有关文件大小、谁拥有它、上次修改时间等信息。这些被称为文件属性,并与 文件本身,而不是它的特定名称。

本节包含有关如何查询和修改文件属性的信息。

2.9.1. 文件属性的含义

The meaning of the File Attributes

当您读取文件的属性时,它们会以一个名为 struct stat 的结构返回。本节介绍属性的名称、它们的数据类型以及它们的含义。有关读取文件属性的函数,请参阅读取文件的属性

头文件 sys/stat.h 声明了本节中定义的所有符号。

数据类型:struct stat

stat 结构类型用于返回有关文件属性的信息。它至少包含以下成员:

mode_t st_mode

指定文件的模式。这包括文件类型信息(请参阅测试文件类型)和文件权限位(请参阅访问权限的模式位)。

ino_t st_ino

文件序列号,用于将此文件与同一设备上的所有其他文件区分开来。

dev_t st_dev

标识包含该文件的设备。st_ino 和 st_dev 一起唯一标识文件。但是,st_dev 值在重新启动或系统崩溃时不一定一致。

nlink_t st_nlink

文件的硬链接数。此计数跟踪有多少目录具有此文件的条目。如果计数曾经减为零,则一旦没有进程仍然保持打开文件,文件本身就会被丢弃。符号链接不计入总数。

uid_t st_uid

文件所有者的用户 ID。请参阅文件所有者

gid_t st_gid

文件的组 ID。请参阅文件所有者。

off_t st_size

这指定了常规文件的大小(以字节为单位)。对于真正是设备的文件,此字段通常没有意义。对于符号链接,这指定了链接引用的文件名的长度。

time_t st_atime

这是文件的最后访问时间。请参阅文件时间

unsigned long int st_atime_usec

这是文件的最后访问时间的小数部分。请参阅文件时间。

time_t st_mtime

这是最后一次修改文件内容的时间。请参阅文件时间。

unsigned long int st_mtime_usec

这是最后一次修改文件内容的时间的小数部分。请参阅文件时间。

time_t st_ctime

这是最后一次修改文件属性的时间。请参阅文件时间。

unsigned long int st_ctime_usec

这是最后一次修改文件属性的时间的小数部分。请参阅文件时间。

blkcnt_t st_blocks

这是文件占用的磁盘空间量,以 512 字节块为单位。

磁盘块的数量与文件的大小并不严格成正比,原因有两个:文件系统可能会使用一些块来保存内部记录;并且文件可能是稀疏的——它可能有包含零但实际上不占用磁盘空间的“洞”。

通过将此值与 st_size 进行比较,您可以(大致)判断文件是否稀疏,如下所示:

(st.st_blocks * 512 < st.st_size)

这个测试并不完美,因为稍微稀疏的文件可能根本不会被检测为稀疏。对于实际应用,这不是问题。

unsigned int st_blksize

读取或写入此文件的最佳块大小,以字节为单位。您可以使用此大小来分配缓冲区空间以读取或写入文件。(这与 st_blocks 无关。)

大文件支持 (LFS) 的扩展要求,即使在 32 位机器上,也需要能够处理高达 2^63 的文件大小的类型。因此,需要对 struct stat 进行新定义。

数据类型:struct stat64

此类型的成员与 struct stat 中的成员相同且名称相同。唯一的区别是成员 st_ino、st_size 和 st_blocks 具有不同的类型以支持更大的值。

mode_t st_mode

指定文件的模式。这包括文件类型信息(请参阅测试文件类型)和文件权限位(请参阅访问权限的模式位)。

ino64_t st_ino

文件序列号,用于将此文件与同一设备上的所有其他文件区分开来。

dev_t st_dev

标识包含该文件的设备。st_ino 和 st_dev 一起唯一标识文件。但是,st_dev 值在重新启动或系统崩溃时不一定一致。

nlink_t st_nlink

文件的硬链接数。此计数跟踪有多少目录具有此文件的条目。如果计数曾经减为零,则一旦没有进程仍然保持打开文件,文件本身就会被丢弃。符号链接不计入总数。

uid_t st_uid

文件所有者的用户 ID。请参阅文件所有者

gid_t st_gid

文件的组 ID。请参阅文件所有者。

off64_t st_size

这指定了常规文件的大小(以字节为单位)。对于真正是设备的文件,此字段通常没有意义。对于符号链接,这指定了链接引用的文件名的长度。

time_t st_atime

这是文件的最后访问时间。请参阅文件时间

unsigned long int st_atime_usec

这是文件的最后访问时间的小数部分。请参阅文件时间。

time_t st_mtime

这是最后一次修改文件内容的时间。请参阅文件时间。

unsigned long int st_mtime_usec

这是最后一次修改文件内容的时间的小数部分。请参阅文件时间。

time_t st_ctime

这是最后一次修改文件属性的时间。请参阅文件时间。

unsigned long int st_ctime_usec

这是最后一次修改文件属性的时间的小数部分。请参阅文件时间。

blkcnt64_t st_blocks

这是文件占用的磁盘空间量,以 512 字节块为单位。

unsigned int st_blksize

读写此文件的最佳块大小,以字节为单位。您可以使用此大小来分配缓冲区空间以读取和写入文件。(这与 st_blocks 无关。)

一些文件属性具有专门为这些属性而存在的特殊数据类型名称。(它们都是您熟悉和喜爱的知名整数类型的别名。)这些 typedef 名称在头文件 sys/types.h 和 sys/stat.h 中定义。这是他们的清单。

数据类型:mode_t

这是用于表示文件模式的整数数据类型。在 GNU C 库中,这是一个不比 unsigned int 窄的无符号类型。

数据类型:ino_t

这是一个无符号整数类型,用于表示文件序列号。(在 Unix 术语中,这些有时称为 inode 编号。)在 GNU C 库中,这种类型并不比 unsigned int 更窄。

如果源代码使用 _FILE_OFFSET_BITS == 64 编译,则此类型将透明地替换为 ino64_t。

数据类型:ino64_t

这是一个无符号整数类型,用于表示在 LFS 中使用的文件序列号。在 GNU C 库中,这种类型并不比 unsigned int 更窄。

当使用 _FILE_OFFSET_BITS == 64 编译时,此类型在名称 ino_t 下可用。

数据类型:dev_t

这是一种用于表示文件设备号的算术数据类型。在 GNU C 库中,这是一个不小于 int 的整数类型。

数据类型:nlink_t

这是一个整数类型,用于表示文件链接计数。

数据类型:blkcnt_t

这是一个有符号整数类型,用于表示块计数。在 GNU C 库中,这种类型并不比 int 更窄。

如果源代码使用 _FILE_OFFSET_BITS == 64 编译,则此类型透明地替换为 blkcnt64_t。

数据类型:blkcnt64_t

这是一个有符号整数类型,用于表示在 LFS 中使用的块计数。在 GNU C 库中,这种类型并不比 int 更窄。

当使用 _FILE_OFFSET_BITS == 64 编译时,此类型在名称 blkcnt_t 下可用。

2.9.2. 读取文件的属性

Reading the Attributes of a File

要检查文件的属性,请使用函数 stat、fstat 和 lstat。它们在 struct stat 对象中返回属性信息。所有三个函数都在头文件 sys/stat.h 中声明。

函数:int stat (const char *filename, struct stat *buf)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

stat函数返回buf指向的结构中由filename命名的文件的属性信息。

如果 filename 是符号链接的名称,则您获得的属性描述链接指向的文件。如果链接指向不存在的文件名,则 stat 无法报告不存在的文件。

如果操作成功则返回值为 0,如果失败则返回 -1。除了通常的文件名错误(参见文件名错误,此函数定义了以下 errno 错误条件:

ENOENT

以 filename 命名的文件不存在。

当使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数实际上是 stat64,因为 LFS 接口透明地替换了正常的实现。

函数:int stat64 (const char *filename, struct stat64 *buf)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数类似于 stat,但它也可以在 32 位系统上处理大于 2^31 字节的文件。为了能够做到这一点,结果存储在 struct stat64 类型的变量中,buf 必须指向该变量。

当使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数在名称 stat 下可用,因此透明地替换了 32 位机器上小文件的接口。

函数:int fstat (int filedes, struct stat *buf)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

fstat 函数类似于 stat,不同之处在于它将打开的文件描述符作为参数而不是文件名。请参阅底层输入/输出

与 stat 一样,fstat 在成功时返回 0,在失败时返回 -1。为 fstat 定义了以下 errno 错误条件:

EBADF

filedes 参数不是有效的文件描述符。

当使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数实际上是 fstat64,因为 LFS 接口透明地替换了正常的实现。

函数:int fstat64 (int filedes, struct stat64 *buf)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数类似于 fstat,但能够在 32 位平台上处理大文件。对于大文件,文件描述符文件应通过 open64 或 creat64 获得。buf 指针指向 struct stat64 类型的变量,该变量能够表示较大的值。

当使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数在名称 fstat 下可用,因此透明地替换了 32 位机器上小文件的接口。

函数:int lstat (const char *filename, struct stat *buf)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

lstat 函数与 stat 类似,只是它不遵循符号链接。如果 filename 是符号链接的名称,lstat 返回有关链接本身的信息;否则 lstat 像 stat 一样工作。请参阅符号链接

当使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数实际上是 lstat64,因为 LFS 接口透明地替换了正常的实现。

函数:int lstat64 (const char *filename, struct stat64 *buf)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数类似于 lstat,但它也可以在 32 位系统上处理大于 2^31 字节的文件。为了能够做到这一点,结果存储在 struct stat64 类型的变量中,buf 必须指向该变量。

当使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数在名称 lstat 下可用,因此透明地替换了 32 位机器上小文件的接口。

2.9.3. 测试文件类型

Testing the Type of a File

文件模式,存储在文件属性的st_mode字段中,包含两种信息:文件类型代码和访问权限位。本节只讨论类型代码,您可以使用它来判断文件是目录、套接字、符号链接等。有关访问权限的详细信息,请参阅访问权限的模式位

有两种方法可以在文件模式下访问文件类型信息。首先,对于每种文件类型,都有一个谓词宏检查给定的文件模式并返回它是否属于该类型。其次,您可以屏蔽文件模式的其余部分以仅保留文件类型代码,并将其与每种支持的文件类型的常量进行比较。

本节中列出的所有符号都在头文件 sys/stat.h 中定义。

以下谓词宏测试文件的类型,给定值 m,它是 stat 在该文件上返回的 st_mode 字段:

宏:int S_ISDIR (mode_t m)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

如果文件是目录,则此宏返回非零值。

宏:int S_ISCHR (mode_t m)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

如果文件是字符特殊文件(终端等设备),此宏返回非零值。

宏:int S_ISBLK (mode_t m)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

如果文件是块特殊文件(如磁盘的设备),此宏返回非零值。

宏:int S_ISREG (mode_t m)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

如果文件是常规文件,则此宏返回非零值。

宏:int S_ISFIFO (mode_t m)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

如果文件是 FIFO 特殊文件或管道,则此宏返回非零值。请参阅管道和 FIFO

宏:int S_ISLNK (mode_t m)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

如果文件是符号链接,则此宏返回非零值。请参阅符号链接

宏:int S_ISSOCK (mode_t m)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

如果文件是套接字,则此宏返回非零值。请参阅套接字

支持另一种测试文件类型的非 POSIX 方法以与 BSD 兼容。该模式可以与 S_IFMT 进行按位与运算以提取文件类型代码,并与适当的常量进行比较。例如,

S_ISCHR (mode)

相当于:

((mode & S_IFMT) == S_IFCHR)

宏:int S_IFMT

这是一个位掩码,用于从模式值中提取文件类型代码。

这些是不同文件类型代码的符号名称:

S_IFDIR

这是目录文件的文件类型常量。

S_IFCHR

这是面向字符的设备文件的文件类型常量。

S_IFBLK

这是面向块的设备文件的文件类型常量。

S_IFREG

这是常规文件的文件类型常量。

S_IFLNK

这是符号链接的文件类型常量。

S_IFSOCK

这是套接字的文件类型常量。

S_IFIFO

这是 FIFO 或管道的文件类型常量。

POSIX.1b 标准引入了更多的对象,这些对象可能可以作为文件系统中的对象来实现。它们是消息队列、信号量和共享内存对象。为了允许将这些对象与其他文件区分开来,POSIX 标准引入了三个新的测试宏。但与其他宏不同,它们不将 st_mode 字段的值作为参数。相反,他们期望一个指向整个 struct stat 结构的指针。

宏:int S_TYPEISMQ (struct stat *s)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

如果系统将 POSIX 消息队列实现为不同的对象并且文件是消息队列对象,则此宏返回非零值。在所有其他情况下,结果为零。

宏:int S_TYPEISSEM (struct stat *s)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

如果系统将 POSIX 信号量实现为不同的对象并且文件是信号量对象,则此宏返回非零值。在所有其他情况下,结果为零。

宏:int S_TYPEISSHM (struct stat *s)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

如果系统将 POSIX 共享内存对象实现为不同对象并且文件是共享内存对象,则此宏返回非零值。在所有其他情况下,结果为零。

2.9.4. 文件所有者

File Owner

每个文件都有一个所有者,它是系统上定义的注册用户名之一。每个文件还有一个组,它是定义的组之一。文件所有者通常可以帮助您显示谁编辑了文件(尤其是当您使用 GNU Emacs 进行编辑时),但其主要目的是用于访问控制。

文件所有者和组在确定访问权限方面发挥作用,因为文件具有一组所有者的访问权限位,另一组适用于属于该文件组的用户,第三组适用于其他所有人。有关如何根据此数据决定访问权限的详细信息,请参阅如何决定您对文件的访问权限

创建文件时,其所有者设置为创建它的进程的有效用户 ID(请参阅进程的角色)。文件的组 ID 可以设置为进程的有效组 ID,也可以设置为包含文件的目录的组 ID,具体取决于存储文件的系统。当您访问远程文件系统时,它会根据自己的规则运行,而不是根据您的程序运行所在的系统。因此,您的程序必须准备好遇到任何一种行为,无论您在哪种系统上运行它。

您可以使用 chown 函数更改现有文件的所有者和/或组所有者。这是 chown 和 chgrp shell 命令的原语。

这个函数的原型在 unistd.h 中声明。

函数:int chown (const char *filename, uid_t owner, gid_t group)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

chown 函数将文件文件名的所有者更改为所有者,并将其组所有者更改为组。

在某些系统上更改文件的所有者会清除 set-user-ID 和 set-group-ID 权限位。(这是因为这些位可能不适合新所有者。)其他文件权限位不会更改。

成功时返回值为 0,失败时返回值为 -1。除了通常的文件名错误(请参阅文件名错误)之外,还为此函数定义了以下 errno 错误条件:

EPERM

此过程缺乏进行请求更改的权限。

只有特权用户或文件所有者才能更改文件的组。在大多数文件系统上,只有特权用户才能更改文件所有者;如果您当前是所有者,某些文件系统允许您更改所有者。当您访问远程文件系统时,您遇到的行为是由实际保存该文件的系统决定的,而不是由运行您的程序的系统决定的。

有关 _POSIX_CHOWN_RESTRICTED 宏的信息,请参阅文件支持中的可选功能

EROFS

该文件位于只读文件系统上。

函数:int fchown (int filedes, uid_t owner, gid_t group)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

这与 chown 类似,不同之处在于它使用描述符文件更改了打开文件的所有者。

fchown 的返回值为 0 表示成功,-1 表示失败。为此函数定义了以下 errno 错误代码:

EBADF

filedes 参数不是有效的文件描述符。

EINVAL

filedes 参数对应于管道或套接字,而不是普通文件。

EPERM

此过程缺乏进行请求更改的权限。有关详细信息,请参阅上面的 chmod。

EROFS

该文件驻留在只读文件系统上。

2.9.5. 访问权限的模式位

The Mode Bits for Access Permission

文件模式,存储在文件属性的st_mode字段中,包含两种信息:文件类型代码和访问权限位。本节仅讨论访问权限位,它控制谁可以读取或写入文件。有关文件类型代码的信息,请参阅测试文件类型

本节中列出的所有符号都在头文件 sys/stat.h 中定义。

这些符号常量是为控制文件访问权限的文件模式位定义的:

S_IRUSR

S_IREAD

文件所有者的读取权限位。在许多系统上,该位是 0400。S_IREAD 是为 BSD 兼容性提供的过时同义词。

S_IWUSR

S_IWRITE

文件所有者的写权限位。通常为 0200。S_IWRITE 是为 BSD 兼容性提供的过时同义词。

S_IXUSR

S_IEXEC

执行(对于普通文件)或搜索(对于目录)文件所有者的权限位。通常为 0100。S_IEXEC 是为 BSD 兼容性提供的过时同义词。

S_IRWXU

这等效于“(S_IRUSR | S_IWUSR | S_IXUSR)”。

S_IRGRP

文件组所有者的读取权限位。通常为 040。

S_IWGRP

文件组所有者的写入权限位。通常为 020。

S_IXGRP

文件组所有者的执行或搜索权限位。通常为 010。

S_IRWXG

这等效于“(S_IRGRP | S_IWGRP | S_IXGRP)”。

S_IROTH

其他用户的读取权限位。通常是 04。

S_IWOTH

其他用户的写权限位。通常是 02。

S_IXOTH

执行或搜索其他用户的权限位。通常为 01。

S_IRWXO

这等效于“(S_IROTH | S_IWOTH | S_IXOTH)”。

S_ISUID

这是执行位上的 set-user-ID,通常为 04000。请参阅应用程序如何更改角色。

S_ISGID

这是执行位上的 set-group-ID,通常为 02000。请参阅应用程序如何更改角色。

S_ISVTX

这是粘性位,通常为 01000。

对于目录,仅当您拥有该文件时,它才允许删除该目录中的文件。通常,用户要么可以删除目录中的所有文件,要么不能删除任何文件(取决于用户是否具有目录的写权限)。同样的限制适用——您必须同时拥有目录的写入权限并拥有要删除的文件。一个例外是目录的所有者可以删除目录中的任何文件,无论是谁拥有它(前提是所有者已授予自己对该目录的写权限)。这通常用于 /tmp 目录,任何人都可以在其中创建文件,但不能删除其他用户创建的文件。

最初可执行文件上的粘性位修改了系统的交换策略。通常,当程序终止时,它在核心中的页面会立即被释放和重用。如果在可执行文件上设置了粘性位,系统会将页面保留在核心中一段时间​​,就好像程序仍在运行一样。这对于可能连续运行多次的程序是有利的。这种用法在现代系统中已过时。当一个程序终止时,只要系统中没有内存不足,它的页面就会一直保留在核心中。当程序下一次运行时,如果自上次运行以来没有出现短缺,它的页面仍将在核心中。

在一些现代系统中,粘滞位对可执行文件没有用处,您根本无法为非目录设置该位。如果您尝试,chmod 会因 EFTYPE 失败;请参阅分配文件权限

某些系统(尤其是 SunOS)对粘性位还有另一种用途。如果在不可执行的文件上设置了粘滞位,则意味着相反:从不缓存该文件的页面。它的主要用途是用于 NFS 服务器机器上的文件,这些文件用作无盘客户端机器的交换区域。这个想法是文件的页面将被缓存在客户端的内存中,因此第二次缓存它们是浪费服务器的内存。使用这种用法,粘性位还意味着文件系统可能无法可靠地将文件的修改时间记录到磁盘上(这个想法是没有人关心交换文件)。

该位仅在 BSD 系统(以及从它们派生的系统)上可用。因此,必须使用 _GNU_SOURCE 功能选择宏,或者不定义任何功能测试宏来获得定义(请参阅功能测试宏)。

上表中列出了符号的实际位值,因此您可以在调试程序时解码文件模式值。这些位值对于大多数系统都是正确的,但不能保证。

警告:为文件权限写明确的数字是不好的做法。它不仅不可移植,而且还要求每个阅读您的程序的人都记住这些位的含义。为了使您的程序干净,请使用符号名称。

2.9.6. 如何决定您对文件的访问

How Your Access to a File is Decided

回想一下,操作系统通常根据进程的有效用户和组 ID 及其补充组 ID 以及文件的所有者、组和权限位来决定文件的访问权限。这些概念在进程角色中进行了详细讨论。

如果进程的有效用户 ID 与文件的所有者用户 ID 匹配,则读取、写入和执行/搜索的权限由相应的“用户”(或“所有者”)位控制。同样,如果进程的任何有效组 ID 或补充组 ID 与文件的组所有者 ID 匹配,则权限由“组”位控制。否则,权限由“其他”位控制。

特权用户,如“root”,可以访问任何文件,而不管其权限位如何。作为一种特殊情况,即使是特权用户也可以执行文件,必须至少设置一个执行位。

2.9.7. 分配文件权限

Assigning File Permissions

用于创建文件的原始函数(例如,open 或 mkdir)采用 mode 参数,该参数指定文件权限以赋予新创建的文件。此模式在使用之前由进程的文件创建掩码或 umask 修改。

在文件创建掩码中设置的位标识对于新创建的文件总是要禁用的权限。例如,如果您在掩码中设置了所有“其他”访问位,那么“其他”类别中的进程根本无法访问新创建的文件,即使传递给 create 函数的模式参数允许这种访问。换句话说,文件创建掩码是您要授予的普通访问权限的补充。

创建文件的程序通常会指定一个模式参数,其中包括对特定文件有意义的所有权限。对于普通文件,这通常是所有类别用户的读写权限。然后,这些权限将按照各个用户自己的文件创建掩码的指定进行限制。

要更改给定名称的现有文件的权限,请调用 chmod。此函数使用指定的权限位并忽略文件创建掩码。

在正常使用中,文件创建掩码由用户的登录 shell(使用 umask shell 命令)初始化,并由所有子进程继承。应用程序通常不需要担心文件创建掩码。它会自动做它应该做的事情。

当您的程序需要创建文件并绕过 umask 以获得访问权限时,最简单的方法是在打开文件后使用 fchmod,而不是更改 umask。实际上,更改 umask 通常只能通过 shell 来完成。他们使用 umask 函数。

本节中的函数在 sys/stat.h 中声明。

函数:mode_t umask (mode_t mask)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

umask函数将当前进程的文件创建掩码设置为mask,并返回文件创建掩码之前的值。

这是一个示例,显示如何使用 umask 读取掩码而不永久更改它:

mode_t
read_umask (void)
{
  mode_t mask = umask (0);
  umask (mask);
  return mask;
}

但是,在 GNU/Hurd 系统上,如果您只想读取掩码值,最好使用 getumask,因为它是可重入的。

函数:mode_t getumask (void)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

返回当前进程的文件创建掩码的当前值。此功能是 GNU 扩展,仅在 GNU/Hurd 系统上可用。

函数:int chmod (const char *filename, mode_t mode)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

chmod 函数将文件名命名的文件的访问权限位设置为模式。

如果 filename 是符号链接,chmod 更改链接指向的文件的权限,而不是链接本身的权限。

如果成功,此函数返回 0,否则返回 -1。除了通常的文件名错误(请参阅文件名错误)之外,还为此函数定义了以下 errno 错误条件:

ENOENT

指定的文件不存在。

EPERM

此进程无权更改此文件的访问权限。只有文件的所有者(由进程的有效用户 ID 判断)或特权用户才能更改它们。

EROFS

该文件驻留在只读文件系统上。

EFTYPE

模式设置了 S_ISVTX 位(“粘性位”),并且命名文件不是目录。有些系统不允许在非目录文件上设置粘滞位,而有些则允许(并且只有一些系统为非目录文件的位赋予有用的含义)。

您只能在粘性位对非目录文件没有用处的系统上获得 EFTYPE,因此在模式下清除该位并再次调用 chmod 始终是安全的。有关粘性位的完整详细信息,请参阅访问权限的模式位

函数:int fchmod (int fields, mode_t mode)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

这类似于 chmod,不同之处在于它更改了由filedes 提供的当前打开文件的权限。

fchmod 的返回值为 0 表示成功,-1 表示失败。为此函数定义了以下 errno 错误代码:

EBADF

filedes 参数不是有效的文件描述符。

EINVAL

filedes 参数对应于管道或套接字,或其他没有真正访问权限的东西。

EPERM

此进程无权更改此文件的访问权限。只有文件的所有者(由进程的有效用户 ID 判断)或特权用户才能更改它们。

EROFS

该文件驻留在只读文件系统上。

2.9.8. 测试访问文件的权限

Testing Permission to Access a File

在某些情况下,希望允许程序访问文件或设备,即使在授予用户权限的情况下这是不可能的。一种可能的解决方案是设置程序文件的 setuid 位。如果启动这样的程序,则进程的有效用户 ID 将更改为程序文件所有者的有效用户 ID。因此,为了允许对像 /etc/passwd 这样的文件进行写访问,这些文件通常只能由超级用户编写,修改程序必须由 root 拥有,并且必须设置 setuid 位。

但除了程序旨在更改的文件之外,不应允许用户访问任何他/她无论如何都无权访问的文件。因此,程序必须在读取或写入文件之前显式检查用户是否具有对文件的必要访问权限。

为此,请使用函数 access,它根据进程的真实用户 ID 而不是有效用户 ID 检查访问权限。(setuid 特性不会改变真实的用户 ID,因此它反映了实际运行程序的用户。)

还有另一种方法可以检查此访问,这很容易描述,但很难使用。这是为了检查文件模式位并模拟系统自己的访问计算。这种方法是不可取的,因为许多系统都有额外的访问控制功能;您的程序不能移植地模仿它们,并且您不想尝试跟踪不同系统具有的各种功能。使用访问很简单,并且会自动执行适合您使用的系统的任何操作。

access 仅适用于 setuid 程序。非 setuid 程序将始终使用有效 ID 而不是真实 ID。

本节中的符号在 unistd.h 中声明。

函数:int access(const char *filename, int how)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

access 函数检查是否可以通过 how 参数指定的方式访问由 filename 命名的文件。how 参数可以是标志 R_OK、W_OK、X_OK 或存在性测试 F_OK 的按位或。

此函数使用调用进程的真实用户 ID 和组 ID,而不是有效 ID,来检查访问权限。因此,如果您使用 setuid 或 setgid 程序中的函数(请参阅应用程序如何更改角色),它会提供与实际运行程序的用户相关的信息。

如果允许访问,则返回值为 0,否则返回 -1。(换句话说,作为一个谓词函数,如果请求的访问被拒绝,访问返回真。)

除了通常的文件名错误(请参阅文件名错误)之外,还为此函数定义了以下 errno 错误条件:

EACCES

how 指定的访问被拒绝。

ENOENT

该文件不存在。

EROFS

为只读文件系统上的文件请求写权限。

这些宏在头文件 unistd.h 中定义,用作访问函数的 how 参数。这些值是整数常量。

宏:int R_OK

标志意义测试读取权限。

宏:int W_OK

标志意义测试写权限。

宏:int X_OK

执行/搜索权限的标志意义测试。

宏:int F_OK

标志意义测试文件是否存在。

2.9.9. 文件时间

File Times

每个文件都有三个与之关联的时间戳:访问时间、修改时间和属性修改时间。这些对应于 stat 结构的 st_atime、st_mtime 和 st_ctime 成员;请参阅文件属性

所有这些时间都以日历时间格式表示为 time_t 对象。此数据类型在 time.h 中定义。有关时间值的表示和操作的更多信息,请参阅日历时间

从文件中读取会更新其访问时间属性,而写入会更新其修改时间。创建文件时,该文件的所有三个时间戳都设置为当前时间。此外,更新包含新条目的目录的属性更改时间和修改时间字段。

使用链接功能为文件添加新名称会更新被链接文件的属性更改时间字段,以及包含新名称的目录的属性更改时间和修改时间字段。如果使用 unlink、remove 或 rmdir 删除文件名,这些相同的字段也会受到影响。使用 rename 重命名文件只影响所涉及的两个父目录的属性更改时间和修改时间字段,而不影响文件被重命名的时间。

更改文件的属性(例如,使用 chmod)会更新其属性更改时间字段。

您还可以使用 utime 函数显式更改文件的某些时间戳——除了属性更改时间。您需要包含头文件 utime.h 才能使用此功能。

数据类型:struct utimbuf

utimbuf 结构与 utime 函数一起使用来指定文件的新访问和修改时间。它包含以下成员:

time_t actime

这是文件的访问时间。

time_t modtime

这是文件的修改时间。

函数:int utime (const char *filename, const struct utimbuf *times)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数用于修改与名为 filename 的文件关联的文件时间。

如果 times 为空指针,则文件的访问和修改时间设置为当前时间。否则,它们被设置为时间指向的 utimbuf 结构的 actime 和 modtime 成员(分别)的值。

无论哪种情况,文件的属性修改时间都设置为当前时间(因为更改时间戳本身就是对文件属性的修改)。

utime 函数在成功时返回 0,在失败时返回 -1。除了通常的文件名错误(请参阅文件名错误)之外,还为此函数定义了以下 errno 错误条件:

EACCES

在将空指针作为时间参数传递的情况下存在权限问题。要更新文件上的时间戳,您必须是文件的所有者、具有文件的写入权限或特权用户。

ENOENT

该文件不存在。

EPERM

如果 times 参数不是空指针,则您必须是文件的所有者或特权用户。

EROFS

该文件位于只读文件系统上。

三个时间戳中的每一个都有一个相应的微秒部分,这扩展了它的分辨率。这些字段称为 st_atime_usec、st_mtime_usec 和 st_ctime_usec;每个都有一个介于 0 和 999,999 之间的值,表示时间(以微秒为单位)。它们对应于 timeval 结构的 tv_usec 字段;请参阅时间类型

utimes 函数类似于 utime,但也允许您指定文件时间的小数部分。这个函数的原型在头文件 sys/time.h 中。

函数:int utimes (const char *filename, const struct timeval tvp[2])

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

该函数设置文件filename的文件访问和修改时间。新的文件访问时间由 tvp[0] 指定,新的修改时间由 tvp[1] 指定。与 utime 类似,如果 tvp 为空指针,则文件的访问和修改时间设置为当前时间。这个函数来自 BSD。

返回值和错误条件与 utime 函数相同。

函数:int lutimes (const char *filename, const struct timeval tvp[2])

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数与 utimes 类似,不同之处在于它不遵循符号链接。如果文件名是符号链接的名称,lutimes 设置符号链接特殊文件本身的文件访问和修改时间(如 lstat 所见;参见符号链接),而 utimes 设置文件符号链接的文件访问和修改时间指。此功能来自 FreeBSD,并非在所有平台上都可用(如果不可用,它将因 ENOSYS 而失败)。

返回值和错误条件与 utime 函数相同。

函数:int futimes (int fd, const struct timeval tvp[2])

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数类似于 utimes,不同之处在于它将打开的文件描述符作为参数而不是文件名。请参阅底层输入/输出。此功能来自 FreeBSD,并非在所有平台上都可用(如果不可用,它将因 ENOSYS 而失败)。

与 utimes 一样,futimes 成功返回 0,失败返回 -1。为 futimes 定义了以下 errno 错误条件:

EACCES

在将空指针作为时间参数传递的情况下存在权限问题。要更新文件上的时间戳,您必须是文件的所有者、具有文件的写入权限或特权用户。

EBADF

filedes 参数不是有效的文件描述符。

EPERM

如果 times 参数不是空指针,则您必须是文件的所有者或特权用户。

EROFS

该文件位于只读文件系统上。

2.9.10. 文件大小

File Size

通常文件大小是自动维护的。文件以 0 大小开始,并在数据写入超过其末尾时自动扩展。也可以通过 open 或 fopen 调用完全清空文件。

但是,有时需要减小文件的大小。这可以通过 truncate 和 ftruncate 函数来完成。它们是在 BSD Unix 中引入的。ftruncate 后来被添加到 POSIX.1。

某些系统允许您使用这些功能扩展文件(创建孔)。这在使用不会自动扩展文件的内存映射 I/O(请参阅内存映射 I/O)时很有用。但是,它不是可移植的,但如果 mmap 允许映射文件(即定义了 _POSIX_MAPPED_FILES),则必须实现它。

在常规文件以外的任何内容上使用这些函数会产生未定义的结果。在许多系统上,这样的调用看似成功,但实际上并没有完成任何事情。

函数:int truncate (const char *filename, off_t length)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

truncate 函数将文件名的大小更改为长度。如果长度比之前的长度短,最后的数据将会丢失。该文件必须可由用户写入才能执行此操作。

如果长度较长,则会在末端添加孔。但是,某些系统不支持此功能,并且会保持文件不变。

当使用 _FILE_OFFSET_BITS == 64 编译源文件时,truncate 函数实际上是 truncate64 并且类型 off_t 有 64 位,这使得处理长达 2^63 字节的文件成为可能。

返回值为 0 表示成功,或 -1 表示错误。除了通常的文件名错误外,还可能出现以下错误:

EACCES

该文件是目录或不可写。

EINVAL

长度为负。

EFBIG

该操作会将文件扩展超出操作系统的限制。

EIO

发生硬件 I/O 错误。

EPERM

该文件是“仅附加”或“不可变”的。

EINTR

操作被信号中断。

函数:int truncate64 (const char *name, off64_t length)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

该函数类似于截断函数。不同之处在于,即使在 32 位机器上,长度参数也是 64 位宽,这允许处理大小高达 2^63 字节的文件。

当在 32 位机器上使用 _FILE_OFFSET_BITS == 64 编译源文件时,此函数实际上在名称 truncate 下可用,因此透明地替换了 32 位接口。

函数:int ftruncate (int fd, off_t length)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

这类似于截断,但它适用于打开文件的文件描述符 fd 而不是文件名来识别对象。必须打开文件进行写入才能成功执行操作。

POSIX 标准让它的实现定义了如果文件的指定新长度大于原始大小会发生什么。ftruncate 函数可能只是不理会文件而不做任何事情,或者它可以将大小增加到所需的大小。在后一种情况下,扩展区域应该是零填充的。因此,使用 ftruncate 不是增加文件大小的可靠方法,但如果可能的话,它可能是最快的方法。如果系统实现了 POSIX 共享内存段,该函数也可以在这些段上运行。

ftruncate 与 mmap 结合使用特别有用。由于映射区域必须具有固定大小,因此无法通过在最后映射页面之外写入内容来扩大文件。相反,必须放大文件本身,然后用新的大小重新映射文​​件。下面的示例显示了它是如何工作的。

当使用 _FILE_OFFSET_BITS == 64 编译源文件时,ftruncate 函数实际上是 ftruncate64 并且类型 off_t 有 64 位,这使得处理长达 2^63 字节的文件成为可能。

返回值为 0 表示成功,或 -1 表示错误。可能会出现以下错误:

EBADF

fd 不对应于打开的文件。

EACCES

fd 是目录或未打开写入。

EINVAL

长度为负。

EFBIG

该操作会将文件扩展超出操作系统的限制。

EIO

发生硬件 I/O 错误。

EPERM

该文件是“仅附加”或“不可变”的。

EINTR

操作被信号中断。

函数:int ftruncate64 (int id, off64_t length)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数类似于 ftruncate 函数。不同之处在于,即使在 32 位机器上,长度参数也是 64 位宽,这允许处理大小高达 2^63 字节的文件。

当在 32 位机器上使用 _FILE_OFFSET_BITS == 64 编译源文件时,此函数实际上以名称 ftruncate 可用,因此透明地替换了 32 位接口。

正如这里所宣布的,这是一个如何结合使用 ftruncate 和 mmap 的小例子:

int fd;
void *start;
size_t len;

int
add (off_t at, void *block, size_t size)
{
  if (at + size > len)
    {
      /* Resize the file and remap.  */
      size_t ps = sysconf (_SC_PAGESIZE);
      size_t ns = (at + size + ps - 1) & ~(ps - 1);
      void *np;
      if (ftruncate (fd, ns) < 0)
        return -1;
      np = mmap (NULL, ns, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
      if (np == MAP_FAILED)
        return -1;
      start = np;
      len = ns;
    }
  memcpy ((char *) start + at, block, size);
  return 0;
}

函数 add 在文件中的任意位置写入一块内存。如果文件的当前大小太小,则将其扩展。请注意,它扩展了整数页。这是mmap的要求。程序必须跟踪实际大小,并在完成最终的 ftruncate 调用后设置文件的实际大小。

2.9.11. 存储分配

Storage Allocation

大多数文件系统支持以非连续方式分配大文件:文件被分割成按顺序分配的片段,但片段本身可以分散在磁盘上。文件系统通常会尽量避免这种碎片化,因为它会降低性能,但如果文件的大小逐渐增加,除了将其碎片化之外可能别无选择。此外,许多文件系统支持带有漏洞的稀疏文件:文件系统没有为其分配后备存储的空字节区域。当孔最终被数据覆盖时,也会出现碎片。

为文件中尚未写入的部分显式分配存储空间可以帮助系统避免碎片。此外,如果存储预分配失败,可以提前报告磁盘不足错误,通常不会填满整个磁盘。但是,由于重复数据删除、写时复制语义和文件压缩,这种预分配可能无法可靠地防止以后发生磁盘空间不足错误。仍然需要检查写入错误,并且写入使用 mmap 创建的内存映射区域仍然会导致 SIGBUS。

函数:int posix_fallocate (int fd, off_t offset, off_t length)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

为描述符 fd 的文件中的字节偏移量开始的长度字节区域分配后备存储。如有必要,文件长度增加到“长度+偏移”。

fd 必须是为写入而打开的常规文件,否则返回 EBADF。如果磁盘空间不足以满足分配请求,则返回 ENOSPC。

注意:如果 fallocate 不可用(因为文件系统不支持),则模拟 posix_fallocate ,它有以下缺点:

  • 这是非常低效的,因为需要检查请求范围内的所有文件系统块(即使它们之前已经分配过)并可能重写。相反,通过适当的 fallocate 支持(见下文),文件系统可以检查内部文件分配数据结构并直接消除漏洞,甚至可能使用未写入的扩展区(在磁盘上预先分配但未初始化)。
  • 如果另一个线程或进程修改了待分配区域中的底层文件,则存在竞争条件。非空字节可以被空字节覆盖。
  • 如果 fd 已使用 O_WRONLY 标志打开,则该函数将失败并返回 EBADF 的 errno 值。
  • 如果 fd 已使用 O_APPEND 标志打开,则该函数将失败并返回 EBADF 的 errno 值。
  • 如果 length 为零,则 ftruncate 用于根据请求增加文件大小,而不分配文件系统块。存在竞争条件,这意味着如果文件已同时扩展,则 ftruncate 可能会意外截断文件。

在 Linux 上,如果应用程序不能从仿真中受益,或者如果仿真由于其固有的竞争条件而有害,则应用程序可以使用 Linux 特定的 fallocate 函数,带有零标志参数。对于 fallocate 函数,如果文件系统不支持分配,GNU C 库不会执行分配模拟。相反,一个 EOPNOTSUPP 被返回给调用者。

函数:int posix_fallocate64 (int fd, off64_t offset, off64_t length)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数是 posix_fallocate64 的变体,它在所有平台上接受 64 位文件偏移量。

2.10. 制作特殊文件

Making Special Files

mknod 函数是用于制作特殊文件的原语,例如与设备对应的文件。GNU C 库包含这个函数是为了与 BSD 兼容。

mknod 的原型在 sys/stat.h 中声明。

函数:int mknod (const char *filename, mode_t mode, dev_t dev)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

mknod 函数创建一个名为 filename 的特殊文件。模式指定文件的模式,可能包括各种特殊的文件位,例如 S_IFCHR(对于字符特殊文件)或 S_IFBLK(对于块特殊文件)。请参阅测试文件的类型

dev 参数指定特殊文件所指的设备。它的确切解释取决于正在创建的特殊文件的类型。

返回值为 0 表示成功,-1 表示错误。除了通常的文件名错误(请参阅文件名错误)之外,还为此函数定义了以下 errno 错误条件:

EPERM

调用进程没有特权。只有超级用户可以创建特殊文件。

ENOSPC

包含新文件的目录或文件系统已满,无法扩展。

EROFS

无法修改包含新文件的目录,因为它位于只读文件系统上。

EEXIST

已经有一个名为 filename 的文件。如果要替换此文件,则必须先明确删除旧文件。

2.11. 临时文件

Temporary Files

如果需要在程序中使用临时文件,可以使用 tmpfile 函数打开它。或者您可以使用 tmpnam(更好的是:tmpnam_r)函数为临时文件提供一个名称,然后您可以使用 fopen 以通常的方式打开它。

tempnam 函数类似于 tmpnam,但允许您选择临时文件将进入的目录,以及它们的文件名的外观。对于多线程程序来说重要的是 tempnam 是可重入的,而 tmpnam 不是,因为它返回一个指向静态缓冲区的指针。

这些设施在头文件 stdio.h 中声明。

函数:FILE * tmpfile (void)

Preliminary: | MT-Safe | AS-Unsafe heap lock | AC-Unsafe mem fd lock | See POSIX Safety Concepts.

该函数为更新模式创建一个临时二进制文件,就像通过使用模式“wb+”调用 fopen 一样。文件关闭或程序终止时会自动删除。(在其他一些 ISO C 系统上,如果程序异常终止,文件可能无法删除)。

这个函数是可重入的。

在 32 位系统上使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数实际上是 tmpfile64,即 LFS 接口透明地替换旧接口。

函数:FILE * tmpfile64 (void)

Preliminary: | MT-Safe | AS-Unsafe heap lock | AC-Unsafe mem fd lock | See POSIX Safety Concepts.

此函数类似于 tmpfile,但它返回的指针指向的流是使用 tmpfile64 打开的。因此,此流可用于 32 位机器上大于 2^31 字节的文件。

请注意,返回类型仍然是 FILE *。LFS 接口没有特殊的 FILE 类型。

如果在 32 位机器上使用 _FILE_OFFSET_BITS == 64 编译源代码,则此函数在名称 tmpfile 下可用,因此可以透明地替换旧接口。

函数:char * tmpnam (char *result)

Preliminary: | MT-Unsafe race:tmpnam/!result | AS-Unsafe | AC-Safe | See POSIX Safety Concepts.

此函数构造并返回不引用任何现有文件的有效文件名。如果结果参数是空指针,则返回值是指向内部静态字符串的指针,该字符串可能会被后续调用修改,因此使此函数不可重入。否则,结果参数应该是一个指向至少包含 L_tmpnam 字符的数组的指针,并将结果写入该数组。

如果您调用它太多次而没有删除以前创建的文件,则 tmpnam 可能会失败。这是因为临时文件名的有限长度只为有限数量的不同名称提供了空间。如果 tmpnam 失败,它会返回一个空指针。

警告:在构建路径名和创建文件之间,另一个进程可能使用 tmpnam 创建了同名文件,从而导致可能的安全漏洞。该实现生成的名称很难预测,但在打开文件时,您应该使用 O_EXCL 标志。使用 tmpfile 或 mkstemp 是避免此问题的安全方法。

函数:char * tmpnam_r (char *result)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

这个函数几乎与 tmpnam 函数相同,除了如果 result 是一个空指针,它返回一个空指针。

这保证了可重入,因为 tmpnam 的不可重入情况在这里不可能发生。

警告:此函数与 tmpnam 存在相同的安全问题。

宏:int L_tmpnam

此宏的值是一个整数常量表达式,它表示字符串的最小大小,该字符串大到足以容纳由 tmpnam 函数生成的文件名。

宏:int TMP_MAX

宏 TMP_MAX 是您可以使用 tmpnam 创建多少临时名称的下限。您可以依靠能够调用 tmpnam 至少多次,然后它可能会失败,说您已经创建了太多临时文件名。

使用 GNU C 库,您可以创建大量临时文件名。如果您实际创建了这些文件,您可能会在用完名称之前用完磁盘空间。其他一些系统对临时文件的数量有一个固定的小限制。限制永远不会低于 25。

函数:char * tempnam (const char *dir, const char *prefix)

Preliminary: | MT-Safe env | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

该函数生成一个唯一的临时文件名。如果前缀不是空指针,则此字符串中最多五个字符用作文件名的前缀。返回值是一个用 malloc 新分配的字符串,所以当不再需要它时,你应该释放它的存储空间。

因为字符串是动态分配的,所以这个函数是可重入的。

临时文件名的目录前缀是通过依次测试以下各项来确定的。该目录必须存在并且可写。

  • 环境变量 TMPDIR(如果已定义)。出于安全原因,仅当程序未启用 SUID 或 SGID 时才会发生这种情况。
  • dir 参数,如果它不是空指针。
  • P_tmpdir 宏的值。
  • 目录 /tmp。

此函数是为 SVID 兼容性而定义的。

警告:在构建路径名和创建文件之间,另一个进程可能使用 tempnam 创建了同名文件,从而导致可能的安全漏洞。该实现生成的名称很难预测,但在打开文件时,您应该使用 O_EXCL 标志。使用 tmpfile 或 mkstemp 是避免此问题的安全方法。

SVID 宏:char * P_tmpdir

此宏是临时文件的默认目录的名称。

较旧的 Unix 系统没有刚才描述的功能。相反,他们使用 mktemp 和 mkstemp。这两个函数都通过修改您传递的文件名模板字符串来工作。此字符串的最后六个字符必须是“XXXXXX”。这六个“X”被替换为六个字符,使整个字符串成为唯一的文件名。通常模板字符串类似于‘/tmp/prefixXXXXXX’,每个程序使用一个唯一的前缀。

注意:因为 mktemp 和 mkstemp 修改模板字符串,所以您不能将字符串常量传递给它们。字符串常量通常在只读存储中,因此当 mktemp 或 mkstemp 尝试修改字符串时,您的程序会崩溃。这些函数在头文件 stdlib.h 中声明。

函数:char * mktemp (char *template)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

mktemp 函数通过如上所述修改模板生成唯一的文件名。如果成功,则返回已修改的模板。如果 mktemp 找不到唯一的文件名,它会将模板设为空字符串并返回。如果模板不以‘XXXXXX’结尾,mktemp 返回一个空指针。

警告:在构建路径名和创建文件之间,另一个进程可能使用 mktemp 创建了同名文件,从而导致可能的安全漏洞。该实现生成的名称很难预测,但在打开文件时,您应该使用 O_EXCL 标志。使用 mkstemp 是避免此问题的安全方法。

函数:int mkstemp (char *template)

Preliminary: | MT-Safe | AS-Safe | AC-Safe fd | See POSIX Safety Concepts.

mkstemp 函数生成一个唯一的文件名,就像 mktemp 一样,但它也会用 open 为您打开文件(请参阅打开和关闭文件)。如果成功,它会修改模板并返回该文件的文件描述符,该文件打开以供读取和写入。如果 mkstemp 无法创建唯一命名的文件,则返回 -1。如果模板不以‘XXXXXX’结尾,mkstemp 返回 -1 并且不修改模板。

该文件使用模式 0600 打开。如果该文件打算由其他用户使用,则必须明确更改此模式。

与 mktemp 不同,mkstemp 实际上可以保证创建一个唯一的文件,该文件不可能与任何其他试图创建临时文件的程序发生冲突。这是因为它通过使用 O_EXCL 标志调用 open 来工作,这表示您要创建一个新文件,如果该文件已经存在,则会出现错误。

函数:char * mkdtemp (char *template)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

mkdtemp 函数创建一个具有唯一名称的目录。如果成功,它将用目录名称覆盖模板,并返回模板。与 mktemp 和 mkstemp 一样,模板应该是一个以“XXXXXX”结尾的字符串。

如果 mkdtemp 无法创建唯一命名的目录,它会返回 NULL 并适当地设置 errno。如果模板不以‘XXXXXX’结尾,mkdtemp 返回 NULL 并且不修改模板。在这种情况下,errno 将被设置为 EINVAL。

该目录是使用模式 0700 创建的。

mkdtemp 创建的目录不能与其他用户创建的临时文件或目录冲突。这是因为目录创建总是像使用 O_EXCL 打开一样。请参阅创建目录

mkdtemp 函数来自 OpenBSD。

3. 参考

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值