1 /* GIO - GLib Input, Output and Streaming Library
2 *
3 * Copyright (C) 2006-2007 Red Hat, Inc.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General
16 * Public License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 *
20 * Author: Alexander Larsson <alexl@redhat.com>
21 */
22
23 #include <config.h>
24
25 #include <string.h>
26 #include <stdlib.h>
27
28 #include <glib/gi18n-lib.h>
29
30 #include "gmountspec.h"
31
32 static GHashTable *unique_hash = NULL;
33 G_LOCK_DEFINE_STATIC(unique_hash);
34
35 static int
item_compare(const void * _a,const void * _b)36 item_compare (const void *_a, const void *_b)
37 {
38 const GMountSpecItem *a = _a;
39 const GMountSpecItem *b = _b;
40
41 return strcmp (a->key, b->key);
42 }
43
44 GMountSpec *
g_mount_spec_new(const char * type)45 g_mount_spec_new (const char *type)
46 {
47 GMountSpec *spec;
48
49 spec = g_new0 (GMountSpec, 1);
50 spec->ref_count = 1;
51 spec->items = g_array_new (FALSE, TRUE, sizeof (GMountSpecItem));
52 spec->mount_prefix = g_strdup ("/");
53
54 if (type != NULL)
55 g_mount_spec_set (spec, "type", type);
56
57 return spec;
58 }
59
60 /* Takes ownership of passed in data */
61 GMountSpec *
g_mount_spec_new_from_data(GArray * items,char * mount_prefix)62 g_mount_spec_new_from_data (GArray *items,
63 char *mount_prefix)
64 {
65 GMountSpec *spec;
66
67 spec = g_new0 (GMountSpec, 1);
68 spec->ref_count = 1;
69 spec->items = items;
70 if (mount_prefix == NULL)
71 spec->mount_prefix = g_strdup ("/");
72 else
73 spec->mount_prefix = g_mount_spec_canonicalize_path (mount_prefix);
74
75 g_array_sort (spec->items, item_compare);
76
77 return spec;
78 }
79
80 GMountSpec *
g_mount_spec_get_unique_for(GMountSpec * spec)81 g_mount_spec_get_unique_for (GMountSpec *spec)
82 {
83 GMountSpec *unique_spec;
84
85 if (spec->is_unique)
86 return g_mount_spec_ref (spec);
87
88 G_LOCK (unique_hash);
89
90 if (unique_hash == NULL)
91 unique_hash = g_hash_table_new (g_mount_spec_hash, (GEqualFunc)g_mount_spec_equal);
92
93 unique_spec = g_hash_table_lookup (unique_hash, spec);
94
95 if (unique_spec == NULL)
96 {
97 spec->is_unique = TRUE;
98 g_hash_table_insert (unique_hash, spec, spec);
99 unique_spec = spec;
100 }
101
102 g_mount_spec_ref (unique_spec);
103
104 G_UNLOCK (unique_hash);
105
106 return unique_spec;
107 }
108
109 void
g_mount_spec_set_mount_prefix(GMountSpec * spec,const char * mount_prefix)110 g_mount_spec_set_mount_prefix (GMountSpec *spec,
111 const char *mount_prefix)
112 {
113 g_free (spec->mount_prefix);
114 spec->mount_prefix = g_mount_spec_canonicalize_path (mount_prefix);
115 }
116
117
118 static void
add_item(GMountSpec * spec,const char * key,char * value)119 add_item (GMountSpec *spec,
120 const char *key,
121 char *value)
122 {
123 GMountSpecItem item;
124
125 g_return_if_fail (key != NULL);
126 g_return_if_fail (value != NULL);
127
128 item.key = g_strdup (key);
129 item.value = value;
130
131 g_array_append_val (spec->items, item);
132 }
133
134 static void
g_mount_spec_set_with_len_internal(GMountSpec * spec,const char * key,const char * value,int value_len,gboolean copy)135 g_mount_spec_set_with_len_internal (GMountSpec *spec,
136 const char *key,
137 const char *value,
138 int value_len,
139 gboolean copy)
140 {
141 int i;
142 char *value_copy;
143
144 g_return_if_fail (key != NULL);
145 g_return_if_fail (value != NULL);
146
147 if (copy)
148 {
149 if (value_len == -1)
150 value_copy = g_strdup (value);
151 else
152 value_copy = g_strndup (value, value_len);
153 }
154 else
155 value_copy = (char*) value;
156
157 if (g_str_equal ("prefix", key))
158 {
159 g_mount_spec_set_mount_prefix (spec, value_copy);
160 g_free (value_copy);
161 return;
162 }
163
164 for (i = 0; i < spec->items->len; i++)
165 {
166 GMountSpecItem *item = &g_array_index (spec->items, GMountSpecItem, i);
167 if (strcmp (item->key, key) == 0)
168 {
169 g_free (item->value);
170 item->value = value_copy;
171 return;
172 }
173 }
174
175 add_item (spec, key, value_copy);
176 g_array_sort (spec->items, item_compare);
177 }
178
179 void
g_mount_spec_set_with_len(GMountSpec * spec,const char * key,const char * value,int value_len)180 g_mount_spec_set_with_len (GMountSpec *spec,
181 const char *key,
182 const char *value,
183 int value_len)
184 {
185 g_mount_spec_set_with_len_internal (spec, key, value, value_len, TRUE);
186 }
187
188 void
g_mount_spec_set(GMountSpec * spec,const char * key,const char * value)189 g_mount_spec_set (GMountSpec *spec,
190 const char *key,
191 const char *value)
192 {
193 g_mount_spec_set_with_len (spec, key, value, -1);
194 }
195
196 void
g_mount_spec_take(GMountSpec * spec,const char * key,char * value)197 g_mount_spec_take (GMountSpec *spec,
198 const char *key,
199 char *value)
200 {
201 g_mount_spec_set_with_len_internal (spec, key, value, -1, FALSE);
202 }
203
204 GMountSpec *
g_mount_spec_copy(GMountSpec * spec)205 g_mount_spec_copy (GMountSpec *spec)
206 {
207 GMountSpec *copy;
208 int i;
209
210 copy = g_mount_spec_new (NULL);
211 g_mount_spec_set_mount_prefix (copy, spec->mount_prefix);
212
213 for (i = 0; i < spec->items->len; i++)
214 {
215 GMountSpecItem *item = &g_array_index (spec->items, GMountSpecItem, i);
216 g_mount_spec_set (copy, item->key, item->value);
217 }
218
219 return copy;
220 }
221
222 GMountSpec *
g_mount_spec_ref(GMountSpec * spec)223 g_mount_spec_ref (GMountSpec *spec)
224 {
225 g_atomic_int_inc (&spec->ref_count);
226 return spec;
227 }
228
229 void
g_mount_spec_unref(GMountSpec * spec)230 g_mount_spec_unref (GMountSpec *spec)
231 {
232 int i;
233
234 if (g_atomic_int_dec_and_test (&spec->ref_count))
235 {
236 G_LOCK (unique_hash);
237 if (unique_hash != NULL &&
238 spec->is_unique)
239 g_hash_table_remove (unique_hash, spec);
240 G_UNLOCK (unique_hash);
241
242 g_free (spec->mount_prefix);
243 for (i = 0; i < spec->items->len; i++)
244 {
245 GMountSpecItem *item = &g_array_index (spec->items, GMountSpecItem, i);
246 g_free (item->key);
247 g_free (item->value);
248 }
249 g_array_free (spec->items, TRUE);
250
251 g_free (spec);
252 }
253 }
254
255 GMountSpec *
g_mount_spec_from_dbus(GVariant * value)256 g_mount_spec_from_dbus (GVariant *value)
257 {
258 GMountSpec *spec;
259 const gchar *key;
260 const gchar *mount_prefix;
261 GVariantIter *iter_mount_spec_items;
262 GVariant *v;
263
264 mount_prefix = NULL;
265 g_variant_get (value, "(^&aya{sv})",
266 &mount_prefix,
267 &iter_mount_spec_items);
268
269 spec = g_mount_spec_new (NULL);
270 g_free (spec->mount_prefix);
271 spec->mount_prefix = NULL;
272 if (mount_prefix && mount_prefix[0])
273 spec->mount_prefix = g_strdup (mount_prefix);
274
275 while (g_variant_iter_loop (iter_mount_spec_items, "{&sv}", &key, &v))
276 {
277 add_item (spec, key, g_variant_dup_bytestring (v, NULL));
278 }
279
280 g_variant_iter_free (iter_mount_spec_items);
281
282 /* Sort on key */
283 g_array_sort (spec->items, item_compare);
284
285 return spec;
286 }
287
288 GVariant *
g_mount_spec_to_dbus_with_path(GMountSpec * spec,const char * path)289 g_mount_spec_to_dbus_with_path (GMountSpec *spec,
290 const char *path)
291 {
292 GVariantBuilder builder;
293 GVariant *v;
294 int i;
295
296 g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
297 for (i = 0; i < spec->items->len; i++)
298 {
299 GMountSpecItem *item = &g_array_index (spec->items, GMountSpecItem, i);
300
301 g_variant_builder_add_value (&builder, g_variant_new ("{sv}",
302 item->key,
303 g_variant_new_bytestring (item->value)));
304 }
305
306 v = g_variant_new ("(^aya{sv})",
307 path ? path : "",
308 &builder);
309 g_variant_builder_clear (&builder);
310
311 return v;
312 }
313
314 GVariant *
g_mount_spec_to_dbus(GMountSpec * spec)315 g_mount_spec_to_dbus (GMountSpec *spec)
316 {
317 return g_mount_spec_to_dbus_with_path (spec, spec->mount_prefix);
318 }
319
320 static gboolean
items_equal(GArray * a,GArray * b)321 items_equal (GArray *a,
322 GArray *b)
323 {
324 int i;
325
326 if (a->len != b->len)
327 return FALSE;
328
329 for (i = 0; i < a->len; i++)
330 {
331 GMountSpecItem *item_a = &g_array_index (a, GMountSpecItem, i);
332 GMountSpecItem *item_b = &g_array_index (b, GMountSpecItem, i);
333
334 if (strcmp (item_a->key, item_b->key) != 0)
335 return FALSE;
336 if (strcmp (item_a->value, item_b->value) != 0)
337 return FALSE;
338 }
339
340 return TRUE;
341 }
342
343 static gboolean
path_has_prefix(const char * path,const char * prefix)344 path_has_prefix (const char *path,
345 const char *prefix)
346 {
347 int prefix_len;
348
349 if (prefix == NULL)
350 return TRUE;
351
352 prefix_len = strlen (prefix);
353
354 if (strncmp (path, prefix, prefix_len) == 0 &&
355 (prefix_len == 0 || /* empty prefix always matches */
356 prefix[prefix_len - 1] == '/' || /* last char in prefix was a /, so it must be in path too */
357 path[prefix_len] == 0 ||
358 path[prefix_len] == '/'))
359 return TRUE;
360
361 return FALSE;
362 }
363
364 guint
g_mount_spec_hash(gconstpointer _mount)365 g_mount_spec_hash (gconstpointer _mount)
366 {
367 GMountSpec *mount = (GMountSpec *) _mount;
368 guint hash;
369 int i;
370
371 hash = 0;
372 if (mount->mount_prefix)
373 hash ^= g_str_hash (mount->mount_prefix);
374
375 for (i = 0; i < mount->items->len; i++)
376 {
377 GMountSpecItem *item = &g_array_index (mount->items, GMountSpecItem, i);
378 hash ^= g_str_hash (item->value);
379 }
380
381 return hash;
382 }
383
384 gboolean
g_mount_spec_equal(GMountSpec * mount1,GMountSpec * mount2)385 g_mount_spec_equal (GMountSpec *mount1,
386 GMountSpec *mount2)
387 {
388 return items_equal (mount1->items, mount2->items) &&
389 ((mount1->mount_prefix == mount2->mount_prefix) ||
390 (mount1->mount_prefix != NULL && mount2->mount_prefix != NULL &&
391 strcmp (mount1->mount_prefix, mount2->mount_prefix) == 0));
392 }
393
394 gboolean
g_mount_spec_match_with_path(GMountSpec * mount,GMountSpec * spec,const char * path)395 g_mount_spec_match_with_path (GMountSpec *mount,
396 GMountSpec *spec,
397 const char *path)
398 {
399 if (items_equal (mount->items, spec->items) &&
400 path_has_prefix (path, mount->mount_prefix))
401 return TRUE;
402 return FALSE;
403 }
404
405 gboolean
g_mount_spec_match(GMountSpec * mount,GMountSpec * path)406 g_mount_spec_match (GMountSpec *mount,
407 GMountSpec *path)
408 {
409 return g_mount_spec_match_with_path (mount, path, path->mount_prefix);
410 }
411
412 const char *
g_mount_spec_get(GMountSpec * spec,const char * key)413 g_mount_spec_get (GMountSpec *spec,
414 const char *key)
415 {
416 int i;
417
418 for (i = 0; i < spec->items->len; i++)
419 {
420 GMountSpecItem *item = &g_array_index (spec->items, GMountSpecItem, i);
421
422 if (strcmp (item->key, key) == 0)
423 return item->value;
424 }
425
426 return NULL;
427 }
428
429 const char *
g_mount_spec_get_type(GMountSpec * spec)430 g_mount_spec_get_type (GMountSpec *spec)
431 {
432 return g_mount_spec_get (spec, "type");
433 }
434
435 char *
g_mount_spec_to_string(GMountSpec * spec)436 g_mount_spec_to_string (GMountSpec *spec)
437 {
438 GString *str;
439 int i;
440 gboolean first;
441
442 if (spec == NULL)
443 return g_strdup ("(null)");
444
445 str = g_string_new (g_mount_spec_get_type (spec));
446 g_string_append_c (str, ':');
447
448 first = TRUE;
449 for (i = 0; i < spec->items->len; i++)
450 {
451 GMountSpecItem *item = &g_array_index (spec->items, GMountSpecItem, i);
452
453 if (strcmp (item->key, "type") == 0)
454 continue;
455
456 if (!first)
457 g_string_append_c (str, ',');
458 first = FALSE;
459
460 g_string_append_printf (str, "%s=", item->key);
461 g_string_append_uri_escaped (str, item->value,
462 "$&'()*+",
463 TRUE);
464 }
465
466 if (strcmp (spec->mount_prefix, "/") != 0)
467 {
468 g_string_append_printf (str, ",prefix=");
469 g_string_append_uri_escaped (str, spec->mount_prefix,
470 "$&'()*+",
471 TRUE);
472 }
473
474 return g_string_free (str, FALSE);
475 }
476
477 GMountSpec *
g_mount_spec_new_from_string(const gchar * str,GError ** error)478 g_mount_spec_new_from_string (const gchar *str,
479 GError **error)
480 {
481 GArray *items;
482 GMountSpec *mount_spec;
483 char **kv_pairs;
484 char *mount_prefix;
485 const char *colon;
486 GMountSpecItem item;
487 int i;
488
489 g_return_val_if_fail (str != NULL, NULL);
490
491 mount_spec = NULL;
492 mount_prefix = NULL;
493 items = g_array_new (FALSE, TRUE, sizeof (GMountSpecItem));
494
495 colon = strchr (str, ':');
496 if (colon)
497 {
498 item.key = g_strdup ("type");
499 item.value = g_strndup (str, colon - str);
500 g_array_append_val (items, item);
501 str = colon + 1;
502 }
503
504 kv_pairs = g_strsplit (str, ",", 0);
505 for (i = 0; kv_pairs[i] != NULL; i++)
506 {
507 char **tokens;
508
509 tokens = g_strsplit (kv_pairs[i], "=", 0);
510 if (g_strv_length (tokens) != 2)
511 {
512 g_set_error (error,
513 G_IO_ERROR,
514 G_IO_ERROR_INVALID_ARGUMENT,
515 "Encountered invalid key/value pair '%s' while decoding GMountSpec",
516 kv_pairs[i]);
517 g_strfreev (tokens);
518 g_strfreev (kv_pairs);
519 goto fail;
520 }
521
522 item.value = g_uri_unescape_string (tokens[1], NULL);
523 if (strcmp (tokens[0], "prefix") == 0)
524 {
525 g_free (mount_prefix);
526 mount_prefix = item.value;
527 }
528 else
529 {
530 item.key = g_strdup (tokens[0]);
531 g_array_append_val (items, item);
532 }
533
534 g_strfreev (tokens);
535 }
536 g_strfreev (kv_pairs);
537
538 if (mount_prefix == NULL)
539 mount_prefix = g_strdup ("/");
540
541 /* this constructor takes ownership of the data we pass in */
542 mount_spec = g_mount_spec_new_from_data (items,
543 mount_prefix);
544
545 return mount_spec;
546
547 fail:
548 for (i = 0; i < items->len; i++)
549 {
550 GMountSpecItem *item = &g_array_index (items, GMountSpecItem, i);
551 g_free (item->key);
552 g_free (item->value);
553 }
554 g_array_free (items, TRUE);
555 g_free (mount_prefix);
556 return NULL;
557 }
558
559
560 char *
g_mount_spec_canonicalize_path(const char * path)561 g_mount_spec_canonicalize_path (const char *path)
562 {
563 char *canon, *start, *p, *q;
564
565 if (*path != '/')
566 canon = g_strconcat ("/", path, NULL);
567 else
568 canon = g_strdup (path);
569
570 /* Skip initial slash */
571 start = canon + 1;
572
573 p = start;
574 while (*p != 0)
575 {
576 if (p[0] == '.' && (p[1] == 0 || p[1] == '/'))
577 {
578 memmove (p, p+1, strlen (p+1)+1);
579 }
580 else if (p[0] == '.' && p[1] == '.' && (p[2] == 0 || p[2] == '/'))
581 {
582 q = p + 2;
583 /* Skip previous separator */
584 p = p - 2;
585 if (p < start)
586 p = start;
587 while (p > start && *p != '/')
588 p--;
589 if (*p == '/')
590 p++;
591 memmove (p, q, strlen (q)+1);
592 }
593 else
594 {
595 /* Skip until next separator */
596 while (*p != 0 && *p != '/')
597 p++;
598
599 /* Keep one separator */
600 if (*p != 0)
601 p++;
602 }
603
604 /* Remove additional separators */
605 q = p;
606 while (*q && *q == '/')
607 q++;
608
609 if (p != q)
610 memmove (p, q, strlen (q)+1);
611 }
612
613 /* Remove trailing slashes */
614 if (p > start && *(p-1) == '/')
615 *(p-1) = 0;
616
617 return canon;
618 }
619
620 GType
g_type_mount_spec_get_gtype(void)621 g_type_mount_spec_get_gtype (void)
622 {
623 static GType type_id = 0;
624
625 if (type_id == 0)
626 type_id = g_boxed_type_register_static (g_intern_static_string ("GMountSpec"),
627 (GBoxedCopyFunc) g_mount_spec_ref,
628 (GBoxedFreeFunc) g_mount_spec_unref);
629 return type_id;
630 }
631