1 /* ide-persistent-map.c
2  *
3  * Copyright 2017 Anoop Chandu <anoopchandu96@gmail.com>
4  * Copyright 2017-2019 Christian Hergert <chergert@redhat.com>
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  * SPDX-License-Identifier: GPL-3.0-or-later
20  */
21 
22 #define G_LOG_DOMAIN "ide-persistent-map"
23 
24 #include "config.h"
25 
26 #include <libide-threading.h>
27 
28 #include "ide-persistent-map.h"
29 
30 typedef struct
31 {
32   guint32 key;
33   guint32 value;
34 } KVPair;
35 
36 struct _IdePersistentMap
37 {
38   GObject            parent;
39 
40   GMappedFile       *mapped_file;
41 
42   GVariant          *data;
43 
44   GVariant          *keys_var;
45   const gchar       *keys;
46 
47   GVariant          *values;
48 
49   GVariant          *kvpairs_var;
50   const KVPair      *kvpairs;
51 
52   GVariantDict      *metadata;
53 
54   gsize              n_kvpairs;
55 
56   gint32             byte_order;
57 
58   guint              load_called : 1;
59   guint              loaded : 1;
60 };
61 
62 G_STATIC_ASSERT (sizeof (KVPair) == 8);
63 
G_DEFINE_FINAL_TYPE(IdePersistentMap,ide_persistent_map,G_TYPE_OBJECT)64 G_DEFINE_FINAL_TYPE (IdePersistentMap, ide_persistent_map, G_TYPE_OBJECT)
65 
66 static void
67 ide_persistent_map_load_file_worker (IdeTask      *task,
68                                      gpointer      source_object,
69                                      gpointer      task_data,
70                                      GCancellable *cancellable)
71 {
72   IdePersistentMap *self = source_object;
73   GFile *file = task_data;
74   g_autofree gchar *path = NULL;
75   g_autoptr(GMappedFile) mapped_file = NULL;
76   g_autoptr(GVariant) data = NULL;
77   g_autoptr(GVariant) keys = NULL;
78   g_autoptr(GVariant) values = NULL;
79   g_autoptr(GVariant) metadata = NULL;
80   g_autoptr(GVariant) kvpairs = NULL;
81   g_autoptr(GError) error = NULL;
82   g_autoptr(GVariantDict) dict = NULL;
83   gint32 version;
84   gsize n_elements;
85 
86   g_assert (IDE_IS_TASK (task));
87   g_assert (IDE_IS_PERSISTENT_MAP (self));
88   g_assert (G_IS_FILE (file));
89   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
90   g_assert (self->loaded == FALSE);
91 
92   self->loaded = TRUE;
93 
94   if (!g_file_is_native (file) || NULL == (path = g_file_get_path (file)))
95     {
96       ide_task_return_new_error (task,
97                                  G_IO_ERROR,
98                                  G_IO_ERROR_INVALID_FILENAME,
99                                  "Index must be a local file");
100       return;
101     }
102 
103   mapped_file = g_mapped_file_new (path, FALSE, &error);
104 
105   if (mapped_file == NULL)
106     {
107       ide_task_return_error (task, g_steal_pointer (&error));
108       return;
109     }
110 
111   data = g_variant_new_from_data (G_VARIANT_TYPE_VARDICT,
112                                   g_mapped_file_get_contents (mapped_file),
113                                   g_mapped_file_get_length (mapped_file),
114                                   FALSE, NULL, NULL);
115 
116   if (data == NULL)
117     {
118       ide_task_return_new_error (task,
119                                  G_IO_ERROR,
120                                  G_IO_ERROR_INVAL,
121                                  "Failed to parse GVariant");
122       return;
123     }
124 
125   g_variant_take_ref (data);
126 
127   dict = g_variant_dict_new (data);
128 
129   if (!g_variant_dict_lookup (dict, "version", "i", &version) || version != 2)
130     {
131       ide_task_return_new_error (task,
132                                  G_IO_ERROR,
133                                  G_IO_ERROR_INVAL,
134                                  "Version mismatch in gvariant. Got %d, expected 1",
135                                  version);
136       return;
137     }
138 
139   keys = g_variant_dict_lookup_value (dict, "keys", G_VARIANT_TYPE_ARRAY);
140   values = g_variant_dict_lookup_value (dict, "values", G_VARIANT_TYPE_ARRAY);
141   kvpairs = g_variant_dict_lookup_value (dict, "kvpairs", G_VARIANT_TYPE_ARRAY);
142   metadata = g_variant_dict_lookup_value (dict, "metadata", G_VARIANT_TYPE_VARDICT);
143 
144   if (!g_variant_dict_lookup (dict, "byte-order", "i", &self->byte_order))
145     self->byte_order = G_BYTE_ORDER;
146 
147   if (keys == NULL || values == NULL || kvpairs == NULL || metadata == NULL || !self->byte_order)
148     {
149       ide_task_return_new_error (task,
150                                  G_IO_ERROR,
151                                  G_IO_ERROR_INVAL,
152                                  "Invalid GVariant index");
153       return;
154     }
155 
156   self->keys = g_variant_get_fixed_array (keys, &n_elements, sizeof (guint8));
157   self->kvpairs = g_variant_get_fixed_array (kvpairs, &self->n_kvpairs, sizeof (KVPair));
158 
159   self->mapped_file = g_steal_pointer (&mapped_file);
160   self->data = g_steal_pointer (&data);
161   self->keys_var = g_steal_pointer (&keys);
162   self->values = g_steal_pointer (&values);
163   self->kvpairs_var = g_steal_pointer (&kvpairs);
164   self->metadata = g_variant_dict_new (metadata);
165 
166   g_assert (!g_variant_is_floating (self->data));
167   g_assert (!g_variant_is_floating (self->keys_var));
168   g_assert (!g_variant_is_floating (self->values));
169   g_assert (!g_variant_is_floating (self->kvpairs_var));
170   g_assert (self->keys != NULL);
171   g_assert (self->kvpairs != NULL);
172   g_assert (self->metadata != NULL);
173 
174   ide_task_return_boolean (task, TRUE);
175 }
176 
177 gboolean
ide_persistent_map_load_file(IdePersistentMap * self,GFile * file,GCancellable * cancellable,GError ** error)178 ide_persistent_map_load_file (IdePersistentMap *self,
179                               GFile            *file,
180                               GCancellable     *cancellable,
181                               GError          **error)
182 {
183   g_autoptr(IdeTask) task = NULL;
184 
185   g_return_val_if_fail (IDE_IS_PERSISTENT_MAP (self), FALSE);
186   g_return_val_if_fail (self->load_called == FALSE, FALSE);
187   g_return_val_if_fail (G_IS_FILE (file), FALSE);
188   g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
189 
190   self->load_called = TRUE;
191 
192   task = ide_task_new (self, cancellable, NULL, NULL);
193   ide_task_set_source_tag (task, ide_persistent_map_load_file);
194   ide_task_set_priority (task, G_PRIORITY_LOW);
195   ide_task_set_kind (task, IDE_TASK_KIND_INDEXER);
196   ide_persistent_map_load_file_worker (task, self, file, cancellable);
197 
198   return ide_task_propagate_boolean (task, error);
199 }
200 
201 void
ide_persistent_map_load_file_async(IdePersistentMap * self,GFile * file,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)202 ide_persistent_map_load_file_async (IdePersistentMap    *self,
203                                     GFile               *file,
204                                     GCancellable        *cancellable,
205                                     GAsyncReadyCallback  callback,
206                                     gpointer             user_data)
207 {
208   g_autoptr(IdeTask) task = NULL;
209 
210   g_return_if_fail (IDE_IS_PERSISTENT_MAP (self));
211   g_return_if_fail (self->load_called == FALSE);
212   g_return_if_fail (G_IS_FILE (file));
213   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
214 
215   self->load_called = TRUE;
216 
217   task = ide_task_new (self, cancellable, callback, user_data);
218   ide_task_set_source_tag (task, ide_persistent_map_load_file_async);
219   ide_task_set_priority (task, G_PRIORITY_LOW);
220   ide_task_set_kind (task, IDE_TASK_KIND_INDEXER);
221   ide_task_set_task_data (task, g_object_ref (file), g_object_unref);
222   ide_task_run_in_thread (task, ide_persistent_map_load_file_worker);
223 }
224 
225 /**
226  * ide_persistent_map_load_file_finish:
227  * @self: an #IdePersistentMap
228  * @result: a #GAsyncResult provided to callback
229  * @error: a location for a #GError, or %NULL
230  *
231  * Returns: Whether file is loaded or not.
232  *
233  * Since: 3.32
234  */
235 gboolean
ide_persistent_map_load_file_finish(IdePersistentMap * self,GAsyncResult * result,GError ** error)236 ide_persistent_map_load_file_finish (IdePersistentMap  *self,
237                                      GAsyncResult      *result,
238                                      GError           **error)
239 {
240   g_return_val_if_fail (IDE_IS_PERSISTENT_MAP (self), FALSE);
241   g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
242 
243   return ide_task_propagate_boolean (IDE_TASK (result), error);
244 }
245 
246 /**
247  * ide_persistent_map_lookup_value:
248  * @self: An #IdePersistentMap instance.
249  * @key: key to lookup value
250  *
251  * Returns: (transfer full) : value associalted with @key.
252  *
253  * Since: 3.32
254  */
255 GVariant *
ide_persistent_map_lookup_value(IdePersistentMap * self,const gchar * key)256 ide_persistent_map_lookup_value (IdePersistentMap *self,
257                                  const gchar      *key)
258 {
259   g_autoptr(GVariant) value = NULL;
260   gint64 l;
261   gint64 r;
262 
263   g_return_val_if_fail (IDE_IS_PERSISTENT_MAP (self), NULL);
264   g_return_val_if_fail (self->loaded, NULL);
265   g_return_val_if_fail (self != NULL, NULL);
266   g_return_val_if_fail (self->kvpairs != NULL, NULL);
267   g_return_val_if_fail (self->keys != NULL, NULL);
268   g_return_val_if_fail (self->values != NULL, NULL);
269   g_return_val_if_fail (self->n_kvpairs < G_MAXINT64, NULL);
270 
271   if (self->n_kvpairs == 0)
272     return NULL;
273 
274   /* unsigned long to signed long */
275   r = (gint64)self->n_kvpairs - 1;
276   l = 0;
277 
278   while (l <= r)
279     {
280       gint64 m;
281       gint32 k;
282       gint cmp;
283 
284       m = (l + r) / 2;
285       g_assert (m >= 0);
286 
287       k = self->kvpairs [m].key;
288       g_assert (k >= 0);
289 
290       cmp = g_strcmp0 (key, &self->keys [k]);
291 
292       if (cmp < 0)
293         r = m - 1;
294       else if (cmp > 0)
295         l = m + 1;
296       else
297         {
298           value = g_variant_get_child_value (self->values, self->kvpairs [m].value);
299           break;
300         }
301     }
302 
303   if (value != NULL && self->byte_order != G_BYTE_ORDER)
304     return g_variant_byteswap (value);
305 
306   return g_steal_pointer (&value);
307 }
308 
309 gint64
ide_persistent_map_builder_get_metadata_int64(IdePersistentMap * self,const gchar * key)310 ide_persistent_map_builder_get_metadata_int64 (IdePersistentMap *self,
311                                                const gchar      *key)
312 {
313   guint64 value = 0;
314 
315   g_return_val_if_fail (IDE_IS_PERSISTENT_MAP (self), 0);
316   g_return_val_if_fail (key != NULL, 0);
317   g_return_val_if_fail (self->metadata != NULL, 0);
318 
319   if (!g_variant_dict_lookup (self->metadata, key, "x", &value))
320     return 0;
321 
322   return value;
323 }
324 
325 static void
ide_persistent_map_finalize(GObject * object)326 ide_persistent_map_finalize (GObject *object)
327 {
328   IdePersistentMap *self = (IdePersistentMap *)object;
329 
330   self->keys = NULL;
331   self->kvpairs = NULL;
332 
333   g_clear_pointer (&self->data, g_variant_unref);
334   g_clear_pointer (&self->keys_var, g_variant_unref);
335   g_clear_pointer (&self->values, g_variant_unref);
336   g_clear_pointer (&self->kvpairs_var, g_variant_unref);
337   g_clear_pointer (&self->metadata, g_variant_dict_unref);
338   g_clear_pointer (&self->mapped_file, g_mapped_file_unref);
339 
340   G_OBJECT_CLASS (ide_persistent_map_parent_class)->finalize (object);
341 }
342 
343 static void
ide_persistent_map_init(IdePersistentMap * self)344 ide_persistent_map_init (IdePersistentMap *self)
345 {
346 }
347 
348 static void
ide_persistent_map_class_init(IdePersistentMapClass * self)349 ide_persistent_map_class_init (IdePersistentMapClass *self)
350 {
351   GObjectClass *object_class = G_OBJECT_CLASS (self);
352 
353   object_class->finalize = ide_persistent_map_finalize;
354 }
355 
356 IdePersistentMap *
ide_persistent_map_new(void)357 ide_persistent_map_new (void)
358 {
359   return g_object_new (IDE_TYPE_PERSISTENT_MAP, NULL);
360 }
361