Port Legacy C App

This topic describes how to get a POSIX/Linux legacy app written in C running on a Legato device and using Legato APIs to access services like SMS, SIM, voice calling, and data connections.

Note
The examples in this topic use the command-line tools. You will need to have your shell configured correctly by installing your dev environment with Leaf (the environment will already be configured) and executing the following commands:
$ leaf shell
$ export PATH=$PATH:$LEGATO_ROOT/bin
$ export CC=$WP76XX_TOOLCHAIN_DIR/arm-poky-linux-gnueabi-gcc

The examples also use a Sierra Wireless WP76XX target. If you are using a different target, substitute your target name wherever you see WP76XX.

The examples use IP address 192.168.2.2. Change it if your target IP uses a different address.

Cross-Build

The most basic way to get your legacy app running on a Legato target device is to recompile it using the provided cross-build tool chain and copy it onto the device using a tool like scp.

1. Build a legacy app executable for your target device using the cross tool chain provided.

$ $CC -o legacyProgram main.c -I ./ -I${LEGATO_ROOT}/framework/include -I${LEGATO_ROOT}/build/${LEGATO_TARGET}/framework/include --sysroot=${LEGATO_SYSROOT}

2. Copy the legacy app executable onto the target using a tool like scp:

$ scp legacyProgram 192.168.2.2:
 
legacyProgram 100% 9366 9.2KB/s 00:00

3. Run the legacy app from the target command-line:

root@swi-mdm9x15:~# ./legacyProgram
Hello world.

Use Legato App Management Tools

By bundling your program as a Legato app, you gain access to a wealth of valuable features:

  • Tools for installing and removing apps and checking app status on the target and on the development host.
  • Remote (over-the-air) installation, upgrade, removal, start, stop.
  • Autonomous fault recovery (automatic restart of process, whole app, or whole device) in the field.
  • Automatic mandatory access control (MAC) configuration.
  • Optional application sandboxing.
  • Optional application signing and/or encryption.

1. Create a .adef file (e.g., legacyProgram.adef) that bundles the cross-compiled executable into an application:

// Disable the sandbox security to make things a little easier.
sandboxed: false
 
// Put the cross-compiled legacy program in the app's bin directory.
// [x] = make it executable.
bundles:
{
file:
{
[x] legacyProgram /bin/
}
}
 
// Tell the Supervisor to start this program when the application is started.
processes:
{
run:
{
( legacyProgram )
}
}

2a. Run mkapp to generate an application bundle for your target:

$ mkapp -t wp76xx legacyProgram.adef

2b. Alternatively you can add the program to your system and build and include your app in your system update for your target.

Add your app to your systems .sdef file in the apps section.

apps:
{
$LEGATO_ROOT/apps/legacyProgram
}

Run mksys to generate the system bundle for your target:

$ mksys -t wp76xx mySystem.sdef

3a. If you have created an app install bundle, install the app bundle on the target using update:

$ update legacyProgram.wp76xx.update
Installing application 'legacyProgram' from file 'legacyProgram.wp76xx'.
Installing app 'legacyProgram'...
Created user 'applegacyProgram' (uid 1011, gid 1011).
DONE

3b. If you have added your app to your system, install the system bundle on the target using update:

$ update mySystem.wp76xx.update $DEST_IP
Unpacking package: 100% ++++++++++++++++++++++++++++++++++++++++++++++++++
Unpacking package: 100% ++++++++++++++++++++++++++++++++++++++++++++++++++
Unpacking package: 100% ++++++++++++++++++++++++++++++++++++++++++++++++++
Applying update: 100% ++++++++++++++++++++++++++++++++++++++++++++++++++
SUCCESS

4. From the target's command line, use app start to run the program:

$ ssh root@192.168.2.2
Linux swi-mdm9x15 3.4.91-8fcd3d08ac_7e84772e18 #1 PREEMPT Wed Jun 3 23:59:46 PDT 2015 armv7l GNU/Linux
root@swi-mdm9x15:~# app start legacyProgram
Starting app 'legacyProgram'...
DONE

5. Look for the program output in the target device's log using logread.

Note
You can filter the log to show just your program's output by piping the output from logread into grep.
root@swi-mdm9x15:~# logread | grep legacyProgram
Jan 16 04:00:53 swi-mdm9x15 user.info Legato: INFO | legacyProgram[27271] | Hello world.

Use Legato Services

Many Legato services are provided through IPC-based APIs. The ifgen tool can generate the IPC code for you, along with a header (.h) file that you can #include to gain access to the service.

Here is how to use a Legato modem service API (e.g., le_info). The source code for this example can be found in apps/sample/legacy/useLegatoApi/.

1. Run ifgen to generate the .c and .h files you need to access the interface.

  • Use the --gen-interface --gen-common-interface options to generate the interface headers (le_info_interface.h and le_info_common.h).
  • Use the --gen-client --gen-common-client options to generate the client-side IPC implementation (le_info_client.c and le_info_commonclient.c).
  • Use the --gen-local option to generate definitions that are shared by both the client side
  • Use the --gen-messages option to generate client-server messages definitions and server side IPC code (le_info_messages.h).
ifgen --gen-interface --gen-common-interface --gen-client --gen-common-client --gen-local --gen-messages $LEGATO_ROOT/interfaces/modemServices/le_info.api

2. Include legato.h in your program.

#include "legato.h"

3. Include the API's generated "interface" header file.

4. Connect to the service by calling le_info_ConnectService() (using legacy main function).

int main(int argc, char** argv)
{
 
return EXIT_SUCCESS;
}
Note
At runtime, if the le_info service isn't available, this will block until it becomes available. In the meantime, you'll see your app in the WAITING CLIENTS list if you run sdir list.

5. Add a call to one of the le_info API functions (e.g., le_info_GetDeviceModel() ).

int main(int argc, char** argv)
{
 
char deviceModelStr[256];
 
le_result_t result = le_info_GetDeviceModel(deviceModelStr, sizeof(deviceModelStr));
 
if (result == LE_OK)
{
printf("Hello world from %s.\n", deviceModelStr);
}
else
{
printf("Failed to get device model. Error = '%s'.\n", LE_RESULT_TXT(result));
}
 
return EXIT_SUCCESS;
}
Note
For hand-written C code, you need to use \n to terminate messages as stdout only displays info in the buffer after it reaches a new line.

6. Compile and link your executable with the code generated by ifgen:

$ $CC -c main.c -I ./ -I${LEGATO_ROOT}/framework/include -I${LEGATO_ROOT}/build/${LEGATO_TARGET}/framework/include --sysroot=${LEGATO_SYSROOT}
$ $CC -c le_info_commonclient.c -I ./ -I${LEGATO_ROOT}/framework/include -I${LEGATO_ROOT}/build/${LEGATO_TARGET}/framework/include --sysroot=${LEGATO_SYSROOT}
$ $CC -c le_info_client.c -I ./ -I${LEGATO_ROOT}/framework/include -I${LEGATO_ROOT}/build/${LEGATO_TARGET}/framework/include --sysroot=${LEGATO_SYSROOT}
$ $CC -o legacyProgram main.o le_info_client.o le_info_commonclient.o -L${LEGATO_ROOT}/build/${LEGATO_TARGET}/framework/lib --sysroot=${LEGATO_SYSROOT} -llegato -lpthread -lrt

7. Create an extern: requires: section for the le_info interface in the .adef file:

extern:
{
requires:
{
le_info = $LEGATO_ROOT/interfaces/modemServices/le_info.api
}
}

8. Specify which instance of the le_info service your app should use by creating a binding in the .adef file:

bindings:
{
.le_info -> modemService.le_info
}
Note
Actually, there's only one instance of le_info today, but if there were multiple, this would specify which one to use; and even when there's only one instance, we create a binding anyway to explicitly grant access permission so access is never unknowingly granted.

9. Re-generate your app or system bundle, install it, and run it on target:

App bundle example:

$ mkapp -t wp76xx legacyProgram.adef
$ update legacyProgram.wp76xx.update 192.168.2.2
Installing application 'legacyProgram' from file 'legacyProgram.wp76xx'.
Removing app 'legacyProgram'...
Deleted user 'applegacyProgram'.
Installing app 'legacyProgram'...
Created user 'applegacyProgram' (uid 1011, gid 1011).
DONE
$ app start legacyProgram 192.168.2.2

System bundle example:

$ mksys -t wp76xx mySystem.sdef
$ update mySystem.wp76xx.update $DEST_IP
Unpacking package: 100% ++++++++++++++++++++++++++++++++++++++++++++++++++
Unpacking package: 100% ++++++++++++++++++++++++++++++++++++++++++++++++++
Unpacking package: 100% ++++++++++++++++++++++++++++++++++++++++++++++++++
Applying update: 100% ++++++++++++++++++++++++++++++++++++++++++++++++++
SUCCESS
Note
Because you are overlaying a new system with the updated system, the apps are not uninstalled and reinstalled, the whole system is replaced.

Use Multi-Instance Legato API

Legato service which is implementing some API, can have a name that is different from the API name. One example is le_gpio API, where the service is instantiated for multiple PINs, so that the instance names are le_gpioPin1, le_gpioPin2 etc.

Example below shows how to build a legacy app that is using GPIO PIN 2.

1a. Run ifgen to generate le_gpioPin2 specific files.

Use the --service-name option to specify the name of the service instance, in combination with options --gen-interface --gen-client --gen-local

ifgen --gen-interface --gen-client --gen-local --name-prefix le_gpioPin2 --service-name le_gpioPin2 $LEGATO_ROOT/interfaces/le_gpio.api

1b. Run ifgen to generate files that are common for any binding using le_gpio.api

Options --gen-common-client -gen-common-interface --gen-messages need to be invoked without specifying the service name.

ifgen --gen-common-client -gen-common-interface --gen-messages $LEGATO_ROOT/interfaces/le_gpio.api

2. Include instance-specific header file and call le_gpioPin2_ConnectService() from your program.

#include "legato.h"
#include "le_gpioPin2_interface.h"
 
int main(int argc, char** argv)
{
le_gpioPin2_ConnectService();
 
le_result_t res = le_gpioPin2_SetInput(LE_GPIOPIN2_ACTIVE_HIGH);
 
return EXIT_SUCCESS;
}

3. Compile and link your executable with the code generated by ifgen:

$ $CC -c le_gpio_commonclient.c -I ./ -I${LEGATO_ROOT}/framework/include -I${LEGATO_ROOT}/build/${LEGATO_TARGET}/framework/include --sysroot=${LEGATO_SYSROOT}
$ $CC -c le_gpioPin2_client.c -I ./ -I${LEGATO_ROOT}/framework/include -I${LEGATO_ROOT}/build/${LEGATO_TARGET}/framework/include --sysroot=${LEGATO_SYSROOT}
$ $CC -c legacy_gpio.c -I ./ -I${LEGATO_ROOT}/framework/include -I${LEGATO_ROOT}/build/${LEGATO_TARGET}/framework/include --sysroot=${LEGATO_SYSROOT}
$ $CC -o legacyGPIO legacy_gpio.o le_gpioPin2_client.o le_gpio_commonclient.o -L${LEGATO_ROOT}/build/${LEGATO_TARGET}/framework/lib --sysroot=${LEGATO_SYSROOT} -llegato -lpthread -lrt

4. Specify the instance name in your .adef file:

extern:
{
requires:
{
le_gpioPin2 = ${LEGATO_ROOT}/interfaces/le_gpio.api
}
}
bindings:
{
.le_gpioPin2 -> gpioService.le_gpioPin2
}

5. Build the bundle, install and run the app the same way as described in the previous example.

Callbacks from Legato APIs

If you need asynchronous callbacks (i.e., handlers), you'll need to service the Legato event loop for your thread. To do this, use le_event_GetFd() and le_event_ServiceLoop(). See Integrating with Legacy POSIX Code for more details.

The sample app for this is found in apps/sample/legacy/useLegatoHandler.

Here's some sample code:

struct pollfd pollControl;
pollControl.fd = le_event_GetFd();
pollControl.events = POLLIN;
 
while (true)
{
int result = poll(&pollControl, 1, -1);
 
if (result > 0)
{
{
/* Work was done by le_event_ServiceLoop(), and it has more to do. */
}
}
else
{
// Poll failed. You could check for zero if you're ultra paranoid,
// but poll should never return zero when timeout is -1.
LE_FATAL("poll() failed with errno %m.");
}
}

Sandboxing Your App

To tell the Supervisor to run your app inside a sandbox, remove the following line from your app's .adef file:

sandboxed: false

Or, you can change false to true:

sandboxed: true

Then re-bundle your app using mkapp.

The most commonly-used system libraries, such as libc and libpthread, will be visible inside your app's sandbox by default, but you may now find that your app won't run because some other files are missing from its sandbox.

Use the requires: section in the app's .adef file to add things to the sandbox.

Sample Legacy Apps

Sample Legacy C apps are available in the Legato/apps/sample/legacy directory.