自作 init プロセスをパワーアップ?

昨日の「initをぶっ壊そうぜ! - ただの日記」続き。

 

コマンドライン引数と環境変数

Linuxで最初に実行されるプログラム init はどうなっているのか? そのために、昨日は init を適当な hello,world! などで置き換えるということをしました。

 

init は特別なプロセスですが、コマンドライン引数と環境変数もきちんとあります。 以下のようなプログラムを作って実行させてみます。

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/reboot.h>

 

extern char **environ;

 

int main(int argc, char *argv[ ])
{
     char buf[256], cmd;
     int i;

 

    // コマンドライン引数を表示する
    printf("\n\n");
    for (i=0; i<argc; i++)
        printf("argv[%d]: %s\n", i, argv[i]);

 

    // 環境変数を表示する
    printf("\n\n");
    i = 0;
    while (environ[i] != NULL) {
        printf("%s\n", environ[i]);
        i++;
    }

 

    // 入力待ち
    cmd = '\0';
    memset(buf, 0, sizeof(buf));
    printf("\n\n command >> ");
    fgets(buf, sizeof(buf), stdin);
    sscanf(buf, "%c", &cmd);

 

    switch (cmd) {
    case 'h':
        reboot(0x4321fedc); // シャットダウン
        break;
    default:
        reboot(RB_AUTOBOOT); // 再起動
        break;
    }

 

    printf("reboot() error!\n");

 

    return 0;
}

 

このプログラムは init として実行されたときのコマンドライン引数と環境変数をダンプし、その後、入力待ちしてシステムをシャットダウンか再起動します。

 

実行結果

 どうやら init のコマンドライン引数はカーネルパラメータとして与えたものがそのまま入っているようです。 環境変数もいくらか設定されています。

 

自作 init から本来の init を起動する

execv()関数で本来の init に成りすましましょう。

argv[0] には、本来の init のパスを設定しておきます。

以下は、main()関数の中のコードです。

 

argv[0] = "/sbin/init";
execv("/sbin/init", argv); 

 実行結果

簡単なコードですが、これで、本来の通常起動に戻るようです。

 

自作 init からシェルを起動する

デーモンの起動などカーネル以外のシステム初期化を行なっていない段階ですが、シェル (/bin/sh) も一応、起動できるようです。 (しかし、サービスが起動してないことから、何らかの制限が潜んでいるかもしれません。)

 

自作 init のプロセスは維持しておいた方がいいかもしれないので、ここでは fork して /bin/sh に exec します。

 

pid_t p;

 

...

 

p = fork();
if (p == -1) {
    printf("fork() error.\n");
} else if (p == 0) {
    execl("/bin/sh", "/bin/sh", NULL);
} else {
    if (waitpid(p, NULL, 0) == -1)
        printf("waitpid() error.\n");
}

 

シェルで exit コマンドを実行してシェルを抜けると、元の自作 init の処理に戻ります。

 

 まとめ

ここまでの機能を1つにまとめたプログラムを作りました。 メニューから処理を選んで実行するプログラムです。

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/reboot.h>

 

extern char **environ;

 

int main(int argc, char *argv[ ])
{
    char buf[256];
    char cmd;
    pid_t p;
    int i;


    while (1) {
        printf("\n");
        printf("i) start /sbin/init\n");
        printf("s) run /bin/sh\n");
        printf("a) show argv\n");
        printf("e) show environ\n");
        printf("h) halt\n");
        printf("r) reboot\n");
        printf(" >> ");

 

        cmd = '\0';
        memset(buf, 0, sizeof(buf));
        fgets(buf, sizeof(buf), stdin);
        sscanf(buf, "%c", &cmd);

 

        switch (cmd) {
        case 'i':
            argv[0] = "/sbin/init";
            execv("/sbin/init", argv);
            break;
        case 's':
            p = fork();
            if (p == -1) {
                printf("fork() error.\n");
            } else if (p == 0) {
                execl("/bin/sh", "/bin/sh", NULL);
            } else {
                if (waitpid(p, NULL, 0) == -1)
                    printf("waitpid() error.\n");
            }
            break;
        case 'a':
            for (i=0; i<argc; i++)
                printf("argv[%d]: %s\n", i, argv[i]);
            break;
        case 'e':
            i = 0;
            while (environ[i] != NULL) {
                printf("%s\n", environ[i]);
                i++;
            }
            break;
        case 'h':
            if (reboot(0x4321fedc) == -1)
                printf("reboot() error.\n");
            break;
        case 'r':
            if (reboot(RB_AUTOBOOT) == -1)
                printf("reboot() error.\n");
            break;
        }
    }

 

    return 0;
}