Yoshiki-Iwasa / minishell

0 stars 0 forks source link

$PATHに登録されたPATHの左から順にコマンドを検索して実行されていない #38

Closed hiroin closed 4 years ago

hiroin commented 4 years ago

事案 $PATHに登録されたPATHの左から順にコマンドを検索して実行されていない

事案詳細 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games となっていて /usr/local/sbin/dummy /usr/local/bin/dummy という実行ファイルがある状態で、 dummy を実行すると、 /usr/local/sbin/dummy が実行される。

再現手順 [bash]

$ echo '' > ls
$ chmod 755 ls
$ ./ls
$ pwd
$ PATH=[ここにpwdの結果を貼り付け]:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
(例)
$ PATH=/home/user42/42/minishell/test_minishell:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
$ echo $PATH
/home/user42/42/minishell/test_bash:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
$ ls
$ rm ls
$ hash -d ls
$ ls

今いるディレクトリにlsという名前で実行できるファイルを作って、今いるディレクトリをPATHに追加すると、作成したlsが実行される…という仕組みです。

[minishell]

minishell$ echo '' > ls
minishell$ chmod 755 ls
minishell$ ./ls
minishell$ pwd
/home/user42/42/minishell/test_minishell
minishell$ PATH=[ここにpwdの結果を貼り付け]:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
minishell$ echo $PATH
/home/user42/42/minishell/test_minishell:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
minishell$ ls
000_AllTest.sh  001_SimpleCommand.sh  002_Arguments.sh  ls  minishell
Yoshiki-Iwasa commented 4 years ago

修正済み

原因:PATH を取ってくるとき、PATH=1234 だとしたら、 =1234 というふうにイコールも含めた形でvalue を取得してた。

hiroin commented 4 years ago

431879547a70c2cd36ef43877e6caaa93b792534 で解消されていませんでした。

実行ログです。

minishell$ echo '' > ls
minishell$ chmod 755 ls
minishell$ ./ls
minishell$ pwd
/home/user42/42/minishell/20200905
minishell$ PATH=/home/user42/42/minishell/20200905
minishell$ 
minishell$ ls
bash: ls: commnad not found
minishell$ echo $PATH
/home/user42/42/minishell/20200905
Yoshiki-Iwasa commented 4 years ago

あー これは新発見の機能ですね。。。 神谷さんがもっているコードで PATH変更後に直下の実行ファイルは実行できるはずです。 PATH は左から順に実行してあって、原因は別のところにありました。

神谷さんが例をしめしてくれて気がついたんですが、どうやら、パスで渡されたファイルが実行ファイルでないときは、その内容を読み取って、そこに有効な実行ファイル名が見つかるまでさがしていって実行してるみたいです。 これは、パスによる実行ファイルの実行というレビューの要件には該当しない気がしますがどうですかね?? 実装しますか?

[再現手順] (カレントディレクトリに "ls" という名前の実行ファイルがすでに存在してます)

bash-3.2$ cat cmd
bad case

ls
bash-3.2$ 
bash-3.2$ ./ls
ls OK
bash-3.2$ 
bash-3.2$ pwd
/Users/iwasayoshiki/42cursus/minishell
bash-3.2$ 
bash-3.2$ PATH=/Users/iwasayoshiki/42cursus/minishell
bash-3.2$ 
bash-3.2$ cmd
/Users/iwasayoshiki/42cursus/minishell/cmd: line 1: bad: command not found
ls OK
bash-3.2$ cat cmd
ls
bash-3.2$ 
bash-3.2$ pwd
/Users/iwasayoshiki/42cursus/minishell
bash-3.2$ 
bash-3.2$ PATH=/Users/iwasayoshiki/42cursus/minishell
bash-3.2$ 
bash-3.2$ cmd
ls OK
bash-3.2$ 

bash-3.2$ cat cmd
invalid line
bash-3.2$ 
bash-3.2$ 
bash-3.2$ pwd
/Users/iwasayoshiki/42cursus/minishell
bash-3.2$ 
bash-3.2$ PATH=/Users/iwasayoshiki/42cursus/minishell
bash-3.2$ echo $PATH
/Users/iwasayoshiki/42cursus/minishell
bash-3.2$ 
bash-3.2$ cmd
/Users/iwasayoshiki/42cursus/minishell/cmd: line 1: invalid: command not found
bash-3.2$ 
hiroin commented 4 years ago

神谷さんが例をしめしてくれて気がついたんですが、どうやら、パスで渡されたファイルが実行ファイルでないときは、その内容を読み取って、そこに有効な実行ファイル名が見つかるまでさがしていって実行してるみたいです。

あー…、これは気がつかなかった… 本当はそうすべきなんでしょうけど…実装の難易度によるかな… 追加でファイルの属性を調べる必要があるのですよね。

ただ、そのケースでなくて、最初のコメントに書いたケースそのものが自分の環境だと動かないです。 以下に再現手順を書きます。

minishell$ echo '' > ls
minishell$ chmod 755 ls
minishell$ ./ls
minishell$ pwd
/home/user42/42/minishell/20200905
minishell$ PATH=/home/user42/42/minishell/20200905:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
minishell$ echo $PATH
/home/user42/42/minishell/20200905:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
minishell$ ls ← ここで本当は、/home/user42/42/minishell/20200905/lsが実行される…つまり何も表示されないのが正解なのですが、/bin/lsが実行されちゃいます。
a.out                command_exit.o    command_unset.o  get_next_line.h        ls           pipe.o            test
change_into_array.c  command_export.c  dollor_value.c   get_next_line.o        main         ppp               test.c
change_into_array.o  command_export.o  dollor_value.o   get_next_line_utils.c  main.c       read_command.c    utils2.c
command_cd.c         command_multi.c   escape2.c        get_next_line_utils.o  main.o       read_command.o    utils2.o
command_cd.o         command_multi.o   escape2.o        libft                  Makefile     redirect.c        utils.c
command_echo.c       command_pwd.c     escape.c         libft.a                manage_fd.c  redirect.o        utils.o
command_echo.o       command_pwd.o     escape.o         list2.c                manage_fd.o  sell_execution.c
command_env.c        command_shell.c   free.c           list2.o                minishell    sell_execution.o
command_env.o        command_shell.o   free.o           list.c                 minishell.h  signal_handle.c
command_exit.c       command_unset.c   get_next_line.c  list.o                 pipe.c       signal_handle.o

6e3fe0503bddc674ea163975cc7f81c77e14f072 の最新Verを使っています。 main.cを使っていますコメントアウトして絵がでるのすごいですね(^^:

Yoshiki-Iwasa commented 4 years ago

あ、そうか。stat 関数使えばファイル種類を獲得できますね。。。

stat 関数でファイルの種類を取得して、通常ファイルなら実行しないとこまでとりあえずやってみますね。

Yoshiki-Iwasa commented 4 years ago

stat 関数では、与えられたファイルが実行ファイルかそうでないかは判断できず、上記の実装は難しいと思います。。。 ちょっと要相談でお願いします

hiroin commented 4 years ago

 stat関数について、おそらくst_modeにファイルのパーミッションの情報が格納されると思いました。 http://www.c-lang.net/stat/ https://www.loose-info.com/main/memolist/c/lib_sys_stat_stat.html フラグの「S_IXUSR」「S_IXGRP」「S_IXOTH」をどう使えばいいのかわからないですが、夜に試してみます。

hiroin commented 4 years ago

statで実行権限があるかを取得できました。 stat(s_file, &buf); のbuf.st_modeにパーミッションの情報が入ります。 buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)の値が0の場合は、実行権限なし buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)の値が0以外の場合は、なんらかの実行権限ありです ただ…、実行権限があることを調べた後に、自分がそのファイルの所有者なのか、グループ所有者なのか、otherなのかを調べて、実行できるかを調べないといけないです… とこまではやらなくていいいかな…と思いました。やれば出来るのだと思いますが…

#include <stdio.h>
#include <sys/stat.h>
#include <dirent.h>
#include <string.h>

int main()
{
    FILE *fp;
    struct dirent *dirst;
    struct stat buf;

    mkdir("test_dir_01", 0755);
    printf("stat()実行 : %d\n\n", stat("test_dir_01", &buf));
    printf("ディレクトリ[test_dir_01]に関する情報\n");
    printf("    st_mode : %#o\n", buf.st_mode);
    printf("    st_uid  : %ld\n", (long)buf.st_uid);
    printf("    st_gid  : %ld\n", (long)buf.st_gid);
    printf("    st_size : %ld\n", (long)buf.st_size);

    fp = fopen("test_dir_01/test_file_01.txt", "w");
    fclose(fp);
    chmod("test_dir_01/test_file_01.txt", 0755);     //test_file_01.txtは実行権限があるファイル
    fp = fopen("test_dir_01/test_file_02.txt", "w");
    fclose(fp);
    chmod("test_dir_01/test_file_02.txt", 0644);     //test_file_02.txtは実行権限がないファイル

    DIR *dp = opendir("test_dir_01");
    while((dirst = readdir(dp)) != NULL)
    {
        if ((strcmp(dirst->d_name, ".") != 0) & (strcmp(dirst->d_name, "..") != 0))
        {
            char s_file[100] = "test_dir_01/";
            strcat(s_file, dirst->d_name);
            stat(s_file, &buf);
            printf("ファイル[%s]に関する情報\n", s_file);
            //buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)は、ファイルに実行権限があると0以外が、ないと0になる
            printf("    st_mode : %#o\n", buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH));
            printf("    st_uid  : %ld\n", (long)buf.st_uid);
            printf("    st_gid  : %ld\n", (long)buf.st_gid);
            printf("    st_size : %ld\n", (long)buf.st_size);
        }
    }
    closedir(dp);

    remove("test_dir_01/test_file_01.txt");
    remove("test_dir_01/test_file_02.txt");
    remove("test_dir_01");

    return 0;
}
Yoshiki-Iwasa commented 4 years ago

ありがとうございます!!!!

そうなんですよね。stat で実行権限があるかないかの判断は可能なんですよね。。。 実行権限の確認をした後、それがバイナリファイルかテキストファイルなのか判断して処理を分岐させる方法がわかりませんでした ( ; _ ;)

a.out の権限

-rwxr-xr-x   1 iwasayoshiki  staff  82192  9  6 08:26 a.out

ls の権限

-rwxr-xr-x   1 root  wheel    51888  8 11 05:56 ls
hiroin commented 4 years ago

バイナリファイルかテキストファイルなのか判断して処理を分岐させる

これが困りごとだったのですね。バイナリかテキストかを判断する必要はなくて、実行権限があるかないかだけ確認して、実行権限があれば、そのまま実行してみればOKのはずです。 現状のminishellの動作で問題ないです。 [bash]

$ touch a.txt
$ chmod 755 a.txt
$ ./a.txt
$ 

[minishell]

minishell$ touch a.txt
minishell$ chmod 755 a.txt
minishell$ ./a.txt
minishell$
Yoshiki-Iwasa commented 4 years ago

これできそうです。が、shell が コマンドラインを読んでくる過程をもう一回やらないと行けなさそうなので関数切り分けがすすんだら実装します。 理由は、テキストファイルが実行されたときはすべての行をコマンドとして読んで実行するらしく、これを実装するにはコマンドの整形から実行部分を関数として切り出す必要があるからです

bash-3.2$ cat a.txt
invalid line

echo ${USER}

invalid line
bash-3.2$ 
bash-3.2$ ./a.txt
./a.txt: line 1: invalid: command not found
iwasayoshiki
./a.txt: line 5: invalid: command not found
bash-3.2$ 
hiroin commented 4 years ago

あー、その機能を実装すると…、minishellのテストをminishell自体ができるようになっちゃいます(^^: 課題の範疇を超えると思いますので…テキストファイルの実行をする試験をやめましょう。 試験を以下のように修正します。 (1) pwdコマンドをカレントディレクトリにコピペする (2) pwdをlsにrenameする (3) PATHの先頭にカレントディレクトリを追加 (4) lsとやると、pwdの出力がでることを確認 (5) カレントディレクトリのlsを消して、lsとすると、いつものlsが実行されることを確認

Yoshiki-Iwasa commented 4 years ago
minishell$ cat a.txt
invalid line

echo ${USER}

invalid line
minishell$ 
minishell$ ./a.txt
bash: invalid: commnad not found
iwasayoshiki
bash: invalid: commnad not found
minishell$ 

ここまでは実装できましたが、行数を表示させるのができないっす。。。。 隠し機能ということで

hiroin commented 4 years ago

うおー! すごい! そこまでできれば、先程も書きましたが、自分自身(minishell)を自分自身で自動試験できます。 スクリプトをせこせこ書いてのですが、いらなかったかも(^^: 以下のとおり試験項目を変更し動作確認しました。本件closeいたします。

014
Environment Path
- Execute commands but this time without any path. (ls, wc, awk etc...)
- コマンドを実行しますが、今回はパスなしで実行します。(ls, wc, awk など)
- Unset the $PATH and check if it is not working anymore
- pathを解除して、動作しなくなったかどうかを確認します。
- Set the $PATH to a multiple directory value (directory1:directory2) and check that directories  are checked in order from left to right
- PATHを複数のディレクトリ値(directory1:directory2)に設定し、ディレクトリが左から右の順にチェックされることを確認します。

[試験コマンド]
date
echo $PATH
unset PATH
echo $PATH
date
cd /tmp
/bin/cp /bin/ps ./
/bin/mv ps date
./date
PATH=/tmp:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
echo $PATH
date
rm date
date