2025年10月30日 星期四

測試 fastcgi

幫助別人很容易, 請人家來幫助我們卻很困難。
網站技術我只會 cgi, 當然也聽過 fastcgi, 但直到 20251027, 靠著 chatgpt, 才成功使用 c 寫出 fastcgi 程式, 包含架設 lighttpd 大概花了 30 分鐘左右。參考: fastcgi by chatgpt

寫 cgi 免不了要架設 web server, 以前使用 apache2, 設定並不容易, 現在 Nginx 是主流, 但我不熟悉, 不想搞這個, chatgpt 列出了 lighttpd, 就你了,
sudo apt-get install lighttpd spawn-fcgi libfcgi-dev
輕鬆搞定 web server。

lighthttpd root 在 /etc/lighttpd/lighttpd.conf 定義
server.document-root = "/var/www/html"
sudo lighty-enable-mod fastcgi
輕鬆掛入 fastcgi 模組。

再來就是 list 1 fastcgi 的設定, 指定 fastcgi path。

最後就是 c 寫的 fastcgi 程式。

一般比較少用 c 寫 cgi 程式, 但我從接觸 cgi 以來, 就是用 c/c++ 寫, 那時候 perl 是最流行的 cgi 語言, 用 c/c++ 還真是難倒當時的我, 花了很多力氣才搞定。我用 cgicc 來幫忙處理 cgi 的輸入, 簡化許多操作。

把這些都集合起來, 就完成了 fastcgi, 多年來想做的終於完成。

在 debian unstable 上測試, 除了 list 1 的設定檔錯誤之外, 大多沒問題, 照這做就成功了。chatgpt 說明要編輯 15-fastcgi.conf, 但在我的系統上, 是編輯 list 1 這個檔案, 貼上 L5 ~ L17 內容即可。

list 1. /etc/lighttpd/conf-available/10-fastcgi.conf
 1 # /usr/share/doc/lighttpd/fastcgi.txt.gz
 2 # http://redmine.lighttpd.net/projects/lighttpd/wiki/Docs:ConfigurationOptions#mod_fastcgi-fastcgi
 3
 4 server.modules += ( "mod_fastcgi" )
 5 fastcgi.server = (
 6     "/fcgi" => (
 7         (
 8             "socket" => "/tmp/hello_fcgi.socket",
 9             "bin-path" => "/usr/lib/cgi-bin/hello_fcgi",
10             "check-local" => "disable",
11             "max-procs" => 1,
12             "bin-environment" => (
13                 "LD_LIBRARY_PATH" => "/lib/x86_64-linux-gnu/"
14             )
15         )
16     )
17 )


hello_fcgi.c 是 fastcgi 範例, 使用了 libfcgi, 編譯指令:
gcc hello_fcgi.c -o hello_fcgi -lfcgi
hello_fcgi 放到 /usr/lib/cgi-bin/hello_fcgi
hello_fcgi.c
 1 #include <fcgi_stdio.h>
 2 #include <stdlib.h>
 3 
 4 int main(void) {
 5     int count = 0;
 6     while (FCGI_Accept() >= 0) {
 7         count++;
 8         printf("Content-Type: text/html\r\n\r\n");
 9         printf("<h1>Hello FastCGI</h1>\n");
10         printf("<p>Request #%d</p>\n", count);
11     }
12     return 0;
13 }


最後在瀏覽器貼上 http://localhost/fcgi 即可, /fcgi 對應到 list 1 L6, 實際執行 /usr/lib/cgi-bin/hello_fcgi, 而在 lighttpd 執行起來時, ps 指令也可以看到 hello_fcgi 會被執行起來。

使用 c++ 寫 fastcgi, 發現問題蠻多的, 「只要 #include <string> 或 <iostream> 就發生「500 Internal Server Error」, 「發生 500 Internal Server Error」一般是類似 segmentation fault 之類的錯誤。但程式並沒有發生 segmentation fault。只要在 include fcgi_stdio.h 之前 include string, iostream 就可以避開。另外也不能用 cout, 一樣會「發生 500 Internal Server Error」, 似乎有個 fastcgipp 可以處理, 但我不想嘗試了。用 printf 沒什麼不好。include vector 就沒這問題, 可以放在 fcgi_stdio.h 之後。

d.cpp
 1 #include <string>
 2 #include <iostream>
 3
 4 #include <fcgi_stdio.h>
 5
 6 using namespace std;
 7
 8 int main() {
 9     int count = 0;
10     while (FCGI_Accept() >= 0) {
11         printf("Content-type: text/html\r\n\r\n");
12         printf("<h1>Hello from C++ FastCGI!</h1>\n");
13         #if 0
14         std::cout << "Content-type: text/html\r\n\r\n";
15         std::cout << "<html><body>";
16         std::cout << "<h1>Hello from C++ FastCGI!</h1>";
17         std::cout << "<p>Request #" << ++count << "</p>";
18         std::cout << "</body></html>" << std::endl;
19         #endif
20     }
21     return 0;
22 }

2025年10月10日 星期五

linux qe editor 與 -O2 double free issue

The most difficult bugs to fix are the ones that don’t exist.
qe 是類似 dos pe2 或是漢書的文字編輯器。在 google 搜尋已經找不到這個軟體的資訊, 所以想寫一篇來紀錄。

原作者是 Jiann-Ching Liu, https://github.com/descent/qe 忘記從那個地方 clone 來, readme 提到的網址 http://www.cc.ncu.edu.tw/~center5/product/qe/ 已經無法連上, 所以只貼我 clone 的 source code。

readme 提到的網站 http://www.cc.ncu.edu.tw/~center5/product/qe/ 當然已經連不上。

按下2次 ESC, 會切到下方的命令列, 打 quit 可以離開 qe。在剛從 dos 轉到 linux 時, 還不太會用 vi 時, 短暫用過 qe。

目前的版本在 gcc 14 編譯會有 double free 的問題, 無法正常執行, 我嚇傻了, 之前用還好好的, 加上 sanitizer -fsanitize=undefined -fsanitize=address 後可以正常執行, 參考 fig 1。

身為軟體工程師, 看到這棘手的問題, 很想找出問題, 用了 valgrind, sanitizer 來查 double free, 不過沒什麼進展。由於是用 ncurses 寫的, 在除錯印出 debug message 上會有點麻煩, 增加點除錯困擾。

追了一下 code, 這應該是早期的 c++, 還沒有容器和 std::string, 要不然應該不會自己寫 linked list 和 string。

fig 1. qe

dirbuffer.cc
180 int dirbuffer::refreshdir(const char *path) {
183     linebuffer            *tmpptr, *ptr;
208     sprintf(linebuf, "[ %s ]", pathname.getString());
209     filename = linebuf;
210
211     if ((dirp = opendir(pathname.getString())) != NULL) {
212
213      for (ptr = head->next; ptr != tail; ptr = current) {
214          current = ptr->next;
215          ptr->~linebuffer(); // 引發 double free
216          delete ptr;
217      }

找了很久, 本來以為是 linebuffer 的 linked list 出問題, 結果是 qeString 引發, 並不是 linebuffer 這個 class, qeString 是類似 std::string 的東西, 我把動態 malloc 改成固定 array。
char         str[10000];
就解決這個問題。
 class qeString {
 protected:
-    char        *str;
+    char        str[10000];
有點奇怪, 感覺還是沒找到關鍵問題。

20251014 終於找到原因了, 的確和 qeString 有關。qe.cc 是簡化的版本, 這個就會得到 free(): double free detected in tcache 2 錯誤訊息。

qe.cc
 1 int main(int argc, char *argv[])
 2 {
 3     linebuffer        *head, *tail, *current;
 4
 5     head    = new linebuffer("=== Top of file ===");
 6     tail    = new linebuffer("=== Bottom of file ===");
 7     current = new linebuffer("");
 8     head->previous     = NULL;
 9     head->next         = current;
10     current->previous  = head;
11     current->next      = tail;
12     tail->previous     = current;
13     tail->next         = NULL;
14
15     current->~linebuffer();
16     printf("current: %p\n", current);
17     delete current;
18 }

原因是 qe.cc L15, linebuffer 繼承 qeString, 當手動喚起 linebuffer 解構函式時, 一併發動 qeString::~qeString(), 會 delete [] str, 而 qe.cc L17, delete current 會再次發動 qeString::~qeString(), 所以又再一次 delete [] str, 造成 double free。
delete current; 其實就會執行 current->~linebuffer();
是不是早期 c++ delete current 不會執行 current->~linebuffer();
qestring.cc
1 qeString::~qeString(void) {
2     if (str != NULL) delete [] str;
3     str = NULL;
4     buflen = len = 0;
5 }

參考 qe_df.log L9, L12, str 被重複 delete。

list 5. qe_df.log
 1 descent@deb64:qe$ ./qe
 2 init s: === Top of file ===, slen: 19
 3 str: 0x5595b8b29700
 4 init s: === Bottom of file ===, slen: 22
 5 str: 0x5595b8b29750
 6 init s: , slen: 0
 7 str: 0x5595b8b297a0
 8 next: 0x5595b8b29720, previous: 0x5595b8b292c0
 9 ~ str: 0x5595b8b297a0
10 current: 0x5595b8b29770
11 next: (nil), previous: (nil)
12 ~ str: 0x5595b8b297a0
13 free(): double free detected in tcache 2
14 Aborted

可是為什麼會這樣, qestring.cc L3 在 delete 之後有把 str 設定為 NULL, 照理來說 delete NULL 是不會有問題的, 後來發現是 -O2 影響的, 把 -O2 拿掉就正常。

list 6. no -O2
 1 descent@deb64:qe$ ./qe
 2 init s: === Top of file ===, slen: 19
 3 str: 0x55bd3ec88700
 4 init s: === Bottom of file ===, slen: 22
 5 str: 0x55bd3ec88750
 6 init s: , slen: 0
 7 str: 0x55bd3ec887a0
 8 next: 0x55bd3ec88720, previous: 0x55bd3ec882c0
 9 ~ str: 0x55bd3ec887a0
10 current: 0x55bd3ec88770
11 next: (nil), previous: (nil)
12 ~ str: (nil)
13 next: 0x55bd3ec88770, previous: (nil)
14 ~ str: 0x55bd3ec88700
15 next: (nil), previous: 0x55bd3ec88770
16 ~ str: 0x55bd3ec88750

list 6 沒有 -O2, 就沒遇到 delete 同個位址 str 的問題, 看來是 -O2 最佳化引起的。

gcc 7 也會編譯出有問題的 code, 一樣要拿掉 -O2。

list 7, 比較有無 -O2 差異
 1 descent@deb64:qe$ ./qe
 2 init s: === Top of file ===, slen: 19
 3 str: 0x55bd3ec88700
 4 init s: === Bottom of file ===, slen: 22
 5 str: 0x55bd3ec88750
 6 init s: , slen: 0
 7 str: 0x55bd3ec887a0
 8 next: 0x55bd3ec88720, previous: 0x55bd3ec882c0
 9 ~ str: 0x55bd3ec887a0
10 current: 0x55bd3ec88770
11 next: (nil), previous: (nil)
12 ~ str: (nil)
13 next: 0x55bd3ec88770, previous: (nil)
14 ~ str: 0x55bd3ec88700
15 next: (nil), previous: 0x55bd3ec88770
16 ~ str: 0x55bd3ec88750
17 
18 no -O2, 觸發 4次 ~str, 
19 
20 descent@deb64:qe$ ./qe
21 init s: === Top of file ===, slen: 19
22 str: 0x55b33169b700
23 init s: === Bottom of file ===, slen: 22
24 str: 0x55b33169b750
25 init s: , slen: 0
26 str: 0x55b33169b7a0
27 next: 0x55b33169b720, previous: 0x55b33169b2c0
28 ~ str: 0x55b33169b7a0
29 current: 0x55b33169b770
30 next: (nil), previous: (nil)
31 ~ str: 0x55b33169b7a0
32 free(): double free detected in tcache 2
33
34 -O2 觸發 2 次 ~str, 的確有最佳化,只是最佳化錯了, delete L28, L31 同樣位址

這個問題, 如果只貼 code 給 ai 看, 是找不出 -O2 的問題, 因為程式碼本身並沒有錯誤。

中文處理, 中文處理比我想像的還要難上不少, qe 支援的是 big5, 目前 linux 已改為 utf8, 顯示方面做了一些修正, 可以正常顯示 utf8 中文, 輸入中文的部份就還有問題, 而且看起來不好改, 暫時不動。

移動游標也不正常, 中文字移動游標需要2次左鍵, 覺得很不爽, 但還不知道怎麼改比較好。



ref:
從 CLE 移植過來的新套件:qe-0.0.34 仿 PE2 文書處理器

2025年10月3日 星期五

20250626 名古屋 栄 商圈 (6/7)

日期: 20250626 (4)
地點: 名古屋 栄

本篇文章提到的時間均為日本時間, 沒寫單位的金額為日圓。

我不會日文, 英文也不好: 文章中有關問路或是其他問人的資訊, 都是用很克難的方式完成 (簡單英文、簡單日文、比手畫腳)。
旅程累積的疲勞慢慢累積起來, 今天快 11 點才出門, 自由行就是可以這麼任性, 慢慢吃早餐, 拉個屎, 舒服又愜意。

結束了前3天的風景行程之後, 今天又回到購物行程, 目的地是「栄」商圈。

這個小雞蛋糕看起來很可愛, 但是不好吃, 建議買一個嘗鮮就好。



主要目標是 montbell, 某人在 2019 就想去, 結果遇到疫情, 直到 2025 才有機會再來日本, 而且地點也換了, 她花了很大的功夫才找到新地點。今天一樣是個下雨天, 好在都在地下道穿梭, 沒被天氣影響。



某人這邊逛了很久, 採購不少東西, 應該滿足了吧! 該吃個午餐了。這邊有間鰻魚之兼光 (うなぎの兼光), 有鰻魚肝, 之前沒吃過, 來吃吃看。







2份餐點共 6000, 不算便宜。



逛書店, 買了幾本漫畫, 沒有退稅。

在明丸超市買了烤雞, 當晚餐, 沒想到除了飛驒牛, 烤雞也好吃。



離開中日大樓, 到附近區域的明治屋名古屋栄大津通ストアー (名古屋市中区栄3丁目16番1号松坂屋名古屋店 地下1階) 商場超市買生食級干貝, 某人為了生食級干貝很努力的找超市。



到附近的唐吉軻德做最後的採購, 明天起床就要準備去機場搭機, 沒有時間做其他事情。

ref: