March 10Mar 10 I found that exporting some of my Unraid ZFS datasets via SMB resulted in Windows Explorer reporting the capacity of the underlying storage pool instead of the quota configured on the dataset in which the share was looking.For example, a dataset with a quota of 1 TB may appear in Windows as having 8 TB or more available, depending on the size of the ZFS pool. While ZFS still correctly enforces the quota, the misleading capacity shown in Windows can be confusing to some user.sThis behaviour occurs because Samba normally determines disk capacity using filesystem statistics from the underlying storage layer. When accessing data through the Unraid /mnt/user FUSE share, those statistics reflect the pool or backing filesystem rather than any dataset quota if one was in place within a share.The steps below hopefully show how I configured Samba (with ChatGPT assistance) to report the correct quota and available space for each ZFS dataset. This shows the use of using Samba’s dfree command feature and a helper script that queries ZFS directly when a Samba client requests the info.The solution works for datasets stored:on a single disk ZFS dataseton a multi-disk ZFS pool (mirror, RAIDZ, etc.)provided the SMB path maps to one dataset with a defined quota.Note: Although I was able to debug a number of potential edge-case uses, I am not a coder and have relied on ChatGPT to generate the necessary scripts! As a result please do not rely on me to provide any 'support' if your use case is outside the limits of the testing I did!Tested EnvironmentThis configuration was tested on:Unraid with ZFS datasetsSMB exports via /mnt/userWindows SMB clientsbut the approach should work with any SMB client (Windows, macOS, Linux).Use CasesThis is particularly useful for setups such as:per-user storage quotasOneDrive / cloud-sync mirrorsproject-based storage limitsmedia or archive shares with defined quotaswhere you want users to see the actual space available to them, not the capacity of the underlying storage pool.When This Fix AppliesThis solution works when the SMB path corresponds to a single ZFS dataset with its own quota, regardless of how many disks are in the pool.Examples where this works:Dataset on a single disk: (note in my use case, disk5 was a ZFS disk in the array, and I created a nested dataset /onedrive/Pictures/mnt/disk5/onedrive/PicturesDataset in a multi-disk ZFS pool:/mnt/zfs_cache/onedrive/PicturesEven if the pool itself consists of multiple disks (mirror, RAIDZ, etc.), the dataset still has:one mountpointone quotawhich can be reported correctly.When This Does NOT ApplyThis method does not apply to normal Unraid shares (mnt/user/....) spanning multiple disks, for example:/mnt/disk1/media /mnt/disk2/media /mnt/disk3/mediaIn this case:there is no single datasetthere is no single quotathe correct behaviour is to report the combined capacityThe helper script automatically falls back to default behaviour in these situations.Why This HappensSamba normally determines disk space using filesystem statistics (statfs).Because Unraid shares are accessed through the FUSE layer:/mnt/user/Samba sees the underlying storage pool, not the dataset quota.How the Fix WorksSamba provides a configuration option called:dfree commandThis allows Samba to call a helper script instead of querying the filesystem directly.The helper script (below):determines which ZFS dataset the SMB path belongs toreads the dataset's quota and available spacereturns those values to SambaSMB clients then display/use these 'corrected' values for the dataset quotas.Architecture OverviewTypical path flow:Windows / SMB Client │ ▼ Samba │ ▼ Unraid Share /mnt/user/ (mapped path) │ ▼ ZFS Dataset │ ▼ ZFS Pool │ ▼ Physical DisksExample:\\NAS\onedrive\Pictures │ ▼ /mnt/user/onedrive/Pictures │ ▼ /mnt/disk5/onedrive/Pictures │ ▼ disk5/onedrive/PicturesStep 1 – Create the 'Helper' ScriptNote. To allow this script to be persitant across reboots, you need to create a script on the USB /boot drive initially.Create:/boot/config/custom/samba_dfree_zfs.shExample script:#!/usr/bin/bash # Samba dfree helper for ZFS dataset quotas on Unraid # Returns: "<total_1K_blocks> <free_1K_blocks>" set -u PATH_IN="${1:-.}" CACHE_FILE="/run/samba_dfree_zfs_mounts.cache" CACHE_TTL=10 get_mounts() { local now mtime age now=$(date +%s) if [ -r "$CACHE_FILE" ]; then mtime=$(stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 0) age=$(( now - mtime )) if [ "$age" -lt "$CACHE_TTL" ]; then cat "$CACHE_FILE" return 0 fi fi zfs list -H -t filesystem -o name,mountpoint 2>/dev/null > "${CACHE_FILE}.tmp" || true mv -f "${CACHE_FILE}.tmp" "$CACHE_FILE" 2>/dev/null || true cat "$CACHE_FILE" 2>/dev/null || true } df_fallback() { local line total avail line="$(df -P "${PATH_IN}" 2>/dev/null | tail -n 1 | tr -s ' ')" total="$(printf '%s\n' "$line" | cut -d' ' -f2)" avail="$(printf '%s\n' "$line" | cut -d' ' -f4)" if [ -n "${total:-}" ] && [ -n "${avail:-}" ]; then printf '%s %s\n' "$total" "$avail" return 0 fi printf '0 0\n' return 0 } if PATH_ABS="$(cd "$PATH_IN" 2>/dev/null && pwd -P)"; then : else PATH_ABS="$PATH_IN" fi if [[ "$PATH_ABS" != /* ]]; then PATH_ABS="/mnt/user/onedrive/$PATH_ABS" fi BEST_DS="" BEST_MP="" BEST_LEN=0 SUFFIX="${PATH_ABS#*/mnt/user}" while IFS=$'\t' read -r ds mp; do [ -n "${mp:-}" ] || continue [ "$mp" = "-" ] && continue case "$mp" in *"$SUFFIX") len=${#mp} if [ "$len" -gt "$BEST_LEN" ]; then BEST_LEN="$len" BEST_DS="$ds" BEST_MP="$mp" fi ;; esac done < <(get_mounts) if [ -z "$BEST_DS" ] && [[ "$PATH_ABS" == /mnt/user/* ]]; then REL="${PATH_ABS#/mnt/user/}" for d in /mnt/disk* /mnt/cache /mnt/*; do [ -d "$d" ] || continue if [ -e "$d/$REL" ]; then PATH_ABS="$d/$REL" break fi done BEST_LEN=0 while IFS=$'\t' read -r ds mp; do [ -n "${mp:-}" ] || continue [ "$mp" = "-" ] && continue case "$PATH_ABS" in "$mp"/*|"$mp") len=${#mp} if [ "$len" -gt "$BEST_LEN" ]; then BEST_LEN="$len" BEST_DS="$ds" fi ;; esac done < <(get_mounts) fi [ -n "$BEST_DS" ] || df_fallback QUOTA_B="$(zfs get -Hp -o value quota "$BEST_DS" 2>/dev/null || true)" if ! printf '%s' "$QUOTA_B" | grep -Eq '^[0-9]+$'; then QUOTA_B=0 fi if [ "$QUOTA_B" -eq 0 ]; then QUOTA_B="$(zfs get -Hp -o value refquota "$BEST_DS" 2>/dev/null || true)" if ! printf '%s' "$QUOTA_B" | grep -Eq '^[0-9]+$'; then QUOTA_B=0 fi fi [ "$QUOTA_B" -gt 0 ] || df_fallback AVAIL_B="$(zfs get -Hp -o value available "$BEST_DS" 2>/dev/null || true)" if ! printf '%s' "$AVAIL_B" | grep -Eq '^[0-9]+$'; then df_fallback fi TOTAL_K=$(( QUOTA_B / 1024 )) AVAIL_K=$(( AVAIL_B / 1024 )) [ "$AVAIL_K" -lt 0 ] && AVAIL_K=0 printf '%s %s\n' "$TOTAL_K" "$AVAIL_K" exit 0Note:The /boot drive is FAT32, so no chmod is required there.Step 2 – Configure SambaGo to:Settings → SMB → SMB ExtrasAdd:dfree command = /usr/local/sbin/samba_dfree_zfs.sh⚠ ImportantSMB configuration can only be modified when the array is stopped.Procedure:Stop the arrayAdd the SMB Extras entryStart the array againStep 3 – Ensure the Script Exists After BootBecause Unraid runs from RAM, /usr/local is recreated on every boot.Create a UserScript which runs on Array Start.Example:#!/bin/bash SRC="/boot/config/custom/samba_dfree_zfs.sh" DST="/usr/local/sbin/samba_dfree_zfs.sh" mkdir -p /usr/local/sbin cp "$SRC" "$DST" chmod 755 "$DST" sleep 10 # Initial seeding/warm-up of values for the datasets/paths to be queried. # not entirely necessary, but belt and braces! for d in Pictures Videos Documents; do "$DST" "$d" >/dev/null 2>&1 done /etc/rc.d/rc.samba restartThe warm-up calls ensure the helper script cache is populated before Samba handles client requests.Note on Dataset Root PermissionsIf the dataset root is exported via SMB, ensure it allows write access.Example:chmod 775 /mnt/disk5/onedrive/PicturesOtherwise Windows clients may be unable to create folders at the root of the share.End Result in Windows!Uploading Attachment...Note that each 'mapped drive' relates to either the root directory in a dataset or a path within the dataset (the Helper script will determine the dataset quota to apply based on path and mount point).I found it worked for my set up! YMMV!!! Edited March 10Mar 10 by Bob_C typo
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.