Saturday 9 June 2012

libssh2 - Executing several commands

As I posted previously, you have to keep in mind that there won't be a context encompassing these commands, i.e., there won't be any state kept between executions.

We'll be modifying this libssh2 example. Our goal will be to refactor the channel life cycle, placing it in a function, so we can invoke it several times.

We'll start by delimiting this life cycle in the example. The first thing we want is the channel's creation:
    /* Exec non-blocking on the remove host */
    while( (channel = libssh2_channel_open_session(session)) == NULL &&
           libssh2_session_last_error(session,NULL,NULL,0) == LIBSSH2_ERROR_EAGAIN)
    {
        waitsocket(sock, session);
    }


And its destruction:
    while( (rc = libssh2_channel_close(channel)) == LIBSSH2_ERROR_EAGAIN )
        waitsocket(sock, session);
    (...)
    libssh2_channel_free(channel);
    channel = NULL;


My first idea was that maybe you could close the channel, but not free it, as freeing is mostly concerned with releasing local resources allocated during channel creation/operation. However, I found no way to open a channel other than libssh2_channel_open_session(), i.e., I found no way to reopen a channel (the ssh RFCs talk about channel reuse, but not channel reopening), so I'm assuming calling libssh2_channel_free() is required.

Having determined the code that defines the channel life cycle, we may now put it in a separate function, which I've called execcmd().

As you can see, the code is basically the same as you have on the libssh2 example. However, now that we have it in a separate function, we can easily invoke it several times.

E.g., we can change the fourth command-line argument, which is the command to run, and turn into a filename. We'll assume each line on the file is a command to run on a remote host, and each command has a max lenght of 80 chars.

Then, we add some simple code to open the file and call our execcmd() function for each line:

    cmd_file = fopen(filename, "rt");
    if (cmd_file == NULL)
    {
        perror("Error opening file");
        goto shutdown;
    }

    /* fgets will read the '\n', but that shouldn't be a problem
       HOWEVER, LINES WITH 80+ CHARS WILL BE A PROBLEM */
    while (fgets(command, 81, cmd_file) != NULL)
    {
        execcmd(command, sock, session);
    }

    fclose(cmd_file);


This code would go right after this on the example:

#if 0
    libssh2_trace(session, ~0 );
#endif


So, we follow the exact same sequence as the example, up until we're authenticated. Then, we open the file and call execcmd() for each line. Once that's done, we resume following the original example.

You can see the whole thing here. Like I've said, the actual work with libssh2 was already done in the original example, I just did a little bit of refactoring.

I've suggested this could be added to the examples in libssh2, because I've seen a fair amount of people asking for pointers on how to run several commands on a remote host, but I haven't actually got a "yes" or "no".

Anyway, I hope you find it useful.

No comments:

Post a Comment