In Development
Devlog Archives 2008-2010
Devlog Archives 2002-2007
Tutorials

Devlog

Calculating Free Space on macOS

12th May 2017 | Programming

As a set of interesting programming exercises, I've written up a number of examples to calculate the amount of free space on a given disk running on macOS. The first two examples are command line solutions, while the other examples take more programatic approaches.

Update (26 November 2017)

Due to some new approaches I came across from this StackOverflow post, two additional methods have been added by using diskutil and the private macOS framework DiskManagement.

df

To easily display the amount of available disk space and how much is free, use the built in utility df which returns the statistics about the amount of free disk space on a specified filesystem. This is a great little utility to use from the terminal or within a script. To get the details on the root drive, run the command df -kP / which will return data formatted like the following:


	Filesystem 1024-blocks      Used  Available Capacity  Mounted on
	/dev/disk2  2018614912 698624560 1319734352    35%    /

If you want just the amount of free disk space (calculated in kilobytes), use one of these two options which calls df and then parses out the pertinent data.


	df -kP / | tail -1 | awk '{print $4}'
	df -kP / | awk '/[0-9]%/{print $(NF-2)}'

diskutil

diskutil is a veritable utility for disk related information and maintenance tasks, which serves as the command line version of the Disk Utility app. To retrieve the amount of free space (sometimes referred to as "available space"), run the command. diskutil info / | grep "Available Space". Due to changes in the output from diskutil over the years, on older version of Mac OS X, use the command diskutil info / | grep "Free Space".


$ diskutil info / | grep "Available Space"
   Volume Available Space:   94.0 GB (93988098048 Bytes) (exactly 183570504 512-Byte-Units) (37.6%)

DiskManagement Framework

Ivan Genchev discovered an interesting approach to determine the available free space by using the private macOS framework DiskManagement, which is used by diskutil (and I assume, Disk Utility, as well). Since this framework is not public, you'll need to generate a header file by using Steve Nygard's excellent utility class-dump, which allows you to examine the Objective-C runtime information stored in a Mach-O file. This is the same approach I used in another project to write a Finder plug-in, which required generating a Finder.h header file to be able to access the private Finder methods.

Download class-dump and copy the executable onto your system, such as in your /usr/local/bin directory.

Next, generate the header file with the command:

 
$ class-dump /System/Library/PrivateFrameworks/DiskManagement.framework/Versions/Current/DiskManagement > DiskManagement.h

This will create the necessary header file to link to the program. This is a fairly large file (the one for macOS High Sierra is 840 lines in length), and contains a number of interesting private methods. The method we are interested in is (id)volumeFreeSpaceForDisk:(struct __DADisk *)arg1 error:(int *)arg2.

Once we have the DiskManagement.h file, we can integrate it into our program. The following code is modified from Ivan Genchev's code in the StackOverflow post.


/*	dmfreespace.m
 *
 *	Description: Get the available free space on the root drive using the method 
 *	volumeFreeSpaceForDisk from the private framework DiskManagement
 *	
 *	Original reference: https://stackoverflow.com/a/20679389/955122
 *	To compile: clang -g dmfreespace.m -F/System/Library/PrivateFrameworks/ -framework Foundation 
 *	-framework DiskArbitration -framework DiskManagement -o dmfreespace
 */

#import <Foundation/Foundation.h>
#import "DiskManagement.h"
#import <DiskArbitration/DADisk.h>
// For statfs
#include <sys/param.h>
#include <sys/mount.h>


int main(int argc, char *argv[])
{
    int                 err = 0;
    const char *        bsdName;
    DASessionRef        session;
    DADiskRef           disk;
    CFDictionaryRef     descDict;
    NSString *.         rootPath = @"/";
    
    session  = NULL;
    disk     = NULL;
    descDict = NULL;
    
    // Get the BSD name for the given path
    struct statfs devStats;
    statfs([rootPath UTF8String], &devStats);
    bsdName = devStats.f_mntfromname;
    NSLog(@"bsdName: %s", bsdName);
    
    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;
}

Run the app and you should get output like the following:


 $ ./dmfreespace
2017-11-26 12:35:44.945 dmfreespace[17103:2781845] bsdName: /dev/disk1s1
2017-11-26 12:35:44.969 dmfreespace[17103:2781845] blockSizeForDisk: 4096
2017-11-26 12:35:44.970 dmfreespace[17103:2781845] totalSizeForDisk: 250035572736
2017-11-26 12:35:44.971 dmfreespace[17103:2781845] volumeTotalSizeForDisk: 250035572736
2017-11-26 12:35:44.971 dmfreespace[17103:2781845] volumeFreeSpaceForDisk: 93637120000

A GitHub Gist with dmfreespace.m and DiskManagement.h is available here.

Cocoa

The following program freesize.m takes five different approaches to calculate the free space on a drive. The first two examples use Cocoa's Foundation framework. The first example uses NSFileManager and asks for the NSFileSystemFreeSize attribute. Simple and straight forward.

The second example uses NSURL with some more modern approaches. This example gets a URL's resources and requests the value for the key NSURLVolumeAvailableCapacityKey. This code also makes use of NSByteCountFormatter, which was introduced with OS X 10.8 Mountain Lion. This method is a little more useful than the original way, since this can display the total available free space, which tends to be slightly smaller than all of the free space, not all which is available to the user, since some space might be reserved for the system.

getattrlist

getattrlist is a method I had read about in the books Advanced Mac OS X Programming and Mac OS X Internals before, but the available examples for this tool are quite sparse and seem to be rarely used. I spent several days trying to get this example to work, but the data never seemed to line up properly with the other results I was obtaining via other methods. getattrlist behaves in a curious and obfuscated way, by returning some blob of data, but one needs to carefully construct a custom structure in which to line up against the data blob. The data I was getting often would come out like the following:


	Volume size: 16229794380578291739
	Free space:  15103894473735667713
	Avail size:  1

This was obviously incorrect, which makes me suspect that the vol_attr structure was not lining up properly with the returned data, and I might even need to add some sort of padding inside the structure. There are far more easier methods to obtain the free space size than by using getattrlist.

statfs + statvfs

The final two examples are similar UNIX system calls to get file system information and statistics. These methods statfs and statvfs are nearly identical in how a structure is passed into a system call and then the necessary information is parsed out. The one major difference I found between these two system calls was that the f_bsize returned different values. The statfs structure returns a value of 4096 for f_bsize, whereas statvfs returns a value of 1048576, a value 256 times larger than the other one. To properly calculate the available free space using the statvfs call, I needed to use f_frsize instead. Multiply f_frsize by f_bavail and that will result in the total number of available bytes. To calculate that number in kilobytes, divide that product by 1024.

freesize.m Gist

Purgeable Space

In more current versions of macOS, if you pull up the Get Info window on a selected disk, the amount of available space might be different from the values which was calculated in the examples above. However, there is a second value which details how much space is purgeable. If you subtract the purgeable space from the available space, you should get a value very similar to the one calculated above. This purgeable space seems to come from snapshots taken by Time Machine or APFS, which are occasionally cleaned up (or purged).

Get Info Window

References