diff options
author | José Bollo <jose.bollo@iot.bzh> | 2016-10-11 17:07:16 +0200 |
---|---|---|
committer | José Bollo <jose.bollo@iot.bzh> | 2017-01-26 21:40:08 +0100 |
commit | 1d4de11a907e41c06063a2cd5028dc4101690f50 (patch) | |
tree | 69af98bbe6512cdbcab33267574c131f85ffd597 | |
parent | bfc9c138b1a9e87f9d387e2f900c14807c9da9b9 (diff) |
Prepare the Integration with systemd
This is an intermediate commit providing
basic functionnalities for setting up
integration of the framework with systemd.
- file afm-unit.conf is a mustache template
- translation of config.xml to json object
- mustache (extended) application of the json to the template
- post processing of the result for extracting unit files
This processing is currently available as a test
(and a tool) and will be integrated after more
developement, test and validation.
Signed-off-by: José Bollo <jose.bollo@iot.bzh>
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | conf/CMakeLists.txt | 5 | ||||
-rw-r--r-- | conf/afm-unit.conf | 168 | ||||
-rw-r--r-- | docs/config.xml.md | 303 | ||||
-rw-r--r-- | docs/permissions.md | 61 | ||||
-rw-r--r-- | docs/widgets.md | 238 | ||||
-rw-r--r-- | mkdocs.yml | 4 | ||||
-rw-r--r-- | src/CMakeLists.txt | 10 | ||||
-rw-r--r-- | src/mustach.c | 257 | ||||
-rw-r--r-- | src/mustach.h | 112 | ||||
-rw-r--r-- | src/tests/CMakeLists.txt | 20 | ||||
-rw-r--r-- | src/tests/test-unit/CMakeLists.txt | 22 | ||||
-rw-r--r-- | src/tests/test-unit/config.xml | 46 | ||||
-rw-r--r-- | src/tests/test-unit/sample.unit | 94 | ||||
-rw-r--r-- | src/tests/test-unit/test-unit.c | 89 | ||||
-rw-r--r-- | src/wgt-json.c | 482 | ||||
-rw-r--r-- | src/wgt-json.h | 27 | ||||
-rw-r--r-- | src/wgt-strings.c | 28 | ||||
-rw-r--r-- | src/wgt-strings.h | 20 | ||||
-rw-r--r-- | src/wgtpkg-mustach.c | 264 | ||||
-rw-r--r-- | src/wgtpkg-mustach.h | 24 | ||||
-rw-r--r-- | src/wgtpkg-unit.c | 254 | ||||
-rw-r--r-- | src/wgtpkg-unit.h | 45 |
23 files changed, 2330 insertions, 245 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 5796837..83e0c39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ project(afm-main C) cmake_minimum_required(VERSION 2.8) include(GNUInstallDirs) +include(CTest) set(PROJECT_NAME "AFM Main") set(PROJECT_PRETTY_NAME "Application Framework Main") @@ -59,6 +60,7 @@ add_definitions( -DFWK_USER_APP_DIR="${afm_user_appdir}" -DWGTPKG_TRUSTED_CERT_DIR="${wgtpkg_trusted_cert_dir}" -DFWK_LAUNCH_CONF="${afm_confdir}/afm-launch.conf" + -DFWK_UNIT_CONF="${afm_confdir}/afm-unit.conf" -DFWK_USER_APP_DIR_LABEL="${afm_user_appdir_label}" ) diff --git a/conf/CMakeLists.txt b/conf/CMakeLists.txt index 32a0cc4..d8611e0 100644 --- a/conf/CMakeLists.txt +++ b/conf/CMakeLists.txt @@ -1,5 +1,5 @@ ########################################################################### -# Copyright 2015 IoT.bzh +# Copyright 2015, 2016, 2017 IoT.bzh # # author: José Bollo <jose.bollo@iot.bzh> # @@ -30,6 +30,7 @@ if(NOT USE_SDK) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/afm-system-daemon.service DESTINATION ${UNITDIR_SYSTEM}) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/afm-user-daemon.conf DESTINATION ${SYSCONFDIR_DBUS_USER}) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/afm-user-daemon.service DESTINATION ${UNITDIR_USER}) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/afm-launch.conf DESTINATION ${afm_confdir}) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/afm-launch.conf DESTINATION ${afm_confdir}) + install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/afm-unit.conf DESTINATION ${afm_confdir}) endif() diff --git a/conf/afm-unit.conf b/conf/afm-unit.conf new file mode 100644 index 0000000..290c3e1 --- /dev/null +++ b/conf/afm-unit.conf @@ -0,0 +1,168 @@ +;--------------------------------------------------------------------------------- +; File: +; +; afm-unit.conf +; +; Role: +; +; Configure how installation of widget produces unit files for systemd +; +; Processing and format: +; +; 1. File load +; +; Lines beginning with ; are firstly removed +; +; 2. File instanciation +; +; Mustache (extended) substitutions are applied using JSON +; data deduced from config.xml file of the widget. +; +; 3. Extraction of units +; +; Extract produced units, pack it (remove empty lines and directives) +; +; Directives: +; +; All directive occopy one whole line starting with % +; +; - %nl +; +; produce an empty line at the end +; +; - %begin systemd-unit +; - %end systemd-unit +; +; delimit the produced unit +; +; - %systemd-unit user +; - %systemd-unit system +; +; tells the kind of unit (user/system) +; +; - %systemd-unit service NAME +; - %systemd-unit socket NAME +; +; gives the name and type of the unit +; +;--------------------------------------------------------------------------------- +{{#targets}} +%begin systemd-unit + +# auto generated by wgtpkg-unit for {{id}} version {{version}} target {{:#target}} +%nl + +[unit] +Description={{description}} +X-AGL-Name={{name.content}} +X-AGL-Name-Short={{name.short}} +X-AGL-Id={{id}} +X-AGL-Idaver={{idaver}} +X-AGL-Target-Name={{:#target}} +X-AGL-Author={{author.content}} +X-AGL-Author-email={{author.email}} +X-AGL-HTTP-port={{:#metadata.http-port}} +%nl + +# Adds check to smack +ConditionSecurity=smack +%nl + +# Automatic bound to required bindings +{{#required-binding}} +BindsTo=afm-api-{{name}} +After=afm-api-{{name}} +{{/required-binding}} +%nl + +[Service] +SmackProcessLabel=User::App::{{id}} + +{{#required-permission}} + {{#urn:AGL:permission::platform:no-oom}} OOMScoreAdjust=-500 {{/urn:AGL:permission::platform:no-oom}} + {{#urn:AGL:permission::partner:real-time}} IOSchedulingClass=realtime {{/urn:AGL:permission::partner:real-time}} + {{^urn:AGL:permission::partner:real-time}} RestrictRealtime=on {{/urn:AGL:permission::partner:real-time}} + {{#urn:AGL:permission::public:display}} SupplementaryGroups=display {{/urn:AGL:permission::public:display}} + {{^urn:AGL:permission::public:syscall:clock}} SystemCallFilter=~@clock {{/urn:AGL:permission::public:syscall:clock}} + {{^urn:AGL:permission::public:internet}} RestrictAddressFamilies=AF_UNIX {{/urn:AGL:permission::public:internet}} +{{/required-permission}} +%nl + +WorkingDirectory={{&#metadata.app-data-dir}} + +;--------------------------------------------------------------------------------- +{{#content.type=text/html}} + +%systemd-unit user + +%systemd-unit service afm-appli-{{idaver}}{{^#target=main}}@{{:#target}}{{/#target=main}} + +ExecStart=/usr/bin/afb-daemon --port={{:#metadata.http-port}} --random-token \ + --rootdir={{:#metadata.install-dir}} \ + --workdir={{&#metadata.app-data-dir}} \ + --roothttp=htdocs \ + {{#required-permission.urn:AGL:permission::public:applications:read}}\ + --alias=/icons:{{:#metadata.icons-dir}} \ + {{/required-permission.urn:AGL:permission::public:applications:read}}\ + {{#required-binding}}\ + {{#value=auto}}\ + --ws-client=unix:%t/apis/ws/{{name}} \ + {{/value=auto}}\ + {{#value=ws}}\ + --ws-client=unix:%t/apis/ws/{{name}} \ + {{/value=ws}}\ + {{#value=dbus}}\ + --dbus-client={{name}} \ + {{/value=dbus}}\ + {{#value=link}}\ + --binding=%t/apis/lib/{{name}} \ + {{/value=link}}\ + {{#value=cloud}}\ + --cloud-client={{name}} \ + {{/value=cloud}}\ + {{/required-binding}}\ + --exec /usr/bin/web-runtime http://localhost:@p/{{content.src}}?token=@t + +{{/content.type=text/html}} + +;--------------------------------------------------------------------------------- +{{#content.type=application/vnd.agl.service}} + +%systemd-unit user +%systemd-unit service afm-api-{{:#target}} + +ExecStart=/usr/bin/afb-daemon \ + --rootdir={{:#metadata.install-dir}} \ + --workdir={{&#metadata.install-dir}} \ + {{^required-permission.urn:AGL:permission::partner:service:no-ws}}\ + --ws-server=unix:%t/bindings/{{:#target}} \ + {{/required-permission.urn:AGL:permission::partner:service:no-ws}}\ + {{^required-permission.urn:AGL:permission::partner:service:no-dbus}}\ + --dbus-server={{:#target}} \ + {{/required-permission.urn:AGL:permission::partner:service:no-dbus}}\ + --no-httpd + +{{^required-permission.urn:AGL:permission::partner:service:no-ws}} + +%end systemd-unit +%begin systemd-unit + +# auto generated by wgtpkg-unit for {{id}} version {{version}} target {{:#target}} +# +%systemd-unit user +%systemd-unit socket afm-api-{{:#target}} + + +[socket] +SmackLabel=* +ListenStream=%t/bindings/{{:#target}} + +{{/required-permission.urn:AGL:permission::partner:service:no-ws}} + +{{/content.type=application/vnd.agl.service}} + +;--------------------------------------------------------------------------------- +%end systemd-unit +{{/targets}} + + diff --git a/docs/config.xml.md b/docs/config.xml.md new file mode 100644 index 0000000..7f413d5 --- /dev/null +++ b/docs/config.xml.md @@ -0,0 +1,303 @@ +The configuration file config.xml +=========== + +The widgets are described by the W3C's technical recommendations +[Packaged Web Apps (Widgets)][widgets] and [XML Digital Signatures for Widgets][widgets-digsig] + that specifies the configuration file **config.xml**. + +Overview +-------- + +The file **config.xml** describes important data of the application +to the framework: + +- the unique identifier of the application +- the name of the application +- the type of the application +- the icon of the application +- the permissions linked to the application +- the services and dependancies of the application + +The file MUST be at the root of the widget and MUST be case sensitively name +***config.xml***. + +The file **config.xml** is a XML file described by the document +[widgets]. + +Here is the example of the config file for the QML application SmartHome. + +```xml +<?xml version="1.0" encoding="UTF-8"?> +<widget xmlns="http://www.w3.org/ns/widgets" id="smarthome" version="0.1"> + <name>SmartHome</name> + <icon src="smarthome.png"/> + <content src="qml/smarthome/smarthome.qml" type="text/vnd.qt.qml"/> + <description>This is the Smarthome QML demo application. It shows some user interfaces for controlling an +automated house. The user interface is completely done with QML.</description> + <author>Qt team</author> + <license>GPL</license> +</widget> +``` + +The most important items are: + +- **<widget id="......"\>**: gives the id of the widget. It must be unique. + +- **<widget version="......"\>**: gives the version of the widget + +- **<icon src="..."\>**: gives a path to the icon of the application + (can be repeated with different sizes) + +- **<content src="..." type="..."\>**: this indicates the entry point and its type. + +Standard elements of "config.xml" +--------------------------------- + +### The element widget + +#### the attribute id of widget + +The attribute *id* is mandatory (for version 2.x, blowfish) and must be unique. + +Values for *id* are any non empty string containing only latin letters, +arabic digits, and the three characters '.' (dot), '-' (dash) and +'_' (underscore). + +Authors can use a mnemonic id or can pick a unique id using +command **uuid** or **uuidgen**. + +### the attribute version of widget + +The attribute *version* is mandatory (for version 2.x, blowfish). + +Values for *version* are any non empty string containing only latin letters, +arabic digits, and the three characters '.' (dot), '-' (dash) and +'_' (underscore). + +Version values are dot separated fields MAJOR.MINOR.REVISION. +Such version would preferabily follow guidelines of +[semantic versionning][semantic-version]. + +### The element content + +The element *content* is mandatory (for version 2.x, blowfish) and must designate a file +(subject to localisation) with its attribute *src*. + +The content designed depends on its type. See below for the known types. + +### The element icon + +The element *icon* is mandatory (for version 2.x, blowfish) and must +be unique. It must designate an image file with its attribute *src*. + +AGL features +------------ + +The AGL framework uses the feature tag for specifying security and binding +requirement of the widget. + +The current version of AGL (up to 2.0.1, blowfish) has no fully implemented +features. + +The features planned to be implemented are described below. + +### feature name="urn:AGL:widget:required-binding" + +List of the bindings required by the widget. + +Each required binding must be explicited using a <param> entry. + +Example: +```xml +<feature name="urn:AGL:widget:required-binding"> + <param name="urn:AGL:permission:A" value="required" /> + <param name="urn:AGL:permission:B" value="optional" /> +</feature> +``` + +This will be *virtually* translated for mustaches to the JSON +```json +"required-binding": { + "param": [ + { "name": "urn:AGL:permission:A", "value": "required", "required": true }, + { "name": "urn:AGL:permission:A", "value": "optional", "optional": true } + ], + "urn:AGL:permission:A": { "name": "urn:AGL:permission:A", "value": "required", "required": true }, + "urn:AGL:permission:B": { "name": "urn:AGL:permission:B", "value": "optional", "optional": true } +} +``` + +#### param name="#target" + +Declares the name of the component requiring the listed bindings. +Only one instance of the param "#target" is allowed. +When there is not instance of the param +The value is either: + +- required: the binding is mandatorily needed except if the feature +isn't required (required="false") and in that case it is optional. +- optional: the binding is optional + +#### param name=[required binding name] + +The value is either: + +- required: the binding is mandatorily needed except if the feature +isn't required (required="false") and in that case it is optional. +- optional: the binding is optional + +### feature name="urn:AGL:widget:required-permission" + +List of the permissions required by the widget. + +Each required permission must be explicited using a <param> entry. + +#### param name=[required permission name] + +The value is either: + +- required: the permission is mandatorily needed except if the feature +isn't required (required="false") and in that case it is optional. +- optional: the permission is optional + +### feature name="urn:AGL:widget:provided-binding" + +Use this feature for each provided binding of the widget. +The parameters are: + +#### param name="subid" + +REQUIRED + +The value is the string that must match the binding prefix. +It must be unique. + +#### param name="name" + +REQUIRED + +The value is the string that must match the binding prefix. +It must be unique. + +#### param name="src" + +REQUIRED + +The value is the path of the shared library for the binding. + +#### param name="type" + +REQUIRED + +Currently it must be ***application/vnd.agl.binding.v1***. + + +#### param name="scope" + +REQUIRED + +The value indicate the availability of the binidng: + +- private: used only by the widget +- public: available to allowed clients as a remote service (requires permission+) +- inline: available to allowed clients inside their binding (unsafe, requires permission+++) + +#### param name="needed-binding" + +OPTIONAL + +The value is a space separated list of binding's names that the binding needs. + +### feature name="urn:AGL:widget:defined-permission" + +Each required permission must be explicited using a <param> entry. + +#### param name=[defined permission name] + +The value is the level of the defined permission. +Standard levels are: + +- system +- platform +- partner +- tiers +- public + +This level defines the level of accreditation required to get the given +permission. The accreditions are given by signatures of widgets. + +Known content types +------------------- + +The configuration file ***/etc/afm/afm-unit.conf*** defines the types +of widget known and how to launch it. + +Known types for the type of content are (for version 2.x, blowfish): + +- ***text/html***: + HTML application, + content.src designates the home page of the application + +- ***application/x-executable***: + Native application, + content.src designates the relative path of the binary + +- ***application/vnd.agl.url***: + Internet url, + content.src designates the url to be used + +- ***application/vnd.agl.service***: + AGL service defined as a binder, + content.src designates the directory of provided binders, + http content, if any, must be put in the subdirectory ***htdocs*** of the widget + +- ***application/vnd.agl.native***: + Native application with AGL service defined as a binder, + content.src designates the relative path of the binary, + bindings, if any must be put in the subdirectory ***lib*** of the widget, + http content, if any, must be put in the subdirectory ***htdocs*** of the widget + +- ***text/vnd.qt.qml***, ***application/vnd.agl.qml***: + QML application, + content.src designate the relative path of the QML root, + imports must be put in the subdirectory ***imports*** of the widget + +- ***application/vnd.agl.qml.hybrid***: + QML application with bindings, + content.src designate the relative path of the QML root, + bindings, if any must be put in the subdirectory ***lib*** of the widget, + imports must be put in the subdirectory ***imports*** of the widget + +- ***application/vnd.agl.html.hybrid***: + HTML application, + content.src designates the home page of the application, + bindings, if any must be put in the subdirectory ***lib*** of the widget, + http content must be put in the subdirectory ***htdocs*** of the widget + +--- + + +[widgets]: http://www.w3.org/TR/widgets "Packaged Web Apps" +[widgets-digsig]: http://www.w3.org/TR/widgets-digsig "XML Digital Signatures for Widgets" +[libxml2]: http://xmlsoft.org/html/index.html "libxml2" +[app-manifest]: http://www.w3.org/TR/appmanifest "Web App Manifest" + + +[meta-intel]: https://github.com/01org/meta-intel-iot-security "A collection of layers providing security technologies" +[widgets]: http://www.w3.org/TR/widgets "Packaged Web Apps" +[widgets-digsig]: http://www.w3.org/TR/widgets-digsig "XML Digital Signatures for Widgets" +[libxml2]: http://xmlsoft.org/html/index.html "libxml2" +[openssl]: https://www.openssl.org "OpenSSL" +[xmlsec]: https://www.aleksey.com/xmlsec "XMLSec" +[json-c]: https://github.com/json-c/json-c "JSON-c" +[d-bus]: http://www.freedesktop.org/wiki/Software/dbus "D-Bus" +[libzip]: http://www.nih.at/libzip "libzip" +[cmake]: https://cmake.org "CMake" +[security-manager]: https://wiki.tizen.org/wiki/Security/Tizen_3.X_Security_Manager "Security-Manager" +[app-manifest]: http://www.w3.org/TR/appmanifest "Web App Manifest" +[tizen-security]: https://wiki.tizen.org/wiki/Security "Tizen security home page" +[tizen-secu-3]: https://wiki.tizen.org/wiki/Security/Tizen_3.X_Overview "Tizen 3 security overview" +[semantic-version]: http://semver.org/ "Semantic versionning" + + + diff --git a/docs/permissions.md b/docs/permissions.md new file mode 100644 index 0000000..300a719 --- /dev/null +++ b/docs/permissions.md @@ -0,0 +1,61 @@ +The permissions +=============== + + +Permission's names +------------------ + +The proposal here is to specify a naming scheme for permissions +that allows the system to be as stateless as possible. The current +current specification includes in the naming of permissions either +the name of the bound binding when existing and the level of the +permission itself. Doing this, there is no real need for the +framework to keep updated a database of installed permissions. + +The permission names are [URN][URN] of the form: + + urn:AGL:permission:<binding>:<level>:<hierarchical-name> + +where "AGL" is the NID (the namespace identifier) dedicated to +AGL (note: a RFC should be produced to standardize this name space). + +The permission names are made of NSS (the namespace specific string) +starting with "permission:" and followed by colon separated +fields. The 2 first fields are <binding> and <level> and the remaining +fields are gouped to form the <hierarchical-name>. + + <binding> ::= [ <pname> ] + + <pname> ::= 1*<pchars> + + <pchars> ::= <upper> | <lower> | <number> | <extra> + + <extra> ::= "-" | "." | "_" | "@" + +The field <binding> can be made of any valid character for NSS except +the characters colon and star (:*). This field designate the binding +providing the permission. It is use to deduce binding requirements +from permission requirements. The field <binding> can be the empty +string when the permission is defined by the AGL system itself. +The field <binding> if starting with the character "@" represents +a transversal permission not bound to any binding. + + <level> ::= 1*<lower> + +The field <level> is made only of letters in lower case. +The field <level> can only take some predefined values: +"system", "platform", "partner", "tiers", "owner", "public". + + <hierarchical-name> ::= <pname> 0*(":" <pname>) + +The field <hierarchical-name> is made <pname> separated by +colons. The names at left are hierarchically grouping the +names at right. This hierarchical behaviour is intended to +be used to request permissions using hierarchical grouping. + +Permission's level +------------------ + + +[URN]: https://tools.ietf.org/rfc/rfc2141.txt "RFC 2141: URN Syntax" + diff --git a/docs/widgets.md b/docs/widgets.md index c23faa8..2acc92a 100644 --- a/docs/widgets.md +++ b/docs/widgets.md @@ -1,246 +1,12 @@ The widgets =========== -The widgets are described by the technical recommendations -[widgets] and [widgets-digsig]. +The widgets are described by the W3C's technical recommendations +[Packaged Web Apps (Widgets)][widgets] and [XML Digital Signatures for Widgets][widgets-digsig] In summary, **widgets are ZIP files that can be signed and whose content is described by the file <config.xml>**. -The configuration file config.xml ---------------------------------- - -The file **config.xml** describes important data of the application -to the framework: - -- the unique identifier of the application -- the name of the application -- the type of the application -- the icon of the application -- the permissions linked to the application -- the services and dependancies of the application - -The file MUST be at the root of the widget and MUST be case sensitively name -***config.xml***. - -The file **config.xml** is a XML file described by the document -[widgets]. - -Here is the example of the config file for the QML application SmartHome. - -```xml -<?xml version="1.0" encoding="UTF-8"?> -<widget xmlns="http://www.w3.org/ns/widgets" id="smarthome" version="0.1"> - <name>SmartHome</name> - <icon src="smarthome.png"/> - <content src="qml/smarthome/smarthome.qml" type="text/vnd.qt.qml"/> - <description>This is the Smarthome QML demo application. It shows some user interfaces for controlling an -automated house. The user interface is completely done with QML.</description> - <author>Qt team</author> - <license>GPL</license> -</widget> -``` - -The most important items are: - -- **\<widget id="......"\>**: gives the id of the widget. It must be unique. - -- **\<widget version="......"\>**: gives the version of the widget - -- **\<icon src="..."\>**: gives a path to the icon of the application - (can be repeated with different sizes) - -- **\<content src="..." type="..."\>**: this indicates the entry point and its type. - The types handled are set through the file /etc/afm/afm-launch.conf - -Further development will add handling of <feature> for requiring and providing -permissions and services. - ---- - -### Standard elements of "config.xml" - -#### The element widget - -##### the attribute id of widget - -The attribute *id* is mandatory (for version 2.x, blowfish) and must be unique. - -Values for *id* are any non empty string containing only latin letters, -arabic digits, and the three characters '.' (dot), '-' (dash) and -'_' (underscore). - -Authors can use a mnemonic id or can pick a unique id using -command **uuid** or **uuidgen**. - -#### the attribute version of widget - -The attribute *version* is mandatory (for version 2.x, blowfish). - -Values for *id* are any non empty string containing only latin letters, -arabic digits, and the three characters '.' (dot), '-' (dash) and -'_' (underscore). - -Version values are dot separated fields MAJOR.MINOR.REVISION. - -#### The element content - -The element *content* is mandatory (for version 2.x, blowfish) and must designate a file -(subject to localisation) with its attribute *src*. - -The content designed depends on its type. See below for the known types. - -#### The element icon - -The element *icon* is mandatory (for version 2.x, blowfish) and must -be unique. It must designate an image file with its attribute *src*. - -### Known widget types and content - -The configuration file ***/etc/afm/afm-launch.conf*** defines the types -of widget known and how to launch it. - -Known types for the type of content are (for version 2.x, blowfish): - -- ***text/html***: - HTML application, - content.src designates the home page of the application - -- ***application/x-executable***: - Native application, - content.src designates the relative path of the binary - -- ***application/vnd.agl.url***: - Internet url, - content.src designates the url to be used - -- ***application/vnd.agl.service***: - AGL service defined as a binder, - content.src designates the directory of provided binders, - http content, if any, must be put in the subdirectory ***htdocs*** of the widget - -- ***application/vnd.agl.native***: - Native application with AGL service defined as a binder, - content.src designates the relative path of the binary, - bindings, if any must be put in the subdirectory ***lib*** of the widget, - http content, if any, must be put in the subdirectory ***htdocs*** of the widget - -- ***text/vnd.qt.qml***, ***application/vnd.agl.qml***: - QML application, - content.src designate the relative path of the QML root, - imports must be put in the subdirectory ***imports*** of the widget - -- ***application/vnd.agl.qml.hybrid***: - QML application with bindings, - content.src designate the relative path of the QML root, - bindings, if any must be put in the subdirectory ***lib*** of the widget, - imports must be put in the subdirectory ***imports*** of the widget - -- ***application/vnd.agl.html.hybrid***: - HTML application, - content.src designates the home page of the application, - bindings, if any must be put in the subdirectory ***lib*** of the widget, - http content must be put in the subdirectory ***htdocs*** of the widget - ---- - -### AGL features - -The AGL framework uses the feature tag for specifying security and binding -requirement of the widget. - -The current version of AGL (up to 2.0.1, blowfish) has no fully implemented -features. - -The features planned to be implemented are described below. - -#### feature name="urn:AGL:required-binding" - -List of the bindings required by the widget. - -Each required binding must be explicited using a <param> entry. - -##### param name=[required binding name] - -The value is either: - -- required: the binding is mandatorily needed except if the feature -isn't required (required="false") and in that case it is optional. -- optional: the binding is optional - -#### feature name="urn:AGL:required-permission" - -List of the permissions required by the widget. - -Each required permission must be explicited using a <param> entry. - -##### param name=[required permission name] - -The value is either: - -- required: the permission is mandatorily needed except if the feature -isn't required (required="false") and in that case it is optional. -- optional: the permission is optional - -#### feature name="urn:AGL:provided-binding" - -Use this feature for each provided binding of the widget. -The parameters are: - -##### param name="name" - -REQUIRED - -The value is the string that must match the binding prefix. -It must be unique. - -##### param name="src" - -REQUIRED - -The value is the path of the shared library for the binding. - -##### param name="type" - -REQUIRED - -Currently it must be ***application/vnd.agl.binding.v1***. - - -##### param name="scope" - -REQUIRED - -The value indicate the availability of the binidng: - -- private: used only by the widget -- public: available to allowed clients as a remote service (requires permission+) -- inline: available to allowed clients inside their binding (unsafe, requires permission+++) - -##### param name="needed-binding" - -OPTIONAL - -The value is a space separated list of binding's names that the binding needs. - -#### feature name="urn:AGL:defined-permission" - -Each required permission must be explicited using a <param> entry. - -##### param name=[defined permission name] - -The value is the level of the defined permission. -Standard levels are: - -- system -- platform -- partner -- public - -This level defines the level of accreditation required to get the given -permission. The accreditions are given by signatures of widgets. - - Tools for managing widgets -------------------------- @@ -8,4 +8,6 @@ pages: - 'The application framework': 'application-framework.md' - 'The security framework': 'security-framework.md' - 'The afm daemons' : 'afm-daemons.md' - - 'Widgets of the framework' : 'widgets.md' + - 'Widgets of the framework' : + - 'Overview of widgets': 'widgets.md' + - 'The configuration file': 'config.xml.md' diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f126346..c38dd07 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -86,14 +86,17 @@ add_library(wgtpkg STATIC wgtpkg-digsig.c wgtpkg-files.c wgtpkg-install.c + wgtpkg-mustach.c wgtpkg-permissions.c wgtpkg-uninstall.c + wgtpkg-unit.c wgtpkg-workdir.c wgtpkg-xmlsec.c wgtpkg-zip.c ) add_library(utils STATIC + mustach.c utils-dir.c utils-file.c utils-json.c @@ -104,6 +107,7 @@ add_library(wgt STATIC wgt-config.c wgt-info.c wgt-strings.c + wgt-json.c wgt.c ) @@ -189,3 +193,9 @@ if(EXTRA2_FOUND) endif() endif(EXTRA2_FOUND) + +########################################################################### +# the tests + +add_subdirectory(tests) + diff --git a/src/mustach.c b/src/mustach.c new file mode 100644 index 0000000..19df16f --- /dev/null +++ b/src/mustach.c @@ -0,0 +1,257 @@ +/* + Author: José Bollo <jobol@nonadev.net> + Author: José Bollo <jose.bollo@iot.bzh> + + https://gitlab.com/jobol/mustach + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> + +#include "mustach.h" + +#define NAME_LENGTH_MAX 1024 +#define DEPTH_MAX 256 + +static int getpartial(struct mustach_itf *itf, void *closure, const char *name, char **result) +{ + int rc; + FILE *file; + size_t size; + + *result = NULL; + file = open_memstream(result, &size); + if (file == NULL) + rc = MUSTACH_ERROR_SYSTEM; + else { + rc = itf->put(closure, name, 0, file); + if (rc == 0) + /* adds terminating null */ + rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0; + fclose(file); + if (rc < 0) { + free(result); + *result = NULL; + } + } + return rc; +} + +static int process(const char *template, struct mustach_itf *itf, void *closure, FILE *file, const char *opstr, const char *clstr) +{ + char name[NAME_LENGTH_MAX + 1], *partial, c; + const char *beg, *term; + struct { const char *name, *again; size_t length; int emit, entered; } stack[DEPTH_MAX]; + size_t oplen, cllen, len, l; + int depth, rc, emit; + + emit = 1; + oplen = strlen(opstr); + cllen = strlen(clstr); + depth = 0; + for(;;) { + beg = strstr(template, opstr); + if (beg == NULL) { + /* no more mustach */ + if (emit) + fwrite(template, strlen(template), 1, file); + return depth ? MUSTACH_ERROR_UNEXPECTED_END : 0; + } + if (emit) + fwrite(template, (size_t)(beg - template), 1, file); + term = strstr(template, clstr); + if (term == NULL) + return MUSTACH_ERROR_UNEXPECTED_END; + template = term + cllen; + beg += oplen; + len = (size_t)(term - beg); + c = *beg; + switch(c) { + case '!': + case '=': + break; + case '{': + for (l = 0 ; clstr[l] == '}' ; l++); + if (clstr[l]) { + if (!len || beg[len-1] != '}') + return MUSTACH_ERROR_BAD_UNESCAPE_TAG; + len--; + } else { + if (term[l] != '}') + return MUSTACH_ERROR_BAD_UNESCAPE_TAG; + template++; + } + c = '&'; + case '^': + case '#': + case '/': + case '&': + case '>': +#if !defined(NO_EXTENSION_FOR_MUSTACH) && !defined(NO_COLON_EXTENSION_FOR_MUSTACH) + case ':': +#endif + beg++; len--; + default: + while (len && isspace(beg[0])) { beg++; len--; } + while (len && isspace(beg[len-1])) len--; + if (len == 0) + return MUSTACH_ERROR_EMPTY_TAG; + if (len > NAME_LENGTH_MAX) + return MUSTACH_ERROR_TAG_TOO_LONG; + memcpy(name, beg, len); + name[len] = 0; + break; + } + switch(c) { + case '!': + /* comment */ + /* nothing to do */ + break; + case '=': + /* defines separators */ + if (len < 5 || beg[len - 1] != '=') + return MUSTACH_ERROR_BAD_SEPARATORS; + beg++; + len -= 2; + for (l = 0; l < len && !isspace(beg[l]) ; l++); + if (l == len) + return MUSTACH_ERROR_BAD_SEPARATORS; + opstr = strndupa(beg, l); + while (l < len && isspace(beg[l])) l++; + if (l == len) + return MUSTACH_ERROR_BAD_SEPARATORS; + clstr = strndupa(beg + l, len - l); + oplen = strlen(opstr); + cllen = strlen(clstr); + break; + case '^': + case '#': + /* begin section */ + if (depth == DEPTH_MAX) + return MUSTACH_ERROR_TOO_DEPTH; + rc = emit; + if (rc) { + rc = itf->enter(closure, name); + if (rc < 0) + return rc; + } + stack[depth].name = beg; + stack[depth].again = template; + stack[depth].length = len; + stack[depth].emit = emit; + stack[depth].entered = rc; + if ((c == '#') == (rc == 0)) + emit = 0; + depth++; + break; + case '/': + /* end section */ + if (depth-- == 0 || len != stack[depth].length || memcmp(stack[depth].name, name, len)) + return MUSTACH_ERROR_CLOSING; + rc = emit && stack[depth].entered ? itf->next(closure) : 0; + if (rc < 0) + return rc; + if (rc) { + template = stack[depth++].again; + } else { + emit = stack[depth].emit; + if (emit && stack[depth].entered) + itf->leave(closure); + } + break; + case '>': + /* partials */ + if (emit) { + rc = getpartial(itf, closure, name, &partial); + if (rc == 0) { + rc = process(partial, itf, closure, file, opstr, clstr); + free(partial); + } + if (rc < 0) + return rc; + } + break; + default: + /* replacement */ + if (emit) { + rc = itf->put(closure, name, c != '&', file); + if (rc < 0) + return rc; + } + break; + } + } +} + +int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file) +{ + int rc = itf->start ? itf->start(closure) : 0; + if (rc == 0) + rc = process(template, itf, closure, file, "{{", "}}"); + return rc; +} + +int fdmustach(const char *template, struct mustach_itf *itf, void *closure, int fd) +{ + int rc; + FILE *file; + + file = fdopen(fd, "w"); + if (file == NULL) { + rc = MUSTACH_ERROR_SYSTEM; + errno = ENOMEM; + } else { + rc = fmustach(template, itf, closure, file); + fclose(file); + } + return rc; +} + +int mustach(const char *template, struct mustach_itf *itf, void *closure, char **result, size_t *size) +{ + int rc; + FILE *file; + size_t s; + + *result = NULL; + if (size == NULL) + size = &s; + file = open_memstream(result, size); + if (file == NULL) { + rc = MUSTACH_ERROR_SYSTEM; + errno = ENOMEM; + } else { + rc = fmustach(template, itf, closure, file); + if (rc == 0) + /* adds terminating null */ + rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0; + fclose(file); + if (rc >= 0) + /* removes terminating null of the length */ + (*size)--; + else { + free(*result); + *result = NULL; + *size = 0; + } + } + return rc; +} + diff --git a/src/mustach.h b/src/mustach.h new file mode 100644 index 0000000..8196679 --- /dev/null +++ b/src/mustach.h @@ -0,0 +1,112 @@ +/* + Author: José Bollo <jobol@nonadev.net> + Author: José Bollo <jose.bollo@iot.bzh> + + https://gitlab.com/jobol/mustach + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef _mustach_h_included_ +#define _mustach_h_included_ + +/** + * mustach_itf - interface for callbacks + * + * All of this function should return a negative value to stop + * the mustache processing. The returned negative value will be + * then returned to the caller of mustach as is. + * + * The functions enter and next should return 0 or 1. + * + * All other functions should normally return 0. + * + * @start: Starts the mustach processing of the closure + * 'start' is optional (can be NULL) + * + * @put: Writes the value of 'name' to 'file' with 'escape' or not + * + * @enter: Enters the section of 'name' if possible. + * Musts return 1 if entered or 0 if not entered. + * When 1 is returned, the function 'leave' will always be called. + * Conversely 'leave' is never called when enter returns 0 or + * a negative value. + * When 1 is returned, the function must activate the first + * item of the section. + * + * @next: Activates the next item of the section if it exists. + * Musts return 1 when the next item is activated. + * Musts return 0 when there is no item to activate. + * + * @leave: Leaves the last entered section + */ +struct mustach_itf { + int (*start)(void *closure); + int (*put)(void *closure, const char *name, int escape, FILE *file); + int (*enter)(void *closure, const char *name); + int (*next)(void *closure); + int (*leave)(void *closure); +}; + +#define MUSTACH_OK 0 +#define MUSTACH_ERROR_SYSTEM -1 +#define MUSTACH_ERROR_UNEXPECTED_END -2 +#define MUSTACH_ERROR_EMPTY_TAG -3 +#define MUSTACH_ERROR_TAG_TOO_LONG -4 +#define MUSTACH_ERROR_BAD_SEPARATORS -5 +#define MUSTACH_ERROR_TOO_DEPTH -6 +#define MUSTACH_ERROR_CLOSING -7 +#define MUSTACH_ERROR_BAD_UNESCAPE_TAG -8 + +/** + * fmustach - Renders the mustache 'template' in 'file' for 'itf' and 'closure'. + * + * @template: the template string to instanciate + * @itf: the interface to the functions that mustach calls + * @closure: the closure to pass to functions called + * @file: the file where to write the result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file); + +/** + * fmustach - Renders the mustache 'template' in 'fd' for 'itf' and 'closure'. + * + * @template: the template string to instanciate + * @itf: the interface to the functions that mustach calls + * @closure: the closure to pass to functions called + * @fd: the file descriptor number where to write the result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int fdmustach(const char *template, struct mustach_itf *itf, void *closure, int fd); + +/** + * fmustach - Renders the mustache 'template' in 'result' for 'itf' and 'closure'. + * + * @template: the template string to instanciate + * @itf: the interface to the functions that mustach calls + * @closure: the closure to pass to functions called + * @result: the pointer receiving the result when 0 is returned + * @size: the size of the returned result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int mustach(const char *template, struct mustach_itf *itf, void *closure, char **result, size_t *size); + +#endif + diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt new file mode 100644 index 0000000..09d4d0b --- /dev/null +++ b/src/tests/CMakeLists.txt @@ -0,0 +1,20 @@ +########################################################################### +# Copyright 2016, 2017 IoT.bzh +# +# author: José Bollo <jose.bollo@iot.bzh> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +########################################################################### + +add_subdirectory(test-unit) + diff --git a/src/tests/test-unit/CMakeLists.txt b/src/tests/test-unit/CMakeLists.txt new file mode 100644 index 0000000..3f6db69 --- /dev/null +++ b/src/tests/test-unit/CMakeLists.txt @@ -0,0 +1,22 @@ +########################################################################### +# Copyright 2016, 2017 IoT.bzh +# +# author: José Bollo <jose.bollo@iot.bzh> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +########################################################################### + +include_directories(../..) +add_executable(test-unit test-unit.c) +target_link_libraries(test-unit wgtpkg wgt utils) +add_test(NAME test-unit COMMAND test-unit ${CMAKE_CURRENT_SOURCE_DIR}/sample.unit ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/src/tests/test-unit/config.xml b/src/tests/test-unit/config.xml new file mode 100644 index 0000000..24a383d --- /dev/null +++ b/src/tests/test-unit/config.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<widget xmlns="http://www.w3.org/ns/widgets" id="bzh.iot.geoloc" version="0.0.1-alpha"> + <name>Sample Service</name> + <icon src="service.png"/> + <content src="index.html" type="text/html"/> + <description> + This is the Smarthome QML demo application. + It shows some user interfaces for controlling an automated house. + The user interface is completely done with QML. + </description> + <author>Qt team</author> + <license>GPL</license> + <feature name="urn:AGL:widget:required-binding"> + <param name="#target" value="main" /> + <param name="gps" value="ws" /> + <param name="phone" value="dbus" /> + <param name="identity" value="link" /> + <param name="log:https://oic@agl.iot.bzh/cloud/log" value="cloud" /> + </feature> + <feature name="urn:AGL:widget:required-permission"> + <param name="#target" value="main" /> + <param name="urn:AGL:permission::platform:no-oom" value="required" /> + <param name="urn:AGL:permission::partner:real-time" value="required" /> + <param name="urn:AGL:permission::public:applications:read" value="required" /> + <param name="urn:AGL:permission::public:display" value="required" /> + </feature> + <feature name="urn:AGL:widget:provided-binding"> + <param name="#target" value="geoloc" /> + <param name="description" value="binding of name geoloc" /> + <param name="content.src" value="libs/binding-geoloc.so" /> + <param name="content.type" value="application/vnd.agl.service" /> + </feature> + <feature name="urn:AGL:widget:required-binding"> + <param name="#target" value="geoloc" /> + <param name="identity" value="auto" /> + </feature> + <feature name="urn:AGL:widget:required-permission"> + <param name="#target" value="geoloc" /> + <param name="urn:AGL:permission:real-time" value="required" /> + <param name="urn:AGL:permission:syscall:*" value="required" /> + </feature> + <feature name="urn:AGL:widget:defined-permission"> + <param name="urn:AGL:permission:geoloc:public:settings" value="public" /> + </feature> +</widget> + diff --git a/src/tests/test-unit/sample.unit b/src/tests/test-unit/sample.unit new file mode 100644 index 0000000..2ee2a2a --- /dev/null +++ b/src/tests/test-unit/sample.unit @@ -0,0 +1,94 @@ +{{#targets.list}} + +%begin systemd-unit + +# auto generated by wgtpkg-unit for {{id}} version {{version}} target {{:#target}} +%nl + +[unit] +Description={{description}} +X-Name={{name.content}} +X-Name-Short={{name.short}} +X-Id={{id}} +X-Idaver={{idaver}} +X-Target-Name={{:#target}} +X-Author={{{author.content}}} +X-Author-email={{author.email}} +%nl + +# Adds check to smack +ConditionSecurity=smack +%nl + +# Automatic bound to required bindings +{{#required-binding.list}} +BindsTo=afm-binding-{{name}} +After=afm-binding-{{name}} +{{/required-binding.list}} +%nl + +[Service] +SmackProcessLabel=User::App::{{id}} + +{{#required-permission.dict}} + {{#urn:AGL:permission::platform:no-oom}} OOMScoreAdjust=-500 {{/urn:AGL:permission::platform:no-oom}} + {{#urn:AGL:permission::partner:real-time}} IOSchedulingClass=realtime {{/urn:AGL:permission::partner:real-time}} + {{^urn:AGL:permission::partner:real-time}} RestrictRealtime=on {{/urn:AGL:permission::partner:real-time}} + {{#urn:AGL:permission::public:display}} SupplementaryGroups=display {{/urn:AGL:permission::public:display}} + {{^urn:AGL:permission::public:syscall:clock}} SystemCallFilter=~@clock {{/urn:AGL:permission::public:syscall:clock}} + {{^urn:AGL:permission::public:internet}} RestrictAddressFamilies=AF_UNIX {{/urn:AGL:permission::public:internet}} +{{/required-permission.dict}} +%nl + +WorkingDirectory={{widget-app-data-dir}} + +{{#content.type=text/html}} + +%systemd-unit user + +%systemd-unit service afm-appli-{{idaver}}{{^#target=main}}@{{:#target}}{{/#target=main}} + +ExecStart=/usr/bin/afb-daemon --port=%P --random-token \ + --rootdir={{widget-install-dir}} \ + --workdir={{{widget-app-data-dir}}} \ + --roothttp=htdocs \ + {{#required-permission.dict.urn:AGL:permission::public:applications:read}}\ + --alias=/icons:{{widget-icons-dir}} \ + \{{/required-permission.dict.urn:AGL:permission::public:applications:read}} + {{#required-binding}}\ + --ws-client=unix:%t/bindings/{{:#target}} + \{{/required-binding}} + --exec /usr/bin/web-runtime http://localhost:@p/{{content.src}}?token=@t + +{{/content.type=text/html}} + +{{#content.type=application/vnd.agl.service}} + +%systemd-unit user +%systemd-unit service afm-binding-{{:#target}} + +ExecStart=/usr/bin/afb-daemon \ + --rootdir={{widget-install-dir}} \ + --workdir={{{widget-app-data-dir}}} \ + --no-httpd \ + --ws-server=unix:%t/bindings/{{:#target}} + +%end systemd-unit +%begin systemd-unit + +# auto generated by wgtpkg-unit for {{id}} version {{version}} target {{:#target}} +# +%systemd-unit user +%systemd-unit socket afm-binding-{{:#target}} + + +[socket] +SmackLabel=* +ListenStream=%t/bindings/{{:#target}} + +{{/content.type=application/vnd.agl.service}} + +%end systemd-unit + +{{/targets.list}} + diff --git a/src/tests/test-unit/test-unit.c b/src/tests/test-unit/test-unit.c new file mode 100644 index 0000000..09b7c86 --- /dev/null +++ b/src/tests/test-unit/test-unit.c @@ -0,0 +1,89 @@ +/* + Copyright 2016, 2017 IoT.bzh + + author: José Bollo <jose.bollo@iot.bzh> + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <json-c/json.h> + +#include <wgt.h> +#include <utils-json.h> +#include <wgt-json.h> +#include <wgtpkg-mustach.h> +#include <wgtpkg-unit.h> + + +#define error(...) fprintf(stderr,__VA_ARGS__),exit(1) + + + +static int process1(const struct unitdesc *desc) +{ + int isuser = desc->scope == unitscope_user; + int issystem = desc->scope == unitscope_system; + int issock = desc->type == unittype_socket; + int isserv = desc->type == unittype_service; + const char *name = desc->name; + const char *content = desc->content; + +printf("\n##########################################################"); +printf("\n### usr=%d sys=%d soc=%d srv=%d name %s%s", isuser, issystem, issock, isserv, name?:"?", issock?".socket":isserv?".service":""); +printf("\n##########################################################"); +printf("\n%s\n\n",content); + return 0; +} + +static int process(void *closure, const struct unitdesc descs[], unsigned count) +{ + while (count--) + process1(descs++); + return 0; +} + +int main(int ac, char **av) +{ + struct json_object *obj; + int rc; + + rc = unit_generator_on(*++av); + if (rc < 0) + error("can't read template %s: %m",*av); + while(*++av) { + obj = wgt_path_to_json(*av); + if (!obj) + error("can't read widget config at %s: %m",*av); + + j_add_string_m(obj, "#metadata.install-dir", "INSTALL-DIR"); + j_add_string_m(obj, "#metadata.app-data-dir", "%h/app-data"); + j_add_string_m(obj, "#metadata.icons-dir", "ICONS-DIR"); + j_add_string_m(obj, "#metadata.http-port", "HTTP-PORT"); + + puts(json_object_to_json_string_ext(obj, JSON_C_TO_STRING_PRETTY)); + rc = unit_generator_process(obj, process, NULL); + if (rc) + error("can't apply generate units, error %d",rc); + json_object_put(obj); + } + return 0; +} + + diff --git a/src/wgt-json.c b/src/wgt-json.c new file mode 100644 index 0000000..cc5ee93 --- /dev/null +++ b/src/wgt-json.c @@ -0,0 +1,482 @@ +/* + Copyright 2015, 2016, 2017 IoT.bzh + + author: José Bollo <jose.bollo@iot.bzh> + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdarg.h> +#include <sys/types.h> + +#include <json-c/json.h> + +#include "utils-json.h" +#include "wgt-info.h" +#include "wgt-json.h" +#include "wgt-strings.h" +#include "verbose.h" + +/* +{ + permissions: { + dict: { + ID: { name: ID, level: LEVEL, index: INDEX }, + ... + }, + list: [ + { name: ID, level: LEVEL, index: 0 }, + ... + } + }, + targets: [ + { name: ID, level: LEVEL, index: 0 }, + ... + ] + } +} +*/ + +struct paramaction +{ + const char *name; + int (*action)(struct json_object *obj, const struct wgt_desc_param *param, void *closure); + void *closure; +}; + + +/* apply params */ +static int apply_params(struct json_object *obj, const struct wgt_desc_param *param, const struct paramaction *actions) +{ + int rc; + const struct paramaction *a; + + if (!obj) + rc = -1; + else { + rc = 0; + while(param) { + for (a = actions ; a->name && strcmp(a->name, param->name) ; a++); + if (a->action && a->action(obj, param, a->closure) < 0) + rc = -1; + param = param->next; + } + } + return rc; +} + +static int get_array(struct json_object **result, struct json_object *obj, const char *key, int create) +{ + int rc = j_enter_m(&obj, &key, 1); + if (rc < 0) + *result = NULL; + else if (!json_object_object_get_ex(obj, key, result)) { + if (!create) { + *result = NULL; + rc = -ENOENT; + } else { + if (!(*result = j_add_new_array(obj, key))) + rc = -ENOMEM; + } + } + return rc; +} + +/* get the param of 'name' for the feature 'feat' */ +static const char *get_target_name(const struct wgt_desc_feature *feat, const char *defval) +{ + const struct wgt_desc_param *param = feat->params; + while (param && strcmp(param->name, string_sharp_target)) + param = param->next; + if (param) { + defval = param->value; + param = param->next; + while (param) { + if (!strcmp(param->name, string_sharp_target)) { + defval = NULL; + break; + } + param = param->next; + } + } + return defval; +} + +/* get the target */ +static struct json_object *get_array_item_by_key(struct json_object *array, const char *key, const char *val) +{ + struct json_object *result, *k; + int i, n; + + n = json_object_array_length(array); + for (i = 0 ; i < n ; i++) { + result = json_object_array_get_idx(array, i); + if (result && json_object_object_get_ex(result, key, &k) + && json_object_get_type(k) == json_type_string + && !strcmp(json_object_get_string(k), val)) + return result; + } + return NULL; +} + +static int get_target(struct json_object **result, struct json_object *targets, const char *name, int create) +{ + int rc; + struct json_object *t; + + t = get_array_item_by_key(targets, string_sharp_target, name); + if (t) { + if (!create) + rc = 0; + else { + ERROR("duplicated target name: %s", name); + t = NULL; + rc = -EEXIST; + } + } else { + if (!create) { + ERROR("target name not found: %s", name); + rc = -ENOENT; + } else { + t = j_add_new_object(targets, NULL); + if (t && j_add_string(t, string_sharp_target, name)) + rc = 0; + else { + json_object_put(t); + t = NULL; + rc = -ENOMEM; + } + } + } + *result = t; + return rc; +} + +static int make_target(struct json_object *targets, const struct wgt_desc_feature *feat) +{ + const char *id; + struct json_object *target; + + id = get_target_name(feat, NULL); + if (id == NULL) { + ERROR("target of feature %s is missing or repeated", feat->name); + return -EINVAL; + } + + return get_target(&target, targets, id, 1); +} + +static int add_icon(struct json_object *target, const char *src, int width, int height) +{ + int rc; + struct json_object *icon, *array; + + rc = get_array(&array, target, string_icon, 1); + if (rc >= 0) { + icon = json_object_new_object(); + if(!icon + || !j_add_string(icon, string_src, src) + || (width > 0 && !j_add_integer(icon, string_width, width)) + || (height > 0 && !j_add_integer(icon, string_height, height)) + || !j_add(array, NULL, icon)) { + json_object_put(icon); + rc = -ENOMEM; + } + } + return rc; +} + +static int make_main_target(struct json_object *targets, const struct wgt_desc *desc) +{ + int rc; + struct wgt_desc_icon *icon; + struct json_object *target; + + /* create the target 'main' */ + rc = get_target(&target, targets, string_main, 1); + + /* adds icons if any */ + icon = desc->icons; + while (rc >= 0 && icon) { + rc = add_icon(target, icon->src, icon->width, icon->height); + icon = icon->next; + } + + if (rc >= 0) + rc = j_add_many_strings_m(target, + "content.src", desc->content_src, + "content.type", desc->content_type, + "content.encoding", desc->content_encoding, + NULL) ? 0 : -errno; + + if (rc >= 0) + if((desc->width && !j_add_integer(target, string_width, desc->width)) + || (desc->height && !j_add_integer(target, string_height, desc->height))) + rc = -ENOMEM; + + return rc; +} + +/***********************************************************************************************************/ + +static struct json_object *object_of_param(const struct wgt_desc_param *param) +{ + struct json_object *value; + + value = json_object_new_object(); + if (value + && j_add_string(value, string_name, param->name) + && j_add_string(value, string_value, param->value)) + return value; + + json_object_put(value); + return NULL; +} + +static int add_param_simple(struct json_object *obj, const struct wgt_desc_param *param, void *closure) +{ + return j_add_string_m(obj, param->name, param->value); +} + +static int add_param_array(struct json_object *obj, const struct wgt_desc_param *param, void *closure) +{ + struct json_object *array, *value; + + if (!closure) + array = obj; + else if (!json_object_object_get_ex(obj, closure, &array)) { + array = j_add_new_array(obj, closure); + if (!array) + return -ENOMEM; + } + value = object_of_param(param); + if (value && j_add(array, NULL, value)) + return 0; + + json_object_put(value); + return -ENOMEM; +} + +static int add_param_object(struct json_object *obj, const struct wgt_desc_param *param, void *closure) +{ + struct json_object *object, *value; + + if (!closure) + object = obj; + else if (!json_object_object_get_ex(obj, closure, &object)) { + object = j_add_new_object(obj, closure); + if (!object) + return -ENOMEM; + } + value = object_of_param(param); + if (value && j_add(object, param->name, value)) + return 0; + + json_object_put(value); + return -ENOMEM; +} + +static int add_targeted_params(struct json_object *targets, const struct wgt_desc_feature *feat, struct paramaction actions[]) +{ + int rc; + const char *id; + struct json_object *obj; + + id = get_target_name(feat, string_main); + rc = get_target(&obj, targets, id, 0); + return rc < 0 ? rc : apply_params(obj, feat->params, actions); +} + +static int add_provided(struct json_object *targets, const struct wgt_desc_feature *feat) +{ + static struct paramaction actions[] = { + { .name = string_sharp_target, .action = NULL, .closure = NULL }, + { .name = NULL, .action = add_param_simple, .closure = NULL } + }; + return add_targeted_params(targets, feat, actions); +} + +static int add_required_binding(struct json_object *targets, const struct wgt_desc_feature *feat) +{ + static struct paramaction actions[] = { + { .name = string_sharp_target, .action = NULL, .closure = NULL }, + { .name = NULL, .action = add_param_array, .closure = (void*)string_required_binding } + }; + return add_targeted_params(targets, feat, actions); +} + + +static int add_required_permission(struct json_object *targets, const struct wgt_desc_feature *feat) +{ + static struct paramaction actions[] = { + { .name = string_sharp_target, .action = NULL, .closure = NULL }, + { .name = NULL, .action = add_param_object, .closure = (void*)string_required_permission } + }; + return add_targeted_params(targets, feat, actions); +} + +static int add_defined_permission(struct json_object *defperm, const struct wgt_desc_feature *feat) +{ + static struct paramaction actions[] = { + { .name = NULL, .action = add_param_array, .closure = NULL } + }; + return apply_params(defperm, feat->params, actions); +} + +/***********************************************************************************************************/ + +static struct json_object *to_json(const struct wgt_desc *desc) +{ + size_t prefixlen; + const struct wgt_desc_feature *feat; + const char *featname; + struct json_object *result, *targets, *permissions; + int rc, rc2; + + /* create the application structure */ + if(!(result = json_object_new_object()) + || !(targets = j_add_new_array(result, string_targets)) + || !(permissions = j_add_new_array(result, string_defined_permission)) + ) + goto error; + + /* first pass: declarations */ + rc = make_main_target(targets, desc); + prefixlen = strlen(string_AGL_widget_prefix); + for (feat = desc->features ; feat ; feat = feat->next) { + featname = feat->name; + if (!memcmp(featname, string_AGL_widget_prefix, prefixlen)) { + if (!feat->required) { + ERROR("feature %s can't be optional", featname); + if (!rc) + rc = -EINVAL; + } + featname += prefixlen; + if (!strcmp(featname, string_provided_binding) + || !strcmp(featname, string_provided_application)) { + rc2 = make_target(targets, feat); + if (rc2 < 0 && !rc) + rc = rc2; + } + } + } + + /* second pass: definitions */ + for (feat = desc->features ; feat ; feat = feat->next) { + featname = feat->name; + if (!memcmp(featname, string_AGL_widget_prefix, prefixlen)) { + featname += prefixlen; + if (!strcmp(featname, string_defined_permission)) { + rc2 = add_defined_permission(permissions, feat); + if (rc2 < 0 && !rc) + rc = rc2; + } + else if (!strcmp(featname, string_provided_application) + || !strcmp(featname, string_provided_binding)) { + rc2 = add_provided(targets, feat); + if (rc2 < 0 && !rc) + rc = rc2; + } + else if (!strcmp(featname, string_required_binding)) { + rc2 = add_required_binding(targets, feat); + if (rc2 < 0 && !rc) + rc = rc2; + } + else if (!strcmp(featname, string_required_permission)) { + rc2 = add_required_permission(targets, feat); + if (rc2 < 0 && !rc) + rc = rc2; + } + } + } + + /* fills the main */ + rc2 = j_add_many_strings_m(result, + string_id, desc->id, + string_idaver, desc->idaver, + string_version, desc->version, + "ver", desc->ver, + "author.content", desc->author, + "author.href", desc->author_href, + "author.email", desc->author_email, + "license.content", desc->license, + "license.href", desc->license_href, + "defaultlocale", desc->defaultlocale, + "name.content", desc->name, + "name.short", desc->name_short, + "description", desc->description, + NULL) ? 0 : -errno; + if (rc2 < 0 && !rc) + rc = rc2; + + /* */ + + /* */ + if (!rc) { + return result; + } + +error: + json_object_put(result); + return NULL; +} + +struct json_object *wgt_info_to_json(struct wgt_info *info) +{ + return to_json(wgt_info_desc(info)); +} + +struct json_object *wgt_to_json(struct wgt *wgt) +{ + struct json_object *result; + struct wgt_info *info; + + info = wgt_info_create(wgt, 1, 1, 1); + if (info == NULL) + result = NULL; + else { + result = wgt_info_to_json(info); + wgt_info_unref(info); + } + return result; +} + +struct json_object *wgt_path_at_to_json(int dfd, const char *path) +{ + struct json_object *result; + struct wgt_info *info; + + info = wgt_info_createat(dfd, path, 1, 1, 1); + if (info == NULL) + result = NULL; + else { + result = wgt_info_to_json(info); + wgt_info_unref(info); + } + return result; +} + +struct json_object *wgt_path_to_json(const char *path) +{ + return wgt_path_at_to_json(AT_FDCWD, path); +} + + diff --git a/src/wgt-json.h b/src/wgt-json.h new file mode 100644 index 0000000..11c4e41 --- /dev/null +++ b/src/wgt-json.h @@ -0,0 +1,27 @@ +/* + Copyright 2015, 2016, 2017 IoT.bzh + + author: José Bollo <jose.bollo@iot.bzh> + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +struct wgt; +struct wgt_info; +struct json_object; + +extern struct json_object *wgt_info_to_json(struct wgt_info *info); +extern struct json_object *wgt_to_json(struct wgt *wgt); +extern struct json_object *wgt_path_at_to_json(int dfd, const char *path); +extern struct json_object *wgt_path_to_json(const char *path); + diff --git a/src/wgt-strings.c b/src/wgt-strings.c index e3d45a0..a359880 100644 --- a/src/wgt-strings.c +++ b/src/wgt-strings.c @@ -1,5 +1,5 @@ /* - Copyright 2016 IoT.bzh + Copyright 2016, 2017 IoT.bzh author: José Bollo <jose.bollo@iot.bzh> @@ -16,6 +16,7 @@ limitations under the License. */ +/* defined by/for config.xml */ const char string_author[] = "author"; const char string_content[] = "content"; const char string_defaultlocale[] = "defaultlocale"; @@ -29,7 +30,6 @@ const char string_icon[] = "icon"; const char string_id[] = "id"; const char string_license[] = "license"; const char string_name[] = "name"; -const char string_optional[] = "optional"; const char string_param[] = "param"; const char string_preference[] = "preference"; const char string_readonly[] = "readonly"; @@ -44,7 +44,27 @@ const char string_widget[] = "widget"; const char string_width[] = "width"; const char string_config_dot_xml[] = "config.xml"; -const char feature_required_binding[] = FWK_PREFIX "required-binding"; -const char feature_required_permission[] = FWK_PREFIX "required-permission"; +/* other strings */ +const char string_AGL_prefix[] = FWK_PREFIX; +const char string_AGL_permission_prefix[] = FWK_PREFIX "permission:"; +const char string_AGL_widget_prefix[] = FWK_PREFIX "widget:"; +const char string_defined_permission[] = "defined-permission"; +const char string_dict[] = "dict"; +const char string_idaver[] = "idaver"; +const char string_index[] = "index"; +const char string_level[] = "level"; +const char string_list[] = "list"; +const char string_main[] = "main"; +const char string_optional[] = "optional"; +const char string_provided_application[] = "provided-application"; +const char string_provided_binding[] = "provided-binding"; +const char string_required_binding[] = "required-binding"; +const char string_required_permission[] = "required-permission"; +const char string_targets[] = "targets"; +const char string_sharp_target[] = "#target"; + + +const char feature_required_binding[] = FWK_PREFIX "widget:required-binding"; +const char feature_required_permission[] = FWK_PREFIX "widget:required-permission"; diff --git a/src/wgt-strings.h b/src/wgt-strings.h index b2befcc..7609723 100644 --- a/src/wgt-strings.h +++ b/src/wgt-strings.h @@ -1,5 +1,5 @@ /* - Copyright 2016 IoT.bzh + Copyright 2016, 2017 IoT.bzh author: José Bollo <jose.bollo@iot.bzh> @@ -16,6 +16,7 @@ limitations under the License. */ +/* defined by/for config.xml */ extern const char string_author[]; extern const char string_content[]; extern const char string_defaultlocale[]; @@ -29,7 +30,6 @@ extern const char string_icon[]; extern const char string_id[]; extern const char string_license[]; extern const char string_name[]; -extern const char string_optional[]; extern const char string_param[]; extern const char string_preference[]; extern const char string_readonly[]; @@ -44,6 +44,22 @@ extern const char string_widget[]; extern const char string_width[]; extern const char string_config_dot_xml[]; +/* other strings */ +extern const char string_AGL_widget_prefix[]; +extern const char string_defined_permission[]; +extern const char string_dict[]; +extern const char string_idaver[]; +extern const char string_index[]; +extern const char string_level[]; +extern const char string_list[]; +extern const char string_main[]; +extern const char string_optional[]; +extern const char string_provided_application[]; +extern const char string_provided_binding[]; +extern const char string_required_binding[]; +extern const char string_required_permission[]; +extern const char string_sharp_target[]; +extern const char string_targets[]; extern const char feature_required_binding[]; extern const char feature_required_permission[]; diff --git a/src/wgtpkg-mustach.c b/src/wgtpkg-mustach.c new file mode 100644 index 0000000..4b05bc2 --- /dev/null +++ b/src/wgtpkg-mustach.c @@ -0,0 +1,264 @@ +/* + Author: José Bollo <jobol@nonadev.net> + Author: José Bollo <jose.bollo@iot.bzh> + + https://gitlab.com/jobol/mustach + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <string.h> + +#include <json-c/json.h> + +#include "mustach.h" + +#define MAX_DEPTH 256 + + +struct expl { + struct json_object *root; + int depth; + struct { + struct json_object *cont; + struct json_object *obj; + int index, count; + } stack[MAX_DEPTH]; +}; + +/* + * Scan a key=val text. + * If the sign = is found, drop it and returns a pointer to the value. + * If the sign = is not here, returns NULL. + * Replace any \= of the key by its unescaped version =. + */ +static char *keyval(char *head) +{ + char *w, c; + + c = *(w = head); + while (c && c != '=') { + if (c == '\\') { + switch (head[1]) { + case '\\': *w++ = c; + case '=': c = *++head; + default: break; + } + } + *w++ = c; + c = *++head; + } + *w = 0; + return c == '=' ? ++head : NULL; +} + +/* + * Returns the unescaped version of the first component + * and update 'name' to point the next components if any. + */ +static char *first(char **name) +{ + char *r, *i, *w, c; + + c = *(i = *name); + if (!c) + r = NULL; + else { + r = w = i; + while (c && c != '.') { + if (c == '\\' && (i[1] == '.' || i[1] == '\\')) + c = *++i; + *w++ = c; + c = *++i; + } + *w = 0; + *name = i + !!c; + } + return r; +} + +/* + * Replace the last occurence of ':' followed by + * any character not being '*' by ':*', the + * globalisation of the key. + * Returns NULL if no globalisation is done + * or else the key globalized. + */ +static char *globalize(char *key) +{ + char *f, *r; + + f = NULL; + for (r = key; *r ; r++) { + if (r[0] == ':' && r[1] && r[1] != '*') + f = r; + } + if (f) { + f[1] = '*'; + f[2] = 0; + f = key; + } + return f; +} + +/* + * find the object of 'name' + */ +static struct json_object *find(struct expl *e, const char *name) +{ + int i; + struct json_object *o, *r; + char *n, *c, *v; + + /* get a local key */ + n = strdupa(name); + + /* extract its value */ + v = keyval(n); + + /* search the first component for each valid globalisation */ + i = e->depth; + c = first(&n); + while (c) { + if (i < 0) { + /* next globalisation */ + i = e->depth; + c = globalize(c); + } + else if (json_object_object_get_ex(e->stack[i].obj, c, &o)) { + + /* found the root, search the subcomponents */ + c = first(&n); + while(c) { + while (!json_object_object_get_ex(o, c, &r)) { + c = globalize(c); + if (!c) + return NULL; + } + o = r; + c = first(&n); + } + + /* check the value if requested */ + if (v) { + i = v[0] == '!'; + if (i == !strcmp(&v[i], json_object_get_string(o))) + o = NULL; + } + return o; + } + i--; + } + return NULL; +} + +static int start(void *closure) +{ + struct expl *e = closure; + e->depth = 0; + e->stack[0].cont = NULL; + e->stack[0].obj = e->root; + e->stack[0].index = 0; + e->stack[0].count = 1; + return 0; +} + +static void print(FILE *file, const char *string, int escape) +{ + if (!escape) + fputs(string, file); + else if (*string) + do { + switch(*string) { + case '%': fputs("%%", file); break; + case '\n': fputs("\\n\\\n", file); break; + default: putc(*string, file); break; + } + } while(*++string); +} + +static int put(void *closure, const char *name, int escape, FILE *file) +{ + struct expl *e = closure; + struct json_object *o = find(e, name); + if (o) + print(file, json_object_get_string(o), escape); + return 0; +} + +static int enter(void *closure, const char *name) +{ + struct expl *e = closure; + struct json_object *o = find(e, name); + if (++e->depth >= MAX_DEPTH) + return MUSTACH_ERROR_TOO_DEPTH; + if (json_object_is_type(o, json_type_array)) { + e->stack[e->depth].count = json_object_array_length(o); + if (e->stack[e->depth].count == 0) { + e->depth--; + return 0; + } + e->stack[e->depth].cont = o; + e->stack[e->depth].obj = json_object_array_get_idx(o, 0); + e->stack[e->depth].index = 0; + } else if (json_object_is_type(o, json_type_object) || json_object_get_boolean(o)) { + e->stack[e->depth].count = 1; + e->stack[e->depth].cont = NULL; + e->stack[e->depth].obj = o; + e->stack[e->depth].index = 0; + } else { + e->depth--; + return 0; + } + return 1; +} + +static int next(void *closure) +{ + struct expl *e = closure; + if (e->depth <= 0) + return MUSTACH_ERROR_CLOSING; + e->stack[e->depth].index++; + if (e->stack[e->depth].index >= e->stack[e->depth].count) + return 0; + e->stack[e->depth].obj = json_object_array_get_idx(e->stack[e->depth].cont, e->stack[e->depth].index); + return 1; +} + +static int leave(void *closure) +{ + struct expl *e = closure; + if (e->depth <= 0) + return MUSTACH_ERROR_CLOSING; + e->depth--; + return 0; +} + +static struct mustach_itf itf = { + .start = start, + .put = put, + .enter = enter, + .next = next, + .leave = leave +}; + +int apply_mustach(const char *template, struct json_object *root, char **result, size_t *size) +{ + struct expl e; + e.root = root; + return mustach(template, &itf, &e, result, size); +} + diff --git a/src/wgtpkg-mustach.h b/src/wgtpkg-mustach.h new file mode 100644 index 0000000..c4ef6e1 --- /dev/null +++ b/src/wgtpkg-mustach.h @@ -0,0 +1,24 @@ +/* + Author: José Bollo <jose.bollo@iot.bzh> + Author: José Bollo <jobol@nonadev.net> + + https://gitlab.com/jobol/mustach + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +struct json_object; + +extern int apply_mustach(const char *template, struct json_object *root, char **result, size_t *size); + + diff --git a/src/wgtpkg-unit.c b/src/wgtpkg-unit.c new file mode 100644 index 0000000..8569c5a --- /dev/null +++ b/src/wgtpkg-unit.c @@ -0,0 +1,254 @@ +/* + Copyright 2016, 2017 IoT.bzh + + author: José Bollo <jose.bollo@iot.bzh> + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include "verbose.h" +#include "utils-file.h" + +#include "wgtpkg-mustach.h" +#include "wgtpkg-unit.h" + +static char *template; + +/* + * Pack a null terminated 'text' by removing empty lines, + * lines made of blanks and terminated with \, lines + * starting with the 'purge' character (can be null). + * Lines made of the 'purge' character followed with + * "nl" exactly (without quotes ") are replaced with + * an empty line. + * + * Returns the pointer to the ending null. + */ +static char *pack(char *text, char purge) +{ + char *read, *write, *begin, *start, c, emit, cont, nextcont; + + cont = 0; + c = *(begin = write = read = text); + while (c) { + emit = nextcont = 0; + start = NULL; + begin = read; + while (c && c != '\n') { + if (c != ' ' && c != '\t') { + if (c == '\\' && read[1] == '\n') + nextcont = 1; + else { + emit = 1; + if (!start) + start = read; + } + } + c = *++read; + } + if (c) + c = *++read; + if (emit) { + if (!cont && start) + begin = start; + if (purge && *begin == purge) { + if (!strncmp(begin+1, "nl\n",3)) + *write++ = '\n'; + } else { + while (begin != read) + *write++ = *begin++; + } + } + cont = nextcont; + } + *write = 0; + return write; +} + +/* + * Searchs the first character of the next line + * of the 'text' and returns its address + * Returns NULL if there is no next line. + */ +static inline char *nextline(char *text) +{ + char *result = strchr(text, '\n'); + return result + !!result; +} + +/* + * Search in 'text' the offset of a line beginning with the 'pattern' + * Returns NULL if not found or the address of the line contning the pattern + * If args isn't NULL and the pattern is found, the pointed pattern is + * updated with the address of the character following the found pattern. + */ +static char *offset(char *text, const char *pattern, char **args) +{ + size_t len; + + if (text) { + len = strlen(pattern); + do { + if (strncmp(text, pattern, len)) + text = nextline(text); + else { + if (args) + *args = &text[len]; + break; + } + } while (text); + } + return text; +} + +/* + * process one unit + */ + +static int process_one_unit(char *spec, struct unitdesc *desc) +{ + char *nsoc, *nsrv; + int isuser, issystem, issock, isserv; + + /* found the configuration directive of the unit */ + isuser = !!offset(spec, "%systemd-unit user\n", NULL); + issystem = !!offset(spec, "%systemd-unit system\n", NULL); + issock = !!offset(spec, "%systemd-unit socket ", &nsoc); + isserv = !!offset(spec, "%systemd-unit service ", &nsrv); + + if (isuser ^ issystem) { + desc->scope = isuser ? unitscope_user : unitscope_system; + } else { + desc->scope = unitscope_unknown; + } + + if (issock ^ isserv) { + if (issock) { + desc->type = unittype_socket; + desc->name = nsoc; + } else { + desc->type = unittype_service; + desc->name = nsrv; + } + desc->name_length = (size_t)(strchrnul(desc->name, '\n') - desc->name); + desc->name = strndup(desc->name, desc->name_length); + } else { + desc->type = unittype_unknown; + desc->name = NULL; + desc->name_length = 0; + } + + desc->content = spec; + desc->content_length = (size_t)(pack(spec, '%') - spec); + + return 0; +} + +/* + */ +static int process_all_units(char *spec, int (*process)(void *closure, const struct unitdesc descs[], unsigned count), void *closure) +{ + int rc, rc2; + unsigned n; + char *beg, *end, *after; + struct unitdesc *descs, *d; + + descs = NULL; + n = 0; + rc = 0; + beg = offset(spec, "%begin systemd-unit\n", NULL); + while(beg) { + beg = nextline(beg); + end = offset(beg, "%end systemd-unit\n", &after); + if (!end) { + /* unterminated unit !! */ + ERROR("unterminated unit description!! %s", beg); + break; + } + *end = 0; + + d = realloc(descs, (n + 1) * sizeof *descs); + if (d == NULL) + rc2 = -ENOMEM; + else { + memset(&d[n], 0, sizeof *d); + descs = d; + rc2 = process_one_unit(beg, &descs[n]); + } + + if (rc2 >= 0) + n++; + else if (rc == 0) + rc = rc2; + beg = offset(after, "%begin systemd-unit\n", NULL); + } + + if (rc == 0 && process) + rc = process(closure, descs, n); + while(n) + free((char *)(descs[--n].name)); + free(descs); + + return rc; +} + +int unit_generator_on(const char *filename) +{ + size_t size; + char *tmp; + int rc; + + rc = getfile(filename ? : FWK_UNIT_CONF, &template, NULL); + if (!rc) { + size = (size_t)(pack(template, ';') - template); + tmp = realloc(template, 1 + size); + if (tmp) + template = tmp; + } + return rc; +} + +void unit_generator_off() +{ + free(template); + template = NULL; +} + +int unit_generator_process(struct json_object *jdesc, int (*process)(void *closure, const struct unitdesc descs[], unsigned count), void *closure) +{ + int rc; + size_t size; + char *instance; + + rc = template ? 0 : unit_generator_on(NULL); + if (!rc) { + instance = NULL; + rc = apply_mustach(template, jdesc, &instance, &size); + if (!rc) { + rc = process_all_units(instance, process, closure); + } + free(instance); + } + return rc; +} + diff --git a/src/wgtpkg-unit.h b/src/wgtpkg-unit.h new file mode 100644 index 0000000..4843685 --- /dev/null +++ b/src/wgtpkg-unit.h @@ -0,0 +1,45 @@ +/* + Copyright 2016, 2017 IoT.bzh + + author: José Bollo <jose.bollo@iot.bzh> + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + + +struct json_object; + +enum unitscope { + unitscope_unknown = 0, + unitscope_system, + unitscope_user +}; + +enum unittype { + unittype_unknown = 0, + unittype_service, + unittype_socket +}; + +struct unitdesc { + enum unitscope scope; + enum unittype type; + const char *name; + size_t name_length; + const char *content; + size_t content_length; +}; + +extern int unit_generator_on(const char *filename); +extern void unit_generator_off(); +extern int unit_generator_process(struct json_object *jdesc, int (*process)(void *closure, const struct unitdesc descs[], unsigned count), void *closure); |