# 文件系统，第 4 部分：使用目录

> 原文：<https://github.com/angrave/SystemProgramming/wiki/File-System%2C-Part-4%3A-Working-with-directories>

## 如何确定文件（inode）是常规文件还是目录？

使用`S_ISDIR`宏函数检查stat数据结构中的模式位：

```c
struct stat s;
stat("/tmp", &s);
if (S_ISDIR(s.st_mode)) { ... 
```

注意，稍后我们将编写健壮的代码来验证 stat 调用是否成功（返回 0）;如果`stat`调用失败，我们应该假设stat的数据结构内容是可以自定义的。

## 我该如何递归到子目录？

首先看下面这个难题--您可以在以下代码中找到多少个错误？

```c
void dirlist(char *path) {

  struct dirent *dp;
  DIR *dirp = opendir(path);
  while ((dp = readdir(dirp)) != NULL) {
     char newpath[strlen(path) + strlen(dp->d_name) + 1];
     sprintf(newpath,"%s/%s", newpath, dp->d_name);
     printf("%s\n", dp->d_name);
     dirlist(newpath);
  }
}

int main(int argc, char **argv) { dirlist(argv[1]); return 0; }
```

你找到了所有 5 个错误吗？

```c
// Check opendir result (perhaps user gave us a path that can not be opened as a directory
if (!dirp) { perror("Could not open directory"); return; }
// +2 as we need space for the / and the terminating 0
char newpath[strlen(path) + strlen(dp->d_name) + 2]; 
// Correct parameter
sprintf(newpath,"%s/%s", path, dp->d_name); 
// Perform stat test (and verify) before recursing
if (0 == stat(newpath,&s) && S_ISDIR(s.st_mode)) dirlist(newpath)
// Resource leak: the directory file handle is not closed after the while loop
closedir(dirp);
```

## 什么是符号链接？他们是如何工作的？我怎么做一个？

```c
symlink(const char *target, const char *symlink);
```

要在 shell 中创建符号链接，请使用`ln -s`

要将链接的内容作为文件使用`readlink`

```
$ readlink myfile.txt
../../dir1/notes.txt 
```

要读取符号链接的元（stat）信息，请使用`lstat`而不是`stat`

```c
struct stat s1, s2;
stat("myfile.txt", &s1); // stat info about  the notes.txt file
lstat("myfile.txt", &s2); // stat info about the symbolic link
```

## 符号链接的优点

*   可以引用尚不存在的文件
*   与硬链接不同，可以引用目录以及常规文件
*   可以引用当前文件系统之外的文件（和目录）

主要缺点：比常规文件和目录慢。读取链接内容时，必须将它们解释为目标文件的新路径。

## 什么是`/dev/null`以及何时使用？

文件`/dev/null`里面存储了个寂寞！发送到`/dev/null/`的字节永远不会被存储 - 它们被简单地丢弃。 `/dev/null`的一个常见用途是丢弃标准输出。例如，

```
$ ls . >/dev/null 
```

## 为什么我要设置目录的粘性位？

当目录的粘滞位仅设置文件的所有者时，目录的所有者和 root 用户可以重命名（或删除）该文件。当多个用户具有对公共目录的写访问权时，这非常有用。

粘滞位的常见用途是共享和可写`/tmp`目录。

## 为什么 shell 和脚本程序以`#!/usr/bin/env python`开头？

答：为了方便！虽然可以将完全限定的路径写入 python 或 perl 解释器，但这种方法不可移植，因为您可能已将 python 安装在不同的目录中。

为了克服这种情况，`env`程序用于在用户路径上查找和执行程序。 env 实用程序本身历来存储在`/usr/bin`中 - 必须使用绝对路径指定。

## 如何制作“隐藏”文件，即“ls”未列出？我如何列出它们？

简单！创建以“.”开头的文件（或目录）。 - 然后（默认情况下）标准工具和实用程序不显示它们。

这通常用于隐藏用户主目录中的配置文件。例如，`ssh`将其首选项存储在名为`.sshd`的目录中

要列出包括常用隐藏条目在内的所有文件，请使用带有`-a`选项的`ls`

```
$ ls -a
.           a.c         myls
..          a.out           other.txt
.secret 
```

## 如果我关闭目录上的执行位会发生什么？

目录的执行位用于控制目录内容是否可列表。

```
$ chmod ugo-x dir1
$ ls -l
drw-r--r--   3 angrave  staff   102 Nov 10 11:22 dir1 
```

但是，在尝试列出目录的内容时，

```
$ ls dir1
ls: dir1: Permission denied 
```

换句话说，目录本身是可发现的，但无法列出其内容。

## 什么是文件匹配（谁来做）？

在执行程序之前，shell 会将参数扩展为匹配的文件名。例如，如果当前目录有三个以 my（my1.txt mytext.txt myomy）开头的文件名，那么

```
$ echo my* 
```

扩展到

```
$ echo my1.txt mytext.txt myomy 
```

这称为文件匹配，在执行命令之前处理。即命令的参数与手动键入每个匹配的文件名相同。

## 创建安全目录

假设您在/ tmp 中创建了自己的目录，然后设置权限，以便只有您可以使用该目录（见下文）。这样安全吗？

```
$ mkdir /tmp/mystuff
$ chmod 700 /tmp/mystuff 
```
在创建目录和更改目录的权限之间有一个机会窗口。这会导致基于争用条件的几个漏洞（攻击者在删除权限之前以某种方式修改目录）。一些示例包括：

另一个用户将`mystuff`替换为第二个用户拥有的现有文件或目录的硬链接，然后他们就能够读取和控制`mystuff`目录的内容。哎，一不小心暴露了，一般人我不告诉他！

但是在此特定示例中，`/tmp`目录设置了粘滞位，因此其他用户可能不会删除`mystuff`目录，并且上述简单攻击情形是不可能的。这并不意味着创建目录然后将目录设为私有是安全的！更好的版本是从一开始就以原子方式创建具有正确权限的目录 -

```
$ mkdir -m 700 /tmp/mystuff 
```

## 如何自动创建父目录？

```
$ mkdir -p d1/d2/d3 
```

如果它们不存在，将自动创建 d1 和 d2。

## 我的默认配置 umask 022;这是什么意思？
umask从777中减去（减少）权限位，并在通过open，mkdir等创建新文件和新目录时使用。因此，022（八进制）意味着组和其他特权将不包括可写位。每个进程（包括 shell）都有一个当前掩码值。分叉时，子项继承父项的掩码值。

例如，通过在 shell 中将 umask 设置为 077，确保将来的文件和目录创建只能由当前用户访问，


```
$ umask 077
$ mkdir secretdir 
```

作为代码示例，假设使用`open()`和模式位`666`（用户，组和其他的写入和读取位）创建新文件：

```c
open("myfile", O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
```

如果 umask 是八进制 022，那么创建的文件的权限将是 0666＆amp; 〜022 即。

```c
           S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
```

## 如何将字节从一个文件复制到另一个文件？

使用通用`dd`命令。例如，以下命令将文件`/dev/urandom`中的 1 MB 数据复制到文件`/dev/null`。数据被复制为 1024 块块大小为 1024 字节。

```
$ dd if=/dev/urandom of=/dev/null bs=1k count=1024 
```

上例中的输入和输出文件都是虚拟的 - 它们不存在于磁盘上。这意味着传输速度不受硬件电源的影响。相反，它们是`dev`文件系统的一部分，后者是内核提供的虚拟文件系统。虚拟文件`/dev/urandom`提供无限的随机字节流，而 virtal 文件`/dev/null`忽略写入它的所有字节。 `/dev/null`的一个常见用法是丢弃命令的输出，

```
$ myverboseexecutable > /dev/null 
```

另一个常用的/ dev 虚拟文件是`/dev/zero`，它提供无限的零字节流。例如，我们可以将内核中读取流零字节的操作系统性能基准测试到进程内存，并将字节写回内核而不需要任何磁盘I/O开销.请注意，吞吐量（~20GB / s）很大程度上取决于 blocksize。对于小块大小，额外`read`和`write`系统调用的开销将占主导地位。

```
$ dd if=/dev/zero of=/dev/null bs=1M count=1024
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB) copied, 0.0539153 s, 19.9 GB/s 
```

## touch文件时会发生什么？

如果文件不存在，`touch`会创建文件，并将文件的上次修改时间更新为当前时间。例如，我们可以使用当前时间创建一个新的私有文件：

```
$ umask 077       # all future new files will maskout all r,w,x bits for group and other access
$ touch file123   # create a file if it does not exist, and update its modified time
$ stat file123
  File: `file123'
  Size: 0           Blocks: 0          IO Block: 65536  regular empty file
Device: 21h/33d Inode: 226148      Links: 1
Access: (0600/-rw-------)  Uid: (395606/ angrave)   Gid: (61019/     ews)
Access: 2014-11-12 13:42:06.000000000 -0600
Modify: 2014-11-12 13:42:06.001787000 -0600
Change: 2014-11-12 13:42:06.001787000 -0600 
```

touch的一个示例用法是强制make在修改makefile中的编译器选项后重新编译未更改的文件。记住 make 是'懒加载的' - 它会将源文件的修改时间与相应的输出文件进行比较，以查看是否需要重新编译该文件

```
$ touch myprogram.c   # force my source file to be recompiled
$ make 
```

[转到文件系统：第 5 部分](https://github.com/angrave/SystemProgramming/wiki/File-System,-Part-5:-Virtual-file-systems)