1. Welcome to the new MacRumors forums. See our announcement and read our FAQ

[Resolved] Accessing usb devices through a bundled binary

Discussion in 'Mac Programming' started by MentalFabric, May 10, 2013.

  1. MentalFabric, May 10, 2013
    Last edited: May 14, 2013

    macrumors 6502

    MentalFabric

    #1
    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?
     
  2. macrumors 603

    #2
    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?
     
  3. macrumors 6502

    MentalFabric

    #3
    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
     
  4. macrumors 603

    #4
    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.
     
  5. macrumors 6502

    MentalFabric

    #5
    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?
     
  6. macrumors 603

    #6
    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
    
     
  7. macrumors 6502

    MentalFabric

    #7
    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.
     
  8. macrumors 6502

    Madd the Sane

    #8
    Do you have sandboxing enabled?
     
  9. macrumors 6502

    MentalFabric

    #9
    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!
     
  10. macrumors 603

    #10
    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.
     
  11. macrumors 6502

    MentalFabric

    #11
    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…)
     
  12. macrumors 603

    #12
    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.
     
  13. MentalFabric, May 12, 2013
    Last edited: May 12, 2013

    macrumors 6502

    MentalFabric

    #13
    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)
     
  14. macrumors 603

    #14
    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.
     
  15. macrumors 6502

    MentalFabric

    #15
    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 :/
     
  16. macrumors 6502

    MentalFabric

    #16
    FYI in the end I took your advice and fed it with a mutable array which worked fine, thanks for your help!
     
  17. macrumors 603

    #17

Share This Page