aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJosé Bollo <jose.bollo@iot.bzh>2016-10-11 17:07:16 +0200
committerJosé Bollo <jose.bollo@iot.bzh>2017-01-26 21:40:08 +0100
commit1d4de11a907e41c06063a2cd5028dc4101690f50 (patch)
tree69af98bbe6512cdbcab33267574c131f85ffd597
parentbfc9c138b1a9e87f9d387e2f900c14807c9da9b9 (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.txt2
-rw-r--r--conf/CMakeLists.txt5
-rw-r--r--conf/afm-unit.conf168
-rw-r--r--docs/config.xml.md303
-rw-r--r--docs/permissions.md61
-rw-r--r--docs/widgets.md238
-rw-r--r--mkdocs.yml4
-rw-r--r--src/CMakeLists.txt10
-rw-r--r--src/mustach.c257
-rw-r--r--src/mustach.h112
-rw-r--r--src/tests/CMakeLists.txt20
-rw-r--r--src/tests/test-unit/CMakeLists.txt22
-rw-r--r--src/tests/test-unit/config.xml46
-rw-r--r--src/tests/test-unit/sample.unit94
-rw-r--r--src/tests/test-unit/test-unit.c89
-rw-r--r--src/wgt-json.c482
-rw-r--r--src/wgt-json.h27
-rw-r--r--src/wgt-strings.c28
-rw-r--r--src/wgt-strings.h20
-rw-r--r--src/wgtpkg-mustach.c264
-rw-r--r--src/wgtpkg-mustach.h24
-rw-r--r--src/wgtpkg-unit.c254
-rw-r--r--src/wgtpkg-unit.h45
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
--------------------------
diff --git a/mkdocs.yml b/mkdocs.yml
index 1102944..fc8cb09 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -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);