Coverage Report

Created: 2026-04-09 06:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-path.c
Line
Count
Source
1
/*
2
 * Copyright 2017 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
0
#define G_LOG_DOMAIN "FuCommon"
8
9
#include "config.h"
10
11
#include <errno.h>
12
#include <glib/gstdio.h>
13
14
#ifdef _WIN32
15
#include <stdlib.h>
16
#endif
17
18
#include "fwupd-error.h"
19
20
#include "fu-common.h"
21
#include "fu-path.h"
22
23
/**
24
 * fu_path_rmtree:
25
 * @directory: a directory name
26
 * @error: (nullable): optional return location for an error
27
 *
28
 * Recursively removes a directory.
29
 *
30
 * Returns: %TRUE for success, %FALSE otherwise
31
 *
32
 * Since: 1.8.2
33
 **/
34
gboolean
35
fu_path_rmtree(const gchar *directory, GError **error)
36
0
{
37
0
  const gchar *filename;
38
0
  g_autoptr(GDir) dir = NULL;
39
40
0
  g_return_val_if_fail(directory != NULL, FALSE);
41
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
42
43
  /* try to open */
44
0
  g_debug("removing %s", directory);
45
0
  dir = g_dir_open(directory, 0, error);
46
0
  if (dir == NULL)
47
0
    return FALSE;
48
49
  /* find each */
50
0
  while ((filename = g_dir_read_name(dir))) {
51
0
    g_autofree gchar *src = NULL;
52
0
    src = g_build_filename(directory, filename, NULL);
53
0
    if (g_file_test(src, G_FILE_TEST_IS_DIR)) {
54
0
      if (!fu_path_rmtree(src, error))
55
0
        return FALSE;
56
0
    } else {
57
0
      if (g_unlink(src) != 0) {
58
0
        g_set_error(error,
59
0
              FWUPD_ERROR,
60
0
              FWUPD_ERROR_INTERNAL,
61
0
              "Failed to delete: %s",
62
0
              src);
63
0
        return FALSE;
64
0
      }
65
0
    }
66
0
  }
67
0
  if (g_remove(directory) != 0) {
68
0
    g_set_error(error,
69
0
          FWUPD_ERROR,
70
0
          FWUPD_ERROR_INTERNAL,
71
0
          "Failed to delete: %s",
72
0
          directory);
73
0
    return FALSE;
74
0
  }
75
0
  return TRUE;
76
0
}
77
78
static gboolean
79
fu_path_get_file_list_internal(GPtrArray *files, const gchar *directory, GError **error)
80
0
{
81
0
  const gchar *filename;
82
0
  g_autoptr(GDir) dir = NULL;
83
84
  /* try to open */
85
0
  dir = g_dir_open(directory, 0, error);
86
0
  if (dir == NULL) {
87
0
    fwupd_error_convert(error);
88
0
    return FALSE;
89
0
  }
90
91
  /* find each */
92
0
  while ((filename = g_dir_read_name(dir))) {
93
0
    g_autofree gchar *src = g_build_filename(directory, filename, NULL);
94
0
    if (g_file_test(src, G_FILE_TEST_IS_SYMLINK))
95
0
      continue;
96
0
    if (g_file_test(src, G_FILE_TEST_IS_DIR)) {
97
0
      if (!fu_path_get_file_list_internal(files, src, error))
98
0
        return FALSE;
99
0
    } else {
100
0
      g_ptr_array_add(files, g_steal_pointer(&src));
101
0
    }
102
0
  }
103
0
  return TRUE;
104
0
}
105
106
/**
107
 * fu_path_get_files:
108
 * @path: a directory name
109
 * @error: (nullable): optional return location for an error
110
 *
111
 * Returns every file found under @directory, and any subdirectory.
112
 * If any path under @directory cannot be accessed due to permissions an error
113
 * will be returned.
114
 *
115
 * Returns: (transfer container) (element-type utf8): array of files, or %NULL for error
116
 *
117
 * Since: 1.8.2
118
 **/
119
GPtrArray *
120
fu_path_get_files(const gchar *path, GError **error)
121
0
{
122
0
  g_autoptr(GPtrArray) files = g_ptr_array_new_with_free_func(g_free);
123
124
0
  g_return_val_if_fail(path != NULL, NULL);
125
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
126
127
0
  if (!fu_path_get_file_list_internal(files, path, error))
128
0
    return NULL;
129
0
  return g_steal_pointer(&files);
130
0
}
131
132
/**
133
 * fu_path_mkdir:
134
 * @dirname: a directory name
135
 * @error: (nullable): optional return location for an error
136
 *
137
 * Creates any required directories, including any parent directories.
138
 *
139
 * Returns: %TRUE for success
140
 *
141
 * Since: 1.8.2
142
 **/
143
gboolean
144
fu_path_mkdir(const gchar *dirname, GError **error)
145
0
{
146
0
  g_return_val_if_fail(dirname != NULL, FALSE);
147
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
148
149
0
  if (!g_file_test(dirname, G_FILE_TEST_IS_DIR))
150
0
    g_debug("creating path %s", dirname);
151
0
  if (g_mkdir_with_parents(dirname, 0755) == -1) {
152
0
    g_set_error(error,
153
0
          FWUPD_ERROR,
154
0
          FWUPD_ERROR_INTERNAL,
155
0
          "Failed to create '%s': %s",
156
0
          dirname,
157
0
          fwupd_strerror(errno));
158
0
    return FALSE;
159
0
  }
160
0
  return TRUE;
161
0
}
162
163
/**
164
 * fu_path_mkdir_parent:
165
 * @filename: a full pathname
166
 * @error: (nullable): optional return location for an error
167
 *
168
 * Creates any required directories, including any parent directories.
169
 *
170
 * Returns: %TRUE for success
171
 *
172
 * Since: 1.8.2
173
 **/
174
gboolean
175
fu_path_mkdir_parent(const gchar *filename, GError **error)
176
0
{
177
0
  g_autofree gchar *parent = NULL;
178
179
0
  g_return_val_if_fail(filename != NULL, FALSE);
180
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
181
182
0
  parent = g_path_get_dirname(filename);
183
0
  return fu_path_mkdir(parent, error);
184
0
}
185
186
/**
187
 * fu_path_find_program:
188
 * @basename: the program to search
189
 * @error: (nullable): optional return location for an error
190
 *
191
 * Looks for a program in the PATH variable
192
 *
193
 * Returns: a new #gchar, or %NULL for error
194
 *
195
 * Since: 1.8.2
196
 **/
197
gchar *
198
fu_path_find_program(const gchar *basename, GError **error)
199
0
{
200
0
  gchar *fn = g_find_program_in_path(basename);
201
0
  if (fn == NULL) {
202
0
    g_set_error(error,
203
0
          FWUPD_ERROR,
204
0
          FWUPD_ERROR_NOT_SUPPORTED,
205
0
          "missing executable %s in PATH",
206
0
          basename);
207
0
    return NULL;
208
0
  }
209
0
  return fn;
210
0
}
211
212
static gint
213
fu_path_glob_sort_cb(gconstpointer a, gconstpointer b)
214
0
{
215
0
  return g_strcmp0(*(const gchar **)a, *(const gchar **)b);
216
0
}
217
218
/**
219
 * fu_path_glob:
220
 * @directory: a directory path
221
 * @pattern: a glob pattern, e.g. `*foo*`
222
 * @error: (nullable): optional return location for an error
223
 *
224
 * Returns all the filenames that match a specific glob pattern.
225
 * Any results are sorted. No matching files will set @error.
226
 *
227
 * Returns:  (element-type utf8) (transfer container): matching files, or %NULL
228
 *
229
 * Since: 1.8.2
230
 **/
231
GPtrArray *
232
fu_path_glob(const gchar *directory, const gchar *pattern, GError **error)
233
0
{
234
0
  const gchar *basename;
235
0
  g_autoptr(GDir) dir = NULL;
236
0
  g_autoptr(GPtrArray) files = g_ptr_array_new_with_free_func(g_free);
237
238
0
  g_return_val_if_fail(directory != NULL, NULL);
239
0
  g_return_val_if_fail(pattern != NULL, NULL);
240
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
241
242
0
  dir = g_dir_open(directory, 0, error);
243
0
  if (dir == NULL)
244
0
    return NULL;
245
0
  while ((basename = g_dir_read_name(dir)) != NULL) {
246
0
    if (!g_pattern_match_simple(pattern, basename))
247
0
      continue;
248
0
    g_ptr_array_add(files, g_build_filename(directory, basename, NULL));
249
0
  }
250
0
  if (files->len == 0) {
251
0
    g_set_error_literal(error,
252
0
            FWUPD_ERROR,
253
0
            FWUPD_ERROR_NOT_FOUND,
254
0
            "no files matched pattern");
255
0
    return NULL;
256
0
  }
257
0
  g_ptr_array_sort(files, fu_path_glob_sort_cb);
258
0
  return g_steal_pointer(&files);
259
0
}
260
261
/**
262
 * fu_path_make_absolute:
263
 * @filename: a path to a filename, perhaps symlinked
264
 * @error: (nullable): optional return location for an error
265
 *
266
 * Returns the resolved absolute file name.
267
 *
268
 * Returns: (transfer full): path, or %NULL on error
269
 *
270
 * Since: 2.0.0
271
 **/
272
gchar *
273
fu_path_make_absolute(const gchar *filename, GError **error)
274
0
{
275
0
  char full_tmp[PATH_MAX];
276
277
0
  g_return_val_if_fail(filename != NULL, NULL);
278
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
279
280
0
#ifdef HAVE_REALPATH
281
0
  if (realpath(filename, full_tmp) == NULL) {
282
0
    g_set_error(error,
283
0
          FWUPD_ERROR,
284
0
          FWUPD_ERROR_INVALID_DATA,
285
0
          "cannot resolve path: %s",
286
0
          fwupd_strerror(errno));
287
0
    return NULL;
288
0
  }
289
#else
290
  if (_fullpath(full_tmp, filename, sizeof(full_tmp)) == NULL) {
291
    g_set_error(error,
292
          FWUPD_ERROR,
293
          FWUPD_ERROR_INVALID_DATA,
294
          "cannot resolve path: %s",
295
          fwupd_strerror(errno));
296
    return NULL;
297
  }
298
#endif
299
0
  if (!g_file_test(full_tmp, G_FILE_TEST_EXISTS)) {
300
0
    g_set_error(error,
301
0
          FWUPD_ERROR,
302
0
          FWUPD_ERROR_INVALID_DATA,
303
0
          "cannot find path: %s",
304
0
          full_tmp);
305
0
    return NULL;
306
0
  }
307
0
  return g_strdup(full_tmp);
308
0
}
309
310
/**
311
 * fu_path_get_symlink_target:
312
 * @filename: a path to a symlink
313
 * @error: (nullable): optional return location for an error
314
 *
315
 * Returns the symlink target.
316
 *
317
 * Returns: (transfer full): path, or %NULL on error
318
 *
319
 * Since: 2.0.0
320
 **/
321
gchar *
322
fu_path_get_symlink_target(const gchar *filename, GError **error)
323
0
{
324
0
  const gchar *target;
325
0
  g_autoptr(GFile) file = NULL;
326
0
  g_autoptr(GFileInfo) info = NULL;
327
328
0
  file = g_file_new_for_path(filename);
329
0
  info = g_file_query_info(file,
330
0
         G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
331
0
         G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
332
0
         NULL,
333
0
         error);
334
0
  if (info == NULL) {
335
0
    fwupd_error_convert(error);
336
0
    return NULL;
337
0
  }
338
0
  target =
339
0
      g_file_info_get_attribute_byte_string(info, G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET);
340
0
  if (target == NULL) {
341
0
    g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no symlink target");
342
0
    return NULL;
343
0
  }
344
345
  /* success */
346
0
  return g_strdup(target);
347
0
}
348
349
/**
350
 * fu_path_verify_safe:
351
 * @filename: an optional local path and basename
352
 * @error: (nullable): optional return location for an error
353
 *
354
 * Verifies the path is safe to use from an archive.
355
 *
356
 * This will reject:
357
 * - an empty string
358
 * - absolute paths, e.g. `/etc/fstab`
359
 * - paths with relative locations, e.g. `../../etc/fstab`)
360
 * - paths with MS-DOS path separators, e.g. `foo\bar`
361
 * - non-ASCII filenames
362
 *
363
 * Returns: %TRUE on success
364
 *
365
 * Since: 2.1.2
366
 **/
367
gboolean
368
fu_path_verify_safe(const gchar *filename, GError **error)
369
13.1k
{
370
13.1k
  g_return_val_if_fail(filename != NULL, FALSE);
371
13.1k
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
372
373
  /* not ASCII */
374
13.1k
  if (!g_str_is_ascii(filename)) {
375
54
    g_set_error_literal(error,
376
54
            FWUPD_ERROR,
377
54
            FWUPD_ERROR_NOT_SUPPORTED,
378
54
            "non-ASCII filenames not allowed");
379
54
    return FALSE;
380
54
  }
381
382
  /* not a basename */
383
13.0k
  if (g_strcmp0(filename, "") == 0) {
384
282
    g_set_error_literal(error,
385
282
            FWUPD_ERROR,
386
282
            FWUPD_ERROR_NOT_SUPPORTED,
387
282
            "empty string not valid");
388
282
    return FALSE;
389
282
  }
390
12.8k
  if (g_strcmp0(filename, ".") == 0 || g_strcmp0(filename, "..") == 0) {
391
2
    g_set_error_literal(error,
392
2
            FWUPD_ERROR,
393
2
            FWUPD_ERROR_NOT_SUPPORTED,
394
2
            "special paths not allowed");
395
2
    return FALSE;
396
2
  }
397
398
  /* absolute */
399
12.8k
  if (g_path_is_absolute(filename)) {
400
81
    g_set_error_literal(error,
401
81
            FWUPD_ERROR,
402
81
            FWUPD_ERROR_NOT_SUPPORTED,
403
81
            "absolute paths not allowed");
404
81
    return FALSE;
405
81
  }
406
407
  /* relative */
408
12.7k
  if (g_str_has_prefix(filename, "../") || g_strstr_len(filename, -1, "/../") != NULL ||
409
12.7k
      g_str_has_suffix(filename, "/..")) {
410
4
    g_set_error_literal(error,
411
4
            FWUPD_ERROR,
412
4
            FWUPD_ERROR_NOT_SUPPORTED,
413
4
            "path traversal detected");
414
4
    return FALSE;
415
4
  }
416
417
  /* MS-DOS */
418
12.7k
  if (g_strstr_len(filename, -1, "\\") != NULL) {
419
3
    g_set_error_literal(error,
420
3
            FWUPD_ERROR,
421
3
            FWUPD_ERROR_NOT_SUPPORTED,
422
3
            "MS-DOS path detected");
423
3
    return FALSE;
424
3
  }
425
426
  /* success */
427
12.7k
  return TRUE;
428
12.7k
}