static int _icd_state_value_from_gvariant(OCRepPayload *repr, GVariantIter *iter)
{
	int ret;
	char *key;
	GVariant *var;
	const char *str_value;
	OCRepPayload *repr_value;
	struct icd_state_list_s value_list = {0};

	while (g_variant_iter_loop(iter, "{sv}", &key, &var)) {

		if (g_variant_is_of_type(var, G_VARIANT_TYPE_BOOLEAN)) {
			OCRepPayloadSetPropBool(repr, key, g_variant_get_boolean(var));

		} else if (g_variant_is_of_type(var, G_VARIANT_TYPE_INT32)) {
			OCRepPayloadSetPropInt(repr, key, g_variant_get_int32(var));

		} else if (g_variant_is_of_type(var, G_VARIANT_TYPE_DOUBLE)) {
			OCRepPayloadSetPropDouble(repr, key, g_variant_get_double(var));

		} else if (g_variant_is_of_type(var, G_VARIANT_TYPE_STRING)) {
			str_value = g_variant_get_string(var, NULL);
			if (NULL == str_value) {
				ERR("g_variant_get_string() Fail");
				_icd_payload_state_list_destroy(&value_list);
				return IOTCON_ERROR_OUT_OF_MEMORY;
			}
			if (IC_STR_EQUAL == strcmp(IC_STR_NULL, str_value))
				OCRepPayloadSetNull(repr, key);
			else
				OCRepPayloadSetPropString(repr, key, str_value);

		} else if (g_variant_is_of_type(var, G_VARIANT_TYPE("ay"))) {
			OCByteString byte_value;
			byte_value.bytes = (uint8_t*)g_variant_get_data(var);
			byte_value.len = g_variant_get_size(var);
			OCRepPayloadSetPropByteString(repr, key, byte_value);

		} else if (g_variant_is_of_type(var, G_VARIANT_TYPE("a{sv}"))) {
			GVariantIter state_iter;
			repr_value = OCRepPayloadCreate();
			g_variant_iter_init(&state_iter, var);

			ret = _icd_state_value_from_gvariant(repr_value, &state_iter);
			if (IOTCON_ERROR_NONE != ret) {
				ERR("_icd_state_value_from_gvariant() Fail(%d)", ret);
				_icd_payload_state_list_destroy(&value_list);
				OCRepPayloadDestroy(repr_value);
				return ret;
			}
			OCRepPayloadSetPropObjectAsOwner(repr, key, repr_value);

		} else if (g_variant_is_of_type(var, G_VARIANT_TYPE_ARRAY)) {
			memset(&value_list, 0, sizeof(struct icd_state_list_s));
			ret = _icd_state_list_from_gvariant(var, &value_list, 0);
			if (IOTCON_ERROR_NONE != ret) {
				ERR("_icd_state_list_from_gvariant() Fail(%d)", ret);
				_icd_payload_state_list_destroy(&value_list);
				return ret;
			}
			ret = _icd_state_array_from_list(repr, &value_list, key);
			if (IOTCON_ERROR_NONE != ret) {
				ERR("_icd_state_array_from_list() Fail(%d)", ret);
				_icd_payload_state_list_destroy(&value_list);
				return ret;
			}

		} else {
			ERR("Invalid type(%s)", g_variant_get_type_string(var));
			return IOTCON_ERROR_INVALID_TYPE;
		}
	}

	return IOTCON_ERROR_NONE;
}
gboolean
_ostree_static_delta_part_open (GInputStream   *part_in,
                                GBytes         *inline_part_bytes,
                                OstreeStaticDeltaOpenFlags flags,
                                const char     *expected_checksum,
                                GVariant    **out_part,
                                GCancellable *cancellable,
                                GError      **error)
{
  const gboolean trusted = (flags & OSTREE_STATIC_DELTA_OPEN_FLAGS_VARIANT_TRUSTED) > 0;
  const gboolean skip_checksum = (flags & OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM) > 0;
  gsize bytes_read;
  guint8 comptype;
  g_autoptr(GChecksum) checksum = NULL;
  g_autoptr(GInputStream) checksum_in = NULL;
  GInputStream *source_in;

  /* We either take a fd or a GBytes reference */
  g_return_val_if_fail (G_IS_FILE_DESCRIPTOR_BASED (part_in) || inline_part_bytes != NULL, FALSE);
  g_return_val_if_fail (skip_checksum || expected_checksum != NULL, FALSE);

  if (!skip_checksum)
    {
      checksum = g_checksum_new (G_CHECKSUM_SHA256);
      checksum_in = (GInputStream*)ostree_checksum_input_stream_new (part_in, checksum);
      source_in = checksum_in;
    }
  else
    {
      source_in = part_in;
    }

  { guint8 buf[1];
    /* First byte is compression type */
    if (!g_input_stream_read_all (source_in, buf, sizeof(buf), &bytes_read,
                                  cancellable, error))
      return glnx_prefix_error (error, "Reading initial compression flag byte");
    comptype = buf[0];
  }

  g_autoptr(GVariant) ret_part = NULL;
  switch (comptype)
    {
    case 0:
      if (!inline_part_bytes)
        {
          int part_fd = g_file_descriptor_based_get_fd ((GFileDescriptorBased*)part_in);

          /* No compression, no checksums - a fast path */
          if (!ot_util_variant_map_fd (part_fd, 1, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0),
                                       trusted, &ret_part, error))
            return FALSE;
        }
      else
        {
          g_autoptr(GBytes) content_bytes = g_bytes_new_from_bytes (inline_part_bytes, 1,
                                                                    g_bytes_get_size (inline_part_bytes) - 1);
          ret_part = g_variant_new_from_bytes (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0),
                                               content_bytes, trusted);
          g_variant_ref_sink (ret_part);
        }

      if (!skip_checksum)
        g_checksum_update (checksum, g_variant_get_data (ret_part),
                           g_variant_get_size (ret_part));

      break;
    case 'x':
      {
        g_autoptr(GConverter) decomp = (GConverter*) _ostree_lzma_decompressor_new ();
        g_autoptr(GInputStream) convin = g_converter_input_stream_new (source_in, decomp);
        g_autoptr(GBytes) buf = ot_map_anonymous_tmpfile_from_content (convin, cancellable, error);
        if (!buf)
          return FALSE;

        ret_part = g_variant_new_from_bytes (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0),
                                             buf, FALSE);
      }
      break;
    default:
      return glnx_throw (error, "Invalid compression type '%u'", comptype);
    }

  if (checksum)
    {
      const char *actual_checksum = g_checksum_get_string (checksum);
      g_assert (expected_checksum != NULL);
      if (strcmp (actual_checksum, expected_checksum) != 0)
        return glnx_throw (error, "Checksum mismatch in static delta part; expected=%s actual=%s",
                           expected_checksum, actual_checksum);
    }

  *out_part = g_steal_pointer (&ret_part);
  return TRUE;
}
static int _icd_state_list_from_gvariant(GVariant *var,
		struct icd_state_list_s *value_list, int depth)
{
	int ret;
	GVariantIter iter;
	const GVariantType *type;
	union icd_state_value_u *value;

	type = g_variant_get_type(var);

	g_variant_iter_init(&iter, var);

	value_list->dimensions[depth] = g_variant_iter_n_children(&iter);
	DBG("[%d]list dim : %d", depth, value_list->dimensions[depth]);

	if (g_variant_type_equal(G_VARIANT_TYPE("ab"), type)) {
		bool b;
		value_list->type = OCREP_PROP_BOOL;
		while (g_variant_iter_loop(&iter, "b", &b)) {
			value = calloc(1, sizeof(union icd_state_value_u));
			if (NULL == value) {
				ERR("calloc() Fail(%d)", errno);
				return IOTCON_ERROR_OUT_OF_MEMORY;
			}
			value->b = b;
			value_list->list = g_list_append(value_list->list, value);
		}
	} else if (g_variant_type_equal(G_VARIANT_TYPE("ai"), type)) {
		int i;
		value_list->type = OCREP_PROP_INT;
		while (g_variant_iter_loop(&iter, "i", &i)) {
			value = calloc(1, sizeof(union icd_state_value_u));
			if (NULL == value) {
				ERR("calloc() Fail(%d)", errno);
				return IOTCON_ERROR_OUT_OF_MEMORY;
			}
			value->i = i;
			value_list->list = g_list_append(value_list->list, value);
		}
	} else if (g_variant_type_equal(G_VARIANT_TYPE("ad"), type)) {
		double d;
		value_list->type = OCREP_PROP_DOUBLE;
		while (g_variant_iter_loop(&iter, "d", &d)) {
			value = calloc(1, sizeof(union icd_state_value_u));
			if (NULL == value) {
				ERR("calloc() Fail(%d)", errno);
				return IOTCON_ERROR_OUT_OF_MEMORY;
			}
			value->d = d;
			value_list->list = g_list_append(value_list->list, value);
		}
	} else if (g_variant_type_equal(G_VARIANT_TYPE("as"), type)) {
		char *s;
		value_list->type = OCREP_PROP_STRING;
		while (g_variant_iter_next(&iter, "s", &s))
			value_list->list = g_list_append(value_list->list, s);
	} else if (g_variant_type_equal(G_VARIANT_TYPE("av"), type)) {
		GVariant *value;
		if (g_variant_iter_loop(&iter, "v", &value)) {
			if (g_variant_is_of_type(value, G_VARIANT_TYPE("a{sv}"))) {
				OCRepPayload *repr;
				GVariantIter state_iter;
				value_list->type = OCREP_PROP_OBJECT;
				do {
					repr = OCRepPayloadCreate();
					g_variant_iter_init(&state_iter, value);
					ret = _icd_state_value_from_gvariant(repr, &state_iter);
					if (IOTCON_ERROR_NONE != ret) {
						ERR("_icd_state_value_from_gvariant() Fail(%d)", ret);
						OCRepPayloadDestroy(repr);
						return ret;
					}
					value_list->list = g_list_append(value_list->list, repr);
				} while (g_variant_iter_loop(&iter, "v", &value));

			} else if (g_variant_is_of_type(value, G_VARIANT_TYPE("ay"))) {
				OCByteString *byte_str;
				value_list->type = OCREP_PROP_BYTE_STRING;
				do {
					byte_str = calloc(1, sizeof(OCByteString));
					if (NULL == byte_str) {
						ERR("calloc() Fail(%d)", errno);
						return IOTCON_ERROR_OUT_OF_MEMORY;
					}

					byte_str->len = g_variant_get_size(value);
					byte_str->bytes = calloc(byte_str->len, sizeof(uint8_t));
					if (NULL == byte_str->bytes) {
						ERR("calloc() Fail(%d)", errno);
						free(byte_str);
						return IOTCON_ERROR_OUT_OF_MEMORY;
					}
					memcpy(byte_str->bytes, g_variant_get_data(value), byte_str->len);

					value_list->list = g_list_append(value_list->list, byte_str);
				} while (g_variant_iter_loop(&iter, "v", &value));

			} else if (g_variant_is_of_type(value, G_VARIANT_TYPE_ARRAY)) {
				do {
					ret = _icd_state_list_from_gvariant(value, value_list, depth + 1);
					if (IOTCON_ERROR_NONE != ret) {
						ERR("_icd_state_list_from_gvariant() Fail(%d)", ret);
						return ret;
					}
				} while (g_variant_iter_loop(&iter, "v", &value));
			}
		}
	}

	return IOTCON_ERROR_NONE;
}
Esempio n. 4
0
static
gboolean do_lookup (GResource             *resource,
                    const gchar           *path,
                    GResourceLookupFlags   lookup_flags,
                    gsize                 *size,
                    guint32               *flags,
                    const void           **data,
                    gsize                 *data_size,
                    GError               **error)
{
  char *free_path = NULL;
  gsize path_len;
  gboolean res = FALSE;
  GVariant *value;

  path_len = strlen (path);
  if (path[path_len-1] == '/')
    {
      path = free_path = g_strdup (path);
      free_path[path_len-1] = 0;
    }

  value = gvdb_table_get_raw_value (resource->table, path);

  if (value == NULL)
    {
      g_set_error (error, G_RESOURCE_ERROR, G_RESOURCE_ERROR_NOT_FOUND,
                   _("The resource at '%s' does not exist"),
                   path);
    }
  else
    {
      guint32 _size, _flags;
      GVariant *array;

      g_variant_get (value, "(uu@ay)",
                     &_size,
                     &_flags,
                     &array);

      _size = GUINT32_FROM_LE (_size);
      _flags = GUINT32_FROM_LE (_flags);

      if (size)
        *size = _size;
      if (flags)
        *flags = _flags;
      if (data)
        *data = g_variant_get_data (array);
      if (data_size)
        {
          /* Don't report trailing newline that non-compressed files has */
          if (_flags & G_RESOURCE_FLAGS_COMPRESSED)
            *data_size = g_variant_get_size (array);
          else
            *data_size = g_variant_get_size (array) - 1;
        }
      g_variant_unref (array);
      g_variant_unref (value);

      res = TRUE;
    }

  g_free (free_path);
  return res;
}
gboolean
_ostree_static_delta_part_open (GInputStream   *part_in,
                                GBytes         *inline_part_bytes,
                                OstreeStaticDeltaOpenFlags flags,
                                const char     *expected_checksum,
                                GVariant    **out_part,
                                GCancellable *cancellable,
                                GError      **error)
{
    gboolean ret = FALSE;
    const gboolean trusted = (flags & OSTREE_STATIC_DELTA_OPEN_FLAGS_VARIANT_TRUSTED) > 0;
    const gboolean skip_checksum = (flags & OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM) > 0;
    gsize bytes_read;
    guint8 comptype;
    g_autoptr(GChecksum) checksum = NULL;
    g_autoptr(GInputStream) checksum_in = NULL;
    g_autoptr(GVariant) ret_part = NULL;
    GInputStream *source_in;

    /* We either take a fd or a GBytes reference */
    g_return_val_if_fail (G_IS_FILE_DESCRIPTOR_BASED (part_in) || inline_part_bytes != NULL, FALSE);
    g_return_val_if_fail (skip_checksum || expected_checksum != NULL, FALSE);

    if (!skip_checksum)
    {
        checksum = g_checksum_new (G_CHECKSUM_SHA256);
        checksum_in = (GInputStream*)ostree_checksum_input_stream_new (part_in, checksum);
        source_in = checksum_in;
    }
    else
    {
        source_in = part_in;
    }

    {   guint8 buf[1];
        /* First byte is compression type */
        if (!g_input_stream_read_all (source_in, buf, sizeof(buf), &bytes_read,
                                      cancellable, error))
        {
            g_prefix_error (error, "Reading initial compression flag byte: ");
            goto out;
        }
        comptype = buf[0];
    }

    switch (comptype)
    {
    case 0:
        if (!inline_part_bytes)
        {
            int part_fd = g_file_descriptor_based_get_fd ((GFileDescriptorBased*)part_in);

            /* No compression, no checksums - a fast path */
            if (!ot_util_variant_map_fd (part_fd, 1, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0),
                                         trusted, &ret_part, error))
                goto out;
        }
        else
        {
            g_autoptr(GBytes) content_bytes = g_bytes_new_from_bytes (inline_part_bytes, 1,
                                              g_bytes_get_size (inline_part_bytes) - 1);
            ret_part = g_variant_new_from_bytes (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0),
                                                 content_bytes, trusted);
            g_variant_ref_sink (ret_part);
        }

        if (!skip_checksum)
            g_checksum_update (checksum, g_variant_get_data (ret_part),
                               g_variant_get_size (ret_part));

        break;
    case 'x':
    {
        g_autofree char *tmppath = g_strdup ("/var/tmp/ostree-delta-XXXXXX");
        g_autoptr(GConverter) decomp = (GConverter*) _ostree_lzma_decompressor_new ();
        g_autoptr(GInputStream) convin = g_converter_input_stream_new (source_in, decomp);
        g_autoptr(GOutputStream) unpacked_out = NULL;
        glnx_fd_close int unpacked_fd = -1;
        gssize n_bytes_written;

        unpacked_fd = g_mkstemp_full (tmppath, O_RDWR | O_CLOEXEC, 0640);
        if (unpacked_fd < 0)
        {
            glnx_set_error_from_errno (error);
            goto out;
        }

        /* Now make it autocleanup on process exit - in the future, we
         * should consider caching unpacked deltas as well.
         */
        if (unlink (tmppath) < 0)
        {
            glnx_set_error_from_errno (error);
            goto out;
        }

        unpacked_out = g_unix_output_stream_new (unpacked_fd, FALSE);

        n_bytes_written = g_output_stream_splice (unpacked_out, convin,
                          G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
                          G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
                          cancellable, error);
        if (n_bytes_written < 0)
            goto out;

        if (!ot_util_variant_map_fd (unpacked_fd, 0, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0),
                                     trusted, &ret_part, error))
            goto out;
    }
    break;
    default:
        g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                     "Invalid compression type '%u'", comptype);
        goto out;
    }

    if (checksum)
    {
        const char *actual_checksum = g_checksum_get_string (checksum);
        g_assert (expected_checksum != NULL);
        if (strcmp (actual_checksum, expected_checksum) != 0)
        {
            g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                         "Checksum mismatch in static delta part; expected=%s actual=%s",
                         expected_checksum, actual_checksum);
            goto out;
        }
    }

    ret = TRUE;
    *out_part = g_steal_pointer (&ret_part);
out:
    return ret;
}
Esempio n. 6
0
// Get the (key,value) pairs back from KWallet.
GHashTable* dt_pwstorage_kwallet_get(const backend_kwallet_context_t *context, const gchar* slot)
{
    GHashTable *table = g_hash_table_new(g_str_hash, g_str_equal);
    GError* error = NULL;

    // Is there an entry in the wallet?
    gboolean has_entry = FALSE;
    int wallet_handle = get_wallet_handle(context);

    /* signature:
     *
     * in  i handle,
     * in  s folder,
     * in  s key,
     * in  s appid,
     *
     * out b arg_0
     */
    GVariant *ret = g_dbus_proxy_call_sync(context->proxy,
                                           "hasEntry",
                                           g_variant_new("(isss)", wallet_handle, kwallet_folder, slot, app_id),
                                           G_DBUS_CALL_FLAGS_NONE,
                                           -1,
                                           NULL,
                                           &error);

    if(check_error(error))
    {
        g_variant_unref(ret);
        return table;
    }

    GVariant *child = g_variant_get_child_value(ret, 0);
    has_entry = g_variant_get_boolean(child);
    g_variant_unref(child);
    g_variant_unref(ret);

    if(!has_entry)
        return table;

    /* signature:
     *
     * in  i handle,
     * in  s folder,
     * in  s key,
     * in  s appid,
     *
     * out a{sv} arg_0)
     */
    ret = g_dbus_proxy_call_sync(context->proxy,
                                 "readMapList",
                                 g_variant_new("(isss)", wallet_handle, kwallet_folder, slot, app_id),
                                 G_DBUS_CALL_FLAGS_NONE,
                                 -1,
                                 NULL,
                                 &error);

    if(check_error(error))
    {
        g_variant_unref(ret);
        return table;
    }

    child = g_variant_get_child_value(ret, 0);

    // we are only interested in the first child. i am not even sure that there can legally be more than one
    if(g_variant_n_children(child)<1)
    {
        g_variant_unref(child);
        g_variant_unref(ret);
        return table;
    }

    GVariant *element = g_variant_get_child_value(child, 0);
    GVariant *v = NULL;
    g_variant_get(element, "{sv}", NULL, &v);

    const gchar *byte_array = g_variant_get_data(v);
    if(!byte_array)
    {
        g_variant_unref(v);
        g_variant_unref(element);
        g_variant_unref(child);
        g_variant_unref(ret);
        return table;
    }

    int entries = GINT_FROM_BE(*((int*)byte_array));
    byte_array += sizeof(gint);

    for(int i=0; i<entries; i++)
    {
        guint length;
        gchar* key = array2string(byte_array, &length);

        byte_array += length;

        gchar* value = array2string(byte_array, &length);

        byte_array += length;

        dt_print(DT_DEBUG_PWSTORAGE,"[pwstorage_kwallet_get] reading (%s, %s)\n",(gchar*)key, (gchar*)value);

        g_hash_table_insert(table, key, value);
    }

    g_variant_unref(v);
    g_variant_unref(element);
    g_variant_unref(child);
    g_variant_unref(ret);

    return table;
}
gboolean
_ostree_static_delta_part_execute_raw (OstreeRepo      *repo,
                                       GVariant        *objects,
                                       GVariant        *part,
                                       GCancellable    *cancellable,
                                       GError         **error)
{
  gboolean ret = FALSE;
  guint8 *checksums_data;
  g_autoptr(GVariant) checksums = NULL;
  g_autoptr(GVariant) mode_dict = NULL;
  g_autoptr(GVariant) xattr_dict = NULL;
  g_autoptr(GVariant) payload = NULL;
  g_autoptr(GVariant) ops = NULL;
  StaticDeltaExecutionState statedata = { 0, };
  StaticDeltaExecutionState *state = &statedata;
  guint n_executed = 0;

  state->repo = repo;
  state->async_error = error;

  if (!_ostree_static_delta_parse_checksum_array (objects,
                                                  &checksums_data,
                                                  &state->n_checksums,
                                                  error))
    goto out;

  state->checksums = checksums_data;
  g_assert (state->n_checksums > 0);

  g_variant_get (part, "(@a(uuu)@aa(ayay)@ay@ay)",
                 &mode_dict,
                 &xattr_dict,
                 &payload, &ops);

  state->mode_dict = mode_dict;
  state->xattr_dict = xattr_dict;

  state->payload_data = g_variant_get_data (payload);
  state->payload_size = g_variant_get_size (payload);

  state->oplen = g_variant_n_children (ops);
  state->opdata = g_variant_get_data (ops);

  while (state->oplen > 0)
    {
      guint8 opcode;

      opcode = state->opdata[0];
      state->oplen--;
      state->opdata++;

      switch (opcode)
        {
        case OSTREE_STATIC_DELTA_OP_OPEN_SPLICE_AND_CLOSE:
          if (!dispatch_open_splice_and_close (repo, state, cancellable, error))
            goto out;
          break;
        case OSTREE_STATIC_DELTA_OP_OPEN:
          if (!dispatch_open (repo, state, cancellable, error))
            goto out;
          break;
        case OSTREE_STATIC_DELTA_OP_WRITE:
          if (!dispatch_write (repo, state, cancellable, error))
            goto out;
          break;
        case OSTREE_STATIC_DELTA_OP_SET_READ_SOURCE:
          if (!dispatch_set_read_source (repo, state, cancellable, error))
            goto out;
          break;
        case OSTREE_STATIC_DELTA_OP_UNSET_READ_SOURCE:
          if (!dispatch_unset_read_source (repo, state, cancellable, error))
            goto out;
          break;
        case OSTREE_STATIC_DELTA_OP_CLOSE:
          if (!dispatch_close (repo, state, cancellable, error))
            goto out;
          break;
        case OSTREE_STATIC_DELTA_OP_BSPATCH:
          if (!dispatch_bspatch (repo, state, cancellable, error))
            goto out;
          break;
        default:
          g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
                       "Unknown opcode %u at offset %u", opcode, n_executed);
          goto out;
        }

      n_executed++;
    }

  if (state->caught_error)
    goto out;

  ret = TRUE;
 out:
  return ret;
}
static void
write_translations_dictionary (GList * licenses, const gchar * dict_filename)
{
  /* maps C string => (dictionary of: locale => translation) */
  GVariantBuilder array;
  /* maps C string => boolean (if it's in the dictionary already */
  GHashTable *translations;
  GVariant *var;
  GList *l;
  FILE *f;

  /* sort langs for prettiness / to make variant dumps easier to read */
  langs = g_list_sort (langs, (GCompareFunc) strcmp);

  g_variant_builder_init (&array, G_VARIANT_TYPE_ARRAY);

  translations = g_hash_table_new (g_str_hash, g_str_equal);

  for (l = licenses; l != NULL; l = l->next) {
    const gchar *en;
    License *license;

    license = l->data;

    if (license->packed_into_source)
      continue;

    /* add title + translations */
    en = g_hash_table_lookup (license->titles, "en");
    g_assert (en != NULL);

    /* check if we already have added translations for this string */
    if (!g_hash_table_lookup (translations, (gpointer) en)) {
      GVariant *trans;

      trans = create_translation_dict (license->titles, en);
      if (trans != NULL) {
        g_variant_builder_add_value (&array,
            g_variant_new_dict_entry (g_variant_new_string (en), trans));
        g_hash_table_insert (translations, (gpointer) en,
            GINT_TO_POINTER (TRUE));
      }
    }

    /* add description + translations */
    if (license->descriptions == NULL)
      continue;

    en = g_hash_table_lookup (license->descriptions, "en");
    g_assert (en != NULL);

    /* check if we already have added translations for this string */
    if (!g_hash_table_lookup (translations, (gpointer) en)) {
      GVariant *trans;

      trans = create_translation_dict (license->descriptions, en);
      if (trans != NULL) {
        g_variant_builder_add_value (&array,
            g_variant_new_dict_entry (g_variant_new_string (en), trans));
        g_hash_table_insert (translations, (gpointer) en,
            GINT_TO_POINTER (TRUE));
      }
    }
  }

  var = g_variant_builder_end (&array);

  f = fopen (dict_filename, "wb");
  if (fwrite (g_variant_get_data (var), g_variant_get_size (var), 1, f) != 1) {
    g_error ("failed to write dict to file: %s", g_strerror (errno));
  }
  fclose (f);

  g_printerr ("Wrote dictionary to %s, size: %u, type: %s\n", dict_filename,
      (guint) g_variant_get_size (var), (gchar *) g_variant_get_type (var));

  g_variant_unref (var);
  g_hash_table_destroy (translations);
}