Coverage Report

Created: 2026-02-14 06:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gstreamer/subprojects/glib-2.86.3/glib/gpathbuf.c
Line
Count
Source
1
/* gpathbuf.c: A mutable path builder
2
 *
3
 * SPDX-FileCopyrightText: 2023  Emmanuele Bassi
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
#include "config.h"
8
9
#include "gpathbuf.h"
10
11
#include "garray.h"
12
#include "gfileutils.h"
13
#include "ghash.h"
14
#include "gmessages.h"
15
#include "gstrfuncs.h"
16
17
/**
18
 * GPathBuf:
19
 *
20
 * `GPathBuf` is a helper type that allows you to easily build paths from
21
 * individual elements, using the platform specific conventions for path
22
 * separators.
23
 *
24
 * ```c
25
 * g_auto (GPathBuf) path;
26
 *
27
 * g_path_buf_init (&path);
28
 *
29
 * g_path_buf_push (&path, "usr");
30
 * g_path_buf_push (&path, "bin");
31
 * g_path_buf_push (&path, "echo");
32
 *
33
 * g_autofree char *echo = g_path_buf_to_path (&path);
34
 * g_assert_cmpstr (echo, ==, "/usr/bin/echo");
35
 * ```
36
 *
37
 * You can also load a full path and then operate on its components:
38
 *
39
 * ```c
40
 * g_auto (GPathBuf) path;
41
 *
42
 * g_path_buf_init_from_path (&path, "/usr/bin/echo");
43
 *
44
 * g_path_buf_pop (&path);
45
 * g_path_buf_push (&path, "sh");
46
 *
47
 * g_autofree char *sh = g_path_buf_to_path (&path);
48
 * g_assert_cmpstr (sh, ==, "/usr/bin/sh");
49
 * ```
50
 *
51
 * Since: 2.76
52
 */
53
54
typedef struct {
55
  /* (nullable) (owned) (element-type filename) */
56
  GPtrArray *path;
57
58
  /* (nullable) (owned) */
59
  char *extension;
60
61
  gpointer padding[6];
62
} RealPathBuf;
63
64
G_STATIC_ASSERT (sizeof (GPathBuf) == sizeof (RealPathBuf));
65
66
0
#define PATH_BUF(b) ((RealPathBuf *) (b))
67
68
/**
69
 * g_path_buf_init:
70
 * @buf: a path buffer
71
 *
72
 * Initializes a `GPathBuf` instance.
73
 *
74
 * Returns: (transfer none): the initialized path builder
75
 *
76
 * Since: 2.76
77
 */
78
GPathBuf *
79
g_path_buf_init (GPathBuf *buf)
80
0
{
81
0
  RealPathBuf *rbuf = PATH_BUF (buf);
82
83
0
  rbuf->path = NULL;
84
0
  rbuf->extension = NULL;
85
86
0
  return buf;
87
0
}
88
89
/**
90
 * g_path_buf_init_from_path:
91
 * @buf: a path buffer
92
 * @path: (type filename) (nullable): a file system path
93
 *
94
 * Initializes a `GPathBuf` instance with the given path.
95
 *
96
 * Returns: (transfer none): the initialized path builder
97
 *
98
 * Since: 2.76
99
 */
100
GPathBuf *
101
g_path_buf_init_from_path (GPathBuf   *buf,
102
                           const char *path)
103
0
{
104
0
  g_return_val_if_fail (buf != NULL, NULL);
105
0
  g_return_val_if_fail (path == NULL || *path != '\0', NULL);
106
107
0
  g_path_buf_init (buf);
108
109
0
  if (path == NULL)
110
0
    return buf;
111
0
  else
112
0
    return g_path_buf_push (buf, path);
113
0
}
114
115
/**
116
 * g_path_buf_clear:
117
 * @buf: a path buffer
118
 *
119
 * Clears the contents of the path buffer.
120
 *
121
 * This function should be use to free the resources in a stack-allocated
122
 * `GPathBuf` initialized using g_path_buf_init() or
123
 * g_path_buf_init_from_path().
124
 *
125
 * Since: 2.76
126
 */
127
void
128
g_path_buf_clear (GPathBuf *buf)
129
0
{
130
0
  RealPathBuf *rbuf = PATH_BUF (buf);
131
132
0
  g_return_if_fail (buf != NULL);
133
134
0
  g_clear_pointer (&rbuf->path, g_ptr_array_unref);
135
0
  g_clear_pointer (&rbuf->extension, g_free);
136
0
}
137
138
/**
139
 * g_path_buf_clear_to_path:
140
 * @buf: a path buffer
141
 *
142
 * Clears the contents of the path buffer and returns the built path.
143
 *
144
 * This function returns `NULL` if the `GPathBuf` is empty.
145
 *
146
 * See also: g_path_buf_to_path()
147
 *
148
 * Returns: (transfer full) (nullable) (type filename): the built path
149
 *
150
 * Since: 2.76
151
 */
152
char *
153
g_path_buf_clear_to_path (GPathBuf *buf)
154
0
{
155
0
  char *res;
156
157
0
  g_return_val_if_fail (buf != NULL, NULL);
158
159
0
  res = g_path_buf_to_path (buf);
160
0
  g_path_buf_clear (buf);
161
162
0
  return g_steal_pointer (&res);
163
0
}
164
165
/**
166
 * g_path_buf_new:
167
 *
168
 * Allocates a new `GPathBuf`.
169
 *
170
 * Returns: (transfer full): the newly allocated path buffer
171
 *
172
 * Since: 2.76
173
 */
174
GPathBuf *
175
g_path_buf_new (void)
176
0
{
177
0
  return g_path_buf_init (g_new (GPathBuf, 1));
178
0
}
179
180
/**
181
 * g_path_buf_new_from_path:
182
 * @path: (type filename) (nullable): the path used to initialize the buffer
183
 *
184
 * Allocates a new `GPathBuf` with the given @path.
185
 *
186
 * Returns: (transfer full): the newly allocated path buffer
187
 *
188
 * Since: 2.76
189
 */
190
GPathBuf *
191
g_path_buf_new_from_path (const char *path)
192
0
{
193
0
  return g_path_buf_init_from_path (g_new (GPathBuf, 1), path);
194
0
}
195
196
/**
197
 * g_path_buf_free:
198
 * @buf: (transfer full) (not nullable): a path buffer
199
 *
200
 * Frees a `GPathBuf` allocated by g_path_buf_new().
201
 *
202
 * Since: 2.76
203
 */
204
void
205
g_path_buf_free (GPathBuf *buf)
206
0
{
207
0
  g_return_if_fail (buf != NULL);
208
209
0
  g_path_buf_clear (buf);
210
0
  g_free (buf);
211
0
}
212
213
/**
214
 * g_path_buf_free_to_path:
215
 * @buf: (transfer full) (not nullable): a path buffer
216
 *
217
 * Frees a `GPathBuf` allocated by g_path_buf_new(), and
218
 * returns the path inside the buffer.
219
 *
220
 * This function returns `NULL` if the `GPathBuf` is empty.
221
 *
222
 * See also: g_path_buf_to_path()
223
 *
224
 * Returns: (transfer full) (nullable) (type filename): the path
225
 *
226
 * Since: 2.76
227
 */
228
char *
229
g_path_buf_free_to_path (GPathBuf *buf)
230
0
{
231
0
  char *res;
232
233
0
  g_return_val_if_fail (buf != NULL, NULL);
234
235
0
  res = g_path_buf_clear_to_path (buf);
236
0
  g_path_buf_free (buf);
237
238
0
  return g_steal_pointer (&res);
239
0
}
240
241
/**
242
 * g_path_buf_copy:
243
 * @buf: (not nullable): a path buffer
244
 *
245
 * Copies the contents of a path buffer into a new `GPathBuf`.
246
 *
247
 * Returns: (transfer full): the newly allocated path buffer
248
 *
249
 * Since: 2.76
250
 */
251
GPathBuf *
252
g_path_buf_copy (GPathBuf *buf)
253
0
{
254
0
  RealPathBuf *rbuf = PATH_BUF (buf);
255
0
  RealPathBuf *rcopy;
256
0
  GPathBuf *copy;
257
258
0
  g_return_val_if_fail (buf != NULL, NULL);
259
260
0
  copy = g_path_buf_new ();
261
0
  rcopy = PATH_BUF (copy);
262
263
0
  if (rbuf->path != NULL)
264
0
    {
265
0
      rcopy->path = g_ptr_array_new_null_terminated (rbuf->path->len, g_free, TRUE);
266
0
      for (guint i = 0; i < rbuf->path->len; i++)
267
0
        {
268
0
          const char *p = g_ptr_array_index (rbuf->path, i);
269
270
0
          if (p != NULL)
271
0
            g_ptr_array_add (rcopy->path, g_strdup (p));
272
0
        }
273
0
    }
274
275
0
  rcopy->extension = g_strdup (rbuf->extension);
276
277
0
  return copy;
278
0
}
279
280
/**
281
 * g_path_buf_push:
282
 * @buf: a path buffer
283
 * @path: (type filename): a path
284
 *
285
 * Extends the given path buffer with @path.
286
 *
287
 * If @path is absolute, it replaces the current path.
288
 *
289
 * If @path contains a directory separator, the buffer is extended by
290
 * as many elements the path provides.
291
 *
292
 * On Windows, both forward slashes and backslashes are treated as
293
 * directory separators. On other platforms, %G_DIR_SEPARATOR_S is the
294
 * only directory separator.
295
 *
296
 * |[<!-- language="C" -->
297
 * GPathBuf buf, cmp;
298
 *
299
 * g_path_buf_init_from_path (&buf, "/tmp");
300
 * g_path_buf_push (&buf, ".X11-unix/X0");
301
 * g_path_buf_init_from_path (&cmp, "/tmp/.X11-unix/X0");
302
 * g_assert_true (g_path_buf_equal (&buf, &cmp));
303
 * g_path_buf_clear (&cmp);
304
 *
305
 * g_path_buf_push (&buf, "/etc/locale.conf");
306
 * g_path_buf_init_from_path (&cmp, "/etc/locale.conf");
307
 * g_assert_true (g_path_buf_equal (&buf, &cmp));
308
 * g_path_buf_clear (&cmp);
309
 *
310
 * g_path_buf_clear (&buf);
311
 * ]|
312
 *
313
 * Returns: (transfer none): the same pointer to @buf, for convenience
314
 *
315
 * Since: 2.76
316
 */
317
GPathBuf *
318
g_path_buf_push (GPathBuf   *buf,
319
                 const char *path)
320
0
{
321
0
  RealPathBuf *rbuf = PATH_BUF (buf);
322
323
0
  g_return_val_if_fail (buf != NULL, NULL);
324
0
  g_return_val_if_fail (path != NULL && *path != '\0', buf);
325
326
0
  if (g_path_is_absolute (path))
327
0
    {
328
#ifdef G_OS_WIN32
329
      char **elements = g_strsplit_set (path, "\\/", -1);
330
#else
331
0
      char **elements = g_strsplit (path, G_DIR_SEPARATOR_S, -1);
332
0
#endif
333
334
0
#ifdef G_OS_UNIX
335
      /* strsplit() will add an empty element for the leading root,
336
       * which will cause the path build to ignore it; to avoid it,
337
       * we re-inject the root as the first element.
338
       *
339
       * The first string is empty, but it's still allocated, so we
340
       * need to free it to avoid leaking it.
341
       */
342
0
      g_free (elements[0]);
343
0
      elements[0] = g_strdup ("/");
344
0
#endif
345
346
0
      g_clear_pointer (&rbuf->path, g_ptr_array_unref);
347
0
      rbuf->path = g_ptr_array_new_null_terminated (g_strv_length (elements), g_free, TRUE);
348
349
      /* Skip empty elements caused by repeated separators */
350
0
      for (guint i = 0; elements[i] != NULL; i++)
351
0
        {
352
0
          if (*elements[i] != '\0')
353
0
            g_ptr_array_add (rbuf->path, g_steal_pointer (&elements[i]));
354
0
          else
355
0
            g_free (elements[i]);
356
0
        }
357
358
0
      g_free (elements);
359
0
    }
360
0
  else
361
0
    {
362
0
      char **elements = g_strsplit (path, G_DIR_SEPARATOR_S, -1);
363
364
0
      if (rbuf->path == NULL)
365
0
        rbuf->path = g_ptr_array_new_null_terminated (g_strv_length (elements), g_free, TRUE);
366
367
      /* Skip empty elements caused by repeated separators */
368
0
      for (guint i = 0; elements[i] != NULL; i++)
369
0
        {
370
0
          if (*elements[i] != '\0')
371
0
            g_ptr_array_add (rbuf->path, g_steal_pointer (&elements[i]));
372
0
          else
373
0
            g_free (elements[i]);
374
0
        }
375
376
0
      g_free (elements);
377
0
    }
378
379
0
  return buf;
380
0
}
381
382
/**
383
 * g_path_buf_pop:
384
 * @buf: a path buffer
385
 *
386
 * Removes the last element of the path buffer.
387
 *
388
 * If there is only one element in the path buffer (for example, `/` on
389
 * Unix-like operating systems or the drive on Windows systems), it will
390
 * not be removed and %FALSE will be returned instead.
391
 *
392
 * |[<!-- language="C" -->
393
 * GPathBuf buf, cmp;
394
 *
395
 * g_path_buf_init_from_path (&buf, "/bin/sh");
396
 *
397
 * g_path_buf_pop (&buf);
398
 * g_path_buf_init_from_path (&cmp, "/bin");
399
 * g_assert_true (g_path_buf_equal (&buf, &cmp));
400
 * g_path_buf_clear (&cmp);
401
 *
402
 * g_path_buf_pop (&buf);
403
 * g_path_buf_init_from_path (&cmp, "/");
404
 * g_assert_true (g_path_buf_equal (&buf, &cmp));
405
 * g_path_buf_clear (&cmp);
406
 *
407
 * g_path_buf_clear (&buf);
408
 * ]|
409
 *
410
 * Returns: `TRUE` if the buffer was modified and `FALSE` otherwise
411
 *
412
 * Since: 2.76
413
 */
414
gboolean
415
g_path_buf_pop (GPathBuf *buf)
416
0
{
417
0
  RealPathBuf *rbuf = PATH_BUF (buf);
418
419
0
  g_return_val_if_fail (buf != NULL, FALSE);
420
0
  g_return_val_if_fail (rbuf->path != NULL, FALSE);
421
422
  /* Keep the first element of the buffer; it's either '/' or the drive */
423
0
  if (rbuf->path->len > 1)
424
0
    {
425
0
      g_ptr_array_remove_index (rbuf->path, rbuf->path->len - 1);
426
0
      return TRUE;
427
0
    }
428
429
0
  return FALSE;
430
0
}
431
432
/**
433
 * g_path_buf_set_filename:
434
 * @buf: a path buffer
435
 * @file_name: (type filename) (not nullable): the file name in the path
436
 *
437
 * Sets the file name of the path.
438
 *
439
 * If the path buffer is empty, the filename is left unset and this
440
 * function returns `FALSE`.
441
 *
442
 * If the path buffer only contains the root element (on Unix-like operating
443
 * systems) or the drive (on Windows), this is the equivalent of pushing
444
 * the new @file_name.
445
 *
446
 * If the path buffer contains a path, this is the equivalent of
447
 * popping the path buffer and pushing @file_name, creating a
448
 * sibling of the original path.
449
 *
450
 * |[<!-- language="C" -->
451
 * GPathBuf buf, cmp;
452
 *
453
 * g_path_buf_init_from_path (&buf, "/");
454
 *
455
 * g_path_buf_set_filename (&buf, "bar");
456
 * g_path_buf_init_from_path (&cmp, "/bar");
457
 * g_assert_true (g_path_buf_equal (&buf, &cmp));
458
 * g_path_buf_clear (&cmp);
459
 *
460
 * g_path_buf_set_filename (&buf, "baz.txt");
461
 * g_path_buf_init_from_path (&cmp, "/baz.txt");
462
 * g_assert_true (g_path_buf_equal (&buf, &cmp);
463
 * g_path_buf_clear (&cmp);
464
 *
465
 * g_path_buf_clear (&buf);
466
 * ]|
467
 *
468
 * Returns: `TRUE` if the file name was replaced, and `FALSE` otherwise
469
 *
470
 * Since: 2.76
471
 */
472
gboolean
473
g_path_buf_set_filename (GPathBuf   *buf,
474
                         const char *file_name)
475
0
{
476
0
  g_return_val_if_fail (buf != NULL, FALSE);
477
0
  g_return_val_if_fail (file_name != NULL, FALSE);
478
479
0
  if (PATH_BUF (buf)->path == NULL)
480
0
    return FALSE;
481
482
0
  g_path_buf_pop (buf);
483
0
  g_path_buf_push (buf, file_name);
484
485
0
  return TRUE;
486
0
}
487
488
/**
489
 * g_path_buf_set_extension:
490
 * @buf: a path buffer
491
 * @extension: (type filename) (nullable): the file extension
492
 *
493
 * Adds an extension to the file name in the path buffer.
494
 *
495
 * If @extension is `NULL`, the extension will be unset.
496
 *
497
 * If the path buffer does not have a file name set, this function returns
498
 * `FALSE` and leaves the path buffer unmodified.
499
 *
500
 * Returns: `TRUE` if the extension was replaced, and `FALSE` otherwise
501
 *
502
 * Since: 2.76
503
 */
504
gboolean
505
g_path_buf_set_extension  (GPathBuf   *buf,
506
                           const char *extension)
507
0
{
508
0
  RealPathBuf *rbuf = PATH_BUF (buf);
509
510
0
  g_return_val_if_fail (buf != NULL, FALSE);
511
512
0
  if (rbuf->path != NULL)
513
0
    return g_set_str (&rbuf->extension, extension);
514
0
  else
515
0
    return FALSE;
516
0
}
517
518
/**
519
 * g_path_buf_to_path:
520
 * @buf: a path buffer
521
 *
522
 * Retrieves the built path from the path buffer.
523
 *
524
 * On Windows, the result contains backslashes as directory separators,
525
 * even if forward slashes were used in input.
526
 *
527
 * If the path buffer is empty, this function returns `NULL`.
528
 *
529
 * Returns: (transfer full) (type filename) (nullable): the path
530
 *
531
 * Since: 2.76
532
 */
533
char *
534
g_path_buf_to_path (GPathBuf *buf)
535
0
{
536
0
  RealPathBuf *rbuf = PATH_BUF (buf);
537
0
  char *path = NULL;
538
539
0
  g_return_val_if_fail (buf != NULL, NULL);
540
541
0
  if (rbuf->path != NULL)
542
0
    path = g_build_filenamev ((char **) rbuf->path->pdata);
543
544
0
  if (path != NULL && rbuf->extension != NULL)
545
0
    {
546
0
      char *tmp = g_strconcat (path, ".", rbuf->extension, NULL);
547
548
0
      g_free (path);
549
0
      path = g_steal_pointer (&tmp);
550
0
    }
551
552
0
  return path;
553
0
}
554
555
/**
556
 * g_path_buf_equal:
557
 * @v1: (not nullable): a path buffer to compare
558
 * @v2: (not nullable): a path buffer to compare
559
 *
560
 * Compares two path buffers for equality and returns `TRUE`
561
 * if they are equal.
562
 *
563
 * The paths inside the path buffers are not going to be normalized,
564
 * so `X/Y/Z/A/..`, `X/./Y/Z` and `X/Y/Z` are not going to be considered
565
 * equal.
566
 *
567
 * This function can be passed to g_hash_table_new() as the
568
 * `key_equal_func` parameter.
569
 *
570
 * Returns: `TRUE` if the two path buffers are equal,
571
 *   and `FALSE` otherwise
572
 *
573
 * Since: 2.76
574
 */
575
gboolean
576
g_path_buf_equal (gconstpointer v1,
577
                  gconstpointer v2)
578
0
{
579
0
  if (v1 == v2)
580
0
    return TRUE;
581
582
  /* We resolve the buffer into a path to normalize its contents;
583
   * this won't resolve symbolic links or `.` and `..` components
584
   */
585
0
  char *p1 = g_path_buf_to_path ((GPathBuf *) v1);
586
0
  char *p2 = g_path_buf_to_path ((GPathBuf *) v2);
587
588
0
  gboolean res = p1 != NULL && p2 != NULL
589
0
               ? g_str_equal (p1, p2)
590
0
               : FALSE;
591
592
0
  g_free (p1);
593
0
  g_free (p2);
594
595
0
  return res;
596
0
}