Skip to content

Instantly share code, notes, and snippets.

@edenwaith
Last active April 9, 2018 02:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save edenwaith/0c67425ebde899e23d936d857810cfee to your computer and use it in GitHub Desktop.
Save edenwaith/0c67425ebde899e23d936d857810cfee to your computer and use it in GitHub Desktop.
Check if a file can be deleted on a Mac
/*
* checkFilePermissions.m
* Description: A collection of methods which checks if a given file can be deleted
* and checks other permissions and properties.
* Author: Chad Armstrong
* Date: 1 December 2017
* To compile: gcc -w -framework Foundation -framework AppKit checkFilePermissions.m -o checkFilePermissions
*/
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#include <sys/param.h>
#include <sys/ucred.h>
#include <sys/mount.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSFileManager *fm = [NSFileManager defaultManager];
if (argc <= 1) {
printf("No path specified.\nUsage: %s path/to/file \n", argv[0]);
return -1;
}
char *path = argv[1];
NSString *objcPath = [NSString stringWithCString: path encoding: NSUTF8StringEncoding];
/*
If the file is owned by someone else (such as root), isWritableFileAtPath and access
will say 'no'. If isDeletableFileAtPath also returns 'no', then that folder is
also write-protected. In such case, then check the parent volume to see if it is
read-only (such as a DMG or CD-ROM. If the volume is read-only, then the file cannot
be deleted. If the volume is NOT read-only, then the file to be deleted is read-only
and its enclosing folder is read-only, but it can still be deleted in such a case.
Cases a file cannot be deleted
1. File is write-protected
2. File is in a directory which is write-protected
3. File is on read-only media
Steps:
1. Call FM's isDeletableFileAtPath
2. Call access or FM's isWritableFileAtPath
3. If both Steps 1 and 2 pass, then the file can be deleted without any problems.
4. If only one of them fails, then the file can still be deleted, need special permissions.
5. If both fail, check errno from Step 2. If the error is EROFS, then the file is on a read-only drive.
If the error is EACCES, it is a permissions issue. Need special permissions.
There could be other errors, but might default to assuming that as long as errno is not
EROFS, try and delete the file with proper authorization.
*/
// https://developer.apple.com/documentation/foundation/nsfilemanager/1416680-iswritablefileatpath?language=objc
// If the file itself is read-only, this will return NO
if ([fm isWritableFileAtPath: objcPath] == YES) {
NSLog(@"You can write to the file %@", objcPath);
} else {
NSLog(@"You CANNOT write to the file %@", objcPath);
perror("isWritable perror:: ");
}
// Use UNIX access() call -----
// This has the same response as isWritableFileAtPath
if (access(path, W_OK) == 0) {
// have access rights to read
printf("You are A-OK to write to %s\n", path);
} else {
printf("Nope, you can't access %s\n", path);
int accessErrno = errno;
// Call strerror to get the string of the error from errno
char *errorMsg = strerror(errno);
// perror automatically returns the error message associated with the current errno
// perror(errorMsg);
// perror(NULL);
perror("perror message ");
NSLog(@"errorMsg:: %s", errorMsg);
// TODO: Also need to check folders which are not empty
NSDictionary *fileAttributes = [fm fileAttributesAtPath: objcPath traverseLink:NO];
// man page for a list of error codes: man 2 intro
// http://sutes.co.uk/2010/08/errno.html
// errno codes: http://www.virtsync.com/c-error-codes-include-errno
if (errno == EROFS) { // errno value of 30 -- Read-only file system
NSLog(@"Write access is requested for a file on a read-only file system.");
} else if (errno == EACCES) { // errno value of 13 -- Permissions error
NSLog(@"Permission bits of the file mode do not permit the requested access, or search permission is denied on a component of the path prefix.");
NSNumber *originalPermissions = [fileAttributes objectForKey: NSFilePosixPermissions];
mode_t permissionsMask = 0700;
// This code gave me hints on how to modify permissions by using bitmasks
// // https://github.com/omnigroup/OmniGroup/blob/master/Frameworks/OmniFoundation/OpenStepExtensions.subproj/NSFileManager-OFExtensions.m
mode_t updatedPermissions = [originalPermissions shortValue] | permissionsMask;
NSLog(@"originalPermissions:: %o | updated permissions: %o", [originalPermissions shortValue], updatedPermissions);
NSError *err = nil;
NSDictionary *attribs = [NSDictionary dictionaryWithObject:[NSNumber numberWithShort:updatedPermissions] forKey:NSFilePosixPermissions];
// Try and change the file's permissions so it can be overwritten. Even if a file
// is owned by the current user but has permissions set like 444, the file cannot
// be deleted until the file has the write bit set.
if ([fm setAttributes: attribs ofItemAtPath: objcPath error: &err] == NO) {
NSLog(@"Error setting attributes: %@ | errno: %s (%d)", [err localizedDescription], strerror(errno), errno);
} else {
NSLog(@"Successfully updated the file's permissions to:: %o", updatedPermissions);
}
} else if (errno == EPERM) { // errno value of 1 -- Operation not permitted
// This can occur if the file has been locked. Try and unlock it.
NSLog(@"Operation not permitted. File might be locked. Attempting to unlock...");
// If the file is locked by the Finder, unlock it before deleting.
if ([[fileAttributes objectForKey:NSFileImmutable] boolValue] == YES) {
NSLog(@"Yes, the file %@ is locked.", [objcPath lastPathComponent]);
}
NSError *error = nil;
if ([fm setAttributes:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:NSFileImmutable] ofItemAtPath: objcPath error: &error] == YES) {
NSLog(@"Successfully unlocked the file %@", [objcPath lastPathComponent]);
} else {
NSLog(@"Could not unlock the file %@", [objcPath lastPathComponent]);
NSLog(@"errno: %s (%d)", strerror(errno), errno);
}
}
}
// Use the NSFileManager methods -----
// https://developer.apple.com/documentation/foundation/nsfilemanager/1408087-isdeletablefileatpath?language=objc
// If the containing folder the file is in is not writable, then this will return NO.
if ([fm isDeletableFileAtPath: objcPath] == YES) {
NSLog(@"The containing path for this file is writable: %@", objcPath);
} else {
NSLog(@"The containing path for this file is NOT writable: %@", objcPath);
perror("isDeletable perror:: ");
}
printf("\n");
// Get the attributes of the path and its file system
NSError *error = nil;
NSDictionary *fileAttr = [fm attributesOfItemAtPath: objcPath error: &error];
NSDictionary *fsAttr = [fm attributesOfFileSystemForPath: objcPath error: &error];
NSLog(@"fileAttr: %@", fileAttr);
NSLog(@"fsAttr: %@", fsAttr);
// Use statfs to get the file system type for the given path
struct statfs stat;
if (statfs([objcPath fileSystemRepresentation], &stat) == 0) {
NSString *fileSystemName = [fm stringWithFileSystemRepresentation: stat.f_fstypename length: strlen(stat.f_fstypename)];
NSLog(@"File system type:: %@", fileSystemName);
}
struct statfs *mounts;
int num_mounts = getmntinfo(&mounts, MNT_WAIT);
if (num_mounts < 0) {
// do something with the error
}
int i = 0;
for (i = 0; i < num_mounts; i++) {
NSLog(@"Disk type '%s' mounted at: %s", mounts[i].f_fstypename, mounts[i].f_mntonname);
}
// Use NSWorkspace method -----
// NSFileManager mountedVolumeURLsIncludingResourceValuesForKeys:options
NSArray *resourceKeys = [NSArray arrayWithObjects: NSURLVolumeURLKey, NSURLIsSystemImmutableKey, nil];
NSArray *mountedVolumesURLs = [[NSFileManager defaultManager] mountedVolumeURLsIncludingResourceValuesForKeys: resourceKeys options: 0];
// NSLog(@"mountedVolumes: %@", mountedVolumesURLs);
NSLog(@"mountedVolumesURLs::");
for (NSURL *url in mountedVolumesURLs) {
NSLog(@"%@", [url path]);
}
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
// NSWorkspace mountedLocalVolumePaths
NSArray *mountedPaths = [ws mountedLocalVolumePaths];
NSLog(@"objcPath: %@", objcPath);
// With these path components (and objcPath should be the full path
// check the first component, which should correspond with the mountedPaths
// Or maybe not, since something in /Volumes would be handled differently.
NSArray *pathComponents = [objcPath pathComponents];
NSLog(@"pathComponents: %@", pathComponents);
// Probably only need to check if the FS is read-only if both the
// isWritableFileAtPath and isDeletableFileAtPath fail, then a check
// should be made to see if the FS is read-only.
NSArray *componentsForPath = [[NSFileManager defaultManager] componentsToDisplayForPath: objcPath];
NSLog(@"componentsForPath: %@", componentsForPath);
NSLog(@"mountedPaths: %@", mountedPaths);
NSLog(@"Start");
NSMutableDictionary *volumesInfo = [[NSMutableDictionary alloc] init];
//for (i = 0; i < 10000; i++) {
for (NSString *volumePath in mountedPaths) {
BOOL removable = NO;
BOOL writeable = NO;
BOOL unmountable = NO;
NSString *fsDescription = nil;
NSString *fsType = nil;
// Check if the file system for the given path is read-only or not
[ws getFileSystemInfoForPath: volumePath
isRemovable: &removable
isWritable: &writeable
isUnmountable: &unmountable
description: &fsDescription
type: &fsType];
[volumesInfo setObject: [NSNumber numberWithBool: writeable] forKey: volumePath];
NSLog(@"\nVolume Path: %@ -----", volumePath);
NSLog(@"removable: %@", removable ? @"YES" : @"NO");
NSLog(@"writeable: %@", writeable ? @"YES" : @"NO");
NSLog(@"unmountable: %@", unmountable ? @"YES" : @"NO");
NSLog(@"FS Description: %@", fsDescription);
NSLog(@"FS Type: %@", fsType);
}
//}
NSLog(@"volumesInfo: %@", volumesInfo);
NSLog(@"End");
[pool drain];
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment