Clamav殺毒軟件源碼分析筆記[二]

 

 Clamav殺毒軟件源碼分析筆記[二]


刺蝟@http://blog.csdn.net/littlehedgehog





[讀取命令行參數]


開始正兒八經地分析源碼了. 從哪裏入手呢,當然還是按照書上的來,我們先來看看Clamd的程序如何從生到死,Clamd是殺毒軟件的服務端,我們在客戶端提交要查殺的信息,然後Clamd服務端進行按照我們指令辦事,然後把結果返回給我們。這個貌似是unix程序的一個經典構架了,所謂的客戶端直接跟用戶打交道,然後解析用戶輸入,把真正要做的工作再傳給服務端完成。服務端其實是一個無名英雄了。 話說回來這裏第一步我們就要處理命令行下瑣碎的各個參數選項.爲了讓Clamd服務端按照我們要求來運行,這貌似是個相當麻煩的工作,比女人都麻煩,呵呵. 不過還好網上有不少linux fans 都寫過相應的博文了.  還是先來看源碼吧: [./clamd/options.c]


  1. /* 這個是clamd的主函數 文件名爲options,那麼這個主函數就主要在處理這個選項(參數選項)問題了*/
  2. int main(int argc, char **argv)
  3. {
  4.     int ret, opt_index, i, len;
  5.     struct optstruct *opt;
  6.     const char *getopt_parameters = "hc:V";
  7.     static struct option long_options[] =
  8.     {
  9.         {"help", 0, 0, 'h'},
  10.         {"config-file", 1, 0, 'c'},
  11.         {"version", 0, 0, 'V'},
  12.         {"debug", 0, 0, 0},
  13.         {0, 0, 0, 0}
  14.     };
  15. #if defined(C_LINUX) && defined(CL_DEBUG)
  16.     /* [email protected]: create a dump if needed */
  17.     struct rlimit rlim;
  18.     rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
  19.     if (setrlimit(RLIMIT_CORE, &rlim) < 0)
  20.         perror("setrlimit");
  21. #endif
  22.     opt=(struct optstruct*)mcalloc(1, sizeof(struct optstruct));
  23.     opt->optlist = NULL;
  24.     opt->filename = NULL;
  25.     while (1)
  26.     {
  27.         opt_index=0;
  28.         ret=getopt_long(argc, argv, getopt_parameters, long_options, &opt_index);
  29.         if (ret == -1)
  30.             break;
  31.         switch (ret)
  32.         {
  33.         case 0:
  34.             register_long_option(opt, long_options[opt_index].name);
  35.             break;
  36.         default:
  37.             if (strchr(getopt_parameters, ret))
  38.                 register_char_option(opt, ret);
  39.             else
  40.             {
  41.                 fprintf(stderr, "ERROR: Unknown option passed./n");
  42.                 free_opt(opt);
  43.                 exit(40);
  44.             }
  45.         }
  46.     }
  47.     if (optind < argc)
  48.     {
  49.         len=0;
  50.         /* count length of non-optin arguments 下面是處理非選項參數*/
  51.         for (i=optind; i<argc; i++)
  52.             len+=strlen(argv[i]);
  53.         len=len+argc-optind-1; /* add spaces between arguments */
  54.         opt->filename=(char*)mcalloc(len + 256, sizeof(char));
  55.         for (i=optind; i<argc; i++)
  56.         {
  57.             strncat(opt->filename, argv[i], strlen(argv[i]));
  58.             if (i != argc-1)
  59.                 strncat(opt->filename, " ", 1);
  60.         }
  61.     }
  62.     clamd(opt);
  63.     free_opt(opt);
  64.     cli_dbgmsg("exit main()");
  65.     return(0);
  66. }

代碼不長,邏輯也不復雜. 不過在理解GNU提供給我們截獲參數選項操作之前,我們確實應該先來明確一下處理的關鍵點在哪裏.比如說有如下命令行 command -h   這是我們來獲取command使用幫助的時候鍵入的命令. 也就是說按照約定,我們鍵入-h 就是表明要顯示出幫助手冊,但是在linux/unix裏,我們有時候也鍵入全稱來輸入參數,比如說command --help 兩橫加上全稱,同樣也是表明我們需要幫助手冊.爲了能處理以上兩種情況,我們需要做更多的準備.  好了,下面的文字是網上拷來的,呵呵,這也是代碼複用的思想...



============================================

摘自冰火天地 《getopt_long及其使用》

========================================


Linux系統下,需要大量的命令行選項,如果自己手動解析他們的話實在是有違軟件複用的思想,不過還好,GNU C library留給我們一個解析命令行的接口(X/Open規範),好好使用它可以使你的程序改觀不少。

使用getopt_long()需要引入頭文件

#include <getopt.h>

     現在我們使用一個例子來說明它的使用。

一個應用程序需要如下的短選項和長選項。


      短選項               長選項                           作用

      -h                      --help                           輸出程序命令行參數說明然後退出
      -o filename        --output filename      給定輸出文件名
      -v                      --version                       顯示程序當前版本後退後

爲了使用getopt_long函數,我們需要先確定兩個結構:

1.一個字符串,包括所需要的短選項字符,如果選項後有參數,字符後加一個":"符號。本例中,這個字符串應該爲"ho:v"。(因爲-o後面有參數filename,所以字符後面要加":")

2.一個包含長選項字符串的結構體數組,每一個結構體包含4個域,第一個域爲長選項字符串,第二個域是一個標識,只能爲0或1,分別代表沒有、有。第三個域永遠爲NULL。第四個域爲對應的短選項字符串。結構體數組的最後一個元素全部爲NULL和0,標識結束。在本例中,它應該像一下的樣子:

     const struct option long_options[] = {
         { "help",        0, NULL, 'h' },
         { "output",      1, NULL, 'o' },
         { "version", 0, NULL, 'v' },
         { NULL,          0, NULL, 0}
      };

調用時需要把main的兩個參數argc和argv以及上述兩個數據結構傳給getopt_long。
每次調用getopt_long,它會解析一個符號,返回相應的短選項字符,如果解析完畢返回-1。所以需要使用一個循環來處理所有的參數,而相應的循環裏會使用switch語句進行選擇。如果getopt_long遇到一個無效的選項字符,它會打印一個錯誤消息並且返回'?',很多程序會打印出幫助信息並且中止運行;當getopt_long解析到一個長選項並且發現後面沒有參數則返回':',表示缺乏參數。當處理一個參數時,全局變量optarg指向下一個要處理的變量。當getopt_long處理完所有的選項後,全局變量optind指向第一個未知的選項索引。


這一個例子代碼爲下:


  1. //編譯使用gcc -o getopt_long getopt_long.c
  2. #include <getopt.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. /*程序的名字*/
  6. const char* program_name;
  7. /* 打印程序參數 */
  8. void print_usage (FILE* stream, int exit_code)
  9. {
  10. fprintf (stream, "Usage: %s options [ inputfile ... ]/n", program_name);
  11. fprintf (stream, " -h --help                       顯示這個幫助信息./n"
  12.                              " -o --output filename 將輸出定位到文件./n"
  13.                              " -v --version                  打印版本信息./n");
  14. exit (exit_code);
  15. }
  16. /* 主程序 */
  17. int main (int argc, char* argv[])
  18. {
  19. int next_option;//下一個要處理的參數符號
  20. int haveargv = 0;//是否有我們要的正確參數,一個標識
  21.            
  22. /* 包含短選項字符的字符串,注意這裏的‘:’ */
  23.           
  24. const charconst short_options = "ho:v";
  25.               
  26. /* 標識長選項和對應的短選項的數組 */
  27.               
  28. const struct option long_options[] = {
  29.                    { "help",        0, NULL, 'h' },
  30.                    { "output",      1, NULL, 'o' },
  31.                    { "version", 0, NULL, 'v' },
  32.                    { NULL,         0, NULL, 0     }};//最後一個元素標識爲NULL
  33.                     
  34.     /* 此參數用於承放指定的參數,默認爲空 */
  35. const char* output_filename = NULL;
  36. /* 一個標誌,是否顯示版本號 */
  37. int verbose = 0;
  38. /* argv[0]始終指向可執行的文件文件名 */
  39.                              
  40. program_name = argv[0];
  41.     
  42. do
  43. {
  44.     next_option = getopt_long (argc, argv, short_options, long_options, NULL);
  45.     switch (next_option)
  46.     {
  47.      case 'h':     /* -h or --help */    
  48.        haveargv = 1;
  49.        print_usage (stdout, 0);
  50.      case 'o':     /* -o or --output */
  51.          /* 此時optarg指向--output後的filename */
  52.       output_filename = optarg;
  53.       haveargv = 1;
  54.       break;
  55.      case 'v':     /* -v or    --version */
  56.       verbose = 1;
  57.       haveargv = 1;
  58.       break;
  59.      case ':':     /* 缺乏長選項內容 */
  60.       break;
  61.      case '?':     /* 出現一個未指定的參數*/
  62.       print_usage (stderr, 1);
  63.      case -1:      /* 處理完畢後返回-1 */
  64.              if (!haveargv)
  65.              {
  66.                    print_usage (stderr, 1);
  67.              }
  68.       break;
  69.      default:      /* 未指定的參數出現,出錯處理 */
  70.       print_usage (stderr, 1);
  71.                                   break;
  72.     }
  73. }while (next_option !=-1);
  74.                             
  75. if (verbose)
  76. {
  77.     int i;
  78.     for (i = optind; i < argc; ++i)
  79.     printf ("Argument: %s/n", argv[i]);
  80. }                           
  81.                         
  82. return 0;
  83. }

============================================

文章節錄完

============================================



把參數一個一個篩選出來後總要放一個地方吧,Clamav的作者選擇把參數選項嵌入到一個鏈表中以方便傳遞。那我們再來看看如何把得到參數加入鏈表。這裏以register_char_option爲例說明: 

  1. /*短選項加入鏈表 比如--config_file=test 注意 這裏雖然是個長選項 但是它含有短字符代表,即是字符c    {"config-file", 1, 0, 'c'}    */
  2. void register_char_option(struct optstruct *opt, char ch)
  3. {
  4.     struct optnode *newnode;
  5.     newnode = (struct optnode *) mmalloc(sizeof(struct optnode));
  6.     newnode->optchar = ch;
  7.     if (optarg != NULL)
  8.     {
  9.         newnode->optarg = (char *) mcalloc(strlen(optarg) + 1, sizeof(char));   //參數值,這裏就是test
  10.         strcpy(newnode->optarg, optarg);
  11.     }
  12.     else
  13.         newnode->optarg = NULL;
  14.     newnode->optname = NULL;    
  15.     newnode->next = opt->optlist;   //連入鏈表
  16.     opt->optlist = newnode;
  17. }
opt和newnode的結構如下所示:


  1. struct optnode
  2. {
  3.     char optchar;   //短選項名 比如文中舉例的 h 選項 
  4.     char *optarg;   //參數值, 有時候我們對參數要設定值的,比如 -p 23 這裏23就是參數值
  5.     char *optname; //長選項名, 文中舉例的help這個字符串就應該放在這裏,跟前面h對應
  6.     struct optnode *next; //下一個節點唄,我們要連成鏈表阿
  7. };

  8. struct optstruct
  9. {
  10.     struct optnode *optlist;
  11.     char *filename;
  12. };

這樣我們就得到了一個滿載參數的鏈表了,這樣函數傳參就會很簡單,因爲直接可以傳遞這個鏈表即可,這樣我們拿着鏈表就相當於得到了用戶所有需求,比如你看看main函數裏面馬上就跟着一句 clamd(opt);  這就是爲什麼我們要急着轉化爲鏈表的原因了。如果需要在鏈表中搜尋所需的參數那是很簡單的,只需要遍歷鏈表即可。比如:


  1. //尋找opt參數鏈表中是否含有指定字符ch的node
  2. int optc(const struct optstruct *opt, char ch)
  3. {
  4.     struct optnode *handler;
  5.     handler = opt->optlist;
  6.     while (1)
  7.     {
  8.         if (handler)
  9.         {
  10.             if (handler->optchar == ch)
  11.                 return 1;
  12.         }
  13.         else
  14.             break;
  15.         handler = handler->next;
  16.     }
  17.     return(0);
  18. }


非常簡潔,因爲我們參數本來就不多,否則我們應該重新設計數據結構,比如我們要用hash表來快速定位。
















發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章