Linux系統命令“rm -rf”的實現
一、基本概念
1、“rm -rf”的意義
-r或-R:遞歸處理,將指定目錄下的所有文件與子目錄一並處理;
-f:強制刪除文件或目錄;
由於rm命令只能刪除空的目錄,因此當我們需要刪除一個非空目錄時可以使用“rm -rf”命令,通過遞歸的方式先將該目錄中的文件刪除使其成為一個空的目錄后再將其刪除,從而達到刪除一個非空目錄的效果。
二、重要函數與結構體
1、目錄操作函數
1 #include <sys/types.h> 2 #include <dirent.h> 3 4 DIR *opendir(const char *name); 5 DIR *fdopendir(int fd); 6 7 8 #include <dirent.h> 9 10 struct dirent *readdir(DIR *dirp); 11 12 struct dirent { 13 ino_t d_ino; /* inode number */ 14 off_t d_off; /* offset to the next dirent */ 15 unsigned short d_reclen; /* length of this record */ 16 unsigned char d_type; /* type of file; not supported by all file system types */ 17 char d_name[256]; /* filename */ 18 };
2、 獲取文件信息
這里必須使用 int lstat(const char *path, struct stat *buf); 函數,否則在處理鏈接文件時會將其鏈接的原文件作為處理對象,而不是其本身。
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <unistd.h> 4 5 int stat(const char *path, struct stat *buf); 6 int fstat(int fd, struct stat *buf); 7 int lstat(const char *path, struct stat *buf); 8 9 struct stat { 10 dev_t st_dev; /* ID of device containing file */ 11 ino_t st_ino; /* inode number */ 12 mode_t st_mode; /* protection */ 13 nlink_t st_nlink; /* number of hard links */ 14 uid_t st_uid; /* user ID of owner */ 15 gid_t st_gid; /* group ID of owner */ 16 dev_t st_rdev; /* device ID (if special file) */ 17 off_t st_size; /* total size, in bytes */ 18 blksize_t st_blksize; /* blocksize for file system I/O */ 19 blkcnt_t st_blocks; /* number of 512B blocks allocated */ 20 time_t st_atime; /* time of last access */ 21 time_t st_mtime; /* time of last modification */ 22 time_t st_ctime; /* time of last status change */ 23 };
3、文件類型及權限的判斷
1 The following POSIX macros are defined to check the file type using the st_mode field:
2
3 S_ISREG(m) is it a regular file?
4 S_ISDIR(m) directory?
5 S_ISCHR(m) character device?
6 S_ISBLK(m) block device?
7 S_ISFIFO(m) FIFO (named pipe)?
8 S_ISLNK(m) symbolic link? (Not in POSIX.1-1996.)
9 S_ISSOCK(m) socket? (Not in POSIX.1-1996.)
4、當前用戶ID
1 #include <sys/types.h> 2 #include <pwd.h> 3 4 struct passwd *getpwnam(const char *name); 5 struct passwd *getpwuid(uid_t uid); 6 7 8 The passwd structure is defined in <pwd.h> as follows: 9 10 struct passwd { 11 char *pw_name; /* username */ 12 char *pw_passwd; /* user password */ 13 uid_t pw_uid; /* user ID */ 14 gid_t pw_gid; /* group ID */ 15 char *pw_gecos; /* user information */ 16 char *pw_dir; /* home directory */ 17 char *pw_shell; /* shell program */ 18 };
5、 刪除文件函數
該函數比較危險,測試代碼時建議先注釋,通過printf函數查看刪除文件路徑,確認后再使用。
1 #include <stdio.h> 2 3 int remove(const char *pathname); 4 5 On success, zero is returned. On error, -1 is returned, and errno is set appropriately.
三、執行結果及對比
1、 執行前
2、執行效果
3、 執行后
四、總結
總的來說,實現“rm -rf”功能所涉及的知識面不廣,代碼有較多的重復片段,可以在實現“ls -R”功能的基礎上進行,但程序細節部分較多,而且程序測試的風險極高,總體難度一般,最重要的是要做好安全備份工作。細節部分具體可以參考程序中的注釋,這里主要說明下前期的安全准備工作:
1. 進行Linux系統的備份,Ubuntu虛擬機系統可以通過Virtual box進行系統備份;
2. 在進入刪除函數之前加一道“確認”流程,以免誤操作;
3. 將所有remove函數注釋,先輸出所刪除文件路徑,所有路徑確認無誤后再取消注釋。
五、實現代碼
1、 myrm.h
1 #ifndef _MYRM_H_ 2 #define _MYRM_H_ 3 4 #include <stdio.h> 5 #include <stdlib.h> 6 #include <string.h> 7 #include <limits.h> 8 #include <unistd.h> 9 #include <dirent.h> 10 #include <sys/stat.h> 11 #include <sys/types.h> 12 #include <fcntl.h> 13 #include <time.h> 14 #include <pwd.h> 15 #include <grp.h> 16 #include <stdbool.h> 17 18 // 該頭文件包含了自定義函數getch(),其功能是:接收單個鍵盤按鍵值,無需結束標志enter 19 #include "getch.h" 20 21 // 處理錯誤 22 void error_printf(const char* ); 23 24 // 刪除路徑下的文件 25 void rm_dir(const char* ); 26 27 // 判斷目錄下文件的擁有者是否是程序調用者 28 bool ismine_dir(const char* ); 29 bool ismine(const char* ); 30 31 #endif//_MYRM_H_
2、myrm.c
1 #include "myrm.h" 2 3 4 int main(const char argc, const char** argv) 5 { 6 char path[PATH_MAX+1] = {}; 7 8 if(argc == 2 && !(strcmp(argv[1],"-rf"))) // 判斷命令格式 9 strcpy(path,"."); 10 else if(argc != 3) 11 { 12 printf("格式有誤! \n"); 13 exit(EXIT_FAILURE); 14 } 15 else 16 strcpy(path,argv[2]); 17 18 if(!(strcmp(argv[1],"-rf"))) 19 { 20 struct stat file_message = {}; 21 int ret_stat = lstat(path, &file_message); 22 23 if(ret_stat == -1) 24 error_printf("stat"); 25 26 if(S_ISDIR(file_message.st_mode)) // 判斷是否為目錄 27 { 28 printf("路徑[%s]下的目錄將會被刪除,按‘enter’確認,其他任意鍵取消\n",path); // 防止誤操作 29 if(10 == getch()) // 自定義函數getch(),其功能是:接收單個鍵盤按鍵值,無需結束標志enter 30 rm_dir(path); 31 else 32 return 0; 33 } 34 else 35 printf("It is not dir!"); 36 } 37 else 38 { 39 printf("error in main!\n"); 40 exit(EXIT_FAILURE); 41 } 42 return 0; 43 }
3、main_myrm.c
1 #include "myrm.h" 2 3 // 處理錯誤 4 void error_printf(const char* funname) 5 { 6 perror(funname); 7 exit(EXIT_FAILURE); 8 } 9 10 // 刪除路徑下的文件 11 void rm_dir(const char* pathname) 12 { 13 if(!ismine_dir(pathname)) // 判斷該路徑目錄中包含的文件是否全部屬於程序調用者 14 { 15 printf("權限不足,無法刪除!"); 16 return; 17 } 18 19 char nextpath[PATH_MAX+1]; 20 21 DIR* ret_opendir = opendir(pathname); // 打開目錄"pathname" 22 if(ret_opendir == NULL) 23 error_printf("opendir"); 24 25 struct dirent* ret_readdir = NULL; // 定義readdir函數返回的結構體變量 26 while(ret_readdir = readdir(ret_opendir)) // 判斷是否讀取到目錄尾 27 { 28 char* filename = ret_readdir->d_name; // 獲取文件名 29 30 int end = 0; // 優化路徑 (其實無論幾個“/”疊加都不會影響路徑) 31 while(pathname[end]) 32 end++; 33 strcpy(nextpath,pathname); 34 if(pathname[end-1] != '/') 35 strcat(nextpath,"/"); 36 strcat(nextpath,filename); 37 38 struct stat file_message = {}; // 定義stat函數返回的結構體變量 39 int ret_stat = lstat(nextpath, &file_message); // 獲取文件信息 40 if(ret_stat == -1) // stat讀取文件錯誤則輸出提示信息 41 { 42 printf("%s error!", filename); 43 } 44 // 注意屏蔽當前目錄和上一級目錄,但不能用filename[0]='.'來判斷,這樣會導致無法刪除隱藏文件,從而導致目錄文件刪除失敗 45 else if(S_ISDIR(file_message.st_mode) && strcmp(filename,".") && strcmp(filename,"..")) 46 { 47 rm_dir(nextpath); 48 printf("delete dir :%s\n",nextpath); 49 remove(nextpath); 50 } 51 else if(strcmp(filename,".") && strcmp(filename,"..")) // 同上 52 { 53 struct passwd* pwd = getpwuid(file_message.st_uid); 54 55 if(pwd->pw_uid == getuid()) // 再次檢查,雙重保險 56 { 57 printf("delete file:%s\n",nextpath); 58 remove(nextpath); 59 } 60 } 61 } 62 closedir(ret_opendir); 63 printf("delete dir :%s\n",pathname); 64 remove(pathname); // 刪除完目錄中的文件后將自身刪除 65 } 66 67 bool ismine_dir(const char* pathname) 68 { 69 char nextpath[PATH_MAX+1]; 70 71 if(!ismine(pathname)) 72 return false; 73 74 DIR* ret_opendir = opendir(pathname); // 打開目錄"pathname" 75 if(ret_opendir == NULL) 76 error_printf("opendir"); 77 78 struct dirent* ret_readdir = NULL; // 定義readdir函數返回的結構體變量 79 while(ret_readdir = readdir(ret_opendir)) // 判斷是否讀取到目錄尾 80 { 81 char* filename = ret_readdir->d_name; // 獲取文件名 82 83 int end = 0; // 優化路徑 84 while(pathname[end]) 85 end++; 86 strcpy(nextpath,pathname); 87 if(pathname[end-1] != '/') 88 strcat(nextpath,"/"); 89 strcat(nextpath,filename); 90 91 struct stat file_message = {}; // 定義stat函數返回的結構體變量 92 int ret_stat = lstat(nextpath, &file_message); // 獲取文件信息 93 if(ret_stat == -1) // stat讀取文件錯誤則輸出提示信息 94 { 95 printf("%s error!", filename); 96 } 97 else if(S_ISDIR(file_message.st_mode) && strcmp(filename,".") && strcmp(filename,"..")) 98 { 99 if(!ismine_dir(nextpath)) 100 return false; 101 } 102 else if(strcmp(filename,".") && strcmp(filename,"..")) 103 { 104 if(!ismine(nextpath)) 105 return false; 106 } 107 } 108 closedir(ret_opendir); 109 return true; 110 } 111 112 bool ismine(const char* pathname) 113 { 114 struct stat file_message = {}; // 定義stat函數返回的結構體變量 115 int ret_stat = lstat(pathname, &file_message); // 獲取文件信息 116 if(ret_stat == -1) // stat讀取文件錯誤則輸出提示信息 117 { 118 printf("stat error!"); 119 } 120 struct passwd* pwd = getpwuid(file_message.st_uid); 121 if(pwd->pw_uid == getuid()) // 判斷該文件是否屬於程序調用者 122 return true; 123 else 124 return false; 125 }