スクリプトファイルの起動


Rev.1を表示中。最新版はこちら

シェルスクリプトは、そのファイルを引数にして、#!以降のコマンドで、シェルが改めて起動するものと思っていました。実はスクリプトの実行は、カーネルとして実装されています。上記処理はカーネルが行っていると言う事です。それ故スクリプトファイルは実行許可属性が必要なわけですが。また、先の理解故、スクリプトは、シェルに限った物だとの誤解があったりもいたします。以下のようにすれば、phpファイルもスクリプトとして、起動することが可能です。
[root@localhost kitamura]# cat script.php
#!/usr/bin/php
<?php
       print "php by script\n";
?>
[root@localhost kitamura]# ./script.php
php by script
シェルスクリプトに限らず、全てのプログラムの起動は、do_execve_common()をコールする事で行っています。概要は、struct linux_binprm *bprmに適切な設定を行い、そこから起動するプログラムのローダを探し出し、それにてプログラムを起動します。

まず、適切なチェックをした後、open_exec()で起動プログラムのファイル構造体を取得し、bprmに設定し、prepare_binprm()をコールしてinodeにかかるbmpのメンバーを設定し、search_binary_handler()で、対応するローダを取得して、そのローダによりプログラムを起動する事になります。
static int do_execve_common(const char *filename,
                               struct user_arg_ptr argv,
                               struct user_arg_ptr envp,
                               struct pt_regs *regs)
{
       struct linux_binprm *bprm;
       struct file *file;
       struct files_struct *displaced;
       bool clear_in_exec;
       int retval;
       const struct cred *cred = current_cred();

       if ((current->flags & PF_NPROC_EXCEEDED) &&
           atomic_read(&cred->user->processes) > rlimit(RLIMIT_NPROC)) {
               retval = -EAGAIN;
               goto out_ret;
       }

       retval = unshare_files(&displaced);
       if (retval)
               goto out_ret;

       retval = -ENOMEM;
       bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
       if (!bprm)
               goto out_files;

       retval = prepare_bprm_creds(bprm);
       if (retval)
               goto out_free;

       retval = check_unsafe_exec(bprm);
       if (retval < 0)
               goto out_free;
       clear_in_exec = retval;
       current->in_execve = 1;

       file = open_exec(filename);
       retval = PTR_ERR(file);
       if (IS_ERR(file))
               goto out_unmark;

       sched_exec();

       bprm->file = file;
       bprm->filename = filename;
       bprm->interp = filename;

  :
  :
       retval = prepare_binprm(bprm);
       if (retval < 0)
               goto out;

       retval = copy_strings_kernel(1, &bprm->filename, bprm);
       if (retval < 0)
               goto out;

       bprm->exec = bprm->p;
       retval = copy_strings(bprm->envc, envp, bprm);
       if (retval < 0)
               goto out;

       retval = copy_strings(bprm->argc, argv, bprm);
       if (retval < 0)
               goto out;

       retval = search_binary_handler(bprm,regs);
       if (retval < 0)
               goto out;

       current->fs->in_exec = 0;
       current->in_execve = 0;
       acct_update_integrals(current);
       free_bprm(bprm);
       if (displaced)
               put_files_struct(displaced);
       return retval;
  :
  :
}
prepare_binprm()は、struct linux_binprm *bprmにinodeにかかる設定を行い、最後にkernel_read()で実行するファイルの先頭からBINPRM_BUF_SIZE=128バイト、bprm->bufに読み込んでいます。ここには、起動ローダの情報が入っています。シェルスクリプトで言うなら、#!/bin/sh と言うことです。
int prepare_binprm(struct linux_binprm *bprm)
{
       umode_t mode;
       struct inode * inode = bprm->file->f_path.dentry->d_inode;
       int retval;

       mode = inode->i_mode;
       if (bprm->file->f_op == NULL)
               return -EACCES;

       bprm->cred->euid = current_euid();
       bprm->cred->egid = current_egid();

       if (!(bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID)) {
               if (mode & S_ISUID) {
                       bprm->per_clear |= PER_CLEAR_ON_SETID;
                       bprm->cred->euid = inode->i_uid;
               }

               if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) {
                       bprm->per_clear |= PER_CLEAR_ON_SETID;
                       bprm->cred->egid = inode->i_gid;
               }
       }

       retval = security_bprm_set_creds(bprm);
       if (retval)
               return retval;
       bprm->cred_prepared = 1;

       memset(bprm->buf, 0, BINPRM_BUF_SIZE);
       return kernel_read(bprm->file, 0, bprm->buf, BINPRM_BUF_SIZE);
}
search_binary_handler()で、struct linux_binprm *bprmに対応するローダを探し出し、ファイルを起動します。カーネルとしてサポートするローダは、struct linux_binfmtとして、formatsをヘッドとしてリストされています。ローダの実装は、モジュール単位で提供され、カーネルにinsされる毎に、formatsのリストに登録されいきます。

struct linux_binfmt *fmtのload_binaryがローダで、retval = fn(bprm, regs);と引数に、bprmでコールします。ローダサイドでは、bprm->bufの内容をチェックする事で、該当するファイルかどうかチェックを行っています。

なお、for (try=0; try<2; try++)と2回ループしているのは、最初のループで該当するローダが無かったら、request_module()で対応するモジュールをinsすることで、再度検索するようになっています。
int search_binary_handler(struct linux_binprm *bprm,struct pt_regs *regs)
{
       unsigned int depth = bprm->recursion_depth;
       int try,retval;
       struct linux_binfmt *fmt;
       pid_t old_pid;

       retval = security_bprm_check(bprm);
       if (retval)
               return retval;

       retval = audit_bprm(bprm);
       if (retval)
               return retval;

       rcu_read_lock();
       old_pid = task_pid_nr_ns(current, task_active_pid_ns(current->parent));
       rcu_read_unlock();

       retval = -ENOENT;
       for (try=0; try<2; try++) {
               read_lock(&binfmt_lock);
               list_for_each_entry(fmt, &formats, lh) {
                       int (*fn)(struct linux_binprm *, struct pt_regs *) = fmt->load_binary;
                       if (!fn)
                               continue;
                       if (!try_module_get(fmt->module))
                               continue;
                       read_unlock(&binfmt_lock);
                       retval = fn(bprm, regs);

                       bprm->recursion_depth = depth;
                       if (retval >= 0) {
                               if (depth == 0) {
                                       UTRACE_HOOK(current, EXEC,
                                               report_exec(fmt, bprm, regs));
                                       ptrace_event(PTRACE_EVENT_EXEC,
                                                       old_pid);
                               }
                               put_binfmt(fmt);
                               allow_write_access(bprm->file);
                               if (bprm->file)
                                       fput(bprm->file);
                               bprm->file = NULL;
                               current->did_exec = 1;
                               proc_exec_connector(current);
                               return retval;
                       }
                       read_lock(&binfmt_lock);
                       put_binfmt(fmt);
                       if (retval != -ENOEXEC || bprm->mm == NULL)
                               break;
                       if (!bprm->file) {
                               read_unlock(&binfmt_lock);
                               return retval;
                       }
               }
               read_unlock(&binfmt_lock);
#ifdef CONFIG_MODULES
               if (retval != -ENOEXEC || bprm->mm == NULL) {
                       break;
               } else {
#define printable(c) (((c)=='\t') || ((c)=='\n') || (0x20<=(c) && (c)<=0x7e))
                       if (printable(bprm->buf[0]) &&
                           printable(bprm->buf[1]) &&
                           printable(bprm->buf[2]) &&
                           printable(bprm->buf[3]))
                               break; /* -ENOEXEC */
                       if (try)
                               break; /* -ENOEXEC */
                       request_module("binfmt-%04x", *(unsigned short *)(&bprm->buf[2]));
               }
#else
               break;
#endif
       }
       return retval;
}
以下は、スクリプトにかかるローダの処理(モジュール)です。このモジュールがinsmodされると、init_script_binfmt()のregister_binfmt(&script_format)で、formatsとヘッドとするリストに登録されます。

load_script()はsearch_binary_handler()からコールされるわけですが、if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!')で、ファイルの先頭が、#!となっているかどうかチェックしています。なっていないなら、スクリプトファイルでありません。

#!なら、それ以降を、ローダとして取得すればいいだけなのですが、なんでごちゃごちゃした処理してんの?って。まあとにかく取得し、このローダファイルのFILEをopen_exec()で取得し、改めてbprmをprepare_binprm()をコールする事で、このローダで再設定しなおし、search_binary_handler()をコールすることで、結果的にこのスクリプトファイル(bprm->filename)が起動されるわけです。

なお、最初のif分のbprm->recursion_depth > BINPRM_MAX_RECURSION(=4)は、load_script()がコールされる毎に、bprm->recursion_depth++としており、#!以降がシェルスクリプトの場合、4回までしか記述できないと言う事です。
static int load_script(struct linux_binprm *bprm,struct pt_regs *regs)
{
       const char *i_arg, *i_name;
       char *cp;
       struct file *file;
       char interp[BINPRM_BUF_SIZE];
       int retval;

       if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!') ||
           (bprm->recursion_depth > BINPRM_MAX_RECURSION))
               return -ENOEXEC;

       bprm->recursion_depth++;
       allow_write_access(bprm->file);
       fput(bprm->file);
       bprm->file = NULL;

       bprm->buf[BINPRM_BUF_SIZE - 1] = '\0';
       if ((cp = strchr(bprm->buf, '\n')) == NULL)
               cp = bprm->buf+BINPRM_BUF_SIZE-1;
       *cp = '\0';
       while (cp > bprm->buf) {
               cp--;
               if ((*cp == ' ') || (*cp == '\t'))
                       *cp = '\0';
               else
                       break;
       }
       for (cp = bprm->buf+2; (*cp == ' ') || (*cp == '\t'); cp++);
       if (*cp == '\0') 
               return -ENOEXEC; /* No interpreter name found */
       i_name = cp;
       i_arg = NULL;
       for ( ; *cp && (*cp != ' ') && (*cp != '\t'); cp++)
               /* nothing */ ;
       while ((*cp == ' ') || (*cp == '\t'))
               *cp++ = '\0';
       if (*cp)
               i_arg = cp;
       strcpy (interp, i_name);

       retval = remove_arg_zero(bprm);
       if (retval)
               return retval;
       retval = copy_strings_kernel(1, &bprm->interp, bprm);
       if (retval < 0) return retval; 
       bprm->argc++;
       if (i_arg) {
               retval = copy_strings_kernel(1, &i_arg, bprm);
               if (retval < 0) return retval; 
               bprm->argc++;
       }
       retval = copy_strings_kernel(1, &i_name, bprm);
       if (retval) return retval; 
       bprm->argc++;
       bprm->interp = interp;

       file = open_exec(interp);
       if (IS_ERR(file))
               return PTR_ERR(file);

       bprm->file = file;
       retval = prepare_binprm(bprm);
       if (retval < 0)
               return retval;
       return search_binary_handler(bprm,regs);
}

static struct linux_binfmt script_format = {
       .module         = THIS_MODULE,
       .load_binary    = load_script,
};

static int __init init_script_binfmt(void)
{
       return register_binfmt(&script_format);
}

static void __exit exit_script_binfmt(void)
{
       unregister_binfmt(&script_format);
}

core_initcall(init_script_binfmt);
module_exit(exit_script_binfmt);
MODULE_LICENSE("GPL");

備考

シェルに限らず、他のスクリプトも、コメントが#となっているのは、binfmt script_formatに起因してるわけです。また、#!以降のファイルは、カーネルが起動する故に、絶対パスで記述しなければならないと言う事です。



最終更新 2013/01/11 04:07:17 - north
(2013/01/11 04:07:17 作成)


検索

アクセス数
3702665
最近のコメント
コアダンプファイル - sakaia
list_head構造体 - yocto_no_yomikata
勧告ロックと強制ロック - wataash
LKMからのファイル出力 - 重松 宏昌
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
Adsense
広告情報が設定されていません。