Prepare the Integration with systemd
authorJosé Bollo <jose.bollo@iot.bzh>
Tue, 11 Oct 2016 15:07:16 +0000 (17:07 +0200)
committerJosé Bollo <jose.bollo@iot.bzh>
Thu, 26 Jan 2017 20:40:08 +0000 (21:40 +0100)
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>
23 files changed:
CMakeLists.txt
conf/CMakeLists.txt
conf/afm-unit.conf [new file with mode: 0644]
docs/config.xml.md [new file with mode: 0644]
docs/permissions.md [new file with mode: 0644]
docs/widgets.md
mkdocs.yml
src/CMakeLists.txt
src/mustach.c [new file with mode: 0644]
src/mustach.h [new file with mode: 0644]
src/tests/CMakeLists.txt [new file with mode: 0644]
src/tests/test-unit/CMakeLists.txt [new file with mode: 0644]
src/tests/test-unit/config.xml [new file with mode: 0644]
src/tests/test-unit/sample.unit [new file with mode: 0644]
src/tests/test-unit/test-unit.c [new file with mode: 0644]
src/wgt-json.c [new file with mode: 0644]
src/wgt-json.h [new file with mode: 0644]
src/wgt-strings.c
src/wgt-strings.h
src/wgtpkg-mustach.c [new file with mode: 0644]
src/wgtpkg-mustach.h [new file with mode: 0644]
src/wgtpkg-unit.c [new file with mode: 0644]
src/wgtpkg-unit.h [new file with mode: 0644]

index 5796837..83e0c39 100644 (file)
@@ -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}"
 )
 
index 32a0cc4..d8611e0 100644 (file)
@@ -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 (file)
index 0000000..290c3e1
--- /dev/null
@@ -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 (file)
index 0000000..7f413d5
--- /dev/null
@@ -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 (file)
index 0000000..300a719
--- /dev/null
@@ -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"
+
index c23faa8..2acc92a 100644 (file)
 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
 --------------------------
 
index 1102944..fc8cb09 100644 (file)
@@ -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'
index f126346..c38dd07 100644 (file)
@@ -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 (file)
index 0000000..19df16f
--- /dev/null
@@ -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 (file)
index 0000000..8196679
--- /dev/null
@@ -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 (file)
index 0000000..09d4d0b
--- /dev/null
@@ -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 (file)
index 0000000..3f6db69
--- /dev/null
@@ -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 (file)
index 0000000..24a383d
--- /dev/null
@@ -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 (file)
index 0000000..2ee2a2a
--- /dev/null
@@ -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 (file)
index 0000000..09b7c86
--- /dev/null
@@ -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 (file)
index 0000000..cc5ee93
--- /dev/null
@@ -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 (file)
index 0000000..11c4e41
--- /dev/null
@@ -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);
+
index e3d45a0..a359880 100644 (file)
@@ -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";
 
 
index b2befcc..7609723 100644 (file)
@@ -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 (file)
index 0000000..4b05bc2
--- /dev/null
@@ -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 (file)
index 0000000..c4ef6e1
--- /dev/null
@@ -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 (file)
index 0000000..8569c5a
--- /dev/null
@@ -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 (file)
index 0000000..4843685
--- /dev/null
@@ -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);