/* GIO - GLib Input, Output and Streaming Library * * Copyright (C) 2006-2007 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * Author: Alexander Larsson */ #include #include #include #include #include "gmountspec.h" static GHashTable *unique_hash = NULL; G_LOCK_DEFINE_STATIC(unique_hash); static int item_compare (const void *_a, const void *_b) { const GMountSpecItem *a = _a; const GMountSpecItem *b = _b; return strcmp (a->key, b->key); } GMountSpec * g_mount_spec_new (const char *type) { GMountSpec *spec; spec = g_new0 (GMountSpec, 1); spec->ref_count = 1; spec->items = g_array_new (FALSE, TRUE, sizeof (GMountSpecItem)); spec->mount_prefix = g_strdup ("/"); if (type != NULL) g_mount_spec_set (spec, "type", type); return spec; } /* Takes ownership of passed in data */ GMountSpec * g_mount_spec_new_from_data (GArray *items, char *mount_prefix) { GMountSpec *spec; spec = g_new0 (GMountSpec, 1); spec->ref_count = 1; spec->items = items; if (mount_prefix == NULL) spec->mount_prefix = g_strdup ("/"); else spec->mount_prefix = g_mount_spec_canonicalize_path (mount_prefix); g_array_sort (spec->items, item_compare); return spec; } GMountSpec * g_mount_spec_get_unique_for (GMountSpec *spec) { GMountSpec *unique_spec; if (spec->is_unique) return g_mount_spec_ref (spec); G_LOCK (unique_hash); if (unique_hash == NULL) unique_hash = g_hash_table_new (g_mount_spec_hash, (GEqualFunc)g_mount_spec_equal); unique_spec = g_hash_table_lookup (unique_hash, spec); if (unique_spec == NULL) { spec->is_unique = TRUE; g_hash_table_insert (unique_hash, spec, spec); unique_spec = spec; } g_mount_spec_ref (unique_spec); G_UNLOCK (unique_hash); return unique_spec; } void g_mount_spec_set_mount_prefix (GMountSpec *spec, const char *mount_prefix) { g_free (spec->mount_prefix); spec->mount_prefix = g_mount_spec_canonicalize_path (mount_prefix); } static void add_item (GMountSpec *spec, const char *key, char *value) { GMountSpecItem item; g_return_if_fail (key != NULL); g_return_if_fail (value != NULL); item.key = g_strdup (key); item.value = value; g_array_append_val (spec->items, item); } static void g_mount_spec_set_with_len_internal (GMountSpec *spec, const char *key, const char *value, int value_len, gboolean copy) { int i; char *value_copy; g_return_if_fail (key != NULL); g_return_if_fail (value != NULL); if (copy) { if (value_len == -1) value_copy = g_strdup (value); else value_copy = g_strndup (value, value_len); } else value_copy = (char*) value; if (g_str_equal ("prefix", key)) { g_mount_spec_set_mount_prefix (spec, value_copy); g_free (value_copy); return; } for (i = 0; i < spec->items->len; i++) { GMountSpecItem *item = &g_array_index (spec->items, GMountSpecItem, i); if (strcmp (item->key, key) == 0) { g_free (item->value); item->value = value_copy; return; } } add_item (spec, key, value_copy); g_array_sort (spec->items, item_compare); } void g_mount_spec_set_with_len (GMountSpec *spec, const char *key, const char *value, int value_len) { g_mount_spec_set_with_len_internal (spec, key, value, value_len, TRUE); } void g_mount_spec_set (GMountSpec *spec, const char *key, const char *value) { g_mount_spec_set_with_len (spec, key, value, -1); } void g_mount_spec_take (GMountSpec *spec, const char *key, char *value) { g_mount_spec_set_with_len_internal (spec, key, value, -1, FALSE); } GMountSpec * g_mount_spec_copy (GMountSpec *spec) { GMountSpec *copy; int i; copy = g_mount_spec_new (NULL); g_mount_spec_set_mount_prefix (copy, spec->mount_prefix); for (i = 0; i < spec->items->len; i++) { GMountSpecItem *item = &g_array_index (spec->items, GMountSpecItem, i); g_mount_spec_set (copy, item->key, item->value); } return copy; } GMountSpec * g_mount_spec_ref (GMountSpec *spec) { g_atomic_int_inc (&spec->ref_count); return spec; } void g_mount_spec_unref (GMountSpec *spec) { int i; if (g_atomic_int_dec_and_test (&spec->ref_count)) { G_LOCK (unique_hash); if (unique_hash != NULL && spec->is_unique) g_hash_table_remove (unique_hash, spec); G_UNLOCK (unique_hash); g_free (spec->mount_prefix); for (i = 0; i < spec->items->len; i++) { GMountSpecItem *item = &g_array_index (spec->items, GMountSpecItem, i); g_free (item->key); g_free (item->value); } g_array_free (spec->items, TRUE); g_free (spec); } } GMountSpec * g_mount_spec_from_dbus (GVariant *value) { GMountSpec *spec; const gchar *key; const gchar *mount_prefix; GVariantIter *iter_mount_spec_items; GVariant *v; mount_prefix = NULL; g_variant_get (value, "(^&aya{sv})", &mount_prefix, &iter_mount_spec_items); spec = g_mount_spec_new (NULL); g_free (spec->mount_prefix); spec->mount_prefix = NULL; if (mount_prefix && mount_prefix[0]) spec->mount_prefix = g_strdup (mount_prefix); while (g_variant_iter_loop (iter_mount_spec_items, "{&sv}", &key, &v)) { add_item (spec, key, g_variant_dup_bytestring (v, NULL)); } g_variant_iter_free (iter_mount_spec_items); /* Sort on key */ g_array_sort (spec->items, item_compare); return spec; } GVariant * g_mount_spec_to_dbus_with_path (GMountSpec *spec, const char *path) { GVariantBuilder builder; GVariant *v; int i; g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); for (i = 0; i < spec->items->len; i++) { GMountSpecItem *item = &g_array_index (spec->items, GMountSpecItem, i); g_variant_builder_add_value (&builder, g_variant_new ("{sv}", item->key, g_variant_new_bytestring (item->value))); } v = g_variant_new ("(^aya{sv})", path ? path : "", &builder); g_variant_builder_clear (&builder); return v; } GVariant * g_mount_spec_to_dbus (GMountSpec *spec) { return g_mount_spec_to_dbus_with_path (spec, spec->mount_prefix); } static gboolean items_equal (GArray *a, GArray *b) { int i; if (a->len != b->len) return FALSE; for (i = 0; i < a->len; i++) { GMountSpecItem *item_a = &g_array_index (a, GMountSpecItem, i); GMountSpecItem *item_b = &g_array_index (b, GMountSpecItem, i); if (strcmp (item_a->key, item_b->key) != 0) return FALSE; if (strcmp (item_a->value, item_b->value) != 0) return FALSE; } return TRUE; } static gboolean path_has_prefix (const char *path, const char *prefix) { int prefix_len; if (prefix == NULL) return TRUE; prefix_len = strlen (prefix); if (strncmp (path, prefix, prefix_len) == 0 && (prefix_len == 0 || /* empty prefix always matches */ prefix[prefix_len - 1] == '/' || /* last char in prefix was a /, so it must be in path too */ path[prefix_len] == 0 || path[prefix_len] == '/')) return TRUE; return FALSE; } guint g_mount_spec_hash (gconstpointer _mount) { GMountSpec *mount = (GMountSpec *) _mount; guint hash; int i; hash = 0; if (mount->mount_prefix) hash ^= g_str_hash (mount->mount_prefix); for (i = 0; i < mount->items->len; i++) { GMountSpecItem *item = &g_array_index (mount->items, GMountSpecItem, i); hash ^= g_str_hash (item->value); } return hash; } gboolean g_mount_spec_equal (GMountSpec *mount1, GMountSpec *mount2) { return items_equal (mount1->items, mount2->items) && ((mount1->mount_prefix == mount2->mount_prefix) || (mount1->mount_prefix != NULL && mount2->mount_prefix != NULL && strcmp (mount1->mount_prefix, mount2->mount_prefix) == 0)); } gboolean g_mount_spec_match_with_path (GMountSpec *mount, GMountSpec *spec, const char *path) { if (items_equal (mount->items, spec->items) && path_has_prefix (path, mount->mount_prefix)) return TRUE; return FALSE; } gboolean g_mount_spec_match (GMountSpec *mount, GMountSpec *path) { return g_mount_spec_match_with_path (mount, path, path->mount_prefix); } const char * g_mount_spec_get (GMountSpec *spec, const char *key) { int i; for (i = 0; i < spec->items->len; i++) { GMountSpecItem *item = &g_array_index (spec->items, GMountSpecItem, i); if (strcmp (item->key, key) == 0) return item->value; } return NULL; } const char * g_mount_spec_get_type (GMountSpec *spec) { return g_mount_spec_get (spec, "type"); } char * g_mount_spec_to_string (GMountSpec *spec) { GString *str; int i; gboolean first; if (spec == NULL) return g_strdup ("(null)"); str = g_string_new (g_mount_spec_get_type (spec)); g_string_append_c (str, ':'); first = TRUE; for (i = 0; i < spec->items->len; i++) { GMountSpecItem *item = &g_array_index (spec->items, GMountSpecItem, i); if (strcmp (item->key, "type") == 0) continue; if (!first) g_string_append_c (str, ','); first = FALSE; g_string_append_printf (str, "%s=", item->key); g_string_append_uri_escaped (str, item->value, "$&'()*+", TRUE); } if (strcmp (spec->mount_prefix, "/") != 0) { g_string_append_printf (str, ",prefix="); g_string_append_uri_escaped (str, spec->mount_prefix, "$&'()*+", TRUE); } return g_string_free (str, FALSE); } GMountSpec * g_mount_spec_new_from_string (const gchar *str, GError **error) { GArray *items; GMountSpec *mount_spec; char **kv_pairs; char *mount_prefix; const char *colon; GMountSpecItem item; int i; g_return_val_if_fail (str != NULL, NULL); mount_spec = NULL; mount_prefix = NULL; items = g_array_new (FALSE, TRUE, sizeof (GMountSpecItem)); colon = strchr (str, ':'); if (colon) { item.key = g_strdup ("type"); item.value = g_strndup (str, colon - str); g_array_append_val (items, item); str = colon + 1; } kv_pairs = g_strsplit (str, ",", 0); for (i = 0; kv_pairs[i] != NULL; i++) { char **tokens; tokens = g_strsplit (kv_pairs[i], "=", 0); if (g_strv_length (tokens) != 2) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Encountered invalid key/value pair '%s' while decoding GMountSpec", kv_pairs[i]); g_strfreev (tokens); g_strfreev (kv_pairs); goto fail; } item.value = g_uri_unescape_string (tokens[1], NULL); if (strcmp (tokens[0], "prefix") == 0) { g_free (mount_prefix); mount_prefix = item.value; } else { item.key = g_strdup (tokens[0]); g_array_append_val (items, item); } g_strfreev (tokens); } g_strfreev (kv_pairs); if (mount_prefix == NULL) mount_prefix = g_strdup ("/"); /* this constructor takes ownership of the data we pass in */ mount_spec = g_mount_spec_new_from_data (items, mount_prefix); return mount_spec; fail: for (i = 0; i < items->len; i++) { GMountSpecItem *item = &g_array_index (items, GMountSpecItem, i); g_free (item->key); g_free (item->value); } g_array_free (items, TRUE); g_free (mount_prefix); return NULL; } char * g_mount_spec_canonicalize_path (const char *path) { char *canon, *start, *p, *q; if (*path != '/') canon = g_strconcat ("/", path, NULL); else canon = g_strdup (path); /* Skip initial slash */ start = canon + 1; p = start; while (*p != 0) { if (p[0] == '.' && (p[1] == 0 || p[1] == '/')) { memmove (p, p+1, strlen (p+1)+1); } else if (p[0] == '.' && p[1] == '.' && (p[2] == 0 || p[2] == '/')) { q = p + 2; /* Skip previous separator */ p = p - 2; if (p < start) p = start; while (p > start && *p != '/') p--; if (*p == '/') p++; memmove (p, q, strlen (q)+1); } else { /* Skip until next separator */ while (*p != 0 && *p != '/') p++; /* Keep one separator */ if (*p != 0) p++; } /* Remove additional separators */ q = p; while (*q && *q == '/') q++; if (p != q) memmove (p, q, strlen (q)+1); } /* Remove trailing slashes */ if (p > start && *(p-1) == '/') *(p-1) = 0; return canon; } GType g_type_mount_spec_get_gtype (void) { static GType type_id = 0; if (type_id == 0) type_id = g_boxed_type_register_static (g_intern_static_string ("GMountSpec"), (GBoxedCopyFunc) g_mount_spec_ref, (GBoxedFreeFunc) g_mount_spec_unref); return type_id; }