对于Unix/Linux程序员来说,"rm -rf /"一直被认为是一个极度危险的操作,因为直接把根目录给删除了,整个操作系统也就崩溃了。但实际上会是这样的吗?呵呵,请看图:
啊哈,世界并没有安静,一如既往地喧嚣。怎么回事儿?让我们来扒一扒源代码,
01 - 下载源代码(coreutils-8.30)
root# cd /tmp root# wget https://download.fedoraproject.org/pub/fedora/linux/updates/29/Everything/SRPMS/Packages/c/coreutils-8.30-7.fc29.src.rpm root# mkdir /tmp/coreutils root# rpm -ivh --define '_topdir /tmp/coreutils' coreutils-8.30-7.fc29.src.rpm root# cd /tmp/coreutils/SOURCES root# ls -l *.xz -rw-rw-r--. 1 root root 5359532 Jul 2 2018 coreutils-8.30.tar.xz root# tar Jxf coreutils-8.30.tar.xz
02 - 查看rm -rf /的运行轨迹
1 root@intel-sharkbay-dh-02:/# strace rm -rf / 2 execve("/usr/bin/rm", ["rm", "-rf", "/"], 0x7ffde6de8960 /* 43 vars */) = 0 3 brk(NULL) = 0x558b0cb0f000 4 ...<snip>... 5 lstat("/", {st_mode=S_IFDIR|0555, st_size=224, ...}) = 0 6 newfstatat(AT_FDCWD, "/", {st_mode=S_IFDIR|0555, st_size=224, ...}, AT_SYMLINK_NOFOLLOW) = 0 7 openat(AT_FDCWD, "/usr/share/locale/en_US.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) 8 ...<snip>... 9 openat(AT_FDCWD, "/usr/share/locale/en/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) 10 write(2, "rm: ", 4rm: ) = 4 11 write(2, "it is dangerous to operate recur"..., 45it is dangerous to operate recursively on '/') = 45 12 write(2, "\n", 1 13 ) = 1 14 write(2, "rm: ", 4rm: ) = 4 15 write(2, "use --no-preserve-root to overri"..., 48use --no-preserve-root to override this failsafe) = 48 16 write(2, "\n", 1 17 ) = 1 18 lseek(0, 0, SEEK_CUR) = -1 ESPIPE (Illegal seek) 19 close(0) = 0 20 close(1) = 0 21 close(2) = 0 22 exit_group(1) = ? 23 +++ exited with 1 +++
注意第5行和11行:
5 lstat("/", {st_mode=S_IFDIR|0555, st_size=224, ...}) = 0 11 write(2, "it is dangerous to operate recur"..., 45it is dangerous to operate recursively on '/') = 45
03 - 查看rm对应的源代码
03.01 main() in rm.c
/* coreutils-8.30/src/rm.c#209 */208 int 209 main (int argc, char **argv) 210 { 211 bool preserve_root = true; ... 229 while ((c = getopt_long (argc, argv, "dfirvIR", long_opts, NULL)) != -1) 230 { 231 switch (c) 232 { ... 237 case 'f': 238 x.interactive = RMI_NEVER; 239 x.ignore_missing_files = true; 240 prompt_once = false; 241 break; ... 255 case 'r': 256 case 'R': 257 x.recursive = true; 258 break; ... 341 } 342 343 if (x.recursive && preserve_root) 344 { 345 static struct dev_ino dev_ino_buf; 346 x.root_dev_ino = get_root_dev_ino (&dev_ino_buf); 347 if (x.root_dev_ino == NULL) 348 die (EXIT_FAILURE, errno, _("failed to get attributes of %s"), 349 quoteaf ("/")); 350 } ... 370 enum RM_status status = rm (file, &x); 371 assert (VALID_STATUS (status)); 372 return status == RM_ERROR ? EXIT_FAILURE : EXIT_SUCCESS; 373 }
在第346行,调用了函数get_root_dev_info(), 用以获取 root设备信息。
346 x.root_dev_ino = get_root_dev_ino (&dev_ino_buf);
其中,get_root_dev_info()实现如下:
/* coreutils-8.30/lib/root-dev-ino.c */25 /* Call lstat to get the device and inode numbers for '/'. 26 Upon failure, return NULL. Otherwise, set the members of 27 *ROOT_D_I accordingly and return ROOT_D_I. */ 28 struct dev_ino * 29 get_root_dev_ino (struct dev_ino *root_d_i) 30 { 31 struct stat statbuf; 32 if (lstat ("/", &statbuf)) 33 return NULL; 34 root_d_i->st_ino = statbuf.st_ino; 35 root_d_i->st_dev = statbuf.st_dev; 36 return root_d_i; 37 }
第32行调用了lstat(), 这和我们在前面看到的strace rm -rf /的输出正好对应。
5 lstat("/", {st_mode=S_IFDIR|0555, st_size=224, ...}) = 0
rm.c的第370行调用了函数rm(), 这是我们接下来要重点分析的函数。
370 enum RM_status status = rm (file, &x);
03.02 rm() in remove.c
/* coreutils-8.30/src/remove.c#576 */574 /* Remove FILEs, honoring options specified via X. 575 Return RM_OK if successful. */ 576 enum RM_status 577 rm (char *const *file, struct rm_options const *x) 578 { 579 enum RM_status rm_status = RM_OK; 580 581 if (*file) 582 { ... 590 FTS *fts = xfts_open (file, bit_flags, NULL); 591 592 while (1) 593 { ... 596 ent = fts_read (fts); ... 607 enum RM_status s = rm_fts (fts, ent, x); ... 610 UPDATE_STATUS (rm_status, s); 611 } ... 618 } 619 620 return rm_status; 621 }
在第607行,函数rm()调用了函数rm_fts()。
03.03 rm_fts() in remove.c
/* coreutils-8.30/src/remove.c#418 */417 static enum RM_status 418 rm_fts (FTS *fts, FTSENT *ent, struct rm_options const *x) 419 { 420 switch (ent->fts_info) 421 { ... 438 /* Perform checks that can apply only for command-line arguments. */ 439 if (ent->fts_level == FTS_ROOTLEVEL) 440 { ... 454 /* POSIX also says: 455 If a command line argument resolves to "/" (and --preserve-root 456 is in effect -- default) diagnose and skip it. */ 457 if (ROOT_DEV_INO_CHECK (x->root_dev_ino, ent->fts_statp)) 458 { 459 ROOT_DEV_INO_WARN (ent->fts_path); 460 fts_skip_tree (fts, ent); 461 return RM_ERROR; 462 } ... 571 } 572 }
在第457行和459行,分别调用了宏函数。其中,L457的宏是把当前操作目录与根(/)做比较。
457 if (ROOT_DEV_INO_CHECK (x->root_dev_ino, ent->fts_statp))
而L459是打印警告信息。
459 ROOT_DEV_INO_WARN (ent->fts_path);
03.04 ROOT_DEV_INO_WARN() in lib/root-dev-info.h
/* coreutils-8.30/lib/root-dev-ino.h#33 */33 # define ROOT_DEV_INO_WARN(Dirname) \ 34 do \ 35 { \ 36 if (STREQ (Dirname, "/")) \ 37 error (0, 0, _("it is dangerous to operate recursively on %s"), \ 38 quoteaf (Dirname)); \ 39 else \ 40 error (0, 0, \ 41 _("it is dangerous to operate recursively on %s (same as %s)"), \ 42 quoteaf_n (0, Dirname), quoteaf_n (1, "/")); \ 43 error (0, 0, _("use --no-preserve-root to override this failsafe")); \ 44 } \ 45 while (0)
第37行和43行打印的信息,和我们执行rm -rf /之后看到的信息完全一致。
root@intel-sharkbay-dh-02:/# rm -rf / rm: it is dangerous to operate recursively on '/' rm: use --no-preserve-root to override this failsafe
03.05 ROOT_DEV_INO_CHECK() in lib/root-dev-info.h
/* coreutils-8.30/lib/root-dev-ino.h#30 */30 # define ROOT_DEV_INO_CHECK(Root_dev_ino, Dir_statbuf) \ 31 (Root_dev_ino && SAME_INODE (*Dir_statbuf, *Root_dev_ino))
宏SAME_INODE的实现如下:
/* coreutils-8.30/lib/same-inode.h#42 */42 # define SAME_INODE(a, b) \ 43 ((a).st_ino == (b).st_ino \ 44 && (a).st_dev == (b).st_dev)
到此为止,我们就完全看明白了为什么"rm -rf /"会被拒绝执行,因为现代版的rm命令已经已经针对野蛮的"rm -rf /"行为做了安全性检查。
结论:
- 在现代的Unix/Linux系统中,"rm -rf /"不再危险,因为rm命令本身做了相应的安全检查。也就是说,不但"rm -rf /"不危险,而且"rm -rf //"和"rm -rf /tmp/../"之类的也不危险。
- 虽然"rm -rf /"已经不再危险,但是"rm -rf /*"还是很危险滴,因为通配符'*'的存在。如好奇,请在你的虚拟机中予以尝试 。但是,本人强烈不推荐这么做! 否则,如下类似的输出就会泛洪于你的终端之上,即使你立即按CTRL+C也于事无补,至少常见的系统命令诸如ls, rm是立马不见了。。。
root@hp-moonshot-03-c31:/# id -un root root@hp-moonshot-03-c31:/# rm -rf /* rm: cannot remove '/boot/efi': Device or resource busy rm: cannot remove '/dev/mqueue': Device or resource busy rm: cannot remove '/dev/hugepages': Device or resource busy rm: cannot remove '/dev/pts/1': Operation not permitted ...<snip>... rm: cannot remove '/proc/63/mountstats': Operation not permitted rm: cannot remove '/proc/63/clear_refs': Operation not permitted ^C root@hp-moonshot-03-c31:/# root@hp-moonshot-03-c31:/# ls bash: /usr/bin/ls: No such file or directory
因为
root# rm -rf /* 等价于 root# rm -rf /bin /boot /dev /etc /home /lib /lib64 /lost+found /media /misc /mnt /net /nfs /opt /proc /root /run /sbin /srv /sys /tmp /usr /var
所以
!!! NEVER TRY "rm -rf /*" !!!