# 2021q1 期末專題 (quiz7A - shell)
contributed by < `robertlin0401` >
###### tags: `linux2021`
> [2021 年第 7 週測驗題:測驗 1](https://hackmd.io/@sysprog/H1l5yStSu)
> [GitHub](https://github.com/robertlin0401/Linux-Kernel-Internals-quiz7)
---
## 專題簡介
:::warning
TODO: 此處補上本程式的功能、設計考量,和實作議題,在實際逐行分析程式碼之前。
一如所有事物的解說,應從高階總覽到個別子系統模組的順序。
:notes: jserv
:::
* 此實作旨在模擬 Unix shell 的運作方式,並且透過解讀此實作的過程去理解 Unix shell 的運作原理是此專題的一大目標
* 目前支援功能包含
* 執行簡單的命令,如結構為 `{command} {argument list}` 的命令
* 命令之間可使用 pipeline
* 檔案重新導向
* 未支援的功能包含但不限於
* `>>` (append), `2>` (導向到 stderr), `&` (背景執行) 等語法
* 上述語法的支援將在後續嘗試進行擴充
<!-- * <span class="todo">TBC</span> -->
## 程式運作原理
### 相關函式說明
* 建立命令列起始符號
```c
static void prompt()
{
write(2, "$ ", 2);
}
```
* 對應的命令列介面如下
```shell
> ./picosh
$ _
```
* 錯誤處理
```c
static void fatal(int retval, int leave)
{
if (retval >= 0)
return;
write(2, "?\n", 2);
if (leave)
exit(1);
}
```
* `retval` 為系統呼叫之回傳值,若為負值則表示有錯誤,須進行處理
* 此處系統呼叫包含 `chdir`, `fork`, `open`, `creat`, `execvp`
* 錯誤處理如下
* 印出 `?` 符號告知使用者錯誤發生
* 判斷錯誤類型,在需要之情況終止該行程,至於何謂需要之情況將於[後續段落](#執行命令1)中說明
* <span id="特殊字元符號之類型判斷">特殊字元符號之類型判斷</span>
```c
static inline int is_delim(int c)
{
return c == 0 || c == '|';
}
static inline int is_redir(int c)
{
return c == '>' || c == '<';
}
static inline int is_blank(int c)
{
return c == ' ' || c == '\t' || c == '\n';
}
static int is_special(int c)
{
return is_delim(c) || is_redir(c) || is_blank(c);
}
```
### main 函式說明
```c
int main()
{
while (1) {
prompt();
char buf[512] = {0}; /* input buffer */
char *c = buf;
if (!fgets(c + 1, sizeof(buf) - 1, stdin))
exit(0);
for (; *++c;) /* skip to end of line */
;
run(c, 0);
}
return 0;
}
```
* 使用一迴圈,重複下列工作
1. 呼叫 `prompt` 函式生成命令列起始符號
2. 讀取一行命令
3. 呼叫 `run` 函式解析並執行命令
* 讀取之命令將存入長度為 512 的字元陣列 `buf` 內,值得一提的是
* 最大讀取長度是 511 個字元,而非 512 個
* 儲存的方式為留一格空格在最前方,從第二格(`buf[1]` 的位置)開始儲存
* 若輸入命令為 `echo hello world`,則儲存方式如下圖所示
```graphviz
digraph {
graph [rankdir=LR];
c [shape=none; margin="0"; label=<
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">c</td></tr>
<tr><td port="start"></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="358"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20"></td></tr>
<tr><td port="end"></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="60"></td></tr>
<tr><td></td></tr>
</table>
</td>
</tr>
</table>>
];
content [shape=none; margin="0"; label=<
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td border="1">
<table border="0" cellspacing="0">
<tr><td port="head"></td></tr>
<tr><td width="20"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">e</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">c</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">h</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">o</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">h</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">e</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">l</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">l</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">o</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">w</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">o</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">r</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">l</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">d</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td port="tail"></td></tr>
<tr><td width="20"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="40">...</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20"></td></tr>
<tr><td></td></tr>
</table>
</td>
</tr>
</table>>
];
c:start -> content:head;
{ rank="same"; c; content; };
}
```
* 呼叫 `run` 函式執行前,會將指向 `buf` 開頭之指標移到該命令之結尾字元的下一個位址
* 因為解析命令時將會從命令字串的尾端開始解析,詳見[後續段落](#命令字串解析)說明
* 同樣以 `echo hello world` 為例,其結果如下圖所示
```graphviz
digraph {
graph [rankdir=LR];
c [shape=none; margin="0"; label=<
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20"></td></tr>
<tr><td port="start"></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="350"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">c</td></tr>
<tr><td port="end"></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="60"></td></tr>
<tr><td></td></tr>
</table>
</td>
</tr>
</table>>
];
content [shape=none; margin="0"; label=<
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td border="1">
<table border="0" cellspacing="0">
<tr><td port="head"></td></tr>
<tr><td width="20"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">e</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">c</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">h</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">o</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">h</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">e</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">l</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">l</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">o</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">w</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">o</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">r</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">l</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">d</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td port="tail"></td></tr>
<tr><td width="20"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="40">...</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20"></td></tr>
<tr><td></td></tr>
</table>
</td>
</tr>
</table>>
];
c:end -> content:tail;
{ rank="same"; c; content; };
}
```
### 執行命令(run 函式)說明
以下分為數個部分說明
* 參數
```c
static void run(char *c, int t)
```
* c - 指向一命令字串之結尾字元的下一個位址的指標,詳見[上一段](#main-函式說明)
* t - 用於指定輸出位置之 file descriptor,若為 0 則表示使用 stdout
* <span id="命令字串解析">命令字串解析</span>
```c
char *redir_stdin = NULL, *redir_stdout = NULL;
int pipefds[2] = {0, 0}, outfd = 0;
char *v[99] = {0};
char **u = &v[98]; /* end of words */
for (;;) {
c--;
if (is_delim(*c)) /* if NULL(start of string) or pipe */
break;
if (!is_special(*c)) {
/* Copy word of regular chars into previous u */
u--;
int count = 0;
while (!is_special(*c)) {
count++;
c--;
}
*u = strndup(c + 1, count);
}
if (is_redir(*c)) { /* if < or > */
if (*c == '<')
redir_stdin = *u;
else
redir_stdout = *u;
if ((u - v) != 98)
u++;
}
}
if ((u - v) == 98) /* empty input */
return;
```
* `v` 為長度 99 的字元指標的陣列,`u` 則為指向陣列 `v` 的最後一個空間(`v[98]` 的位置)的指標
* 使用迴圈從命令的尾端開始解析字元,若遇到下列 3 種情況則進行對應處理,各情況所代表之意義請見[這裡](#特殊字元符號之類型判斷)
1. `is_delim` 成立
* 表示解析完畢或是遇到 pipe 符號(`|`),此時的處理為離開迴圈
* 故命令字串儲存的方式為留一格空格在最前方,將之作為命令解析完畢的判斷依據
* 且由此可知,<span class="highlight">最後的命令</span>將優先解析,並且也是優先開始執行的,但透過 pipe 的機制,最終會是<span class="highlight">最前的命令</span>率先執行完畢
:::info
受限於 pipe 的機制,若前方的命令未執行完成並產生輸出,後方的命令無法完成執行
:::warning
節錄自 [pipe(7) — Linux manual page](https://man7.org/linux/man-pages/man7/pipe.7.html):
> If a process attempts to read from an empty pipe, then read(2) will block until data is available.
:::
2. `is_special` 不成立
* 表示解析到命令內容的一般字元,此時的處理為將此字元所屬的整段文字的複本的指標,存入當前 `u` 所指向的位置的前一個位置
* 承上,以 `echo hello world` 為例,則處理過程如下圖所示
```graphviz
digraph {
graph [rankdir=LR];
subgraph cluster_level2 {
label="after ";
u2 [shape=none; margin="0"; label=<
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="48"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">u</td></tr>
<tr><td port="ptr"></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="60"></td></tr>
<tr><td></td></tr>
</table>
</td>
</tr>
</table>>
];
v2 [shape=none; margin="0"; label=<
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="50">v</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">0</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="40">...</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td port="end"></td></tr>
<tr><td width="20" port="echo"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20" port="hello"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20" port="world"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td port="start"></td></tr>
<tr><td width="20">0</td></tr>
<tr><td></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="60"></td></tr>
<tr><td></td></tr>
</table>
</td>
</tr>
</table>>
];
world [shape=none; margin="0"; label=<
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="190"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="30" port="echo"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="26" port="hello"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td port="world"></td></tr>
<tr><td width="20">w</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">o</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">r</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">l</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">d</td></tr>
<tr><td></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="80"></td></tr>
<tr><td></td></tr>
</table>
</td>
</tr>
</table>>
];
hello [shape=none; margin="0"; label=<
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="163"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="50" port="echo"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td port="hello"></td></tr>
<tr><td width="20">h</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">e</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">l</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">l</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">o</td></tr>
<tr><td></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="95"></td></tr>
<tr><td></td></tr>
</table>
</td>
</tr>
</table>>
];
echo [shape=none; margin="0"; label=<
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="115"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td port="echo"></td></tr>
<tr><td width="20">e</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">c</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">h</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">o</td></tr>
<tr><td></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="100"></td></tr>
<tr><td></td></tr>
</table>
</td>
</tr>
</table>>
];
c2 [shape=none; margin="0"; label=<
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">c</td></tr>
<tr><td port="end"></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="358"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20"></td></tr>
<tr><td port="start"></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="60"></td></tr>
<tr><td></td></tr>
</table>
</td>
</tr>
</table>>
];
content2 [shape=none; margin="0"; label=<
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td border="1">
<table border="0" cellspacing="0">
<tr><td port="head"></td></tr>
<tr><td width="20"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td port="echo"></td></tr>
<tr><td width="20">e</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">c</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">h</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">o</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td port="hello"></td></tr>
<tr><td width="20">h</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">e</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">l</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">l</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">o</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td port="world"></td></tr>
<tr><td width="20">w</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">o</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">r</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">l</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">d</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td port="tail"></td></tr>
<tr><td width="20"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="40">...</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20"></td></tr>
<tr><td></td></tr>
</table>
</td>
</tr>
</table>>
];
c2:end -> content2:head [arrowhead=normal; constraint=false];
u2:ptr -> v2:end [arrowhead=normal; constraint=false];
v2:echo -> world:echo [arrowhead=none; constraint=false];
world:echo -> hello:echo [arrowhead=none; constraint=false];
hello:echo -> echo:echo [arrowhead=normal; constraint=false];
v2:hello -> world:hello [arrowhead=none; constraint=false];
world:hello-> hello:hello [arrowhead=normal; constraint=false];
v2:world -> world:world [arrowhead=normal; constraint=false];
}
subgraph cluster_level1 {
label="before ";
u1 [shape=none; margin="0"; label=<
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="180"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">u</td></tr>
<tr><td port="ptr"></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="60"></td></tr>
<tr><td></td></tr>
</table>
</td>
</tr>
</table>>
];
v1 [shape=none; margin="0"; label=<
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="50">v</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">0</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="40">...</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td port="end"></td></tr>
<tr><td width="20" port="echo">0</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20" port="hello">0</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20" port="world">0</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td port="start"></td></tr>
<tr><td width="20">0</td></tr>
<tr><td></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="60"></td></tr>
<tr><td></td></tr>
</table>
</td>
</tr>
</table>>
];
c1 [shape=none; margin="0"; label=<
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20"></td></tr>
<tr><td port="end"></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="350"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">c</td></tr>
<tr><td port="start"></td></tr>
</table>
</td>
<td>
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="60"></td></tr>
<tr><td></td></tr>
</table>
</td>
</tr>
</table>>
];
content1 [shape=none; margin="0"; label=<
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td border="1">
<table border="0" cellspacing="0">
<tr><td port="head"></td></tr>
<tr><td width="20"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td port="echo"></td></tr>
<tr><td width="20">e</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">c</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">h</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">o</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td port="hello"></td></tr>
<tr><td width="20">h</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">e</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">l</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">l</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">o</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td port="world"></td></tr>
<tr><td width="20">w</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">o</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">r</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">l</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20">d</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td port="tail"></td></tr>
<tr><td width="20"></td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="40">...</td></tr>
<tr><td></td></tr>
</table>
</td>
<td border="1">
<table border="0" cellspacing="0">
<tr><td></td></tr>
<tr><td width="20"></td></tr>
<tr><td></td></tr>
</table>
</td>
</tr>
</table>>
];
c1:start -> content1:tail [constraint=false];
u1:ptr -> v1:start [constraint=false];
}
}
```
* 值得注意的是,根據上述處理可知,陣列 `v` 的最後一個位置之值保留為 0,不會被更動,作為一命令之 argument list 的結尾標記,詳見[這裡](#execvp說明)
3. `is_redir` 成立
* 表示解析到重新導向符號(`<` 或 `>`),此時的處理為記錄欲重新導向的路徑
* 因為是由右至左解析命令,故解析到重新導向符號時,當前的 `u` 所指向的字元指標所指向的字串即為欲重新導向的路徑
* 將其指標存入 `redir_stdin` 或 `resir_stdout` 後,便執行 `u++`,使後續解析的命令將其覆蓋掉,可視為將其指標從陣列 `v` 中移除
* 解析完成後,判斷 `v`(即指向 `v[0]` 的指標)與 `u`(原本為指向 `v[98]` 的指標)之差,若正好為 98 則表示 `u` 在該次解析前後一致,故可將其視為一個空命令
* <span id="執行命令1">執行內建命令</span>
「內建命令」(bultin) 指不需要透過 execve 系統呼叫執行程式而獲得功能的操作。
```c
if (!strcmp(*u, "cd")) {
fatal(chdir(u[1]), 0);
return;
}
```
* 若目前 `u` 所指向的字元指標所指向的字串為 `cd`,則 `u[1]` 便應是欲切換的路徑
* `cd` 為一 builtin command,故直接使用系統呼叫 `chdir(u[1])` 來實現
:::warning
節錄自 GNU [bash 手冊內容](https://www.gnu.org/software/bash/manual/html_node/Shell-Builtin-Commands.html):
> Builtin commands are contained within the shell itself. When the name of a builtin command is used as the first word of a simple command, the shell executes the command directly, without invoking another program.
:::
* 正因為無新增子行程來執行其他程式,故當此系統呼叫發生錯誤時,無需將當前行程終止,至於其他命令的作法之說明詳見[後續段落](#執行命令2)
* pipe 處理
```c
if (*c) {
pipe(pipefds);
outfd = pipefds[1]; /* write end of the pipe */
}
```
* 若離開解析命令的迴圈後,指標 `c` 所指向的空間仍有內容,則代表是因為遇到 pipe 符號而離開迴圈的
* 故使用 `pipe` 系統呼叫來建立一個 pipe,並將其 read、write end 的 file descriptors 分別記錄在整數變數 `pipefds[0]` 與 `pipefds[1]` 中
* <span id="執行命令2">執行命令:其他命令</span>
```c
pid_t pid = fork();
if (pid) { /* parent or error */
fatal(pid, 1);
if (outfd) {
run(c, outfd); /* parse the rest of the cmdline */
close(outfd); /* close output fd */
close(pipefds[0]); /* close read end of the pipe */
}
wait(0);
return;
}
if (outfd) {
dup2(pipefds[0], 0); /* dup read fd to stdin */
close(pipefds[0]); /* close read fd */
close(outfd); /* close output */
}
if (redir_stdin) {
/* replace stdin with redir_stdin */
close(0);
fatal(open(redir_stdin, 0), 1);
}
if (t) {
dup2(t, 1); /* replace stdout with t */
close(t);
}
if (redir_stdout) {
/* replace stdout with redir */
close(1);
fatal(creat(redir_stdout, 438), 1);
}
fatal(execvp(*u, u), 1);
```
* 使用 `fork` 系統呼叫建立子行程 (process),負責執行該輪解析出來的命令,此作法為模擬非 builtin command 的行為。父行程則在命令未解析完的狀況(即存在 pipe 的狀況),遞迴呼叫 `run` 來繼續解析
* 父行程內容詳述
* 若建立子行程失敗,則程式無法正常運作,故將此行程終止
* 遞迴呼叫 `run` 來繼續解析命令,須將先前建立的 pipe 的 write end file descriptor 傳入,使前方命令的輸出寫入 pipe 中的 buffer,作為後方命令的輸入
* 子行程內容詳述
* 首先,在有需要的情況下,進行一系列輸入、輸出重新導向,且若在開檔的系統呼叫過程出現錯誤,皆須將目前行程終止。此部分應考慮以下情況
1. 存在 pipe,則使用 `dup2` 系統呼叫,將原本代表 stdin 的 file descriptor,改寫成代表先前建立的 pipe 的 read end,使其能夠讀取前方命令的輸出
:::info
```c
int dup2(int oldfd, int newfd);
```
:::warning
節錄自 [dup(2) - Linux manual page](https://man7.org/linux/man-pages/man2/dup.2.html):
> The file descriptor `newfd` is adjusted so that it now refers to the same open file description as `oldfd`.
:::
2. 命令內容有輸入重新導向符號(`<`),則關閉 file decriptor 為 0 者(stdin 或先前建立的 pipe 的 read end),並使用 `open` 系統呼叫開啟指定檔案,使其 file descriptor 成為 0,因此被當作新的輸入位置
3. 呼叫 `run` 時有傳入 file descriptor,則將 stdout 以 `dup2` 系統呼叫做更改
4. 命令內容有輸出重新導向符號(`>`),則關閉 file decriptor 為 1 者(stdout、先前建立的 pipe 的 write end 或其他檔案位置),並使用 `creat` 系統呼叫開啟指定檔案。其中,`438` 為檔案 mode,轉換為二進位為 `110110110`,表示對所有使用者而言此檔案可讀可寫
:::warning
用八進位 (對! C 語言內建八進位就為了檔案權限的描述) 來解讀上述數值的作用。見: [CHMOD Calculator](https://clickcalculators.com/chmod/CHMOD~438~r---wxrwx)
:notes: jserv
:::
:::info
更新:
* `438` 為檔案 mode(以十進位表示),將其轉換為八進位為 `666`
* 八進位的三個位數從左至右分別代表 User(檔案擁有者)、Group(擁有者所屬群組)與 Other(其他使用者)對此檔案的操作權限
* 以單獨一個位數來看,其由 3 個 bits 組成,每個 bit 由右至左分別代表該類別的使用者是否擁有讀取、寫入與執行的權限
* 雖然也能夠以二進位來解讀,但<span class="highlight">無法使用二進位來操作</span>
:::
* 若重新導向部分未出錯,則接著使用 `execvp` 系統呼叫執行當前指令,而若系統呼叫出錯亦將此行程終止
:::info
<span id="execvp說明"></span>
```c
int execvp(const char *file, char *const argv[]);
```
:::warning
節錄自 [exec(3) — Linux manual page](https://man7.org/linux/man-pages/man3/exec.3.html):
> The `char *const argv[]` argument is an array of pointers to null-terminated strings that represent the argument list available to the new program.
> The first argument, by convention, should point to the filename associated with the file being executed. The array of pointers must be terminated by a null pointer.
---
## 提升開發便利性之修改
### 支援輸入重新導向自檔案
* 每當修改程式碼,需要將所有命令輸入一輪進行測試,以確認程式是按照預期來運作
* 而每次手動輸入所有指令相當沒有效率,故將其寫成檔案 [test.cmd](https://github.com/robertlin0401/Linux-Kernel-Internals-quiz7/blob/main/test.cmd),並使用輸入重新導向的方式輸入
* 為此將程式碼做少量更動,使用 `isatty(0)` 判斷是否有做輸入重新導向,若有,則無須輸出如命令列起始符號等資訊至螢幕
```dif
- prompt();
+ if (isatty(0))
+ prompt();
```
### 支援輸出位置選擇
* 使用 argument list 來指定輸出位置的類別,格式如下
```shell
> ./picosh {output_class}
```
* 該引數可寫在輸入、輸出重新導向之前或之後
* 舉例而言,以下格式皆正確
```shell
> ./picosh < {input_dir} {output_class}
> ./picosh {output_class} < {input_dir}
```
* 引數 `output_class` 說明
* 0 表示輸出至 stdout,1 表示將輸出寫入 output.log 檔案
* 預設值為 0
* 實作如下
```c
int t = open("./output.log", O_WRONLY | O_TRUNC | O_CREAT);
int out_class = 0;
if (argc > 1 && !(argc % 2)) {
for (int i = 0; i < argc / 2; ++i) {
if (!strncmp(argv[1 + 2 * i], "<", 1) ||
!strncmp(argv[1 + 2 * i], ">", 1)) {
continue;
}
out_class = atoi(argv[1 + 2 * i]);
break;
}
}
...
run(c, out_class ? t : 0);
```
### 引入 linenoise
* 透過 linenoise 可提供 `tab` 鍵自動補其功能與 `↑`、`↓` 鍵瀏覽命令輸入歷史紀錄功能
* 其目的性有二
* 增進開發過程中的測試效率
* 模擬如 Bash 的介面操作方式
<!-- * <span class="todo">進行中...</span> -->
<!-- customized style -->
<style>
.highlight {
background-color: yellow;
}
.todo {
background-color: red;
}
</style>