3

I'm trying to use pipes to handle a command that requires multiple inputs, but not quite sure how to do it. Here's a snippet of what I'm trying to do. I know how to handle the first input, but I'm at lost as to piping in the second input -newstdinpass

NSTask *task = [[NSTask alloc] init];
NSPipe *pipe = [NSPipe pipe];

[task setLaunchPath: @"/bin/sh"];
[task setArguments: [NSArray arrayWithObjects: @"-c", @"/usr/bin/hdiutil chpass -oldstdinpass -newstdinpass /path/to/dmg", nil]];
[task setStandardInput:pipe];
[task launch];

[[pipe fileHandleForWriting] writeData:[@"thepassword" dataUsingEncoding:NSUTF8StringEncoding]];
[[pipe fileHandleForWriting] closeFile];

[task waitUntilExit];
[task release];

So I know using hdiutil in this manner is a bit of a hack, but in terms of pipes, am I going about it the correct way?

Thanks.

UPDATE: In case others are wondering about this, a quick solution to my problem is to pass in a null-terminated string as Ken Thomases had pointed out below. Use [[NSString stringWithFormat:@"oldpass\0newpass\0"] dataUsingEncoding:NSUTF8StringEncoding] into the pipe. Now, still need to learn how to bridge multiple NSTasks with pipes...

Daniel
  • 648
  • 1
  • 8
  • 14

2 Answers2

3

You can create multiple NSTasks and a bunch of NSPipes and hook them together, or you can use the sh -c trick to feed a shell a command, and let it parse it and set up all the IPC.

Example :

NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: @"/bin/sh"];

NSArray *arguments;
arguments = [NSArray arrayWithObjects: @"-c",
                     @"cat /usr/share/dict/words | grep -i ham | rev | tail -5", nil];
[task setArguments: arguments];
// and then do all the other jazz for running an NSTask.

Reference : http://borkware.com/quickies/one?topic=nstask


Now, as for a "proper" command execution function, here's one I'm usually using...

Code :

/*******************************************************
 *
 * MAIN ROUTINE
 *
 *******************************************************/

- (void)runCommand:(NSString *)cmd withArgs:(NSArray *)argsArray
{
    //-------------------------------
    // Set up Task
    //-------------------------------

    if (task) { [self terminate]; }

    task = [[NSTask alloc] init];
    [task setLaunchPath:cmd];
    [task setArguments:argsArray];

    [task setStandardOutput:[NSPipe pipe]];
    [task setStandardError:[task standardOutput]];

    //-------------------------------
    // Set up Observers
    //-------------------------------

    [PP_NOTIFIER removeObserver:self];
    [PP_NOTIFIER addObserver:self 
                    selector:@selector(commandSentData:) 
                        name: NSFileHandleReadCompletionNotification 
                      object: [[task standardOutput] fileHandleForReading]];

    [PP_NOTIFIER addObserver:self 
                    selector:@selector(taskTerminated:) 
                        name:NSTaskDidTerminateNotification 
                      object:nil];

    //-------------------------------
    // Launch
    //-------------------------------
    [[[task standardOutput] fileHandleForReading] readInBackgroundAndNotify];

    [task launch];
}

/*******************************************************
 *
 * OBSERVERS
 *
 *******************************************************/

- (void)commandSentData:(NSNotification*)n
{
    NSData* d;
    d = [[n userInfo] valueForKey:NSFileHandleNotificationDataItem];

    if ([d length])
    {
        NSString* s = [[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding];

        NSLog(@"Received : %@",s);
    }

    [[n object] readInBackgroundAndNotify]; 
}

- (void)taskTerminated:(NSNotification*)n
{
    [task release];
    task = nil;
}
Dr.Kameleon
  • 22,532
  • 20
  • 115
  • 223
  • 1
    Thanks for your code. I'm still a little confused on how to hook together multiple tasks together for the same command, but I will take a slab at it. – Daniel Apr 19 '12 at 05:58
  • 1
    Use one pipe as the standard output of one task and the standard input of the next. Then another pipe hooking the output of the second task to the input of the third. Etc. If you want to feed input to the whole chain, make a pipe and set it up as the input to the first task and write to it as you've already done. If you want output from the whole chain, make a pipe and set it as the standard output of the last task in the chain and read from it. – Ken Thomases Apr 20 '12 at 08:58
  • 1
    you have saved the day – Charlton Provatas Dec 15 '17 at 01:47
2

Your use of the pipe looks correct to me.

I'm not sure why you're using /bin/sh. Just set up the NSTask with its launch path being @"/usr/bin/hdiutil" and with its arguments being an array of @"chpass", @"-oldstdinpass", @"-newstdinpass", and @"/path/to/dmg". This is much safer. For example, what if the path to the dmg contains a character the shell would interpret, like $?

Unless you specifically want to take advantage of a shell feature, don't use the shell.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • This is good advice, but it still doesn't address how you can send both the `-oldstdinpass` value *and* the `-newstdinpass` value via a single `stdin` pipe. – Rob Keniger Apr 19 '12 at 04:09
  • @Ken, that is good advice. I actually had weird issues with `NSTask` not working correctly with the launch path set to `@"/usr/bin/hdiutil"` and passing in the `create` verb as an argument. I'll try out the non-shell way for this. – Daniel Apr 19 '12 at 05:49
  • 2
    @RobKeniger, I hadn't actually looked at the details of the `hdiutil` command and how it takes the password. It claims to take them in order, null-terminated (not newline-terminated). So, it's feasible to write both on the pipe before closing it. – Ken Thomases Apr 19 '12 at 14:04
  • 1
    A NULL terminated string seems to work! @Rob, you can pass `[[NSString stringWithFormat:@"oldpass\0newpass\0"] dataUsingEncoding:NSUTF8StringEncoding]` into the pipe. – Daniel Apr 19 '12 at 21:15