45

Basically I want the output of df -h, which includes both the free space and the total size of the volume. The solution needs to work on Windows, Linux, and Mac and be written in Go.

I have looked through the os and syscall Go documentation and haven't found anything. On Windows, even command line utils are either awkward (dir C:\) or need elevated privileges (fsutil volume diskfree C:\). Surely there is a way to do this that I haven't found yet...

UPDATE:
Per nemo's answer and invitation, I have provided a cross-platform Go package that does this.

Rick Smith
  • 9,031
  • 15
  • 81
  • 85
  • All I've got is that you could drop to C with [cgo](http://blog.golang.org/c-go-cgo): write freespace_windows.go and freespace_{linux,bsd}.go, and use [GetDiskFreeSpace](http://msdn.microsoft.com/en-us/library/windows/desktop/aa364935(v=vs.85).aspx) and [statvfs](http://stackoverflow.com/questions/3992171/how-do-i-programmatically-get-the-free-disk-space-for-a-directory-in-linux) to get free space. – twotwotwo Nov 21 '13 at 01:14

3 Answers3

73

On POSIX systems you can use sys.unix.Statfs.
Example of printing free space in bytes of current working directory:

import "golang.org/x/sys/unix"
import "os"

var stat unix.Statfs_t

wd, err := os.Getwd()

unix.Statfs(wd, &stat)

// Available blocks * size per block = available space in bytes
fmt.Println(stat.Bavail * uint64(stat.Bsize))

For Windows you need to go the syscall route as well. Example (source, updated to match new sys/windows package):

import "golang.org/x/sys/windows"

var freeBytesAvailable uint64
var totalNumberOfBytes uint64
var totalNumberOfFreeBytes uint64

err := windows.GetDiskFreeSpaceEx(windows.StringToUTF16Ptr("C:"),
    &freeBytesAvailable, &totalNumberOfBytes, &totalNumberOfFreeBytes)

Feel free to write a package that provides the functionality cross-platform. On how to implement something cross-platform, see the build tool help page.

Ananth
  • 848
  • 11
  • 26
nemo
  • 55,207
  • 13
  • 135
  • 135
  • 2
    Thanks for the detailed post. I have updated my question to include the link to my package that does this. – Rick Smith Nov 21 '13 at 23:27
  • Do you know the difference between `stat.Bavail` and `stat.Bfree`? Documented here: https://golang.org/pkg/syscall/#Statfs_t – bantl23 Apr 28 '17 at 01:58
  • 1
    @bantl23 `Bavail` is the amount of blocks available to unprivileged users. `Bfree` is simply the total number of free blocks. – nemo Apr 29 '17 at 20:25
  • 5
    "On POSIX systems you can use syscall.Statfs" Unfortunatly this isn't a POSIX compliant syscall. The POSIX compliant call is "statvfs". Statfs gives different information on different *NIX OS'es. Why the Go makers choose to implement the non-POSIX compliant version is beyond me. – Jasper Siepkes Aug 03 '17 at 10:51
  • gives me error: `'wd' unexpected` in line `wd, err := os.Getwd()` – SL5net Nov 19 '19 at 04:48
  • 2
    From the package docs: "Deprecated: this package is locked down. Callers should use the corresponding package in the golang.org/x/sys repository instead. https://golang.org/pkg/syscall/#Statfs – Brett Holman Jan 24 '21 at 23:27
  • 1
    @BrettHolman quite right, updated the answer – nemo Jan 26 '21 at 13:24
  • I think there is a typo in the declaration of freeBytes: int64 should be uint64, no? – toussa Sep 08 '22 at 18:21
9

Minio has a package (GoDoc) to show disk usage that is cross platform, and seems to be well maintained:

import (
        "github.com/minio/minio/pkg/disk"
        humanize "github.com/dustin/go-humanize"
)

func printUsage(path string) error {
        di, err := disk.GetInfo(path)
        if err != nil {
            return err
        }
        percentage := (float64(di.Total-di.Free)/float64(di.Total))*100
        fmt.Printf("%s of %s disk space used (%0.2f%%)\n", 
            humanize.Bytes(di.Total-di.Free), 
            humanize.Bytes(di.Total), 
            percentage,
        )
}
Iain McLaren
  • 91
  • 1
  • 1
  • This code is actually provides data from output of `du -h` e.g. directory usage instead of requested `df -h` command providing a disk usage. Anyway It may be a good example of using minio package. – Roman Shishkin Sep 30 '20 at 13:30
  • go: module github.com/minio/minio@upgrade found (v0.0.0-20230424202818-8fd07bcd51cf), but does not contain package github.com/minio/minio/pkg/disk – Milad Jahandideh Apr 24 '23 at 21:03
8

Here is my version of the df -h command based on github.com/shirou/gopsutil library

package main

import (
    "fmt"

    human "github.com/dustin/go-humanize"
    "github.com/shirou/gopsutil/disk"
)

func main() {
    formatter := "%-14s %7s %7s %7s %4s %s\n"
    fmt.Printf(formatter, "Filesystem", "Size", "Used", "Avail", "Use%", "Mounted on")

    parts, _ := disk.Partitions(true)
    for _, p := range parts {
        device := p.Mountpoint
        s, _ := disk.Usage(device)

        if s.Total == 0 {
            continue
        }

        percent := fmt.Sprintf("%2.f%%", s.UsedPercent)

        fmt.Printf(formatter,
            s.Fstype,
            human.Bytes(s.Total),
            human.Bytes(s.Used),
            human.Bytes(s.Free),
            percent,
            p.Mountpoint,
        )
    }
}

Roman Shishkin
  • 2,097
  • 20
  • 21