Launching App using 'launchedTaskWithLaunchPath' Cocoa/objective-c API - objective-c

I need to launch 'TextMate' from an App, and I used the following code.
[NSTask launchedTaskWithLaunchPath:#"/Applications/TextMate.app" arguments:[NSArray arrayWithObjects:#"hello.txt", nil]];
But, I got the following error return.
*** NSTask: Task create for path '/Applications/TextMate.app' failed: 22, "Invalid argument". Terminating temporary process.
What's wrong with my code? I just tried to run "TextMate hello.txt".
ADDED
I could make it run as follows.
[NSTask launchedTaskWithLaunchPath:#"/Applications/TextMate.app/Contents/MacOS/TextMate" arguments:[NSArray arrayWithObjects:#"hello.txt", nil]];
And I asked another question to see how many other ways available.

In this case, the invalid parameter is the application's name.
If you check the documentation for NSTask you'll see that the method you're using is basically a wrapper for the low-level exec() system call. This means you need to provide the name of an actual executable or binary file for it to be able to create the process. In your case, you're giving it a directory (use a terminal to confirm that most app bundles in /Applications are directories). That's why it barfs.
You could look inside TextMate's bundle directory to find the actual executable (should be somewhere in /Applications/TextMate.app/Contents/MacOS). You could then modify your code to call the actual executable.
However, it would seem that the correct, Cocoa-ish way to do it is by using NSWorkspace, you might look into its openFile:withApplication: method, which seems to do what you need, and in this case you DO specify the application bundle directory as a parameter, the way you were originally doing it.
Official documentation is here.
By the way, I can't fully take credit for it; see this StackOverflow answer to learn more about this topic.

You're trying to launch a directory, not a binary.

With the onset of sandboxing this does not work anymore and fails with "forbidden-sandbox-reinit" if you try to launch yourself.

Related

Error building Shoutem preview build after modifying extension

I've working on modification of the 'shoutem.notification-center' extension using as a guide this tutorial and I'm having some issues.
At first I tried to use the Extend the extension approach, but like I've posted on this issue, didn't quite work.
So I've tried the Directly modify approach, which works fine on my local phone, but once I use the command shoutem push to send my modifications to the server, the instance on Appetize never stops the 'Building your application' message.
The major problem is that there's no error code or feedback.
That was not the first time that happened, I had the same issue modifying other extensions. Any idea why this is happening?
The issue is likely one of two things.
New native dependencies were added that the Builder preview cannot process due to it's predefined binary.
Your directly modified extension works locally, but not on the Builder because locally it's path is still AppName/extensions/shoutem.extName, but on the Builder it's AppName/extensions/yourDevName.extName, so it fails.
The first one can be resolved by either using a non-native solution as a replacement for the native dependency you were using, or to simply use a local emulator for previewing purposes.
The second can be resolved by making sure all extensions that reference the one you directly modified are edited to now reference your new directly modified extension instead of shoutem.extName.
If you could shoot me your app ID in a comment I can let you know which one it is and what the best steps to fix it would be.

Why does fileManager copyItemAtPath:ToPath:error return an error, when it successfully copies the file?

This is purely a curiosity item -- the solution turned out to be to check the return value instead of the error -- but this code is months old, and that particular section hasn't been a problem in the past (possibly related to the fact that this code only runs on the first-run of the app?).
I have a section of code where I copy a file from the bundle to the documents directory (a copy of JQuery for some html documents to use). The copy succeeds, but there is in fact an error returned (cocoa error 4, could not complete). Why would the function provide me with an error when it has, in fact, successfully completed?
I'm curious, at least in part, because I suspect that this indicates some major misunderstanding on my part as to how I should handle error files.
I have seen a lot of example code that checks if there is a nil reference for the error: argument to determine if the method was successful, this is wrong! Apple warns in the Error Handling in Cocoa Documentation that this isn't the documented behavior. You must first check the return value of the method, if it returns an error indication you can then check the NSError reference to learn more about the error.
Error code 4 appears to be NSFileNoSuchFileError as per the docs.
I suspect that since the file in the documents directory didn't exist before copying it sets this error.
Reading the documentation for NSFileManager, it says to not use the singleton method [NSFileManager defaultManager] because NSFileManager is not thread safe. Hope this clears your curiosity.

Gaining Root Access w/ Elevated Helper & SMJobBless

I'm working on something that needs to install files periodically into a folder in /Library.
I understand that in the past I could have used one of the Authenticate methods but those have since been deprecated in 10.7.
What I've understood from my reading so far:
I should create a helper that somehow gets authenticated and have that helper do all of the moving tasks. I've taken a look at some of the sample code, including some involving XPC and one called Elevator but I'm a bit confused.
A lot of it seems to deal with setting up some sort of client / server model but I'm not sure how this would translate into me actually installing my files into the correct directories. Most of the examples are just passing strings.
My question simply: How can I create my folder in /Library programmatically and periodically write files to it while only prompting the user for a password ONCE and never again? I'm really not sure how to approach this and there doesn't seem to be much documentation.
You are correct that there isn't much documentation for this. You'll basically write another app, the helper app, which will get installed with SMJobBless(). Not surprisingly,
the tricky part here is the code signing. The least obvious part for me was that the SMAuthorizedClients and SMPrivilegedExecutables entries in the info plist files of each app are dependent on the identity/certificate that you used to sign the app with. There is also a trick with the compiler/linker to getting the info plist file compiled into the helper tool, which will be a single executable file, rather than a bundle.
Once you get the helper app up and running then you have to devise a way to communicate with it since these are two different processes. XPC is one option, perhaps the easiest. XPC is typically used with server processes, but what you are using here is the communication side of XPC only. Basically it passes dictionaries back and forth between the two apps. Create a standard format for the dictionary. I used #"action", #"source", and #"destination" with 3 different action values, #"filemove", #"filecopy", and #"makedirectory". Those are the 3 things that my helper app can do and I can easily add more if necessary.
The helper app will basically setup the XPC connection and event handler stuff and wait for a connection and commands. The commands will just be a dictionary so you check for the appropriate keys/values and do whatever.
I can provide more details and code if you need more help, but this question is 9 months old so I don't want to waste time giving you details you've already figured out.

App Sandbox: document-scoped bookmark not resolving; not returning any error

I'm sandboxing my app, and trying to allow for import/export of multiple files, using an XML file to refer to them. To allow my app (or another sandboxed app) access to the files listed in the XML, I'm also including a serialized security-scoped bookmark. I'm serializing it as described in this answer, and my unit tests (which are not sandboxed) write and read the XML data without issue. When my app resolves the bookmark, the NSURL returned is nil, as is the NSError reference. Since I don't believe that should be the case, why is it happening? I can work around it by prompting the user to select a file/directory with an NSOpenPanel, but I'd still like to get the bookmarks to work as they should.
Reproduced in a test project
To reproduce at home, create a new Cocoa app in Xcode, and use the following Gist for the files in the project: https://gist.github.com/2582589 (updated with a proper next-view loop)
Then, follow Apple's instructions to code-sign the project. You reproduce the problem (which I submitted to Apple as rdar://11369377) by clicking the buttons in sequence. You pick any file on disk (outside the app's container), then an XML to export to, and then the same XML to import.
Hopefully you guys will be able to help me figure out what I'm doing wrong. Either I'm doing something wrong and the framework is erroneously keeping to itself, or I'm doing it right and it's totally broken. I try not to blame the framework, so which is it? Or is there another possibility?
Sample Code
Exporting the XML to docURL:
// After the user picks an XML (docURL) destination with NSSavePanel
[targetURL startAccessingSecurityScopedResource];
NSData *bookmark = [targetURL bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
includingResourceValuesForKeys:nil
relativeToURL:docURL
error:&error];
[targetURL stopAccessingSecurityScopedResource];
Importing the XML from docURL:
// After the user selected the XML (docURL) from an NSOpenPanel
NSURL *result = [NSURL URLByResolvingBookmarkData:bookmarkData
options:NSURLBookmarkResolutionWithSecurityScope
relativeToURL:docURL
bookmarkDataIsStale:nil
error:&error];
I tried surrounding this call with[docURL ..AccessingSecurityScopedResource], which didn't make a difference (as expected, since the docURL is already within scope after having been selected in the Open Panel
Also, I specify the following in my app.entitlements file:
com.apple.security.files.user-selected.read-write
com.apple.security.files.bookmarks.app-scope
com.apple.security.files.bookmarks.collection-scope
As mentioned above, the second step (resolving the bookmark) completes, but leaves both error and result nil. As I've been implementing sandboxing, most of the mistakes I've made have resulted in an NSError being returned, which helped me to resolve the bug. But now there's no error, and no URL is resolved.
Miscellaneous troubleshooting steps
I tried placing the XML file into my app's sandbox, which didn't make a difference, so access to the XML file is not the problem
The app uses ARC, but so do the unit tests, which succeed. I tried using an alloc/init instead of the autoreleased class method, too (just in case)
I pasted the URL resolution code immediately after creating the bookmark, and it runs fine, producing a security-scoped URL
I did a po on the originally created bookmark (before serialization), and then on the bookmark after deserialization, and they match 100%. Serialization is not the problem
I replaced the resolution call with CFURLCreateByResolvingBookmarkData(..), with no change. If it is a bug, it's present in the Core Foundation API as well as the Cocoa layer
Specifying a value for bookmarkDataIsStale: has no effect
If I specify 0 for options:, then I do get back a valid NSURL, but it has no security scope, and therefore subsequent calls to read the file do still fail
In other words, the deserialized bookmark does appear to be valid. If the bookmark data were corrupted, I doubt NSURL would be able to do anything with it
NSURL.h didn't contain any useful comments to point out something I'm doing wrong
Is anyone else using security-scoped document bookmarks in a sandboxed application with success? If so, what are you doing differently than I am?
OS Version Request
Can someone with access to the Mountain Lion beta verify whether or not my sample project shows the same (lack of an) error? If it's a bug that has been fixed after Lion, I won't worry about it. I'm not in the developer program yet, and so don't have access. I'm not sure if answering that question would violate the NDA, but I hope not.
In your Gist code, change the following line in AppDelegate.m (line 61):
[xmlTextFileData writeToURL:savePanel.URL atomically:YES];
to
[xmlTextFileData writeToURL:savePanel.URL atomically:NO];
Your code will then work.
The reason for this is likely the same reason for which it is necessary to have an existing (but empty) file that will contain the document-scoped bookmarks before calling [anURL bookmarkDataWithOptions]: While creating the NSData instance, the ScopedBookmarkAgent adds something (like a tag, probably an extended file attribute) to that file.
If you write data (i.e. the bookmark URLs) to that file atomically, in fact they're written not directly to the file but first to a temporary file that is renamed if the write operation was successful. It seems that the tag that has been added to the (empty, but existing) file that will contain the bookmarks is getting lost during this process of writing to a temporary file and then renaming it (and thereby likely deleting the original, empty file).
By the way: It shouldn't be necessary to create app-scoped bookmarks before passing the respective URLs to the xml file containing the document-scoped bookmarks.
Addition: com.apple.security.files.bookmarks.collection-scope has been renamed to com.apple.security.files.bookmarks.document-scope in 10.7.4.

find path of diskutil

I need to find out the path of the diskutil in Cocoa app. In terminal, if I type 'which diskutil', it gives me: '/usr/sbin/diskutil/'. Will it always be this path? If not, how can I programmatically find out the path as I need to use it with NSTask
Thanks
Try /usr/sbin/diskutil. If not found, get a list of the paths in $PATH:
[[[NSProcessInfo processInfo] environment] objectForKey:#"PATH"]
and look up the command diskutil on each of them using the NSFileManager. If the user has access, your application will find it.
An unusual case would be that the command is not on the default system location and the user messed with his PATH. Then you could hardcode a look up in certain locations (/opt/local/bin, /usr/local/bin, ...) or ask the user where it is.
You can have the env command do the work for you. Simply say “env diskutil blah blah blah”, and env will look for diskutil in the directories of the user's PATH and, if it finds it, run it with the remaining arguments.
For NSTask, this means you'll want to set your task's launch path to /usr/bin/env, and its arguments array to the command (diskutil) and its arguments; for example, [NSArray arrayWithObjects:#"diskutil", #"list", #"-plist", nil].
When you run such a task, it will run env, and that will exec diskutil in turn, so you will be indirectly running whatever diskutil the env program finds without having to find diskutil yourself.
You may want to use PRHTask, a replacement for NSTask that I wrote that automates this and some other things. With PRHTask, you can create the diskutil task in one line: [PRHTask taskWithProgramName:#"diskutil" arguments:#"list", #"-plist", nil]. (You'll still have to run it, of course; there are some other nice features you may want to turn on before you do, detailed on the wiki page.)