# linenoise 原始碼分析 ###### tags: `Linux Kernel 2022 spring` [Github](https://github.com/antirez/linenoise) [ANSI转义序列](https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97) 參考資料 [linenoise 源码分析(一)](https://blog.csdn.net/u012842205/article/details/51016910) [linenoise 源码分析(二)](https://blog.csdn.net/u012842205/article/details/51034411) [反樸歸真: 文字模式下的程式設計](https://www.cyut.edu.tw/~ckhung/b/mi/textmode.php) ## 摘要 >Line editing with some support for history is a really important feature for command line utilities. Instead of retyping almost the same stuff again and again it's just much better to hit the up arrow and edit on syntax errors, or in order to try a slightly different command. But apparently code dealing with terminals is some sort of Black Magic: readline is 30k lines of code, libedit 20k. Is it reasonable to link small utilities to huge libraries just to get a minimal support for line editing? `linenoise` 是一個輕量化, zero-config 且可以取代 `readline` 的函式庫。 ## Let trace! 在 `linenoise.c` 的開頭註解直接表明了 ```c /* linenoise.c -- guerrilla line editing library against the idea that a * line editing lib needs to be 20,000 lines of C code. ``` ### Low level terminal handling #### `linenoiseMaskModeEnable()` & `linenoiseMaskModeDisable()` ```c /* Enable "mask mode". When it is enabled, instead of the input that * the user is typing, the terminal will just display a corresponding * number of asterisks, like "****". This is useful for passwords and other * secrets that should not be displayed. */ void linenoiseMaskModeEnable(void) { maskmode = 1; } /* Disable mask mode. */ void linenoiseMaskModeDisable(void) { maskmode = 0; } ``` 這兩個函數設定使用者輸入的 `maskmode` - `maskmode == 1` -> 使用者的輸入會由 `*` 代替 - `maskmode == 0` -> 使用者的輸入正常顯示 ``` hello> /mask hello> *********** echo: 'dasdwdasddd' hello> ``` #### `linenoiseSetMultiLine()` ```c /* Set if to use or not the multi line mode. */ void linenoiseSetMultiLine(int ml) { mlmode = ml; } ``` 設定 `MultiLine` 模式 在 `example.c` 中可以看到是否為 `multiline` 可以在執行時透過設定 `argv` 來決定 `example.c` ```c /* Parse options, with --multiline we enable multi line editing. */ while(argc > 1) { argc--; argv++; if (!strcmp(*argv,"--multiline")) { linenoiseSetMultiLine(1); printf("Multi-line mode enabled.\n"); } else if (!strcmp(*argv,"--keycodes")) { linenoisePrintKeyCodes(); exit(0); } else { fprintf(stderr, "Usage: %s [--multiline] [--keycodes]\n", prgname); exit(1); } } ``` ``` ckd@ckd-Lenovo-Y520-15IKBN:~/linux2022spring/linenoise$ ./linenoise_example --multiline Multi-line mode enabled. hello> ``` 有無 `multiline` 的差異可以參考 [antirez linenoise](https://github.com/antirez/linenoise) 的 `README` >By default, Linenoise uses single line editing, that is, a single row on the screen will be used, and as the user types more, the text will scroll towards left to make room. This works if your program is one where the user is unlikely to write a lot of text, otherwise multi line editing, where multiple screens rows are used, can be a lot more comfortable. 設定 `multiline` 會讓輸入大量文字的使用者較方便(超出範圍會換行) #### `isUnsupportedTerm()` ```c /* Return true if the terminal name is in the list of terminals we know are * not able to understand basic escape sequences. */ static int isUnsupportedTerm(void) { char *term = getenv("TERM"); int j; if (term == NULL) return 0; for (j = 0; unsupported_term[j]; j++) if (!strcasecmp(term,unsupported_term[j])) return 1; return 0; } ``` 檢查是否為 unsupported terminal ,在先前就有定義 `unsupported_term` `static char *unsupported_term[] = {"dumb","cons25","emacs",NULL};` 根據 C語言規格書 **==7.20.4.5 The getenv function==** >The getenv function searches an environment list, provided by the host environment, for a string that matches the string pointed to by **name**. The set of environment names and the method for altering the environment list are implementation-defined. `strcasecmp` 用法和 `strcmp` 相同(同樣定義於 `string.h` ),不過不會區分大小寫。 #### `enableRawMode()` ```c static int rawmode = 0; /* For atexit() function to check if restore is needed*/ ``` ```c /* Raw mode: 1960 magic shit. */ static int enableRawMode(int fd) { struct termios raw; if (!isatty(STDIN_FILENO)) goto fatal; if (!atexit_registered) { atexit(linenoiseAtExit); atexit_registered = 1; } if (tcgetattr(fd,&orig_termios) == -1) goto fatal; raw = orig_termios; /* modify the original mode */ /* input modes: no break, no CR to NL, no parity check, no strip char, * no start/stop output control. */ raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); /* output modes - disable post processing */ raw.c_oflag &= ~(OPOST); /* control modes - set 8 bit chars */ raw.c_cflag |= (CS8); /* local modes - choing off, canonical off, no extended functions, * no signal chars (^Z,^C) */ raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); /* control chars - set return condition: min number of bytes and timer. * We want read to return every single byte, without timeout. */ raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ /* put terminal in raw mode after flushing */ if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal; rawmode = 1; return 0; fatal: errno = ENOTTY; return -1; } ``` 這段需要參考 [termois.h](https://pubs.opengroup.org/onlinepubs/7908799/xsh/termios.h.html) 以便了解 [POSIX](https://zh.wikipedia.org/wiki/%E5%8F%AF%E7%A7%BB%E6%A4%8D%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E6%8E%A5%E5%8F%A3) ,相關介紹可以參考 - [termios結構體詳解](https://www.itread01.com/content/1547596651.html) - [posix是什麼都不知道,就別說你懂Linux了!](https://www.gushiciku.cn/pl/gXCz/zh-tw) 另外 [tcsetattr()](https://pubs.opengroup.org/onlinepubs/7908799/xsh/tcgetattr.html) 用來獲取終端的相關引數 要注意他的 **RETURN VALUE** >Upon successful completion, 0 is returned. Otherwise, -1 is returned and errno is set to indicate the error. 此外 **ENOTTY** 定義於 [errno.h](https://kernel.googlesource.com/pub/scm/linux/kernel/git/nico/archive/+/v0.97/include/linux/errno.h) ``` #define ENOTTY 25 /* Not a typewriter */ ``` 可以參考 [Not a typewriter](https://en.wikipedia.org/wiki/Not_a_typewriter) #### `disableRawMode()` ```c static void disableRawMode(int fd) { /* Don't even check the return value as it's too late. */ if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1) rawmode = 0; } ``` 將 `rawmode` 設定為 0 `getCursorPosition()` ```c /* Use the ESC [6n escape sequence to query the horizontal cursor position * and return it. On error -1 is returned, on success the position of the * cursor. */ static int getCursorPosition(int ifd, int ofd) { char buf[32]; int cols, rows; unsigned int i = 0; /* Report cursor location */ if (write(ofd, "\x1b[6n", 4) != 4) return -1; /* Read the response: ESC [ rows ; cols R */ while (i < sizeof(buf)-1) { if (read(ifd,buf+i,1) != 1) break; if (buf[i] == 'R') break; i++; } buf[i] = '\0'; /* Parse it. */ if (buf[0] != ESC || buf[1] != '[') return -1; if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1; return cols; } ``` 用 `ESC [6n` 跳出序列來查找光標位置並回傳。 - 根據 [ANSI Escape Sequences](https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797) - `ESC[6n` 的描述為 request cursor position (reports as `ESC[#;#R`) >```c >/* >* DSR (Device Status Report) >* Sequence: ESC [ 6 n >* Effect: reports the current cusor position as ESC [ n ; m R >*/ >``` - 搭配 [ASCII](https://zh.wikipedia.org/wiki/ASCII) 的定義, `ESC` 的十六進制表示法為 `1B` 這邊的用法參考 `linenoise.c` 的註解 :::spoiler Comment ```c /* * References: * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html * * Todo list: * - Filter bogus Ctrl+<char> combinations. * - Win32 support * * Bloat: * - History search like Ctrl+r in readline? * * List of escape sequences used by this program, we do everything just * with three sequences. In order to be so cheap we may have some * flickering effect with some slow terminal, but the lesser sequences * the more compatible. * * EL (Erase Line) * Sequence: ESC [ n K * Effect: if n is 0 or missing, clear from cursor to end of line * Effect: if n is 1, clear from beginning of line to cursor * Effect: if n is 2, clear entire line * * CUF (CUrsor Forward) * Sequence: ESC [ n C * Effect: moves cursor forward n chars * * CUB (CUrsor Backward) * Sequence: ESC [ n D * Effect: moves cursor backward n chars * * The following is used to get the terminal width if getting * the width with the TIOCGWINSZ ioctl fails * * DSR (Device Status Report) * Sequence: ESC [ 6 n * Effect: reports the current cusor position as ESC [ n ; m R * where n is the row and m is the column * * When multi line mode is enabled, we also use an additional escape * sequence. However multi line editing is disabled by default. * * CUU (Cursor Up) * Sequence: ESC [ n A * Effect: moves cursor up of n chars. * * CUD (Cursor Down) * Sequence: ESC [ n B * Effect: moves cursor down of n chars. * * When linenoiseClearScreen() is called, two additional escape sequences * are used in order to clear the screen and position the cursor at home * position. * * CUP (Cursor position) * Sequence: ESC [ H * Effect: moves the cursor to upper left corner * * ED (Erase display) * Sequence: ESC [ 2 J * Effect: clear the whole screen */ ``` ::: #### `getColumns()` ```c /* Try to get the number of columns in the current terminal, or assume 80 * if it fails. */ static int getColumns(int ifd, int ofd) { struct winsize ws; if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { /* ioctl() failed. Try to query the terminal itself. */ int start, cols; /* Get the initial position so we can restore it later. */ start = getCursorPosition(ifd,ofd); if (start == -1) goto failed; /* Go to right margin and get position. */ if (write(ofd,"\x1b[999C",6) != 6) goto failed; cols = getCursorPosition(ifd,ofd); if (cols == -1) goto failed; /* Restore position. */ if (cols > start) { char seq[32]; snprintf(seq,32,"\x1b[%dD",cols-start); if (write(ofd,seq,strlen(seq)) == -1) { /* Can't recover... */ } } return cols; } else { return ws.ws_col; } failed: return 80; } ``` `struct winsize` 定義於 [termois.h](https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/termios.h) ```c struct winsize { unsigned short ws_row; unsigned short ws_col; unsigned short ws_xpixel; unsigned short ws_ypixel; }; ``` #### `linenoiseClearScreen()` ```c /* Clear the screen. Used to handle ctrl+l */ void linenoiseClearScreen(void) { if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) { /* nothing to do, just to avoid warning. */ } } ``` #### `linenoiseBeep` ```c /* Beep, used for completion when there is nothing to complete or when all * the choices were already shown. */ static void linenoiseBeep(void) { fprintf(stderr, "\x7"); fflush(stderr); } ``` 這裡的 `\x7` 分別為 [Hexadecimal Escape Sequence](https://www.ibm.com/docs/en/zos/2.4.0?topic=details-hexadecimal-escape-sequence) 的 `\x` 以及在 [ASCII](https://zh.wikipedia.org/wiki/ASCII) 中代表響鈴的 `7` ### Completion **結構體** ```c typedef struct linenoiseCompletions { size_t len; char **cvec; } linenoiseCompletions; ``` 結構包含一個型態為 `size_t` 的長度以及 pointer to a pointer 的 `cvec` 這個結構體在兩個函式中被使用 ```c typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); ``` ```c void linenoiseAddCompletion(linenoiseCompletions *, const char *); ``` #### `linenoiseAddCompletion` ```c= /* This function is used by the callback function registered by the user * in order to add completion options given the input string when the * user typed <tab>. See the example.c source code for a very easy to * understand example. */ void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { size_t len = strlen(str); char *copy, **cvec; copy = malloc(len+1); if (copy == NULL) return; memcpy(copy,str,len+1); cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1)); if (cvec == NULL) { free(copy); return; } lc->cvec = cvec; lc->cvec[lc->len++] = copy; } ``` 註解說明這個函式提供使用者按下 \<tab\> 就補齊剩下的指令的功能。 :::info ==這裡記好,方便之後分析更好懂== 將引數 `const char *str` 給 `linenoiseCompletions *lc` ,將 `lc->cvec` 重新分配空間後 (長度加 1 ) 在將新的 `str` 放進去,所以最終結果在 `lc` 中會新增一個 `str` ::: 這裡先看一下源碼給的 `example.c` 的使用 ```c void completion(const char *buf, linenoiseCompletions *lc) { if (buf[0] == 'h') { linenoiseAddCompletion(lc,"hello"); linenoiseAddCompletion(lc,"hello there"); } } /* Set the completion callback. This will be called every time the * user uses the <tab> key. */ linenoiseSetCompletionCallback(completion); linenoiseSetHintsCallback(hints); ``` 所以在這裡會是新增一個補全 `str` = `"hello"` , 在新增一個補全 `str` = `hello there` ,所以在有 `h` 開頭的字樣時按下 `tab` 會補全成 `hello` 再次按下 `tab` 會補全程 `hello there` 。 :::success **做個小實驗驗證:** 把 `completion` 中的兩行 `linenoiseAddCompletion` 對調,在按下 `tab` 之後會先補全成 `hello there` 再補成 `hello` ::: ```c /* Register a callback function to be called for tab-completion. */ void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { completionCallback = fn; } /* Register a hits function to be called to show hits to the user at the * right of the prompt. */ void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) { hintsCallback = fn; } ``` 這裡可以看到 `completion` 和 `hints` 作為引數傳入 `callback` 系列的函式,並在 `linenoise.c` 中分別為 `completionCallback` 和 `hintsCallback` 從 `completion` 的內容來看,當使用者鍵入開頭為 `h` 的指令時,會補足成 `hello` 接著在補足為 `hello there` **先檢視一下結構體** ```c /* The linenoiseState structure represents the state during line editing. * We pass this state to functions implementing specific editing * functionalities. */ struct linenoiseState { int ifd; /* Terminal stdin file descriptor. */ int ofd; /* Terminal stdout file descriptor. */ char *buf; /* Edited line buffer. */ size_t buflen; /* Edited line buffer size. */ const char *prompt; /* Prompt to display. */ size_t plen; /* Prompt length. */ size_t pos; /* Current cursor position. */ size_t oldpos; /* Previous refresh cursor position. */ size_t len; /* Current edited line length. */ size_t cols; /* Number of columns in terminal. */ size_t maxrows; /* Maximum num of rows used so far (multiline mode) */ int history_index; /* The history index we are currently editing. */ }; ``` #### **==核心函數==** `completeLine` ```c= /* This is an helper function for linenoiseEdit() and is called when the * user types the <tab> key in order to complete the string currently in the * input. * * The state of the editing is encapsulated into the pointed linenoiseState * structure as described in the structure definition. */ static int completeLine(struct linenoiseState *ls) { linenoiseCompletions lc = { 0, NULL }; int nread, nwritten; char c = 0; completionCallback(ls->buf,&lc); if (lc.len == 0) { linenoiseBeep(); } else { size_t stop = 0, i = 0; while(!stop) { /* Show completion or original buffer */ if (i < lc.len) { struct linenoiseState saved = *ls; ls->len = ls->pos = strlen(lc.cvec[i]); ls->buf = lc.cvec[i]; refreshLine(ls); ls->len = saved.len; ls->pos = saved.pos; ls->buf = saved.buf; } else { refreshLine(ls); } nread = read(ls->ifd,&c,1); if (nread <= 0) { freeCompletions(&lc); return -1; } switch(c) { case 9: /* tab */ i = (i+1) % (lc.len+1); if (i == lc.len) linenoiseBeep(); break; case 27: /* escape */ /* Re-show original buffer */ if (i < lc.len) refreshLine(ls); stop = 1; break; default: /* Update buffer and return */ if (i < lc.len) { nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]); ls->len = ls->pos = nwritten; } stop = 1; break; } } } freeCompletions(&lc); return c; /* Return last read character */ } ``` 可以看到這段在 `line 12` 用到了先前定義的 `completionCallback` 在這個 `example.c` 中也就是 `completion` 函數。 --- 如果這邊 `lc` 的長度是 0 的話會直接 `linenoiseBeep` **如果這邊長度不為 0 :** 在 `line 20` 至 `line 28` 做了以下操作: - `ls->len` 和 `ls->pos` 都指派為查找 `lc.cvec` 中的第 `i` 個符合項目的長度,以 `example.c` 作為舉例 - `i = 0`,`lc.cvcec[i]` 為一開始輸入的開頭為 `h` 的指令 - `i = 1`,`lc.cvcec[i]` 為 `hello` - `i = 2`,`lc.cvcec[i]` 為 `hello there` - 調用 `refreshLine()` 將其顯示在 cmd window 上面 - 將原本的 `ls` 的各個 attribute 用先前複製的 `saved` 指派回來 (這點很重要,在進 `else` 時才會返回原本的輸入) 在 `while` loop 中 ```c nread = read(ls->ifd,&c,1); ``` 等待 keyboard 輸入對應到不同的 `case` ,閒置狀態會持續 run `default` ,這裡把 `ls->len` 和 `ls->pos` 指派成 `lc.cvec` 的長度,這樣 enter 按下去之後才會是正確的。 這裡特別題一下在 `case 9` 也就是 `tab` 按下時的行為 ```c i = (i+1) % (lc.len+1); ``` 可以將 `i` 的值控制在 `[0, lc.len]` 之間並且==循環== ### Line editing **結構體** ```c /* We define a very simple "append buffer" structure, that is an heap * allocated string where we can append to. This is useful in order to * write all the escape sequences in a buffer and flush them to the standard * output in a single call, to avoid flickering effects. */ struct abuf { char *b; int len; }; ``` 這個結構體實現了 append buffer ,是一個 heap allocated string ,包含了 - `char*` 變量作為 buffer - `int` 變量作為 buffer 長度 #### `abInit` ```c static void abInit(struct abuf *ab) { ab->b = NULL; ab->len = 0; } ``` 初始化,在這裡還沒有配置空間。 #### `abAppend` ```c static void abAppend(struct abuf *ab, const char *s, int len) { char *new = realloc(ab->b,ab->len+len); if (new == NULL) return; memcpy(new+ab->len,s,len); ab->b = new; ab->len += len; } ``` 在這邊調用 `realloc` 來配置空間並用 `memecpy` 來複製數據。 #### `abFree` ```c static void abFree(struct abuf *ab) { free(ab->b); } ``` 釋放 `abuf` 結構中的 `char*` , `abuf` 本體還在 ### History ```c static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; // 100 static int history_len = 0; static char **history = NULL; ``` 這裡的每一則 `history` 型態為 `char*` ,透過操作型態為 `char**` 的 history 操作具有固定大小的 deque (double-ended queue) 在 `linenoise.h` 中的宣告 ```c int linenoiseHistoryAdd(const char *line); int linenoiseHistorySetMaxLen(int len); int linenoiseHistorySave(const char *filename); int linenoiseHistoryLoad(const char *filename); ``` 根據名稱可以推斷出函式用途 #### `freeHistory` ```c /* Free the history, but does not reset it. Only used when we have to * exit() to avoid memory leaks are reported by valgrind & co. */ static void freeHistory(void) { if (history) { int j; for (j = 0; j < history_len; j++) free(history[j]); free(history); } } ``` 釋放 `history` 空間但是沒有重置它,只有在需要 `exit()` 的時候使用它,以避免 `valgrind` 報 memory leak 錯。 #### `linenoiseHistoryAdd` ```c /* This is the API call to add a new entry in the linenoise history. * It uses a fixed array of char pointers that are shifted (memmoved) * when the history max length is reached in order to remove the older * entry and make room for the new one, so it is not exactly suitable for huge * histories, but will work well for a few hundred of entries. * * Using a circular buffer is smarter, but a bit more complex to handle. */ int linenoiseHistoryAdd(const char *line) { char *linecopy; if (history_max_len == 0) return 0; /* Initialization on first call. */ if (history == NULL) { history = malloc(sizeof(char*)*history_max_len); if (history == NULL) return 0; memset(history,0,(sizeof(char*)*history_max_len)); } /* Don't add duplicated lines. */ if (history_len && !strcmp(history[history_len-1], line)) return 0; /* Add an heap allocated copy of the line in the history. * If we reached the max length, remove the older line. */ linecopy = strdup(line); if (!linecopy) return 0; if (history_len == history_max_len) { free(history[0]); memmove(history,history+1,sizeof(char*)*(history_max_len-1)); history_len--; } history[history_len] = linecopy; history_len++; return 1; } ``` - 檢查 `history` 是否初始化,沒有的話 `malloc` 配置空間 - 不加入重複的 `history` - 先把 `line` 指派給 `linecopy`,檢查 `history` 有沒有滿,如果滿的話就 `free` 掉 `history[0]` ,接著在用 `memmove` 移出最後一個空間,之後在將 `history[history_len] = linecopy` #### `linenoiseSetMaxLen` ```c /* Set the maximum length for the history. This function can be called even * if there is already some history, the function will make sure to retain * just the latest 'len' elements if the new history length value is smaller * than the amount of items already inside the history. */ int linenoiseHistorySetMaxLen(int len) { char **new; if (len < 1) return 0; if (history) { int tocopy = history_len; new = malloc(sizeof(char*)*len); if (new == NULL) return 0; /* If we can't copy everything, free the elements we'll not use. */ if (len < tocopy) { int j; for (j = 0; j < tocopy-len; j++) free(history[j]); tocopy = len; } memset(new,0,sizeof(char*)*len); memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy); free(history); history = new; } history_max_len = len; if (history_len > history_max_len) history_len = history_max_len; return 1; } ``` - 先 `malloc` 新空間,將原本的東西複製過來,在將新的指派給 `history` - `len < tocopy` 如果沒辦法複製所有的東西,將比較近的 `history` 保留下來。 #### `linenoiseHistorySave` ```c /* Save the history in the specified file. On success 0 is returned * otherwise -1 is returned. */ int linenoiseHistorySave(const char *filename) { mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO); FILE *fp; int j; fp = fopen(filename,"w"); umask(old_umask); if (fp == NULL) return -1; chmod(filename,S_IRUSR|S_IWUSR); for (j = 0; j < history_len; j++) fprintf(fp,"%s\n",history[j]); fclose(fp); return 0; } ``` 將 `history` 儲存到特定的 file 中 ,成功 `return 0` 失敗 `return -1` 這裡用到的函式可以分別參考: - `fopen` C 語言規格書 7.19.5.3 The fopen function - `umask` and `chmode` [鳥哥 Linux 檔案與目錄管理](https://linux.vbird.org/linux_basic/mandrake9/0220filemanager.php) - `chmod` 改變檔案的可寫、可讀、可執行等屬性 - `umask` 改變預設的建立檔案或目錄時的屬性 - `umask` 的引數 [src/sys/sys/stat.h](https://github.com/openbsd/src/blob/master/sys/sys/stat.h) - `#define S_IXUSR 0000100 /* X for owner */` - `#define S_IRWXG 0000070 /* RWX mask for group */` - `#define S_IRWXO 0000007 /* RWX mask for other */` - `chmod` 的引數 [src/sys/sys/stat.h](https://github.com/openbsd/src/blob/master/sys/sys/stat.h) - `#define S_IRUSR 0000400 /* R for owner */` - `#define S_IWUSR 0000200 /* W for owner */` #### `linenoiseHistoryLoad` ```c= /* Load the history from the specified file. If the file does not exist * zero is returned and no operation is performed. * * If the file exists and the operation succeeded 0 is returned, otherwise * on error -1 is returned. */ int linenoiseHistoryLoad(const char *filename) { FILE *fp = fopen(filename,"r"); char buf[LINENOISE_MAX_LINE]; if (fp == NULL) return -1; while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) { char *p; p = strchr(buf,'\r'); if (!p) p = strchr(buf,'\n'); if (p) *p = '\0'; linenoiseHistoryAdd(buf); } fclose(fp); return 0; } ``` 讀取 `history` 檔案到,如果為 error 則 `return -1` 之後循環調用 `linenoiseHistoryAdd` ,前面有提到 `linenoiseHistory`只有在尚未分配空間時才會分配空間,所以在這裡只會有一個 `history` 的空間配置 - `fgets` 將 `fp` 的資料讀進 `buf` - `strchr` 尋找某個字元的指標 - `line 15` 至 `line 17` 這些操作可以參考 [CS:APP 第十章 system I/O](https://www.youtube.com/watch?v=vaOT9KeIUDk&list=PLcQU3vbfgCc9sVAiHf5761UUApjZ3ZD3x&index=17) 中有提到 Windows 和 Linux 的 EOF(End of Line) 不同 - Linux and Mac OS : `\n`(0xa) - line feed (LF) - Windows and Internet protocols :`\r\n` (0xd 0xa) - Carriage return (CR) followed by line feed (LF) ## 這邊補充一下 [Linux 細部權限 ACL](https://ithelp.ithome.com.tw/articles/10221185) :::info **ACL** stand for Access Contorl List ::: ### 基本權限對應 - owner <-----> read - group <-----> write - read <-----> execute >如果有一個目錄底下有一堆人要使用,每個人或群組所需要的權限每個都不一樣,而基本的權限管理只有檔案擁有者,群組、其他人,沒辦法針對 aming 這個單一用戶設定權限,因此就有以下的 ACL 概念。 ### ACL 的控制權 - 使用者 (user): 針對使用者設定權限 - 群組 (group): 針對群組設定權限 - ==預設屬性(mask):該目錄新建檔案/目錄時,規範新資料的預設權限== 參考 [Linux 的檔案權限與目錄](https://linux.vbird.org/linux_basic/centos7/0210filepermission.php) ![](https://i.imgur.com/u8Hiv1c.png) --- #### 第一欄 在檔案類型權限中會有十個字元,對應如下 --- ![](https://i.imgur.com/A80sPfz.png) --- - 第一個字元代表這個檔案是**目錄、檔案或連結檔等等** - d: 目錄 - -: 檔案 - l: 連結檔(link file) - b: 裝置檔裡面可供儲存的周邊設備(可隨機存取裝置) - c: 裝置檔裡面的序列阜設備,例如鍵盤、滑鼠(一次性讀取裝置) - 接下來為三個一組、且均為 **rwx** 三個參數的組合。 - r: 可讀(read) - w: 可寫(write) - x: 可執行(execute) - -: 沒有權限 - 第一組為 **檔案擁有者可具備的權限** - 第二組為 **加入此群組之帳號的權限** - 第三組為 **非本人且沒有加入本群組之其他帳號的權限** 實際創建一個檔案試試看 ``` $ touch acl $ ll acl ``` ``` -rw-rw-r-- 1 ckd ckd 0 四 9 15:38 acl 1234567890 ``` 這邊顯示 `acl` 這個檔案 - [-] 是一個檔案 - [rw-] 擁有者可讀可寫 - [rw-] 同群組使用者可讀可寫 - [r--] 其他使用者可讀(唯讀的意思) #### 第二欄 表示有多少檔名連結到此節點 (i-node): >每個檔案都會將他的權限與屬性記錄到檔案系統的i-node中,不過,我們使用的目錄樹卻是使用檔名來記錄, 因此每個檔名就會連結到一個i-node囉!這個屬性記錄的,就是有多少不同的檔名連結到相同的一個i-node號碼去就是了。 #### 第三欄 >這個檔案(目錄)的**擁有者帳號** #### 第四欄 >這個檔案(目錄)的所屬群組 #### 第五欄 >這個檔案(目錄)的容量大小(bytes) #### 第六欄 >這個檔案(目錄)的建檔日期或是最近修改日期 >- 如果太久遠僅會顯示年份 #### 第七欄 >檔名 ### 改變權限 `chmod` >檔案權限的改變使用的是chmod這個指令,但是,權限的設定方法有兩種, 分別可以使用數字或者是符號來進行權限的變更。 >Linux檔案的基本權限就有九個,分別是owner/group/others三種身份各有自己的read/write/execute權限, 先複習一下剛剛上面提到的資料:檔案的權限字元為:『-rwxrwxrwx』, 這九個權限是三個三個一組的!其中,我們可以使用數字來代表各個權限,各權限的分數對照表如下: > - r:4 > - w:2 > - x:1 **==上面這段要記好!==** >每種身份(owner/group/others)各自的三個權限(r/w/x)分數是需要累加的,例如當權限為: [-rwxrwx---] 分數則是: > - owner = rwx = 4+2+1 = 7 > - group = rwx = 4+2+1 = 7 > - others= --- = 0+0+0 = 0 這裡對應到在 [src/sys/sys/stat.h](https://github.com/openbsd/src/blob/master/sys/sys/stat.h) 中的巨集設定