sambaでディレクトリの容量制限
sambaでディレクトリ毎の容量制限(クオータ)をしたいと考えました。
OSのクオータを利用する方法や、lvmでディスクを作成してマウントしたり、コンテナ化したディスクスペースをloopしてマウントする方法等いろいろとありますが、ここでは、sambaに用意されているクオータのインタフェースを使って実現してみたいと思います。
管理したいディレクトリが親子関係を作っている場合はいくつか問題がありますが、そうでなければOSがディレクトリクオータに対応していないDebian環境でもディレクトリの容量制限が実現できました。
sambaのビルドの確認
sambaでクオータインターフェースを利用するには、sambaがQUOTAオプション付きでビルドされていなければいけません。
Debian10や11でaptからインストールしたものに関しては問題なさそうでしたが、それを確認するにはsmbdに-bオプションをつけてビルド内容を確認します。
$ smbd -b | grep QUOTA
HAVE_SYS_QUOTAS
HAVE_SYS_QUOTA_H
HAVE_RPCSVC_RQUOTA_H
HAVE_GETQUOTA_RSLT_GETQUOTA_RSLT_U
HAVE_NFS_QUOTAS
HAVE_QUOTACTL_LINUX
HAVE_XFS_QUOTAS
WITH_QUOTAS
get quota command
smb.iniに、「get quota command」のエントリーを記述します。
これは、実際にクオータの判定をするバイナリへのパスを記述するもので、グローバル宣言部に書くことができます。
smbdがディレクトリアクセスを検知するたびに、所定のバイナリを起動して結果を求めるようになります。
詳細はman smb.confで確認できますが、このスクリプトは次の3つの引数を受け取ります。
- $1:ディレクトリ
ディレクトリが入るということですが、manにもかいてあるようにほとんどの場合で、カレントディレクトリを示す.が入っています。
この値は利用できそうにないです。
- $2:クエリのタイプ
この値は1:ユーザークォート、2:ユーザーデフォルトクォート、3:グループクォート、4:グループデフォルトクォートです。
グループクォートがいつ呼び出されるかわかりませんでしたが、試してみたところ、1回のアクセスに対してタイプ1と2で、合計2回スクリプトが呼び出されていました。
- $3:UIDかGID
ユーザーIDか、グループのIDが入ります。タイプがユーザークォートの際は-1が入るようでした。
クオータの判定結果である戻り値は、スペース区切りの数値を1行で返します。そのレイアウトは次の通りです。
- クオータフラグ
0:クオータなし、1:クオータ利用可能、2:クオータ必須
クオータを利用する際は2を指定します。
- 現在のブロック数
現在使用中のディスクのブロックサイズを入れます。
- ブロック数のソフトリミット
任意のファイルのサイズをブロック数に換算するには、ファイルサイズ÷ブロックサイズで求めることができます。ブロックサイズは保存時の単位で、ファイルを保存する際に使いきれない分はブランクになります。
ブロックサイズはディスク毎に変えることができます。そのファイルシステムのブロックサイズを調べるには、nkjmkzk.net:「ファイルシステムのblock sizeを調べるには」によると、「tune2fs -l /dev/sda2 | grep "Block size"」とするそうです。
- ブロック数のハードリミット
ソフトリミットは警告を出す基準値、ハードリミットが実際の制限値というのが一般的なのだと思いますが、筆者の試してみた環境だと、ソフトリミットもハードリミットも区別をしないようでした。現在のブロック数に新たなファイルを書き込むのに必要なブロック数を加えた値がソフトかハードどちらかの制限を超えると書き込みができなくなります。
- inode数
WhizzTechnology:「linuxで使っているinode数を知りたい」によると、inodeは使用中のファイル数にほぼ等しいということです。dfコマンドに-iオプションをつければその確認ができます。
今回はindoe数は利用しません。
- inode数のソフトリミット
- inode数のハードリミット
- ブロックサイズ
この値だけ省略可能です。省略すると1024が設定されます。ブロック数をバイト数に換算する際に用いられます。
ここではクオータの判定をするバイナリを/usr/local/bin/smb-quota.shと指定しています。設定の記述では引数がつけられませんので注意してください。つけると引数全体を関数とみなされ実行できません。これは前述のようにパラメータを受け取る都合によるものだと思います。ちなみに似たような設定の「panic action」には引数がつけられます。
smb.conf
... [ global ] ... get quota command=/usr/local/bin/smb-quota.sh ...
スクリプト例
バイナリは先の仕様に従ったものを作成します。ここではbashスクリプトで作成しています。対象となるディレクトリは、引数からは.という形でしか取得できません。しかし、smb.confにpathとして設定されているディレクトリからコマンドが呼び出されるようで、そのpathはpwdコマンドを使うことで取得できます。
pwdで取得できるディレクトリ毎にブロックサイズの制限を定義しておき、duコマンドで取得できるディレクトリ内で実際に使用されているブロックサイズとともに、結果を生成して返します。
duコマンドは-sオプションで合計だけの値にし、--block-sizeを指定してバイト数からブロック数に変換しています。ブロックサイズは前述の通り環境に合わせて変更してください。
indoe数に関しては今回はすべて0としました。
smb-quota.sh
#!/usr/bin/bash # ログ出力の有無 LOG_EXPORT=1 # ログの出力先 LOG_PATH=/usr/local/bin/quota.log # クオータの対象ディレクトリ TARGET_DIR=`pwd` # デフォルトの制限ブロック数 LIMIT_BLOCK=5000 # ブロックサイズ BLOCK_SIZE=4096 # 使用中のサイズを取得、()でくくることでスペースで分割した配列として取得 DU_RESULT=(`du -s --block-size=$BLOCK_SIZE $TARGET_DIR`) # ディレクトリに応じて上限値をセット case $TARGET_DIR in /var/share/dir1 ) LIMIT_BLOCK=50000;; /var/share/dir2 ) LIMIT_BLOCK=80000;; esac # ログ出力 if [ "$LOG_EXPORT" = "1" ]; then MESSAGE=`date`" TARGETDIR:${TARGET_DIR} PARAMS:${1} ${2} ${3} CURRENT:${DU_RESULT[0]} LIMIT:${LIMIT_BLOCK}" echo $MESSAGE>>$LOG_PATH fi # sambaに結果を返す echo 2 ${DU_RESULT[0]} $LIMIT_BLOCK $LIMIT_BLOCK 0 0 0 $BLOCK_SIZE
問題点
この方法はsmb.confのpathに記述する単一のディレクトリを扱うには問題がないのですが、子孫ディレクトリに対しても制限をかけたい場合は問題が発生します。
まず、通常の方法ではpathに指定したディレクトリの、子孫ディレクトリに対しては制限ができません。子孫ディレクトリのどの階層にアクセスしてもpwdで取得できるのはpathで指定したディレクトリとなるからです。
smb.confに親ディレクトリのエントリーと子孫ディレクトリのエントリーを別々に記述すると、子孫ディレクトリにアクセスした経路によってpwdの戻り値は親と子孫のどちらにもなりえます。親ディレクトリからのシンボリックリンクで仮想的に階層構造を作っても結果は同じでした。
ひとつの回避策としては、ディレクトリの階層構造をやめたうえで、Windowsで作ったリンク(.lnk)で仮想の階層構造を作成するというものがあるのですが、これはまた別の問題を生みます。この方法は(架空の)子孫フォルダにアクセスする際Windows上で¥¥share.name¥dirといったリンクをたどるだけなので、Linux環境ではリンクをたどることができません。さらに、このリンクはLinuxで管理するシンボリックリンクではないのでduの-Lオプションが使えません。親ディレクトリでのブロックサイズの計算は、子孫ディレクトリの容量を含むように個別に作らなければいけません。