Reporting correct space usage for Samba shared ZFS volumes

ZFS is all the rage now and there are lots of tutorials and how-to’s out there covering most of the topics. There is one issue, for which I could not find any ready solution. When sharing a zfs volume over Samba, Windows would report incorrect total volume size. More precisely, Windows would always show the same size for both total size and free size and both values will be changing as the volume gets used.
This is obviously not what we want. Some digging uncovered that Samba relies internally on the result of the df program, which will report incorrect values for ZFS systems. More digging lead to this page and to the man pages of smb.conf, showing that it is possible to override space usage detection behaviour by creating a custom script and pointing Samba server to it using the following entry in smb.conf:


[global]
dfree command = /usr/local/bin/dfree


The following bash script is where the magic lies (tested on FreeBSD):

#!/bin/sh

CUR_PATH=`pwd`

let USED=`zfs get -o value -Hp used $CUR_PATH` / 1024 > /dev/null
let AVAIL=`zfs get -o value -Hp available $CUR_PATH` / 1024 > /dev/null

let TOTAL = $USED + $AVAIL > /dev/null

echo $TOTAL $AVAIL

And the following is a variation, which works on Linux (courtesy commenter nem):

#!/bin/bash

CUR_PATH=`pwd`

USED=$((`zfs get -o value -Hp used $CUR_PATH` / 1024)) > /dev/null
AVAIL=$((`zfs get -o value -Hp available $CUR_PATH` / 1024)) > /dev/null

TOTAL=$(($USED+$AVAIL)) > /dev/null

echo $TOTAL $AVAIL

Make sure to check the comments section, as several variations of this script are posted there, for example taking account for both ZFS and non-ZFS shares on the same system!

I can’t use zpool list as it reports the total size for the pool, including parity disks, so the total size might be greater than the real usable total size.
zfs list could have been used if there was a way to display the information in bytes and not in human-readable form of varying granularity.
The solution was to use zfs get and then normalise the values reported to Samba to the 1024 byte blocks. (I tried providing the third, optional, parameter of 1 as mentioned in the man pages, but Samba seemed to have trouble parsing really large byte values, so I ended up doing the normalisation in the script).

Also, I can’t rely on the $1 input parameter to the script, as it turned out to always be equal to ‘.’, which is usable for df, but not for zfs. This ‘.’ lead me to check the working directory of the invocation and, bingo, it turned out to be the root path of the requested volume, so I could simply get the value from pwd and pass it to zfs.

13 thoughts on “Reporting correct space usage for Samba shared ZFS volumes

  1. I have long been searching for a solution to this annoying problem, so I was happy to find your blog today. I tried your script, but it doesn’t seem to work. Instead of properly reporting the free and total disk sizes, I get 1MB free of 2MB total. I actually have several hundred GB free on a 3TB raidz, so that isn’t quite right.

    When I run the script from the command line, I get this:

    $ /usr/local/bin/dfree
    cannot open ‘/storage/data/private’: invalid dataset name
    let: arithmetic expression: syntax error: “USED= / 1024”
    cannot open ‘/storage/data/private’: invalid dataset name
    let: arithmetic expression: syntax error: “AVAIL= / 1024”
    0 0

    Any ideas? Thanks!

  2. I’ve figured it out. It seems that the leading slash from the pwd command causes zfs not to recognize a dataset. I’m not sure if that’s the intended behavior or not. But I added the following line to the script after the “CUR_PATH=`pwd`”:

    CUR_PATH=${CUR_PATH#/}

    That seems to have fixed the issue.

  3. Good to hear that the script helped and that you found the solution to your specific issue.

    Which versions do you use? I am on FreeBSD 9.0 with:

    $ zpool get version zstore
    NAME PROPERTY VALUE SOURCE
    zstore version 28 default

    $ zfs get -o value version zstore
    VALUE
    5

    And in my case the following command (with the leading slash) produces the expected correct result:

    $ zfs get -o value -Hp used /zstore/Generic
    1128413938436

  4. Hi!

    Ive been searching for a fix for this and stumbled upon your fix here, however i cant get it to work.

    Im on Ubuntu.

    I added the line to smb.conf and made the script as you did above.

    Output:
    /usr/local/bin/dfree: 6: /usr/local/bin/dfree: let: not found
    /usr/local/bin/dfree: 7: /usr/local/bin/dfree: let: not found
    /usr/local/bin/dfree: 9: /usr/local/bin/dfree: let: not found

    dfree script is a cut n paste job from your guide.
    As you probably notice im fairly new to linux and i dont understand this as well as i should. However it seems that “let” command is not recognized ? If i type let in terminal it is recognized tho.

    I also tried it manually using ” zfs get -o value -Hp used /datastore/here” and that returns the expected values..

    Please advice,

    Thanks!

  5. Hi.

    I am on FreeBSD, which has a more mature port of ZFS. 🙂

    Which shell are you using as default? I set my /bin/sh to point to bash.
    ‘Let ‘ in bash is the operator used to assign a value after performing arithmetic operations. (http://ss64.com/bash/let.html)

    You can try to change the first line in the script:
    #!/bin/sh
    to point to bash.
    Run the following command:
    type -a bash
    and copy the reported path to after #!

  6. Hi, thanks for your reply!

    Took a while before i got a chance to try out what you said (Bash is indeed the default shell on Ubuntu) and while it removed the error regarding the command, it still wont run correctly.

    Below is the output when running the script:

    /usr/local/bin/dfree: line 6: let: /: syntax error: operand expected (error token is “/”)
    /usr/local/bin/dfree: line 7: let: /: syntax error: operand expected (error token is “/”)
    /usr/local/bin/dfree: line 9: let: =: syntax error: operand expected (error token is “=”)
    2331817695840

    Apparently its working, kinda. But the calculation part doesnt seem to work.. Any further tips?

  7. Here is the updated code for use with Ubuntu and bash:

    #!/bin/bash
         
    CUR_PATH=`pwd`
    
    USED=$((`zfs get -o value -Hp used $CUR_PATH` / 1024)) > /dev/null
    AVAIL=$((`zfs get -o value -Hp available $CUR_PATH` / 1024)) > /dev/null
         
    TOTAL=$(($USED+$AVAIL)) > /dev/null
         
    echo $TOTAL $AVAIL
    
  8. Nice, I am going to test this on Ubuntu! However, how do I make sure this script is only used for ZFS shares? (Since I also share non-ZFS folders). Can I put “dfree command” also under a share instead of [global]?

  9. I did some experimenting, and following code should work on Ubuntu/Linux. It tries to detect if given path is ZFS and then acts accordingly

    #!/bin/bash
     
      CUR_PATH=`pwd`
      ZFS_CHECK_OUTPUT=$(zfs get type $CUR_PATH 2>&1 > /dev/null) > /dev/null
      if [[ $ZFS_CHECK_OUTPUT == *not\ a\ ZFS* ]]
      then
        IS_ZFS=false
      else
        IS_ZFS=true
      fi
      if [[ $IS_ZFS = false ]]
      then
        df $CUR_PATH | tail -1 | awk '{print $2" "$4}'
      else
        USED=$((`zfs get -o value -Hp used $CUR_PATH` / 1024)) > /dev/null
        AVAIL=$((`zfs get -o value -Hp available $CUR_PATH` / 1024)) > /dev/null
      
        TOTAL=$(($USED+$AVAIL)) > /dev/null
     
        echo $TOTAL $AVAIL
      fi
    

    See this gist for full code: https://gist.github.com/umito/9198097

  10. I also created a gist that determines if the FS is zfs or not and calculates accordingly. This was tested on ubuntu 14.04, I needed this because some of my mounts are on zfs and some are not, and they have similar beginnings to the name ( /mnt/storage and /mnt/storage2 for example ) would return the disk free for the wrong volume at times, so here is the gist:

    #!/bin/bash
     
     
    if [[ `findmnt -n -o FSTYPE -T "$PWD" | grep -c zfs` > 0 ]]
     then
    # echo "DEBUG: this is zfs"
     USED=$((`zfs get -o value -Hp used $PWD` / 1024)) > /dev/null
     AVAIL=$((`zfs get -o value -Hp available $PWD` / 1024)) > /dev/null
     TOTAL=$(($USED+$AVAIL)) > /dev/null
     echo $TOTAL $AVAIL
    else
    # echo "DEBUG: this is not zfs "
     USED=$((`df -P $PWD | cut -d " " -f 6`)) > /dev/null
     AVAIL=$((`df -P $PWD | cut -d " " -f 8`)) > /dev/null
     TOTAL=$(($USED+$AVAIL)) > /dev/null
     echo $TOTAL $AVAIL
    fi
    

    https://gist.github.com/sling00/cd69ffbef4b23ec48d10

Comments are closed.