(root)/
glib-2.79.0/
glib/
gpathbuf.c
       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  #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  {
      81    RealPathBuf *rbuf = PATH_BUF (buf);
      82  
      83    rbuf->path = NULL;
      84    rbuf->extension = NULL;
      85  
      86    return buf;
      87  }
      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  {
     104    g_return_val_if_fail (buf != NULL, NULL);
     105    g_return_val_if_fail (path == NULL || *path != '\0', NULL);
     106  
     107    g_path_buf_init (buf);
     108  
     109    if (path == NULL)
     110      return buf;
     111    else
     112      return g_path_buf_push (buf, path);
     113  }
     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  {
     130    RealPathBuf *rbuf = PATH_BUF (buf);
     131  
     132    g_return_if_fail (buf != NULL);
     133  
     134    g_clear_pointer (&rbuf->path, g_ptr_array_unref);
     135    g_clear_pointer (&rbuf->extension, g_free);
     136  }
     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  {
     155    char *res;
     156  
     157    g_return_val_if_fail (buf != NULL, NULL);
     158  
     159    res = g_path_buf_to_path (buf);
     160    g_path_buf_clear (buf);
     161  
     162    return g_steal_pointer (&res);
     163  }
     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  {
     177    return g_path_buf_init (g_new (GPathBuf, 1));
     178  }
     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  {
     193    return g_path_buf_init_from_path (g_new (GPathBuf, 1), path);
     194  }
     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  {
     207    g_return_if_fail (buf != NULL);
     208  
     209    g_path_buf_clear (buf);
     210    g_free (buf);
     211  }
     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  {
     231    char *res;
     232  
     233    g_return_val_if_fail (buf != NULL, NULL);
     234  
     235    res = g_path_buf_clear_to_path (buf);
     236    g_path_buf_free (buf);
     237  
     238    return g_steal_pointer (&res);
     239  }
     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  {
     254    RealPathBuf *rbuf = PATH_BUF (buf);
     255    RealPathBuf *rcopy;
     256    GPathBuf *copy;
     257  
     258    g_return_val_if_fail (buf != NULL, NULL);
     259  
     260    copy = g_path_buf_new ();
     261    rcopy = PATH_BUF (copy);
     262  
     263    if (rbuf->path != NULL)
     264      {
     265        rcopy->path = g_ptr_array_new_null_terminated (rbuf->path->len, g_free, TRUE);
     266        for (guint i = 0; i < rbuf->path->len; i++)
     267          {
     268            const char *p = g_ptr_array_index (rbuf->path, i);
     269  
     270            if (p != NULL)
     271              g_ptr_array_add (rcopy->path, g_strdup (p));
     272          }
     273      }
     274  
     275    rcopy->extension = g_strdup (rbuf->extension);
     276  
     277    return copy;
     278  }
     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  {
     321    RealPathBuf *rbuf = PATH_BUF (buf);
     322  
     323    g_return_val_if_fail (buf != NULL, NULL);
     324    g_return_val_if_fail (path != NULL && *path != '\0', buf);
     325  
     326    if (g_path_is_absolute (path))
     327      {
     328  #ifdef G_OS_WIN32
     329        char **elements = g_strsplit_set (path, "\\/", -1);
     330  #else
     331        char **elements = g_strsplit (path, G_DIR_SEPARATOR_S, -1);
     332  #endif
     333  
     334  #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        g_free (elements[0]);
     343        elements[0] = g_strdup ("/");
     344  #endif
     345  
     346        g_clear_pointer (&rbuf->path, g_ptr_array_unref);
     347        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        for (guint i = 0; elements[i] != NULL; i++)
     351          {
     352            if (*elements[i] != '\0')
     353              g_ptr_array_add (rbuf->path, g_steal_pointer (&elements[i]));
     354            else
     355              g_free (elements[i]);
     356          }
     357  
     358        g_free (elements);
     359      }
     360    else
     361      {
     362        char **elements = g_strsplit (path, G_DIR_SEPARATOR_S, -1);
     363  
     364        if (rbuf->path == NULL)
     365          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        for (guint i = 0; elements[i] != NULL; i++)
     369          {
     370            if (*elements[i] != '\0')
     371              g_ptr_array_add (rbuf->path, g_steal_pointer (&elements[i]));
     372            else
     373              g_free (elements[i]);
     374          }
     375  
     376        g_free (elements);
     377      }
     378  
     379    return buf;
     380  }
     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  {
     417    RealPathBuf *rbuf = PATH_BUF (buf);
     418  
     419    g_return_val_if_fail (buf != NULL, FALSE);
     420    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    if (rbuf->path->len > 1)
     424      {
     425        g_ptr_array_remove_index (rbuf->path, rbuf->path->len - 1);
     426        return TRUE;
     427      }
     428  
     429    return FALSE;
     430  }
     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  {
     476    g_return_val_if_fail (buf != NULL, FALSE);
     477    g_return_val_if_fail (file_name != NULL, FALSE);
     478  
     479    if (PATH_BUF (buf)->path == NULL)
     480      return FALSE;
     481  
     482    g_path_buf_pop (buf);
     483    g_path_buf_push (buf, file_name);
     484  
     485    return TRUE;
     486  }
     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  {
     508    RealPathBuf *rbuf = PATH_BUF (buf);
     509  
     510    g_return_val_if_fail (buf != NULL, FALSE);
     511  
     512    if (rbuf->path != NULL)
     513      return g_set_str (&rbuf->extension, extension);
     514    else
     515      return FALSE;
     516  }
     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  {
     536    RealPathBuf *rbuf = PATH_BUF (buf);
     537    char *path = NULL;
     538  
     539    g_return_val_if_fail (buf != NULL, NULL);
     540  
     541    if (rbuf->path != NULL)
     542      path = g_build_filenamev ((char **) rbuf->path->pdata);
     543  
     544    if (path != NULL && rbuf->extension != NULL)
     545      {
     546        char *tmp = g_strconcat (path, ".", rbuf->extension, NULL);
     547  
     548        g_free (path);
     549        path = g_steal_pointer (&tmp);
     550      }
     551  
     552    return path;
     553  }
     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 path inside the paths 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  {
     579    if (v1 == v2)
     580      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    char *p1 = g_path_buf_to_path ((GPathBuf *) v1);
     586    char *p2 = g_path_buf_to_path ((GPathBuf *) v2);
     587  
     588    gboolean res = p1 != NULL && p2 != NULL
     589                 ? g_str_equal (p1, p2)
     590                 : FALSE;
     591  
     592    g_free (p1);
     593    g_free (p2);
     594  
     595    return res;
     596  }