getopt を用いたオプションからの文字列置換

5年振りに C 言語のプログラムを書いています(^ ^;;
「実行する実コマンドをユーザから分からないように難読化したい。」というあんまり幸せになれなさそうな要件があって、サンプルを作成してみました。本当は *cmd[] の中身は暗号化文字列に変換するのですが、ここでは分かり易くするために平文で書いています。

ソースは1つにして、機能拡張(コマンド追加とか)のメンテナンス時に main のロジックを変更せずに容易に修正可能な仕組みをどう設計するか。最も単純なのが、引数でうまく制御できないものかなと考えました。

単純に作ると、こんな感じになります。

#include <stdio.h>
#include <stdlib.h>

char *cmd[] =  {
      "ls"
    , "pwd"
    , "find"
};

void usage(void);

int main(int argc, char **argv) {
    int  n;
    char cmd_str[1024];

    if(argc < 2 || argv == NULL) {
        usage();
        return EXIT_SUCCESS;
    }
    
    n = atoi(argv[1]);
    printf("%d: %s\n", n, cmd[n]);
    (void)sprintf(cmd_str, "%s 2>&1", cmd[n]);
    
    if(!system(cmd_str))
        return EXIT_SUCCESS;
    else
        return EXIT_FAILURE;
}

void usage(void) {
    printf("./a.out 0|1|2\n");
}

実行結果。

$ ./a.out 0
0: ls
a.out  enum_sample.c  getopt_sample.c

$ ./a.out 3
3: (null)
sh: null: command not found

でも、これだと、提供者(ユーザ)が引数の何番はどのコマンドに対応付けているかを知っている必要があります。コマンドが1000個ぐらいになったら、きっと訳が分からなくなりそうです。駄目だー、こんなの(> <)

オプションは「名前」を与えた方が分かり易くて良いです。

$ man 3 getopt

とすると、私のイメージに近いものがありました。

#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>

struct option long_options[] = {
      {"ls", no_argument, 0, 0}
    , {"pwd", no_argument, 0, 0}
    , {"find", required_argument, 0, 0}
    , {0, 0, 0, 0}
};

char *cmd[] =  {
      "ls"
    , "pwd"
    , "find"
    , NULL
};

void usage(void);

int main(int argc, char **argv) {
    int  c, option_index = -1;
    char cmd_str[1024];

    if(argc < 2 || argv == NULL) {
        usage();
        return EXIT_SUCCESS;
    }

    c = getopt_long_only(argc, argv, "", long_options, &option_index);
    if(c == -1 || option_index < 0) {
        usage();
        return EXIT_FAILURE;
    }
    printf("%d: %s\n", c, long_options[option_index].name);

    if(c == 0 && long_options[option_index].has_arg >= 1 && optarg) {
        (void)sprintf(cmd_str, "%s %s 2>&1", cmd[option_index], optarg);
    }
    else {
        (void)sprintf(cmd_str, "%s 2>&1", cmd[option_index]);
    }

    if(!system(cmd_str))
        return EXIT_SUCCESS;
    else
        return EXIT_FAILURE;
}

void usage(void) {
    printf("./a.out --ls|--pwd|--find [dir]\n");
}

実行結果

$ ./a.out --ls
0: ls
a.out  enum_sample.c  getopt_sample.c

$ ./a.out --find
./a.out: option '--find' requires an argument
./a.out --ls|--pwd|--find [dir]

$ ./a.out --find /tmp/ssh-BmOar12656/
0: find
/tmp/ssh-BmOar12656/
/tmp/ssh-BmOar12656/agent.12656

「名前」で与えたオプションに対して option_index が相対位置を返すので前者の方法よりはメンテナンスコストが下がりそうです。さらに has_arg でオプションそのものが取る引数の有無も制御できます。

先人の知恵は偉大だなと実感したひと時でした。

GETOPT

int getopt_long(int argc, char * const argv,
           const char *optstring,
           const struct option *longopts, int *longindex);

int getopt_long_only(int argc, char * const argv,
           const char *optstring,
           const struct option *longopts, int *longindex);

longopts は struct option の要素からなる配列の、
先頭要素へのポインタである。
struct option は  で以下のように定義されている。

   struct option {
       const char *name;
       int has_arg;
       int *flag;
       int val;
   };

それぞれのフィールドの意味は以下の通り。

name   長いオプションの名前。

has_arg
       no_argument (または 0) なら、オプションは引き数をとらない。
       required_argument (または 1) なら、
       オプションは引き数を必要とする。 
       optional_argument (または 2) なら、
       オプションは引き数をとってもとらなくても良い。

flag   長いオプションに対する結果の返し方を指定する。
       flag が NULL なら getopt_long() は val を返す
       (例えば呼び出し元のプログラムは、
       val に等価なオプション文字を代入することができる)。 
       NULL 以外の場 合には、getopt_long() は 0 を返す。
       このときオプションが見つかると flag がポイントする
       変数に val が代入される。
       見つからないとこの変数は変更されない。

val    返り値、または flag がポイントする変数へロードされる値。
       配列の最後の要素は、全て 0 で埋められていなければならない。

longindex は、NULL でなければ、長いオプションのインデックスを
longopts からの相対位置として保持している変数へのポインタとなる。

getopt_long_only() は getopt_long() と同様の動作をするが、
aq-aq も "--" と同様に、長いオプションとして扱われる。
aq-aq で始まる ("--" 以外の) オプションが、
長いものにはマッチしないが短いものにマッチする場合においては、
それは短いオプションとして解釈される。