對于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 /*" !!!