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 (¤t);
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 (¤t);
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