--- /dev/null
+CMAKE_MINIMUM_REQUIRED(VERSION 3.6)
+PROJECT(isu C)
+SET(PREFIX ${CMAKE_INSTALL_PREFIX})
+ADD_SUBDIRECTORY(src/libisu)
+ADD_SUBDIRECTORY(src/isud)
-d /opt/isu 0755 root root -
+d /opt/isu 0755 system system -
t /opt/isu - - - - security.SMACK64="_"
-d /run/isu 0755 root root -
+d /run/isu 0755 system system -
t /run/isu - - - - security.SMACK64="_"
+%define libisu_version 0.0.1
Summary: Individual Service Upgrade support
Name: isu
Version: 8.0.16
Release: 1
Source0: %{name}-%{version}.tar.gz
License: MIT
+BuildRequires: cmake
+BuildRequires: pkgconfig
+BuildRequires: pkgconfig(libarchive)
+BuildRequires: pkgconfig(dlog)
+BuildRequires: pkgconfig(glib-2.0)
+BuildRequires: pkgconfig(mount)
+BuildRequires: pkgconfig(capi-system-info)
%description
Individual Service Upgrade is Tizen mechanism used to allow extending
from extension packages and modified bubblewrap to run service in
partially sandboxed environment.
+%package -n libisu
+Summary: ISU API library
+%description -n libisu
+Provies ISU API library
+
+%package -n libisu-devel
+Summary: Package provides headers needed to develop programs using ISU API
+%description -n libisu-devel
+
%prep
%setup -q
%build
cp packaging/isu.manifest .
+%cmake . -DCMAKE_INSTALL_PREFIX=%{_prefix} -DLIBISU_VERSION=%{libisu_version}
+make %{?_smp_mflags}
%install
+rm -rf %{buildroot}
+%make_install
+
mkdir -p %{buildroot}/usr/libexec
mkdir -p %{buildroot}/usr/lib/systemd/system-generators
mkdir -p %{buildroot}/usr/lib/systemd/user-generators
/etc/isu/user-service-common.inc
/etc/isu/upgrade.cfg
%{_bindir}/isu
+
+%files -n libisu
+%manifest isu.manifest
+%license LICENSE.MIT
+%{_libdir}/libisu.so.*
+%{_bindir}/isud
+%attr(-,root,root) /etc/dbus-1/system.d/isud.conf
+%attr(-,root,root) %{_unitdir}/isud.service
+%attr(-,root,root) %{_datadir}/dbus-1/system-services/org.tizen.system.isu.service
+
+
+%files -n libisu-devel
+%manifest isu.manifest
+%license LICENSE.MIT
+%{_libdir}/libisu.so
+%{_includedir}/libisu.h
+%{_includedir}/libisu-internal.h
+%{_libdir}/pkgconfig/*.pc
--- /dev/null
+FIND_PACKAGE(PkgConfig)
+INCLUDE(GNUInstallDirs)
+
+pkg_check_modules(deps REQUIRED
+ dlog
+ gio-2.0
+ gio-unix-2.0
+ capi-system-info
+ )
+
+
+ADD_EXECUTABLE(isud isud.c)
+TARGET_COMPILE_OPTIONS(isud PRIVATE ${deps_CFLAGS_OTHER} -fPIC)
+TARGET_INCLUDE_DIRECTORIES(isud PRIVATE ${deps_INCLUDE_DIRS} ../libisu/)
+TARGET_LINK_LIBRARIES(isud PRIVATE isu ${deps_LDFLAGS} -pie -larchive)
+
+
+INSTALL(TARGETS isud DESTINATION bin)
+INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/isud.conf DESTINATION /etc/dbus-1/system.d)
+INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/isud.service DESTINATION /usr/lib/systemd/system)
+INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/org.tizen.system.isu.service DESTINATION /usr/share/dbus-1/system-services)
+
--- /dev/null
+/*
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ *
+ * 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 <assert.h>
+#include <gio/gio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <dlog/dlog.h>
+
+#include "libisu-internal.h"
+
+#ifdef LOG_TAG
+ #undef LOG_TAG
+#endif
+#define LOG_TAG "ISUSERVICE"
+
+#define TIMEOUT_INTERVAL_SEC 30
+
+static GMainLoop *loop;
+static GMutex timeout_mutex;
+static guint timeout_id;
+static GDBusNodeInfo *introspection_data;
+static const gchar introspection_xml[] =
+"<node>"
+" <interface name='org.tizen.system.isu'>"
+" <method name='install'>"
+" <arg type='s' name='path' direction='in'/>"
+" <arg type='i' name='result' direction='out'/>"
+" </method>"
+" <method name='uninstall'>"
+" <arg type='s' name='name' direction='in'/>"
+" <arg type='i' name='result' direction='out'/>"
+" </method>"
+" </interface>"
+"</node>";
+
+static int timeout_cb(gpointer data)
+{
+ SLOGI("Time out!");
+ g_main_loop_quit((GMainLoop *)data);
+
+ return 0;
+}
+
+static void add_timeout(void)
+{
+ g_mutex_lock(&timeout_mutex);
+
+ if (timeout_id)
+ g_source_remove(timeout_id);
+ timeout_id = g_timeout_add_seconds(TIMEOUT_INTERVAL_SEC, timeout_cb, loop);
+
+ g_mutex_unlock(&timeout_mutex);
+ SLOGD("Add loop timeout (%d)", TIMEOUT_INTERVAL_SEC);
+}
+
+static void remove_timeout(void)
+{
+ g_mutex_lock(&timeout_mutex);
+
+ if (timeout_id) {
+ g_source_remove(timeout_id);
+ timeout_id = 0;
+ }
+
+ g_mutex_unlock(&timeout_mutex);
+ SLOGD("Remove loop timeout");
+}
+
+static void call_handler(GDBusMethodInvocation *invocation, bool install)
+{
+ GDBusMessage *incoming_message = g_dbus_method_invocation_get_message(invocation);
+ GVariant *body = g_dbus_message_get_body(incoming_message);
+ gchar *data;
+ g_variant_get(body, "(s)", &data);
+ int res;
+ if (install) {
+ res = isu_install_internal(data);
+ } else {
+ res = isu_uninstall_internal(data);
+ }
+ SLOGD("ISU Package \"%s\" %s %s (%d)", data,
+ install ? "installation" : "uninstallation",
+ res == ISU_RES_OK ? "success" : "error",
+ res);
+ g_dbus_method_invocation_return_value(invocation,
+ g_variant_new("(i)", res));
+}
+
+static void install_handler(GDBusMethodInvocation *invocation)
+{
+ call_handler(invocation, true);
+}
+
+static void uninstall_handler(GDBusMethodInvocation *invocation)
+{
+ call_handler(invocation, false);
+}
+
+static void method_call_handler(GDBusConnection *conn,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *iface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ remove_timeout();
+
+ if (g_strcmp0(method_name, "install") == 0) {
+ SLOGD("Install");
+ install_handler(invocation);
+ } else if (g_strcmp0(method_name, "uninstall") == 0) {
+ SLOGD("Uninstall");
+ uninstall_handler(invocation);
+ } else {
+ SLOGE("Unsupported method: %s", method_name);
+ }
+
+ add_timeout();
+}
+
+static const GDBusInterfaceVTable interface_vtable = {
+ method_call_handler,
+ NULL,
+ NULL
+};
+
+static void on_bus_acquired(GDBusConnection *conn,
+ const gchar *name,
+ gpointer user_data)
+{
+ const char *object_path;
+ GDBusInterfaceInfo *interface_info;
+ guint registration_id;
+ GError *error = NULL;
+
+ if (strcmp(name, ISUSERVICE_BUS_NAME) == 0) {
+ object_path = ISUSERVICE_OBJECT_PATH;
+ interface_info = introspection_data->interfaces[0];
+ } else {
+ SLOGE("on_bus_acquired: unknown name: %s", name);
+ return;
+ }
+
+ registration_id = g_dbus_connection_register_object(conn,
+ object_path,
+ interface_info,
+ &interface_vtable,
+ NULL,
+ NULL,
+ &error);
+ if (registration_id == 0 || error) {
+ SLOGD("Failed to g_dbus_connection_register_object for %s: %s",
+ name,
+ error ? error->message : "");
+ if (error)
+ g_error_free(error);
+ }
+}
+
+static void on_name_acquired(GDBusConnection *conn,
+ const gchar *name,
+ gpointer user_data)
+{
+ SLOGD("Acquired the name %s on the system bus", name);
+}
+
+static void on_name_lost(GDBusConnection *conn,
+ const gchar *name,
+ gpointer user_data)
+{
+ SLOGD("Lost the name %s on the system bus", name);
+}
+
+static bool dbus_init(void)
+{
+ GError *error = NULL;
+
+ introspection_data =
+ g_dbus_node_info_new_for_xml(introspection_xml, NULL);
+ if (introspection_data == NULL) {
+ SLOGE("Failed to init g_dbus_info_new_for_xml");
+ return false;
+ }
+
+ GDBusConnection *conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!conn || error) {
+ SLOGE("Failed to get dbus: %s", error ? error->message : "");
+ if (error)
+ g_error_free(error);
+ return false;
+ }
+
+ g_bus_own_name(G_BUS_TYPE_SYSTEM, ISUSERVICE_BUS_NAME,
+ G_BUS_NAME_OWNER_FLAGS_NONE, on_bus_acquired,
+ on_name_acquired, on_name_lost, NULL, NULL);
+
+ return true;
+}
+
+int main(void)
+{
+ loop = g_main_loop_new(NULL, false);
+ if (loop == NULL) {
+ SLOGE("GLib main loop create error");
+ return EXIT_FAILURE;
+ }
+
+ if (!dbus_init()) {
+ g_main_loop_unref(loop);
+ return EXIT_FAILURE;
+ }
+
+ g_mutex_init(&timeout_mutex);
+ add_timeout();
+
+ g_main_loop_run(loop);
+
+ if (introspection_data)
+ g_dbus_node_info_unref(introspection_data);
+ g_mutex_clear(&timeout_mutex);
+ g_main_loop_unref(loop);
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+ <policy user="root">
+ <allow own="org.tizen.system.isu"/>
+ <allow send_destination="org.tizen.system.isu"
+ send_interface="org.tizen.system.isu"/>
+ </policy>
+
+ <policy user="system">
+ <allow own="org.tizen.system.isu"/>
+ </policy>
+
+ <policy context="default">
+ <deny own="org.tizen.system.isu"/>
+ <deny send_destination="org.tizen.system.isu"/>
+
+ <check send_destination="org.tizen.system.isu"
+ send_interface="org.tizen.system.isu"
+ privilege="http://tizen.org/privilege/isu.admin"/>
+ </policy>
+</busconfig>
--- /dev/null
+[Unit]
+Description=Individual Service Upgrade management daemon
+
+[Service]
+Type=dbus
+User=system
+Group=system
+BusName=org.tizen.system.isu
+ExecStart=/usr/bin/isud
+SmackProcessLabel=System
+KillMode=mixed
--- /dev/null
+[D-BUS Service]
+Name=org.tizen.system.isu
+Exec=/bin/false
+SystemdService=isud.service
+
--- /dev/null
+FIND_PACKAGE(PkgConfig)
+INCLUDE(GNUInstallDirs)
+
+pkg_check_modules(deps REQUIRED
+ dlog
+ gio-2.0
+ gio-unix-2.0
+ capi-system-info
+ mount
+ )
+
+
+SET(PC_VERSION ${LIBISU_VERSION})
+SET(PC_PREFIX ${CMAKE_INSTALL_PREFIX})
+SET(PC_NAME ${PROJECT_NAME})
+SET(PC_LIBDIR "${CMAKE_INSTALL_LIBDIR}")
+SET(PC_INCLUDE "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}")
+SET(PC_DESCRIPTION "Individual Service Upgrade support library")
+SET(PC_REQUIRED "dlog glib-2.0 gio-unix-2.0 capi-system-info")
+SET(PC_LDFLAGS -lisu)
+CONFIGURE_FILE(libisu.pc.in ${CMAKE_CURRENT_SOURCE_DIR}/libisu.pc @ONLY)
+
+ADD_LIBRARY(isu SHARED libisu.c libisu-internal.c)
+SET_TARGET_PROPERTIES(isu PROPERTIES VERSION ${LIBISU_VERSION})
+SET_TARGET_PROPERTIES(isu PROPERTIES SOVERSION 1)
+SET_TARGET_PROPERTIES(isu PROPERTIES PUBLIC_HEADER libisu.h)
+TARGET_COMPILE_OPTIONS(isu PUBLIC -fPIE)
+TARGET_INCLUDE_DIRECTORIES(isu PRIVATE . ${deps_INCLUDE_DIRS})
+
+INSTALL(TARGETS isu PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
+INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/libisu.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig)
+INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/libisu-internal.h DESTINATION ${INCLUDE_INSTALL_DIR})
+
--- /dev/null
+/*
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ *
+ * 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 <assert.h>
+#include <archive_entry.h>
+#include <archive.h>
+#include <dlog/dlog.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <ftw.h>
+#include <system_info.h>
+#include <libmount.h>
+#include <gio/gio.h>
+#include "libisu-internal.h"
+
+// Arbitrarily determined block size
+#define BLOCK_SIZE 10240
+#define CFG_NAME "name"
+#define CFG_VERSION "version"
+#define CFG_SYSTEM_SERVICE "system_service"
+
+bool is_isu_feature_supported()
+{
+ int ret;
+ bool feature_res;
+
+ ret = system_info_get_platform_bool(ISU_FEATURE, &feature_res);
+ if (ret != SYSTEM_INFO_ERROR_NONE) {
+ SLOGE("Failed to get feature info");
+ return false;
+ }
+
+ return feature_res;
+}
+
+isu_result isu_dbus_call(const char *method, const char *parameter)
+{
+ GError *error = NULL;
+ int res;
+ GDBusConnection *conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (conn == NULL) {
+ SLOGE("isu_%s: g_bus_get_sync error: %s", method, error ? error->message : "");
+ if (error)
+ g_error_free(error);
+ return ISU_RES_ERR_INTERNAL;
+ }
+
+ GDBusMessage *msg = g_dbus_message_new_method_call(
+ ISUSERVICE_BUS_NAME,
+ ISUSERVICE_OBJECT_PATH,
+ ISUSERVICE_INTERFACE_NAME,
+ method);
+ if (msg == NULL) {
+ SLOGE("isu_%s: g_dbus_message_new_method_call error", method);
+ return ISU_RES_ERR_INTERNAL;
+ }
+
+ g_dbus_message_set_body(msg, g_variant_new("(s)", parameter));
+
+ GDBusMessage *reply = g_dbus_connection_send_message_with_reply_sync(
+ conn,
+ msg,
+ G_DBUS_SEND_MESSAGE_FLAGS_NONE,
+ -1,
+ NULL,
+ NULL,
+ &error);
+ if (reply == NULL) {
+ SLOGE("isu_%s: send message error: %s", method, error ? error->message : "" );
+ g_error_free(error);
+ }
+
+ GDBusMessageType msg_type = g_dbus_message_get_message_type(reply);
+ if (msg_type == G_DBUS_MESSAGE_TYPE_ERROR) {
+ SLOGE("isu_%s: No permission to call this method", method);
+ res = ISU_RES_ERR_NOT_PERMITTED;
+ goto exit;
+ }
+
+ GVariant *body = g_dbus_message_get_body(reply);
+ if (body == NULL) {
+ SLOGE("isu_%s: get message body error", method);
+ return ISU_RES_ERR_INTERNAL;
+ }
+ g_variant_get(body, "(i)", &res);
+exit:
+ g_object_unref(msg);
+ g_object_unref(reply);
+ g_object_unref(conn);
+ return res;
+}
+
+static void fill_system_services(char *line, struct _isu_pkg_info *pkg_info)
+{
+ char *saveptr;
+ char *token = strtok_r(line, " ", &saveptr);
+ while (token != NULL) {
+ pkg_info->service_files_len++;
+ pkg_info->service_files = realloc(pkg_info->service_files,
+ pkg_info->service_files_len*sizeof(char*));
+ pkg_info->service_files[pkg_info->service_files_len-1] = strdup(token);
+ token = strtok_r(NULL, " ", &saveptr);
+ }
+}
+
+static int copy_data(struct archive *ar, struct archive *aw)
+{
+ assert(ar);
+ assert(aw);
+
+ int res;
+ const void *buff;
+ size_t size;
+ la_int64_t offset;
+
+ for (;;) {
+ res = archive_read_data_block(ar, &buff, &size, &offset);
+ if (res == ARCHIVE_EOF)
+ return ARCHIVE_OK;
+ if (res == ARCHIVE_RETRY)
+ continue;
+ if (res < ARCHIVE_OK) {
+ SLOGE("Archive read data block error (%d): %s\n", res, archive_error_string(ar));
+ return res;
+ }
+ res = archive_write_data_block(aw, buff, size, offset);
+ if (res < ARCHIVE_OK) {
+ SLOGE("Archive write data block error (%d): %s\n", res, archive_error_string(aw));
+ return res;
+ }
+ }
+}
+
+static int extract(const char *arch_path, const char *dest_path, const char *file_name)
+{
+ assert(arch_path);
+ assert(dest_path);
+
+ struct archive *a;
+ struct archive *ext;
+ struct archive_entry *entry;
+ int res;
+ int result = ISU_RES_ERR_INVALID_FORMAT;
+ int flags = ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS | ARCHIVE_EXTRACT_XATTR;
+
+ a = archive_read_new();
+ archive_read_support_format_all(a);
+ archive_read_support_filter_all(a);
+ ext = archive_write_disk_new();
+ archive_write_disk_set_options(ext, flags);
+ archive_write_disk_set_standard_lookup(ext);
+
+ res = archive_read_open_filename(a, arch_path, BLOCK_SIZE);
+ if (res != 0) {
+ SLOGE("Open archive %s error (%d): %s\n", arch_path, res, archive_error_string(a));
+ goto exit;
+ }
+ for (;;) {
+ res = archive_read_next_header(a, &entry);
+ if (res == ARCHIVE_EOF) {
+ result = ISU_RES_OK;
+ break;
+ }
+ if (res < ARCHIVE_OK) {
+ SLOGE("Archive read next header error (%d): %s\n", res, archive_error_string(a));
+ break;
+ }
+
+ const char *entry_path = archive_entry_pathname(entry);
+ if (entry_path == NULL) {
+ SLOGE("Entry path is empty");
+ continue;
+ }
+ if (file_name != NULL) {
+ // In case we are extracting a particular file (file_name != NULL),
+ // skip if the current one is not the one we are looking for
+ if (strcmp(entry_path, file_name) != 0) {
+ continue;
+ }
+ }
+
+ char new_entry_path[PATH_MAX];
+ snprintf(new_entry_path, sizeof(new_entry_path), "%s/%s", dest_path, entry_path);
+ archive_entry_set_pathname(entry, new_entry_path);
+
+ res = archive_write_header(ext, entry);
+ if (res < ARCHIVE_OK) {
+ SLOGE("write error: %s\n", archive_error_string(ext));
+ result = ISU_RES_ERR_INTERNAL;
+ break;
+ } else if (archive_entry_size(entry) > 0) {
+ res = copy_data(a, ext);
+ if (res < ARCHIVE_OK) {
+ SLOGE("copy data error: %s\n", archive_error_string(ext));
+ result = ISU_RES_ERR_INTERNAL;
+ break;
+ }
+ }
+
+ res = archive_write_finish_entry(ext);
+ if (res < ARCHIVE_OK) {
+ SLOGE("write finish error: %s\n", archive_error_string(ext));
+ result = ISU_RES_ERR_INTERNAL;
+ break;
+ }
+ }
+
+exit:
+ archive_read_close(a);
+ archive_read_free(a);
+ archive_write_close(ext);
+ archive_write_free(ext);
+
+ return result;
+}
+
+static bool path_exists(const char *path)
+{
+ assert(path);
+
+ struct stat st;
+ return stat(path, &st) == 0;
+}
+
+static int rm_cb(const char *path, const struct stat *sb, int type_flag, struct FTW *ftw)
+{
+ assert(path);
+ assert(sb);
+ assert(ftw);
+
+ int res = remove(path);
+ if (res != 0) {
+ SLOGE("Remove %s error (%d): %m\n", path, errno);
+ }
+ return res;
+}
+
+static int remove_dir(const char *path)
+{
+ assert(path);
+ return nftw(path, rm_cb, FTW_F | FTW_D, FTW_DEPTH | FTW_PHYS);
+}
+
+static struct _isu_pkg_info* isu_pkg_info_prepare()
+{
+ struct _isu_pkg_info *pkg_info = malloc(sizeof(struct _isu_pkg_info));
+ if (pkg_info == NULL) {
+ SLOGE("Memory allocation error (%d): %m\n", errno);
+ return NULL;
+ }
+ pkg_info->name = NULL;
+ pkg_info->version = NULL;
+ pkg_info->service_files_len = 0;
+ pkg_info->service_files = NULL;
+
+ return pkg_info;
+}
+
+struct _isu_pkg_info* get_pkg_info(const char *isu_cfg_path)
+{
+ assert(isu_cfg_path);
+
+ struct _isu_pkg_info *pkg_info = isu_pkg_info_prepare();
+ if (pkg_info == NULL) {
+ return NULL;
+ }
+
+ FILE *f = fopen(isu_cfg_path, "r");
+ if (f == NULL) {
+ SLOGE("File %s open error (%d): %m\n", isu_cfg_path, errno);
+ free(pkg_info);
+ pkg_info = NULL;
+ return NULL;
+ }
+
+ char *lineptr = NULL;
+ size_t n = 0;
+ while (getline(&lineptr, &n, f) > 0) {
+ char *line = lineptr;
+ while (line && line[0] == ' ') { line++; };
+ if (line == NULL) continue;
+ if (line[0] == '#') continue;
+
+ char *saveptr;
+ char *token = strtok_r(line, "=", &saveptr);
+ if (!token) continue;
+ char *value = strtok_r(NULL, "=", &saveptr);
+ if (!value) continue;
+ while (value[0] == ' ') { value++; };
+
+ size_t val_len = strlen(value);
+
+ if (val_len > 0 && value[val_len-1] == '\n') {
+ value[val_len-1] = '\0';
+ }
+
+ if (strncmp(token, CFG_NAME, strlen(CFG_NAME)) == 0) {
+ pkg_info->name = strdup(value);
+ } else if (strncmp(token, CFG_VERSION, strlen(CFG_VERSION)) == 0) {
+ pkg_info->version = strdup(value);
+ } else if (strncmp(token, CFG_SYSTEM_SERVICE, strlen(CFG_SYSTEM_SERVICE)) == 0) {
+ fill_system_services(value, pkg_info);
+ }
+
+ }
+ if (!pkg_info->name || !pkg_info->version) {
+ SLOGE("Cannot read content of isu.cfg");
+ isu_pkg_info_free(pkg_info);
+ pkg_info = NULL;
+ }
+
+ free(lineptr);
+ if (f != NULL) {
+ fclose(f);
+ }
+ return pkg_info;
+}
+
+isu_result isu_install_internal(const char *path)
+{
+ RET_IF_FEATURE_NOT_SUPPORTED(ISU_RES_ERR_FEATURE);
+ assert(path);
+
+ int result = ISU_RES_ERR_INVALID_FORMAT;
+ struct _isu_pkg_info *pkg_info = NULL;
+
+ char temp_path[] = "/tmp/isu-XXXXXX";
+ if (mkdtemp(temp_path) == NULL) {
+ result = ISU_RES_ERR_INTERNAL;
+ goto exit;
+ }
+
+ if (extract(path, temp_path, ISU_CFG) != ISU_RES_OK) {
+ goto exit;
+ }
+
+ char buff[PATH_MAX];
+ snprintf(buff, sizeof(buff), "%s/%s", temp_path, ISU_CFG);
+ pkg_info = get_pkg_info(buff);
+ if (pkg_info == NULL) {
+ SLOGE("File %s is not an ISU Package\n", path);
+ goto exit;
+ }
+ if (pkg_info->name == NULL) {
+ SLOGE("Package %s does not contain the name\n", path);
+ goto exit;
+ }
+
+ snprintf(buff, sizeof(buff), "%s/%s", ISU_PKG_PATH, pkg_info->name);
+ if (path_exists(buff) && remove_dir(buff) != 0) {
+ goto exit;
+ }
+
+ if (extract(path, buff, NULL) != ISU_RES_OK) {
+ goto exit;
+ }
+
+ result = ISU_RES_OK;
+
+exit:
+ if (result == ISU_RES_OK) {
+ SLOGI("ISU Package (%s %s) install success\n", pkg_info->name, pkg_info->version);
+ } else {
+ SLOGE("ISU Package (%s) install error: %d\n", path, result);
+ }
+
+ if (path_exists(temp_path)) {
+ remove_dir(temp_path);
+ }
+
+ isu_pkg_info_free(pkg_info);
+ return result;
+}
+
+isu_result isu_uninstall_internal(const char *name)
+{
+ RET_IF_FEATURE_NOT_SUPPORTED(ISU_RES_ERR_FEATURE);
+ assert(name);
+
+ char path[PATH_MAX];
+ snprintf(path, sizeof(path), "%s/%s", ISU_PKG_PATH, name);
+ int result = remove_dir(path);
+ if (result == 0) {
+ SLOGI("ISU Package (%s) uninstall success\n", name);
+ result = ISU_RES_OK;
+ } else {
+ SLOGE("ISU Package (%s) uninstall error: %d\n", name, result);
+ result = ISU_RES_ERR_NOT_EXIST;
+ }
+ return result;
+}
+
+void cleanup_libmnt_table(struct libmnt_table **table)
+{
+ mnt_unref_table(*table);
+}
+
+void cleanup_char_ptr(char **ptr)
+{
+ free(*ptr);
+}
+
+isu_file_res is_isu_file(const char *path, pid_t pid, char **source_path)
+{
+ if (!is_isu_feature_supported())
+ return ISU_FILE_RES_NOT_SUPPORTED;
+
+ assert(path);
+ assert(source_path);
+ *source_path = NULL;
+
+ char mountinfo_path[PATH_MAX];
+ snprintf(mountinfo_path, sizeof(mountinfo_path), "/proc/%d/mountinfo", pid);
+
+ __attribute__ ((__cleanup__(cleanup_libmnt_table))) struct libmnt_table *table = mnt_new_table_from_file(mountinfo_path);
+ if (table == NULL) {
+ SLOGW("Invalid mountinfo path (%s)\n", mountinfo_path);
+ return ISU_FILE_RES_ERROR;
+ }
+
+ __attribute__ ((__cleanup__(cleanup_char_ptr))) char *fname_orig = mnt_resolve_path(path, NULL);
+
+ /*
+ * Check if the fname_orig file or it subpath is bindmounted
+ */
+ struct libmnt_fs *fs = NULL;
+ do {
+ if ((fs = mnt_table_find_target(table, fname_orig, MNT_ITER_FORWARD)) != NULL) {
+ /*
+ * Yes, fname_orig is mounted
+ */
+ break;
+ }
+ char *slash = strrchr(fname_orig, '/');
+ if (slash == NULL)
+ break;
+ /*
+ * Cut off the last part of the path
+ */
+ *slash = '\0';
+ } while (true);
+
+ if (fs == NULL) {
+ goto normal_file;
+ }
+
+ const char *source = mnt_fs_get_source(fs);
+ if (source == NULL) {
+ SLOGE("mnt_fs_get_source error\n");
+ return ISU_FILE_RES_ERROR;
+ }
+ /*
+ * Check if the fname_orig path is mounted from the loop device
+ */
+ if (strstr(source, "/dev/loop") == NULL) {
+ goto normal_file;
+ }
+
+ /*
+ * If so, get the path in the mounted image
+ */
+ char *in_image = strdup(mnt_fs_get_root(fs));
+ if (in_image == NULL) {
+ SLOGE("Memory allocation error");
+ return ISU_FILE_RES_ERROR;
+ }
+
+ fs = mnt_table_find_source(table, mnt_fs_get_source(fs), MNT_ITER_FORWARD);
+ if (fs == NULL) {
+ SLOGE("mnt_table_find_source error");
+ return ISU_FILE_RES_ERROR;
+ }
+
+ /*
+ * Get the full path of the file outside the sandbox
+ */
+ const char *img_mount_dir = mnt_fs_get_target(fs);
+ if (asprintf(source_path, "%s/%s", img_mount_dir, in_image) == -1) {
+ SLOGE("Memory allocation error");
+ return ISU_FILE_RES_ERROR;
+ }
+
+ return ISU_FILE_RES_IS_ISU_FILE;
+
+normal_file:
+ return ISU_FILE_RES_IS_NORMAL_FILE;
+}
+
--- /dev/null
+/*
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ *
+ * 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.
+ */
+#pragma once
+#include <stdbool.h>
+#include "libisu.h"
+
+#define ISUSERVICE_BUS_NAME "org.tizen.system.isu"
+#define ISUSERVICE_OBJECT_PATH "/Org/Tizen/System/Isu"
+#define ISUSERVICE_INTERFACE_NAME ISUSERVICE_BUS_NAME
+#define ISU_FEATURE "http://tizen.org/feature/isu"
+#define ISU_PKG_PATH "/opt/isu/"
+#define ISU_RUN_PATH "/run/isu/"
+#define ISU_CFG "isu.cfg"
+
+#ifdef LOG_TAG
+ #undef LOG_TAG
+#endif
+#define LOG_TAG "LIBISU"
+
+typedef enum {
+ ISU_FILE_RES_NOT_SUPPORTED = -2,
+ ISU_FILE_RES_ERROR = -1,
+ ISU_FILE_RES_IS_NORMAL_FILE = 0,
+ ISU_FILE_RES_IS_ISU_FILE = 1,
+} isu_file_res;
+
+struct _isu_pkg_list {
+ struct _isu_pkg_list_element *head, *cur;
+};
+
+bool is_isu_feature_supported();
+#define RET_IF_FEATURE_NOT_SUPPORTED(value) \
+ { \
+ if (!is_isu_feature_supported()) { \
+ SLOGE("ISU feature is not supported"); \
+ return value; \
+ } \
+ } while(0) \
+
+
+struct _isu_pkg_list_element {
+ char *name;
+ struct _isu_pkg_list_element *next;
+};
+
+struct _isu_pkg_info {
+ char *name;
+ char *version;
+ size_t service_files_len;
+ char **service_files;
+};
+
+isu_result isu_install_internal(const char *path);
+isu_result isu_uninstall_internal(const char *name);
+isu_file_res is_isu_file(const char *path, pid_t pid, char **source_path);
+struct _isu_pkg_info* get_pkg_info(const char *isu_cfg_path);
--- /dev/null
+/*
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ *
+ * 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 <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <linux/limits.h>
+#include <limits.h>
+#include <ftw.h>
+#include <errno.h>
+#include <dirent.h>
+#include <assert.h>
+
+#include <dlog/dlog.h>
+
+#include "libisu-internal.h"
+
+
+static bool is_valid_isu_pkg(const char *path)
+{
+ assert(path);
+
+ struct stat statbuff;
+ char pathbuff[PATH_MAX+8];
+ snprintf(pathbuff, sizeof(pathbuff), "%s/%s", path, ISU_CFG);
+
+ return stat(pathbuff, &statbuff) == 0;
+}
+
+/*
+ * Public API Functions
+ */
+
+isu_pkg_list isu_list_init()
+{
+ RET_IF_FEATURE_NOT_SUPPORTED(NULL);
+ struct _isu_pkg_list *pkg_list = malloc(sizeof(struct _isu_pkg_list));
+ if (pkg_list == NULL) {
+ SLOGE("Memory allocation error (%d): %m\n", errno);
+ return NULL;
+ }
+
+ pkg_list->head = NULL;
+ pkg_list->cur = NULL;
+ DIR *dir = opendir(ISU_PKG_PATH);
+ if (dir == NULL) {
+ SLOGE("Open dir %s error (%d): %m\n", ISU_PKG_PATH, errno);
+ return NULL;
+ }
+
+ struct _isu_pkg_list_element *prev = NULL;
+ struct dirent *entry;
+ while ((entry = readdir(dir)) != NULL) {
+ if (entry->d_type != DT_DIR) continue;
+
+ char pathbuff[PATH_MAX];
+ snprintf(pathbuff, sizeof(pathbuff), "%s/%s", ISU_PKG_PATH, entry->d_name);
+ if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
+ continue;
+
+ if (is_valid_isu_pkg(pathbuff)) {
+ struct _isu_pkg_list_element *element = malloc(sizeof(struct _isu_pkg_list_element));
+ if (element == NULL) {
+ SLOGE("Memory allocation error (%d): %m\n", errno);
+ isu_list_free(pkg_list);
+ closedir(dir);
+ return NULL;
+ }
+ element->name = strdup(entry->d_name);
+ element->next = NULL;
+ if (prev == NULL) {
+ pkg_list->head = element;
+ pkg_list->cur = element;
+ } else {
+ prev->next = element;
+ }
+ prev = element;
+ } else {
+ SLOGW("Invalid ISU package directory: %s\n", pathbuff);
+ }
+ }
+
+ closedir(dir);
+
+ return pkg_list;
+}
+
+isu_result isu_list_free(isu_pkg_list pkg_list)
+{
+ RET_IF_FEATURE_NOT_SUPPORTED(ISU_RES_ERR_FEATURE);
+ if (pkg_list == NULL)
+ return ISU_RES_OK;
+
+ struct _isu_pkg_list *pkg_list_i = (struct _isu_pkg_list*)pkg_list;
+
+ struct _isu_pkg_list_element *element = pkg_list_i->head;
+ while (element != NULL) {
+ struct _isu_pkg_list_element *tmp = element->next;
+ free(element->name);
+ free(element);
+ element = tmp;
+ }
+
+ free(pkg_list);
+ return ISU_RES_OK;
+}
+
+isu_pkg_info isu_list_next(isu_pkg_list pkg_list)
+{
+ RET_IF_FEATURE_NOT_SUPPORTED(NULL);
+ struct _isu_pkg_list *pkg_list_i = (struct _isu_pkg_list *)pkg_list;
+
+ if (pkg_list == NULL) {
+ SLOGE("Argument is NULL");
+ return NULL;
+ }
+
+ if (pkg_list_i->cur == NULL)
+ return NULL;
+
+ char isu_cfg_path[PATH_MAX];
+ snprintf(isu_cfg_path, sizeof(isu_cfg_path), "%s/%s/%s", ISU_PKG_PATH, pkg_list_i->cur->name, ISU_CFG);
+ isu_pkg_info pkg_info = get_pkg_info(isu_cfg_path);
+
+ pkg_list_i->cur = pkg_list_i->cur->next;
+ return pkg_info;
+}
+
+isu_result isu_pkg_get_name(isu_pkg_info pkg_info, char *name, size_t len)
+{
+ RET_IF_FEATURE_NOT_SUPPORTED(ISU_RES_ERR_FEATURE);
+
+ if (pkg_info == NULL || name == NULL)
+ return ISU_RES_ERR_ARGUMENT;
+
+ struct _isu_pkg_info* pkg_info_i = (struct _isu_pkg_info*)pkg_info;
+
+ if (len < strlen(pkg_info_i->name)) {
+ return ISU_RES_ERR_BUFF_TOO_SMALL;
+ }
+
+ if (snprintf(name, len, "%s", pkg_info_i->name) < strlen(pkg_info_i->name)) {
+ return ISU_RES_ERR_INTERNAL;
+ }
+ return ISU_RES_OK;
+}
+
+isu_result isu_pkg_get_version(isu_pkg_info pkg_info, char *version, size_t len)
+{
+ RET_IF_FEATURE_NOT_SUPPORTED(ISU_RES_ERR_FEATURE);
+
+ if (pkg_info == NULL || version == NULL)
+ return ISU_RES_ERR_ARGUMENT;
+
+ struct _isu_pkg_info* pkg_info_i = (struct _isu_pkg_info*)pkg_info;
+
+ if (len < strlen(pkg_info_i->version)) {
+ return ISU_RES_ERR_BUFF_TOO_SMALL;
+ }
+
+ if (snprintf(version, len, "%s", pkg_info_i->version) < strlen(pkg_info_i->version)) {
+ return ISU_RES_ERR_INTERNAL;
+ }
+ return ISU_RES_OK;
+}
+
+isu_result isu_pkg_info_free(isu_pkg_info pkg_info)
+{
+ RET_IF_FEATURE_NOT_SUPPORTED(ISU_RES_ERR_FEATURE);
+ if (pkg_info == NULL) {
+ goto exit;
+ }
+
+ if (pkg_info == NULL)
+ return ISU_RES_ERR_ARGUMENT;
+
+ struct _isu_pkg_info* pkg_info_i = (struct _isu_pkg_info*)pkg_info;
+
+ if (pkg_info_i != NULL) {
+ for (size_t i = 0; i < pkg_info_i->service_files_len; i++) {
+ free(pkg_info_i->service_files[i]);
+ }
+ free(pkg_info_i->service_files);
+ }
+
+ free(pkg_info_i->name);
+ free(pkg_info_i->version);
+ free(pkg_info_i);
+exit:
+ return ISU_RES_OK;
+}
+
+isu_result isu_install(const char *path)
+{
+ RET_IF_FEATURE_NOT_SUPPORTED(ISU_RES_ERR_FEATURE);
+
+ if (path == NULL)
+ return ISU_RES_ERR_ARGUMENT;
+
+ return isu_dbus_call("install", path);
+}
+
+isu_result isu_uninstall(const char *path)
+{
+ RET_IF_FEATURE_NOT_SUPPORTED(ISU_RES_ERR_FEATURE);
+
+ if (path == NULL)
+ return ISU_RES_ERR_ARGUMENT;
+
+ return isu_dbus_call("uninstall", path);
+}
--- /dev/null
+/*
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ *
+ * 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.
+ */
+
+#pragma once
+#include <sys/types.h>
+
+/**
+ * @addtogroup CAPI_ISU_MODULE
+ * @{
+ */
+
+/**
+ * @brief ISU package list context
+ */
+typedef void* isu_pkg_list;
+
+/**
+ * @brief ISU package info context
+ */
+typedef void* isu_pkg_info;
+
+/**
+ * @brief Result
+ */
+typedef enum {
+ ISU_RES_OK, /**< Successful */
+ ISU_RES_ERR_ARGUMENT, /**< Invalid arguments provided */
+ ISU_RES_ERR_NAME, /**< Invalid name */
+ ISU_RES_ERR_PATH, /**< Invalid path */
+ ISU_RES_ERR_NOT_EXIST, /**< Package not exist */
+ ISU_RES_ERR_NOT_PERMITTED, /**< No permission to perform the operation */
+ ISU_RES_ERR_INVALID_FORMAT, /**< Invalid or corrupted ISU archive */
+ ISU_RES_ERR_CHECKSUM, /**< Checksum does not match */
+ ISU_RES_ERR_FEATURE, /**< Feature not supported */
+ ISU_RES_ERR_INTERNAL, /**< Internal error */
+ ISU_RES_ERR_BUFF_TOO_SMALL /**< Provided buffer is too small to store th full content */
+} isu_result;
+
+/**
+ * @platform
+ * @brief Install ISU package
+ * @since_tizen 9.0
+ * @privlevel platform
+ * @privilege http://tizen.org/privilege/isu.admin
+ *
+ *
+ * @param[in] path - Path to the zip archive
+ *
+ * @return #isu_result
+ * @retval #ISU_RES_OK Success
+ * @retval #ISU_RES_ERR_ARGUMENT Invalid argument provided
+ * @retval #ISU_RES_ERR_FEATURE ISU Feature not supported
+ * @retval #ISU_RES_ERR_NOT_PERMITTED No permission to perform the operation
+ * @retval #ISU_RES_ERR_PATH Provided file not exist
+ * @retval #ISU_RES_ERR_INVALID_FORMAT Incorrect or corrupted ISU package
+ * @retval #ISU_RES_ERR_CHECKSUM Incorrect or corrupted ISU package
+ * @retval #ISU_RES_ERR_INTERNAL Internal error
+ */
+isu_result isu_install(const char *path);
+
+/**
+ * @brief Init ISU package list
+ *
+ * @return #isu_pkg_list on success, otherwise NULL
+ *
+ * @code
+ *
+ * void print_isu_pkgs(void) {
+ * isu_pkg_list pkg_list = isu_list_init();
+ * if (pkg_list == NULL) {
+ * printf("isu_list_init error\n");
+ * return;
+ * }
+ *
+ * isu_pkg_info pkg_info = NULL;
+ *
+ * while ((pkg_info = isu_list_next(isu_pkg_list)) != NULL) {
+ * char name[256];
+ * isu_result = isu_pkg_get_name(pkg_info, name, sizeof(name));
+ * isu_pkg_info_free(isu_result);
+ *
+ * if (isu_result != ISU_RES_OK) {
+ * printf("isu_pkg_get_name() error\n");
+ * return;
+ * }
+ * printf("ISU package name: %s\n", name);
+ * }
+ *
+ * isu_list_free(pkg_list);
+ * }
+ *
+ * @endcode
+ */
+isu_pkg_list isu_list_init();
+
+/**
+ * @brief Get the next ISU package
+ * @since_tizen 9.0
+ *
+ * @param[in] pkg_list ISU list returned by #isu_list_init
+ *
+ * This function allocates new isu_pkg_list, which should be freed with #isu_list_free
+ *
+ * @return #isu_pkg_info on success, otherwise NULL
+ * @retval #isu_pkg_info
+ */
+isu_pkg_info isu_list_next(isu_pkg_list pkg_list);
+
+/**
+ * @brief Get ISU package name
+ * @since_tizen 9.0
+ *
+ * @param[in] pkg_info ISU package info
+ * @param[out] name buffer for the ISU package name
+ * @param[in] len The name buffer length
+ *
+ * @return #isu_result
+ * @retval #ISU_RES_OK Success
+ * @retval #ISU_RES_ERR_ARGUMENT Invalid argument provided
+ * @retval #ISU_RES_ERR_BUFF_TOO_SMALL Buffer is to small to store the full content
+ * @retval #ISU_RES_ERR_INTERNAL Internal error
+ */
+isu_result isu_pkg_get_name(isu_pkg_info pkg_info, char *name, size_t len);
+
+/**
+ * @brief Get ISU version
+ * @since_tizen 9.0
+ *
+ * @param[in] pkg_info ISU package info
+ * @param[out] version buffer for the ISU package version
+ * @param[in] len The name buffer length
+ *
+ * @return #isu_result
+ * @retval #ISU_RES_OK Success
+ * @retval #ISU_RES_ERR_ARGUMENT Invalid argument provided
+ * @retval #ISU_RES_ERR_INTERNAL Internal error
+ * @retval #ISU_RES_ERR_BUFF_TOO_SMALL Buffer is to small to store the full content
+ * @retval #ISU_RES_ERR_FEATURE ISU Feature not supported
+ */
+isu_result isu_pkg_get_version(isu_pkg_info pkg_info, char *version, size_t len);
+
+/**
+ * @brief Free the ISU package info context
+ * @since_tizen 9.0
+ *
+ * @param[in] pkg_info ISU package info context
+ *
+ * @return #isu_result
+ * @retval #ISU_RES_OK Success
+ * @retval #ISU_RES_ERR_ARGUMENT Invalid argument provided
+ * @retval #ISU_RES_ERR_FEATURE ISU Feature not supported
+ */
+isu_result isu_pkg_info_free(isu_pkg_info pkg_info);
+
+/**
+ * @brief Free the ISU packages list context
+ * @since_tizen 9.0
+ *
+ * @param[in] pkg_list ISU packages list info context
+ *
+ * @return #isu_result
+ * @retval #ISU_RES_OK Success
+ */
+isu_result isu_list_free(isu_pkg_list pkg_list);
+
+/**
+ * @platform
+ * @brief Uninstall ISU package
+ * @since_tizen 9.0
+ * @privlevel platform
+ * @privilege http://tizen.org/privilege/isu.admin
+ *
+ * @param[in] pkg_name ISU package name
+ *
+ * @return #isu_result
+ * @retval #ISU_RES_OK Success
+ * @retval #ISU_RES_ERR_ARGUMENT Invalid argument provided
+ * @retval #ISU_RES_ERR_NOT_EXIST ISU package not installed
+ * @retval #ISU_RES_ERR_FEATURE ISU Feature not supported
+ * @retval #ISU_RES_ERR_NOT_PERMITTED No permission to perform the operation
+ */
+isu_result isu_uninstall(const char *pkg_name);
+
+/**
+ * @}
+ */
--- /dev/null
+# Package Information for pkg-config
+
+prefix=@PC_PREFIX@
+exec_prefix=/usr
+libdir=@PC_LIBDIR@
+includedir=@PC_INCLUDE@
+
+Name: @PC_NAME@
+Description: @PC_DESCRIPTION@
+Version: @PC_VERSION@
+Requires: @PC_REQUIRED@
+Libs: -L${libdir} @PC_LDFLAGS@
+Cflags: -I${includedir}
+