-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathSSYSuperFileManager.m
354 lines (304 loc) · 10.4 KB
/
SSYSuperFileManager.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
#if MAC_OS_X_VERSION_MIN_REQUIRED > 1050
#define CPH_TASKMASTER_AVAILABLE 1
#else
#define CPH_TASKMASTER_AVAILABLE 0
#endif
#if CPH_TASKMASTER_AVAILABLE
#import "CPHTaskMaster+SetPermissions.h"
#import "CPHTaskMaster+StatPaths.h"
#endif
#import "SSYSuperFileManager.h"
#import <sys/stat.h>
#import "NSError+InfoAccess.h"
#import "NSError+MyDomain.h"
static SSYSuperFileManager* defaultManager = nil ;
@implementation SSYSuperFileManager
+ (SSYSuperFileManager*)defaultManager {
@synchronized(self) {
if (!defaultManager) {
defaultManager = [[self alloc] init] ;
}
}
// No autorelease. This sticks around forever.
return defaultManager ;
}
- (BOOL)setPermissions:(mode_t)permissions
path:(NSString*)path
changedPermissions:(NSMutableDictionary*)changedPermissions
error_p:(NSError**)error_p {
NSNumber* oldPermissions = nil ;
#if CPH_TASKMASTER_AVAILABLE
NSNumber* newPermissions = [NSNumber numberWithUnsignedShort:permissions] ;
BOOL ok = [[CPHTaskmaster sharedTaskmaster] getPermissionNumber_p:&oldPermissions
setPermissionNumber:newPermissions
path:path
error_p:error_p] ;
#else
BOOL ok = NO ;
if (error_p) {
*error_p = SSYMakeError(259101, @"Attempted an operation which is not supported in this old version of BookMacster") ;
}
#endif
if (!ok) {
return NO ;
}
if (oldPermissions) {
[changedPermissions setObject:oldPermissions
forKey:path] ;
}
return ok ;
}
/*!
@brief Returns whether or not an item exists at a given path, presenting
an authentication dialog and changing permissions on its parent
directory to octual 040777 if necessary to determine this existence.
@param path The path to check for existence
@param changedPermissions If it is not necessary to relax permissions
on the parent directory, the passed-in dictionary will be untouched. If
it is necessary to relax permissions on the parent directory, and if
this change was successful, the parent directory's path will be added as a
key to this dictionary, with a value equal to an NSNumber whose unsigned
short value is the original permissions of the parent directory before
they were changed to octal 040777.
@param error_p Pointer to an NSError which, if permissions needed to be
changed and could not be, will point to an NSError instance explaining why
permissions could not be changed. Otherwise, this pointer will be
untouched.
@result YES if the given path was determined to exist. NO if it was
determined to not exist, or if existence could not be determined because
an error occurred when changing parent permissions. In the latter case,
error_p will be set to the relevant NSError instance.
*/
- (BOOL)fileExistsAtPath:(NSString*)path
changedPermissions:(NSMutableDictionary*)changedPermissions
error_p:(NSError**)error_p {
#if 0
// Even though all we want to know is whether not not a file exists
// at a given path, we use -attributesOfItemAtPath:error: instead of
// -fileExistsAtPath because the former gives us an error output which
// tells us whether or not we need to relax permissions on the parent.
NSError* error = nil ;
[self attributesOfItemAtPath:path
error:&error] ;
if (
([[error domain] isEqualToString:NSCocoaErrorDomain])
&&
([error code] == NSFileReadNoSuchFileError) // == 260
) {
return NO ;
}
// else if (!isLastComponent) {
// The following just caused false errors. For some strange reason, it is not
// possible to change permissions of a mounted disk in /Volumes. Try it in
// Terminal. chmod returns no error but "just doesn't work".
//mode_t perms = [[attributes objectForKey:NSFilePosixPermissions] unsignedShortValue] ;
//if ((perms & S_IXOTH) == 0) {
// needToDigDeeperButWillBeStoppedByPermissions = YES ;
//}
//needToDigDeeperButWillBeStoppedByPermissions = ![self canExecuteDirectoryAtPath:path] ;
//}
if (!error) {
return YES ;
}
#endif
if ([super fileExistsAtPath:path]) {
return YES ;
}
NSString* parentPath = [path stringByDeletingLastPathComponent] ;
unsigned short parentPermissions = [[[self attributesOfItemAtPath:parentPath
error:NULL] objectForKey:NSFilePosixPermissions] unsignedShortValue] ;
if (
(
((parentPermissions & S_IXOTH) == 0)
// [[error domain] isEqualToString:NSCocoaErrorDomain]
// &&
// [error code] == NSFileReadNoPermissionError
)
) {
// Inadequate permissions on parent
// NSError* error = nil ;
// Typically, the permissions on an inaccessible file's parent are:
// octal 040700 = 0x41C0 = decimal 16832
// We change them temporarily to
// octal 040777 = 0x41FF = decimal 16895
BOOL ok = [self setPermissions:040777
path:parentPath
changedPermissions:changedPermissions
error_p:error_p] ;
if (!ok) {
return NO ;
}
// Now that parent's permissions have been liberalized,
// we try again to see if the file exists.
BOOL answer = [self fileExistsAtPath:path] ;
return answer ;
}
return NO ;
}
- (BOOL)setBulkPermissions:(NSDictionary*)permissions
error_p:(NSError**)error_p {
#if CPH_TASKMASTER_AVAILABLE
return [[CPHTaskmaster sharedTaskmaster] getPermissions_p:NULL
setPermissions:permissions
error_p:error_p] ;
#else
if (error_p) {
*error_p = SSYMakeError(259107, @"Attempted an operation which is not supported in this old version of BookMacster") ;
}
return NO ;
#endif
}
- (BOOL)setDeepPermissions:(mode_t)permissions
path:(NSString*)path
changedPermissions:(NSMutableDictionary*)changedPermissions
error_p:(NSError**)error_p {
NSNumber* oldPermissions = nil ;
#if CPH_TASKMASTER_AVAILABLE
NSNumber* newPermissions = [NSNumber numberWithUnsignedShort:permissions] ;
BOOL ok = [[CPHTaskmaster sharedTaskmaster] getPermissionNumber_p:&oldPermissions
setPermissionNumber:newPermissions
path:path
error_p:error_p] ;
#else
BOOL ok = NO ;
if (error_p) {
*error_p = SSYMakeError(259102, @"Attempted an operation which is not supported in this old version of BookMacster") ;
}
#endif
if (!ok) {
return NO ;
}
if (oldPermissions) {
[changedPermissions setObject:oldPermissions
forKey:path] ;
}
return ok ;
}
- (BOOL)fileExistsAtPath:(NSString*)path
isDirectory_p:(BOOL*)isDirectory_p
didElevate_p:(BOOL*)didElevate_p
error_p:(NSError**)error_p {
NSArray* components = [path pathComponents] ;
BOOL ok = YES ;
BOOL exists = YES ;
NSString* partialPath = @"" ;
NSMutableDictionary* changedPermissions = [[NSMutableDictionary alloc] initWithCapacity:[components count]] ;
for (NSString* component in components) {
partialPath = [partialPath stringByAppendingPathComponent:component] ;
ok = [self fileExistsAtPath:partialPath
changedPermissions:changedPermissions
error_p:error_p] ;
if (!ok) {
exists = NO ;
break ;
}
}
if (didElevate_p) {
*didElevate_p = ([changedPermissions count] > 0) ;
}
// Run -fileExistsAtPath:isDirectory: for the sole purpose of setting isDirectory_p
[self fileExistsAtPath:path
isDirectory:isDirectory_p] ;
// Restore old permissions (even if !ok)
NSError* restoreError = nil ;
BOOL restoredOk = YES ;
if ([changedPermissions count] > 0) {
ok = [self setBulkPermissions:changedPermissions
error_p:&restoreError] ;
}
[changedPermissions release] ;
if (ok & !restoredOk) {
if (error_p) {
*error_p = restoreError ;
}
}
return exists ;
}
- (BOOL)fileExistsAtPath:(NSString*)path
didElevate_p:(BOOL*)didElevate_p
error_p:(NSError**)error_p {
return [self fileExistsAtPath:path
isDirectory_p:NULL
didElevate_p:didElevate_p
error_p:error_p] ;
}
- (BOOL)canExecutePath:(NSString *)fullPath
groupID:(uid_t)groupID
userID:(uid_t)userID {
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 1060)
NSDictionary* attributes = [self fileAttributesAtPath:fullPath
traverseLink:YES] ;
#else
NSError* error = nil ;
NSDictionary* attributes = [self attributesOfItemAtPath:fullPath
error:&error] ;
if (error) {
NSLog(@"Internal Error 529-2390 %@", error) ;
}
#endif
BOOL canX = NO ;
uint32_t posixPermissions = (uint32_t)[[attributes objectForKey:NSFilePosixPermissions] unsignedIntegerValue] ;
// See if anyone can execute it
if ((posixPermissions & S_IXOTH) != 0) {
canX = YES ;
}
else {
// See if given userID can execute it as owner
uint32_t ownerID = (uint32_t)[[attributes objectForKey:NSFileOwnerAccountID] unsignedIntegerValue] ;
if ( (ownerID==userID) && ((posixPermissions & S_IXUSR) != 0) ) {
canX = YES ;
}
else {
// See if given groupID can execute it as group
uint32_t owningGroupID = (uint32_t)[[attributes objectForKey:NSFileGroupOwnerAccountID] unsignedIntegerValue] ;
if ( (owningGroupID==groupID) && ((posixPermissions & S_IXGRP) != 0) ) {
canX = YES ;
}
}
}
return canX ;
}
- (NSDate*)modificationDateForPath:(NSString*)path
error_p:(NSError**)error_p {
// For efficiency in case the caller expects the path may not exist,
// and has passed error_p = NULL, we don't create a local error.
NSDate* date = nil ;
struct stat aStat ;
BOOL ok = NO ;
NSInteger result = stat([path fileSystemRepresentation], &aStat) ;
if (result == 0) {
ok = YES ;
}
else if (errno == EACCES) {
// Permission denied. Haul out the big gun.
#if CPH_TASKMASTER_AVAILABLE
ok = [[CPHTaskmaster sharedTaskmaster] statPath:path
stat:&aStat
error_p:error_p] ;
if (!ok && error_p) {
*error_p = [SSYMakeError(518383, @"Authorized stat failed") errorByAddingUnderlyingError:*error_p] ;
}
#else
ok = NO ;
if (error_p) {
*error_p = SSYMakeError(259103, @"Attempted an operation which is not supported in this old version of BookMacster") ;
}
#endif
}
else if (error_p) {
NSString* msg = [NSString stringWithFormat:
@"stat got errno %ld",
(long)errno] ;
// The following error was 513560 until BookMacster 1.11 when I discovered
// that it duplicated the same number in SSYSuperFileManager.
*error_p = SSYMakeError(513504, msg) ;
}
if (ok) {
time_t secs = aStat.st_mtimespec.tv_sec ;
long nanosecs = aStat.st_mtimespec.tv_nsec ;
NSTimeInterval timeSince1970 = secs + 1e-9 * nanosecs ;
date = [NSDate dateWithTimeIntervalSince1970:timeSince1970] ;
}
return date ;
}
@end