{"action":"created","comment":{"url":"https://api.github.com/repos/cfengine/core/pulls/comments/30955672","id":30955672,"diff_hunk":"@@ -0,0 +1,1611 @@\n+/*\n+   Copyright (C) CFEngine AS\n+\n+   This file is part of CFEngine 3 - written and maintained by CFEngine AS.\n+\n+   This program is free software; you can redistribute it and/or modify it\n+   under the terms of the GNU General Public License as published by the\n+   Free Software Foundation; version 3.\n+\n+   This program is distributed in the hope that it will be useful,\n+   but WITHOUT ANY WARRANTY; without even the implied warranty of\n+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+   GNU General Public License for more details.\n+\n+  You should have received a copy of the GNU General Public License\n+  along with this program; if not, write to the Free Software\n+  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA\n+\n+  To the extent this program is licensed as part of the Enterprise\n+  versions of CFEngine, the applicable Commercial Open Source License\n+  (COSL) may apply to this file if you as a licensee so wish it. See\n+  included file COSL.txt.\n+*/\n+\n+#include <package_module.h>\n+#include <pipes.h>\n+#include <signals.h>\n+#include <buffer.h>\n+#include <string_lib.h>\n+#include <actuator.h>\n+#include <file_lib.h>\n+#include <known_dirs.h>\n+#include <locks.h>\n+#include <rlist.h>\n+#include <policy.h>\n+#include <eval_context.h>\n+\n+#define INVENTORY_LIST_BUFFER_SIZE 100 * 80 /* 100 entries with 80 characters \n+                                             * per line */\n+\n+static bool UpdateSinglePackageModuleCache(EvalContext *ctx,\n+                                    const PackageModuleWrapper *module_wrapper,\n+                                    UpdateType type, bool force_update);\n+static char *GetPackageModuleRealPath(const char *package_manager_name);\n+static int NegotiateSupportedAPIVersion(PackageModuleWrapper *wrapper);\n+\n+\n+void DeletePackageModuleWrapper(PackageModuleWrapper *wrapper)\n+{\n+    free(wrapper->path);\n+    free(wrapper->name);\n+    free(wrapper);\n+}\n+\n+PackageModuleWrapper *NewPackageModuleWrapper(PackageModuleBody *package_module)\n+{\n+    assert(package_module && package_module->name);\n+\n+    PackageModuleWrapper *wrapper = xmalloc(sizeof(PackageModuleWrapper));\n+    \n+    wrapper->path = GetPackageModuleRealPath(package_module->name);\n+    wrapper->name = SafeStringDuplicate(package_module->name);\n+    wrapper->package_module = package_module;\n+    \n+    /* Check if file exists */\n+    struct stat sb;\n+    if (!wrapper->path || (stat(wrapper->path, &sb) != 0))\n+    {\n+        Log(LOG_LEVEL_VERBOSE,\n+            \"can not find package wrapper in provided location: %s\",\n+            wrapper->path);\n+        DeletePackageModuleWrapper(wrapper);\n+        return NULL;\n+    }\n+    \n+    /* Negotiate API version */\n+    wrapper->supported_api_version = NegotiateSupportedAPIVersion(wrapper);\n+    if (wrapper->supported_api_version != 1)\n+    {\n+        Log(LOG_LEVEL_VERBOSE,\n+            \"unsupported package module wrapper API version: %d\",\n+            wrapper->supported_api_version);\n+        DeletePackageModuleWrapper(wrapper);\n+        return NULL;\n+    }\n+    return wrapper;\n+}\n+\n+\n+void UpdatePackagesCache(EvalContext *ctx, bool force_update)\n+{\n+    Log(LOG_LEVEL_DEBUG, \"Updating package cache.\");\n+    \n+    PackagePromiseGlobalLock package_lock = \n+            AcquireGlobalPackagePromiseLock(ctx);\n+                        \n+    if (package_lock.g_lock.lock == NULL)\n+    {\n+        Log(LOG_LEVEL_INFO, \"Can not acquire global lock for package promise. \"\n+            \"Skipping updating cache.\");\n+        return;\n+    }\n+                        \n+    Rlist *default_inventory = GetDefaultInventoryFromContext(ctx);\n+    \n+    for (const Rlist *rp = default_inventory; rp != NULL; rp = rp->next)\n+    {\n+        const char *pm_name =  RlistScalarValue(rp);\n+        \n+        /* We don't want inventory to be reported. */\n+        if (StringSafeEqual(pm_name, \"cf_null\"))\n+        {\n+            break;\n+        }\n+        \n+        PackageModuleBody *module = GetPackageModuleFromContext(ctx, pm_name);\n+        if (!module)\n+        {\n+            Log(LOG_LEVEL_ERR,\n+                \"Can not find body for package module: %s\", pm_name);\n+            continue;\n+        }\n+\n+        PackageModuleWrapper *module_wrapper = NewPackageModuleWrapper(module);\n+\n+        if (!module_wrapper)\n+        {\n+            Log(LOG_LEVEL_ERR,\n+                \"Can not set up wrapper for module: %s\", pm_name);\n+            continue;\n+        }\n+\n+        UpdateSinglePackageModuleCache(ctx, module_wrapper,\n+                                       UPDATE_TYPE_INSTALLED, force_update);\n+        UpdateSinglePackageModuleCache(ctx, module_wrapper,\n+                                       force_update ? UPDATE_TYPE_UPDATES : \n+                                           UPDATE_TYPE_LOCAL_UPDATES,\n+                                       force_update);\n+\n+        DeletePackageModuleWrapper(module_wrapper);\n+    }\n+    YieldGlobalPackagePromiseLock(package_lock);\n+}\n+\n+PackagePromiseGlobalLock AcquireGlobalPackagePromiseLock(EvalContext *ctx)\n+{\n+    Bundle bundle = {.name = \"package_global\"};\n+    PromiseType promise_type = {.name = \"package_global\",\n+                               .parent_bundle = &bundle};\n+    Promise pp = {.promiser = \"package_global\",\n+                  .parent_promise_type = &promise_type};\n+    \n+    CfLock package_promise_global_lock;\n+    \n+    package_promise_global_lock =\n+            AcquireLock(ctx, GLOBAL_PACKAGE_PROMISE_LOCK_NAME, VUQNAME, CFSTARTTIME,\n+                        (TransactionContext) {.ifelapsed = VIFELAPSED,\n+                                              .expireafter = VEXPIREAFTER},\n+                        &pp, false);\n+                        \n+    return (PackagePromiseGlobalLock) {.g_lock = package_promise_global_lock,\n+                                       .lock_ctx = ctx};\n+}\n+\n+void YieldGlobalPackagePromiseLock(PackagePromiseGlobalLock lock)\n+{\n+    Bundle bundle = {.name = \"package_global\"};\n+    PromiseType promise_type = {.name = \"package_global\",\n+                               .parent_bundle = &bundle};\n+    Promise pp = {.promiser = \"package_global\",\n+                  .parent_promise_type = &promise_type};\n+    \n+    YieldCurrentLockAndRemoveFromCache(lock.lock_ctx, lock.g_lock,\n+                                       GLOBAL_PACKAGE_PROMISE_LOCK_NAME, &pp);\n+}\n+\n+static void ParseAndLogErrorMessage(const Rlist *data)\n+{\n+    for (const Rlist *rp = data; rp != NULL; rp = rp->next)\n+    {\n+        char *line = RlistScalarValue(rp);\n+                   \n+        if (StringStartsWith(line, \"Error=\"))\n+        {\n+            Log(LOG_LEVEL_VERBOSE, \"Caught error: %s\", line + strlen(\"Error=\"));\n+        }\n+        else if (StringStartsWith(line, \"ErrorMessage=\"))\n+        {\n+            Log(LOG_LEVEL_VERBOSE, \"Caught error message: %s\", \n+                line + strlen(\"ErrorMessage=\"));\n+        }\n+        else\n+        {\n+            Log(LOG_LEVEL_VERBOSE, \"Caught unsupported error info: %s\", line);\n+        }\n+    }\n+}\n+\n+static int IsReadWriteReady(const IOData *io, int timeout_sec)\n+{\n+    fd_set  rset;\n+    FD_ZERO(&rset);\n+    FD_SET(io->read_fd, &rset);\n+\n+    struct timeval tv = {\n+        .tv_sec = timeout_sec,\n+        .tv_usec = 0,\n+    };\n+\n+    //TODO: For Windows we will need different method and select might not \n+    //      work with file descriptors.\n+    int ret = select(io->read_fd + 1, &rset, NULL, NULL, &tv);\n+\n+    if(ret < 0)\n+    {\n+        Log(LOG_LEVEL_VERBOSE, \"Failed checking for data. (select: %s)\",\n+            GetErrorStr());\n+        return -1;\n+    }\n+\n+    else if (FD_ISSET(io->read_fd, &rset))\n+    {\n+        return io->read_fd;\n+    }\n+\n+    /* We have reached timeout */\n+    if(ret == 0)\n+    {\n+        Log(LOG_LEVEL_DEBUG, \"Timeout reading from package module.\");\n+        return 0;\n+    }\n+\n+    Log(LOG_LEVEL_VERBOSE,\n+        \"Unknown outcome (ret > 0 but our only fds is not set). (select: %s)\",\n+        GetErrorStr());\n+\n+    return -1;\n+}\n+\n+static Rlist *ReadDataFromPackageScript(const IOData *io)\n+{\n+    char buff[CF_BUFSIZE] = {0};\n+    \n+    Buffer *data = BufferNew();\n+    if (!data)\n+    {\n+        Log(LOG_LEVEL_VERBOSE, \n+            \"Unable to allocate buffer for handling package module responses.\");\n+        return NULL;\n+    }\n+    \n+    int timeout_seconds_left = PACKAGE_PROMISE_SCRIPT_TIMEOUT_SEC;\n+    \n+    while(!IsPendingTermination() && timeout_seconds_left > 0)\n+    {\n+        int fd = IsReadWriteReady(io, PACKAGE_PROMISE_TERMINATION_CHECK_SEC);\n+        \n+        if (fd < 0)\n+        {\n+            Log(LOG_LEVEL_VERBOSE, \n+                \"Error reading data from package module: %s\",\n+                GetErrorStr());\n+            return NULL;\n+        }\n+        else if (fd == io->read_fd)\n+        {\n+            ssize_t res = read(fd, buff, sizeof(buff) - 1);\n+            if (res == -1)\n+            {\n+                if (errno == EINTR)\n+                {\n+                    continue;\n+                }\n+                else\n+                {\n+                    Log(LOG_LEVEL_ERR,\n+                        \"Unable to read output from package module: %s\",\n+                        GetErrorStr());\n+                    BufferDestroy(data);\n+                    return NULL;\n+                }\n+            }\n+            else if (res == 0) /* reached EOF */\n+            {\n+                break;\n+            }\n+            Log(LOG_LEVEL_DEBUG, \"Data read from package module: %zu [%s]\",\n+                res, buff);\n+\n+            BufferAppendString(data, buff);\n+            memset(buff, 0, sizeof(buff));\n+        }\n+        else if (fd == 0) /* timeout */\n+        {\n+            timeout_seconds_left -= PACKAGE_PROMISE_TERMINATION_CHECK_SEC;\n+            continue;\n+        }\n+    }\n+    \n+    char *read_string = BufferClose(data);\n+    Rlist *response_lines = RlistFromSplitString(read_string, '\\n');\n+    free(read_string);\n+    \n+    return response_lines;\n+}\n+\n+static int WriteScriptData(const char *data, IOData *io)\n+{\n+    /* If there is nothing to write close writing end of pipe. */\n+    if (data == NULL || strlen(data) == 0)\n+    {\n+        if (io->write_fd >= 0)\n+        {\n+            close(io->write_fd);\n+            io->write_fd = -1;\n+        }\n+        return 0;\n+    }\n+    \n+    ssize_t wrt = write(io->write_fd, data, strlen(data));\n+    \n+    /* Make sure to close write_fd after sending all data. */\n+    if (io->write_fd >= 0)\n+    {\n+        close(io->write_fd);\n+        io->write_fd = -1;\n+    }\n+    return wrt;\n+}\n+\n+int WriteDataToPackageModule(const char *args, const char *data,\n+                             const PackageModuleWrapper *wrapper)\n+{\n+    char *command = StringFormat(\"%s %s\", wrapper->path, args);\n+    IOData io = cf_popen_full_duplex(command, false);\n+    free(command);\n+    \n+    if (io.write_fd == 0 || io.read_fd == 0)\n+    {\n+        Log(LOG_LEVEL_VERBOSE, \"Error occurred while opening pipes for \"\n+            \"communication with package module.\");\n+        return -1;\n+    }\n+    \n+    int res = 0;\n+    if (WriteScriptData(data, &io) != strlen(data))\n+    {\n+        Log(LOG_LEVEL_VERBOSE,\n+            \"Was not able to send whole data to package module.\");\n+        res = -1;\n+    }\n+    \n+    /* If script returns non 0 status */\n+    if (cf_pclose_full_duplex(&io) != EXIT_SUCCESS)\n+    {\n+        Log(LOG_LEVEL_VERBOSE,\n+            \"Package module returned with non zero return code.\");\n+        res = -1;\n+    }\n+    return res;\n+}\n+\n+/* In some cases the response is expected to be not filled out. Some requests\n+   will have response filled only in case of errors. */\n+static int ReadWriteDataToPackageScript(const char *args, const char *request,\n+                                        Rlist **response,\n+                                        const PackageModuleWrapper *wrapper)\n+{\n+    assert(args && wrapper);\n+    \n+    char *command = StringFormat(\"%s %s\", wrapper->path, args);\n+    IOData io = cf_popen_full_duplex(command, false);\n+    free(command);\n+    \n+    if (io.write_fd == 0 || io.read_fd == 0)\n+    {\n+        Log(LOG_LEVEL_INFO, \"Some error occurred while communicating \"\n+            \"package module.\");\n+        return -1;\n+    }\n+    \n+    if (WriteScriptData(request, &io) != strlen(request))\n+    {\n+        Log(LOG_LEVEL_VERBOSE, \"Couldn't send whole data to package module.\");\n+        return -1;\n+    }\n+    \n+    /* We can have some error message here. */\n+    Rlist *res = ReadDataFromPackageScript(&io);\n+    \n+    /* If script returns non 0 status */\n+    if (cf_pclose_full_duplex(&io) != EXIT_SUCCESS)\n+    {\n+        Log(LOG_LEVEL_VERBOSE,\n+            \"Package module returned with non zero return code.\");\n+        RlistDestroy(res);\n+        return -1;\n+    }\n+    \n+    *response = res;\n+    return 0;\n+}\n+\n+static void FreePackageInfo(PackageInfo *package_info)\n+{\n+    if (package_info)\n+    {\n+        free(package_info->arch);\n+        free(package_info->name);\n+        free(package_info->version);\n+\n+        free(package_info);\n+    }\n+}\n+\n+static char *ParseOptions(Rlist *options)\n+{\n+    if (RlistIsNullList(options))\n+    {\n+        return SafeStringDuplicate(\"\");\n+    }\n+    \n+    Buffer *data = BufferNew();\n+    for (Rlist *rp = options; rp != NULL; rp = rp->next)\n+    {\n+        char *value = RlistScalarValue(rp);\n+        BufferAppendString(data, \"options=\");\n+        BufferAppendString(data, value);\n+        BufferAppendString(data, \"\\n\");\n+    }\n+    return BufferClose(data);\n+}\n+\n+static PackageInfo *ParseAndCheckPackageDataReply(const Rlist *data)\n+{\n+    PackageInfo * package_data = xcalloc(1, sizeof(PackageInfo));\n+    \n+    for (const Rlist *rp = data; rp != NULL; rp = rp->next)\n+    {\n+        char *line = RlistScalarValue(rp);\n+                   \n+        if (StringStartsWith(line, \"PackageType=\"))\n+        {\n+            char *type = line + strlen(\"PackageType=\");\n+            if (StringSafeEqual(type, \"file\"))\n+            {\n+                package_data->type = PACKAGE_TYPE_FILE;\n+            }\n+            else if (StringSafeEqual(type, \"repo\"))\n+            {\n+                package_data->type = PACKAGE_TYPE_REPO;\n+            }\n+            else\n+            {\n+                Log(LOG_LEVEL_VERBOSE, \"unsupported package type: %s\", type);\n+                free(package_data);\n+                return NULL;\n+            }\n+        }\n+        else if (StringStartsWith(line, \"Name=\"))\n+        {\n+            if (package_data->name)\n+            {\n+                /* Some error occurred as we already have name for \n+                 * given package. */\n+                Log(LOG_LEVEL_ERR, \"Duplicated package name \"\n+                    \"received: [%s] %s\", line, package_data->name);\n+                free(package_data);\n+                return NULL;\n+            }\n+            package_data->name = \n+                SafeStringDuplicate(line + strlen(\"Name=\"));\n+        }\n+        else if (StringStartsWith(line, \"Version=\"))\n+        {\n+            if (package_data->version)\n+            {\n+                /* Some error occurred as we already have version for \n+                 * given package. */\n+                Log(LOG_LEVEL_ERR, \"Duplicated package version \"\n+                    \"received: [%s] %s\", line, package_data->version);\n+                free(package_data);\n+                return NULL;\n+            }\n+            package_data->version = \n+                SafeStringDuplicate(line + strlen(\"Version=\"));\n+        }\n+        else if (StringStartsWith(line, \"Architecture=\"))\n+        {\n+            if (package_data->arch)\n+            {\n+                /* Some error occurred as we already have arch for \n+                 * given package. */\n+               Log(LOG_LEVEL_ERR, \"Duplicated package architecture \"\n+                    \"received: [%s] %s\", line, package_data->arch);\n+                free(package_data);\n+                return NULL;\n+            }\n+            package_data->arch = \n+                SafeStringDuplicate(line + strlen(\"Architecture=\"));\n+        }\n+        /* For handling errors */\n+        else if (StringStartsWith(line, \"Error=\"))\n+        {\n+            Log(LOG_LEVEL_VERBOSE, \"have error: %s\",\n+                line + strlen(\"Error=\"));\n+        }\n+        else if (StringStartsWith(line, \"ErrorMessage=\"))\n+        {\n+            Log(LOG_LEVEL_VERBOSE, \"have error message: %s\",\n+                line + strlen(\"ErrorMessage=\"));\n+        }\n+        else\n+        {\n+            Log(LOG_LEVEL_VERBOSE, \"unsupported option: %s\", line);\n+        }\n+    }\n+    \n+    return package_data;\n+}\n+\n+static int NegotiateSupportedAPIVersion(PackageModuleWrapper *wrapper)\n+{\n+    int api_version = -1;\n+\n+    Rlist *response = NULL;\n+    if (ReadWriteDataToPackageScript(\"supports-api-version\", \"\",\n+                                     &response, wrapper) != 0)\n+    {\n+        Log(LOG_LEVEL_INFO, \"Some error occurred while communicating with \"\n+            \"wrapper while getting supported API version.\");\n+        return -1;\n+    }\n+    \n+    if (response)\n+    {\n+        if (RlistLen(response) == 1)\n+        {\n+            api_version = atoi(RlistScalarValue(response));\n+            Log(LOG_LEVEL_DEBUG, \"package wrapper API version: %d\", api_version);\n+        }\n+        RlistDestroy(response);\n+    }\n+    return api_version;\n+}\n+\n+/* IMPORTANT: this might not return all the data we need like version\n+              or architecture but package name MUST be known. */\n+static\n+PackageInfo *GetPackageData(const char *name, Rlist *options,\n+                            const PackageModuleWrapper *wrapper)\n+{   \n+    char *options_str = ParseOptions(options);\n+    char *request = StringFormat(\"%sFile=%s\\n\", options_str, name);\n+    \n+    Rlist *response = NULL;\n+    if (ReadWriteDataToPackageScript(\"get-package-data\", request, &response,\n+            wrapper) != 0)\n+    {\n+        Log(LOG_LEVEL_INFO, \"Some error occurred while communicating with \"\n+            \"package module while collecting package data.\");\n+        free(options_str);\n+        free(request);\n+        return NULL;\n+    }\n+    \n+    PackageInfo *package_data = NULL;\n+    \n+    if (response)\n+    {   \n+        package_data = ParseAndCheckPackageDataReply(response);\n+        RlistDestroy(response);\n+        \n+        if (package_data)\n+        {\n+            /* At this point at least package name MUST be known (if no error) */\n+            if (!package_data->name || package_data->type == PACKAGE_TYPE_NONE)\n+            {\n+                Log(LOG_LEVEL_INFO, \"Unknown package name.\");\n+                FreePackageInfo(package_data);\n+                package_data = NULL;\n+            }\n+        }\n+    }\n+    free(options_str);\n+    free(request);\n+        \n+    return package_data;\n+}\n+\n+static char *GetPackageModuleRealPath(const char *package_module_name)\n+{\n+    \n+    return StringFormat(\"%s%c%s%c%s\", GetWorkDir(), FILE_SEPARATOR,\n+                        \"package_modules\", FILE_SEPARATOR,\n+                        package_module_name);\n+}\n+\n+static int IsPackageInCache(EvalContext *ctx,\n+                            const PackageModuleWrapper *module_wrapper,\n+                            const char *name, const char *ver, const char *arch)\n+{\n+    const char *version = ver;\n+    /* Handle latest version in specific way for repo packages. \n+     * Please note that for file packages 'latest' version is not supported\n+     * and check against that is made elsewhere. */\n+    if (version && StringSafeEqual(version, \"latest\"))\n+    {\n+        version = NULL;\n+    }\n+    \n+    /* Make sure cache is updated. */\n+    if (ctx)\n+    {\n+        if (!UpdateSinglePackageModuleCache(ctx, module_wrapper,\n+                                            UPDATE_TYPE_INSTALLED, false))\n+        {\n+            Log(LOG_LEVEL_ERR, \"Can not update cache.\");\n+        }\n+    }\n+    \n+    CF_DB *db_cached;\n+    if (!OpenSubDB(&db_cached, dbid_packages_installed,\n+                   module_wrapper->package_module->name))\n+    {\n+        Log(LOG_LEVEL_INFO, \"Can not open cache database.\");\n+        return -1;\n+    }\n+    \n+    char *key = NULL;\n+    if (version && arch)\n+    {\n+        key = StringFormat(\"N<%s>V<%s>A<%s>\", name, version, arch);\n+    }\n+    else if (version)\n+    {\n+        key = StringFormat(\"N<%s>V<%s>\", name, version);\n+    }\n+    else if (arch)\n+    {\n+        key = StringFormat(\"N<%s>A<%s>\", name, arch);\n+    }\n+    else\n+    {\n+         key = StringFormat(\"N<%s>\", name);\n+    }\n+    \n+    int is_in_cache = 0;\n+    char buff[1];\n+    \n+    Log(LOG_LEVEL_DEBUG, \"looking for key in cache: %s\", key);\n+    \n+    if (ReadDB(db_cached, key, buff, 1))\n+    {\n+        /* Just make sure DB is not corrupted. */\n+        if (buff[0] == '1')\n+        {\n+            is_in_cache = 1;\n+        }\n+        else\n+        {\n+            Log(LOG_LEVEL_INFO,\n+                \"Seem to have corrupted data in cache database\");\n+            is_in_cache = -1;\n+        }\n+    }\n+    \n+    Log(LOG_LEVEL_DEBUG,\n+        \"Looking for package %s in cache returned: %d\", name, is_in_cache);\n+    \n+    CloseDB(db_cached);\n+    \n+    return is_in_cache;\n+}\n+\n+void WritePackageDataToDB(CF_DB *db_installed,\n+        const char *name, const char *ver, const char *arch,\n+        UpdateType type)\n+{\n+    char package_key[strlen(name) + strlen(ver) +\n+                     strlen(arch) + 11];\n+    \n+    xsnprintf(package_key, sizeof(package_key),\n+              \"N<%s>\", name);\n+    if ((type == UPDATE_TYPE_UPDATES || type == UPDATE_TYPE_LOCAL_UPDATES) && \n+            HasKeyDB(db_installed, package_key, strlen(package_key) + 1))\n+    {\n+        size_t val_size =\n+                ValueSizeDB(db_installed, package_key, strlen(package_key));\n+        char buff[val_size + strlen(arch) + strlen(ver) + 8];\n+        \n+        ReadDB(db_installed, package_key, buff, val_size);\n+        xsnprintf(buff + val_size, sizeof(package_key), \"V<%s>A<%s>\\n\", ver, arch);\n+        WriteDB(db_installed, package_key, buff, strlen(buff));\n+    }\n+    else if (type == UPDATE_TYPE_UPDATES || type == UPDATE_TYPE_LOCAL_UPDATES)\n+    {\n+        char buff[strlen(arch) + strlen(ver) + 8];\n+        xsnprintf(buff, sizeof(package_key), \"V<%s>A<%s>\\n\", ver, arch);\n+        WriteDB(db_installed, package_key, buff, strlen(buff));\n+    }\n+    else /* UPDATE_TYPE_INSTALLED */\n+    {\n+        WriteDB(db_installed, package_key, \"1\", 1);\n+        xsnprintf(package_key, sizeof(package_key),\n+                \"N<%s>V<%s>\", name, ver);\n+        WriteDB(db_installed, package_key, \"1\", 1);\n+        xsnprintf(package_key, sizeof(package_key),\n+                \"N<%s>A<%s>\", name, arch);\n+        WriteDB(db_installed, package_key, \"1\", 1);\n+        xsnprintf(package_key, sizeof(package_key),\n+                \"N<%s>V<%s>A<%s>\", name, ver, arch);\n+        WriteDB(db_installed, package_key, \"1\", 1);\n+    }\n+}\n+\n+int UpdatePackagesDB(Rlist *data, const char *pm_name, UpdateType type)\n+{\n+    assert(pm_name);\n+    \n+    bool have_error = false;\n+\n+    CF_DB *db_cached;\n+    dbid db_id = type == UPDATE_TYPE_INSTALLED ? dbid_packages_installed :\n+                                                 dbid_packages_updates;\n+    if (OpenSubDB(&db_cached, db_id, pm_name))\n+    {\n+        CleanDB(db_cached);\n+        \n+        Buffer *inventory_data = BufferNewWithCapacity(INVENTORY_LIST_BUFFER_SIZE);\n+\n+        char *package_data[3] = {NULL, NULL, NULL};\n+\n+        for (const Rlist *rp = data; rp != NULL; rp = rp->next)\n+        {\n+            char *line = RlistScalarValue(rp);\n+\n+            if (StringStartsWith(line, \"Name=\"))\n+            {\n+                /* We have all the information we need from previous loop\n+                 * iteration. */\n+                if (package_data[0] && package_data[1] && package_data[2])\n+                {\n+                    WritePackageDataToDB(db_cached, package_data[0],\n+                                         package_data[1], package_data[2], type);\n+                    \n+                    char inventory_line[strlen(package_data[0]) +\n+                                        strlen(package_data[1]) +\n+                                        strlen(package_data[2]) + 4];\n+                    \n+                    xsnprintf(inventory_line, sizeof(inventory_line),\n+                              \"%s,%s,%s\\n\", package_data[0], package_data[1],\n+                              package_data[2]);\n+                    BufferAppendString(inventory_data, inventory_line);\n+\n+                    package_data[1] = NULL;\n+                    package_data[2] = NULL;\n+                }\n+                else if (package_data[0] && (!package_data[1] || !package_data[2]))\n+                {\n+                    /* some error occurred */\n+                    Log(LOG_LEVEL_VERBOSE, \"Malformed response from package \"\n+                        \"manager for package %s\", package_data[0]);\n+                    package_data[1] = NULL;\n+                    package_data[2] = NULL;\n+                }\n+\n+                /* This must be the first entry on a list */\n+                package_data[0] = line + strlen(\"Name=\");\n+\n+            }\n+            else if (StringStartsWith(line, \"Version=\"))\n+            {\n+                package_data[1] = line + strlen(\"Version=\");\n+            }\n+            else if (StringStartsWith(line, \"Architecture=\"))\n+            {\n+                package_data[2] = line + strlen(\"Architecture=\");\n+            }\n+            else if (StringStartsWith(line, \"Error=\"))\n+            {\n+                Log(LOG_LEVEL_VERBOSE, \"have error: %s\",\n+                    line + strlen(\"Error=\"));\n+                have_error = true;\n+            }\n+            else if (StringStartsWith(line, \"ErrorMessage=\"))\n+            {\n+                Log(LOG_LEVEL_VERBOSE, \"have error message: %s\",\n+                    line + strlen(\"ErrorMessage=\"));\n+                have_error = true;\n+            }\n+            else\n+            {\n+                 Log(LOG_LEVEL_INFO,\n+                     \"Unsupported response received form package module: %s\",\n+                     line);\n+                 have_error = true;\n+            }\n+        }\n+        /* We have one more entry left. */\n+        if (package_data[0] && package_data[1] && package_data[2])\n+        {\n+            WritePackageDataToDB(db_cached, package_data[0],\n+                             package_data[1], package_data[2], type);\n+            \n+            char inventory_line[strlen(package_data[0]) +\n+                                strlen(package_data[1]) +\n+                                strlen(package_data[2]) + 4];\n+\n+            xsnprintf(inventory_line, sizeof(inventory_line),\n+                      \"%s,%s,%s\\n\", package_data[0], package_data[1],\n+                      package_data[2]);\n+            BufferAppendString(inventory_data, inventory_line);\n+        }","path":"cf-agent/package_module.c","position":814,"original_position":814,"commit_id":"885974c0b4a2ee6f06a126dfec63e5aa0a8ad785","original_commit_id":"885974c0b4a2ee6f06a126dfec63e5aa0a8ad785","user":{"login":"pasinskim","id":6329055,"avatar_url":"https://avatars.githubusercontent.com/u/6329055?v=3","gravatar_id":"","url":"https://api.github.com/users/pasinskim","html_url":"https://github.com/pasinskim","followers_url":"https://api.github.com/users/pasinskim/followers","following_url":"https://api.github.com/users/pasinskim/following{/other_user}","gists_url":"https://api.github.com/users/pasinskim/gists{/gist_id}","starred_url":"https://api.github.com/users/pasinskim/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pasinskim/subscriptions","organizations_url":"https://api.github.com/users/pasinskim/orgs","repos_url":"https://api.github.com/users/pasinskim/repos","events_url":"https://api.github.com/users/pasinskim/events{/privacy}","received_events_url":"https://api.github.com/users/pasinskim/received_events","type":"User","site_admin":false},"body":"Thanks for this one. Fixed!","created_at":"2015-05-24T20:39:39Z","updated_at":"2015-05-24T20:39:39Z","html_url":"https://github.com/cfengine/core/pull/2215#discussion_r30955672","pull_request_url":"https://api.github.com/repos/cfengine/core/pulls/2215","_links":{"self":{"href":"https://api.github.com/repos/cfengine/core/pulls/comments/30955672"},"html":{"href":"https://github.com/cfengine/core/pull/2215#discussion_r30955672"},"pull_request":{"href":"https://api.github.com/repos/cfengine/core/pulls/2215"}}},"pull_request":{"url":"https://api.github.com/repos/cfengine/core/pulls/2215","id":35834193,"html_url":"https://github.com/cfengine/core/pull/2215","diff_url":"https://github.com/cfengine/core/pull/2215.diff","patch_url":"https://github.com/cfengine/core/pull/2215.patch","issue_url":"https://api.github.com/repos/cfengine/core/issues/2215","number":2215,"state":"open","locked":false,"title":"New packages promise","user":{"login":"pasinskim","id":6329055,"avatar_url":"https://avatars.githubusercontent.com/u/6329055?v=3","gravatar_id":"","url":"https://api.github.com/users/pasinskim","html_url":"https://github.com/pasinskim","followers_url":"https://api.github.com/users/pasinskim/followers","following_url":"https://api.github.com/users/pasinskim/following{/other_user}","gists_url":"https://api.github.com/users/pasinskim/gists{/gist_id}","starred_url":"https://api.github.com/users/pasinskim/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pasinskim/subscriptions","organizations_url":"https://api.github.com/users/pasinskim/orgs","repos_url":"https://api.github.com/users/pasinskim/repos","events_url":"https://api.github.com/users/pasinskim/events{/privacy}","received_events_url":"https://api.github.com/users/pasinskim/received_events","type":"User","site_admin":false},"body":"Complete implementation of new package promise feature.","created_at":"2015-05-20T13:57:20Z","updated_at":"2015-05-24T20:39:39Z","closed_at":null,"merged_at":null,"merge_commit_sha":"b062882610ffcb76cdcc00c944ebb34f19aa6ea9","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/cfengine/core/pulls/2215/commits","review_comments_url":"https://api.github.com/repos/cfengine/core/pulls/2215/comments","review_comment_url":"https://api.github.com/repos/cfengine/core/pulls/comments{/number}","comments_url":"https://api.github.com/repos/cfengine/core/issues/2215/comments","statuses_url":"https://api.github.com/repos/cfengine/core/statuses/885974c0b4a2ee6f06a126dfec63e5aa0a8ad785","head":{"label":"pasinskim:new_packages_promise","ref":"new_packages_promise","sha":"885974c0b4a2ee6f06a126dfec63e5aa0a8ad785","user":{"login":"pasinskim","id":6329055,"avatar_url":"https://avatars.githubusercontent.com/u/6329055?v=3","gravatar_id":"","url":"https://api.github.com/users/pasinskim","html_url":"https://github.com/pasinskim","followers_url":"https://api.github.com/users/pasinskim/followers","following_url":"https://api.github.com/users/pasinskim/following{/other_user}","gists_url":"https://api.github.com/users/pasinskim/gists{/gist_id}","starred_url":"https://api.github.com/users/pasinskim/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pasinskim/subscriptions","organizations_url":"https://api.github.com/users/pasinskim/orgs","repos_url":"https://api.github.com/users/pasinskim/repos","events_url":"https://api.github.com/users/pasinskim/events{/privacy}","received_events_url":"https://api.github.com/users/pasinskim/received_events","type":"User","site_admin":false},"repo":{"id":15708117,"name":"core","full_name":"pasinskim/core","owner":{"login":"pasinskim","id":6329055,"avatar_url":"https://avatars.githubusercontent.com/u/6329055?v=3","gravatar_id":"","url":"https://api.github.com/users/pasinskim","html_url":"https://github.com/pasinskim","followers_url":"https://api.github.com/users/pasinskim/followers","following_url":"https://api.github.com/users/pasinskim/following{/other_user}","gists_url":"https://api.github.com/users/pasinskim/gists{/gist_id}","starred_url":"https://api.github.com/users/pasinskim/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pasinskim/subscriptions","organizations_url":"https://api.github.com/users/pasinskim/orgs","repos_url":"https://api.github.com/users/pasinskim/repos","events_url":"https://api.github.com/users/pasinskim/events{/privacy}","received_events_url":"https://api.github.com/users/pasinskim/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/pasinskim/core","description":"CFEngine Community","fork":true,"url":"https://api.github.com/repos/pasinskim/core","forks_url":"https://api.github.com/repos/pasinskim/core/forks","keys_url":"https://api.github.com/repos/pasinskim/core/keys{/key_id}","collaborators_url":"https://api.github.com/repos/pasinskim/core/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/pasinskim/core/teams","hooks_url":"https://api.github.com/repos/pasinskim/core/hooks","issue_events_url":"https://api.github.com/repos/pasinskim/core/issues/events{/number}","events_url":"https://api.github.com/repos/pasinskim/core/events","assignees_url":"https://api.github.com/repos/pasinskim/core/assignees{/user}","branches_url":"https://api.github.com/repos/pasinskim/core/branches{/branch}","tags_url":"https://api.github.com/repos/pasinskim/core/tags","blobs_url":"https://api.github.com/repos/pasinskim/core/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/pasinskim/core/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/pasinskim/core/git/refs{/sha}","trees_url":"https://api.github.com/repos/pasinskim/core/git/trees{/sha}","statuses_url":"https://api.github.com/repos/pasinskim/core/statuses/{sha}","languages_url":"https://api.github.com/repos/pasinskim/core/languages","stargazers_url":"https://api.github.com/repos/pasinskim/core/stargazers","contributors_url":"https://api.github.com/repos/pasinskim/core/contributors","subscribers_url":"https://api.github.com/repos/pasinskim/core/subscribers","subscription_url":"https://api.github.com/repos/pasinskim/core/subscription","commits_url":"https://api.github.com/repos/pasinskim/core/commits{/sha}","git_commits_url":"https://api.github.com/repos/pasinskim/core/git/commits{/sha}","comments_url":"https://api.github.com/repos/pasinskim/core/comments{/number}","issue_comment_url":"https://api.github.com/repos/pasinskim/core/issues/comments{/number}","contents_url":"https://api.github.com/repos/pasinskim/core/contents/{+path}","compare_url":"https://api.github.com/repos/pasinskim/core/compare/{base}...{head}","merges_url":"https://api.github.com/repos/pasinskim/core/merges","archive_url":"https://api.github.com/repos/pasinskim/core/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/pasinskim/core/downloads","issues_url":"https://api.github.com/repos/pasinskim/core/issues{/number}","pulls_url":"https://api.github.com/repos/pasinskim/core/pulls{/number}","milestones_url":"https://api.github.com/repos/pasinskim/core/milestones{/number}","notifications_url":"https://api.github.com/repos/pasinskim/core/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/pasinskim/core/labels{/name}","releases_url":"https://api.github.com/repos/pasinskim/core/releases{/id}","created_at":"2014-01-07T15:24:45Z","updated_at":"2015-03-11T16:49:34Z","pushed_at":"2015-05-21T13:10:12Z","git_url":"git://github.com/pasinskim/core.git","ssh_url":"git@github.com:pasinskim/core.git","clone_url":"https://github.com/pasinskim/core.git","svn_url":"https://github.com/pasinskim/core","homepage":"https://cfengine.com/community/","size":52599,"stargazers_count":0,"watchers_count":0,"language":"C","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"cfengine:master","ref":"master","sha":"0b34201ab6705590fed804ae9ac2f52fa4fef904","user":{"login":"cfengine","id":1239823,"avatar_url":"https://avatars.githubusercontent.com/u/1239823?v=3","gravatar_id":"","url":"https://api.github.com/users/cfengine","html_url":"https://github.com/cfengine","followers_url":"https://api.github.com/users/cfengine/followers","following_url":"https://api.github.com/users/cfengine/following{/other_user}","gists_url":"https://api.github.com/users/cfengine/gists{/gist_id}","starred_url":"https://api.github.com/users/cfengine/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cfengine/subscriptions","organizations_url":"https://api.github.com/users/cfengine/orgs","repos_url":"https://api.github.com/users/cfengine/repos","events_url":"https://api.github.com/users/cfengine/events{/privacy}","received_events_url":"https://api.github.com/users/cfengine/received_events","type":"Organization","site_admin":false},"repo":{"id":3543548,"name":"core","full_name":"cfengine/core","owner":{"login":"cfengine","id":1239823,"avatar_url":"https://avatars.githubusercontent.com/u/1239823?v=3","gravatar_id":"","url":"https://api.github.com/users/cfengine","html_url":"https://github.com/cfengine","followers_url":"https://api.github.com/users/cfengine/followers","following_url":"https://api.github.com/users/cfengine/following{/other_user}","gists_url":"https://api.github.com/users/cfengine/gists{/gist_id}","starred_url":"https://api.github.com/users/cfengine/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cfengine/subscriptions","organizations_url":"https://api.github.com/users/cfengine/orgs","repos_url":"https://api.github.com/users/cfengine/repos","events_url":"https://api.github.com/users/cfengine/events{/privacy}","received_events_url":"https://api.github.com/users/cfengine/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/cfengine/core","description":"CFEngine Community","fork":false,"url":"https://api.github.com/repos/cfengine/core","forks_url":"https://api.github.com/repos/cfengine/core/forks","keys_url":"https://api.github.com/repos/cfengine/core/keys{/key_id}","collaborators_url":"https://api.github.com/repos/cfengine/core/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/cfengine/core/teams","hooks_url":"https://api.github.com/repos/cfengine/core/hooks","issue_events_url":"https://api.github.com/repos/cfengine/core/issues/events{/number}","events_url":"https://api.github.com/repos/cfengine/core/events","assignees_url":"https://api.github.com/repos/cfengine/core/assignees{/user}","branches_url":"https://api.github.com/repos/cfengine/core/branches{/branch}","tags_url":"https://api.github.com/repos/cfengine/core/tags","blobs_url":"https://api.github.com/repos/cfengine/core/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/cfengine/core/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/cfengine/core/git/refs{/sha}","trees_url":"https://api.github.com/repos/cfengine/core/git/trees{/sha}","statuses_url":"https://api.github.com/repos/cfengine/core/statuses/{sha}","languages_url":"https://api.github.com/repos/cfengine/core/languages","stargazers_url":"https://api.github.com/repos/cfengine/core/stargazers","contributors_url":"https://api.github.com/repos/cfengine/core/contributors","subscribers_url":"https://api.github.com/repos/cfengine/core/subscribers","subscription_url":"https://api.github.com/repos/cfengine/core/subscription","commits_url":"https://api.github.com/repos/cfengine/core/commits{/sha}","git_commits_url":"https://api.github.com/repos/cfengine/core/git/commits{/sha}","comments_url":"https://api.github.com/repos/cfengine/core/comments{/number}","issue_comment_url":"https://api.github.com/repos/cfengine/core/issues/comments{/number}","contents_url":"https://api.github.com/repos/cfengine/core/contents/{+path}","compare_url":"https://api.github.com/repos/cfengine/core/compare/{base}...{head}","merges_url":"https://api.github.com/repos/cfengine/core/merges","archive_url":"https://api.github.com/repos/cfengine/core/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/cfengine/core/downloads","issues_url":"https://api.github.com/repos/cfengine/core/issues{/number}","pulls_url":"https://api.github.com/repos/cfengine/core/pulls{/number}","milestones_url":"https://api.github.com/repos/cfengine/core/milestones{/number}","notifications_url":"https://api.github.com/repos/cfengine/core/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/cfengine/core/labels{/name}","releases_url":"https://api.github.com/repos/cfengine/core/releases{/id}","created_at":"2012-02-25T09:19:05Z","updated_at":"2015-05-14T19:15:35Z","pushed_at":"2015-05-22T19:00:23Z","git_url":"git://github.com/cfengine/core.git","ssh_url":"git@github.com:cfengine/core.git","clone_url":"https://github.com/cfengine/core.git","svn_url":"https://github.com/cfengine/core","homepage":"https://cfengine.com/community/","size":95838,"stargazers_count":217,"watchers_count":217,"language":"C","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":135,"mirror_url":null,"open_issues_count":11,"forks":135,"open_issues":11,"watchers":217,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/cfengine/core/pulls/2215"},"html":{"href":"https://github.com/cfengine/core/pull/2215"},"issue":{"href":"https://api.github.com/repos/cfengine/core/issues/2215"},"comments":{"href":"https://api.github.com/repos/cfengine/core/issues/2215/comments"},"review_comments":{"href":"https://api.github.com/repos/cfengine/core/pulls/2215/comments"},"review_comment":{"href":"https://api.github.com/repos/cfengine/core/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/cfengine/core/pulls/2215/commits"},"statuses":{"href":"https://api.github.com/repos/cfengine/core/statuses/885974c0b4a2ee6f06a126dfec63e5aa0a8ad785"}}}}