重复创建软链接产生的奇怪现象

image-20240817175839125

问题现象

当前有个目录 a,需要对其创建一个软链接 b。

   tree
   .
   `-- a
       |-- a1.txt
       `-- a2.txt

   1 directory, 2 files

第一次执行ln -s a b

  1. 成功执行,符合预期

    tree
    .
    |-- a
    |   |-- a1.txt
    |   `-- a2.txt
    `-- b -> a
    
    2 directories, 2 files
  2. 通过软链能正确访问到 a1.txt

    ls -l b/a1.txt
    -rw-r--r-- 1 root root 0 Aug 17 02:08 b/a1.txt

第二次执行ln -s a b

  1. 成功执行,不符合预期

    tree
    .
    |-- a
    |   |-- a -> a
    |   |-- a1.txt
    |   `-- a2.txt
    `-- b -> a
    
    2 directories, 3 files
  2. 此时软链接 b 已存在,我的预期是执行失败,或者覆盖软链接 b,但实际上在 a 下创建了一个软链接 a,这是第一个问题

  3. 第二个问题,为什么新创建的软链接文件名是 a,而不是 b

  4. 第三个问题,通过新创建的软链,无法访问到 a1.txt

    ls -l a/a/a1.txt
    ls: cannot access 'a/a/a1.txt': Too many levels of symbolic links

第三次执行ln -s a b

执行失败,提示软链接 b/a 已存在。

ln -s a b
ln: failed to create symbolic link 'b/a': File exists

第四个问题,为什么第二次执行都没报错,第三次却报错了?到底能不能重复执行?

软链接原理

先介绍下文件系统。

磁盘布局

在 VSFS(Very SimpleFile System,简单文件系统)中,磁盘布局如下图所示。

image-20240817153944782

superblock

超级块里存储了文件系统信息,inode 数量,数据块数量,inode 区域的开始位置等信息。

inode bitmap

用 bitmap 来储存 inode 区域的分配情况。当写入一个新文件时,需要创建一个 inode,保存在 inode 空闲区域。如果直接扫描 inode 区域,寻找空闲位置,是非常耗费 IO 性能的。而通过 inode bitmap,只需要一次 IO,就能知道整个 inode 区域的分配情况。再在内存中遍历下,得到一个空闲的 inode 号。通过 inode 号,就能找到 inode 区域中对应位置。

data bitmap

和 inode bitmap 功能类似。

inode

inode 是index node(索引节点)的缩写,因为inode号用于索引磁盘上的inode数组,以便查找该inode号对应的inode。

inode 用于保存文件的元数据,例如文件类型(例如,常规文件、目录等)、大小、分配给它的块数、保护信息(如谁拥有该文件以及谁可以访问它)、一些时间信息(包括文件创建、修改或上次访问的时间文件下),以及有关其数据块驻留在磁盘上的位置的信息(如某种类型的指针)。

注意,inode 里没有文件名,在文件系统中,文件的路径是由目录文件构成的。每个目录在数据块区域是一个目录文件,包含这个目录下的文件名、文件的 inode 号、这条记录的长度、文件名字符串的长度等信息。所以通过硬链接或软链接,可以使用不同的路径访问到同一个文件。

延伸想想同一个文件系统下,mv 命令是如何工作的。

使用 df -i 命令查看 inode 的使用量

df -i
Filesystem      Inodes  IUsed   IFree IUse% Mounted on
overlay        3907584 119123 3788461    4% /
tmpfs          1003656     17 1003639    1% /dev
shm            1003656      1 1003655    1% /dev/shm
/dev/vda1      3907584 119123 3788461    4% /etc/hosts
tmpfs          1003656      1 1003655    1% /sys/firmware

小文件太多,可能造成磁盘没满,但是 inode 用完了,导致无法创建文件。可通过上诉命令检查。

拿上面的目录举例

tree
.
`-- a
    |-- a1.txt
    `-- a2.txt

1 directory, 2 files

下图左边部分在磁盘的 inode 区域,右边部分在磁盘的数据块区域。

image-20240817153228006

执行软链命令后

ln -s a b

tree
.
|-- a
|   |-- a1.txt
|   `-- a2.txt
`-- b -> a

2 directories, 2 files

创建软链接执行了下列动作

  1. 修改当前目录对应的目录文件,增加软链接 b 的信息

  2. 创建软链接 b 的 inode

    注意,没有磁盘指针,软链接是不会在数据块区域分配空间。

    另外指向目标路径是个相对路径,和创建软链接的方式有关。

image-20240817161123665

软链接相对路径问题

准备 case

echo `date` > c.txt
mkdir d
ln -s c.txt d/c.txt

查看目录结果

tree
.
|-- c.txt
`-- d
    `-- c.txt -> c.txt

查看软链报错

cat d/c.txt
cat: d/c.txt: Too many levels of symbolic links

使用 stat 查看软链信息

stat d/c.txt
  File: d/c.txt -> c.txt
  Size: 5           Blocks: 0          IO Block: 4096   symbolic link
Device: 3bh/59d Inode: 533547      Links: 1
Access: (0777/lrwxrwxrwx)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2024-08-17 08:27:32.335451007 +0000
Modify: 2024-08-17 08:27:12.832303012 +0000
Change: 2024-08-17 08:27:12.832303012 +0000
 Birth: -

d/c.txt -> c.txt 这是一个相对路径,相对于软链本身,那么这个软链指向的是自己,所以报错:“Too many levels of symbolic links”。

ln 命令 help 信息里也有说明相对路径的情况。

ln --help
Usage: ln [OPTION]... [-T] TARGET LINK_NAME
  or:  ln [OPTION]... TARGET
  or:  ln [OPTION]... TARGET... DIRECTORY
  or:  ln [OPTION]... -t DIRECTORY TARGET...
In the 1st form, create a link to TARGET with the name LINK_NAME.
In the 2nd form, create a link to TARGET in the current directory.
In the 3rd and 4th forms, create links to each TARGET in DIRECTORY.
Create hard links by default, symbolic links with --symbolic.
By default, each destination (name of new link) should not already exist.
When creating hard links, each TARGET must exist.  Symbolic links
can hold arbitrary text; if later resolved, a relative link is
interpreted in relation to its parent directory.

如何避免此问题?

  1. 使用绝对路径。
  2. 创建软链时,先进入到即将创建的软链的所在目录,然后被软链文件使用相对于当前目录的路径。

解释问题现象

第一个问题

为什么第二次执行ln -s a b时,没有覆盖软链接 b,而是在 a 下创建了一个软链接 a?

第一次创建软链后,生成了软链 b。在第二次创建软链时,会判断目标路径是否是存在的目录,此时目标路径是 b,它指向了 a,所以目标路径是存在的 a,那么就在 a 目录下创建软链。也就是上面ln --help信息里的第三种和第四种用法。

第二个问题

为什么新创建的软链接文件名是 a,而不是 b?

创建软链的目标路径是目录时,就在该目录下创建自身的同名软链,指向自己。

第三个问题

通过新创建的软链,无法访问到 a1.txt。

原因是上文提到的软链相对路径问题,检查 a 目录下的软链 a,看看它指向哪里。

stat a/a
  File: a/a -> a
  Size: 1           Blocks: 0          IO Block: 4096   symbolic link
Device: 3bh/59d Inode: 533417      Links: 1
Access: (0777/lrwxrwxrwx)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2024-08-17 03:18:11.949193005 +0000
Modify: 2024-08-17 03:18:10.232193004 +0000
Change: 2024-08-17 03:18:10.232193004 +0000
 Birth: -

发现它指向的是自己,而不是预期的父目录,所以访问它时报错:“Too many levels of symbolic links”。

第四个问题

为什么第二次执行都没报错,第三次却报错了?到底能不能重复执行?

原因是软链目录是存在的目录时,就在该目录下创建自身的同名软链,指向自己。而第二次执行时已经创建了这个软链 a/a,再次创建时就会报错:“ File exists”。

解决

方案一

创建软链前先检查软链是否存在,存在的话检查是否是指向预期的源文件。

方案二

使用 ln 命令的选项 n。

  • -n, --no-dereference treat LINK_NAME as a normal file if it is a symbolic link to a directory

  • 达到的效果是在第二次创建软链时,因为软链 b 已存在,报错。而不是在 b 软链指向的 a 目录下,再去创建一个软链指向自己。

    ln -sn a b
    ln: failed to create symbolic link 'b': File exists

参考

  1. 《操作系统导论》
  2. https://linuxconfig.org/how-to-fix-too-many-levels-of-symbolic-links-error
  3. https://unix.stackexchange.com/questions/588021/why-does-ln-s-create-a-directory-if-soft-link-exists?rq=1
作者:Yuyy
博客:https://yuyy.info
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇