Menu

Filelist example explained

Explained filelist example

The better way to get started is some sort example code. For API it's better to use manpages going with distribution.
Sure, not all features can be covered by simple example, however it's a good point to start with.

What this example do

This example is a simple client server program list directory contents thru the messages.

Where to find it

Under the examples/ directory:
* filelistc.c
* filelistd.c

To get it compile add option --enable-build-examples

Server program

To get server works you need a minimal set of callback functions.

Function to return an RPC lists set, in this example we don't implement any access restrictions for available functions. It can be done like the following:

/* firstly we need to take an rpc calls data,
 * sxmp uses usrtc from libtdata to store it
 */
static usrtc_t *fulist;

/* ok, here we will define out sxmp specific functions */

/*
 * RPC list contain groups of RPC calls called channel types
 * for each sxmp link depends on access rules you can change
 * available channel types.
 * To do so you must provide a special callback returning this list.
 * Since we haven't any access restrictions in our example, just
 * create a fake.
 */
static usrtc_t *__rettlist(sxlink_t *l)
{
  return fulist;
}

In real world you are able to filter this set.

SXMP has a feature to make an additional test of the client certificate and you need to provide a function to do this.

/*
 * Each sxmp link validated by certificate ID,
 * i.e. when basic checking is done, you can also
 * review the certificate ID, for example you need
 * to disable certificate for a time being.
 *
 * Here we don't have those checks, just always accept
 */
static int __validate_sslpem(sxlink_t *l)
{
  return 0;
}

As explained 0 mean is ok, 1 means certificate is blocked, it might be useful if you need to block certificates for a time being.

Another feature of SXMP is a login/password checking, and you need to provide a callback for this also:

/*
 * To be more paranoic, sxmp also allow to
 * check up username and password.
 * We don't overview this tuff here, just
 * always allow.
 */
static int __secure_check(sxlink_t *l)
{
  return SXE_SUCCESS;
}

Returning SXE_SUCCESS you will allow the link initiation, otherwise other SXMP error returned will be passed to the client and link will be closed.

As many libraries SXMP needs some kind of initialization, this can be done via sxmp_init() function easy as you can find in example:

sxmp_init();

Each link has a hub, hub is a template for a link, it contain x.509 root certificate, callbacks and other stuff used to initiate links. That mean you need to create a hub first.

sxhub_t *sxmphub = sxhub_create();

To make encryption works hub needs a certificates pointed by a file name, this is done via the following code:

/* set working certificates */
  opt = sxhub_setsslserts(sxmphub, rootca, cert, cert);
  if(opt) {
    fprintf(stderr, "Subsystem init failed (set SSL x.509 pems): %d\n", opt);
    return opt;
  }

Well, did you forgot about callbacks pointed early? Here the code setting this callback for hub:

/* set important callbacks to do the security checking */
  sxhub_set_authcheck(sxmphub, __secure_check);
  sxhub_set_sslvalidate(sxmphub, __validate_sslpem);

The next step is to create a proper RPC list set and assign a callback mentioned early related to RPC.
SXMP groups RPC calls by the so-called channels it was made to create a functional restriction, channel is an entry with some amount of RPC functions determined by text description and type ID.
Thw following code create a proper list, adds a channel and functions for this channel:

/* ok, now we need to construct RPC lists (channels) */
  if(!(fulist = malloc(sizeof(usrtc_t)))) {
    fprintf(stderr, "Cannot allocate memory for RPC lists\n Failure.\n");
    return ENOMEM;
  }
  opt = sxmp_rpclist_init(fulist);
  if(opt) {
    fprintf(stderr, "Failed to init rpc list\n Failure.\n");
    return opt;
  }

  /* we will add one channel with type id READONLY_CHANNEL for read only operations */
  opt = sxmp_rpclist_add(fulist, READONLY_CHANNEL, "Read only operations", NULL);
  if(opt) {
    fprintf(stderr, "Failed to add typed RPC channel\n Failure.\n");
    return opt;
  }
  opt = sxmp_rpclist_add(fulist, 1, "Public", NULL);
  if(opt) {
    fprintf(stderr, "Failed to add typed RPC channel\n Failure.\n");
    return opt;
  }

  /* ok, let's add stream functions */
  opt = sxmp_rpclist_add_function(fulist, READONLY_CHANNEL, "dir-open", __dir_open);
  if(opt) {
  __fail:
    fprintf(stderr, "Failed to add functions to typed RPC channel\n Failure.\n");
    return opt;
  }
  opt = sxmp_rpclist_add_function(fulist, READONLY_CHANNEL, "dir-read", __dir_read);
  if(opt) goto __fail;
  opt = sxmp_rpclist_add_function(fulist, READONLY_CHANNEL, "dir-close", __dir_close);
  if(opt) goto __fail;

  /* ok, setup it */
  sxhub_set_rpcvalidator(sxmphub, __rettlist);

The last line is our early added callback. Now all you need is create a loop and write your own RPC function implementation.
SXMP can accept the link and in case of success library creates a thread poll serving the link and you don't need to care about that, it will also clean up all the stuff in case of link broking or closing (there are a more callback might be assigned to track it and destroy your own data assigned for link).

The next code lines implements a simple loop:

  /* now we're ready to run the listen process */
  int srv = openlistener_socket(port);

  while(1) {
    struct sockaddr_in addr;
    socklen_t len = sizeof(addr);
    sxlink_t *link;

    int client = accept(srv, (struct sockaddr*)&addr, &len); /* accept connection as usual */

    link = sxlink_master_accept(sxmphub, client, NULL);

    if(!link) fprintf(stderr, "Cannot create sxmp link (%d)\n", errno);
  }

Accept the connection as usually and give a file descriptor to the SXMP accept function, this function will initiate encryption, will call your own callback and create a link.

Finally, to avoid memory leaks on program close call:

 sxhub_destroy(sxmphub);

  sxmp_finalize();

Well, but what about RPC functions implementation? It can be found in this file also, here the explanation of one of them:

static int __dir_close(void *m, sexp_t *sx)
{
  sexp_t *lsx = NULL;
  sxmsg_t *msg = (sxmsg_t *)m;

The first argument is a pointer to message, the second is a pointer to S-expression already parsed by SXMP library i.e. it ready to works on.

Now we will parse it:

if (sexp_list_cdr(sx, &lsx) || !sx->list->next || !sx->list->next->val
      || (0 >= (stid = atoi(sx->list->next->val) )) )
  {
    fprintf(stderr, "Invalid protocol\n");
    return sxmsg_return(msg, SXE_BADPROTO);
  }

In case of error just return an error code, take a note - message and S-expression will be destroyed by SXMP library itself.

In case of success:

  return sxmsg_return(msg, SXE_SUCCESS);
}

Here the whole function:

static int __dir_close(void *m, sexp_t *sx)
{
  sexp_t *lsx = NULL;
  sxmsg_t *msg = (sxmsg_t *)m;
  int stid = -1;
  datastream_t *item;
  usrtc_node_t *node;

  if (sexp_list_cdr(sx, &lsx) || !sx->list->next || !sx->list->next->val
      || (0 >= (stid = atoi(sx->list->next->val) )) )
  {
    fprintf(stderr, "Invalid protocol\n");
    return sxmsg_return(msg, SXE_BADPROTO);
  }

  /* get stream item */
  pthread_rwlock_rdlock(&_lock);
  if ( !(node = usrtc_lookup(_rd_streams, &stid)) ) {
    pthread_rwlock_unlock(&_lock);
    return sxmsg_return(msg, ENOENT);
  }
  pthread_rwlock_unlock(&_lock);

  pthread_rwlock_wrlock(&_lock);
  item = usrtc_node_getdata(node);
  closedir(item->dp);
  usrtc_delete(_rd_streams, node);
  FREE(item);
  pthread_rwlock_unlock(&_lock);

  return sxmsg_return(msg, SXE_SUCCESS);
}

that's all.
To read complete source code check out the links provided before (actual source code might be changed and it's better to check more actual code for this case).

Client program

For a client side to make SXMP happy there are also few minimal stuff required.

Initiation process make a call for each allowed channel and you need to provide a callback to check if it's supported or no. It might be useful to determine supported features, activate plugins etc...

Define it:

/*
 * This function might be used to check supported RPC calls
 * i.e. for each channel type id this callback is called.
 * Whereas the place to check it with ID and description.
 * Should return SXE_SUCCESS if supported.
 */
static int __rpcscheck_onclient(sxlink_t *l, int ch_tid, char *description)
{
  return SXE_SUCCESS;
}

Since we don't do anything with that, return SXE_SUCCESS i.e. told SXMP all is fine and go on.

As on the server we need to initialize sxmp library and create a hub on the client:

/* initialize sxmp first */
  sxmp_init(); /* init library */
  lhub = sxhub_create(); /* create sxhub for link creation */
  if(!lhub) return ENOMEM;

  i = sxhub_setsslserts(lhub, rootca, cert, cert);
  if(i) return i;

To make code looking more pretty the following was structure was defined to make a link:

struct _remote_host {
  char *root_x509;
  char *user_x509;
  char *hostname;
  char *login;
  char *password;
  int port;
  sxhub_t *hub;
  sxlink_t *link;
};

And a function create a link i.e. connect to the server. It will be explained a little bit.

Firstly, assign our callback for channel:

static int __remote_host_connect(struct _remote_host *rh)
{
  int r = 0;

  sxhub_set_channelcall(rh->hub, __rpcscheck_onclient);

Next step is a create link:

 /* here we go, connect to the hist i.e. create a link to it */
  rh->link = sxlink_connect(rh->hub, rh->hostname, rh->port, rh->user_x509,
                            rh->login, rh->password);

This function will create a link to the server with desired parameters. But no thread poll will be created in this case, the link will be closed if the process will die.

The whole function:

static int __remote_host_connect(struct _remote_host *rh)
{
  int r = 0;

  sxhub_set_channelcall(rh->hub, __rpcscheck_onclient);

  /* here we go, connect to the hist i.e. create a link to it */
  rh->link = sxlink_connect(rh->hub, rh->hostname, rh->port, rh->user_x509,
                            rh->login, rh->password);
  if(!rh->link) r = errno;

  return r;
}

The next step is a channels and messages ....
This example has a function that will:
1. open channel
2. open something via the message
3. read by the message data
4. close somthing
5. close the channel
The whole function:

static int __remote_ls(FILE *fso, struct _remote_host *rh, const char *opts)
{
  char *remote_rpc;
  size_t remote_rpc_len;
  sxmsg_t *msg = NULL;
  sxchnl_t *rpc_channel = NULL;
  sexp_t *sx, *isx, *iisx;
  char *remote_reply = NULL, *dname, *dtype;
  int rrc = 0, stream_id = 0, i, ii;

  /* ok determine the lenght */
  remote_rpc_len = strlen("dir-open") + 12*sizeof(char) +
    (opts ? strlen (opts) : strlen("./"));
  if(!(remote_rpc = malloc(remote_rpc_len))) return ENOMEM;
  else remote_rpc_len = snprintf(remote_rpc, remote_rpc_len,
                                 "(dir-open \"%s\")", opts ? opts : "./");

  /* firstly open the required channel with required type */
  if(!(rpc_channel = sxchannel_open(rh->link, READONLY_CHANNEL))) {
    rrc = errno;
    goto __fini;
  }

  rrc = sxmsg_send(rpc_channel, remote_rpc, remote_rpc_len, &msg);
  if(rrc == SXE_RAPIDMSG || rrc == SXE_REPLYREQ) {
    remote_reply = strdup(sxmsg_payload(msg));

    if(rrc == SXE_RAPIDMSG) sxmsg_clean(msg);
    else rrc = sxmsg_return(msg, SXE_SUCCESS);
  }

  if(!remote_reply && (rrc != 0 || rrc != SXE_SUCCESS)) goto __fini;
  else sx = parse_sexp(remote_reply, strlen(remote_reply));

  if(!sx) { rrc = EINVAL; goto __fini; }

  /* ok, now we will take a pass with our received S expression */
  rrc = EINVAL;
  SEXP_ITERATE_LIST(sx, isx, i) {
    if(isx->ty == SEXP_LIST) stream_id = -1;
    if(!i && strcmp(isx->val, "dir-stream")) stream_id = -1;

    if(i == 1 && !stream_id) stream_id = atoi(isx->val);
  }

  /* free all the stuff */
  destroy_sexp(sx);
  free(remote_reply); remote_reply = NULL;

  if(stream_id < 0) goto __fini;
  else rrc = SXE_SUCCESS;

  free(remote_rpc);

  /* now we're ready to read contents from the remote host */
  remote_rpc_len = strlen("dir-read") + sizeof(char)*32;
  if(!(remote_rpc = malloc(remote_rpc_len))) { rrc = ENOMEM; goto __fini; }
  else remote_rpc_len = snprintf(remote_rpc, remote_rpc_len, "(dir-read %d)", stream_id);

  /* print header */
  fprintf(fso, "%-24s | %-12s\n", "Name", "Type");
  fprintf(fso, "---------------------------------------\n");
  while(1) {
    rrc = sxmsg_send(rpc_channel, remote_rpc, remote_rpc_len, &msg);

    if(rrc == SXE_RAPIDMSG || rrc == SXE_REPLYREQ) {
      remote_reply = strdup(sxmsg_payload(msg));

      if(rrc == SXE_RAPIDMSG) sxmsg_clean(msg);
      else rrc = sxmsg_return(msg, SXE_SUCCESS);
    } else goto __fini;

    if(!remote_reply) goto __fini;
    else if(!(sx = parse_sexp(remote_reply, strlen(remote_reply)))) {
      rrc = ENOMEM;
      goto __fini;
    }

    rrc = EINVAL;
    SEXP_ITERATE_LIST(sx, isx, i) {
      if(!i && isx->ty == SEXP_LIST) { rrc = EINVAL; goto __fini; }
      if(!i && !strcmp(isx->val, "dir-end")) {
        rrc = __close_dir_stream(rpc_channel, stream_id);
        goto __fini;
      }

      if(i && isx->ty != SEXP_LIST) { rrc = EINVAL; goto __fini; }
      else if(i) {
        SEXP_ITERATE_LIST(isx, iisx, ii) {
          if(!ii) dname = iisx->val;
          else dtype = iisx->val;
        }
        fprintf(fso, "%-24s | %-12s\n", dname, dtype);
      }
    }

    /* free it */
    destroy_sexp(sx);
    free(remote_reply); remote_reply = NULL;
  }

 __fini:
  if(remote_reply) free(remote_reply);
  if(remote_rpc) free(remote_rpc);
  i = SXE_SUCCESS;
  if(rpc_channel) i = sxchannel_close(rpc_channel);

  if(rrc == SXE_SUCCESS && i == SXE_SUCCESS) rrc = 0;

  return rrc;
}

That's all.

For each SXMP function you can find a manual page it will also help you to start.


Related

Wiki: Home