Skip to content
View in the app

A better way to browse. Learn more.

Unraid

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.

Configuring SMB to report ZFS Dataset Quotas instead of ZFS pool sizes (Fix using 'dfree command')

Featured Replies

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.s

This 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 dataset

  • on 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 Environment

This configuration was tested on:

  • Unraid with ZFS datasets

  • SMB exports via /mnt/user

  • Windows SMB clients

but the approach should work with any SMB client (Windows, macOS, Linux).


Use Cases

This is particularly useful for setups such as:

  • per-user storage quotas

  • OneDrive / cloud-sync mirrors

  • project-based storage limits

  • media or archive shares with defined quotas

where you want users to see the actual space available to them, not the capacity of the underlying storage pool.

When This Fix Applies

This 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/Pictures

Dataset in a multi-disk ZFS pool:

/mnt/zfs_cache/onedrive/Pictures

Even if the pool itself consists of multiple disks (mirror, RAIDZ, etc.), the dataset still has:

  • one mountpoint

  • one quota

which can be reported correctly.


When This Does NOT Apply

This method does not apply to normal Unraid shares (mnt/user/....) spanning multiple disks, for example:

/mnt/disk1/media
/mnt/disk2/media
/mnt/disk3/media

In this case:

  • there is no single dataset

  • there is no single quota

  • the correct behaviour is to report the combined capacity

The helper script automatically falls back to default behaviour in these situations.


Why This Happens

Samba 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 Works

Samba provides a configuration option called:

dfree command

This allows Samba to call a helper script instead of querying the filesystem directly.

The helper script (below):

  1. determines which ZFS dataset the SMB path belongs to

  2. reads the dataset's quota and available space

  3. returns those values to Samba

SMB clients then display/use these 'corrected' values for the dataset quotas.


Architecture Overview

Typical path flow:

Windows / SMB Client
        │
        ▼
      Samba
        │
        ▼
   Unraid Share
    /mnt/user/   (mapped path)
        │
        ▼
   ZFS Dataset
        │
        ▼
     ZFS Pool
        │
        ▼
   Physical Disks

Example:

\\NAS\onedrive\Pictures
        │
        ▼
/mnt/user/onedrive/Pictures
        │
        ▼
/mnt/disk5/onedrive/Pictures
        │
        ▼
disk5/onedrive/Pictures

Step 1 – Create the 'Helper' Script

Note. 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.sh

Example 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 0

Note:
The /boot drive is FAT32, so no chmod is required there.


Step 2 – Configure Samba

Go to:

Settings → SMB → SMB Extras

Add:

dfree command = /usr/local/sbin/samba_dfree_zfs.sh

Important

SMB configuration can only be modified when the array is stopped.

Procedure:

  1. Stop the array

  2. Add the SMB Extras entry

  3. Start the array again


Step 3 – Ensure the Script Exists After Boot

Because 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 restart

The warm-up calls ensure the helper script cache is populated before Samba handles client requests.


Note on Dataset Root Permissions

If the dataset root is exported via SMB, ensure it allows write access.

Example:

chmod 775 /mnt/disk5/onedrive/Pictures

Otherwise 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!!!

This_PC_-_File_Explorer_2026-03-10_20-53-46.png

Edited by Bob_C
typo

  • 2 weeks later...

Thank you for posting this, works perfectly

  • Author

Glad it helped you!

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.

Guest
Reply to this topic...

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.