main 함수
가장 먼저 main가 시작되겠지요. main에서 가장 먼저 처리하는 것은 getopt_long함수를 이용한 옵션처리입니다.
옵션처리 예제
옵션 처리를 알아보기 위해 다음처럼 간단한 예제를 만들어봤습니다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
char short_options[]="-Cl:n:";
struct option long_options[] = {
{"create", 0, 0, 'C'},
{"level", 1, 0, 'l'},
{"raid-disks",1, 0, 'n'},
{0, 0, 0, 0}
};
int main(int argc, char *argv[])
{
int option_index;
int opt;
while ((option_index = -1) ,
(opt=getopt_long(argc, argv,
short_options, long_options,
&option_index)) != -1) {
printf("%c %d %d %s\n", (char)opt, opt, option_index, optarg);
}
return 0;
}
이 예제는 mdadm.c 파일에 있는 main함수에서 옵션을 처리하는 부분중에 우리가 사용할 create, level, raid-disks옵션만 따로 뽑아놓은 것입니다.
create옵션은
- struct options에서 has_arg 필드가 0이므로 create옵션에 추가 파라미터가 없습니다. 즉 --create=arg 나 --create arg 같은 옵션이 없다고 설정합니다.
- 짧은 옵션은 'C'입니다. long_options에서 'C'로 지정했기 때문입니다.
level, raid-disks옵션은
- 추가 파라미터가 있습니다. struct option에서 has_arg필드가 1이므로 추가 파라미터가 있다고 설정했습니다.
- short_options에서 'l:', 'n:'처럼 옵션뒤에 ':'를 붙이는게 짧은 옵션을 지정하는 문자열에서 추가 파라미터가 있다고 설정하는 것입니다.
- 짧은 옵션은 각각 'l'과 'n'입니다.
그리고 optarg라는게 있는데, 옵션외에 문자열을 읽는 것입니다. 'l'과 'n'의 추가 옵션으로 찾아지는 "1"이나 "2"등을 가르키는 포인터입니다.
다음은 실행했을 때 동일하게 동작하는지를 본 것입니다.
~ $ gcc a.c
~ $ ./a.out --create /dev/md0 --level 1 --raid-disks 2 /dev/loop0 /dev/loop1
C 67 0 (null)
1 -1 /dev/md0
l 108 1 1
n 110 2 2
1 -1 /dev/loop0
1 -1 /dev/loop1
~ $ ./a.out -C /dev/md0 -l 1 -n 2 /dev/loop0 /dev/loop1
C 67 -1 (null)
1 -1 /dev/md0
l 108 -1 1
n 110 -1 2
1 -1 /dev/loop0
1 -1 /dev/loop1
짧은 옵션을 쓰면 getopt_long에서 option_index값을 반환시켜주지 못합니다. 하지만 옵션에 상관없이 opt값에는 동일한 값이 반환됩니다.
실행되는 순서대로 생각해보겠습니다.
가장 먼저 getopt_long함수는 'C'옵션을 처리한 후에 '/dev/md0'을 읽습니다. 이 문자열은 옵션에 해당하지 않으므로 getopt_long함수는 1을 반환합니다.
그리고 'l'옵션은 추가 파라미터가 있다고 했으므로, '1'문자열을 읽어서 optarg로 반환합니다.
'n'옵션도 마찬가지로 추가 파라미터가 있어서 optarg에 '2'가 반환됩니다.
'/dev/loop0'옵션과 '/dev/loop1'옵션은 옵션 리스트에 없으므로 opt갑이 1이 되고 optarg로 해당 문자열이 반환됩니다.
중요한 것은 short_options와 long_options가 동일한 옵션 처리를 하도록 만들었다는 것입니다.
mdadm의 main함수
그러면 이제 mdadm툴에서 어떻게 옵션을 처리하는지 보겠습니다.
먼저 RAID로 묶을 장치를 만들기 위해 다음과 같이 loop0와 loop1을 생성합니다.
$ mkdir ./tmp
$ dd if=/dev/zero of=./tmp/diska bs=1M count=10
10+0 records in
10+0 records out
10485760 bytes (10 MB) copied, 0,00434252 s, 2,4 GB/s
$ dd if=/dev/zero of=./tmp/diskb bs=1M count=10
10+0 records in
10+0 records out
10485760 bytes (10 MB) copied, 0,00425071 s, 2,5 GB/s
$ sudo losetup /dev/loop0 tmp/diska
$ sudo losetup /dev/loop1 tmp/diskb
그리고 다음과 같이 mdadm 프로그램으로 /dev/md0 장치를 만든다고 생각해보겠습니다.
$ sudo mdadm --create /dev/md0 --level 1 --raid-disks 2 /dev/loop0 /dev/loop1
main함수에서 '--create'옵션을 처리하는 코드를 보겠습니다.
while ((option_index = -1) ,
(opt=getopt_long(argc, argv,
shortopt, long_options,
&option_index)) != -1) {
...
case 'C': newmode = CREATE;
shortopt = short_bitmap_auto_options;
break;
...
} else if (!mode && newmode) {
mode = newmode;
if (mode == MISC && devs_found) {
pr_err("No action given for %s in --misc mode\n",
devlist->devname);
cont_err("Action options must come before device names\n");
exit(2);
}
'create'옵션을 만나면 mode = newmode = CREATE 값이 됩니다.
다음은 '/dev/md0' 옵션을 처리하는 코드입니다.
if (opt == 1) {
/* an undecorated option - must be a device name.
*/
if (devs_found > 0 && devmode == DetailPlatform) {
pr_err("controller may only be specified once. %s ignored\n",
optarg);
continue;
}
if (devs_found > 0 && mode == MANAGE && !devmode) {
pr_err("Must give one of -a/-r/-f for subsequent devices at %s\n", optarg);
exit(2);
}
if (devs_found > 0 && mode == GROW && !devmode) {
pr_err("Must give -a/--add for devices to add: %s\n", optarg);
exit(2);
}
dv = xmalloc(sizeof(*dv));
dv->devname = optarg;
dv->disposition = devmode;
dv->writemostly = writemostly;
dv->used = 0;
dv->next = NULL;
*devlistend = dv;
devlistend = &dv->next;
devs_found++;
continue;
}
옵션 리스트에 없는 문자열이므로 opt값은 1이 됩니다. 그러면 dv객체를 만들어서 optarg 값을 읽어서 '/dev/md0'문자열을 dv객체에 저장합니다.
그리고 devlist라는 리스트에 dv를 추가합니다.
다음은 'level'옵션을 처리하는 코드입니다.
case O(GROW,'l'):
case O(CREATE,'l'):
case O(BUILD,'l'): /* set raid level*/
if (s.level != UnSet) {
pr_err("raid level may only be set once. Second value is %s.\n", optarg);
exit(2);
}
s.level = map_name(pers, optarg);
if (s.level == UnSet) {
pr_err("invalid raid level: %s\n",
optarg);
exit(2);
}
if (s.level != 0 && s.level != LEVEL_LINEAR && s.level != 1 &&
s.level != LEVEL_MULTIPATH && s.level != LEVEL_FAULTY &&
s.level != 10 &&
mode == BUILD) {
pr_err("Raid level %s not permitted with --build.\n",
optarg);
exit(2);
}
if (s.sparedisks > 0 && s.level < 1 && s.level >= -1) {
pr_err("raid level %s is incompatible with spare-devices setting.\n",
optarg);
exit(2);
}
ident.level = s.level;
continue;
'l'옵션은 추가 파라미터가 있다고 설정했으므로 optarg값이 "1"일 것입니다. 그래서 s.level = ident.level = 1이 됩니다.
case O(GROW,'n'):
case O(CREATE,'n'):
case O(BUILD,'n'): /* number of raid disks */
if (s.raiddisks) {
pr_err("raid-devices set twice: %d and %s\n",
s.raiddisks, optarg);
exit(2);
}
s.raiddisks = parse_num(optarg);
if (s.raiddisks <= 0) {
pr_err("invalid number of raid devices: %s\n",
optarg);
exit(2);
}
ident.raid_disks = s.raiddisks;
continue;
'n'옵션도 추가 파라미터가 있으므로 optarg 포인터가 가르키는 '2'문자열을 읽어서 ident.raid_disks = s.raiddisks = 2로 저장합니다.
마지막으로 "/dev/loop0"과 "/dev/loop1" 문자열도 읽어서 dv 객체를 만들어서 devlist 리스트에 추가합니다.
이렇게 옵션에 대한 처리가 끝나면 이제 실제로 장치를 생성할 차례입니다.
다음과 같이 Create함수를 호출합니다.
switch(mode) {
case CREATE:
...
rv = Create(ss, devlist->devname,
ident.name, ident.uuid_set ? ident.uuid : NULL,
devs_found-1, devlist->next,
&s, &c, data_offset);
break;