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