KasperskyOS Community Edition 1.3
[Topic developing][Topic sc_application_start]

Overview: Einit and init.yaml

Einit initializing program

When a solution is started, the KasperskyOS kernel finds the executable file named Einit (initializing program) in the solution image and runs this executable file. The initializing program performs the following operations:

  • Creates and starts processes when a solution is started.
  • Creates IPC channels between processes when a solution is started (statically creates IPC channels).

A process of the initializing program belongs to the Einit class.

Generating the source code of the initializing program

The KasperskyOS SDK includes the einit tool, which generates the C-language source code of the initializing program. The standard way to use the einit tool is to integrate an einit call into one of the steps of the build script, which generates the einit.c file containing the source code of the initializing program. In one of the following steps of the build script, you must compile the einit.c file into the executable file of Einit and include it into the solution image.

You are not required to create formal specification files for the initializing program. These files are provided in the KasperskyOS SDK and are automatically applied during a solution build. However, the Einit process class must be specified in the security.psl file.

The einit tool generates the source code of the initializing program based on the init description, which consists of a text file that is usually named init.yaml.

Syntax of init.yaml

An init description contains data in YAML format. This data identifies the following:

  • Processes that are started when the solution starts.
  • IPC channels that are created when the solution starts and are used by processes to interact with each other (not with the kernel).

This data consists of a dictionary with the entities key containing a list of dictionaries of processes. Process dictionary keys are presented in the table below.

Process dictionary keys in an init description

Key

Required

Value

name

Yes

Process class name (from the EDL description).

task

No

Process name. If this name is not specified, the process class name will be used. Each process must have a unique name.

You can start multiple processes of the same class if they have different names.

path

No

Name of the executable file in ROMFS (in the solution image). If this name is not specified, the process class name (without prefixes and dots) will be used. For example, processes of the Client and net.Client classes for which an executable file name is not specified will be started from the Client file.

You can start multiple processes from the same executable file.

connections

No

Process IPC channel dictionaries list. This list defines the statically created IPC channels whose client IPC handles will be owned by the process. The list is empty by default. (In addition to statically created IPC channels, processes can also use dynamically created IPC channels.)

args

No

List of program startup parameters (main() function parameters). The maximum size of one item on the list is 1024 bytes.

env

No

Dictionary of program environment variables. The keys in this dictionary are the names of environment variables. The maximum size of an environment variable value is 65524 bytes.

Process IPC channel dictionary keys are presented in the table below.

IPC channel dictionary keys in an init description

Key

Required

Value

id

Yes

IPC channel name, which can be defined as a specific value or as a link such as

{var: <constant name>, include: <path to header file>}.

target

Yes

Name of the process that will own the server handle of the IPC channel.

Page top
[Topic einit_overview]

Example init descriptions

This section provides examples of init descriptions that demonstrate various aspects of starting processes.

The build system can automatically create an init description based on the init.yaml.in template.

Starting a client and server and creating an IPC channel between them

In this example, a process of the Client class and a process of the Server class are started. The names of the processes are not specified, so they will match the names of their respective process classes. The names of the executable files are not specified, so they will also match the names of their respective process classes. The processes will be connected by an IPC channel named server_connection.

init.yaml

entities: - name: Client connections: - target: Server id: server_connection - name: Server

Starting processes from defined executable files

This example will start a Client-class process from the executable file named cl, a ClientServer-class process from the executable file named csr, and a MainServer-class process from the executable file named msr. The names of the processes are not specified, so they will match the names of their respective process classes.

init.yaml

entities: - name: Client path: cl - name: ClientServer path: csr - name: MainServer path: msr

Starting two processes from the same executable file

This example will start a Client-class process from the executable file named Client, and two processes of the MainServer and BkServer classes from the executable file named srv. The names of the processes are not specified, so they will match the names of their respective process classes.

init.yaml

entities: - name: Client - name: MainServer path: srv - name: BkServer path: srv

Starting two servers of the same class and a client, and creating IPC channels between the client and servers

This example will start a Client-class process (named Client) and two Server-class processes named UserServer and PrivilegedServer. The client will be connected to the servers via IPC channels named server_connection_us and server_connection_ps. The names of the executable files are not specified, so they will match the names of their respective process classes.

init.yaml

entities: - name: Client connections: - id: server_connection_us target: UserServer - id: server_connection_ps target: PrivilegedServer - task: UserServer name: Server - task: PrivilegedServer name: Server

Setting the startup parameters and environment variables of programs

This example will start a VfsFirst-class process (named VfsFirst) and a VfsSecond-class process (named VfsSecond). The program that will run in the context of the VfsFirst process will be started with the parameter -f /etc/fstab, and will receive the ROOTFS environment variable with the value ramdisk0,0 / ext2 0 and the UNMAP_ROMFS environment variable with the value 1. The program that will run in the context of the VfsSecond process will be started with the -l devfs /dev devfs 0 parameter. The names of the executable files are not specified, so they will match the names of their respective process classes.

init.yaml

entities: - name: VfsFirst args: - -f - /etc/fstab env: ROOTFS: ramdisk0,0 / ext2 0 UNMAP_ROMFS: 1 - name: VfsSecond args: - -l - devfs /dev devfs 0
Page top
[Topic using_einit]

Starting processes using the system program ExecutionManager

The ExecutionManager component provides a C++ interface for creating, starting and stopping processes in solutions that are based on KasperskyOS.

The interface of the ExecutionManager component is not suitable for use in code that is written in C. To manage processes in the C language, use the task.h interface of the libkos library.

You can use the ExecutionManager component to start processes from the following:

The ExecutionManager component API is built on top of IPC and helps simplify program development. ExecutionManager is a separate system program that is accessed through IPC. However, developers are provided with a client library that eliminates the necessity of directly using IPC calls.

The programming interface of the ExecutionManager component is described in the article titled "ExecutionManager component".

ExecutionManager component usage scenario

Hereinafter "the client" refers to the application that uses the ExecutionManager component API to manage other applications.

The typical usage scenario for the ExecutionManager component includes the following steps:

  1. Add the ExecutionManager program to a solution. To add ExecutionManager to a solution:
    find_package (execution_manager REQUIRED) include_directories (${execution_manager_INCLUDE}) add_subdirectory (execution_manager)

    The BlobContainer program is required for the ExecutionManager program to work properly. This program is automatically added to a solution when adding ExecutionManager.

    • The ExecutionManager component is provided in the SDK as a set of static libraries and header files, and is built for a specific solution by using the CMake command create_execution_manager_entity() from the CMake library execution_manager.

      To build the ExecutionManager program, create a directory named execution_manager in the root directory of the project. In the new directory, create a CMakeLists.txt file containing the create_execution_manager_entity() command.

      The CMake command create_execution_manager_entity() takes the following parameters:

      Mandatory ENTITY parameter that specifies the name of the executable file for the ExecutionManager program.

      Optional parameters:

      • DEPENDS – additional dependencies for building the ExecutionManager program.
      • MAIN_CONN_NAME – name of the IPC channel for connecting to the ExecutionManager process. It must match the value of the mainConnection variable when calling the ExecutionManager API in the client code.
      • ROOT_PATH – path to the root directory for service files of the ExecutionManager program. The default root path is "/ROOT".
      • VFS_CLIENT_LIB – name of the client transport library used to connect the ExecutionManager program to the VFS program.
    include (execution_manager/create_execution_manager_entity) create_execution_manager_entity( ENTITY ExecMgrEntity MAIN_CONN_NAME ${ENTITY_NAME} ROOT_PATH "/root" VFS_CLIENT_LIB ${vfs_CLIENT_LIB})
    • When building a solution (CMakeLists.txt file for the Einit program), add the following executable files to the solution image:
      • Executable file of the ExecutionManager program
      • Executable file of the BlobContainer program
  2. Link the client executable file to the client proxy library of ExecutionManager by adding the following command to the CMakeLists.txt file for building the client:
    target_link_libraries (<name of the CMake target for building the client> ${execution_manager_EXECMGR_PROXY})
  3. Add permissions for the necessary events to the solution security policy description:
    1. To enable the ExecutionManager program to run other processes, the solution security policy must allow the following interactions for the execution_manager.ExecMgrEntity process class:
      • Security events of the execute type for all classes of processes that will be run.
      • Access to all endpoints of the VFS program.
      • Access to all endpoints of the BlobContainer program.
      • Access to the core endpoints Sync, Task, VMM, Thread, HAL, Handle, FS, Notice, CM and Profiler (their descriptions are located in the directory sysroot-*-kos/include/kl/core from the SDK).
    2. To enable a client to call the ExecutionManager program, the solution security policy must allow the following interactions for the client process class:
      • Access to the appropriate endpoints of the ExecutionManager program (their descriptions are located in the directory sysroot-*-kos/include/kl/execution_manager from the SDK).
  4. Use of the ExecutionManager program API in the client code.

    Use the header file component/execution_manager/kos_ipc/execution_manager_proxy.h for this. For more details, refer to "ExecutionManager component".

Page top
[Topic app_static_start]

Overview: Env program

The system program Env is intended for setting startup parameters and environment variables of programs. If the Env program is included in a solution, the processes connected via IPC channel to the Env process will automatically send IPC requests to this program and receive startup parameters and environment variables when these processes are started.

Use of the Env system program is an outdated way of setting startup parameters and environment variables of programs. Instead, you must set the startup parameters and environment variables of programs via the init.yaml.in or init.yaml file.

If the value of a startup parameter or environment variable of a program is defined through the Env program and via the init.yaml.in or init.yaml file, the value defined through the Env program will be applied.

To use the Env program in a solution, you need to do the following:

  1. Develop the code of the Env program using the macros and functions from the header file sysroot-*-kos/include/env/env.h from the KasperskyOS SDK.
  2. Build the executable file of the Env program by linking it to the env_server library from the KasperskyOS SDK.
  3. In the init description, indicate that the Env process must be started and connected to other processes (Env acts as a server in this case). The name of the IPC channel is assigned by the ENV_SERVICE_NAME macro defined in the header file env.h.
  4. Include the Env executable file in the solution image.

Source code of the Env program

The source code of the Env program utilizes the following macros and functions from the header file env.h:

  • ENV_REGISTER_ARGS(name,argarr) sets the argarr startup parameters for the program that will run in the context of the name process.
  • ENV_REGISTER_VARS(name,envarr) sets the envarr environment variables for the program that will run in the context of the name process.
  • ENV_REGISTER_PROGRAM_ENVIRONMENT(name,argarr,envarr) sets the argarr startup parameters and envarr environment variables for the program that will run in the context of the name process.
  • envServerRun() initializes the server part of the Env program so that it can respond to IPC requests.

Env program usage examples

Page top
[Topic env_overview]

Examples of using Env to set the startup parameters and environment variables of programs

Use of the Env system program is an outdated way of setting startup parameters and environment variables of programs. Instead, you must set the startup parameters and environment variables of programs via the init.yaml.in or init.yaml file.

If the value of a startup parameter or environment variable of a program is defined through the Env program and via the init.yaml.in or init.yaml file, the value defined through the Env program will be applied.

Example of setting startup parameters of a program

Source code of the Env program is presented below. When the process named NetVfs starts, the program passes the following two program startup parameters to this process: -l devfs /dev devfs 0 and -l romfs /etc romfs ro:

env.c

#include <env/env.h> #include <stdlib.h> int main(int argc, char** argv) { const char* NetVfsArgs[] = { "-l", "devfs /dev devfs 0", "-l", "romfs /etc romfs ro" }; ENV_REGISTER_ARGS("NetVfs", NetVfsArgs); envServerRun(); return EXIT_SUCCESS; }

Example of setting environment variables of a program

Source code of the Env program is presented below. When the process named Vfs3 starts, the program passes the following two program environment variables to this process: ROOTFS=ramdisk0,0 / ext2 0 and UNMAP_ROMFS=1:

env.c

#include <env/env.h> #include <stdlib.h> int main(int argc, char** argv) { const char* Vfs3Envs[] = { "ROOTFS=ramdisk0,0 / ext2 0", "UNMAP_ROMFS=1" }; ENV_REGISTER_VARS("Vfs3", Vfs3Envs); envServerRun(); return EXIT_SUCCESS; }
Page top
[Topic using_env_app]

File systems and network

In KasperskyOS, operations with file systems and the network are executed via a separate system program that implements a virtual file system (VFS).

In the SDK, the VFS component consists of a set of executable files, libraries, formal specification files, and header files. For more details, see the Contents of the VFS component section.

The main scenario of interaction with the VFS system program includes the following:

  1. An application connects via IPC channel to the VFS system program and then links to the client library of the VFS component during the build.
  2. In the application code, POSIX calls for working with file systems and the network are converted into client library function calls.

    Input and output to file handles for standard I/O streams (stdin, stdout and stderr) are also converted into queries to the VFS. If the application is not linked to the client library of the VFS component, printing to stdout is not possible. If this is the case, you can only print to the standard error stream (stderr), which in this case is performed via special methods of the KasperskyOS kernel without using VFS.

  3. The client library makes IPC requests to the VFS system program.
  4. The VFS system program receives an IPC requests and calls the corresponding file system implementations (which, in turn, may make IPC requests to device drivers) or network drivers.
  5. After the request is handled, the VFS system program responds to the IPC requests of the application.

Using multiple VFS programs

Multiple copies of the VFS system program can be added to a solution for the purpose of separating the data streams of different system programs and applications. You can also separate the data streams within one application. For more details, refer to Using VFS backends to separate data streams.

Adding VFS functionality to an application

The complete functionality of the VFS component can be included in an application, thereby avoiding the need to pass each request via IPC. For more details, refer to Including VFS functionality in a program.

However, use of VFS functionality via IPC enables the solution developer to do the following:

  • Use a solution security policy to control method calls for working with the network and file systems.
  • Connect multiple client programs to one VFS program.
  • Connect one client program to two VFS programs to separately work with the network and file systems.

In this section

Contents of the VFS component

Creating an IPC channel to VFS

Including VFS functionality in a program

Overview: startup parameters and environment variables of VFS

Mounting file systems when VFS starts

Using VFS backends to separate data streams

Creating a VFS backend

Dynamically configuring the network stack

Page top
[Topic sc_filesystems_and_net]

Contents of the VFS component

The VFS component implements the virtual file system. In the KasperskyOS SDK, the VFS component consists of a set of executable files, libraries, formal specification files and header files that enable the use of file systems and/or a network stack.

VFS libraries

The vfs CMake package contains the following libraries:

  • vfs_fs contains implementations of the devfs, ramfs and ROMFS file systems, and adds implementations of other file systems to VFS.
  • vfs_net contains the implementation of the devfs file system and the network stack.
  • vfs_imp contains the vfs_fs and vfs_net libraries.
  • vfs_remote is the client transport library that converts local calls into IPC requests to VFS and receives IPC responses.
  • vfs_server is the VFS server transport library that receives IPC requests, converts them into local calls, and sends IPC responses.
  • vfs_local is used to include VFS functionality in a program.

VFS executable files

The precompiled_vfs CMake package contains the following executable files:

  • VfsRamFs
  • VfsSdCardFs
  • VfsNet

The VfsRamFs and VfsSdCardFs executable files include the vfs_server, vfs_fs, vfat and lwext4 libraries. The VfsNet executable file includes the vfs_server, vfs_imp libraries.

Each of these executable files has its own default values for startup parameters and environment variables.

Formal specification files and header files of VFS

The sysroot-*-kos/include/kl directory from the KasperskyOS SDK contains the following VFS files:

  • Formal specification files VfsRamFs.edl, VfsSdCardFs.edl, VfsNet.edl and VfsEntity.edl, and the header files generated from them.
  • Formal specification file Vfs.cdl and the header file Vfs.cdl.h generated from it.
  • Formal specification files Vfs*.idl and the header files generated from them.

Libc library API supported by VFS

VFS functionality is available to programs through the API provided by the libc library.

The functions implemented by the vfs_fs and vfs_net libraries are presented in the table below. The * character denotes the functions that are optionally included in the vfs_fs library (depending on the library build parameters).

Functions implemented by the vfs_fs library

mount()

unlink()

ftruncate()

lsetxattr()*

umount()

rmdir()

chdir()

fsetxattr()*

open()

mkdir()

fchdir()

getxattr()*

openat()

mkdirat()

chmod()

lgetxattr()*

read()

fcntl()

fchmod()

fgetxattr()*

readv()

statvfs()

fchmodat()

listxattr()*

write()

fstatvfs()

chroot()

llistxattr()*

writev()

getvfsstat()

fsync()

flistxattr()*

stat()

pipe()

fdatasync()

removexattr()*

lstat()

futimens()

pread()

lremovexattr()*

fstat()

utimensat()

pwrite()

fremovexattr()*

fstatat()

link()

sendfile()

acl_set_file()*

lseek()

linkat()

getdents()

acl_get_file()*

close()

symlink()

sync()

acl_delete_def_file()*

rename()

symlinkat()

ioctl()

 

renameat()

unlinkat()

setxattr()*

 

Functions implemented by the vfs_net library

read()

bind()

getsockname()

recvfrom()

readv()

listen()

gethostbyname()

recvmsg()

write()

connect()

getnetbyaddr()

send()

writev()

accept()

getnetbyname()

sendto()

fstat()

poll()

getnetent()

sendmsg()

close()

shutdown()

setnetent()

ioctl()

fcntl()

getnameinfo()

endnetent()

sysctl()

fstatvfs()

getaddrinfo()

getprotobyname()

 

pipe()

freeaddrinfo()

getprotobynumber()

 

futimens()

getifaddrs()

getsockopt()

 

socket()

freeifaddrs()

setsockopt()

 

socketpair()

getpeername()

recv()

 

If there is no implementation of a called function in VFS, the EIO error code is returned.

Page top
[Topic vfs_overview]

Creating an IPC channel to VFS

In this example, the Client process uses the file systems and network stack, and the VfsFsnet process handles the IPC requests of the Client process related to the use of file systems and the network stack. This approach is utilized when there is no need to separate data streams related to file systems and the network stack.

The IPC channel name must be assigned by the _VFS_CONNECTION_ID macro defined in the header file sysroot-*-kos/include/vfs/defs.h from the KasperskyOS SDK.

Init description of the example:

init.yaml

- name: Client connections: - target: VfsFsnet id: {var: _VFS_CONNECTION_ID, include: vfs/defs.h} - name: VfsFsnet
Page top
[Topic client_and_vfs_ipc_channel]

Including VFS functionality in a program

In this example, the Client program includes the VFS program functionality for working with the network stack (see the figure below).

VFS component libraries in a program

The client.c implementation file is compiled and the vfs_local, vfs_implementation and dnet_implementation libraries are linked:

CMakeLists.txt

project (client) include (platform/nk) # Set compile flags project_header_default ("STANDARD_GNU_17:YES" "STRICT_WARNINGS:NO") # Generates the Client.edl.h file nk_build_edl_files (client_edl_files NK_MODULE "client" EDL "${CMAKE_SOURCE_DIR}/resources/edl/Client.edl") add_executable (Client "src/client.c") add_dependencies (Client client_edl_files) # Linking with VFS libraries target_link_libraries (Client ${vfs_LOCAL_LIB} ${vfs_IMPLEMENTATION_LIB} ${dnet_IMPLEMENTATION_LIB}

If the Client program needs to use file systems, you must link the vfs_local and vfs_fs libraries, and link the libraries for implementing these file systems. In this case, you must also add a block device driver to the solution.

Page top
[Topic client_and_vfs_linked]

Overview: startup parameters and environment variables of VFS

VFS program startup parameters

  • -l <entry in fstab format>

    The startup parameter -l mounts the defined file system.

  • -f <path to fstab file>

    The parameter -f mounts the file systems specified in the fstab file. If the UNMAP_ROMFS environment variable is not defined, the fstab file will be sought in the ROMFS image. If the UNMAP_ROMFS environment variable is defined, the fstab file will be sought in the file system defined through the ROOTFS environment variable.

Examples of using VFS program startup parameters

Environment variables of the VFS program

  • UNMAP_ROMFS

    If the UNMAP_ROMFS environment variable is defined, the ROMFS image will be deleted from memory. This helps conserve memory. When using the startup parameter -f, it also provides the capability to search for the fstab file in the file system defined through the ROOTFS environment variable instead of searching the ROMFS image.

    Example of using the UNMAP_ROMFS environment variable

  • ROOTFS = <entry in fstab format>

    The ROOTFS environment variable mounts the defined file system to the root directory. When using the startup parameter -f, a combination of the ROOTFS and UNMAP_ROMFS environment variables provides the capability to search for the fstab file in the file system defined through the ROOTFS environment variable instead of searching the ROMFS image.

    Example of using the ROOTFS environment variable

  • VFS_CLIENT_MAX_THREADS

    The VFS_CLIENT_MAX_THREADS environment variable redefines the SDK configuration parameter VFS_CLIENT_MAX_THREADS.

  • VFS_NETWORK_BACKEND=<VFS backend name>:<name of the IPC channel to the VFS process>

    The VFS_NETWORK_BACKEND environment variable defines the VFS backend for working with the network stack. You can specify the name of the standard VFS backend: client (for a program that runs in the context of a client process), server (for a VFS program that runs in the context of a server process) or local, and the name of a custom VFS backend. If the local VFS backend is used, the name of the IPC channel is not specified (VFS_NETWORK_BACKEND=local:). You can specify more than one IPC channel by separating them with a comma.

  • VFS_FILESYSTEM_BACKEND=<VFS backend name>:<name of the IPC channel to the VFS process>

    The VFS_FILESYSTEM_BACKEND environment variable defines the VFS backend for working with file systems. The name of the VFS backend and the name of the IPC channel to the VFS process are defined the same way as they are defined in the VFS_NETWORK_BACKEND environment variable.

Default values for startup parameters and environment variables of VFS

For the VfsRamFs executable file:

ROOTFS = ramdisk0,0 / ext4 0 VFS_FILESYSTEM_BACKEND = server:kl.VfsRamFs

For the VfsSdCardFs executable file:

ROOTFS = mmc0,0 / fat32 0 VFS_FILESYSTEM_BACKEND = server:kl.VfsSdCardFs -l nodev /tmp ramfs 0 -l nodev /var ramfs 0

For the VfsNet executable file:

VFS_NETWORK_BACKEND = server:kl.VfsNet VFS_FILESYSTEM_BACKEND = server:kl.VfsNet -l devfs /dev devfs 0
Page top
[Topic vfs_args_and_envs_overview]

Mounting file systems when VFS starts

When the VfsRamFs and VfsSdCardFs programs start, only the RAMFS file system is mounted to the root directory by default. If you need to mount other file systems, this can be done not only by calling the mount() function but also by setting the startup parameters and environment variables of the VFS program.

The ROMFS and squashfs file systems are intended for read-only operations. For this reason, you must specify the ro parameter to mount these file systems.

Using the startup parameter -l

One way to mount a file system is to set the startup parameter -l <entry in fstab format> for the VFS program.

In these examples, the devfs and ROMFS file systems will be mounted when the VFS program is started:

init.yaml.(in)

... - name: VfsFirst args: - -l - devfs /dev devfs 0 - -l - romfs /etc romfs ro ...

CMakeLists.txt

... set_target_properties (${vfs_ENTITY} PROPERTIES EXTRA_ARGS " - -l - devfs /dev devfs 0 - -l - romfs /etc romfs ro") ...

Using the fstab file from the ROMFS image

When building a solution, you can add the fstab file to the ROMFS image. This file can be used to mount file systems by setting the startup parameter -f <path to the fstab file> for the VFS program.

In these examples, the file systems defined via the fstab file that was added to the ROMFS image during the solution build will be mounted when the VFS program is started:

init.yaml.(in)

... - name: VfsSecond args: - -f - fstab ...

CMakeLists.txt

... set_target_properties (${vfs_ENTITY} PROPERTIES EXTRA_ARGS " - -f - fstab") ...

Using an "external" fstab file

If the fstab file resides in another file system instead of in the ROMFS image, you must set the following startup parameters and environment variables for the VFS program to enable use of this file:

  1. ROOTFS. This environment variable mounts the file system containing the fstab file to the root directory.
  2. UNMAP_ROMFS. If this environment variable is defined, the fstab file will be sought in the file system defined through the ROOTFS environment variable.
  3. -f. This startup parameter is used to mount the file systems specified in the fstab file.

In these examples, the ext2 file system that should contain the fstab file at the path /etc/fstab will be mounted to the root directory when the VFS program starts:

init.yaml.(in)

... - name: VfsThird args: - -f - /etc/fstab env: ROOTFS: ramdisk0,0 / ext2 0 UNMAP_ROMFS: 1 ...

CMakeLists.txt

... set_target_properties (${vfs_ENTITY} PROPERTIES EXTRA_ARGS " - -f - /etc/fstab" EXTRA_ENV " ROOTFS: ramdisk0,0 / ext2 0 UNMAP_ROMFS: 1") ...
Page top
[Topic mount_on_start]

Using VFS backends to separate data streams

This example employs a secure development pattern that separates data streams related to file system use from data streams related to the use of a network stack.

The Client process uses file systems and the network stack. The VfsFirst process works with file systems, and the VfsSecond process provides the capability to work with the network stack. The environment variables of programs that run in the contexts of the Client, VfsFirst and VfsSecond processes are used to define the VFS backends that ensure the segregated use of file systems and the network stack. As a result, IPC requests of the Client process that are related to the use of file systems are handled by the VfsFirst process, and IPC requests of the Client process that are related to network stack use are handled by the VfsSecond process (see the figure below).

Process interaction scenario

Init description of the example:

init.yaml

entities: - name: Client connections: - target: VfsFirst id: VFS1 - target: VfsSecond id: VFS2 env: VFS_FILESYSTEM_BACKEND: client:VFS1 VFS_NETWORK_BACKEND: client:VFS2 - name: VfsFirst env: VFS_FILESYSTEM_BACKEND: server:VFS1 - name: VfsSecond env: VFS_NETWORK_BACKEND: server:VFS2
Page top
[Topic client_and_two_vfs]

Creating a VFS backend

This example demonstrates how to create and use a custom VFS backend.

The Client process uses the fat32 and ext4 file systems. The VfsFirst process works with the fat32 file system, and the VfsSecond process provides the capability to work with the ext4 file system. The environment variables of programs that run in the contexts of the Client, VfsFirst and VfsSecond processes are used to define the VFS backends ensuring that IPC requests of the Client process are handled by the VfsFirst or VfsSecond process depending on the specific file system being used by the Client process. As a result, IPC requests of the Client process related to use of the fat32 file system are handled by the VfsFirst process, and IPC requests of the Client process related to use of the ext4 file system are handled by the VfsSecond process (see the figure below).

On the VfsFirst process side, the fat32 file system is mounted to the directory /mnt1. On the VfsSecond process side, the ext4 file system is mounted to the directory /mnt2. The custom VFS backend custom_client used on the Client process side sends IPC requests over the IPC channel VFS1 or VFS2 depending on whether or not the file path begins with /mnt1. The custom VFS backend uses the standard VFS backend client as an intermediary.

Process interaction scenario

Source code of the VFS backend

This implementation file contains the source code of the VFS backend custom_client, which uses the standard client VFS backends:

backend.c

#include <vfs/vfs.h> #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <errno.h> #include <string.h> #include <getopt.h> #include <assert.h> /* Code for managing file handles */ #define MAX_FDS 50 struct entry { Handle handle; bool is_vfat; }; struct fd_array { struct entry entries[MAX_FDS]; int pos; pthread_rwlock_t lock; }; struct fd_array fds = { .pos = 0, .lock = PTHREAD_RWLOCK_INITIALIZER }; int insert_entry(Handle fd, bool is_vfat) { pthread_rwlock_wrlock(&fds.lock); if (fds.pos == MAX_FDS) { pthread_rwlock_unlock(&fds.lock); return -1; } fds.entries[fds.pos].handle = fd; fds.entries[fds.pos].is_vfat = is_vfat; fds.pos++; pthread_rwlock_unlock(&fds.lock); return 0; } struct entry *find_entry(Handle fd) { pthread_rwlock_rdlock(&fds.lock); for (int i = 0; i < fds.pos; i++) { if (fds.entries[i].handle == fd) { pthread_rwlock_unlock(&fds.lock); return &fds.entries[i]; } } pthread_rwlock_unlock(&fds.lock); return NULL; } /* Custom VFS backend structure */ struct context { struct vfs wrapper; pthread_rwlock_t lock; struct vfs *vfs_vfat; struct vfs *vfs_ext4; }; struct context ctx = { .wrapper = { .dtor = _vfs_backend_dtor, .disconnect_all_clients = _disconnect_all_clients, .getstdin = _getstdin, .getstdout = _getstdout, .getstderr = _getstderr, .open = _open, .read = _read, .write = _write, .close = _close, } }; /* Implementation of custom VFS backend methods */ static bool is_vfs_vfat_path(const char *path) { char vfat_path[5] = "/mnt1"; if (memcmp(vfat_path, path, sizeof(vfat_path)) != 0) return false; return true; } static void _vfs_backend_dtor(struct vfs *vfs) { ctx.vfs_vfat->dtor(ctx.vfs_vfat); ctx.vfs_ext4->dtor(ctx.vfs_ext4); } static void _disconnect_all_clients(struct vfs *self, int *error) { (void)self; (void)error; ctx.vfs_vfat->disconnect_all_clients(ctx.vfs_vfat, error); ctx.vfs_ext4->disconnect_all_clients(ctx.vfs_ext4, error); } static Handle _getstdin(struct vfs *self, int *error) { (void)self; Handle handle = ctx.vfs_vfat->getstdin(ctx.vfs_vfat, error); if (handle != INVALID_HANDLE) { if (insert_entry(handle, true)) { *error = ENOMEM; return INVALID_HANDLE; } } return handle; } static Handle _getstdout(struct vfs *self, int *error) { (void)self; Handle handle = ctx.vfs_vfat->getstdout(ctx.vfs_vfat, error); if (handle != INVALID_HANDLE) { if (insert_entry(handle, true)) { *error = ENOMEM; return INVALID_HANDLE; } } return handle; } static Handle _getstderr(struct vfs *self, int *error) { (void)self; Handle handle = ctx.vfs_vfat->getstderr(ctx.vfs_vfat, error); if (handle != INVALID_HANDLE) { if (insert_entry(handle, true)) { *error = ENOMEM; return INVALID_HANDLE; } } return handle; } static Handle _open(struct vfs *self, const char *path, int oflag, mode_t mode, int *error) { (void)self; Handle handle; bool is_vfat = false; if (is_vfs_vfat_path(path)) { handle = ctx.vfs_vfat->open(ctx.vfs_vfat, path, oflag, mode, error); is_vfat = true; } else handle = ctx.vfs_ext4->open(ctx.vfs_ext4, path, oflag, mode, error); if (handle == INVALID_HANDLE) return INVALID_HANDLE; if (insert_entry(handle, is_vfat)) { if (is_vfat) ctx.vfs_vfat->close(ctx.vfs_vfat, handle, error); *error = ENOMEM; return INVALID_HANDLE; } return handle; } static ssize_t _read(struct vfs *self, Handle fd, void *buf, size_t count, bool *nodata, int *error) { (void)self; struct entry *found_entry = find_entry(fd); if (found_entry != NULL && found_entry->is_vfat) return ctx.vfs_vfat->read(ctx.vfs_vfat, fd, buf, count, nodata, error); return ctx.vfs_ext4->read(ctx.vfs_ext4, fd, buf, count, nodata, error); } static ssize_t _write(struct vfs *self, Handle fd, const void *buf, size_t count, int *error) { (void)self; struct entry *found_entry = find_entry(fd); if (found_entry != NULL && found_entry->is_vfat) return ctx.vfs_vfat->write(ctx.vfs_vfat, fd, buf, count, error); return ctx.vfs_ext4->write(ctx.vfs_ext4, fd, buf, count, error); } static int _close(struct vfs *self, Handle fd, int *error) { (void)self; struct entry *found_entry = find_entry(fd); if (found_entry != NULL && found_entry->is_vfat) return ctx.vfs_vfat->close(ctx.vfs_vfat, fd, error); return ctx.vfs_ext4->close(ctx.vfs_ext4, fd, error); } /* Custom VFS backend builder. ctx.vfs_vfat and ctx.vfs_ext4 are initialized * as standard backends named "client". */ static struct vfs *_vfs_backend_create(Handle client_id, const char *config, int *error) { (void)config; ctx.vfs_vfat = _vfs_init("client", client_id, "VFS1", error); assert(ctx.vfs_vfat != NULL && "Can't initialize client backend!"); assert(ctx.vfs_vfat->dtor != NULL && "VFS FS backend has not set the destructor!"); ctx.vfs_ext4 = _vfs_init("client", client_id, "VFS2", error); assert(ctx.vfs_ext4 != NULL && "Can't initialize client backend!"); assert(ctx.vfs_ext4->dtor != NULL && "VFS FS backend has not set the destructor!"); return &ctx.wrapper; } /* Registration of the custom VFS backend under the name custom_client */ static void _vfs_backend(create_vfs_backend_t *ctor, const char **name) { *ctor = &_vfs_backend_create; *name = "custom_client"; } REGISTER_VFS_BACKEND(_vfs_backend)

Linking the Client program

Creating a static VFS backend library:

CMakeLists.txt

... add_library (backend_client STATIC "src/backend.c") ...

Linking the Client program to the static VFS backend library:

CMakeLists.txt

... add_dependencies (Client vfs_backend_client backend_client) target_link_libraries (Client pthread ${vfs_CLIENT_LIB} "-Wl,--whole-archive" backend_client "-Wl,--no-whole-archive" backend_client ) ...

Setting the startup parameters and environment variables of programs

Init description of the example:

init.yaml

entities: - name: vfs_backend.Client connections: - target: vfs_backend.VfsFirst id: VFS1 - target: vfs_backend.VfsSecond id: VFS2 env: _VFS_FILESYSTEM_BACKEND: custom_client:VFS1,VFS2 - name: vfs_backend.VfsFirst args: - -l - ahci0 /mnt1 fat32 0 env: _VFS_FILESYSTEM_BACKEND: server:VFS1 - name: vfs_backend.VfsSecond - -l - ahci1 /mnt2 ext4 0 env: _VFS_FILESYSTEM_BACKEND: server:VFS2
Page top
[Topic vfs_backends]

Dynamically configuring the network stack

To change the default network stack parameters, use the sysctl() function or sysctlbyname() function that are declared in the header file sysroot-*-kos/include/sys/sysctl.h from the KasperskyOS SDK. The parameters that can be changed are presented in the table below.

Configurable network stack parameters

Parameter name

Parameter description

net.inet.ip.ttl

Maximum time to live (TTL) of sent IP packets. It does not affect the ICMP protocol.

net.inet.ip.mtudisc

If its value is set to 1, "Path MTU Discovery" (RFC 1191) mode is enabled. This mode affects the maximum size of a TCP segment (Maximum Segment Size, or MSS). In this mode, the MSS value is determined by the limitations of network nodes. If "Path MTU Discovery" mode is not enabled, the MSS value does not exceed the value defined by the net.inet.tcp.mssdflt parameter.

net.inet.tcp.mssdflt

MSS value (in bytes) that is applied if only the communicating side failed to provide this value when opening the TCP connection, or if "Path MTU Discovery" mode (RFC 1191) is not enabled. This MSS value is also forwarded to the communicating side when opening a TCP connection.

net.inet.tcp.minmss

Minimum MSS value, in bytes.

net.inet.tcp.mss_ifmtu

If its value is set to 1, the MSS value is calculated for an opened TCP connection based on the maximum size of a transmitted data block (Maximum Transmission Unit, or MTU) of the employed network interface. If its value is set to 0, the MSS value for an opened TCP connection is calculated based on the MTU of the network interface that has the highest value for this parameter among all available network interfaces (except the loopback interface).

net.inet.tcp.keepcnt

Number of times to send test messages (Keep-Alive Probes, or KA) without receiving a response before the TCP connection will be considered closed. If its value is set to 0, the number of sent keep-alive probes is unlimited.

net.inet.tcp.keepidle

TCP connection idle period, after which keep-alive probes begin. This is defined in conditional units, which can be converted into seconds via division by the net.inet.tcp.slowhz parameter value.

net.inet.tcp.keepintvl

Time interval between recurring keep-alive probes when no response is received. This is defined in conditional units, which can be converted into seconds via division by the net.inet.tcp.slowhz parameter value.

net.inet.tcp.recvspace

Size of the buffer (in bytes) for data received over the TCP protocol.

net.inet.tcp.sendspace

Size of the buffer (in bytes) for data sent over the TCP protocol.

net.inet.udp.recvspace

Size of the buffer (in bytes) for data received over the UDP protocol.

net.inet.udp.sendspace

Size of the buffer (in bytes) for data sent over the UDP protocol.

MSS configuration example:

static const int mss_max = 1460; static const int mss_min = 100; static const char* mss_max_opt_name = "net.inet.tcp.mssdflt"; static const char* mss_min_opt_name = "net.inet.tcp.minmss"; int main(void) { ... if ((sysctlbyname(mss_max_opt_name, NULL, NULL, &mss_max, sizeof(mss_max)) != 0) || (sysctlbyname(mss_min_opt_name, NULL, NULL, &mss_min, sizeof(mss_min)) != 0)) { ERROR(START, "Can't set tcp default maximum/minimum MSS value."); return EXIT_FAILURE; } }
Page top
[Topic vfs_net_stack_dyn_conf][Topic sc_using_ipc]

Creating IPC channels

IPC channels can be created statically or dynamically.

Statically created IPC channels

Static creation of IPC channel has the following characteristics:

  • Creation of an IPC channel is performed by the parent process that starts the client and server (this is normally Einit).
  • The client and server are not yet running when the IPC channel is created.
  • You cannot create a new IPC channel in place of a deleted one.

The IPC channels defined in the init description are statically created. You can also use the task.h API to statically create IPC channels.

To get the client and server IPC handles and the endpoint ID (RIID), use the sl_api.h API.

Dynamically created IPC channels

Dynamic creation of IPC channels lets you change the topology of interaction between processes on the fly. For example, this is necessary if it is unknown which specific server provides the endpoint required by the client.

Dynamic creation of IPC channel has the following characteristics:

  • The IPC channel is jointly created by the client and server.
  • The client and server are already running when the IPC channel is created.
  • You can create a new IPC channel in place of a deleted one.

To dynamically create IPC channels, use the DCM system program.

Aside from using the DCM system program, you can also dynamically create IPC channels by using the cm_api.h and ns_api.h APIs provided by the libkos library together with the NameServer system program. However, this latter method is obsolete and therefore not recommended.

If a dynamically created IPC channel is no longer required, its client and server handles can be closed. The IPC channel can be created again if necessary.

Page top
[Topic ipc_channels]

Adding an endpoint from KasperskyOS Community Edition to a solution

To ensure that a Client program can use some specific functionality via the IPC mechanism, the following is required:

  1. In KasperskyOS Community Edition, find the executable file (we'll call it Server) that implements the necessary functionality. (The term "functionality" used here refers to one or more endpoints that have their own IPC interfaces.)
  2. Include the CMake package containing the Server file and its client library.
  3. Add the Server executable file to the solution image.
  4. Edit the init description so that when the solution starts, the Einit program starts a new server process from the Server executable file and connects it, using an IPC channel, to the process started from the Client file.

    You must indicate the correct name of the IPC channel so that the transport libraries can identify this channel and find its IPC handles. The correct name of the IPC channel normally matches the name of the server process class. VFS is an exception in this case.

  5. Edit the PSL description to allow startup of the server process and IPC interaction between the client and the server.
  6. In the source code of the Client program, include the server methods header file.
  7. Link the Client program with the client library.

Example of adding a GPIO driver to a solution

KasperskyOS Community Edition includes a gpio_hw file that implements GPIO driver functionality.

The following commands connect the gpio CMake package:

.\CMakeLists.txt

... find_package (gpio REQUIRED COMPONENTS CLIENT_LIB ENTITY) include_directories (${gpio_INCLUDE}) ...

The gpio_hw executable file is added to a solution image by using the gpio_HW_ENTITY variable, whose name can be found in the configuration file of the package at /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/lib/cmake/gpio/gpio-config.cmake:

einit\CMakeLists.txt

... set (ENTITIES Client ${gpio_HW_ENTITY}) ...

The following strings need to be added to the init description:

init.yaml.in

... - name: client.Client connections: - target: kl.drivers.GPIO id: kl.drivers.GPIO - name: kl.drivers.GPIO path: gpio_hw

The following strings need to be added to the PSL description:

security.psl.in

... execute src=Einit, dst=kl.drivers.GPIO { grant() } request src=client.Client, dst=kl.drivers.GPIO { grant() } response src=kl.drivers.GPIO, dst=client.Client { grant() } ...

In the code of the Client program, you need to include the header file in which the GPIO driver methods are declared:

client.c

... #include <gpio/gpio.h> ...

Finally, you need to link the Client program with the GPIO client library:

client\CMakeLists.txt

... target_link_libraries (Client ${gpio_CLIENT_LIB}) ...

To ensure correct operation of the GPIO driver, you may need to add the BSP component to the solution. To avoid overcomplicating this example, BSP is not examined here. For more details, see the gpio_output example: /opt/KasperskyOS-Community-Edition-<version>/examples/gpio_output

Page top
[Topic using_sdk_endpoints_examples][Topic sc_using_new_endpoints]

Overview: IPC message structure

In KasperskyOS, all interactions between processes have statically defined types. The permissible structures of an IPC message are defined by the IDL descriptions of servers.

An IPC message (request and response) contains a constant part and an (optional) arena.

Constant part of an IPC message

The constant part of an IPC message contains the RIID, MID, and (optionally) fixed-size parameters of interface methods.

Fixed-size parameters are parameters that have IDL types of a fixed size.

The RIID and MID identify the interface and method being called:

  • The RIID (Runtime Implementation ID) is the sequence number of the utilized endpoint within the set of server endpoints (starting at zero).
  • The MID (Method ID) is the sequence number of the called method within the set of methods of the utilized endpoint (starting at zero).

The type of the constant part of the IPC message is generated by the NK compiler based on the IDL description of the interface. A separate structure is generated for each interface method. Union types are also generated for storing any request to a process, component or interface. For more details, refer to Example generation of transport methods and types.

IPC message arena

An IPC message arena (hereinafter also referred to as an arena) contains variable-size parameters of interface methods (and/or elements of these parameters).

Variable-size parameters are parameters that have IDL types of a variable size.

For more details, refer to Working with an IPC message arena.

Maximum IPC message size

The maximum size of an IPC message is determined by the KasperskyOS kernel parameters. On most hardware platforms supported by KasperskyOS, the cumulative size of the constant part and arena of an IPC message cannot exceed 4, 8, or 16 MB.

IPC message structure verification by the security module

Prior to querying IPC message-related rules, the Kaspersky Security Module verifies that the sent IPC message is correct. Requests and responses are both validated. If the IPC message has an incorrect structure, it will be rejected without calling the security model methods associated with it.

Implementation of IPC interaction

To make it easier for a developer to implement IPC interaction, KasperskyOS Community Edition provides the following:

Implementation of simple IPC interaction is demonstrated in the echo and ping examples (/opt/KasperskyOS-Community-Edition-<version>/examples/).

Page top
[Topic ipc_message_structure_overview]

Example generation of transport methods and types

When building a solution, the NK compiler uses the EDL, CDL and IDL descriptions to generate a set of special methods and types that simplify the creation, forwarding, receipt and processing of IPC messages.

As an example, we will examine the Server process class that provides the FS endpoint, which contains a single Open() method:

Server.edl

task class Server /* OpsComp is the named instance of the Operations component */ components { OpsComp: Operations }

Operations.cdl

component Operations /* FS is the local name of the endpoint implementing the Filesystem interface */ endpoints { FS: Filesystem }

Filesystem.idl

package Filesystem interface { Open(in string<256> name, out UInt32 h); }

These descriptions will be used to generate the files named Server.edl.h, Operations.cdl.h, and Filesystem.idl.h, which contain the following methods and types:

Methods and types that are common to the client and server

  • Abstract interfaces containing the pointers to the implementations of the methods included in them.

    In our example, one abstract interface (Filesystem) will be generated:

    typedef struct Filesystem { const struct Filesystem_ops *ops; } Filesystem; typedef nk_err_t Filesystem_Open_fn(struct Filesystem *, const struct Filesystem_Open_req *, const struct nk_arena *, struct Filesystem_Open_res *, struct nk_arena *); typedef struct Filesystem_ops { Filesystem_Open_fn *Open; } Filesystem_ops;
  • Set of interface methods.

    When calling an interface method, the corresponding values of the RIID and MID are automatically inserted into the request.

    In our example, a single Filesystem_Open interface method will be generated:

    nk_err_t Filesystem_Open(struct Filesystem *self, struct Filesystem_Open_req *req, const struct nk_arena *req_arena, struct Filesystem_Open_res *res, struct nk_arena *res_arena)

Methods and types used only on the client

  • Types of proxy objects.

    A proxy object is used as an argument in an interface method. In our example, a single Filesystem_proxy proxy object type will be generated:

    typedef struct Filesystem_proxy { struct Filesystem base; struct nk_transport *transport; nk_iid_t iid; } Filesystem_proxy;
  • Functions for initializing proxy objects.

    In our example, the single initializing function Filesystem_proxy_init will be generated:

    void Filesystem_proxy_init(struct Filesystem_proxy *self, struct nk_transport *transport, nk_iid_t iid)
  • Types that define the structure of the constant part of a message for each specific method.

    In our example, two such types will be generated: Filesystem_Open_req (for a request) and Filesystem_Open_res (for a response).

    typedef struct __nk_packed Filesystem_Open_req { __nk_alignas(8) struct nk_message base_; __nk_alignas(4) nk_ptr_t name; } Filesystem_Open_req; typedef struct Filesystem_Open_res { union { struct { __nk_alignas(8) struct nk_message base_; __nk_alignas(4) nk_uint32_t h; }; struct { __nk_alignas(8) struct nk_message base_; __nk_alignas(4) nk_uint32_t h; } res_; struct Filesystem_Open_err err_; }; } Filesystem_Open_res;

Methods and types used only on the server

  • Type containing all endpoints of a component, and the initializing function. (For each server component.)

    If there are embedded components, this type also contains their instances, and the initializing function takes their corresponding initialized structures. Therefore, if embedded components are present, their initialization must begin with the most deeply embedded component.

    In our example, the Operations_component structure and Operations_component_init function will be generated:

    typedef struct Operations_component { struct Filesystem *FS; }; void Operations_component_init(struct Operations_component *self, struct Filesystem *FS)
  • Type containing all endpoints provided directly by the server; all instances of components included in the server; and the initializing function.

    In our example, the Server_entity structure and Server_entity_init function will be generated:

    #define Server_entity Server_component typedef struct Server_component { struct : Operations_component *OpsComp; } Server_component; void Server_entity_init(struct Server_entity *self, struct Operations_component *OpsComp)
  • Types that define the structure of the constant part of a message for any method of a specific interface.

    In our example, two such types will be generated: Filesystem_req (for a request) and Filesystem_res (for a response).

    typedef union Filesystem_req { struct nk_message base_; struct Filesystem_Open_req Open; }; typedef union Filesystem_res { struct nk_message base_; struct Filesystem_Open_res Open; };
  • Types that define the structure of the constant part of a message for any method of any endpoint of a specific component.

    If embedded components are present, these types also contain structures of the constant part of a message for any method of any endpoint included in all embedded components.

    In our example, two such types will be generated: Operations_component_req (for a request) and Operations_component_res (for a response).

    typedef union Operations_component_req { struct nk_message base_; Filesystem_req FS; } Operations_component_req; typedef union Operations_component_res { struct nk_message base_; Filesystem_res FS; } Operations_component_res;
  • Types that define the structure of the constant part of a message for any method of any endpoint of a specific component whose instance is included in the server.

    If embedded components are present, these types also contain structures of the constant part of a message for any method of any endpoint included in all embedded components.

    In our example, two such types will be generated: Server_entity_req (for a request) and Server_entity_res (for a response).

    #define Server_entity_req Server_component_req typedef union Server_component_req { struct nk_message base_; Filesystem_req OpsComp_FS; } Server_component_req; #define Server_entity_res Server_component_res typedef union Server_component_res { struct nk_message base_; Filesystem_res OpsComp_FS; } Server_component_res;
  • Dispatch methods (dispatchers) for a separate interface, component, or process class.

    Dispatchers analyze the received query (the RIID and MID values), call the implementation of the corresponding method, and then save the response in the buffer. In our example, three dispatchers will be generated: Filesystem_interface_dispatch, Operations_component_dispatch, and Server_entity_dispatch.

    The process class dispatcher handles the request and calls the methods implemented by this class. If the request contains an incorrect RIID (for example, an RIID for a different endpoint that this process class does not have) or an incorrect MID, the dispatcher returns NK_EOK or NK_ENOENT.

    nk_err_t Server_entity_dispatch(struct Server_entity *self, const struct nk_message *req, const struct nk_arena *req_arena, struct nk_message *res, struct nk_arena *res_arena)

    In special cases, you can use dispatchers of the interface and the component. They take an additional argument: interface implementation ID (nk_iid_t). The request will be handled only if the passed argument and RIID from the request match, and if the MID is correct. Otherwise, the dispatchers return NK_EOK or NK_ENOENT.

    nk_err_t Operations_component_dispatch(struct Operations_component *self, nk_iid_t iidOffset, const struct nk_message *req, const struct nk_arena *req_arena, struct nk_message *res, struct nk_arena *res_arena) nk_err_t Filesystem_interface_dispatch(struct Filesystem *impl, nk_iid_t iid, const struct nk_message *req, const struct nk_arena *req_arena, struct nk_message *res, struct nk_arena *res_arena)
Page top
[Topic transport_code_overview]

Working with an IPC message arena

Arena overview

From the perspective of a developer of KasperskyOS-based solutions, an IPC message arena is a byte buffer in the memory of a process intended for storing variable-size data transmitted over IPC. This variable-size data includes input parameters, output parameters, and error parameters of interface methods (and/or elements of these parameters) that have variable-size IDL types. An arena is also used when querying the Kaspersky Security Module to store input parameters of security interface methods (and/or elements of these parameters) that have variable-size IDL types. (Parameters of fixed-size interface methods are stored in the constant part of an IPC message.) Arenas are used on the client side and on the server side. One arena is intended either for transmitting or for receiving variable-size data through IPC, but not for both transmitting and receiving this data at the same time. In other words, arenas can be divided into IPC request arenas (containing input parameters of interface methods) and IPC response arenas (containing output parameters and error parameters of interface methods).

Only the utilized part of an arena that is occupied by data is transmitted over IPC. (If it has no data, the arena is not transmitted.) The utilized part of an arena includes one or more segments. One segment of an arena stores an array of same-type objects, such as an array of single-byte objects or an array of structures. Arrays of different types of objects may be stored in different segments of an arena. The starting address of an arena must be equal to the boundary of a 2^N-byte sequence, where 2^N is a value that is greater than or equal to the size of the largest primitive type in the arena (for example, the largest field of a primitive type in a structure). The address of an arena chunk must also be equal to the boundary of a 2^N-byte sequence, where 2^N is a value that is greater than or equal to the size of the largest primitive type in the arena chunk.

An arena must have multiple segments if the interface method has multiple variable-size input, output, or error parameters, or if multiple elements of input, output, or error parameters of the interface method have a variable size. For example, if an interface method has an input parameter of the sequence IDL type and an input parameter of the bytes IDL type, the IPC request arena will have at least two segments. In this case, it may even require additional segments if a parameter of the sequence IDL type consists of elements of a variable-size IDL type (for example, if elements of a sequence are string buffers). As another example, if an interface method has one output parameter of the struct IDL type that contains two fields of the bytes and string type, the IPC response arena will have two segments.

Due to the alignment of arena chunk addresses, there may be unused intervals between these chunks. Therefore, the size of the utilized part of an arena may exceed the size of the data it contains.

API for working with an arena

The set of functions and macros for working with an arena is defined in the header file sysroot-*-kos/include/nk/arena.h from the KasperskyOS SDK. In addition, the function for copying a string to an arena is declared in the header file sysroot-*-kos/include/coresrv/nk/transport-kos.h from the KasperskyOS SDK.

You should not use functions from the header file sysroot-*-kos/include/nk/arena.h whose names begin with the _ or __ character because these functions are internal implementation details that may change.

On hardware platforms running an Arm processor architecture, input and output parameters of the API cannot be saved in "Device memory" because this could lead to unpredictable behavior. API parameters must be saved in "Normal memory". To copy data from "Device memory" to "Normal memory" and vice versa, you must use the RtlPedanticMemcpy() function declared in the header file sysroot-*-kos/include/rtl/string_pedantic.h from the KasperskyOS SDK.

Information on the functions and macros defined in the header file sysroot-*-kos/include/nk/arena.h is provided in the table below. In these functions and macros, an arena and arena chunk are identified by an arena descriptor (the nk_arena type) and an arena chunk descriptor (the nk_ptr_t type), respectively. An arena descriptor is a structure containing three pointers: one pointer to the start of the arena, one pointer to the start of the unused part of the arena, and one pointer to the end of the arena. An arena chunk descriptor is a structure containing the offset of the arena chunk in bytes (relative to the start of the arena) and the size of the arena chunk in bytes. (The arena chunk descriptor type is defined in the header file sysroot-*-kos/include/nk/types.h from the KasperskyOS SDK.)

Creating an arena

To pass variable-size parameters of interface methods over IPC, you must create arenas on the client side and on the server side. (When IPC requests are handled on the server side using the NkKosDoDispatch() or NkKosDoDispatchEx() functions declared in the header file sysroot-*-kos/include/coresrv/nk/transport-kos-dispatch.h from the KasperskyOS SDK, the IPC request arena and IPC response arena are created automatically.)

To create an arena, you must create a buffer (in the stack or heap) and initialize the arena descriptor.

The address of the buffer must be aligned to comply with the maximum size of a primitive type that can be put into this buffer. The address of a dynamically created buffer usually has adequate alignment to hold the maximum amount of data of the primitive type. To ensure the required alignment of the address of a statically created buffer, you can use the alignas specifier.

To initialize an arena descriptor using the pointer to an already created buffer, you must use an API function or macro:

  • NK_ARENA_INITIALIZER() macro
  • nk_arena_init() function
  • nk_arena_create() function
  • NK_ARENA_FINAL() macro
  • nk_arena_init_final() macro

The type of pointer makes no difference because this pointer is converted into a pointer to a single-byte object in the code of API functions and macros.

The NK_ARENA_INITIALIZER() macro and the nk_arena_init() and nk_arena_create() functions initialize such arena descriptor that may contain one or more segments. The NK_ARENA_FINAL() and nk_arena_init_final() macros initialize the descriptor of this arena, which contains only one chunk spanning the entire arena throughout its entire lifetime.

To create a buffer in the stack and initialize the arena descriptor in one step, use the NK_ARENA_AUTO() macro. This macro creates an arena that may contain one or more segments, and the address of the buffer created by this macro has adequate alignment to hold the maximum amount of data of the primitive type.

The size of an arena must be sufficient to hold variable-size parameters for IPC requests or IPC responses of one interface method or a set of interface methods corresponding to one interface, component, or process class while accounting for the alignment of segment addresses. Automatically generated transport code (the header files *.idl.h, *.cdl.h, and *.edl.h) contain *_arena_size constants whose values are guaranteed to comply with sufficient sizes of arenas in bytes.

The header files *.idl.h, *.cdl.h, and *.edl.h contain the following *_arena_size constants:

  • <interface name>_<interface method name>_req_arena_size – size of an IPC request arena for the specified interface method of the specified interface
  • <interface name>_<interface method name>_res_arena_size – size of an IPC response arena for the specified interface method of the specified interface
  • <interface name>_req_arena_size – size of an IPC request arena for any interface method of the specified interface
  • <interface name>_res_arena_size – size of an IPC response arena for any interface method of the specified interface

The header files *.cdl.h and *.edl.h also contain the following *_arena_size constants:

  • <component name>_component_req_arena_size – size of an IPC request arena for any interface method of the specified component
  • <component name>_component_res_arena_size – size of an IPC response arena for any interface method of the specified component

The *.edl.h header files also contain the following *_arena_size constants:

  • <process class name>_entity_req_arena_size – size of an IPC request arena for any interface method of the specified process class
  • <process class name>_entity_res_arena_size – size of an IPC response arena for any interface method of the specified process class

Constants containing the size of an IPC request arena or IPC response arena for one interface method (<interface name>_<interface method name>_req_arena_size and <interface name>_<interface method name>_res_arena_size) are intended for use on the client side. All other constants can be used on the client side and on the server side.

Examples of creating an arena:

/* Example 1 */ alignas(8) char reqBuffer[Write_WriteInLog_req_arena_size]; struct nk_arena reqArena = NK_ARENA_INITIALIZER( reqBuffer, reqBuffer + sizeof(reqBuffer)); /* Example 2 */ struct nk_arena res_arena; char res_buf[kl_rump_DhcpcdConfig_GetOptionNtpServers_res_arena_size]; nk_arena_init(&res_arena, res_buf, res_buf + sizeof(res_buf)); /* Example 3 */ char req_buffer[kl_CliApplication_Run_req_arena_size]; struct nk_arena req_arena = nk_arena_create(req_buffer, sizeof(req_buffer)); /* Example 4 */ nk_ptr_t ptr; const char *cstr = "example"; nk_arena arena = NK_ARENA_FINAL(&ptr, cstr, strlen(cstr)); /* Example 5 */ const char *path = "path_to_file"; size_t len = strlen(path); /* Structure for saving the constant part of an IPC request */ struct kl_VfsFilesystem_Rmdir_req req; struct nk_arena req_arena; nk_arena_init_final(&req_arena, &req.path, path, len); /* Example 6 */ struct nk_arena res_arena = NK_ARENA_AUTO(kl_Klog_component_res_arena_size);

Adding data to an arena before transmission over IPC

Before transmitting an IPC request on the client side or an IPC response on the server side, data must be added to the arena. If the NK_ARENA_FINAL() macro or the nk_arena_init_final() macro is used to create an arena, you do not need to reserve an arena chunk. Instead, you only need to add data to this chunk. If the NK_ARENA_INITIALIZER() or NK_ARENA_AUTO() macro, or the nk_arena_init() or nk_arena_create() function is used to create an arena, one or multiple segments in the arena must be reserved to hold data. To reserve an arena chunk, you must use an API function or macro:

  • nk_arena_alloc_aligned() macro
  • nk_arena_alloc() macro
  • nk_arena_store_aligned() macro
  • nk_arena_store() macro
  • NkKosCopyStringToArena() function

The arena chunk descriptor, which is passed through the output parameter of these macros and functions and through the output parameter of the NK_ARENA_FINAL() and nk_arena_init_final() macros, must be put into the constant part or into the arena of an IPC message. If an interface method has a variable-size parameter, the constant part of IPC messages contains arena chunk descriptor containing the parameter instead of the actual parameter. If an interface method has a fixed-size parameter with variable-size elements, the constant part of IPC messages contains arena chunk descriptors containing the parameter elements instead of the actual parameter elements. If an interface method has a variable-size parameter containing variable-size elements, the constant part of IPC messages contains arena chunk descriptor containing the descriptors of other arena chunks that contain these parameter elements.

The nk_arena_store_aligned() and nk_arena_store() macros and the NkKosCopyStringToArena() function not only reserve an arena chunk, but also copy data to this chunk.

The nk_arena_alloc_aligned() and nk_arena_alloc() macros get the address of a reserved arena chunk. You can also get an arena chunk address by using the nk_arena_get() macro, which additionally uses the output parameter to pass the quantity of objects accommodated in this chunk.

A reserved arena chunk can be reduced. To do so, you need to use the nk_arena_shrink() macro.

To undo a current reservation of arena chunks so that new chunks can be reserved for other data (after sending an IPC message), call the nk_arena_reset() function. If the NK_ARENA_FINAL() macro or nk_arena_init_final() macro is used to create an arena, you do not need to undo a segment reservation because the arena contains one segment spanning the entire arena throughout its entire life cycle.

Examples of adding data to an arena:

/* Example 1 */ char req_buffer[kl_rump_NpfctlFilter_TableAdd_req_arena_size]; struct nk_arena req_arena = NK_ARENA_INITIALIZER(req_buffer, req_buffer + sizeof(req_buffer)); /* Structure for saving the constant part of an IPC request */ struct kl_rump_NpfctlFilter_TableAdd_req req; if (nk_arena_store(char, &req_arena, &req.tid, tid, tidlen)) return ENOMEM; if (nk_arena_store(char, &req_arena, &req.cidrAddr, cidr_addr, cidr_addrlen)) return ENOMEM; /* Example 2 */ char req_arena_buf[StringMaxSize]; struct nk_arena req_arena = NK_ARENA_INITIALIZER(req_arena_buf, req_arena_buf + sizeof(req_arena_buf)); /* Structure for saving the constant part of an IPC request */ kl_drivers_FBConsole_SetFont_req req; size_t buf_size = strlen(fileName) + 1; char *buf = nk_arena_alloc(char, &req_arena, &req.fileName, buf_size); memcpy(buf, fileName, buf_size); /* Example 3 */ char reqArenaBuf[kl_core_DCM_req_arena_size]; struct nk_arena reqArena = NK_ARENA_INITIALIZER(reqArenaBuf, reqArenaBuf + sizeof(reqArenaBuf)); /* Structure for saving the constant part of an IPC request */ kl_core_DCM_Subscribe_req req; rc = NkKosCopyStringToArena(&reqArena, &req.endpointType, endpointType); if (rc != rcOk) return rc; rc = NkKosCopyStringToArena(&reqArena, &req.endpointName, endpointName); if (rc != rcOk) return rc; rc = NkKosCopyStringToArena(&reqArena, &req.serverName, serverName); if (rc != rcOk) return rc; /* Example 4 */ unsigned counter = 0; nk_ptr_t *paths; /* Reserve an arena chunk for descriptors of other arena chunks */ paths = nk_arena_alloc(nk_ptr_t, resArena, &res->logRes, msgCount); while(...) { ... /* Reserve arena chunks and save their descriptors in * a previously reserved arena chunk with the paths address */ char *str = nk_arena_alloc( char, resArena, &paths[counter], stringLength + 1); if (str == NK_NULL) return NK_ENOMEM; snprintf(str, (stringLength + 1), "%s", buffer); ... counter++; }

Retrieving data from an arena after receipt over IPC

Prior to receiving an IPC request on the server side or an IPC response on the client side for an arena that will store the data received over IPC, you must undo the current reservation of segments by calling the nk_arena_reset() function. This must be done even if the NK_ARENA_FINAL() macro or the nk_arena_init_final() macro is used to create the arena. (The NK_ARENA_INITIALIZER() and NK_ARENA_AUTO() macros, and the nk_arena_init() and nk_arena_create() functions create an arena without reserved segments. You do not need to call the nk_arena_reset() function if this arena will only be used once to save data received over IPC.)

You do not need to free the currently reserved arena chunks if one of the syscalls.h API functions is directly used to receive an IPC message. However, you must do that prior to calling transport code methods on the client side and the nk_transport_recv() function on the server side. (The nk_transport_recv() function is declared in the header file sysroot-*-kos/include/nk/transport.h from the KasperskyOS SDK.)

When calling the NkKosTransport_Dispatch() function declared in the header file sysroot-*-kos/include/coresrv/nk/transport-kos.h from the KasperskyOS SDK, you must specify arenas that do not have reserved chunks. If the NK_ARENA_FINAL() or nk_arena_init_final() macro is used to create these arenas, call the nk_arena_reset() function to free the reserved chunks.

To get pointers to arena chunks and the quantity of objects accommodated in these chunks, you must use the nk_arena_get() macro while using the input parameter to pass the corresponding arena chunk descriptors received from the constant part and arena of the IPC message.

Example of receiving data from an arena:

struct nk_arena res_arena; char res_buf[kl_rump_DhcpcdConfig_Version_res_ver_size]; nk_arena_init(&res_arena, res_buf, res_buf + sizeof(res_buf)); /* Structure for saving an IPC request */ struct kl_rump_DhcpcdConfig_Version_req req; req.buflen = buflen; /* Structure for saving an IPC response */ struct kl_rump_DhcpcdConfig_Version_res res; /* Call the interface method */ if (kl_rump_DhcpcdConfig_Version(dhcpcd.proxy, &req, NULL, &res, &res_arena) != NK_EOK) return -1; size_t ptrlen; char *ptr = nk_arena_get(char, &res_arena, &res.ver, &ptrlen); memcpy(buf, ptr, ptrlen);

Additional capabilities of the API

To get the arena size, call the nk_arena_capacity() function.

To get the size of the utilized part of the arena, call the nk_arena_allocated_size() function.

To verify that the arena chunk descriptor is correct, use the nk_arena_validate() macro.

Information about API functions and macros

Functions and macros of arena.h

Function/Macro

Information about the function/macro

NK_ARENA_INITIALIZER()

Purpose

Initializes the arena descriptor.

Parameters

  • [in] _start – pointer to the start of the arena.
  • [in] _end – pointer to the end of the arena.

Macro values

Code that initializes the arena descriptor.

nk_arena_init()

Purpose

Initializes the arena descriptor.

Parameters

  • [out] self – pointer to the arena descriptor.
  • [in] start – pointer to the start of the arena.
  • [in] end – pointer to the end of the arena.

Returned values

N/A

nk_arena_create()

Purpose

Creates and initializes the arena descriptor.

Parameters

  • [in] start – pointer to the start of the arena.
  • [in] size – arena size in bytes.

Returned values

Arena descriptor.

NK_ARENA_AUTO()

Purpose

Creates a buffer in the stack, and creates and initializes the arena descriptor.

Parameters

  • [in] size – arena size in bytes. It must be defined as a constant.

Macro values

Arena descriptor.

NK_ARENA_FINAL()

Purpose

Initializes the arena descriptor containing only one segment.

Parameters

  • [out] ptr – pointer to the arena chunk descriptor.
  • [in] start – pointer to the start of the arena.
  • [in] count – number of objects in the arena chunk.

Macro values

Arena descriptor.

nk_arena_reset()

Purpose

Resets the reservation of arena chunks.

Parameters

  • [in,out] self – pointer to the arena descriptor.

Returned values

N/A

nk_arena_capacity()

Purpose

Gets the size of an arena.

Parameters

  • [in] self – pointer to the arena descriptor.

Returned values

Size of the arena in bytes.

Additional information

If the parameter has the NK_NULL value, it returns 0.

nk_arena_validate()

Purpose

Verifies that the arena chunk descriptor is correct.

Parameters

  • [in] type – type of objects in the arena chunk.
  • [in] arena – pointer to the arena descriptor.
  • [in] ptr – pointer to the arena chunk descriptor.

Macro values

It has a value of 0 when the arena size is not zero if all of the following conditions are fulfilled:

  1. The offset specified in the arena chunk descriptor does not exceed the arena size.
  2. The size specified in the arena chunk descriptor does not exceed the arena size reduced by the offset specified in the arena chunk descriptor.
  3. The size specified in the arena chunk descriptor is a multiple of the size of the type of objects in this arena chunk.

It has a value of 0 when the arena size is zero if all of the following conditions are fulfilled:

  1. The offset specified in the arena chunk descriptor is equal to zero.
  2. The size specified in the arena chunk descriptor is equal to zero.

It has a value of -1 if even one of the conditions is not fulfilled (regardless of whether the arena size is zero or non-zero), or if the ptr parameter has the NK_NULL value.

nk_arena_allocated_size()

Purpose

Gets the size of the utilized part of the arena.

Parameters

  • [in] self – pointer to the arena descriptor.

Returned values

Size of the utilized part of the arena, in bytes.

Additional information

If the parameter has the NK_NULL value, it returns 0.

nk_arena_init_final()

Purpose

Initializes the arena descriptor containing only one segment.

Parameters

  • [out] arena – pointer to the arena descriptor.
  • [out] ptr – pointer to the arena chunk descriptor.
  • [in] start – pointer to the start of the arena.
  • [in] count – number of objects in the arena chunk.

Macro values

N/A

nk_arena_alloc_aligned()

Purpose

Reserves an arena chunk with the specified alignment for the defined number of objects of the specified type.

Parameters

  • [in] type – type of objects in the arena chunk.
  • [in,out] arena – pointer to the arena descriptor.
  • [out] ptr – pointer to the arena chunk descriptor.
  • [in] count – number of objects in the arena chunk.
  • [in] align – value defining the arena chunk alignment. The arena chunk address can be unaligned (align=1) or aligned (align=2,4,...,2^N) to the boundary of a 2^N-byte sequence (for example, two-byte or four-byte).

Macro values

It has the address of the reserved arena chunk if successful, otherwise NK_NULL.

nk_arena_alloc()

Purpose

Reserves an arena chunk for the defined number of objects of the specified type.

Parameters

  • [in] type – type of objects in the arena chunk.
  • [in,out] arena – pointer to the arena descriptor.
  • [out] ptr – pointer to the arena chunk descriptor.
  • [in] count – number of objects in the arena chunk.

Macro values

It has the address of the reserved arena chunk if successful, otherwise NK_NULL.

nk_arena_store_aligned()

Purpose

Reserves an arena chunk with the specified alignment for the defined number of objects of the specified type and copies these objects to the reserved chunk.

Parameters

  • [in] type – type of objects in the arena chunk.
  • [in,out] arena – pointer to the arena descriptor.
  • [out] ptr – pointer to the arena chunk descriptor.
  • [in] src – pointer to the buffer containing the objects that need to be copied to the arena chunk.
  • [in] count – number of objects in the arena chunk.
  • [in] align – value defining the arena chunk alignment. The arena chunk address can be unaligned (align=1) or aligned (align=2,4,...,2^N) to the boundary of a 2^N-byte sequence (for example, two-byte or four-byte).

Macro values

It has a value of 0 if successful, otherwise it has a value of -1.

nk_arena_store()

Purpose

Reserves an arena chunk for the specified number of objects of the defined type and copies these objects to the reserved chunk.

Parameters

  • [in] type – type of objects in the arena chunk.
  • [in,out] arena – pointer to the arena descriptor.
  • [out] ptr – pointer to the arena chunk descriptor.
  • [in] src – pointer to the buffer containing the objects that need to be copied to the arena chunk.
  • [in] count – number of objects in the arena chunk.

Macro values

It has a value of 0 if successful, otherwise it has a value of -1.

nk_arena_get()

Purpose

Gets the address of an arena chunk and the quantity of objects of the specified type in this chunk.

Parameters

  • [in] type – type of objects in the arena chunk.
  • [in] arena – pointer to the arena descriptor.
  • [in] ptr – pointer to the arena chunk descriptor.
  • [out] count – pointer to the number of objects in the arena chunk.

Macro values

It has the address of the arena chunk if successful, otherwise NK_NULL.

Additional information

If the size of the arena chunk is not a multiple of the size of the type of objects in this chunk, it has the NK_NULL value.

nk_arena_shrink()

Purpose

Reduces the size of an arena chunk.

Parameters

  • [in] type – type of objects in the arena chunk.
  • [in,out] arena – pointer to the arena descriptor.
  • [in,out] ptr – pointer to the arena chunk descriptor.
  • [in] count – quantity of objects in the reduced arena chunk.

Macro values

It has the address of the reduced arena chunk if successful, otherwise NK_NULL.

Additional information

If the required size of the arena chunk exceeds the current size, it has the NK_NULL value.

If the alignment of the arena chunk that needs to be reduced does not match the type of objects for which the reduced chunk is intended, it has the NK_NULL value.

If the arena chunk that needs to be reduced is the last chunk in the arena, the freed part of this chunk will become available for reservation of subsequent chunks.

Page top
[Topic ipc_arena]

Transport code in C++

Before reading this section, you should review the information on the IPC mechanism in KasperskyOS and the IDL, CDL, and EDL descriptions.

Implementation of interprocess interaction requires transport code, which is responsible for generating, sending, receiving, and processing IPC messages.

However, a developer of a KasperskyOS-based solution does not have to write their own transport code. Instead, you can use the special tools and libraries included in the KasperskyOS SDK. These libraries enable a solution component developer to generate transport code based on IDL, CDL and EDL descriptions related to this component.

Transport code

The KasperskyOS SDK includes the nkppmeta compiler for generating transport code in C++.

The nkppmeta compiler lets you generate transport C++ proxy objects and stubs for use by both a client and a server.

Proxy objects are used by the client to pack the parameters of the called method into an IPC request, execute the IPC request, and unpack the IPC response.

Stubs are used by the server to unpack the parameters from the IPC request, dispatch the call to the appropriate method implementation, and pack the IPC response.

Generating transport code for development in C++

The CMake commands add_nk_idl(), add_nk_cdl() and add_nk_edl() are used to generate transport proxy objects and stubs using the nkppmeta compiler when building a solution.

C++ types in the *.idl.cpp.h file

Each interface is defined in an IDL description. This description defines the interface name, signatures of interface methods, and data types for the parameters of interface methods.

The CMake command add_nk_idl() is used to generate transport code when building a solution. This command creates a CMake target for generating header files for the defined IDL file when using the nkppmeta compiler.

The generated header files contain a C++ representation for the interface and data types described in the IDL file, and the methods required for use of proxy objects and stubs.

The mapping of data types declared in an IDL file to C++ types are presented in the table below.

Mapping IDL types to C++ types

IDL type

C++ type

SInt8

int8_t

SInt16

int16_t

SInt32

int32_t

SInt64

int64_t

UInt8

uint8_t

UInt16

uint16_t

UInt32

uint32_t

UInt64

uint64_t

Handle

Handle (defined in sysroot-*-kos/include/handle/handletype.h)

string

std::string

union

std::variant

struct

struct

array

std::array

sequence

std::vector

bytes

std::vector<std::byte>

Working with transport code in C++

The scenarios for developing a client and server that exchange IPC messages are presented in the sections titled Statically creating IPC channels for C++ development and Dynamically creating IPC channels for C++ development.

Page top
[Topic cpp_proxy_stubs]

Statically creating IPC channels for C++ development

To implement a client program that calls a method of an endpoint provided by a server program:

  1. Include the generated header file (*.edl.cpp.h) of the client program description.
  2. Include the generated header files of the descriptions of the utilized interfaces (*.idl.cpp.h).
  3. Include the header files from the KasperskyOS SDK:
    • sysroot-*-kos/include/kosipc/application.h
    • sysroot-*-kos/include/kosipc/api.h
    • sysroot-*-kos/include/kosipc/connect_static_channel.h
  4. Create and initialize the application object by calling the kosipc::MakeApplicationAutodetect() function. (You can also use the kosipc::MakeApplication() and kosipc::MakeApplicationPureClient() functions.)
  5. Get the client IPC handle of the channel and the Runtime Implementation Identifier (RIID) by calling the kosipc::ConnectStaticChannel() function.

    This function gets the name of the IPC channel (from the init.yaml file) and the qualified name of the endpoint (from the CDL and EDL descriptions of the solution component).

  6. Create and initialize the proxy object for the utilized endpoint by calling the MakeProxy() function.

To implement a server program that provides endpoints to other programs:

  1. Include the generated header file *.edl.cpp.h containing a description of the component structure of the program, including all provided endpoints.
  2. Include the header files from the KasperskyOS SDK:
    • sysroot-*-kos/include/kosipc/event_loop.h
    • sysroot-*-kos/include/kosipc/api.h
    • sysroot-*-kos/include/kosipc/serve_static_channel.h
  3. Create classes containing the implementations of interfaces that this program and its components provide as endpoints.
  4. Initialize the application object by calling the kosipc::MakeApplicationAutodetect() function.
  5. Create and initialize the kosipc::components::Root structure, which describes the component structure of the program and describes the interfaces of all endpoints provided by the program.
  6. Bind fields of the kosipc::components::Root structure to the objects that implement the corresponding endpoints.

    Fields of the Root structure replicate the hierarchy of components and endpoints that are collectively defined by the CDL and EDL files.

  7. Get the server IPC handle of the channel by calling the ServeStaticChannel() function.

    This function gets the name of the IPC channel (from the init.yaml file) and the structure created at step 5.

  8. Create the kosipc::EventLoop object by calling the MakeEventLoop() function.
  9. Start the loop for dispatching incoming IPC messages by calling the Run() method of the kosipc::EventLoop object.
Page top
[Topic static_IPC_kosipc]

Dynamically creating IPC channels for C++ development

Dynamic creation of an IPC channel on the client side includes the following steps:

  1. Include the generated description header file (*.edl.cpp.h) in the client program.
  2. Include the generated header files of the descriptions of the utilized interfaces (*.idl.cpp.h).
  3. Include the header files:
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/application.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/make_application.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/connect_dynamic_channel.h
  4. Get the pointers to the server name and the qualified name of the endpoint by using a name server (NameServer system program). To do so, you must connect to the name server by calling the NsCreate() function and find the server that provides the required endpoint by using the NsEnumServices() function. For more details, refer to Dynamically creating IPC channels (cm_api.h, ns_api.h).
  5. Create an application object by calling the kosipc::MakeApplicationAutodetect() function. (You can also use the kosipc::MakeApplication() and kosipc::MakeApplicationPureClient() functions.)
  6. Create a proxy object for the required endpoint by calling the MakeProxy() function. Use the kosipc::ConnectDynamicChannel() function call as the input parameter of the MakeProxy() function. Pass the pointers for the server name and qualified name of the endpoint obtained at step 4 to the kosipc::ConnectDynamicChannel() function.

After successful initialization of the proxy object, the client can call methods of the required endpoint.

Example

NsHandle ns; // Connect to a name server Retcode rc = NsCreate(RTL_NULL, INFINITE_TIMEOUT, &ns); char serverName[kl_core_Types_UCoreStringSize]; char endpointName[kl_core_Types_UCoreStringSize]; // Get pointers to the server name and qualified name of the endpoint rc = NsEnumServices( ns, interfaceName, 0, serverName, kl_core_Types_UCoreStringSize, endpointName, kl_core_Types_UCoreStringSize); // Create and initialize the application object kosipc::Application app = kosipc::MakeApplicationAutodetect(); // Create and initialize the proxy object auto proxy = app.MakeProxy<IDLInterface>( kosipc::ConnectDynamicChannel(serverName, endpointName)) // Call the method of the required endpoint proxy->DoSomeWork();

Dynamic creation of an IPC channel on the server side includes the following steps:

  1. Include the generated header file (*.edl.cpp.h) containing a description of the component structure of the server, including all provided endpoints, in the server program.
  2. Include the header files:
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/application.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/event_loop.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/make_application.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/root_component.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/serve_dynamic_channel.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/simple_connection_acceptor.h
  3. Create classes containing the implementations of interfaces that the server provides as endpoints. Create and initialize the objects of these classes.
  4. Create an application object by calling the kosipc::MakeApplicationAutodetect() function.
  5. Create and initialize the kosipc::components::Root class object that describes the structure of components and endpoints of the server. This structure is generated from the descriptions in the CDL and EDL files.
  6. Bind the kosipc::components::Root class object to the class objects created at step 3.
  7. Create and initialize the kosipc::EventLoop class object that implements a loop for dispatching incoming IPC messages by calling the MakeEventLoop() function. Use the ServeDynamicChannel() function call as an input parameter of the MakeEventLoop() function. Pass the kosipc::components::Root class object created at step 5 to the ServeDynamicChannel() function.
  8. Start the loop for dispatching incoming IPC messages in a separate thread by calling the Run() method of the kosipc::EventLoop object.
  9. Create and initialize the object that implements the handler for receiving incoming requests to dynamically create an IPC channel.

    When creating an object, you can use the kosipc::SimpleConnectionAcceptor class, which is the standard implementation of the kosipc::IConnectionAcceptor interface. (The kosipc::IConnectionAcceptor interface is defined in the file named /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/connection_acceptor.h.) In this case, the handler will implement the following logic: if the endpoint requested by the client was published on the server, the request from the client will be accepted. Otherwise, it will be rejected.

    If you need to create your own handler, you should implement your own request handling logic in the OnConnectionRequest() method inherited from the kosipc::IConnectionAcceptor interface. This method will be called by the server when it receives a request for dynamic IPC channel creation from the client.

    You must call the ServeDynamicChannel() function (see step 7) strictly before creating the kosipc::SimpleConnectionAcceptor object.

  10. Create a kosipc::EventLoop class object that implements a loop for receiving incoming requests to dynamically create an IPC channel by calling the MakeEventLoop() function. Use the ServeConnectionRequests() function call as an input parameter of the MakeEventLoop() function. Pass the object created at step 9 to the ServeConnectionRequests() function.

    There can only be one loop for receiving incoming requests to dynamically create an IPC channel. The loop must work in one thread. The loop for receiving incoming requests to dynamically create an IPC channel must be created after the loop for dispatching incoming IPC channels is created (see step 7).

  11. Start the loop for receiving incoming requests for a dynamic connection in the current thread by calling the Run() method of the kosipc::EventLoop object.

Example

// Create class objects that implement interfaces // provided by the server as endpoints MyIDLInterfaceImp_1 impl_1; MyIDLInterfaceImp_2 impl_2; // Create and initialize the application object kosipc::Application app = kosipc::MakeApplicationAutodetect(); // Create and initialize the root object that describes // the components and endpoints of the server kosipc::components::Root root; // Bind the root object to the class objects that implement the server endpoints. // The fields of the root object repeat the description of components and endpoints // defined collectively by the CDL and EDL files. root.component1.endpoint1 = &impl_1; root.component2.endpoint2 = &impl_2; // Create and initialize the object that implements the // loop for dispatching incoming IPC messages kosipc::EventLoop loopDynamicChannel = app.MakeEventLoop(ServeDynamicChannel(root)); // Start the loop for dispatching incoming IPC messages in a separate thread std::thread dynChannelThread( [&loopDynamicChannel]() { loopDynamicChannel.Run(); } ); // Create an object that implements a standard handler for receiving incoming requests // to dynamically create an IPC channel kosipc::SimpleConnectionAcceptor acceptor(root); // Create an object that implements a loop for receiving incoming requests // to dynamically create an IPC channel kosipc::EventLoop loopDynamicChannel = app.MakeEventLoop(ServeConnectionRequests(&acceptor)); // Start the loop for receiving incoming requests to dynamically create an IPC channel in the current thread loopConnectionReq.Run();

If necessary, you can create and initialize multiple kosipc::components::Root class objects combined into a list of objects of the ServiceList type using the AddServices() method. For example, use of multiple objects enables you to separate components and endpoints of the server into groups or publish endpoints under different names.

Example

// Create and initialize the group_1 object kosipc::components::Root group_1; group_1.component1.endpoint1 = &impl_1; group_1.component2.endpoint2 = &impl_2; // Create and initialize the group_2 object kosipc::components::Root group_2; group_2.component1.endpoint1 = &impl_3; group_2.component2.endpoint2 = &impl_4; // Create and initialize the group_3 object kosipc::components::Root group_3; group_3.component1.endpoint1 = &impl_5; // Create a list of objects ServiceList endpoints; endpoints.AddServices(group_1); endpoints.AddServices(group_2); endpoints.AddServices(group_3.component1.endpoint1, "SomeCustomEndpointName"); // Create an object that implements the handler for receiving incoming requests // to dynamically create an IPC channel kosipc::SimpleConnectionAcceptor acceptor(std::move(endpoints)); // Create an object that implements a loop for receiving incoming requests // to dynamically create an IPC channel kosipc::EventLoop loopDynamicChannel = app.MakeEventLoop(ServeConnectionRequests(&acceptor));
Page top
[Topic dynamic_IPC_kosipc]

Working with KPA packages

A KPA package is a file in the proprietary KPA format that serves as packaging for a program intended for installation in a KasperskyOS-based solution. It includes the following elements:

  • KPA package header. Unique sequence of bytes used to identify the KPA format.
  • KPA package manifest. Data structure that describes a JSON file containing detailed information about the KPA package.
  • KPA package components. Aligned byte sequences with arbitrary contents. KPA package components may include executable files, libraries, text data, or any other data required for the program to work.
  • KPA package index. Data structure that describes the number of KPA package components, their checksums, and their sizes.

A compressed KPA package is a file in KPAC format. KPAC format is a variant of the ZIP format but with the following limitations: no directory hierarchy, no embedded ZIP archives, and a limit on the size and quantity of its archived files. A KPAC file contains a KPA package, its external signature, and an index file. The external signature of a KPA package is a file in the proprietary KCAT format that resides outside of the KPA package file. The external signature protects against spoofing and modifications of the KPA package itself and the index file of the KPA package. The index file of a KPA package is a file in the proprietary KIDX format that is used to check the integrity of the KPA package.

Managing KPA packages

KasperskyOS Community Edition includes the following resources for KPA package management:

  • CMake library platform/kpa intended for building KPA packages during the KasperskyOS-based solution build process. When using functions of the CMake library platform/kpa, the KPA package manifest is created automatically.
  • Tools that let you build a KPA package from program source files in the system where the SDK is installed and then install the KPA package in a built KasperskyOS-based solution image.
  • PackageManager component that lets you install KPA packages in an operational KasperskyOS-based solution, remove KPA packages, and receive information about them.

In this section

KPA package manifest

Tools for managing KPA packages

PackageManager component usage scenario

Page top
[Topic kpa_package]

KPA package manifest

A KPA package manifest is a JSON file that contains the information required when installing and using a KPA package. A list of the main keys for the KPA package manifest is presented in the table below.

Main keys of a KPA package manifest

Key name

Value type

Description

Required

Example

version

String

Manifest version number in the format {major}.{minor}, where major is the major version of the manifest and minor is the minor version of the manifest. The current version of the manifest is 2.0. It is not recommended to change this version because any change could result in problems with program operation.

Yes

"version": "2.0"

digestType

String

Identifier (OID) of the hash function that is used to calculate the checksum of object files and other checksums. If the value is not defined or is an empty string, the value "2.16.840.1.101.3.4.2.1" (SHA256 hash function) is used by default.

No

"digestType": "2.16.840.1.101.3.4.2.1"

application

Object

Information about the program.

Yes

Application object

platform

Object

Information about the target platform of the program.

Yes

Platform object

components

List of objects

List of KPA package components.

No

List of "components" objects

runConfiguration

List of objects

List of program startup configurations.

No

List of runConfiguration objects

privateStorage

Object

Isolated storage of program data.

No

PrivateStorage object

extensions

Object

User-defined object that lets you add custom contents to the manifest to meet the specific demands of the program developer.

No

Custom contents

In this section

Application object

Platform object

List of Components objects

List of runConfiguration objects

PrivateStorage object

Page top
[Topic kpa_manifest]

Application object

The application object includes keys containing information about the program installed from the KPA package. A list of these keys is provided in the table below.

List of keys of an application object

Key name

Value type

Description

Required

Example

id

String

Unique ID of the program. This ID enables unequivocal identification of installed program. After it is published, the program ID must not change. If the program ID changes, all other participants of the installed program life cycle will see it as a different program.

Yes

"id": "helloworld"

name

String

Displayed named of the program (for example, used for display in user interfaces).

Yes

"name": "Hello World"

version

String

Program version.

Yes

"version": "1.2.34"

buildNumber

String

Program build number.

Yes

"buildNumber": "182"

systemApplication

Logical

Logical value indicating whether the program is a system program: true means that it is a system program, and false means that it is application. In the current version of KasperskyOS Community Edition, the only available value is false.

Yes

"systemApplication": false

description

String

Program description.

No

"description": "Sample application"

Page top
[Topic manifest_application]

Platform object

The platform object includes keys containing information about the target platform of the program. A list of these keys is provided in the table below.

List of keys of a platform object

Key name

Value type

Description

Required

Example

id

String

Operating system ID.

Yes

"id": "kos.ce"

sdk

Object

Target API level that must be provided by the KasperskyOS Community Edition device for optimal operation of the program, and the name and version of the SDK used to build this program.

Yes

"sdk":

{

"buildSdkName": "KasperskyOS-Community-Edition-RaspberryPi4b"

"buildSdkVersion": "1.3.0.3"

"targetApiLevel": 4

}

 

buildSdkName

String

Name of the SDK used to build this program.

Yes

buildSdkVersion

String

Version of the SDK used to build this program.

Yes

targetApiLevel

Integer

Target API level that must be provided by the device hosting KasperskyOS Community Edition.

Yes

hwSpecification

Object

Technical specification of the hardware platform.

Yes

"hwSpecification":

{

"arch": "aarch64-kos"

"cpu": "bcm2711"

}

 

arch

String

Architecture of the hardware platform.

Yes

cpu

String

Name of the processor used to run the hardware platform.

Yes

Page top
[Topic manifest_platform]

List of "components" objects

The list of components objects includes keys containing information about the components added to the KPA package. A list of these keys is provided in the table below.

List of keys for describing a component instance in the list of components objects

Key name

Value type

Description

Required

Example

name

String

Name of the KPA package component.

Yes

"name": "imageHighResolution"

directoryPath

String

Path of the directory relative to the path /<package name>/res where KPA package component should be installed. It is ignored if the componentType key value is set to bin or lib.

No

"directoryPath": "images/highResolution"

digest

String

Checksum of the KPA package component file.

Yes

"digest": "2d541fe063c195a3b8a90204f2c234e1b5daf664db381faa4f2b81067733d6c3"

componentType

String

Component type:

  • bin – an executable file. It is put into the /<program_name>/bin directory when the program is installed.
  • lib – shared library. It is put into the directory /<program_name>/lib when the program is installed.
  • res – user-defined resource. If the directoryPath key value is defined, the resource is put into the directory /<package_name>/res/<directoryPath> when the program is installed. Otherwise, the resource is put into the directory /<package_name>/res.
  • manifestLocale – file containing information for KPA package manifest localization. When the program is installed, KPA package manifest localization files are put into the directory /<program_name>/manifest_locales if a value is not defined for the directoryPath key.

Yes

"componentType": "res"

Page top
[Topic manifest_components]

List of runConfiguration objects

The list of runConfiguration objects includes keys containing information about the possible startup configurations of the program. A list of these keys is provided in the table below.

List of keys for describing a startup configuration instance for the list of runConfiguration objects

Key name

Value type

Description

Required

Example

id

String

Startup configuration ID that is unique within the specific KPA package.

Yes

"id": "app"

name

String

Startup configuration name.

This is a localized string.

Yes

"name": "application"

or

"name": "@runConfigurationLocalizedName@"

type

String

Startup configuration type:

  • gui – process with a graphical user interface.
  • service – endpoint process.

Yes

"type": "service"

args

List of strings

List of arguments in the form of a string array.

No

"args":

[

"networkSpeed=4096",

"nthreads=2"

]

envVariables

List of objects

List of environment variables.

No

"envVariables":

{

"name": "IMAGES"

"value": "images"

}

 

name

String

Environment variable name.

Yes (if envVariables is used)

value

String

Environment variable value (can be an empty string).

Yes (if envVariables is used)

primary

Logical

Indicates whether the startup configuration is the primary one during program startup: true means that it is primary, and false means that it is not.

Yes

"primary": true

autorun

Logical

Indicates whether this configuration is started automatically: true means that it is started automatically, and false means that it is not. The default value is false.

No

"autorun": false

eiid

String

Program security class. Required for the KasperskyOS security module.

No

"eiid": "application.Application"

path

String

Path to the KPA package component file. The path is defined relative to /<application_id>.

Yes

"path": "bin/application"

usesService

List of objects

List of startup configurations on which this startup configuration depends. Not supported in the current version of KasperskyOS Community Edition.

No

"usesService":

{

"name": "database"

"useType": "child"

}

 

name

String

Name of the utilized startup configuration. If the startup configuration is in another KPA package, the value of this field must have the format "<package_name>:<startup_configuration_name>". If the startup configuration is in this KPA package, the field may have a value in the format "<package_name>:<startup_configuration_name>" (<package_name> corresponds to the current package) or only "<startup_configuration_name>".

Yes (if usesService is used)

useType

String

Startup configuration usage type:

  • child – the program can start the specified startup configuration as a child process.
  • serviceCanUse – the program may require the specified endpoint.
  • serviceActive – the specified endpoint must be active when the program is started.

Yes (if usesService is used)

Page top
[Topic manifest_run_configuration]

PrivateStorage object

The privateStorage object includes keys containing information about program data storage. A list of these keys is provided in the table below.

The privateStorage object is not supported in the current version of KasperskyOS Community Edition.

List of keys of a privateStorage object

Key name

Value type

Description

Required

Example

size

String

Program data storage size in MB.

Yes

"size": "512"

fsType

String

Program data storage file system type.

Yes

"fsType": "ext4"

Page top
[Topic manifest_private_storage]

Tools for managing KPA packages

KasperskyOS Community Edition includes the following tools for managing KPA packages:

  • cas-pack for building a KPA package in a system where the SDK is installed.
  • cas-inspect for getting information about KPA package contents when working with the SDK.
  • cas-pm for installing one or more KPA packages in a built KasperskyOS-based solution image.

The executable files of these tools are located in the directory /opt/KasperskyOS-Community-Edition-<version>/toolchain/bin/.

In this section

cas-pack tool

cas-inspect tool

cas-pm tool

Page top
[Topic cas_tools]

cas-pack tool

KasperskyOS Community Edition includes the cas-pack tool (the toolchain/bin/cas-pack executable file) intended for building a KPA package in a system where the KasperskyOS Community Edition SDK is installed.

Syntax of the shell command for running the cas-pack tool:

cas-pack {-o|--output} <FILE> --manifest <FILE> --verify [--version] [-h|--help] <FILES>...

Parameters:

  • {-o|--output} <FILE>

    Full name of the built KPA package file.

  • --manifest <FILE>

    Full name of the KPA package manifest file.

  • <FILES>

    List of the full names of files that will be included in the KPA package. Use a space to separate list items. You can use the * character to select all files in the directory.

  • --verify

    Verification of the presence of all KPA package components specified in its manifest and the absence of unspecified components, and calculation of the checksums of KPA package components and their comparison with those specified in the KPA package manifest.

  • --version

    Tool version.

  • -h|--help

    Help text.

Example of the shell command for running the cas-pack tool:

# Packages all program source files residing in the # files directory into the KPA package named helloworld.kpa. A KPA package is built # using information from the manifest.json file, which is the # KPA package manifest. The built KPA package is verified. /opt/KasperskyOS-Community-Edition-<version>/toolchain/bin/cas-pack --output ./helloworld.kpa --manifest ./manifest.json --verify ./files/*
Page top
[Topic tools_cas_pack]

cas-inspect tool

The KasperskyOS Community Edition is delivered with the cas-inspect tool (toolchain/bin/cas-inspect executable file), which lets you get information about the contents of KPA package when working with SDK.

Syntax of the shell command for running the cas-inspect tool:

cas-inspect [-h|--help] [--version] {-i|--input} <PACKAGE> --verify [<COMMAND>] [-o <path>]

Parameters:

  • {-i|--input} <PACKAGE>

    Path to the KPA package (*.kpa file).

  • <COMMAND>

    Commands:

    • dump – prints to standard output the KPA package manifest and information about KPA package components, which includes the size in bytes (Size), offset in bytes (Offset – relative to the end of the KPA package manifest, and Absolute – relative to the start of the KPA package), and the checksum (Digest). This parameter is applied by default.
    • read <manifest|blobs|<hash> – prints the KPA package manifest (read manifest), the contents of all KPA package components (read blobs), or the contents of one KPA package component with the defined checksum (read <hash>). When using the -o <path> parameter, the output is printed to a file. Otherwise, it is printed to the standard output.
    • list – prints the checksum, offset (in bytes) relative to the start of the KPA package, and the size (in bytes) for all KPA package components to the standard output.
    • read-files <FILES>... – prints the contents of a KPA package component based on the component file name. You can specify multiple names of KPA package component files if you separate these names with a space. When using the -o <path> parameter, the output is printed to a file.
    • list-files – prints to standard output a list of all names of KPA package component files included in the KPA package manifest.
  • -o <path>

    Path to the file or directory for saving data when using the commands read {manifest|blobs|<hash>} and read-files <FILES>.... When printing the KPA package manifest (read manifest) or the contents of the KPA package component with the defined checksum (read <hash>), you must specify the file path. When printing the contents of all program components (read blobs), you must specify the path to the directory where each program component will be saved in a separate file with a name matching the checksum of the specific component. When printing the contents of all KPA package components (read-files <FILES>...), you must specify the path to the directory where each KPA package component will be saved in a separate file with the name of the specific component.

  • --verify

    Verification of the presence of all KPA package components specified in its manifest and the absence of unspecified KPA package components, and calculation of the checksums of KPA package components and their comparison with those specified in the KPA package manifest.

  • -h|--help

    Help text.

  • --version

    Tool version.

Examples of shell commands for running the cas-inspect tool:

# Prints the KPA package manifest and information about # KPA package components. cas-inspect -i helloworld.kpa # Prints the KPA package manifest and information about # KPA package components to the console, and verifies the availability of the KPA package components # specified in the manifest and the checksums of # KPA package components. cas-inspect -i helloworld.kpa --verify # Prints the KPA package manifest to a file. cas-inspect -i helloworld.kpa read manifest -o ./manifest # Prints the contents of the KPA package component with the defined # checksum to a file. cas-inspect -i helloworld.kpa read 5d8071308518a7bb003aa084fc995 d2f09b79e9e52f8cd296cb3ee2644ad3951 -o ./comp # Prints the contents of each KPA package component to a separate file. cas-inspect -i helloworld.kpa read blobs -o . # Prints information about KPA package components to the console. cas-inspect -i helloworld.kpa list
Page top
[Topic tools_cas_inspect]

cas-pm tool

KasperskyOS Community Edition includes the cas-pm tool (the toolchain/bin/cas-pm executable file), which installs KPA packages in a built KasperskyOS-based solution image.

Syntax of the shell command for running the cas-pm tool:

cas-pm {-p|--pkgsdir} <DIR> {-d|--dbpath} <PATH> {-a|--appsdir} <DIR> [--rootdir <DIR>] [{-l|--layout} <PATH>] {-e|--extention} <ARG> {-r|--reinstall} <-v[v...]> [--sign-ext <ARG>] [--index-ext <ARG>] <PACKAGES>... [--version] [-h|--help]

Parameters:

  • {-p|--pkgsdir} <DIR>

    SDK host system path to the directory containing the KPA packages to be installed.

  • {-d|--dbpath} <PATH>

    Full name of the SQLite database file that contains data on the installed KPA packages. If the database has not yet been created, it will be automatically generated upon startup of the tool with the specified name, and information about the installed KPA packages will be added to it.

    To ensure that the PackageManager component can detect the database after startup of the KasperskyOS-based solution, complete the following steps:

    1. After calling the cas-pm tool, copy the database file into the file system that will be put into the KasperskyOS-based solution image. If the full name of the database file was originally specified in this file system, this step can be omitted.
    2. The full name of the database file in the file system that will be put into the KasperskyOS-based solution image must be passed to the CMake command create_package_manager_entity() via the DB_PATH parameter (for more details, see PackageManager component usage scenario).
  • {-a|--appsdir} <DIR>

    SDK host system path to the directory intended for storing KPA packages before they are written to the KasperskyOS-based solution image.

  • --rootdir <DIR>

    Relative directory that will be used for installing KPA packages to the KasperskyOS-based solution image. Specify the directory in the file system that will be put into the KasperskyOS-based solution image. Information about the location of KPA packages will be entered into the database and will be required by the PackageManager component when removing KPA packages.

  • {-l|--layout} <PATH>

    Full name of the JSON file that is used to redefine the paths for installing KPA package components. Specify the full file name in the system where the SDK is installed. By default, when a KPA package is installed, its components are put into directories depending on the specific type of KPA package component (for more details, see the componentType key in the article titled List of "components" objects). To change the names of the default directories, define your own values for the keys: bin, res, lib and manifestLocale. To ensure that the PackageManager component can detect KPA package components after the KasperskyOS-based solution is started, the name of this file must be passed in the CUSTOM_LAYOUT parameter of the CMake command create_package_manager_entity() (for more details, see PackageManager component usage scenario).

    Example of a custom_layout_schema.json file:

    { "bin" : "custom-bin-path", "res" : "CustomResPath", "lib" : "CustomLibPath", "manifestLocale" : "Custom_manifestLocale_Path" }
  • {-e|--extention} <ARG>

    Extension for a KPA package file. The default value is kpa.

  • {-r|--reinstall}

    Reinstallation of KPA packages.

  • -v[v...]

    Log level for actions performed by the tool. The number of v characters indicates the log level. Messages are printed to standard output. Available values:

    • -v

      This level logs non-detailed information about normal operation of the tool, and errors and warnings about potential problems.

    • -vv[v...]

      This level adds detailed logging of tool operation information that may be useful to developers for troubleshooting.

  • --sign-ext <ARG>

    Extension for a KPA package external signature file. For more details about a KPA package external signature, see Working with KPA packages.

  • --index-ext <ARG>

    Extension for a KPA package index file. For more details about a KPA package index file, see Working with KPA packages.

  • <PACKAGES>

    List of full names of installed KPA packages in the system where the SDK is installed. You do not need to specify the file extension. Use a space to separate list items.

  • --version

    Tool version.

  • -h|--help

    Help text.

Examples of shell commands for running the cas-pm tool:

# Reinstall the helloworld.kpa package located in the directory defined by the ${PKG_DIR} variable. # The package will first be put into the directory ${ROOTFS_DIR}/package before it is written to the image of the # KasperskyOS-based solution. The directory ${ROOTFS_DIR}/package resides in the file system # that will be copied to the solution image. The repository.sqlite database containing information about the # packages installed in the image will reside in the directory defined by the ${ROOTFS_DIR} variable. # During installation, the actions performed by the tool are logged in detail. /opt/KasperskyOS-Community-Edition-<version>/toolchain/bin/cas-pm --pkgsdir ${PKG_DIR} --reinstall -vvv --dbpath ${ROOTFS_DIR}/repository.sqlite --appsdir ${ROOTFS_DIR}/package --rootdir ${ROOTFS_DIR}/package --extension kpa ${PKG_DIR}/helloworld
Page top
[Topic tools_cas_pm]

PackageManager component usage scenario

The PackageManager component provides an API for managing KPA packages in solutions that are based on KasperskyOS.

The PackageManager component API is built on top of IPC and helps simplify program development. PackageManager is a separate system program that is accessed through IPC. However, developers are provided with a client library that eliminates the necessity of directly using IPC calls.

The programming interface of the PackageManager component is described in the article titled "PackageManager component".

Adding the PackageManager component to a KasperskyOS-based solution

Hereinafter the "client" refers to the program that uses the PackageManager component API to manage KPA packages.

The typical usage scenario for the PackageManager component includes the following steps:

  1. Add the PackageManager program to a solution. To add PackageManager to a solution:
    find_package (package_manager REQUIRED) include_directories (${package_manager_INCLUDE}) add_subdirectory (package_manager)
    • The PackageManager component is provided in the SDK as a set of static libraries and header files, and is built for a specific solution by using the CMake command create_package_manager_entity() from the CMake library package_manager.

      To build the PackageManager program, create a directory named package_manager in the root directory of the project. In the new directory, create a CMakeLists.txt file containing the create_package_manager_entity() command.

      The CMake command create_package_manager_entity() takes the following parameters:

      Mandatory ENTITY parameter that specifies the name of the executable file for the PackageManager program.

      Optional parameters:

      • DEPENDS – additional dependencies for building the PackageManager program.
      • MAIN_CONN_NAME – name of the IPC channel for connecting to the PackageManager process. It must match the value of the mainConnection variable when calling the PackageManager API in the client code.
      • ROOT_PATH – path to the root directory for service files of the PackageManager program. The default value is "/ROOT".
      • PKGS_DIR – path to the directory containing the KPA packages to be installed.
      • PKG_EXTENSION – extension for the KPA package file.
      • DB_PATH – full name of the SQLite database file in the KasperskyOS-based solution image containing data on the installed KPA packages.
      • APPS_DIR – path to the directory where the KPA packages will be installed.
      • VFS_CLIENT_LIB – name of the client transport library used to connect the PackageManager program to the VFS program.
      • NK_MODULE_NAME – path for installing the header files of the PackageManager component in the SDK relative to the directory /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/. Default value: kl/package_manager.
      • AUDIT_CONN_NAME – name of the IPC channel for connecting to the AuditStorage process.
      • WITHOUT_SIGN_MODE – external signature verification mode: true – lack of an external signature is not considered an error, false – lack of an external signature is considered an error. The default value is false.
      • MANIFEST_SCHEMA_BUILD_STORE – path to the build directory of the KasperskyOS-based solution image containing the manifest schema.
      • MANIFEST_SCHEMA_RUNTIME_PATH – path to the directory of the started KasperskyOS-based solution containing the manifest schema.
      • PATH_TO_ADDITIONAL_EXTENSIONS_SCHEMAS – path to the directory containing additional manifest schemas for objects of an arbitrary format that are defined in the extentions key value of the KPA package manifest.
      • CUSTOM_LAYOUT – full name of the JSON file that is used to redefine the paths for installing KPA package components.
    include (package_manager/create_package_manager_entity) create_package_manager_entity( ENTITY PkgMgrEntity NK_MODULE_NAME "package_manager" MAIN_CONN_NAME "PkgMgrEntity" ROOT_PATH "/" PKGS_DIR "/packages" PKG_EXTENSION "kpa" DB_PATH "${DB_PATH}" APPS_DIR "${APPS_PATH}" MANIFEST_SCHEMA_BUILD_STORE "${CMAKE_BINARY_DIR}/rootdir/schema" MANIFEST_SCHEMA_RUNTIME_PATH "/schema" PATH_TO_ADDITIONAL_EXTENSIONS_SCHEMAS "${CMAKE_SOURCE_DIR}/resources/additional_extensions/" CUSTOM_LAYOUT "/custom_layout_schema.json" VFS_CLIENT_LIB vfs::client AUDIT_CONN_NAME "audit_storage" WITHOUT_SIGN_MODE TRUE)
  2. Link the client executable file to the client proxy library of PackageManager by adding the following command to the CMakeLists.txt file for building the client:
    target_link_libraries (<name of the CMake target for building the client> ${package_manager_CLIENT_LIBS})
  3. Add permissions for the necessary events to the solution security policy description:
    1. To enable the PackageManager program to manage KPA packages, the solution security policy must allow the following interactions for the package_manager.PkgMgrEntity process class:
      • Access to all endpoints of the VFS program.
      • Access to the core endpoints Sync, VMM, Thread, HAL, Handle, FS, Notice, CM and Profiler (their descriptions are located in the directory sysroot-*-kos/include/kl/core from the SDK).
    2. To enable a client to call the PackageManager program, the solution security policy must allow the following interactions for the client process class:
      • Access to the appropriate endpoints of the PackageManager program (their descriptions are located in the directory sysroot-*-kos/include/kl/package_manager from the SDK).
  4. Use of the PackageManager program API in the client code.

    Use the header file component/package_manager/kos_ipc/package_manager_proxy.h for this. For more details, refer to PackageManager component.

Page top
[Topic pm_scenario]