Chapter 39 Interpolation: documentation and directories

第 39 章 插叙:文件和目录

目前为止有两项关键操作系统技术:

  • 进程,虚拟化的CPU

  • 地址空间,虚拟化的内存

这两个抽象的共同作用下,程序运行时好像他在自己的私立空间里...使得系统编程变得容易

虚拟化拼图还有更为关键的一块:持久存储 persistent storage...体现为文件

1. 文件和目录

存储虚拟化形成了两个关键的抽象:

  • 文件 file 线性字节数组

  • 目录 directory

1.1. 文件的低级名称

用户不知道 叫 inode number

1.2. 目录

低级名称和用户可读名称不一致

用户可读名称通常更为具象,可以编码为字符

而低级名称更容易存储

在底层通过构造映射来进行记录。用户可以构建任意的目录树 directory tree 或 目录层次结构 directory hierarchy。

2. 文件系统接口

创建、访问、删除文件的接口...

3. 创建文件

3.1. open() 调用

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
  int fd = open("foo", O_CREAT | O_WRONLY | O_TRUNC);
  if (fd == -1)
  {
    perror("open");
    return 1;
  }
  else
  {
    printf("File opened successfully.\n");
    close(fd);
  }
}

传入 O_CREAT 程序创建一个新文件

In this example

  • the second parameter creates the file (O_CREAT) if it does not exist

  • ensures that the file can only be written to (O_WRONLY)

  • if the file already exists, truncates it to a size of zero bytes thus removing any existing content (O_TRUNC).

它的返回值

文件描述符 file descriptor 是一个整数,每个进程私有的 相当于一种权限 capability 一个不透明的句柄

一旦有这样的对象可以用其他方法来访问 如下

4. 读写文件

strace 跟踪系统调用

Why does the first call to open() return 3, not 0 or perhaps 1 as you might expect?

As it turns out, each running process already has three files open, standard input (which the process can read to receive input), standard output (which the process can write to in order to dump information to the screen), and standard error (which the process can write error messages to). These are represented by file descriptors 0, 1, and 2, respectively. Thus, when you first open another file (as cat does above), it will almost certainly be file descriptor 3.

对于大型文件,可能会重复调用 read() 和 write() system call...

5. 读取和写入 但不按顺序

先前介绍的都是 sequential,现在介绍 random

5.1. Function Prototype

off_t lseek(int fildes, off_t offset, int whence);

参数:

  1. 文件描述符 fd

  2. 偏移量

  3. whence

    1. If whence is SEEK_SET, the offset is set to offset bytes.

    2. If whence is SEEK_CUR, the offset is set to its current location plus offset bytes.

    3. If whence is SEEK_END, the offset is set to the size of the file plus offset bytes.

可以通过指定偏移量来完成读取和写入操作

6. 用fsync()立即写入

write 只是会接受写入请求,而且不是实时的,而是有一个缓存机制,并且极少数情况下会丢失

然而DBMS等程序需要开发正确的恢复协议要求能够强制写入磁盘

6.1. fsync()

强制将所有 dirty data写入磁盘

#include <fcntl.h>  // 包含 open 函数
#include <unistd.h> // 包含 write 和 fsync 函数
#include <assert.h> // 包含 assert 函数
#include <stdio.h>  // 包含 perror 函数(用于调试)

int main()
{
  // 定义文件描述符和返回值变量
  int fd;
  int rc;

  // 定义要写入的数据缓冲区和大小
  const char *buffer = "Hello, World!"; // 示例数据
  size_t size = sizeof(buffer) - 1;     // 计算字符串长度(不包括结尾的 '\0')

  // 打开文件
  fd = open("foo", O_CREAT | O_WRONLY | O_TRUNC, 0644);
  assert(fd > -1); // 确保文件打开成功

  // 写入数据
  rc = write(fd, buffer, size);
  assert(rc == size); // 确保写入成功且写入的字节数与预期一致

  // 同步文件数据到磁盘
  rc = fsync(fd);
  assert(rc == 0); // 确保同步成功

  // 关闭文件描述符
  close(fd);

  printf("File operations completed successfully.\n");
  return 0;
}
  1. 当调用write函数时,数据首先被写入到操作系统的页缓存(page cache)中。页缓存是内存中的一块区域,用于缓存文件数据,以提高读写性能。

  2. 同步到磁盘是指将内存中的数据(通常是页缓存中的数据)强制写入到物理磁盘中。这个操作通常由 fsync 或sync系统调用完成。

7. 文件重命名

rename 系统调用(char * old, char * new)

用 strace 跟踪 mv 可以发现...

#include <fcntl.h>  // 包含 open 函数
#include <unistd.h> // 包含 write、fsync 和 close 函数
#include <string.h> // 包含 strlen 函数
#include <stdio.h>  // 包含 perror 函数(用于调试)
#include <stdlib.h> // 包含 exit 函数

int main()
{
  // 定义文件描述符和返回值变量
  int fd;
  ssize_t rc; // write 和 fsync 的返回值类型为 ssize_t

  // 定义要写入的数据缓冲区和大小
  const char *buffer = "Hello, World!"; // 示例数据
  size_t size = strlen(buffer);         // 计算字符串长度(不包括结尾的 '\0')

  // 打开文件
  fd = open("foo.txt.tmp", O_CREAT | O_WRONLY | O_TRUNC, 0644);
  if (fd == -1)
  {
    perror("open");
    return 1;
  }

  // 写入数据
  rc = write(fd, buffer, size);
  if (rc != size)
  {
    perror("write");
    close(fd);
    return 1;
  }

  // 同步数据到磁盘
  rc = fsync(fd);
  if (rc == -1)
  {
    perror("fsync");
    close(fd);
    return 1;
  }

  // 关闭文件描述符
  if (close(fd) == -1)
  {
    perror("close");
    return 1;
  }

  // 重命名文件
  if (rename("foo.txt.tmp", "foo.txt") == -1)
  {
    perror("rename");
    return 1;
  }

  printf("File operations completed successfully.\n");
  return 0;
}

将文件的新版本写入临时名称(foot.txt.tmp),使用fsync()将其强制写入磁盘... and then, when the application is certain the new file metadata and contents are on the disk, rename the temporary file to the original file’s name. This last step atomically swaps the new file into place, while concurrently deleting the old version of the file, and thus an atomic file update is achieved.

8. 获取文件信息

文件的各种信息

struct stat {
dev_t st_dev; // ID of device containing file
ino_t st_ino; // inode number
mode_t st_mode; // protection
nlink_t st_nlink; // number of hard links
uid_t st_uid; // user ID of owner
gid_t st_gid; // group ID of owner
dev_t st_rdev; // device ID (if special file)
off_t st_size; // total size, in bytes
blksize_t st_blksize; // blocksize for filesystem I/O
blkcnt_t st_blocks; // number of blocks allocated
time_t st_atime; // time of last access
time_t st_mtime; // time of last modification
time_t st_ctime; // time of last status change
};

8.1. test

root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter39# echo hello > a.txt
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter39# stat a.txt
  File: a.txt
  Size: 6               Blocks: 0          IO Block: 4096   regular file
Device: 0,68    Inode: 4222124650717537  Links: 1
Access: (0777/-rwxrwxrwx)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2025-04-08 09:35:23.888049300 +0800
Modify: 2025-04-08 09:35:23.888049300 +0800
Change: 2025-04-08 09:35:23.888049300 +0800
 Birth: -

Each file system usually keeps this type of information in a structure called an inode1 . We’ll be learning a lot more about inodes when we talk about file system implementation. For now, you should just think of an inode as a persistent data structure kept by the file system that has information like we see above inside of it. All inodes reside on disk; a copy of active ones are usually cached in memory to speed up access.

9. 删除文件

We’ve removed a bunch of unrelated cruft from the traced output, leaving just a single call to the mysteriously-named system call unlink(). As you can see, unlink() just takes the name of the file to be removed, and returns zero upon success. But this leads us to a great puzzle: why is this system call named unlink? Why not just remove or delete? To understand the answer to this puzzle, we must first understand more than just files, but also directories.

root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter39# strace rm a.txt
execve("/usr/bin/rm", ["rm", "a.txt"], 0x7ffdf3e85168 /* 36 vars */) = 0
brk(NULL)                               = 0x5638c66d0000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa92cb1f000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=82531, ...}) = 0
mmap(NULL, 82531, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa92cb0a000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\220\243\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
fstat(3, {st_mode=S_IFREG|0755, st_size=2125328, ...}) = 0
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
mmap(NULL, 2170256, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fa92c8f8000
mmap(0x7fa92c920000, 1605632, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x7fa92c920000
mmap(0x7fa92caa8000, 323584, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b0000) = 0x7fa92caa8000
mmap(0x7fa92caf7000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1fe000) = 0x7fa92caf7000
mmap(0x7fa92cafd000, 52624, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fa92cafd000
close(3)                                = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa92c8f5000
arch_prctl(ARCH_SET_FS, 0x7fa92c8f5740) = 0
set_tid_address(0x7fa92c8f5a10)         = 1388
set_robust_list(0x7fa92c8f5a20, 24)     = 0
rseq(0x7fa92c8f6060, 0x20, 0, 0x53053053) = 0
mprotect(0x7fa92caf7000, 16384, PROT_READ) = 0
mprotect(0x5638a8032000, 4096, PROT_READ) = 0
mprotect(0x7fa92cb57000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0x7fa92cb0a000, 82531)           = 0
getrandom("\x2a\xe4\x5b\x47\xa2\xbf\xf0\x0a", 8, GRND_NONBLOCK) = 8
brk(NULL)                               = 0x5638c66d0000
brk(0x5638c66f1000)                     = 0x5638c66f1000
openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/share/locale/locale.alias", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=2996, ...}) = 0
read(3, "# Locale name alias data base.\n#"..., 4096) = 2996
read(3, "", 4096)                       = 0
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_IDENTIFICATION", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_IDENTIFICATION", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=258, ...}) = 0
mmap(NULL, 258, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa92cb1e000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=27028, ...}) = 0
mmap(NULL, 27028, PROT_READ, MAP_SHARED, 3, 0) = 0x7fa92cb17000
close(3)                                = 0
futex(0x7fa92cafc72c, FUTEX_WAKE_PRIVATE, 2147483647) = 0
openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_MEASUREMENT", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_MEASUREMENT", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=23, ...}) = 0
mmap(NULL, 23, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa92cb16000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_TELEPHONE", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_TELEPHONE", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=47, ...}) = 0
mmap(NULL, 47, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa92cb15000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_ADDRESS", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_ADDRESS", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=127, ...}) = 0
mmap(NULL, 127, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa92cb14000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_NAME", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_NAME", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=62, ...}) = 0
mmap(NULL, 62, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa92cb13000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_PAPER", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_PAPER", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=34, ...}) = 0
mmap(NULL, 34, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa92cb12000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_MESSAGES", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_MESSAGES", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_MESSAGES/SYS_LC_MESSAGES", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=48, ...}) = 0
mmap(NULL, 48, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa92cb11000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_MONETARY", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_MONETARY", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=270, ...}) = 0
mmap(NULL, 270, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa92cb10000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_COLLATE", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_COLLATE", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=1406, ...}) = 0
mmap(NULL, 1406, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa92cb0f000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_TIME", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_TIME", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=3360, ...}) = 0
mmap(NULL, 3360, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa92cb0e000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_NUMERIC", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_NUMERIC", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=50, ...}) = 0
mmap(NULL, 50, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa92cb0d000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_CTYPE", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_CTYPE", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=360460, ...}) = 0
mmap(NULL, 360460, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa92c89c000
close(3)                                = 0
ioctl(0, TCGETS, {c_iflag=ICRNL|IXON, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ICANON|ECHO|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0
newfstatat(AT_FDCWD, "a.txt", {st_mode=S_IFREG|0777, st_size=6, ...}, AT_SYMLINK_NOFOLLOW) = 0
geteuid()                               = 0
unlinkat(AT_FDCWD, "a.txt", 0)          = 0
lseek(0, 0, SEEK_CUR)                   = -1 ESPIPE (Illegal seek)
close(0)                                = 0
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

其中 第111行unlinkat(AT_FDCWD, "a.txt", 0) = 0

10. 创建目录

mkdir 系统调用

10.1. 小心强大的指令

rm 提供了一个强大命令,也说明太多权力是一个问题,要一次删除一堆文件可以用 rm *...有时候可以用 rm -rf *如果在 root 就麻烦了

10.2. 目录条目

空目录也包含两个:

  • .引用自身条目

  • ..引用父目录的条目

ls -a 可以看到

11. 读取目录

ls 来读取 -l 和 不-l 也有区别

11.1. CODE

#include <dirent.h> // 包含 opendir、readdir 和 closedir 函数
#include <assert.h> // 包含 assert 函数
#include <stdio.h>  // 包含 printf 函数
#include <stdlib.h> // 包含 exit 函数

int main(int argc, char *argv[])
{
  // 打开当前目录
  DIR *dp = opendir(".");
  if (dp == NULL)
  {
    perror("opendir");
    return 1; // 如果打开目录失败,退出程序
  }

  // 遍历目录中的条目
  struct dirent *d;
  while ((d = readdir(dp)) != NULL)
  {
    // 打印 inode 号和文件名
    printf("%lu %s\n", (unsigned long)d->d_ino, d->d_name);
  }

  // 关闭目录流
  if (closedir(dp) == -1)
  {
    perror("closedir");
    return 1; // 如果关闭目录失败,退出程序
  }

  return 0; // 程序正常结束
}

11.2. OUTPUT

root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter39# ./print_dir
9288674231496336 .
9288674231496336 ..
3659174697296224 fork-seek.c
3659174697296217 fsync.c
3659174697296218 Makefile
5348024557548538 open.c
4222124650717558 print_dir
4503599627428193 print_dir.c
4503599627428187 rename.c

输出 inode 号 和 文件名称...

12. 删除目录

rmfir() 调用,但是要求此时目录是空的:只包含...

13. 硬链接

ln 指令 相当于之前用过的创造快捷方式...?

通过两个人类可读名称指向同一个文件(同一个inode号的文件)

14. 符号连接

symbolic link 符号连接 有时称为 soft link 软连接

ln -s 标识...

14.1. 硬链接

创建硬链接link1指向file.txt

  • 删除file.txt后,link1仍然可以访问内容“Hello, World!”,因为它们共享同一个文件内容

14.2. 软链接

创建软链接link2指向file.txt

  • 删除file.txt后,link2就变成一个坏链接,无法访问任何内容。

15. 创建并挂载文件系统

mkfs() 调用

make file system

15.1. 创建文件系统 mkfs()

目标挂载点 mount point

规划硬盘分区...存储文件

15.2. 挂载 mount()

挂载点是一个目录,比如 /mnt/mydisk 。这个目录就像是一个“入口”。

  • 想象你有一个硬盘分区(比如/dev/sdb/),上面已经用mkfs创建了文件系统。

  • 这个硬盘分区就像是一栋房子,里面有房间(文件和目录),但你现在还不能进去。

  • mount就像是在房子和外面的街道之间修了一条路,让你可以通过某个入口(目录)进入这栋房子。

16. 作业(编码)

16.1. Stat

Write your own version of the command line program stat, which simply calls the stat() system call on a given file or directory. Print out file size, number of blocks allocated, reference (link) count, and so forth. What is the link count of a directory, as the number of entries in the directory changes? Useful interfaces: stat(), naturally

CODE

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>

void print_file_info(const char *path, struct stat *file_stat)
{
  printf("File: %s\n", path);
  printf("Size: %lld bytes\n", (long long)file_stat->st_size);
  printf("Blocks: %lld\n", (long long)file_stat->st_blocks);
  printf("Reference (Link) Count: %ld\n", (long)file_stat->st_nlink);
  printf("User ID: %ld\n", (long)file_stat->st_uid);
  printf("Group ID: %ld\n", (long)file_stat->st_gid);
  printf("Device ID: %ld\n", (long)file_stat->st_dev);
  printf("Inode Number: %ld\n", (long)file_stat->st_ino);
  printf("Access Time: %ld\n", (long)file_stat->st_atime);
  printf("Modification Time: %ld\n", (long)file_stat->st_mtime);
  printf("Change Time: %ld\n", (long)file_stat->st_ctime);
}

int main(int argc, char *argv[])
{
  if (argc != 2)
  {
    fprintf(stderr, "Usage: %s <file_or_directory>\n", argv[0]);
    return EXIT_FAILURE;
  }

  const char *path = argv[1];
  struct stat file_stat;

  if (stat(path, &file_stat) == -1)
  {
    perror("stat");
    return EXIT_FAILURE;
  }

  print_file_info(path, &file_stat);

  return EXIT_SUCCESS;
}

OUTPUT

root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter39/hwk_code# make Stat
gcc -o Stat stat.c -Wall
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter39/hwk_code# ./Stat Makefile
File: Makefile
Size: 189 bytes
Blocks: 0
Reference (Link) Count: 1
User ID: 0
Group ID: 0
Device ID: 68
Inode Number: 1970324837074668
Access Time: 1744080102
Modification Time: 1744080102
Change Time: 1744080102
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter39/hwk_code# ln Makefile make
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter39/hwk_code# ./Stat make
File: make
Size: 189 bytes
Blocks: 0
Reference (Link) Count: 2
User ID: 0
Group ID: 0
Device ID: 68
Inode Number: 1970324837074668
Access Time: 1744080102
Modification Time: 1744080102
Change Time: 1744081104

16.2. List Files

Write a program that lists files in the given directory. When called without any arguments, the program should just print the file names. When invoked with the -l flag, the program should print out information about each file, such as the owner, group, permissions, and other information obtained from the stat() system call. The program should take one additional argument, which is the directory to read, e.g., myls -l directory. If no directory is given, the program should just use the current working directory. Useful interfaces: stat(), opendir(), readdir(), getcwd().

CODE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>

// Function to print file permissions in human-readable format
void print_permissions(mode_t mode)
{
  printf((mode & S_IRUSR) ? "r" : "-");
  printf((mode & S_IWUSR) ? "w" : "-");
  printf((mode & S_IXUSR) ? "x" : "-");
  printf((mode & S_IRGRP) ? "r" : "-");
  printf((mode & S_IWGRP) ? "w" : "-");
  printf((mode & S_IXGRP) ? "x" : "-");
  printf((mode & S_IROTH) ? "r" : "-");
  printf((mode & S_IWOTH) ? "w" : "-");
  printf((mode & S_IXOTH) ? "x" : "-");
}

// Function to print detailed file information
void print_file_info(const char *path, struct stat *file_stat)
{
  // Get user and group information
  struct passwd *pwd = getpwuid(file_stat->st_uid);
  struct group *grp = getgrgid(file_stat->st_gid);

  // Print permissions
  print_permissions(file_stat->st_mode);
  printf(" ");

  // Print number of hard links
  printf("%4ld ", (long)file_stat->st_nlink);

  // Print user and group names
  printf("%-8s %-8s ", pwd ? pwd->pw_name : "unknown", grp ? grp->gr_name : "unknown");

  // Print file size
  printf("%8lld ", (long long)file_stat->st_size);

  // Print file name
  printf("%s\n", path);
}

// Function to list files in a directory
void list_files(const char *directory, int long_format)
{
  DIR *dir;
  struct dirent *entry;
  struct stat file_stat;
  char full_path[1024];

  // Open the directory
  dir = opendir(directory);
  if (!dir)
  {
    perror("opendir");
    return;
  }

  // Read directory entries
  while ((entry = readdir(dir)) != NULL)
  {
    if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
    {
      continue; // Skip "." and ".."
    }

    // Construct full path
    snprintf(full_path, sizeof(full_path), "%s/%s", directory, entry->d_name);

    // Get file information
    if (stat(full_path, &file_stat) == -1)
    {
      perror("stat");
      closedir(dir);
      return;
    }

    // Print file information
    if (long_format)
    {
      print_file_info(entry->d_name, &file_stat);
    }
    else
    {
      printf("%s\n", entry->d_name);
    }
  }

  // Close the directory
  closedir(dir);
}

int main(int argc, char *argv[])
{
  int long_format = 0;
  const char *directory = "."; // Default to current directory

  // Parse command-line arguments
  for (int i = 1; i < argc; i++)
  {
    if (strcmp(argv[i], "-l") == 0)
    {
      long_format = 1;
    }
    else
    {
      directory = argv[i];
    }
  }

  // List files in the specified directory
  list_files(directory, long_format);

  return EXIT_SUCCESS;
}

OUTPUT

root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter39/hwk_code# ./LF -l
rwxrwxrwx    1 root     root        16584 LF
rwxrwxrwx    1 root     root         2779 LF.c
rwxrwxrwx    1 root     root          188 Makefile
rwxrwxrwx    1 root     root            0 RS.c
rwxrwxrwx    1 root     root        16200 Stat
rwxrwxrwx    1 root     root         1178 Stat.c
rwxrwxrwx    1 root     root            0 Tail.c
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter39/hwk_code# ./LF
LF
LF.c
Makefile
RS.c
Stat
Stat.c
Tail.c
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter39/hwk_code# ./LF -l /mnt/d/CSLab/osTEP/chapter39/
rwxrwxrwx    1 root     root          603 fork-seek.c
rwxrwxrwx    1 root     root          969 fsync.c
rwxrwxrwx    1 root     root         4096 hwk_code
rwxrwxrwx    1 root     root          329 Makefile
rwxrwxrwx    1 root     root          297 open.c
rwxrwxrwx    1 root     root          787 print_dir.c
rwxrwxrwx    1 root     root         1305 rename.c

16.3. Tail

Write a program that prints out the last few lines of a file. The program should be efficient, in that it seeks to near the end of the file, reads in a block of data, and then goes backwards until it finds the requested number of lines; at this point, it should print out those lines from beginning to the end of the file. To invoke the program, one should type: mytail -n file, where n is the number of lines at the end of the file to print. Useful interfaces: stat(), lseek(), open(), read(), close().

CODE

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>

#define BLOCK_SIZE 4096

void print_last_n_lines(int fd, off_t file_size, int n)
{
  char buffer[BLOCK_SIZE];
  off_t offset = file_size;
  int lines_read = 0;
  int buffer_len;

  // Seek to the end of the file and read backwards
  while (lines_read <= n && offset > 0)
  {
    // Calculate the block size to read
    off_t block_size = (offset > BLOCK_SIZE) ? BLOCK_SIZE : offset;
    offset -= block_size;

    // Seek to the calculated offset
    if (lseek(fd, offset, SEEK_SET) == -1)
    {
      perror("lseek");
      exit(EXIT_FAILURE);
    }

    // Read the block
    buffer_len = read(fd, buffer, block_size);
    if (buffer_len == -1)
    {
      perror("read");
      exit(EXIT_FAILURE);
    }

    // Find newlines in the block
    for (int i = buffer_len - 1; i >= 0; i--)
    {
      if (buffer[i] == '\n')
      {
        lines_read++;
        if (lines_read == n)
        {
          // Print from the start of the buffer to the current position
          if (offset > 0)
          {
            write(STDOUT_FILENO, buffer + i + 1, buffer_len - i - 1);
          }
          else
          {
            write(STDOUT_FILENO, buffer, buffer_len);
          }
          return;
        }
      }
    }
  }

  // If we haven't read enough lines, print the entire block
  if (lines_read < n)
  {
    write(STDOUT_FILENO, buffer, buffer_len);
  }
}

int main(int argc, char *argv[])
{
  if (argc != 4 || strncmp(argv[1], "-n") != 0)
  {
    fprintf(stderr, "Usage: %s -n <number> <file>\n", argv[0]);
    return EXIT_FAILURE;
  }

  int n = atoi(argv[2]); // Extract the number from "-n"
  if (n <= 0)
  {
    fprintf(stderr, "Number of lines must be positive.\n");
    return EXIT_FAILURE;
  }

  const char *file_path = argv[3];
  int fd = open(file_path, O_RDONLY);
  if (fd == -1)
  {
    perror("open");
    return EXIT_FAILURE;
  }

  struct stat file_stat;
  if (fstat(fd, &file_stat) == -1)
  {
    perror("fstat");
    close(fd);
    return EXIT_FAILURE;
  }

  print_last_n_lines(fd, file_stat.st_size, n);

  close(fd);
  return EXIT_SUCCESS;
}

OUTPUT

有个 bug,n和实际输出的行数不对应...不知道怎么修了

root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter39/hwk_code# gcc -o Tail Tail.c
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter39/hwk_code# ./Tail -n 4 Makefile
all:
        Stat LF Tail RS

Stat:
        gcc -o Stat Stat.c -Wall

LF:
        gcc -o LF LF.c -Wall

Tail:
        gcc -o Tail Tail.c -Wall

RS:
        gcc -o RS RS.c -Wall

clean:
        rm -f Stat LF Tail RS

16.4. Recursive Search

Write a program that prints out the names of each file and directory in the file system tree, starting at a given point in the tree. For example, when run without arguments, the program should start with the current working directory and print its contents, as well as the contents of any sub-directories, etc., until the entire tree, root at the CWD, is printed. If given a single argument (of a directory name), use that as the root of the tree instead. Refine your recursive search with more fun options, similar to the powerful find command line tool. Useful interfaces: figure it out.

CODE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>

// Function to print the contents of a directory recursively
void list_directory(const char *path, int level)
{
  DIR *dir;
  struct dirent *entry;
  struct stat file_stat;
  char full_path[1024];

  // Open the directory
  dir = opendir(path);
  if (!dir)
  {
    perror("opendir");
    return;
  }

  // Read directory entries
  while ((entry = readdir(dir)) != NULL)
  {
    if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
    {
      continue; // Skip "." and ".."
    }

    // Construct full path
    snprintf(full_path, sizeof(full_path), "%s/%s", path, entry->d_name);

    // Print the path with indentation based on the level
    for (int i = 0; i < level; i++)
    {
      printf("  ");
    }
    printf("%s\n", entry->d_name);

    // Get file information
    if (lstat(full_path, &file_stat) == -1)
    {
      perror("lstat");
      continue;
    }

    // If it's a directory, recurse into it
    if (S_ISDIR(file_stat.st_mode))
    {
      list_directory(full_path, level + 1);
    }
  }

  // Close the directory
  closedir(dir);
}

int main(int argc, char *argv[])
{
  const char *root_path;

  // Determine the root directory
  if (argc == 1)
  {
    // Use the current working directory if no argument is provided
    char cwd[1024];
    if (getcwd(cwd, sizeof(cwd)) == NULL)
    {
      perror("getcwd");
      return EXIT_FAILURE;
    }
    root_path = cwd;
  }
  else if (argc == 2)
  {
    // Use the specified directory as the root
    root_path = argv[1];
  }
  else
  {
    fprintf(stderr, "Usage: %s [directory]\n", argv[0]);
    return EXIT_FAILURE;
  }

  // List the contents of the root directory
  list_directory(root_path, 0);

  return EXIT_SUCCESS;
}

OUTPUT

root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter39/hwk_code# ./RS
LF
LF.c
Makefile
RS
RS.c
Stat
Stat.c
Tail
Tail.c

Last updated