Unmounting Drive/volume without ejecting - cocoa

I am want to unmount a disk WITHOUT EJECTING. To do that I tried following code
{
NSString *path;
CFStringRef *volumeName=(__bridge CFStringRef)path;
DASessionRef session = DASessionCreate(kCFAllocatorDefault);
CFURLRef pathRef = CFURLCreateWithString(NULL, CFSTR("/volumes/Untitled"), NULL);
DADiskRef disk = DADiskCreateFromVolumePath(kCFAllocatorDefault, session, pathRef);
DADiskUnmount(disk, kDADiskUnmountOptionForce, NULL, NULL);
}
This code is from this question, Thanks to #zeFree
Its working but I want dynamic path to the volume where as in the code its static. I tried changing NSString to CFStringRef and then tried to use at place of path("/volumes/Untitled") mention but its still same.
Any suggestion is welcome.

First of all, you are strongly discouraged from using kDADiskUnmountOptionForce.
This is a method to unmount a volume at given URL with basic error handling and memory management.
- (BOOL)unmountVolumeAtURL:(NSURL *)url
BOOL returnValue = NO;
DASessionRef session = DASessionCreate(kCFAllocatorDefault);
if (session) {
DADiskRef disk = DADiskCreateFromVolumePath(kCFAllocatorDefault, session, (__bridge CFURLRef)url);
if (disk) {
DADiskUnmount(disk, kDADiskUnmountOptionDefault, NULL , NULL);
returnValue = YES;
CFRelease(disk);
} else {
NSLog(#"Could't create disk reference from %#", url.path);
}
} else {
NSLog(#"Could't create DiskArbritation session");
}
if (session) CFRelease(session);
return returnValue;
}
The error handling could be still improved by providing a callback handler in the DADiskUnmount function.

Related

No callback get called from FSEventStreamCreate with modifications created by self in watched file

I am trying to make a console window in my OS X application, which is basically displaying the content of the application log file stored in ~/Library/Application Support/CocosBuilder/cocosbuilder.log
I redirected all NSLog statement from my application inside this file:
+ (void)redirectNSLogToDocumentFolder
{
NSString *logFilePath = [self logfilePath];
freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding],"a+",stderr);
}
Then I watch for modifications in the folder:
- (void)subscribeFileSystemChanges
{
NSString* _pathToObserve = [ConsoleWindow logfileDir];
NSArray * pathArray = [NSArray arrayWithObject:_pathToObserve];
// if already subscribed then unsubscribe
if (stream)
{
FSEventStreamStop(stream);
FSEventStreamInvalidate(stream);
FSEventStreamRelease(stream);
}
FSEventStreamContext context;
context.info = (__bridge void *)self;
context.version = 0;
context.retain = NULL;
context.release = NULL;
context.copyDescription = NULL;
stream = FSEventStreamCreate(kCFAllocatorDefault,
(FSEventStreamCallback)refreshConsoleTextView,
&context,
(__bridge CFArrayRef)pathArray,
kFSEventStreamEventIdSinceNow,
1.0,
kFSEventStreamCreateFlagWatchRoot);
FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(),
kCFRunLoopDefaultMode);
FSEventStreamStart(stream);
}
My callback method refreshConsoleTextView is called when I add something to my log file from terminal with:
echo "toto" >> cocosbuilder.log
But it's not called when my own application writes something to they log file (with my NSLog redirection).
I did not use the flag kFSEventStreamCreateFlagIgnoreSelf
Do you have any idea ?

SecItemCopyMatching - Finding specific kSecClassIdentity by name

I'm trying to find a specific entry in my Mac OS X keychain, based on it's name (kSecAttrLabel), but it looks like SecItemCopyMatching is broken and applies no filtering whatsoever when looking for items of type: kSecClassIdentity.
This piece of code will return all identities found in all keychains, despite the kSecAttrLabel: #"MyIdentity" parameter:
NSDictionary *query = #{ (__bridge id)kSecClass: (__bridge NSString*)kSecClassIdentity,
(__bridge id)kSecAttrLabel: #"MyIdentity",
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitAll,
(__bridge id)kSecReturnAttributes: #YES,
(__bridge id)kSecReturnRef: #YES };
OSStatus status;
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&privateKey);
Granted, I can then find the one identity I'm looking for by filtering manually the returned array, however, beside the fact that, IMHO, it should just work, I also would like to remove this identity from my keychain using SecItemDelete(), which takes a query as parameter, just like SecItemCopyMatching.
If filtering doesn't work for SecItemCopyMatching, then it is likely that it won't work for SecItemDelete and this mean I will simply erase the content of my keychain if I try to call SecItemDelete with this query.
What am I doing wrong?
I think I have just found a solution. It was suggested on on another forum that
Using labels on an identity is tricky because identities are not stored in the keychain as an atomic item but are store as a separate private key and certificate, and those items use labels in different ways.
This made me realise that a solution is to search for a certificate by label using SecItemCopyMatching, and then create the identity using SecIdentityCreateWithCertificate. The latter call should find the matching private key in the Keychain. Here is the full code (in C++) that seems to work for me on macOS Mojave:
SecIdentityRef identity = nullptr;
const char* certificateName = ...;
const void* keys[] = {
kSecClass,
kSecMatchLimit,
kSecReturnRef,
kSecAttrLabel
};
const void* values[] = {
kSecClassCertificate,
kSecMatchLimitOne,
kCFBooleanTrue,
CFStringCreateWithCString(nullptr, certificateName, kCFStringEncodingUTF8)
};
CFDictionaryRef query = CFDictionaryCreate(nullptr, keys, values, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFTypeRef result;
OSStatus status = SecItemCopyMatching(query, &result);
CFRelease(query);
CFRelease(values[3]);
if (status) {
// error
}
else {
SecCertificateRef certificate = (SecCertificateRef)result;
status = SecIdentityCreateWithCertificate(nullptr, certificate, &identity);
CFRelease(certificate);
if (status) {
// error
}
}
I had a similar problem, but I was using kSecAttrApplicationTag rather than kSecAttrLabel. I am not an objective c nor an IOS security expert by any means. It turns out the method I was using to create the lookup tag was incorrect. Here is what worked for me:
- (void) getOrCreateKey: (NSNumber*)bits publicIdentifier:(NSString*)publicID
// no! This appears in several samples, but did not work for me on iOS
// NSData * publicLookupTag = [NSData dataWithBytes:publicId length:strlen((const char *)publicId)];
//
// yes!
NSData * publicLookupTag = [NSData dataWithBytes:[publicId UTF8String] length:publicId.length];
NSMutableDictionary *queryPublicKey = [[NSMutableDictionary alloc] init];
[queryPublicKey setObject:(id)kSecClassKey forKey:(id)kSecClass];
[queryPublicKey setObject:publicLookupTag forKey:(id)kSecAttrApplicationTag];
[queryPublicKey setObject:(id)kSecAttrKeyTypeRSA forKey:(id)kSecAttrKeyType];
[queryPublicKey setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnRef];
OSStatus lookupStatus = noErr;
lookupStatus = SecItemCopyMatching((CFDictionaryRef)queryPublicKey, (CFTypeRef *)&publicLookupKey);

Is it possible to use FSEvents to get notifications that a folder has been moved?

I'm using the FSEvents API to get notifications of changes in a local directory that I'm tracking.
Is it possible to get a notification that the watched directory has been moved to another location on disk, using FSEvents or anything else?
Update:
Here is the code I have so far, I'm now trying to use the kFSEventStreamCreateFlagWatchRoot flag with FSEventStreamCreate to get the root changed notification, so far without success.
- (void)registerForFileSystemNotifications {
NSString *watchedDirectoryPath = [[NSUserDefaults standardUserDefaults] valueForKey:kMyWatchedDirectoryPathKey];
self.watchedDirectoryFileDescriptor = open([watchedDirectoryPath cStringUsingEncoding:NSUTF8StringEncoding], O_RDONLY);
NSArray *paths = [NSArray arrayWithObject:watchedDirectoryPath];
void *appController = (void *)self;
FSEventStreamContext context = {0, appController, NULL, NULL, NULL};
FSEventStreamRef streamRef = FSEventStreamCreate(NULL,
&fsevents_callback,
&context,
(CFArrayRef) paths,
kFSEventStreamEventIdSinceNow,
(CFTimeInterval)2.0,
kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagWatchRoot);
FSEventStreamScheduleWithRunLoop(streamRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
FSEventStreamStart(streamRef);
}
void fsevents_callback(ConstFSEventStreamRef streamRef,
void *userData,
size_t numumberOfEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[]) {
MyAppController *appController = (MyAppController *)userData;
char *newPath = calloc(4096, sizeof(char));
int pathIntPointer = (int)newPath;
int length = fcntl(appController.watchedDirectoryFileDescriptor, F_GETPATH, pathIntPointer);
NSString *newPathString = [[NSString alloc] initWithBytes:newPath length:(NSUInteger)length encoding:NSUTF8StringEncoding];
NSLog(#"newPathString: %#", newPathString); // empty
}
Yes. Pass kFSEventStreamCreateFlagWatchRoot as the last argument to FSEventStreamCreate, and you'll be notified if the directory's moved or renamed. From the docs:
Request notifications of changes along the path to the path(s) you're watching. For example, with this flag, if you watch "/foo/bar" and it is renamed to "/foo/bar.old", you would receive a RootChanged event. The same is true if the directory "/foo" were renamed. The event you receive is a special event: the path for the event is the original path you specified, the flag kFSEventStreamEventFlagRootChanged is set and event ID is zero. RootChanged events are useful to indicate that you should rescan a particular hierarchy because it changed completely (as opposed to the things inside of it changing). If you want to track the current location of a directory, it is best to open the directory before creating the stream so that you have a file descriptor for it and can issue an F_GETPATH fcntl() to find the current path.
Edit: adding fcntl example
That cocoadev example suggests the author's a bit inexperienced with pointers. The pathIntPointer is not only unnecessary, it's also the cause of your problem. Error checking of the return code from fnctl would have caught it. Here's a revised version of your callback:
void fsevents_callback(ConstFSEventStreamRef streamRef,
void *userData,
size_t numumberOfEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[]) {
MyAppController *appController = (MyAppController *)userData;
char newPath[ MAXPATHLEN ];
int rc;
rc = fcntl( appController.watchedDirectoryFileDescriptor, F_GETPATH, newPath );
if ( rc == -1 ) {
perror( "fnctl F_GETPATH" );
return;
}
NSString *newPathString = [[NSString alloc] initWithUTF8String: newPath ];
NSLog(#"newPathString: %#", newPathString);
[ newPathString release ];
}

How do you make your App open at login? [duplicate]

This question already has answers here:
Register as Login Item with Cocoa?
(7 answers)
Closed 9 years ago.
Just wondering how I can make my app open automatically at login, but make this be able to be toggled on and off using a check box in the preferences window.
Here's some code that I use, it's based on the Growl source.
+ (BOOL) willStartAtLogin:(NSURL *)itemURL
{
Boolean foundIt=false;
LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginItems) {
UInt32 seed = 0U;
NSArray *currentLoginItems = [NSMakeCollectable(LSSharedFileListCopySnapshot(loginItems, &seed)) autorelease];
for (id itemObject in currentLoginItems) {
LSSharedFileListItemRef item = (LSSharedFileListItemRef)itemObject;
UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes;
CFURLRef URL = NULL;
OSStatus err = LSSharedFileListItemResolve(item, resolutionFlags, &URL, /*outRef*/ NULL);
if (err == noErr) {
foundIt = CFEqual(URL, itemURL);
CFRelease(URL);
if (foundIt)
break;
}
}
CFRelease(loginItems);
}
return (BOOL)foundIt;
}
+ (void) setStartAtLogin:(NSURL *)itemURL enabled:(BOOL)enabled
{
OSStatus status;
LSSharedFileListItemRef existingItem = NULL;
LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginItems) {
UInt32 seed = 0U;
NSArray *currentLoginItems = [NSMakeCollectable(LSSharedFileListCopySnapshot(loginItems, &seed)) autorelease];
for (id itemObject in currentLoginItems) {
LSSharedFileListItemRef item = (LSSharedFileListItemRef)itemObject;
UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes;
CFURLRef URL = NULL;
OSStatus err = LSSharedFileListItemResolve(item, resolutionFlags, &URL, /*outRef*/ NULL);
if (err == noErr) {
Boolean foundIt = CFEqual(URL, itemURL);
CFRelease(URL);
if (foundIt) {
existingItem = item;
break;
}
}
}
if (enabled && (existingItem == NULL)) {
LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst,
NULL, NULL, (CFURLRef)itemURL, NULL, NULL);
} else if (!enabled && (existingItem != NULL))
LSSharedFileListItemRemove(loginItems, existingItem);
CFRelease(loginItems);
}
}
If you want an easy to implement checkbox, make a #property BOOL startAtLogin; in one of your classes and implement it as follows. Just bind the checkbox value to the property and it should all work seamlessly.
- (NSURL *)appURL
{
return [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
}
- (BOOL)startAtLogin
{
return [LoginItem willStartAtLogin:[self appURL]];
}
- (void)setStartAtLogin:(BOOL)enabled
{
[self willChangeValueForKey:#"startAtLogin"];
[LoginItem setStartAtLogin:[self appURL] enabled:enabled];
[self didChangeValueForKey:#"startAtLogin"];
}
There is a decent description of what to do at CocoaDev.
Basically, you'll want to use the API in LaunchServices/LSSharedFileList.h if you can target Mac OS X 10.5 or later. Before 10.5 there was no clean API, so you have to manually manipulate the login items (Sample code at the Developer Connectiong).
Here's the sample code(dead) for Leopard I mentioned in the comments. Found via this blog post. The code you need to enable or disable startup at login is in Controller.m.
Call the method pasted below with a file URL pointing at your application to add it to the current user's login items.
To disable again, you'll need to get that same loginListRef, convert it to an array, and iterate through it until you find the item with the url you want to disable. Finally, call LSSharedFileListItemRemove with the appropriate arguments.
Good luck :)
- (void)enableLoginItemWithURL:(NSURL *)itemURL
{
LSSharedFileListRef loginListRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginListRef) {
// Insert the item at the bottom of Login Items list.
LSSharedFileListItemRef loginItemRef = LSSharedFileListInsertItemURL(loginListRef,
kLSSharedFileListItemLast,
NULL,
NULL,
(CFURLRef)itemURL,
NULL,
NULL);
if (loginItemRef) {
CFRelease(loginItemRef);
}
CFRelease(loginListRef);
}
}
See also SO question: Register as login item with cocoa

Register as Login Item with Cocoa?

Google gave me: http://developer.apple.com/samplecode/LoginItemsAE/index.html
And I figured there must be a better way than using AppleScript Events.
So I downloaded the Growl sources. They use the exact sources from that Apple developer article.
Is there a better way?
(I refer to Login Items in Accounts in System Preferences, ie. making my program start when the user Logs in, programmatically)
There's an API that's new in Leopard called LSSharedFileList. One of the things it lets you do is view and edit the Login Items list (called Session Login Items in that API).
BTW, I'm the lead developer of Growl. We haven't switched away from AE yet because we still require Tiger, but I'm thinking of dropping that for 1.2 (haven't talked it over with the other developers yet). When we do drop Tiger, we'll drop LoginItemsAE as well, and switch to the Shared File List API.
EDIT from the year 2012: Since 2009, when I originally wrote this answer, Growl has switched to LSSharedFileList and I've left the project.
I stumbled across Ben Clark-Robinson's LaunchAtLoginController. A very elegant solution to a very common problem.
This works on xcode 5.
- (BOOL)isLaunchAtStartup {
// See if the app is currently in LoginItems.
LSSharedFileListItemRef itemRef = [self itemRefInLoginItems];
// Store away that boolean.
BOOL isInList = itemRef != nil;
// Release the reference if it exists.
if (itemRef != nil) CFRelease(itemRef);
return isInList;
}
- (void)toggleLaunchAtStartup {
// Toggle the state.
BOOL shouldBeToggled = ![self isLaunchAtStartup];
// Get the LoginItems list.
LSSharedFileListRef loginItemsRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginItemsRef == nil) return;
if (shouldBeToggled) {
// Add the app to the LoginItems list.
CFURLRef appUrl = (__bridge CFURLRef)[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
LSSharedFileListItemRef itemRef = LSSharedFileListInsertItemURL(loginItemsRef, kLSSharedFileListItemLast, NULL, NULL, appUrl, NULL, NULL);
if (itemRef) CFRelease(itemRef);
}
else {
// Remove the app from the LoginItems list.
LSSharedFileListItemRef itemRef = [self itemRefInLoginItems];
LSSharedFileListItemRemove(loginItemsRef,itemRef);
if (itemRef != nil) CFRelease(itemRef);
}
CFRelease(loginItemsRef);
}
- (LSSharedFileListItemRef)itemRefInLoginItems {
LSSharedFileListItemRef res = nil;
// Get the app's URL.
NSURL *bundleURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
// Get the LoginItems list.
LSSharedFileListRef loginItemsRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginItemsRef == nil) return nil;
// Iterate over the LoginItems.
NSArray *loginItems = (__bridge NSArray *)LSSharedFileListCopySnapshot(loginItemsRef, nil);
for (id item in loginItems) {
LSSharedFileListItemRef itemRef = (__bridge LSSharedFileListItemRef)(item);
CFURLRef itemURLRef;
if (LSSharedFileListItemResolve(itemRef, 0, &itemURLRef, NULL) == noErr) {
// Again, use toll-free bridging.
NSURL *itemURL = (__bridge NSURL *)itemURLRef;
if ([itemURL isEqual:bundleURL]) {
res = itemRef;
break;
}
}
}
// Retain the LoginItem reference.
if (res != nil) CFRetain(res);
CFRelease(loginItemsRef);
CFRelease((__bridge CFTypeRef)(loginItems));
return res;
}
I do this in an app I'm writing:
Check out UKLoginItemRegistry for an easy way to do this pragmatically. Afaik, there is no way in Tiger to do this without Apple Events; in Leopard there's a better way, but if you use UKLoginItemRegistry it really is no problem. Here's the complete code for implementing an "Open at Logon" menu item
+ (bool)isAppSetToRunAtLogon {
int ret = [UKLoginItemRegistry indexForLoginItemWithPath:[[NSBundle mainBundle] bundlePath]];
NSLog(#"login item index = %i", ret);
return (ret >= 0);
}
- (IBAction)toggleOpenAtLogon:(id)sender {
if ([PopupController isAppSetToRunAtLogon]) {
[UKLoginItemRegistry removeLoginItemWithPath:[[NSBundle mainBundle] bundlePath]];
} else {
[UKLoginItemRegistry addLoginItemWithPath:[[NSBundle mainBundle] bundlePath] hideIt: NO];
}
}
I've refactored some of the answers here to provide a category on NSApplication that provides a launchAtLogin property.
https://gist.github.com/joerick/73670eba228c177bceb3
SMLoginItemSetEnabled is another modern option, see Modern Login Items article by Cory Bohon where he explains that you have to create a helper application whose sole purpose is to launch the main application. There's also a full step by step explanation in SMLoginItemSetEnabled - Start at Login with App Sandboxed on Stack Overflow.
Check here an open source example: https://github.com/invariant/rhpnotifier (LoginItem.m, LoginItem.h)

Resources