1 /* ide-gfile.c
2  *
3  * Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program 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
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  * SPDX-License-Identifier: GPL-3.0-or-later
19  */
20 
21 #define G_LOG_DOMAIN "ide-gfile"
22 
23 #include "config.h"
24 
25 #include <glib/gstdio.h>
26 #include <errno.h>
27 
28 #include <libide-threading.h>
29 
30 #include "ide-gfile.h"
31 #include "ide-gfile-private.h"
32 
33 static GPtrArray *g_ignored;
34 G_LOCK_DEFINE_STATIC (ignored);
35 
36 static GPtrArray *
get_ignored_locked(void)37 get_ignored_locked (void)
38 {
39   static const gchar *ignored_patterns[] = {
40     /* Ignore Gio temporary files */
41     ".goutputstream-*",
42     /* Ignore minified JS */
43     "*.min.js",
44     "*.min.js.*",
45   };
46 
47   if (g_ignored == NULL)
48     {
49       g_ignored = g_ptr_array_new ();
50       for (guint i = 0; i < G_N_ELEMENTS (ignored_patterns); i++)
51         g_ptr_array_add (g_ignored, g_pattern_spec_new (ignored_patterns[i]));
52     }
53 
54   return g_ignored;
55 }
56 
57 /**
58  * ide_g_file_add_ignored_pattern:
59  * @pattern: a #GPatternSpec style glob pattern
60  *
61  * Adds a pattern that can be used to match ignored files. These are global
62  * to the application, so they should only include well-known ignored files
63  * such as those internal to a build system, or version control system, and
64  * similar.
65  *
66  * Since: 3.32
67  */
68 void
ide_g_file_add_ignored_pattern(const gchar * pattern)69 ide_g_file_add_ignored_pattern (const gchar *pattern)
70 {
71   G_LOCK (ignored);
72   g_ptr_array_add (get_ignored_locked (), g_pattern_spec_new (pattern));
73   G_UNLOCK (ignored);
74 }
75 
76 /**
77  * ide_path_is_ignored:
78  * @path: the path to the file
79  *
80  * Checks if @path should be ignored using the global file
81  * ignores registered with Builder.
82  *
83  * Returns: %TRUE if @path should be ignored, otherwise %FALSE
84  *
85  * Since: 3.32
86  */
87 gboolean
ide_path_is_ignored(const gchar * path)88 ide_path_is_ignored (const gchar *path)
89 {
90   g_autofree gchar *name = NULL;
91   g_autofree gchar *reversed = NULL;
92   GPtrArray *ignored;
93   gsize len;
94   gboolean ret = FALSE;
95 
96   name = g_path_get_basename (path);
97   len = strlen (name);
98   reversed = g_utf8_strreverse (name, len);
99 
100   /* Ignore empty files for whatever reason */
101   if (ide_str_empty0 (name))
102     return TRUE;
103 
104   /* Ignore builtin backup files by GIO */
105   if (name[len - 1] == '~')
106     return TRUE;
107 
108   G_LOCK (ignored);
109 
110   ignored = get_ignored_locked ();
111 
112   for (guint i = 0; i < ignored->len; i++)
113     {
114       GPatternSpec *pattern_spec = g_ptr_array_index (ignored, i);
115 
116       if (g_pattern_spec_match (pattern_spec, len, name, reversed))
117         {
118           ret = TRUE;
119           break;
120         }
121     }
122 
123   G_UNLOCK (ignored);
124 
125   return ret;
126 }
127 
128 /**
129  * ide_g_file_is_ignored:
130  * @file: a #GFile
131  *
132  * Checks if @file should be ignored using the internal ignore rules.  If you
133  * care about the version control system, see #IdeVcs and ide_vcs_is_ignored().
134  *
135  * Returns: %TRUE if @file should be ignored; otherwise %FALSE.
136  *
137  * Since: 3.32
138  */
139 gboolean
ide_g_file_is_ignored(GFile * file)140 ide_g_file_is_ignored (GFile *file)
141 {
142   g_autofree gchar *name = NULL;
143   g_autofree gchar *reversed = NULL;
144   GPtrArray *ignored;
145   gsize len;
146   gboolean ret = FALSE;
147 
148   name = g_file_get_basename (file);
149   len = strlen (name);
150   reversed = g_utf8_strreverse (name, len);
151 
152   /* Ignore empty files for whatever reason */
153   if (ide_str_empty0 (name))
154     return TRUE;
155 
156   /* Ignore builtin backup files by GIO */
157   if (name[len - 1] == '~')
158     return TRUE;
159 
160   G_LOCK (ignored);
161 
162   ignored = get_ignored_locked ();
163 
164   for (guint i = 0; i < ignored->len; i++)
165     {
166       GPatternSpec *pattern_spec = g_ptr_array_index (ignored, i);
167 
168       if (g_pattern_spec_match (pattern_spec, len, name, reversed))
169         {
170           ret = TRUE;
171           break;
172         }
173     }
174 
175   G_UNLOCK (ignored);
176 
177   return ret;
178 }
179 
180 /**
181  * ide_g_file_get_uncanonical_relative_path:
182  * @file: a #GFile
183  * @other: a #GFile with a common ancestor to @file
184  *
185  * This function is similar to g_file_get_relative_path() except that
186  * @file and @other only need to have a shared common ancestor.
187  *
188  * This is useful if you must use a relative path instead of the absolute,
189  * canonical path.
190  *
191  * This is being implemented for use when communicating to GDB. When that
192  * becomes unnecessary, this should no longer be used.
193  *
194  * Returns: (nullable): A relative path, or %NULL if no common ancestor was
195  *   found for the relative path.
196  *
197  * Since: 3.32
198  */
199 gchar *
ide_g_file_get_uncanonical_relative_path(GFile * file,GFile * other)200 ide_g_file_get_uncanonical_relative_path (GFile *file,
201                                           GFile *other)
202 {
203   g_autoptr(GFile) ancestor = NULL;
204   g_autoptr(GString) relatives = NULL;
205   g_autofree gchar *path = NULL;
206   g_autofree gchar *suffix = NULL;
207 
208   g_return_val_if_fail (G_IS_FILE (file), NULL);
209   g_return_val_if_fail (G_IS_FILE (other), NULL);
210 
211   /* Nothing for matching files */
212   if (file == other || g_file_equal (file, other))
213     return NULL;
214 
215   /* Make sure we're working with files of the same type */
216   if (G_OBJECT_TYPE (file) != G_OBJECT_TYPE (other))
217     return NULL;
218 
219   /* Already descendant, just give the actual path */
220   if (g_file_has_prefix (other, file))
221     return g_file_get_path (other);
222 
223   relatives = g_string_new ("/");
224 
225   /* Find the common ancestor */
226   ancestor = g_object_ref (file);
227   while (ancestor != NULL &&
228          !g_file_has_prefix (other, ancestor) &&
229          !g_file_equal (other, ancestor))
230     {
231       g_autoptr(GFile) parent = g_file_get_parent (ancestor);
232 
233       /* We reached the root, nothing more to do */
234       if (g_file_equal (parent, ancestor))
235         return NULL;
236 
237       g_string_append_len (relatives, "../", strlen ("../"));
238 
239       g_clear_object (&ancestor);
240       ancestor = g_steal_pointer (&parent);
241     }
242 
243   g_assert (G_IS_FILE (ancestor));
244   g_assert (g_file_has_prefix (other, ancestor));
245   g_assert (g_file_has_prefix (file, ancestor));
246 
247   path = g_file_get_path (file);
248   suffix = g_file_get_relative_path (ancestor, other);
249 
250   if (path == NULL)
251     path = g_strdup ("/");
252 
253   if (suffix == NULL)
254     suffix = g_strdup ("/");
255 
256   return g_build_filename (path, relatives->str, suffix, NULL);
257 }
258 
259 typedef struct
260 {
261   gchar *attributes;
262   GFileQueryInfoFlags flags;
263 } GetChildren;
264 
265 static void
ide_g_file_get_children_worker(IdeTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)266 ide_g_file_get_children_worker (IdeTask      *task,
267                                 gpointer      source_object,
268                                 gpointer      task_data,
269                                 GCancellable *cancellable)
270 {
271   g_autoptr(GFileEnumerator) enumerator = NULL;
272   g_autoptr(GPtrArray) children = NULL;
273   g_autoptr(GError) error = NULL;
274   GetChildren *gc = task_data;
275   GFile *dir = source_object;
276 
277   g_assert (IDE_IS_TASK (task));
278   g_assert (G_IS_FILE (dir));
279   g_assert (gc != NULL);
280   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
281 
282   children = g_ptr_array_new_with_free_func (g_object_unref);
283 
284   enumerator = g_file_enumerate_children (dir,
285                                           gc->attributes,
286                                           gc->flags,
287                                           cancellable,
288                                           &error);
289 
290   if (enumerator == NULL)
291     {
292       ide_task_return_error (task, g_steal_pointer (&error));
293       return;
294     }
295 
296   for (;;)
297     {
298       g_autoptr(GFileInfo) file_info = NULL;
299 
300       file_info = g_file_enumerator_next_file (enumerator, cancellable, &error);
301 
302       if (error != NULL)
303         {
304           ide_task_return_error (task, g_steal_pointer (&error));
305           return;
306         }
307 
308       if (file_info == NULL)
309         break;
310 
311       g_ptr_array_add (children, g_steal_pointer (&file_info));
312     }
313 
314   g_file_enumerator_close (enumerator, NULL, NULL);
315 
316   ide_task_return_pointer (task,
317                            g_steal_pointer (&children),
318                            g_ptr_array_unref);
319 }
320 
321 static void
get_children_free(gpointer data)322 get_children_free (gpointer data)
323 {
324   GetChildren *gc = data;
325 
326   g_free (gc->attributes);
327   g_slice_free (GetChildren, gc);
328 }
329 
330 #ifdef DEVELOPMENT_BUILD
331 static gboolean
delayed_run(gpointer data)332 delayed_run (gpointer data)
333 {
334   g_autoptr(IdeTask) subtask = data;
335   ide_task_run_in_thread (subtask, ide_g_file_get_children_worker);
336   return G_SOURCE_REMOVE;
337 }
338 #endif
339 
340 /**
341  * ide_g_file_get_children_async:
342  * @file: a #IdeGlib
343  * @attributes: attributes to retrieve
344  * @flags: flags for the query
345  * @io_priority: the io priority
346  * @cancellable: (nullable): a #GCancellable or %NULL
347  * @callback: a callback to execute upon completion
348  * @user_data: closure data for @callback
349  *
350  * This function is like g_file_enumerate_children_async() except that
351  * it returns a #GPtrArray of #GFileInfo instead of an enumerator.
352  *
353  * This can be convenient when you know you need all of the #GFileInfo
354  * accessable at once, or the size will be small.
355  *
356  * Since: 3.32
357  */
358 void
ide_g_file_get_children_async(GFile * file,const gchar * attributes,GFileQueryInfoFlags flags,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)359 ide_g_file_get_children_async (GFile               *file,
360                                const gchar         *attributes,
361                                GFileQueryInfoFlags  flags,
362                                gint                 io_priority,
363                                GCancellable        *cancellable,
364                                GAsyncReadyCallback  callback,
365                                gpointer             user_data)
366 {
367   g_autoptr(IdeTask) task = NULL;
368   GetChildren *gc;
369 
370   g_return_if_fail (G_IS_FILE (file));
371   g_return_if_fail (attributes != NULL);
372   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
373 
374   gc = g_slice_new0 (GetChildren);
375   gc->attributes = g_strdup (attributes);
376   gc->flags = flags;
377 
378   task = ide_task_new (file, cancellable, callback, user_data);
379   ide_task_set_source_tag (task, ide_g_file_get_children_async);
380   ide_task_set_priority (task, io_priority);
381   ide_task_set_task_data (task, gc, get_children_free);
382 
383 #ifdef DEVELOPMENT_BUILD
384   /* Useful for testing slow interactions on project-tree and such */
385   if (g_getenv ("IDE_G_FILE_DELAY"))
386     {
387       g_timeout_add_seconds (1, delayed_run, g_object_ref (task));
388       return;
389     }
390 #endif
391 
392   ide_task_run_in_thread (task, ide_g_file_get_children_worker);
393 }
394 
395 /**
396  * ide_g_file_get_children_finish:
397  * @file: a #GFile
398  * @result: a #GAsyncResult provided to callback
399  * @error: a location for a #GError, or %NULL
400  *
401  * Completes an asynchronous request to ide_g_file_get_children_async().
402  *
403  * Returns: (transfer full) (element-type Gio.FileInfo): A #GPtrArray
404  *   of #GFileInfo if successful, otherwise %NULL.
405  *
406  * Since: 3.32
407  */
408 GPtrArray *
ide_g_file_get_children_finish(GFile * file,GAsyncResult * result,GError ** error)409 ide_g_file_get_children_finish (GFile         *file,
410                                 GAsyncResult  *result,
411                                 GError       **error)
412 {
413   GPtrArray *ret;
414 
415   g_return_val_if_fail (G_IS_FILE (file), NULL);
416   g_return_val_if_fail (IDE_IS_TASK (result), NULL);
417   g_return_val_if_fail (ide_task_is_valid (IDE_TASK (result), file), NULL);
418 
419   ret = ide_task_propagate_pointer (IDE_TASK (result), error);
420 
421   return IDE_PTR_ARRAY_STEAL_FULL (&ret);
422 }
423 
424 typedef struct
425 {
426   GPatternSpec *spec;
427   guint         depth;
428 } Find;
429 
430 static void
find_free(Find * f)431 find_free (Find *f)
432 {
433   g_clear_pointer (&f->spec, g_pattern_spec_free);
434   g_slice_free (Find, f);
435 }
436 
437 static void
populate_descendants_matching(GFile * file,GCancellable * cancellable,GPtrArray * results,GPatternSpec * spec,guint depth)438 populate_descendants_matching (GFile        *file,
439                                GCancellable *cancellable,
440                                GPtrArray    *results,
441                                GPatternSpec *spec,
442                                guint         depth)
443 {
444   g_autoptr(GFileEnumerator) enumerator = NULL;
445   g_autoptr(GPtrArray) children = NULL;
446 
447   g_assert (G_IS_FILE (file));
448   g_assert (results != NULL);
449   g_assert (spec != NULL);
450 
451   if (depth == 0)
452     return;
453 
454   enumerator = g_file_enumerate_children (file,
455                                           G_FILE_ATTRIBUTE_STANDARD_NAME","
456                                           G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK","
457                                           G_FILE_ATTRIBUTE_STANDARD_TYPE,
458                                           G_FILE_QUERY_INFO_NONE,
459                                           cancellable,
460                                           NULL);
461 
462   if (enumerator == NULL)
463     return;
464 
465   for (;;)
466     {
467       g_autoptr(GFileInfo) info = g_file_enumerator_next_file (enumerator, cancellable, NULL);
468       const gchar *name;
469       GFileType file_type;
470 
471       if (info == NULL)
472         break;
473 
474       name = g_file_info_get_name (info);
475       file_type = g_file_info_get_file_type (info);
476 
477       if (g_pattern_spec_match_string (spec, name))
478         g_ptr_array_add (results, g_file_enumerator_get_child (enumerator, info));
479 
480       if (!g_file_info_get_is_symlink (info) && file_type == G_FILE_TYPE_DIRECTORY)
481         {
482           if (children == NULL)
483             children = g_ptr_array_new_with_free_func (g_object_unref);
484           g_ptr_array_add (children, g_file_enumerator_get_child (enumerator, info));
485         }
486     }
487 
488   g_file_enumerator_close (enumerator, cancellable, NULL);
489 
490   if (children != NULL)
491     {
492       for (guint i = 0; i < children->len; i++)
493         {
494           GFile *child = g_ptr_array_index (children, i);
495 
496           /* Don't recurse into known bad directories */
497           if (!ide_g_file_is_ignored (child))
498             populate_descendants_matching (child, cancellable, results, spec, depth - 1);
499         }
500     }
501 }
502 
503 static void
ide_g_file_find_worker(IdeTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)504 ide_g_file_find_worker (IdeTask      *task,
505                         gpointer      source_object,
506                         gpointer      task_data,
507                         GCancellable *cancellable)
508 {
509   GFile *file = source_object;
510   Find *f = task_data;
511   g_autoptr(GPtrArray) ret = NULL;
512 
513   g_assert (IDE_IS_TASK (task));
514   g_assert (G_IS_FILE (file));
515   g_assert (f != NULL);
516   g_assert (f->spec != NULL);
517   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
518 
519   ret = g_ptr_array_new_with_free_func (g_object_unref);
520   populate_descendants_matching (file, cancellable, ret, f->spec, f->depth);
521   ide_task_return_pointer (task, g_steal_pointer (&ret), g_ptr_array_unref);
522 }
523 
524 /**
525  * ide_g_file_find_with_depth:
526  * @file: a #GFile
527  * @pattern: the glob pattern to search for using GPatternSpec
528  * @max_depth: maximum tree depth to search
529  * @cancellable: (nullable): a #GCancellable or %NULL
530  *
531  *
532  * Returns: (transfer full) (element-type GFile): a #GPtrArray of #GFile.
533  *
534  * Since: 3.32
535  */
536 GPtrArray *
ide_g_file_find_with_depth(GFile * file,const gchar * pattern,guint max_depth,GCancellable * cancellable)537 ide_g_file_find_with_depth (GFile        *file,
538                             const gchar  *pattern,
539                             guint         max_depth,
540                             GCancellable *cancellable)
541 {
542   g_autoptr(GPatternSpec) spec = NULL;
543   GPtrArray *ret;
544 
545   g_return_val_if_fail (G_IS_FILE (file), NULL);
546   g_return_val_if_fail (pattern != NULL, NULL);
547 
548   if (!(spec = g_pattern_spec_new (pattern)))
549     {
550       g_warning ("Failed to build pattern spec for \"%s\"", pattern);
551       return NULL;
552     }
553 
554   if (max_depth == 0)
555     max_depth = G_MAXUINT;
556 
557   ret = g_ptr_array_new ();
558   populate_descendants_matching (file, cancellable, ret, spec, max_depth);
559   return IDE_PTR_ARRAY_STEAL_FULL (&ret);
560 }
561 
562 /**
563  * ide_g_file_find_with_depth_async:
564  * @file: a #IdeGlib
565  * @pattern: the glob pattern to search for using GPatternSpec
566  * @max_depth: maximum tree depth to search
567  * @cancellable: (nullable): a #GCancellable or %NULL
568  * @callback: a callback to execute upon completion
569  * @user_data: closure data for @callback
570  *
571  * Searches descendants of @file for files matching @pattern.
572  *
573  * Only up to @max_depth subdirectories will be searched. However, if
574  * @max_depth is zero, then all directories will be searched.
575  *
576  * You may only match on the filename, not the directory.
577  *
578  * Since: 3.32
579  */
580 void
ide_g_file_find_with_depth_async(GFile * file,const gchar * pattern,guint depth,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)581 ide_g_file_find_with_depth_async (GFile               *file,
582                                   const gchar         *pattern,
583                                   guint                depth,
584                                   GCancellable        *cancellable,
585                                   GAsyncReadyCallback  callback,
586                                   gpointer             user_data)
587 {
588   g_autoptr(IdeTask) task = NULL;
589   Find *f;
590 
591   g_return_if_fail (G_IS_FILE (file));
592   g_return_if_fail (pattern != NULL);
593   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
594 
595   if (depth == 0)
596     depth = G_MAXUINT;
597 
598   task = ide_task_new (file, cancellable, callback, user_data);
599   ide_task_set_source_tag (task, ide_g_file_find_async);
600   ide_task_set_priority (task, G_PRIORITY_LOW + 100);
601 
602   f = g_slice_new0 (Find);
603   f->spec = g_pattern_spec_new (pattern);
604   f->depth = depth;
605   ide_task_set_task_data (task, f, find_free);
606 
607   if (f->spec == NULL)
608     {
609       ide_task_return_new_error (task,
610                                  G_IO_ERROR,
611                                  G_IO_ERROR_INVAL,
612                                  "Invalid pattern spec: %s",
613                                  pattern);
614       return;
615     }
616 
617   ide_task_run_in_thread (task, ide_g_file_find_worker);
618 }
619 
620 /**
621  * ide_g_file_find_async:
622  * @file: a #IdeGlib
623  * @pattern: the glob pattern to search for using GPatternSpec
624  * @cancellable: (nullable): a #GCancellable or %NULL
625  * @callback: a callback to execute upon completion
626  * @user_data: closure data for @callback
627  *
628  * Searches descendants of @file for files matching @pattern.
629  *
630  * You may only match on the filename, not the directory.
631  *
632  * Since: 3.32
633  */
634 void
ide_g_file_find_async(GFile * file,const gchar * pattern,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)635 ide_g_file_find_async (GFile               *file,
636                        const gchar         *pattern,
637                        GCancellable        *cancellable,
638                        GAsyncReadyCallback  callback,
639                        gpointer             user_data)
640 {
641   ide_g_file_find_with_depth_async (file, pattern, G_MAXUINT, cancellable, callback, user_data);
642 }
643 
644 /**
645  * ide_g_file_find_finish:
646  * @file: a #GFile
647  * @result: a result provided to callback
648  * @error: a location for a #GError or %NULL
649  *
650  * Gets the files that were found which matched the pattern.
651  *
652  * Returns: (transfer full) (element-type Gio.File): A #GPtrArray of #GFile
653  *
654  * Since: 3.32
655  */
656 GPtrArray *
ide_g_file_find_finish(GFile * file,GAsyncResult * result,GError ** error)657 ide_g_file_find_finish (GFile         *file,
658                         GAsyncResult  *result,
659                         GError       **error)
660 {
661   GPtrArray *ret;
662 
663   g_return_val_if_fail (G_IS_FILE (file), NULL);
664   g_return_val_if_fail (IDE_IS_TASK (result), NULL);
665 
666   ret = ide_task_propagate_pointer (IDE_TASK (result), error);
667 
668   return IDE_PTR_ARRAY_STEAL_FULL (&ret);
669 }
670 
671 /**
672  * ide_g_host_file_get_contents:
673  * @path: the path on the host
674  * @contents: (out): a location for the contents
675  * @len: (out): a location for the size, not including trailing \0
676  * @error: location for a #GError, or %NULL
677  *
678  * This is similar to g_file_get_contents() but ensures that we get
679  * the file from the host, rather than our mount namespace.
680  *
681  * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
682  *
683  * Since: 3.32
684  */
685 gboolean
ide_g_host_file_get_contents(const gchar * path,gchar ** contents,gsize * len,GError ** error)686 ide_g_host_file_get_contents (const gchar  *path,
687                               gchar       **contents,
688                               gsize        *len,
689                               GError      **error)
690 {
691   g_autoptr(IdeSubprocessLauncher) launcher = NULL;
692   g_autoptr(IdeSubprocess) subprocess = NULL;
693   g_autofree gchar *tmpfile = NULL;
694   gboolean ret = FALSE;
695   int fd;
696 
697   g_return_val_if_fail (path != NULL, FALSE);
698 
699   if (contents != NULL)
700     *contents = NULL;
701 
702   if (len != NULL)
703     *len = 0;
704 
705   if (!ide_is_flatpak ())
706     return g_file_get_contents (path, contents, len, error);
707 
708   tmpfile = g_build_filename (g_get_tmp_dir (), ".ide-host-file-XXXXXX", NULL);
709 
710   /* We open a FD locally that we can write to and then pass that as our
711    * stdout across the boundary so we can avoid incrementally reading
712    * and instead do it once at the end.
713    */
714   if (-1 == (fd = g_mkstemp (tmpfile)))
715     {
716       int errsv = errno;
717       g_set_error_literal (error,
718                            G_FILE_ERROR,
719                            g_file_error_from_errno (errsv),
720                            g_strerror (errsv));
721       return FALSE;
722     }
723 
724   launcher = ide_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE |
725                                           G_SUBPROCESS_FLAGS_STDERR_SILENCE);
726   ide_subprocess_launcher_take_stdout_fd (launcher, fd);
727   ide_subprocess_launcher_set_run_on_host (launcher, TRUE);
728   ide_subprocess_launcher_push_argv (launcher, "cat");
729   ide_subprocess_launcher_push_argv (launcher, path);
730 
731   if (!(subprocess = ide_subprocess_launcher_spawn (launcher, NULL, error)))
732     goto failure;
733 
734   if (!ide_subprocess_wait_check (subprocess, NULL, error))
735     goto failure;
736 
737   ret = g_file_get_contents (tmpfile, contents, len, error);
738 
739 failure:
740   g_unlink (tmpfile);
741 
742   return ret;
743 }
744 
745 /**
746  * ide_g_file_walk_with_ignore:
747  * @directory: a #GFile that is a directory
748  * @attributes: attributes to include in #GFileInfo
749  * @ignore_file: (nullable): the filename within @directory to indicate that
750  *   the directory should be ignored
751  * @cancellable: (nullable): an optional cancellable
752  * @callback: (scope call): a callback for each directory starting from
753  *   the @directory
754  * @callback_data: closure data for @callback
755  *
756  * Calls @callback for every directory starting from @directory.
757  *
758  * All of the fileinfo for the directory will be provided to the callback for
759  * each directory.
760  *
761  * If @ignore_file is set, this function will check to see if that file exists
762  * within @directory and skip it (and all descendants) if discovered.
763  *
764  * Since: 3.34
765  */
766 void
ide_g_file_walk_with_ignore(GFile * directory,const gchar * attributes,const gchar * ignore_file,GCancellable * cancellable,IdeFileWalkCallback callback,gpointer callback_data)767 ide_g_file_walk_with_ignore (GFile               *directory,
768                              const gchar         *attributes,
769                              const gchar         *ignore_file,
770                              GCancellable        *cancellable,
771                              IdeFileWalkCallback  callback,
772                              gpointer             callback_data)
773 {
774   g_autoptr(GFileEnumerator) enumerator = NULL;
775   g_autoptr(GPtrArray) directories = NULL;
776   g_autoptr(GPtrArray) file_infos = NULL;
777   g_autoptr(GString) str = NULL;
778   g_autoptr(GError) error = NULL;
779   GFileType directory_type;
780   gpointer infoptr;
781   static const gchar *required[] = {
782     G_FILE_ATTRIBUTE_STANDARD_NAME,
783     G_FILE_ATTRIBUTE_STANDARD_TYPE,
784     NULL
785   };
786 
787   g_return_if_fail (G_IS_FILE (directory));
788   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
789 
790   if (g_cancellable_is_cancelled (cancellable))
791     return;
792 
793   directory_type = g_file_query_file_type (directory,
794                                            G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
795                                            cancellable);
796 
797   if (directory_type != G_FILE_TYPE_DIRECTORY)
798     return;
799 
800   if (ignore_file != NULL)
801     {
802       g_autoptr(GFile) ignore = g_file_get_child (directory, ignore_file);
803 
804       if (g_file_query_exists (ignore, cancellable))
805         return;
806     }
807 
808   str = g_string_new (attributes);
809 
810   for (guint i = 0; required[i]; i++)
811     {
812       if (!strstr (str->str, required[i]))
813         g_string_append_printf (str, ",%s", required[i]);
814     }
815 
816   directories = g_ptr_array_new_with_free_func (g_object_unref);
817   file_infos = g_ptr_array_new_with_free_func (g_object_unref);
818 
819   enumerator = g_file_enumerate_children (directory,
820                                           str->str,
821                                           G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
822                                           cancellable,
823                                           NULL);
824 
825   if (enumerator == NULL)
826     return;
827 
828   while ((infoptr = g_file_enumerator_next_file (enumerator, cancellable, &error)))
829     {
830       g_autoptr(GFileInfo) info = infoptr;
831       g_autoptr(GFile) child = g_file_enumerator_get_child (enumerator, info);
832 
833       if (ide_g_file_is_ignored (child))
834         continue;
835 
836       if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
837         g_ptr_array_add (directories, g_steal_pointer (&child));
838 
839       g_ptr_array_add (file_infos, g_steal_pointer (&info));
840     }
841 
842   callback (directory, file_infos, callback_data);
843 
844   for (guint i = 0; i < directories->len; i++)
845     {
846       GFile *child = g_ptr_array_index (directories, i);
847 
848       if (g_cancellable_is_cancelled (cancellable))
849         break;
850 
851       ide_g_file_walk_with_ignore (child,
852                                    attributes,
853                                    ignore_file,
854                                    cancellable,
855                                    callback,
856                                    callback_data);
857     }
858 }
859 
860 /**
861  * ide_g_file_walk:
862  * @directory: a #GFile that is a directory
863  * @attributes: attributes to include in #GFileInfo
864  * @cancellable: (nullable): an optional cancellable
865  * @callback: (scope call): a callback for each directory starting from @directory
866  * @callback_data: closure data for @callback
867  *
868  * Calls @callback for every directory starting from @directory.
869  *
870  * All of the fileinfo for the directory will be provided to the callback for
871  * each directory.
872  *
873  * Since: 3.32
874  */
875 void
ide_g_file_walk(GFile * directory,const gchar * attributes,GCancellable * cancellable,IdeFileWalkCallback callback,gpointer callback_data)876 ide_g_file_walk (GFile               *directory,
877                  const gchar         *attributes,
878                  GCancellable        *cancellable,
879                  IdeFileWalkCallback  callback,
880                  gpointer             callback_data)
881 {
882   ide_g_file_walk_with_ignore (directory, attributes, NULL, cancellable, callback, callback_data);
883 }
884 
885 static gboolean
iter_parents(GFile ** fileptr)886 iter_parents (GFile **fileptr)
887 {
888   g_autoptr(GFile) item = *fileptr;
889   *fileptr = g_file_get_parent (item);
890   return *fileptr != NULL;
891 }
892 
893 static gboolean
is_symlink(GFile * file,gchar ** target)894 is_symlink (GFile  *file,
895             gchar **target)
896 {
897   g_autoptr(GFileInfo) info = NULL;
898 
899   g_assert (G_IS_FILE (file));
900   g_assert (target != NULL);
901 
902   *target = NULL;
903 
904   if (!g_file_is_native (file))
905     return FALSE;
906 
907   info = g_file_query_info (file,
908                             G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK","
909                             G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
910                             G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
911                             NULL, NULL);
912 
913   if (info == NULL)
914     return FALSE;
915 
916   if (g_file_info_get_is_symlink (info))
917     {
918       *target = g_strdup (g_file_info_get_symlink_target (info));
919       return TRUE;
920     }
921 
922   return FALSE;
923 }
924 
925 GFile *
_ide_g_file_readlink(GFile * file)926 _ide_g_file_readlink (GFile *file)
927 {
928   g_autoptr(GFile) iter = NULL;
929 
930   g_return_val_if_fail (G_IS_FILE (file), NULL);
931 
932   if (!g_file_is_native (file))
933     return g_object_ref (file);
934 
935   iter = g_file_dup (file);
936 
937   do
938     {
939       g_autofree gchar *target = NULL;
940 
941       if (is_symlink (iter, &target))
942         {
943           g_autoptr(GFile) parent = g_file_get_parent (iter);
944           g_autoptr(GFile) base = g_file_get_child (parent, target);
945           g_autofree gchar *relative = g_file_get_relative_path (iter, file);
946 
947           if (relative == NULL)
948             return g_steal_pointer (&base);
949           else
950             return g_file_get_child (base, relative);
951         }
952     }
953   while (iter_parents (&iter));
954 
955   return g_object_ref (file);
956 }
957 
958 static void
find_in_ancestors_worker(IdeTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)959 find_in_ancestors_worker (IdeTask      *task,
960                           gpointer      source_object,
961                           gpointer      task_data,
962                           GCancellable *cancellable)
963 {
964   const gchar *name = task_data;
965   GFile *directory = (GFile *)source_object;
966   GFile *current = NULL;
967 
968   g_assert (IDE_IS_TASK (task));
969   g_assert (G_IS_FILE (directory));
970   g_assert (name != NULL);
971   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
972 
973   current = g_object_ref (directory);
974 
975   while (current != NULL)
976     {
977       g_autoptr(GFile) target = g_file_get_child (current, name);
978       g_autoptr(GFile) tmp = NULL;
979 
980       if (g_file_query_exists (target, cancellable))
981         {
982           ide_task_return_pointer (task, g_steal_pointer (&target), g_object_unref);
983           goto cleanup;
984         }
985 
986       tmp = g_steal_pointer (&current);
987       current = g_file_get_parent (tmp);
988     }
989 
990   ide_task_return_new_error (task,
991                              G_IO_ERROR,
992                              G_IO_ERROR_NOT_FOUND,
993                              "Failed to locate file \"%s\" in ancestry",
994                              name);
995 
996 cleanup:
997   g_clear_object (&current);
998 }
999 
1000 void
ide_g_file_find_in_ancestors_async(GFile * directory,const gchar * name,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1001 ide_g_file_find_in_ancestors_async (GFile               *directory,
1002                                     const gchar         *name,
1003                                     GCancellable        *cancellable,
1004                                     GAsyncReadyCallback  callback,
1005                                     gpointer             user_data)
1006 {
1007   g_autoptr(IdeTask) task = NULL;
1008 
1009   g_return_if_fail (G_IS_FILE (directory));
1010   g_return_if_fail (name != NULL);
1011   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
1012 
1013   task = ide_task_new (directory, cancellable, callback, user_data);
1014   ide_task_set_source_tag (task, ide_g_file_find_in_ancestors_async);
1015   ide_task_set_task_data (task, g_strdup (name), g_free);
1016   ide_task_run_in_thread (task, find_in_ancestors_worker);
1017 }
1018 
1019 /**
1020  * ide_g_file_find_in_ancestors_finish:
1021  *
1022  * Returns: (transfer full): a #GFile if successful; otherwise %NULL
1023  *   and @error is et.
1024  *
1025  * Since: 3.34
1026  */
1027 GFile *
ide_g_file_find_in_ancestors_finish(GAsyncResult * result,GError ** error)1028 ide_g_file_find_in_ancestors_finish (GAsyncResult  *result,
1029                                      GError       **error)
1030 {
1031   g_return_val_if_fail (IDE_IS_TASK (result), NULL);
1032 
1033   return ide_task_propagate_pointer (IDE_TASK (result), error);
1034 }
1035 
1036 gboolean
_ide_g_file_query_exists_on_host(GFile * file,GCancellable * cancellable)1037 _ide_g_file_query_exists_on_host (GFile        *file,
1038                                   GCancellable *cancellable)
1039 {
1040   g_autoptr(IdeSubprocessLauncher) launcher = NULL;
1041   g_autoptr(IdeSubprocess) subprocess = NULL;
1042 
1043   g_return_val_if_fail (G_IS_FILE (file), FALSE);
1044   g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
1045 
1046   if (!g_file_is_native (file))
1047     return FALSE;
1048 
1049   if (!ide_is_flatpak ())
1050     return g_file_query_exists (file, cancellable);
1051 
1052   launcher = ide_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_SILENCE | G_SUBPROCESS_FLAGS_STDERR_SILENCE);
1053   ide_subprocess_launcher_set_run_on_host (launcher, TRUE);
1054   ide_subprocess_launcher_push_argv (launcher, "ls");
1055   ide_subprocess_launcher_push_argv (launcher, "-d");
1056   ide_subprocess_launcher_push_argv (launcher, g_file_peek_path (file));
1057 
1058   if (!(subprocess = ide_subprocess_launcher_spawn (launcher, cancellable, NULL)))
1059     return FALSE;
1060 
1061   return ide_subprocess_wait_check (subprocess, cancellable, NULL);
1062 }
1063 
1064 gboolean
_ide_path_query_exists_on_host(const char * path)1065 _ide_path_query_exists_on_host (const char *path)
1066 {
1067   g_autofree char *locally = NULL;
1068   g_autoptr(GFile) file = NULL;
1069 
1070   g_return_val_if_fail (path != NULL, FALSE);
1071 
1072   if (!ide_is_flatpak ())
1073     return g_file_test (path, G_FILE_TEST_EXISTS);
1074 
1075   /* First try via /var/run/host */
1076   locally = g_build_filename ("/var/run/host", path, NULL);
1077   if (g_file_test (locally, G_FILE_TEST_EXISTS))
1078     return TRUE;
1079 
1080   /* Fallback to using GFile functionality */
1081   file = g_file_new_for_path (path);
1082   return _ide_g_file_query_exists_on_host (file, NULL);
1083 }
1084