4

Is there a way to programmatically get the same information that diskutil info / | grep "Free Space" gives you? (For obvious reasons I'd rather have a better way to do this than just parsing the result of that command.)

Currently I'm using statfs; however, it was brought to my attention that the space this reports is not always accurate, because OS X also places temporary files such as Time Machine snapshots on the drive. These files automatically get deleted if space is running out, and the OS does not report the usage of these files. In other words, statfs often gives a lower number of free space than diskutil info or looking at the disk information in Finder.

houbysoft
  • 32,532
  • 24
  • 103
  • 156
  • That is interesting about `statfs()` being inaccurate due Time Machine temp files. I wonder how it does that? Are you sure that's the case? – trojanfoe Dec 19 '13 at 10:22
  • @trojanfoe: I believe `statfs` returns the "true" amount of free disk space there is. However, Finder "cheats" by showing a bit more free space, accounting for the fact some files would get deleted automatically. I have received bug reports about this, and it turns out this is the issue (narrowed it down to `df -h` reporting less free space than `diskutil info / | grep "Free Space"`) – houbysoft Dec 19 '13 at 10:31
  • diskutil is showing the same free space as df -H. df -h is using base 2 for sizes and df -H is using 10. See `man 1 df`. – Ivan Genchev Dec 19 '13 at 10:44
  • `Wherever a size is supplied as an output, it is always presented as a base-ten approximation with one decimal digit and a base-ten SI multiplier`. That's from `man 8 diskutil`, that's why you see different sizes. – Ivan Genchev Dec 19 '13 at 10:49
  • @IvanGenchev: that is not the case from my tests. Users are reporting that this : `(100.0 * (double)(stats.f_blocks - stats.f_bfree) / (double)stats.f_blocks)` gives a different free percentage than the Finder. – houbysoft Dec 19 '13 at 10:53
  • For more modern versions of macOS, change "Free Space" to "Available Space", since the output for diskutil has changed: `diskutil info / | grep "Available Space"` – edenwaith Nov 25 '17 at 06:30

1 Answers1

9

You can use popen(3):

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    FILE *f;
    char info[256];

    f = popen("/usr/sbin/diskutil info /", "r");
    if (f == NULL) {
        perror("Failed to run diskutil");
        exit(0);
    }

    while (fgets(info, sizeof(info), f) != NULL) {
        printf("%s", info);
    }

    pclose(f);

    return 0;
}

EDIT

Sorry, I didn't read the question carefully. You can also use the Disk Arbitration Framework. There's also some sample code that might be helpful (FSMegaInfo).

UPDATE

I took a look at the output from otool -L $(which diskutil) and it seems that it's using a private framework called DiskManagement.framework. After looking at the output from class-dump I saw there's a volumeFreeSpaceForDisk:error: method. So the sizes I got from diskutil -info / and FSMegaInfo FSGetVolumeInfo / and my tool were:

  • diskutil: 427031642112 Bytes

  • my tool: volumeFreeSpaceForDisk: 427031642112

  • FSMegaInfo: freeBytes = 427031642112 (397 GB)

I also observed that the sizes differ (with a few KB) every time I ran one of the tools and also that diskutil is dividing by 1000 and FSMegaInfo is dividing by 1024, so the size in GB will be always different (same reason as with df -h and df -H and diskutil - base 10 and base 2).

Here's my sample tool:

#import <Foundation/Foundation.h>
#import "DiskManagement.h"
#import <DiskArbitration/DADisk.h>

int main(int argc, char *argv[])
{
    int                 err;
    const char *        bsdName = "disk0s2";
    DASessionRef        session;
    DADiskRef           disk;
    CFDictionaryRef     descDict;
    session  = NULL;
    disk     = NULL;
    descDict = NULL;
    if (err == 0) {session = DASessionCreate(NULL); if (session == NULL) {err = EINVAL;}}
    if (err == 0) {disk = DADiskCreateFromBSDName(NULL, session, bsdName); if (disk == NULL) {err = EINVAL;}}
    if (err == 0) {descDict = DADiskCopyDescription(disk); if (descDict == NULL) {err = EINVAL;}}

    DMManager *dmMan = [DMManager sharedManager];
    NSLog(@"blockSizeForDisk: %@", [dmMan blockSizeForDisk:disk error:nil]);
    NSLog(@"totalSizeForDisk: %@", [dmMan totalSizeForDisk:disk error:nil]);
    NSLog(@"volumeTotalSizeForDisk: %@", [dmMan volumeTotalSizeForDisk:disk error:nil]);
    NSLog(@"volumeFreeSpaceForDisk: %@", [dmMan volumeFreeSpaceForDisk:disk error:nil]);

    return 0;
}

You can obtain the DiskManagement.h by running class-dump /System/Library/PrivateFrameworks/DiskManagement.framework/Versions/Current/DiskManagement > DiskManagement.h and you can link to that framework by including the private frameworks path using -F/System/Library/PrivateFrameworks/ and add -framework.

Compile:

clang -g tool.m -F/System/Library/PrivateFrameworks/ -framework Foundation -framework DiskArbitration -framework DiskManagement -o tool

UPDATE2: You can also take a look here and here. If the FSMegaInfo sample is not working for you, then you can just stat the /Volumes/.MobileBackups and subtract it's size from what you get from statfs("/", &stats).

Community
  • 1
  • 1
Ivan Genchev
  • 2,746
  • 14
  • 25
  • I found that on another question here, but it doesn't seem that framework actually provides free space info. If it does could you point me to a specific function? – houbysoft Dec 19 '13 at 11:00
  • Look at the sample, that I added in my answer and the `FileManager.c` in particular. – Ivan Genchev Dec 19 '13 at 11:08
  • 1
    Thanks. I'll try sending that to the user who reported the bug and see if it's any better than what `statfs` does – houbysoft Dec 19 '13 at 11:26
  • Awesome, thanks. I think I'll go with just subtracting the size of the `.MobileBackups` as that seems easiest, in case the `FSMegaInfo` sample still reports the wrong size for the user who reported this. – houbysoft Dec 19 '13 at 15:02
  • See this - http://www.opensource.apple.com/source/kext_tools/kext_tools-268.13.1/update_boot.c, this - https://www.opensource.apple.com/source/kext_tools/kext_tools-252.2/update_boot.c and this - https://github.com/aosm/kext_tools/blob/master/update_boot.c. It seems that the `.MobileBackups` is in `/` and not in `/Volumes/` like some of the other articles suggest. – Ivan Genchev Dec 20 '13 at 10:29
  • I just enabled the feature on my own machine (though I couldn't get it to actually back up anything as my drive is too full), and there are two places it is located it seems. There's `/.MobileBackups` and `/Volumes/MobileBackups`. In any case, still waiting for the customer to reply to see if this fixed the problem, I'll accept the answer once he does – houbysoft Dec 21 '13 at 11:42
  • THIS SAVED MY LIFE!!! I cannot explain how much this answer helped me. Just two small things: I had to change `int err;` to `int err = 0;` for the sample code to work, and neither `volumeTotalSizeForDisk:` nor `volumeFreeSpaceForDisk:` seem to work for me. – Coder-256 Nov 29 '16 at 22:50
  • If the results for `volumeTotalSizeForDisk` or `volumeFreeSpaceForDisk` are coming back as `null`, then the `bsdName` is not a valid disk on your system (it might need to be changed to something like `disk1s1`). I have created a GitHub Gist which modifies Ivan's code by determining the BSD name of the root disk. https://gist.github.com/edenwaith/9b33103a0aa4aab63b7dee9f73fe49a3 – edenwaith Nov 26 '17 at 17:53