6

EDITED: as @abhink pointed out, was not invoking Size().

I tried two different go methods, and then compared to df. Of course, all 3 give different results:

package main

import (
    "os"
    "syscall"
    "fmt"
)

func main() {
    disk := "/dev/sda1"
    statout, err := os.Stat(disk)
    if err != nil {
        fmt.Errorf("Error %x", err)
        os.Exit(1)
    }
    println("os.Stat Size   : ", statout.Size())

    var stat syscall.Statfs_t
    syscall.Statfs(disk, &stat)
    println("syscall.Statfs_t Type: ",  stat.Type)
    println("syscall.Statfs_t Bsize: ",  stat.Bsize)
    println("syscall.Statfs_t Blocks: ",  stat.Blocks)
}

Running the programs:

$ main
os.Stat Size   :  0
syscall.Statfs_t Type: 16914836
syscall.Statfs_t Bsize: 4096
syscall.Statfs_t Blocks: 2560

And df:

$ df /dev/sda1
Filesystem           1K-blocks      Used Available Use% Mounted on
/dev/sda1             65792556  43694068  18726712  70% /var

Net:

  • os.Stat() gives 0 which it is not, but might be an OS issue.
  • syscall.Statfs() gives 2560 blocks * 4096 block size = 10,485,760. More realistic, but still incorrect
  • df gives 65792556 1K-blocks * 1024 bytes / K = 67,371,577,344

How do I reliably get the size of a block device without mounting it?

Essentially, I am looking for the equivalent of the ioctl call on the device.

ioctl(fd,BLKGETSIZE64,&size)
deitch
  • 14,019
  • 14
  • 68
  • 96
  • 3
    `os.Stat` returns an `os.FileInfo` type which is an interface type. `Size` is a method on the returned type, not a field. `0xc420031f58` is the address of the function `Size`. https://golang.org/pkg/os/#FileInfo – abhink Oct 04 '17 at 07:05
  • 2
    Sidenote: `fmt.Errorf` does not what you think it does. Much like printing an os.FileInfos Size field. How about looking up the documentation for all functions/methods used? – Volker Oct 04 '17 at 07:09
  • Oh, oops. I was supposed to do `Size()`. Nice catch. – deitch Oct 04 '17 at 09:17
  • Well, `Size()` returns `0`, so that certainly isn't it. I believe that is more of an OS thing, since `# stat /dev/sda1` gives `Size: 0` – deitch Oct 04 '17 at 09:18
  • "Sidenote: fmt.Errorf does not..." I don't particularly care, @volker. Copy-paste and edit the minimum necessary to show what works/doesn't. I care about getting it working, not purity of the example on SO. – deitch Oct 04 '17 at 09:25
  • @deitch have you found an answer to your question? I'm looking to exactly the same thing – Fabiano Tarlao Dec 08 '18 at 17:52
  • Not that I recall @FabianoTarlao. Sorry. This was a year ago, trying to recall which project it was that was using it... – deitch Dec 08 '18 at 23:03
  • Ok, I think I'll change from Golang to C++, but I'm just curious about the solution you found :-( – Fabiano Tarlao Dec 11 '18 at 09:40

2 Answers2

7

When calling syscall.Statfs(), you have to pass the path where the device is mounted, e.g. /, and not the device file (not /dev/sda1). In your case this is /var.

You get the result in a value of type syscall.Statfs_t. Interpretation:

var stat syscall.Statfs_t
if err := syscall.Statfs("/", &stat); err != nil {
    panic(err)
}

size := stat.Blocks * uint64(stat.Bsize)
free := stat.Bfree * uint64(stat.Bsize)
avail := stat.Bavail * uint64(stat.Bsize)
fmt.Println("Size:", size)
fmt.Println("Free:", free)
fmt.Println("Available:", avail)
fmt.Println("Used:", size-free)

See this possible duplicate: Get amount of free disk space using Go

icza
  • 389,944
  • 63
  • 907
  • 827
  • 2
    Ah, very good point, thank you @icza. So, how would I actually get it _without_ mounting? I can do it in C with the right `ioctl` call (edited question), is there a go-native method? – deitch Oct 04 '17 at 09:23
7

The OP asked how to get the size of a block device. To get the size of a block device (or any file), you can File.Seek to the end of the file using io.SeekEnd and read the position returned. Credit to others for python and C.

Running the example getsize.go below shows:

$ sudo go run getsize.go /dev/sda
/dev/sda is 256060514304 bytes.

lsblk --bytes /dev/device will give you the same information. That is how much data the block device can store.

The Statfs_t path, and df /path/to/mounted/filesystem will give you information about how much data you can store in the filesystem mounted at provided path. Filesystems have overhead, probably in the 2-5% range depending on details of the filesystem, and also keep track of how much space is Free or Used.

There is no api that I am aware of that can provide information about unmounted filesystems on a block device. dumpe2fs can give you that information for the ext{2,3,4} filesystems. There likely exist tools for other filesystems. Such tools are filesystem specific. When you mount the filesystem, then the linux kernel's filesystem driver exposes that information that is returned by df.

Code:

// getsize.go: get the size of a block device or file
package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    var path string
    if len(os.Args) < 2 {
        fmt.Println("Give path to file/disk")
        os.Exit(1)
    }
    path = os.Args[1]
    file, err := os.Open(path)
    if err != nil {
        fmt.Printf("error opening %s: %s\n", path, err)
        os.Exit(1)
    }
    pos, err := file.Seek(0, io.SeekEnd)
    if err != nil {
        fmt.Printf("error seeking to end of %s: %s\n", path, err)
        os.Exit(1)
    }
    fmt.Printf("%s is %d bytes.\n", path, pos)
}
smoser
  • 1,321
  • 12
  • 6
  • This is very nice. I hadn't thought of it. Is it accurate? I get slightly off numbers. https://gist.github.com/deitch/20c611e60f240eea7591bd973a0d5915 – deitch Sep 07 '19 at 19:41
  • The OP asked how to get the size of a block device. The io.Seek answer is the correct answer. The block device in your gist can store 53686025728 bytes of data. Which is 1065472 bytes shy of 50GiB ([GiB vs GB](https://en.wikipedia.org/wiki/Gibibyte)). – smoser Sep 09 '19 at 13:09
  • I updated the answer to try to explain filesystem vs block device. – smoser Sep 09 '19 at 13:50
  • your example gets me same value for system partition and mounted NAS: `/mnt/ix4300d is 9223372036854775807 bytes. / is 9223372036854775807 bytes.` Value of NAS is wrong for me. – SL5net Nov 19 '19 at 21:51