How to compute 'purgeable' disk on APFS volume, on macOS? - macos

My application needs to know the free space on a APFS volume.
It seems the operating system can 'reserve' purgeable disk space, starting from macOS High Sierra and APFS.
When using NSFileManager function, it only returns 'real' free space:
NSDictionary* pathAttrs = [[NSFileManager defaultManager] fileSystemAttributesAtPath: path];
NSNumber* pathSize = pathAttrs[ NSFileSystemSize];
NSNumber* pathFreeSize = pathAttrs[ NSFileSystemFreeSize];
My question: how to compute or estimate this 'purgeable' free space?

Related

Get Ram Information OSX

I need to get RAM information like in system_profiler
but I can't find any API to get this on OS X system (10.9 and higher are supported only).
Previously I got the information from the system_profiler
NSTask *profilerTask = [[NSTask alloc] init];
[profilerTask setLaunchPath:#"/usr/sbin/system_profiler"];
And parsed it's output what what should I do if system profiler output language is not english ?
//Also it is slow as hell :(
Is there a good way to achieve the RAM hardware info
e.g. Number of slots , Ecc ,Type ,Bank, Speed ?
If you want to specifically get memory information try using:
NSTask *profilerTask = [[NSTask alloc] init];
[profilerTask setLaunchPath:#"/usr/sbin/system_profiler"];
[profilerTask setArguments:[NSArray arrayWithObjects:#"SPMemoryDataType",nil]];
By specifying the data type it should be quicker. To get a list of the available data types use:
$ system_profiler -listDataTypes

Lost data while moving file out of iCloud that was not yet completely downloaded (no error reported)

I have a Mac app with iCloud integration. It's not based on NSDocument and I handle moving files in and out of iCloud myself via [NSFileManager setUbiquitous:…]. Here's what I ran into:
I added a large (15 MB) document to my app while connected to iCloud
Waited for document to be completely uploaded to iCloud
Now I signed out of my iCloud account on my Mac → the file was removed from the Mac
I opened my app, signed back in to iCloud, the file appeared on disk
Quickly, through my app I moved all documents out of iCloud to the local disk (internally using [NSFileManager setUbiquitous:NO…]
The large document was not copied to the local disk (I suspect because it was not yet downloaded 100% from iCloud), but it also disappeared from iCloud. No way to recover the data. There was no error reported by NSFileManager.
Here is the relevant code:
NSArray *files = [fileManager contentsOfDirectoryAtURL:iCloudDataFolderURL
includingPropertiesForKeys:nil options:0 error:&error];
for (NSURL *fileURL in files) {
// figure out URLs […]
if (![fileManager setUbiquitous:NO
itemAtURL:iCloudFileURL
destinationURL:localDocumentURL
error:&error]) {
hadError = YES;
NSLog(#"Error moving file from iCloud: %# to local storage: %# Error: %#",
iCloudFileURL, localDocumentURL, error);
}
}
I would have expected the call to [NSFileManager setUbiquitous:NO…] to either block or fail if the file is not completely on the local disk. Instead I end up with a file wrapper that shows a file size of 15 MB in Finder, but is actually empty.
What is a safe way to move documents out of iCloud to the local disk?
You can just use NSFileCoordinator and NSFileManager methods to move the files in and out. I don't know if it is safer, but it should give you more control over error conditions and what should happen when something goes wrong.
For an example, take a look at the iCloud backend of the Ensembles framework here (Disclosure: it is my project). Search for uploadLocalFile:... and downloadFromPath:.... They are particularly advanced methods, with timeouts, but the idea is the same.
First, create a file coordinator.
NSFileCoordinator *coordinator =
[[NSFileCoordinator alloc] initWithFilePresenter:nil];
Then copy or move the file.
[coordinator coordinateReadingItemAtURL:fromURL options:0
writingItemAtURL:toURL
options:NSFileCoordinatorWritingForReplacing
error:&fileCoordinatorError
byAccessor:^(NSURL *newReadingURL, NSURL *newWritingURL) {
[fileManager removeItemAtPath:newWritingURL.path error:NULL];
[fileManager copyItemAtPath:newReadingURL.path
toPath:newWritingURL.path error:&fileManagerError];
}];
If you don't want to program this all yourself, I split off a class that may help (iCloudAccess).

How to iterate all mounted file systems on OSX

I am interested in iterating all mounted file systems on OSX (currently running 10.9 Mavericks). I am looking for something similar to getmntent() or the output of the mount shell command (although I want to do it from objective C, so parsing the output of a shell command is obviously not optimal).
I have been looking a bit at the disk arbitration framework, and it appears that I could be notified about mount and unmount events using this framework. I may be missing something there, but it isn't clear to me if there is a way to iterate existing mounted file systems using Disk Arbitration.
I have explored using getfsent() which seemed like it would provide a solution, but after testing I discovered that I am not getting more than one entry from iterating getfsent(). See the following code:
struct fstab* fsentry;
setfsent();
fsentry = getfsent();
while(fsentry)
{
//do something with fsentry
fsentry = getfsent();
}
endfsent();
The only entry I am getting here is for the / file system. The second time I call getfsent() it returns NULL, as if there are no more entries. The mount command shows me several others including a mounted cifs/smb file system:
/dev/disk0s2 on / (hfs, local, journaled)
devfs on /dev (devfs, local, nobrowse)
map -hosts on /net (autofs, nosuid, automounted, nobrowse)
map auto_home on /home (autofs, automounted, nobrowse)
//user#<ip address>/public on /Volumes/public (smbfs, nodev, nosuid, mounted by user)
So it seems like getfsent() starts doing what I expect, but for some reason stops?
My question in summary is: What is the best way to iterate file systems on OSX?
If anyone has an answer to why I am only getting one result from getfsent() I would also be interested in that.
There are a couple of different ways to enumerate mounted volumes on OS X, each a using different set of APIs. At the highest (and easiest) level, you can use NSFileManager's mountedVolumeURLsIncludingResourceValuesForKeys:options:. Here's an abbreviated example:
NSArray *urls = [[NSFileManager defaultManager] mountedVolumeURLsIncludingResourceValuesForKeys:#[NSURLVolumeNameKey] options:0];
for (NSURL *url in urls) {
NSLog(#"Volume mounted at: %#", [url path]);
}
The next option takes us back to C territory - and you were so close with your original approach. On OS X (and BSD), there isn't getmntent(); instead, there is getmntinfo(), which is strikingly similar. To list mounted volumes via getmntinfo(), you can do the following:
struct statfs* mounts;
int num_mounts = getmntinfo(&mounts, MNT_WAIT);
if (num_mounts < 0) {
// do something with the error
}
for (int i = 0; i < num_mounts; i++) {
NSLog(#"Disk type '%s' mounted at: %s", mounts[i].f_fstypename, mounts[i].f_mntonname);
}
I've used both of these APIs side-by-side since the release of 10.6. getmntinfo() is always more complete than [NSFileManager mountedVolumeURLsIncludingResourceValuesForKeys:options:]: the latter will filter the /dev and other filesystems that you may or may not want to know about. It is generally reliable, however, for the disks you plug into your system.
The purpose behind the DiskArbitration framework is different, as you noticed. DiskArbitration is about monitoring and managing disk assets. With DA, you can get called whenever a new disk is mounted or unmounted. You can also manage those disks by renaming, mounting, unmounting, or ejecting them, as well as inserting yourself in the mount/unmount process - and potentially suspending requests to do the same. But, as you pointed out, it does not provide an interface for listing existing disks. Once you do get your list of mounted volumes, DA is an excellent next stop (depending, of course, on your reason for getting that list!).

How to programmatically determine if a disk is encrypted on OS X?

Given a volume, how do I determine whether it is encrypted or not? I've found stuff like DADiskCopyDescription() and NSURL's getResourceValue:forKey:error: which give a wealth of information, but not whether the volume is encrypted.
Even if there isn't a public API for this then scraping output from a command line tool which ships with the OS would be acceptable. The closest I found was 'diskutil info /dev/disk0', but again no encryption information. Annoyingly the GUI Disk Utility app does provide this information when you click on the blue info button.
You can (ab)use IOKit for this. Note that the CoreStorage Encrypted property is not officially defined anywhere, so this is decidedly not public API. Also, you'll need to inspect the whole disk object that Core Storage offers to the OS (e.g. disk1), not the partition that the Core Storage LV lives on (e.g. disk0s2).
const char *bsdDisk = "disk1";
DASessionRef session = DASessionCreate(kCFAllocatorDefault);
DADiskRef disk = DADiskCreateFromBSDName(kCFAllocatorDefault, session, "disk1");
io_service_t diskService = DADiskCopyIOMedia(disk);
CFBooleanRef isEncrypted = IORegistryEntryCreateCFProperty(diskService,
CFSTR("CoreStorage Encrypted"),
kCFAllocatorDefault,
0);
fprintf(stdout,
"%s %s encrypted\n",
bsdDisk,
(CFBooleanGetValue(isEncrypted)) ? "is" : "is not");
CFRelease(isEncrypted);
IOObjectRelease(diskService);
CFRelease(disk);
CFRelease(session);
It looks like this information is available using system_profiler -detailLevel basic.

Leaking memory with Cocoa garbage collection

I've been beating my head against a wall trying to figure out how I had a memory leak in a garbage collected Cocoa app. (The memory usage in Activity Monitor would just grow and grow, and running the app using the GC Monitor instruments would also show an ever-growing graph.)
I eventually narrowed it down to a single pattern in my code. Data was being loaded into an NSData and then parsed by a C library (the data's bytes and length were passed into it). The C library has callbacks which would fire and return sub-string starting pointers and lengths (to avoid internal copying). However, for my purposes, I needed to turn them into NSStrings and keep them around awhile. I did this by using NSString's initWithBytes:length:encoding: method. I assumed that would copy the bytes and NSString would manage it appropriately, but something is going wrong because this leaks like crazy.
This code will "leak" or somehow trick the garbage collector:
- (void)meh
{
NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"holmes" ofType:#"txt"]];
const int substrLength = 80;
for (const char *substr = [data bytes]; substr-(const char *)[data bytes] < [data length]; substr += substrLength) {
NSString *cocoaString = [[NSString alloc] initWithBytes:substr length:substrLength encoding:NSUTF8StringEncoding];
[cocoaString length];
}
}
I can put this in timer and just watch memory usage go up and up with Activity Monitor as well as with the GC Monitor instrument. (holmes.txt is 594KB)
This isn't the best code in the world, but it shows the problem. (I'm running 10.6, the project is targeted for 10.5 - if that matters). I read over the garbage collection docs and noticed a number of possible pitfalls, but I don't think I'm doing anything obviously against the rules here. Doesn't hurt to ask, though. Thanks!
Project zip
Here's a pic of the object graph just growing and growing:
This is an unfortunate edge case. Please file a bug (http://bugreport.apple.com/) and attach your excellent minimal example.
The problem is two fold;
The main event loop isn't running and, thus, the collector isn't triggered via MEL activity. This leaves the collector doing its normal background only threshold based collections.
The data stores the data read from the file into a malloc'd buffer that is allocated from the malloc zone. Thus, the GC accounted allocation -- the NSData object itself -- is really tiny, but points to something really large (the malloc allocation). The end result is that the collector's threshold isn't hit and it doesn't collect. Obviously, improving this behavior is desired, but it is a hard problem.
This is a very easy bug to reproduce in a micro-benchmark or in isolation. In practice, there is typically enough going on that this problem won't happen. However, there may be certain cases where it does become problematic.
Change your code to this and the collector will collect the data objects. Note that you shouldn't use collectExhaustively often -- it does eat CPU.
- (void)meh
{
NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"holmes" ofType:#"txt"]];
const int substrLength = 80;
for (const char *substr = [data bytes]; substr-(const char *)[data bytes] < [data length]; substr += substrLength) {
NSString *cocoaString = [[NSString alloc] initWithBytes:substr length:substrLength encoding:NSUTF8StringEncoding];
[cocoaString length];
}
[data self];
[[NSGarbageCollector defaultCollector] collectExhaustively];
}
The [data self] keeps the data object alive after the last reference to it.

Resources