Типичной ситуацией является некорректное отображение общего размера сетевой шары, если она лежит на BTRFS-рейде — вместо действительного размера тома показывается сумма размеров всех дисков в рейде. Для исправления этой ситуации можно воспользоваться опцией «dfree command», которая позволяет указать кастомный исполняемый файл, которые будет подсчитывать и возвращать данные о размерах диска.
Данные опции нужно добавить в конфигурацию шары:
[Public] .... dfree command = /etc/samba/btrfs-dfree.py dfree cache time = 60 force user = root force group = root ....
Здесь стоит обратить внимание на опцию «force user = root», которая с одной стороны принудительно сделает рута владельцем новых файлов и папок, что может быть вам не нужно, но также позволит выполняться скрипту с рутовыми правами — это необходимо для получения таких данных из вывода «btrfs filesystem usage» как «ratio» — коэффициент данных на рейде, учитывающий сколько копий данных фактически на рейде расположено, и «free (estimated) min» — более точное значение свободного места.
Если в ваши планы не входит менять юзера с дефолтного, то свободное место будет браться скриптом из «Free (statfs, df)», а для показа точного общего размера сетевой шары можно вручную в скрипте указать значение «ratio», присвоив его переменной «CUSTOMRATIO».
Итак, создаем скрипт «/etc/samba/btrfs-dfree.py» (спасибо проекту openmediavault, я использовал их скрипт «omv-btrfs-dfree» как основу), не забываем сделать его исполняемым. Скрипт на гитхабе
#!/usr/bin/env python3 import os import re import subprocess import sys from systemd import journal CUSTOMRATIO = 0 # set ratio of your btrfs volume if samba user is not root (smb.conf: force user = root) LOGGING = 0 # 0 - off, 1 - on. journalctl -t 'SMBUS' def main(): path = os.path.realpath(sys.argv[1] if len(sys.argv) > 0 else '.') output = subprocess.run( ['btrfs', 'filesystem', 'usage', '--raw', '-T', path], capture_output=True ).stdout.decode() re_matches_for_total = r'.+Device size:\s+(\d+)\n.+' matches_for_total = re.match( re_matches_for_total, output, re.DOTALL ) if os.getuid() == 0: # if samba user is root we can get ratio to calculate total space re_matches_for_ratio = r'.+Data ratio:\s+(\d+\.\d+).+' matches_for_ratio = re.match( re_matches_for_ratio, output, re.DOTALL ) ratio = float(matches_for_ratio.group(1)) else: # else set ratio = 1 but can get wrong total size if CUSTOMRATIO == 0: ratio = 1 else: ratio = CUSTOMRATIO if os.getuid() == 0: # if user is root we can get estimated (minimum) free space re_matches_for_available = r'.+Free \(estimated\):\s+\d+\s+\(min: (\d+)\)\n.+' else: # otherwise only 'statfs' free re_matches_for_available = r'.+Free \(statfs, df\):\s+(\d+)\n.+' matches_for_available = re.match( re_matches_for_available, output, re.DOTALL ) if matches_for_total is not None: totalb = matches_for_total.group(1) availableb = matches_for_available.group(1) if LOGGING == 1: journal.stream('SMBUS').write(f'SMB VARS: userid={os.getuid()}, ratio={ratio}, availableb={availableb}, totalb={totalb}') for line in output.splitlines(): journal.stream('SMBUS').write(f'{line}') total = round(int(totalb) // (1024 * ratio)) available = round(int(availableb) // 1024) else: output = subprocess.run( ['df', '--portability', '--print-type', '--block-size=1K', path], capture_output=True ).stdout.decode() _, _, total, _, available, *_ = output.split('\n')[1].split() sys.stdout.write(f'{total} {available} 1024\n') if __name__ == "__main__": sys.exit(main())
Проверить работу скрипта можно выполнив его вручную, указав в качестве аргумента путь к папке на btrfs: