首页 > 代码库 > 在CentOS 5.5上使用sed遇到的一个bug

在CentOS 5.5上使用sed遇到的一个bug

在 CentOS 5.5 上使用 sed 遇到一个bug

$ echo AAA > config
$ ln -s config cfg
$ sed -i ‘s/AAA/aaa/‘ cfg
sed: ck_follow_symlink: couldn‘t lstat c/config: No such file or directory

这个bug发生在 sed -i do_sth symbolic_links_without_slash 时

下载它的源代码 https://google-search-appliance-mirror.googlecode.com/files/sed-4.1.5-5.fc6.src.rpm

解压开可以看到redhat在sed-4.1.5上打上了3个补丁

sed-4.1.5-bz185374.patch

sed-4.1.5-relsymlink.patch

sed-4.1.5-utf8performance.patch


redhat为什么要为sed打上补丁呢,这源于另外一个问题,当sed -i do_sth file时实际的过程是这样的

sed do_sth file > tmp
mv tmp file

那么问题来了,如果file是指向文件file2的软连接,sed -i do_sth file 的结果是:

sed "剪短" 了 file 到 file2 的软连接
file2 的内容不会改变
file 成为一个file2的副本 它的内容改变了 (如果do_sth会修改)

这本身不是一个大问题 (算 sed 的一个 "小坑"吧)

不过有人试图解决这个问题, 所以就有了 sed-4.1.5-bz185374.patch 

这个补丁里面新增了一个函数 ck_follow_symlink 

它核心的地方是使用readlink

比如 文件 a -> b 文件 b -> c

ck_follow_symlink能够成功的寻迹到最终的文件 a -> c

不过 ck_follow_symlink 在有种情况下不能正常工作

文件 a -> ../b 而文件 b -> c

也就是说 a -> ../c 而 ck_follow_symlink 把它理解为了 a -> c

也就是说新引入了一个问题


为了解决这个新问题,所以又有了补丁sed-4.1.5-relsymlink.patch

这个补丁就是为了解决 a -> ../b 且 b -> c 这样的场景

解决方案是这样的:(补丁片段)

  //buf 是记录当前文件名的buffer  
  err = readlink (buf, buf2, bufsize);
  buf2 [err] = ‘\0‘;
  if (buf2[0] != ‘/‘)
    {
      dir = dirname (buf);    // dir part of orig path
      int len = strlen (dir); // orig path len
      buf[len] = ‘/‘;
      strncpy (buf+len+1, buf2, bufsize - len - 1);

那么问题来了,dirname 是怎么工作的

char ch[] = "/tmp/123";
char * p = dirname(ch);

dirname(ch) 的行为是 把 ch[4] 从 ‘/‘ 改成了 0 并把ch的地址作为返回值

但当 char ch[] = "tmp"时 dirname(ch) 的行为又是如何呢?

答案是 不对ch做任何修改 dirname(ch) 返回一个指针 这个指针指向一个常字符串 "." 

这里对dirname的使用是错误的, 他错误的认为dirname一定会去修改入参并返回入参地址

所以对dirname的错误使用引入了文章开头的那个bug

这个bug在存在于CentOS/RHEL/Fedora这一支的某些版本中, 有无最终修复未考证


对与sed主线,它没有这个问题,对于sed -i 作用与软连接 "剪短" 软连接这个问题

在4.1.5之后的 sed 加了一个新参数--follow-symlinks,它可以保证sed -i 时不“剪断”软连接

没有这个参数时 sed -i 的行为和之前版本一致


在CentOS 5.5上使用sed遇到的一个bug