FreeBSD 設置 NFS v4

首先修改 /etc/rc.conf:

啟動 nfsd:

修改 /etc/exports:

參考資料:
http://www.blissfulidiot.com/2013/06/configuring-nfsv4-on-freebsd.html
https://www.freebsd.org/cgi/man.cgi?query=nfsv4&sektion=4
https://www.freebsd.org/cgi/man.cgi?query=exports&sektion=5
http://www.jasonvanpatten.com/2015/11/26/freebsd-as-my-network-storage-server-part-2/

std::string 轉換成 char*

** 這篇的程式沒有實際編譯執行過,不保證沒有錯誤 **

很多時候我們得在 C++ 使用 C 語言寫的函式庫,
這時候 std::string 有幾種不同的方法來轉為 char*:

最安全也最常見的用法,是呼叫 std::string.c_str()。
這個函式會回傳一個標準的 char array,但是是唯讀的。

如果我們要讓 char array 可以寫入呢?
那就要先將字串複製到可寫入的 buffer:

變得麻煩了,對吧?

有沒有什麼方法可以直接更新到 string 而不用複製來複製去的呢?
std::string.data() 可以用來做這件事。
它回傳的是 string 內部用來儲存字串的記憶體區塊,所以修改值的話就會反應到 string 去。
不過舊的 C++ 標準並沒有強迫內部的記憶體區塊是連續的,所以這是有一點風險的。
另外,內部的記憶體也不保證會有 NULL 結尾,使用時一定要搭配長度的限制。

但風險也沒有那麼大,Herb Sutter 指出他目前所知的 std::string 實作,都是連續且 NULL 結尾的[1]。
所以用起來還算可以放心。

另外還有一個方式,就是使用陣列運算子。
因為 std::string[] 會回傳單一字元的參考,若對此參考取指標,就可以得到一個能用來修改內容的指標。
然而若對空字串取第一個字元 (empty_string[0]) 的話,不保證能夠得到 ‘\0’。
因此還是建議使用 std::string.data()。

在參考資料[2]裡面有很詳細的解說。基本上 C++11 解決了很多這類不確定的混亂情況,若是允許的話還是用新的標準編譯吧。

參考資料:
[1] http://herbsutter.com/2008/04/07/cringe-not-vectors-are-guaranteed-to-be-contiguous/
[2] http://stackoverflow.com/questions/347949/how-to-convert-a-stdstring-to-const-char-or-char

Nagios Plugin PATH 問題

今天在寫 Nagios Plugin 遇到的,從 Nagios 可以正常執行,但是從 NRPE 去執行就會失敗。失敗的時候顯示這個訊息:

使用下面的 script 來檢查,最後查出是因為 PATH 的問題:

這段程式在 Nagios 顯示:

但是在 NRPE 卻是:

因此就會發生某些程式無法執行的問題,最簡單的方法就是用完整路徑去執行程式,這樣也比較容易安裝到其它機器去執行。

另外,從本機測試 NRPE 會比每次都從遠端 Nagios 來的方便:

Apache mod_rewrite 筆記

官網的說明在此:
http://httpd.apache.org/docs/current/mod/mod_rewrite.html

mod_rewrite對我來說算是常用的一個功能, 但是網路上很少找到完整的教學. 所以就只好自己看官網的文件邊啃邊測試了.

注意事項:

  1. 要使用 mod_rewrite 的虛擬主機或資料夾, 必須設定 Options FollowSymLinks, 否則無法運作.
  2. Pattern 比對的可能是 URL, 也可能是檔案路徑, 這要看我們在何處使用 RewriteRule. 差別在於 URL 開頭會有斜線, 路徑不會, 因此在寫 Pattern 時要注意.
  3. 有可能形成無限迴圈. 這會嚴重影響效能. 在新版 Apache 若改寫前後的檔案相同, 會自動停止.

設定紀錄檔:

在 httpd.conf 中加入以上設定, 就可以開啟 log 功能. log 等級從 1 到 9, 數字越大越詳細. 開發或除錯時可以很方便的瞭解實際的運作情形. 我們可以給每個虛擬主機設定不同的紀錄檔, 但無法給資料夾設定紀錄檔. 例如以下這段設定, 上面的可以執行, 下面的則會出現錯誤訊息

錯誤訊息會抱怨說 RewriteLog 這個設定不能使用在這邊

基本語法:

這個設定就是 mod_rewrite 真正在做事情的地方了, Pattern 是用來比對我們想要改寫的原始內容, Substitution 則是我們要將其改寫成什麼樣子. 換句話說, 只有符合 Pattern 的情況下才會進行改寫的動作. flags 可以設定一些選項, 例如說不想區分大小寫, 就可以使用 [NC] 這個 flag.

在一般情況下, RewriteRule 是照順序一個一個執行下去的, 且前面一條改寫過的輸出會成為下一條的輸入, 所以安排好合理的順序會是很重要的關鍵. 下面的例子, 首先 about.htm 符合 Pattern, 變成 about1.php, 然後執行下一條規則, about1.php 又符合 Pattern, 所以最後會跑去 about2.php

兩種情境:

當使用在 VirtualHost 標籤中, 或是不在任何標籤中, 將會以 Host 情境執行. 在這個情況下, 拿來與 Pattern 比對的是 URL, 包含開頭的斜線. 反之, 若我們是在 Directory 或是 htaccess 檔案中使用, 則會以 per-directory 情境執行, 這時會用檔案路徑去與 Pattern 比對. 例如我們分別用上面的設定來執行以下例子, 會發現在 log 中它是用不同的字串去與 Pattern 比對:

Pattern 的用法:

Pattern 的部份是使用正則表達式(Regular Expression), 在大部分常見的用途當中, 其實並不會用到太複雜的語法. 然而它畢竟是個威力強大的表示方法, 一有不小心就可能產生預期之外的結果, 例如:

注意這個例子前後 url 的變化, 路徑不見了, 只剩下檔名. 這是因為 Pattern 給的不夠精確, 造成我們不管存取任何路徑的 about.htm, 都會符合 Pattern. 而改寫時, Substitution 又不包含路徑, 所以路徑也就自然不見了.

單一入口頁面:

另外, 一個經典的使用方法, 是將所有的網頁請求交給單一頁面處理, 例如:

但這邊用來判定檔案是否存在的方法需要注意, 若是在VirtualHost中使用, %{REQUEST_FILENAME}得到的是 /something.php 這樣的路徑, 於是rewrite會去檢查系統根目錄底下的something.php, 而不是網頁根目錄底下的檔案, 這會造成功能不正常, 可以說完全無法使用.
解決辦法, 就是加上<Location />, 並在其中設置rewrite的語法:

強制使用 https :

如果想要將整個網站改成使用 https, 這是其中一種方法

Design Pattern — Data Access Object

DAO(Data Access Object) 應該是最基礎且最廣泛運用的一個設計模式了。其適用的情境為:

  • 程式需要利用別的媒介來儲存或取得資料
  • 程式需要能移植到其它儲存媒介
  • 各個儲存媒介之間使用的存取方式不同
  • 希望程式不會受到儲存媒介的變換而修改

DAO 通常看到的範例都會與另外一個物件搭配使用,稱之為 Data Transfer Object 。但是這兩者之間並不一定要綁在一起用。DAO 的重點在提供一個統一的資料存取方式,而回傳的資料可以用 DTO 表示,但也可以用其它方式(例如陣列)來實作。這邊以一個會員系統為例,如何以 DAO 模式來提供會員相關資料的存取。

首先需要設計一個 Interface 來提供資料的存取,這邊為了簡化程式數量,還是按照常見作法搭配 DTO 來使用:

有了這個 Interface 之後,我們就可以實作兩種 DAO 分別對應到記憶體與資料庫這兩種不同的儲存媒介:

因為偷懶的關係所以這兩個 DAO 長的根本一模一樣。但是實際應用的時候 MemoryUserDAO 應該只會帶有一些陣列操作之類的程式碼,而 MySQLUserDAO 裡面可能會有 PDO 及 SQL 語法,或是利用 ORM 來存取資料。

使用的時候我們就可以不用管 DAO 實際對應的媒介,只需要呼叫其界面提供的方法就可以了:

另外,當需要支援更多種類的儲存媒介,你可以很容易的實作更多的 DAO 來處理。而且完全不用修改現有的程式碼。這同時也提供了程式良好的可測性,我們只需要實作一個 DAO 來控制回傳的資料,就能測試各種資料組合的單元測試了。

總結一下優點:

  • 將特定媒介的 API 使用方式隔離在 DAO 內,易於維護
  • 易於新增或抽換儲存媒介
  • 降低程式的複雜度
  • 提昇可測性

參考資料:
http://www.oracle.com/technetwork/java/dataaccessobject-138824.html
http://www.adam-bien.com/roller/abien/entry/value_object_vs_data_transfer
http://martinfowler.com/eaaCatalog/dataTransferObject.html

使用 systemd.timer 代替 crontab

systemd 提供的 timer 功能,可以用來代替 crontab,且其功能更多一些,也可達成類似 anacron 的功能。但設置起來當然也就複雜了一點點,習慣用 crontab 的人應該會強烈的感到麻煩吧 :p

這邊要紀錄的是基本功能,也就是在指定的時間,或是定期執行某個 script。

systemd 的概念,是將你要執行的 script 先設置成 service,然後你應該可以手動執行這個 service,也可以透過 timer 去執行它。也就是說,要將原本 crontab 做的項目改用 systemd,一個項目就會對應到兩個檔案,service 與 timer。

先從 service 開始,現在我們有個 script 需要每分鐘定期執行,這個 script 個工作很簡單,紀錄下自己執行的時間,寫到 logs 資料夾內。

將這個 script 存放到 /root/bin/log_time.sh 這個位置。接著就可以建立 service 了:

Unit 那邊只是給個描述,說明這個的用途。Service 那邊就比較重要了,其中 Type=simple 表示這個 service 是標準行為。若將 Type 設為 oneshot 就只會被執行一次。ExecStart 則是要執行的 script,要加上參數也是直接接在後面就可以了喔。

這個 service 檔案應該要存放在 /etc/systemd/system/logtime.service,然後就可以測試看看是否能夠執行了:

看起來成功執行嘍,接著就是要讓它定期能夠運作了。我們開啟一個新的 timer 檔案:

Timer 那邊是指定要執行的時間與 service。時間的格式很簡單,由左到右分別是 年-月-日 時:分:秒。以 * 代表的話就表示任意。上面的例子裡我們讓 timer 在每分鐘 00 秒時執行。最底下的 install 也很重要,它決定這個 timer 在什麼情況下會啟動。WantedBy=multi-user.target 意思是在多人模式下會自動啟動這個 timer。我想大部分應該都是用這個才對。

最後我們一樣將這個 timer 檔案存放到 /etc/systemd/system/logtime.timer 就可以了。設置到這個階段,還需要將這個 timer 啟用。其中包括了開機時自動啟用,以及現在不重開機的狀況下開始執行。

用下列命令來開機自動啟用:

用這個命令來檢查是否已啟用:

啟用後的 timer 會在開機後自動執行,但是若不想重開機就開始的話,得手動去執行它:

然後這樣檢查是否有在執行:

去檢查一下 logs 資料夾,果然每分鐘都會出現新的 log 了:

同場加映

上面講的是系統層級的設定方法,那麼有沒有使用者個人的 timer 與 service 可以用呢?

有的。首先要說明的是,systemd 只會在使用者有登入的狀態才會幫它執行 timer。若是要讓使用者登出後還能正常運作,需要做這個設置:

你可以這樣檢查 linger 是否啟用:

接著,使用者的 service 與 timer 檔應該要放在 ~/.config/systemd/user 裡面。啟用 timer 的方式基本上相同,但是要多加一個參數 –user:

參考資料

  • https://wiki.archlinux.org/index.php/Systemd
  • https://wiki.archlinux.org/index.php/Systemd/Timers
  • https://wiki.archlinux.org/index.php/Systemd/User
  • https://sevenbyte.org/2014/11/15/cron-jobs-with-systemd-timer/
  • http://jason.the-graham.com/2013/03/06/how-to-use-systemd-timers/
  • http://www.freedesktop.org/software/systemd/man/systemd.unit.html

BSD Packet Filter 筆記

系統更新之後 IPF 跑起來怪怪的,換成 PF 試試看會不會正常一點。兩者語法差異並不大,但 PF 多出比較方便的功能。

官方文件:
https://www.freebsd.org/doc/handbook/firewalls-pf.html

MAN:
https://www.freebsd.org/cgi/man.cgi?query=pf.conf

rc.conf 選項

port 表示法

語法:
[in/out] [quick] [on if] [proto tcp/udp/…] [from ip/CIDR] [to ip/CIDR] [port ports] [flags S/SA/…] [keep-state]

以前若是機器有多個 IP,需要根據 IP 來選擇路由的時候,使用 IPF 會這樣做:

PF 沒有支援類似的語法,同樣的功能需要用 reply-to 來達成:

若要加上詳細的條件:

差別在原本是封包出去的時候再重導,現在變成封包進來時就先決定回應要往哪裡丟。

PF 的規則是最後比對到的優先,而且對於順序有一定的要求,必須是按照 options, normalization, queueing, translation, filtering 由上往下列出。因此像是變數的設定,或是選項的調整都要放在最前面,接著是 NAT 的功能,最後才是放過濾的條件。

KDE 為特定程式解決快速鍵衝突的好方法

程式的快速鍵與系統的快速鍵衝突也不是什麼稀奇的事情。通常我個人不太使用系統的快速鍵,所以遇到這種情況就是把系統的改掉或取消。但今天遇到問題了,怎麼樣都找不到某個快速鍵系統設置的位置在哪裡。參考了網路上的文章,我找的地方並沒有錯,但就是沒那一個項目。(題外話,KDE 每個版本放快速鍵的位置都有變動是怎樣…)

也因為一直沒找到問題,反而讓我看到有更好的方法,也就是為特定程式完全關閉系統快速鍵。對於不太使用系統快速鍵的我,這可以說是一勞永逸的方法。

首先打開你要用的程式,然後在視窗標題上面按右鍵,選擇 “更多動作” -> “特殊的應用程式設定”。

然後在 “外觀與修復” 這個分頁,有個選項叫做 “忽略全域快速鍵”。

勾選起來,下拉選單改為 “強制”,右邊項目選 “是”。

這樣就完成了。測試了一下果然快速鍵都不會再衝突了。

用 PHP 做檔案下載功能時遇到的記憶體問題

一般來說,為了避免使用者上傳了什麼奇奇怪怪的檔案被執行,會將上傳的檔案放在網頁根目錄以外的地方,再利用 PHP 將檔案內容輸出給 Client。

最簡單的方法是用 readfile 這個函式直接將檔案內容輸出,然而如果允許上傳的檔案比較大的話, readfile 會一次將檔案讀到記憶體內,造成記憶體用量超出上限的問題。

這個不能單純將記憶體上限提高來解決,不然只要同時幾個人下載檔案就會造成伺服器記憶體用盡。我想目前最簡單的方法是利用 stream 的功能來做:

這樣子就可以了,經測試下載數百 MB 的檔案也不會直接造成 PHP 錯誤。