Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

MentalFabric

macrumors 6502
Original poster
Mar 10, 2004
372
25
Hi All,

I've been running command-line app binaries through an obj-c gui interface using NSTask and I've come across a stumbling block in that they sometimes need access to /dev/cu.*

This fails with NSTask and I believe it doesn't have permission to read that location.

What methods are possible to grant a binary bundled with an app these permissions ideally while retaining some level of control over the running process?
 
Last edited:

chown33

Moderator
Staff member
Aug 9, 2009
10,740
8,416
A sea of green
This fails with NSTask and I believe it doesn't have permission to read that location.
Please explain how you came to this belief. What symptoms? What evidence? What logic? Be specific.


I have long used sub-processes to access devices matching /dev/cu.*, and it works on at least 10.4 thru 10.8 versions of OS X. In particular, my code is running the 'sttty' command with certain args in order to configure a serial port. There is a specific order of operations, but it's worked fine for years.

FWIW, /dev/cu.* isn't "USB devices" in a general sense. It's specifically the "calling unit" of a serial-port, which is distinct from the "receiving unit" device whose name is typically /dev/tty.*.


Your post is essentially an XY Problem:
http://www.perlmonks.org/index.pl?node_id=542341
.. You want to do X, and you think Y is the best way of doing it.
.. Instead of asking about X, you ask about Y.

Here, Y is "obtain permissions" because you believe some extra permissions are needed. Since the permissions on /dev/cu.* are rw to everyone, please explain how that would be insufficient.

And also explain exactly what you're trying to do with the device, what libraries (if any) you're using, etc.

Are pipes involved at all in your use of NSTask?
 

MentalFabric

macrumors 6502
Original poster
Mar 10, 2004
372
25
OK when I run the binary from the terminal it works fine, but when I run it through the app it says 'all devices disabled'

I'm using a usb serial driver with the device too but it's connected through a machine's usb port.

I was assuming NSTask was put together without access to external devices due to the heightened security of post Lion OS X but I'd love to be corrected.

I am using a standardOutput pipe with the device - it's a modified version of Apple's TaskWrapper

The app itself is only using standard libraries (cocoa/foundation) but the binary uses curl, jansson, libusb

It's bitcoin mining - you can see most of the code here:
https://github.com/fabulouspanda/MacMiner
 

chown33

Moderator
Staff member
Aug 9, 2009
10,740
8,416
A sea of green
OK when I run the binary from the terminal it works fine, but when I run it through the app it says 'all devices disabled'

I suggest a test case. Run the '/bin/ls' command using NSTask. Give it the -l option, and the name of a specific known /dev/cu device. Is the output the same as if you issue the command in a Terminal shell window?

If /bin/ls works, then make a copy of /bin/ls into a directory you own. This is to get a version of 'ls' where you're the owner instead of root. Repeat the test.


Maybe the problem is the app sandbox. I'm guessing, because you haven't given any details on how the app is built nor what your sandbox/Gatekeeper settings are. The sandbox would likely prevent access to files (e.g. any device using an absolute pathname) outside the app's default sandbox entitlements.
 

MentalFabric

macrumors 6502
Original poster
Mar 10, 2004
372
25
I suggest a test case. Run the '/bin/ls' command using NSTask. Give it the -l option, and the name of a specific known /dev/cu device. Is the output the same as if you issue the command in a Terminal shell window?

If /bin/ls works, then make a copy of /bin/ls into a directory you own. This is to get a version of 'ls' where you're the owner instead of root. Repeat the test.


Maybe the problem is the app sandbox. I'm guessing, because you haven't given any details on how the app is built nor what your sandbox/Gatekeeper settings are. The sandbox would likely prevent access to files (e.g. any device using an absolute pathname) outside the app's default sandbox entitlements.

The app's not sandboxed/doesn't use entitlements so it shouldn't need com.apple.security.device.serial or any other provision if my thinking is correct… It's code-signed and distributed in an installer. Gatekeeper settings are default store/identified.

I tried your test and the output is the same as the Terminal both in bin and the app's resources folder. I should mention I only tried it on the bluetooth modem as the device I'm talking about testing with is in the possession of a journalist on a different continent to me… any more ideas I can try?
 

chown33

Moderator
Staff member
Aug 9, 2009
10,740
8,416
A sea of green
I tried your test and the output is the same as the Terminal both in bin and the app's resources folder. I should mention I only tried it on the bluetooth modem as the device I'm talking about testing with is in the possession of a journalist on a different continent to me… any more ideas I can try?

A likely conclusion from the successful 'ls -l' test is that it's not a permissions issue. If it were a permissions issue, then one would expect any program to exhibit the same problem. You might want to try 'cat' or 'stty' and see what they do, since 'ls' just reads the inode and doesn't try to open the device.

I assume you have the source to the program that's producing the error message "all devices disabled". It's unclear to me if this is the bundled app or the executable being run by NSTask.

So look at the source, find out exactly what triggers the error message, and look at that code. Or work back from the proximal cause of the error message, isolating other causes, and test each one of them separately.

In other words, Break It Down into the individual steps that lead to the "all devices disabled" message, and find out exactly which step is failing. It may be something completely non-obvious, such as it's expecting a char device for /dev/fd/1 and it fails without one (i.e. a pipe is not a char device).
Example shell cmd lines that illustrate the distinction:
Code:
ls -ld /dev/fd/1
ls -ld /dev/fd/1 | cat
 

MentalFabric

macrumors 6502
Original poster
Mar 10, 2004
372
25
it's the included binary returning that message, the strange thing is that i'm passing precisely the same arguments with NSTask as i would at the command line. I guess I'll have to get a bit more familiar with the code of the binary then. Thanks for your help.
 

MentalFabric

macrumors 6502
Original poster
Mar 10, 2004
372
25
Nope. FWIW I ended up being lazy and launching the binary via an app created then run .sh file running the same command and accessing it via it's API so I've no idea why it wasn't working through the gui.

I don't know if it's somehow linked to the fact that each 'argument' passed to NSTask will only take one argument ie when: -w 64 -I 6 is passed only -w takes effect and when: -I 6 -w 64 is passed only I takes effect but that shouldn't be the problem here as it was just one argument… both issues are mystifying to me!
 

chown33

Moderator
Staff member
Aug 9, 2009
10,740
8,416
A sea of green
I don't know if it's somehow linked to the fact that each 'argument' passed to NSTask will only take one argument ie when: -w 64 -I 6 is passed only -w takes effect and when: -I 6 -w 64 is passed only I takes effect but that shouldn't be the problem here as it was just one argument… both issues are mystifying to me!

Post your code. Specifically, post exactly what the NSTask args are.

I'm not sure what you mean by "just one argument". If you mean you passed one argument containing "-w 64 -I 6", then that's almost certainly wrong, because almost no programs support that.

NSTask is not a shell. It does not take a single string that contains "-w 64 -I 6 " and break it into sub-strings that are passed as argv. If you need to pass 4 args (and you may), then you must pass in 4 args.

NSTask is more similar to execv() or execve() than it is to system() or popen(). Read the man page of each to understand the difference. FWIW, also see the -c option to bash.
 

MentalFabric

macrumors 6502
Original poster
Mar 10, 2004
372
25
Here's the code:
Code:
NSString *oString = @"-o";
        NSString *poolString = [oString stringByAppendingString:asicPoolView.stringValue];
        NSString *uString = @"-u";
        NSString *userString = [uString stringByAppendingString:asicUserView.stringValue];
        NSString *pString = @"-p";
        NSString *passString = [pString stringByAppendingString:asicPassView.stringValue];
        
        
        if ([asicOptionsView.stringValue isEqual: @""]) {
            [asicOptionsView setStringValue:@"-S /dev/cu.usbserial-FTWILFLM"];
        }
        NSString *optionsString = asicOptionsView.stringValue;
        
        

        NSString *launchPath = @"/Applications/MacMiner.app/Contents/Resources/asicminer/bin/bfgminer";
        NSString *asicPath = @"/Applications/MacMiner.app/Contents/Resources/asicminer/bin/bfgminer";

        [self.asicOutputView setString:@""];

        NSString *startingText = @"Starting…";
        self.asicStatLabel.stringValue = startingText;

        asicTask=[[TaskWrapper alloc] initWithController:self arguments:[NSArray arrayWithObjects:launchPath, asicPath, poolString, userString, passString, optionsString, nil]];

        [asicTask startProcess];

arguments are taken from text fields and end up acting like
-o mining.server.com:3333 -u username -p password [various arguments which can, when on the CLI can take multiple commands including but not limited to -I 9 -w 64 which equates to intensity 9 work size 64 but when passed to 'optionsString' only the first of any number of arguments will take effect. Which is how I figure one argument given to NSTask must be 1 single argument (although that argument may contain a space…)
 

chown33

Moderator
Staff member
Aug 9, 2009
10,740
8,416
A sea of green
You're doing it wrong.

Every option such as "-a" and every parameter must be a separate string. Eliminate all the string-appending stuff, and append to a mutable array instead.

This means some of your code like:
Code:
            [asicOptionsView setStringValue:@"-S /dev/cu.usbserial-FTWILFLM"];
may have to be broken into two separate strings, as @"-S" and @"/dev/cu.usbserial-FTWILFLM". I say "may" because some programs accept options with parameter as "-Xparam" or "-X param", but others don't.

See example using NSTask here:
http://cocoadev.com/wiki/NSTask


If you're familiar with shell syntax, each string in the array is as if you quoted it in shell:
Code:
"command" "-x -y -z param" "-S /dev/cu.usbserial-FTWILFLM" "other things here".
I suggest you do some testing by writing a simple command-line tool that just prints its args (argv), one per line. Run that using NSTask or your TaskWrapper, and see what it tells you. It's a remarkably easy C program to write, yet it can be very informative.
 

MentalFabric

macrumors 6502
Original poster
Mar 10, 2004
372
25
That sounds like an interesting test, I'll give it a shot. I'm just a little confused by the fact that the created shell script will run like this:
/Applications/MacMiner.app/Contents/Resources/asicminer/bin/bfgminer -ostratum.bitcoin.cz:3333 -uuser -ppass -S /dev/cu.usbserial-FTWILFLM

created like this:
Code:
    NSString *oString = @"-o ";
    NSString *poolString = [oString stringByAppendingString:asicPoolView.stringValue];
    NSString *uString = @"-u ";
    NSString *userString = [uString stringByAppendingString:asicUserView.stringValue];
    NSString *pString = @"-p ";
    NSString *passString = [pString stringByAppendingString:asicPassView.stringValue];
    
    
    if ([asicOptionsView.stringValue isEqual: @""]) {
        [asicOptionsView setStringValue:@"-S /dev/cu.usbserial-FTWILFLM"];
    }
    NSString *optionsString = asicOptionsView.stringValue;
    
    
NSArray *apiArray = [NSArray arrayWithObjects: poolString, userString, passString, optionsString, nil];


    NSString * result = [apiArray componentsJoinedByString:@" "];
    NSString *startBFGCommand = @"/Applications/MacMiner.app/Contents/Resources/asicminer/bin/bfgminer ";
    NSString *fullCommand = [startBFGCommand stringByAppendingString:result];
    NSString *plusAPI = [fullCommand stringByAppendingString:@" --api-listen --api-allow W:0/0"];

    [plusAPI writeToFile:@"/Applications/MacMiner.app/Contents/Resources/startASICMining.sh"
                          atomically:YES
                            encoding:NSASCIIStringEncoding
                               error:nil];
    

    [NSTask launchedTaskWithLaunchPath:@"/bin/bash" arguments: [NSArray arrayWithObjects:@"/Applications/MacMiner.app/Contents/Resources/startASICMining.sh", nil]];

but if created for NSTask as in the previous post, it won't run on -S /dev/cu.... but will run with -w 64 or even -w 64 -I 9 (but only taking the first argument)
 
Last edited:

chown33

Moderator
Staff member
Aug 9, 2009
10,740
8,416
A sea of green
Details are important.

This code:
Code:
    NSString *oString = @"-o ";
    NSString *poolString = [oString stringByAppendingString:asicPoolView.stringValue];
    NSString *uString = @"-u ";
    NSString *userString = [uString stringByAppendingString:asicUserView.stringValue];
is inconsistent with this "shell script":
Code:
/Applications/MacMiner.app/Contents/Resources/asicminer/bin/bfgminer -ostratum.bitcoin.cz:3333 -uuser -ppass -S /dev/cu.usbserial-FTWILFLM

Note that the Obj-C code is putting a space between each option (e.g. -o) and its following param. Then notice that the "shell script" has no spaces between any option and its following param.

As I previously noted, some programs accepts options-with-params in the form "-Oparam" (i.e. a single concatenated string, where O is a single option-character). If the shell script is actually as posted, then somehow you've done something that removes the spaces, thus potentially making the args acceptable to the child process. Or maybe what you posted wasn't exactly what the shell got. Hard to say without seeing the actual shell script file, with no alterations that might occur from copy/paste (i.e. zip it and attach to a post).

I don't know if your 'bfgminer' actually accepts args like "-Oparam" or not. That's one reason I suggested writing a specific test program, so you can more easily inspect exactly what's happening, i.e. debug it.

If you want to find out exactly what args are being given to a program, you need to run a program that tells you those args, exactly Debugging is the process of confirming an expectation. You have an expectation, so do something to confirm it. Guessing is guessing, not confirming.

If you're wondering why the shell would split strings at spaces into separate args, that's its job. It takes a command-line with spaces, quoting, etc., performs various substitutions and parsing, then passes the parsed and split args to another program. Again, a simple command-line tool written in C would do wonders at illustrating the net result of what the shell does before running the program.
 

MentalFabric

macrumors 6502
Original poster
Mar 10, 2004
372
25
ah sorry that was copied and pasted but it hadn't been run locally for a while as the actual testing is being done by someone else with the device. Hence I tried another approach as they're a journalist and I don't want to waste too much of their time.

bfgminer does run with either -ostratum.bitcoin.cz:3333 or -o stratum.bitcoin.cz:3333 and -w 64 or -w64 at the terminal, but the actual latest output to the shell script is:

/Applications/MacMiner.app/Contents/Resources/asicminer/bin/bfgminer -o stratum.bitcoin.cz:3333 -u user -p pass -S /dev/cu.usbserial-FTWILFLM --api-listen --api-allow W:0/0

to be honest because it works I'm probably going to leave perfecting it until I return to another part of the app that's still set up by the former method as I'm spending all my spare coding time on the interface for this device at the moment.. trying to figure out how to make various pieces of data form an aesthetically pleasing output :/
 

MentalFabric

macrumors 6502
Original poster
Mar 10, 2004
372
25
FYI in the end I took your advice and fed it with a mutable array which worked fine, thanks for your help!
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.