Last active
April 9, 2018 02:21
-
-
Save edenwaith/0c67425ebde899e23d936d857810cfee to your computer and use it in GitHub Desktop.
Check if a file can be deleted on a Mac
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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